fix(story-mode): a new file counts what it ends with, a rewrite is a change, short endings are read (review round 11)

From the fact-checker:
- A script created and then edited in the same panel showed "+95 -3":
  a file created in the span counts what it ends with, never lines
  removed from its own draft (+92), on the badge, the panel and the page.
- "Claude writes 4 files" for files an earlier panel wrote and this one
  rewrote with here-documents: writing a file the turn already touched is
  changing it.
- "Claude says: Compacted": a local command's output is the CLI's own,
  a note, not Claude's words.
- "The CHANGELOG helper is still running" was dropped under "Mission
  complete!": key sentences were read only in endings over 160
  characters; a short ending is read too, and that one is a note.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 17:47:44 +02:00
1 parent d3c55e04a3
commit dc980d121c
9 files changed
+109 -36

No files matched your search

@@ -282,7 +282,7 @@
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"openNote": "note",
"asks": false,
"panels": [
{
@@ -318,7 +318,7 @@
"story": "Done in 7 seconds.",
"unaskedSave": false,
"director": "turn_duration · 7 s · 1 tool call",
"balloon": "The background test run finished and all 23 tests passed, including the new --json test.",
"balloon": "The background test run finished and all 23 tests passed, including the new --json test. The CHANGELOG helper is still running; I'll report when it finishes.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -7,8 +7,8 @@
"durationMs": 121460,
"toolCalls": 13,
"filesChanged": 2,
"added": 96,
"removed": 3,
"added": 93,
"removed": 0,
"helpers": 1,
"helpersRunning": 0,
"finishedLater": 1,
@@ -68,7 +68,7 @@
"ok": null,
"story": "Claude changes rename_by_date.sh and -dash.jpg.",
"unaskedSave": false,
"director": "Write · Edit ×3 · Bash · rename_by_date.sh +95 −3 · -dash.jpg +1",
"director": "Write · Edit ×3 · Bash · rename_by_date.sh +92 · -dash.jpg +1",
"balloon": "Now I'll create the samples and run the dry run.",
"tests": null,
"roundsOk": null,
@@ -7,8 +7,8 @@
"durationMs": 83905,
"toolCalls": 9,
"filesChanged": 2,
"added": 35,
"removed": 1,
"added": 34,
"removed": 0,
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
@@ -44,7 +44,7 @@
"ok": true,
"story": "The first run fails on purpose, as you asked; the second passes.",
"unaskedSave": false,
"director": "2 checks · 3 edits · +35 −1 · last: ok · after 1 second opinion",
"director": "2 checks · 3 edits · +34 · last: ok · after 1 second opinion",
"balloon": "All 4 pass.",
"tests": {
"passed": 4,
@@ -7,8 +7,8 @@
"durationMs": 192853,
"toolCalls": 11,
"filesChanged": 3,
"added": 220,
"removed": 1,
"added": 219,
"removed": 0,
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
@@ -44,7 +44,7 @@
"ok": true,
"story": "Fix and test, 2 rounds: a test fails and Claude fixes it, then a formatting check fails and Claude fixes that; everything passes. Claude starts a git repository and saves the work in 4 commits along the way.",
"unaskedSave": true,
"director": "3 checks · 5 edits · +218 −1 · last: ok · after 1 second opinion",
"director": "3 checks · 5 edits · +217 · last: ok · after 1 second opinion",
"balloon": "All 19 tests pass, ruff check is clean, and the output matches real wc column for column.",
"tests": {
"passed": 19,
@@ -7,8 +7,8 @@
"durationMs": 297209,
"toolCalls": 18,
"filesChanged": 6,
"added": 221,
"removed": 2,
"added": 220,
"removed": 1,
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
@@ -56,7 +56,7 @@
"ok": true,
"story": "The first run fails on purpose, as you asked; the second passes.",
"unaskedSave": false,
"director": "2 checks · 4 edits · +151 −1 · last: ok · after 1 second opinion",
"director": "2 checks · 4 edits · +150 · last: ok · after 1 second opinion",
"balloon": "All 19 test cases pass (13 test functions), and ruff check and ruff format both come back clean.",
"tests": {
"passed": 19,
@@ -297,7 +297,7 @@
{
"kind": "check",
"ok": true,
"story": "Claude writes 4 files; the tests pass.",
"story": "Claude changes 4 files; the tests pass.",
"unaskedSave": false,
"director": "npm test 2>&1 → ok · 8 passed · writes 4 files",
"balloon": "Every @example output matches what the code actually returns, and 8/8 tests pass.",
+37
View File
@@ -571,6 +571,8 @@ describe('buildStoryTurns', () => {
expect(page.panels.map((p) => p.kind)).toEqual(['mission', 'compact', 'complete']);
expect(page.panels[0].caption.story).toBe('You run /compact.');
expect(page.events[0]).toMatchObject({ tool: 'Command', output: 'Compacted' });
// The CLI's own output is a note, not Claude's words.
expect(page.panels.at(-1)!.balloon).toEqual({ who: 'note', text: 'Compacted' });
// A local command like /model ends at its output too.
const [, model] = buildStoryTurns(
entriesOf([
@@ -1059,6 +1061,41 @@ describe('buildStoryPage', () => {
expect(keyKindOf('Anything unknown is left out rather than guessed.')).toBeUndefined();
});
it('a new file counts what it ends with; a file written earlier and rewritten is changed', () => {
const write = (id: string, file: string, content: string) => [
tool(id, 'Write', { file_path: file, content }),
result(id, 'File created', {
toolUseResult: { type: 'create', filePath: file, content, structuredPatch: [] },
}),
];
const page = pageOf([
prompt('build it'),
...write('w1', '/w/run.sh', 'a\nb\nc\nd\n'),
...edit('e1', '/w/run.sh', ['-b', '+B']),
...edit('e2', '/w/run.sh', ['-c']),
...bash('b1', 'pytest -q', 'Test', '1 passed in 0.1s'),
...bash('b2', "cat > /w/run.sh <<'EOF'\na\nB\nd\nEOF\npytest -q", 'Rewrite and test', '1 passed in 0.1s'),
end(1000),
]);
// Written (4 lines), then 1 line changed and 1 removed in the same panel: +3, nothing removed.
expect(page.panels.find((p) => p.kind === 'fix')).toMatchObject({ added: 3, removed: 0 });
expect(page.panels.find((p) => p.kind === 'fix')!.caption.story).toBe('Claude writes the new file run.sh.');
// The here-document rewrites run.sh whole, but an earlier panel wrote it: that is a change.
expect(page.panels.map((p) => p.caption.story)).toContain('Claude changes run.sh; the tests pass.');
});
it('a short ending keeps the sentence that leaves something open', () => {
const page = pageOf([
prompt('run them'),
said('The background test run finished and all 23 tests passed. The CHANGELOG helper is still running.'),
end(1000),
]);
expect(page.panels.at(-1)!.balloon!.text).toBe(
'The background test run finished and all 23 tests passed. The CHANGELOG helper is still running.'
);
expect(page.openNote).toBe('note');
});
it('the first save no prompt asked for is marked, once; a new branch is named', () => {
const saves = (ask: string) =>
pageOf([