Skip to content

test(prose): bugfix corpus — the investigation-centric surfaces#548

Open
leeovery wants to merge 11 commits into
prose-tests/feature-corpusfrom
prose-tests/bugfix-corpus
Open

test(prose): bugfix corpus — the investigation-centric surfaces#548
leeovery wants to merge 11 commits into
prose-tests/feature-corpusfrom
prose-tests/bugfix-corpus

Conversation

@leeovery

@leeovery leeovery commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • Three crash-fix worlds (created / investigating / investigated) and four cases covering only what bugfix does differently from the linear pipeline — the delivery phases it shares with feature are already covered and deliberately not repeated: continue-bugfix routing a fresh bug to investigation, investigation entry seeding from the carrier without re-asking, the root-cause validation agent's full lifecycle (dispatch → scan → incorporate → verdict routing), and a specification whose source is the investigation rather than a discussion.
  • Two framework extensions the corpus demanded, both small and reusable across the remaining work types:
    • a free-text ### stub section — prose that dispatches a background agent is walked with the walker playing the agent (writing its report at the path the engine's dispatch allocated, returning the stated result) rather than spending a real sub-agent analysing fixture content. What the case tests is the lifecycle around the agent; the agent's own judgment is not under test.
    • a json {file} {dot.path} {value} state assertion, so the agent row's closing status is verified deterministically in the cache store instead of being read out of a transcript by a grader.
  • The bugfix-investigating world exists precisely at the pre-validation moment, so the lifecycle case starts from a clean agent store.

Test plan

  • npm test green: 1692 tests, 0 fail — corpus validation (now covering the stub section and json grammar) plus all nine fixtures rebuilding byte-identical (~25s).
  • The new machinery was proven before the cases shipped: built a live bugfix-investigating world, hand-walked the validation lifecycle exactly as the stub instructs (dispatch → stub report at the returned path → scan → incorporate), then ran run.cjs grade — both state assertions passed, and run.cjs prompt was checked to confirm stubs reach the walker while expect: claims never do.

🤖 Generated with Claude Code

Stack

  1. docs(design): prose-tests programme design log #544
  2. feat(prose-tests): the framework — cases, worlds, runner, skill #545
  3. test(prose): feature happy-path corpus — five worlds, seven cases #546
  4. test(prose): bugfix corpus — the investigation-centric surfaces #548 👈 current
  5. test: retry recursive teardown removals — kill a class of phantom failures #549
  6. fix(entry-skills): close the handoff fences — six files render their arms wrong #550
  7. docs: a contributing page for working on the system #551
  8. fix(entry-skills): every handoff arm says to invoke the skill #552
  9. fix(implementation): environment setup belongs to the setup reference alone #553
  10. fix(prose-tests): the asserter is told which substitutions were armed #554
  11. feat(prose-tests): the mid-flow substitution, and a world only prose can describe #555
  12. test(prose): claims assert consequences, not what was displayed #556
  13. feat(prose-tests): record everything the agents do, results included #557
  14. fix(discussion-entry): the handoff reports the source it actually had #558
  15. fix(prose-tests): the stop hook records, and names the model that walked #559
  16. fix(prose-tests): command output was never actually recorded #560
  17. feat(prose-tests): judge the walk as told, not the summary returned #561
  18. feat(prose-tests): decide in code what an agent should not be deciding #562
  19. test(prose): a case starts where a session starts #563
  20. feat(prose-tests): walk on Sonnet, judge on Opus, escalate a failure #564
  21. test(prose): give the eight read-only cases something that can fail #565
  22. test(prose): only walks that can be observed, and checks that survive the trip #566
  23. fix(prose-tests): the verdict names only the model the record names #567
  24. test(prose): discovery, walked to the point where work first exists #568
  25. fix(prose-tests): the asserter judges which of prose or walker was at fault #569
  26. docs(conventions): a step whose reference routes every exit still signposts #570
  27. test(prose): discovery's epic arm, to the same durability boundary #571

leeovery and others added 11 commits July 25, 2026 11:36
Stage 3b of design/prose-tests.md. Three `crash-fix` worlds and four
cases covering only what bugfix does differently from the linear
pipeline: continue-bugfix routing a fresh bug to investigation,
investigation entry seeding from the carrier without re-asking, the
root-cause validation agent's dispatch → scan → incorporate lifecycle,
and a spec sourced from the investigation rather than a discussion.

Two framework extensions the corpus demanded: a free-text `stub:`
section (agent-dispatching prose is walked with the walker playing the
agent — the lifecycle is under test, not the agent's judgment) and a
`json` state assertion (the agent row's closing status is checked in
the cache store, not read out of a transcript). Both were proven by
hand-walking the lifecycle in a live world first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lee's call, restored: cases were bundled several-per-file against what
he'd asked for. Each case now lives at tests/prose/{flow}/{case-id}.md
with the filename validated against the heading id, so the convention
can't drift; a flow's intro prose moves to its README.md, which the
loader ignores. 13 cases split out verbatim — no grammar, fixture, or
claim changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…wned stub triggers

Lee's review of the corpus, four corrections:

- Cases go flat — tests/prose/{case-id}.md, filename = id. Grouping by
  work type organised the corpus around the build sequence, not the
  prose under test, and no single-parent taxonomy fits cases spanning
  skills. Selection always ran off `files:`, never the directory.
- The assertion DSL is deleted. The expectation is a whole committed
  world; code diffs acted-vs-expected and the asserting agent classifies
  each difference volatile or material. Nothing goes unchecked because
  nobody thought to assert it, and there is no normalisation table or
  engine-vs-model file split to maintain.
- Stub triggers move to the case. Binding a substitution to a moment is
  what prevents reuse — the same content is wanted at different moments.
  A stub is named content; the case says when. Trigger-less stubs fail
  validation as arrange in disguise.
- The trace is granular. Coarse assertions let an agent silently
  course-correct around broken prose; a step-by-step expected trace
  turns that into a mismatch. The When stays coarse so the walker must
  still derive the path from the prose. Adds a DEVIATION: marker.

Proven end to end: walked the validation lifecycle by hand in a live
world, diffed against the expected fixture — the sole difference was the
volatile agent-row timestamp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lee reading the case file: a stub declared in `given` states a trigger
for a moment the reader hasn't reached yet. The format contradicted
itself — scripted `answers:` already sat in `when`, and a stub is the
same kind of thing: harness input consumed during the act, one playing
the user, the other the dispatched agent.

`given` is now the world and nothing else; `when` carries the
instruction plus everything fed in while it runs. Structure-only cases
drop `given` entirely.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… no CI claims

Three of Lee's points from the TUI review:

- `given` carries situational prose beside world_before — what the world
  represents, what has already happened, where the session stands. It
  reaches the walker as a SITUATION block. The root-cause case had this
  context smuggled into its `when`, mixing the situation with the act.
- A stub trigger names an observable event, not a narrative moment:
  "when the engine records a dispatch of kind root-cause-validation",
  not "when the prose dispatches the validation agent". The loose form
  is only unambiguous while a walk contains exactly one dispatch, and it
  leaves nothing in the transcript for the asserter to check the
  substitution against.
- The docs claimed CI. This project has no .github/workflows — the gate
  is `npm test`, run before every commit. Reworded throughout rather
  than implying anything runs on push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lee's review, three faults in the flat-markdown shape:

- Code regex-parsed values out of markdown, the exact pattern the
  analysis-state campaign eliminated. Now case.json holds what code
  branches on, prose files are read whole, recipes are required as
  modules. Nothing is parsed and prose never shares a file with code —
  which also deletes ~150 lines of parser.
- world_before/world_after named foreign fixtures the case merely
  pointed at, scattering one test across six locations. A case now owns
  its worlds: fixture-state.cjs and assertion-state.cjs, split because
  they change for different reasons, snapshots beside them. Duplication
  accepted; reuse moves to mainlines/ as composable stage functions.
- Naming follows the stage served — fixture, act, assert — so assert.md
  and assertion-state.cjs sit together by design.

act.md and assert.md stay separate files because that IS the
walker/asserter boundary, enforced by the filesystem rather than by
convention.

Adds a recipe-hash skip, without which per-case worlds would grow the
gate linearly. Proven both ways: 0.09s when nothing moved, and a full
rebuild reporting DRIFT when a mainline's content changed. Whole pipe
re-proven live — world built, lifecycle walked by hand, delta against
the assertion state was the volatile timestamp alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The case-directory restructure was meant to remove tests/prose/fixtures
but the rm ran from the wrong working directory and silently deleted
nothing, leaving 98 committed files that no code references. Spotted by
Lee reading the PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…f code

Lee's design, taken properly this time: /prose-test dispatches one
prose-orchestrator per case, which owns the world, dispatches
prose-walker and prose-asserter, escalates a FAIL to Opus, and returns a
verdict alone. Transcripts never reach the main session — the reason
this scales past a handful of cases.

Standing instructions move to .claude/agents/prose-*.md; run.cjs emits
only the per-case payload from tests/prose/prompts/. That removes the
last 52 lines of prose living as JS string literals in the runner.

Every agent is told in prose that its job is to run the test and report
— never to fix, never to work out why. The first real runs showed an
asserter wandering into engine source to explain a failure; tool
restrictions can't be relied on to stop that, so the instruction is
explicit in each definition.

Also from those runs: the walker must quote every block the prose
directs it to emit, since a skipped emission was otherwise invisible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n is worth nothing

Three runs of evidence: a Sonnet walker performed the walk correctly
every time but narrated it in summary, omitting the quoted evidence the
asserter requires — even with a worked transcript example in front of
it. The asserter was right to fail it; the tiering was wrong. Tiered
models produced a DISAGREEMENT on every run, which is noise rather than
signal, so the cheaper tier was a false economy.

Walker and asserter both run on Opus, and the orchestrator never
overrides a model — each definition names the model its result is
trusted at. A FAIL now confirms by repetition from a fresh world rather
than by promotion to a bigger model; a failure that doesn't reproduce
reports as FLAKY.

Also: renames test-prose-fixtures.cjs to test-prose-snapshots.cjs (it
tests snapshots), and clears two stale fragments left in the contract by
the Given-When-Then reshape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lee's call, recorded before it becomes tempting: deriving assert.md from
a recorded walk would turn the corpus into approval testing — pinning
what the prose currently does, defects included, so a mummified bug
passes forever. The authoring effort is what finds pre-existing defects;
it is the point, not the overhead. Snapshots stay generated because the
engine authors them and drift is visible; expectations are written
because only a person can say what correct means.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four real runs exposed the framework's worst failure mode: three walkers
skipped straight into the middle of a flow — one began at the gateway
call instead of initialisation, another at project-skills discovery,
past the entire entry skill. Both then "failed" at steps the walk never
reached. Lee's read is the decisive one: he has never seen the
project-skills prompt in real use, so a live session very likely
course-corrects past that guard exactly as our walker did — which means
every finding from those runs is unsafe until the walker is disciplined.

The walker now starts at the entry point the task names and performs
every step including ones whose effect the world already holds
("already booted", "the plan already exists" are named as the reasoning
that invalidates a walk). The asserter returns INVALID WALK when a
transcript opens mid-flow and judges nothing further; the orchestrator
retries once, then reports INVALID rather than FAIL.

A false finding is worse than no finding: it spends attention on prose
that was never exercised and teaches distrust of the runs that are real.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant