From 8008d8233c6163df106b7012374f9b3879a2ef3b Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Mon, 20 Jul 2026 14:47:53 +0200 Subject: [PATCH 1/6] feat(profile): set symmetric on managed externals + MCPs (BDR-079) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MANAGED_EXTERNALS (emil-design-eng, frontend-design, design-motion-principles, impeccable) + MANAGED_MCPS (magic): cmd_set now trims both when the profile does not list them — design leftovers no longer survive a 'set backend' - cmd_set refactored to 4 symmetric trim helpers; nothing outside the MANAGED_* allowlists is ever auto-toggled (darwin-skill manual) - enable_skill external: from-source fallback (ln -sf skills-external/), mirrors toggle-external.sh - stale usage() NOTE + SKILL.md updated to the both-ways reality - hermetic test: 16 checks, fixture repo + fake claude shim (gstack on-demand, from-source, park/restore, magic add/remove, non-managed untouched); shellcheck + full make test green --- .claude/memory/decisions.md | 3 + .claude/memory/journal.md | 1 + .claude/tasks/TODO.md | 18 +++++ CHANGELOG.md | 3 + lib/profile.sh | 95 ++++++++++++++++++++++----- lib/tests/profile-set-managed.test.sh | 76 +++++++++++++++++++++ skills/profile/SKILL.md | 18 ++++- 7 files changed, 196 insertions(+), 18 deletions(-) create mode 100644 lib/tests/profile-set-managed.test.sh diff --git a/.claude/memory/decisions.md b/.claude/memory/decisions.md index 1bb5deb..8ed599c 100644 --- a/.claude/memory/decisions.md +++ b/.claude/memory/decisions.md @@ -1065,3 +1065,6 @@ Supersedes BDR-076 scope + amends BDR-066. Doctrine: session model (Fable) = mai ### BDR-078 — ctx7 coverage: central fast-libs list + once-per-session reminder hook; every code path covered [accepted] (2026-07-20) Refines BDR-053 (single surface). Audit 2026-07-20: coverage PARTIAL — find-docs fired on user doc-questions only; ship-feature 0c / init-project 5c pre-fetched; /feat //bugfix executors + ad-hoc coding NEVER consulted ctx7; fast-libs list hardcoded 3× (drift risk). 4 closures shipped: (a) find-docs description += BEFORE-writing-code trigger (fast-moving lib, even without doc question, unless fresh cache) + cache-first rule in body (tee fetched docs to .ctx7-cache/); (b) feater+bugfixer briefs += fast-lib docs rule — read fresh `.ctx7-cache/*.md`, else `npx ctx7@latest` fetch max 2 topics, else `ctx7 cache miss: ` in NOTES + proceed (executors lack Skill tool → Bash path); (c) hooks/ctx7-reminder.sh UserPromptSubmit — ONE fire/session (sentinel on session_id), only when project manifest carries fast-libs; reports cache state; skips turns; always exit 0; (d) lib/fast-libs.sh = SINGLE SOURCE (detect / cache-status verbs, JS package.json anchored full-key match + Python requirements/pyproject, 7-day freshness, LC_ALL=C sort locale-independent) consumed by hook + 3 pipeline skills + 2 briefs. 2nd session surface DELIBERATE, not a BDR-053 reversal: 053 killed a 490-tok ALWAYS-ON rule duplicate; hook costs ~0 quiet, 1 line once when fast-libs present. Alternatives rejected: PreToolUse Edit/Write gate (fires per-edit = noise); description-only fix (probabilistic, executors unreachable). Tests: lib/tests/fast-libs.test.sh 11 checks (anchored/near-miss/py/none, cache fresh/stale/missing, hook fire/sentinel/quiet×2); shellcheck + full make test green. Branch feature/ctx7-coverage, unmerged (human gate). Amendment (same session): skills/find-docs = machine-owned dist (gitignored, ctx7 regenerates on fresh clone) → durable copy of closure (a) lives in install-plugins.sh STEP ctx7 (idempotent grep-guarded python patch, fixture-verified); live SKILL.md carries the same edit uncommitted by design. + +### BDR-079 — profile `set` symmetric on managed externals + MCPs [accepted] (2026-07-20) +Audit (user ask "profile toggles externals both ways?"): ASYMMETRIC. Enable side OK — gstack on-demand from submodule when pack off (shared `skills-disabled/gstack__*` convention with toggle-external.sh, interoperable), externals restored from parked, magic delegated to toggle-external. Disable side MISSING: `cmd_set` trimmed only gstack + MANAGED_PLUGINS → `set backend` left emil/frontend-design/design-motion/impeccable active + magic registered; SKILL.md claimed both-ways toggle (true only at enable). Shipped: (1) `MANAGED_EXTERNALS` (emil-design-eng, frontend-design, design-motion-principles, impeccable = exact union of profile `external` usage; darwin-skill excluded — not task-type-driven) + `MANAGED_MCPS` (magic) allowlists, same doctrine as MANAGED_PLUGINS; (2) cmd_set refactored to 4 trim helpers (`disable_{gstack,plugins,externals,mcps}_not_in`) — symmetric, nothing outside allowlists ever auto-touched; (3) enable_skill external += from-source fallback (`ln -sf skills-external/`, mirrors toggle-external) — closes the "missing symlink" warn; (4) stale usage() NOTE ("NOT toggled automatically") + SKILL.md fixed. Hermetic test profile-set-managed.test.sh 16 checks: fixture repo (both *_REPO_OVERRIDE), fake `claude` shim on PATH logging calls + flat-file MCP registry — gstack on-demand, external from-source, park/restore round-trip, magic add/remove calls, non-managed untouched. shellcheck + make test green. Branch feature/profile-managed-externals, unmerged (human gate). diff --git a/.claude/memory/journal.md b/.claude/memory/journal.md index 1975d9f..1227885 100644 --- a/.claude/memory/journal.md +++ b/.claude/memory/journal.md @@ -417,3 +417,4 @@ rules: ## 2026-07-20 - ctx7 coverage audit (user ask "ctx7 appelé à chaque techno ?") → verdict PARTIAL. 4 gaps: find-docs question-only, /feat //bugfix executors blind, ad-hoc coding uncovered, fast-libs hardcoded 3×. All 4 closed → BDR-078 (fast-libs.sh single source + ctx7-reminder hook + description trigger + executor-brief rule). fast-libs test 11/0, make test + review-guards green. feature/ctx7-coverage, UNMERGED. - v1.2.0 cut + pushed (release-candidate flow: prep/finish via release-executor, tag on main 51b6572). CHANGELOG backfilled at prep: 10 entries added to Unreleased (plan-challenge, seo-data verbs, model-tiering v2, integrity pass, safe_fetch/url-guard) — was ctx7-only. /doc full post-release: README model-routing table v1→v2 reframe + ctx7 two-surface wording, chore/doc-sync-v1.2.0 merged. All pushed on explicit go. +- profile↔toggle-external audit (user) → enable side already symmetric (gstack on-demand LIVE), disable side missing → BDR-079: MANAGED_EXTERNALS+MANAGED_MCPS trim at set, external from-source fallback, 16-check hermetic test (claude shim). feature/profile-managed-externals, UNMERGED. diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index a41d64b..df65784 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -1,5 +1,23 @@ # TODO +## 2026-07-20 — profile ↔ toggle-external symmetry (feature/profile-managed-externals, BDR-079) +Audit verdict: gstack on-demand + design enable already work; DISABLE side +missing — `set backend` leaves emil/frontend-design/design-motion/impeccable +active + magic registered. Doc claims auto-toggle both ways (only enable true). +- [x] profile.sh: `MANAGED_EXTERNALS` (emil-design-eng, frontend-design, + design-motion-principles, impeccable — union of profile usage) + + `MANAGED_MCPS` (magic) allowlists; cmd_set refactored to 4 trim + helpers (disable_{gstack,plugins,externals,mcps}_not_in). +- [x] profile.sh enable_skill external: from-source fallback + (`ln -sf skills-external/`) mirroring toggle-external. +- [x] Texts: cmd_set info line, usage() NOTE (stale "NOT toggled + automatically"), header; skills/profile/SKILL.md Mechanism+tradeoffs. +- [x] Hermetic test lib/tests/profile-set-managed.test.sh — 16/0: gstack + on-demand, external from-source, park/restore round-trip, magic + add/remove via claude shim, non-managed untouched. +- [x] Gate: shellcheck OK + make test exit 0 (review-guards 5/0). BDR-079 + + journal + CHANGELOG done. Committed on branch, NO merge (human gate). + ## 2026-07-20 — ctx7 coverage extension (feature/ctx7-coverage, BDR-078) Close the 4 gaps from the ctx7 coverage audit: /feat //bugfix + ad-hoc coding never consult ctx7; fast-libs list hardcoded 3×; zero deterministic backstop. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed4240..a528426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +### Added +- **Profile switches now toggle external packs and MCPs both ways (BDR-079)** — `profile.sh set` was asymmetric: it enabled what a profile listed (including gstack skills on demand when the whole pack is off, and the `magic` MCP) but never disabled the managed leftovers, so `set backend` after design work kept emil-design-eng / frontend-design / design-motion-principles / impeccable active and magic registered. `set` now trims managed externals (`MANAGED_EXTERNALS`) and managed MCPs (`MANAGED_MCPS`, delegated to `toggle-external.sh`) not listed in the profile — same allowlist doctrine as `MANAGED_PLUGINS`, nothing outside the allowlists is ever auto-touched (darwin-skill stays manual). Also: an `external` entry whose symlink never existed is now created from `skills-external/` (mirroring toggle-external's from-source path), and the stale "NOT toggled automatically" note in `profile.sh` usage was corrected. Covered by a hermetic 16-check test (`lib/tests/profile-set-managed.test.sh`) with a fake `claude` shim. + ## [1.2.1] — 2026-07-20 ### Fixed diff --git a/lib/profile.sh b/lib/profile.sh index 3f20971..57d2e33 100755 --- a/lib/profile.sh +++ b/lib/profile.sh @@ -14,6 +14,9 @@ # - MCPs: delegated to lib/toggle-external.sh for known servers (magic), # advisory otherwise # - CLIs: advisory only (rtk, gsd, ctx7, graphify — installed externally) +# - `set` is SYMMETRIC on managed items (BDR-079): plugins, external packs +# and MCPs in the MANAGED_* allowlists are disabled when the profile +# does not list them — nothing outside those lists is ever auto-toggled. # # Always-on plugins (never toggled by `set`): security-guidance, # superpowers + rtk hook + .claude internal. The script refuses to disable @@ -61,6 +64,23 @@ MANAGED_PLUGINS=( "pr-review-toolkit@claude-code-plugins" ) +# External skill packs that are toggle-managed by `set` — same allowlist +# doctrine as MANAGED_PLUGINS: listed here only when the enabled state is +# task-type-driven. `set` disables these when the profile does not list +# them; anything else external (e.g. darwin-skill) is never auto-touched. +MANAGED_EXTERNALS=( + emil-design-eng + frontend-design + design-motion-principles + impeccable +) + +# MCP servers that are toggle-managed by `set`, both ways (enable AND +# disable), delegated to lib/toggle-external.sh. Same allowlist doctrine. +MANAGED_MCPS=( + magic +) + # Plugins that MUST stay enabled — `set` will refuse to disable these even if # they're not in the profile. (Defensive: belt-and-suspenders alongside # MANAGED_PLUGINS allowlist.) @@ -271,6 +291,11 @@ enable_skill() { ok "enabled: $skill ($type)" elif [ -e "$SKILLS_DIR/$skill" ]; then : + elif [ "$type" = external ] && [ -d "$REPO/skills-external/$skill" ]; then + # Symlink never created (or hand-removed): recreate it from the + # vendored pack — mirrors toggle-external.sh's from-source path. + ln -sf "$REPO/skills-external/$skill" "$SKILLS_DIR/$skill" + ok "enabled: $skill (external, symlink created)" else warn "missing: $skill ($type)" fi @@ -422,6 +447,48 @@ parked_gstack_count() { find "$DISABLED_DIR" -maxdepth 1 -name 'gstack__*' 2>/dev/null | wc -l | tr -d ' ' } +# ── `set` trim helpers — one per managed category ───────────── +# Each disables the managed items NOT listed in the given profile. Allowlist +# doctrine: only MANAGED_* entries are ever auto-disabled. + +disable_plugins_not_in() { + local prof="$1" keep_file p plugin_name marketplace + keep_file="$(mktemp)" + read_profile "$prof" \ + | awk -F'\t' '$2 ~ /^plugin@/ { sub(/^plugin@/, "", $2); print $1"@"$2 }' \ + | sort -u > "$keep_file" + for p in "${MANAGED_PLUGINS[@]}"; do + if ! grep -qx "$p" "$keep_file"; then + plugin_name="${p%@*}" + marketplace="${p#*@}" + disable_skill "$plugin_name" "plugin@${marketplace}" + fi + done + rm -f "$keep_file" +} + +disable_externals_not_in() { + local prof="$1" keep_file x + keep_file="$(mktemp)" + read_profile "$prof" | awk -F'\t' '$2 == "external" { print $1 }' \ + | sort -u > "$keep_file" + for x in "${MANAGED_EXTERNALS[@]}"; do + grep -qx "$x" "$keep_file" || disable_skill "$x" external + done + rm -f "$keep_file" +} + +disable_mcps_not_in() { + local prof="$1" keep_file s + keep_file="$(mktemp)" + read_profile "$prof" | awk -F'\t' '$2 == "mcp" { print $1 }' \ + | sort -u > "$keep_file" + for s in "${MANAGED_MCPS[@]}"; do + grep -qx "$s" "$keep_file" || disable_skill "$s" mcp + done + rm -f "$keep_file" +} + # ── Commands ────────────────────────────────────────────── cmd_list() { @@ -506,24 +573,20 @@ cmd_apply() { cmd_set() { local prof="$1" - info "Setting profile: $prof (exclusive — disables non-listed gstack skills + managed plugins)" + info "Setting profile: $prof (exclusive — disables non-listed gstack skills + managed plugins/externals/MCPs)" # Disable gstack-origin skills not in profile. disable_gstack_not_in "$prof" # Disable managed plugins not in profile (PROTECTED_PLUGINS are excluded # by disable_skill itself — belt and suspenders). - local plugin_keep_file p plugin_name marketplace - plugin_keep_file="$(mktemp)" - read_profile "$prof" | awk -F'\t' '$2 ~ /^plugin@/ { sub(/^plugin@/, "", $2); print $1"@"$2 }' | sort -u > "$plugin_keep_file" - for p in "${MANAGED_PLUGINS[@]}"; do - if ! grep -qx "$p" "$plugin_keep_file"; then - plugin_name="${p%@*}" - marketplace="${p#*@}" - disable_skill "$plugin_name" "plugin@${marketplace}" - fi - done - rm -f "$plugin_keep_file" + disable_plugins_not_in "$prof" + + # Symmetry (BDR-079): a profile switch also parks the managed external + # packs and unregisters the managed MCPs the new profile does not need — + # design leftovers (emil, magic…) no longer survive a `set backend`. + disable_externals_not_in "$prof" + disable_mcps_not_in "$prof" # Enable everything listed in the profile. cmd_apply "$prof" @@ -679,9 +742,11 @@ EXAMPLES: bash lib/profile.sh reset # restore everything NOTE: - Plugin and MCP entries print advisory commands — they are NOT toggled - automatically. Run "claude plugin enable|disable" or "claude mcp add|remove" - yourself for those. + "set" toggles the MANAGED items automatically, both ways: plugins + (ui-ux-pro-max, plugin-dev, pr-review-toolkit), external packs + (emil-design-eng, frontend-design, design-motion-principles, impeccable) + and the magic MCP. Anything outside those allowlists stays advisory — + run "claude plugin enable|disable" or "claude mcp add|remove" yourself. EOF } diff --git a/lib/tests/profile-set-managed.test.sh b/lib/tests/profile-set-managed.test.sh new file mode 100644 index 0000000..e0a3eea --- /dev/null +++ b/lib/tests/profile-set-managed.test.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# lib/tests/profile-set-managed.test.sh — `set` symmetry on managed +# externals + MCPs, gstack on-demand, external from-source (BDR-079). +# Hermetic: fixture repo via *_REPO_OVERRIDE + fake `claude` on PATH. +set -u +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +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; } + +FX="$(mktemp -d)"; trap 'rm -rf "$FX"' EXIT +mkdir -p "$FX/skills" "$FX/skills-disabled" "$FX/lib/profiles" "$FX/bin" \ + "$FX/skills-external/emil-design-eng" "$FX/skills-external/other-ext" +for g in gs-a gs-b gs-c; do + mkdir -p "$FX/skills-external/gstack/$g" + touch "$FX/skills-external/gstack/$g/SKILL.md" +done +cp "$ROOT/lib/profile.sh" "$ROOT/lib/toggle-external.sh" "$FX/lib/" +printf 'MAGIC_API_KEY=test-secret-000\n' > "$FX/.env" + +# Non-managed external, enabled from the start — must never be touched. +ln -s "$FX/skills-external/other-ext" "$FX/skills/other-ext" + +cat > "$FX/lib/profiles/designish.profile" <<'EOF' +gs-a +gs-b +emil-design-eng external +magic mcp +EOF +cat > "$FX/lib/profiles/backendish.profile" <<'EOF' +gs-c +EOF + +# Fake claude: logs every call; keeps MCP registry state in a flat file. +cat > "$FX/bin/claude" <> "\$FX/claude-calls.log" +case "\$1 \${2:-}" in + "mcp list") cat "\$FX/mcp-state" 2>/dev/null ;; + "mcp add") echo "magic: stub" > "\$FX/mcp-state" ;; + "mcp remove") : > "\$FX/mcp-state" ;; +esac +exit 0 +EOF +chmod +x "$FX/bin/claude" + +run() { PATH="$FX/bin:$PATH" PROFILE_REPO_OVERRIDE="$FX" \ + TOGGLE_EXTERNAL_REPO_OVERRIDE="$FX" bash "$FX/lib/profile.sh" "$@"; } + +# --- set designish: gstack on-demand + external from-source + magic on --- +run set designish >/dev/null 2>&1 +check T1-gsa-on "$([ -e "$FX/skills/gs-a" ] && echo on || echo off)" on +check T2-gsb-on "$([ -e "$FX/skills/gs-b" ] && echo on || echo off)" on +check T3-gsc-off "$([ -e "$FX/skills/gs-c" ] && echo on || echo off)" off +check T4-emil-src "$([ -L "$FX/skills/emil-design-eng" ] && echo on || echo off)" on +check T5-magic-on "$(grep -c '^magic:' "$FX/mcp-state" 2>/dev/null)" 1 +check T6-add-call "$(grep -c '^mcp add magic' "$FX/claude-calls.log")" 1 + +# --- set backendish: managed leftovers parked/unregistered --- +run set backendish >/dev/null 2>&1 +check T7-gsc-on "$([ -e "$FX/skills/gs-c" ] && echo on || echo off)" on +check T8-gsa-park "$([ -e "$FX/skills-disabled/gstack__gs-a" ] && echo p || echo n)" p +check T9-emil-off "$([ -e "$FX/skills/emil-design-eng" ] && echo on || echo off)" off +check T10-emil-park "$([ -e "$FX/skills-disabled/emil-design-eng" ] && echo p || echo n)" p +check T11-magic-off "$(grep -c '^magic:' "$FX/mcp-state" 2>/dev/null || true)" 0 +check T12-rm-call "$(grep -c '^mcp remove magic' "$FX/claude-calls.log")" 1 +check T13-other-untouched "$([ -e "$FX/skills/other-ext" ] && echo on || echo off)" on + +# --- back to designish: parked external restored (not re-sourced) --- +run set designish >/dev/null 2>&1 +check T14-emil-back "$([ -e "$FX/skills/emil-design-eng" ] && echo on || echo off)" on +check T15-park-gone "$([ -e "$FX/skills-disabled/emil-design-eng" ] && echo p || echo n)" n +check T16-magic-back "$(grep -c '^magic:' "$FX/mcp-state" 2>/dev/null)" 1 + +printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] diff --git a/skills/profile/SKILL.md b/skills/profile/SKILL.md index e986809..2f9edc8 100644 --- a/skills/profile/SKILL.md +++ b/skills/profile/SKILL.md @@ -61,6 +61,15 @@ protected — `set` will refuse to disable them even if the profile omits them. **Managed plugins** that `set` may disable when not in profile: `ui-ux-pro-max@ui-ux-pro-max-skill`, `plugin-dev@claude-code-plugins`, `pr-review-toolkit@claude-code-plugins`. Other plugins are never auto-toggled. +**Managed externals** (`emil-design-eng`, `frontend-design`, +`design-motion-principles`, `impeccable`) and **managed MCPs** (`magic`) +follow the same symmetry (BDR-079): `set` enables them when the profile +lists them (from parked state, or from `skills-external/` if the symlink +never existed) and parks/unregisters them when it does not — e.g. `set +backend` after design work turns emil and magic off. `darwin-skill` and any +other unlisted external are never auto-touched. gstack works the same +all the way down: a profile listing gstack skills while the whole pack is +off (via `toggle-external.sh`) re-enables JUST those skills on demand. ## Commands @@ -117,8 +126,11 @@ bash "$HOME/.claude/lib/profile.sh" $ARGUMENTS update-check, learnings — script doesn't touch that infra. Disabled skills are just hidden from Claude Code's scanner; the gstack repo stays installed. - Profile changes DO toggle the managed Claude Code plugins (ui-ux-pro-max, - plugin-dev, pr-review-toolkit) and the `magic` MCP — see the Mechanism table - above (BDR-008). Anything outside that managed set stays manual: - `claude plugin enable|disable`, `claude mcp add|remove`. + plugin-dev, pr-review-toolkit), the managed external packs (emil-design-eng, + frontend-design, design-motion-principles, impeccable) and the `magic` MCP — + in BOTH directions: `set` enables what the profile lists and disables the + managed leftovers it doesn't (BDR-008, BDR-079). Anything outside those + allowlists stays manual: `claude plugin enable|disable`, `claude mcp + add|remove`. - `set` is destructive in the sense that it disables non-listed gstack skills. Use `apply` if the user wants additive behavior. From ecbe8abde701ac95fa66f8693b734fbec2bf9e0c Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Mon, 20 Jul 2026 16:29:31 +0200 Subject: [PATCH 2/6] =?UTF-8?q?docs:=20profile=20list=20=C3=973=20+=20test?= =?UTF-8?q?=20glob=20+=20BDR-ID=20strip=20+=20layout=20tree=20=E2=86=92=20?= =?UTF-8?q?ARCHITECTURE.md=20=E2=80=94=20/doc=20clean=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ARCHITECTURE.md | 33 +++++++++++++++++++++++++++++++++ README.md | 46 ++++++++++++---------------------------------- USAGE.md | 2 +- 3 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..434a70c --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,33 @@ +# Architecture — claude-config + +Repo layout and structural principles. Command workflows live in +[`USAGE.md`](./USAGE.md); version history in [`CHANGELOG.md`](./CHANGELOG.md). + +## Project layout + +``` +claude-config/ +├── CLAUDE.global.md # Global coding preferences — deployed as ~/.claude/CLAUDE.md +├── CLAUDE.md # Project-scope instructions (this repo only) +├── settings.json # Global permissions (deny / ask / allow rules) +├── install.sh # Bootstrap: Claude Code CLI + auth + submodules + link + plugins +├── install-plugins.sh # One-shot installer: prerequisites + all plugins +├── link.sh # Symlinks this repo into ~/.claude/ +├── doctor.sh # Setup diagnostic +├── update-all.sh # One-command update for all components +├── Makefile # Unified entry point: make install / doctor / update +├── plugins.lock.json # Version pinning for non-marketplace dependencies +├── hooks/ # Session start, statusline, RTK rewrite + ctx7 + design-toolchain reminders +├── agents/ # Execution units called by skills (never invoked directly) +├── skills/ # Entry points invoked via /skill-name +├── skills-external/ # Vendored skill packs (gstack submodule + installer-fetched design packs) +├── templates/ # Per-project templates (CLAUDE.md, settings, memory registries, deploy runbook, gitignore) +└── lib/ # Shared shell libs (gitflow, profiles, commit helpers, archetypes, tests) +``` + +## Architecture principles + +- `skills/` = entry points you invoke via `/skill-name` +- `agents/` = execution units called by skills (never invoked directly by user) +- `templates/` = symlinked to `~/.claude/templates/` — copy into projects via `/onboard` or manually +- **Graphify** builds a knowledge graph of any codebase (`/graphify query`), producing a navigable wiki in `graphify-out/wiki/`. This map helps Claude understand project structure, find relevant code faster, and reason across files. Essential for large-scope tasks (multi-file features, complex bugs, architectural changes). Small tasks should skip it and read files directly. diff --git a/README.md b/README.md index 081c9d1..62e052b 100644 --- a/README.md +++ b/README.md @@ -11,33 +11,11 @@ Global Claude Code configuration — agents, skills, plugins, and project templa This repo is your personal Claude Code setup, versioned and reproducible across machines. -``` -claude-config/ -├── CLAUDE.global.md # Global coding preferences — deployed as ~/.claude/CLAUDE.md -├── CLAUDE.md # Project-scope instructions (this repo only) -├── settings.json # Global permissions (deny / ask / allow rules) -├── install.sh # Bootstrap: Claude Code CLI + auth + submodules + link + plugins -├── install-plugins.sh # One-shot installer: prerequisites + all plugins -├── link.sh # Symlinks this repo into ~/.claude/ -├── doctor.sh # Setup diagnostic -├── update-all.sh # One-command update for all components -├── Makefile # Unified entry point: make install / doctor / update -├── plugins.lock.json # Version pinning for non-marketplace dependencies -├── hooks/ # Session start, statusline, RTK rewrite + ctx7 + design-toolchain reminders -├── agents/ # Execution units called by skills (never invoked directly) -├── skills/ # Entry points invoked via /skill-name -├── skills-external/ # Vendored skill packs (gstack submodule + installer-fetched design packs) -├── templates/ # Per-project templates (CLAUDE.md, settings, memory registries, deploy runbook, gitignore) -└── lib/ # Shared shell libs (gitflow, profiles, commit helpers, archetypes, tests) -``` +See [`ARCHITECTURE.md`](./ARCHITECTURE.md) for the full project layout and +structural principles (skills = entry points, agents = execution units, +templates = per-project scaffolding, graphify = codebase knowledge graph). -**Architecture principle:** -- `skills/` = entry points you invoke via `/skill-name` -- `agents/` = execution units called by skills (never invoked directly by user) -- `templates/` = symlinked to `~/.claude/templates/` — copy into projects via `/onboard` or manually -- **Graphify** builds a knowledge graph of any codebase (`/graphify query`), producing a navigable wiki in `graphify-out/wiki/`. This map helps Claude understand project structure, find relevant code faster, and reason across files. Essential for large-scope tasks (multi-file features, complex bugs, architectural changes). Small tasks should skip it and read files directly. - -### Agent model routing (BDR-076/077 — model-tiering v2) +### Agent model routing (model-tiering v2) Doctrine: the session model (Fable) does main-loop reflection ONLY — brainstorm, plan, contract, audit judgment, gates, loop decisions — enforced @@ -90,11 +68,11 @@ The plugins step logs to `install-YYYYMMDD-HHMMSS.log`. **Optional — Context7** (fast doc lookup for React / Next.js / Prisma…): the plugins step installs the `ctx7` CLI and wires it into Claude Code. The doc-fetch surface is -the `find-docs` skill alone (BDR-053 — the generated `rules/context7.md` is purged by +the `find-docs` skill alone (the generated `rules/context7.md` is purged by design; if you run `ctx7 setup` manually, delete that rule or re-run `make plugin`). A once-per-session `ctx7-reminder` hook nudges toward it when the current project -carries fast-moving libs (`lib/fast-libs.sh`) — a scoped second surface refining -BDR-053, not reversing it (BDR-078). +carries fast-moving libs (`lib/fast-libs.sh`) — a scoped second surface, a +refinement of the single-surface rule, not a reversal. ```bash ctx7 login # optional: OAuth / API key for higher rate limits @@ -160,7 +138,7 @@ a different package, ships its own conflicting `graphify` bin) — see | `/web-validate` | W3C HTML/CSS validity + WCAG 2.1 accessibility audit | | `/geo` | GEO-only audit — AI-search visibility (ChatGPT, Perplexity, Claude, Gemini…) | | `/client-handover` | Final project delivery — audits + branded deliverable (Markdown / HTML / PDF) | -| `/profile` | Activate a skill profile (design / dev / qa / audit / minimal) | +| `/profile` | Activate a skill profile (web / seo / web-full / full / backend / design / dev / qa / audit / minimal) | | `/tour` | Grouped all-axes sweep — cleanup + security + reconcile + doc, fix and loop until clean | > This table lists personal skills. Gstack skills (investigate, review, retro, @@ -236,7 +214,7 @@ See [`templates/settings/SETTINGS.md`](templates/settings/SETTINGS.md) for the f `~/.claude.json` (or the project's `.mcp.json`) — if you pass the real secret on that command line, it materializes as a second plaintext copy outside `~/.claude/.env`, invisible to the repo's `.gitignore`/allowlist reach (this -bit us once: job7/BDR-026). +bit us once). Claude Code expands `${VAR}` and `${VAR:-default}` in `mcpServers` config — in `env`, `command`, `args`, `url`, and `headers` — for both project (`.mcp.json`) @@ -273,7 +251,7 @@ can `POST` to it and that body is injected **verbatim** into the tool result the model consumes (job8 audit, `dist/utils/callback-server.js:36`). This is in the third-party package's code, not this repo's config — **we don't patch it**. The mitigation lives entirely on our side: `settings.json` -`permissions.ask` explicitly lists all 4 `mcp__magic__*` tools ([[BDR-059]]), +`permissions.ask` explicitly lists all 4 `mcp__magic__*` tools, so every call — builder included — requires a live confirmation and can never auto-execute. Don't allowlist `21st_magic_component_builder` or `21st_magic_component_refiner` (arbitrary @@ -299,10 +277,10 @@ make plugin # install plugins only make link # create/update symlinks into ~/.claude/ make doctor # diagnostic make update # update Claude Code, config, submodules, plugins, and verify -make test # run deterministic tests (lib/tests/*.test.sh + lib/seo-data/*.test.sh + lib/gitflow-test.sh) +make test # run deterministic tests (lib/tests/*.test.sh + lib/seo-data/*.test.sh + lib/gitflow-test.sh + lib/tests/run-*.sh) make onboard # onboard an existing project (run from its dir) make seo-connect # connect a Google account for /seo FULL (OAuth consent) -make profile cmd="set X" # activate a skill profile (design/dev/qa/audit/minimal/full) +make profile cmd="set X" # activate a skill profile (web/seo/web-full/full/backend/design/dev/qa/audit/minimal) make profile-list # list skill profiles make profile-current # show the active profile make profile-reset # re-enable all gstack skills diff --git a/USAGE.md b/USAGE.md index 537fa5d..c20feec 100644 --- a/USAGE.md +++ b/USAGE.md @@ -163,7 +163,7 @@ Tu veux... | `/pdf-translate` | Traduire un PDF vers une autre langue | Sortie HTML fidèle (images, layout, style préservés) | | `/impeccable` | Audit/polish design + détecteur anti-slop déterministe | 23 verbes ; `npx impeccable detect` (exit 0/2) | | `/tour` | Sweep groupé sur un ou plusieurs projets | Sécu + nettoyage + reconcile + doc, boucle jusqu'à un pass propre | -| `/profile` | Changer le profil de skills | design / dev / qa / audit / minimal | +| `/profile` | Changer le profil de skills | web / seo / web-full / full / backend / design / dev / qa / audit / minimal | > Cette table couvre les skills personnels principaux. Les plugins (gstack, > pr-review-toolkit…) et marketplaces externes en ajoutent beaucoup d'autres — From 655e364e806e33ce3fac286202d9266d7258ed53 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Mon, 20 Jul 2026 16:40:58 +0200 Subject: [PATCH 3/6] chore(docs): purge transient plan/spec artifacts missed by post-merge cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/plans + docs/specs (deploy-skill 2026-06-27): predate the BDR-065 lifecycle codification, never swept - docs/superpowers/{plans,specs} (model-routing 2026-07-15): 6-wave chantier — final W6 merge closed it without the purge step Git history at the feature commits is the archive (BDR-065). --- docs/plans/2026-06-27-deploy-skill.md | 385 ---- docs/specs/2026-06-27-deploy-skill-design.md | 165 -- .../plans/2026-07-15-model-routing.md | 1953 ----------------- .../specs/2026-07-15-model-routing-design.md | 143 -- 4 files changed, 2646 deletions(-) delete mode 100644 docs/plans/2026-06-27-deploy-skill.md delete mode 100644 docs/specs/2026-06-27-deploy-skill-design.md delete mode 100644 docs/superpowers/plans/2026-07-15-model-routing.md delete mode 100644 docs/superpowers/specs/2026-07-15-model-routing-design.md diff --git a/docs/plans/2026-06-27-deploy-skill.md b/docs/plans/2026-06-27-deploy-skill.md deleted file mode 100644 index be01332..0000000 --- a/docs/plans/2026-06-27-deploy-skill.md +++ /dev/null @@ -1,385 +0,0 @@ -# Deploy Skill — Implementation Plan - -> **Superseded by BDR-054** (`52f6678`): the shipped skill has NO `NEXT.sh` file and NO -> AskUserQuestion hand-back — see `skills/deploy/SKILL.md` for current behavior. This -> plan is kept as historical record; do not implement its NEXT.sh/hand-back sections. - -> **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:** Build a `deploy` skill — a per-project shell runbook that re-instantiates from the delta since the last deploy, hands control to the user for out-of-band execution, resumes cold (even in a new session), and learns from deploy errors in place. - -**Architecture:** A surgical-commit helper (`lib/deploy-commit.sh`, allowlist-scoped to `.claude/deploy/`) is the foundation. Five per-project artifacts under `.claude/deploy/` carry runbook, incident ledger, deploy oracle, in-flight bridge, and the instantiated checklist. The skill is a two-moment SKILL.md (before → user deploys out-of-band → after, on the user's report), resumable cold from the JSON bridge per the `audit-delta` state-file convention. Bootstrap scaffolds the runbook for a project that has none. - -**Tech Stack:** Bash (helper + git), Markdown (SKILL.md + runbook + ledger), JSON (oracle + bridge). No new runtime deps — Claude reads JSON natively in skill steps; the helper never parses JSON. - -## Global Constraints - -- Surgical commits only: `deploy-commit.sh` commits via explicit argv pathspec, never `git add -A`. (mirror BDR-034/036) -- Allowlist scope = `.claude/deploy/` ONLY; any other path is a loud rc-4 refusal. Inverse of `doc-commit.sh`'s `.claude/**` exclusion (BDR-022). Verified: real `doc-commit.sh` returns rc 4 on `.claude/deploy/PROCEDURE.md`. -- Delta = `git diff --name-only HEAD` — **explicit two endpoints, no dots** (two-dot ≡ this; three-dot undercounts — verified). Never `git rev-list` ancestry (phantom deltas on rebase — verified). -- First-deploy detection = `[ -f .claude/deploy/STATE.json ]` (deterministic). NEVER `git describe` (hard-errors rc 128 on no tag — verified). -- Resume convention = `audit-delta`: "the state file is the only memory between runs; never infer prior scope from context." Bridge read at STEP 0. -- Helper inherits from `lib/memory-commit.sh`/`lib/doc-commit.sh`: rc 3 on unsafe git state (detached/merge/rebase/cherry-pick), short-hash on stdout only on a real commit, per-file changed-paths filter, diagnostics to stderr. -- User executes the deploy out-of-band (prod ssh) — the skill NEVER runs deploy commands itself. -- Registries/spec language English; the spec of record is `docs/specs/2026-06-27-deploy-skill-design.md`. - ---- - -## Decisions resolved at plan time - -**§10 (cross-session state) — TRANCHÉ: separate bridge artifact.** -- Bridge = `.claude/deploy/PENDING.json` (JSON), **distinct from the ephemeral `NEXT.sh`**, **uncommitted** (transient local working state; gitignored). Schema: - ```json - { "base_sha": "", "target_sha": "", - "delta": ["supabase/migrations/0033_x.sql", "docker-compose.yml"], - "step_reached": "awaiting-user", "started_at": "", "runbook_rev": "" } - ``` -- Follows `audit-delta` ("state file is the only memory between runs"). Resolves the n°1↔n°3 coupling: NEXT.sh stays ephemeral per §3; the bridge persists and carries base+target+delta so moment 3 lays the correct marker and capitalizes the correct incident — **without re-parsing shell**, readable cold. -- Form-novelty (mid-flow pause-resume) is new → `writing-skills` formalizes the convention in Task 3. -- **LIMIT (acknowledged, not to be discovered):** `PENDING.json` is gitignored ⇒ cold-resume is **same-machine only** — it does not survive a clone or a move to another machine. Acceptable because a project's deploys run from one local; recorded as a constraint, not assumed away. - -**§8 item 1 — tag push:** annotated tag `git tag -a deploy/ -m ""` laid in MARK (success). **Project knob `# @config push_deploy_tags=true|false`** in the `PROCEDURE.md` header (default `false`): when true, MARK runs `git push origin deploy/` — always **best-effort/non-fatal** (the push never blocks the deploy; tag is a bookmark, STATE.json is the oracle). Same-day re-deploy → suffix `-N`. - -**§8 item 2 — INCIDENTS ID/name:** `.claude/deploy/INCIDENTS.md`, append-only, entries `DEP-NNN` (next = `grep '^## DEP-' | max+1`), fields mirror `blockers.md`: date, step, error (verbatim), root cause, fix. Resolution derivable from git: the commit that adds the entry IS the fix (atomic patch+incident); recover via `git log -S 'DEP-NNN' -- .claude/deploy/INCIDENTS.md`. Name confirmed `INCIDENTS.md` (not `ERRORS-LEARNED.md`). - -**§8 item 3 — `@delta:` grammar:** directives on a runbook step's preceding comment line, patterns matched against the delta file list. `glob=` carries TWO required semantics (a single "checklist-only" reading was REJECTED — it breaks the game example, where step 3 runs `psql -f 0033` THEN `psql -f 0034` = one command PER file): -- `# @delta: glob=:each` — **repeat**: emit the step's command once per delta file matching `` (e.g. `psql -f `). -- `# @delta: glob=:list` — **checklist**: emit the command once, with matching files as `# VERIFY:` items (e.g. `supabase migration up`). -- `# @delta: when=[,...]` — **conditional**: include the step only if the delta intersects any pattern (e.g. rebuild when compose/Dockerfile changed). -- Patterns are git-pathspec/shell-glob; comma-separates alternatives. **Un-annotated step = fixed**, always emitted verbatim. The exact `:each`/`:list` keyword spelling is DEFERRED to `writing-skills` (Task 3); both semantics are mandatory. - -**§8 item 4 — frontmatter / gates:** -```yaml -name: deploy -description: | - Use when deploying a project via its per-project runbook — instantiates the - delta since last deploy, hands off for out-of-band execution, resumes cold, - learns from errors. - Triggers: "deploy", "déploie", "run the deploy", "ship to prod", "deploy runbook". -allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion] -``` -Gate vocabulary reused from `capitalize`/`client-handover`: `all / pick / edit / skip-all`. Gates marked **[GATE]** in Task 3. - ---- - -## File Structure - -- Create `lib/deploy-commit.sh` — surgical commit helper, allowlist `.claude/deploy/`. (Task 1) -- Create `lib/tests/deploy-commit.test.sh` — real-git behavioral tests. (Task 1) -- Create `skills/deploy/SKILL.md` — the two-moment skill. (Task 3) -- Create `templates/deploy/PROCEDURE.md` — annotated starter runbook (scaffold source). (Task 2/4) -- Create `templates/deploy/INCIDENTS.md` — empty ledger header. (Task 2) -- Modify `.gitignore` — ignore `.claude/deploy/NEXT.sh` and `.claude/deploy/PENDING.json`. (Task 2) -- Per-project, created at runtime (NOT in this repo): `.claude/deploy/{PROCEDURE.md, INCIDENTS.md, STATE.json, PENDING.json, NEXT.sh}`. - -**Artifact lifecycle:** - -| Artifact | Committed? | Lifecycle | -|---|---|---| -| `PROCEDURE.md` | yes (deploy-commit) | in-place edits (learning) | -| `INCIDENTS.md` | yes (deploy-commit) | append-only `DEP-NNN` | -| `STATE.json` | yes (deploy-commit) | overwritten on success = oracle | -| `PENDING.json` | **no** (gitignored) | written at hand-back, deleted on success = cold-resume bridge | -| `NEXT.sh` | **no** (gitignored) | regenerated per deploy, ephemeral checklist | - ---- - -### Task 1: `lib/deploy-commit.sh` — surgical commit helper (FOUNDATION, TDD) - -**Files:** -- Create: `lib/deploy-commit.sh` -- Test: `lib/tests/deploy-commit.test.sh` - -**Interfaces:** -- Produces: `deploy-commit.sh pending ...` → exit 0 if any passed file in-scope has changes, else 1. `deploy-commit.sh commit "" ...` → commits ONLY passed in-scope files, prints short hash on stdout; rc 0 success, rc 1 clean/no-op, rc 3 unsafe git state, rc 4 out-of-scope path. -- Consumes: nothing (foundation). - -- [ ] **Step 1: Write the failing test harness** - -```bash -# lib/tests/deploy-commit.test.sh -#!/usr/bin/env bash -set -u -H="$(cd "$(dirname "$0")/.." && pwd)/deploy-commit.sh" -pass=0; fail=0 -mkrepo() { local d; d=$(mktemp -d); git -C "$d" init -q; git -C "$d" config user.email t@t; - git -C "$d" config user.name t; mkdir -p "$d/.claude/deploy"; printf 'x\n' >"$d/seed"; - git -C "$d" add seed; git -C "$d" commit -q -m seed; printf '%s' "$d"; } -check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1)); - printf 'FAIL %s: got[%s] want[%s]\n' "$1" "$2" "$3"; fi; } - -d=$(mkrepo); printf 'run\n' >"$d/.claude/deploy/PROCEDURE.md" -out=$( cd "$d" && bash "$H" commit "docs(deploy): t" .claude/deploy/PROCEDURE.md ); rc=$? -check T1-rc "$rc" 0 -check T1-committed-only "$(git -C "$d" show --name-only --format= HEAD)" ".claude/deploy/PROCEDURE.md" -check T1-hash-nonempty "$([ -n "$out" ] && echo y || echo n)" y - -d=$(mkrepo); printf 'b\n' >"$d/src.txt" -( cd "$d" && bash "$H" commit "x" src.txt ) >/dev/null 2>&1; check T2-out-of-scope-rc "$?" 4 - -d=$(mkrepo) -( cd "$d" && bash "$H" commit "x" ".claude/deploy/../memory/secret" ) >/dev/null 2>&1 -check T3-traversal-rc "$?" 4 - -d=$(mkrepo); printf 'p\n' >"$d/.claude/deploy/PROCEDURE.md"; printf 's\n' >"$d/src.txt" -( cd "$d" && bash "$H" commit "x" .claude/deploy/PROCEDURE.md src.txt ) >/dev/null 2>&1 -check T4-mixed-refuses-all "$?" 4 -check T4-nothing-committed "$(git -C "$d" rev-list --count HEAD)" 1 - -d=$(mkrepo); git -C "$d" checkout -q --detach -printf 'p\n' >"$d/.claude/deploy/PROCEDURE.md" -( cd "$d" && bash "$H" commit "x" .claude/deploy/PROCEDURE.md ) >/dev/null 2>&1 -check T5-unsafe-rc "$?" 3 - -d=$(mkrepo) -( cd "$d" && bash "$H" pending .claude/deploy/PROCEDURE.md ); check T6-pending-clean-rc "$?" 1 - -d=$(mkrepo); printf 'p\n' >"$d/.claude/deploy/PROCEDURE.md" -printf 'i\n' >"$d/.claude/deploy/INCIDENTS.md"; printf '{}\n' >"$d/.claude/deploy/STATE.json" -( cd "$d" && bash "$H" commit "docs(deploy): learn" .claude/deploy/PROCEDURE.md \ - .claude/deploy/INCIDENTS.md .claude/deploy/STATE.json ) >/dev/null 2>&1 -check T7-atomic-rc "$?" 0 -check T7-three-files "$(git -C "$d" show --name-only --format= HEAD | grep -c deploy)" 3 - -printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ] -``` - -- [ ] **Step 2: Run the test, verify it FAILS** - -Run: `bash lib/tests/deploy-commit.test.sh` -Expected: FAIL (helper absent) — every check fails or the harness errors on missing `lib/deploy-commit.sh`. - -- [ ] **Step 3: Implement `lib/deploy-commit.sh`** - -```bash -#!/usr/bin/env bash -# deploy-commit.sh — surgical commit for the .claude/deploy/ runbook family. -# Allowlist scope = .claude/deploy/ ONLY (inverse of doc-commit's .claude exclusion). -set -u - -_in_git_repo() { git rev-parse --is-inside-work-tree >/dev/null 2>&1; } - -_unsafe_state() { # 0 = unsafe - local g; g=$(git rev-parse --git-dir 2>/dev/null) || return 0 - git symbolic-ref -q HEAD >/dev/null 2>&1 || return 0 # detached HEAD - [ -e "$g/MERGE_HEAD" ] || [ -d "$g/rebase-merge" ] || \ - [ -d "$g/rebase-apply" ] || [ -e "$g/CHERRY_PICK_HEAD" ] && return 0 - return 1 -} - -_out_of_scope() { # 0 = forbidden, 1 = in scope - case "$1" in - *..*) return 0 ;; # traversal — forbidden FIRST - .claude/deploy/*) return 1 ;; # allowed - *) return 0 ;; # everything else forbidden - esac -} - -_scope_violations() { local p; for p in "$@"; do _out_of_scope "$p" && printf '%s\n' "$p"; done; } - -_changed_only() { # echo passed files that actually have changes - local p; for p in "$@"; do - [ -n "$(git status --porcelain -- "$p" 2>/dev/null)" ] && printf '%s\n' "$p"; done -} - -cmd="${1:-}"; shift || true -_in_git_repo || { echo "deploy-commit: not a git repo" >&2; exit 2; } - -case "$cmd" in - pending) - [ "$#" -gt 0 ] || { echo "deploy-commit: pending needs file args" >&2; exit 2; } - [ -n "$(_changed_only "$@")" ] && exit 0 || exit 1 ;; - commit) - msg="${1:-}"; shift || true - [ -n "$msg" ] && [ "$#" -gt 0 ] || { echo "deploy-commit: commit needs ..." >&2; exit 2; } - viol=$(_scope_violations "$@") - if [ -n "$viol" ]; then - { echo "deploy-commit: REFUSED — path(s) outside .claude/deploy/ allowlist:"; - printf ' - %s\n' $viol; - echo "deploy-commit: NOTHING committed. Caller must pass only .claude/deploy/ files."; } >&2 - exit 4 - fi - _unsafe_state && { echo "deploy-commit: unsafe git state (detached/merge/rebase) — not committing" >&2; exit 3; } - mapfile -t changed < <(_changed_only "$@") - [ "${#changed[@]}" -gt 0 ] || exit 1 - git commit -q -m "$msg" -- "${changed[@]}" || { echo "deploy-commit: git commit failed" >&2; exit 1; } - git rev-parse --short HEAD ;; - *) echo "usage: deploy-commit.sh pending ... | commit \"\" ..." >&2; exit 2 ;; -esac -``` - -- [ ] **Step 4: Run the test, verify it PASSES** - -Run: `bash lib/tests/deploy-commit.test.sh` -Expected: `PASS=12 FAIL=0` (exit 0). - -- [ ] **Step 5: shellcheck** - -Run: `shellcheck lib/deploy-commit.sh lib/tests/deploy-commit.test.sh` -Expected: clean (matches repo Health Stack norm). - -- [ ] **Step 6: Commit** - -```bash -git add lib/deploy-commit.sh lib/tests/deploy-commit.test.sh -git commit -m "feat(deploy): deploy-commit.sh — allowlist surgical commit for .claude/deploy/" -``` - ---- - -### Task 2: Artifacts + bridge formats (§10 materialized) - -**Files:** -- Create: `templates/deploy/PROCEDURE.md`, `templates/deploy/INCIDENTS.md` -- Modify: `.gitignore` - -**Interfaces:** -- Produces: the on-disk shapes the skill reads/writes — `PROCEDURE.md` annotation grammar, `INCIDENTS.md` `DEP-NNN` template, `STATE.json` and `PENDING.json` schemas. -- Consumes: nothing. - -- [ ] **Step 1: Write `templates/deploy/PROCEDURE.md`** (annotated starter — fixed steps verbatim, dynamic steps annotated) - -```bash -#!/usr/bin/env bash -# === deploy runbook (reference) — NOT run directly. Instantiated to NEXT.sh per delta. === -# Fixed steps run every deploy; `# @delta:` steps re-instantiate from the delta. -# @config push_deploy_tags=false -# NOTE grammar: glob=:each repeats the command per matching file (e.g. psql -f ); -# glob=:list runs once + lists matching files as VERIFY items; when= is conditional. - -# 1) backup BEFORE any forward-only migration -ssh "$DEPLOY_HOST" 'pg_dump "$DB" > ~/backups/pre-deploy-$(date +%F-%H%M).sql' # VERIFY: dump size > 0 - -# @delta:migrations glob=supabase/migrations/*.sql:list -# 2) apply NEW migrations (one command; skill lists the delta migrations to VERIFY) -ssh "$DEPLOY_HOST" 'supabase migration up' # VERIFY: "Applied" for each - -# @delta:rebuild when=docker-compose*.yml,Dockerfile,Dockerfile.* -# 3) rebuild + restart services (only if build inputs changed) -ssh "$DEPLOY_HOST" 'docker compose up -d --build' # VERIFY: docker compose ps healthy - -# @delta:deps when=package.json,*lock*,requirements.txt,pyproject.toml -# 4) install deps (only if manifests changed) -ssh "$DEPLOY_HOST" 'cd app && npm ci' # VERIFY: exit 0 - -# 5) reload cache + smoke test (fixed) -ssh "$DEPLOY_HOST" 'systemctl reload app' -curl -fsS https://$DEPLOY_HOST/health # VERIFY: HTTP 200 -``` - -- [ ] **Step 2: Write `templates/deploy/INCIDENTS.md`** (ledger header) - -```markdown -# Deploy incidents (append-only) — DEP-NNN - - - - -``` - -- [ ] **Step 3: Record the JSON schemas** (no parsing in shell — Claude reads them in skill steps) - -`STATE.json` (committed oracle, overwritten on success): -```json -{ "deployed_sha": "", "deployed_at": "", "outcome": "ok", - "tag": "deploy/" } -``` -`PENDING.json` (gitignored bridge, deleted on success): schema as in "Decisions resolved at plan time / §10". - -- [ ] **Step 4: Update `.gitignore`** - -```gitignore -# deploy: transient per-deploy state (the runbook/ledger/oracle ARE committed) -.claude/deploy/NEXT.sh -.claude/deploy/PENDING.json -``` - -- [ ] **Step 5: Verify templates are well-formed** - -Run: `bash -n templates/deploy/PROCEDURE.md && grep -c '^# @delta:' templates/deploy/PROCEDURE.md` -Expected: no syntax error; `3` annotations. - -- [ ] **Step 6: Commit** - -```bash -git add templates/deploy/PROCEDURE.md templates/deploy/INCIDENTS.md .gitignore -git commit -m "feat(deploy): runbook/ledger templates + bridge schemas + gitignore transient state" -``` - ---- - -### Task 3: `skills/deploy/SKILL.md` — the two-moment skill (REQUIRES writing-skills) - -> **At this task, invoke `superpowers:writing-skills`** to shape SKILL.md to house conventions AND to formalize the **cross-session cold-resume** form (deploy's defining novelty; `audit-delta` is the state-file precedent, `client-handover` only an in-context pause). The step behaviors below are the contract; writing-skills governs structure/frontmatter/spine. - -**Files:** -- Create: `skills/deploy/SKILL.md` - -**Interfaces:** -- Consumes: `lib/deploy-commit.sh` (Task 1); artifact shapes (Task 2). -- Produces: the runtime behavior. STEP spine below. - -**STEP spine (each = a SKILL.md section; [GATE] = mandatory stop):** - -- [ ] **STEP 0 — PRE-FLIGHT + RESUME BRANCH.** Read `.claude/deploy/PENDING.json` FIRST (state file = only memory between runs). - - `PENDING.json` present → **RESUME**: jump to STEP 3 with its `{base, target, delta, step_reached}` (do not recompute). - - else `PROCEDURE.md` absent → **BOOTSTRAP** (Task 4). - - else → FRESH: continue STEP 1. -- [ ] **STEP 1 — DELTA.** `base = STATE.json.deployed_sha` (or, if `STATE.json` absent, first-deploy = full runbook). `git diff --name-only HEAD` → delta file list. `target = git rev-parse HEAD`. -- [ ] **STEP 2 — INSTANTIATE + [GATE].** Expand `PROCEDURE.md`: emit fixed steps verbatim; expand `@delta:glob=…:each` steps by repeating the command per matching delta file, and `@delta:glob=…:list` steps once with matching files as `# VERIFY:` items; include `@delta:when=` steps only if the delta intersects. Read `INCIDENTS.md` and prepend matching `# PRE-WARN: DEP-NNN …` notes. Write `NEXT.sh`. **[GATE]** present `NEXT.sh` → `all / edit / skip-all`. On approve: write `PENDING.json` (`step_reached: awaiting-user`), then **hand back** (AskUserQuestion: "Run NEXT.sh step by step. Report back: Deployed OK / Failed at step X / Not yet"). -- [ ] **STEP 3 — RESUME / REACT** (entry point on the user's report; may be a fresh session). - - "Deployed OK" → STEP 5. - - "Failed at step X: " → STEP 4. - - "Not yet" → re-state pending, stop. -- [ ] **STEP 4 — LEARN + [GATE] + ATOMIC COMMIT.** Diagnose. Draft: (a) in-place `PROCEDURE.md` patch to step X; (b) `INCIDENTS.md` append `DEP-NNN` (error verbatim). **[GATE]** `all / pick / edit / skip-all` (significant edit). On approve: write both, then **one atomic** `bash lib/deploy-commit.sh commit "docs(deploy): patch — recovered from " .claude/deploy/PROCEDURE.md .claude/deploy/INCIDENTS.md`. The commit that adds `DEP-NNN` IS its resolution (derive via git later). Then bump `PENDING.json.runbook_rev` to the new `PROCEDURE.md` commit sha (keep `step_reached` at X). **Resume = REGENERATE `NEXT.sh` from `step_reached` against the PATCHED runbook** (steps X…end — X+1…end never ran), NOT replay a single step. The bumped `runbook_rev` is exactly the trigger: runbook changed ⇒ prior `NEXT.sh` is stale ⇒ regenerate. Re-present via STEP 2's hand-back. -- [ ] **STEP 5 — MARK (success).** Write `STATE.json` (`deployed_sha = PENDING.target_sha`, outcome ok, tag). `git tag -a deploy/ -m ""`; **if `@config push_deploy_tags=true`** then `git push origin deploy/` (best-effort, non-fatal). `bash lib/deploy-commit.sh commit "chore(deploy): mark @ " .claude/deploy/STATE.json`. **Delete `PENDING.json`** (+ `NEXT.sh`). Report. - -- [ ] **Verification scenarios** (dry-run walkthroughs, no prod): - - First deploy (no `STATE.json`): full runbook fires; STATE laid; PENDING deleted. - - Delta deploy: only changed-bucket steps instantiate; `git diff` form is ` HEAD`. - - **Cold resume**: write a `PENDING.json` by hand, start `deploy` in a *fresh* context → STEP 0 detects it, resumes at STEP 3 from disk alone (no conversation memory). - - Failure→learn: report "failed at step X" → patch + DEP append committed atomically (one sha, both files). -- [ ] **Commit:** `git add skills/deploy/SKILL.md && git commit -m "feat(deploy): two-moment cross-session skill (resumes cold from PENDING.json)"` - ---- - -### Task 4: Bootstrap (project without a runbook) - -**Files:** -- Modify: `skills/deploy/SKILL.md` (STEP 0 BOOTSTRAP branch) - -**Interfaces:** -- Consumes: `templates/deploy/*` (Task 2); STEP spine (Task 3). - -- [ ] **Step 1 — BOOTSTRAP branch + [GATE].** When `PROCEDURE.md` absent, offer two paths (AskUserQuestion): - - **Paste** — user provides an existing runbook → adopt verbatim, then propose `@delta:` annotations for migration/build/deps steps. - - **Scaffold** — detect artifacts (`supabase/migrations/`, `docker-compose*.yml`/`Dockerfile`, `package.json`/lockfiles, `.env*`) + short interview (ssh host, backup cmd, health URL, rollback note) → fill `templates/deploy/PROCEDURE.md`. - - **[GATE]** present drafted `PROCEDURE.md` → `all / edit / skip-all`. On approve: write `PROCEDURE.md` + empty `INCIDENTS.md`; `bash lib/deploy-commit.sh commit "feat(deploy): bootstrap runbook" .claude/deploy/PROCEDURE.md .claude/deploy/INCIDENTS.md`. First deploy then proceeds (no STATE.json ⇒ full runbook). -- [ ] **Step 2 — Verify:** dry-run on a repo with `supabase/migrations/` + `docker-compose.yml` present → scaffold proposes migration + rebuild steps annotated; on a bare repo → interview-only path. -- [ ] **Commit:** `git add skills/deploy/SKILL.md && git commit -m "feat(deploy): bootstrap — paste-or-scaffold initial runbook"` - ---- - -## Gates identified - -- **[GATE] STEP 2** — approve instantiated `NEXT.sh` before hand-back. -- **[GATE] STEP 4** — approve runbook patch + `DEP-NNN` incident before the atomic learning commit. -- **[GATE] STEP 0/Task 4** — approve scaffolded `PROCEDURE.md` before first write. -- **Hand-back (STEP 2→3)** — AskUserQuestion is the resume point; the user executes out-of-band. -- **Task gates** — each Task ends test-green + shellcheck-clean + committed before the next (deps: 1 → 2 → 3 → 4). - -## Self-review - -- **Spec coverage:** 4 artifacts + bridge (§3/§10) → Task 2; STATE-oracle + ` HEAD` delta (§4) → Task 1 constraints + STEP 1; runbook+INCIDENTS learning, atomic couple (§5) → STEP 4; `deploy-commit.sh` inverse allowlist (§6) → Task 1; bootstrap (§7) → Task 4; two-moment cold resume (§10) → STEP 0/2/3 + PENDING.json. All §8 items resolved above. ✓ -- **Placeholder scan:** none — helper code, test code, schemas, annotation grammar all concrete. -- **Type consistency:** `STATE.json.deployed_sha` (STEP 1 base, STEP 5 write), `PENDING.json.{base_sha,target_sha,delta,step_reached}` (STEP 0 read, STEP 2 write, STEP 4 update), `deploy-commit.sh commit "" ...` (Tasks 1/3/4) — names align. -- **Open at execution (not assumed):** the `writing-skills` consultation in Task 3 may rename/restructure SKILL.md sections to match the formalized cold-resume convention, and finalizes the `@delta:` `:each`/`:list` keyword spelling (both semantics mandatory); STEP behaviors and the §6 helper contract above are fixed regardless. - -## Execution Handoff - -Build order is strict by dependency: **Task 1 (helper, foundation) → Task 2 (formats) → Task 3 (skill, writing-skills) → Task 4 (bootstrap)**. diff --git a/docs/specs/2026-06-27-deploy-skill-design.md b/docs/specs/2026-06-27-deploy-skill-design.md deleted file mode 100644 index a70ced8..0000000 --- a/docs/specs/2026-06-27-deploy-skill-design.md +++ /dev/null @@ -1,165 +0,0 @@ -# Deploy skill — design spec - -> **Superseded by BDR-054** (`52f6678`): the shipped skill has NO `NEXT.sh` file and NO -> AskUserQuestion hand-back — see `skills/deploy/SKILL.md` for current behavior. This -> spec is kept as historical record; do not implement its NEXT.sh/hand-back sections. - -- **Date:** 2026-06-27 -- **Status:** Design approved (5 knobs settled). **No skill code written yet.** Next step = implementation plan. -- **Scope:** A new `deploy` skill = a per-project shell RUNBOOK that lives in `.claude/deploy/`, gets re-instantiated from the delta since the last deploy, and LEARNS from deploy errors in place. - -## 1. Vision — deployment memory that learns - -Three moments: - -1. **BEFORE** — produce the *instantiated* runbook: reference runbook + delta since last deploy, parameterized steps rewritten with the real artifacts (e.g. the migration step lists the migrations actually added since last deploy, not the runbook's examples). -2. **DURING** — the **user executes out-of-band** (prod ssh — Claude must not run it) and reports `deployed and tested` OR `failed at step X, here is the error` → fix together until success. -3. **AFTER** — on confirmed success: (a) if errors were hit + fixed, update the reference runbook so the next deploy does not repeat them; (b) lay the marker "deployed up to here" for the next diff. - -Structural ancestor in the corpus: `client-handover` (BEFORE baseline → DURING user-deploy gate via `AskUserQuestion` → AFTER validate + react). No existing skill owns a learning per-project runbook — clean gap, no `.claude/deploy/` precedent. - -## 2. Locked decisions - -| # | Knob | Decision | -|---|------|----------| -| 1 | Marker / oracle | **STATE file is the oracle** (deployed SHA), **annotated tag** added as a human bookmark only | -| 2 | Learning storage | **In-place runbook edits + append-only `INCIDENTS.md`** (distinct jobs, atomic coupling) | -| 3 | Parameterization | **`# @delta:` annotations** bind dynamic steps to path-patterns; un-annotated steps are fixed | -| 4 | Bootstrap | **Offer both** — user pastes an existing runbook OR skill scaffolds via artifact detection + interview | -| 5 | Execution model | **`NEXT.sh` is a step-by-step CHECKLIST** — runnable shell, but driven by hand with manual `# VERIFY:` gates; never `bash NEXT.sh` unattended | - -**Why #5 is design-time, not impl:** the execution model is load-bearing for moments 2 and 3. Moment 2 is defined as "user reports *failed at step X*", and moment 3's LEARN loop must know *which* step failed to patch it. A single `bash NEXT.sh` blob collapses both into "exited non-zero somewhere" and can strand a prod deploy (migrations, restarts) in partial state with no step control. Checklist is *entailed* by the three-moment structure, not merely safer. - -Treated as settled corollaries: user executes out-of-band; a **new** `lib/deploy-commit.sh` helper (existing helpers cannot commit the runbook — see §6, verified). - -## 3. Architecture - -``` -.claude/deploy/ - PROCEDURE.md reference runbook — fixed shell + `# @delta:` annotated steps (edited IN-PLACE) - INCIDENTS.md DEP-NNN incident ledger: date, step, error verbatim, root cause, - fix (APPEND-ONLY; resolution = introducing commit, derive via git) - STATE.json deployed SHA + timestamp + outcome — the diff oracle (overwritten each deploy) - NEXT.sh instantiated runbook — EPHEMERAL, not committed ; run STEP-BY-STEP - (checklist, manual # VERIFY: gates) — never `bash NEXT.sh` unattended - -lib/deploy-commit.sh surgical commit, allowlist = .claude/deploy/ , rc3 unsafe-git guard, short-hash stdout - -Skill STEP spine (PRE-FLIGHT -> PROPOSE+GATE -> WRITE+COMMIT, house style): - 0 PRE-FLIGHT runbook present? absent -> bootstrap (paste | scaffold+interview) - 1 DELTA STATE absent -> first deploy = full runbook ; else diff HEAD - 2 INSTANTIATE expand @delta steps + read INCIDENTS pre-warns -> NEXT.sh -> GATE - 3 (user executes out-of-band; reports "done" | "failed at step X: ") - 4 LEARN on failure: patch PROCEDURE step + append DEP-NNN -> GATE -> deploy-commit (ATOMIC) - 5 MARK on success: write STATE@sha ; annotate + push tag ; optional doc -``` - -## 4. Delta mechanism — verified (git 2.53.0) - -All three facts re-run live before writing this spec; observed output recorded, not assumed. - -**First-deploy detection = STATE-absent, deterministic. `describe` is off the detection path.** -``` -[ -f .claude/deploy/STATE.json ] => exit 1 (absent = first deploy) <- THE detector -git describe --tags --match 'deploy/*' => fatal: No names found ; exit 128 <- only the reason NOT to use describe -[ -f .claude/deploy/STATE.json ] => exit 0 (present = delta path) -``` - -**Delta = `git diff --name-only HEAD`** (two explicit endpoints; no dots, so it cannot be misread as three-dot). -``` -LINEAR git diff --name-only HEAD => 0033_new.sql, svc.yml (== two-dot == three-dot; merge-base == STATE) -DIVERGED two-dot sideA sideB => fileA.txt, fileB.txt (both endpoints = true tree delta) -DIVERGED three-dot sideA...sideB => fileB.txt (merge-base — UNDERCOUNTS) -``` -Two-dot/explicit-endpoints is the literal tree difference between the deployed tree and HEAD = what deploy needs. It is also rebase-robust: an orphaned marker still yields the correct tree diff, whereas `git rev-list A..B` (ancestry) reports phantom deltas after history rewrite (LRN-054's trap; verified in an earlier run). **Never use `rev-list` ancestry for the artifact list.** - -**delta -> steps:** `# @delta:` annotations bind a dynamic step to the path-pattern that feeds it; the diff buckets straight into steps: -``` -# @delta:migrations glob=supabase/migrations/*.sql -# @delta:rebuild when=docker-compose*.yml,Dockerfile -# @delta:deps when=package.json,*lock* -``` - -## 5. Learning model — runbook + INCIDENTS, non-redundant - -| Artifact | Job | Lifecycle | -|---|---|---| -| `PROCEDURE.md` | The corrected procedure you run. A fix is baked into the step so the next run cannot repeat it. | in-place | -| `INCIDENTS.md` | The incident ledger; **read at BEFORE-time to pre-warn** ("0033 hit a lock timeout last deploy; runbook already carries `--timeout`, watch for it"). | append-only | - -The pre-warn read is the function `git log` serves badly — that is why the ledger is not duplication. This mirrors the memory system's own split (append-only `journal.md`/`blockers.md` alongside in-place TODO/code). - -**Coupling invariant:** one incident → **one in-place `PROCEDURE.md` patch + one `INCIDENTS.md` append, committed atomically in a single `deploy-commit.sh` call.** Never one without the other (mirrors BDR-034/036 "couple the commit to the integration step"). Significant patch (changes a prod path) → surface + approve before writing. - -## 6. `lib/deploy-commit.sh` — new helper, inverse `.claude/` rule (verified) - -Neither existing helper can commit the runbook — confirmed live: -``` -REAL doc-commit.sh .claude/deploy/PROCEDURE.md => rc 4 "REFUSED — out-of-scope ... BDR-022 ... NOTHING committed" -REAL memory-commit.sh pending (deploy changed) => rc 1 (ignores it; allowlist = .claude/memory|tasks only) -``` -`doc-commit.sh` is built to keep `.claude/**` *out* of public-doc commits; `.claude/deploy/` is under `.claude/`, so reuse is not just blocked, it is semantically wrong. `deploy-commit.sh` needs the **inverse** rule: a TARGET allowlist for `.claude/deploy/*`, modeled on `memory-commit.sh` (rc 3 unsafe-git guard, short-hash on stdout, `chore(deploy):`/`docs(deploy):` messages). - -Allowlist guard — traversal reject ordered FIRST. Prototype matrix verified live: -```sh -_in_deploy_scope() { - case "$1" in - *..*) return 1 ;; # reject path traversal FIRST - .claude/deploy/*) return 0 ;; # ALLOW the deploy family only - *) return 1 ;; # reject everything else - esac -} -``` -``` -ALLOW .claude/deploy/{PROCEDURE.md,INCIDENTS.md,STATE} -REJECT .claude/memory/* .claude/tasks/* .claude/secret CLAUDE.md src/* -REJECT .claude/deploy (bare dir, no slash) -REJECT .claude/deploy-other/x (trailing-slash requirement closes prefix confusion) -REJECT .claude/deploy/../memory/secret (traversal closed by *..* matched first) -``` - -## 7. Bootstrap - -`STEP 0 PRE-FLIGHT`: `PROCEDURE.md` present? Absent → bootstrap, two offered paths: -1. **Paste** — user supplies an existing runbook (the game example); skill adopts + annotates it. -2. **Scaffold** — skill detects deploy artifacts (migrations dir, compose/Dockerfile, package scripts, `.env`) + a short interview (ssh target, backup cmd, rollback note) → writes an annotated `PROCEDURE.md`. - -First deploy has no marker → STATE-absent ⇒ full runbook fires; then lay STATE at the deployed SHA. The first deploy *is* the creation of the runbook + the first marker. - -## 8. Open items (for the implementation plan) - -> `NEXT.sh` execution model resolved → decision #5 (checklist), promoted to design-time. - -- Tag push: tags don't push by default → AFTER step should `git push --tag deploy/` or remind. -- `INCIDENTS.md` ID/format detail (mirror `blockers.md` `DEP-NNN`); confirm name vs `ERRORS-LEARNED.md`. -- `@delta:` annotation grammar (glob= vs when=) — finalize the small DSL. -- Frontmatter `allowed-tools` set; STEP gate wording reuse from `capitalize`/`client-handover`. - -## 9. Build sequencing & a structural flag - -**Two distinct disciplines, in order — do not conflate:** -1. `writing-plans` — global task ordering (helper → skill → bootstrap), dependencies, gates. The build plan. -2. → execution → -3. At the *skill* task ONLY: `writing-skills` — the discipline for the SKILL.md itself (structure, frontmatter, spine, config conventions). Used WHEN we reach the skill task, **not before** (it does not fire at plan time). - -**Structural flag for `writing-skills` to resolve — do NOT assume the linear-spine convention suffices:** -deploy's spine is unusual — **two parts split by out-of-band execution**: STEP 0–2 before → *user deploys by hand* → STEP 4–5 after, on the `done`/`failed` report. A skill that **hands back control mid-run and resumes**. - -Preliminary recon (confirm at the skill task — NOT verified now): -- The 6 completion flux (close, ship-feature, feat, bugfix, hotfix, commit-change) appear linear one-shot — synchronous gates at most, no out-of-band hand-back. -- The relevant precedent is OUTSIDE those 6: `client-handover` already hands back — a synchronous "Deploy done?" `AskUserQuestion` pause (STEP 5) — but it holds state in *conversation context*, not on disk. -- deploy's genuinely-new bit *may* be **disk-bridged resume** (`NEXT.sh` + `STATE` on disk as the bridge) — but **whether `NEXT.sh` alone suffices to resume cross-session is an OPEN design question, not a settled answer** (see §10). An earlier draft of this spec framed it as resolved; it is not. `writing-skills` must establish the convention (how to mark "I wait for your return here", detect + resume a pending deploy, hold state across the gap) — confirm there, do not assume the linear mould suffices. - -## 10. Open design question (DESIGN-TIME, unresolved) — state across the two moments - -deploy is a **two-moment skill**: moments 0–2 (BEFORE) → user deploys out-of-band → moment 3 (AFTER) on the `done`/`failed` report. **The report may arrive in a different session.** So the design must answer how state crosses the gap and what moment 3 must know to resume correctly. - -> **`skill deux-temps, état entre temps = [à concevoir : NEXT.sh seul suffit-il pour reprendre cross-session ?]`** - -Sub-questions (to settle when we resume — NOT now, NOT assumed): -- **What must the bridge record?** Moment 3 must (a) lay the correct marker = `STATE ← target sha`, and (b) capitalize the correct incident (which step, which delta). HEAD may have moved since NEXT.sh was generated → "current HEAD" is unsafe. The bridge must persist at least **{base STATE sha, target sha, delta manifest}** — inside NEXT.sh (header block) or a sidecar (`.claude/deploy/PENDING`)? Undecided. -- **Resume detection (re-entrancy):** STEP 0 PRE-FLIGHT must detect "a deploy is pending, awaiting your report" — likely *pending-bridge present + STATE not advanced to target* — and branch RESUME (ask done/failed) vs FRESH. Is moment 3 a new `deploy` call that re-detects from disk, or a `deploy --report`? Undecided. -- **Ephemeral vs persistent tension — LINKED to sub-question 1 (not independent).** §3 calls NEXT.sh "EPHEMERAL, not committed", yet a cross-session bridge MUST survive on disk. So: **if the bridge must persist, NEXT.sh-as-bridge is impossible while NEXT.sh stays ephemeral.** Likely *binary* resolution at plan time — either (a) NEXT.sh becomes persistent (contradicts §3), or (b) the bridge is a **separate** "deploy-in-progress" artifact `{base/target/delta}` distinct from NEXT.sh. Settle with `writing-skills`. (Uncommitted local state is fine; note the single-machine assumption — an uncommitted bridge won't follow a clone.) -- **Form-novelty — deploy's DEFINING characteristic: cross-session COLD resume.** `client-handover` is a *near* precedent, not exact: it hands back **in-context** (same conversation, state held in memory). deploy must resume with the **context lost** — so the **disk alone must carry everything to resume cold**. No existing skill resumes without context; that is what sets deploy apart, and it makes sub-question 1 **load-bearing** (disk must suffice for a cold restart). deploy likely introduces a NEW skill form → `writing-skills` establishes the convention. Confirm there. - -**Next step:** `writing-plans` to turn this spec into an implementation plan (helper first, then skill); at the skill task, `writing-skills` to shape it to convention and **resolve the §10 two-moment state question** — which is design-time, deferred only because we are stopped here, not because it is impl detail. diff --git a/docs/superpowers/plans/2026-07-15-model-routing.md b/docs/superpowers/plans/2026-07-15-model-routing.md deleted file mode 100644 index 6c76e2f..0000000 --- a/docs/superpowers/plans/2026-07-15-model-routing.md +++ /dev/null @@ -1,1953 +0,0 @@ -# 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 ` clean and `bash -n ` 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: ' > .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=` for fixtures. -- Produces: stdout `:` 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 : : (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 `:`; 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 () — self-check says ` and ask the user to confirm before continuing (BDR-025: unknown never silently passes) | - -**STOP means**: print exactly - - ⛔ MODEL GATE — session on . 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: -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 `, `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: — rule , ~ files, -``` - -## 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/--.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: - [ ] — - [ ] — - [ ] — -``` - -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: -PLAN: -BRANCH: -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(): - - -``` - -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 : -FILE(S) : -TEST(S) : -VERIFIED : -``` - -## STEP 6 — DOC SYNC (automatic) - -Load `$HOME/.claude/agents/doc-syncer.md`. -Execute in automatic mode: -`auto-mode scope: ` - -**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 — (optionnel) - [learnings.md] LRN-XXX — (optionnel) -Valider ? (all / / 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 : -TESTS : -NOTES : -``` -```` - -- [ ] **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: ". - 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`. - ---- - -# 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) : - FIX : - SMOKE : - NOTES : - ``` - -- [ ] **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 ` → `gitflow start release `, set `version.txt`, - rewrite CHANGELOG (`## [Unreleased]` → `## [] — `, 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 ` → `gitflow finish` (fan-out), `git tag -a v main - -m "release "` 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 ` → **HUMAN GATE — when to release** (`AskUserQuestion`, - explicit go, never on "tests pass") → `Agent(subagent_type="release-executor")` - `SPAN: finish ` → **push GATE (ASK)** (`AskUserQuestion`; on go only, - LRN-069): `git push origin main develop && git push origin v` 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. - ---- - -# WAVE 3 — reflection-split the last two inline execution-carrying agents (user directive 2026-07-15) - -`/bugfix` and `/code-clean` are still thin wrappers that inline-load an agent -doing BOTH reflection and execution on the big session model — the exact -pre-split state `/feat` (Task 5) and `/hotfix` (Task 12) were in. Split each -like the shipped pattern: reflection inline (session model, behind the MODEL -GATE both skills already carry), execution dispatched to a sonnet executor. - -**Honest tradeoff (recorded in the wave-3 BDR bullet):** bugfix's investigation -and fix are tightly coupled; handing a context-free sonnet executor a closed -FIX PLAN is the same bet `/feat` makes — mitigated by the structured DIAGNOSIS -+ the verify loop catching drift. Further supersedes the BDR-050 "bugfix stays -inline" carve-out (hotfix already reversed in wave 2). code-clean's win is -larger than it looks: today it INLINE-LOADS the refactorer (so the refactor -runs on the big model, sonnet pin inert) — after the split the refactor runs -inside the sonnet executor for the first time. - -Gate membership is UNCHANGED: both skills keep reflection (investigation / -audit), so both STAY in the wired gate list — this wave only adds -executor-shape asserts, it does not move either skill between the wired and -excluded lists. - -## Global Constraints (wave 3) - -Same as waves 1–2: branch `feature/model-routing`, no merge, no attribution -trailers, `make test` green per commit, shellcheck clean, config-protection -sentinel before each `lib/tests/*` write (controller applies guarded test -edits — subagents cannot create the sentinel), YAML `safe_load`-parseable -frontmatter. - ---- - -### Task 16: `/bugfix` split — reflection inline + dispatched sonnet executor - -**Files:** -- Modify (rewrite): `skills/bugfix/SKILL.md` — thin wrapper → reflection orchestrator -- Modify (rewrite): `agents/bugfixer.md` — full agent → pure sonnet executor -- Modify (surgical): `lib/verify-secure-loop.md` — bugfix's dev is now dispatched too -- Modify: `lib/tests/loops-light.test.sh` — repoint bugfix locks (guarded — CONTROLLER applies) - -**Pattern:** mirror the shipped `/hotfix` split (skills/hotfix/SKILL.md — it is -the closest template: reflection orchestrator + a security gate whose decisions -live in the main loop) and `/feat` (agents/feater.md — the pure-executor shape). -The difference from hotfix: bugfix keeps the FULL verify+secure loop (fresh -verifier + fresh security, bounded 3×, per `lib/verify-secure-loop.md`) and the -interactive pre-commit + capitalize gates — hotfix has none of those. - -- [ ] **Step 1: Rewrite `skills/bugfix/SKILL.md` as the reflection orchestrator.** - Keep `Agent` in allowed-tools; the frontmatter description block is unchanged. - Keep the existing `MODEL GATE (blocking)` line verbatim (bugfix stays gated). - Absorb, INLINE (session model), what were bugfixer STEPs 1–3.5 — reflection: - - STEP 1 GATHER CONTEXT (git status/log; what/where/when). - - STEP 1.5 DESIGN GATE (`$HOME/.claude/lib/design-gate.md`, unchanged). - - STEP 2 INVESTIGATE (trace symptom → root cause, blast radius). - - STEP 2.5 MEMORY READ-BEFORE (`$HOME/.claude/lib/analyze-before-plan.md`, - blockers-weighted; keep the TEETH clause — DIAGNOSIS must name any binding - prior or state none bears). - - STEP 3 DIAGNOSE + PLAN — emit the `BUGFIX — DIAGNOSIS` block (BUG / ROOT - CAUSE / EVIDENCE / BLAST RADIUS / FIX PLAN / RISK) verbatim from today's - bugfixer STEP 3. Keep the significance gate: trivial → proceed; significant - (>10 lines / multi-file / behavior change) → wait for user approval; - root-cause unclear → list ranked hypotheses, ask before proceeding. - - STEP 3.5 CONTRACT (`$HOME/.claude/lib/contract-interview.md`, main loop; - DIAGNOSIS feeds it — REQUEST verbatim = the bug report, ACCEPTANCE = - symptom reproduced-then-gone + regression test present+passing, FILE SCOPE - = the FIX PLAN files; keep the contract path for STEP 5). - - STEP 4 BRANCH: gitflow aiguillage (`$HOME/.claude/lib/gitflow-aiguillage.md`, - type `bugfix`; never finish). - - STEP 5 DISPATCH EXECUTOR: - ``` - Agent(subagent_type="bugfixer") - prompt: "CONTRACT: - DIAGNOSIS: - FIX PLAN: - BRANCH: - Apply the fix to the letter + the regression test. No commit, no branch - ops, no security dispatch. Finish with the BUGFIX-EXEC REPORT." - ``` - Parse `BUGFIX-EXEC REPORT`: `STATUS : DONE` → STEP 6; `NEED-DECISION` → - decide HERE (reflection), append to the plan, re-dispatch a FRESH bugfixer, - max 2 round-trips → escalate; `BLOCKED` → surface + stop. - - STEP 6 VERIFY + SECURE + PRE-COMMIT GATE + COMMIT (main loop, LRN-083): - run the two fresh gates per `$HOME/.claude/lib/verify-secure-loop.md` with - `CONTRACT` = STEP 3.5 path, `DIFF` = the executor's working-tree diff, - `TEST` = the suite from its report (the loop's "dev" is the dispatched - bugfixer — re-dispatched FRESH on ECARTS/BLOCK). After both gates pass, - keep today's interactive `BUGFIX — READY TO COMMIT` pre-commit gate - (diff-stat + message → yes / edit message / skip / amend last), then commit - with the conventional `fix():` message, then print `BUGFIX COMPLETE`. - - STEP 7 DOC SYNC (doc-syncer auto-mode + `$HOME/.claude/lib/doc-commit.md`, - verbatim from today's bugfixer STEP 6). - - STEP 8 CAPITALIZE (BLK-XXX pre-filled from DIAGNOSIS + optional LRN; - interactive gate; `$HOME/.claude/lib/capitalize-commit.md`; verbatim from - today's bugfixer STEP 7). - - RULES: no fix without root cause first; execution never stays inline, - reflection never leaves it (BDR-066); executor dispatched fresh; keep - regression-test + scoped-fix + escalate-to-/ship-feature-if->5-files rules. - -- [ ] **Step 2: Rewrite `agents/bugfixer.md` as the pure executor.** Replace the - ENTIRE file with exactly: - -````markdown ---- -name: bugfixer -description: Bug-fix EXECUTOR — dispatched by /bugfix with a closed DIAGNOSIS + FIX PLAN + contract. Applies the fix and a regression test, runs the suite, reports. No investigation, no questions, no commit. -tools: Read, Edit, Write, Bash, Grep, Glob -model: sonnet ---- - -# BUGFIXER — fix executor - -You receive a CLOSED diagnosis + fix plan from the /bugfix orchestrator. The -investigation already happened; your job is faithful execution, not analysis. -Every choice was 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 (symptom reproduced-then-gone + a regression test present) + FILE - SCOPE bound everything you do. -- `DIAGNOSIS`: root cause + evidence, from the orchestrator's investigation. -- `FIX PLAN`: the exact edits (file:line → change) + the regression test to add. -- `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 - -- Apply the FIX PLAN to the letter — fix the ROOT CAUSE named in DIAGNOSIS, - not the symptom. A plan hole or an open choice (naming, data shape, API - surface, dependency) → STOP, report `NEED-DECISION` with the precise - question. Never re-investigate or improvise a different fix. -- Stay inside the contract FILE SCOPE. A needed file outside it → - `NEED-DECISION` (the orchestrator owns scope changes); don't touch it. -- Add or update the regression test the plan names — it must fail before the - fix and pass after. Run the relevant suite incrementally; run it fully - before reporting. -- Follow existing code patterns and CLAUDE.md limits (function size, params, - no global state). Keep the fix minimal — no "while we're here" cleanups. -- FORBIDDEN: `git commit`, branch ops, push, merge, new dependencies, - security/verifier dispatch, 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) - -``` -BUGFIX-EXEC REPORT -STATUS : DONE | NEED-DECISION | BLOCKED -FILE(S) : -TEST(S) : -SMOKE : -NOTES : -``` -```` - -- [ ] **Step 3: Update `lib/verify-secure-loop.md` (both consumers now dispatched).** - The header line `(feat, bugfix)` stays. In the intro, the sentence that says - the dev step is "either inline (bugfix) or a dispatched sonnet executor - (/feat's feater)" is now stale — BOTH are dispatched. Edit it to: the dev - step is a dispatched sonnet executor (feat's `feater`, bugfix's `bugfixer`); - "hand the dev" below means re-dispatch a FRESH executor with exactly those - inputs. Keep GATE 1 / GATE 2 / order-invariant bodies unchanged (they already - read "inline dev fixes in place; a dispatched dev is re-dispatched FRESH" — - the inline branch simply no longer has a consumer, harmless). - -- [ ] **Step 4 (CONTROLLER — guarded): repoint `lib/tests/loops-light.test.sh` - bugfix locks.** Sentinel first: - `printf 'model-routing plan: repoint bugfix structure locks to the skill orchestrator' > .claude/.config-edit-ok` - Introduce `BSK="$REPO/skills/bugfix/SKILL.md"`. The `bugfixer.md (bugfix wiring)` - block currently greps `$BUG` for orchestration clauses now moved to the skill — - repoint them to `$BSK`: `STEP 3.5 — CONTRACT`, `feeds it: REQUEST verbatim`, - `Fresh gates (verify + secure)` (or the skill's actual STEP-6 wording — match - it), `lib/verify-secure-loop.md`. Add a new `bugfixer.md (executor — sonnet, - no Agent)` block mirroring the hotfixer one: `tn` bugfixer LACKS `Agent`, - `tf` `model: sonnet`, `tf` `BUGFIX-EXEC REPORT`. Add `tf "bugfix dispatches - bugfixer" "$BSK" 'subagent_type="bugfixer"'`. Keep include/feat/hotfix blocks - untouched. - -- [ ] **Step 5: Verify + commit.** `bash lib/tests/loops-light.test.sh` green; - YAML check both rewritten files - (`python3 -c "import yaml; [yaml.safe_load(open(f).read().split('---')[1]) for f in ['agents/bugfixer.md','skills/bugfix/SKILL.md']]; print('YAML OK')"`); - `make test` green. - ```bash - git add skills/bugfix/SKILL.md agents/bugfixer.md lib/verify-secure-loop.md lib/tests/loops-light.test.sh - git commit -m "feat(model-routing): /bugfix split — reflection inline, bugfixer = sonnet executor (supersedes BDR-050 bugfix carve-out)" - ``` - ---- - -### Task 17: `/code-clean` split — audit + gate inline + dispatched sonnet executor - -**Files:** -- Modify (rewrite): `skills/code-clean/SKILL.md` — thin wrapper → audit orchestrator -- Modify (rewrite): `agents/code-cleaner.md` — full agent → pure PHASE-2 sonnet executor - -**Pattern:** mirror `/feat` (skill = orchestrator holding reflection + interactive -gate; agent = pure executor). The interactive VALIDATION GATE must stay in the -orchestrator (a dispatched subagent cannot ask). code-cleaner gains `model: sonnet`. - -- [ ] **Step 1: Rewrite `skills/code-clean/SKILL.md` as the audit orchestrator.** - Add `Agent` to allowed-tools (keep `AskUserQuestion`, `Read/Edit/Write/Bash/ - Grep/Glob`). Keep the existing `MODEL GATE (blocking)` line verbatim (audit = - reflection, stays gated). Absorb code-cleaner's PHASE 1 INLINE (session model): - - `# /code-clean — cleanup orchestrator (audit inline, execution dispatched)` - - STEP 1 LOAD PROJECT NORMS (CLAUDE.md > lang configs > community defaults). - - STEP 2 SCAN (A dead code / B style / C structural — verbatim from today's - code-cleaner PHASE 1 STEP 2, keep the TODO-age bash). - - STEP 3 BUILD REPORT (the `CODE-CLEAN AUDIT` block, severity levels). - - STEP 4 VALIDATION GATE (interactive, `AskUserQuestion`): present the report; - approve all / cherry-pick / clarify. Do NOT proceed until explicit approval. - Exported/public-API symbols flagged in the audit are resolved HERE, per-item - (this is where the today's guard-rail consent lives — the executor never asks). - - STEP 5 PERSIST SCOPE + DISPATCH: write the approved items to - `.claude/audits/CODE-CLEAN-SCOPE.md` (`mkdir -p .claude/audits` first), one - per line `file:line — item — severity — proposed fix`, then: - ``` - Agent(subagent_type="code-cleaner") - prompt: "SCOPE: .claude/audits/CODE-CLEAN-SCOPE.md - APPROVED: - BRANCH: - Execute PHASE 2 on the approved scope only. Zero behavior change. No commit. - Finish with the CODE-CLEAN-EXEC REPORT." - ``` - Parse `CODE-CLEAN-EXEC REPORT`: `DONE` → STEP 6; `BLOCKED` → surface + stop. - - STEP 6 SUMMARY: present the `CODE-CLEAN COMPLETE` block (removed / refactored - / skipped / bugs-found / tests) from the executor's report. No commit here - (code-clean has never auto-committed — leave the working tree for the user - or a follow-up `/commit-change`; keep today's behavior). - - RULES: zero behavior change; no scope creep; exported symbols need explicit - per-item consent AT THE GATE; bugs → BUGS-FOUND.md not fixed; no plugin - check (lightweight); systemic issues → suggest `/ship-feature`. - -- [ ] **Step 2: Rewrite `agents/code-cleaner.md` as the PHASE-2 executor.** Replace - the ENTIRE file with exactly: - -````markdown ---- -name: code-cleaner -description: Cleanup EXECUTOR (PHASE 2) — dispatched by /code-clean with an APPROVED scope. Deletes approved dead code, hands style/structural items to the refactorer, re-audits. Zero behavior change. No audit, no questions, no commit. -tools: Read, Edit, Write, Bash, Grep, Glob -model: sonnet ---- - -# CODE-CLEANER — cleanup executor (PHASE 2) - -You receive an APPROVED cleanup scope from the /code-clean orchestrator. The -audit and the user approval already happened; your job is faithful execution. -The iron law is unchanged: ZERO behavior change — identical observable output -before and after. - -## INPUT (in the dispatch prompt) - -- `SCOPE`: path to `.claude/audits/CODE-CLEAN-SCOPE.md` — the approved items - (`file:line — item — severity — proposed fix`), the on-disk contract. -- `APPROVED`: the item list the user confirmed (may be a subset of the audit), - including any exported/public-API symbols the gate explicitly cleared. -- `BRANCH`: verify with `git branch --show-current`; mismatch → STATUS - BLOCKED — never create or switch branches. - -## EXECUTION — in order - -### 1. Delete approved dead code (safest first) - -Remove approved unused imports / variables / functions, commented-out blocks, -stale TODO/FIXME. **Guard rail**: an exported / public-API symbol the -`APPROVED` list did NOT explicitly clear → do NOT delete; SKIP it and record -it under NOTES. The per-item exported-symbol consent lives in the -orchestrator's gate — you never ask. - -### 2. Style + structural fixes → INLINE-LOAD the refactorer - -Load `$HOME/.claude/agents/refactorer.md` and continue AS the refactorer in -THIS SAME context — you *become* it. This is an inline load, NOT a subagent -dispatch: the `Agent` tool is not involved and no new context is spawned. Its -scope = the style / structural items in `SCOPE`. Its own safety process runs -(pre-report, function-by-function, test after each) — zero behavior change. -Running inside this sonnet executor, the refactor finally runs on sonnet (the -refactorer pin was inert under the old inline-load on the session model). - -### 3. Log discovered bugs (do NOT fix) - -Real defects found during cleanup (not style issues) → append each to -`.claude/audits/BUGS-FOUND.md` (`mkdir -p .claude/audits` first): file:line, -description, severity, discovered-while. Cleanup and bugfixing are separate -concerns — never fix a bug here. - -### 4. Re-audit - -Re-scan only the modified files; verify no new issues were introduced; run the -project test suite + linter/formatter if available. - -## RULES - -- Zero behavior change. Unsure a deletion is safe → leave it, record under NOTES. -- No "while we're here" scope creep — only the APPROVED items. -- FORBIDDEN: `git commit`, branch ops, push, merge, new dependencies, user - questions (report instead), editing `.claude/**` or memory registries, - attribution trailers of any kind. - -## OUTPUT — end with exactly this report (your final message) - -``` -CODE-CLEAN-EXEC REPORT -STATUS : DONE | BLOCKED -REMOVED : -REFACTORED: -SKIPPED : -BUGS : -TESTS : -NOTES : -``` -```` - -- [ ] **Step 3: Verify + commit.** - `grep -c 'subagent_type="code-cleaner"' skills/code-clean/SKILL.md` → 1; - `grep -c 'model: sonnet' agents/code-cleaner.md` → 1; - `grep -c 'AskUserQuestion' agents/code-cleaner.md` → 0; - YAML check both files; `make test` green. - ```bash - git add skills/code-clean/SKILL.md agents/code-cleaner.md - git commit -m "feat(model-routing): /code-clean split — audit+gate inline, code-cleaner = sonnet PHASE-2 executor" - ``` - ---- - -### Task 18: wave-3 census + docs + memory - -**Files:** -- Modify: `lib/tests/model-routing.test.sh` (guarded — CONTROLLER applies) — wave-3 asserts -- Modify: `README.md`, `CHANGELOG.md`, `.claude/memory/decisions.md`, - `.claude/memory/journal.md`, `.claude/tasks/TODO.md` - -- [ ] **Step 1 (CONTROLLER — guarded): extend the census.** Sentinel first: - `printf 'model-routing plan: wave-3 executor-shape asserts (bugfix, code-clean)' > .claude/.config-edit-ok` - In `lib/tests/model-routing.test.sh`, add a `# 8) wave-3 — bugfix/code-clean - reflection-split executors` section: - `has skills/bugfix/SKILL.md 'subagent_type="bugfixer"'`; - `has agents/bugfixer.md 'model: sonnet'`; - `lacks agents/bugfixer.md 'AskUserQuestion'`; - `has skills/code-clean/SKILL.md 'subagent_type="code-cleaner"'`; - `has agents/code-cleaner.md 'model: sonnet'`; - `lacks agents/code-cleaner.md 'AskUserQuestion'`. - (bugfix + code-clean stay in the existing `# 1)` wired-gate loop — do NOT move - them.) Update the printed expected count. Flip-test one new assertion (LRN-096: - e.g. temporarily break the bugfixer dispatch string, confirm red, restore). - -- [ ] **Step 2: README + CHANGELOG.** In the BDR-066 agent-model table, MOVE - `bugfixer` and `code-cleaner` out of the "inherit session" row into the - executor/dispatched rows (bugfixer → sonnet executor; code-cleaner → sonnet - PHASE-2 executor). The "inherit session" row keeps analyzer + seo/geo/validator - analyzers (+ commit-changer stays wherever wave-2 placed it). CHANGELOG - Unreleased `### Changed`: add the wave-3 bullet (bugfix/code-clean split — - reflection inline, executors sonnet; refactor now runs on sonnet inside the - code-clean executor). - -- [ ] **Step 3: Capitalize.** Append a `**Wave 3**` bullet to the BDR-066 entry - in `.claude/memory/decisions.md`: bugfix + code-clean reflection-split - (executors sonnet); supersedes the BDR-050 "bugfix inline" carve-out (hotfix - went in wave 2, bugfix now); code-clean refactor finally on sonnet (inline-load - pin was inert). Note the accepted tradeoff (bugfix investigation↔fix coupling, - mitigated by DIAGNOSIS + verify loop). Journal line under 2026-07-15 + TODO - tick. - ```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-3 census + docs + BDR-066 update (bugfix/code-clean split)" - ``` - -- [ ] **Step 4: Final wave-3 review** — dispatch a whole-branch reviewer (opus) - over the wave-3 range; confirm: bugfix keeps root-cause-first + the pre-commit - gate + verify+secure loop with the executor as the loop's dev; code-clean keeps - the interactive validation gate + exported-symbol per-item consent at the gate - (not in the executor); zero execution left on the big model in either skill; - both executors have no `AskUserQuestion` and are `model: sonnet`. Report the - full `git log --oneline develop..HEAD`. Do NOT merge. - ---- - -# WAVE 4 — client-handover: dispatch the doc-generation to sonnet (redaction-only, spec §5, user directive 2026-07-16) - -**Shape DECIDED (user, 2026-07-16): redaction-only**, NOT whole-writer. Rationale -(from the full read of the 1774-line writer): the nested audit dispatches -(STEP 3/4/7 run `/seo`, `/harden`, `/web-validate`) must run on the BIG model -either way — those skills are gated themselves (wave 1), so a sonnet parent -would force-pin them anyway or trip their own gate. So the only work that truly -belongs on sonnet is the DOC GENERATION (writing the deliverable). Whole-writer -would add ~7 extra gate-yields + a resumable state machine on a client-facing -pipeline for ~zero extra sonnet work. Redaction-only gets the same sonnet -savings with the pipeline + all interactive gates staying NATIVE in the big -main loop. - -## Architecture - -- **`agents/client-handover-writer.md`** stays INLINE-LOADED by the skill → - runs on the big session model (its `model: opus` frontmatter is inert under - inline-load; leave or drop — see Task 20). It becomes the **pipeline + - delegator**: STEP 1–8 unchanged (pre-flight, detect, baseline audits, fix - loops, commit/push, deploy pause, web-validate, gate eval) — all its - interactive gates (STEP 4 escalation, STEP 5 push/push-failed, STEP 6 deploy - pause + URL) work NATIVELY (main loop) and its nested audit dispatches inherit - the big session model with NO force-pinning. Then it does the INTERACTIVE + - detection parts of doc-gen — STEP 11 questions (Q1 deploy-chapter, Q2 - language), the STEP 12 §4 NAP-table build (incl. the business-name ask), - STEP 14.5 platform detection + the batch-unknowns `AskUserQuestion`, and - resolves the output path + overwrite decision (STEP 15 gate) + `CLIENT_NAME` - (STEP 16 gate). It assembles a PACKAGE and dispatches the doc-writer, then - reports the returned deliverable. Keeps `AskUserQuestion` + `Agent`. -- **`agents/handover-doc-writer.md`** (NEW, `model: sonnet`, tools - `Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch` — **NO - AskUserQuestion, NO Agent**): the pure writer. Reads material itself (STEP 9 - memory, STEP 10 git history) and, from the PACKAGE, does STEP 12 synthesis + - STEP 13 §7 checklist content + STEP 14 §8 (only if `INCLUDE_DEPLOY`) + - STEP 14.5 apply-the-given-precheck-set + STEP 15 write MD + the three gates - (word-count ≤300 on §3, skill-leak, anchor-resolution) + STEP 16 render - (HTML always, PDF when an engine is present). **Gate-free** — every - interactive decision was resolved by the parent and travels in the PACKAGE. - -## The PACKAGE (parent → doc-writer dispatch prompt) - -A single structured block the doc-writer treats as ground truth: -`LANG`; `PROJECT` (name, root, type, sub-type, is_local_business, deployed_url, -period first→last commit); `SCORES` (seo/geo/harden/validate or cso — before & -after, each with pass-status + any code-ceiling note); `AUDIT_REPORTS` (paths to -`.claude/audits/*.md` + HUMAN-ACTIONS/THRESHOLD-OVERRIDE if present, for §6 -sourcing); `INCLUDE_DEPLOY` (yes|no); `NAP` (the full resolved §4 table) + -`PRECHECK_DONE` (platforms already confirmed, for §5/§7 checkboxes); -`CLIENT_NAME` (string or `—`); `OUTPUT` (final md path + overwrite decision -`overwrite | versioned | skip-write`). - -## Global Constraints (wave 4) - -Branch `feature/client-handover-dispatch` (off develop, already checked out). -Same as prior waves: no merge, no attribution trailers, `make test` green per -commit, shellcheck clean, config-protection sentinel before each `lib/tests/*` -write (controller applies), YAML `safe_load`-parseable frontmatter. **Preserve -every deliverable invariant** (6-chapter structure, §2=scores, §4 NAP before §5, -§3 ≤300 words + zero jargon, skill-leak gate over chapters 1–5, clickable -anchors, ZenQuality render) — a dropped gate degrades a client deliverable. - ---- - -### Task 19: create `agents/handover-doc-writer.md` (sonnet, gate-free doc generator) - -**Files:** Create `agents/handover-doc-writer.md`. - -Extract the doc-generation half of `agents/client-handover-writer.md` (its -STEP 9, 10, 12, 13, 14, 14.5-apply, 15, 16 — lines 835–1774, MINUS the -interactive detection/questions that the parent now owns) into a new standalone -sonnet agent driven by the PACKAGE. - -- [ ] **Step 1:** Frontmatter: `name: handover-doc-writer`; - `description:` (one line — "Deliverable writer — dispatched by - client-handover with a resolved PACKAGE. Synthesizes the 6-chapter client doc, - writes the MD, renders branded HTML+PDF. No audits, no questions."); - `tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch`; - `model: sonnet`. NO `AskUserQuestion`, NO `Agent`. -- [ ] **Step 2:** Body. Open with an `## INPUT — the PACKAGE` section listing the - fields above and stating they are ground truth (never re-ask, never re-audit). - Then port, in order: - - STEP 9 LOAD MEMORY (verbatim — reads `.claude/memory/*`) and STEP 10 GIT - HISTORY (verbatim; keep the ≥200-commit clustering, but if it delegates to a - sub-agent, DROP that — a sonnet doc-writer has no `Agent`; do the clustering - inline). - - STEP 12 SYNTHESIZE — the full 6-chapter structure + ALL "Hard rules" - (clickable anchors, no skill names in ch.1–5, §3 ≤300 words, §2 score table - from `SCORES`, §4 NAP from `PACKAGE.NAP` before §5). Verbatim from source. - - STEP 13 §7 SEO/GEO checklist content + STEP 14 §8 deploy chapter (render - ONLY if `INCLUDE_DEPLOY=yes`). - - STEP 14.5 APPLY-ONLY: apply `PACKAGE.PRECHECK_DONE` to §5 `- [ ]`→`- [x]` - and §7 `- ☐`→`- ☑` + the cleanup pass. DROP the detection + the - batch-unknowns `AskUserQuestion` (the parent did both; the doc-writer only - applies the resolved set). - - STEP 15 WRITE — honor `PACKAGE.OUTPUT` (path + overwrite decision; NO - overwrite `AskUserQuestion` — obey the decision). Keep all three gates - verbatim: `wc -w` ≤300 on §3, the skill-leak grep over ch.1–5, the anchor - `comm -23` check. A gate failure → fix the MD and re-check (as today). - - STEP 16 RENDER — use `PACKAGE.CLIENT_NAME` (no `AskUserQuestion`); run - `scripts/handover-to-pdf.sh` with the env vars as today. - - FORBIDDEN block: `git commit`/branch ops/push, new deps, `Agent` dispatch, - `AskUserQuestion`, editing `.claude/**`, attribution trailers. - - End with a `HANDOVER-DOC REPORT` (STATUS DONE|BLOCKED; MD path; HTML path; - PDF path or "no engine"; the three gate results; NOTES). -- [ ] **Step 3:** YAML check - (`python3 -c "import yaml; yaml.safe_load(open('agents/handover-doc-writer.md').read().split('---')[1]); print('YAML OK')"`); - `grep -c 'AskUserQuestion\|subagent_type\|Agent(' agents/handover-doc-writer.md` → 0. - `make test` green (new agent isn't yet referenced — pure addition). - ```bash - git add agents/handover-doc-writer.md - git commit -m "feat(model-routing): handover-doc-writer — sonnet gate-free deliverable generator (wave 4)" - ``` - ---- - -### Task 20: trim `agents/client-handover-writer.md` to pipeline + delegator - -**Files:** Modify `agents/client-handover-writer.md`. - -- [ ] **Step 1:** Keep STEP 1–8 verbatim (pipeline; native gates; nested audit - dispatches unchanged — they inherit the big session model since this agent is - inline-loaded and runs big). -- [ ] **Step 2:** Replace STEP 9–16 with a **doc-gen orchestration** section that: - (a) runs STEP 11 questions inline (Q1 deploy-chapter → `INCLUDE_DEPLOY`, Q2 - language); (b) builds the §4 NAP table inline (incl. the business-name - `AskUserQuestion` at today's line ~1107); (c) runs STEP 14.5 platform - DETECTION + the batch-unknowns `AskUserQuestion` inline → `PRECHECK_DONE`; - (d) resolves the output path + overwrite (`test -f` then the STEP 15 question) - and `CLIENT_NAME` (detect or the STEP 16 question); (e) assembles the PACKAGE - and dispatches: - ``` - Agent(subagent_type="handover-doc-writer") - prompt: "PACKAGE:\n\nSynthesize + write + render the - deliverable per your steps. Report the HANDOVER-DOC REPORT." - ``` - then parses `HANDOVER-DOC REPORT` and reports the deliverable paths to the - user (surface BLOCKED verbatim). Keep `AskUserQuestion` + `Agent` in tools. -- [ ] **Step 3:** Frontmatter — drop the now-misleading `model: opus` line (this - agent inherits the session model like the other big-model reflection agents; - inline-load already made the pin inert). Update its `description` to say it - runs the ship pipeline then DELEGATES the deliverable writing to the sonnet - `handover-doc-writer`. -- [ ] **Step 4:** YAML check; `make test` green. - ```bash - git add agents/client-handover-writer.md - git commit -m "feat(model-routing): client-handover-writer trimmed to pipeline + delegates doc-gen to sonnet doc-writer" - ``` - ---- - -### Task 21: `skills/client-handover/SKILL.md` — MODEL GATE + updated overview - -**Files:** Modify `skills/client-handover/SKILL.md`. - -- [ ] **Step 1:** Add the orchestrator MODEL GATE block right under the H1 / - before the `Load and follow strictly:` line (client-handover orchestrates - audits = reflection → it MUST be gated): - ``` - 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. - ``` - Keep the inline-load of `client-handover-writer.md` (it runs the big-model - pipeline). Update the overview prose: the writer runs the audit/fix/gate - pipeline on the big model, then delegates the deliverable writing to the - sonnet `handover-doc-writer`. -- [ ] **Step 2:** `grep -c 'lib/model-gate.md' skills/client-handover/SKILL.md` → ≥1; - `make test` green. - ```bash - git add skills/client-handover/SKILL.md - git commit -m "feat(model-routing): client-handover MODEL GATE (pipeline orchestrates audits = reflection)" - ``` - ---- - -### Task 22: wave-4 census + docs + memory - -**Files:** `lib/tests/model-routing.test.sh` (guarded — CONTROLLER), `README.md`, -`CHANGELOG.md`, `.claude/memory/decisions.md`, `.claude/memory/journal.md`, -`.claude/tasks/TODO.md`. - -- [ ] **Step 1 (CONTROLLER — guarded):** sentinel, then add to - `lib/tests/model-routing.test.sh`: add `client-handover` to the wired-gate - loop (`# 1)`); a `# 9) wave-4` block — - `has skills/client-handover/SKILL.md 'lib/model-gate.md'` (redundant w/ loop — - keep in the loop), `has agents/handover-doc-writer.md 'model: sonnet'`, - `lacks agents/handover-doc-writer.md 'AskUserQuestion'`, - `has agents/client-handover-writer.md 'subagent_type="handover-doc-writer"'`. - Update the printed count; flip-test one new assertion. -- [ ] **Step 2:** README agent-model table — add `handover-doc-writer` (sonnet, - deliverable writer); move `client-handover-writer` from its old "opus (pinned, - inert)" row into the "inherit session" reflection row (it now runs the - big-model pipeline). CHANGELOG Unreleased `### Changed` — wave-4 bullet. -- [ ] **Step 3:** BDR-066 `**Wave 4**` bullet (redaction-only chosen over - whole-writer + why: audits big either way; doc-gen → sonnet doc-writer; - pipeline + all gates native on big; client-handover joins the gated group). - Journal line + TODO tick. - ```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-4 census + docs + BDR-066 (client-handover doc-gen → sonnet)" - ``` - -- [ ] **Step 4: Final wave-4 review** — dispatch a whole-branch reviewer (opus) - over the wave-4 range: verify the doc-writer preserves EVERY deliverable - invariant (6 chapters, §2 scores, §4-before-§5, §3 ≤300 words, skill-leak - gate, anchors, render), is gate-free (no AskUserQuestion/Agent), and that the - parent still owns all interaction + assembles a complete PACKAGE (no field the - doc-writer needs is missing). Report `git log --oneline develop..HEAD`. Do NOT - merge. diff --git a/docs/superpowers/specs/2026-07-15-model-routing-design.md b/docs/superpowers/specs/2026-07-15-model-routing-design.md deleted file mode 100644 index e1b1419..0000000 --- a/docs/superpowers/specs/2026-07-15-model-routing-design.md +++ /dev/null @@ -1,143 +0,0 @@ -# Model routing — reflection inline (big model) / execution pinned (Sonnet) — design - -**Date**: 2026-07-15 · **Status**: approved (user, 2026-07-15) · **Branch**: `feature/model-routing` -**Lifecycle**: transient planning artifact (BDR-065) — committed during the run, deleted post-merge. - -## Principle - -The session model is assumed to be a big reasoning model (Fable 5, or Opus when -Fable is unavailable). Everything that **thinks** — brainstorming, planning, -technical decisions, audits, loop decisions — runs INLINE in the main -conversation, or in subagents that inherit the session model. Everything that -**executes** a ready-made plan — writing code, applying fix bundles, commits, -deliverable rendering — runs on Sonnet-pinned subagents. A blocking gate -enforces the "session = big model" assumption at the entry of every reflection -orchestrator. - -User verdicts baked in (2026-07-14/15): -- Scope = hybrid: ship-feature/init-project execution → sonnet; `/feat` - re-architected (plan inline → dispatch executor); bugfix/hotfix stay fully - inline (BDR-050 conserved for them). -- Gate = BLOCKING, not advisory. -- Audit agents inherit the session model (no opus pin); the gate extends to - audit orchestrators. -- verifier + security-auditor KEEP `model: sonnet` (job9 decision confirmed). -- client-handover-writer → sonnet (requires converting its inline-load to a - true dispatch; human gates relocate to the main loop). - -## 1. Blocking model gate - -New `lib/model-check.sh`: resolves the current session model from -`settings.json` (physical path resolution — LRN-023 class), normalizes -(`claude-fable-5[1m]` → fable, `claude-opus-*` → opus, sonnet, haiku), prints -`big|small|unknown`. Exit 0 = big, 2 = small, 3 = unknown. - -New `lib/model-gate.md` snippet (same include pattern as `lib/design-gate.md`): -run the check; `small` → STOP the skill: "session model is — reflection -requires Fable/Opus. Switch with /model, then relaunch." `unknown` → -fail-visible: show the raw value, ask the user to confirm or abort (BDR-025 -doctrine — unknown never silently passes). - -Wired as a STEP 0 line in the reflection orchestrators: -`ship-feature, init-project, feat, bugfix, onboard, seo, geo, web-validate, -harden, audit-delta, tour, code-clean`. -NOT wired in: `hotfix` (trivial by definition), `commit-change`, `doc`, -`status`, `release-candidate`. - -Caveats to prove at implementation time: -- `/model` mid-session rewrites settings.json (LRN-098 observed it once — - re-prove with a live flip-test before trusting the source). -- The helper itself must be flip-tested (LRN-096: an unproven guard is a - vacuous guard). - -## 2. Frontmatter pins (`agents/*.md`) - -| Agent | Before | After | Rationale | -|---|---|---|---| -| feater | (inherit) | **sonnet** | executor as subagent: seo/geo L1 applier + new /feat dispatch | -| hotfixer | (inherit) | **sonnet** | L1 applier (seo/geo/web-validate); /hotfix inline unaffected (pin inert on inline load) | -| client-handover-writer | opus | **sonnet** | deliverable executor; pin becomes EFFECTIVE only with §5 dispatch conversion (today's opus pin is inert — the agent is inline-loaded) | -| analyzer | haiku | **(none — inherit)** | analysis feeds the plan = reflection; runs big via the session model | -| verifier | sonnet | keep | F1 confirmed (job9) | -| security-auditor | sonnet | keep | F1 confirmed (job9) | -| seo-analyzer, geo-analyzer, validator-analyzer | (inherit) | keep (inherit) | audit = reflection = session model; covered by the gate | -| code-cleaner | (inherit) | keep (inherit) | audit phase = reflection; fixes hand off to refactorer (sonnet) via CODE-CLEAN-SCOPE.md (job9 H1) | -| doc-syncer, onboarder, scaffolder, refactorer, interviewer, plugin-advisor | sonnet | keep | workers/executors | -| status-reporter | haiku | keep | mechanical collector | -| bugfixer, commit-changer | (inherit) | keep | inline-only playbooks — a pin would be inert | - -## 3. `/feat` re-architecture (partial supersede of BDR-050 — feat only) - -`skills/feat/SKILL.md` absorbs the reflection: analyze-before-plan, design -gate, MINI-PLAN, contract (`lib/contract-interview.md`) — all inline. Then -dispatches `Agent(subagent_type="feater")` (sonnet via pin) with: the -contract, the plan, the branch name, repo conventions. - -`agents/feater.md` is rewritten as a pure executor: implement the plan to the -letter, run project checks, commit (no attribution trailers), return a -structured summary. No user interaction inside feater (subagents cannot ask) — -every decision must be closed pre-dispatch. - -The verify-secure loop moves out of feater.md into the /feat main loop -(LRN-083 invariant: loop decisions live in the main loop): fresh verifier → -ECARTS → re-dispatch feater with the verdict deltas, bounded 3×; then the -security gate. Escalation paths unchanged. - -## 4. SDD execution pinned (ship-feature STEP 4, init-project STEP 8) - -One instruction line in each SKILL.md: every implementation subagent -dispatched under `superpowers:subagent-driven-development` MUST carry -`model: "sonnet"` in the Agent call. No fork of the superpowers skill — the -main loop emits the Agent calls and controls the params. - -## 5. client-handover conversion (inline-load → true dispatch) - -`skills/client-handover/SKILL.md`: collect params inline (URL, logo, options), -then `Agent(subagent_type="client-handover-writer")` — the sonnet pin becomes -effective. Human gates (per-axis threshold escalation, overrides) RELOCATE to -the main loop: the writer returns a structured `GATE NEEDED` status instead of -asking; the dispatcher asks the user and re-dispatches (or continues via -SendMessage) with the decision. `AskUserQuestion` is removed from the writer's -tools. - -OPEN VERIFY POINT: the writer's own nested dispatches (seo/harden re-runs as -general-purpose subagents) — verify at implementation what nested children -inherit (session model vs parent model). If they inherit the sonnet parent, -the re-run audits violate the principle → force the model explicitly in those -nested dispatches or lift them to the main loop. - -## 6. web-validate fixes → L1 applier - -STEP 3 stops applying fixes via inline Edit; dispatches `hotfixer` (sonnet) -with the fix bundle — same pattern as seo/geo (BDR-061 alignment). - -## 7. Memory / doc / tests - -- New BDR: model-routing principle (reflection inline big / executors sonnet / - blocking gate); partial supersede of BDR-050 (feat only); records F1 - (verifier/security stay sonnet) and the analyzer haiku→inherit change. -- README: agent-model table refresh. CHANGELOG Unreleased entry. -- Tests: flip-tests for `model-check.sh` (fable[1m] / opus / sonnet / garbage - fixtures); gate STOP proven on a small-model fixture (LRN-096); /feat smoke - on a throwaway repo (LRN-079): plan inline → dispatch carries sonnet → - verify loop decided in main loop; grep census: no executor dispatch without - an effective pin. - -## Out of scope / accepted deviations - -- `/doc` and `/commit-change` stay inline on the session model (judgment and - execution interleaved; converting them buys little). Revisit under quota - pressure. -- bugfix/hotfix fully inline (BDR-050 conserved). -- No per-agent "fable-else-opus" fallback exists in the harness — the session - model IS the fallback mechanism; the gate is its backstop. - -## Risks - -- Model strings in settings.json may change shape with CC updates → - model-check must return `unknown` (fail-visible), never guess. -- feater as a subagent loses main-conversation context → the plan becomes the - contract; weak plans cost verify-loop iterations. Mitigation: - contract-interview stays mandatory in /feat. -- Nested model inheritance under client-handover-writer unknown → §5 verify - point. From e75ea79ae6c7664eb3332f981f7f02d3fe8e51ad Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Mon, 20 Jul 2026 16:46:42 +0200 Subject: [PATCH 4/6] =?UTF-8?q?chore(tasks):=20reconcile=202026-07-20=20?= =?UTF-8?q?=E2=80=94=204=20stale=20claims=20corrected=20+=20pending-gates?= =?UTF-8?q?=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - seo/geo STATUS: H1+C1 were done (url-guard, sitemap verb) and the branch merged (92301fe) + shipped v1.2.0 — 'NEXT'/'nothing merged' lines stale - ctx7 + opus-pin sections: 'NO merge' notes stale (8ee7d19, 17fbe51 both shipped v1.2.0) - f1c9c474 transcript decision moot: auto-rotated (cleanupPeriodDays=7) - new open items: 2 unmerged branches + Makefile help-text fix --- .claude/tasks/TODO.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index a41d64b..172b677 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -1,5 +1,13 @@ # TODO +## 2026-07-20 — pending merge gates (reconcile) +- [ ] merge feature/profile-managed-externals → develop (BDR-079 profile + symmetry + /doc clean pass: README/USAGE/ARCHITECTURE.md) +- [ ] merge chore/purge-transient-docs → develop (docs/ transient purge + 655e364 + this reconcile) — reaches main at next release +- [ ] Makefile help text: profiles 5/10 listed (:57) + test glob missing + run-*.sh (:31) — 2-line hotfix (flagged by /doc audit) + ## 2026-07-20 — ctx7 coverage extension (feature/ctx7-coverage, BDR-078) Close the 4 gaps from the ctx7 coverage audit: /feat //bugfix + ad-hoc coding never consult ctx7; fast-libs list hardcoded 3×; zero deterministic backstop. @@ -18,7 +26,7 @@ never consult ctx7; fast-libs list hardcoded 3×; zero deterministic backstop. - [x] `lib/tests/fast-libs.test.sh` (lib verbs + hook fire/sentinel/quiet) — 11/0, auto-discovered by the make test glob. - [x] Gate: shellcheck + make test green (review-guards 5/0). BDR-078 + - journal + CHANGELOG done. Committed on branch, NO merge (human gate). + journal + CHANGELOG done. Merged 8ee7d19, shipped v1.2.0. ## 2026-07-19 — Opus-pin dispatched judgment agents (branch feature/opus-pin-audit-agents) @@ -45,8 +53,8 @@ on audits). User approved: opus for judgment agents, drop local opus pin. - [x] `.claude/settings.local.json` — drop `"model": "opus-4-8[1m]"` (local, gitignored; Fable default from settings.json applies). - [x] Tests: model-routing + loops-light + shellcheck + make test. -- [x] Memory: BDR-076 append + journal line. Commit (feat + chore), - NO merge (human gate). +- [x] Memory: BDR-076 append + journal line. Commit (feat + chore); + merged 17fbe51, shipped v1.2.0 (reconcile 2026-07-20). ## 2026-07-17 — STATUS seo/geo parity (branch bugfix/seo-geo-integrity — MERGED to develop, 92301fe; "UNMERGED" note was stale, corrected 2026-07-19 W0) PHASE 1 — integrity: **DONE 7/7**. I3 8b0c98c · I1 57c67f2 · I2 4ea2fb8 · @@ -54,8 +62,8 @@ I5 64f175f · I4 e70e1d6 · I6 9da1dec · I8 acd452b. Plus 9cd7b51 (A1+A2, two process anomalies surfaced by dogfooding /harden at zenquality.fr from the wrong CWD). PHASE 2 — free wins: W3 fe93b79 · W1 a6d423b · **W2 DEFERRED** (see below). -NEXT: H1 (SSRF/injection guard) → C1 (sitemap crawl). Human merge gate: all -10 commits await review; nothing merged to develop. +H1 DONE (url-guard 7d6aa09) · C1 DONE (sitemap verb, C1a/b/c). Branch MERGED +to develop (92301fe), shipped in v1.2.0 (reconcile 2026-07-20). ### Plan corrections made while executing (the plan was wrong 4×) - **B3 KILLED** — GSC Links API does not exist. Verified against the API @@ -478,6 +486,8 @@ manipuler une valeur de secret — edits sur les mécanismes seulement. Transcript `f1c9c474-...jsonl` (generic-api-key, 8) — PAS choisi par l'utilisateur parmi les options (auto-inspect / TODO / rm) → **laissé intact, à trancher** ; ni lu ni caractérisé (règle job7). + [sans objet : transcript auto-roté (cleanupPeriodDays=7), absent + du disque — reconcile 2026-07-20] - [x] **NOUVEAU (bruit, pas un item D)** : transcript de CETTE session (`4b5c02a9-...jsonl`, aws-access-token, 2) = mes propres fixtures synthétiques de test (AKIA random) loggées dans mon propre From 10589d484bf1a487fec7f749c07a9b968f97795c Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Mon, 20 Jul 2026 19:25:07 +0200 Subject: [PATCH 5/6] =?UTF-8?q?docs:=20README=20polish=20=E2=80=94=20real?= =?UTF-8?q?=20clone=20URL=20+=20make=20targets,=20magic=20example=20simpli?= =?UTF-8?q?fied,=20SEO=20env=20vars=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fresh-install block: clone URL → github.com/bchanot/claude, bash install.sh/doctor.sh → make install / make doctor (user pass) - magic MCP example: placeholder key line instead of WRONG/RIGHT contrast - new subsection: SEO data layer needs GOOGLE_OAUTH_CLIENT_ID/SECRET + CRUX_API_KEY in ~/.claude/.env (GCP steps, make seo-connect, graceful degradation) — mirrors .env.example --- README.md | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 62e052b..dfe1061 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,14 @@ children are dispatched `model:"fable"` (they carry reflection). ```bash # 1. Clone with submodules -git clone --recurse-submodules git@github.com:youruser/claude-config.git -cd claude-config +git clone --recurse-submodules https://github.com/bchanot/claude +cd claude # 2. Bootstrap (CLI + auth + symlinks + plugins) -bash install.sh +make install # 3. Verify setup -bash doctor.sh +make doctor # 4. Restart Claude Code — plugins load automatically ``` @@ -221,10 +221,8 @@ in `env`, `command`, `args`, `url`, and `headers` — for both project (`.mcp.js and user (`~/.claude.json`) scope. Use that instead of a literal value: ```bash -# WRONG — plaintext key lands in ~/.claude.json: -claude mcp add magic --scope user --env API_KEY="$MAGIC_API_KEY" -- npx -y @21st-dev/magic@latest - -# RIGHT — single-quoted so bash doesn't expand it; Claude Code expands it at +MAGIC_API_KEY= +# single-quoted so bash doesn't expand it; Claude Code expands it at # launch, reading the var from its own process environment: claude mcp add magic --scope user --env 'API_KEY=${MAGIC_API_KEY}' -- npx -y @21st-dev/magic@latest ``` @@ -242,6 +240,26 @@ There is no `claude mcp add` flag that writes the reference form for you — the `${VAR}` syntax has to be typed by hand (or via a wrapper script), same as above. +### SEO data layer (`/seo` FULL) — Google OAuth + CrUX keys + +The same `~/.claude/.env` also feeds `lib/seo-data`, which pulls real Google +Search Console and Chrome UX Report data into `/seo` FULL audits. Add these +three vars (template with the GCP console steps in `.env.example`): + +```bash +# OAuth Desktop client — GCP console → APIs & Services → Credentials → +# OAuth client (Desktop). Consent scope: webmasters.readonly only. +GOOGLE_OAUTH_CLIENT_ID= +GOOGLE_OAUTH_CLIENT_SECRET= +# CrUX + PageSpeed API key — GCP console → Credentials → API key, +# restricted to those two APIs. https://developer.chrome.com/docs/crux/api +CRUX_API_KEY= +``` + +Then run the one-time consent flow: `make seo-connect` (per-label token +store, multi-site safe). Missing credentials never break an audit — `/seo` +degrades gracefully to anonymous PageSpeed lab data. + ### magic MCP (`@21st-dev/magic`) — known callback-injection risk `21st_magic_component_builder` opens an **unauthenticated** local callback From 711eacd9001fe2fd4723f1897dcb88a58a0e0603 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Mon, 20 Jul 2026 19:39:35 +0200 Subject: [PATCH 6/6] =?UTF-8?q?chore(release):=201.3.0=20=E2=80=94=20versi?= =?UTF-8?q?on.txt=20+=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++++++ version.txt | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a528426..46da1fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +## [1.3.0] — 2026-07-20 + ### Added - **Profile switches now toggle external packs and MCPs both ways (BDR-079)** — `profile.sh set` was asymmetric: it enabled what a profile listed (including gstack skills on demand when the whole pack is off, and the `magic` MCP) but never disabled the managed leftovers, so `set backend` after design work kept emil-design-eng / frontend-design / design-motion-principles / impeccable active and magic registered. `set` now trims managed externals (`MANAGED_EXTERNALS`) and managed MCPs (`MANAGED_MCPS`, delegated to `toggle-external.sh`) not listed in the profile — same allowlist doctrine as `MANAGED_PLUGINS`, nothing outside the allowlists is ever auto-touched (darwin-skill stays manual). Also: an `external` entry whose symlink never existed is now created from `skills-external/` (mirroring toggle-external's from-source path), and the stale "NOT toggled automatically" note in `profile.sh` usage was corrected. Covered by a hermetic 16-check test (`lib/tests/profile-set-managed.test.sh`) with a fake `claude` shim. +### Changed +- **README restructured for public readers** — the project-layout tree and architecture principles moved verbatim to a new `ARCHITECTURE.md` (README links it); bare decision-registry citations (`BDR-XXX`) stripped from README prose, meaning preserved; `/profile` documentation corrected in three places to the real 10-profile set (web / seo / web-full / full / backend / design / dev / qa / audit / minimal); fresh-install block now uses the real clone URL + `make install` / `make doctor`; new "SEO data layer" subsection documents the `GOOGLE_OAUTH_CLIENT_ID` / `GOOGLE_OAUTH_CLIENT_SECRET` / `CRUX_API_KEY` vars in `~/.claude/.env` (mirrors `.env.example`, `make seo-connect` one-time consent). + +### Fixed +- **Transient planning artifacts purged from the repo** — `docs/plans`, `docs/specs`, `docs/superpowers/{plans,specs}` (deploy-skill 2026-06-27, model-routing 2026-07-15) were run-time pipeline artifacts that should have been deleted in their chantiers' post-merge cleanup and slipped through (one pair predates the lifecycle rule, one missed the purge step of a 6-wave chantier). Git history at the feature commits remains their archive; `docs/` no longer exists. + ## [1.2.1] — 2026-07-20 ### Fixed diff --git a/version.txt b/version.txt index 6085e94..f0bb29e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.1 +1.3.0