17 Commits
Author SHA1 Message Date
Bastien Chanot b96fab7719 chore(memory): BDR-091 BDR-092 LRN-155..157, journal 2026-09-16 2026-09-17 11:43:39 +02:00
Bastien Chanot 56bd035281 Merge feature/ask-dont-guess into develop 2026-09-17 11:41:39 +02:00
Bastien Chanot cd98bfafe4 chore: purge transient planning artifacts (BDR-065) 2026-09-17 11:41:39 +02:00
Bastien Chanot ddadca6bae Merge feature/automode-docker-node into develop 2026-09-17 11:40:27 +02:00
Bastien Chanot 22ce57f323 docs(changelog): ask-don't-guess doctrine 2026-09-16 22:15:48 +02:00
Bastien Chanot 17370d7e4c feat(executors): NEED-DECISION and BLOCKED carry a CLASS tag 2026-09-16 22:14:13 +02:00
Bastien Chanot 7cc95952bd feat(interviewer): a visible or public choice is asked, never assumed 2026-09-16 22:13:50 +02:00
Bastien Chanot a1357a6ab5 feat(ship-feature,init-project): pass B at the plan and design steps 2026-09-16 22:13:42 +02:00
Bastien Chanot 97591ca737 feat(hotfix): pass B at LOCATE, class-tagged BLOCKED relayed as a question 2026-09-16 22:13:32 +02:00
Bastien Chanot 590482b622 feat(bugfix): pass B at FIX PLAN, NEED-DECISION routed on class 2026-09-16 22:13:16 +02:00
Bastien Chanot 6d9a3497a2 feat(feat): pass B at PLAN, NEED-DECISION routed on class 2026-09-16 22:13:06 +02:00
Bastien Chanot 5f9a9c0f6b feat(rules): ask rather than guess replaces one-question-upfront 2026-09-16 22:12:56 +02:00
Bastien Chanot a2978b6f23 feat(contract): STEP 2 CLARIFY, mid-run channel, how-to-ask 2026-09-16 22:12:13 +02:00
Bastien Chanot 0d52f3a888 docs(plan): ask-don't-guess implementation plan, TODO trace 2026-09-16 22:10:24 +02:00
Bastien Chanot 5eccc3f1c4 feat(automode): docker and node framed by the classifier, ask rules retired 2026-09-16 22:03:23 +02:00
Bastien Chanot 823ce42225 chore(config): default model fable 5.1 2026-09-16 21:58:18 +02:00
Bastien Chanot 9eb69346ce docs(spec): design for the ask-don't-guess clarification doctrine 2026-09-16 20:39:15 +02:00
21 changed files with 320 additions and 57 deletions
+21
View File
@@ -100,6 +100,8 @@ rules:
| BDR-088 | 2026-09-15 | gstack Playwright bump shared via lib, re-applied after submodule update; update helper never touches the submodule tree | accepted | | BDR-088 | 2026-09-15 | gstack Playwright bump shared via lib, re-applied after submodule update; update helper never touches the submodule tree | accepted |
| BDR-089 | 2026-09-15 | No Playwright browser-cache pruner; read-only doctor report — .links proved 0 bytes reclaimable | accepted | | BDR-089 | 2026-09-15 | No Playwright browser-cache pruner; read-only doctor report — .links proved 0 bytes reclaimable | accepted |
| BDR-090 | 2026-09-15 | Destructive shell work → autoMode soft_deny/hard_deny; `ask` tier abandoned (inert under auto) | accepted | | BDR-090 | 2026-09-15 | Destructive shell work → autoMode soft_deny/hard_deny; `ask` tier abandoned (inert under auto) | accepted |
| BDR-091 | 2026-09-16 | Ask, don't guess: open-choice sweep (3 classes) at plan step + mid-run CLASS channel supersede "one question upfront" | accepted |
| BDR-092 | 2026-09-16 | docker + node framed by the classifier via autoMode.allow + soft_deny; ask entries retired | accepted |
--- ---
@@ -1157,3 +1159,22 @@ Branch feature/user-writing-web-rules, UNMERGED (human gate).
- **Caveat**: the guardrail `hard_deny` bars REMOVING a `deny`/`soft_deny`/`hard_deny` entry, not adding one. Future loosening goes through `/permissions` or the user's own edit — deliberate, confirmed with the user. - **Caveat**: the guardrail `hard_deny` bars REMOVING a `deny`/`soft_deny`/`hard_deny` entry, not adding one. Future loosening goes through `/permissions` or the user's own edit — deliberate, confirmed with the user.
- **Status**: accepted. - **Status**: accepted.
- **Reference**: `settings.json`, `doctor.sh` `check_automode`, `templates/settings/SETTINGS.md`. Links [[LRN-153]], [[LRN-146]], [[BDR-004]]. - **Reference**: `settings.json`, `doctor.sh` `check_automode`, `templates/settings/SETTINGS.md`. Links [[LRN-153]], [[LRN-146]], [[BDR-004]].
## BDR-091 — Ask, don't guess: open-choice sweep + mid-run channel supersede "one question upfront"
- **Date**: 2026-09-16
- **Decision**: `CLAUDE.global.md` rule → ask on a VISIBLE (placement, wording, order, behavior), PUBLIC NAME (command, flag, endpoint, file) or SCOPE ("X too?") choice the request leaves open, even mid-task; class 4 (internal technical, no observable effect) never. `lib/contract-interview.md` STEP 2 = CLARIFY: pass A (gaps: outcome / scope / constraints) at contract time; pass B (open-choice sweep, 3 classes) ONCE at each flow's PLAN step, no question cap, >5 open → under-specified, list + stop; "you decide" recorded `A: delegated — <default>`, never re-asked. New MID-RUN CLARIFICATION: executor halts `NEED-DECISION` + `CLASS:` tag; visible / public-name / scope → human verbatim; internal → loop decides, max 2 round-trips. New HOW TO ASK (LRN-102: ≤4 → one AskUserQuestion, context in option descriptions; else plain text ending the turn). Wiring: feat STEP 1, bugfix STEP 3, hotfix LOCATE (pass A stays silent autofill; one re-dispatch on class-tagged BLOCKED = the sole hotfix re-dispatch), ship-feature STEP 2, init-project STEP 3; interviewer: class 1-3 item never `(assumed)`, one extra targeted question. Executors (feater, bugfixer, hotfixer) report the class. Locks: contract-verifier (9), loops-light (hotfix), gates (3).
- **Why**: gap-only trigger structurally blind to taste — "add a share icon" passes outcome / scope / constraints and the icon lands wherever the executor put it; raising the 3-question cap changes nothing. feat:153 / bugfix:165 told the orchestrator "make the decision HERE", twice, before escalating = institutional guessing. Fresh re-dispatch keeps the tree, loses the executor's reasoning → a plan-time batch costs less than the same question mid-run; the mid-run channel stays for leftovers.
- **Alternatives rejected**: bigger budget (quota was never the limiter); new `lib/clarify.md` (extra hop, STEP 2 already the mandatory passage every orchestrator runs); global rule only (skills carried explicit counter-instructions — `zero questions ever`, `make the decision HERE`, `max 3 questions` — the specific beats the general).
- **Risk watched**: chattiness. Brakes = class 4 exclusion + over-5 guard. hotfix identity (speed, silence) = the flow to watch; if pass B fires on most hotfixes the class definitions are too wide, not the flow.
- **Status**: accepted. Behavioral check OPEN: `/feat "add a share icon to the header"` must ask placement before dispatch; the fully specified variant must ask nothing → record in `evals.md`.
- **Reference**: spec + plan `docs/superpowers/{specs,plans}/2026-09-16-ask-dont-guess*` (purged at finish, in history at `22ce57f`), commits `9eb6934..22ce57f`, merge `56bd035`. Supersedes the `CLAUDE.global.md:51` rule line. Refines [[BDR-049]] (contract), applies [[LRN-102]]. Links [[LRN-157]].
## BDR-092 — docker + node framed by the classifier (`autoMode.allow`), `ask` rules retired
- **Date**: 2026-09-16
- **Decision**: `Bash(docker run|exec *)`, `Bash(docker[-| ]compose up*)`, `Bash(node -e *)` out of `permissions.ask`. New `autoMode.allow` (`$defaults` first): (1) local dev containers — `docker exec/run/compose` against a workstation container whose name lacks `prod`/`production`, running a repo SQL file or script inside, output piped; Remote Shell Writes / Production Reads / Sensitive Remote Exec scoped to sensitive-named hosts; a literal `DROP/TRUNCATE/DELETE` on the command line stays under Mass Delete. (2) project-local node — `node <file>`, `npm run`/`pnpm`/`yarn` scripts, `npx`/`pnpm exec` of a lockfile-declared package, effects in cwd. +2 `soft_deny`: docker data destruction (`rm -f`, `volume rm/prune`, `system prune`, `compose down -v`, `--privileged`, bind mount outside cwd); undeclared node packages (`npx`/`dlx` absent from lockfile, `npm install <name>`). `model` bump to fable 5.1 committed alongside.
- **Why**: real gate = built-in `Remote Shell Writes` / `Production Reads` soft_deny catching `docker exec` into `supabase_db_game`; inside the classifier `allow` = exception tier (hard_deny > soft_deny > allow > explicit intent). Static `Bash(node *)` allow is suspended under auto (wildcarded interpreter) → prose is the only lever for a conditional node permission; `awk`/`echo` statics short-circuit, `node` cannot. `ask` inert on 2.1.273 (probe, [[LRN-155]]) — retiring it is forward-safe: if the documented prompt behavior lands, those entries would prompt for exactly what should run free.
- **Alternatives rejected**: static `permissions.allow` for docker (short-circuits the classifier, framing impossible); keep the `ask` entries (inert today, wrong tomorrow); strict on every `.sql` (blocks the repo's verify scripts).
- **Trade-off accepted**: a repo SQL file runs even when its content is opaque to the classifier — local dev DB only, resettable.
- **Guardrail**: S6 (loosening = user's own edit) overridden explicitly by the user for this change; diff reviewed on the branch before merge.
- **Status**: accepted. Verified: `jq` valid; `claude auto-mode config` shows the 4 entries with `$defaults` expanded; `doctor.sh` autoMode PASS; live `docker exec -i supabase_db_game psql … -f - < verify/0043 … | tail` → `ROLLBACK`, exit 0, no prompt. `claude auto-mode critique` printed nothing (2.1.273).
- **Reference**: `settings.json`, `templates/settings/SETTINGS.md` (`autoMode.allow` row + interpreter note), commit `5eccc3f`, merge `ddadca6`. Links [[BDR-090]], [[LRN-153]], [[LRN-155]], [[LRN-156]].
+5
View File
@@ -471,3 +471,8 @@ rules:
- `make test` 0 RED, `doctor.sh` 0 errors, `shellcheck` clean. - `make test` 0 RED, `doctor.sh` 0 errors, `shellcheck` clean.
- graphify skill untracked + gitignored (written by `graphify install --platform claude` since `~/.claude/skills` symlinks to `skills/`). Cost one self-inflicted incident: `git rm --cached` kept the files, `gitflow finish` deleted them at the merge ([[LRN-154]]). Restored at 0.9.61, guarded configs snapshotted and verified untouched. - graphify skill untracked + gitignored (written by `graphify install --platform claude` since `~/.claude/skills` symlinks to `skills/`). Cost one self-inflicted incident: `git rm --cached` kept the files, `gitflow finish` deleted them at the merge ([[LRN-154]]). Restored at 0.9.61, guarded configs snapshotted and verified untouched.
- `.claude/settings.local.json` 14.6 KB -> 6.2 KB. It was not just duplication: its local `deny` still carried the 4 rules moved out of global deny, making [[BDR-090]]'s soft_deny a dead letter in this repo, and its `allow` carried `sed *` / `cp *` / `python3 -`, which short-circuit the classifier on the same rules. - `.claude/settings.local.json` 14.6 KB -> 6.2 KB. It was not just duplication: its local `deny` still carried the 4 rules moved out of global deny, making [[BDR-090]]'s soft_deny a dead letter in this repo, and its `allow` carried `sed *` / `cp *` / `python3 -`, which short-circuit the classifier on the same rules.
## 2026-09-16
- Ask, don't guess ([[BDR-091]]): spec + plan, 9 lock-first tasks (contract-interview CLARIFY two passes, MID-RUN CLARIFICATION with `CLASS:` tag, HOW TO ASK; global rule; feat / bugfix / hotfix / ship-feature / init-project wired; interviewer; 3 executors), suite green. Behavioral fixture check still open ([[LRN-157]]).
- docker + node under auto mode ([[BDR-092]]): `ask` entries retired (inert on 2.1.273, probe — [[LRN-155]]), `autoMode.allow` + 2 soft_deny, live `docker exec … psql` OK. Static interpreter allow is suspended under auto → prose only ([[LRN-156]]).
- Both merged into develop 2026-09-17 via gitflow (`ddadca6`, `56bd035`), two stack conflicts (TODO, CHANGELOG) resolved keeping both blocks. Symlinked `settings.json` follows the checkout: live config = whatever branch is out.
+21
View File
@@ -144,6 +144,9 @@ rules:
| LRN-152 | 2026-09-15 | git `protocol.file=user` blocks submodule fixtures; `-c` misses the code under test, `GIT_CONFIG_*` env does not | tests building git fixtures | | LRN-152 | 2026-09-15 | git `protocol.file=user` blocks submodule fixtures; `-c` misses the code under test, `GIT_CONFIG_*` env does not | tests building git fixtures |
| LRN-153 | 2026-09-15 | `autoMode` lists replace built-ins without `"$defaults"`; a user-scope block reaches every project | any `autoMode` edit | | LRN-153 | 2026-09-15 | `autoMode` lists replace built-ins without `"$defaults"`; a user-scope block reaches every project | any `autoMode` edit |
| LRN-154 | 2026-09-15 | `git rm --cached` + merge into a branch that still tracks the file DELETES it from disk | untracking a generated file | | LRN-154 | 2026-09-15 | `git rm --cached` + merge into a branch that still tracks the file DELETES it from disk | untracking a generated file |
| LRN-155 | 2026-09-16 | ask under auto: doc says prompt, probe on 2.1.273 says no; re-probe after upgrades | any permission-tier reasoning |
| LRN-156 | 2026-09-16 | autoMode.allow = exception tier; static interpreter allow suspended under auto → conditions live in prose | conditional permissions |
| LRN-157 | 2026-09-16 | gap-only trigger blind to taste → add a trigger class, not budget; ask at plan, mid-run for leftovers | any "ask more" request |
--- ---
@@ -1469,3 +1472,21 @@ Rule: when editing a doctrine file under structure locks, grep the test's lock s
- **Future application**: untracking any generated file — know the regeneration command BEFORE merging, and `ls` the path right after `finish`. If nothing regenerates it, keep it tracked. - **Future application**: untracking any generated file — know the regeneration command BEFORE merging, and `ls` the path right after `finish`. If nothing regenerates it, keep it tracked.
- **graphify specifics**: `graphify install --platform claude` copies the skill and touches nothing else. `graphify claude install` is a different command — it writes the CLAUDE.md section and the `.claude/settings.json` hooks, rewrites both guarded configs, and does NOT copy the skill. Confusing the two wastes a recovery attempt. - **graphify specifics**: `graphify install --platform claude` copies the skill and touches nothing else. `graphify claude install` is a different command — it writes the CLAUDE.md section and the `.claude/settings.json` hooks, rewrites both guarded configs, and does NOT copy the skill. Confusing the two wastes a recovery attempt.
- **Reference**: `CLAUDE.md` machine-owned section, commit 80ccdaf. Links [[BDR-090]]. - **Reference**: `CLAUDE.md` machine-owned section, commit 80ccdaf. Links [[BDR-090]].
## LRN-155 — `permissions.ask` under auto mode: the probe beats the doc
- **Date**: 2026-09-16
- **Pattern**: `auto-mode-config` + `permissions` docs say a content-scoped `ask` rule (`Bash(git push *)`) is evaluated BEFORE the classifier and always prompts, even in auto mode. Probe on 2.1.273: `node -e 'console.log(...)'` matching `Bash(node -e *)` in `ask` ran, no prompt, exit 0. [[LRN-146]] holds. Either the doc describes a later build or "content-scoped" means something narrower; observed wins.
- **Future application**: before reasoning about a permission tier, probe it with a benign command matching the rule; re-probe after every Claude Code upgrade — the day `ask` starts prompting, every leftover `ask` entry becomes a nag for things meant to run free.
- **Reference**: [[BDR-092]], `templates/settings/SETTINGS.md` "ask is not a prompt" §.
## LRN-156 — Conditional permissions live in classifier prose, not static rules
- **Date**: 2026-09-16
- **Pattern**: `autoMode.allow` = exception tier: an entry overrides a matching `soft_deny`, built-in or own (precedence hard_deny > soft_deny > allow > explicit intent). Under auto, static allow rules granting arbitrary execution (`Bash(*)`, wildcarded interpreters like `Bash(node *)`) are suspended → classifier anyway; non-interpreter statics (`awk`, `echo`) resolve before it. A condition ("package declared in the lockfile", "container is local dev") is therefore expressible ONLY as `autoMode.allow` prose. Word it narrowly: it punches through built-in rules too.
- **Tooling**: `claude auto-mode defaults` prints the built-in lists (grep it for the rule that bit); `claude auto-mode config` = effective lists with `$defaults` expanded; `claude auto-mode critique` printed nothing on 2.1.273. Shell-snapshot `claude` wrapper is broken (`exec command claude` → "command: not found") → call `~/.local/bin/claude` directly.
- **Reference**: [[BDR-092]], [[LRN-153]].
## LRN-157 — Taste is invisible to a gap-only trigger; ask at plan time
- **Date**: 2026-09-16
- **Pattern**: a trigger that fires only on missing outcome / scope / constraints lets every taste choice through — "add a share icon" is complete by those criteria and the icon's side is decided downstream. More budget changes nothing; the fix is a new trigger class (VISIBLE / PUBLIC NAME / SCOPE). Cost geometry: a fresh re-dispatch keeps the working tree and loses the executor's reasoning → the same question costs about one executor run more mid-run than at PLAN. So: sweep once at the plan step, keep the mid-run channel for leftovers. Executor tags the class; orchestrator re-reads it (tag = hint, a mis-tag would offload class 4 onto the human). Relayed questions obey [[LRN-102]]: context inside `AskUserQuestion`, nothing the user needs printed before it.
- **Future application**: any "ask more" request → check WHICH trigger is blind before touching a quota. Any orchestrator with a "decide it yourself" fallback on an executor halt → route by class first.
- **Reference**: [[BDR-091]], `lib/contract-interview.md` STEP 2 + MID-RUN CLARIFICATION.
+51
View File
@@ -1,5 +1,56 @@
# TODO # TODO
## 2026-09-16 — docker + node framed by the classifier (feature/automode-docker-node)
User: `docker exec -i supabase_db_game psql … -f - < supabase/verify/*.sql | tail`
must run unprompted under auto mode; same for node/npm/npx when the package
is declared and effects stay in the cwd; "ajoute du soft deny pour bien le
cadrer". Findings: `ask` is inert under auto (LRN-146 re-verified on 2.1.273
with a `node -e` probe; the docs claim otherwise for content-scoped rules);
the real gate is the built-in `Remote Shell Writes` / `Production Reads`
classifier rules; a static `Bash(node *)` allow rule is suspended under auto
(wildcarded interpreter), so `autoMode.allow` prose is the only lever for
node. User approved the design and the `ask` removal explicitly (S6 override
for this change, diff reviewed on the branch).
- [x] A1 `settings.json` — drop 4 docker + `node -e` from `ask`; new
`autoMode.allow` (`$defaults` + local dev containers + project-local
node); 2 `soft_deny` entries (docker data destruction, undeclared
node packages); `model` bump committed separately
- [x] A2 `templates/settings/SETTINGS.md` — `autoMode.allow` tier row +
why a static interpreter allow rule cannot do it; LRN-146 re-verify note
- [x] A3 CHANGELOG [Unreleased] Changed
- [x] A4 verify (2026-09-16, all green; `critique` printed nothing): `jq`, `claude auto-mode config`,
`doctor.sh`, live `docker exec` in game
- [x] A5 registries written 2026-09-17: LRN (doc vs observed `ask` under auto,
2.1.273; `autoMode.allow` = exception tier; wildcarded-interpreter
allow suspended), BDR-090 addendum
## 2026-09-16 — ask, don't guess: orchestrators ask about open choices (feature/ask-dont-guess)
User: the orchestrators (ship-feature, feat, hotfix, bugfix, init-project)
settle choices they should ask about ("cet icône, plutôt à gauche ou à
droite ?"), even mid-run. Diagnosis: contract-interview STEP 2 only fires on
gaps (outcome / scope / constraints), so a taste choice never triggers a
question; feat:153 and bugfix:165 tell the orchestrator to "make the
decision HERE" on NEED-DECISION. Decisions (user, 2026-09-15/16): global
rule changes for all work, hotfix included; classes VISIBLE / PUBLIC NAME /
SCOPE ask, internal technical choices never. Spec:
`docs/superpowers/specs/2026-09-16-ask-dont-guess-design.md`; plan:
`docs/superpowers/plans/2026-09-16-ask-dont-guess.md` (9 tasks, lock-first).
- [x] P1 `lib/contract-interview.md` — STEP 2 CLARIFY (pass A gaps, pass B
open choices), MID-RUN CLARIFICATION, HOW TO ASK; 9 locks in
`contract-verifier.test.sh`
- [x] P2 `CLAUDE.global.md:51-55` — "Ask rather than guess" replaces the
one-question rule; bug line reconciled
- [x] P3 `skills/feat/SKILL.md` — pass B at STEP 1, NEED-DECISION routed on class
- [x] P4 `skills/bugfix/SKILL.md` — pass B at STEP 3, NEED-DECISION routed on class
- [x] P5 `skills/hotfix/SKILL.md` — pass B at LOCATE, tagged BLOCKED relayed;
lock `loops-light.test.sh:84`
- [x] P6 `skills/ship-feature` STEP 2 + `skills/init-project` contract §/STEP 3
- [x] P7 `agents/interviewer.md` — visible/public/scope item never `(assumed)`
- [x] P8 `agents/{feater,bugfixer,hotfixer}.md` — CLASS tag; 3 locks in `gates.test.sh`
- [x] P9 `make test` green (2026-09-16), CHANGELOG, TODO tick; manual behavioral check still OPEN before merge
- [x] P10 registries written 2026-09-17: BDR (supersedes the one-question rule),
LRN (taste is invisible to a gap-only trigger; fresh re-dispatch cost
favors plan-time questions)
## 2026-09-15 — align config + deployment on the hand-edited settings.json (feature/automode-config-alignment) ## 2026-09-15 — align config + deployment on the hand-edited settings.json (feature/automode-config-alignment)
User edited global `settings.json` by hand: 4 destructive rules moved User edited global `settings.json` by hand: 4 destructive rules moved
deny→ask (`rsync`, `kill -9`, `killall`, `pkill`), 4 removed from ask deny→ask (`rsync`, `kill -9`, `killall`, `pkill`), 4 removed from ask
+34
View File
@@ -28,6 +28,40 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
destructive command under auto mode. destructive command under auto mode.
### Changed ### Changed
- **Docker and node go through the classifier with a framing, instead of
an inert `ask` tier.** `Bash(docker run|exec *)`, `Bash(docker[-| ]compose
up*)` and `Bash(node -e *)` leave `permissions.ask` (no prompt under auto
mode, re-verified on 2.1.273). A new `autoMode.allow` list, `$defaults`
first, names the two routine cases the built-in `Remote Shell Writes` /
`Production Reads` rules were catching: `docker exec`/`run`/`compose`
against a local dev container whose name does not carry `prod`, running a
SQL file or script from the repo inside it; and project-local node
(`node <file>`, `npm run`, `npx`/`pnpm exec` of a lockfile-declared
package, effects inside the cwd). Two `soft_deny` entries frame what that
opens: docker data destruction (`rm -f`, `volume rm`/`prune`, `system
prune`, `compose down -v`, `--privileged`, bind mounts outside the cwd)
and undeclared node packages (`npx`/`dlx` of a package absent from the
lockfile, `npm install <name>`). `SETTINGS.md` gains the `autoMode.allow`
tier and the reason a static `Bash(node *)` rule cannot do this job.
- **Ask, don't guess: the orchestrators ask about open choices instead of
settling them.** `CLAUDE.global.md` replaces "one question upfront, never
mid-task" with: a choice visible in the result, a name that becomes
public, or a scope the request does not settle → ask, even mid-task;
internal technical choices stay Claude's. `lib/contract-interview.md`
STEP 2 becomes CLARIFY: pass A (the three gap checks, at contract time)
and pass B (the open-choice sweep in three classes, run once at each
flow's PLAN step, no question cap, over-5 guard, "you decide" recorded as
delegated). New MID-RUN CLARIFICATION section: an executor's
`NEED-DECISION` carries a `CLASS:` tag; visible / public-name / scope go
to the user verbatim, internal is decided in the loop; answers land in
the contract `[gated]`. New HOW TO ASK section (LRN-102). `/feat`,
`/bugfix`, `/hotfix`, `/ship-feature`, `/init-project` wire pass B at
their plan step; `/feat` and `/bugfix` stop deciding `NEED-DECISION`
themselves; `/hotfix` drops "zero questions ever" and allows one
re-dispatch for a class-tagged BLOCKED; the interviewer never ships a
visible / public-name / scope item as `(assumed)`; feater, bugfixer and
hotfixer report the class. Locks updated in the `contract-verifier`,
`loops-light` and `gates` tests.
- **The classifier, not `permissions.ask`, now guards destructive shell - **The classifier, not `permissions.ask`, now guards destructive shell
work** (BDR-090). Ten rules left the static tiers: `rsync`, `kill -9`, work** (BDR-090). Ten rules left the static tiers: `rsync`, `kill -9`,
`killall`, `pkill` out of `deny`, and `python3 -c`, `python -c`, `killall`, `pkill` out of `deny`, and `python3 -c`, `python -c`,
+6 -2
View File
@@ -48,11 +48,15 @@ Apply unless repo-specific instructions override.
calls. Skill-mandated gates (fresh verifier/security/challenge) calls. Skill-mandated gates (fresh verifier/security/challenge)
always dispatch as written. Don't redo delegated work by hand — always dispatch as written. Don't redo delegated work by hand —
failed gates re-dispatch fresh executors instead. failed gates re-dispatch fresh executors instead.
- One question upfront if needed — don't interrupt mid-task. - Ask rather than guess. A choice visible in the result (placement,
wording, order, behavior), a name that becomes public (command, flag,
endpoint, file), or a scope the request does not settle → ask, even
mid-task. Batch what can be batched. Internal technical choices with
no observable effect stay yours.
*Exception: skill-mandated gates and checkpoints (orchestrator *Exception: skill-mandated gates and checkpoints (orchestrator
validation gates, approval gates, darwin checkpoints) always fire.* validation gates, approval gates, darwin checkpoints) always fire.*
- Bug received → fix directly: check logs, find root cause, resolve - Bug received → fix directly: check logs, find root cause, resolve
autonomously. autonomously; a visible choice in the fix still gets asked.
- Something goes wrong → STOP, re-plan. Never push through. - Something goes wrong → STOP, re-plan. Never push through.
- Deviations: minor or clearly justified → do, explain after. - Deviations: minor or clearly justified → do, explain after.
Significant or shaky justification → ask before deviating. Significant or shaky justification → ask before deviating.
+5 -3
View File
@@ -27,8 +27,9 @@ Every choice was made in the plan or is a NEED-DECISION to report.
- Apply the FIX PLAN to the letter — fix the ROOT CAUSE named in DIAGNOSIS, - 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 not the symptom. A plan hole or an open choice (naming, data shape, API
surface, dependency) → STOP, report `NEED-DECISION` with the precise surface, dependency, a user-visible choice such as placement, wording or
question. Never re-investigate or improvise a different fix. behavior) → STOP, report `NEED-DECISION` with the precise question and
its `CLASS:`. Never re-investigate or improvise a different fix.
- Stay inside the contract FILE SCOPE. A needed file outside it → - Stay inside the contract FILE SCOPE. A needed file outside it →
`NEED-DECISION` (the orchestrator owns scope changes); don't touch 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 - Add or update the regression test the plan names — it must fail before the
@@ -73,5 +74,6 @@ FILE(S) : <created/modified paths>
TEST(S) : <regression test added/updated + final suite run result, verbatim line> TEST(S) : <regression test added/updated + final suite run result, verbatim line>
SMOKE : <build/typecheck result if run, or n/a> SMOKE : <build/typecheck result if run, or n/a>
NOTES : <DONE: deviations (must be none) | NEED-DECISION: the exact NOTES : <DONE: deviations (must be none) | NEED-DECISION: the exact
question + the options you see | BLOCKED: the blocker verbatim> question + the options you see + CLASS: visible | public-name |
scope | internal | BLOCKED: the blocker verbatim>
``` ```
+5 -3
View File
@@ -37,8 +37,9 @@ report below is optional on this path (the dispatcher needs the edit applied
## EXECUTION RULES ## EXECUTION RULES
- Follow the plan to the letter. A plan hole or an open choice (naming, - Follow the plan to the letter. A plan hole or an open choice (naming,
data shape, API surface, dependency) → STOP, report `NEED-DECISION` with data shape, API surface, dependency, a user-visible choice such as
the precise question. Never improvise a design decision. placement, wording or behavior) → STOP, report `NEED-DECISION` with the
precise question and its `CLASS:`. Never improvise a design decision.
- Stay inside the contract FILE SCOPE. A needed file outside it → - Stay inside the contract FILE SCOPE. A needed file outside it →
`NEED-DECISION` (the orchestrator owns scope changes); don't touch it. On `NEED-DECISION` (the orchestrator owns scope changes); don't touch it. On
the applier path the scope is the files named in the bundle item — apply the applier path the scope is the files named in the bundle item — apply
@@ -84,5 +85,6 @@ STATUS : DONE | NEED-DECISION | BLOCKED
FILES : <created/modified paths> FILES : <created/modified paths>
TESTS : <added/updated + final suite run result, verbatim line> TESTS : <added/updated + final suite run result, verbatim line>
NOTES : <DONE: deviations (must be none) | NEED-DECISION: the exact NOTES : <DONE: deviations (must be none) | NEED-DECISION: the exact
question + the options you see | BLOCKED: the blocker verbatim> question + the options you see + CLASS: visible | public-name |
scope | internal | BLOCKED: the blocker verbatim>
``` ```
+6 -1
View File
@@ -43,6 +43,10 @@ the edit applied + self-verified, not the report grammar).
BLOCKED`, report why (the orchestrator escalates to `/bugfix`), never BLOCKED`, report why (the orchestrator escalates to `/bugfix`), never
expand scope yourself. On the applier path it is the files named in the expand scope yourself. On the applier path it is the files named in the
bundle item — apply only those. bundle item — apply only those.
- An open user-visible choice the contract does not settle (placement,
wording, behavior) → `STATUS BLOCKED` with `CLASS: visible | public-name |
scope` in NOTES, BEFORE editing anything. The orchestrator asks the user
and re-dispatches once.
- If tests exist for the affected code, run them. Detection cascade: - If tests exist for the affected code, run them. Detection cascade:
```bash ```bash
# JS/TS # JS/TS
@@ -78,5 +82,6 @@ STATUS : DONE | BLOCKED
FILE(S) : <changed files — suffix files you CREATED with " (new)"> FILE(S) : <changed files — suffix files you CREATED with " (new)">
FIX : <one-line description> FIX : <one-line description>
SMOKE : <test/build result, verbatim line> SMOKE : <test/build result, verbatim line>
NOTES : <BLOCKED: the blocker; DONE: none> NOTES : <BLOCKED: the blocker, + CLASS: visible | public-name | scope when
you halted at an open choice before editing; DONE: none>
``` ```
+3 -3
View File
@@ -14,13 +14,13 @@ Gather context. Produce complete PROJECT BRIEF as single source of truth.
- If the initial prompt already provides name + purpose + stack + features + architecture → skip questions and generate the BRIEF directly. - If the initial prompt already provides name + purpose + stack + features + architecture → skip questions and generate the BRIEF directly.
- Otherwise ask only what's genuinely missing, in a single structured block. - Otherwise ask only what's genuinely missing, in a single structured block.
- After answers: produce BRIEF. One follow-up allowed if answer is ambiguous. - After answers: produce BRIEF. One follow-up allowed if answer is ambiguous.
- Hard budget: 2 question rounds total (initial block + one follow-up). The BRIEF ships after round 2 no matter what — gaps become OPEN DECISIONS, never a third round. - Hard budget: 2 question rounds total (initial block + one follow-up) for gaps. The BRIEF ships after round 2 — gaps become OPEN DECISIONS. Sole exception: a VISIBLE, PUBLIC NAME or SCOPE choice (a user-facing placement or wording, a public command/flag/endpoint name, whether X is in scope) still open after round 2 gets ONE more targeted question; it never ships as `(assumed)`.
## FAILURE MODES ## FAILURE MODES
| Trigger | First response | If still unresolved | | Trigger | First response | If still unresolved |
|---|---|---| |---|---|---|
| Answer vague/ambiguous | One targeted follow-up on that item only | Record item in OPEN DECISIONS with the safest reading, marked `(assumed)` — never invent a confident value | | Answer vague/ambiguous | One targeted follow-up on that item only | Gap: record it in OPEN DECISIONS with the safest reading, marked `(assumed)` — never invent a confident value. Visible / public-name / scope item: one more targeted question instead, never `(assumed)` |
| "I don't know / you decide" | Propose ONE concrete default + why, ask yes/no | Take the default, mark `(assumed)`, list in OPEN DECISIONS | | "I don't know / you decide" | Propose ONE concrete default + why, ask yes/no | Take the default, mark `(assumed)`, list in OPEN DECISIONS |
| Contradictory answers (e.g. embedded runtime + managed cloud DB) | Name the contradiction, ask which side wins | Put BOTH options in OPEN DECISIONS; do not silently pick one | | Contradictory answers (e.g. embedded runtime + managed cloud DB) | Name the contradiction, ask which side wins | Put BOTH options in OPEN DECISIONS; do not silently pick one |
| Partial answer to the block | Re-ask ONLY the missing items in the follow-up round | Missing fields → `none stated` + OPEN DECISIONS entry | | Partial answer to the block | Re-ask ONLY the missing items in the follow-up round | Missing fields → `none stated` + OPEN DECISIONS entry |
@@ -77,6 +77,6 @@ Stop after BRIEF. Orchestrator handles next step.
- Design, architect, or implement anything — the BRIEF is the entire deliverable. - Design, architect, or implement anything — the BRIEF is the entire deliverable.
- Recommend a stack/framework unless the user asks or a FAILURE MODES default applies. - Recommend a stack/framework unless the user asks or a FAILURE MODES default applies.
- Re-ask a question the initial prompt or a previous answer already covered. - Re-ask a question the initial prompt or a previous answer already covered.
- Exceed the 2-round budget, whatever is still missing. - Exceed the 2-round budget for gaps; the only extra question is the single targeted one a visible / public-name / scope item earns.
- Fill any BRIEF field with an invented value — `(assumed)` + OPEN DECISIONS is the only path for gaps. - Fill any BRIEF field with an invented value — `(assumed)` + OPEN DECISIONS is the only path for gaps.
- Editorialize on the user's choices (no "great choice", no unsolicited warnings — one factual flag in OPEN DECISIONS if a choice conflicts with a stated constraint). - Editorialize on the user's choices (no "great choice", no unsolicited warnings — one factual flag in OPEN DECISIONS if a choice conflicts with a stated constraint).
+72 -9
View File
@@ -7,8 +7,9 @@ subagents = execution + report only; gates and loop decisions live in the
main loop). main loop).
Run this in the ORCHESTRATOR MAIN LOOP, never in a subagent — STEP 2 may Run this in the ORCHESTRATOR MAIN LOOP, never in a subagent — STEP 2 may
talk to the human. Mandatory passage in every flow; questions are optional talk to the human, at contract time (pass A) and again at the flow's PLAN
and proportional — a complete request goes through silently. step (pass B). Questions follow the open choices, never a quota — a complete
request goes through silently.
## STEP 1 — CAPTURE (verbatim) ## STEP 1 — CAPTURE (verbatim)
@@ -17,16 +18,51 @@ message). No paraphrase, no cleanup, no translation, no summarizing. This
section is IMMUTABLE for the life of the run — every later consumer section is IMMUTABLE for the life of the run — every later consumer
(planner, dev, verifier) reads THESE words, never a restatement. (planner, dev, verifier) reads THESE words, never a restatement.
## STEP 2 — AMBIGUITY CHECK (questions optional, proportional) ## STEP 2 — CLARIFY (ask, never guess)
Ask ONLY if one of these is missing AND not derivable from the repo: Two passes, both in the main loop, both may talk to the human.
**Pass A — gaps.** Run here, against the request. Ask if one of these is
missing AND not derivable from the repo:
- a testable expected outcome - a testable expected outcome
- an unambiguous scope (what is allowed to change) - an unambiguous scope (what is allowed to change)
- non-contradictory constraints - non-contradictory constraints
Complete request → ZERO questions, stay silent. Otherwise: max 3 questions, **Pass B — open choices.** Defined here, run ONCE at the flow's PLAN step
one single batch (house rule: one question upfront, never mid-task). Never (see "Where pass B fires" below), against the plan just written — that is
ask what the repo can answer — verify paths/APIs/behavior yourself first. where choices become concrete. Enumerate every choice the run will settle
that the request leaves open; keep those in these classes:
1. VISIBLE — the user would see it in the result: placement, label, wording,
color, order, what a click does.
2. PUBLIC NAME — a name that outlives the run: command, flag, endpoint, env
var, a file the human will read.
3. SCOPE — "should X change too?", where the request does not name X.
NEVER ask class 4 — internal technical choices with no observable effect
(function decomposition, data shape, local naming, layout inside an
already-scoped zone). Those are delegated; asking them is the noise that
makes classes 1-3 ignorable. Never ask what the repo or the request already
answers — verify paths/APIs/behavior yourself first.
No question cap. Each pass asks what it finds, in ONE batch. A request that
leaves nothing open goes through silently. More than 5 open choices in pass B
= the request is under-specified: list them, say so, stop — do not fire a
questionnaire. "You decide" / "peu importe" is an answer: record it as
`A: delegated — <default taken>` and never re-ask it.
Pass B answers land in the contract's CLARIFICATIONS marked
`[gated <YYYY-MM-DD>]` — the contract is already on disk by then.
### Where pass B fires
| Flow | Pass B runs at | Against |
|------|----------------|---------|
| feat | STEP 1 PLAN, before 1b CHALLENGE | the PLAN checklist |
| bugfix | STEP 3 FIX PLAN, before 3b | the FIX PLAN |
| hotfix | STEP 1 LOCATE | the 1-2 target files' visible effect |
| ship-feature | STEP 2 PLAN, after the brainstorm | the plan, minus what the brainstorm settled |
| init-project | STEP 3 DESIGN, before VALIDATION GATE #1 | the DESIGN, minus what the interview and brainstorm settled |
| onboard | its STEP 3 interview, unchanged | scope, in one block |
## STEP 3 — DERIVE ## STEP 3 — DERIVE
@@ -85,7 +121,7 @@ Template:
<the user's exact words> <the user's exact words>
## CLARIFICATIONS ## CLARIFICATIONS
Q: <question> / A: <answer> Q: <question> / A: <answer> (pass B and mid-run entries: [gated <YYYY-MM-DD>])
(or: none — request complete) (or: none — request complete)
## ACCEPTANCE CRITERIA ## ACCEPTANCE CRITERIA
@@ -105,6 +141,33 @@ Q: <question> / A: <answer>
Print one line to the user, then continue the flow: Print one line to the user, then continue the flow:
`CONTRACT: <path> — <n> criteria, scope <files|repo-wide>, <q> questions asked` `CONTRACT: <path> — <n> criteria, scope <files|repo-wide>, <q> questions asked`
## MID-RUN CLARIFICATION (the channel executors halt into)
An executor cannot talk to the human. It halts with `NEED-DECISION`, the
exact question, the options it sees, and a `CLASS:` tag (visible |
public-name | scope | internal). `/hotfix`: the hotfixer keeps
`DONE | BLOCKED`; a BLOCKED carrying the tag follows the same routing instead
of escalating to `/bugfix`. The orchestrator re-reads the class — the tag is
a hint, not a verdict — then routes:
- visible / public-name / scope → ASK THE HUMAN, verbatim question and
options. Never decide these yourself, never spend a round-trip guessing.
- internal → decide here, note the decision, re-dispatch. The only case the
orchestrator settles alone; max 2 such round-trips → escalate.
Every answer, human or orchestrator, appends to the contract's
CLARIFICATIONS marked `[gated <YYYY-MM-DD>]` — the same micro-gate as scope
enrichment — and to the plan handed to the FRESH re-dispatched executor,
which reads the decision from disk, never from a transcript.
## HOW TO ASK (LRN-102)
The harness reliably renders only the turn's FINAL text; text printed before
a tool call may be swallowed. So:
- up to 4 questions → one `AskUserQuestion` call; option descriptions carry
the context; print nothing the user needs before the call.
- more than 4, or a list handed back for re-specification → plain text, end
the turn.
## Lifecycle ## Lifecycle
- **REQUEST**: immutable, for the life of the run. Never rewritten, never - **REQUEST**: immutable, for the life of the run. Never rewritten, never
@@ -134,7 +197,7 @@ Print one line to the user, then continue the flow:
| Flow | Weight | | Flow | Weight |
|------|--------| |------|--------|
| hotfix | Silent autofill — criteria: "symptom gone; build/tests green"; scope = the 1-2 target files. Zero questions ever. | | hotfix | Pass A silent autofill — criteria: "symptom gone; build/tests green"; scope = the 1-2 target files. Pass B runs at LOCATE against the 1-2 target files' visible effect; a typo fix asks nothing. |
| feat / bugfix | Proportional. bugfix: the DIAGNOSIS feeds the criteria (symptom reproduced-then-gone + regression test present). | | feat / bugfix | Proportional. bugfix: the DIAGNOSIS feeds the criteria (symptom reproduced-then-gone + regression test present). |
| ship-feature | Full. Design decisions approved at the validation gate append criteria `[gated <date>]` — the human validates the enriched contract, the verifier receives that version. | | ship-feature | Full. Design decisions approved at the validation gate append criteria `[gated <date>]` — the human validates the enriched contract, the verifier receives that version. |
| init-project | Full. The interviewer's PROJECT BRIEF pours into the contract (V1 features → criteria). | | init-project | Full. The interviewer's PROJECT BRIEF pours into the contract (V1 features → criteria). |
+9 -2
View File
@@ -48,8 +48,15 @@ fi
tf "verbatim request immutable" "$LIB" "REQUEST (verbatim — IMMUTABLE)" tf "verbatim request immutable" "$LIB" "REQUEST (verbatim — IMMUTABLE)"
tf "contracts dir committed path" "$LIB" ".claude/tasks/contracts/" tf "contracts dir committed path" "$LIB" ".claude/tasks/contracts/"
tf "unique per-run slug" "$LIB" "<YYYY-MM-DD>-<slug>-<HHMM>" tf "unique per-run slug" "$LIB" "<YYYY-MM-DD>-<slug>-<HHMM>"
tf "silent when complete" "$LIB" "ZERO questions" tf "silent when nothing open" "$LIB" "goes through silently"
tf "question budget" "$LIB" "max 3 questions" tf "no question cap" "$LIB" "No question cap"
tf "pass B classes" "$LIB" "PUBLIC NAME"
tf "class 4 excluded" "$LIB" "NEVER ask class 4"
tf "over-5 guard" "$LIB" "More than 5 open choices"
tf "delegated answer" "$LIB" "delegated —"
tf "mid-run channel" "$LIB" "## MID-RUN CLARIFICATION"
tf "class tag" "$LIB" "CLASS:"
tf "how to ask" "$LIB" "## HOW TO ASK"
tf "aborted status" "$LIB" "status: aborted" tf "aborted status" "$LIB" "status: aborted"
tf "never left dirty" "$LIB" "NEVER left dirty" tf "never left dirty" "$LIB" "NEVER left dirty"
tf "scope enrichment micro-gate" "$LIB" "micro-gate" tf "scope enrichment micro-gate" "$LIB" "micro-gate"
+3
View File
@@ -311,6 +311,9 @@ lock "bugfixer passes" "$BF" "## FOUR PASSES"
lock "bugfixer stays minimal" "$BF" "keep the fix minimal" lock "bugfixer stays minimal" "$BF" "keep the fix minimal"
lock "bugfixer neg control" "$BF" "**Negative control.**" lock "bugfixer neg control" "$BF" "**Negative control.**"
lock "bugfixer test must fail" "$BF" "A test that passes both ways" lock "bugfixer test must fail" "$BF" "A test that passes both ways"
lock "feater class tag" "$FE" "CLASS:"
lock "bugfixer class tag" "$BF" "CLASS:"
lock "hotfixer class tag" "$REPO/agents/hotfixer.md" "CLASS:"
echo "" echo ""
echo "gates: $PASS pass, $FAIL fail" echo "gates: $PASS pass, $FAIL fail"
+1 -1
View File
@@ -81,7 +81,7 @@ tf "hotfixer report grammar" "$HOT" "HOTFIX-EXEC REPORT"
echo "── skills/hotfix/SKILL.md (hotfix wiring — revert, not loop) ──" echo "── skills/hotfix/SKILL.md (hotfix wiring — revert, not loop) ──"
tf "hotfix silent contract" "$HSKL" "STEP 1.7 — CONTRACT (silent autofill)" tf "hotfix silent contract" "$HSKL" "STEP 1.7 — CONTRACT (silent autofill)"
tf "hotfix zero questions" "$HSKL" "questions ever" tf "hotfix pass B at locate" "$HSKL" "run pass B of"
tf "hotfix security gate" "$HSKL" "Security gate (fresh auditor)" tf "hotfix security gate" "$HSKL" "Security gate (fresh auditor)"
tf "hotfix block reverts" "$HSKL" "failure REVERTS, never loops" tf "hotfix block reverts" "$HSKL" "failure REVERTS, never loops"
tf "hotfix no verifier" "$HSKL" "No verifier is dispatched at hotfix weight" tf "hotfix no verifier" "$HSKL" "No verifier is dispatched at hotfix weight"
+9 -7
View File
@@ -224,13 +224,8 @@
"Bash(curl * | sh)", "Bash(curl * | sh)",
"Bash(wget * | sh)", "Bash(wget * | sh)",
"Bash(mkfifo *)", "Bash(mkfifo *)",
"Bash(node -e *)",
"Bash(git push *)", "Bash(git push *)",
"Bash(git push)", "Bash(git push)",
"Bash(docker run *)",
"Bash(docker exec *)",
"Bash(docker-compose up*)",
"Bash(docker compose up*)",
"Bash(brew install *)", "Bash(brew install *)",
"Bash(apt install *)", "Bash(apt install *)",
"Bash(apt-get install *)", "Bash(apt-get install *)",
@@ -250,7 +245,7 @@
"disableBypassPermissionsMode": "disable", "disableBypassPermissionsMode": "disable",
"additionalDirectories": [] "additionalDirectories": []
}, },
"model": "opus[1m]", "model": "claude-fable-5-1[1m]",
"hooks": { "hooks": {
"SessionStart": [ "SessionStart": [
{ {
@@ -360,6 +355,11 @@
"inputNeededNotifEnabled": true, "inputNeededNotifEnabled": true,
"skipAutoPermissionPrompt": true, "skipAutoPermissionPrompt": true,
"autoMode": { "autoMode": {
"allow": [
"$defaults",
"Local dev containers: `docker exec`, `docker run`, `docker compose up`/`exec`/`logs`/`ps` against a container running on this workstation whose name does not carry `prod` or `production` (a local Supabase or Postgres such as `supabase_db_*`) is routine development, not a remote shell into a shared host. Running a SQL file or script that lives in the repo inside it (`psql -f`, migrations, verify scripts) and piping the output through `tail` or `grep` passes. Remote Shell Writes, Production Reads and Sensitive Remote Exec apply only to hosts named as sensitive in Environment or carrying `prod`. A literal `DROP`, `TRUNCATE` or `DELETE` without a predicate typed on the command line stays under Mass Delete.",
"Project-local node: `node <file>`, `npm run`, `pnpm` or `yarn` scripts, and `npx` or `pnpm exec` of a package declared in the project's manifest or lockfile, with effects inside the current working directory, pass like `awk` or `echo`. `node -e` that computes or edits inside the working directory passes; the soft block on inline interpreters that delete or write outside it still applies."
],
"soft_deny": [ "soft_deny": [
"$defaults", "$defaults",
"Scope of intent: an instruction clears a SOFT BLOCK for the current turn only. An approval given in an earlier turn is not an approval now, and the same action repeated in a later turn has to be asked for again.", "Scope of intent: an instruction clears a SOFT BLOCK for the current turn only. An approval given in an earlier turn is not an approval now, and the same action repeated in a later turn has to be asked for again.",
@@ -368,7 +368,9 @@
"Sending SIGKILL (`kill -9`) or killing processes by name (`killall`, `pkill`). These reach processes outside this session, including the user's editors, shells, dtach sessions and background jobs, and the target is chosen by a pattern, so a typo kills the wrong thing. Clear only when the user named the process in this turn.", "Sending SIGKILL (`kill -9`) or killing processes by name (`killall`, `pkill`). These reach processes outside this session, including the user's editors, shells, dtach sessions and background jobs, and the target is chosen by a pattern, so a typo kills the wrong thing. Clear only when the user named the process in this turn.",
"Editing more than one file in place in a single command: `sed -i` or `perl -pi` over a glob, or a loop over `git ls-files`. The damage is not loss, since git recovers it, but a diff spanning hundreds of files that nobody reads before committing. `sed -i` on a single named file passes. Clear only when the user asked for the sweep.", "Editing more than one file in place in a single command: `sed -i` or `perl -pi` over a glob, or a loop over `git ls-files`. The damage is not loss, since git recovers it, but a diff spanning hundreds of files that nobody reads before committing. `sed -i` on a single named file passes. Clear only when the user asked for the sweep.",
"Moving or renaming a directory inside the repo (`mv src/api src/api_old`, or any `mv` of a tree). It breaks imports and paths silently, and the breakage surfaces far from the command. Clear only when the user asked for that move.", "Moving or renaming a directory inside the repo (`mv src/api src/api_old`, or any `mv` of a tree). It breaks imports and paths silently, and the breakage surfaces far from the command. Clear only when the user asked for that move.",
"An inline interpreter or `xargs` that deletes, or that writes outside the current working directory: `python3 -c`, `python -c` or `node -e` calling `rmtree`, `remove`, `unlink` or `truncate`; `xargs` feeding `rm`, `mv` or `dd`. `find ... | xargs rm` is the case that matters, since it routes around the `find * -exec rm` deny rule. Reading, computing, and editing a file inside the working directory pass untouched." "An inline interpreter or `xargs` that deletes, or that writes outside the current working directory: `python3 -c`, `python -c` or `node -e` calling `rmtree`, `remove`, `unlink` or `truncate`; `xargs` feeding `rm`, `mv` or `dd`. `find ... | xargs rm` is the case that matters, since it routes around the `find * -exec rm` deny rule. Reading, computing, and editing a file inside the working directory pass untouched.",
"Docker data destruction on this workstation: `docker rm -f`, `docker volume rm` or `prune`, `docker system prune`, `docker compose down -v` (drops named volumes, which hold local database data with no undo), and `docker run` with `--privileged` or a bind mount outside the current working directory. Clear only when the user named the container or volume in this turn.",
"Undeclared node packages: `npx <pkg>`, `pnpm dlx` or `yarn dlx` of a package absent from the manifest and lockfile runs code fetched at call time; `npm install <name>` or `pnpm add <name>` adds a dependency the house rule requires naming first. Clear only when the user named the package in this turn."
], ],
"hard_deny": [ "hard_deny": [
"$defaults", "$defaults",
+11 -5
View File
@@ -116,6 +116,10 @@ RISK: <low/medium — what could go wrong>
obvious fix. obvious fix.
- If the fix is significant (>10 lines, multiple files, - If the fix is significant (>10 lines, multiple files,
behavior change): wait for user approval. behavior change): wait for user approval.
- Then run pass B of `$HOME/.claude/lib/contract-interview.md` against the
FIX PLAN: every VISIBLE / PUBLIC NAME / SCOPE choice it settles that the
bug report left open → one batch of questions, before STEP 3b. The trivial
fast-path is not exempt: a 1-line fix with a visible choice still asks.
## STEP 3b — CHALLENGE THE FIX PLAN (before the contract) ## STEP 3b — CHALLENGE THE FIX PLAN (before the contract)
Unless the fix is the trivial 1-2 line case STEP 3 already fast-paths, the Unless the fix is the trivial 1-2 line case STEP 3 already fast-paths, the
@@ -135,8 +139,8 @@ the STEP 3 approval gate.
Run `$HOME/.claude/lib/contract-interview.md` (main loop). The DIAGNOSIS Run `$HOME/.claude/lib/contract-interview.md` (main loop). The DIAGNOSIS
feeds it: REQUEST verbatim = the bug report as received; ACCEPTANCE CRITERIA feeds it: REQUEST verbatim = the bug report as received; ACCEPTANCE CRITERIA
= the symptom reproduced-then-gone + a regression test present and passing; = the symptom reproduced-then-gone + a regression test present and passing;
FILE SCOPE = the FIX PLAN files. Questions stay proportional (a clear, FILE SCOPE = the FIX PLAN files. Pass A only here (pass B ran at STEP 3); a
reproduced bug → zero). It writes the contract to clear, reproduced bug asks nothing. It writes the contract to
`.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`; keep the path — the `.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`; keep the path — the
executor reads it first and GATE 1 (STEP 6) hands it to a fresh verifier. executor reads it first and GATE 1 (STEP 6) hands it to a fresh verifier.
@@ -162,9 +166,11 @@ ops, no security dispatch. Finish with the BUGFIX-EXEC REPORT."
Parse the `BUGFIX-EXEC REPORT`: Parse the `BUGFIX-EXEC REPORT`:
- `STATUS : DONE` → STEP 6. - `STATUS : DONE` → STEP 6.
- `STATUS : NEED-DECISION` → make the decision HERE (that is reflection), - `STATUS : NEED-DECISION` → route on its `CLASS:` per MID-RUN CLARIFICATION
append it to the plan, re-dispatch a FRESH bugfixer with plan + decision. in `$HOME/.claude/lib/contract-interview.md`: visible / public-name / scope
Max 2 decision round-trips → escalate to the user. → ask the user, verbatim; internal → decide HERE (max 2 such round-trips
→ escalate). Append the answer to the contract `[gated]` and to the plan,
re-dispatch a FRESH bugfixer with plan + decision.
- `STATUS : BLOCKED` → surface the blocker to the user, stop. - `STATUS : BLOCKED` → surface the blocker to the user, stop.
## STEP 6 — VERIFY + SECURE + PRE-COMMIT GATE + COMMIT (main loop, LRN-083) ## STEP 6 — VERIFY + SECURE + PRE-COMMIT GATE + COMMIT (main loop, LRN-083)
+15 -10
View File
@@ -85,9 +85,9 @@ MEMORY; feed STEP 1 PLAN. Inline consumption — reader = planner, no injection.
## STEP 0.7 — CONTRACT ## STEP 0.7 — CONTRACT
Run `$HOME/.claude/lib/contract-interview.md` (main loop — you are it). It Run `$HOME/.claude/lib/contract-interview.md` (main loop — you are it). It
captures the request verbatim, asks 0-3 questions PROPORTIONAL to ambiguity captures the request verbatim, runs pass A (gaps: outcome, scope,
(a complete request → zero questions, silent), derives testable acceptance constraints — a complete request goes through silently), derives testable
criteria + file scope, and writes the contract to acceptance criteria + file scope, and writes the contract to
`.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`. Keep the path — the `.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`. Keep the path — the
executor reads it first and GATE 1 (STEP 4) hands it to a fresh verifier. executor reads it first and GATE 1 (STEP 4) hands it to a fresh verifier.
@@ -114,8 +114,11 @@ PLAN:
[ ] <test file> — <test to add> [ ] <test file> — <test to add>
``` ```
If the approach is ambiguous: ask the user ONE focused question BEFORE Then run pass B of `$HOME/.claude/lib/contract-interview.md` against this
dispatching — never after (the executor cannot relay questions). plan: every VISIBLE / PUBLIC NAME / SCOPE choice the plan settles that the
request left open → one batch of questions BEFORE dispatching; answers land
in the contract's CLARIFICATIONS `[gated]` and in the plan. A choice that
surfaces only during execution comes back as `NEED-DECISION` (STEP 3).
## STEP 1b — CHALLENGE THE PLAN (before branching) ## STEP 1b — CHALLENGE THE PLAN (before branching)
The STEP 1 plan is a reflection worth attacking before a branch is spent on it. The STEP 1 plan is a reflection worth attacking before a branch is spent on it.
@@ -125,8 +128,8 @@ Persist it to `.claude/tasks/plans/<date>-<slug>-<HHMM>.md`, then run
Three blind challengers attack it; RE-THINK every aspect a BLOCKER lands (a named Three blind challengers attack it; RE-THINK every aspect a BLOCKER lands (a named
plan change, or `[deferred]`), re-challenge once if the plan materially changed. The plan change, or `[deferred]`), re-challenge once if the plan materially changed. The
STEP 3 executor receives the REVISED plan. Before dispatch, print a CHALLENGE SUMMARY STEP 3 executor receives the REVISED plan. Before dispatch, print a CHALLENGE SUMMARY
(BLOCKERs addressed / deferred / lenses returned), surfacing any deferred BLOCKER via (BLOCKERs addressed / deferred / lenses returned), surfacing any deferred BLOCKER in
STEP 1's one-question gate. the STEP 1 pass B batch.
## STEP 2 — BRANCH ## STEP 2 — BRANCH
@@ -150,9 +153,11 @@ Finish with the FEAT-EXEC REPORT."
Parse the `FEAT-EXEC REPORT`: Parse the `FEAT-EXEC REPORT`:
- `STATUS : DONE` → STEP 4. - `STATUS : DONE` → STEP 4.
- `STATUS : NEED-DECISION` → make the decision HERE (that is reflection), - `STATUS : NEED-DECISION` → route on its `CLASS:` per MID-RUN CLARIFICATION
append it to the plan, re-dispatch a FRESH feater with plan + decision. in `$HOME/.claude/lib/contract-interview.md`: visible / public-name / scope
Max 2 decision round-trips → escalate to the user. → ask the user, verbatim; internal → decide HERE (max 2 such round-trips
→ escalate). Append the answer to the contract `[gated]` and to the plan,
re-dispatch a FRESH feater with plan + decision.
- `STATUS : BLOCKED` → surface the blocker to the user, stop. - `STATUS : BLOCKED` → surface the blocker to the user, stop.
## STEP 4 — VERIFY + SECURE (fresh gates, bounded loops) ## STEP 4 — VERIFY + SECURE (fresh gates, bounded loops)
+19 -7
View File
@@ -47,6 +47,10 @@ git log --oneline -3
as `/bugfix` (root-cause investigation, then a scoped fix)." as `/bugfix` (root-cause investigation, then a scoped fix)."
- Settle the proposed fix HERE — the executor cannot ask questions, so the - Settle the proposed fix HERE — the executor cannot ask questions, so the
exact edit (what changes, in which file(s)) must be closed before dispatch. exact edit (what changes, in which file(s)) must be closed before dispatch.
- Then run pass B of `$HOME/.claude/lib/contract-interview.md` against that
edit: a VISIBLE / PUBLIC NAME / SCOPE choice the bug description leaves
open (which way the icon aligns, the label's wording) → ask before
dispatch. A typo or a wrong value asks nothing.
OPTIONAL — memory check (exempt by default; hotfix = obvious fix, mirror of its capitalize OPTIONAL — memory check (exempt by default; hotfix = obvious fix, mirror of its capitalize
skip). For a RECURRING or urgent bug only, a quick blockers-only glance may save time: skip). For a RECURRING or urgent bug only, a quick blockers-only glance may save time:
@@ -66,8 +70,9 @@ Follow `$HOME/.claude/lib/design-gate.md`:
## STEP 1.7 — CONTRACT (silent autofill) ## STEP 1.7 — CONTRACT (silent autofill)
Run `$HOME/.claude/lib/contract-interview.md` at hotfix weight: **zero Run `$HOME/.claude/lib/contract-interview.md` at hotfix weight: pass A is a
questions ever** (a hotfix is an obvious fix by definition). Autofill the silent autofill (a hotfix is an obvious fix by definition); pass B already
ran at STEP 1, ask nothing more here. Autofill the
contract — REQUEST verbatim = the bug description as given; ACCEPTANCE contract — REQUEST verbatim = the bug description as given; ACCEPTANCE
CRITERIA = "symptom gone; build/tests green"; FILE SCOPE = the 1-2 target CRITERIA = "symptom gone; build/tests green"; FILE SCOPE = the 1-2 target
files from STEP 1. It writes `.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`. files from STEP 1. It writes `.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`.
@@ -135,8 +140,14 @@ security dispatch, no revert. Finish with the HOTFIX-EXEC REPORT."
Parse the `HOTFIX-EXEC REPORT`: Parse the `HOTFIX-EXEC REPORT`:
- `STATUS : DONE` → STEP 4 (the SMOKE line in the report decides pass/fail - `STATUS : DONE` → STEP 4 (the SMOKE line in the report decides pass/fail
there; DONE here means execution completed, not that it verified clean). there; DONE here means execution completed, not that it verified clean).
- `STATUS : BLOCKED` → if any edits were made, revert ONLY the executor's - `STATUS : BLOCKED` with `CLASS: visible | public-name | scope` in NOTES →
files: `git restore --source=$PRE -- <FILE(S) from the report>` and delete the executor halted at an open choice before editing (nothing to revert):
ask the user per MID-RUN CLARIFICATION in
`$HOME/.claude/lib/contract-interview.md`, append the answer to the
contract `[gated]`, re-dispatch ONCE with the closed choice. This is the
one re-dispatch hotfix allows; it is not a retry of a failed attempt.
- `STATUS : BLOCKED` otherwise → if any edits were made, revert ONLY the
executor's files: `git restore --source=$PRE -- <FILE(S) from the report>` and delete
any NEW file the report lists (untracked, absent from $PRE). Never any NEW file the report lists (untracked, absent from $PRE). Never
`git restore .` — it would wipe the tolerated pre-existing edits too. `git restore .` — it would wipe the tolerated pre-existing edits too.
Surface the blocker to the user; STOP. One attempt only — hotfix never Surface the blocker to the user; STOP. One attempt only — hotfix never
@@ -227,9 +238,10 @@ trivial hotfix still produces a `chore(memory): journal — …` commit (Frame 2
- Reflection (LOCATE, contract, gate decisions) NEVER leaves this main - Reflection (LOCATE, contract, gate decisions) NEVER leaves this main
loop; execution NEVER stays in it — the executor is the sonnet-pinned loop; execution NEVER stays in it — the executor is the sonnet-pinned
hotfixer subagent (BDR-066). hotfixer subagent (BDR-066).
- The executor is dispatched FRESH, once — hotfix never re-dispatches (no - The executor is dispatched FRESH, once — hotfix never re-dispatches after
decision round-trips; a blocked or failed attempt reverts and escalates a failed or blocked attempt (it reverts and escalates to `/bugfix`, it
to `/bugfix`, it does not retry). does not retry). Sole exception: a class-tagged BLOCKED answered by the
user (STEP 3), re-dispatched once with the closed choice.
- Design gate only if CSS/style signals detected. See STEP 1.5. - Design gate only if CSS/style signals detected. See STEP 1.5.
- **Revert-not-loop preserved**: smoke FAIL or security BLOCK → - **Revert-not-loop preserved**: smoke FAIL or security BLOCK →
file-scoped revert from `$PRE` (STEP 4's protocol — never `git file-scoped revert from `$PRE` (STEP 4's protocol — never `git
+5 -2
View File
@@ -58,8 +58,8 @@ In both cases: MANDATORY STOP until user answers remaining questions. Produce PR
**Then run `$HOME/.claude/lib/contract-interview.md`** seeded from the BRIEF: **Then run `$HOME/.claude/lib/contract-interview.md`** seeded from the BRIEF:
REQUEST verbatim = the user's project description; ACCEPTANCE CRITERIA = the REQUEST verbatim = the user's project description; ACCEPTANCE CRITERIA = the
V1 FEATURES (each testable); FILE SCOPE = the planned tree. No new questions V1 FEATURES (each testable); FILE SCOPE = the planned tree. Pass A is covered
(the interview already asked). It writes by the interview; pass B runs at STEP 3 against the DESIGN. It writes
`.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`; the DESIGN approved at STEP `.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`; the DESIGN approved at STEP
4 ENRICHES it, and STEP 9's verifier judges the MVP against the enriched 4 ENRICHES it, and STEP 9's verifier judges the MVP against the enriched
contract. contract.
@@ -70,6 +70,9 @@ Load `$HOME/.claude/agents/analyzer.md`. Analyze BRIEF: existing code, stack con
## STEP 3 — DESIGN ## STEP 3 — DESIGN
Invoke `superpowers:brainstorming` with BRIEF + ANALYSIS REPORT. Invoke `superpowers:brainstorming` with BRIEF + ANALYSIS REPORT.
Produce DESIGN: stack+versions, full folder tree, module responsibilities, data flow, interfaces (signatures only), config+tooling, test strategy, resolved decisions, prereqs list. Produce DESIGN: stack+versions, full folder tree, module responsibilities, data flow, interfaces (signatures only), config+tooling, test strategy, resolved decisions, prereqs list.
Then run pass B of `$HOME/.claude/lib/contract-interview.md` against the DESIGN
(minus what the BRIEF and the brainstorm settled): one batch before STEP 4;
answers append to the contract `[gated]`.
## STEP 4 — VALIDATION GATE #1 ★ MANDATORY STOP ## STEP 4 — VALIDATION GATE #1 ★ MANDATORY STOP
Present: Present:
+4
View File
@@ -116,6 +116,10 @@ Refine request into validated design via Socratic questioning. Don't proceed unt
Invoke `superpowers:writing-plans` with the validated design AND the 0d digest: every task Invoke `superpowers:writing-plans` with the validated design AND the 0d digest: every task
must be consistent with the in-force constraints; where a task implements or affects one, must be consistent with the in-force constraints; where a task implements or affects one,
note the ID inline. Break design into tasks (2-5 min each). Each task: exact file paths, full code, verification steps. note the ID inline. Break design into tasks (2-5 min each). Each task: exact file paths, full code, verification steps.
Then run pass B of `$HOME/.claude/lib/contract-interview.md` against the plan:
every VISIBLE / PUBLIC NAME / SCOPE choice the plan settles that neither the
request nor the STEP 1 brainstorm settled (check the contract's CLARIFICATIONS
first) → one batch before STEP 2b; answers append to the contract `[gated]`.
## STEP 2b — CHALLENGE THE PLAN (adversarial, before the gate) ## STEP 2b — CHALLENGE THE PLAN (adversarial, before the gate)
Before the human sees the plan, harden it. Run `$HOME/.claude/lib/challenge-plan.md`: Before the human sees the plan, harden it. Run `$HOME/.claude/lib/challenge-plan.md`:
+15 -2
View File
@@ -79,8 +79,11 @@ one repo feeds the classifier false facts in all the others.
### `ask` is not a prompt under auto mode ### `ask` is not a prompt under auto mode
Verified in-session (LRN-146): with `defaultMode: auto`, Bash rules in Verified in-session (LRN-146, re-verified on 2.1.273 on 2026-09-16 with a
`permissions.ask` were auto-approved and raised no prompt. `deny` is the only `node -e` probe matching an `ask` rule): with `defaultMode: auto`, Bash rules
in `permissions.ask` were auto-approved and raised no prompt. The auto-mode
docs claim the opposite for "content-scoped" rules such as `Bash(git push *)`;
the observed behavior wins until a probe shows a prompt. `deny` is the only
tier the classifier cannot lift. tier the classifier cannot lift.
So for a destructive command you want gated but still reachable, `ask` is the So for a destructive command you want gated but still reachable, `ask` is the
@@ -94,11 +97,21 @@ it. Keep `deny` for what must never run at all.
| Never runs, no exception, matchable by a command pattern | `permissions.deny` | | Never runs, no exception, matchable by a command pattern | `permissions.deny` |
| Never runs, and a pattern cannot express it (a read then a send, a prod target) | `autoMode.hard_deny` | | Never runs, and a pattern cannot express it (a read then a send, a prod target) | `autoMode.hard_deny` |
| Runs when the user asks for it, blocked otherwise | `autoMode.soft_deny` | | Runs when the user asks for it, blocked otherwise | `autoMode.soft_deny` |
| Runs freely when a condition holds that only the classifier can judge (a local dev container, a package declared in the lockfile) | `autoMode.allow` |
| Runs freely | `permissions.allow`, or nothing | | Runs freely | `permissions.allow`, or nothing |
`permissions.ask` is not on this list on purpose. Under `defaultMode: auto` it `permissions.ask` is not on this list on purpose. Under `defaultMode: auto` it
gates nothing. gates nothing.
`autoMode.allow` is the exception tier: inside the classifier an `allow` entry
overrides a matching `soft_deny`, built-in or yours, so word it as narrowly as
the condition allows. It is also the only tier that can open an interpreter:
under auto mode Claude Code suspends the static allow rules that grant
arbitrary code execution (`Bash(*)`, wildcarded interpreters such as
`Bash(node *)`), so those commands reach the classifier whatever
`permissions.allow` says. `awk` and `echo` pass through a static rule; `node`
cannot.
### Scope of intent ### Scope of intent
A `soft_deny` clears on the user's instruction, and this config scopes that to A `soft_deny` clears on the user's instruction, and this config scopes that to