From b96fab771991917381b0d6cc2fad1977a4f95f01 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Thu, 17 Sep 2026 11:43:39 +0200 Subject: [PATCH] chore(memory): BDR-091 BDR-092 LRN-155..157, journal 2026-09-16 --- .claude/memory/decisions.md | 21 +++++++++++++++++++++ .claude/memory/journal.md | 5 +++++ .claude/memory/learnings.md | 21 +++++++++++++++++++++ .claude/tasks/TODO.md | 4 ++-- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/.claude/memory/decisions.md b/.claude/memory/decisions.md index 0a585a0..2cfd963 100644 --- a/.claude/memory/decisions.md +++ b/.claude/memory/decisions.md @@ -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-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-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. - **Status**: accepted. - **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 — `, 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 `, `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 `). `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]]. diff --git a/.claude/memory/journal.md b/.claude/memory/journal.md index 2e317d5..481c517 100644 --- a/.claude/memory/journal.md +++ b/.claude/memory/journal.md @@ -471,3 +471,8 @@ rules: - `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. - `.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. diff --git a/.claude/memory/learnings.md b/.claude/memory/learnings.md index b7b7d4a..67e1cd9 100644 --- a/.claude/memory/learnings.md +++ b/.claude/memory/learnings.md @@ -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-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-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. - **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]]. + +## 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. diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index 63c51f1..fc5bf9d 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -20,7 +20,7 @@ for this change, diff reviewed on the branch). - [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 -- [ ] A5 registries at capitalize: LRN (doc vs observed `ask` under auto, +- [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) @@ -47,7 +47,7 @@ SCOPE ask, internal technical choices never. Spec: - [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 -- [ ] P10 registries at capitalize: BDR (supersedes the one-question rule), +- [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)