Files
claude/docs/superpowers/plans/2026-07-15-model-routing.md
T

1061 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Model Routing Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Reflection (planning, audits, loop decisions) stays on the session big model behind a blocking gate; execution (code from a closed plan, fix-bundle application) runs on sonnet-pinned subagents.
**Architecture:** A deterministic witness (`lib/model-check.sh`) + a blocking include (`lib/model-gate.md`) wired into 12 reflection orchestrators; frontmatter `model:` pins on executor agents; `/feat` re-architected from inline playbook to "plan inline → dispatch sonnet executor"; SDD implementation subagents and web-validate fix application routed to sonnet.
**Tech Stack:** bash (shellcheck-clean), Claude Code SKILL.md/agent.md markdown, agent frontmatter `model:` field, Makefile test loop.
**Spec:** `docs/superpowers/specs/2026-07-15-model-routing-design.md` (approved 2026-07-15).
## Global Constraints
- Work on branch `feature/model-routing` (already checked out). Commit per task. NEVER merge/finish — human gate.
- NO commit attribution trailers of any kind (Co-Authored-By, Claude-Session) — user ban, guards will red.
- `make test` must be green at every commit (run from repo root `/home/bchanot/Documents/claude`).
- New/edited `.sh` files: `shellcheck <file>` clean and `bash -n <file>` clean.
- The `config-protection` PreToolUse hook BLOCKS Edit/Write on `lib/tests/*`, `hooks/*`, `settings.json`, `lib/gitflow.sh`. Before EACH Edit/Write to `lib/tests/*` in this plan, write the one-shot bypass sentinel (consumed per use): `printf 'model-routing plan: <what you are writing>' > .claude/.config-edit-ok`
- Agent frontmatter must stay `yaml.safe_load`-parseable (job9 gate): if a value contains `: `, quote it.
- Memory registries: append-only, caveman format, English.
- SPEC §5 (client-handover conversion) is DEFERRED to a separate plan — do NOT touch `agents/client-handover-writer.md` or `skills/client-handover/SKILL.md` in this plan.
---
### Task 1: `lib/model-check.sh` witness + flip-tests
**Files:**
- Create: `lib/model-check.sh`
- Test: `lib/tests/model-check.test.sh` (guarded path — sentinel required)
**Interfaces:**
- Consumes: `$HOME/.claude/settings.json` `"model"` key; env override `MODEL_CHECK_SETTINGS=<path>` for fixtures.
- Produces: stdout `<class>:<raw>` where class ∈ `big|small|unknown`; exit `0`=big, `2`=small, `3`=unknown. Task 2's `lib/model-gate.md` calls `bash "$HOME/.claude/lib/model-check.sh"` and branches on these exact codes.
- [ ] **Step 1: Write the failing test**
```bash
printf 'model-routing plan: create model-check flip-tests' > .claude/.config-edit-ok
```
Then create `lib/tests/model-check.test.sh` with exactly:
```bash
#!/usr/bin/env bash
# lib/tests/model-check.test.sh — flip-tests for lib/model-check.sh (LRN-096)
set -u
S="$(cd "$(dirname "$0")/../.." && pwd)/lib/model-check.sh"
pass=0; fail=0
check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1));
printf 'FAIL %s: got[%s] want[%s]\n' "$1" "$2" "$3"; fi; }
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
fx() { printf '{"model": "%s"}' "$1" > "$T/s.json"; }
run() { MODEL_CHECK_SETTINGS="$T/s.json" bash "$S" >"$T/out" 2>&1; echo "$?"; }
fx 'claude-fable-5[1m]'; check T1-fable-exit "$(run)" 0
check T1-fable-class "$(cut -d: -f1 <"$T/out")" big
fx 'claude-opus-4-8'; check T2-opus "$(run)" 0
fx 'claude-sonnet-5'; check T3-sonnet "$(run)" 2
fx 'claude-haiku-4-5-20251001'; check T4-haiku "$(run)" 2
fx 'opusplan'; check T5-opusplan "$(run)" 3
fx 'gpt-9-mega'; check T6-foreign "$(run)" 3
printf '{"no_model": true}' > "$T/s.json"; check T7-no-key "$(run)" 3
printf '{broken' > "$T/s.json"; check T8-malformed "$(run)" 3
check T9-missing-file "$(MODEL_CHECK_SETTINGS="$T/absent.json" bash "$S" >/dev/null 2>&1; echo $?)" 3
printf 'model-check: %d pass, %d fail\n' "$pass" "$fail"
[ "$fail" -eq 0 ]
```
- [ ] **Step 2: Run test to verify it fails**
Run: `bash lib/tests/model-check.test.sh`
Expected: FAIL on every check (script missing → bash exits non-zero, got[127]-style mismatches), final line `model-check: 1 pass, 9 fail` or similar non-zero fail count, exit 1. (T1-fable-class may pass vacuously on empty output only if cut returns empty — any red is enough: the suite CAN fail.)
- [ ] **Step 3: Write the implementation**
Create `lib/model-check.sh` with exactly:
```bash
#!/usr/bin/env bash
# lib/model-check.sh — classify the persisted session model: big | small | unknown
#
# Witness for lib/model-gate.md (reflection requires a big model). Reads the
# "model" key of the user-scope settings (the file /model rewrites — LRN-098).
# Override the source with MODEL_CHECK_SETTINGS (tests use fixtures).
#
# stdout : <class>:<raw> (raw = value found, empty if none)
# exit : 0 = big (fable/opus) · 2 = small (sonnet/haiku) · 3 = unknown
set -u
SETTINGS="${MODEL_CHECK_SETTINGS:-$HOME/.claude/settings.json}"
raw=""
if [ -f "$SETTINGS" ]; then
raw="$(python3 - "$SETTINGS" 2>/dev/null <<'PY'
import json, sys
try:
v = json.load(open(sys.argv[1])).get("model", "")
print(v if isinstance(v, str) else "")
except Exception:
print("")
PY
)"
fi
norm="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
case "$norm" in
*opusplan*) printf 'unknown:%s\n' "$raw"; exit 3 ;; # opus-for-plan, sonnet otherwise — ambiguous
*fable*|*opus*) printf 'big:%s\n' "$raw"; exit 0 ;;
*sonnet*|*haiku*) printf 'small:%s\n' "$raw"; exit 2 ;;
*) printf 'unknown:%s\n' "$raw"; exit 3 ;;
esac
```
(The heredoc passes the settings path as `sys.argv[1]` — never pipe INTO a heredoc'd interpreter, LRN-012.)
- [ ] **Step 4: Run test to verify it passes**
Run: `bash lib/tests/model-check.test.sh`
Expected: `model-check: 10 pass, 0 fail`, exit 0.
- [ ] **Step 5: Lint**
Run: `shellcheck lib/model-check.sh lib/tests/model-check.test.sh && bash -n lib/model-check.sh`
Expected: no output (clean), exit 0.
- [ ] **Step 6: Full suite + commit**
Run: `make test`
Expected: every suite line green, exit 0.
```bash
git add lib/model-check.sh lib/tests/model-check.test.sh
git commit -m "feat(model-routing): model-check witness (big/small/unknown) + flip-tests"
```
---
### Task 2: `lib/model-gate.md` blocking include
**Files:**
- Create: `lib/model-gate.md`
**Interfaces:**
- Consumes: `lib/model-check.sh` exit codes (Task 1).
- Produces: the include that Tasks 3 and 5 reference verbatim as `` `$HOME/.claude/lib/model-gate.md` ``.
- [ ] **Step 1: Create the include**
Create `lib/model-gate.md` with exactly:
```markdown
# Model gate — reflection requires a big model (BLOCKING)
Shared include. Runs FIRST in any orchestrator whose reflection —
brainstorming, planning, contract, audit judgment, loop decisions —
executes inline or in inherit-model subagents. Sonnet-pinned executors are
not what this gate protects; it protects the thinking around them (BDR-066).
## 1. Self-check
Your system prompt names the model powering this session. Fable or Opus →
big. Sonnet, Haiku, anything else → small.
## 2. Witness — deterministic check
bash "$HOME/.claude/lib/model-check.sh"
Output `<class>:<raw>`; exit 0 = big, 2 = small, 3 = unknown. The witness
reads the PERSISTED model (settings.json — the file `/model` rewrites,
LRN-098). It can lag reality (session launched with `--model`, settings not
yet rewritten) — that is why the self-check exists alongside it.
## 3. Verdict
| self-check | witness | action |
|---|---|---|
| big | big (0) | proceed, SILENT — the nominal path prints nothing |
| small | any | **STOP** |
| big | small (2) | disagreement — **STOP**, surface BOTH values; the user confirms or relaunches |
| big | unknown (3) | fail-visible: print `model gate: witness unknown (<raw>) — self-check says <model>` and ask the user to confirm before continuing (BDR-025: unknown never silently passes) |
**STOP means**: print exactly
⛔ MODEL GATE — session on <model>. Reflection steps of this skill
require Fable or Opus. Switch with /model, then relaunch the skill.
then end the turn. No later step runs, no agent is dispatched, nothing is
edited.
```
- [ ] **Step 2: Commit**
```bash
git add lib/model-gate.md
git commit -m "feat(model-routing): blocking model-gate include (self-check + witness)"
```
---
### Task 3: Wire the gate into the 12 reflection orchestrators
**Files:**
- Modify: `skills/ship-feature/SKILL.md`, `skills/init-project/SKILL.md`, `skills/onboard/SKILL.md`, `skills/seo/SKILL.md`, `skills/geo/SKILL.md`, `skills/web-validate/SKILL.md`, `skills/harden/SKILL.md`, `skills/audit-delta/SKILL.md`, `skills/tour/SKILL.md` (orchestrator idiom), `skills/feat/SKILL.md`, `skills/bugfix/SKILL.md`, `skills/code-clean/SKILL.md` (thin-wrapper idiom)
**Interfaces:**
- Consumes: `lib/model-gate.md` (Task 2).
- Produces: the string `lib/model-gate.md` present in each of the 12 files — Task 8's census greps exactly this.
- [ ] **Step 1: Insert the orchestrator gate block (9 files)**
For each of the 9 orchestrator skills, Edit with `old_string` = the file's unique H1 line (below), `new_string` = the same H1 line followed by a blank line and this exact block:
```markdown
## MODEL GATE (blocking — run before any other step)
Run `$HOME/.claude/lib/model-gate.md`. Reflection here (planning, audit
judgment, loop decisions) requires Fable/Opus. Verdict `small` → STOP: the
gate prints the remedy; end the turn — no later step, no dispatch. Nominal
(big) path is silent.
```
H1 anchors (verbatim, one per file):
- `skills/ship-feature/SKILL.md` → `# ORCHESTRATOR: SHIP FEATURE`
- `skills/init-project/SKILL.md` → `# ORCHESTRATOR: INIT PROJECT`
- `skills/onboard/SKILL.md` → `# ORCHESTRATOR: ONBOARD`
- `skills/seo/SKILL.md` → `# /seo — parallel SEO + GEO dispatcher`
- `skills/geo/SKILL.md` → `# /geo — GEO (AI-search) audit + fix dispatcher`
- `skills/web-validate/SKILL.md` → `# /web-validate — web standards audit (W3C + WCAG)`
- `skills/harden/SKILL.md` → `# /harden — web hardening audit`
- `skills/audit-delta/SKILL.md` → `# /audit-delta — Incremental multi-axis code audit`
- `skills/tour/SKILL.md` → `# /tour — grouped multi-axis sweep (clean + security + reconcile + doc)`
- [ ] **Step 2: Insert the thin-wrapper gate paragraph (3 files)**
For `skills/feat/SKILL.md`, `skills/bugfix/SKILL.md`, `skills/code-clean/SKILL.md`: Edit with `old_string` = `Load and follow strictly:` and `new_string` =
```markdown
MODEL GATE (blocking): run `$HOME/.claude/lib/model-gate.md` BEFORE loading
the agent below. Verdict `small` → STOP — print the gate's remedy, end the
turn, do not load the agent.
Load and follow strictly:
```
(`Load and follow strictly:` occurs once per file — safe anchor.)
- [ ] **Step 3: Verify the wiring by census**
Run: `for s in ship-feature init-project feat bugfix onboard seo geo web-validate harden audit-delta tour code-clean; do grep -L 'lib/model-gate.md' "skills/$s/SKILL.md"; done`
Expected: no output (grep -L lists files MISSING the pattern — empty = all wired).
Run: `for s in hotfix commit-change doc status release-candidate; do grep -l 'lib/model-gate.md' "skills/$s/SKILL.md"; done`
Expected: no output (excluded skills stay unwired).
- [ ] **Step 4: Full suite + commit**
Run: `make test`
Expected: green, exit 0.
```bash
git add skills/ship-feature/SKILL.md skills/init-project/SKILL.md skills/onboard/SKILL.md skills/seo/SKILL.md skills/geo/SKILL.md skills/web-validate/SKILL.md skills/harden/SKILL.md skills/audit-delta/SKILL.md skills/tour/SKILL.md skills/feat/SKILL.md skills/bugfix/SKILL.md skills/code-clean/SKILL.md
git commit -m "feat(model-routing): wire blocking model gate into 12 reflection orchestrators"
```
---
### Task 4: Frontmatter pins — hotfixer sonnet, analyzer un-pinned
**Files:**
- Modify: `agents/hotfixer.md:1-5` (frontmatter)
- Modify: `agents/analyzer.md:1-7` (frontmatter)
**Interfaces:**
- Produces: `model: sonnet` line in hotfixer frontmatter (Task 7's applier dispatches and seo/geo L1 appliers ride on it); NO `model:` line in analyzer frontmatter (inherits session). Task 8's census greps both.
- [ ] **Step 1: Pin hotfixer**
Edit `agents/hotfixer.md`, `old_string`:
```
tools: Read, Edit, Write, Bash, Grep, Glob, Agent
---
```
`new_string`:
```
tools: Read, Edit, Write, Bash, Grep, Glob, Agent
model: sonnet
---
```
- [ ] **Step 2: Un-pin analyzer**
Edit `agents/analyzer.md`, `old_string`:
```
tools: Read, Grep, Glob, Bash
model: haiku
memory: project
```
`new_string`:
```
tools: Read, Grep, Glob, Bash
memory: project
```
- [ ] **Step 3: Verify YAML stays parseable**
Run: `python3 -c "import yaml,sys; [yaml.safe_load(open(f).read().split('---')[1]) for f in ['agents/hotfixer.md','agents/analyzer.md']]; print('YAML OK')"`
Expected: `YAML OK`.
- [ ] **Step 4: Full suite + commit**
Run: `make test`
Expected: green (includes the job9 review guards).
```bash
git add agents/hotfixer.md agents/analyzer.md
git commit -m "feat(model-routing): pin hotfixer sonnet (executor), un-pin analyzer (inherits session)"
```
---
### Task 5: `/feat` re-architecture — reflection inline, execution dispatched
**Files:**
- Modify (full rewrite): `skills/feat/SKILL.md`
- Modify (full rewrite): `agents/feater.md`
- Modify (3 surgical edits): `lib/verify-secure-loop.md`
**Interfaces:**
- Consumes: `lib/model-gate.md` (Task 2), `lib/verify-secure-loop.md`, `lib/contract-interview.md`, `lib/gitflow-aiguillage.md`, `lib/analyze-before-plan.md`, `lib/design-gate.md` (all existing).
- Produces: `Agent(subagent_type="feater")` dispatch in feat/SKILL.md; feater `FEAT-EXEC REPORT` grammar `STATUS : DONE | NEED-DECISION | BLOCKED`; `model: sonnet` in feater frontmatter. Task 8's census greps `subagent_type="feater"`, `verify-secure-loop.md`, feater `model: sonnet`, feater has NO `AskUserQuestion`.
- [ ] **Step 1: Rewrite `skills/feat/SKILL.md`**
Replace the ENTIRE file content with:
````markdown
---
name: feat
description: |
Small feature implementation (1-5 files). Reflection inline (scope,
plan, contract — session model), execution dispatched to the
sonnet-pinned feater executor. For features that don't need the full
/ship-feature pipeline (no design brainstorm, no plugin check gate).
Trigger: "feat", "small feature", "add this", "petite feature",
"quick feature", "ajoute ca", "implement this small thing".
For multi-file features needing design → use /ship-feature.
For bug fixes → use /hotfix or /bugfix.
argument-hint: <feature description>
allowed-tools:
- Read
- Edit
- Write
- Bash
- Grep
- Glob
- Agent
---
# /feat — small-feature orchestrator (reflection inline, execution dispatched)
MODEL GATE (blocking): run `$HOME/.claude/lib/model-gate.md` BEFORE any
step below. Verdict `small` → STOP — print the gate's remedy, end the
turn, dispatch nothing.
## REQUEST
$ARGUMENTS
---
## STEP 0 — SCOPE CHECK
Before starting, verify this is actually a small feature:
```bash
git status
git log --oneline -3
```
Read the relevant existing code to understand the context.
### Decision rules (apply in order — first match wins)
| Rule | Trigger | Action |
|---|---|---|
| 1 | Estimated diff < 2 files AND no logic (config value, copy fix, missing field) | DOWNGRADE → load `$HOME/.claude/agents/hotfixer.md` |
| 2 | New external dependency (`npm install <x>`, `pip install`, `cargo add`) required | ESCALATE → `/ship-feature` (dep choices need design gate) |
| 3 | New route family / new top-level module / new DB migration | ESCALATE → `/ship-feature` |
| 4 | Estimated diff > 5 files | ESCALATE → `/ship-feature` |
| 5 | User wording is uncertain ("not sure how", "what do you think") | ESCALATE → `/ship-feature` (needs brainstorming) |
| 6 | UI feature on a stack with a design system AND the design toolchain incomplete | Proceed in `/feat`, but flag it in STEP 0.5 design gate |
| 7 | Otherwise | PROCEED in `/feat` |
### Worked examples
- "Add `/health` endpoint returning `{status:"ok",version}`" → 1-2 files, no new dep, route added to existing router → **PROCEED**.
- "Add a dark-mode toggle bound to `prefers-color-scheme`" → 2-3 files, design system exists → **PROCEED** (design gate triggers in STEP 0.5).
- "Add OAuth login (Google + GitHub providers)" → new deps, new routes, secrets handling → **ESCALATE** to `/ship-feature`.
- "Show a 'New' badge on items created this week" → 1-2 files, pure UI predicate → **PROCEED**.
- "Fix copy: 'Sign In' → 'Sign in'" in 1 file → **DOWNGRADE** to `/hotfix`.
Print a one-line scope confirmation (use the rule that fired):
```
FEAT: <feature name> — rule <N>, ~<N> files, <brief approach>
```
## STEP 0.5 — DESIGN GATE
Follow `$HOME/.claude/lib/design-gate.md`:
- Scan $ARGUMENTS and target files for design/UI/style signals.
- If signals found → run `design-tool-gate.sh`; if it reports INCOMPLETE,
tell the user to run `/profile design` before proceeding.
- If no signals → skip (zero overhead).
## STEP 0.6 — MEMORY READ-BEFORE (decisions-first)
Run the scan per `$HOME/.claude/lib/analyze-before-plan.md`, decisions-weighted: a BDR may
already constrain or forbid the approach; an LRN may name a gotcha to apply. Emit RELATED
MEMORY; feed STEP 1 PLAN. Inline consumption — reader = planner, no injection.
`.claude/memory/` absent → guarded no-op (zero overhead on a memory-less repo).
## STEP 0.7 — CONTRACT
Run `$HOME/.claude/lib/contract-interview.md` (main loop — you are it). It
captures the request verbatim, asks 0-3 questions PROPORTIONAL to ambiguity
(a complete request → zero questions, silent), derives testable acceptance
criteria + file scope, and writes the contract to
`.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`. Keep the path — the
executor reads it first and GATE 1 (STEP 4) hands it to a fresh verifier.
## STEP 1 — PLAN (dispatch-ready)
The executor follows this plan to the letter and CANNOT ask questions —
close every decision here:
1. Files to create or modify (with line references).
2. Approach in 2-5 bullets — name every choice (naming, data shape, API
surface); an open choice left here comes back as a NEED-DECISION
round-trip.
3. Edge cases to handle.
4. Tests to add/update (exact files).
5. Disposition (from STEP 0.6): name each in-force BDR/LRN this plan honors
(`honors BDR-xxx by …`), or state `no in-force decision constrains this feature`.
A plan with neither = read-then-ignore; the disposition must surface as a trace.
Print the plan as a compact checklist:
```
PLAN:
[ ] <file> — <what to do>
[ ] <file> — <what to do>
[ ] <test file> — <test to add>
```
If the approach is ambiguous: ask the user ONE focused question BEFORE
dispatching — never after (the executor cannot relay questions).
## STEP 2 — BRANCH
**Gitflow aiguillage (before dispatch):** follow `$HOME/.claude/lib/gitflow-aiguillage.md`
— your type = `feature`. On `main`/`develop` it branches first; on a working
branch it's a no-op (commit in place). Never `finish`.
## STEP 3 — DISPATCH EXECUTOR
Dispatch the executor — sonnet by frontmatter pin, do not override:
```
Agent(subagent_type="feater")
prompt: "CONTRACT: <path from STEP 0.7>
PLAN: <the STEP 1 checklist + approach bullets + edge cases, verbatim>
BRANCH: <current branch — verify with git branch --show-current, never switch>
Implement the plan to the letter. Tests alongside code. No commit, no
branch ops, no new dependencies, no files outside the contract FILE SCOPE.
Finish with the FEAT-EXEC REPORT."
```
Parse the `FEAT-EXEC REPORT`:
- `STATUS : DONE` → STEP 4.
- `STATUS : NEED-DECISION` → make the decision HERE (that is reflection),
append it to the plan, re-dispatch a FRESH feater with plan + decision.
Max 2 decision round-trips → escalate to the user.
- `STATUS : BLOCKED` → surface the blocker to the user, stop.
## STEP 4 — VERIFY + SECURE (fresh gates, bounded loops)
Run the two fresh gates per `$HOME/.claude/lib/verify-secure-loop.md` with
`CONTRACT` = the STEP 0.7 path, `DIFF` = the working-tree diff the executor
produced, `TEST` = the suite named in its report:
- GATE 1 — a FRESH verifier judges the diff against the contract (blind).
CONFORME on the first pass → straight to GATE 2, no loop. ECARTS → the
"dev" of the loop is the dispatched executor: re-dispatch a FRESH feater
with the CONTRACT path + the exact gap lines, nothing else. Max 3 →
escalate.
- GATE 2 — a FRESH security-auditor (`MODE: gate`) scans the diff. PASS →
STEP 5. BLOCK → re-dispatch a FRESH feater with the BLOCKING list + the
CONTRACT path; re-verify the request THEN re-scan, max 3 → escalate.
Loop decisions stay HERE, in the main loop (LRN-083). Nominal (clear
request, conform first pass, clean diff) = one executor + one verifier +
one security dispatch.
## STEP 5 — COMMIT
Commit using conventional format:
```
feat(<scope>): <what was added>
<brief description of the feature>
```
If the feature touched multiple concerns (e.g., feature + config +
test), consider splitting into 2-3 atomic commits — load
`$HOME/.claude/agents/commit-changer.md` and follow its grouping logic.
Print summary:
```
FEAT COMPLETE
FEATURE : <name>
FILE(S) : <created/modified files>
TEST(S) : <added tests>
VERIFIED : <what was checked>
```
## STEP 6 — DOC SYNC (automatic)
Load `$HOME/.claude/agents/doc-syncer.md`.
Execute in automatic mode:
`auto-mode scope: <list of files modified during this session>`
**Then commit the docs** — follow `$HOME/.claude/lib/doc-commit.md`: it surgically commits
ONLY the files doc-syncer patched (its `PATCHED_FILES` output), never `git add -A`, never
`.claude/`/`CLAUDE.md` (rc 4 = a loud BDR-022 anomaly, not a silent skip), and no-ops when
nothing was patched — the common case for a trivial change. No FINISH in an inline flow, so
it just commits the docs on the current branch (no ordering concern).
## STEP 7 — CAPITALIZE (memory registries)
A small feature may or may not involve a design choice. Scan the work for:
- **Non-trivial design choice** (even small: a library pick, a naming convention, a data-model tradeoff) → propose `BDR-XXX` in `.claude/memory/decisions.md` with alternatives considered.
- **Reusable pattern or gotcha encountered** → propose `LRN-XXX` in `.claude/memory/learnings.md`.
Present the candidates grouped:
```
CAPITALIZE — proposé
[decisions.md] BDR-XXX — <titre> (optionnel)
[learnings.md] LRN-XXX — <pattern> (optionnel)
Valider ? (all / <IDs> / edit / skip)
```
Always append a 1-line entry to today's heading in `.claude/memory/journal.md`.
**Language rule**: written entries are ALWAYS in English (see CLAUDE.md "Memory registries" § Language). The interactive gate may mirror the user's language; the appended entries must not.
If no substantive capture candidate → skip with `CAPITALIZE: nothing to log`.
**Then commit the memory** — follow `$HOME/.claude/lib/capitalize-commit.md`: it
surgically commits what capitalize just wrote (`.claude/memory` + `.claude/tasks`
only, never `git add -A`) as one `chore(memory)` commit, reports the memory-commit
hash, and no-ops if nothing was written.
---
## RULES
- Max 5 files. If more needed → `/ship-feature`.
- Reflection (scope, plan, contract, loop decisions) NEVER leaves this main
loop; execution NEVER stays in it — the executor is the sonnet-pinned
feater subagent (BDR-066).
- The executor is dispatched FRESH on every round-trip — feedback travels
as contract path + named gaps/decisions, never as transcript.
- Design gate only (not full plugin check). See STEP 0.5.
- No brainstorm/design phase (if needed → `/ship-feature`).
- Keep scope tight. If scope creep happens mid-work, stop
and suggest splitting into `/feat` + follow-up task.
- Follow existing code patterns. Don't introduce new patterns
for a small feature.
````
(Note: this rewrite REPLACES the Task 3 thin-wrapper gate paragraph for feat — the gate line is now native under the H1. The census greps `lib/model-gate.md`, satisfied either way.)
- [ ] **Step 2: Rewrite `agents/feater.md`**
Replace the ENTIRE file content with:
````markdown
---
name: feater
description: Small-feature EXECUTOR — dispatched by /feat with a closed plan + contract. Implements to the letter, tests, reports. No planning, no questions, no commit.
tools: Read, Edit, Write, Bash, Grep, Glob
model: sonnet
---
# FEATER — plan executor
You receive a CLOSED plan from the /feat orchestrator. Your job is faithful
execution, not design. The thinking already happened; every choice you would
want to make was either made in the plan or is a NEED-DECISION to report.
## INPUT (in the dispatch prompt)
- `CONTRACT`: path to the contract file — read it FIRST; its acceptance
criteria + FILE SCOPE bound everything you do.
- `PLAN`: files + approach + edge cases + tests.
- `BRANCH`: verify with `git branch --show-current`; mismatch → STATUS
BLOCKED — never create or switch branches.
- `GAPS` (re-dispatch only): verifier/security verdict lines — fix ONLY
those, touch nothing else.
## EXECUTION RULES
- Follow the plan to the letter. A plan hole or an open choice (naming,
data shape, API surface, dependency) → STOP, report `NEED-DECISION` with
the precise question. Never improvise a design decision.
- Stay inside the contract FILE SCOPE. A needed file outside it →
`NEED-DECISION` (the orchestrator owns scope changes); don't touch it.
- Write tests alongside the code, as the plan names them. Run the relevant
suite incrementally; run it fully before reporting.
- Follow existing code patterns and CLAUDE.md limits (function size,
params, no global state). Match comment density and naming.
- FORBIDDEN: `git commit`, branch ops, push, merge, new dependencies,
editing `.claude/**` or memory registries, user questions (you cannot
ask — report instead), attribution trailers of any kind.
## OUTPUT — end with exactly this report (your final message)
```
FEAT-EXEC REPORT
STATUS : DONE | NEED-DECISION | BLOCKED
FILES : <created/modified paths>
TESTS : <added/updated + final suite run result, verbatim line>
NOTES : <DONE: deviations (must be none) | NEED-DECISION: the exact
question + the options you see | BLOCKED: the blocker verbatim>
```
````
- [ ] **Step 3: Update `lib/verify-secure-loop.md` (3 surgical edits)**
Edit 1 — header, `old_string`:
```
finished diff into a verified, security-cleared change through two fresh
gates and bounded loops. The dev stays inline (LRN-083: subagents =
execution + report; loop decisions live here, in the main loop).
```
`new_string`:
```
finished diff into a verified, security-cleared change through two fresh
gates and bounded loops. Loop decisions live here, in the main loop
(LRN-083: subagents = execution + report). The dev step is either inline
(bugfix) or a dispatched sonnet executor (/feat's feater): "hand the dev"
below means fix inline, or re-dispatch a FRESH executor with exactly those
inputs.
```
Edit 2 — GATE 1 ECARTS bullet, `old_string`:
```
lines (NOT-MET / out-of-scope), nothing else. Dev fixes inline, then
re-dispatch a FRESH verifier.
```
`new_string`:
```
lines (NOT-MET / out-of-scope), nothing else. Inline dev fixes in place;
a dispatched dev is re-dispatched FRESH with those inputs only. Then
re-dispatch a FRESH verifier.
```
Edit 3 — GATE 2 BLOCK bullet, `old_string`:
```
- `BLOCK(n)` → hand the dev the `BLOCKING` list + the CONTRACT path. Dev
fixes inline. Then **re-verify the REQUEST first** (GATE 1, fresh
```
`new_string`:
```
- `BLOCK(n)` → hand the dev the `BLOCKING` list + the CONTRACT path (inline
fix, or FRESH executor re-dispatch). Then **re-verify the REQUEST first** (GATE 1, fresh
```
- [ ] **Step 4: Structural verification**
Run: `grep -c 'subagent_type="feater"' skills/feat/SKILL.md; grep -c 'verify-secure-loop.md' skills/feat/SKILL.md; grep -c 'model: sonnet' agents/feater.md; grep -c 'tools: Read, Edit, Write, Bash, Grep, Glob$' agents/feater.md; grep -c 'AskUserQuestion' agents/feater.md; true`
Expected: `1` / `1` (or more) / `1` / `1` (exact tools line — no Agent tool) / `0` (no AskUserQuestion anywhere).
Run: `python3 -c "import yaml; yaml.safe_load(open('agents/feater.md').read().split('---')[1]); print('YAML OK')"`
Expected: `YAML OK`.
- [ ] **Step 5: Full suite + commit**
Run: `make test`
Expected: green.
```bash
git add skills/feat/SKILL.md agents/feater.md lib/verify-secure-loop.md
git commit -m "feat(model-routing): /feat re-architecture — reflection inline, feater = sonnet executor (partial supersede BDR-050)"
```
---
### Task 6: Pin SDD implementation subagents to sonnet (ship-feature, init-project)
**Files:**
- Modify: `skills/ship-feature/SKILL.md:144-148`
- Modify: `skills/init-project/SKILL.md:166-170`
**Interfaces:**
- Produces: the literal `model: "sonnet"` in both files — Task 8's census greps it.
- [ ] **Step 1: ship-feature STEP 4**
Edit `skills/ship-feature/SKILL.md`, `old_string`:
```
`finishing-a-development-branch` step — this orchestrator owns integration via
`gitflow finish` (STEP 9). When SDD's flow reaches "Use
finishing-a-development-branch", stop and return.
```
`new_string`:
```
`finishing-a-development-branch` step — this orchestrator owns integration via
`gitflow finish` (STEP 9). When SDD's flow reaches "Use
finishing-a-development-branch", stop and return.
**Model routing (BDR-066):** every subagent dispatched under SDD — per-task
implementers AND its reviewers — MUST carry `model: "sonnet"` in the Agent
call. The plan is closed; execution and plan-conformity review are sonnet
work. Reflection (task decomposition, review verdict arbitration) stays in
this loop.
```
- [ ] **Step 2: init-project STEP 8**
Edit `skills/init-project/SKILL.md`, `old_string`:
```
`finishing-a-development-branch` step — this orchestrator owns integration via
`gitflow finish` (STEP 11). When SDD's flow reaches "Use
finishing-a-development-branch", stop and return.
```
`new_string`:
```
`finishing-a-development-branch` step — this orchestrator owns integration via
`gitflow finish` (STEP 11). When SDD's flow reaches "Use
finishing-a-development-branch", stop and return.
**Model routing (BDR-066):** every subagent dispatched under SDD — per-task
implementers AND its reviewers — MUST carry `model: "sonnet"` in the Agent
call. The plan is closed; execution and plan-conformity review are sonnet
work. Reflection (task decomposition, review verdict arbitration) stays in
this loop.
```
- [ ] **Step 3: Verify + commit**
Run: `grep -c 'model: "sonnet"' skills/ship-feature/SKILL.md skills/init-project/SKILL.md`
Expected: `1` for each file.
Run: `make test` — Expected: green.
```bash
git add skills/ship-feature/SKILL.md skills/init-project/SKILL.md
git commit -m "feat(model-routing): SDD implementation + review subagents dispatched model sonnet"
```
---
### Task 7: web-validate fixes via hotfixer L1 applier
**Files:**
- Modify: `skills/web-validate/SKILL.md:274-277` (STEP 3, options A and B)
**Interfaces:**
- Consumes: hotfixer sonnet pin (Task 4); mirrors the geo L1 applier idiom (`skills/geo/SKILL.md:72-77`).
- Produces: `subagent_type="hotfixer"` in web-validate — Task 8's census greps it.
- [ ] **Step 1: Replace inline-Edit application with L1 dispatch**
Edit `skills/web-validate/SKILL.md`, `old_string`:
```
4. On `A` : apply each bundle via `Edit` (targeted `old_string` /
`new_string`). Never use `Write` on shared templates (risk of
overwriting /seo or /geo content — meta tags, JSON-LD).
5. On `B` : for each diff, show and ask yes/no/skip.
```
`new_string`:
````
4. On `A` : dispatch each file-group's applier at L1 (execution = sonnet;
this loop only orchestrates), serially — one applier at a time, appliers
share files:
```
Agent(subagent_type="hotfixer")
prompt: "<paste the file-group's bundle items: file, issue, current,
expected fix>.
Context: web-validate fix bundle, user-approved scope — no
confirmation needed. Apply via targeted Edit (old_string/new_string);
NEVER Write whole files (shared templates carry /seo and /geo
content — meta tags, JSON-LD). Do NOT commit — apply and self-verify
only."
```
5. On `B` : for each diff, show and ask yes/no/skip; apply approved diffs
as in `A` (hotfixer dispatch).
````
- [ ] **Step 2: Verify + commit**
Run: `grep -c 'subagent_type="hotfixer"' skills/web-validate/SKILL.md`
Expected: `1`.
Run: `make test` — Expected: green.
```bash
git add skills/web-validate/SKILL.md
git commit -m "feat(model-routing): web-validate fix bundle applied via hotfixer at L1 (BDR-061 alignment)"
```
---
### Task 8: Census guard `lib/tests/model-routing.test.sh` + flip-test
**Files:**
- Test: `lib/tests/model-routing.test.sh` (guarded path — sentinel required)
**Interfaces:**
- Consumes: every string produced by Tasks 3-7 (see greps below). Auto-discovered by the Makefile `test` glob `lib/tests/*.test.sh` — no runner edit needed.
- [ ] **Step 1: Write the census test**
```bash
printf 'model-routing plan: add census guard test' > .claude/.config-edit-ok
```
Then create `lib/tests/model-routing.test.sh` with exactly:
```bash
#!/usr/bin/env bash
# lib/tests/model-routing.test.sh — census: gate wiring + pins + executor shape (BDR-066)
set -u
R="$(cd "$(dirname "$0")/../.." && pwd)"
pass=0; fail=0
ok() { pass=$((pass+1)); }
ko() { fail=$((fail+1)); printf 'FAIL %s\n' "$1"; }
has() { if grep -qF "$2" "$R/$1"; then ok; else ko "$1 missing: $2"; fi; }
lacks() { if grep -qF "$2" "$R/$1"; then ko "$1 must NOT contain: $2"; else ok; fi; }
fm_lacks() { if awk 'NR<=10' "$R/$1" | grep -qF "$2"; then ko "$1 frontmatter must NOT contain: $2"; else ok; fi; }
# 1) gate wired in the 12 reflection orchestrators
for s in ship-feature init-project feat bugfix onboard seo geo web-validate harden audit-delta tour code-clean; do
has "skills/$s/SKILL.md" 'lib/model-gate.md'
done
# 2) gate NOT wired in the excluded skills (encodes the spec exclusion list)
for s in hotfix commit-change doc status release-candidate; do
lacks "skills/$s/SKILL.md" 'lib/model-gate.md'
done
# 3) executor + gate pins
has "agents/feater.md" 'model: sonnet'
has "agents/hotfixer.md" 'model: sonnet'
has "agents/verifier.md" 'model: sonnet'
has "agents/security-auditor.md" 'model: sonnet'
fm_lacks "agents/analyzer.md" 'model:'
# 4) /feat executor shape
has "skills/feat/SKILL.md" 'subagent_type="feater"'
has "skills/feat/SKILL.md" 'verify-secure-loop.md'
lacks "agents/feater.md" 'AskUserQuestion'
# 5) SDD execution pinned
has "skills/ship-feature/SKILL.md" 'model: "sonnet"'
has "skills/init-project/SKILL.md" 'model: "sonnet"'
# 6) web-validate applies via L1 applier
has "skills/web-validate/SKILL.md" 'subagent_type="hotfixer"'
printf 'model-routing census: %d pass, %d fail\n' "$pass" "$fail"
[ "$fail" -eq 0 ]
```
- [ ] **Step 2: Run — expect green (everything already wired by Tasks 3-7)**
Run: `bash lib/tests/model-routing.test.sh`
Expected: `model-routing census: 28 pass, 0 fail`, exit 0. (Count: 12 wired + 5 excluded + 5 pins + 3 feat-shape + 2 SDD + 1 web-validate.)
- [ ] **Step 3: Flip-test the guard (LRN-096 — prove it CAN fail)**
```bash
sed -i 's|lib/model-gate.md|lib/model-gate-REMOVED.md|' skills/tour/SKILL.md
bash lib/tests/model-routing.test.sh; echo "exit=$?"
git checkout -- skills/tour/SKILL.md
bash lib/tests/model-routing.test.sh; echo "exit=$?"
```
Expected: first run prints `FAIL skills/tour/SKILL.md missing: lib/model-gate.md` and `exit=1`; second run prints `28 pass, 0 fail` and `exit=0`.
- [ ] **Step 4: Lint + full suite + commit**
Run: `shellcheck lib/tests/model-routing.test.sh && make test`
Expected: clean + green.
```bash
git add lib/tests/model-routing.test.sh
git commit -m "test(model-routing): census guard — gate wiring, pins, executor shape (flip-tested)"
```
---
### Task 9: README + CHANGELOG
**Files:**
- Modify: `README.md` (agent/model documentation)
- Modify: `CHANGELOG.md` (Unreleased section)
- [ ] **Step 1: Locate the README insertion point**
Run: `grep -niE 'agents?/|sonnet|haiku|model' README.md | head -20`
If README has a table listing agents (a row per agent), refresh/add its model info from the table below. If not, insert a new subsection `### Agent model routing (BDR-066)` immediately after the section that documents `agents/` (fallback: before the "Skills" section), with exactly:
```markdown
### Agent model routing (BDR-066)
Reflection (brainstorm, plan, contract, audit judgment, loop decisions) runs
INLINE on the session model — assumed Fable/Opus, enforced by a blocking
gate (`lib/model-gate.md` + `lib/model-check.sh`) at the entry of the 12
reflection orchestrators. Execution runs on pinned subagents:
| Agent | Model | Tier |
|---|---|---|
| feater, hotfixer | sonnet (pinned) | executors — code from a closed plan, fix-bundle appliers |
| verifier, security-auditor | sonnet (pinned) | fresh gates (≤3×/loop) |
| doc-syncer, onboarder, scaffolder, refactorer, interviewer, plugin-advisor | sonnet (pinned) | workers |
| status-reporter | haiku (pinned) | mechanical collector |
| client-handover-writer | opus (pinned, currently inert — inline-loaded; sonnet conversion planned) | deliverable writer |
| analyzer, seo-analyzer, geo-analyzer, validator-analyzer, code-cleaner, bugfixer, commit-changer | inherit session (Fable/Opus) | reflection / audit / inline playbooks |
```
- [ ] **Step 2: CHANGELOG**
Run: `grep -n 'Unreleased' CHANGELOG.md`
Under the `## [Unreleased]` heading (create `### Added` / `### Changed` subsections if absent), add:
```markdown
### Added
- Model routing (BDR-066): blocking model gate (`lib/model-gate.md` +
`lib/model-check.sh`, flip-tested) wired into 12 reflection orchestrators;
census guard `lib/tests/model-routing.test.sh`.
- `/feat` re-architected: reflection inline (scope/plan/contract), execution
dispatched to the sonnet-pinned `feater` executor; verify+secure loop
decided in the main loop with fresh executor re-dispatches.
### Changed
- `hotfixer` pinned `model: sonnet` (seo/geo/web-validate L1 applier);
`analyzer` haiku pin removed (inherits the session model).
- ship-feature / init-project: SDD implementation + review subagents
dispatched with `model: "sonnet"`.
- web-validate `--fix`: bundle applied via `hotfixer` at L1 instead of
inline Edit (BDR-061 alignment).
```
- [ ] **Step 3: Commit**
Run: `make test` — Expected: green.
```bash
git add README.md CHANGELOG.md
git commit -m "docs(model-routing): README agent-model table + CHANGELOG entry"
```
---
### Task 10: Capitalize memory + TODO follow-up
**Files:**
- Modify: `.claude/memory/decisions.md` (append BDR-066 + Index row)
- Modify: `.claude/memory/journal.md` (1 line under a `## 2026-07-15` heading)
- Modify: `.claude/tasks/TODO.md` (chantier section + plan-2 backlog)
- [ ] **Step 1: Append BDR-066 to `.claude/memory/decisions.md`**
Add to the Index table (after the BDR-065 row):
```markdown
| BDR-066 | 2026-07-15 | Model routing: reflection inline (session big model) + sonnet-pinned executors + blocking gate | accepted |
```
Append at end of file:
```markdown
## BDR-066 — Model routing: reflection inline (session big model), executors pinned sonnet, blocking gate
- **Date**: 2026-07-15
- **Status**: accepted (partial supersede of BDR-050: /feat dev no longer inline; bugfix/hotfix dev-inline CONSERVED)
- **Decision**: reflection (brainstorm, plan, contract, audit judgment, loop decisions) runs on session model (Fable; Opus fallback) — inline or inherit subagents, never pinned down. Execution (code from closed plan, fix-bundle application) runs sonnet-pinned subagents: feater + hotfixer pinned sonnet; SDD implementation+review subagents dispatched `model: "sonnet"` (ship-feature/init-project); web-validate fixes via hotfixer L1 (was inline Edit). analyzer haiku pin REMOVED (digest feeds plan = reflection tier). verifier + security-auditor STAY sonnet (job9 confirmed — procedural gates, ≤3×/loop). Blocking gate `lib/model-gate.md` (self-check + witness `lib/model-check.sh`) wired in 12 reflection orchestrators; small → STOP, unknown → fail-visible; census guard `lib/tests/model-routing.test.sh` flip-tested.
- **Why**: big-model quota burned on mechanical execution (Fable exhausted mid-job8); plan closed at dispatch → executor needs obedience not judgment; fresh sonnet gates catch executor drift.
- **Alternatives rejected**: opus pins on audit agents (session-independent) — rejected: session assumed big + blocking gate as backstop, one tier fewer; advisory gate — rejected by user, blocking; split bugfix/hotfix too — rejected: bugfix investigation interleaved w/ fix, hotfix gain marginal vs dispatch overhead.
- **Caveats**: client-handover-writer conversion (inline-load → sonnet dispatch, 11 human-gate sites to relocate) DEFERRED to own plan — its opus pin stays inert meanwhile; feater cannot ask → NEED-DECISION report = escalation valve, plan must close decisions; witness reads settings.json — lags `--model`-launched sessions (self-check compensates).
- **Reference**: spec `docs/superpowers/specs/2026-07-15-model-routing-design.md` + plan `docs/superpowers/plans/2026-07-15-model-routing.md` (transient, BDR-065 lifecycle), branch `feature/model-routing`.
```
- [ ] **Step 2: Journal line**
Append under a `## 2026-07-15` heading (create it if absent) in `.claude/memory/journal.md`:
```markdown
- model routing shipped on feature/model-routing: BDR-066 (reflection inline big / executors sonnet / blocking gate), /feat re-arch, census guard. client-handover conversion deferred to plan 2.
```
- [ ] **Step 3: TODO follow-up entry**
Add at the top of `.claude/tasks/TODO.md` (above the 2026-07-08 section):
```markdown
## 2026-07-15 — model routing (feature/model-routing)
Spec + plan in docs/superpowers/ (transient, BDR-065). BDR-066. Branch
unmerged — human gate.
- [x] gate lib/model-check.sh + lib/model-gate.md (flip-tested) wired ×12
- [x] pins: hotfixer/feater sonnet, analyzer un-pinned; SDD model:"sonnet";
web-validate → hotfixer L1; census guard model-routing.test.sh
- [x] /feat re-arch: reflection inline → feater sonnet executor (partial
supersede BDR-050)
- [ ] DOGFOOD (manual, next sessions): /feat live run — plan closes
decisions, dispatch carries sonnet, verify loop in main loop; gate
STOP on a sonnet session (LRN-079 class, not automatable here)
- [ ] PLAN 2 — client-handover conversion (spec §5): inline-load → sonnet
dispatch, relocate 11 human-gate sites to dispatcher (inventory in
plan-1 session), or lighter variant: dispatch only the redaction
phase. Decide shape at plan time.
```
- [ ] **Step 4: Commit memory scoped**
```bash
git add .claude/memory/decisions.md .claude/memory/journal.md .claude/tasks/TODO.md
git commit -m "chore(memory): BDR-066 model routing + journal + TODO follow-ups"
```
- [ ] **Step 5: Final gate**
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`.