/cook
When to invoke: Implement an approved spec or focused unambiguous task through stale-safe source edits. Use when the user wants code written — “implement this”, “cook this spec”, “/cook .cheese/specs//mold), no-write discussion (/culture), or review-only work (/age).
Inputs
Section titled “Inputs”Accept one of:
- A spec path. When explicit, read it verbatim wherever it points.
- A bare slug. Resolve it to the durable spec path with
SPEC=$(python3 shared/scripts/artifact_path.py specs <slug>), then read"$SPEC". If you’re on a host that only exposes the packaged helper,python3 ${CLAUDE_SKILL_DIR}/scripts/cook.pyz artifact-path specs <slug>is the fallback. The resolver anchors specs at the per-project durable corpus (see../cheese/references/formatting.md§ Corpus location); this is the form/ultracookuses when chaining. - A pasted spec or issue.
- A focused implementation request with acceptance criteria.
- A clear, unambiguous task — single-file fix, named bug, well-scoped tweak — even without a spec.
Optional flags:
--auto— autonomous mode: skip every handoff gate and chain/press → /age → /cure(see## Auto modebelow; full selection/cap rules inreferences/auto-mode.md).--hard— propagate through/press → /age → /cure → /plate;/platefires/hard-cheeseafter its final artifact-writing gate.--open-pr— propagate to terminal/plate, which follows its explicit-choice and review-shape policy for a new PR.--resume <slug>— resume a crashed fan run from its manifest (full mechanics:references/fan-pathway.md§ –resume).
Standalone fast-path
Section titled “Standalone fast-path”/cook runs without /mold when the task is unambiguous. Treat a request as unambiguous when all three are present or trivially derivable:
- Inputs/outputs are clear. “Tail returns wrong byte count when file ends without newline” ✓; “make tail better” ✗.
- Scope is bounded. A named function, a single failing test, a specific call site, or a small region of one or two files.
- Verification is obvious. A failing test that can be made to pass, or a runnable command whose output should change in a stated way.
When the fast-path applies, derive a slug from the task (e.g. tail-trailing-newline), treat Contract as a one-sentence restatement of the request, and proceed directly to Cut without a spec round-trip. Route to /mold only when one of the three checks fails — silent ambiguity is the cardinal sin.
- Contract — confirm behaviour, non-goals, likely scope, quality gates. For standalone fast-path tasks, the contract is the user’s request restated in one sentence. If
.cheese/glossary/<slug>.mdexists, read it before implementation so naming follows the resolved canonical terms. - Cut — write failing tests for the changed behaviour. See
references/tdd-loop.md. - Implement — make the cut tests pass with the smallest production change.
- Taste-test — check spec drift, readability, scope, plus three fresh-context lenses (production path, wired callers, locked-decision). Dispatch the fresh-context
reviewerfor multi-file or public-surface diffs; keep the inline check otherwise. Two-round cap. Cost gate, reviewer-model pin, and the coder-nested degrade live inreferences/tdd-loop.md. - Hand off — produce the package-ready report (
references/package-report.md), write the handoff slug (## Handoff slugbelow), and prompt the next step via the shared handoff gate (see## Handoffbelow). The default chain is/press→/age→/cure.
Fan pathway
Section titled “Fan pathway”/cook’s single pathway routes a spec through one of three shapes, gated on whether the spec already carries a decomposition.
(a) Curded spec. If the spec already carries an embedded curds:/waves: block (produced by /mold’s curdle step, or a sibling curd’s prior decomposition), skip straight to wave fan-out below — the decomposition is already locked, no fresh decompose pass runs.
(b) Un-curded, small. Ordinary single-coder Cut → Implement → Taste-test, unchanged from today’s /cook (## Flow above). Sizing signal: /mold’s curd-count hint is advisory; otherwise use AC count and edit-site estimate. Per the spec’s cook-gate row: “un-curded (curd block, else AC count and edit-site estimate) | single vs fan vs decompose-first; wave plan; transport”.
(c) Un-curded, big. Dispatch the decomposer per ../cheese/references/decomposer.md (the locked curd-block schema — do not use ../ultracook/references/decomposer-prompt.md, which produces the incompatible legacy manifest schema) against the spec text to produce a curd_block-schema block (curds[], waves[], decomposer{}), then validate it with src/fanout/curd_block.py::validate_curd_block. Gate with the user by showing the wave plan plus a projected agent-dispatch count — exact phrasing: “12 ACs -> 5 curds, 2 waves, up to 30 agent dispatches. Go?” — unless --auto is set. The count is an upper bound, 5 + 5 x curds, excluding wiring; the derivation lives in references/fan-pathway.md § Phase-chain topology.
Wave cap. Waves are capped at <=4 curds, enforced by MAX_WAVE_SIZE in src/fanout/curd_block.py — cited, not reimplemented here.
Read references/fan-pathway.md before orchestrating a wave-fan run — it owns the existing-handoffs guard, mode selection, the publication-topology preflight, the milknado seam, phase-chain topology, the deterministic phase loop, worker-exhaustion/aggregate-gate recovery, worktree harvest and teardown, --resume <slug>, and resolution provenance.
A terminal age is publishable only with next: done; next: cure or a missing next halts — this applies to both fan-pathway tables (references/fan-pathway.md) and the single-coder --auto chain’s terminal age (## Auto mode below).
Baseline capture
Section titled “Baseline capture”Before any curd cooks, /cook captures the run’s broad-gate baseline once, in the orchestrator’s own tree, right after mode selection. Full capture steps, classification, hand-down, and the repair pathway: references/quality-gates.md.
For source changes, call the selected backend directly and follow code-intelligence-routing.md, including search → fresh bounded read → stale-safe write.
Portability reference: ../cheese/references/harness-portability.md. It covers helper resolution, sub-agent dispatch, GitHub operations, and handoff transitions; prefer the bundled or repo-local helper first, and treat ${CLAUDE_SKILL_DIR} as optional host-provided fallback.
The handoff blocks below are the portable contract; slash commands are host renderings, not the control model.
Preferred tools and fallbacks
Section titled “Preferred tools and fallbacks”| Need | Prefer | Fallback |
|---|---|---|
| Diffs | delta |
plain git diff |
| GitHub context | gh |
local git history or user-provided links |
| Merge assistance | mergiraf | manual conflict resolution with tests |
| Task commands | just, package scripts |
direct documented commands |
| Code navigation | semantic symbol search, then caller search | LSP or bounded native search; report precision loss |
| Read before edit | fresh bounded read from the write backend family | another snapshot-capable bounded read; re-read if anchors are incompatible |
Falling back, mention any loss of precision that affects risk.
Quality gates
Section titled “Quality gates”Run existing project commands only — the most relevant tests for the touched area, plus lint/type/build if defined. Never remove, skip, or weaken unrelated tests to make the change pass.
Gate failures are baseline-aware. Policy, the classification taxonomy, and the baseline: block shape are the shared reference references/quality-gates.md; every downstream phase links there instead of restating it.
Output
Section titled “Output”House style and citations: ../cheese/references/formatting.md. Authoritative report shape: references/package-report.md; the bullets below sketch it:
- Files changed and why.
- Tests or checks run.
- Remaining risks or skipped checks.
- Suggested next skill: usually
/press→/age→/cure.
Handoff slug
Section titled “Handoff slug”Write a minimum-shape handoff slug at the top of .cheese/cook/<slug>.md — same file as the report, no second file — so downstream phases (and cook’s own fan pathway when orchestrating a wave) can resume or chain without re-reading it. Schema:
status: ok | halt: <one-line reason>next: mold | cook | press | age | doneartifact: <path-to-richer-report-if-any>taste_test: inline-pass | dispatched-pass | revised | deferred-to-orchestratordurable_flags: none | <one line per flag: what durable knowledge changed -> target wiki page>baseline: none | <block — shape in references/quality-gates.md § Baseline block shape><one-line orientation: what cook changed>next: names the next runnable phase — press (standard chain), age (press skipped), cook (rerun after a blocker), mold (spec needs another pass) — or done only for true terminal completion, never a blocked-but-resumable halt; halt: reasons follow the package-report stop conditions. The orientation line is one factual sentence. Omit taste_test: when the cost gate didn’t warrant one.
durable_flags: is a conservative gate, default none. Add one line per architecture/protocol/convention/rationale delta (<what changed> -> <target wiki page>); mechanical and test-only changes stay none. Cook records flags only — the publish-boundary writer (cure/plate/affinage) reads them as its write-back candidates.
baseline: is written only when the ## Quality gates capture rule above ran and recorded at least one identical-to-baseline failure; omit it otherwise. Block shape: references/quality-gates.md § Baseline block shape.
Handoff
Section titled “Handoff”Pipeline: culture → mold → [cook] → press → age → cure → plate
After the package-ready report and handoff slug are on disk, ask via the shared handoff gate in ../cheese/references/handoff-gate.md (its Standard forward-step menu): lead each option with the verb, backed by the skill command (with any in-scope --hard):
- Harden tests before review (recommended) —
/press <slug>. - Plate it —
/press <slug> --auto --open-pr: run the remaining review chain, then/plateresolves topology and publishes. - Checkpoint & stop —
/wheypoint: write a resumable handoff and pause. - Stop — dispatch none; leave further hardening for later.
Pre-select Harden tests before review when the diff added new behaviour or touched untested seams. To skip straight to review, reply other: /age <slug>; manual chaining works via each step’s own gate. Never dispatch before selection; run the selected command immediately.
When invoked with --auto, skip this gate entirely and proceed straight into the auto-mode chain (see ## Auto mode below).
Auto mode
Section titled “Auto mode”--auto is the autonomous-pipeline switch: skip every gate and chain forward without asking between steps. It runs /press --auto → /age --auto → /cure --auto --stake medium+, capped at two cure passes total — pass 1 fixes the initial findings, pass 2 fixes anything the re-age surfaces, then the chain stops regardless of remaining findings. /cook itself never invokes /plate; /cure dispatches it at the chain terminal (existing PR always, new PR only with --open-pr).
Auto mode stops early when: a quality gate fails new or changed against baseline and the fix rounds exhaust, the no-progress check trips, or the fix is design-shaped; /press returns blocked; a cure pass cannot apply any finding; or two cure passes complete (success path). Every early stop surfaces the failing skill’s report and states the cap reached or the blocker hit — never a silent downgrade.
Read references/auto-mode.md before running or dispatching auto mode — it owns the full per-step chain, cap-enforcement mechanics, the fan-pathway no-chain isolation directive (a spawned phase sub-agent never chains forward on its own; the orchestrator drives), cure’s per-finding failure handling, and the final-report template.
- Keep changes scoped to the accepted contract.
- Prefer existing dependencies and patterns.
- Do not invent architecture already rejected by the spec.
- Stop and ask when implementation reveals a design decision the spec did not answer.
- If the spec or fast-path request rests on a false premise, stop and surface it before writing code; do not work the wrong angle to honour the request literally.
- Apply the shared voice kernel (
../age/references/voice.md): lead the report with the answer, name loaded assumptions in the contract, flag residual risk ascertain | speculating | don't know. - Verification before
status: ok: identify the gate command, run it fresh this turn, read the full output, only then claim. Hedging words (should,probably,I think) are banned — state what the gate output showed.
Discipline
Section titled “Discipline”Iron Law, Red Flags, and the TDD Rationalization table live in
references/cook-discipline.md.
Agent resolution
Section titled “Agent resolution”Resolve implementation and taste-test dispatches through ../cheese/references/agent-resolution.md.
| Work | Preferred types | Permissions/isolation | Minimum power | Effort | Fallback |
|---|---|---|---|---|---|
| Implement the contract | coder | write, isolated-worktree | default | high | compatible coder, then general |
| Fresh-context taste-test | reviewer | read-only, fresh-context | powerful | high | compatible reviewer, then general |
| Decompose the spec | planner, general | write (manifest only), fresh-context | powerful | high | compatible planner, then general |
| Harvest and plate | parent | parent-owned repository state | powerful | high | no fallback; halt |
The canonical cook handoff and package report carry the shared agent_resolution block.
/cook — Auto mode chain mechanics
Section titled “/cook — Auto mode chain mechanics”Full mechanics for --auto, the autonomous-pipeline switch: the per-step chain, the two-cure-pass cap’s enforcement, early-stop conditions, the fan-pathway no-chain isolation directive, and the final-report template. SKILL.md’s ## Auto mode keeps the one-paragraph summary and the publishable-gate rule; this file is everything downstream of that summary.
What auto mode does
Section titled “What auto mode does”- After the package-ready report, invoke
/press <slug> --auto; append--open-prso terminal/platemay publish a new PR. /press --autoruns its hardening pass and, if readiness isready for /ageorfollow-up recommended, invokes/age <slug> --auto. Both states mean the cooked contract is sound and every changed behaviour has a hardening test; documented follow-ups are review-safe. Onlyblockedstops auto — blocked criteria: defined once in../../press/references/gap-analysis.md./age <slug> --autowrites the report and invokes/cure <slug> --auto --stake medium+./cure --auto --stake medium+bypasses the selection gate, applies every finding ofblocker,high, ormediumseverity plus every cheap (contained-fix)Low, then invokes/age --scope <touched-paths> --autofor verification.- The age → cure cycle is capped at two cure passes total. Pass 1 fixes the initial findings. Pass 2 fixes anything the re-age surfaces. After pass 2 the chain stops with a final summary, regardless of whether new findings remain.
/cookitself never invokes/plate. At the chain terminal,/curedispatches/platefor an existing PR, and for a new PR only when--open-pris in scope./platehonors explicit topology, selects an obviously cohesive single without asking, and asks before mutation when stacked is recommended or shape is ambiguous, including under auto.
Cap enforcement
Section titled “Cap enforcement”The two-cure-pass cap is enforced by chain length, not by age — age boots in fresh context each pass and cannot count prior passes. Each age pass writes next: from what it observes on that one run (next: cure when a medium+ finding remains, next: done when none do); before the terminal position this drives an early stop, but the value itself is informational for cap purposes — the loop’s fixed two-pass structure, not age’s own next: value, is what terminates the chain. /cook does not pass a pass-ordinal hint to age: age has no need to know whether it is the first or second post-cure check, since the orchestrator owns the position.
When auto mode stops early
Section titled “When auto mode stops early”- A quality gate fails new or changed against baseline (see
quality-gates.md) and the 2 fix rounds exhaust, the no-progress check trips, or the fix is design-shaped. Identical-to-baseline failures outside the cooked contract are recorded and never stop auto. /pressreturnsblocked(blocked criteria:../../press/references/gap-analysis.md).- A cure pass cannot apply any finding (every selected fix breaks tests on revert-or-keep evaluation).
- Two cure passes complete (success path).
In every early-stop case, surface the report from the failing skill and tell the user the cap reached or the blocker hit. Do not silently downgrade.
No-chain isolation directive
Section titled “No-chain isolation directive”Each phase’s existing --auto contract chains forward in-session — /cook --auto invokes /press --auto, which invokes /age --auto, and so on. When /cook is running as its own fan-pathway orchestrator (fan-pathway.md), that default is overridden for every per-curd or post-merge dispatch: each phase sub-agent runs only its own phase, writes its handoff slug, and stops — it never chains forward to the next phase itself, even though its own --auto contract documents that behavior. The fan-pathway orchestrator loop (fan-pathway.md’s ## Deterministic phase loop) owns deciding and dispatching what runs next, exactly as the retired /ultracook orchestrator once did.
The override travels in the spawn prompt as an explicit no-chain directive, carried over verbatim from /ultracook’s original wording: “Do not chain forward to the next phase even though your auto-mode contract documents that. Write your handoff slug and stop. /cook’s fan pathway is driving the chain. Run in the foreground — do not background yourself, spawn detached processes, or defer work to a later session. If you cannot complete the phase within your context window, write a partial slug with status: halt: <reason> and stop; do not silently timeout.”
Each phase’s own SKILL.md ## Auto mode section honours this under its ### When invoked from /ultracook heading (now: when invoked from /cook’s fan pathway) — see e.g. skills/press/SKILL.md, skills/age/SKILL.md, skills/cure/SKILL.md.
Failure handling inside cure
Section titled “Failure handling inside cure”See skills/cure/SKILL.md ## Auto mode for cure’s per-finding revert/defer behaviour. Cook does not duplicate the contract — cure owns it.
Final report
Section titled “Final report”The skill that ends the chain prints the summary below. On the success path that is the final /age --auto (after the two-cure-pass cap is reached); on an early stop it is the skill that surfaced the blocker.
Auto-mode summaryPasses: <1|2>Findings fixed: <count by severity>Deferred: <count, with cure-report path>Final age: <path>Next step: review the diff, then /plate when ready/cook — TDD Discipline
Section titled “/cook — TDD Discipline”Iron Law
Section titled “Iron Law”No production code without a failing test first.
The Cut step (write the failing test) is not optional and is never done “right after” implementation. If the test does not exist and is not failing, the Cook loop has not started.
Red Flags
Section titled “Red Flags”Stop if you notice yourself thinking any of these:
- “The behavior is obvious from the spec; a test would just restate it.”
- “I’ll add tests in the press pass.”
- “This is a small change; tests are overkill.”
- “The existing tests already cover this implicitly.”
- “The type system / linter makes a test redundant here.”
- Treating the post-implementation taste-test as proof the Cut step can be skipped.
Each of these is a rationalization. Name it and stop.
Rationalization table
Section titled “Rationalization table”| Rationalization | Why it fails | Required action |
|---|---|---|
| “The change is obvious; a test would just mirror the code.” | A mirroring test still catches future regressions. Its job is to fail when behavior changes, not to surprise you today. | Write the test. |
| “I’ll write the test in the press pass.” | Press hardens existing tests; it does not write the first test for new behavior. A behavior with no test has no harness. | Write the test in Cut, before any production code. |
| “This is a one-line fix; tests are overkill.” | The size of the change does not predict the probability of regression. One-line fixes often have subtle edge cases. | Write the narrowest test that would have caught the original bug. |
| “The existing suite already covers this path.” | Verify it. Find the specific test that would fail if the new behavior regressed. If you cannot name it, coverage is imagined, not real. | Name the specific test, or write a new one. |
| “The type system makes a runtime test redundant.” | Types verify shape; tests verify behavior. A function with the right signature can still return the wrong value. | Write a test that asserts the return value, not just that it compiles. |
| “The taste-test lenses will catch any issues.” | Taste-test is a post-implementation smell check, not a substitute for executable assertions. | Write the test first. |
| “We’re under time pressure; I’ll skip cut for this task.” | Time pressure is when regressions hurt most. The test is the cheapest insurance available. | Write the test. Time pressure is never grounds to skip Cut. |
/cook — Fan pathway mechanics
Section titled “/cook — Fan pathway mechanics”Full mechanics for /cook’s wave-fan pathway: the existing-handoffs guard, mode selection, publication-topology preflight, the milknado seam, phase-chain topology, the deterministic phase loop, worker-exhaustion/aggregate-gate recovery, worktree harvest and teardown, --resume <slug>, and resolution provenance. SKILL.md’s ## Fan pathway keeps the three-shape gate (a/b/c) and the wave cap; this file is everything downstream of that gate.
Existing handoffs guard
Section titled “Existing handoffs guard”Before dispatching the decomposer for an un-curded big spec, check whether any of .cheese/cook/<slug>.md, .cheese/press/<slug>.md, .cheese/age/<slug>.md, .cheese/cure/<slug>.md already exist. If any do, stop — print only the ones present — and tell the user to either run /cheese --continue <slug> to resume from the latest phase or rm the listed files to start fresh. Never wipe an existing handoff silently:
Slug `<slug>` has existing handoffs: .cheese/cook/<slug>.md (when present) .cheese/press/<slug>.md (when present) .cheese/age/<slug>.md (when present) .cheese/cure/<slug>.md (when present)Use `/cheese --continue <slug>` to resume from the latest phase, or`rm` the listed files to start fresh.Mode selection
Section titled “Mode selection”Whether a decomposed spec wave-fans or stays a single dispatch is a deterministic rule, not a deliberation. src/fanout/mode.py is the single source of truth: PARALLEL_THRESHOLD = 2, and select_mode(curds) returns "parallel" when len(curds) >= PARALLEL_THRESHOLD, else "linear". The same check runs from the shell as python3 skills/ultracook/scripts/ultracook.pyz mode --count <curd-count> — mode is still one of the .pyz’s live subcommands (alongside baseline, phase_decision, worktree, milknado, validate_decomposition, validate_manifest, manifest_update, wiring_topo_sort) even though the skill that used to own the CLI is retired. There is one threshold in the tree: the selector, validate_decomposition (python3 skills/ultracook/scripts/ultracook.pyz validate_decomposition <manifest>, re-run on validation failure, max 2 retries — validates the manifest-level seed[]/curds[]/wiring[] shape, distinct from curd_block.py’s spec-level curds/waves schema above), and /mold’s curd-count hint all read it. A 1-curd spec runs the single-coder path — select_mode calls this linear mode (no wave-fan); 2 or more curds selects parallel mode and always wave-fans.
No-curd-block fallback. select_mode_from_score(score), also in src/fanout/mode.py, is the fallback for a PR or fresh branch with no handoff — no curd block at all. It returns "linear" at score <= DECOMPOSE_FIRST_THRESHOLD (250) and "decompose-first" above it, and it never returns "parallel" for any input. The same check runs from the shell as python3 skills/ultracook/scripts/ultracook.pyz mode --score <score>. With no curd block there is no file-disjointness proof, so fanning coders is unsafe at any size — a large no-handoff branch triggers the decomposer, not blind parallelism. Curd count stays authoritative whenever a curd block exists: PARALLEL_THRESHOLD and select_mode(curds) above are unchanged, and the score fallback fires only when no curd block is present. The underlying principle: coders write, reviewers and debuggers read. File-disjointness exists to stop two writers colliding; read-only fan-out carries no such constraint, which is why the age ladder and the pasteurize policy need no curd block, and why /cook cannot fan without one.
Fast-path. When /mold’s curd-count hint = 1 and blast radius is low or medium, skip the decomposer spawn entirely and go straight to the single-coder path — the hint is trusted only to skip work in this indivisible case, never to pick parallel; the decomposer remains authoritative for hint >= 2 or absent.
Publication topology preflight
Section titled “Publication topology preflight”When the selected mode is parallel, --open-pr is present, and no PR exists, dispatch /plate in topology-preflight mode against .cheese/ultracook/<slug>/manifest.yaml before Phase 1 seed or any worker commit — before Seed (coder). runs (## Worktree harvest and teardown below). Apply /plate’s review-shape policy: preserve an explicit user choice, persist single without asking for one cohesive review unit, or ask once when stacked is recommended or shape is ambiguous — do not ask twice. Read back plate_layout: single | stacked and re-run validate_manifest. Existing PRs preserve detected topology; runs without --open-pr do not preflight because their workers remain commit-only.
Milknado seam
Section titled “Milknado seam”Before running any curd, probe which of three roles the available toolset supports (src/fanout/milknado.py::probe, exposed as python3 skills/ultracook/scripts/ultracook.pyz milknado --tools "<available tool names>"):
engine— bothmilknado_todo_claimandmilknado_node_verifyare present. milknado owns the DAG, per-node worktrees, and verify-until-green (it re-runs the project gates itself until they pass);/cookspawns the phase agent per claimed node instead of managing worktrees directly.tracker— onlymilknado_todo_addis present. milknado records curd status but doesn’t run curds;/cookstill owns native fan-out.none— no milknado tools. Native fan-out end to end:/cookowns worktrees itself, and curds self-verify by running the project gates once, in-worker.
This parity difference is deliberate: native curds self-verify (gates run once, in-worker); milknado, when present, does verify-until-green (re-runs gates until green). See ../../cheese/references/optional-plugins.md for the detect-and-degrade contract — announce milknado’s absence once and proceed; none is never a blocker.
Phase-chain topology
Section titled “Phase-chain topology”| Stage | Chain | phase_decision --table |
|---|---|---|
| Per curd | coder(cook) → coder(press) → reviewer(age) → coder(cure) → reviewer(final age) |
parallel-curd |
| Post-merge, once, over the merged diff | press → age → cure → age |
parallel-postmerge |
This is not the old 7-spawn linear /ultracook chain (cook → press → age → cure → age → cure → age, table linear in src/fanout/phase_decision.py) run verbatim — that table still backs the single-coder path’s own --auto chain (auto-mode.md) — the fan pathway’s own topology is the two tables above. The per-curd table can end early: a first age reporting next: done clean-completes the curd (action=clean_complete) and skips cure and the final age, because nothing has touched the tree since that review and the post-merge pass re-covers the merged diff anyway. The post-merge table never short-circuits on that signal — it is the last review before publication, so cure and final age always run, and only a final-age next: done is publishable; next: cure or a missing next halts.
Projected dispatch count. The upper bound /cook’s decompose gate shows the user (SKILL.md § Fan pathway (c)) is derived from the two tables above: 1 seed coder + 5 × curds (the per-curd chain, shortened to 3 by a first-age clean_complete) + 4 (the post-merge chain) = 5 + 5 × curds. Wiring dispatches are not counted — wiring rows live in the manifest, not the curd block, so they are unknown at gate time.
Deterministic phase loop
Section titled “Deterministic phase loop”Between dispatches, /cook’s fan-pathway orchestrator decides the next action mechanically:
- Parse the slug —
python3 shared/scripts/read_handoff_slug.py --phase <phase> --slug <slug>(the same helper/age’s own flow calls, e.g.skills/age/SKILL.md) → JSON{status, next, artifact, orientation, halt_reason}. Never infer success from a sub-agent’s last line of stdout — read the file. - Compute the verdict —
python3 skills/ultracook/scripts/ultracook.pyz phase_decision --phase-index <i> --status <status> [--next <next>] --table parallel-curd|parallel-postmerge(src/fanout/phase_decision.py) → JSON{action, next_phase, exit_message}.action=haltsurfaces the reason and stops;action=clean_complete(per-curd table only) records the first age’s review context as final and skips ahead;action=spawndispatchesnext_phase.
Worker exhaustion and aggregate-gate recovery
Section titled “Worker exhaustion and aggregate-gate recovery”- Worker exhaustion. A curd worker that runs out of context or turns writes a partial
status: halt: <reason>slug. Retry that curd once with the error folded into its context; if it halts again, mark it failed, keep harvesting the rest, and report the failed curd in the final summary — never silently drop it. - Aggregate-gate cross-curd conflict vs. drift. After harvesting all curds, run the project gates over the merged tree. On failure, distinguish a real cross-curd conflict (curds passed individually but collide in aggregate — a decomposer error → halt and surface it) from harmless drift (a formatter or generated-file delta the post-merge cure pass can absorb → continue). Never auto-resolve a real conflict.
(ported from the retired /ultracook’s recovery-paths section, issue #194)
Worktree harvest and teardown
Section titled “Worktree harvest and teardown”- Give each curd its own worktree; when the host lacks a native worktree-isolated sub-agent primitive, create it first with
python3 skills/ultracook/scripts/ultracook.pyz worktree create --slug <id> --base <orchestrator-branch>(returns{path, branch}). - Harvest by cherry-picking each curd branch onto the orchestrator branch:
python3 skills/ultracook/scripts/ultracook.pyz worktree harvest --branch <curd-branch> --onto <orchestrator-branch>— the parent and sub-agent share one.gitobject store, so this needs nogit fetch. On conflict, invoke/melt; if it cannot resolve, fall back to per-curd PRs. - Tear down every worktree after harvest:
python3 skills/ultracook/scripts/ultracook.pyz worktree teardown --path <worktree-path> --branch <curd-branch>./cook’s fan pathway owns teardown — worktrees leak otherwise. A completed run leaks nothing: noworktree-agent-*branch (the one exempt case is the repair pathway’s ownworktree-agent-repair-*branch,skills/plate/SKILL.md, which has an independent lifecycle) and no stray.claude/worktrees/agent-*directory.
Wave-fan mechanics, in order (baseline capture through publication; each step’s literal manifest_update set-phase call is given so the phase-string writer/reader/schema round-trip stays exact):
- Capture the run’s broad-gate baseline once, in the orchestrator’s own tree, before Seed (see
SKILL.md’s## Baseline captureandquality-gates.md). - Seed (coder). Dispatch a
coderfor shared types/interfaces in an isolated worktree, commit via/platein commit-only mode, thenmanifest_update set-phase --manifest <path> --phase seed_complete. - Per curd (
## Worktree harvest and teardown,## Phase-chain topologyabove): run the five sequential dispatches; mark each curdrunningthencompleted/failed; after all curds return,manifest_update set-phase --manifest <path> --phase curds_complete. - Harvest and tear down every curd;
manifest_update set-phase --manifest <path> --phase merge_complete. - Run wiring tasks topo-sorted (
ultracook.pyz wiring_topo_sort), dispatching acodersequentially within each wave;manifest_update set-phase --manifest <path> --phase wiring_completethen immediatelymanifest_update set-phase --manifest <path> --phase final_merge_complete(wiring commits land directly on the orchestrator branch in this flow, so the two markers coincide). - Run the post-merge integration pass (
## Phase-chain topologyabove);manifest_update set-phase --manifest <path> --phase post_review_complete. /cookitself alone performs harvest and, at the very end, dispatches/plate— never mid-run;manifest_update set-phase --manifest <path> --phase pr_publish_completeafter/plateverifies publication.
Every set-phase call above uses the same manifest_update CLI (src/fanout/manifest_update.py), atomic and re-validated against the schema: manifest_update set-phase --manifest <path> --phase <phase-name>, manifest_update set-curd-status --manifest <path> --curd <id> --status running|completed|failed [--commit-sha <sha> --base-commit <sha> --reviewed-tree-oid <oid> --diff-hash sha256:<hex> --scope <path> ...], and manifest_update set-wiring-status --manifest <path> --wiring <id> --status running|completed|failed [--commit-sha <sha>].
–resume
Section titled “–resume ”--resume <slug> is the sanctioned re-entry into a crashed wave-fan run. It reads .cheese/ultracook/<slug>/manifest.yaml (path unchanged from the retired /ultracook) and continues from where the crash left off:
- Load the manifest. If missing, fail fast:
"no manifest at .cheese/ultracook/<slug>/manifest.yaml — nothing to resume". Optionally re-check its shape withpython3 skills/ultracook/scripts/ultracook.pyz validate_manifest <path>. - Rebase guard. For every non-null
commit_sharecorded on a completed seed item, curd, or wiring row, rungit cat-file -e <sha>(the schema permitscommit_sha: nullon acompletedrow — skip those). If any recorded SHA is gone, fail fast and name the missing SHA — resuming onto rewritten history would harvest the wrong tree. This guard is orchestrator prose, not new engine code; there is no auto-recovery. - Restore continuity. Read
phase_summaryandcarry_forwardfrom the manifest — the cross-seam continuity a resumed orchestrator reasons from, since a fresh spawn has no conversation history. - Pick up at the next incomplete phase. Read the
phasefield, one of the ordered enum inskills/ultracook/references/manifest-schema.json:gate_approved -> seed_complete -> curds_complete -> merge_complete -> wiring_complete -> final_merge_complete -> post_review_complete -> pr_publish_complete. Continue from the next incomplete phase, skipping every curd/wiring row alreadycompleted. ReportResuming <slug> from phase <next-phase>. Ifphaseis alreadypr_publish_complete, the run is done — report and stop.
A bare re-run (no --resume) that finds an existing manifest stops and tells the user to pass --resume <slug> to continue or rm -r .cheese/ultracook/<slug>/ to start fresh — never wipe an existing manifest silently.
Resolution provenance and the output contract
Section titled “Resolution provenance and the output contract”Every phase and curd dispatch resolves against the typed-role table in SKILL.md’s ## Agent resolution section and the shared protocol in ../../cheese/references/agent-resolution.md:
| Work | Preferred types |
|---|---|
| Decompose the spec | planner, general |
| Cook, press, cure, seed, or wiring | coder |
| Every age pass | reviewer |
| Harvest and plate | parent |
The resolver filters for required capabilities/tools/permissions/isolation first, then picks minimum power and maximum specificity; a prompt-only read-only general fallback may continue with degraded: true, while a missing required tool or write permission halts. Typed-role shorthand, ported verbatim from the retired /ultracook’s Rules: planner/general for decomposition, coder for cook/press/cure/seed/wiring, reviewer for every age, and parent ownership for harvest and plate. Every phase’s handoff slug and the fan pathway’s own summary carry the resulting agent_resolution block, so role, fallback, and degradation stay visible rather than implicit.
A terminal age is publishable only with next: done; next: cure or a missing next halts as not publishable — this applies at the end of both the per-curd table and the post-merge table above, and to the single-coder --auto chain’s own terminal age (auto-mode.md).
The fan pathway and the single-coder path keep the same behavioral output and final-summary shape (SKILL.md’s ## Handoff slug, ## Output); required agent_resolution provenance records the selected role, fallback, and topology regardless of which pathway ran.
Package-ready report
Section titled “Package-ready report”Before opening a PR or handing off to /age, cook produces a package-ready report.
Cross-cutting house style and citation form: formatting.md. This file owns the package-report shape; formatting.md owns the voice rules and the footnote primitive. Quality-gate failure handling (baseline classification, the three-way policy, the baseline: block) is owned by quality-gates.md — this file only shapes how that policy renders in the report.
Output shape
Section titled “Output shape”## Cook Report — <slug>
### Contract- Behaviour: <one line>- Non-goals: <list or "none">- Quality gates: <commands>
### Files changed- <path>: <one-line reason>- <path>: collateral repair: <one-line reason> — for a repair outside the cooked contract, per the three-way policy in [`quality-gates.md`](#quality-gates--baseline-aware-three-way-policy)
### Tests- <command>: <pass | fail | skipped with reason>
### Risks- <bullet — known unknown, deferred decision, or anything you'd want a reviewer to look at>
### Baseline (if any recorded)- <suite>/<test_id>: <signature> — identical to baseline, outside the cooked contract, not fixed (see [`quality-gates.md`](#quality-gates--baseline-aware-three-way-policy))
### Self-eval- [x] Cut wrote failing tests before production changes.- [x] Cook made tests pass without speculative behaviour.- [x] Taste-test passed.- [x] Quality gates pass, or all remaining red is recorded baseline failure (see Baseline section).
### Next step- /press <slug> — harden tests and check coverage- /age <slug> — review the diff- /cure <slug> — apply selected age findings (after /age)Honesty rules
Section titled “Honesty rules”- Never claim green on partial work. If a test is skipped, list the command and the reason.
- Never hide a failed gate. If lint failed and you didn’t fix it, the report says so and recommends a follow-up.
- Never claim “ready for /age” if any taste-test lens returned
reviseand you didn’t address it. That’s the cardinal sin. - When the Baseline section lists any recorded failures, the final summary states plainly that the full suite is not green and lists those failures — loud, never hidden, per
quality-gates.md.
Stop conditions
Section titled “Stop conditions”Cook stops (does not produce a “ready” report) when:
- A spec decision was missing and the user has not answered.
- Tests cannot be made to fail for the expected reason.
- The two-round taste-test cap was hit and findings remain.
- A quality gate fails on new or changed behaviour and the fix requires a design decision outside the spec (identical-to-baseline failures are recorded, not a stop condition — see
quality-gates.md).
In each case, the report says “blocked” with the precise reason.
Quality gates — baseline-aware three-way policy
Section titled “Quality gates — baseline-aware three-way policy”Single source of truth for how /cook, /press, /cure, and /ultracook treat quality-gate failures against a baseline. Every downstream skill links here instead of restating the rules.
Baseline capture ownership
Section titled “Baseline capture ownership”Baseline capture is frame-owned, not per-cook:
/cook’s fan pathway — captures the broad-gate baseline once per run, before any curd cooks, and hands it down to curd cooks via dispatch. A curd never captures its own baseline. The hand-down happens before Seed, writing the classified result into.cheese/ultracook/<slug>/manifest.yaml’sbaseline:block, then reaching each curd’scookdispatch through../../ultracook/references/curd-prompt.md’s{baseline}field. Documented example:python3 skills/ultracook/scripts/ultracook.pyz baseline.- Bare
/cook(no frame) — captures lazily, on the first red broad gate, from the pre-change tree (git stashor a clean worktree checkout), classifies the failures, then proceeds with the classified result.
Frame capture and the gate re-run happen in the same environment (same worktree, same toolchain) to minimize signature drift from environment-sensitive flakes.
Classification taxonomy
Section titled “Classification taxonomy”Classification is deterministic and computed by the tested helper src/fanout/baseline.py::classify() — never agent-eyeballed.
FailureRecord = {suite, test_id, signature}, where signature is the first line of the failure message, whitespace-normalized.
- identical — same test, same signature as baseline.
- new — not in baseline.
- changed — same test, different signature. Treated as
new. - resolved — in baseline, now green. Recorded for the summary; not a failure.
Three-way gate policy
Section titled “Three-way gate policy”- Identical, outside the cooked contract — record in the handoff’s
baseline:block, continue; never halt, never fix silently. - New or changed — the cook fixes it: up to 2 fix rounds per gate, with a no-progress check. The same failure signature appearing twice consecutively halts early. Collateral repairs (files outside the cooked contract) are allowed freely; record each one in the report’s Files-changed with reason
collateral repair: <one line>. - Halt only when: rounds exhaust, the no-progress check trips, or the fix is design-shaped (requires a decision outside the spec). The halt handoff carries the classification so resume never re-asks.
Baseline block shape
Section titled “Baseline block shape”Optional, additive. Statuses stay ok/halt; this introduces no new status enum.
baseline: captured_at: <UTC ISO-8601> gates: - cmd: <gate command> failures: [{suite, test_id, signature}] repair_dispatch: # optional — present once a repair is dispatched slug: <pasteurize slug> branch: <repair worktree branch> pr: <PR number or URL> # optional — present once platedLoud, never hidden
Section titled “Loud, never hidden”Identical-to-baseline failures are recorded loud: the final summary lists them and states the full suite is not green. A concurrent repair may already be in flight — see § Repair pathway.
Repair pathway
Section titled “Repair pathway”Recording a debt is not fixing it. When a run’s baseline capture records ≥1 identical-to-baseline failure, both frames follow the same repair pathway — expressed once here, linked from cook/SKILL.md and ultracook/SKILL.md rather than restated.
At the frame’s existing record point (ultracook: pre-Seed manifest write; bare cook: post-classify handoff-slug write):
- Dedupe — dedupe against a live
repair_dispatch: if thebaseline:block already carries one (its branch still exists and its handoff chain has not reached a terminalstatus: okorstatus: halt), skip. Never dispatch a second repair for the same debt. - Consent — automatic under
--auto; otherwise prompt once at record time (../../cheese/references/ask-user-question.md) with the failure count. Decline skips the repair; the debt stays recorded either way. - Worktree — create a repair worktree via the shared primitive:
<skill>.pyz worktree create --slug repair-<run-slug> --base origin/main. Never the cook’s own tree — an independent lifecycle, excluded from the run’s worktree teardown. Bare/cookexample:python3 skills/cook/scripts/cook.pyz worktree create --slug repair-<slug> --base origin/main. - Dispatch — to dispatch a concurrent
/pasteurizein an isolated worktree, brief it with the recorded failures (suite, test_id, signature per entry) as the symptom, plus one explicit per-dispatch override: chain forward at Phase 6 with/cook <repair-slug> --auto --open-pr, not pasteurize’s own documented/cook <repair-slug> --auto. This is a dispatch-time instruction in the brief, not a change to pasteurize’s SKILL.md — it is more specific than the skill’s generic default and governs for this one invocation, so the repair publishes its own PR by default./pasteurize’s own contract is unchanged. - Record — write
repair_dispatch: {slug, branch}into thebaseline:block (manifest for ultracook, handoff slug for bare cook); addpronce one is plated.
The run never waits on the repair: a failed, halted, or still-in-flight repair leaves the recorded debt untouched and never blocks the run’s completion or publication. The final summary reports repair status when known; the repair_dispatch link and the pasteurize slug are the resume path otherwise.
Merge-time topology
Section titled “Merge-time topology”The repair worktree’s own /plate step, at publication time, applies a mechanical file-overlap check before its ordinary New-PR topology policy: compare the repair’s changed files against the originating run’s branch, if that branch still exists.
- No shared files (or the run branch is already gone — merged or deleted) — plate the repair as an ordinary independent PR against
main. This is/plate’s existing New-PR flow; no run-diff comparison needed. - Shared files, repair ≤2 files and ≤50 changed lines — skip publication; harvest the repair’s commits onto the run branch with the shared
worktree_harvest(branch, onto=run_branch)primitive instead. - Shared files, repair over that threshold — restack: the repair becomes the base PR, the run’s PR(s) rebase on top, via
/plate’s existing stack machinery.
Consumers
Section titled “Consumers”/cookwrites thebaseline:block./press,/age,/curehonor it: no re-halt, no re-flag of identical entries./cheese --continuetreats it as settled state, not an open question./cook’s fan pathway validates it in the run manifest./plateapplies the repair pathway’s merge-time topology check when publishing a repair-worktree branch (§ Repair pathway, Merge-time topology).
The TDD loop: cut → implement → taste-test
Section titled “The TDD loop: cut → implement → taste-test”The cook skill runs a sequential TDD discipline. Each phase has a clear exit before the next starts.
Cut — failing tests first
Section titled “Cut — failing tests first”When the change adds or modifies behaviour, write the test before the implementation.
Cut must report:
- Test files added or changed.
- The spec requirement each test covers.
- The observed red failure for each new behaviour.
- Whether existing tests were touched and why (only allowed for related-fixture or shared-helper updates — never to weaken assertions).
If a test cannot be made to fail for the expected reason, stop and fix the test before cooking. A test that passes against unimplemented code is a false-positive factory.
Implement — minimal green
Section titled “Implement — minimal green”Implement the smallest production change that turns the cut tests green.
Implement must:
- Use existing dependencies and project patterns.
- Run the narrowest useful test (the new cut tests) plus relevant wider gates (lint, typecheck, build).
- Preserve strong assertions written by cut.
- Stop and ask if implementation reveals a design decision the spec did not answer.
If cook reports partial or skipped work, stop and resolve before taste-test.
Taste-test — drift, readability, scope, simplify, plus three fresh-context lenses
Section titled “Taste-test — drift, readability, scope, simplify, plus three fresh-context lenses”After cook says “I completed all the changes”, run a taste test before press. The taste-test is a fresh-context review: when the cooked diff is non-trivial it is dispatched to a read-only reviewer that did not write the code. Small diffs keep the cheap inline check.
Cost gate — where it runs. Dispatch the fresh-context reviewer unless all four hold: single file AND no new public surface AND <~40 changed lines AND no risk flag — then run the coder self-check instead. Any one term failing routes to 1 fresh opus reviewer.
Risk flag — one of the override categories in src/fanout/age_route.py’s OVERRIDE_FLAGS constant: auth/secrets/crypto/tenant isolation; payments/ledgers/irreversible effects; concurrency/idempotency/ordering/retries; schema/migration/protocol/public-API change; production-destructive ops; weak integration coverage around a global invariant. On a bundle-only host the same constant ships in the age bundle (python3 ${CLAUDE_SKILL_DIR}/../age/scripts/age.pyz age-route consumes the flags; see skills/age/SKILL.md § Router call for the exact vocabulary).
Who runs it.
- Top-level
/cook: resolve the fresh-context taste-test through../../cheese/references/agent-resolution.md, requesting a read-onlyrevieweratpowerful/high. Pass{spec/contract, diff, cut-test list, any locked/user-approved decisions}; it returns the per-lens verdict below, not a full/agereport. A general worker may qualify only under the shared prompt-only read-only degradation. - Coder-nested
/cook: when the active coder cannot dispatch, run the inline self-check and recordtaste_test: deferred-to-orchestrator; the orchestrator must resolve and run the authoritative reviewer before accepting the handoff.
Lenses. Inline or dispatched, the taste-test returns pass | revise | escalate per lens (halt for Locked-decision):
| Lens | Question | Pass criterion |
|---|---|---|
| Spec | Did the implementation drift from the spec? | Every behaviour described in the spec is present; nothing extra. |
| Readability | Is the change as concise and clear as possible? | A reviewer can understand each changed file without external context. |
| Scope | Did cook add more than asked? | The diff matches the spec’s bullets; no speculative helpers. |
| Simplify | Does the diff reuse what exists, stay clean, and avoid wasted work? | See sub-checks below; all three must pass. |
| Production path | Does every spec acceptance criterion have a production path that exercises it? | The behaviour is reachable from real callers, not only from tests that manufacture the state. |
| Wired callers | Does each new public function have a non-test caller? | A non-test caller exists, or the diff carries an explicit “wired in phase X” note. |
| Locked-decision | If the dispatch prompt carries a locked/user-approved decision, does the diff implement that decision? | The diff honours the locked decision, or the reviewer returns halt flagging the divergence. |
The last three lenses are the fresh-context additions — they encode the failures the inline taste-test historically passed: a missing production path, public functions with zero non-test callers, and a silently-substituted design decision. A halt from the Locked-decision lens stops the chain for a human decision; it is not a corrective-cook finding.
Escalate-unverifiable. When a lens cannot verify its claim from available evidence, it returns escalate for that lens, never a guessed pass or revise (cross-cutting contract 1 in the spec: “a claim no evidence can settle returns escalate, never a guessed pass or fail”).
The Simplify lens runs three sub-checks (the same three axes /simplify uses):
- Reuse — new code does not duplicate an existing utility/helper/component; inline logic that has a project helper uses it; no near-duplicates of an existing function.
- Quality — no redundant state (cached value that can be derived), no parameter sprawl (added params instead of restructuring), no copy-paste-with-variation, no leaky abstraction (exposing internals across a slice boundary), no stringly-typed code where a constant/enum/union exists.
- Efficiency — no unnecessary work (redundant compute, repeated reads, N+1), no missed concurrency on independent ops, no recurring no-op state/store updates in loops or handlers, no pre-existence checks that should instead perform the operation and handle the resulting error, no unbounded structures or leaked listeners/timers, no full-file/dataset reads when a slice would do.
Each lens returns pass, revise, or escalate (halt for Locked-decision). Pipe every revise finding back into a bounded corrective cook pass with the original spec, the cook report, and the taste evidence.
Two-round cap
Section titled “Two-round cap”best: implement → taste-test (all pass) → pressworst: implement → taste-test → implement → taste-test → implement (final)After the second taste test, allow only one final corrective cook pass. If that final pass cannot fully resolve the taste findings, stop and report blocked instead of continuing to press.
Self-evaluation before handoff
Section titled “Self-evaluation before handoff”Confirm every item the package report asserts (package-report.md § Self-eval),
plus two it does not surface:
- Spec or acceptance criteria are clear.
- Remaining risks or skipped checks are documented.