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 <noreply@anthropic.com>
This commit is contained in:
Chris Wanstrath 2026-03-18 23:08:41 -07:00
parent 8172065928
commit 92dbad3cad
4 changed files with 30 additions and 15 deletions

View File

@ -2,7 +2,7 @@ import { homedir } from "os"
import * as git from "../git.ts" import * as git from "../git.ts"
import * as vm from "../vm.ts" import * as vm from "../vm.ts"
import * as state from "../state.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 }) { export async function action(opts: { json?: boolean }) {
const root = await git.repoRoot() const root = await git.repoRoot()
@ -46,6 +46,7 @@ export async function action(opts: { json?: boolean }) {
// Determine status for each session in parallel // Determine status for each session in parallel
const statusEntries = await Promise.all( const statusEntries = await Promise.all(
sessions.map(async (s): Promise<[string, string]> => { 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"] if (await vm.isClaudeActive(s.worktree, s.branch)) return [s.branch, "active"]
const dirty = await git.isDirty(s.worktree) const dirty = await git.isDirty(s.worktree)
if (dirty) return [s.branch, "dirty"] if (dirty) return [s.branch, "dirty"]
@ -61,8 +62,8 @@ export async function action(opts: { json?: boolean }) {
return return
} }
const icons: Record<string, string> = { idle: `${dim}${reset}`, active: `${cyan}${reset}`, dirty: `${yellow}${reset}`, saved: `${green}${reset}` } const icons: Record<string, string> = { idle: `${dim}${reset}`, active: `${cyan}${reset}`, dirty: `${yellow}${reset}`, saved: `${green}${reset}`, review: `${magenta}⦿${reset}` }
const branchColors: Record<string, string> = { idle: dim, active: cyan, dirty: yellow, saved: green } const branchColors: Record<string, string> = { idle: dim, active: cyan, dirty: yellow, saved: green, review: magenta }
const branchWidth = Math.max(6, ...sessions.map((s) => s.branch.length)) const branchWidth = Math.max(6, ...sessions.map((s) => s.branch.length))
const cols = process.stdout.columns || 80 const cols = process.stdout.columns || 80
const prefixWidth = branchWidth + 4 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(`${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") { if ((await vm.status()) !== "running") {
console.log(`\n${red}VM is not running.${reset}`) console.log(`\n${red}VM is not running.${reset}`)

View File

@ -1,9 +1,15 @@
import * as git from "../git.ts"
import * as vm from "../vm.ts" import * as vm from "../vm.ts"
import * as state from "../state.ts"
import { spinner } from "../spinner.ts" import { spinner } from "../spinner.ts"
import { requireSession, saveChanges } from "./helpers.ts" import { requireSession, saveChanges } from "./helpers.ts"
export async function action(branch: string, extra: string | undefined, opts: { print?: boolean }) { 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) const spin = spinner("Starting container", branch)
await vm.ensure((msg) => { spin.text = msg }) await vm.ensure((msg) => { spin.text = msg })
@ -67,15 +73,21 @@ Your thoughts, in brief.
` `
if (extra) prompt += "\n\n" + extra if (extra) prompt += "\n\n" + extra
if (opts.print) { try {
spin.text = "Running review…" if (opts.print) {
const result = await vm.claude(session.worktree, { print: prompt }) spin.text = "Running review…"
spin.stop() const result = await vm.claude(session.worktree, { print: prompt })
if (result.output) process.stdout.write(result.output + "\n") spin.stop()
} else { if (result.output) process.stdout.write(result.output + "\n")
spin.succeed("Session ready") } else {
await vm.claude(session.worktree, { prompt }) 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)
}
} }

View File

@ -5,6 +5,7 @@ export const dim = "\x1b[2m"
export const green = "\x1b[32m" export const green = "\x1b[32m"
export const yellow = "\x1b[33m" export const yellow = "\x1b[33m"
export const red = "\x1b[31m" export const red = "\x1b[31m"
export const magenta = "\x1b[35m"
export const cyan = "\x1b[36m" export const cyan = "\x1b[36m"
// ── Formatted output ──────────────────────────────────────────────── // ── Formatted output ────────────────────────────────────────────────

View File

@ -7,6 +7,7 @@ export interface Session {
worktree: string worktree: string
created_at: string created_at: string
prompt?: string prompt?: string
in_review?: boolean
} }
export interface State { export interface State {