fix(story-mode): last words in context, the check after a push, a fresh copy (review round 10)

From the person who asked for the work, reading fresh pages:
- "The result was 4 passed, 1 failed" under "Mission complete!" read as
  a failing end. A test count is no failed step, and Claude's second
  sentence is kept when it is the outcome ("All 5 tests now pass, and the
  change is committed.").
- "Two places where I didn't follow your wording:" showed one. A list
  intro that announces how many brings that many lines.
- "…would fix it for up to 10 files. Want me to add that?" never said
  what "it" was. An offer that points back brings the start of its
  paragraph ("Suffix swap: the two files with the same time would trade
  their _1 suffix. …").
- "Decisions you may want to change" is something for you, like an offer.
- A second opinion whose call failed read as given; it now reads "after
  trying to ask … (no answer came)".
- "but a step after that fails" after a push sounded as if the push
  broke: when everything after it only looks (git log, status), it is "a
  check after that fails".
- Tests run in a fresh clone read "The tests pass in a fresh copy made
  from remote.git."

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 16:59:29 +02:00
1 parent 2859b3a207
commit f46369a5af
16 files changed
+195 -34

No files matched your search

@@ -172,7 +172,7 @@
"ok": null,
"story": "Done in 11 seconds.",
"director": "turn_duration · 11 s · 1 tool call",
"balloon": "Both helpers have finished.",
"balloon": "Both helpers have finished. Against the real wordcount.py, all 22 tests pass: my 4 in tests/test_basic.py and helper B's 18 in tests/test_extra.py.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -52,7 +52,7 @@
"kind": "backup",
"ok": null,
"story": "Claude sends 1 helper off to work in the background.",
"director": "Agent · general-purpose · 6 calls inside · after 1 second opinion",
"director": "Agent · general-purpose · 6 calls inside · after 1 second opinion (no answer)",
"balloon": null,
"tests": null,
"roundsOk": null,
@@ -244,7 +244,7 @@
"ok": null,
"story": "Done in 1 minute.",
"director": "turn_duration · 1 min 14 s · 9 tool calls · 2 files changed",
"balloon": "I added a --prefix option to rename_by_date.sh. Making the script sort file names the plain byte-by-byte way would fix it for up to 10 files with the same time. Want me to add that?",
"balloon": "I added a --prefix option to rename_by_date.sh. Suffix swap: the two files with the same time (2024-05-01_093000*) would trade their _1 suffix. … Making the script sort file names the plain byte-by-byte way would fix it for up to 10 files with the same time. Want me to add that?",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -238,7 +238,7 @@
"ok": null,
"story": "Done in 1 minute.",
"director": "turn_duration · 1 min 7 s · 10 tool calls · 2 files changed",
"balloon": "I added wrap() as planned, with 3 new tests.",
"balloon": "I added wrap() as planned, with 3 new tests. All 8 tests pass and ruff reports no problems.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -268,7 +268,7 @@
{
"kind": "save",
"ok": true,
"story": "Claude saves the work and copies it to origin.git, a folder on this computer, but a step after that fails.",
"story": "Claude saves the work and copies it to origin.git, a folder on this computer, but a check after that fails.",
"director": "git commit -q -F - · git push · 1 error",
"balloon": null,
"tests": null,
@@ -84,7 +84,7 @@
"ok": null,
"story": "Done in 1 minute.",
"director": "turn_duration · 1 min 24 s · 10 tool calls · 2 files changed",
"balloon": "slugify.mjs and its test file are written, all 4 tests pass, and both files are committed. First run (deliberately failing): the \"strips leading and trailing hyphens\" test expected '-hello-world'. That's wrong against the spec, and the run ended with 3 passed, 1 failed, exit code 1. Set git config user.name if you want a different name on future commits.",
"balloon": "slugify.mjs and its test file are written, all 4 tests pass, and both files are committed. Set git config user.name if you want a different name on future commits.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -152,7 +152,7 @@
"ok": null,
"story": "Done in 21 seconds.",
"director": "turn_duration · 21 s · 5 tool calls · 1 file changed",
"balloon": "I added a fifth test for slugify(''). The result was 4 passed, 1 failed, exit code 1.",
"balloon": "I added a fifth test for slugify(''). All 5 tests now pass, and the change is committed.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -150,7 +150,7 @@
"ok": null,
"story": "Done in 6 minutes.",
"director": "turn_duration · 6 min 25 s · 26 tool calls · 13 files changed",
"balloon": "The inventory/ package is built and committed. --csv is part of the CLI commit rather than its own commit, because you asked for it before that module existed. Known quirks: remove pear prints the name as you typed it, not as it was stored (\"Pear\"). Because of how the temporary file is created, the saved JSON file is readable only by its owner (permissions 0600).",
"balloon": "The inventory/ package is built and committed. Decisions you may want to change: I committed CLAUDE.md. .claude/settings.local.json, .codeman-agent-case.json and the default inventory.json are gitignored. --csv is part of the CLI commit rather than its own commit, because you asked for it before that module existed. Known quirks: remove pear prints the name as you typed it, not as it was stored (\"Pear\"). Because of how the temporary file is created, the saved JSON file is readable only by its owner (permissions 0600).",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -305,7 +305,7 @@
"ok": null,
"story": "Done in 1 minute.",
"director": "turn_duration · 1 min 16 s · 16 tool calls · 2 files changed",
"balloon": "I ran all 16 steps in order, one tool call each. Two places where I didn't follow your wording exactly: Step 6: this session has no Grep tool, so I ran grep -rn 'def ' inventory/ through Bash instead. It found the same matches.",
"balloon": "I ran all 16 steps in order, one tool call each. Two places where I didn't follow your wording exactly: Step 6: this session has no Grep tool, so I ran grep -rn 'def ' inventory/ through Bash instead. It found the same matches. Steps 5, 10 and 15: I added a second -m with the Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> line to each commit, as the earlier commits have. The commit subjects are exactly as you wrote them.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -86,7 +86,7 @@
"ok": null,
"story": "Done in 3 minutes.",
"director": "turn_duration · 3 min 13 s · 13 tool calls · 3 files changed",
"balloon": "I built wcount.py and test_wcount.py. What failed and how I fixed it: one test failed on the first run.",
"balloon": "I built wcount.py and test_wcount.py. All 19 tests pass (19 passed in 0.46s) and ruff check . is clean. What failed and how I fixed it: one test failed on the first run.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -94,7 +94,7 @@
{
"kind": "save",
"ok": true,
"story": "Claude starts a git repository, saves the work and copies it to remote.git, a folder on this computer, but a step after that fails.",
"story": "Claude starts a git repository, saves the work and copies it to remote.git, a folder on this computer, but a check after that fails.",
"director": "git commit -q -F - · git push -u origin main · 1 error",
"balloon": null,
"tests": null,
@@ -105,7 +105,7 @@
{
"kind": "check",
"ok": true,
"story": "The tests pass.",
"story": "The tests pass in a fresh copy made from remote.git.",
"director": "python3 -m pytest -q -p no:cacheprovider) → ok · 19 passed · then 1 second opinion",
"balloon": null,
"tests": {
@@ -104,7 +104,7 @@
"ok": null,
"story": "Claude's part is done in 3 minutes. Its helper finished later and reported back.",
"director": "turn_duration · 2 min 44 s · 11 tool calls · 5 files changed · 1 helper",
"balloon": "I've built csvstat.py, a command-line tool that prints a CSV's row count and the min, max and mean of each column. A background helper is writing README.md; I didn't wait for it, so it isn't confirmed yet. Problem: numbers are shown to only 6 significant digits. Once it has finished, the fix is a one-line formatting change and an update to the README example; say if you want it.",
"balloon": "I've built csvstat.py, a command-line tool that prints a CSV's row count and the min, max and mean of each column. Its 17 pytest tests pass, the linter (ruff) is clean, and the dry run on a sample CSV worked. A background helper is writing README.md; I didn't wait for it, so it isn't confirmed yet. Problem: numbers are shown to only 6 significant digits. Once it has finished, the fix is a one-line formatting change and an update to the README example; say if you want it.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -92,7 +92,7 @@
"ok": null,
"story": "Done in 3 minutes.",
"director": "turn_duration · 2 min 39 s · 10 tool calls · 3 files changed",
"balloon": "I built the pomodoro timer. Opening the page: it won't work if you double-click index.html. Serve the folder instead, for example with python3 -m http.server in the folder, then open http://localhost:8000. I didn't open it in a browser; I only checked that its script is valid.",
"balloon": "I built the pomodoro timer. All 7 tests pass under node --test, and everything is committed on main as c4e7b0b feat: add pomodoro timer with tests. Opening the page: it won't work if you double-click index.html. Serve the folder instead, for example with python3 -m http.server in the folder, then open http://localhost:8000. I didn't open it in a browser; I only checked that its script is valid.",
"tests": null,
"roundsOk": null,
"printed": null,
+2 -2
View File
@@ -127,7 +127,7 @@ describe('Story Mode on real sessions', () => {
// One commit, then a push in a command of its own: one commit, not two.
// The folder had no repository: the first save starts one, and the reader is told.
story:
'Claude starts a git repository, saves the work and copies it to remote.git, a folder on this computer, but a step after that fails.',
'Claude starts a git repository, saves the work and copies it to remote.git, a folder on this computer, but a check after that fails.',
});
});
@@ -142,7 +142,7 @@ describe('Story Mode on real sessions', () => {
'Claude saves the work: "add calc_stats.mode with smallest-value tie-breaking", then copies it to origin.git, a folder on this computer.'
);
expect(third.panels.find((p) => p.kind === 'save')?.story).toBe(
'Claude saves the work and copies it to origin.git, a folder on this computer, but a step after that fails.'
'Claude saves the work and copies it to origin.git, a folder on this computer, but a check after that fails.'
);
});
+23
View File
@@ -886,6 +886,29 @@ describe('story-mode.js overlay', () => {
expect(note.textContent).toBe('after asking a stronger AI model for a second opinion');
});
it('a second opinion that came back with an error says no answer came', async () => {
const failed = {
type: 'assistant',
timestamp: '2026-10-18T09:41:20.000Z',
message: {
content: [
{ type: 'server_tool_use', id: 'srv1', name: 'advisor', input: {} },
{
type: 'advisor_tool_result',
tool_use_id: 'srv1',
content: { type: 'advisor_tool_result_error', error_code: 'unavailable' },
},
],
},
};
boot(storyOf([prompt('fix it'), failed, ...edit('e1', '/work/demo-shop/a.py', ['+a = 1']), end(1000)]));
app.openStoryMode(SID);
await settle();
expect($('.story-panel[data-kind="fix"] .story-cap-note')!.textContent).toBe(
'after trying to ask a stronger AI model for a second opinion (no answer came)'
);
});
it('the final panel counts the commits of the whole turn, and says when the work was uploaded', async () => {
const chipsFor = async (to: string) => {
boot(
+69
View File
@@ -1163,6 +1163,75 @@ describe('buildStoryPage', () => {
expect(page).toMatchObject({ openNote: 'catch', asks: true });
});
it('the closing balloon keeps the outcome, the items a list announces and what an offer points back to (round 10)', () => {
const last = (text: string) => pageOf([prompt('go'), said(text), end(1000)]).panels.at(-1)!.balloon!.text;
const filler = 'It reads the file and prints the counts for each column, one line per column. '.repeat(3);
// The outcome after the first sentence, and a test count ("1 failed") is no failed step.
expect(
last(
[
'I added a fifth test.',
'All 5 tests now pass, and the change is committed.',
filler,
'- First run: the result was 4 passed, 1 failed.',
].join('\n')
)
).toBe('I added a fifth test. All 5 tests now pass, and the change is committed.');
// "Two places…:" brings both list lines, not only the first.
expect(
last(
[
'I ran all 16 steps in order, one tool call each.',
filler,
"Two places where I didn't follow your wording exactly:",
'- Step 6: there is no Grep tool, so I ran grep through Bash. It found the same matches.',
'- Steps 5 and 10: I added a co-author line to each commit.',
].join('\n')
)
).toBe(
"I ran all 16 steps in order, one tool call each. Two places where I didn't follow your wording exactly: Step 6: there is no Grep tool, so I ran grep through Bash. It found the same matches. Steps 5 and 10: I added a co-author line to each commit."
);
// An offer whose "it" is further back brings the start of its paragraph.
expect(
last(
[
'I added a --prefix option.',
filler,
'Suffix swap: the two files with the same time would trade their _1 suffix. This was already in the script before. Sorting byte by byte would fix it. Want me to add that?',
].join('\n')
)
).toBe(
'I added a --prefix option. Suffix swap: the two files with the same time would trade their _1 suffix. … Sorting byte by byte would fix it. Want me to add that?'
);
// A decision left to you is something for you.
expect(keyKindOf('Decisions you may want to change: I committed CLAUDE.md.')).toBe('offer');
});
it('a check after a push is named a check, and tests in a fresh copy say so', () => {
const page = pageOf([
prompt('ship it'),
...bash('b1', 'git commit -qm "add a"', 'Commit', ''),
...bash(
'b2',
'git push -u origin main && git remote -v && git -C remote.git log --oneline',
'Push',
'Exit code 128\nTo ./remote.git\n * [new branch] main -> main\nfatal: your current branch does not have any commits yet',
true
),
...bash(
'b3',
'C=/tmp/x && git clone -q remote.git "$C" && (cd "$C" && python3 -m pytest -q)',
'Fresh copy',
'19 passed in 0.11s'
),
end(1000),
]);
expect(page.panels.find((p) => p.kind === 'save')!.caption.story).toMatch(/, but a check after that fails\.$/);
expect(page.panels.find((p) => p.kind === 'check')!.caption.story).toBe(
'The tests pass in a fresh copy made from remote.git.'
);
});
it("a run panel's terminal card shows what the command printed, never what looks like a secret", () => {
const page = pageOf([
prompt('dry run please'),