From 92dbad3cad15d3facc7729547efa538fb57ebe05 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 18 Mar 2026 23:08:41 -0700 Subject: [PATCH] Add review status tracking to sessions The list command needs to show when a session is under active review so users don't interrupt it. Wrapping the review body in try/finally ensures the flag is always cleared on exit. Co-Authored-By: Claude Opus 4.6 --- src/commands/list.ts | 9 +++++---- src/commands/review.ts | 34 +++++++++++++++++++++++----------- src/fmt.ts | 1 + src/state.ts | 1 + 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/commands/list.ts b/src/commands/list.ts index c7897d0..8b09b40 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, white, red } from "../fmt.ts" +import { reset, dim, bold, green, yellow, cyan, magenta, white, red } from "../fmt.ts" export async function action(opts: { json?: boolean }) { const root = await git.repoRoot() @@ -46,6 +46,7 @@ export async function action(opts: { json?: boolean }) { // Determine status for each session in parallel const statusEntries = await Promise.all( sessions.map(async (s): Promise<[string, string]> => { + if (s.in_review) return [s.branch, "review"] if (await vm.isClaudeActive(s.worktree, s.branch)) return [s.branch, "active"] const dirty = await git.isDirty(s.worktree) if (dirty) return [s.branch, "dirty"] @@ -61,8 +62,8 @@ export async function action(opts: { json?: boolean }) { return } - const icons: Record = { idle: `${dim}◯${reset}`, active: `${cyan}◎${reset}`, dirty: `${yellow}◐${reset}`, saved: `${green}●${reset}` } - const branchColors: Record = { idle: dim, active: cyan, dirty: yellow, saved: green } + const icons: Record = { idle: `${dim}◯${reset}`, active: `${cyan}◎${reset}`, dirty: `${yellow}◐${reset}`, saved: `${green}●${reset}`, review: `${magenta}⦿${reset}` } + const branchColors: Record = { idle: dim, active: cyan, dirty: yellow, saved: green, review: magenta } const branchWidth = Math.max(6, ...sessions.map((s) => s.branch.length)) const cols = process.stdout.columns || 80 const prefixWidth = branchWidth + 4 @@ -79,7 +80,7 @@ export async function action(opts: { json?: boolean }) { console.log(`${icon} ${bc}${s.branch.padEnd(branchWidth)}${reset} ${dim}${truncated}${reset}`) } - console.log(`\n${dim}◯ idle${reset} · ${cyan}◎ active${reset} · ${yellow}◐ unsaved${reset} · ${green}● saved${reset}`) + console.log(`\n${dim}◯ idle${reset} · ${cyan}◎ active${reset} · ${yellow}◐ unsaved${reset} · ${green}● saved${reset} · ${magenta}⦿ review${reset}`) if ((await vm.status()) !== "running") { console.log(`\n${red}VM is not running.${reset}`) diff --git a/src/commands/review.ts b/src/commands/review.ts index 614d81c..9e0a862 100644 --- a/src/commands/review.ts +++ b/src/commands/review.ts @@ -1,9 +1,15 @@ +import * as git from "../git.ts" import * as vm from "../vm.ts" +import * as state from "../state.ts" import { spinner } from "../spinner.ts" import { requireSession, saveChanges } from "./helpers.ts" export async function action(branch: string, extra: string | undefined, opts: { print?: boolean }) { - const { session } = await requireSession(branch) + const { root, session } = await requireSession(branch) + + // Mark session as in review + session.in_review = true + await state.setSession(root, session) const spin = spinner("Starting container", branch) await vm.ensure((msg) => { spin.text = msg }) @@ -67,15 +73,21 @@ Your thoughts, in brief. ` if (extra) prompt += "\n\n" + extra - 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") - await vm.claude(session.worktree, { prompt }) - } + 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") + await vm.claude(session.worktree, { prompt }) + } - await saveChanges(session.worktree, session.branch) + await saveChanges(session.worktree, session.branch) + } finally { + // Clear review state + session.in_review = false + await state.setSession(root, session) + } } diff --git a/src/fmt.ts b/src/fmt.ts index 2bafd68..97ed382 100644 --- a/src/fmt.ts +++ b/src/fmt.ts @@ -5,6 +5,7 @@ export const dim = "\x1b[2m" export const green = "\x1b[32m" export const yellow = "\x1b[33m" export const red = "\x1b[31m" +export const magenta = "\x1b[35m" export const cyan = "\x1b[36m" // ── Formatted output ──────────────────────────────────────────────── diff --git a/src/state.ts b/src/state.ts index f4d0015..f212ec5 100644 --- a/src/state.ts +++ b/src/state.ts @@ -7,6 +7,7 @@ export interface Session { worktree: string created_at: string prompt?: string + in_review?: boolean } export interface State {