mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
feat(story-mode): a plain Simple caption on every panel
Story Mode gets a third reading level for someone who has never seen a terminal (a parent, a manager). Every panel now carries caption.simple: one short sentence in everyday words, with no file name, command, tool name, flag or jargon. The sentences are templates only, so nothing from the transcript (a path, a commit message, a branch) can reach them, and they are written from the same facts as the story caption: a failed check never reads as a pass, a failure you asked for says so, and a check that fails after one passed is a failure again. The final panel's simple caption is a plain summary: how the turn ended and roughly how long it took, how many files changed, whether the checks pass, whether the work is saved (only claimed "not saved" where saving is in play: you asked for it, or the turn used git), and what the reader must not miss (a problem left, a note, a question for them). test/story-plain-words.ts is the guard: the corpus test runs it over every panel of every page of the eleven sessions, and the unit tests cover the honest endings (failed check, catch, note, question, stopped, not done, to be continued, done for now) and the guard itself. The corpus expectations only gain "simple" lines. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ed6c89ff98
commit
b98deebafc
16 files changed
+738
-4
No files matched your search
+304
-2
@@ -2486,12 +2486,149 @@ function plannedFailure(turn: StoryTurn, events: StoryEvent[]): false | 'asked'
|
||||
return events.some((e) => e.intent === 'say' && EXPECTED_BY_CLAUDE.test(e.label)) ? 'planned' : false;
|
||||
}
|
||||
|
||||
// ========== Simple captions ==========
|
||||
// The Simple reading level is for someone who has never seen a terminal (a parent, a manager). Its
|
||||
// captions are TEMPLATES ONLY: never a word taken from the transcript (no file name, command, commit
|
||||
// message, branch or helper name), so no path, code or jargon can reach them. They are written from
|
||||
// the same facts as the story caption, so a failed check never reads as a pass.
|
||||
|
||||
/** "a file", "3 files". */
|
||||
const someFiles = (n: number): string => (n === 1 ? 'a file' : `${n} files`);
|
||||
/** "a helper", "2 helpers". */
|
||||
const someHelpers = (n: number): string => (n === 1 ? 'a helper' : `${n} helpers`);
|
||||
/** A caption sentence continued after a semicolon: "A check fails." becomes "a check fails.". */
|
||||
const lowerFirst = (s: string): string => (s.startsWith('Claude') ? s : `${s[0]?.toLowerCase() ?? ''}${s.slice(1)}`);
|
||||
/** "a, b and c". */
|
||||
const andList = (parts: string[]): string =>
|
||||
parts.length < 2 ? (parts[0] ?? '') : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`;
|
||||
/** A check other than the tests, in everyday words; a type check or a build is just "another check". */
|
||||
const simpleCheckName = (e: StoryEvent | undefined): string => {
|
||||
const name = checkName(e);
|
||||
return name === 'a formatting check' || name === 'a style check' ? name : 'another check';
|
||||
};
|
||||
/** A prompt that keeps the work out of git ("no git", "don't commit"). */
|
||||
const NO_SAVE = /\b(no|without|skip|don't use|do not use) git\b|\b(don't|do not|never) commit\b|\bno commits?\b/i;
|
||||
/** git's own line for a commit it made (`[main 1a2b3c4] message`): the save went through. */
|
||||
const COMMIT_MADE = /^\[[^\]\n]+ [0-9a-f]{7,}\]/m;
|
||||
/** git's own lines for a push that went through. */
|
||||
const PUSH_MADE = /\[new branch\]|\S+\.\.\S+\s+\S+\s+->|\bset up to track\b|Everything up-to-date/;
|
||||
|
||||
/** A montage in everyday words: the parts that matter most first, three at most. */
|
||||
function simpleMontage(evs: StoryEvent[]): string {
|
||||
const has = (...intents: StoryIntent[]) => evs.some((e) => intents.includes(e.intent));
|
||||
const parts = [
|
||||
has('edit') ? 'changes files' : '',
|
||||
has('test', 'build') ? 'checks the work' : '',
|
||||
has('save') || evs.some((e) => (e.commits ?? 0) > 0) ? 'saves the work' : '',
|
||||
has('read', 'search', 'look') ? 'reads files' : '',
|
||||
has('run', 'tool') ? 'runs some steps' : '',
|
||||
has('plan') ? 'makes a plan' : '',
|
||||
has('web') ? 'looks things up' : '',
|
||||
].filter(Boolean);
|
||||
if (!parts.length) return 'takes a few more steps';
|
||||
return parts.length > 3 ? `${parts.slice(0, 3).join(', ')}, and more` : andList(parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* The final panel in everyday words: how the turn ended and how long it took, how many files changed,
|
||||
* whether the checks pass, whether the work is saved, and what the reader must not miss.
|
||||
*/
|
||||
function simpleEnding(turn: StoryTurn, page: Omit<StoryPage, 'panels'>): string {
|
||||
const events = page.events;
|
||||
// Rounded to minutes past one, so "about": the chip beside it says "2 min 41 s".
|
||||
const ms = turn.durationMs;
|
||||
const took = ms !== undefined && ms >= 1000 ? `${ms >= 60_000 ? 'about ' : ''}${roughDuration(ms)}` : '';
|
||||
const running = page.helpersRunning;
|
||||
const commands = events.filter((e) => e.running && !e.helper).length;
|
||||
const later = events.filter((e) => e.finishedLater && e.intent !== 'prompt');
|
||||
const out: string[] = [];
|
||||
if (turn.interrupted) out.push(took ? `You stopped Claude after ${took}.` : 'You stopped Claude partway.');
|
||||
else if (page.noneWorked) out.push('Nothing Claude tried worked, so the job is not done.');
|
||||
else if (turn.endedBy === 'next prompt') out.push('The work stops here; the next message starts something new.');
|
||||
else if (running || commands)
|
||||
out.push(
|
||||
`Claude's part ${took ? `took ${took}` : 'is done'}, but ${
|
||||
running
|
||||
? `${running === 1 ? 'a helper is' : `${running} helpers are`} still working`
|
||||
: 'some work is still running'
|
||||
}.`
|
||||
);
|
||||
else if (later.length)
|
||||
out.push(
|
||||
`Claude's part ${took ? `took ${took}` : 'is done'}; ${
|
||||
later.every((e) => e.helper) ? (later.length === 1 ? 'its helper' : 'its helpers') : 'the work it left running'
|
||||
} finished later.`
|
||||
);
|
||||
else out.push(took ? `Done in ${took}.` : 'Done.');
|
||||
|
||||
// A report turn's helper wrote its files in its own transcript: "no files changed" would contradict it.
|
||||
if (page.filesChanged) out.push(`${page.filesChanged === 1 ? 'One file' : `${page.filesChanged} files`} changed.`);
|
||||
else if (turn.origin === 'human') out.push('No files changed.');
|
||||
|
||||
// The checks: how the LAST one went, failed counting a run that reports failed tests.
|
||||
const checks = events.filter((e) => e.intent === 'test' || e.intent === 'build');
|
||||
const last = checks[checks.length - 1];
|
||||
if (last && (last.running || last.ok === undefined)) out.push('A check is still running.');
|
||||
else if (last && last.ok && !(last.tests && last.tests.failed > 0)) out.push('The checks pass.');
|
||||
else if (last) {
|
||||
// "As you asked" only while no run has passed yet: a check failing after one passed is a problem.
|
||||
const planned = checks.some((e) => e.ok && !(e.tests && e.tests.failed > 0)) ? false : plannedFailure(turn, events);
|
||||
out.push(
|
||||
planned === 'asked'
|
||||
? 'The checks fail, as you asked.'
|
||||
: planned
|
||||
? 'The checks fail, as planned.'
|
||||
: 'A check still fails.'
|
||||
);
|
||||
} else if (page.filesChanged) {
|
||||
// No check, and nothing run after the first change either (a practice run counts as trying it).
|
||||
const firstEditAt = events.findIndex((e) => editsOf(e).length > 0);
|
||||
const tried = events.some((e, i) => i > firstEditAt && e.tool === 'Bash' && e.intent === 'run');
|
||||
if (!tried) out.push('Claude did not try out its changes.');
|
||||
}
|
||||
|
||||
// Saved: a commit or an upload that went through, whatever came after it in the same command.
|
||||
const savedAt = events.reduce(
|
||||
(at, e, i) =>
|
||||
((e.commits ?? 0) > 0 && (e.ok !== false || COMMIT_MADE.test(e.output ?? ''))) ||
|
||||
(e.ships && (e.ok !== false || PUSH_MADE.test(e.output ?? '')))
|
||||
? i
|
||||
: at,
|
||||
-1
|
||||
);
|
||||
const lastEditAt = events.reduce((at, e, i) => (editsOf(e).length ? i : at), -1);
|
||||
if (savedAt >= 0 && lastEditAt > savedAt) out.push('Some of the work is saved, but the latest changes are not.');
|
||||
else if (savedAt >= 0) out.push('The work is saved.');
|
||||
// "Not saved" only where saving is in play (you asked for it, or the project keeps its history in
|
||||
// git): in a folder you said to keep out of git, files written are simply there.
|
||||
else if (
|
||||
page.filesChanged &&
|
||||
(events.some((e) => e.intent === 'prompt' && ASKS_TO_SAVE.test(e.label) && !NO_SAVE.test(e.label)) ||
|
||||
events.some((e) => e.tool === 'Bash' && /\bgit\b/.test(e.label)))
|
||||
)
|
||||
out.push('The work is not saved yet.');
|
||||
|
||||
// What the reader must not miss: Claude's own words hold the details, one click away.
|
||||
if (page.openNote === 'catch') out.push('Claude says a problem is left: read its message.');
|
||||
else if (page.openNote === 'note') out.push('Claude left you a note: read its message.');
|
||||
if (page.asks) out.push('Claude has a question for you.');
|
||||
if (turn.interrupted) {
|
||||
const todos = [...events].reverse().find((e) => e.todos?.length)?.todos;
|
||||
if (todos?.some((t) => !t.done)) out.push('Some of its plan is not done yet.');
|
||||
}
|
||||
return out.join(' ');
|
||||
}
|
||||
|
||||
function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, index: number): StoryPanel {
|
||||
const all = draft.events.map((i) => events[i]);
|
||||
// Second opinions riding on this step are counted, never part of its story.
|
||||
const opinions = all.filter((e) => e.tool === 'Advisor').length;
|
||||
const evs = opinions && opinions < all.length ? all.filter((e) => e.tool !== 'Advisor') : all;
|
||||
const panel: StoryPanel = { kind: draft.kind, events: draft.events, caption: { story: '', director: '' } };
|
||||
const panel: StoryPanel = {
|
||||
kind: draft.kind,
|
||||
events: draft.events,
|
||||
caption: { story: '', director: '', simple: '' },
|
||||
};
|
||||
if (evs !== all) panel.secondOpinion = opinions;
|
||||
if (draft.balloon) panel.balloon = draft.balloon;
|
||||
const edits = evs.flatMap(editsOf);
|
||||
@@ -2513,6 +2650,8 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
const tools = countBy(evs, (e) => e.tool);
|
||||
let story = '';
|
||||
let director = tools;
|
||||
// The Simple level's sentence, from the same facts (templates only: see "Simple captions").
|
||||
let simple = '';
|
||||
|
||||
switch (draft.kind) {
|
||||
case 'mission': {
|
||||
@@ -2535,6 +2674,22 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
: origin === 'peer'
|
||||
? 'Another session chimes in while Claude works.'
|
||||
: lines[origin];
|
||||
const plainly: Record<StoryTurnOrigin, string> = {
|
||||
human: 'You give Claude a job.',
|
||||
task: 'A job Claude left running reports back.',
|
||||
peer: 'Another Claude sends a message.',
|
||||
scheduled: 'A job set up ahead of time starts this work.',
|
||||
system: 'Claude picks up the work by itself.',
|
||||
};
|
||||
simple = command
|
||||
? 'You give Claude a short instruction.'
|
||||
: index === 0
|
||||
? plainly[turn.origin]
|
||||
: origin === 'human'
|
||||
? 'You add something while Claude works.'
|
||||
: origin === 'peer'
|
||||
? 'Another Claude chimes in while this one works.'
|
||||
: plainly[origin];
|
||||
// Words that started the turn but are not yours are a note, not your balloon.
|
||||
if (panel.balloon && !command && origin !== 'human') panel.balloon = { ...panel.balloon, who: 'note' };
|
||||
// A helper reporting back: its own words, not only the line saying it finished, so what it
|
||||
@@ -2598,6 +2753,25 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
: checking
|
||||
? 'Claude checks the work that came back.'
|
||||
: 'Claude looks around the project.';
|
||||
simple = evs.every((e) => e.tool === 'ToolSearch')
|
||||
? evs.some((e) => /PlanMode/.test(e.label))
|
||||
? 'Claude gets ready to plan.'
|
||||
: 'Claude gets ready for the job.'
|
||||
: shown.length && shown.length <= 2
|
||||
? checking
|
||||
? 'Claude checks the work that came back.'
|
||||
: shown.length === 1
|
||||
? 'Claude reads a file from the project.'
|
||||
: 'Claude reads two files from the project.'
|
||||
: shown.length
|
||||
? `Claude reads ${shown.length} files to find its way around.`
|
||||
: evs.every((e) => e.intent === 'search')
|
||||
? 'Claude searches the project.'
|
||||
: savedBefore && evs.every((e) => e.tool === 'Bash' && GIT_LOOK.test(e.label.trim()))
|
||||
? 'Claude checks what it saved.'
|
||||
: checking
|
||||
? 'Claude checks the work that came back.'
|
||||
: 'Claude looks around the project.';
|
||||
// A tool lookup's query (`select:EnterPlanMode,ExitPlanMode`) means nothing to a reader.
|
||||
if (evs.every((e) => e.tool === 'ToolSearch')) panel.notes = [];
|
||||
break;
|
||||
@@ -2605,6 +2779,7 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
case 'research':
|
||||
panel.notes = evs.map(shortCommand).slice(0, 4);
|
||||
story = 'Claude looks things up on the web.';
|
||||
simple = story;
|
||||
break;
|
||||
case 'plan': {
|
||||
const todos = [...evs].reverse().find((e) => e.todos)?.todos;
|
||||
@@ -2615,6 +2790,11 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
: evs.every((e) => e.tool === 'EnterPlanMode')
|
||||
? 'Claude switches to plan mode to think it through.'
|
||||
: 'Claude thinks it through.';
|
||||
simple = todos
|
||||
? `Claude makes a plan with ${plural(todos.length, 'step')}.`
|
||||
: evs.every((e) => e.tool === 'EnterPlanMode')
|
||||
? 'Claude stops to plan before it acts.'
|
||||
: 'Claude thinks it through.';
|
||||
// A plan written as text has no progress to report; a plan tool's list does.
|
||||
if (todos && evs.every((e) => e.tool === 'Text')) director = `written as text · ${plural(todos.length, 'step')}`;
|
||||
else if (todos && evs.some((e) => e.file && PLAN_FILE.test(e.file)))
|
||||
@@ -2648,6 +2828,15 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
? `Claude sends ${plural(helpers.length, 'helper')} off to work in the background.`
|
||||
: `Claude calls in ${plural(helpers.length, 'helper')}.`
|
||||
: 'Claude sends a message to another session.';
|
||||
simple = helpers.length
|
||||
? advisor === helpers.length
|
||||
? evs.every((e) => e.tool !== 'Advisor' || e.ok === false)
|
||||
? 'Claude asks a stronger AI for advice, but no answer comes.'
|
||||
: 'Claude asks a stronger AI for advice.'
|
||||
: behind === helpers.length
|
||||
? `Claude sends ${someHelpers(helpers.length)} off to work while it carries on.`
|
||||
: `Claude calls in ${someHelpers(helpers.length)}.`
|
||||
: 'Claude sends a message to another Claude.';
|
||||
const inside = evs.reduce((n, e) => n + (e.helper?.toolUses ?? 0), 0);
|
||||
const types = unique(helpers.map((h) => h.type).filter((t) => t !== 'advisor'));
|
||||
director = `${tools}${advisor ? ' · a second opinion' : ''}${types.length ? ` · ${types.join(' + ')}` : ''}${inside ? ` · ${plural(inside, 'call')} inside` : ''}`;
|
||||
@@ -2696,6 +2885,16 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
: files.length
|
||||
? `Claude changes ${fileWords(files)}.`
|
||||
: 'Claude changes a file.';
|
||||
simple =
|
||||
created && created === files.length
|
||||
? files.length === 1
|
||||
? 'Claude writes a new file.'
|
||||
: `Claude writes ${files.length} new files.`
|
||||
: whole && files.length && wholeList.length === files.length
|
||||
? `Claude writes ${someFiles(files.length)}.`
|
||||
: mixed
|
||||
? `Claude writes ${someFiles(mixed.whole.length)} and changes ${someFiles(mixed.patched.length)}.`
|
||||
: `Claude changes ${someFiles(Math.max(1, files.length))}.`;
|
||||
const perFile = files.slice(0, 3).map((f) => {
|
||||
const mine = netCounts(edits.filter((e) => e.file === f));
|
||||
return `${basename(f)} ${fmtCounts(mine.added, mine.removed)}`;
|
||||
@@ -2754,12 +2953,33 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
: panel.ok === false
|
||||
? `Fix and test, ${plural(n, 'round')}. Still failing.`
|
||||
: `Fix and test, ${plural(n, 'round')}.`;
|
||||
simple =
|
||||
panel.ok === true && !fails
|
||||
? 'Claude changes the work and checks it: everything passes.'
|
||||
: panel.ok === true && fails === lintFails
|
||||
? `${simpleCheckName(failed.find((e) => !!e.tests && e.tests.passed > 0 && !e.tests.failed && !e.tests.errors)).replace(/^a/, 'A')} fails at first; Claude fixes it, and everything passes.`
|
||||
: panel.ok === true && planned === 'asked'
|
||||
? checks.length === 2 && loadsNot(checks[0])
|
||||
? "At first the checks can't even start, as you asked; then they pass."
|
||||
: 'The first check fails on purpose, as you asked, then passes.'
|
||||
: panel.ok === true && planned
|
||||
? 'The first check fails, as planned, then everything passes.'
|
||||
: panel.ok === true && checks.length === 2 && fails === 1
|
||||
? 'The first check fails; Claude fixes it, and the second passes.'
|
||||
: panel.ok === true
|
||||
? 'Some checks fail at first; Claude fixes them until all pass.'
|
||||
: panel.ok === false && tests && tests.passed && !tests.failed && !tests.errors
|
||||
? 'Claude fixes and checks the work, but one check still fails.'
|
||||
: panel.ok === false
|
||||
? 'Claude keeps fixing and checking, but it still fails.'
|
||||
: 'Claude fixes and checks the work.';
|
||||
// Commits made inside the loop (a test one-liner that ends in one) are part of its story.
|
||||
const saved = evs.reduce((k, e) => k + (e.commits ?? 0), 0);
|
||||
if (saved)
|
||||
story += ` Claude ${startsRepoAt(draft.events, events) ? 'starts a git repository and ' : ''}saves the work${
|
||||
saved > 1 ? ` in ${saved} commits` : ''
|
||||
} along the way.`;
|
||||
if (saved) simple += ' It also saves the work.';
|
||||
director = `${plural(checks.length, 'check')} · ${plural(edits.length, 'edit')} · ${fmtCounts(panel.added, panel.removed)} · last: ${
|
||||
lastCheck ? outcome(lastCheck) : 'running'
|
||||
}`;
|
||||
@@ -2796,6 +3016,25 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
const plannedCheck = ok === false ? plannedFailure(turn, events) : false;
|
||||
if (plannedCheck)
|
||||
story = `${story.replace(/[.]$/, '')}, ${plannedCheck === 'asked' ? 'as you asked' : 'as planned'}.`;
|
||||
// The same verdicts in everyday words ("checks", never tests, builds or linters).
|
||||
if (lastCheck?.running) simple = 'Claude starts a check that runs on its own.';
|
||||
else if (lastCheck?.background && ok !== undefined)
|
||||
simple = lastCheck.finishedLater
|
||||
? `Claude starts a check that runs on its own; later it ${ok ? 'passes' : 'fails'}.`
|
||||
: `A check runs on its own and ${ok ? 'passes' : 'fails'}.`;
|
||||
else if (ok === undefined) simple = 'Claude checks the work.';
|
||||
else if (!ok && tests && !tests.passed && !tests.failed && tests.errors) simple = "The checks can't even start.";
|
||||
else if (!ok && tests && tests.passed && !tests.failed && !tests.errors)
|
||||
simple = 'The main checks pass, but another one fails.';
|
||||
else if (ok)
|
||||
simple = failedFirst ? 'A check fails at first, then passes.' : 'Claude checks the work: everything passes.';
|
||||
else
|
||||
simple =
|
||||
tests && tests.failed > 1
|
||||
? `Claude checks the work: ${tests.failed} checks fail.`
|
||||
: 'Claude checks the work: one check fails.';
|
||||
if (plannedCheck)
|
||||
simple = `${simple.replace(/[.]$/, '')}, ${plannedCheck === 'asked' ? 'as you asked' : 'as planned'}.`;
|
||||
// The same command with the same outcome twice in a row reads once, with a count.
|
||||
const runs: Array<{ text: string; n: number }> = [];
|
||||
for (const e of checks) {
|
||||
@@ -2836,6 +3075,12 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
patched.length ? `changes ${fileWords(patched)}` : '',
|
||||
].filter(Boolean);
|
||||
if (did.length) story = `Claude ${did.join(' and ')}; ${story[0].toLowerCase()}${story.slice(1)}`;
|
||||
// The files by count only, one verb (a new file is a change too): the sentence stays short.
|
||||
const didPlainly = written.length ? `${patched.length ? 'changes' : 'writes'} ${someFiles(written.length)}` : '';
|
||||
if (didPlainly)
|
||||
simple = simple.startsWith('Claude ')
|
||||
? `Claude ${didPlainly}, then ${simple.slice(7)}`
|
||||
: `Claude ${didPlainly}; ${lowerFirst(simple)}`;
|
||||
const saved = checks.reduce((n, e) => n + (e.commits ?? 0), 0);
|
||||
if (saved) {
|
||||
director += ` · ${plural(saved, 'commit')}`;
|
||||
@@ -2843,6 +3088,7 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
story = `${story.slice(0, -1)}, ${startsRepoAt(draft.events, events) ? 'then Claude starts a git repository and' : 'and Claude'} saves the work${
|
||||
newBranchAt(draft.events, events) ? ` on a new branch (${newBranchAt(draft.events, events)})` : ''
|
||||
}${checks.some((e) => e.ships === 'push') ? ' and sends it up' : ''}.`;
|
||||
if (ok) simple += ' Then it saves the work.';
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2883,6 +3129,26 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
? 'Claude does the dry run you asked for: it shows what would happen, changing nothing.'
|
||||
: 'Claude does a dry run: it shows what would happen, changing nothing.'
|
||||
: 'Claude works in the terminal.';
|
||||
const one = evs.length === 1;
|
||||
simple = evs.every((e) => e.running)
|
||||
? 'Claude starts something that keeps running on its own.'
|
||||
: errors && errors === evs.length && evs.every(emptyAnswer)
|
||||
? 'Claude looks something up and finds nothing, which is fine.'
|
||||
: errors && errors === evs.length
|
||||
? `Claude tries ${one ? 'a step' : 'some steps'}, but ${
|
||||
evs.some((e) => /\bblocked\b|PreToolUse|denied/i.test(e.output ?? ''))
|
||||
? `${one ? 'it is' : 'they are'} not allowed`
|
||||
: one
|
||||
? 'it fails'
|
||||
: 'they fail'
|
||||
}.`
|
||||
: evs.every((e) => e.tool !== 'Bash')
|
||||
? 'Claude uses one of its tools.'
|
||||
: dryRun
|
||||
? events.some((e) => e.intent === 'prompt' && DRY_RUN.test(e.label))
|
||||
? 'Claude does the practice run you asked for; nothing is changed.'
|
||||
: 'Claude does a practice run; nothing is changed.'
|
||||
: 'Claude runs some steps on the computer.';
|
||||
director = `${tools} · ${shortCommand(evs[0])}${errors ? ` · ${plural(errors, 'error')}` : ''}`;
|
||||
break;
|
||||
}
|
||||
@@ -2995,6 +3261,29 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
? `Claude saves the work${said}, then ${uploads('it')}.`
|
||||
: `Claude saves the work${inCommits} and ${uploads('it')}${said}.`
|
||||
: `Claude saves the work${inCommits}${said}.`;
|
||||
const shipPlainly = uploadOnly
|
||||
? localTarget
|
||||
? 'copies the work to another folder'
|
||||
: 'uploads the work'
|
||||
: ships === 'push'
|
||||
? localTarget
|
||||
? 'saves the work and copies it to another folder'
|
||||
: 'saves the work and uploads it'
|
||||
: 'saves the work';
|
||||
simple =
|
||||
savedAnyway || hiddenFail
|
||||
? `Claude ${uploadOnly ? shipPlainly : 'saves the work'}, but ${
|
||||
noTags ? 'a small lookup' : looksOnly ? 'a check' : 'a step'
|
||||
} after that fails${allowed ? ', as you said it might' : ''}${fixedNext ? '; Claude fixes it' : ''}.`
|
||||
: panel.ok === false
|
||||
? ships === 'pr'
|
||||
? 'Claude tries to send the work for review, but it fails.'
|
||||
: ships === 'push'
|
||||
? 'Claude tries to upload the work, but it fails.'
|
||||
: 'Claude tries to save the work, but it fails.'
|
||||
: ships === 'pr'
|
||||
? 'Claude sends the work to be reviewed.'
|
||||
: `Claude ${shipPlainly}.`;
|
||||
const wrote = unique(edits.map((d) => d.file));
|
||||
const startsRepo = startsRepoAt(draft.events, events);
|
||||
const wroteWhole = wrote.filter(
|
||||
@@ -3019,6 +3308,13 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
? `Claude ${lead.join(', ')}, and ${rest}`
|
||||
: `Claude ${lead[0]}${rest.replace(/"[^"]*"/g, '').includes(' and ') ? ',' : ' and'} ${rest}`;
|
||||
}
|
||||
// Files written before the save, by count: "Claude writes a file, then saves the work."
|
||||
const wrotePlainly = [
|
||||
wroteWhole.length ? `writes ${someFiles(wroteWhole.length)}` : '',
|
||||
wrotePatched.length ? `changes ${someFiles(wrotePatched.length)}` : '',
|
||||
].filter(Boolean);
|
||||
if (wrotePlainly.length && panel.ok !== false && !savedAnyway && !hiddenFail)
|
||||
simple = `Claude ${wrotePlainly.join(' and ')}, then ${simple.slice(7)}`;
|
||||
const branch = newBranchAt(draft.events, events);
|
||||
if (branch && commits) story = story.replace('saves the work', `saves the work on a new branch (${branch})`);
|
||||
director = `${evs.map(shortCommand).slice(0, 2).join(' · ')}${errors ? ` · ${plural(errors, 'error')}` : ''}${
|
||||
@@ -3039,6 +3335,7 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
: q?.answer
|
||||
? 'Claude asks you, and you answer.'
|
||||
: 'Claude asks you something.';
|
||||
simple = plan ? story : q?.answer ? 'Claude asks you a question, and you answer.' : 'Claude asks you a question.';
|
||||
director = `${tools}${q?.answer ? ` · answer: ${clip(q.answer, 60)}` : ''}`;
|
||||
break;
|
||||
}
|
||||
@@ -3046,12 +3343,16 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
story = evs.some((e) => e.label.includes('memory was full'))
|
||||
? "Claude's memory is full: it packs up its notes."
|
||||
: 'Claude packs up its notes to free memory.';
|
||||
simple = evs.some((e) => e.label.includes('memory was full'))
|
||||
? "Claude's memory is full, so it tidies up its notes."
|
||||
: 'Claude tidies up its notes to free up memory.';
|
||||
director = evs.map((e) => e.description).find(Boolean) ?? 'compact_boundary';
|
||||
break;
|
||||
case 'montage': {
|
||||
panel.notes = notesOf(evs.filter((e) => e.tool === 'Bash'));
|
||||
const added = evs.filter((e) => e.intent === 'prompt').length;
|
||||
story = `Claude ${index <= 1 ? 'gets started' : 'keeps going'}${added ? ` (you add ${plural(added, 'thing')})` : ''}: ${montageSummary(evs)}.`;
|
||||
simple = `Claude ${index <= 1 ? 'gets started' : 'keeps going'}${added ? ', and you add something' : ''}: it ${simpleMontage(evs)}.`;
|
||||
break;
|
||||
}
|
||||
case 'complete':
|
||||
@@ -3086,7 +3387,7 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
const count = plural(panel.secondOpinion, 'second opinion');
|
||||
director += ` · ${when === 'during' ? `${count} along the way` : `${when} ${count}`}${unanswered ? ' (no answer)' : ''}`;
|
||||
}
|
||||
panel.caption = { story, director };
|
||||
panel.caption = { story, director, simple: simple || 'Claude keeps working.' };
|
||||
return panel;
|
||||
}
|
||||
|
||||
@@ -3158,6 +3459,7 @@ function completePanel(turn: StoryTurn, page: Omit<StoryPage, 'panels'>, trailin
|
||||
caption: {
|
||||
story: turn.durationMs !== undefined && turn.durationMs < 1000 && story === done ? 'Done.' : story,
|
||||
director,
|
||||
simple: simpleEnding(turn, page),
|
||||
},
|
||||
};
|
||||
// A local command answers itself: its own first line is the last word.
|
||||
|
||||
+6
-1
@@ -164,7 +164,12 @@ export interface StoryPanel {
|
||||
kind: StoryPanelKind;
|
||||
/** Indices into the page's `events`. */
|
||||
events: number[];
|
||||
caption: { story: string; director: string };
|
||||
/**
|
||||
* Three reading levels: `simple` for someone who has never seen a terminal (one short sentence in
|
||||
* everyday words, no file names, commands, tools or jargon), `story` for anyone, `director` naming
|
||||
* the tools. All three are written from the same facts, so none says more (or better) than happened.
|
||||
*/
|
||||
caption: { story: string; director: string; simple: string };
|
||||
/** `note`: words that start a turn but are not yours (a background job's report, another session). */
|
||||
balloon?: { who: 'you' | 'claude' | 'note'; text: string };
|
||||
files?: string[];
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 87 words",
|
||||
"balloon": "Build a tiny Python word-count tool here (no git, no installs). First, use the Agent tool TWICE with run_in_background: true to start two background helper agents in parallel: helper A writes README.md describing wordcount.py (usage: python3 wordcount.py FILE prints lines, words, chars), helper B writes extra pytest tests in tests/test_extra.py for count(text) returning a (lines, words, chars) tu…",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "backup",
|
||||
"ok": null,
|
||||
"story": "Claude sends 2 helpers off to work in the background.",
|
||||
"simple": "Claude sends 2 helpers off to work while it carries on.",
|
||||
"unaskedSave": false,
|
||||
"director": "Agent ×2 · general-purpose · 8 calls inside",
|
||||
"balloon": "Starting both helpers in parallel first.",
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes the new files wordcount.py and test_basic.py.",
|
||||
"simple": "Claude writes 2 new files.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write ×2 · wordcount.py +23 · test_basic.py +21",
|
||||
"balloon": null,
|
||||
@@ -55,6 +58,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q 2>&1 → ok · 4 passed",
|
||||
"balloon": null,
|
||||
@@ -70,6 +74,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Claude's part is done in 36 seconds. Its 2 helpers finished later and reported back.",
|
||||
"simple": "Claude's part took 36 seconds; its helpers finished later. 2 files changed. The checks pass. Claude left you a note: read its message.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 36 s · 5 tool calls · 2 files changed · 2 helpers",
|
||||
"balloon": "I wrote wordcount.py and tests/test_basic.py, and the one python3 -m pytest -q run passed 4 of 4 tests. Only 4 tests ran, so tests/test_extra.py from helper B wasn't there yet and those tests haven't been run.",
|
||||
@@ -100,6 +105,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"simple": "A job Claude left running reports back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 6 words · task",
|
||||
"balloon": "\"Helper A: write README.md\" reports back with the file it wrote: README.md.",
|
||||
@@ -112,6 +118,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks README.md.",
|
||||
"simple": "Claude checks the work that came back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · cat /work/cases/story-r6-b/README.md",
|
||||
"balloon": null,
|
||||
@@ -124,6 +131,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 14 seconds.",
|
||||
"simple": "Done in 14 seconds. Claude has a question for you.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 14 s · 1 tool call",
|
||||
"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.",
|
||||
@@ -154,6 +162,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"simple": "A job Claude left running reports back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 8 words · task",
|
||||
"balloon": "\"Helper B: write extra pytest tests\" reports: I wrote the file: tests/test_extra.py It has 18 test cases and I worked out each expected value by hand. I haven't run it against the real wordcount.py.",
|
||||
@@ -166,6 +175,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q 2>&1 → ok · 22 passed",
|
||||
"balloon": "Helper B finished; running the full suite against the real wordcount.py now.",
|
||||
@@ -181,6 +191,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 11 seconds.",
|
||||
"simple": "Done in 11 seconds. The checks pass.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 11 s · 1 tool call",
|
||||
"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.",
|
||||
@@ -211,6 +222,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 93 words",
|
||||
"balloon": "Follow-up, no git: first start ONE background helper with the Agent tool (run_in_background: true) that writes CHANGELOG.md slowly: it must run sleep 20 with Bash, then Write CHANGELOG.md with one entry, then run sleep 20 again, then add a second entry with the Edit tool. Next, start the full test suite in the background with the Bash tool's run_in_background: true parameter, as: sleep 30; python…",
|
||||
@@ -223,6 +235,7 @@
|
||||
"kind": "backup",
|
||||
"ok": null,
|
||||
"story": "Claude sends 1 helper off to work in the background.",
|
||||
"simple": "Claude sends a helper off to work while it carries on.",
|
||||
"unaskedSave": false,
|
||||
"director": "Agent · general-purpose · 4 calls inside",
|
||||
"balloon": null,
|
||||
@@ -235,6 +248,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "Claude starts the tests in the background; later they pass.",
|
||||
"simple": "Claude starts a check that runs on its own; later it passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q → ok",
|
||||
"balloon": null,
|
||||
@@ -247,6 +261,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude changes 3 files.",
|
||||
"simple": "Claude changes 3 files.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write ×2 · Bash · wordcount.py +11 −4 · test_basic.py +7 · README.md ±0",
|
||||
"balloon": null,
|
||||
@@ -259,6 +274,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Claude's part is done in 52 seconds. Its helper finished later and reported back. Its background command finished later.",
|
||||
"simple": "Claude's part took 52 seconds; the work it left running finished later. 3 files changed. The checks pass. Claude left you a note: read its message.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 52 s · 5 tool calls · 3 files changed · 1 helper",
|
||||
"balloon": "I added the --json flag and started the helper and the test run as you asked. The helper and the tests are still running in the background; I haven't waited for either, so I don't have results yet. No git, as you asked.",
|
||||
@@ -289,6 +305,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"simple": "A job Claude left running reports back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 14 words · task",
|
||||
"balloon": "Background command \"Run full test suite after a 30s delay\" completed (exit code 0)",
|
||||
@@ -301,6 +318,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "tail -5 /tmp/claude/-work-cases-story-r6-b/a1a47282-e3ca-45… → ok · 23 passed",
|
||||
"balloon": null,
|
||||
@@ -316,6 +334,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 7 seconds.",
|
||||
"simple": "Done in 7 seconds. The checks pass. Claude left you a note: read its message.",
|
||||
"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. The CHANGELOG helper is still running; I'll report when it finishes.",
|
||||
@@ -346,6 +365,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"simple": "A job Claude left running reports back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 6 words · task",
|
||||
"balloon": "\"Helper: write CHANGELOG.md slowly\" reports back with the file it wrote: CHANGELOG.md.",
|
||||
@@ -358,6 +378,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks CHANGELOG.md.",
|
||||
"simple": "Claude checks the work that came back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · cat /work/cases/story-r6-b/CHANGELOG.md",
|
||||
"balloon": null,
|
||||
@@ -370,6 +391,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 8 seconds.",
|
||||
"simple": "Done in 8 seconds.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 8 s · 1 tool call",
|
||||
"balloon": "Everything you asked for this round is done. The helper used four tools, one for each step you asked for, so it looks like it followed the sleep, write, sleep, edit sequence.",
|
||||
@@ -400,6 +422,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 26 words",
|
||||
"balloon": "Small follow-up, no git: run python3 -m pytest -q once, then show the first 5 lines of CHANGELOG.md with head -5, then reply with one sentence.",
|
||||
@@ -412,6 +435,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q 2>&1 → ok · 23 passed",
|
||||
"balloon": null,
|
||||
@@ -427,6 +451,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude reads CHANGELOG.md.",
|
||||
"simple": "Claude reads a file from the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · head -5 /work/cases/story-r6-b/CHANGELOG.md",
|
||||
"balloon": null,
|
||||
@@ -439,6 +464,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 13 seconds.",
|
||||
"simple": "Done in 13 seconds. No files changed. The checks pass.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 13 s · 2 tool calls",
|
||||
"balloon": "All 23 tests passed, and the first five lines of CHANGELOG.md are the # Changelog title, the ## 2026-10-18 heading and the wordcount.py entry, with the --json entry on line 6, just past what head -5 shows.",
|
||||
@@ -469,6 +495,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 8 words",
|
||||
"balloon": "Reply with just the word OK, no tools.",
|
||||
@@ -481,6 +508,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 3 seconds.",
|
||||
"simple": "Done in 3 seconds. No files changed.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 3 s · 0 tool calls",
|
||||
"balloon": "OK",
|
||||
@@ -511,6 +539,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 8 words",
|
||||
"balloon": "Reply with just the word DONE, no tools.",
|
||||
@@ -523,6 +552,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 5 seconds.",
|
||||
"simple": "Done in 5 seconds. No files changed.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 5 s · 0 tool calls",
|
||||
"balloon": "DONE",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 65 words",
|
||||
"balloon": "First write a short todo list for this job, then build a bash script rename_by_date.sh in this folder that renames *.jpg files to their modification time as YYYY-MM-DD_HHMMSS.jpg with a --dry-run flag, and while you do that use the Agent tool to have one helper agent write README.md for the script; finally test it on a few sample files you create in a samples folder.",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "plan",
|
||||
"ok": null,
|
||||
"story": "Claude writes a plan: 5 steps.",
|
||||
"simple": "Claude makes a plan with 5 steps.",
|
||||
"unaskedSave": false,
|
||||
"director": "written as text · 5 steps",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git log --oneline | head; which shellcheck; bash…",
|
||||
"balloon": null,
|
||||
@@ -55,6 +58,7 @@
|
||||
"kind": "backup",
|
||||
"ok": null,
|
||||
"story": "Claude sends 1 helper off to work in the background.",
|
||||
"simple": "Claude sends a helper off to work while it carries on.",
|
||||
"unaskedSave": false,
|
||||
"director": "Agent · general-purpose · 6 calls inside · after 1 second opinion (no answer)",
|
||||
"balloon": null,
|
||||
@@ -67,6 +71,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude changes rename_by_date.sh and -dash.jpg.",
|
||||
"simple": "Claude changes 2 files.",
|
||||
"unaskedSave": false,
|
||||
"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.",
|
||||
@@ -79,6 +84,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude does the dry run you asked for: it shows what would happen, changing nothing.",
|
||||
"simple": "Claude does the practice run you asked for; nothing is changed.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×5 · rm -- ./-- && S=/tmp/claude/-work-cases-story-review-c/740e…",
|
||||
"balloon": "My mk -- ... helper call left a stray file named --. I'll remove it, then run the dry run and save a listing to confirm nothing changed.",
|
||||
@@ -95,6 +101,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Claude's part is done in 2 minutes. Its helper finished later and reported back.",
|
||||
"simple": "Claude's part took about 2 minutes; its helper finished later. 2 files changed. The work is not saved yet.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 2 min 1 s · 13 tool calls · 2 files changed · 1 helper",
|
||||
"balloon": "All the script tests pass.",
|
||||
@@ -125,6 +132,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"simple": "A job Claude left running reports back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 7 words · task",
|
||||
"balloon": "\"Write README for rename script\" reports: I wrote the README at story-review-c/README.md. It covers the description, requirements, usage with dry-run, real-run and re-run sample output, name clashes, notes and limitations, and exit codes. The script wasn't there when I started, but it appeared while I was writing, so I read it (without running or editing it) and matched the README to it: it agrees with the spec, and I added the exact summary line (Done: N renamed, M skipped.), the -- option for a directory name starting with -, the…",
|
||||
@@ -137,6 +145,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks README.md.",
|
||||
"simple": "Claude checks the work that came back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Read",
|
||||
"balloon": null,
|
||||
@@ -149,6 +158,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude works in the terminal.",
|
||||
"simple": "Claude runs some steps on the computer.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · mkdir -p \"$S/ro\" && printf x > \"$S/ro/q.jpg\" && chmod a-w \"…",
|
||||
"balloon": "The README matches the script.",
|
||||
@@ -165,6 +175,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 21 seconds.",
|
||||
"simple": "Done in 21 seconds. Claude has a question for you.",
|
||||
"unaskedSave": false,
|
||||
"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. Say if you want me to run git init and commit. Only lowercase .jpg is matched, as you asked.",
|
||||
@@ -195,6 +206,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 26 words",
|
||||
"balloon": "Add a --prefix option to rename_by_date.sh that puts a given text in front of the new names, and show me a dry run on the samples.",
|
||||
@@ -207,6 +219,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude reads rename_by_date.sh.",
|
||||
"simple": "Claude reads a file from the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Read",
|
||||
"balloon": null,
|
||||
@@ -219,6 +232,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude changes rename_by_date.sh.",
|
||||
"simple": "Claude changes a file.",
|
||||
"unaskedSave": false,
|
||||
"director": "Edit ×3 · rename_by_date.sh +21 −6",
|
||||
"balloon": null,
|
||||
@@ -231,6 +245,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude does the dry run you asked for: it shows what would happen, changing nothing.",
|
||||
"simple": "Claude does the practice run you asked for; nothing is changed.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · bash -n \"$P/rename_by_date.sh\" && echo \"syntax ok\"",
|
||||
"balloon": "Here's the dry run you asked for on the samples, plus a check that nothing on disk changed:",
|
||||
@@ -247,6 +262,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude changes README.md.",
|
||||
"simple": "Claude changes a file.",
|
||||
"unaskedSave": false,
|
||||
"director": "Edit ×3 · README.md +18 −2",
|
||||
"balloon": "All passing.",
|
||||
@@ -259,6 +275,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 1 minute.",
|
||||
"simple": "Done in about 1 minute. 2 files changed. Claude has a question for you.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 1 min 14 s · 9 tool calls · 2 files changed",
|
||||
"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?",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 41 words",
|
||||
"balloon": "Write mathx.py with a function clamp(value, low, high) that returns value limited to the range low..high and raises ValueError when low > high, then write test_mathx.py with exactly 5 pytest tests for it, and run them with python3 -m pytest -q.",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git status && git log --oneline -5 2>&1; python3… · 1 error",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes the new files mathx.py and test_mathx.py.",
|
||||
"simple": "Claude writes 2 new files.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write ×2 · mathx.py +11 · test_mathx.py +26 · after 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -55,6 +58,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q → ok · 5 passed",
|
||||
"balloon": null,
|
||||
@@ -70,6 +74,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude starts a git repository and saves the work: \"add clamp() with pytest coverage\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": "rule",
|
||||
"director": "git commit -q -F -",
|
||||
"balloon": null,
|
||||
@@ -82,6 +87,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 1 minute.",
|
||||
"simple": "Done in about 1 minute. 2 files changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 1 min 12 s · 6 tool calls · 2 files changed",
|
||||
"balloon": "I wrote clamp and its five tests, and all five pass. I didn't commit CLAUDE.md, .claude/ or .codeman-agent-case.json.",
|
||||
@@ -112,6 +118,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You run /compact.",
|
||||
"simple": "You give Claude a short instruction.",
|
||||
"unaskedSave": false,
|
||||
"director": "Command · /compact",
|
||||
"balloon": "/compact",
|
||||
@@ -124,6 +131,7 @@
|
||||
"kind": "compact",
|
||||
"ok": null,
|
||||
"story": "Claude packs up its notes to free memory.",
|
||||
"simple": "Claude tidies up its notes to free up memory.",
|
||||
"unaskedSave": false,
|
||||
"director": "manual · 46.9k → 3.6k tokens · 24 s",
|
||||
"balloon": null,
|
||||
@@ -136,6 +144,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 24 seconds.",
|
||||
"simple": "Done in 24 seconds. No files changed.",
|
||||
"unaskedSave": false,
|
||||
"director": "local command · 24 s · 0 tool calls",
|
||||
"balloon": "Compacted",
|
||||
@@ -166,6 +175,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 50 words",
|
||||
"balloon": "Call your EnterPlanMode tool to enter plan mode, then write a short 3-step plan for adding a wrap(value, low, high) function (modular wrap into the range) to mathx.py with 3 more pytest tests, and present that plan with your ExitPlanMode tool. After I approve, implement it and run the tests.",
|
||||
@@ -178,6 +188,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude gets ready to plan.",
|
||||
"simple": "Claude gets ready to plan.",
|
||||
"unaskedSave": false,
|
||||
"director": "ToolSearch",
|
||||
"balloon": null,
|
||||
@@ -190,6 +201,7 @@
|
||||
"kind": "plan",
|
||||
"ok": null,
|
||||
"story": "Claude writes a plan: 3 steps.",
|
||||
"simple": "Claude makes a plan with 3 steps.",
|
||||
"unaskedSave": false,
|
||||
"director": "EnterPlanMode · Write · plan file · 3 steps · 1 second opinion along the way",
|
||||
"balloon": null,
|
||||
@@ -202,6 +214,7 @@
|
||||
"kind": "ask",
|
||||
"ok": null,
|
||||
"story": "Claude shows you its plan, and you approve it.",
|
||||
"simple": "Claude shows you its plan, and you approve it.",
|
||||
"unaskedSave": false,
|
||||
"director": "ExitPlanMode · answer: Approved",
|
||||
"balloon": null,
|
||||
@@ -214,6 +227,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude changes mathx.py and test_mathx.py.",
|
||||
"simple": "Claude changes 2 files.",
|
||||
"unaskedSave": false,
|
||||
"director": "Edit ×3 · mathx.py +11 · test_mathx.py +20 −1",
|
||||
"balloon": null,
|
||||
@@ -226,6 +240,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q → ok · 8 passed",
|
||||
"balloon": null,
|
||||
@@ -241,6 +256,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"add wrap() for modular wrapping into a range\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -F -",
|
||||
"balloon": null,
|
||||
@@ -253,6 +269,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 1 minute.",
|
||||
"simple": "Done in about 1 minute. 2 files changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 1 min 7 s · 9 tool calls · 2 files changed",
|
||||
"balloon": "I added wrap() as planned, with 3 new tests. All 8 tests pass and ruff reports no problems.",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 55 words",
|
||||
"balloon": "Build a tiny Python module calc_stats.py with mean(xs) and median(xs), strictly test-first: FIRST write tests/test_calc_stats.py that imports calc_stats, THEN run python3 -m pytest -q once while calc_stats.py does not exist yet (it must error at collection), THEN write calc_stats.py and run python3 -m pytest -q again until it passes. Install nothing, stay in this folder.",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git log --oneline -5 2>&1; git status; python3 --…",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude works in the terminal.",
|
||||
"simple": "Claude runs some steps on the computer.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · python3 -c \"import calc_stats\" 2>&1 | tail -1; ls pytest.in… · 1 second opinion along the way",
|
||||
"balloon": null,
|
||||
@@ -55,6 +58,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes the new file test_calc_stats.py.",
|
||||
"simple": "Claude writes a new file.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write · test_calc_stats.py +55",
|
||||
"balloon": null,
|
||||
@@ -67,6 +71,7 @@
|
||||
"kind": "check",
|
||||
"ok": false,
|
||||
"story": "The tests don't even load, as you asked.",
|
||||
"simple": "The checks can't even start, as you asked.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q → error · 0 passed, 1 error",
|
||||
"balloon": "Now the red run, once, with calc_stats.py confirmed absent:",
|
||||
@@ -83,6 +88,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude writes .gitignore and saves the work: \"add failing tests for calc_stats mean/median\".",
|
||||
"simple": "Claude writes a file, then saves the work.",
|
||||
"unaskedSave": true,
|
||||
"director": "git commit -q -F - · writes 1 file",
|
||||
"balloon": "Red as expected: collection error, exit code 2.",
|
||||
@@ -95,6 +101,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes the new file calc_stats.py.",
|
||||
"simple": "Claude writes a new file.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write · calc_stats.py +24",
|
||||
"balloon": null,
|
||||
@@ -107,6 +114,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q → ok · 12 passed",
|
||||
"balloon": "Green on the first implementation pass.",
|
||||
@@ -122,6 +130,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"implement calc_stats mean and median\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -F - · then 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -134,6 +143,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 2 minutes.",
|
||||
"simple": "Done in about 2 minutes. 3 files changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 2 min 10 s · 10 tool calls · 3 files changed",
|
||||
"balloon": "I built it test-first: the suite failed at collection before calc_stats.py existed, then passed once the module was written. I didn't push, didn't touch origin.git/ and didn't create a README, since none existed to keep in sync.",
|
||||
@@ -164,6 +174,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 95 words",
|
||||
"balloon": "Add mode(xs) to calc_stats.py (ValueError on empty input, ties return the smallest value) in two separate steps without committing in between and without consulting the advisor: first add mode tests to tests/test_calc_stats.py and run python3 -m pytest -q (it should fail because mode is missing), then implement mode in calc_stats.py and run python3 -m pytest -q again until green; only then git co…",
|
||||
@@ -176,6 +187,7 @@
|
||||
"kind": "loop",
|
||||
"ok": true,
|
||||
"story": "The first run fails on purpose, as you asked; the second passes.",
|
||||
"simple": "The first check fails on purpose, as you asked, then passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "2 checks · 3 edits · +40 −1 · last: ok",
|
||||
"balloon": "Green: 18 passed and ruff is clean.",
|
||||
@@ -194,6 +206,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"add calc_stats.mode with smallest-value tie-breaking\" and copies it to origin.git, a folder on this computer, but the version-tag lookup after that fails (there are no tags yet), as you said it might.",
|
||||
"simple": "Claude saves the work, but a small lookup after that fails, as you said it might.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -F - · git push -u origin HEAD",
|
||||
"balloon": null,
|
||||
@@ -206,6 +219,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks what it saved.",
|
||||
"simple": "Claude checks what it saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · git --git-dir=origin.git log --oneline master; git status -…",
|
||||
"balloon": null,
|
||||
@@ -218,6 +232,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 37 seconds.",
|
||||
"simple": "Done in 37 seconds. 2 files changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"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. git describe --tags failed because the repo has no tags.",
|
||||
@@ -248,6 +263,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 66 words",
|
||||
"balloon": "In ONE Bash command, append exactly two lines to NOTES.md with cat >> NOTES.md <<'EOF' (two short lines about mode) EOF and then cat calc_stats.py; next run python3 -m pytest -q; then git add NOTES.md and commit it, and push with exactly this command, run as written with nothing appended: git push && git describe --tags (it is expected to fail after pushing; just report it).",
|
||||
@@ -260,6 +276,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes NOTES.md.",
|
||||
"simple": "Claude writes a file.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · NOTES.md +2",
|
||||
"balloon": null,
|
||||
@@ -272,6 +289,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q → ok · 18 passed",
|
||||
"balloon": null,
|
||||
@@ -287,6 +305,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"add NOTES.md describing mode behavior\" and copies it to origin.git, a folder on this computer, but the version-tag lookup after that fails (there are no tags yet), as you said it might.",
|
||||
"simple": "Claude saves the work, but a small lookup after that fails, as you said it might.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -F - · git push · 1 error",
|
||||
"balloon": null,
|
||||
@@ -299,6 +318,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks what it saved.",
|
||||
"simple": "Claude checks what it saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · git --git-dir=origin.git log --oneline -1 master; git statu…",
|
||||
"balloon": null,
|
||||
@@ -311,6 +331,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 27 seconds.",
|
||||
"simple": "Done in 27 seconds. One file changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 27 s · 5 tool calls · 1 file changed",
|
||||
"balloon": "NOTES.md has the two lines and is committed and pushed. As you expected, git describe --tags then failed because the repo has no tags.",
|
||||
@@ -341,6 +362,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 9 words",
|
||||
"balloon": "Reply with just the word ok, use no tools.",
|
||||
@@ -353,6 +375,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 3 seconds.",
|
||||
"simple": "Done in 3 seconds. No files changed.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 3 s · 0 tool calls",
|
||||
"balloon": "ok",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 67 words",
|
||||
"balloon": "Create a small JavaScript ES module slugify.mjs exporting slugify(str) (lowercase, trim, spaces and punctuation to single hyphens, no leading or trailing hyphens) plus slugify.test.mjs with 4 node:test tests; on purpose make ONE test expectation wrong so the first run of node --test fails, run it, then fix that expectation, rerun until all pass, then git init if needed and git commit both files w…",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git status && git log --oneline -5 2>&1; node --v…",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "loop",
|
||||
"ok": true,
|
||||
"story": "The first run fails on purpose, as you asked; the second passes.",
|
||||
"simple": "The first check fails on purpose, as you asked, then passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "2 checks · 3 edits · +34 · last: ok · after 1 second opinion",
|
||||
"balloon": "All 4 pass.",
|
||||
@@ -61,6 +64,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude works in the terminal.",
|
||||
"simple": "Claude runs some steps on the computer.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · git init -b main && echo \"name=$(git config user.name) emai…",
|
||||
"balloon": null,
|
||||
@@ -76,6 +80,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude starts a git repository and saves the work: \"add slugify module with node:test suite\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -F -",
|
||||
"balloon": null,
|
||||
@@ -88,6 +93,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 1 minute.",
|
||||
"simple": "Done in about 1 minute. 2 files changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 1 min 24 s · 9 tool calls · 2 files changed",
|
||||
"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.",
|
||||
@@ -118,6 +124,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 38 words",
|
||||
"balloon": "Add a fifth node:test test for slugify('') to slugify.test.mjs that on purpose expects 'x' so the first run fails, run node --test, then fix that expectation to the empty string, rerun until all pass, and commit the change.",
|
||||
@@ -130,6 +137,7 @@
|
||||
"kind": "loop",
|
||||
"ok": true,
|
||||
"story": "The first run fails on purpose, as you asked; the second passes.",
|
||||
"simple": "The first check fails on purpose, as you asked, then passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "2 checks · 2 edits · +5 −1 · last: ok",
|
||||
"balloon": "All 5 pass.",
|
||||
@@ -148,6 +156,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"cover empty-string input for slugify\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -F -",
|
||||
"balloon": null,
|
||||
@@ -160,6 +169,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 21 seconds.",
|
||||
"simple": "Done in 21 seconds. One file changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 21 s · 5 tool calls · 1 file changed",
|
||||
"balloon": "I added a fifth test for slugify(''). All 5 tests now pass, and the change is committed.",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 96 words",
|
||||
"balloon": "Build a small Python package inventory/ (a CLI to add, list and remove items kept in a JSON file) split into at least 4 modules (for example storage.py, models.py, commands.py, cli.py), with a pytest suite of at least 25 tests in total; build it one module at a time, and after EACH module write its tests and run pytest, fixing any failures before moving on to the next module; then git init the fo…",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git status 2>&1 | head; git log --oneline 2>&1 |… · 1 error",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude works in the terminal.",
|
||||
"simple": "Claude runs some steps on the computer.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · for t in ruff flake8 pyflakes pycodestyle pylint mypy black…",
|
||||
"balloon": null,
|
||||
@@ -59,6 +62,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes 3 files.",
|
||||
"simple": "Claude writes 3 files.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · pyproject.toml +11 · .gitignore +11 · __init__.py +3 · after 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -71,6 +75,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You add something while Claude works.",
|
||||
"simple": "You add something while Claude works.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 11 words",
|
||||
"balloon": "Also add a --csv option to the list command, with tests.",
|
||||
@@ -83,6 +88,7 @@
|
||||
"kind": "loop",
|
||||
"ok": true,
|
||||
"story": "Build and test, 4 rounds. The tests pass every time; a formatting check fails once, then passes.",
|
||||
"simple": "A formatting check fails at first; Claude fixes it, and everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "5 checks · 10 edits · +694 · last: ok",
|
||||
"balloon": "All 4 modules are done: 63 tests pass and ruff is clean.",
|
||||
@@ -104,6 +110,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes the new file README.md.",
|
||||
"simple": "Claude writes a new file.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write · README.md +79",
|
||||
"balloon": null,
|
||||
@@ -116,6 +123,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude works in the terminal.",
|
||||
"simple": "Claude runs some steps on the computer.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · D=/tmp/claude/-work-cases-story-r2-a/94066c8e-0855-4c6b-a14…",
|
||||
"balloon": "README examples match the real output.",
|
||||
@@ -132,6 +140,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude starts a git repository and saves the work in 6 commits.",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -m \"chore: scaffold inventory package\" -m \"Ad…",
|
||||
"balloon": "Commits are in.",
|
||||
@@ -144,6 +153,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q -p no:cacheprovider 2>&1 → ok · 63 passed · then 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -159,6 +169,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 6 minutes.",
|
||||
"simple": "Done in about 6 minutes. 13 files changed. The checks pass. The work is saved. Claude says a problem is left: read its message.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 6 min 25 s · 24 tool calls · 13 files changed",
|
||||
"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).",
|
||||
@@ -189,6 +200,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 188 words",
|
||||
"balloon": "Now do these 15 small steps strictly one at a time, each as its own separate tool call, in exactly this order, never combining two steps into one call and without extra steps in between: 1) Read inventory/models.py with the Read tool; 2) with the Edit tool add the line 'Version 0.1.0.' at the end of README.md; 3) run 'python3 -m pytest -q' in Bash; 4) run 'uname -s' in Bash; 5) run 'git add -A &&…",
|
||||
@@ -201,6 +213,7 @@
|
||||
"kind": "montage",
|
||||
"ok": null,
|
||||
"story": "Claude gets started: reads 1 file, runs 2 commands, changes files, runs checks, saves the work.",
|
||||
"simple": "Claude gets started: it changes files, checks the work, saves the work, and more.",
|
||||
"unaskedSave": false,
|
||||
"director": "Read · Edit · Bash ×4",
|
||||
"balloon": "Step 1:",
|
||||
@@ -213,6 +226,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You add something while Claude works.",
|
||||
"simple": "You add something while Claude works.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 15 words",
|
||||
"balloon": "Also, as a 16th step at the very end, run git log --oneline in Bash.",
|
||||
@@ -225,6 +239,7 @@
|
||||
"kind": "montage",
|
||||
"ok": null,
|
||||
"story": "Claude keeps going: runs 1 command, changes files, runs checks.",
|
||||
"simple": "Claude keeps going: it changes files, checks the work and reads files.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write · Bash ×2",
|
||||
"balloon": "Noted, I'll add step 16 at the end.",
|
||||
@@ -237,6 +252,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"changelog\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -m \"docs: changelog\" -m \"Co-Authored-By: Claude…",
|
||||
"balloon": "Step 10:",
|
||||
@@ -249,6 +265,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude reads cli.py.",
|
||||
"simple": "Claude reads a file from the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Read",
|
||||
"balloon": "Step 11:",
|
||||
@@ -261,6 +278,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude changes CHANGELOG.md.",
|
||||
"simple": "Claude changes a file.",
|
||||
"unaskedSave": false,
|
||||
"director": "Edit · CHANGELOG.md +2",
|
||||
"balloon": "Step 12:",
|
||||
@@ -273,6 +291,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q → ok · 63 passed",
|
||||
"balloon": "Step 13:",
|
||||
@@ -288,6 +307,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude works in the terminal.",
|
||||
"simple": "Claude runs some steps on the computer.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · uname -m",
|
||||
"balloon": "Step 14:",
|
||||
@@ -302,6 +322,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"changelog entry\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -am \"docs: changelog entry\" -m \"Co-Authored-By:…",
|
||||
"balloon": "Step 15:",
|
||||
@@ -314,6 +335,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks what it saved.",
|
||||
"simple": "Claude checks what it saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · git log --oneline",
|
||||
"balloon": "Step 16:",
|
||||
@@ -326,6 +348,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 1 minute.",
|
||||
"simple": "Done in about 1 minute. 2 files changed. The checks pass. The work is saved. Claude left you a note: read its message.",
|
||||
"unaskedSave": false,
|
||||
"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. 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.",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 52 words",
|
||||
"balloon": "Build a small Python command-line tool wcount.py in this folder that prints the line, word and character counts of the files given as arguments, like wc, with a --json flag that prints the counts as JSON, plus pytest tests in test_wcount.py; run pytest and fix anything that fails until all tests pass.",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git log --oneline -5 2>&1; git branch -a; python3… · 1 error",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "loop",
|
||||
"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.",
|
||||
"simple": "Some checks fail at first; Claude fixes them until all pass. It also saves the work.",
|
||||
"unaskedSave": true,
|
||||
"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.",
|
||||
@@ -62,6 +65,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude writes .gitignore and saves the work: \"ignore Python and pytest caches\".",
|
||||
"simple": "Claude writes a file, then saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -m \"chore: ignore Python and pytest caches · writes 1 file",
|
||||
"balloon": null,
|
||||
@@ -74,6 +78,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude works in the terminal.",
|
||||
"simple": "Claude runs some steps on the computer.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · python3 wcount.py nope nope2; echo \"exit=$?\"; wc nope nope2… · after 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -90,6 +95,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 3 minutes.",
|
||||
"simple": "Done in about 3 minutes. 3 files changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 3 min 13 s · 11 tool calls · 3 files changed",
|
||||
"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.",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 121 words",
|
||||
"balloon": "Build a tiny Python command-line tool in this folder that turns text into URL slugs. Before doing anything else, ask me with your AskUserQuestion tool whether the tool should be named slugify or kebab (offer exactly those two options). After my answer, first write me your plan as a short numbered list, then write the pytest tests FIRST (at least 8 tests in tests/), run them with python3 -m pytest…",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "ask",
|
||||
"ok": null,
|
||||
"story": "Claude asks you, and you answer.",
|
||||
"simple": "Claude asks you a question, and you answer.",
|
||||
"unaskedSave": false,
|
||||
"director": "AskUserQuestion · answer: kebab",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git status 2>&1 | head -5; git rev-parse --show-t…",
|
||||
"balloon": null,
|
||||
@@ -55,6 +58,7 @@
|
||||
"kind": "loop",
|
||||
"ok": true,
|
||||
"story": "At first the tests don't even load, as you asked; the second run passes.",
|
||||
"simple": "At first the checks can't even start, as you asked; then they pass.",
|
||||
"unaskedSave": false,
|
||||
"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.",
|
||||
@@ -73,6 +77,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes README.md and .gitignore and changes CLAUDE.md.",
|
||||
"simple": "Claude writes 2 files and changes a file.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write ×2 · Edit · README.md +54 · .gitignore +11 · CLAUDE.md +5 −1",
|
||||
"balloon": null,
|
||||
@@ -85,6 +90,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude works in the terminal.",
|
||||
"simple": "Claude runs some steps on the computer.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×3 · python3 src/kebab.py \"Hello, World!\" && python3 src/kebab.p…",
|
||||
"balloon": "Now I'll run each documented README command by hand and check the git identity:",
|
||||
@@ -101,6 +107,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude starts a git repository, saves the work: \"add kebab CLI that turns text into URL slugs\" and copies it to remote.git, a folder on this computer, but a check after that fails; Claude fixes it, and the check passes next.",
|
||||
"simple": "Claude saves the work, but a check after that fails; Claude fixes it.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -F - · git push -u origin main · 1 error",
|
||||
"balloon": null,
|
||||
@@ -113,6 +120,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass in a fresh copy made from remote.git.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "python3 -m pytest -q -p no:cacheprovider) → ok · 19 passed · then 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -128,6 +136,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 5 minutes.",
|
||||
"simple": "Done in about 5 minutes. 6 files changed. The checks pass. The work is saved. Claude left you a note: read its message.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 4 min 57 s · 18 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. 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.",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 77 words",
|
||||
"balloon": "Create a tiny node project here: package.json (type module) and index.js exporting two functions add(a,b) and slugify(s). Then start TWO helpers with the Agent tool IN THE BACKGROUND (run_in_background: true): one writes README.md describing the project, the other writes CONTRIBUTING.md. While they work, write index.test.mjs using node:test with a few tests for both functions and run it with node…",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git log --oneline -3 2>&1; node --version · 1 error",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude starts a git repository, writes package.json and index.js, and saves the work: \"add package.json and index.js with add and slugify\".",
|
||||
"simple": "Claude writes 2 files, then saves the work.",
|
||||
"unaskedSave": "rule",
|
||||
"director": "git commit -q -m \"feat: add package.json and index.js with… · writes 2 files · after 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -55,6 +58,7 @@
|
||||
"kind": "backup",
|
||||
"ok": null,
|
||||
"story": "Claude sends 2 helpers off to work in the background.",
|
||||
"simple": "Claude sends 2 helpers off to work while it carries on.",
|
||||
"unaskedSave": false,
|
||||
"director": "Agent ×2 · general-purpose",
|
||||
"balloon": null,
|
||||
@@ -67,6 +71,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "Claude writes index.test.mjs; the tests pass.",
|
||||
"simple": "Claude writes a file, then checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "node --test index.test.mjs → ok · 8 passed · writes 1 file",
|
||||
"balloon": null,
|
||||
@@ -82,6 +87,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"add node:test suite for add and slugify\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -m \"test: add node:test suite for add and slu…",
|
||||
"balloon": null,
|
||||
@@ -94,6 +100,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Claude's part is done in 2 minutes. Its 2 helpers finished later and reported back.",
|
||||
"simple": "Claude's part took about 2 minutes; its helpers finished later. 3 files changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "stop_hook_summary · 1 min 44 s · 7 tool calls · 3 files changed · 2 helpers",
|
||||
"balloon": "I've set up the project, started both helpers, and the tests pass: node --test index.test.mjs ran 8 tests, 8 passed, 0 failed. No linter is set up in this project, so I didn't run one.",
|
||||
@@ -124,6 +131,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"simple": "A job Claude left running reports back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 26 words · task",
|
||||
"balloon": "contributing-writer: I wrote /work/cases/story-r2-b/CONTRIBUTING.md covering setup, tests, code style, commits, README sync and PRs; it isn't committed because git was off-limits, and index.test.mjs and README.md, which…",
|
||||
@@ -136,6 +144,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks CONTRIBUTING.md.",
|
||||
"simple": "Claude checks the work that came back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · ls -la && git status --short && cat CONTRIBUTING.md",
|
||||
"balloon": null,
|
||||
@@ -148,6 +157,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"add CONTRIBUTING.md\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -m \"docs: add CONTRIBUTING.md",
|
||||
"balloon": null,
|
||||
@@ -160,6 +170,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 15 seconds.",
|
||||
"simple": "Done in 15 seconds. The work is saved. Claude left you a note: read its message.",
|
||||
"unaskedSave": false,
|
||||
"director": "stop_hook_summary · 15 s · 2 tool calls",
|
||||
"balloon": "The CONTRIBUTING.md helper has finished, and I've committed its file (docs: add CONTRIBUTING.md). The helper reported that index.test.mjs and README.md didn't exist yet. That's out of date: it must have looked before I wrote the tests, and both files are there now. README.md is already on disk, but its helper hasn't reported back yet, so I haven't committed it in case it's still being written.",
|
||||
@@ -190,6 +201,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"simple": "A job Claude left running reports back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 6 words · task",
|
||||
"balloon": "readme-writer: README done, test file missing",
|
||||
@@ -202,6 +214,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks README.md.",
|
||||
"simple": "Claude checks the work that came back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · cat README.md && ls index.test.mjs",
|
||||
"balloon": null,
|
||||
@@ -214,6 +227,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass, and Claude saves the work.",
|
||||
"simple": "Claude checks the work: everything passes. Then it saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "npm test 2>&1 → ok · 8 passed · 1 commit",
|
||||
"balloon": null,
|
||||
@@ -229,6 +243,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 16 seconds.",
|
||||
"simple": "Done in 16 seconds. The checks pass. The work is saved. Claude has a question for you.",
|
||||
"unaskedSave": false,
|
||||
"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. Say if you want CLAUDE.md committed too.",
|
||||
@@ -259,6 +274,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 56 words",
|
||||
"balloon": "Big refactor: split index.js into three modules under lib/ (lib/add.js, lib/slugify.js, lib/util.js with shared helpers), keep index.js as a re-export barrel, add full JSDoc to every function, add at least 15 more node:test tests in separate files under test/, update README and CONTRIBUTING to match, run the tests after each step and commit each step separately.",
|
||||
@@ -271,6 +287,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "Claude writes 4 files; the tests pass.",
|
||||
"simple": "Claude writes 4 files, then checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "npm test 2>&1 → ok · 8 passed · writes 4 files · after 1 second opinion",
|
||||
"balloon": "All 8 tests pass, the README examples give the same outputs, and the barrel exports only add and slugify.",
|
||||
@@ -286,6 +303,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"split index.js into lib/ modules behind an index.js barrel\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -m \"refactor: split index.js into lib/ module…",
|
||||
"balloon": null,
|
||||
@@ -298,6 +316,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "Claude changes 4 files; the tests pass.",
|
||||
"simple": "Claude changes 4 files, then checks the work: everything passes.",
|
||||
"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.",
|
||||
@@ -313,6 +332,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude saves the work: \"add full JSDoc to every function and module\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -m \"docs: add full JSDoc to every function an…",
|
||||
"balloon": null,
|
||||
@@ -325,6 +345,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "You stop Claude here, during its step 3 (new tests under test/). Saved before the stop: 2 commits.",
|
||||
"simple": "You stopped Claude after about 3 minutes. 4 files changed. The checks pass. The work is saved.",
|
||||
"unaskedSave": false,
|
||||
"director": "interrupted · 2 min 41 s · 4 tool calls · 4 files changed",
|
||||
"balloon": "Step 3: new tests under test/.",
|
||||
@@ -355,6 +376,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 23 words",
|
||||
"balloon": "Probe turn, no other work: run the shell command sleep 65 with Bash (foreground, not background), then reply with just the word done.",
|
||||
@@ -367,6 +389,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude tries a command, and it is blocked.",
|
||||
"simple": "Claude tries a step, but it is not allowed.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · sleep 65 · 1 error",
|
||||
"balloon": null,
|
||||
@@ -381,6 +404,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Nothing Claude tried worked, so the job is not done.",
|
||||
"simple": "Nothing Claude tried worked, so the job is not done. No files changed.",
|
||||
"unaskedSave": false,
|
||||
"director": "stop_hook_summary · 6 s · 1 tool call",
|
||||
"balloon": "Not done. The harness blocked sleep 65: a sleep on its own in the foreground isn't allowed. I didn't try to get around the block, for example by chaining shorter sleeps or running it in the background.",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 81 words",
|
||||
"balloon": "First reply with a numbered plan of exactly 4 steps (as text, not a tool). Then build csvstat.py, a Python CLI that prints row count and per-column min/max/mean of a CSV, with pytest tests. Do a dry run on a sample CSV and save its log with `| tee /tmp/csvstat-dryrun.log`. Start ONE helper with the Agent tool, run_in_background: true and WITHOUT a name parameter, to write README.md; do not wait f…",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "plan",
|
||||
"ok": null,
|
||||
"story": "Claude writes a plan: 4 steps.",
|
||||
"simple": "Claude makes a plan with 4 steps.",
|
||||
"unaskedSave": false,
|
||||
"director": "written as text · 4 steps",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude looks around the project.",
|
||||
"simple": "Claude looks around the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git status && git branch -a && git log --oneline… · 1 error",
|
||||
"balloon": null,
|
||||
@@ -55,6 +58,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes 4 new files.",
|
||||
"simple": "Claude writes 4 new files.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write ×4 · csvstat.py +115 · sample.csv +6 · test_csvstat.py +129 · after 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -67,6 +71,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "Claude writes pyproject.toml and changes test_csvstat.py; the tests pass, then Claude starts a git repository and saves the work on a new branch (feat/csvstat).",
|
||||
"simple": "Claude changes 2 files, then checks the work: everything passes. Then it saves the work.",
|
||||
"unaskedSave": true,
|
||||
"director": "python3 -m pytest -q 2>&1 → ok ×2 · 17 passed · writes 2 files · 1 commit",
|
||||
"balloon": "Code and tests are committed.",
|
||||
@@ -82,6 +87,7 @@
|
||||
"kind": "backup",
|
||||
"ok": null,
|
||||
"story": "Claude sends 1 helper off to work in the background.",
|
||||
"simple": "Claude sends a helper off to work while it carries on.",
|
||||
"unaskedSave": false,
|
||||
"director": "Agent · general-purpose · 7 calls inside",
|
||||
"balloon": null,
|
||||
@@ -94,6 +100,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude does the dry run you asked for: it shows what would happen, changing nothing.",
|
||||
"simple": "Claude does the practice run you asked for; nothing is changed.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · set -o pipefail && python3 csvstat.py sample.csv | tee /tmp…",
|
||||
"balloon": null,
|
||||
@@ -110,6 +117,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Claude's part is done in 3 minutes. Its helper finished later and reported back.",
|
||||
"simple": "Claude's part took about 3 minutes; its helper finished later. 5 files changed. The checks pass. The work is saved. Claude says a problem is left: read its message. Claude has a question for you.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 2 min 44 s · 10 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. 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.",
|
||||
@@ -140,6 +148,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"simple": "A job Claude left running reports back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 6 words · task",
|
||||
"balloon": "\"Write README for csvstat\" reports: I wrote README.md and committed it on its own on feat/csvstat. … The README has every section you asked for and no badges.",
|
||||
@@ -152,6 +161,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude checks the work that came back.",
|
||||
"simple": "Claude checks the work that came back.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · git log --oneline && git status --short && git show --stat…",
|
||||
"balloon": null,
|
||||
@@ -164,6 +174,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 9 seconds.",
|
||||
"simple": "Done in 9 seconds. Claude says a problem is left: read its message. Claude has a question for you.",
|
||||
"unaskedSave": false,
|
||||
"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. 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.",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"kind": "mission",
|
||||
"ok": null,
|
||||
"story": "You give Claude a job.",
|
||||
"simple": "You give Claude a job.",
|
||||
"unaskedSave": false,
|
||||
"director": "Prompt · 56 words",
|
||||
"balloon": "Build a tiny single-page pomodoro timer in this folder: index.html with inline CSS, timer.js exporting the timer logic as plain functions, and timer.test.mjs using node:test that covers start, pause and reset; run the tests with node --test until they pass, then git init and commit everything with a short message, with no remote and no push.",
|
||||
@@ -31,6 +32,7 @@
|
||||
"kind": "investigate",
|
||||
"ok": null,
|
||||
"story": "Claude reads CLAUDE.md.",
|
||||
"simple": "Claude reads a file from the project.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash ×2 · ls -la && git status 2>&1 | head; git log --oneline 2>&1 |…",
|
||||
"balloon": null,
|
||||
@@ -43,6 +45,7 @@
|
||||
"kind": "run",
|
||||
"ok": null,
|
||||
"story": "Claude looks a few things up; the last one finds a git setting that is not set, which is no error.",
|
||||
"simple": "Claude looks something up and finds nothing, which is fine.",
|
||||
"unaskedSave": false,
|
||||
"director": "Bash · grep -oE '\"(permissions|env|allow|deny)\"|[A-Za-z_]*(TOKEN|K… · 1 error",
|
||||
"balloon": null,
|
||||
@@ -58,6 +61,7 @@
|
||||
"kind": "fix",
|
||||
"ok": null,
|
||||
"story": "Claude writes 3 new files.",
|
||||
"simple": "Claude writes 3 new files.",
|
||||
"unaskedSave": false,
|
||||
"director": "Write ×3 · timer.js +42 · timer.test.mjs +74 · index.html +120 · after 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -70,6 +74,7 @@
|
||||
"kind": "check",
|
||||
"ok": true,
|
||||
"story": "The tests pass.",
|
||||
"simple": "Claude checks the work: everything passes.",
|
||||
"unaskedSave": false,
|
||||
"director": "node --test 2>&1 → ok · 7 passed",
|
||||
"balloon": "All 7 tests pass, with no module-type warning.",
|
||||
@@ -85,6 +90,7 @@
|
||||
"kind": "save",
|
||||
"ok": true,
|
||||
"story": "Claude starts a git repository and saves the work: \"add pomodoro timer with tests\".",
|
||||
"simple": "Claude saves the work.",
|
||||
"unaskedSave": false,
|
||||
"director": "git commit -q -m \"feat: add pomodoro timer with tests\" -m \"… · then 1 second opinion",
|
||||
"balloon": null,
|
||||
@@ -97,6 +103,7 @@
|
||||
"kind": "complete",
|
||||
"ok": null,
|
||||
"story": "Done in 3 minutes.",
|
||||
"simple": "Done in about 3 minutes. 3 files changed. The checks pass. The work is saved. Claude says a problem is left: read its message.",
|
||||
"unaskedSave": false,
|
||||
"director": "turn_duration · 2 min 39 s · 8 tool calls · 3 files changed",
|
||||
"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.",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildStoryPage, buildStoryTurns, createStoryReducer } from '../src/story-mode.js';
|
||||
import { plainWordsProblem } from './story-plain-words.js';
|
||||
|
||||
const DIR = join(import.meta.dirname, 'fixtures', 'story-corpus');
|
||||
const NAMES = readdirSync(DIR)
|
||||
@@ -46,6 +47,7 @@ function pagesOf(name: string) {
|
||||
kind: x.kind,
|
||||
ok: x.ok ?? null,
|
||||
story: x.caption.story,
|
||||
simple: x.caption.simple,
|
||||
unaskedSave: x.unaskedSave ?? false,
|
||||
director: x.caption.director,
|
||||
balloon: x.balloon?.text ?? null,
|
||||
@@ -63,6 +65,13 @@ describe('Story Mode on real sessions', () => {
|
||||
expect(NAMES.length).toBeGreaterThanOrEqual(11);
|
||||
});
|
||||
|
||||
// The Simple level is for a reader who has never seen a terminal: every caption of every page.
|
||||
it.each(NAMES)('%s reads plainly at the Simple level', (name) => {
|
||||
for (const [t, page] of pagesOf(name).entries())
|
||||
for (const [i, panel] of page.panels.entries())
|
||||
expect(plainWordsProblem(panel.simple), `turn ${t + 1}, panel ${i + 1}: ${panel.simple}`).toBeNull();
|
||||
});
|
||||
|
||||
it.each(NAMES)('%s draws as it did when it was checked by hand', (name) => {
|
||||
const pages = pagesOf(name);
|
||||
const file = join(DIR, `${name}.expected.json`);
|
||||
|
||||
+141
-1
@@ -24,6 +24,7 @@ import {
|
||||
type StoryEntry,
|
||||
} from '../src/story-mode.js';
|
||||
import { bash, demoTurn, edit, end, prompt, read, resetClock, result, said, tool } from './story-fixtures.js';
|
||||
import { plainWordsProblem } from './story-plain-words.js';
|
||||
|
||||
function entriesOf(lines: unknown[]): StoryEntry[] {
|
||||
const reducer = createStoryReducer();
|
||||
@@ -600,7 +601,11 @@ describe('buildStoryTurns', () => {
|
||||
expect(turns).toHaveLength(2);
|
||||
const page = buildStoryPage(turns[1]);
|
||||
expect(page.panels.map((p) => p.kind)).toEqual(['mission', 'compact', 'complete']);
|
||||
expect(page.panels[0].caption).toEqual({ story: 'You run /compact.', director: 'Command · /compact' });
|
||||
expect(page.panels[0].caption).toEqual({
|
||||
story: 'You run /compact.',
|
||||
director: 'Command · /compact',
|
||||
simple: 'You give Claude a short instruction.',
|
||||
});
|
||||
expect(page.complete).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1507,6 +1512,7 @@ describe('buildStoryPage', () => {
|
||||
expect(page.panels[3].caption).toEqual({
|
||||
story: "Claude's memory is full: it packs up its notes.",
|
||||
director: 'automatic · 46.9k → 3.6k tokens · 24 s',
|
||||
simple: "Claude's memory is full, so it tidies up its notes.",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1858,3 +1864,137 @@ describe('buildStoryPage', () => {
|
||||
expect(page.origin).toBe('task');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the Simple reading level', () => {
|
||||
/** The final panel's Simple caption, every other panel's checked for plain words on the way. */
|
||||
const ending = (lines: unknown[], turn = -1) => {
|
||||
const turns = buildStoryTurns(entriesOf(lines));
|
||||
const page = buildStoryPage(turns.at(turn)!);
|
||||
for (const p of page.panels) expect(plainWordsProblem(p.caption.simple), p.caption.simple).toBeNull();
|
||||
return page.panels.at(-1)!.caption.simple;
|
||||
};
|
||||
|
||||
it('the guard itself catches what a reader who never saw a terminal stops at', () => {
|
||||
for (const bad of [
|
||||
'Claude changes app.py.',
|
||||
'Claude writes .gitignore.',
|
||||
'Claude runs npm test.',
|
||||
'Claude uses Bash.',
|
||||
'Claude saves the work in 2 commits.',
|
||||
'Claude adds a --json option.',
|
||||
'Claude reads src/app.ts.',
|
||||
'Claude starts a git repository.',
|
||||
'Claude works in the terminal.',
|
||||
'The tests pass.',
|
||||
])
|
||||
expect(plainWordsProblem(bad), bad).not.toBeNull();
|
||||
expect(plainWordsProblem('Done in 3 minutes. 2 files changed. The checks pass. The work is saved.')).toBeNull();
|
||||
});
|
||||
|
||||
it('the demo turn, one plain sentence per panel and a summary at the end', () => {
|
||||
const page = pageOf(demoTurn());
|
||||
expect(page.panels.map((p) => p.caption.simple)).toEqual([
|
||||
'You give Claude a job.',
|
||||
'Claude reads two files from the project.',
|
||||
'Claude makes a plan with 2 steps.',
|
||||
'Claude calls in a helper.',
|
||||
'The first check fails; Claude fixes it, and the second passes.',
|
||||
'Claude saves the work.',
|
||||
'Done in about 7 minutes. One file changed. The checks pass. The work is saved.',
|
||||
]);
|
||||
for (const p of page.panels) expect(plainWordsProblem(p.caption.simple), p.caption.simple).toBeNull();
|
||||
});
|
||||
|
||||
it('a failed check never reads as a pass: on its panel and in the summary', () => {
|
||||
const lines = [
|
||||
prompt('fix the total'),
|
||||
...edit('e1', '/w/cart.js', ['+ return sum;']),
|
||||
...bash('b1', 'npm test', 'Run tests', ' Tests 3 failed | 39 passed (42)', true),
|
||||
said('Three tests still fail.'),
|
||||
end(4000),
|
||||
];
|
||||
const page = pageOf(lines);
|
||||
expect(page.panels.find((p) => p.kind === 'check')!.caption.simple).toBe('Claude checks the work: 3 checks fail.');
|
||||
expect(ending(lines)).toBe('Done in 4 seconds. One file changed. A check still fails.');
|
||||
// A failure you asked for (test first) is said as such, until a run has passed.
|
||||
const asked = [
|
||||
prompt('write the tests first; they should fail because add() is missing'),
|
||||
...edit('e1', '/w/test_add.py', ['+def test_add(): assert add(1, 2) == 3']),
|
||||
...bash('b1', 'pytest -q', 'Run tests', '1 failed in 0.1s', true),
|
||||
end(2000),
|
||||
];
|
||||
expect(pageOf(asked).panels.find((p) => p.kind === 'check')!.caption.simple).toBe(
|
||||
'Claude checks the work: one check fails, as you asked.'
|
||||
);
|
||||
expect(ending(asked)).toBe('Done in 2 seconds. One file changed. The checks fail, as you asked.');
|
||||
});
|
||||
|
||||
it('a catch, a note and a question each reach the summary in plain words', () => {
|
||||
expect(ending([prompt('go'), said('Done. One known issue: big numbers are shown rounded.'), end(1000)])).toBe(
|
||||
'Done in 1 second. No files changed. Claude says a problem is left: read its message.'
|
||||
);
|
||||
expect(ending([prompt('go'), said('Done. The README update is left out for now.'), end(1000)])).toBe(
|
||||
'Done in 1 second. No files changed. Claude left you a note: read its message.'
|
||||
);
|
||||
expect(ending([prompt('go'), said('I built it. Want me to add a --json flag?'), end(1000)])).toBe(
|
||||
'Done in 1 second. No files changed. Claude has a question for you.'
|
||||
);
|
||||
});
|
||||
|
||||
it('stopped, not done, to be continued, done for now: an honest ending each', () => {
|
||||
const stopped = ending([
|
||||
prompt('three things'),
|
||||
tool('t1', 'TodoWrite', {
|
||||
todos: [
|
||||
{ content: 'Split the module', status: 'completed' },
|
||||
{ content: 'Write the new tests', status: 'in_progress' },
|
||||
],
|
||||
}),
|
||||
result('t1', 'Todos have been modified successfully.'),
|
||||
...edit('e1', '/w/a.js', ['+x']),
|
||||
prompt('[Request interrupted by user]'),
|
||||
]);
|
||||
expect(stopped).toMatch(/^You stopped Claude (after \d+ seconds|partway)\. One file changed\./);
|
||||
expect(stopped).toContain('Some of its plan is not done yet.');
|
||||
// Every step failed (blocked): not done, and never a pass.
|
||||
expect(
|
||||
ending([
|
||||
prompt('sleep'),
|
||||
...bash('b1', 'sleep 65', 'Wait', 'Blocked by a PreToolUse hook: denied', true),
|
||||
end(2000),
|
||||
])
|
||||
).toBe('Nothing Claude tried worked, so the job is not done. No files changed.');
|
||||
// A turn the next prompt cut off (no end of its own).
|
||||
expect(ending([prompt('one'), ...bash('b1', 'ls', 'List', 'a'), prompt('two'), said('ok'), end(1000)], 0)).toBe(
|
||||
'The work stops here; the next message starts something new. No files changed.'
|
||||
);
|
||||
// A helper still out when the turn ended.
|
||||
expect(
|
||||
ending([
|
||||
prompt('docs'),
|
||||
tool('a1', 'Agent', { description: 'Write README', subagent_type: 'general-purpose', prompt: 'Write it' }),
|
||||
result('a1', 'Async agent launched successfully. agentId: h9', {
|
||||
toolUseResult: { isAsync: true, status: 'async_launched', agentId: 'h9' },
|
||||
}),
|
||||
end(3000),
|
||||
])
|
||||
).toBe("Claude's part took 3 seconds, but a helper is still working. No files changed.");
|
||||
});
|
||||
|
||||
it('saved, partly saved, not saved: only where saving is in play', () => {
|
||||
const commit = (id: string) =>
|
||||
bash(id, 'git add -A && git commit -qm "feat: total"', 'Commit', '[main 1a2b3c4] feat: total');
|
||||
expect(ending([prompt('fix it and commit'), ...edit('e1', '/w/a.js', ['+x']), ...commit('c1'), end(1000)])).toBe(
|
||||
'Done in 1 second. One file changed. Claude did not try out its changes. The work is saved.'
|
||||
);
|
||||
expect(
|
||||
ending([prompt('fix it and commit'), ...commit('c1'), ...edit('e1', '/w/a.js', ['+x']), end(1000)])
|
||||
).toContain('Some of the work is saved, but the latest changes are not.');
|
||||
// You asked for a commit and none came: the reader is told.
|
||||
expect(ending([prompt('fix it and commit'), ...edit('e1', '/w/a.js', ['+x']), end(1000)])).toContain(
|
||||
'The work is not saved yet.'
|
||||
);
|
||||
// "No git": files written are simply there, nothing is "not saved".
|
||||
expect(ending([prompt('fix it, no git'), ...edit('e1', '/w/a.js', ['+x']), end(1000)])).not.toMatch(/saved/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @fileoverview The Simple reading level's promise, as a check: a caption a reader who has never seen a
|
||||
* terminal can read. No path, code or file name, no tool name, no flag and no developer jargon. Used by
|
||||
* test/story-mode.test.ts and test/story-corpus.test.ts (helper only, no tests of its own).
|
||||
*/
|
||||
|
||||
/** Claude Code's tool names: matched case-sensitively, so "Claude reads" is fine and "Read" is not. */
|
||||
const TOOL_NAMES = [
|
||||
'Bash',
|
||||
'BashOutput',
|
||||
'KillShell',
|
||||
'Edit',
|
||||
'MultiEdit',
|
||||
'Write',
|
||||
'Read',
|
||||
'Grep',
|
||||
'Glob',
|
||||
'LS',
|
||||
'Task',
|
||||
'Agent',
|
||||
'WebFetch',
|
||||
'WebSearch',
|
||||
'TodoWrite',
|
||||
'NotebookEdit',
|
||||
'NotebookRead',
|
||||
'EnterPlanMode',
|
||||
'ExitPlanMode',
|
||||
'ToolSearch',
|
||||
'AskUserQuestion',
|
||||
'SlashCommand',
|
||||
'SendMessage',
|
||||
'Skill',
|
||||
'Advisor',
|
||||
];
|
||||
|
||||
/** Words a developer reads past and a parent stops at. Whole words, any case. */
|
||||
const JARGON = [
|
||||
'test suite',
|
||||
'tests?',
|
||||
'lint(?:s|er|ers|ing)?',
|
||||
'commits?',
|
||||
'committed',
|
||||
'repo(?:s|sitory|sitories)?',
|
||||
'branch(?:es)?',
|
||||
'diffs?',
|
||||
'API',
|
||||
'dependency',
|
||||
'dependencies',
|
||||
'builds?',
|
||||
'deploy(?:s|ed|ment)?',
|
||||
'terminal',
|
||||
'commands?',
|
||||
'scripts?',
|
||||
'code',
|
||||
'git',
|
||||
'push(?:es|ed)?',
|
||||
'pull request',
|
||||
'merge[sd]?',
|
||||
'compiles?',
|
||||
'server',
|
||||
'functions?',
|
||||
'JSON',
|
||||
'CLI',
|
||||
'shell',
|
||||
'flags?',
|
||||
'tool calls?',
|
||||
'exit code',
|
||||
'stdout',
|
||||
'stderr',
|
||||
'prompts?',
|
||||
'sessions?',
|
||||
'background',
|
||||
'mode',
|
||||
'context',
|
||||
'tokens?',
|
||||
'refactor\\w*',
|
||||
'modules?',
|
||||
'packages?',
|
||||
'install\\w*',
|
||||
'npm',
|
||||
'pytest',
|
||||
'node',
|
||||
];
|
||||
const JARGON_RE = new RegExp(`\\b(?:${JARGON.join('|')})\\b`, 'i');
|
||||
const TOOL_RE = new RegExp(`\\b(?:${TOOL_NAMES.join('|')})\\b`);
|
||||
/** A file name or extension (`a.py`, `README.md`, `.gitignore`): in prose a full stop is followed by a space. */
|
||||
const FILE_RE = /\S*\.[A-Za-z0-9]\S*/;
|
||||
/** A command-line flag: `-q`, `--json`. */
|
||||
const FLAG_RE = /(?:^|\s)-{1,2}[A-Za-z]/;
|
||||
|
||||
/** What makes `text` unfit for the Simple level (the offending part), or null when it reads plainly. */
|
||||
export function plainWordsProblem(text: string): string | null {
|
||||
if (!text.trim()) return '(empty)';
|
||||
for (const ch of ['/', '\\', '`', '|', '{', '}', '<', '>', '=', '_', '#', '$', '~', '*'])
|
||||
if (text.includes(ch)) return ch;
|
||||
for (const re of [FILE_RE, FLAG_RE, TOOL_RE, JARGON_RE]) {
|
||||
const m = re.exec(text);
|
||||
if (m) return m[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in new issue
Block a user