From 74528a691018e6cdf73eab842100579611a2b9f6 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Sun, 19 Jul 2026 20:57:10 +0200 Subject: [PATCH] =?UTF-8?q?feat(agents):=20W2/S1=20plugin=20split=20?= =?UTF-8?q?=E2=80=94=20probe=20(sonnet)=20+=20advisor=20reasoner=20(opus)?= =?UTF-8?q?=20+=20plugin-gate=20include=20(BDR-077)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit plugin-advisor keeps its name, becomes the opus REASONER: PHASE 1 bash extracted to new plugin-probe (sonnet, facts-only PROBE REPORT), PHASE 4 apply + checkpoint hoisted to new lib/plugin-gate.md (main-loop include, doc-commit.md x6 pattern). Fail-closed: advisor ERRORs on missing report. 4 consumers rewired (plugin-check, onboard, init-project, ship-feature). In-wave planted-input smoke PASSED: probe report complete w/ fallbacks; advisor consumed every planted field (monorepo per-package note, fast-libs ctx7 reco) with zero re-detection; ERROR verdict on absent report. Census §13 (81 pass). Note: new subagent_type registers next session — resolution re-check before wave merge. --- agents/plugin-advisor.md | 143 ++++++-------------------------- agents/plugin-probe.md | 90 ++++++++++++++++++++ lib/plugin-gate.md | 90 ++++++++++++++++++++ lib/tests/model-routing.test.sh | 16 ++++ skills/init-project/SKILL.md | 3 +- skills/onboard/SKILL.md | 2 +- skills/plugin-check/SKILL.md | 14 +++- skills/ship-feature/SKILL.md | 3 +- 8 files changed, 236 insertions(+), 125 deletions(-) create mode 100644 agents/plugin-probe.md create mode 100644 lib/plugin-gate.md diff --git a/agents/plugin-advisor.md b/agents/plugin-advisor.md index 35998a6..106c45c 100644 --- a/agents/plugin-advisor.md +++ b/agents/plugin-advisor.md @@ -1,71 +1,35 @@ --- name: plugin-advisor -description: Plugin-fit checker — dispatched by /plugin-check and orchestrator gates (init-project, ship-feature). Recommends enable/disable. -tools: Read, Bash, Glob, Grep -model: sonnet +description: Plugin-fit REASONER — dispatched by lib/plugin-gate.md with a PROBE REPORT (from plugin-probe). Classifies signals, scores complexity, recommends enable/disable via the decision table + compatibility matrix. Report-only. +tools: Read, Glob, Grep +model: opus --- # PLUGIN ADVISOR ## ROLE -Detect active plugins and project signals. Recommend enable/disable. Apply compatibility matrix. Block or warn as needed. +Reason over the PROBE REPORT + request. Classify signals, score complexity, +recommend enable/disable, apply the compatibility matrix. Block or warn. +Detection is NOT your job (plugin-probe did it); applying is NOT your job +(the dispatcher's lib/plugin-gate.md apply gate does it). --- -## PHASE 1 — DETECT +## INPUT — PROBE REPORT (ground truth, from plugin-probe) -```bash -# Claude Code plugins -claude plugin list 2>/dev/null || echo "plugin-list-unavailable" - -# External (non-marketplace) tools status — gstack, emil-design-eng, -# darwin-skill. Managed by lib/toggle-external.sh since -# `claude plugin enable|disable` does not apply to them. -bash "$HOME/.claude/lib/toggle-external.sh" list 2>/dev/null || echo "toggle-external-unavailable" - -# Active skill profile — design / dev / qa / audit / minimal / custom. -# Profiles partition gstack + personal skills by purpose. See -# lib/profile.sh and lib/profiles/*.profile. -bash "$HOME/.claude/lib/profile.sh" current 2>/dev/null || echo "profile-unavailable" - -# Context7 CLI -command -v ctx7 &>/dev/null && ctx7 --version 2>/dev/null | head -1 || echo "ctx7-not-installed" - -# Standalone CLIs -command -v gsd &>/dev/null && gsd --version 2>/dev/null | head -1 || echo "gsd-not-installed" -command -v rtk &>/dev/null && rtk --version 2>/dev/null | head -1 || echo "rtk-not-installed" - -# Project signals (run from project root) -ls package.json pyproject.toml Cargo.toml go.mod 2>/dev/null | head -5 -grep -rl "next\|react\|vue\|prisma\|supabase" package.json 2>/dev/null | head -3 || true -find . -name "*.tsx" -o -name "*.jsx" 2>/dev/null | head -3 | wc -l -find . -name "docker-compose*" -o -name "Dockerfile" 2>/dev/null | head -3 | wc -l - -# Animation lib status (motion / motion-v) — read-only detection -if [ -f "$HOME/.claude/lib/animation-lib-check.sh" ]; then - source "$HOME/.claude/lib/animation-lib-check.sh" - detect_anim_eligibility # outputs '||' - is_anim_lib_installed || echo "anim-lib-not-installed" -fi -# Monorepo detection (current dir + parent dirs for sub-package context) -ls apps/ packages/ services/ workspaces/ 2>/dev/null | head -5 -ls pnpm-workspace.yaml turbo.json nx.json lerna.json 2>/dev/null -# Upstream check: detect if current dir is itself a package inside a monorepo -ls ../pnpm-workspace.yaml ../turbo.json ../nx.json ../../turbo.json ../../pnpm-workspace.yaml 2>/dev/null | head -3 -# Embedded/firmware detection via filesystem -ls CMakeLists.txt platformio.ini 2>/dev/null -ls *.ld *.lds linker*.ld 2>/dev/null | head -3 # linker scripts = bare-metal -ls Makefile 2>/dev/null -# Presence of .c files used only when combined with Makefile AND no Node/Rust/Go manifest -ls src/*.c 2>/dev/null | head -3 -ls package.json Cargo.toml go.mod pubspec.yaml setup.py pyproject.toml 2>/dev/null | head -1 # counterindicators (ecosystem present = not bare embedded) -``` +The dispatcher passes `REQUEST` (the project description, verbatim) and the +full `PROBE REPORT` (fields: PLUGINS, EXTERNAL, PROFILE, CLIS, MANIFESTS, +FRAMEWORK-DEPS, TSX-JSX-COUNT, DOCKER-COUNT, ANIM, MONOREPO, EMBEDDED, +CHECKPOINT). Treat it as ground truth — never re-detect, never invent a +field. PROBE REPORT missing or a field absent → emit +`PLUGIN CHECK — VERDICT: ERROR(probe report missing/invalid: )` and +STOP. Fail closed: no recommendations over invented detection. --- -## PHASE 2 — ANALYZE $ARGUMENTS +## PHASE 2 — ANALYZE -Detect signals from the project description and filesystem scan: +Detect signals from REQUEST + the PROBE REPORT fields: | Signal | How to detect | |---|---| @@ -146,70 +110,11 @@ ACTION REQUIRED? YES / NO > packages itself — it just states the status. Installation happens in > `/init-project` STEP 5e (auto) or `/onboard` STEP 2.5 (opt-in). -## PHASE 4 — AUTO-ACTIVATION (when called from /init-project or /ship-feature) - -After presenting RECOMMENDATIONS, if any plugin has ⚡ ENABLE status: -1. List the changes to apply: - ``` - PROPOSED CHANGES: - ⚡ Enable ui-ux-pro-max (frontend detected, complexity 65%) - ⚡ Pre-fetch ctx7 docs for next.js, prisma - Apply these changes? (yes / no / customize) - ``` -2. On "yes" → apply changes (rename .disabled dirs, update MCP config). -3. On "customize" → user picks which to apply. -4. On "no" → proceed with current config. - -**Never auto-activate without showing the list and getting confirmation.** - -### Rollback on partial failure - -Toggle commands occasionally fail mid-batch (rename collision, permission, MCP -restart hang). Track each toggle and roll back the partial set rather than -leave a half-applied configuration: - -```bash -applied=() -for change in "${PROPOSED_CHANGES[@]}"; do - if bash "$HOME/.claude/lib/toggle-external.sh" enable "$change"; then - applied+=("$change") - else - echo "❌ failed to enable $change — rolling back ${#applied[@]} prior change(s)" - for prior in "${applied[@]}"; do - bash "$HOME/.claude/lib/toggle-external.sh" disable "$prior" \ - || echo "⚠️ rollback of $prior also failed — manual cleanup required: see ~/.claude/plugins/cache" - done - exit 1 - fi -done -``` - -Surface to the user: - -``` -✅ Applied N change(s). -``` - -Or, on failure: - -``` -⚠️ Toggle failed at change . Rolled back the N prior change(s). - To inspect manually: ls ~/.claude/plugins/cache; bash ~/.claude/lib/toggle-external.sh list - Re-run /plugin-check after fixing the underlying cause (e.g. permissions). -``` - -### Pre-recommendation validation checkpoint - -Between PHASE 1 (DETECT) and PHASE 2 (ANALYZE), validate the detection -findings before producing recommendations: - -- `toggle-external.sh list` returned non-empty AND each listed plugin's - directory exists in `~/.claude/plugins/cache` or `~/.agents/skills/`. -- At least one project signal was detected (else: print `"⚠️ No project - signals detected — recommendations will be conservative."` and continue). -- If `toggle-external.sh` is missing or unexecutable: print `"⚠️ toggle script - unavailable — recommendations will be advisory only, no auto-activation."` - and skip PHASE 4 entirely. +> **Apply, confirmation, and rollback are the DISPATCHER'S job** — +> `lib/plugin-gate.md` steps 4-5 (main loop: present, ACTION-REQUIRED stop, +> PROPOSED-CHANGES confirmation, toggle + rollback). This agent only +> recommends and emits the EXACT toggle commands. It never applies, never +> asks the user (it cannot — it is dispatched). --- @@ -418,4 +323,6 @@ or by applying a profile that lists it (e.g. `apply web` to restore → Free higher rate limits: `ctx7 login` (OAuth) or API key from context7.com/dashboard → Type "force" to proceed without context7 (not recommended for fast-evolving libs) -Never modify files. If action required → stop and wait. If not → say "proceed". +Never modify files. Never ask the user. Report-only: the PLUGIN CHECK block +is your entire output; the dispatcher's gate (lib/plugin-gate.md) owns the +stop/proceed decision and every state change. diff --git a/agents/plugin-probe.md b/agents/plugin-probe.md new file mode 100644 index 0000000..16f646d --- /dev/null +++ b/agents/plugin-probe.md @@ -0,0 +1,90 @@ +--- +name: plugin-probe +description: Mechanical detection probe — dispatched by lib/plugin-gate.md BEFORE the plugin-advisor reasoner. Runs the CLI/filesystem probes, reports raw facts as a PROBE REPORT. No analysis, no recommendations. +tools: Bash, Read, Glob, Grep +model: sonnet +--- + +# PLUGIN PROBE + +## ROLE +Collect the raw plugin/project facts the plugin-advisor reasons over. +Facts only — no signals, no recommendations, no complexity scoring. + +## PROBES (run all; a failing probe reports its fallback string, never aborts) + +```bash +# Claude Code plugins +claude plugin list 2>/dev/null || echo "plugin-list-unavailable" + +# External (non-marketplace) tools status — gstack, emil-design-eng, +# darwin-skill. Managed by lib/toggle-external.sh since +# `claude plugin enable|disable` does not apply to them. +bash "$HOME/.claude/lib/toggle-external.sh" list 2>/dev/null || echo "toggle-external-unavailable" + +# Active skill profile — design / dev / qa / audit / minimal / custom. +bash "$HOME/.claude/lib/profile.sh" current 2>/dev/null || echo "profile-unavailable" + +# Context7 CLI +command -v ctx7 &>/dev/null && ctx7 --version 2>/dev/null | head -1 || echo "ctx7-not-installed" + +# Standalone CLIs +command -v gsd &>/dev/null && gsd --version 2>/dev/null | head -1 || echo "gsd-not-installed" +command -v rtk &>/dev/null && rtk --version 2>/dev/null | head -1 || echo "rtk-not-installed" + +# Project signals (run from project root) +ls package.json pyproject.toml Cargo.toml go.mod 2>/dev/null | head -5 +grep -rl "next\|react\|vue\|prisma\|supabase" package.json 2>/dev/null | head -3 || true +find . -name "*.tsx" -o -name "*.jsx" 2>/dev/null | head -3 | wc -l +find . -name "docker-compose*" -o -name "Dockerfile" 2>/dev/null | head -3 | wc -l + +# Animation lib status (motion / motion-v) — read-only detection +if [ -f "$HOME/.claude/lib/animation-lib-check.sh" ]; then + source "$HOME/.claude/lib/animation-lib-check.sh" + detect_anim_eligibility # outputs '||' + is_anim_lib_installed || echo "anim-lib-not-installed" +fi +# Monorepo detection (current dir + parent dirs for sub-package context) +ls apps/ packages/ services/ workspaces/ 2>/dev/null | head -5 +ls pnpm-workspace.yaml turbo.json nx.json lerna.json 2>/dev/null +# Upstream check: detect if current dir is itself a package inside a monorepo +ls ../pnpm-workspace.yaml ../turbo.json ../nx.json ../../turbo.json ../../pnpm-workspace.yaml 2>/dev/null | head -3 +# Embedded/firmware detection via filesystem +ls CMakeLists.txt platformio.ini 2>/dev/null +ls *.ld *.lds linker*.ld 2>/dev/null | head -3 # linker scripts = bare-metal +ls Makefile 2>/dev/null +# Presence of .c files used only when combined with Makefile AND no Node/Rust/Go manifest +ls src/*.c 2>/dev/null | head -3 +ls package.json Cargo.toml go.mod pubspec.yaml setup.py pyproject.toml 2>/dev/null | head -1 # counterindicators (ecosystem present = not bare embedded) + +# Checkpoint inputs (consumed by lib/plugin-gate.md's validation checkpoint) +[ -x "$HOME/.claude/lib/toggle-external.sh" ] && echo "toggle-script: executable" || echo "toggle-script: UNAVAILABLE" +ls "$HOME/.claude/plugins/cache" 2>/dev/null | head -10 +ls "$HOME/.agents/skills" 2>/dev/null | head -10 +``` + +## OUTPUT — PROBE REPORT (every field present; unavailable = the probe's fallback string, never invented) + +``` +PROBE REPORT +PLUGINS : +EXTERNAL : +PROFILE : +CLIS : ctx7= gsd= rtk= +MANIFESTS : +FRAMEWORK-DEPS: +TSX-JSX-COUNT : +DOCKER-COUNT : +ANIM : eligibility= installed= +MONOREPO : dirs= configs= parent= +EMBEDDED : cmake-pio= linker= makefile= src-c= ecosystem= +CHECKPOINT : toggle-script= plugin-dirs= +``` + +## RULES +- Facts only. No signal classification, no complexity score, no + recommendations — that is the plugin-advisor's job. +- Never modify files. Never install anything. Never ask the user + (you cannot — report facts instead). +- A probe that errors reports its fallback string; the report is emitted + with EVERY field line present regardless. diff --git a/lib/plugin-gate.md b/lib/plugin-gate.md new file mode 100644 index 0000000..1f228fd --- /dev/null +++ b/lib/plugin-gate.md @@ -0,0 +1,90 @@ +# Plugin gate — shared consumer include (plugin-check, onboard, init-project, ship-feature STEP 0) + +Runs in the CONSUMER'S MAIN LOOP. The detection and the reasoning are +dispatched (BDR-077 tiers); the validation checkpoint, the report +presentation, and the apply gate live HERE — a dispatched agent can neither +ask the user nor safely mutate plugin state. + +## 1. PROBE (dispatch — sonnet) + +``` +Agent(subagent_type="plugin-probe", description="plugin gate — probe", + prompt="Run your probes from . Emit the PROBE REPORT.") +``` + +## 2. VALIDATION CHECKPOINT (main loop — between probe and reasoner) + +Validate the PROBE REPORT before any reasoning: +- `EXTERNAL` non-empty AND each listed plugin's directory appears under + `CHECKPOINT plugin-dirs`. +- At least one project signal present (MANIFESTS / FRAMEWORK-DEPS / + TSX-JSX-COUNT > 0 / DOCKER-COUNT > 0 / EMBEDDED hits). Else print + `⚠️ No project signals detected — recommendations will be conservative.` + and continue. +- `CHECKPOINT toggle-script=UNAVAILABLE` → print `⚠️ toggle script + unavailable — recommendations will be advisory only, no auto-activation.` + and SKIP step 5 (apply) entirely. +- PROBE REPORT missing/unparsable → retry the probe ONCE fresh; a 2nd + failure → STOP and surface (never reason over invented detection). + +## 3. REASON (dispatch — opus) + +``` +Agent(subagent_type="plugin-advisor", description="plugin gate — reason", + prompt=""" +REQUEST: +PROBE REPORT (ground truth — do not re-detect): + +""") +``` + +## 4. PRESENT + BLOCKING GATE (main loop) + +Show the returned PLUGIN CHECK block. +- `ACTION REQUIRED? YES` → offer: A) fix plugins B) type "force". STOP until + answered. +- OK → print `✅ Plugin check passed — [active plugins] — complexity: %`. + +## 5. APPLY GATE (main loop — only when the flow auto-activates) + +If any plugin has ⚡ ENABLE status: +1. List the changes: + ``` + PROPOSED CHANGES: + ⚡ Enable ui-ux-pro-max (frontend detected, complexity 65%) + ⚡ Pre-fetch ctx7 docs for next.js, prisma + Apply these changes? (yes / no / customize) + ``` +2. "yes" → apply via the exact commands the advisor emitted. "customize" → + user picks. "no" → proceed with current config. + +**Never auto-activate without showing the list and getting confirmation.** + +### Rollback on partial failure + +Track each toggle; roll back the partial set rather than leave a +half-applied configuration: + +```bash +applied=() +for change in "${PROPOSED_CHANGES[@]}"; do + if bash "$HOME/.claude/lib/toggle-external.sh" enable "$change"; then + applied+=("$change") + else + echo "❌ failed to enable $change — rolling back ${#applied[@]} prior change(s)" + for prior in "${applied[@]}"; do + bash "$HOME/.claude/lib/toggle-external.sh" disable "$prior" \ + || echo "⚠️ rollback of $prior also failed — manual cleanup required: see ~/.claude/plugins/cache" + done + exit 1 + fi +done +``` + +Surface: `✅ Applied N change(s).` — or on failure: + +``` +⚠️ Toggle failed at change . Rolled back the N prior change(s). + To inspect manually: ls ~/.claude/plugins/cache; bash ~/.claude/lib/toggle-external.sh list + Re-run /plugin-check after fixing the underlying cause (e.g. permissions). +``` diff --git a/lib/tests/model-routing.test.sh b/lib/tests/model-routing.test.sh index 96b3ba6..838e0ea 100755 --- a/lib/tests/model-routing.test.sh +++ b/lib/tests/model-routing.test.sh @@ -81,6 +81,22 @@ has "skills/ship-feature/SKILL.md" 'model: "opus"' has "skills/init-project/SKILL.md" 'model: "opus"' has "lib/model-gate.md" 'model: "fable"' lacks "lib/model-gate.md" 'model: "sonnet" in the Agent call' +# 13) BDR-077 W2 — plugin split: probe (sonnet, facts only) + advisor +# reasoner (opus, PROBE REPORT is ground truth, fail-closed); gate +# include owns checkpoint + apply; 4 consumers run the include, none +# inline-loads the advisor anymore +has "agents/plugin-probe.md" 'model: sonnet' +lacks "agents/plugin-probe.md" 'AskUserQuestion' +has "agents/plugin-advisor.md" 'model: opus' +has "agents/plugin-advisor.md" 'PROBE REPORT' +lacks "agents/plugin-advisor.md" 'PHASE 1 — DETECT' +has "lib/plugin-gate.md" 'subagent_type="plugin-probe"' +has "lib/plugin-gate.md" 'subagent_type="plugin-advisor"' +for s in plugin-check onboard init-project ship-feature; do + has "skills/$s/SKILL.md" 'lib/plugin-gate.md' + # shellcheck disable=SC2016 # literal $HOME wanted: matching the exact inline-load string + lacks "skills/$s/SKILL.md" 'Load `$HOME/.claude/agents/plugin-advisor.md`' +done printf 'model-routing census: %d pass, %d fail\n' "$pass" "$fail" [ "$fail" -eq 0 ] diff --git a/skills/init-project/SKILL.md b/skills/init-project/SKILL.md index fbab873..a413e8a 100644 --- a/skills/init-project/SKILL.md +++ b/skills/init-project/SKILL.md @@ -36,7 +36,8 @@ so the user does not assume Claude has hung. --- ## STEP 0 — PLUGIN CHECK + AUTO-ACTIVATE -Load `$HOME/.claude/agents/plugin-advisor.md`. Feed request. +Run `$HOME/.claude/lib/plugin-gate.md`. Feed request (dispatch plugin-probe → +checkpoint → dispatch plugin-advisor; gates stay in this loop — BDR-077). - ACTION REQUIRED → show RECOMMENDATIONS block, offer: A) fix plugins B) type "force". STOP. - PROPOSED CHANGES exist → show list, ask "Apply? (yes / no / customize)". Apply on confirm. - OK → `✅ Plugin check passed — [active plugins] — complexity: %`, continue. diff --git a/skills/onboard/SKILL.md b/skills/onboard/SKILL.md index c6f3ed6..66f7a76 100644 --- a/skills/onboard/SKILL.md +++ b/skills/onboard/SKILL.md @@ -21,7 +21,7 @@ $ARGUMENTS ## STEP 0 — PLUGIN CHECK + AUTO-ACTIVATE -Load `$HOME/.claude/agents/plugin-advisor.md` with hint "onboarding existing project + $ARGUMENTS". +Run `$HOME/.claude/lib/plugin-gate.md` with hint "onboarding existing project + $ARGUMENTS" (dispatch plugin-probe → checkpoint → dispatch plugin-advisor → gates in this loop, BDR-077). - ACTION REQUIRED → show RECOMMENDATIONS block, offer: A) apply recos B) type "force". STOP. - PROPOSED CHANGES exist → show list, ask "Apply? (yes / no / customize)". Apply on confirm. diff --git a/skills/plugin-check/SKILL.md b/skills/plugin-check/SKILL.md index b50f928..d81fc6a 100644 --- a/skills/plugin-check/SKILL.md +++ b/skills/plugin-check/SKILL.md @@ -2,13 +2,19 @@ name: plugin-check description: 'Audit active plugins vs project needs. Read-only advisory recommending enable/disable. Triggers: "plugin-check", "quels plugins".' argument-hint: '[ex: "React + FastAPI" or "Rust CLI, no frontend"]' -allowed-tools: Read, Bash, Glob, Grep +allowed-tools: Read, Bash, Glob, Grep, Agent --- -Load and follow strictly: `$HOME/.claude/agents/plugin-advisor.md`. +Run `$HOME/.claude/lib/plugin-gate.md` on the context below — dispatch +plugin-probe (sonnet) → validation checkpoint → dispatch plugin-advisor +(opus) → present the PLUGIN CHECK block (BDR-077: detection and reasoning +dispatched, gates in this loop). -Analyze active plugins + context below, produce PLUGIN ADVISOR REPORT. +/plugin-check is READ-ONLY advisory: SKIP the gate's step 5 (apply) — show +the advisor's exact toggle commands for the user instead. Never write; user +toggles via `claude plugin enable/disable`. -If `$HOME/.claude/agents/plugin-advisor.md` unreachable: emit `Plugin advisor agent missing.` and STOP. Never write — user toggles via `claude plugin enable/disable`. +If `$HOME/.claude/lib/plugin-gate.md` unreachable: emit `Plugin gate include +missing.` and STOP. $ARGUMENTS diff --git a/skills/ship-feature/SKILL.md b/skills/ship-feature/SKILL.md index 907d581..8ded6d4 100644 --- a/skills/ship-feature/SKILL.md +++ b/skills/ship-feature/SKILL.md @@ -20,7 +20,8 @@ $ARGUMENTS --- ## STEP 0 — PLUGIN CHECK + AUTO-ACTIVATE -Load `$HOME/.claude/agents/plugin-advisor.md`. Feed request. +Run `$HOME/.claude/lib/plugin-gate.md`. Feed request (dispatch plugin-probe → +checkpoint → dispatch plugin-advisor; gates stay in this loop — BDR-077). - ACTION REQUIRED → show RECOMMENDATIONS block, offer: A) fix plugins B) type "force". STOP. - PROPOSED CHANGES exist → show list, ask "Apply? (yes / no / customize)". Apply on confirm. - OK → `✅ Plugin check passed — [active plugins] — complexity: %`, continue.