diff --git a/src/commands/list.ts b/src/commands/list.ts index 14d615c..eb8c839 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -2,7 +2,7 @@ import { homedir } from "os" import * as git from "../git.ts" import * as vm from "../vm.ts" import * as state from "../state.ts" -import { reset, dim, bold, green, yellow, cyan, magenta, red } from "../fmt.ts" +import { reset, dim, green, yellow, cyan, magenta, red } from "../fmt.ts" export async function action(opts: { json?: boolean }) { const root = await git.repoRoot() @@ -30,22 +30,26 @@ export async function action(opts: { json?: boolean }) { } catch {} } - if (sessions.length === 0 && !opts.json) { - console.log("◆ No active sessions.") - if ((await vm.status()) !== "running") { - console.log(`\n${red}VM is not running.${reset}`) + if (sessions.length === 0) { + if (opts.json) { + console.log("[]") + } else { + console.log("◆ No active sessions.") + if ((await vm.status()) !== "running") { + console.log(`\n${red}VM is not running.${reset}`) + } } return } // Determine status for each session in parallel + const staleReviewBranches: string[] = [] const statusEntries = await Promise.all( 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"] if (!active && s.in_review) { - // Self-heal stale in_review flag (fire-and-forget) - state.patchSession(root, s.branch, { in_review: false }).catch(() => {}) + staleReviewBranches.push(s.branch) s.in_review = false } if (active) return [s.branch, "active"] @@ -57,6 +61,15 @@ export async function action(opts: { json?: boolean }) { ) const statuses = Object.fromEntries(statusEntries) + // Batch-clear stale in_review flags in a single write + if (staleReviewBranches.length > 0) { + const freshState = await state.load(root) + for (const branch of staleReviewBranches) { + if (freshState.sessions[branch]) freshState.sessions[branch].in_review = false + } + await state.save(root, freshState).catch(() => {}) + } + if (opts.json) { const withStatus = sessions.map(s => ({ ...s, status: statuses[s.branch] })) console.log(JSON.stringify(withStatus, null, 2)) diff --git a/src/commands/review.ts b/src/commands/review.ts index 5ef95b4..56ae0f2 100644 --- a/src/commands/review.ts +++ b/src/commands/review.ts @@ -68,13 +68,13 @@ Your thoughts, in brief. ` if (extra) prompt += "\n\n" + extra - await state.patchSession(root, session.branch, { in_review: true }) + session.in_review = true + await state.setSession(root, session).catch(() => {}) try { if (opts.print) { spin.text = "Running review…" const result = await vm.claude(session.worktree, { print: prompt }) - spin.stop() if (result.output) process.stdout.write(result.output + "\n") } else { spin.succeed("Session ready") @@ -82,7 +82,8 @@ Your thoughts, in brief. } } finally { spin.stop() - await state.patchSession(root, session.branch, { in_review: false }).catch(() => {}) + session.in_review = false + await state.setSession(root, session).catch(() => {}) if (!opts.print) await saveChanges(session.worktree, session.branch).catch(() => {}) } } diff --git a/src/state.ts b/src/state.ts index 4708d26..f212ec5 100644 --- a/src/state.ts +++ b/src/state.ts @@ -37,27 +37,20 @@ export async function save(repoRoot: string, state: State): Promise { } export async function getSession(repoRoot: string, branch: string): Promise { - const st = await load(repoRoot) - return st.sessions[branch] + const state = await load(repoRoot) + return state.sessions[branch] } export async function setSession(repoRoot: string, session: Session): Promise { - const st = await load(repoRoot) - st.sessions[session.branch] = session - await save(repoRoot, st) -} - -export async function patchSession(repoRoot: string, branch: string, patch: Partial): Promise { - const st = await load(repoRoot) - if (!st.sessions[branch]) throw new Error(`session not found: ${branch}`) - Object.assign(st.sessions[branch], patch) - await save(repoRoot, st) + const state = await load(repoRoot) + state.sessions[session.branch] = session + await save(repoRoot, state) } export async function removeSession(repoRoot: string, branch: string): Promise { - const st = await load(repoRoot) - delete st.sessions[branch] - await save(repoRoot, st) + const state = await load(repoRoot) + delete state.sessions[branch] + await save(repoRoot, state) } export interface GlobalSession extends Session {