diff --git a/README.md b/README.md index 0ff7f35..7f4dee4 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The pipeline skills, each with the agents it primarily dispatches: | `brainstorm` | New cycle with an open design | ratified design handed to `specify` (writes no spec itself) | Optional discovery front-end | | `specify` | Design settled in sources, or handed over by `brainstorm` | spec under `docs/specs` | Hard-gate before plan | | `planner` | New iteration within an open cycle | plan under `docs/plans` | Hard-gate before implement | -| `implement` | Plan exists | code + tests, uncommitted in working tree | Standard iteration path | +| `implement` | Plan exists, OR a debug/tdd RED handoff | code + tests, uncommitted in working tree (via the `implement-loop` Workflow) | Standard iteration path | | `audit` | Cycle closing OR baseline drift suspected | drift report + regression report | Mandatory at cycle close | | `debug` | Bug observed | RED test in working tree + cause analysis | Mandatory RED-first for any bug | | `tdd` | Test-specifiable feature / issue (third entry path, alongside `brainstorm` and `specify`) | RED executable-spec in working tree → `implement` mini-mode | Standard entry path; bounces to `brainstorm` on a design fork | @@ -30,6 +30,30 @@ The pipeline skills, each with the agents it primarily dispatches: | `docwriter` | API surface stable across N cycles | rustdoc / docstring sweep | Optional | | `boss` | User types `/boss` | autonomous-orchestrator session — dispatches the other skills until done-state or bounce-back; can optionally sign specs in the user's place (opt-in, see below) | User-invoked, never auto-dispatched | +**Entry is a selector cascade, not a single door.** New feature work is +routed on a verification / enumeration axis, walked top to bottom, first +match wins: an observed bug → `debug` (RED-first, always first); a design +fork → `brainstorm`; a **behaviour-preserving type/signature edit at a +definition site** → the `compiler-driven` arm; new behaviour pinnable as +one assertion → `tdd`; everything else → spec-driven (`specify` → +`planner`). The `compiler-driven` arm is **observe-then-bounce**: make the +edit, build, run the suite — clean build AND suite green *unchanged* +commits; a hole bounces up (`specify`, or `tdd` for test-specifiable new +behaviour), a regression bounces to `debug`. It is not a standalone skill +but an arm of `implement` plus a shipped Workflow. + +**The autonomous execution loops run on the Workflow substrate.** The +per-task implement loop and the compiler-driven loop are shipped as +Workflow scripts under `implement/workflows/` (`implement-loop.js`, +`compiler-driven-edit.js`), symlinked into `~/.claude/workflows/` by +`install.sh`. A Workflow orchestrates from the top level, so each per-task +phase (implementer → spec-compliance → quality) is a real, +independently-invokable `agent()` call — which retired the old +`implement-orchestrator` agent's inline-role-switch workaround. The four +phase agents survive as the agent-types the scripts dispatch. The +interactive paths (`specify`, `brainstorm`) stay prose skills — their +oracle is human intent and needs a human in the loop. + Three further **utility skills** are invoked on demand rather than as pipeline phases: `issue` (file or update a tracker item), `glossary` (build or maintain the project glossary — see `docs/glossary-convention.md`), @@ -77,7 +101,9 @@ This repo (the **plugin**) carries everything that is universal: - Working-tree-as-quarantine and only-orchestrator-commits - main HEAD sacrosanct - No nested subagent dispatch (Claude Code platform constraint; - Opus 4.8 Workflows fan out at the top level but do not lift it) + Workflows orchestrate at the top level but do not lift it — the + execution loops run as Workflow scripts so their phase agents are + top-level calls, not nested ones) - No orphan agents The constants that used to be configurable but were the same in diff --git a/boss/SKILL.md b/boss/SKILL.md index 525de05..1301e22 100644 --- a/boss/SKILL.md +++ b/boss/SKILL.md @@ -100,6 +100,8 @@ brainstorm → specify → planner → implement → audit → fieldtest + tdd → implement (mini) (test-specifiable feature) + + compiler-driven (behaviour-preserving type/signature edit; observe-then-bounce) + + docwriter (post-stability) ``` @@ -110,63 +112,111 @@ new feature work with no spec or plan yet, run the **Entry-path reflection** below before dispatching anything. Read each skill's `SKILL.md` trigger section if unsure. -**Entry-path reflection (feature work).** There are **three** entry -paths for new feature work, and the choice is made by reflection every -time, not by habit. Before -dispatching, decide which fits the item in hand and record the one-line -verdict ("test-specifiable → `tdd`" / "design settled → `specify`" / -"design fork → `brainstorm`") in the loop. The discriminator is the -**design line**: +**Entry-path reflection (feature work).** Entry paths form an **ordered +cascade**, and the choice is made by reflection every time, not by +habit. The discriminator is no longer a single "design line": it adds a +**verification / enumeration axis** *ahead of* the settled-vs-fork +question, so each lighter path carries a **positive** trigger matched by +signature rather than being reached by elimination. Walk the cascade top +to bottom and take the **first** arm that matches; record the one-line +verdict ("observed bug → `debug`" / "design fork → `brainstorm`" / +"mechanical type-edit → `compiler-driven`" / "test-specifiable → +`tdd`" / "settled prose design → `specify`") in the loop. -- The item's desired behaviour can be pinned by one honest minimal - assertion — "calling X with Y returns Z" — then it is - **test-specifiable** and `tdd` owns it. Dispatch `tdd` autonomously, - the same way a bug issue is dispatched to `debug`. -- The sources resolve every load-bearing design decision — an - exhaustive issue body, a long in-context discussion — then the design - is **settled** and `specify` owns it. Dispatch `specify` - autonomously: it is bounded (no interview), produces the spec through - all the gates, and — unless the project enables spec auto-sign (see - below) — pauses at its own user-review gate (a problem-state notify, - not a pre-dispatch checkpoint). -- Writing the spec would force a choice between two or three plausible - designs with real trade-offs — a genuine design fork — then discovery - must resolve it first, and `brainstorm` owns it. Starting a fresh +``` +route(task): + observed bug -> debug # RED-first gate, FIRST — never bypassed, even for a one-line fix + forces a genuine design choice -> brainstorm # the only place doubt resolves "up" + a type/signature edit at a definition + site, propagating mechanically -> compiler-driven # observe-then-bounce; behaviour-preserving + new behaviour as one honest assertion -> tdd + else -> spec-driven # the deliberate heavy fallback (specify -> planner) +``` + +- **Observed bug → `debug`.** A regression of existing behaviour is + RED-first, always, and this check is **first in the cascade** so a bug + whose fix happens to be a one-line type edit cannot slip into the + compiler-driven arm and bypass the RED-first gate. Dispatch + autonomously, as a bug issue already is. +- **Forces a genuine design choice → `brainstorm`.** Writing the work + down would force a choice between two or three plausible designs with + real trade-offs. Discovery must resolve it first. Starting a fresh `brainstorm` cycle is itself a bounce-back per §"Direction freedom" - trigger 4: green-light the cycle (and the session shape) with the - user first; only then does `brainstorm` get dispatched. + trigger 4 — green-light the cycle (and the session shape) with the + user first. +- **A type/signature edit at a definition site, propagating mechanically + → `compiler-driven`.** The positive trigger is *syntactic and + observable*: a carrier/newtype narrowing, a constructor or field + rename, a signature change at a definition that the type checker then + enumerates across N call sites — **behaviour preserved**. The arm is + **observe-then-bounce** (its executor is `../implement` "The + compiler-driven arm" + the `compiler-driven-edit` workflow): make the + edit, build, run the suite; **clean build AND suite green unchanged → + commit; a hole that forces a design decision → bounce to `specify` (or + `tdd` if the hole turns out to be test-specifiable new behaviour, per + the straddle rule); the suite not green-unchanged → bounce to `debug`, + RED-first.** Entering on + the observable trigger and letting a real run settle the verdict is + what makes this sound — a mis-routed behaviour change fails the + post-condition and bounces instead of shipping green-but-wrong. + Dispatch autonomously; its bounces are reactive, not a pre-dispatch + checkpoint. +- **New behaviour as one honest assertion → `tdd`.** The desired + behaviour can be pinned by one minimal falsifiable assertion — "calling + X with Y returns Z". Dispatch autonomously. +- **Else → spec-driven (`specify` → `planner`).** The deliberate heavy + fallback for prose-design work the sources already settle (an + exhaustive issue body, a long in-context discussion). `specify` is + bounded (no interview); dispatch it autonomously and it pauses at its + own user-review gate (a problem-state notify, not a pre-dispatch + checkpoint) unless the project enables spec auto-sign. -The asymmetry in autonomy — `tdd` and `specify` dispatch proceed, a -fresh `brainstorm` cycle bounces back — is **not** a ranking of the -skills. It is a consequence of context budget: `tdd` and `specify` are -both bounded (no open Q&A), whereas a fresh `brainstorm` is high-context -discovery the orchestrator cannot compact on its own (see trigger 4). -`specify` dispatched in `/boss` pauses at its own Step-6 user-review -gate to take sign-off — that is a final-sign-off notify, not a +**The straddle rule (a rule, not an example).** "Add an enum variant" +straddles the compiler-driven and the tdd/spec arms. Decide by what the +edit *encodes*, not by its surface shape: an arm that is **mechanical / +forwarding** — the type checker enumerates the sites and behaviour is +preserved (an exhaustiveness-only variant, a pass-through case) — is +**compiler-driven**; an arm that **encodes new behaviour** — a variant +whose handling is a new code path with new observable output — is +**tdd** (one assertion) or **spec-driven** (a real design). When unsure +which side a change falls on, that doubt is itself the signal it carries +behaviour: route up to tdd/spec, not down to compiler-driven. + +The asymmetry in autonomy — `debug`, `compiler-driven`, `tdd`, and +`specify` all dispatch autonomously, while a fresh `brainstorm` cycle +bounces back — is **not** a ranking of the paths. It is a consequence of +context budget: the four are bounded (no open Q&A; `compiler-driven` and +`debug` self-settle on a run), whereas a fresh `brainstorm` is +high-context discovery the orchestrator cannot compact on its own (see +trigger 4). `specify` dispatched in `/boss` pauses at its own Step-6 +user-review gate to take sign-off — a final-sign-off notify, not a pre-dispatch checkpoint. The one exception is spec auto-sign: when a -project enables it in its CLAUDE.md project facts, a -spec that clears all objective gates AND a unanimous adversarial -`spec-skeptic` panel is signed by the orchestrator without pausing, -and the run continues to `planner` — see §"Spec auto-sign" below. The -gate is built so that the orchestrator's own confidence is never what -signs; absent that, the human signature stays mandatory. Do not -let the asymmetry harden into a reflex -of routing borderline items to `brainstorm` "to be safe" — that is the -exact bias this reflection exists to break; apply the design-line test +project enables it in its CLAUDE.md project facts, a spec that clears all +objective gates AND a unanimous adversarial `spec-skeptic` panel is +signed by the orchestrator without pausing, and the run continues to +`planner` — see §"Spec auto-sign" below. The gate is built so the +orchestrator's own confidence is never what signs; absent that, the +human signature stays mandatory. Do not let the asymmetry harden into a +reflex of routing borderline items to `brainstorm` "to be safe" — that +is the exact bias this reflection exists to break; walk the cascade honestly each time. The one principled tilt is two-sided and lives *inside* the test: genuine doubt about whether the sources (or one assertion) can pin the design *is itself* the design-fork signal, so it resolves to `brainstorm` — not because `brainstorm` is preferred, but -because unresolved ambiguity is a fork by definition. A `specify` or -`tdd` bounce-back also fires reactively: if either later reports the -design was not settled / not test-specifiable after all, that escalation -routes to `brainstorm` and — being a new cycle needing a fresh -discovery — is a bounce-back to the user per the same trigger 4. +because unresolved ambiguity is a fork by definition. The lighter arms +also bounce reactively: a `specify` or `tdd` that later reports the +design was not settled / not test-specifiable, or a `compiler-driven` +run that hits a hole, escalates — `specify` / `tdd` to `brainstorm` (a +fresh cycle, a trigger-4 bounce-back), `compiler-driven` to `specify` or +`tdd` (a hole — a design choice vs. test-specifiable new behaviour, per +the straddle rule) or `debug` (the regression). -All three entry paths are always available — none is profile-gated. -The reflection is therefore always three-way: `tdd` for -test-specifiable behaviour, `specify` for a settled design, -`brainstorm` for an open one. +All entry paths are always available — none is profile-gated. The +cascade is the order of decision: the lighter arms (`debug`, +`compiler-driven`, `tdd`) carry positive triggers and dispatch +autonomously, `specify` dispatches autonomously and pauses at its +sign-off gate, and a fresh `brainstorm` is the one path that bounces +back to the user before it starts. If the working tree is mid-flight (uncommitted changes left over from a previous session): inspect first, then resume the right @@ -409,9 +459,12 @@ and defined there. The boss-side contract is just this: | "Notify can name the iteration code — user will figure out what it means" | No. The notify text is for the user-as-reader who did not watch the orchestrator work. Iteration codes, crate names, function identifiers, type names — all internal, all banned. | | "The previous cycle closed cleanly and the next backlog item is obvious — just dispatch `brainstorm` and keep going" | No. Starting a new cycle is itself a bounce-back. A fresh `brainstorm` is high-context work, and the orchestrator cannot compact its own context — only the user can decide whether the next cycle wants a fresh session or a continuation of this one. The "obviousness" of the candidate is irrelevant; the *cross-cycle hop* is the checkpoint, not the topic choice. | | "It's the same broad area as the cycle I just closed, that's not really a new cycle" | If there is no spec file for it yet and it would route through `brainstorm` to get one, it IS a new cycle for this rule's purposes. The rule keys on "needs a fresh spec", not on subjective continuity. | -| "Feature work, so route it to `brainstorm` — that's the safe default" | `brainstorm`, `specify`, and `tdd` are co-equal entry paths; none is the default. Routing a settled design to `brainstorm` re-litigates decided choices; routing a test-specifiable item there wastes the test path. Run the Entry-path reflection and pick by the design line. The only tilt toward `brainstorm` is a genuinely unresolved fork — and that is a fork, not a default. | +| "Feature work, so route it to `brainstorm` — that's the safe default" | The entry paths are an ordered cascade, none the default. Routing a settled design to `brainstorm` re-litigates decided choices; routing a test-specifiable item there wastes the test path; routing a behaviour-preserving type-edit there is the heavy-path gravity well this whole reflection exists to break. Walk the cascade and take the first matching positive trigger. The only tilt toward `brainstorm` is a genuinely unresolved fork — and that is a fork, not a default. | +| "It's just a mechanical rename across N files — run the full implement loop on it" | That is the `compiler-driven` arm's job, not the heavy loop. Route it there; the type checker enumerates the sites, and the done-signal (clean build AND suite green unchanged) is the gate. Funnelling a behaviour-preserving edit through the full per-task loop is the disproportionality the cascade was added to fix. | +| "The compiler-driven edit built clean — that's the done-signal, commit" | Clean build is only half. The done-signal is clean build **AND suite green unchanged**. A behaviour change can build clean; the unchanged-green suite is what catches it. No suite run, no commit — and if the suite isn't green-unchanged, bounce to `debug`. | +| "It's a bug but the fix is a one-line type edit — route it `compiler-driven`" | No. An observed bug is first in the cascade → `debug`, RED-first, regardless of how mechanical the fix looks. The bug-gate sits before the type-edit arm precisely so a regression cannot launder itself around RED-first. | | "The issue is exhaustive but it's a new cycle, so bounce to the user before `specify`" | `specify` direct-entry is bounded and autonomously dispatchable — it is NOT the high-context `brainstorm` cycle that trigger 4 reserves for the user. Dispatch it; it will pause at its own user-review gate for sign-off. The pre-dispatch bounce is for an *open* design that needs discovery, not for a settled one that needs only production. | -| "`tdd` is the secondary skill, `brainstorm` is the real entry" | All three entry paths are always available and co-equal. The choice among them is decided by fit per item, reflected on each time — not by treating `brainstorm` as primary and `tdd` as the exception. | +| "`tdd` is the secondary skill, `brainstorm` is the real entry" | All entry paths are always available. The choice among them is the cascade, walked per item — not a habit of treating `brainstorm` as primary and the lighter arms as exceptions. The lighter arms exist to be reached; reaching past them by elimination is the failure the verification axis was added to prevent. | | "Auto-sign is on and this spec is clearly good — I'll sign it and skip the panel" | The panel IS how a spec gets signed under auto-sign; there is no signing on judgement. Your sense that it is clearly good is the precise signal the gate is built not to trust. Run the objective gates, dispatch the five jurors, require unanimity. | | "Four jurors said SOUND, one blocked on something I think is wrong — I'll sign" | Unanimous-or-nothing. You never sign over a `BLOCK`. If it is an editorial lens, repair it and re-run the whole panel (≤ 2 rounds); if it is a design lens or the budget is spent, it routes to the human sign-off it would have had anyway. Signing because *you* think the juror is wrong is overruling the panel — confidence by the back door. | | "Auto-sign let me continue, so I don't need to notify — it's just progress" | The auto-sign notify is mandatory and carries a decision (the user's veto over a signature made without them). It is the one sanctioned mid-flow notify precisely because it is not progress — it is the audit trail for a delegated gate. | @@ -424,7 +477,10 @@ and defined there. The boss-side contract is just this: - About to send a notify that names a crate, an iteration code, or an agent. - About to bounce back at every iteration boundary "just to be safe" — that's reactive deference, not direction freedom. - About to dispatch `brainstorm` on a backlog issue that does not yet have a spec file, without first bouncing back to the user. New cycles never start autonomously. -- About to route feature work to `brainstorm` without running the Entry-path reflection — defaulting to it because it "feels safer" than `specify` or `tdd`, rather than applying the design-line test. The three are co-equal; the choice is reflected on each time. Routing a *settled* design to `brainstorm` (re-litigating decided choices) is as much a failure as skipping discovery on an open one. +- About to route feature work to `brainstorm` without walking the Entry-path cascade — defaulting to it because it "feels safer" than the lighter arms, rather than taking the first matching positive trigger. Routing a *settled* design, a *test-specifiable* item, or a *behaviour-preserving type-edit* to `brainstorm` is as much a failure as skipping discovery on an open one. +- About to route an observed bug to the `compiler-driven` arm because "the fix is mechanical" — a regression is `debug`, RED-first; the bug-gate is first in the cascade for exactly this reason. +- About to route a behaviour-encoding change to `compiler-driven` because it "looks mechanical" — apply the straddle rule: if the change encodes new behaviour it is `tdd` / `spec-driven`, and doubt means it carries behaviour. +- About to accept a `compiler-driven` edit on a clean build without confirming the suite is green *unchanged* — the conjunction is the done-signal; a green-but-wrong change builds clean. - About to sign a spec under auto-sign on confidence — without all objective gates green and a unanimous `spec-skeptic` panel; signing over any `BLOCK`; self-correcting a *design*-lens (`scope-fork` / `grounding`) `BLOCK` instead of escalating; or looping past the 2-round budget. The gate exists so the orchestrator's confidence never signs. - About to run the auto-sign path at all when the project has not enabled spec auto-sign, or to skip the mandatory auto-sign notify after signing. @@ -453,3 +509,9 @@ and defined there. The boss-side contract is just this: `../fieldtest`, `../debug`, `../tdd` (test-specifiable feature, always available; autonomously dispatchable like `../debug`), `../docwriter`. +- **Compiler-driven arm:** the lighter `compiler-driven` cascade arm and + its observe-then-bounce executor are owned by `../implement` ("The + compiler-driven arm") and shipped as the `compiler-driven-edit` + workflow. The selector arm here and that executor 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. diff --git a/debug/SKILL.md b/debug/SKILL.md index bb49f52..acc3a05 100644 --- a/debug/SKILL.md +++ b/debug/SKILL.md @@ -95,9 +95,9 @@ separate one. Common Rationalisations table, and the Red Flags list. The orchestrator does not execute these phases directly. - **Hand-off target:** `../implement/SKILL.md` — runs the - GREEN side after the orchestrator has decided whether to - commit the RED test separately or as part of the final fix - commit. + GREEN side as the `implement-loop` workflow in `mode: "mini"`, after + the orchestrator has decided whether to commit the RED test separately + or as part of the final fix commit. - **Sibling RED-first skill:** `../tdd/SKILL.md` — same two-stage RED→GREEN handoff to `implement` mini-mode, but triggered by a new test-specifiable behaviour rather than an diff --git a/docs/agent-template.md b/docs/agent-template.md index ba0ccb3..7872bbf 100644 --- a/docs/agent-template.md +++ b/docs/agent-template.md @@ -76,8 +76,7 @@ plugin's agent path is enough disambiguator). Examples: `architect`, `bencher`, `debugger`, `implementer`, `tester`, `fieldtester`, `docwriter`, `grounding-check`, -`spec-skeptic`, `plan-recon`, `spec-reviewer`, `quality-reviewer`, -`implement-orchestrator`. +`spec-skeptic`, `plan-recon`, `spec-reviewer`, `quality-reviewer`. ### `description` @@ -98,9 +97,13 @@ Common tool sets: grounding-check, spec-skeptic, plan-recon): `Read, Glob, Grep, Bash` - Implementation (implementer, tester, debugger, docwriter, fieldtester, bencher): `Read, Edit, Write, Bash, Glob, Grep` -- Orchestrator (implement-orchestrator): `Read, Edit, Write, - Bash, Glob, Grep` — same as implementer; the orchestration - happens via sequential role-switches within its own context + +The autonomous execution loops are **Workflow scripts**, not agents +(`implement/workflows/`): they orchestrate the implementation phase +agents from the top level via `agent()` calls. The agents they +dispatch still carry no `Agent` tool — the no-nested-dispatch rule is +unchanged; the workflow simply does the orchestration the retired +`implement-orchestrator` agent used to fake with inline role-switches. ## Sections in detail diff --git a/docs/design.md b/docs/design.md index 547149b..002a9e1 100644 --- a/docs/design.md +++ b/docs/design.md @@ -29,8 +29,13 @@ genuinely-varying parts moved into each project's `CLAUDE.md`. The plugin owns: - **Pipeline form** — the directed graph of phases: - `design → plan → execute → review → close`, with the - bug-driven side path `debug → execute (mini)`. + `design → plan → execute → review → close`, entered through a + **selector cascade** of entry paths walked on a verification / + enumeration axis (observed bug → `debug`; design fork → + `brainstorm`; behaviour-preserving type/signature edit → + `compiler-driven`; test-specifiable → `tdd`; else → spec-driven). + The taxonomy of paths and their oracles lives in + `pipeline.md` § The methodology taxonomy. - **Hard-gates** — spec before plan, plan before implement, audit before cycle-close. Skipping rules are codified per skill. @@ -50,13 +55,22 @@ The plugin owns: the working tree, never commit. Only the orchestrator commits. - **main HEAD sacrosanct** — no reset, no revert, by any actor. main moves forward only via orchestrator commits. -- **No nested subagent dispatch** — a hard Claude Code - platform constraint; the implement-orchestrator runs phases - as sequential role-switches inside its own context. Opus 4.8 - added Workflows as an orthogonal top-level fan-out mechanism - but did not lift this constraint — a workflow script - orchestrates from the top level, yet the agents it spawns - still cannot spawn further agents. +- **No nested subagent dispatch** — a hard Claude Code platform + constraint: an agent cannot spawn further agents. Workflows do not + lift it; a workflow script orchestrates from the **top level**, and + the agents it spawns still cannot spawn further agents. This is + exactly why the autonomous execution loops moved onto the Workflow + substrate: the `implement-loop` and `compiler-driven-edit` scripts + (`implement/workflows/`) run the per-phase agents as their own + top-level `agent()` calls, so a single phase is independently + invokable — which retired the former `implement-orchestrator` + agent's inline-role-switch workaround (it had to absorb the three + phases into one context precisely because it could not nest). The + four phase agents survive as the agent-types the scripts dispatch; + what was retired is the dispatch/aggregation prose. The interactive + paths (`specify`, `brainstorm`) stay prose skills — their oracle is + human intent and needs a human in the loop, which a + run-to-completion workflow cannot host. - **No orphan agents** — every agent lives under the skill that dispatches it. - **Output budget discipline** — agents have word budgets on diff --git a/docs/migration.md b/docs/migration.md index 5fcb851..6cec237 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -30,6 +30,16 @@ it into `~/.claude/skills/`; if the skill has an discovery still finds everything while the source tree keeps the structural binding. +A skill that ships executable **Workflow scripts** keeps them in a +`workflows/` subdirectory beside its `SKILL.md` (e.g. +`implement/workflows/implement-loop.js`). `install.sh` symlinks each +`*.js` there into the flat `~/.claude/workflows/` directory — where the +Workflow tool resolves named workflows from — keeping the same +structural binding (the script lives with its dispatching skill) as +agents do. `uninstall.sh` removes those symlinks too. A workflow script +is the deterministic form of an autonomous execution loop; it dispatches +the skill's agent-types via top-level `agent()` calls (no nesting). + ## Migration checklist per skill 1. Strip project-specific paths (`docs/specs`, `docs/plans`, diff --git a/docs/pipeline.md b/docs/pipeline.md index 5106e1e..7fae570 100644 --- a/docs/pipeline.md +++ b/docs/pipeline.md @@ -1,14 +1,15 @@ # Pipeline ``` -[new cycle] [test-specifiable feature] [bug observed] - | | | - v v v - brainstorm -> specify -> plan -> implement debug -> implement (mini) - ^ ^ | - | | tdd -> implement (mini) - | specify -> plan (design settled in sources) - +----(design fork)----------/ (specify/tdd bounce here; RED executable-spec -> GREEN) + ENTRY PATHS — the selector cascade, walked top to bottom (see boss/SKILL.md) + bug observed ........... debug ........................ implement (mini) # RED-first; FIRST in the cascade + design fork ............ brainstorm -> specify -> plan -> implement + type/signature edit .... compiler-driven -> commit (clean build + suite green unchanged) + -> bounce: specify | tdd (a hole) | debug (suite red) + test-specifiable ....... tdd -> implement (mini) # RED executable-spec -> GREEN + settled prose design ... specify -> plan -> implement + + (specify / tdd bounce to brainstorm on a design fork) (per iteration loop) | [cycle close — a loop step, not a milestone close] @@ -33,6 +34,30 @@ next cycle ``` +## The methodology taxonomy + +Development paths differ on two irreducible axes — the **first artefact +written**, and the **correctness oracle**. (Error-recovery is not a +third axis: whole-attempt discard is the plugin's universal containment, +not a per-path signature.) The top-level cut — ratified prose intent +before code (spec-driven) vs. machine / test / run oracles — is the hard +boundary; the paths cannot be centralised in one skill, only share the +implement executor as a primitive and the cycle-close `audit` tail. + +| Path | First artefact | Oracle | Bounce-out when | +|------|----------------|--------|-----------------| +| **spec-driven** | ratified prose spec | human intent, judged at the document | (the deliberate heavy sink) | +| **tdd / debug** | a RED test | the assertion goes green | not test-specifiable → `brainstorm` | +| **compiler-driven** | a type / signature edit | clean build + suite green unchanged | a hole needs a decision → `specify` (or `tdd` if it is test-specifiable new behaviour); suite red → `debug` | +| **try-and-error** | throwaway code | an observed run | attempt budget exhausted | + +`try-and-error` is **deferred** — listed for completeness, not yet a +live path. It is an exploration front-end terminating in a tdd-style +handoff, and its attempt tail is structurally unbounded (it can cost +more wall-clock than the spec-driven baseline it is meant to beat, and +its attempt-budget is a magic number the selector cannot derive). Out of +scope until it is given a bounded form. + ## Cycle vs. milestone These are two distinct axes, and conflating them is a bug. @@ -130,12 +155,35 @@ the plan-recon agent for read-only file-structure mapping. ### implement -Dispatches the implement-orchestrator agent, which runs the -entire per-task loop (implementer phase → spec-compliance check -→ quality check) as sequential role-switches inside its own -context. Writes code, tests, and stats files directly in the -working tree as unstaged changes. On `PARTIAL` or `BLOCKED`, -also writes `BLOCKED.md` at the repo root. +Runs the `implement-loop` Workflow (`implement/workflows/implement-loop.js`), +a deterministic script that executes the per-task loop — +implementer → spec-compliance → quality, each a separate +`agent()` call — and aggregates the verdicts in code. A Workflow +orchestrates from the top level, so the per-task phases are real, +independently-invokable agent calls; this retired the former +`implement-orchestrator` agent's inline-role-switch workaround +(the four phase agents survive as the agent-types the script +dispatches). Writes code, tests, and stats files directly in the +working tree as unstaged changes; never commits. On `PARTIAL` or +`BLOCKED`, also writes `BLOCKED.md` at the repo root. + +### compiler-driven + +The lighter executor for a **behaviour-preserving type/signature +edit at a definition site** — the cascade arm between `debug` and +`tdd`. Runs the `compiler-driven-edit` Workflow +(`implement/workflows/compiler-driven-edit.js`): make the edit, +propagate it mechanically across the sites the build enumerates, +then let a real build + suite run settle the verdict — +**observe-then-bounce**. Clean build AND suite green *unchanged* → +the edit is committed by the orchestrator; a hole that forces a +design decision → bounce to `specify` (or `tdd` if it turns out to be +test-specifiable new behaviour, per the straddle rule); the suite not +green-unchanged (a regression — the edit was not behaviour-preserving) +→ bounce to `debug`, RED-first. A truly trivial edit may run inline +without the +workflow, under the same done-signal. Never skips the +review-and-commit discipline; the orchestrator inspects and commits. ### audit @@ -209,14 +257,23 @@ documents what the skill skips and under what conditions: before it: skipped when the design is already settled in the sources (the work enters through `specify` directly), run when a load-bearing decision is still open. -- `planner` is never skipped at iteration start, except for - the bug-driven `debug → implement (mini)` side path. +- `planner` is never skipped at iteration start, except for the + side paths that carry no prose plan: the bug-driven + `debug → implement (mini)` path and the `compiler-driven` path + (whose "plan" is the type-checker's enumeration of edit sites). - `implement` is the iteration body; not skippable. - `audit` is mandatory at cycle close. -- `debug` is mandatory RED-first for any observable bug. +- `debug` is mandatory RED-first for any observable bug — first in + the selector cascade, so a mechanical-looking fix cannot bypass it. - `tdd` is a standard alternative entry to `brainstorm` for test-specifiable work; it bounces back to `brainstorm` on a design fork. Always available — not opt-in. +- `compiler-driven` is the cascade arm for a behaviour-preserving + type/signature edit: it skips `specify` and `planner`, and its + done-signal (clean build + suite green unchanged) is the gate. It + bounces on a hole to `specify` (a design choice) or `tdd` (discovered + test-specifiable new behaviour), and to `debug` on a regression. + Always available — not opt-in. - `fieldtest` and `docwriter` are optional and orchestrator- dispatched. diff --git a/implement/SKILL.md b/implement/SKILL.md index 4e0f3b4..fc98994 100644 --- a/implement/SKILL.md +++ b/implement/SKILL.md @@ -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: "", plan_path: "", - 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-", # tdd: "feat-" + iter_id: "bugfix-", // tdd: "feat-" red_test_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. diff --git a/implement/agents/implement-orchestrator.md b/implement/agents/implement-orchestrator.md deleted file mode 100644 index 3b01c9c..0000000 --- a/implement/agents/implement-orchestrator.md +++ /dev/null @@ -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-"` (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-`. - -### 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-/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-/task-1.md` of the form: - - ``` - Make this RED test pass: - - Context (from debugger cause, or tdd spec_summary): - - - 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-/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-/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 - -**Date:** YYYY-MM-DD -**Started from:** -**Status:** PARTIAL | BLOCKED -**Tasks completed:** of - -## What ran - -- iter .1: -- iter .2: -- ... - -## What did not - -Task : -Worker says: -Suggested next step: - -## Concerns (on the tasks that ran) - - - -## Files touched - - -``` - -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-/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": "", - "date": "YYYY-MM-DD", - "mode": "standard|mini", - "outcome": "DONE|PARTIAL|BLOCKED", - "tasks_total": , - "tasks_completed": , - "reloops_per_task": { "1": 0, "2": 1, ... }, - "review_loops_spec": , - "review_loops_quality": , - "blocked_reason": "" -} -``` - -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: -Started from: -Tasks completed: of - - - - - ... -Working tree: dirty (N files changed) -BLOCKED file: BLOCKED.md (uncommitted; only on PARTIAL/BLOCKED) -Stats: (uncommitted) -Files touched: -Tests: green, red -E2E coverage: -Blocked detail: (only if BLOCKED or PARTIAL — also written to BLOCKED.md) - Task: - Reason: context-exhausted | review-loop-exhausted | worker-blocked | spec-ambiguous | infra - Worker says: - Suggested next step: -``` - -## 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. diff --git a/implement/agents/implementer.md b/implement/agents/implementer.md index 1a49bd1..724e679 100644 --- a/implement/agents/implementer.md +++ b/implement/agents/implementer.md @@ -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 diff --git a/implement/agents/quality-reviewer.md b/implement/agents/quality-reviewer.md index e30d23a..1812fb2 100644 --- a/implement/agents/quality-reviewer.md +++ b/implement/agents/quality-reviewer.md @@ -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 diff --git a/implement/agents/spec-reviewer.md b/implement/agents/spec-reviewer.md index b20a911..1ffb8ee 100644 --- a/implement/agents/spec-reviewer.md +++ b/implement/agents/spec-reviewer.md @@ -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 diff --git a/implement/agents/tester.md b/implement/agents/tester.md index 24d2e85..ee0a15c 100644 --- a/implement/agents/tester.md +++ b/implement/agents/tester.md @@ -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 diff --git a/implement/workflows/compiler-driven-edit.js b/implement/workflows/compiler-driven-edit.js new file mode 100644 index 0000000..2d415f2 --- /dev/null +++ b/implement/workflows/compiler-driven-edit.js @@ -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 || '' : '', +} diff --git a/implement/workflows/implement-loop.js b/implement/workflows/implement-loop.js new file mode 100644 index 0000000..c3c9f81 --- /dev/null +++ b/implement/workflows/implement-loop.js @@ -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-' / 'feat-' (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, +} diff --git a/install.sh b/install.sh index 76c2585..1eeb8cc 100755 --- a/install.sh +++ b/install.sh @@ -5,6 +5,10 @@ # a skill. For each such , creates symlinks: # ~/.claude/skills/ -> / # ~/.claude/agents/ -> //agents (if that dir exists) +# and, for each executable Workflow script the skill ships: +# ~/.claude/workflows/.js -> //workflows/.js +# (the flat .claude/workflows/ dir is where the Workflow tool resolves +# named workflows from — e.g. implement/workflows/implement-loop.js). # # Idempotent — existing symlinks pointing to this repo are left # alone; existing entries that point elsewhere are reported and @@ -16,8 +20,9 @@ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CLAUDE_SKILLS="$HOME/.claude/skills" CLAUDE_AGENTS="$HOME/.claude/agents" +CLAUDE_WORKFLOWS="$HOME/.claude/workflows" -mkdir -p "$CLAUDE_SKILLS" "$CLAUDE_AGENTS" +mkdir -p "$CLAUDE_SKILLS" "$CLAUDE_AGENTS" "$CLAUDE_WORKFLOWS" link_one() { local src="$1" @@ -48,6 +53,12 @@ for d in "$REPO_DIR"/*/; do if [ -d "$d/agents" ]; then link_one "${d%/}/agents" "$CLAUDE_AGENTS/$name" fi + if [ -d "$d/workflows" ]; then + for wf in "$d"workflows/*.js; do + [ -f "$wf" ] || continue + link_one "$wf" "$CLAUDE_WORKFLOWS/$(basename "$wf")" + done + fi done echo diff --git a/postmortem/tests/test_terminal_status.py b/postmortem/tests/test_terminal_status.py index b508732..f9a6209 100644 --- a/postmortem/tests/test_terminal_status.py +++ b/postmortem/tests/test_terminal_status.py @@ -48,7 +48,7 @@ def status_of(final_text): os.unlink(path) -# A realistic implement-orchestrator end-report that SHIPPED (Status: DONE) +# A realistic plain-text iteration end-report that SHIPPED (Status: DONE) # but whose body carries the template's BLOCKED-bearing lines. This is the # exact shape that issue #3 reports as misclassified. SHIPPED_DONE_REPORT = """\ diff --git a/specify/SKILL.md b/specify/SKILL.md index aa43982..5dd4eb1 100644 --- a/specify/SKILL.md +++ b/specify/SKILL.md @@ -434,7 +434,7 @@ CLAUDE.md project facts): **Re-loop limit: ≤ 2 self-correction rounds. The 3rd unresolved editorial `BLOCK` escalates** — same shape as the per-task re-loop - limit in `implement-orchestrator`. + limit in the implement loop (`../implement/workflows/implement-loop.js`). Three properties keep this loop honest, not a way to grind the panel into submission: diff --git a/tdd/SKILL.md b/tdd/SKILL.md index 3a0121e..dfda3a4 100644 --- a/tdd/SKILL.md +++ b/tdd/SKILL.md @@ -206,9 +206,10 @@ iteration and gets queued for a separate one. and Red Flags. The orchestrator does not execute these phases directly. - **Hand-off target (GREEN):** `../implement/SKILL.md` — runs the - GREEN side in mini mode after the orchestrator decides whether to - commit the RED test separately or as part of the final commit. - This is the same mini-mode handoff `../debug/SKILL.md` uses. + GREEN side as the `implement-loop` workflow in `mode: "mini"`, after + the orchestrator decides whether to commit the RED test separately or + as part of the final commit. This is the same mini-mode handoff + `../debug/SKILL.md` uses. - **Bounce-back target:** `../brainstorm/SKILL.md` — reclaims the work when the behaviour is not test-specifiable. - **Sibling fast path:** `../specify/SKILL.md` — the other bounded diff --git a/templates/CLAUDE.md.fragment b/templates/CLAUDE.md.fragment index 3a04930..dff46cf 100644 --- a/templates/CLAUDE.md.fragment +++ b/templates/CLAUDE.md.fragment @@ -29,9 +29,15 @@ discipline the agents are designed to enforce. a fixed design, tester for E2E coverage, debugger for diagnostics, architect for read-only drift review. - **Trivial mechanical edits** (one-line fixes, doc typos, - rename across N files) — fine to do directly. Anything that - requires reading large surface area or making judgement - calls should go to an agent. + rename across N files) — fine to do directly. This is the + inline form of the `compiler-driven` cascade arm, and the + **same done-signal binds**: a clean build AND the suite green + *unchanged*, or it bounces (a hole → `specify`, or `tdd` if it + is test-specifiable new behaviour; a regression → `debug`, + RED-first). An observed bug never enters here — it + is `debug`'s job. Anything that requires reading large surface + area or making judgement calls should go to an agent (or the + `compiler-driven-edit` / `implement-loop` workflow). - **Verify the work** — agent reports describe intent, not outcome. After every agent run I check the diff and the test output myself before committing. diff --git a/uninstall.sh b/uninstall.sh index 54030b3..c6f7dff 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -28,5 +28,6 @@ unlink_pointing_here() { unlink_pointing_here "$HOME/.claude/skills" unlink_pointing_here "$HOME/.claude/agents" +unlink_pointing_here "$HOME/.claude/workflows" echo "Uninstall complete."