fix(story-mode): Claude's last words keep the sentences aimed at you (review round 9)

Two fresh cold readers (the person who asked for the work, a newcomer to
the project) found the round-8 balloons still cut exactly where Claude
turns from describing to asking, offering or warning: "Want me to add
that?", "I can fix the number format whenever you want", the
http.server instruction, the fix that followed a reported bug.

Key sentences are now picked by kind, one of each, at most three, and
shown in the order Claude wrote them: a problem left (with the fix right
after it, when there is one, so it never reads as open), a question or
offer, how to use what was built, something not done or checked yet, a
step that failed, a word on what you asked for, a corrected warning. A
list intro is judged on its own words and brings its first items; a
pointer ("described below") is skipped; a short offer brings what
"that" was.

The final panel reads "Done, with a catch" for a problem left, "Done,
with a note" for something pending, and "Done, with a question" when
the next move is yours (`openNote` is now 'catch' | 'note'; `asks`).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 15:41:57 +02:00
1 parent 9c81e4c425
commit 59d70caeb3
17 files changed
+202 -77

No files matched your search

@@ -12,7 +12,8 @@
"helpers": 2,
"helpersRunning": 0,
"finishedLater": 2,
"openNote": true,
"openNote": "note",
"asks": false,
"panels": [
{
"kind": "mission",
@@ -88,6 +89,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": true,
"panels": [
{
"kind": "mission",
@@ -116,7 +118,7 @@
"ok": null,
"story": "Done in 14 seconds.",
"director": "turn_duration · 14 s · 1 tool call",
"balloon": "Helper A has finished writing README.md, and I checked it against the code. It describes the command-line usage, count(text) and how to run the tests correctly, and its example ((2, 4, 24)) is right. It doesn't mention that running the tool without exactly one file prints a usage message and exits…",
"balloon": "Helper A has finished writing README.md, and I checked it against the code. It doesn't mention that running the tool without exactly one file prints a usage message and exits with code 2; I can add that line if you want it.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -138,6 +140,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -190,7 +193,8 @@
"helpers": 1,
"helpersRunning": 0,
"finishedLater": 2,
"openNote": true,
"openNote": "note",
"asks": false,
"panels": [
{
"kind": "mission",
@@ -263,6 +267,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -316,6 +321,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -366,6 +372,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -430,6 +437,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -469,6 +477,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -13,6 +13,7 @@
"helpersRunning": 0,
"finishedLater": 1,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -122,6 +123,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": true,
"panels": [
{
"kind": "mission",
@@ -165,7 +167,7 @@
"ok": null,
"story": "Done in 21 seconds.",
"director": "turn_duration · 21 s · 2 tool calls",
"balloon": "I built rename_by_date.sh, the helper agent wrote README.md, and the script passed every test I ran on sample files in samples/. Nothing is committed because this folder isn't a git repository, even though the session context said it was. Only lowercase .jpg is matched, as you asked.",
"balloon": "I built rename_by_date.sh, the helper agent wrote README.md, and the script passed every test I ran on sample files in samples/. Nothing is committed because this folder isn't a git repository, even though the session context said it was. Say if you want me to run git init and commit. Only lowercase .jpg is matched, as you asked.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -187,6 +189,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": true,
"panels": [
{
"kind": "mission",
@@ -252,7 +255,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. The samples folder didn't change.",
"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?",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -13,6 +13,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -110,6 +111,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -160,6 +162,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -13,6 +13,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -169,6 +170,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -225,7 +227,7 @@
"ok": null,
"story": "Done in 37 seconds.",
"director": "turn_duration · 37 s · 8 tool calls · 2 files changed",
"balloon": "mode(xs) is in calc_stats.py, committed once after the tests went green, and pushed. I didn't use the advisor, gh or the network.",
"balloon": "mode(xs) is in calc_stats.py, committed once after the tests went green, and pushed. git describe --tags failed because the repo has no tags.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -247,6 +249,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -333,6 +336,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -13,6 +13,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -94,7 +95,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.",
"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.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -116,6 +117,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -161,7 +163,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('').",
"balloon": "I added a fifth test for slugify(''). The result was 4 passed, 1 failed, exit code 1.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -12,7 +12,8 @@
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"openNote": "catch",
"asks": false,
"panels": [
{
"kind": "mission",
@@ -156,7 +157,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.",
"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).",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -178,6 +179,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -310,7 +312,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. I didn't re-run ruff, because only README.md and CHANGELOG.md changed, and ruff doesn't check Markdown.",
"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.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -13,6 +13,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -107,7 +108,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. With a missing file plus a readable one, the total row didn't appear because the code counted files that could be read rather than files that were named.",
"balloon": "I built wcount.py and test_wcount.py. What failed and how I fixed it: one test failed on the first run.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -12,7 +12,8 @@
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
"openNote": true,
"openNote": "note",
"asks": false,
"panels": [
{
"kind": "mission",
@@ -142,7 +143,7 @@
"ok": null,
"story": "Done in 5 minutes.",
"director": "turn_duration · 4 min 57 s · 20 tool calls · 6 files changed",
"balloon": "I've built the kebab slug tool in this folder, committed it, and pushed it to the new bare repo remote.git. Not tested: the README says pip install . gives you a kebab command.",
"balloon": "I've built the kebab slug tool in this folder, committed it, and pushed it to the new bare repo remote.git. If you want a different author, run git commit --amend --reset-author and push again. Not tested: the README says pip install . gives you a kebab command.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -13,6 +13,7 @@
"helpersRunning": 0,
"finishedLater": 2,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -120,7 +121,8 @@
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
"openNote": true,
"openNote": "note",
"asks": false,
"panels": [
{
"kind": "mission",
@@ -182,6 +184,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": true,
"panels": [
{
"kind": "mission",
@@ -224,7 +227,7 @@
"ok": null,
"story": "Done in 16 seconds.",
"director": "stop_hook_summary · 16 s · 2 tool calls",
"balloon": "The project is done: both helpers finished, and I checked their files and committed them. Both helpers warned that index.test.mjs didn't exist, but they had looked before I created it, so the README's Testing section is correct.",
"balloon": "The project is done: both helpers finished, and I checked their files and committed them. Both helpers warned that index.test.mjs didn't exist, but they had looked before I created it, so the README's Testing section is correct. Say if you want CLAUDE.md committed too.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -246,6 +249,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -346,6 +350,7 @@
"helpersRunning": 0,
"finishedLater": 0,
"openNote": false,
"asks": false,
"panels": [
{
"kind": "mission",
@@ -12,7 +12,8 @@
"helpers": 1,
"helpersRunning": 0,
"finishedLater": 1,
"openNote": true,
"openNote": "catch",
"asks": true,
"panels": [
{
"kind": "mission",
@@ -114,7 +115,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. The population mean shows a problem, described below.",
"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.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -135,7 +136,8 @@
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
"openNote": true,
"openNote": "catch",
"asks": true,
"panels": [
{
"kind": "mission",
@@ -164,7 +166,7 @@
"ok": null,
"story": "Done in 9 seconds.",
"director": "turn_duration · 9 s · 1 tool call",
"balloon": "The background helper has finished README.md, and I've checked its work. Rounding: the README now explains that numbers are shown to 6 significant digits, so the population mean of 214574.2 appears as 214574. That note makes the current behaviour clear, but it doesn't fix it.",
"balloon": "The background helper has finished README.md, and I've checked its work. Rounding: the README now explains that numbers are shown to 6 significant digits, so the population mean of 214574.2 appears as 214574. That note makes the current behaviour clear, but it doesn't fix it. Now that the helper is done, nothing else is writing to the repo, so I can fix the number format and update the README example whenever you want.",
"tests": null,
"roundsOk": null,
"printed": null,
@@ -12,7 +12,8 @@
"helpers": 0,
"helpersRunning": 0,
"finishedLater": 0,
"openNote": true,
"openNote": "catch",
"asks": false,
"panels": [
{
"kind": "mission",
@@ -113,7 +114,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. The page loads timer.js as a module, and Chrome and Firefox block that for local files, so you'd get a timer that never moves.",
"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.",
"tests": null,
"roundsOk": null,
"printed": null,
+2 -1
View File
@@ -41,6 +41,7 @@ function pagesOf(name: string) {
helpersRunning: p.helpersRunning,
finishedLater: p.finishedLater ?? 0,
openNote: p.openNote ?? false,
asks: p.asks ?? false,
panels: p.panels.map((x) => ({
kind: x.kind,
ok: x.ok ?? null,
@@ -164,7 +165,7 @@ describe('Story Mode on real sessions', () => {
// An open problem, said further into the final message than its first sentence.
expect(last('web-page-node-test', 0).balloon).toMatch(/won't work if you double-click index\.html/);
// ...and the turn is not drawn as a clean finish.
expect(pagesOf('web-page-node-test')[0].openNote).toBe(true);
expect(pagesOf('web-page-node-test')[0].openNote).toBe('catch');
expect(last('text-plan-dry-run-helper', 1).balloon).toMatch(/doesn't fix it/);
// What you expected, and what you asked for mid-turn.
expect(last('errors-push-append', 2).balloon).toMatch(/As you expected, git describe --tags then failed/);
+6 -4
View File
@@ -1019,7 +1019,7 @@ describe('buildStoryPage', () => {
expect(page.panels.at(-1)!.kind).toBe('complete');
});
it('the closing balloon keeps an open problem from far into the message, never a table row or a question', () => {
it('the closing balloon keeps an open problem and a question to you from far into the message, never a table row', () => {
const long = [
'I built the csvstat tool for your CSV files.',
'It reads the file and prints the counts for each column, one line per column. '.repeat(4),
@@ -1028,10 +1028,12 @@ describe('buildStoryPage', () => {
'Want me to add a --json flag?',
'One known issue: big numbers are shown rounded.',
].join('\n');
const balloon = pageOf([prompt('build it'), said(long), end(1000)]).panels.at(-1)!.balloon!.text;
expect(balloon).toBe(
'I built the csvstat tool for your CSV files. One known issue: big numbers are shown rounded.'
const page = pageOf([prompt('build it'), said(long), end(1000)]);
// In the order Claude wrote them; the question is the sentence aimed at you, so it stays.
expect(page.panels.at(-1)!.balloon!.text).toBe(
'I built the csvstat tool for your CSV files. Want me to add a --json flag? One known issue: big numbers are shown rounded.'
);
expect(page).toMatchObject({ openNote: 'catch', asks: true });
});
it("a run panel's terminal card shows what the command printed, never what looks like a secret", () => {