From 79b37122df8f81c168317382266d859345ad9a02 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Sun, 18 Oct 2026 16:14:45 +0200 Subject: [PATCH] fix(story-mode): a stop names the step Claude was on and what its to-do list still had open From review round 9: "You stop Claude here, during its step 3." said where Claude was, not what that meant. The stop now carries the step's own words, "during its step 3 (new tests under test/)", and when Claude kept a to-do list, what was still open on it: "Not done yet: Write the new tests; Update the README and 1 more." Co-Authored-By: Claude Opus 5.5 (1M context) --- src/story-mode.ts | 16 ++++++++++-- .../teammates-then-interrupt.expected.json | 2 +- test/story-corpus.test.ts | 2 +- test/story-mode.test.ts | 25 +++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/story-mode.ts b/src/story-mode.ts index 7b27b381..dac6000a 100644 --- a/src/story-mode.ts +++ b/src/story-mode.ts @@ -2742,11 +2742,23 @@ function completePanel(turn: StoryTurn, page: Omit, trailin // Where a stop landed: the step Claude last announced ("Step 3: …"), and what it had saved by then. const announced = [...page.events].reverse().find((e) => e.intent === 'say' && /^\W{0,4}step \d+\b/i.test(e.label)); const step = announced ? /step (\d+)/i.exec(announced.label)?.[1] : undefined; + // What that step was, in Claude's words: "Step 3: new tests under test/." brings "new tests under test/". + const stepWhat = announced + ? /^\W{0,4}step \d+\W{0,3}[:.-]\s*([^.!?\n]{3,80}?)\s*(?:[.!?](?:\s|$)|\n|$)/i + .exec(announced.label.replace(/[`*]/g, ''))?.[1] + ?.replace(/[:;,]$/, '') + : undefined; const savedBefore = page.events.reduce((n, e) => n + (e.commits ?? 0), 0); + // What was still open on Claude's own to-do list when you stopped it. + const todos = [...page.events].reverse().find((e) => e.todos?.length)?.todos; + const open = todos?.filter((t) => !t.done).map((t) => clip(t.text, 60)) ?? []; + const notDone = open.length + ? ` Not done yet: ${open.slice(0, 2).join('; ')}${open.length > 2 ? ` and ${open.length - 2} more` : ''}.` + : ''; const story = turn.interrupted - ? `You stop Claude here${step ? `, during its step ${step}` : ''}.${ + ? `You stop Claude here${step ? `, during its step ${step}${stepWhat ? ` (${stepWhat})` : ''}` : ''}.${ savedBefore ? ` Saved before the stop: ${plural(savedBefore, 'commit')}.` : '' - }` + }${notDone}` : page.noneWorked ? 'Nothing Claude tried worked, so the job is not done.' : turn.endedBy === 'next prompt' diff --git a/test/fixtures/story-corpus/teammates-then-interrupt.expected.json b/test/fixtures/story-corpus/teammates-then-interrupt.expected.json index eab757ba..8e69d1c6 100644 --- a/test/fixtures/story-corpus/teammates-then-interrupt.expected.json +++ b/test/fixtures/story-corpus/teammates-then-interrupt.expected.json @@ -304,7 +304,7 @@ { "kind": "complete", "ok": null, - "story": "You stop Claude here, during its step 3. Saved before the stop: 2 commits.", + "story": "You stop Claude here, during its step 3 (new tests under test/). Saved before the stop: 2 commits.", "director": "interrupted · 2 min 41 s · 5 tool calls · 4 files changed", "balloon": "Step 3: new tests under test/.", "tests": null, diff --git a/test/story-corpus.test.ts b/test/story-corpus.test.ts index eec0a710..72a35ba4 100644 --- a/test/story-corpus.test.ts +++ b/test/story-corpus.test.ts @@ -101,7 +101,7 @@ describe('Story Mode on real sessions', () => { ['human', true], ]); expect(pages[3].panels.at(-1)?.story).toBe( - 'You stop Claude here, during its step 3. Saved before the stop: 2 commits.' + 'You stop Claude here, during its step 3 (new tests under test/). Saved before the stop: 2 commits.' ); }); diff --git a/test/story-mode.test.ts b/test/story-mode.test.ts index 0024f187..6f0b4703 100644 --- a/test/story-mode.test.ts +++ b/test/story-mode.test.ts @@ -984,6 +984,31 @@ describe('buildStoryPage', () => { expect(pageOf([prompt('c'), ...bash('b1', 'git commit -qm x', 'Commit', ''), end(1000)]).events[1].commits).toBe(1); }); + it('a stop names the step Claude was on and what its to-do list still had open', () => { + const stopped = (lines: unknown[]) => + pageOf([...lines, prompt('[Request interrupted by user]')]).panels.find((p) => p.kind === 'complete')!.caption + .story; + expect( + stopped([ + prompt('three things'), + tool('t1', 'TodoWrite', { + todos: [ + { content: 'Split the module', status: 'completed' }, + { content: 'Write the new tests', status: 'in_progress' }, + { content: 'Update the README', status: 'pending' }, + { content: 'Update CONTRIBUTING', status: 'pending' }, + ], + }), + result('t1', 'Todos have been modified successfully.'), + ...bash('b1', 'git commit -qm "split"', 'Commit', ''), + said('Step 2: **new tests** under `test/`. First I work out the values.'), + ]) + ).toBe( + 'You stop Claude here, during its step 2 (new tests under test/). Saved before the stop: 1 commit. Not done yet: Write the new tests; Update the README and 1 more.' + ); + expect(stopped([prompt('go'), ...bash('b1', 'ls', 'List', 'a')])).toBe('You stop Claude here.'); + }); + it('the first save in a folder Claude made a repository says so; a bare copy target does not count', () => { const save = (lines: unknown[]) => pageOf(lines).panels.find((p) => p.kind === 'save')!.caption.story; expect(