diff --git a/CLAUDE.md b/CLAUDE.md index 4ce03b1..d021883 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -151,7 +151,7 @@ Always use `.nothrow()` for commands that may fail non-fatally. Use `.quiet()` t - `sandlot merge` and `sandlot squash` both delegate to `mergeAndClose()` in `helpers.ts`, which merges, resolves conflicts, commits, and then calls `closeAction()` to clean up - `sandlot list` discovers missing session prompts by parsing Claude's `history.jsonl` from inside the container - `sandlot list` shows five status icons: idle (dim `◯`), active (cyan `◎`), dirty/unsaved (yellow `◐`), saved (green `●`), review (magenta `⦿`) -- `sandlot review` sets `in_review` on the session during the review and clears it in a `finally` block on exit; `list` only shows review status if Claude is also active (self-heals stale flags) +- `sandlot review` sets `in_review` on the session during the review and clears it in a `finally` block on exit; `list` detects stale `in_review` flags (Claude not active) and clears them from state - `sandlot new` and `sandlot open` auto-save changes when Claude exits (disable with `--no-save`) - `sandlot close` has a hidden `rm` alias - Default behavior (no subcommand): always runs `list` (which prints "No active sessions." if empty) diff --git a/src/commands/list.ts b/src/commands/list.ts index a7dda79..a87b647 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -30,12 +30,11 @@ export async function action(opts: { json?: boolean }) { } catch {} } - if (opts.json) { - console.log(JSON.stringify(sessions, null, 2)) - return - } - if (sessions.length === 0) { + if (opts.json) { + console.log("[]") + return + } console.log("◆ No active sessions.") if ((await vm.status()) !== "running") { console.log(`\n${red}VM is not running.${reset}`) @@ -48,6 +47,11 @@ export async function action(opts: { json?: boolean }) { sessions.map(async (s): Promise<[string, string]> => { const active = await vm.isClaudeActive(s.worktree, s.branch) if (active && s.in_review) return [s.branch, "review"] + // Self-heal: clear stale in_review flag if Claude is not active + if (!active && s.in_review) { + s.in_review = false + await state.setSession(root, s).catch(() => {}) + } if (active) return [s.branch, "active"] const dirty = await git.isDirty(s.worktree) if (dirty) return [s.branch, "dirty"] diff --git a/src/commands/review.ts b/src/commands/review.ts index 9b92623..4600e5d 100644 --- a/src/commands/review.ts +++ b/src/commands/review.ts @@ -9,10 +9,6 @@ export async function action(branch: string, extra: string | undefined, opts: { const spin = spinner("Starting container", branch) await vm.ensure((msg) => { spin.text = msg }) - // Mark session as in review only after container is confirmed running - session.in_review = true - await state.setSession(root, session) - let prompt = ` You're a grumpy old senior software engineer. You need to review some code my co-worker wrote. @@ -73,6 +69,10 @@ Your thoughts, in brief. if (extra) prompt += "\n\n" + extra try { + // Mark session as in review inside try so finally always clears it + session.in_review = true + await state.setSession(root, session) + if (opts.print) { spin.text = "Running review…" const result = await vm.claude(session.worktree, { print: prompt })