From 304a02d70af0b4f75812369c15293d18e86f8e18 Mon Sep 17 00:00:00 2001 From: bchanot Date: Sun, 11 Oct 2026 11:57:56 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20README/USAGE=20first-use=20dialog,=20ro?= =?UTF-8?q?uting.json=20source=20+=20config=20layers,=20ARCHITECTURE,=20CH?= =?UTF-8?q?ANGELOG=20Unreleased=20=E2=80=94=20feat=20model-router=20wave?= =?UTF-8?q?=203-A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ARCHITECTURE.md | 2 +- CHANGELOG.md | 5 +++-- README.md | 17 +++++++++++------ USAGE.md | 10 ++++++---- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3a323e4..6fb3821 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -36,6 +36,6 @@ claude-config/ - `skills/` = entry points you invoke via `/skill-name` - `agents/` = execution units called by skills (never invoked directly by user) -- `mods/` = Claude Code mods (function-hooks plugins); each loads through the tracked symlink `skills/` as `@skills-dir`, live at the next session +- `mods/` = Claude Code mods (function-hooks plugins); each loads through the tracked symlink `skills/` as `@skills-dir`, live at the next session; `mods/model-router/routing.json` (tracked) holds the phase table, every skill and agent row and the first-use decisions - `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. Proposed only from 200 tracked code files: the session-start banner informs, the user decides; nothing builds a graph without that go. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec6fde..6225592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and this project ## [Unreleased] ### Added -- **model-router mod**: `mods/model-router/`, a Claude Code mod (function-hooks plugin), routes every repo skill and agent from phase rows (`plan`, `reflect`, `orchestrate`, `escalate`, `judge`, `implement`, `write`, `verify`, `explore`, `apply`, `mechanical`; built-ins Explore on sonnet/medium, Plan on opus/xhigh). A typed skill routes the main loop to its row, and a best-tier row holds across turns in a run slot until `/route clear`, `/route off`, a user `/model` or a typed skill on a non-best row. Agents get their row's model at spawn (within the tier, upward only) and its effort on every step; explicit Agent params win. Orchestrators declare their phases through the `mcp__model-router__route` tool. `ultrathink` in a prompt sets the turn's minimum effort and `/route effort=max` holds until `/route clear`; the built-in `/effort` is not a lever inside a run. `/route show` names the run slot (`main: run `), and a `null` row in the override drops a default row. The model gets a `route` tool and the user a `/route` command (`show|clear|off|on|reload||model= effort=|switch on|off|verbose on|off`). Optional per-machine config `~/.claude/model-router.json`, where `"enabled": false` turns it off on that machine. The spinner suffix and the status line show the route in force. It loads in every session through the tracked symlink `skills/model-router` (`model-router@skills-dir`). `make doctor` gains a Mods section; suite `make test suite=lib/tests/mods.test.sh`. Known limits: the main loop switches model only with `mainModelSwitch` on (default off, one cold-cache step per switch into another model), and the hooks send full model ids, so the `models` table has to follow new versions. +- **model-router mod**: `mods/model-router/`, a Claude Code mod (function-hooks plugin), routes every repo skill and agent from phase rows (`plan`, `reflect`, `orchestrate`, `escalate`, `judge`, `implement`, `write`, `verify`, `explore`, `apply`, `mechanical`; built-ins Explore on sonnet/medium, Plan on opus/xhigh). A typed skill routes the main loop to its row, and a best-tier row holds across turns in a run slot until `/route clear`, `/route off`, a user `/model` or a typed skill on a non-best row. Agents get their row's model at spawn (within the tier, upward only) and its effort on every step; explicit Agent params win. Orchestrators declare their phases through the `mcp__model-router__route` tool. `ultrathink` in a prompt sets the turn's minimum effort and `/route effort=max` holds until `/route clear`; the built-in `/effort` is not a lever inside a run. `/route show` names the run slot (`main: run `), and a `null` row in the override drops a default row. The model gets a `route` tool and the user a `/route` command (`show|clear|off|on|reload|pending|ask on|off||model= effort=|switch on|off|verbose on|off`). Optional per-machine config `~/.claude/model-router.json`, where `"enabled": false` turns it off on that machine. The spinner suffix and the status line show the route in force. It loads in every session through the tracked symlink `skills/model-router` (`model-router@skills-dir`). `make doctor` gains a Mods section; suite `make test suite=lib/tests/mods.test.sh`. Known limits: the main loop switches model only with `mainModelSwitch` on (default off, one cold-cache step per switch into another model), and the hooks send full model ids, so the `models` table has to follow new versions. +- **model-router first-use confirmation**: `mods/model-router/routing.json` (tracked) is the single source of the phase table and of every skill and agent row, and keeps the decisions: `confirmed`, `changed` (`from`/`to`), `projects` exceptions keyed by the origin remote reduced to `host/path` (credentials and local paths never stored), and `ask`. The first use of a rowed typed skill, a rowed agent spawn or a main-loop phase declared through the `route` tool opens a dialog: Later, Keep or another phase, then Everywhere or This project only. One dialog at a time, never in headless (`-p`) runs or inside a sub-agent. Only a dialog answer or `/route ask on|off` writes the file (serialized, 64 KiB cap, never created when absent); each write asks you to commit it from the config repo. `/route pending` lists unconfirmed rows and phases. Layers: routing.json < `~/.claude/model-router.json` (its `ask` wins); a project's `.claude/model-router.json` is never read. Tests: `mods/model-router/hooks/register.test.ts`. - **Manual-push mode**: `git config gitflow.autopush false` (human-set) now stops every push the gitflow lib makes, not only the post-commit / post-merge hooks. `gitflow start` and `finish` branch, commit and merge locally and push nothing; `gitflow delete` leaves the `origin/` copy in place and prints `git push origin --delete
` for the user to run. `hooks/unpushed-guard.sh` stays silent at turn end in this mode and opens each session with one `ℹ manual push mode:` line counting the commits no remote holds across every local branch; an unparseable or unreadable `gitflow.autopush` value is treated as manual push mode too, and that line names it. `hooks/push-guard.sh` (PreToolUse, `Bash|Monitor`) refuses any `git push` Claude types while `gitflow.autopush` reads false in the session cwd or in a literal `-C`/`cd` directory the command names (global config counts outside a repo); the refusal tells the user to run it with `! git push`. It reads the mode through the same lib verb as every other reader and fails closed: an unparseable or unreadable value reads as manual, and an internal error, a missing `lib/gitflow.sh`, more than 20 distinct directory tokens in one command (capped before any token is classified), a `cd`/`-C` directory token mixing quoted and unquoted parts, or a payload jq cannot parse whose raw text looks like a push refuse the push (these pathological cases fire in auto mode too). Directory tokens are read as whole shell words, adjacent quoted segments and backslash escapes included. In manual mode it over-blocks any command where a `push` word follows a `git` token; the misses listed in its header fall to a new `autoMode.soft_deny` rule that no request in the turn clears. The session banner adds `🔒 push : manual (autopush=false) — ! git push` when the key reads false, and `🔒 push : manual (autopush bad) — ! git push` when the value is invalid. Skills read the mode through a new lib verb, `bash ~/.claude/lib/gitflow.sh push-mode`: it prints `auto`, `manual` or `invalid` (rc 0) and names an invalid value on stderr (printable characters only, 64 at most). It is the one reader a skill may call, since the `git config` read of the key is denied to Claude. Skills push nothing on their own, except the `/release-candidate` tag in auto-push mode on an explicit go. Every "on origin" or "not pushed" line they print comes from `git rev-list --count origin/
..
` read after the fact, with the complete `! git …` command when something is left for the user to push. An invalid value (anything but unset, true or false, or a read that fails) is manual push mode for every reader and is named where it is read (see Fixed). Tests: `lib/gitflow-test.sh` T11b (push-mode verb), T18m and T18q blocks, `lib/tests/unpushed-guard.test.sh` T10-T16, `lib/tests/push-guard.test.sh` (98 checks). ### Changed -- `lib/model-gate.md` calls the model-router `route` tool and takes its answer as the witness; with the mod off the gate stops and names `/route on`. The `model:` / `effort:` frontmatter of agents and skills is now the off-state floor, census-locked equal to the rows (`lib/tests/effort-routing.test.sh`). `analyzer` effort goes from high to xhigh. Built-in judgment dispatches carry an explicit `effort=`. +- `lib/model-gate.md` calls the model-router `route` tool and takes its answer as the witness; with the mod off the gate stops and names `/route on`. The `model:` / `effort:` frontmatter of agents and skills is now the off-state floor, census-locked equal to the rows (`lib/tests/effort-routing.test.sh`; a row changed through the first-use dialog passes with a `WARN floor drift` line while its frontmatter still holds the shipped value). `analyzer` effort goes from high to xhigh. Built-in judgment dispatches carry an explicit `effort=`. - `settings.json` denies every write form of the human-only `gitflow.*` keys (18 entries): any `git … config` spelling, section remove/rename, `git -c`, the git config env overrides, and Edit/Write of `.git/config`, `.gitconfig` and `~/.config/git/config`. Side effect: Claude can no longer read `gitflow.autopush` through `git config` either; hooks and `lib/gitflow.sh` still read it. The `hard_deny` rule on routing around a guardrail now names PreToolUse hook refusals. - `gitflow start` and `finish` warn on stderr when a base is behind origin and cannot fast-forward, instead of a silent `git pull --ff-only || true` (T18l, T18n). - `/close` (`/capitalize` STEP 5C) no longer runs its own push of develop: `gitflow finish` already pushes develop in auto-push mode (BDR-095). The closing line reports the real state, read after the merge: pushed, manual push mode with the `! git push origin develop` to run, not on origin, push failed, or an invalid `gitflow.autopush` value named and treated as manual push mode. A finish whose merge landed but whose branch delete failed (rc 5/2/6) still reports the push state. diff --git a/README.md b/README.md index 33fa880..afb7697 100644 --- a/README.md +++ b/README.md @@ -104,14 +104,17 @@ children are dispatched `model:"fable"` (they carry reflection). ## Effort routing (BDR-107, BDR-108) Second axis of the same table: how hard each phase thinks. Session default -`high`. The model-router mod (below) holds the live source: one phase row -per repo skill and agent, each phase naming a tier and an effort level +`high`. The live source is `mods/model-router/routing.json`, tracked with the +model-router mod (below): the phase table and one phase row per repo skill +and agent, each phase naming a tier and an effort level (`plan` best/xhigh, `reflect` best/high, `orchestrate` best/medium, `escalate` best/max, `judge` big/xhigh, `implement` work/medium, `write` work/high, `verify` work/xhigh, `explore` work/medium, `apply` work/low, `mechanical` cheap/low). The `model:` and `effort:` frontmatter of every typed agent and user-invoked skill stays as the off-state floor, kept equal -to the rows by the census `lib/tests/effort-routing.test.sh`: low +to the rows by the census `lib/tests/effort-routing.test.sh` (a row +changed through the first-use dialog passes with a `WARN floor drift` line +until its frontmatter follows): low appliers, medium executors, high writers, xhigh judgment and gates (none on haiku, which rejects the parameter); `/status` low … `/ship-feature` xhigh. The vendored externals (design stack, superpowers, agent-skills, @@ -131,10 +134,12 @@ effort, never version. Transcript audit `python3 lib/effort-audit.py`. - Main loop: every request gets the route in force. A typed skill with a row routes the main loop to it; a best-tier row (`plan`, `reflect`, `orchestrate`, `escalate`) holds across turns in a run slot until `/route clear`, `/route off`, a user `/model` or a typed skill on a non-best row. A skill without a row leaves the route as it is. - Sub-agents: a routed agent gets its row's model at spawn, within its tier and only upward from its frontmatter model, and the row's effort on every step. Explicit `model` / `effort` params on the Agent call win; a project-defined agent of the same name keeps its own definition. Built-ins: Explore runs on sonnet/medium, Plan on opus/xhigh. - Levers inside a run: `ultrathink` in a prompt sets the turn's minimum effort; `/route effort=max` holds until `/route clear`. The built-in `/effort` is not a lever inside a run, rows and routes outrank it. -- `/route` (user command) shows or sets the route: `show`, `clear`, `off`, `on`, `reload`, a phase name, `model= effort=`, `switch on|off`, `verbose on|off`. `/route show` names the run slot when one holds (`main: run `). The model sets routes through a `route` tool. +- `/route` (user command) shows or sets the route: `show`, `clear`, `off`, `on`, `reload`, `pending` (rows and phases not confirmed yet), `ask on|off` (first-use dialog on or off), a phase name, `model= effort=`, `switch on|off`, `verbose on|off`. `/route show` names the run slot when one holds (`main: run `). The model sets routes through a `route` tool. +- First use: the first time a rowed skill is typed, a rowed agent is spawned or a phase is declared on the main loop through the `route` tool, the mod asks once whether the route is right. A row offers Later, Keep and two alternative phases (Other takes any phase name); a declared phase offers Later or Keep. Picking another phase then asks Everywhere or This project only. Everywhere moves the row and records the shipped phase under `changed`; This project only stores an exception under `projects`, keyed by the origin remote reduced to `host/path` (no credentials, no local paths; a remote that cannot be read that way offers no project choice). Keep lands under `confirmed`; Later asks again next session. One dialog at a time, never in a headless (`-p`) run, never inside a sub-agent. +- Only a dialog answer or `/route ask on|off` writes `routing.json`, never the model. Writes are serialized, capped at 64 KiB, and refused when the file is missing (it is never created). Each write leaves the config repo dirty; a toast reminds you to commit it from there. - The spinner suffix and the status line under the prompt show the route in force. -Optional per-machine config: `~/.claude/model-router.json`. Keys: `models` (alias → full id), `windows` (context window per full id), `phases`, `agents`, `skills`, `prompt` (rules), `mainModelSwitch` (default `false`), `verbose` (default `false`), `spinner` (default `true`), `enabled` (default `true`; `false` turns the mod off on that machine). A `null` value in `agents` or `skills` drops a default row. `/route reload` re-reads it. +Config layers: `mods/model-router/routing.json` (tracked: phases, rows, decisions, `ask`), then the optional per-machine `~/.claude/model-router.json`, which wins. A `.claude/model-router.json` inside a project is never read. Machine keys: `models` (alias → full id), `windows` (context window per full id), `tiers` (ordered alias lists per tier: `best` fable>opus>sonnet, `big` opus>fable>sonnet, `work` sonnet>opus, `cheap` haiku>sonnet; the first alias not down is used), `fallback` (the rank order of the aliases, best first, used by the breaker), `cooldownMinutes` (how long a model stays marked down after an availability error, 15 by default, doubling per episode up to 300), `mainUpgrade` (default `true`: the main loop may move up to a phase's tier), `upgradeMaxTokens` (default 200000: no main-loop upgrade above this context size, since an upgrade re-reads the whole context cold), `phases`, `agents`, `skills`, `prompt` (rules), `mainModelSwitch` (default `false`), `verbose` (default `false`), `spinner` (default `true`), `enabled` (default `true`; `false` turns the mod off on that machine), `ask` (overrides routing.json's `ask` on that machine). A `null` value in `agents` or `skills` drops a row. Edit phases and rows by hand in routing.json; without it the mod runs the code's default phases, with no rows and no dialog. `/route reload` re-reads both files. Limits: the main loop changes model only with `mainModelSwitch` on, and each switch into another model costs one cold-cache step. The hooks send full model ids, so the `models` table has to follow new model versions. @@ -228,7 +233,7 @@ a different package, ships its own conflicting `graphify` bin) — see | `/profile` | Activate a skill profile (web / seo / web-full / full / max / backend / design / dev / qa / audit / minimal) (default: full) | | `/tour` | Grouped all-axes sweep — cleanup + security + reconcile + doc, fix and loop until clean | | `/site-motion` | Site-level motion: scroll engine choice, page transitions, pin/scrub sequencing across a page or Astro route (design stack) | -| `/route` | model-router mod: show or set the main-loop route (show, clear, off, on, reload, , model=… effort=…, switch on\|off, verbose on\|off) | +| `/route` | model-router mod: show or set the main-loop route (show, clear, off, on, reload, pending, ask on\|off, , model=… effort=…, switch on\|off, verbose on\|off) | > This table lists personal skills. Gstack skills (investigate, review, retro, > office-hours, cso…) and marketplace plugins add many more — run diff --git a/USAGE.md b/USAGE.md index 15869b9..1e0edc4 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 | -| `/route` | Voir ou fixer la route du mod model-router | show / clear / off / on / reload / / model=… effort=… / switch on\|off / verbose on\|off | +| `/route` | Voir ou fixer la route du mod model-router | show / clear / off / on / reload / pending / ask on\|off / / model=… effort=… / switch on\|off / verbose on\|off | | `/profile` | Changer le profil de skills | web / seo / web-full / full / max / backend / design / dev / qa / audit / minimal | > Cette table couvre les skills personnels principaux. Les plugins (gstack, @@ -174,8 +174,8 @@ Tu veux... ### Niveau d'effort -Chaque commande a une ligne de phase dans le mod model-router -(`mods/model-router/`, actif dans chaque session), qui fixe son niveau de +Chaque commande a une ligne de phase dans `mods/model-router/routing.json`, +le fichier suivi du mod model-router (actif dans chaque session), qui fixe son niveau de réflexion : low pour la tenue de registre (`/status`, `/close`, `/commit-change`), medium pour le courant (`/gitflow`, `/prune-memory`), high pour un fix ou un refactor (`/feat`, `/hotfix`, `/bugfix`, @@ -187,7 +187,9 @@ l'outil `mcp__model-router__route` (`lib/effort-shift.md`). Les skills externes vendorés (pile design, superpowers, agent-skills, skills scroll MengTo, 21st) ont aussi leur ligne. -Taper un skill qui a une ligne route la boucle principale dessus. Une ligne du tier best (plan, reflect, orchestrate, escalate) tient d'un tour à l'autre pendant tout le run, jusqu'à `/route clear`, `/route off`, un `/model` tapé ou un skill d'un autre tier tapé. Pour relancer un tour bloqué : `ultrathink` dans le prompt (plancher du tour) ou `/route effort=max` (tient jusqu'à `/route clear`). Le `/effort` intégré n'a pas d'effet dans un run : les lignes et les routes passent devant. Les sous-agents reçoivent le modèle de leur ligne au lancement (dans leur tier, jamais en dessous de leur frontmatter) et son niveau à chaque étape ; un `model` ou `effort` explicite sur l'appel gagne. Explore tourne en sonnet/medium, Plan en opus/xhigh. `/route show` affiche la route en cours, slot de run compris (`main: run `). La config par machine, optionnelle, vit dans `~/.claude/model-router.json` ; `"enabled": false` y coupe le mod sur cette machine, et une ligne à `null` y retire une ligne par défaut. +Taper un skill qui a une ligne route la boucle principale dessus. Une ligne du tier best (plan, reflect, orchestrate, escalate) tient d'un tour à l'autre pendant tout le run, jusqu'à `/route clear`, `/route off`, un `/model` tapé ou un skill d'un autre tier tapé. Pour relancer un tour bloqué : `ultrathink` dans le prompt (plancher du tour) ou `/route effort=max` (tient jusqu'à `/route clear`). Le `/effort` intégré n'a pas d'effet dans un run : les lignes et les routes passent devant. Les sous-agents reçoivent le modèle de leur ligne au lancement (dans leur tier, jamais en dessous de leur frontmatter) et son niveau à chaque étape ; un `model` ou `effort` explicite sur l'appel gagne. Explore tourne en sonnet/medium, Plan en opus/xhigh. `/route show` affiche la route en cours, slot de run compris (`main: run `). Première utilisation : la première fois qu'un skill avec ligne est tapé, qu'un agent avec ligne est lancé ou qu'une phase est déclarée sur la boucle principale via l'outil `route`, le mod demande une fois si la route convient. Pour une ligne : Later, Keep ou deux phases alternatives (Other accepte un nom de phase) ; pour une phase déclarée : Later ou Keep. Un changement demande ensuite Everywhere ou This project only (exception rangée sous le remote origin du dépôt réduit à `host/path`, jamais d'identifiants). La réponse est écrite dans `routing.json` ; Later redemande à une session suivante. Un seul dialogue à la fois, jamais en headless (`-p`) ni dans un sous-agent, et le modèle n'écrit jamais ce fichier. Chaque réponse laisse le repo de config modifié : commite-le depuis ce repo. `/route pending` liste ce qui reste à confirmer, `/route ask off|on` coupe ou rallume le dialogue. + +La config par machine, optionnelle, vit dans `~/.claude/model-router.json` et passe devant `routing.json` ; `"enabled": false` y coupe le mod sur cette machine, `"ask": false` y coupe le dialogue, une ligne à `null` y retire une ligne. Un `.claude/model-router.json` dans un projet n'est jamais lu. Les phases et les lignes se modifient à la main dans `routing.json` ; `/route reload` relit les deux fichiers. ## Les plugins — décision rapide