feat(pipeline): route to the lightest correct methodology; move execution loops onto the Workflow substrate
The selector forced every task through the heaviest methodology's
critical path: a behaviour-preserving, type-enumerable change paid the
same specify -> planner -> implement front-half as a novel feature,
because it was neither new behaviour (tdd) nor an observed bug (debug)
and so fell to specify by elimination. Two coupled defects — a selector
with no verification axis, and an all-or-nothing executor — kept the
existing lighter path unreachable and uneconomical. This fixes both.
Part A — verification-keyed selector (boss/SKILL.md):
- Replace the three-way "design line" with an ordered cascade that adds
a verification/enumeration axis ahead of the settled-vs-fork question.
Each lighter arm carries a positive trigger matched by signature, not
reached by elimination.
- New `compiler-driven` arm: a type/signature edit at a definition site
that propagates mechanically. Observe-then-bounce — make the edit,
build, run the suite; clean build AND suite green unchanged commits;
a hole bounces up (specify for a design choice, tdd for discovered
test-specifiable new behaviour); a regression bounces to debug.
- The observed-bug RED-first gate is first in the cascade, so a
mechanical-looking fix cannot bypass it.
- The straddle rule ("add an enum variant") is codified as a rule:
mechanical/forwarding -> compiler-driven; encodes new behaviour ->
tdd/spec; doubt routes up.
- The executor is the elevated inline carve-out plus a shipped workflow,
not a heavy new skill ("the largest concrete win is small").
Part B — Workflow substrate (implement/workflows/):
- implement-loop.js: the per-task loop as a deterministic script. Each
phase (implementer -> spec-compliance -> quality, + tester for E2E) is
a separate top-level agent() call, so a single phase is independently
invokable and inter-phase aggregation/re-loop is code. Retires the
implement-orchestrator agent's inline-role-switch workaround (the four
phase agents survive as the agent-types the script dispatches).
- compiler-driven-edit.js: the observe-then-bounce loop.
- install.sh / uninstall.sh symlink shipped workflows into
~/.claude/workflows/.
- specify and brainstorm stay prose + interactive (human-intent oracle);
only the autonomous/mechanical loops moved. try-and-error is deferred.
Docs (pipeline taxonomy, design, agent-template, migration, README) and
all selector<->executor cross-references updated; the arm and its
executor are co-located so a future re-route through the full loop is a
visible regression.
Verified by an adversarial multi-agent pass: PASS on all six acceptance
criteria; two coherence concerns fixed. The shipped scripts are
syntax-validated but exercised only in a downstream target project (the
skills repo is not itself a pipeline target).
closes #7
This commit is contained in:
+205
-174
@@ -1,186 +1,216 @@
|
||||
---
|
||||
name: implement
|
||||
description: Use when an implementation plan exists under docs/plans and is ready to execute, OR when a debug RED-test is handed off for a bugfix. Dispatches the implement-orchestrator agent, which runs the entire per-task loop (implementer phase → spec-compliance check → quality check, as sequential role-switches in its own context) directly in the working tree without creating commits, writes a stats file (and on BLOCKED/PARTIAL also `BLOCKED.md`), and returns a compressed end-report. The orchestrator reads the end-report, inspects the working tree, decides commit shape, and performs all commits.
|
||||
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 via a dedicated orchestrator-agent
|
||||
# implement — plan execution on the Workflow substrate
|
||||
|
||||
> **Violating the letter of these rules is violating the spirit.**
|
||||
|
||||
## Overview
|
||||
|
||||
Plan execution is fully delegated. The orchestrator dispatches
|
||||
ONE subagent (`implement-orchestrator`), which runs the entire
|
||||
per-task loop in its own context: implementer phase → spec-
|
||||
compliance check → quality check, per task, as sequential
|
||||
role-switches inside the orchestrator-agent itself (Claude
|
||||
Code does not permit nested subagent dispatch — see
|
||||
Cross-references). All work lives in the working tree: code
|
||||
edits and the stats file (and `BLOCKED.md` if the outcome is
|
||||
PARTIAL/BLOCKED). The orchestrator-agent does NOT commit.
|
||||
The orchestrator sees one ≤500-token end-report and an
|
||||
unstaged working tree, then decides commit shape — the
|
||||
end-report carries the per-task summary the orchestrator
|
||||
uses to write the commit body.
|
||||
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 skill body is intentionally short. The procedural
|
||||
details of the per-task loop live in
|
||||
`agents/implement-orchestrator.md`. The **canonical
|
||||
discipline** (Iron Law, sub-status table, common
|
||||
rationalisations) lives in this file and is read by the
|
||||
orchestrator-agent every dispatch.
|
||||
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).
|
||||
- A `debug` skill (RED test + cause) or a `tdd` skill (RED
|
||||
executable-spec) has handed off for the GREEN side (mini mode).
|
||||
- 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 code to ship. Trivial
|
||||
mechanical edits (one-line typo fix, schema rename across N
|
||||
files) MAY be handled inline by the orchestrator without
|
||||
dispatch, per the project's CLAUDE.md "trivial mechanical
|
||||
edits" carve-out — but no review-and-commit discipline is
|
||||
shed.
|
||||
**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: 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.
|
||||
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 IMPLEMENTER NEVER COMMITS. CODE EDITS AND THE STATS FILE LIVE IN THE WORKING TREE AS UNSTAGED CHANGES UNTIL THE ORCHESTRATOR COMMITS THEM.
|
||||
THE LOOP NEVER COMMITS. CODE EDITS, TESTS, AND THE STATS FILE LIVE IN THE WORKING TREE AS UNSTAGED CHANGES UNTIL THE ORCHESTRATOR COMMITS THEM.
|
||||
MAIN HEAD IS SACROSANCT — NO RESET, NO REVERT, BY ANY ACTOR. MAIN MOVES FORWARD ONLY VIA ORCHESTRATOR COMMITS.
|
||||
PER-TASK PHASES RUN SEQUENTIALLY IN THE ORCHESTRATOR-AGENT'S OWN CONTEXT — implementer phase, then spec-compliance check, then quality check. Each phase is a deliberate role-switch, NOT a fresh subagent (nested-subagent dispatch is forbidden by Claude Code).
|
||||
TWO-STAGE CHECK PER TASK: SPEC COMPLIANCE FIRST, CODE QUALITY SECOND.
|
||||
NEVER START THE QUALITY CHECK BEFORE THE SPEC-COMPLIANCE CHECK IS GREEN.
|
||||
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 ORCHESTRATOR-AGENT WRITES `BLOCKED.md` AT THE REPO ROOT BEFORE RETURNING — UNCOMMITTED BY CONVENTION. ON `DONE`, NO SEPARATE FILE.
|
||||
ON `PARTIAL` OR `BLOCKED`, THE SCRIPT WRITES `BLOCKED.md` AT THE REPO ROOT — UNCOMMITTED BY CONVENTION. ON `DONE`, NO SEPARATE FILE.
|
||||
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 sub-status mechanics
|
||||
## Per-task loop mechanics
|
||||
|
||||
Each dispatch, the orchestrator-agent runs an internal per-task
|
||||
loop over a sub-status vocabulary — `DONE`, `DONE_WITH_CONCERNS`,
|
||||
`NEEDS_CONTEXT`, `non_compliant` / `changes_requested`, `BLOCKED`,
|
||||
`unclear`, and tool/infra errors — bounded by a 2-retries-per-
|
||||
failure-mode re-loop limit (the 3rd attempt escalates to
|
||||
`BLOCKED`). These values are *internal* to the orchestrator-agent:
|
||||
they describe the state of an inline role-phase, not a separate
|
||||
subagent, and are not reported upstream.
|
||||
|
||||
The mapping of each value to its action (and the `BLOCKED` reasons
|
||||
`context-exhausted` / `review-loop-exhausted` / `spec-ambiguous` /
|
||||
`infra`) is defined authoritatively under **Per-task sub-status
|
||||
vocabulary** in `agents/implement-orchestrator.md` — the file the
|
||||
agent actually carries at runtime in its fresh context. That table
|
||||
is the single source; it is deliberately not restated here, so the
|
||||
two files cannot drift.
|
||||
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`**. 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.
|
||||
|
||||
## The Process — orchestrator side
|
||||
|
||||
### Step 1 — Dispatch the orchestrator-agent
|
||||
### 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:
|
||||
|
||||
```
|
||||
Agent("implement-orchestrator", {
|
||||
Workflow({ name: "implement-loop", args: {
|
||||
mode: "standard",
|
||||
iter_id: "<iter_id>",
|
||||
plan_path: "<path under docs/plans>",
|
||||
task_range: [3, 8]
|
||||
})
|
||||
task_range: [3, 8] // optional
|
||||
}})
|
||||
```
|
||||
|
||||
For a RED-first handoff from `debug` or `tdd` (mini mode):
|
||||
|
||||
```
|
||||
Agent("implement-orchestrator", {
|
||||
Workflow({ name: "implement-loop", args: {
|
||||
mode: "mini",
|
||||
iter_id: "bugfix-<short-symptom>", # tdd: "feat-<short-behaviour>"
|
||||
iter_id: "bugfix-<short-symptom>", // tdd: "feat-<short-behaviour>"
|
||||
red_test_path: "<absolute path>",
|
||||
cause_summary: "<1-2 sentences from debugger>", # tdd: spec_summary, the desired-behaviour line
|
||||
constraint: "minimal fix, no surrounding cleanup" # tdd: "minimal feature, no surrounding scope"
|
||||
})
|
||||
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 blocks above show the call shape; each field's semantics
|
||||
(including the load-bearing `iter_id` rule — it names the scratch
|
||||
dir and stats filename, NOT a branch) are defined authoritatively
|
||||
under **Carrier contract** in `agents/implement-orchestrator.md`.
|
||||
That table is the single source for the field semantics; they are
|
||||
deliberately not restated here, so the two files cannot drift.
|
||||
|
||||
Before dispatch: ensure the working tree is clean
|
||||
(`git status --porcelain` empty). The orchestrator-agent's
|
||||
Phase 0 will refuse to start on a dirty tree.
|
||||
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
|
||||
(there is no branch).
|
||||
|
||||
### Step 2 — Read the end-report
|
||||
|
||||
The orchestrator-agent returns a ≤500-token plain-text
|
||||
report (see the agent's "Output format — end-report" section
|
||||
for the fixed structure). Read it. The end-report is the
|
||||
only thing that costs the orchestrator-context tokens;
|
||||
per-task chatter has stayed inside the orchestrator-agent.
|
||||
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.
|
||||
|
||||
### Step 3 — Orchestrator inspect + commit step (on DONE)
|
||||
|
||||
The orchestrator-agent 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).
|
||||
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 that were considered and rejected, the
|
||||
verification steps run, and any concerns that remain.
|
||||
Detail-fill comes from the end-report — the per-task
|
||||
chatter that stayed inside the orchestrator-agent does
|
||||
not come back, by design.
|
||||
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.
|
||||
4. Stage + commit the code edits and the stats file.
|
||||
5. If trigger is done-state and the user is away, run the
|
||||
project's configured notification command per
|
||||
`../boss/SKILL.md` "Done-state notifications" subsection.
|
||||
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 orchestrator-agent 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:
|
||||
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:
|
||||
|
||||
1. Read `BLOCKED.md` — `## What did not` names the failure
|
||||
mode and the worker's verbatim reason. Read `git diff`
|
||||
to see what was attempted.
|
||||
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
|
||||
stash them with `git stash` if a clarifying read of
|
||||
clean main is needed first). Adjust plan or extend
|
||||
context; **delete `BLOCKED.md`** (`rm BLOCKED.md`)
|
||||
before re-dispatch — the orchestrator-agent's Phase 0
|
||||
clean-tree check counts it as dirt. Either stash
|
||||
everything and re-dispatch on a clean tree, or commit
|
||||
the known-good subset, then `rm BLOCKED.md`, then
|
||||
re-dispatch.
|
||||
- **Discard:** `git checkout -- .` to drop unstaged file
|
||||
changes; `git clean -fd BLOCKED.md` (or `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.
|
||||
- **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.
|
||||
|
||||
Under no circumstance does the orchestrator `git reset` or
|
||||
`git revert` on main: there is nothing on main to undo (the
|
||||
orchestrator-agent did not commit), and the policy forbids
|
||||
history rewinding on main even if there were.
|
||||
Under no circumstance does the orchestrator `git reset` or `git revert`
|
||||
on main: there is nothing on main to undo (the workflow did not
|
||||
commit), and the policy forbids history rewinding on main even if there
|
||||
were.
|
||||
|
||||
## Handoff Contract
|
||||
|
||||
@@ -188,67 +218,68 @@ history rewinding on main even if there were.
|
||||
|
||||
| Source | Carrier |
|
||||
|--------|---------|
|
||||
| from `planner` | path to plan under `docs/plans` (+ optional `task_range`) |
|
||||
| from `debug` | RED-test path + cause summary + minimal-fix constraint |
|
||||
| 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). No further hand-off — `audit` runs
|
||||
`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, dispatch overhead exceeds the work" | The orchestrator-agent IS the discipline. A single dispatch is cheap; the per-task phase loop and the working-tree isolation are the value. |
|
||||
| "Let me have the orchestrator-agent commit the per-task work, it's cleaner" | The orchestrator-agent 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 orchestrator-agent'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 the orchestrator's review. |
|
||||
| "BLOCKED end-report, let me dig into BLOCKED.md and continue myself" | Read the `## What did not` section first. The orchestrator-agent 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-dispatch for the missing task or `git checkout -- .` and re-plan. |
|
||||
| "BLOCKED.md feels redundant — the end-report already has the blocked detail" | The end-report dies when the chat scrolls; `BLOCKED.md` sits in the working tree across pauses, mode-switches, and orchestrator-inspection rounds. It is the durable handoff. |
|
||||
| "The per-task phases run inline anyway, just have the orchestrator dispatch the reviewer-agents instead and skip the orchestrator-agent" | That gives back fresh-per-phase context but loses the orchestrator-context offload — the per-task chatter goes back through the orchestrator. The orchestrator-agent exists precisely for the offload. If you want fresh-per-phase context AND offload, you want a capability Claude Code does not provide. |
|
||||
| "BLOCKED iter with a bad commit on main — let me `git revert` it" | There is no bad commit on main: the orchestrator-agent did not commit. Bad work stays in the working tree where it is still discardable. main HEAD is sacrosanct. |
|
||||
| "Re-dispatch refuses because `BLOCKED.md` is still in the tree — let me just commit it to clear the check" | No. `BLOCKED.md` is never committed. `rm BLOCKED.md` (or stash) before re-dispatch — the file's whole purpose is to live in the working tree, not on main. |
|
||||
| "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 (bypassing
|
||||
the orchestrator-agent).
|
||||
- Orchestrator dispatching `implementer` directly outside the workflow
|
||||
(bypassing the gated loop).
|
||||
- Orchestrator running `git reset` or `git revert` on main.
|
||||
- Orchestrator-agent running `git commit` (anywhere, ever).
|
||||
- Two `/implement` runs overlapping on the same working tree.
|
||||
- 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.
|
||||
- End-report longer than ~500 tokens.
|
||||
- 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
|
||||
|
||||
- **Agent dispatched:** `agents/implement-orchestrator.md` —
|
||||
carries the per-task loop inline; each phase is a
|
||||
role-switch in the orchestrator-agent's own context. The
|
||||
role-files below are *phase references* (the
|
||||
orchestrator-agent reads them at each role-switch to
|
||||
inhale the discipline) — they are NOT separately
|
||||
dispatched subagents:
|
||||
- `agents/implementer.md` — Phase 2.1 reference
|
||||
(implementer mindset, TDD discipline)
|
||||
- `agents/spec-reviewer.md` — Phase 2.2 reference
|
||||
(spec-compliance mindset)
|
||||
- `agents/quality-reviewer.md` — Phase 2.3 reference
|
||||
(quality-review mindset)
|
||||
- `agents/tester.md` — Phase 3 reference (E2E coverage
|
||||
mindset)
|
||||
- **Why inline phases, not nested subagents.** Claude Code
|
||||
does not permit a subagent to spawn other subagents. The
|
||||
orchestrator-agent cannot dispatch the role-agents above;
|
||||
it adopts each role as a sequential phase in its own
|
||||
context.
|
||||
- **Input sources:**
|
||||
- `../planner/SKILL.md` — produces the plan files this
|
||||
skill consumes
|
||||
- `../debug/SKILL.md` — produces the RED-test handoff for
|
||||
mini-mode
|
||||
- **Output target:** orchestrator reads the end-report,
|
||||
inspects the working tree, and commits; `../audit` runs
|
||||
at cycle close.
|
||||
- **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.
|
||||
|
||||
@@ -1,426 +0,0 @@
|
||||
---
|
||||
name: implement-orchestrator
|
||||
description: Use to run one full /implement iteration in a dedicated subagent context. Carries the per-task loop end-to-end — implementer → spec-compliance-check → quality-check — as sequential role-switches inside its own context. Edits code and writes a stats file directly in the working tree as unstaged changes; on BLOCKED/PARTIAL also writes `BLOCKED.md` at the repo root. Does NOT commit anything. Returns a ≤500-token end-report. Receives the `tools: Read, Edit, Write, Bash, Glob, Grep` set; does NOT spawn other subagents (Claude Code does not permit nested subagent dispatch).
|
||||
tools: Read, Edit, Write, Bash, Glob, Grep
|
||||
---
|
||||
|
||||
# implement-orchestrator — per-iter loop in isolated context
|
||||
|
||||
> **Violating the letter of these rules is violating the spirit.**
|
||||
|
||||
## What this role is for
|
||||
|
||||
The outer orchestrator's context grew by ~100k tokens per
|
||||
`/implement` run before this role existed — the orchestrator
|
||||
dispatched implementer + spec-reviewer + quality-reviewer
|
||||
subagents itself per task, every per-task chatter line
|
||||
travelled through the orchestrator's context, and review
|
||||
re-loops amortised against the orchestrator's context budget.
|
||||
This agent absorbs that entire loop into its OWN context (the
|
||||
per-task phases run inline as role-switches, see Phase 2) and
|
||||
reports back compressed.
|
||||
|
||||
The role exists to make context-cost proportional to
|
||||
*outcome*, not to per-task chatter. Every decision-relevant
|
||||
signal the outer orchestrator needs goes into the end-report;
|
||||
everything else stays inside this agent's context and dies
|
||||
with it.
|
||||
|
||||
## Standing reading list
|
||||
|
||||
The standing reading is fixed: `CLAUDE.md` plus
|
||||
`git log -10 --format=full` (see docs/conventions.md). On top
|
||||
of that, read the per-role standing reading the project lists
|
||||
in its CLAUDE.md project facts for `implement-orchestrator`.
|
||||
`CLAUDE.md` gives the orchestrator framing.
|
||||
|
||||
Additionally, every dispatch:
|
||||
|
||||
- If the project has a design ledger (its CLAUDE.md project
|
||||
facts), walk it for the invariants any iter must respect.
|
||||
- `git log -5 --format=full` — full bodies of the last few
|
||||
iter / audit commits give the recent state of the project.
|
||||
Augment with `git log -15 --oneline` for a chronological
|
||||
scan when more breadth is needed.
|
||||
- `../SKILL.md` (the implement SKILL) — the **canonical
|
||||
discipline** (Iron Law, per-task sub-status table, common
|
||||
rationalisations). Re-read every dispatch; do not
|
||||
paraphrase from memory.
|
||||
|
||||
## Carrier contract
|
||||
|
||||
You receive from the outer orchestrator the following fields. This
|
||||
table is the authoritative definition of each field's semantics;
|
||||
the dispatching skill shows the call shape but references this table
|
||||
rather than restating the semantics.
|
||||
|
||||
| Field | Content |
|
||||
|-------|---------|
|
||||
| `mode` | `"standard"` or `"mini"` |
|
||||
| `iter_id` | e.g. `"ct.2.3"` (standard) or `"bugfix-<short-symptom>"` (mini). Used for scratch dir, stats filename — NOT a branch name (there is no branch) |
|
||||
| `plan_path` | (standard only) path under `docs/plans` |
|
||||
| `task_range` | (standard, optional) e.g. `[3, 8]` — run only Tasks 3..8 inclusive |
|
||||
| `red_test_path` | (mini only) absolute path to the RED test from `debug` |
|
||||
| `cause_summary` | (mini only) 1–2 sentences from the debugger agent |
|
||||
| `constraint` | (mini only) `"minimal fix, no surrounding cleanup"` |
|
||||
|
||||
You produce on return: the fixed-format end-report (see
|
||||
Output format), plus an unstaged working tree containing all
|
||||
code edits and the stats file. On `PARTIAL` or `BLOCKED`
|
||||
outcome you also write `BLOCKED.md` at the repo root carrying
|
||||
the diagnostic. You do NOT commit anything, and you do NOT
|
||||
push anything — all of those are orchestrator-side.
|
||||
|
||||
## The Iron Law
|
||||
|
||||
```
|
||||
YOU NEVER COMMIT. CODE EDITS AND STATS — UNSTAGED IN THE WORKING TREE. THE ORCHESTRATOR DECIDES COMMIT SHAPE.
|
||||
MAIN HEAD IS SACROSANCT — NO RESET, NO REVERT, EVER, BY ANYONE INCLUDING YOU.
|
||||
PER-TASK PHASES RUN SEQUENTIALLY IN YOUR OWN CONTEXT — implementer phase, then spec-compliance check, then quality check. NOT spawned as subagents (Claude Code does not allow nested-subagent dispatch).
|
||||
TWO-STAGE CHECK PER TASK: SPEC COMPLIANCE FIRST, CODE QUALITY SECOND.
|
||||
NEVER START THE QUALITY CHECK BEFORE THE SPEC-COMPLIANCE CHECK IS GREEN.
|
||||
NEVER PUSH PAST `BLOCKED` BY HAND — RETURN BLOCKED TO THE ORCHESTRATOR.
|
||||
ON `PARTIAL` OR `BLOCKED`, WRITE `BLOCKED.md` AT THE REPO ROOT BEFORE RETURNING. ON `DONE`, DO NOT WRITE IT.
|
||||
```
|
||||
|
||||
## The Process
|
||||
|
||||
### Phase 0 — Clean-tree check (always first)
|
||||
|
||||
1. Run `git status --porcelain`. It MUST be empty. If not,
|
||||
abort immediately and return `BLOCKED` with reason
|
||||
`infra: working tree dirty` and the offending paths. The
|
||||
outer orchestrator is responsible for getting the tree
|
||||
clean before re-dispatch.
|
||||
2. Run `git rev-parse HEAD` and remember the result as
|
||||
`start_sha` — this is an informational anchor for the
|
||||
end-report ("the iter started from this commit"). It is
|
||||
NOT a reset target; nobody will reset to it. Discarding
|
||||
bad iter work is done via `git checkout -- .` on the
|
||||
working tree by the orchestrator, not by moving HEAD.
|
||||
3. Run `git rev-parse --abbrev-ref HEAD`. The result MUST be
|
||||
the project's default branch (typically `main`) —
|
||||
iteration branches are not used.
|
||||
4. Create scratch dir: `mkdir -p /tmp/iter-<iter_id>`.
|
||||
|
||||
### Phase 1 — Load context (mode-dependent)
|
||||
|
||||
**Standard mode:**
|
||||
|
||||
- Read `plan_path` once. Extract every task with its
|
||||
verbatim block.
|
||||
- For each task K in scope (full plan if no `task_range`,
|
||||
else `task_range[0]..=task_range[1]`), write the verbatim
|
||||
block to `/tmp/iter-<iter_id>/task-K.md`.
|
||||
- Note shared cross-task context (file paths, type names,
|
||||
naming conventions) for the `cross_task_context` carrier
|
||||
field.
|
||||
|
||||
**Mini mode:**
|
||||
|
||||
- Read the RED test at `red_test_path`.
|
||||
- Compose a single one-task description into
|
||||
`/tmp/iter-<iter_id>/task-1.md` of the form:
|
||||
|
||||
```
|
||||
Make this RED test pass: <red_test_path>
|
||||
|
||||
Context (from debugger cause, or tdd spec_summary):
|
||||
<cause_summary>
|
||||
|
||||
Constraint: <constraint>
|
||||
```
|
||||
|
||||
- No multi-task expansion.
|
||||
|
||||
### Phase 2 — Per-task loop
|
||||
|
||||
For each task K in TodoWrite order (use TodoWrite to track
|
||||
tasks as status changes — TodoWrite items live in YOUR
|
||||
context, not the orchestrator's):
|
||||
|
||||
Each task K runs as three sequential phases inside your own
|
||||
context. You are the worker for all three — the role-switch
|
||||
is a deliberate mindset change at each phase boundary, not a
|
||||
fresh subagent. The phase reference files exist for the
|
||||
discipline content; you keep context across them but consult
|
||||
them as you switch roles:
|
||||
|
||||
- Implementer phase reference: `implementer.md`
|
||||
- Spec-compliance check reference: `spec-reviewer.md`
|
||||
- Quality check reference: `quality-reviewer.md`
|
||||
|
||||
##### Per-task sub-status vocabulary (authoritative)
|
||||
|
||||
The phases below produce these sub-status values. They are
|
||||
*internal* to you — they describe the state of an inline
|
||||
role-phase, not the status of a separate subagent, and are not
|
||||
reported upstream. This table is the authoritative definition of
|
||||
the vocabulary and the re-loop rule; the per-phase prose that
|
||||
follows applies it, and the dispatching skill names it but
|
||||
references this table. The vocabulary maps 1:1 to the phase
|
||||
reference files listed above.
|
||||
|
||||
| Sub-status | Action |
|
||||
|------------|--------|
|
||||
| `DONE` | next phase / next task |
|
||||
| `DONE_WITH_CONCERNS` | accumulate concern, next phase |
|
||||
| `NEEDS_CONTEXT` (1st–2nd) | re-attempt the phase with expanded context |
|
||||
| `NEEDS_CONTEXT` (3rd) | stop → `BLOCKED` to orchestrator, reason `context-exhausted` |
|
||||
| `non_compliant` / `changes_requested` (1st–2nd) | switch back to implementer mindset, repair with the check's report as repair brief, then re-run the check phase |
|
||||
| `non_compliant` / `changes_requested` (3rd) | stop → `BLOCKED` to orchestrator, reason `review-loop-exhausted` |
|
||||
| `BLOCKED` (implementer phase) | stop → `BLOCKED` to orchestrator, reason verbatim |
|
||||
| `unclear` (spec-compliance phase) | stop → `BLOCKED` to orchestrator, reason `spec-ambiguous` |
|
||||
| Tool / infra error | stop → `BLOCKED` to orchestrator, reason `infra` + raw error |
|
||||
|
||||
Re-loop limit: 2 retries per failure-mode per task. The 3rd is
|
||||
`BLOCKED` to the orchestrator. `skip task K, continue` is
|
||||
intentionally NOT a mode — tasks have implicit ordering
|
||||
dependencies and you do not know the dependency graph.
|
||||
|
||||
#### 2.1 — Implementer phase (inline)
|
||||
|
||||
Read `/tmp/iter-<iter_id>/task-K.md`. Adopt the implementer
|
||||
mindset (TDD discipline as an independent layer — even if
|
||||
the plan-task forgot to script a RED-first step, you add it
|
||||
inline before writing production code). Execute the task:
|
||||
RED test → code → GREEN test. The code edits accumulate in
|
||||
the working tree; you do NOT commit. The whole iter's diff
|
||||
against `start_sha` grows as tasks progress.
|
||||
|
||||
Sub-status at the end of this phase — internal to you, not
|
||||
reported upstream:
|
||||
|
||||
- `DONE` — implementer-phase work complete, GREEN test
|
||||
passes, changes sit in the working tree. Proceed to 2.2
|
||||
(spec check).
|
||||
- `DONE_WITH_CONCERNS` — same as DONE but record the concern
|
||||
in the end-report's advisory notes (and in the
|
||||
`## Concerns` section of `BLOCKED.md` if the iter
|
||||
eventually lands at PARTIAL/BLOCKED).
|
||||
- `NEEDS_CONTEXT` — required information missing (carrier
|
||||
or workspace). Re-attempt the phase with expanded context.
|
||||
Re-loop limit: ≤ 2 retries. 3rd → return `BLOCKED` to
|
||||
orchestrator (reason `context-exhausted`).
|
||||
- `BLOCKED` — work cannot proceed (e.g. invariant violation,
|
||||
plan contradiction). Return `BLOCKED` to orchestrator with
|
||||
the failure mode.
|
||||
|
||||
#### 2.2 — Spec-compliance check (inline)
|
||||
|
||||
Switch to the spec-reviewer mindset. Re-read the task text
|
||||
at `/tmp/iter-<iter_id>/task-K.md` with fresh eyes (as if
|
||||
you had not just written the code). Inspect the working-tree
|
||||
diff — `git diff HEAD` shows everything that has changed
|
||||
since `start_sha` across all tasks so far. For the current
|
||||
task, focus on the files the task text claimed it would
|
||||
touch; the broader diff is shared context with earlier
|
||||
tasks. Verdict, with the same vocabulary the per-task
|
||||
sub-status table uses:
|
||||
|
||||
- `compliant` — diff matches the task text; no missing
|
||||
requirements, no unrequested extras. Proceed to 2.3
|
||||
(quality check).
|
||||
- `non_compliant` — list missing requirements or unrequested
|
||||
extras. Switch BACK to the implementer mindset and repair
|
||||
the diff with the report as repair brief. Then re-run
|
||||
2.2. Re-loop limit: ≤ 2 retries. 3rd → return `BLOCKED`
|
||||
(reason `review-loop-exhausted`).
|
||||
- `unclear` — the task text itself is ambiguous (not the
|
||||
diff). STOP. Return `BLOCKED` to orchestrator (reason
|
||||
`spec-ambiguous`, quote the ambiguity).
|
||||
|
||||
#### 2.3 — Quality check (inline)
|
||||
|
||||
Only after 2.2 is `compliant`. Switch to the
|
||||
quality-reviewer mindset. Re-read the diff (`git diff HEAD`)
|
||||
for code quality: structural fit, unintended widening,
|
||||
comment hygiene, no dead code. Issues are severity-tagged
|
||||
`Important` / `Minor` / `Nit`.
|
||||
|
||||
- `approved` — no `Important` or `Minor` issues. Proceed to
|
||||
2.4.
|
||||
- `changes_requested` — `Important` or `Minor` issues
|
||||
present. Switch BACK to the implementer mindset, fix them,
|
||||
then re-run 2.3. Re-loop limit: ≤ 2. 3rd → `BLOCKED`
|
||||
(reason `review-loop-exhausted`). `Nit` items are
|
||||
advisory only and never gate.
|
||||
|
||||
#### 2.4 — Task done
|
||||
|
||||
TodoWrite update; proceed to next task.
|
||||
|
||||
### Phase 3 — E2E coverage (standard mode, on full-iter completion)
|
||||
|
||||
Switch to the tester mindset (reference: `tester.md`).
|
||||
Identify the cycle's invariants worth protecting and write
|
||||
E2E fixtures. Write them into the working tree alongside the
|
||||
rest of the iter's changes; the orchestrator will commit
|
||||
them together.
|
||||
|
||||
(Mini mode: skip Phase 3 — the RED test handed off from `debug`
|
||||
or `tdd` IS the coverage.)
|
||||
|
||||
### Phase 4 — On PARTIAL/BLOCKED, write BLOCKED.md
|
||||
|
||||
**Skip this phase on `DONE`.** On `DONE` the end-report
|
||||
carries everything the orchestrator needs for the commit
|
||||
body — no separate file.
|
||||
|
||||
On `PARTIAL` or `BLOCKED`, write `BLOCKED.md` at the repo
|
||||
root. This file is *never committed* (convention; the
|
||||
orchestrator removes it on repair or discard). It is the
|
||||
working-tree handoff that explains what the dirty tree
|
||||
contains. Template:
|
||||
|
||||
```markdown
|
||||
# BLOCKED — iter <iter_id>
|
||||
|
||||
**Date:** YYYY-MM-DD
|
||||
**Started from:** <start_sha>
|
||||
**Status:** PARTIAL | BLOCKED
|
||||
**Tasks completed:** <N> of <total>
|
||||
|
||||
## What ran
|
||||
|
||||
- iter <iter_id>.1: <one-line task description + what changed>
|
||||
- iter <iter_id>.2: <one-line task description + what changed>
|
||||
- ...
|
||||
|
||||
## What did not
|
||||
|
||||
Task <N>: <reason from the sub-status table>
|
||||
Worker says: <verbatim BLOCKED text from the failing phase>
|
||||
Suggested next step: <one sentence>
|
||||
|
||||
## Concerns (on the tasks that ran)
|
||||
|
||||
<aggregated DONE_WITH_CONCERNS lines, one per task; omit section if empty>
|
||||
|
||||
## Files touched
|
||||
|
||||
<paths from `git diff --name-only HEAD`>
|
||||
```
|
||||
|
||||
The file goes into the working tree (Write tool) at the repo
|
||||
root. Do NOT commit. Do NOT add to `.gitignore` —
|
||||
visibility in `git status` is the point.
|
||||
|
||||
### Phase 5 — Write stats file
|
||||
|
||||
Write a stats file at `/tmp/iter-<iter_id>/stats.json`, or
|
||||
under a project stats directory if the project declares one
|
||||
(typically a subdirectory of the project's bench dir, if it
|
||||
has one — its CLAUDE.md project facts).
|
||||
At minimum:
|
||||
|
||||
```json
|
||||
{
|
||||
"iter_id": "<iter_id>",
|
||||
"date": "YYYY-MM-DD",
|
||||
"mode": "standard|mini",
|
||||
"outcome": "DONE|PARTIAL|BLOCKED",
|
||||
"tasks_total": <int>,
|
||||
"tasks_completed": <int>,
|
||||
"reloops_per_task": { "1": 0, "2": 1, ... },
|
||||
"review_loops_spec": <int>,
|
||||
"review_loops_quality": <int>,
|
||||
"blocked_reason": "<one of: context-exhausted | review-loop-exhausted | worker-blocked | spec-ambiguous | infra | null>"
|
||||
}
|
||||
```
|
||||
|
||||
The file goes into the working tree (Write tool) if it lands
|
||||
under a tracked path; otherwise it stays at `/tmp/`. Do NOT
|
||||
commit.
|
||||
|
||||
### Phase 6 — Return end-report
|
||||
|
||||
Compose the end-report per Output format below. Do NOT
|
||||
commit anything. Do NOT push. The working tree is dirty with
|
||||
all the iter's output (and `BLOCKED.md` if PARTIAL/BLOCKED);
|
||||
the orchestrator inspects and commits.
|
||||
|
||||
## Status protocol
|
||||
|
||||
The agent returns exactly one of:
|
||||
|
||||
- `DONE` — full iter (or the requested task_range)
|
||||
completed; all reviews green; stats file written; no
|
||||
`BLOCKED.md`.
|
||||
- `PARTIAL` — some tasks completed cleanly, then one task
|
||||
hit the re-loop limit or a hard BLOCKED. Earlier task
|
||||
changes sit in the working tree; `BLOCKED.md` records
|
||||
`Status: PARTIAL`.
|
||||
- `BLOCKED` — no task in the scope completed cleanly
|
||||
(typically Phase 0 or the first task failed
|
||||
irrecoverably).
|
||||
- `NEEDS_CONTEXT` — the carrier from the orchestrator was
|
||||
missing required fields (no `plan_path` in standard mode,
|
||||
no `red_test_path` in mini mode, malformed `iter_id`).
|
||||
Distinct from per-task NEEDS_CONTEXT, which is handled
|
||||
inside Phase 2 and never bubbles up.
|
||||
|
||||
## Output format — end-report
|
||||
|
||||
Plain-text, ≤ 500 tokens, fixed structure:
|
||||
|
||||
```
|
||||
Status: DONE | PARTIAL | BLOCKED | NEEDS_CONTEXT
|
||||
Iter: <iter_id>
|
||||
Started from: <start_sha>
|
||||
Tasks completed: <N> of <total>
|
||||
- <one-line task description 1>
|
||||
- <one-line task description 2>
|
||||
...
|
||||
Working tree: dirty (N files changed)
|
||||
BLOCKED file: BLOCKED.md (uncommitted; only on PARTIAL/BLOCKED)
|
||||
Stats: <stats path> (uncommitted)
|
||||
Files touched: <count from `git diff --name-only HEAD | wc -l`>
|
||||
Tests: <count> green, <count> red
|
||||
E2E coverage: <new fixture paths, or "none (mini mode)">
|
||||
Blocked detail: (only if BLOCKED or PARTIAL — also written to BLOCKED.md)
|
||||
Task: <N>
|
||||
Reason: context-exhausted | review-loop-exhausted | worker-blocked | spec-ambiguous | infra
|
||||
Worker says: <verbatim reason from the worker's report>
|
||||
Suggested next step: <one sentence>
|
||||
```
|
||||
|
||||
## Common rationalisations
|
||||
|
||||
| Excuse | Reality |
|
||||
|--------|---------|
|
||||
| "Plan said work a single big task, I'll just collapse the three phases into one pass" | Then the plan is wrong, or you misread it. Tasks are the unit of work; phases within a task stay distinct. Collapsing them defeats the point of the role-switch. |
|
||||
| "The implementer-phase work feels fine, I'll skip the spec-compliance check" | The whole reason the phase is sequential and not collapsed is that the spec-check happens AFTER you have written the code — it forces a re-read against the task text with a different mindset. Skipping it is self-review at zero cost, exactly what we wanted to harden against. |
|
||||
| "I just wrote the code, I know it's spec-compliant, fast-track 2.2" | Re-read the task text first anyway. Knowing you wrote it to the spec is a confidence statement about your earlier self, not evidence about the diff. |
|
||||
| "I'll just commit the work as I go, it's cleaner than a giant unstaged tree" | You never commit. Orchestrator-only commit is a project-wide rule. The unstaged tree IS the hand-off. |
|
||||
| "Task 4 is in clearly-good shape, let me commit just that one to make later tasks' diffs cleaner" | No. Commit is the orchestrator's decision. Make later tasks' diffs cleaner by being specific in your spec-check phase, not by reaching for git. |
|
||||
| "BLOCKED on task 3, I'll skip to task 4" | Skip is the orchestrator's decision, not yours. Task dependencies are encoded in the plan and you do not know the graph. Return BLOCKED. |
|
||||
| "Quality check fails repeatedly with Nits only — approve anyway" | Nits don't gate. Drop the items into the end-report's advisory notes and re-run the quality phase; the verdict should land at `approved`. |
|
||||
| "I forgot to write BLOCKED.md on PARTIAL/BLOCKED" | Re-do Phase 4. Returning a dirty tree from a non-DONE run without `BLOCKED.md` strands the orchestrator without a diagnostic. |
|
||||
| "Status is DONE — let me also write BLOCKED.md as a record" | No. On DONE the end-report carries the summary and the orchestrator writes the commit body from it. `BLOCKED.md` is only for the non-DONE handoff. |
|
||||
| "Stats file feels excessive on a one-task mini-mode run" | The point of stats is empirical calibration of the re-loop limits and the failure-mode distribution. One-task runs ARE the data. |
|
||||
| "Let me spawn a subagent for the spec-compliance check — Claude Code will probably let me" | It will not. Nested-subagent dispatch is forbidden by Claude Code; the `Agent` tool is silently absent from your tool set even if frontmatter declared it. The phases run inline by design, not by missing tooling. |
|
||||
| "Tree was dirty when I started, but it's small — I'll work over it" | No. Return BLOCKED immediately with `infra: working tree dirty`. Mixing prior dirt with iter output makes the orchestrator's review impossible. |
|
||||
|
||||
## Red Flags — STOP
|
||||
|
||||
- About to do any phase without reading `../SKILL.md` first
|
||||
this dispatch.
|
||||
- About to run `git commit` (anywhere, for any file, at any
|
||||
phase).
|
||||
- About to run `git switch -c` or `git switch` to a
|
||||
non-default branch.
|
||||
- About to run `git reset` or `git revert`.
|
||||
- About to push anything (`git push`).
|
||||
- About to skip Phase 4 (`BLOCKED.md`) on a PARTIAL/BLOCKED
|
||||
outcome.
|
||||
- About to write `BLOCKED.md` on a DONE outcome.
|
||||
- About to commit or stage `BLOCKED.md` — it stays
|
||||
uncommitted by convention.
|
||||
- About to return more than 500 tokens of end-report.
|
||||
- About to run the quality phase before the spec phase is
|
||||
`compliant`.
|
||||
- About to skip Phase 5 (stats file) "because mini mode".
|
||||
- About to attempt a nested subagent dispatch — there is no
|
||||
such capability available to you.
|
||||
- About to proceed past Phase 0 on a dirty working tree.
|
||||
@@ -12,11 +12,11 @@ You are the **implementer** for this project. You are
|
||||
dispatched by the `implement` skill per task, with a fresh
|
||||
context every time.
|
||||
|
||||
(In current Claude Code, this agent is consulted as a
|
||||
*phase reference* by `implement-orchestrator` Phase 2.1 —
|
||||
the orchestrator-agent adopts your mindset for the inline
|
||||
implementer phase. It is not separately dispatched as a
|
||||
nested subagent.)
|
||||
(You are dispatched as the **implementer phase** of the
|
||||
`implement-loop` workflow (`../workflows/implement-loop.js`) —
|
||||
one `agent()` call per task, fresh context each time. The
|
||||
workflow re-dispatches you with the reviewer's findings as a
|
||||
repair brief when a check phase comes back non-compliant.)
|
||||
|
||||
## What this role is for
|
||||
|
||||
@@ -167,10 +167,10 @@ substitute.
|
||||
production code and start over. (TDD is letter-and-
|
||||
spirit.)
|
||||
9. Report. Your changes stay in the working tree as
|
||||
unstaged edits. You do NOT commit. The orchestrator-
|
||||
agent's spec-compliance and quality phases read your
|
||||
work via `git diff HEAD`; the outer orchestrator commits
|
||||
at the end of the iter.
|
||||
unstaged edits. You do NOT commit. The workflow's
|
||||
spec-compliance and quality phases — separate `agent()`
|
||||
calls — read your work via `git diff HEAD`; the
|
||||
orchestrator commits at the end of the iter.
|
||||
|
||||
## Status protocol
|
||||
|
||||
|
||||
@@ -12,11 +12,10 @@ You are the **code-quality reviewer** for this project. You
|
||||
are dispatched by the `implement` skill after `spec-reviewer`
|
||||
has reported `compliant`, never before.
|
||||
|
||||
(In current Claude Code, this agent is consulted as a
|
||||
*phase reference* by `implement-orchestrator` Phase 2.3 —
|
||||
the orchestrator-agent adopts your mindset for the inline
|
||||
quality check. It is not separately dispatched as a nested
|
||||
subagent.)
|
||||
(You are dispatched as the **quality phase** of the
|
||||
`implement-loop` workflow (`../workflows/implement-loop.js`),
|
||||
only after the spec-compliance phase reports `compliant` —
|
||||
a separate `agent()` call with a fresh context.)
|
||||
|
||||
## What this role is for
|
||||
|
||||
|
||||
@@ -12,11 +12,12 @@ You are the **spec-compliance reviewer** for this project.
|
||||
You are dispatched by the `implement` skill after each
|
||||
implementer task, before the code-quality reviewer runs.
|
||||
|
||||
(In current Claude Code, this agent is consulted as a
|
||||
*phase reference* by `implement-orchestrator` Phase 2.2 —
|
||||
the orchestrator-agent adopts your mindset for the inline
|
||||
spec-compliance check. It is not separately dispatched as a
|
||||
nested subagent.)
|
||||
(You are dispatched as the **spec-compliance phase** of the
|
||||
`implement-loop` workflow (`../workflows/implement-loop.js`),
|
||||
after the implementer phase and before the quality phase —
|
||||
a separate `agent()` call with a fresh context, so the
|
||||
re-read of the diff against the task text is genuinely
|
||||
fresh-eyed rather than a mindset switch.)
|
||||
|
||||
## What this role is for
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@ by the `implement` skill (Phase 3 — E2E coverage) after the
|
||||
last task of an iteration completes, or directly by the
|
||||
orchestrator when regression coverage is needed.
|
||||
|
||||
(In current Claude Code, this agent is consulted as a
|
||||
*phase reference* by `implement-orchestrator` Phase 3 — the
|
||||
orchestrator-agent adopts your mindset for the inline E2E
|
||||
coverage phase. It is not separately dispatched as a nested
|
||||
subagent.)
|
||||
(You are dispatched as the **E2E coverage phase** of the
|
||||
`implement-loop` workflow (`../workflows/implement-loop.js`),
|
||||
once the last task of a standard-mode iteration completes —
|
||||
a separate `agent()` call with a fresh context. Mini-mode
|
||||
runs skip this phase; the handed-off RED test is the
|
||||
coverage.)
|
||||
|
||||
## What this role is for
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// compiler-driven-edit — the observe-then-bounce loop for a
|
||||
// behaviour-preserving type/signature edit, as a Workflow script.
|
||||
//
|
||||
// This is the executor for the `compiler-driven` selector arm (see
|
||||
// boss/SKILL.md "Entry-path reflection"). The arm is entered on an
|
||||
// OBSERVABLE/syntactic trigger — "a type or signature edit at a definition
|
||||
// site that propagates mechanically across the sites the type checker
|
||||
// enumerates" — NOT on a prediction that the change is behaviour-preserving.
|
||||
// A real build + suite run then settles the verdict:
|
||||
//
|
||||
// clean build AND suite green unchanged -> DONE (edits unstaged; orchestrator commits)
|
||||
// a hole the edit cannot resolve -> BOUNCE up, per the straddle rule:
|
||||
// forces a design choice -> specify
|
||||
// is test-specifiable new behaviour -> tdd (RED-first)
|
||||
// the suite is not green / not unchanged -> BOUNCE to debug (RED-first)
|
||||
//
|
||||
// The fallible ex-ante guess ("this is behaviour-preserving") is replaced by
|
||||
// a cheap ex-post detection: a mis-routed behaviour change fails the
|
||||
// post-condition and bounces instead of shipping green-but-wrong. This
|
||||
// generalises the plugin's RED-first philosophy — observe, do not predict.
|
||||
//
|
||||
// "Suite green UNCHANGED" is load-bearing: the suite must pass with NO test
|
||||
// added, removed, or weakened to make it pass. A new test would mean the
|
||||
// edit encoded new behaviour — which is the tdd/spec arm, not this one.
|
||||
//
|
||||
// The `verify` step below (build + suite-green-unchanged) is the single
|
||||
// phase the implement loop's pure-refactor case could never invoke on its
|
||||
// own; here it is a standalone, independently-invokable agent call.
|
||||
//
|
||||
// Discipline: the script never commits and never moves main HEAD; all edits
|
||||
// stay unstaged for the orchestrator. The script has no shell of its own —
|
||||
// the edit and the verify both run inside agent() calls.
|
||||
//
|
||||
// Carrier — Workflow `args`:
|
||||
// edit_description: what type/signature change to make
|
||||
// def_site: the definition site (file + symbol) it originates from
|
||||
|
||||
export const meta = {
|
||||
name: 'compiler-driven-edit',
|
||||
description:
|
||||
'Execute a behaviour-preserving type/signature edit at a definition site, propagate it mechanically across the sites the build enumerates, then let a real build + suite run settle the verdict: clean build AND suite green unchanged -> done (edits unstaged for the orchestrator to commit); a hole the edit cannot resolve -> bounce up per the straddle rule (specify for a design choice, tdd for test-specifiable new behaviour); suite not green-unchanged -> bounce to debug (RED-first). Never commits, never moves main HEAD.',
|
||||
phases: [{ title: 'Edit + propagate' }, { title: 'Verify' }, { title: 'Verdict' }],
|
||||
}
|
||||
|
||||
const { edit_description, def_site } = args || {}
|
||||
|
||||
const STANDING =
|
||||
"Standing reading first (the plugin convention): read the project's CLAUDE.md " +
|
||||
'(its "## Skills plugin: project facts" name the build and test commands) and ' +
|
||||
'`git log -10 --format=full`. Resolve the build/test commands from those facts.'
|
||||
|
||||
const EDIT_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['build_clean', 'hole_needs_decision'],
|
||||
properties: {
|
||||
build_clean: { type: 'boolean', description: 'the project build is clean after the edit + mechanical propagation' },
|
||||
hole_needs_decision: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'a site could not be resolved mechanically: it either forces a design choice, or resolving it would encode new behaviour — so the change was not purely behaviour-preserving after all',
|
||||
},
|
||||
bounce_to: {
|
||||
type: 'string',
|
||||
enum: ['specify', 'tdd'],
|
||||
description:
|
||||
'on hole_needs_decision, per the straddle rule: `specify` if the hole forces a genuine design choice; `tdd` if the discovered work is new behaviour pinnable as one falsifiable assertion. When unsure, `specify` (the conservative heavy path, which itself bounces onward).',
|
||||
},
|
||||
hole_detail: { type: 'string', description: 'on hole_needs_decision: what the hole forces (the decision, or the new behaviour discovered)' },
|
||||
sites: { type: 'integer', description: 'number of sites the build flagged and the edit touched' },
|
||||
},
|
||||
}
|
||||
const VERIFY_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['build_clean', 'suite_green_unchanged'],
|
||||
properties: {
|
||||
build_clean: { type: 'boolean' },
|
||||
suite_green_unchanged: {
|
||||
type: 'boolean',
|
||||
description: 'the full suite passes with NO test added, removed, or weakened to make it pass',
|
||||
},
|
||||
detail: { type: 'string', description: 'on failure: which tests went red, or which test was changed' },
|
||||
},
|
||||
}
|
||||
|
||||
// ---- Phase 1 — make the edit and let the compiler enumerate the sites ----
|
||||
phase('Edit + propagate')
|
||||
const edit = await agent(
|
||||
`${STANDING}\n\nMake this behaviour-preserving type/signature edit at its definition site, then propagate it ` +
|
||||
'MECHANICALLY across every site the build flags — let the type checker enumerate the edit sites. Introduce NO ' +
|
||||
'new behaviour, add NO new test, change NO existing test. If a flagged site cannot be resolved without making a ' +
|
||||
'design decision, OR resolving it would encode new behaviour, STOP and report it as a hole rather than guessing. ' +
|
||||
'When you report a hole, CLASSIFY it (straddle rule): set bounce_to = "specify" if it forces a genuine design ' +
|
||||
'choice, or "tdd" if the discovered work is new behaviour pinnable as one falsifiable assertion. When unsure, ' +
|
||||
'"specify". Leave all edits UNSTAGED; never commit.\n\n' +
|
||||
`EDIT: ${edit_description}\nDEFINITION SITE: ${def_site}\n\n` +
|
||||
'Then run the project build and report: is the build clean, does any hole need a decision (with detail + bounce_to), and how many sites were touched.',
|
||||
{ agentType: 'implementer', label: 'edit+propagate', phase: 'Edit + propagate', schema: EDIT_SCHEMA },
|
||||
)
|
||||
if (!edit) return { status: 'BLOCKED', reason: 'edit agent did not return' }
|
||||
if (edit.hole_needs_decision) {
|
||||
// The change was not purely mechanical after all. Per the straddle rule,
|
||||
// route UP: a genuine design choice -> specify; test-specifiable new
|
||||
// behaviour -> tdd (RED-first). Default to specify when unclassified —
|
||||
// the conservative heavy path, which itself bounces onward.
|
||||
const to = edit.bounce_to === 'tdd' ? 'tdd' : 'specify'
|
||||
return {
|
||||
status: 'BOUNCE',
|
||||
to,
|
||||
reason: to === 'tdd' ? 'discovered test-specifiable new behaviour' : 'a hole forces a design decision',
|
||||
detail: edit.hole_detail || '',
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Phase 2 — verify (the independently-invokable phase) ---------------
|
||||
phase('Verify')
|
||||
const verify = await agent(
|
||||
`${STANDING}\n\nVerify the edit is behaviour-preserving WITHOUT changing any code or test. Run the project build ` +
|
||||
'and the full test suite. Report whether the build is clean and whether the suite is green AND UNCHANGED — i.e. ' +
|
||||
'every test passes and NO test was added, removed, or weakened versus the pre-edit baseline (compare `git diff ' +
|
||||
'HEAD` for test-file changes). Do NOT edit anything; do NOT commit.',
|
||||
{ label: 'verify(build+suite)', phase: 'Verify', schema: VERIFY_SCHEMA },
|
||||
)
|
||||
|
||||
// ---- Phase 3 — verdict --------------------------------------------------
|
||||
phase('Verdict')
|
||||
if (verify && verify.build_clean && verify.suite_green_unchanged) {
|
||||
return {
|
||||
status: 'DONE',
|
||||
sites: edit.sites || null,
|
||||
note: 'behaviour-preserving edit verified; edits unstaged in the working tree for the orchestrator to commit',
|
||||
}
|
||||
}
|
||||
// observe-then-bounce: the post-condition failed, so the change was NOT
|
||||
// behaviour-preserving after all. Route RED-first via debug rather than
|
||||
// shipping a green-but-wrong commit.
|
||||
return {
|
||||
status: 'BOUNCE',
|
||||
to: 'debug',
|
||||
reason: !verify
|
||||
? 'verify agent did not return'
|
||||
: !verify.build_clean
|
||||
? 'build not clean after the edit'
|
||||
: 'suite not green-unchanged — a regression surfaced, so the edit was not behaviour-preserving',
|
||||
detail: verify ? verify.detail || '' : '',
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
// implement-loop — the per-task implement loop, as a Workflow script.
|
||||
//
|
||||
// This is the Workflow-substrate form of what used to be the
|
||||
// `implement-orchestrator` agent's inline three-phase role-switch loop.
|
||||
// The substrate change is the point of issue #7's Part B:
|
||||
//
|
||||
// • Each per-task phase is now a separate `agent()` call — implementer,
|
||||
// spec-reviewer, quality-reviewer are dispatched as their existing
|
||||
// agent-types (they SURVIVE the migration; the inline-role-switch
|
||||
// ORCHESTRATOR is what retires). A single phase is therefore
|
||||
// independently invokable, which the inline loop could not offer.
|
||||
// • Inter-phase aggregation and the re-loop limit are deterministic
|
||||
// code here, not natural-language reasoning between dispatches.
|
||||
// • Routing keys on the structured verdict each agent reports (which
|
||||
// encodes the project's real build/test outcome), not on a guess.
|
||||
//
|
||||
// Discipline preserved verbatim from the old loop:
|
||||
// • The script NEVER commits and NEVER moves main HEAD. All code edits,
|
||||
// the stats file, and (on PARTIAL/BLOCKED) BLOCKED.md live in the
|
||||
// working tree as unstaged changes; the orchestrator that invoked
|
||||
// this workflow inspects and commits.
|
||||
// • Tasks run SEQUENTIALLY — plan tasks carry implicit ordering
|
||||
// dependencies, and the script does not know the graph, so a BLOCKED
|
||||
// task stops the loop rather than skipping ahead.
|
||||
// • Spec-compliance is gated BEFORE quality, per task.
|
||||
// • Re-loop limit: 2 repair retries per failure-mode per task; the 3rd
|
||||
// unresolved attempt escalates to BLOCKED.
|
||||
// • The script has no filesystem or shell access of its own — every
|
||||
// working-tree mutation (code, stats.json, BLOCKED.md) happens inside
|
||||
// an agent() call.
|
||||
//
|
||||
// Carrier — passed as the Workflow `args` object by the orchestrator:
|
||||
// mode: 'standard' | 'mini'
|
||||
// iter_id: e.g. 'ct.2.3' (standard) or 'bugfix-<symptom>' / 'feat-<behaviour>' (mini)
|
||||
// plan_path: (standard) path under docs/plans
|
||||
// task_range: (standard, optional) [from, to] inclusive, 1-based
|
||||
// red_test_path: (mini) absolute path to the RED test from debug / tdd
|
||||
// cause_summary: (mini) 1–2 sentences (debugger cause, or tdd spec_summary)
|
||||
// constraint: (mini) e.g. 'minimal fix, no surrounding cleanup'
|
||||
|
||||
export const meta = {
|
||||
name: 'implement-loop',
|
||||
description:
|
||||
'Execute a plan iteration (standard) or a debug/tdd RED->GREEN handoff (mini) task-by-task: implementer -> spec-compliance -> quality, with deterministic re-loop and aggregation. Writes code, tests, stats.json, and (on PARTIAL/BLOCKED) BLOCKED.md to the working tree as unstaged changes; never commits, never moves main HEAD.',
|
||||
phases: [
|
||||
{ title: 'Preflight' },
|
||||
{ title: 'Per-task loop' },
|
||||
{ title: 'E2E coverage' },
|
||||
{ title: 'Report' },
|
||||
],
|
||||
}
|
||||
|
||||
const {
|
||||
mode = 'standard',
|
||||
iter_id,
|
||||
plan_path,
|
||||
task_range = null,
|
||||
red_test_path,
|
||||
cause_summary,
|
||||
constraint = 'minimal change, no surrounding cleanup',
|
||||
} = args || {}
|
||||
|
||||
const STANDING =
|
||||
"Standing reading first (the plugin convention): read the project's CLAUDE.md " +
|
||||
'(its "## Skills plugin: project facts" name the build and test commands and any ' +
|
||||
'design ledger) and `git log -10 --format=full`. Resolve the build/test commands ' +
|
||||
'from those facts — do not assume a toolchain.'
|
||||
|
||||
// JSON Schemas — force structured verdicts so the script can branch on real outcomes.
|
||||
const PREFLIGHT_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['clean', 'head_sha', 'branch'],
|
||||
properties: {
|
||||
clean: { type: 'boolean', description: '`git status --porcelain` produced no output' },
|
||||
head_sha: { type: 'string' },
|
||||
branch: { type: 'string' },
|
||||
dirty_paths: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
}
|
||||
const TASKS_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['tasks'],
|
||||
properties: {
|
||||
tasks: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['id', 'title', 'text'],
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
title: { type: 'string' },
|
||||
text: { type: 'string', description: 'verbatim task block from the plan' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
const IMPL_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['status', 'summary'],
|
||||
properties: {
|
||||
status: { type: 'string', enum: ['DONE', 'DONE_WITH_CONCERNS', 'NEEDS_CONTEXT', 'BLOCKED'] },
|
||||
summary: { type: 'string', description: 'one line: what changed (paths + functions)' },
|
||||
concerns: { type: 'array', items: { type: 'string' } },
|
||||
reason: { type: 'string', description: 'on BLOCKED/NEEDS_CONTEXT: the blocker' },
|
||||
},
|
||||
}
|
||||
const SPEC_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['status'],
|
||||
properties: {
|
||||
status: { type: 'string', enum: ['compliant', 'non_compliant', 'unclear', 'infra_blocked'] },
|
||||
findings: { type: 'array', items: { type: 'string' }, description: 'missing requirements + unrequested extras' },
|
||||
ambiguity: { type: 'string' },
|
||||
},
|
||||
}
|
||||
const QUAL_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['status'],
|
||||
properties: {
|
||||
status: { type: 'string', enum: ['approved', 'changes_requested', 'infra_blocked'] },
|
||||
issues: { type: 'array', items: { type: 'string' }, description: 'Important + Minor only (Nits never gate)' },
|
||||
},
|
||||
}
|
||||
const E2E_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['status'],
|
||||
properties: {
|
||||
status: { type: 'string', enum: ['DONE', 'DONE_WITH_CONCERNS', 'NEEDS_CONTEXT', 'BLOCKED'] },
|
||||
fixtures: { type: 'array', items: { type: 'string' } },
|
||||
note: { type: 'string' },
|
||||
},
|
||||
}
|
||||
const FINALIZE_SCHEMA = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['stats_path', 'wrote_blocked_md'],
|
||||
properties: {
|
||||
stats_path: { type: 'string' },
|
||||
wrote_blocked_md: { type: 'boolean' },
|
||||
files_touched: { type: 'integer' },
|
||||
},
|
||||
}
|
||||
|
||||
// ---- Phase 0 — clean-tree preflight ------------------------------------
|
||||
phase('Preflight')
|
||||
const pre = await agent(
|
||||
`${STANDING}\n\nYou are the clean-tree preflight for an implement iteration. ` +
|
||||
'Run `git status --porcelain`, `git rev-parse HEAD`, and `git rev-parse --abbrev-ref HEAD`. ' +
|
||||
'Report whether the tree is clean (no porcelain output), the HEAD sha, the branch, and any dirty paths. ' +
|
||||
'Do NOT modify anything, do NOT commit. The HEAD sha is an informational anchor only — never a reset target.',
|
||||
{ label: 'preflight', phase: 'Preflight', schema: PREFLIGHT_SCHEMA },
|
||||
)
|
||||
if (!pre) return { status: 'BLOCKED', iter_id, reason: 'infra: preflight agent did not return' }
|
||||
if (!pre.clean) {
|
||||
return {
|
||||
status: 'BLOCKED',
|
||||
iter_id,
|
||||
reason: 'infra: working tree dirty — the orchestrator must clean it before re-invoking',
|
||||
dirty_paths: pre.dirty_paths || [],
|
||||
}
|
||||
}
|
||||
const startSha = pre.head_sha
|
||||
|
||||
// ---- Phase 1 — assemble the task list ----------------------------------
|
||||
let tasks
|
||||
if (mode === 'mini') {
|
||||
tasks = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'drive the RED test to GREEN',
|
||||
text:
|
||||
`Make this RED test pass: ${red_test_path}\n\n` +
|
||||
`Context (debugger cause, or tdd spec_summary): ${cause_summary}\n\n` +
|
||||
`Constraint: ${constraint}`,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
const extracted = await agent(
|
||||
`${STANDING}\n\nRead the plan at ${plan_path}. Extract ` +
|
||||
(task_range ? `tasks ${task_range[0]}..${task_range[1]} (inclusive, 1-based)` : 'every task') +
|
||||
', each as its VERBATIM block with the task id and a one-line title. Do not summarise or reorder. Do not edit anything.',
|
||||
{ label: 'plan-extract', phase: 'Per-task loop', schema: TASKS_SCHEMA },
|
||||
)
|
||||
if (!extracted || !extracted.tasks || extracted.tasks.length === 0) {
|
||||
return { status: 'NEEDS_CONTEXT', iter_id, reason: `no tasks extracted from ${plan_path}` }
|
||||
}
|
||||
tasks = extracted.tasks
|
||||
}
|
||||
|
||||
// ---- Phase 2 — per-task loop (sequential) ------------------------------
|
||||
phase('Per-task loop')
|
||||
|
||||
async function runTask(task) {
|
||||
// 2.1 — implementer phase
|
||||
const impl = await agent(
|
||||
`${STANDING}\n\nImplement this task exactly, RED-first if it adds behaviour (TDD is an independent ` +
|
||||
'inner-loop discipline — add the failing test inline even if the task text forgot to script it). ' +
|
||||
'Build and test must be green before you report DONE. Leave all edits UNSTAGED; never commit.\n\n' +
|
||||
`mode: ${mode}\niter_id: ${iter_id}\n\nTASK ${task.id} — ${task.title}\n${task.text}`,
|
||||
{ agentType: 'implementer', label: `impl:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA },
|
||||
)
|
||||
if (!impl) return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: 'implementer agent did not return' }
|
||||
if (impl.status === 'BLOCKED') return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: impl.reason || 'worker-blocked' }
|
||||
if (impl.status === 'NEEDS_CONTEXT') return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: `context-exhausted: ${impl.reason || ''}` }
|
||||
const concerns = impl.concerns || []
|
||||
|
||||
// 2.2 — spec-compliance check (gated before quality), with repair re-loop
|
||||
for (let round = 0; ; round++) {
|
||||
const spec = await agent(
|
||||
`${STANDING}\n\nSpec-compliance review of task ${task.id}. Compare \`git diff HEAD\` against the task ` +
|
||||
'text ONLY — list missing requirements and unrequested extras. No quality opinions, no fix proposals. ' +
|
||||
`Focus on the files this task claims to touch.\n\nTASK ${task.id} — ${task.title}\n${task.text}`,
|
||||
{ agentType: 'spec-reviewer', label: `spec:${task.id}`, phase: 'Per-task loop', schema: SPEC_SCHEMA },
|
||||
)
|
||||
if (spec && spec.status === 'compliant') break
|
||||
if (spec && spec.status === 'unclear') return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: 'spec-ambiguous', detail: spec.ambiguity }
|
||||
if (round >= 2) return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: 'review-loop-exhausted (spec-compliance)' }
|
||||
// repair: re-dispatch the implementer with the review findings as the repair brief
|
||||
await agent(
|
||||
`${STANDING}\n\nRepair task ${task.id} to satisfy the spec-compliance review. Address EACH finding; ` +
|
||||
'do not widen scope. Keep build + tests green; leave edits unstaged.\n\n' +
|
||||
`TASK ${task.id} — ${task.title}\n${task.text}\n\nFINDINGS:\n- ${(spec?.findings || ['(none reported)']).join('\n- ')}`,
|
||||
{ agentType: 'implementer', label: `impl-fix-spec:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA },
|
||||
)
|
||||
}
|
||||
|
||||
// 2.3 — quality check, with repair re-loop
|
||||
for (let round = 0; ; round++) {
|
||||
const qual = await agent(
|
||||
`${STANDING}\n\nQuality review of task ${task.id} (spec-compliance is already green — do not re-check it). ` +
|
||||
'Read every chunk of `git diff HEAD` in this task\'s footprint. Report only Important and Minor issues; ' +
|
||||
'Nits never gate.',
|
||||
{ agentType: 'quality-reviewer', label: `qual:${task.id}`, phase: 'Per-task loop', schema: QUAL_SCHEMA },
|
||||
)
|
||||
if (qual && qual.status === 'approved') break
|
||||
if (round >= 2) return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: 'review-loop-exhausted (quality)' }
|
||||
await agent(
|
||||
`${STANDING}\n\nFix the quality issues on task ${task.id}. Address EACH Important/Minor issue; do not widen ` +
|
||||
'scope. Keep build + tests green; leave edits unstaged.\n\nISSUES:\n- ' + (qual?.issues || ['(none reported)']).join('\n- '),
|
||||
{ agentType: 'implementer', label: `impl-fix-qual:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA },
|
||||
)
|
||||
}
|
||||
|
||||
return { id: task.id, title: task.title, outcome: 'DONE', concerns }
|
||||
}
|
||||
|
||||
const results = []
|
||||
for (const task of tasks) {
|
||||
const r = await runTask(task)
|
||||
results.push(r)
|
||||
if (r.outcome === 'BLOCKED') break // do not skip ahead — task ordering dependencies are unknown
|
||||
}
|
||||
|
||||
const completed = results.filter((r) => r.outcome === 'DONE')
|
||||
const blocked = results.find((r) => r.outcome === 'BLOCKED')
|
||||
let outcome
|
||||
if (!blocked) outcome = 'DONE'
|
||||
else if (completed.length > 0) outcome = 'PARTIAL'
|
||||
else outcome = 'BLOCKED'
|
||||
|
||||
// ---- Phase 3 — E2E coverage (standard mode, only on a full clean run) --
|
||||
let e2e = null
|
||||
if (mode === 'standard' && outcome === 'DONE') {
|
||||
phase('E2E coverage')
|
||||
e2e = await agent(
|
||||
`${STANDING}\n\nThe iteration completed. Identify 1–3 invariants worth protecting and write the smallest ` +
|
||||
'E2E fixtures for them in the project\'s canonical fixture form; each test\'s doc comment names the property ' +
|
||||
'it protects. Run the project\'s test command — all green. Leave edits unstaged; never commit.\n\n' +
|
||||
`iter_id: ${iter_id}\nshipped: ${completed.map((c) => `#${c.id} ${c.title}`).join('; ')}`,
|
||||
{ agentType: 'tester', label: 'e2e', phase: 'E2E coverage', schema: E2E_SCHEMA },
|
||||
)
|
||||
}
|
||||
|
||||
// ---- Phase 4/5 — finalize: stats.json always, BLOCKED.md on non-DONE ----
|
||||
phase('Report')
|
||||
const aggregate = {
|
||||
iter_id,
|
||||
mode,
|
||||
started_from: startSha,
|
||||
outcome,
|
||||
tasks_total: tasks.length,
|
||||
tasks_completed: completed.length,
|
||||
task_summaries: results.map((r) => ({ id: r.id, title: r.title, outcome: r.outcome })),
|
||||
concerns: results.flatMap((r) => r.concerns || []),
|
||||
blocked_detail: blocked ? { task: blocked.id, reason: blocked.reason, detail: blocked.detail || null } : null,
|
||||
}
|
||||
|
||||
const fin = await agent(
|
||||
`${STANDING}\n\nFinalize an implement iteration. Using the aggregate below:\n` +
|
||||
'1. Write a stats file at `/tmp/iter-' + iter_id + '/stats.json` (or under the project stats dir if its facts ' +
|
||||
'declare one) containing: iter_id, date (from `date +%F`), mode, outcome, tasks_total, tasks_completed, and ' +
|
||||
'blocked_reason (or null).\n' +
|
||||
(outcome === 'DONE'
|
||||
? '2. Do NOT write BLOCKED.md — this run is DONE.\n'
|
||||
: '2. Write `BLOCKED.md` at the repo ROOT (uncommitted by convention; never staged). Sections: a `# BLOCKED — ' +
|
||||
'iter ' + iter_id + '` header with Date/Started-from/Status/Tasks-completed, `## What ran` (one line per DONE ' +
|
||||
'task), `## What did not` (the blocked task, its reason, and a one-line suggested next step), `## Concerns`, ' +
|
||||
'and `## Files touched` (from `git diff --name-only HEAD`).\n') +
|
||||
'Report the stats path, whether you wrote BLOCKED.md, and the count from `git diff --name-only HEAD | wc -l`. ' +
|
||||
'Do NOT commit anything.\n\nAGGREGATE:\n' + JSON.stringify(aggregate, null, 2),
|
||||
{ label: 'finalize', phase: 'Report', schema: FINALIZE_SCHEMA },
|
||||
)
|
||||
|
||||
// ---- End-report — the orchestrator reads this, inspects the tree, commits.
|
||||
return {
|
||||
status: outcome,
|
||||
iter_id,
|
||||
mode,
|
||||
started_from: startSha,
|
||||
tasks_total: tasks.length,
|
||||
tasks_completed: completed.length,
|
||||
task_summaries: aggregate.task_summaries,
|
||||
concerns: aggregate.concerns,
|
||||
e2e_fixtures: e2e ? e2e.fixtures || [] : [],
|
||||
stats_path: fin ? fin.stats_path : null,
|
||||
blocked_md: fin && fin.wrote_blocked_md ? 'BLOCKED.md (uncommitted)' : null,
|
||||
files_touched: fin ? fin.files_touched : null,
|
||||
blocked_detail: aggregate.blocked_detail,
|
||||
}
|
||||
Reference in New Issue
Block a user