chore(memory): BDR-116 amendment (forget) + journal/TODO/contract w3c — feat model-router wave 3-C
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# PLAN r3 — model-router wave 3-C: `/route forget` (2026-10-11)
|
||||
|
||||
r1 → r2 after simplicity CONCERNS(3), correctness CONCERNS(4), robustness
|
||||
CONCERNS(4); r2 → r3 after the confirmation (correctness CONCERNS(3)).
|
||||
Precedence r3 > r2 > r1. Contract
|
||||
`.claude/tasks/contracts/2026-10-11-model-router-w3c-forget-1457.md`.
|
||||
Base W3-B (604a6c4 + 32b71d2 + 938c3d1). Corrected facts: `Memory` holds
|
||||
ok/ask/askLocal/base/confirmed/about/local/key only (no `changed`, no
|
||||
`projects`); the composer gate lives in `registerCommandHook`, not in
|
||||
`handleCommand`; `writeNow` always writes once the patch ran and toasts
|
||||
UPDATED, returns false on refusal, throws when the rebuild fails after a
|
||||
landed write; `show` prints no command list (the only visible list is the
|
||||
`argumentHint`); `routeMainBySkill` copies a route into `turnMain`/`runMain`
|
||||
and a rebuild never touches those slots; the shipped file holds one changed
|
||||
row (security-auditor verify → judge) whose frontmatter floor was aligned
|
||||
to `opus`: restoring it without re-aligning the floor makes the census
|
||||
FAIL (no `changed` entry = no WARN exemption).
|
||||
|
||||
## Behaviour (composer `/route forget <arg>`, exactly ONE argument)
|
||||
- Parsing: no arg or more than one → "usage: /route forget <name|all|projects>".
|
||||
Reserved words `all` and `projects`; a row or phase named like them
|
||||
cannot be forgotten by name (answer says "edit routing.json by hand").
|
||||
- Guard: while a dialog is open (`st.asking !== null`) → "answer the open
|
||||
dialog first", nothing written. The forget confirmation TAKES the slot
|
||||
itself (`st.asking = 'forget:<target>'` in a try/finally around `askOr`)
|
||||
so no first-use dialog opens under it and two forgets cannot stack.
|
||||
- The decision runs on the FRESH file, twice: a PRE-read (after awaiting
|
||||
`st.writes`, so a landed Keep is visible) decides the no-write paths and
|
||||
the confirmation counts; the PATCH itself re-runs `forgetPlan(file,
|
||||
target)` on `writeNow`'s own freshly read `file` (the closure exports the
|
||||
counts actually applied; the answer reports those). Pre-read missing or
|
||||
unparsable → the writer's refusal path ("routing.json is missing or
|
||||
unreadable: nothing saved"); nothing to remove, no refused restore and
|
||||
the key(s) not in `st.asked` → "nothing to forget for <name>" / "nothing
|
||||
to forget", NO write; only a refused restore → its reason, NO write;
|
||||
key(s) only in `st.asked` → reset them, NO write, "asked again at the
|
||||
next use, nothing was saved" (when the key is decided by the machine
|
||||
override or a project row, say "still decided by …" instead).
|
||||
- `<name>`: for EVERY kind in KINDS (skills, agents, phases): delete
|
||||
`confirmed.<kind>.<name>`; restore `changed.<kind>.<name>` → row =
|
||||
`from` ONLY when `from` is a string, the row exists, the row equals
|
||||
`changed.to`, and `from` is a phase of `file.phases` or
|
||||
`DEFAULT_CONFIG.phases` (else the entry is kept and the answer says
|
||||
why); delete `projects[k].<kind>.<name>` for every k, pruning an emptied
|
||||
kind table then an emptied `projects[k]`; then `st.asked.delete(key)`.
|
||||
- `all`: the same over every entry of `confirmed`, `changed`, `projects`;
|
||||
`projects`: only `projects` emptied. Both ASK FIRST through `askOr`:
|
||||
"Forget <n> decision(s): <m> row(s) restored, <k> project exception(s)
|
||||
in <r> repo(s)?" options ["Cancel", "Forget"] (Cancel first; any other
|
||||
answer, a dismissed dialog or headless = cancelled, nothing written);
|
||||
skipped when every count is zero ("nothing to forget").
|
||||
- Write: ONE `writeRouting` patch (the closure carries the counts out);
|
||||
outcomes: false → "nothing saved" (the writer's toast already explains);
|
||||
true → asked keys deleted, config rebuilt; a rebuild failure after a
|
||||
landed write is signalled by a dedicated error class (`RebuildFailed`,
|
||||
thrown by `writeNow` there and only there) → "saved, config not rebuilt:
|
||||
/route reload" (asked keys untouched); any other throw (patch, fs.write)
|
||||
→ "forget not applied (<err kind>)", asked keys untouched.
|
||||
- Answer, one formatter (used by every form, per restored row): "forgot
|
||||
<label>: <c> confirmed, <m> row(s) restored, <k> project exception(s)
|
||||
removed[, <j> restore(s) kept: <reasons>]" + per restored row " (<kind>.
|
||||
<name> → <from>: <alias> at <effort>; if <file> was aligned to <to>, set
|
||||
model: <alias>, effort: <effort> and its lock in
|
||||
lib/tests/model-routing.test.sh, then `make test`)" where `<file>` =
|
||||
`agents/<name>.md` or `skills/<name>/SKILL.md` (no file clause for a
|
||||
built-in agent such as Explore/Plan), alias = head of the phase's tier in
|
||||
the SHIPPED `DEFAULT_CONFIG.tiers` (or the phase's model), effort from
|
||||
the phase;
|
||||
+ "; ~/.claude/model-router.json still sets <name> and wins here" when
|
||||
`mem.local` has it, checked only after a successful rebuild;
|
||||
+ "; current run may keep <phase>: /route clear to apply" when a
|
||||
restored SKILL row's `to` phase equals the phase of `runMain` or
|
||||
`turnMain` with source `run`/`skill`;
|
||||
+ "; other live sessions see it after /route reload".
|
||||
- `all` prunes emptied kind tables in `confirmed`, `changed`, `projects`
|
||||
(a kept unrestorable `changed` entry stays and is reported).
|
||||
- `decisions` count = confirmed entries + changed entries + project rows
|
||||
(an entry counted once per table it sits in).
|
||||
- The route tool has no `forget`; sub-agents cannot run commands; the
|
||||
origin gate is the existing one in `registerCommandHook`.
|
||||
|
||||
## Docs and comments (same commit)
|
||||
- `argumentHint` gains `forget <name|all|projects>` (locked by a grep in
|
||||
the contract CHECK, not by a kit test); register.ts comment "Only a
|
||||
dialog answer or `/route ask` writes" → "… or `/route ask` / `/route
|
||||
forget` writes".
|
||||
- README.md: the `/route` subcommand list (~138) gains `forget
|
||||
<name|all|projects>` and the writers sentence (~140) names it;
|
||||
USAGE.md: the sentence "`/route pending` liste …, `/route ask off|on`
|
||||
…" (~190) gains `/route forget <nom|all|projects>` (efface une
|
||||
décision, la ligne revient à la phase livrée); CHANGELOG.md: the
|
||||
argument form (~10) and the writers sentence (~11); `lib/effort-shift.md`
|
||||
commands line.
|
||||
|
||||
## Steps
|
||||
- [ ] S1 `forgetPlan(file, target)` (pure over the fresh file data:
|
||||
removals, restores, counts, skipped restores with reasons) and
|
||||
`forgetCommand($, st, args)` (arity, reserved words, asking guard,
|
||||
read, no-write paths, confirmation for all/projects, write via
|
||||
`writeRouting` with a patch that applies the plan, outcome branches,
|
||||
asked reset, answer); `handleCommand` case `forget`; argumentHint.
|
||||
- [ ] S2 docs/comments above.
|
||||
- [ ] S3 kit tests, folded: forget a confirmed skill → entry gone, asked
|
||||
again in the same session, `/route pending` lists it; forget a changed
|
||||
agent → row restored, entry gone, the next spawn on the restored model,
|
||||
answer carries the realign clause with the file and values; forget a
|
||||
phase → confirmed.phases gone, T3 asks again; forget a name with a
|
||||
project exception in two repos → both rows removed, emptied keys
|
||||
pruned; undecided row → "nothing to forget", no write; asked-only key
|
||||
→ reset, no write; unknown name → "nothing to forget for", no write;
|
||||
arity (two words → usage); reserved word collision answer; open dialog
|
||||
→ refused; `all` cancelled → nothing written; `all` confirmed →
|
||||
everything empty, counts in the answer; `projects` → only projects
|
||||
emptied; restore refused (row ≠ to / from unknown) → entry kept, reason;
|
||||
override-pinned row → the "wins here" clause; restored skill with a run
|
||||
slot → the "/route clear" clause; missing file → "nothing saved";
|
||||
rebuild throw → "saved, config not rebuilt"; route tool `clear` leaves
|
||||
decisions; the existing origin test extended with `forget x`;
|
||||
`failWrite` → "forget not applied"; (argumentHint: grep lock in the
|
||||
contract, no kit test).
|
||||
- [ ] S4 live smoke after the feater (orchestrator, user): one `/route
|
||||
forget projects` (or a name) typed in the terminal with the dialog
|
||||
answered → recorded `[gated]` in the contract (`$.ui.ask` from an
|
||||
`immediate` command is unverified live).
|
||||
- Disposition: honors BDR-116 (user-only writers, same path/caps; the
|
||||
shared tracked memory stays the user's choice), LRN-210 (one clause per
|
||||
test, folded where one setup proves two clauses), LRN-212.
|
||||
|
||||
## Challenge ledger (r1 → r2)
|
||||
- simp 1, corr 1, rob 2 (false `st.mem` premise) → plan over the fresh
|
||||
file, closure counts, pre-read no-write paths.
|
||||
- simp 2, corr 2, rob 7 (kind order, reserved words, no-op writes) → all
|
||||
kinds, removed-count rule, arity, reserved-word answer.
|
||||
- simp 3, corr 4/9/10, rob (docs) → docs/comment scope, AC4 = argumentHint.
|
||||
- simp 4 (projects form) → kept, one-liner, with the confirmation.
|
||||
- simp 5/6, corr 3/7, rob 3/9 (restore + floors + validation) → guarded
|
||||
restore, realign clause with file + values, one formatter.
|
||||
- corr 5, rob 8 (override-pinned) → "wins here" clause.
|
||||
- corr 6, rob 6 (dialog in flight) → refuse while asking.
|
||||
- corr 8, rob 4 (run slot) → "/route clear" clause.
|
||||
- rob 1 (no confirmation) → askOr confirmation for all/projects, Cancel first.
|
||||
- rob 5 (three write outcomes) → branches.
|
||||
- simp 7 (test folds) → S3 folded.
|
||||
|
||||
## Confirmation ledger (r2 → r3)
|
||||
- conf 1 (throw mapping) → `RebuildFailed` class; other throws "not applied".
|
||||
- conf 2 (slot not taken) → forget takes `st.asking` around its askOr.
|
||||
- conf 3 (stale plan at write) → patch recomputes on its own file; pre-read
|
||||
awaits `st.writes`; applied counts reported.
|
||||
- conf 4 (run-slot rule) → kind skills, source run/skill, phase == `to`.
|
||||
- conf 5 (realign clause) → conditional, shipped tiers, no file for built-ins.
|
||||
- conf 6 (argumentHint test) → grep lock in the contract CHECK.
|
||||
- conf 7 (live dialog from a command) → S4 smoke.
|
||||
- conf 8 (ambiguities a-f) → sentences added.
|
||||
- conf 9 (docs anchors) → README list + USAGE anchor + CHANGELOG form.
|
||||
Reference in New Issue
Block a user