Skip to content

/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/.md”, or “fix this bug” when the fix is clear; also when the user just says “go” or “ship it” with a spec or clear acceptance criteria in scope. Runs standalone on an unambiguous task — a spec helps but is not required. Do NOT use for fuzzy planning (/mold), no-write discussion (/culture), or review-only work (/age).


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 /ultracook uses 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 mode below; full selection/cap rules in references/auto-mode.md).
  • --hard — propagate through /press → /age → /cure → /plate; /plate fires /hard-cheese after 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).

/cook runs without /mold when the task is unambiguous. Treat a request as unambiguous when all three are present or trivially derivable:

  1. Inputs/outputs are clear. “Tail returns wrong byte count when file ends without newline” ✓; “make tail better” ✗.
  2. Scope is bounded. A named function, a single failing test, a specific call site, or a small region of one or two files.
  3. 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.

  1. 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>.md exists, read it before implementation so naming follows the resolved canonical terms.
  2. Cut — write failing tests for the changed behaviour. See references/tdd-loop.md.
  3. Implement — make the cut tests pass with the smallest production change.
  4. Taste-test — check spec drift, readability, scope, plus three fresh-context lenses (production path, wired callers, locked-decision). Dispatch the fresh-context reviewer for 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 in references/tdd-loop.md.
  5. Hand off — produce the package-ready report (references/package-report.md), write the handoff slug (## Handoff slug below), and prompt the next step via the shared handoff gate (see ## Handoff below). The default chain is /press/age/cure.

/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).

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.

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.

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.

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.

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 | done
artifact: <path-to-richer-report-if-any>
taste_test: inline-pass | dispatched-pass | revised | deferred-to-orchestrator
durable_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.

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 /plate resolves 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 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 as certain | 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.

Iron Law, Red Flags, and the TDD Rationalization table live in references/cook-discipline.md.

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.

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.

  1. After the package-ready report, invoke /press <slug> --auto; append --open-pr so terminal /plate may publish a new PR.
  2. /press --auto runs its hardening pass and, if readiness is ready for /age or follow-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. Only blocked stops auto — blocked criteria: defined once in ../../press/references/gap-analysis.md.
  3. /age <slug> --auto writes the report and invokes /cure <slug> --auto --stake medium+.
  4. /cure --auto --stake medium+ bypasses the selection gate, applies every finding of blocker, high, or medium severity plus every cheap (contained-fix) Low, then invokes /age --scope <touched-paths> --auto for verification.
  5. 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.
  6. /cook itself never invokes /plate. At the chain terminal, /cure dispatches /plate for an existing PR, and for a new PR only when --open-pr is in scope. /plate honors explicit topology, selects an obviously cohesive single without asking, and asks before mutation when stacked is recommended or shape is ambiguous, including under auto.

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.

  • 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.
  • /press returns blocked (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.

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.

See skills/cure/SKILL.md ## Auto mode for cure’s per-finding revert/defer behaviour. Cook does not duplicate the contract — cure owns it.

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 summary
Passes: <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

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.


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 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.

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.

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.

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.

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.

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 — both milknado_todo_claim and milknado_node_verify are present. milknado owns the DAG, per-node worktrees, and verify-until-green (it re-runs the project gates itself until they pass); /cook spawns the phase agent per claimed node instead of managing worktrees directly.
  • tracker — only milknado_todo_add is present. milknado records curd status but doesn’t run curds; /cook still owns native fan-out.
  • none — no milknado tools. Native fan-out end to end: /cook owns 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.

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.

Between dispatches, /cook’s fan-pathway orchestrator decides the next action mechanically:

  1. Parse the slugpython3 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.
  2. Compute the verdictpython3 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=halt surfaces the reason and stops; action=clean_complete (per-curd table only) records the first age’s review context as final and skips ahead; action=spawn dispatches next_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)

  • 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 .git object store, so this needs no git 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: no worktree-agent-* branch (the one exempt case is the repair pathway’s own worktree-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 capture and quality-gates.md).
  • Seed (coder). Dispatch a coder for shared types/interfaces in an isolated worktree, commit via /plate in commit-only mode, then manifest_update set-phase --manifest <path> --phase seed_complete.
  • Per curd (## Worktree harvest and teardown, ## Phase-chain topology above): run the five sequential dispatches; mark each curd running then completed/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 a coder sequentially within each wave; manifest_update set-phase --manifest <path> --phase wiring_complete then immediately manifest_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 topology above); manifest_update set-phase --manifest <path> --phase post_review_complete.
  • /cook itself alone performs harvest and, at the very end, dispatches /plate — never mid-run; manifest_update set-phase --manifest <path> --phase pr_publish_complete after /plate verifies 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 <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:

  1. Load the manifest. If missing, fail fast: "no manifest at .cheese/ultracook/<slug>/manifest.yaml — nothing to resume". Optionally re-check its shape with python3 skills/ultracook/scripts/ultracook.pyz validate_manifest <path>.
  2. Rebase guard. For every non-null commit_sha recorded on a completed seed item, curd, or wiring row, run git cat-file -e <sha> (the schema permits commit_sha: null on a completed row — 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.
  3. Restore continuity. Read phase_summary and carry_forward from the manifest — the cross-seam continuity a resumed orchestrator reasons from, since a fresh spawn has no conversation history.
  4. Pick up at the next incomplete phase. Read the phase field, one of the ordered enum in skills/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 already completed. Report Resuming <slug> from phase <next-phase>. If phase is already pr_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.

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.

## 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)
  • 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 revise and 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.

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 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’s baseline: block, then reaching each curd’s cook dispatch 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 stash or 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 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.
  • 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.

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 plated

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.

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):

  1. Dedupe — dedupe against a live repair_dispatch: if the baseline: block already carries one (its branch still exists and its handoff chain has not reached a terminal status: ok or status: halt), skip. Never dispatch a second repair for the same debt.
  2. 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.
  3. 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 /cook example: python3 skills/cook/scripts/cook.pyz worktree create --slug repair-<slug> --base origin/main.
  4. Dispatch — to dispatch a concurrent /pasteurize in 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.
  5. Record — write repair_dispatch: {slug, branch} into the baseline: block (manifest for ultracook, handoff slug for bare cook); add pr once 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.

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.
  • /cook writes the baseline: block.
  • /press, /age, /cure honor it: no re-halt, no re-flag of identical entries.
  • /cheese --continue treats it as settled state, not an open question.
  • /cook’s fan pathway validates it in the run manifest.
  • /plate applies 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.

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 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-only reviewer at powerful / high. Pass {spec/contract, diff, cut-test list, any locked/user-approved decisions}; it returns the per-lens verdict below, not a full /age report. 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 record taste_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.

best: implement → taste-test (all pass) → press
worst: 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.

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.