Files
Skills/implement/SKILL.md
T
claude a1d6409163 feat(conventions): substantive work isolates into git worktrees, merge user-ratified
Introduce the worktree convention the corpus previously lacked: any
substantive work stream targeting main — in every mode, autonomous
(/boss) and interactive alike — runs in a git worktree on its own
branch (issue-N-<slug>), and the branch reaches main only through a
user-ratified merge performed by the orchestrator (fast-forward
preferred, push covered by the ratification, no pull request).
Background sessions were already harness-isolated; this extends the
same isolation to the sessions the harness does not guard. The
convention depends on worktree.baseRef=head (the default 'fresh'
bases branches on origin's routinely-stale tip under the
no-autonomous-push discipline), now pinned in conventions.md.

boss/SKILL.md gains the run-level mechanics (§ The run worktree):
one worktree per run named after the reference issue, entered before
the first dispatch; the merge is a user-reserved run-close act; the
done-state notify names the branch as the review handle; closes #N
fires at merge-and-push time. Iron Law, rollback-sandbox framing,
user-reserved-act enumerations, rationalisations, and red flags
updated to match.

Corollary reconciliations after adversarial review: implement's
'there is no branch' narrowed to 'the loop creates no branch of its
own'; specify's auto-signed-commits-on-main heading reworded to
ratified-history framing; the sandbox bullets in conventions.md and
the CLAUDE.md fragment note the anchor is the run branch's base.

The skills meta-repo itself stays exempt (its CLAUDE.md: install
symlinks resolve to the primary clone) and direct-on-main.
2026-07-14 12:32:26 +02:00

384 lines
23 KiB
Markdown

---
name: implement
description: Use when an implementation plan exists under docs/plans and is ready to execute, OR when a debug/tdd RED-test is handed off for the GREEN side. Runs the `implement-loop` Workflow — a deterministic script that executes the per-task loop (implementer → spec-compliance → quality, each a separate agent call) and aggregates in code, writing edits, tests, and a stats file to the working tree without committing (and `BLOCKED.md` on PARTIAL/BLOCKED). The orchestrator reads the end-report, inspects the working tree, decides commit shape, and performs all commits. Also documents the `compiler-driven` light-edit arm.
---
# implement — plan execution on the Workflow substrate
> **Violating the letter of these rules is violating the spirit.**
## Overview
Plan execution runs as the **`implement-loop` Workflow** (shipped at
`workflows/implement-loop.js`, symlinked into `~/.claude/workflows/` by
`install.sh`). The orchestrator invokes it through the Workflow tool;
the script runs the entire per-task loop deterministically — for each
task, implementer → spec-compliance → quality, **each a separate
`agent()` call** dispatching the surviving phase agent-types
(`implementer`, `spec-reviewer`, `quality-reviewer`, and `tester` for
the E2E phase). The inter-phase re-loop, the aggregation, and the
DONE/PARTIAL/BLOCKED verdict are code in the script, not
natural-language reasoning between dispatches.
This replaces the former `implement-orchestrator` agent, which carried
the loop as inline role-switches inside one subagent context because
Claude Code forbids nested-subagent dispatch. A Workflow orchestrates
from the top level, so that workaround is retired: each phase is now a
real, **independently-invokable** agent call, and the routing keys on
the structured verdict each agent reports (which encodes the project's
real build/test outcome). The four phase agents survive unchanged as the
agent-types the script dispatches; only the dispatch/aggregation prose
the orchestrator-agent carried is gone.
All work lives in the working tree: code edits, the stats file, and —
on PARTIAL/BLOCKED — `BLOCKED.md` at the repo root. The script **never
commits** and never moves main HEAD. The orchestrator reads one
end-report (the workflow's return value), inspects the unstaged tree,
and decides commit shape.
## When to Use / Skipping
Triggers:
- A plan exists under `docs/plans` (standard mode → `implement-loop`
with `mode: "standard"`).
- A `debug` (RED test + cause) or `tdd` (RED executable-spec) handoff
for the GREEN side (mini mode → `implement-loop` with `mode: "mini"`).
**Never skipped when there is plan-driven or RED-handoff code to ship.**
### The compiler-driven arm (light path)
A **behaviour-preserving type/signature edit at a definition site**
one the type checker enumerates across N sites, e.g. a carrier/newtype
narrowing or a constructor rename propagated mechanically — does NOT go
through the full per-task loop. It is the `compiler-driven` selector
arm (see `../boss/SKILL.md` "Entry-path reflection"), routed here by a
**positive** trigger matched on the edit's signature, not reached by
elimination.
Its executor is **observe-then-bounce**, shipped as
`workflows/compiler-driven-edit.js`:
1. Make the edit; propagate it mechanically across the sites the build
flags. Introduce no new behaviour; add or weaken no test.
2. **Run the project's real build + full suite.** This run — not an
ex-ante guess that the change is "behaviour-preserving" — is the
oracle.
3. **Done-signal: an edit actually landed AND clean build AND suite
green unchanged → the edits sit unstaged for the orchestrator to
commit.** That conjunction is the whole gate; it keeps the light path
light without letting a green-but-wrong change through. The
"edit actually landed" conjunct is load-bearing: `clean build AND
suite green` is *vacuously* true on an empty tree, so a no-op edit
would otherwise read as DONE. A clean working tree after the edit
phase is a no-op, not a success → distinct `BLOCKED` (no-op-edit),
never DONE. Positive evidence (tree differs from HEAD) is required,
checked both at the edit phase and independently at verify. (A
malformed or missing carrier is likewise a distinct `BLOCKED`
(bad-carrier) before any agent runs — never a bounce.)
4. **Bounce** otherwise — the change was not purely behaviour-preserving
after all, so route up per the straddle rule: a site that forces a
design decision → `specify`; a site that turns out to **encode new
behaviour** pinnable as one assertion → `tdd`, RED-first; the suite
not green-unchanged (a regression surfaced) → `debug`, RED-first. The
fallible ex-ante guess becomes a cheap ex-post detection.
A truly trivial mechanical edit (a one-line typo, a rename across a
handful of files) the orchestrator MAY still do inline without running
the workflow — but the **same done-signal binds**: clean build + suite
green unchanged, or it bounces. No review-and-commit discipline is shed;
the orchestrator still inspects and commits.
**Hard gate — observed bugs never enter this arm.** An observed bug is
`debug`'s job (RED-first), even when the fix is a one-line type edit.
The selector places the observed-bug check *before* the type-edit arm
for exactly this reason; "the fix is mechanical" must not reroute a
regression around the RED-first gate.
## The Iron Law
```
THE LOOP NEVER COMMITS. CODE EDITS, TESTS, AND THE STATS FILE LIVE IN THE WORKING TREE AS UNSTAGED CHANGES UNTIL THE ORCHESTRATOR COMMITS THEM.
WITHIN THIS LOOP, MAIN HEAD IS SACROSANCT — NO RESET, NO REVERT; THE LOOP ONLY EVER WRITES UNSTAGED CHANGES. MAIN MOVES FORWARD ONLY VIA ORCHESTRATOR COMMITS. (THE `/boss` ROLLBACK SANDBOX WINDS BACK ALREADY-COMMITTED AUTONOMOUS WORK — A BOSS-LEVEL RECOVERY, NOT A STEP IN THIS LOOP.)
NO IN-LOOP AGENT EVER DISCARDS WORKING-TREE STATE VIA `git checkout` / `git restore` — WHOLE-FILE OR `--patch`: NOTHING COMMITS BETWEEN TASKS, SO A SHARED FILE CARRIES EARLIER TASKS' UNCOMMITTED DONE WORK, AND A CHECKOUT SILENTLY DESTROYS IT (issue #23). RECOVERY IS FORWARD — EDIT BACK, OR REPORT `BLOCKED`. (THE ORCHESTRATOR'S OWN END-OF-ITERATION `git checkout -- .` DISPOSITION HAPPENS OUTSIDE THE LOOP.)
PER-TASK PHASES ARE SEPARATE AGENT CALLS IN THE WORKFLOW SCRIPT — implementer, then spec-compliance, then quality. SPEC COMPLIANCE IS GATED BEFORE QUALITY.
TASKS RUN SEQUENTIALLY; A BLOCKED TASK STOPS THE LOOP — NO SKIP-AHEAD (TASK ORDERING DEPENDENCIES ARE UNKNOWN).
NEVER PUSH PAST `BLOCKED` BY HAND.
ON `PARTIAL` OR `BLOCKED`, THE SCRIPT WRITES `BLOCKED.md` AT THE REPO ROOT — UNCOMMITTED BY CONVENTION. ON `DONE`, NO SEPARATE FILE. (EXCEPTIONS THAT WRITE NO `BLOCKED.md`: A NO-OP ITERATION — ITS TREE IS ALREADY CLEAN — AND ANY PRE-LOOP EARLY EXIT (MALFORMED CARRIER, DIRTY-TREE PREFLIGHT, PLAN-EXTRACT GUARD): NOTHING RAN AND NOTHING WAS WRITTEN, SO THE STATUS + `infra:`-PREFIXED REASON RIDE THE END-REPORT AND THERE IS NOTHING TO CLEAN UP OR EXPLAIN.)
`DONE` REQUIRES POSITIVE EVIDENCE THAT AN EDIT LANDED — A NO-OP ITERATION (`git status --porcelain` EMPTY, untracked files INCLUDED) IS NEVER `DONE`, IT IS `BLOCKED` (the issue #11 vacuous-green shape, guarded on the main path). THE VERDICT IS THE GIT TREE COUNT, NEVER A SELF-REPORT.
IN MINI MODE THE RED->GREEN IS OBSERVED, NOT ASSERTED: AN INDEPENDENT VERIFY RE-RUNS THE HANDED-OFF RED TEST AND THE SUITE; A STILL-RED TEST OR A REGRESSION IS `BLOCKED` (route back to debug, RED-first).
THE COMPILER-DRIVEN ARM COMMITS ONLY ON CLEAN BUILD + SUITE GREEN UNCHANGED; ELSE IT BOUNCES (specify for a design hole, tdd for discovered new behaviour, debug for a regression).
```
## Per-task loop mechanics
The script runs, per task: implementer phase, then spec-compliance,
then quality — gated in that order. Each is a separate `agent()` call
with a structured-output schema, so the script branches on a real
verdict. The re-loop limit is **2 repair retries per failure-mode per
task; the 3rd unresolved attempt escalates to `BLOCKED`** — except that
a quality round whose findings are all `Minor`, arriving after at least
one repair dispatch, surfaces them as concerns and closes the task
instead of gating (cosmetic residue must not discard a green task). A
`non_compliant` / `changes_requested` verdict re-dispatches the
`implementer` with the review findings as the repair brief; `unclear`
task text → `spec-ambiguous` BLOCKED; a tooling failure → `infra`
BLOCKED. This logic lives as the loops in
`workflows/implement-loop.js` — the single source; it is deliberately
not restated as prose elsewhere, so the two cannot drift.
**Positive-evidence precondition on `DONE`** (issue #12 — the issue #11
vacuous-green shape on the main path). `build green AND suite green` is
not enough: it is satisfiable by doing nothing. So `DONE` also requires
positive evidence that an edit landed. The verdict is taken from **git
ground truth, never a self-report** — a self-report must never be able to
*fail* a run that actually did the work:
- **Iteration gate (standard mode)** — a dedicated `tree-check` agent runs
`git status --porcelain | wc -l` after the per-task loop but **before**
E2E and finalize. A `DONE` outcome with a zero count is a no-op iteration
`BLOCKED`. `git status --porcelain` (not `git diff HEAD`) is used so a
brand-new **untracked** file — the implementer leaves edits unstaged —
still counts; running before E2E/finalize keeps fixtures and the
stats/`BLOCKED.md` artefacts from inflating it; and a non-returning agent
is treated as `BLOCKED` (infra), never waved through. A clean-tree no-op
writes no `BLOCKED.md` (nothing to clean up — see Step 4).
- **Mini mode gate** — an independent `mini-verify` agent re-runs the
handed-off RED test and the suite and checks the tree is dirty
(`git status --porcelain`); a still-red test, a regression, or a clean
tree is `BLOCKED`. The GREEN is observed, not taken from the
implementer's self-report.
- **Per-task `applied_changes`** (self-report, OR-ed across the initial
dispatch and every repair) does NOT gate the outcome — a self-report must
never be able to *fail* a run that actually did the work (e.g. the initial
implementer no-ops but a repair writes the files). It feeds only a neutral
concern when a task wrote nothing (it may be genuinely already-satisfied —
the orchestrator weighs it).
The E2E phase's `status` is also read now (not just its fixtures): a
non-`DONE` E2E status or a zero-fixture run surfaces as a concern.
**Cross-task discard guard** (issue #23). In a multi-task run nothing
commits between tasks, so one task's file-level `git checkout`/`git
restore` can silently destroy an earlier task's DONE work in a shared
file — invisible to the reviewers (scoped to the current task's
footprint) and to the tree-count gate (the surviving changes keep the
count nonzero). The loop therefore records a snapshot boundary after
every task (`git stash create` — a dangling commit; HEAD, index, and
working tree untouched) and hard-`BLOCK`s when a path that was
HEAD-modified at one boundary is HEAD-identical at the next: the
end-report names the lost paths, the boundary, and the recovery
snapshot sha the discarded content can be read from
(`git show <sha>:<path>`). Coarse by design, in both directions: a
checkout followed by re-edits of the same file, or a `--patch`-level
restore of a single hunk, escapes the comparison (the path never
leaves the diff) — the snapshot still keeps any loss diagnosable and
recoverable; conversely a task that legitimately edits a shared file
back to byte-identical HEAD content trips the guard, so a tripped
verdict means *adjudicate against the snapshot*, not proven malice.
Renames do not trip it (the boundary diff runs `--no-renames`, so a
renamed file's old path stays listed as a deletion). Untracked files
sit outside the threat model (`git checkout -- <path>` cannot discard
them). Cost: one extra sonnet/medium snapshot call per task of a
multi-task run; single-task and mini runs skip the guard. The
detection details live in `workflows/implement-loop.js`, the single
source.
## The Process — orchestrator side
### Step 1 — Run the `implement-loop` workflow
Before invoking: ensure the working tree is clean
(`git status --porcelain` empty). The workflow's preflight refuses to
start on a dirty tree.
For a standard iteration:
```
Workflow({ name: "implement-loop", args: {
mode: "standard",
iter_id: "<iter_id>",
plan_path: "<path under docs/plans>",
task_range: [3, 8] // optional
}})
```
For a RED-first handoff from `debug` or `tdd` (mini mode):
```
Workflow({ name: "implement-loop", args: {
mode: "mini",
iter_id: "bugfix-<short-symptom>", // tdd: "feat-<short-behaviour>"
red_test_path: "<absolute path>",
cause_summary: "<1-2 sentences from debugger>", // tdd: the desired-behaviour line
constraint: "minimal fix, no surrounding cleanup" // tdd: "minimal feature, no surrounding scope"
}})
```
The carrier fields are defined authoritatively at the top of
`workflows/implement-loop.js`. The load-bearing `iter_id` rule is there
too: it names the scratch dir and the stats filename, NOT a branch
(the loop creates no branch of its own; it runs in whatever checkout
the session sits in — under the worktree convention, the run's
worktree branch).
### Step 2 — Read the end-report
The workflow returns a small structured end-report (its return value):
`status`, `iter_id`, `started_from`, `tasks_total` / `tasks_completed`,
per-task summaries, concerns, E2E fixtures, the stats path, and — on
PARTIAL/BLOCKED — the blocked detail. Per-task chatter stayed inside
the workflow's agent contexts and never reached the orchestrator. Read
the end-report; it is the per-task summary you build the commit body
from. An **infra early-exit** (malformed or missing carrier, dirty-tree
preflight, a plan-extract guard) returns a minimal report instead:
`status: BLOCKED` plus an `infra:`-prefixed `reason`, no `blocked_detail`
and none of the per-task fields — nothing ran.
### Step 3 — Orchestrator inspect + commit step (on DONE)
The workflow returns with code edits and the stats file sitting in the
working tree as unstaged changes. Nothing is committed yet, and there
is no `BLOCKED.md` (DONE never writes one).
1. Inspect: `git status` and `git diff` — confirm the changes match
what the end-report claims. The end-report is the per-task summary;
use it as the basis for the commit body.
2. Decide commit shape — by default one cohesive commit for the whole
iter; split into a few logical commits only if the diff genuinely
covers multiple unrelated changes. Per-task commit splitting is NOT
a goal; the iter is the unit of consistency the orchestrator is
committing to.
3. Write the commit body. It carries everything a future reader needs
that the diff itself does not: the *why*, the alternatives
considered and rejected, the verification steps run, and any
concerns that remain. Detail-fill comes from the end-report.
A concern phrased as a *held* or *unresolved quality finding* (the
per-task quality gate kept a plan-prescribed element against a
reviewer finding, or a repair was a no-op) is the gate handing you
the judgement it could not make in code: verify that finding by
hand against the diff before committing — do not auto-sign past it,
even under `/boss`. Keep it if it is a principled plan-hold; if it
is an unaddressed defect, send the iter back rather than commit.
4. Stage + commit the code edits and the stats file.
5. If the trigger is done-state and the user is away, run the project's
configured notification command per `../boss/SKILL.md` "Done-state
notifications".
### Step 4 — Orchestrator handling (on PARTIAL or BLOCKED)
The workflow returns with whatever work-in-progress it managed plus
`BLOCKED.md` at the repo root carrying the diagnostic. Nothing is
committed. The orchestrator decides what to do with the dirty working
tree:
0. **No-op `BLOCKED` special case** — if the end-report's status is
`BLOCKED` with `blocked_md` null and a no-op `blocked_detail` (the reason
names a no-op iteration or a clean-tree no-op fix; `files_touched` is 0 in
standard mode, null in mini), there is **no `BLOCKED.md`** and the tree is
already clean. The diagnostic is in the end-report's `blocked_detail`, not
a file. Nothing to clean up or stash; re-plan or re-run from the same
clean tree.
The same no-file shape occurs for an **infra early-exit** (`reason`
prefixed `infra:` — malformed/missing carrier, dirty preflight tree, a
plan-extract guard): no `BLOCKED.md`, no `blocked_detail`, and the tree
exactly as the loop found it. Fix the invocation (or clean the tree) and
re-invoke.
1. Read `BLOCKED.md``## What did not` names the failure mode and the
blocked task's reason. Read `git diff` to see what was attempted.
2. Decide:
- **Repair:** keep the working-tree changes in place (or `git stash`
them if a clarifying read of clean main is needed first). Adjust
plan or extend context; **delete `BLOCKED.md`** (`rm BLOCKED.md`)
before re-running the workflow — its preflight clean-tree check
counts the file as dirt. Either stash everything and re-run on a
clean tree, or commit the known-good subset, then `rm BLOCKED.md`,
then re-run.
- **Discard:** `git checkout -- .` to drop unstaged file changes;
`git clean -fd` / `rm BLOCKED.md` plus anything else new. main HEAD
does NOT move.
- **Escalate:** ask the user via the configured notification
command. `BLOCKED.md` sits in the working tree until the
conversation resumes.
Within the `implement` loop the orchestrator does not `git reset` or
`git revert` on main: there is nothing on main to undo — the workflow
never commits, it only writes unstaged working-tree changes. (The one
place autonomous work is ever wound back is the `/boss` rollback sandbox,
which hard-resets *already-committed, unpushed* autonomous commits above
the session anchor on a dead-ended run — a boss-level recovery, not a
step in this loop. See `../boss/SKILL.md` § Direction freedom.)
## Handoff Contract
`implement` consumes:
| Source | Carrier |
|--------|---------|
| from `planner` | path to plan under `docs/plans` (+ optional `task_range`) → `implement-loop` standard mode |
| from `debug` / `tdd` | RED-test path + cause/spec summary + minimal-change constraint → `implement-loop` mini mode |
| from the `compiler-driven` selector arm | a type/signature edit + its definition site → `compiler-driven-edit` workflow |
`implement` produces: an unstaged working tree containing the code
edits and the stats file; on PARTIAL/BLOCKED, also `BLOCKED.md` at the
repo root. The orchestrator inspects, commits the code + stats (DONE)
or repairs/discards (PARTIAL/BLOCKED). The `compiler-driven-edit`
workflow produces an unstaged edit on `DONE`, or a `BOUNCE` verdict
naming `specify` / `debug`. No further hand-off — `audit` runs
independently at cycle close.
## Common Rationalisations
| Excuse | Reality |
|--------|---------|
| "Single task, running a whole workflow exceeds the work" | The workflow IS the discipline. Invoking it is cheap; the gated per-task phases and the working-tree isolation are the value. For a genuinely trivial mechanical edit, the inline compiler-driven path exists — but its done-signal still binds. |
| "Let me have the workflow commit the per-task work, it's cleaner" | The workflow never commits. Orchestrator-only commit is a project-wide rule: only the orchestrator decides when a state is consistent enough to enter main history. |
| "Per-task commits would help bisection later" | The workflow's per-task phases are review gates, not bisection points. Iter-level commits are the bisection unit — and they only exist if the whole iter passes review. |
| "BLOCKED end-report, let me dig into BLOCKED.md and continue myself" | Read the `## What did not` section first. The loop stopped at the re-loop limit for a reason. Continuing by hand undoes the discipline. |
| "End-report says PARTIAL with 4/5 tasks DONE — close enough, commit them" | The 5th task may carry an invariant the earlier 4 silently depend on. Either re-run for the missing task or `git checkout -- .` and re-plan. |
| "BLOCKED.md feels redundant — the end-report already has the detail" | The end-report dies when the chat scrolls; `BLOCKED.md` sits in the working tree across pauses and inspection rounds. It is the durable handoff. |
| "The compiler-driven edit built fine, ship it without running the suite" | Clean build is half the done-signal. Suite-green-UNCHANGED is the other half — it is what catches a behaviour change the build can't see. No suite run, no commit. |
| "The compiler-driven suite went red but the fix is one line — I'll just fix it here" | A red suite means the edit was not behaviour-preserving. That is an observed regression → bounce to `debug`, RED-first. Patching it inline reintroduces exactly the green-but-wrong path the bounce prevents. |
| "Nested-subagent dispatch is back via workflows, so an agent can spawn agents" | No. The Workflow orchestrates from the TOP level; the agents it spawns still cannot spawn further agents. The platform constraint is unchanged — the workflow simply does not need nesting. |
## Red Flags — STOP
- Orchestrator dispatching `implementer` directly outside the workflow
(bypassing the gated loop).
- Orchestrator running `git reset` or `git revert` on main.
- The workflow (or any agent it spawns) running `git commit`.
- Two `implement-loop` runs overlapping on the same working tree.
- `BLOCKED.md` staged or committed by anyone.
- A compiler-driven edit committed without a clean build AND an
unchanged-green suite.
- An observed bug routed to the compiler-driven arm instead of `debug`.
## Cross-references
- **Workflows dispatched:**
- `workflows/implement-loop.js` — the per-task loop (standard + mini).
Single source for the carrier contract, the re-loop limit, the
stats schema, the `BLOCKED.md` template, and the end-report shape.
- `workflows/compiler-driven-edit.js` — the observe-then-bounce light
path for a behaviour-preserving type/signature edit.
- **Phase agent-types the workflow dispatches** (they survive the
migration; the inline-role-switch orchestrator-agent does not):
- `agents/implementer.md` — implementer phase (TDD discipline)
- `agents/spec-reviewer.md` — spec-compliance phase
- `agents/quality-reviewer.md` — quality phase
- `agents/tester.md` — E2E coverage phase
- **Selector that routes here:** `../boss/SKILL.md` "Entry-path
reflection" — the `compiler-driven` arm names this executor, and this
file names that arm. They are co-located on purpose: a future edit
re-routing the light path back through the full loop changes both
cross-references and is a visible regression.
- **Why the substrate, not nested subagents.** Claude Code still
forbids a subagent from spawning subagents. A Workflow script
orchestrates from the top level, so the per-task phases run as its own
agent calls without nesting — see `../docs/design.md` § Plugin layer.
- **Input sources:** `../planner/SKILL.md` (plans), `../debug/SKILL.md`
and `../tdd/SKILL.md` (RED-test handoff for mini mode).
- **Output target:** orchestrator reads the end-report, inspects, and
commits; `../audit` runs at cycle close.