docs(plan): model routing wave 2 — pure-execution + reflection-split skills (doc/status/hotfix/commit-change/release-candidate)

This commit is contained in:
Bastien Chanot
2026-07-15 12:34:20 +02:00
parent 2ad712cfd4
commit 89093a7835
@@ -1058,3 +1058,305 @@ git commit -m "chore(memory): BDR-066 model routing + journal + TODO follow-ups"
Run: `make test` Run: `make test`
Expected: green, exit 0. Then report the full commit list (`git log --oneline develop..HEAD`) for the human merge gate. Do NOT run `gitflow finish`. Expected: green, exit 0. Then report the full commit list (`git log --oneline develop..HEAD`) for the human merge gate. Do NOT run `gitflow finish`.
---
# WAVE 2 — pure-execution + reflection-split skills (user directive 2026-07-15)
The wave-1 exclusion list left `hotfix, commit-change, doc, status, release-candidate`
inheriting the session model — i.e. running EXECUTION on the big model, the
waste the split exists to kill. User verdicts (2026-07-15):
- **doc / status** = pure non-interactive execution → convert inline-load to a
dispatched subagent so its pin takes effect. doc-syncer stays sonnet;
status-reporter stays **haiku** (right tier for a read-only collector; the
win is getting it off the big model, not the tier).
- **hotfix** = reflection (locate root cause + propose fix) + execution → split
like /feat: reflection inline (+ MODEL GATE), execution dispatched to the
sonnet hotfixer executor. hotfix JOINS the gated group (12→13).
- **commit-change** = grouping (judgment) + committing (execution), interactive
→ dispatch EVERYTHING (grouping included) to a sonnet commit-changer, relocate
the two approval gates to the dispatcher (propose→confirm→execute, seo-applier
shape). No MODEL GATE (no inline reflection — grouping runs on sonnet).
- **release-candidate** = create a sonnet `release-executor` agent for the
mechanical spans (version.txt, CHANGELOG rewrite, gitflow start/finish, tag),
relocate the two human gates (when-to-release, push) to the dispatcher. No
MODEL GATE (user's explicit choice — force dispatch, not gate).
Post-wave-2 gate exclusion list = `commit-change, doc, status, release-candidate`
(hotfix removed — now wired).
## Global Constraints (wave 2)
Same as wave 1: branch `feature/model-routing`, no merge, no attribution
trailers, `make test` green per commit, shellcheck clean, config-protection
sentinel before each `lib/tests/*` write, YAML `safe_load`-parseable frontmatter.
---
### Task 11: doc + status → dispatched execution
**Files:**
- Modify: `skills/doc/SKILL.md` (add `Agent` to allowed-tools; body → dispatch)
- Modify: `skills/status/SKILL.md` (add `Agent` to allowed-tools; body → dispatch)
**Interfaces:** doc-syncer.md is already `model: sonnet`; status-reporter.md is
already `model: haiku` — no agent edits. Only the skills change from inline-load
to `Agent(subagent_type=…)` so the pins take effect.
- [ ] **Step 1: doc → dispatch.** In `skills/doc/SKILL.md`, add ` - Agent` to the
`allowed-tools` list, and replace the body block
```
Load and follow strictly:
- $HOME/.claude/agents/doc-syncer.md
Execute the DOC SYNCER on this project.
Context from the user (if any):
$ARGUMENTS
```
with:
```
Dispatch the doc-syncer as a subagent so its `model: sonnet` pin takes
effect (doc-sync = execution, not the session's big model):
Agent(subagent_type="doc-syncer")
prompt: "Audit + sync public docs for this project. Context from the user:
$ARGUMENTS. Report PATCHED_FILES and a summary — do NOT commit."
Then commit the patched docs from THIS loop per `$HOME/.claude/lib/doc-commit.md`
(surgical: only doc-syncer's PATCHED_FILES, never `.claude/`/`CLAUDE.md`,
no-op if nothing patched).
```
- [ ] **Step 2: status → dispatch.** In `skills/status/SKILL.md`, add `Agent` to
`allowed-tools` (`Read, Bash, Glob, Grep, Agent`), and replace the body
`Load and follow strictly:\n- $HOME/.claude/agents/status-reporter.md\n\nProduce the full PROJECT STATUS report for the current working directory.`
with a dispatch:
```
Dispatch the status-reporter as a subagent so its `model: haiku` pin takes
effect (read-only collection = cheapest tier, off the big session model):
Agent(subagent_type="status-reporter")
prompt: "Produce the full PROJECT STATUS report for the current working
directory. $ARGUMENTS"
```
Keep the existing "Fallback when agent file missing" section intact (it still
applies — if the dispatch target is unreachable, emit the missing-agent line
and STOP).
- [ ] **Step 3: Verify + commit.** `grep -c 'subagent_type="doc-syncer"' skills/doc/SKILL.md`
→ 1; `grep -c 'subagent_type="status-reporter"' skills/status/SKILL.md` → 1.
`make test` green.
```bash
git add skills/doc/SKILL.md skills/status/SKILL.md
git commit -m "feat(model-routing): doc/status dispatch their agent (sonnet/haiku pins take effect)"
```
---
### Task 12: hotfix — reflection inline + dispatched sonnet executor (/feat pattern)
**Files:**
- Modify (rewrite): `skills/hotfix/SKILL.md` — becomes the reflection orchestrator
- Modify (rewrite): `agents/hotfixer.md` — becomes pure executor
- Modify: `lib/tests/loops-light.test.sh` — repoint hotfix structure locks (guarded)
**Pattern:** mirror the shipped `/feat` split (skills/feat/SKILL.md + agents/feater.md).
- [ ] **Step 1: Rewrite `skills/hotfix/SKILL.md` as the orchestrator.** Keep `Agent`
in allowed-tools. Structure:
- `# /hotfix — quick-fix orchestrator (reflection inline, execution dispatched)`
- `MODEL GATE (blocking): run $HOME/.claude/lib/model-gate.md BEFORE any step. small → STOP.`
(hotfix now has a reflection phase → it joins the gated group.)
- STEP 1 LOCATE (reflection, inline): find the bug from the description, read
the file(s), CONFIRM the root cause is obvious/superficial, escalate to
`/bugfix` if deeper. Optional blockers-only memory glance (as today).
- STEP 1.5 DESIGN GATE (`lib/design-gate.md`, as today).
- STEP 1.7 CONTRACT (silent autofill, `lib/contract-interview.md`, zero
questions, as today).
- STEP 2 PRE-FLIGHT (inline): gitflow aiguillage (type `hotfix`); snapshot
`git rev-parse HEAD` (the revert SHA) + dirty-tree check (as today's STEP 2
pre-flight).
- STEP 3 DISPATCH EXECUTOR: `Agent(subagent_type="hotfixer")` with the
contract path, the located file(s), the proposed minimal fix, and the branch.
Parse a `HOTFIX-EXEC REPORT` with `STATUS : DONE | BLOCKED`.
- STEP 4 VERIFY + SECURE + COMMIT (main loop, LRN-083): on executor DONE, the
smoke result is in its report; then the security gate — dispatch a FRESH
security-auditor (`MODE: gate`, SCOPE = diff vs the pre-flight SHA). **hotfix
keeps revert-not-loop**: smoke FAIL or security BLOCK → `git restore .` to the
pre-flight SHA + STOP + "escalate to /bugfix" (verbatim from today's STEP 3).
No verifier at hotfix weight. Commit only after smoke + security pass.
- STEP 5 DOC SYNC + STEP 6 CAPITALIZE: identical to today's STEP 4/5 (doc-sync
auto-mode + `doc-commit.md`; lightweight capitalize + always-on journal +
`capitalize-commit.md`).
- RULES: max 2 files; execution never stays inline, reflection never leaves it
(BDR-066); executor dispatched fresh; revert-not-loop preserved.
- [ ] **Step 2: Rewrite `agents/hotfixer.md` as the executor.** Frontmatter:
`tools: Read, Edit, Write, Bash, Grep, Glob` (DROP `Agent` — no nested dispatch;
security moved to the orchestrator), `model: sonnet`. Body: receive
CONTRACT + located file(s) + proposed fix + BRANCH (verify with
`git branch --show-current`, never switch). Apply the minimal edit (no
refactoring), run the stack smoke/test cascade (keep today's detection cascade),
report. FORBIDDEN: git commit, branch ops, security dispatch, user questions,
attribution trailers. End with:
```
HOTFIX-EXEC REPORT
STATUS : DONE | BLOCKED
FILE(S) : <changed files>
FIX : <one-line description>
SMOKE : <test/build result, verbatim line>
NOTES : <BLOCKED: the blocker; DONE: none>
```
- [ ] **Step 3: Repoint `lib/tests/loops-light.test.sh` hotfix locks** (guarded —
sentinel first). The hotfix block currently checks `agents/hotfixer.md` for
orchestration clauses now moved to the skill. Introduce `HSKL="$REPO/skills/hotfix/SKILL.md"`
and repoint: contract/silent → HSKL `STEP 1.7 — CONTRACT`; security gate +
`failure REVERTS, never loops` + `No verifier is dispatched at hotfix weight`
→ HSKL; `hotfix skill has Agent` (HSK ` - Agent`) unchanged. The
`hotfix has Agent tool` lock on hotfixer INVERTS (hotfixer no longer has Agent)
→ change to assert hotfixer LACKS Agent and carries `model: sonnet` + the
`HOTFIX-EXEC REPORT` grammar. Add a `hotfix dispatches hotfixer` lock
(`subagent_type="hotfixer"` in HSKL). Keep include/feat/bugfix blocks untouched.
Add a negative-match helper `tn()` (mirror `tf`, invert the grep) if asserting
Agent-absence.
- [ ] **Step 4: Wire hotfix into the census + gate lists.** In
`lib/tests/model-routing.test.sh` (guarded — sentinel first): MOVE `hotfix`
from the excluded loop to the wired loop (`has "skills/hotfix/SKILL.md" 'lib/model-gate.md'`).
Update the expected count in the run message accordingly.
- [ ] **Step 5: Verify + commit.** `bash lib/tests/loops-light.test.sh` green;
`bash lib/tests/model-routing.test.sh` green; YAML check both rewritten files;
`make test` green.
```bash
git add skills/hotfix/SKILL.md agents/hotfixer.md lib/tests/loops-light.test.sh lib/tests/model-routing.test.sh
git commit -m "feat(model-routing): /hotfix split — reflection inline + gate, hotfixer = sonnet executor"
```
---
### Task 13: commit-change — dispatch grouping+commit to sonnet, relocate approval gates
**Files:**
- Modify (rewrite): `skills/commit-change/SKILL.md` — dispatcher owns the two gates
- Modify (rewrite): `agents/commit-changer.md` — sonnet, propose/execute phases, no AskUserQuestion
**Pattern:** seo-applier shape (subagent proposes → dispatcher confirms → subagent executes).
- [ ] **Step 1: Rewrite `agents/commit-changer.md`.** Frontmatter:
`tools: Bash, Read, Grep, Glob` (DROP `AskUserQuestion` — gates move to the
dispatcher), `model: sonnet`. Body: two modes driven by the dispatch prompt.
- `MODE: propose` → Phase 0 (gitflow aiguillage, type chore — bash), Phase 1
(gather), Phase 2 (reconstruct steps), Phase 2.5 → EMIT the `COMMIT PLAN`
block + any edge-case flags (sensitive files, staged-only, conflicts) +
Phase-4 capitalize candidates, then STOP with
`READY TO APPLY — awaiting dispatcher confirmation`. Writes NOTHING.
- `MODE: apply` → receive the APPROVED plan (steps + messages) + approved
capitalize entries; execute Phase 3 (stage-per-step + commit) and write the
approved memory via `capitalize-commit.md`; report the commit hashes.
- [ ] **Step 2: Rewrite `skills/commit-change/SKILL.md` as dispatcher.** Keep
`Agent` + `AskUserQuestion` in allowed-tools. Flow: pre-flight (detached HEAD /
conflicts / identity — STOP as today) → `Agent(subagent_type="commit-changer")`
with `MODE: propose` → show the returned COMMIT PLAN, `AskUserQuestion`
(all / numbers / edit / skip) → show capitalize candidates, `AskUserQuestion`
(all / IDs / skip) → `Agent(subagent_type="commit-changer")` with `MODE: apply`
+ the approved plan + approved entries → report hashes. NO MODEL GATE (grouping
runs on the sonnet subagent — no inline reflection to protect).
- [ ] **Step 3: Verify + commit.** `grep -c 'subagent_type="commit-changer"' skills/commit-change/SKILL.md`
≥ 1; `grep -c 'model: sonnet' agents/commit-changer.md` → 1;
`grep -c 'AskUserQuestion' agents/commit-changer.md` → 0; YAML check; `make test` green.
```bash
git add skills/commit-change/SKILL.md agents/commit-changer.md
git commit -m "feat(model-routing): /commit-change dispatch to sonnet commit-changer, gates relocated to dispatcher"
```
---
### Task 14: release-candidate — sonnet release-executor, human gates relocated
**Files:**
- Create: `agents/release-executor.md` — sonnet, mechanical release spans
- Modify (rewrite): `skills/release-candidate/SKILL.md` — dispatcher owns the two human gates
- [ ] **Step 1: Create `agents/release-executor.md`.** Frontmatter:
`tools: Read, Edit, Write, Bash, Grep, Glob`, `model: sonnet`. Two-span
executor driven by the dispatch prompt (a human gate sits BETWEEN the spans, so
it cannot be one dispatch):
- `SPAN: prep <X.Y.Z>` → `gitflow start release <X.Y.Z>`, set `version.txt`,
rewrite CHANGELOG (`## [Unreleased]` → `## [<X.Y.Z>] — <date>`, re-open empty
Unreleased; a MAJOR must spell out breaking), run the test suite (RC gate —
never release red), commit the prep on the release branch. Report the branch
+ test result. No merge, no tag, no push.
- `SPAN: finish <X.Y.Z>` → `gitflow finish` (fan-out), `git tag -a v<X.Y.Z> main
-m "release <X.Y.Z>"` AFTER finish. Report. NEVER push (dispatcher's gate).
- FORBIDDEN: deciding the version number (dispatcher/user owns it), the
when-to-release decision, `git push`, attribution trailers.
- [ ] **Step 2: Rewrite `skills/release-candidate/SKILL.md` as dispatcher.** Add
`allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Agent, AskUserQuestion` to
the frontmatter (it currently has none). Keep all the Overview/Versioning/Common-
mistakes doctrine. Flow: preconditions (clean tree, identity, develop ahead of
main) → the version-number decision stays HERE (judgment: derives from change
nature; decide before running) → `Agent(subagent_type="release-executor")`
`SPAN: prep <X.Y.Z>` → **HUMAN GATE — when to release** (`AskUserQuestion`,
explicit go, never on "tests pass") → `Agent(subagent_type="release-executor")`
`SPAN: finish <X.Y.Z>` → **push GATE (ASK)** (`AskUserQuestion`; on go only,
LRN-069): `git push origin main develop && git push origin v<X.Y.Z>` from THIS
loop. No MODEL GATE.
- [ ] **Step 3: Verify + commit.** `RC_WORK=$(mktemp -d) RC_TAG=1 bash lib/tests/run-release-candidate.sh`
→ 5/5 (the release mechanics test is unchanged — the lib still fans out + the
dispatcher still tags); `grep -c 'subagent_type="release-executor"' skills/release-candidate/SKILL.md`
≥ 1; YAML check the new agent; `make test` green.
```bash
git add agents/release-executor.md skills/release-candidate/SKILL.md
git commit -m "feat(model-routing): /release-candidate dispatches sonnet release-executor, human gates in dispatcher"
```
---
### Task 15: census + docs + memory for wave 2
**Files:**
- Modify: `lib/tests/model-routing.test.sh` (guarded) — wave-2 assertions
- Modify: `README.md`, `CHANGELOG.md`, `.claude/memory/decisions.md`, `.claude/tasks/TODO.md`
- [ ] **Step 1: Extend the census** (`lib/tests/model-routing.test.sh`, guarded —
sentinel first). The excluded loop drops `hotfix` (moved to wired by Task 12 Step 4)
and now reads `for s in commit-change doc status release-candidate`. Add
execution-dispatch asserts: `has skills/doc/SKILL.md 'subagent_type="doc-syncer"'`;
`has skills/status/SKILL.md 'subagent_type="status-reporter"'`;
`has skills/commit-change/SKILL.md 'subagent_type="commit-changer"'`;
`has skills/release-candidate/SKILL.md 'subagent_type="release-executor"'`;
pins `has agents/commit-changer.md 'model: sonnet'`,
`has agents/release-executor.md 'model: sonnet'`; and executor-shape
`lacks agents/commit-changer.md 'AskUserQuestion'`. Update the printed expected
count. Flip-test one new assertion (LRN-096).
- [ ] **Step 2: README + CHANGELOG.** Update the BDR-066 agent-model table:
hotfixer stays sonnet (now an effective executor), commit-changer → sonnet,
release-executor (new) → sonnet, status-reporter → haiku (now effective via
dispatch). Move doc/status/commit-change/release-candidate out of the "inherit"
row into a new "execution — dispatched" line. CHANGELOG Unreleased: add the
wave-2 bullets (doc/status/hotfix/commit-change/release-candidate routing).
- [ ] **Step 3: Capitalize.** Append to the BDR-066 entry a `**Wave 2**` bullet:
doc/status dispatched (sonnet/haiku pins effective); hotfix split like /feat
(joins gated group); commit-change dispatched sonnet with relocated gates;
release-candidate sonnet release-executor with relocated human gates; exclusion
list now commit-change/doc/status/release-candidate. Journal line +
TODO tick under the 2026-07-15 section.
```bash
git add lib/tests/model-routing.test.sh README.md CHANGELOG.md .claude/memory/decisions.md .claude/memory/journal.md .claude/tasks/TODO.md
git commit -m "chore(model-routing): wave-2 census + docs + BDR-066 update"
```
- [ ] **Step 4: Final wave-2 review** — dispatch a whole-branch reviewer (opus) over
the wave-2 range; confirm both consumers of verify-secure-loop still coherent,
hotfix revert-not-loop preserved, no execution left on the big model in the
five converted skills. Report the full `git log --oneline develop..HEAD`. Do NOT
merge.