chore(memory): BDR-116 amendment + EVAL-044 + journal/TODO/contract w3b — feat model-router wave 3-B
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# PLAN r3 — model-router wave 3-B: a dialog with context, model then effort on change (2026-10-11)
|
||||
|
||||
r1 → r2 after simplicity CONCERNS(3), robustness FATAL(5), correctness
|
||||
FATAL(8): all three rejected inline-route rows and dialog edits of phases.
|
||||
r2 → r3 after the confirmation (correctness CONCERNS(2)). Precedence r3 > r2 > r1. Contract `.claude/tasks/contracts/2026-10-11-model-router-w3b-dialog-1240.md`.
|
||||
Base = W3-A (22455c0, plan r4, BDR-116). Facts: `agent.offer` payload carries
|
||||
`description` and `source`; `$.ui.ask` takes 2-4 labels + Other; `cfg.models`
|
||||
maps the four aliases to ids; each default alias heads one tier (best fable,
|
||||
big opus, work sonnet, cheap haiku); `readCapped` exists (size cap, stat).
|
||||
|
||||
## Decisions
|
||||
- D1 rows stay PHASE NAMES everywhere (routing.json, projects, override).
|
||||
No inline route rows. The dialog's model+effort choice maps to a phase.
|
||||
- D2 the dialog never edits a phase: T3 offers Later / Keep only, with
|
||||
context. Phases change by hand in routing.json + DEFAULT_CONFIG (census
|
||||
lock kept). BDR-116 unchanged.
|
||||
- D3 one description rule for every kind, hardened read, no cache.
|
||||
|
||||
## Texts (ASK1)
|
||||
- `clean(s, n)`: FIRST fold every `\s` and `\p{Z}` (incl. U+2028/2029) to
|
||||
a space, THEN strip `\p{Cc}` and `\p{Cf}`, collapse spaces, trim; when
|
||||
longer than n code points keep n-1 and append "…" (total ≤ n). Applied to
|
||||
every description and `about` (no answer echo anywhere).
|
||||
- Leads are KEPT as the first words (existing assertions stay valid):
|
||||
"First route for /feat — <desc>. Routed to reflect (<about>): claude-fable-5-1
|
||||
at high. OK?", "First dispatch of security-auditor — <desc>. Routed to
|
||||
verify (<about>): claude-sonnet-5-5 at xhigh. OK?", "First use of
|
||||
orchestrate on the main loop — <about>; used by …: … OK?". Without a
|
||||
description the " — <desc>" part is omitted; without `about` the
|
||||
parenthesis is omitted.
|
||||
- Skill row desc = `skillDesc($, st, name)`: name must match
|
||||
`^[A-Za-z0-9][A-Za-z0-9._:-]*$` (else name only); `readCapped` of
|
||||
`${HOME}/.claude/skills/<name>/SKILL.md` (stat kind must be `file`, size
|
||||
cap as the override; HOME from `$.env.get`, unset → name only); parse the
|
||||
frontmatter `description:` in its five forms (plain, `|`, `>`/`>-`,
|
||||
single-quoted with `''`, double-quoted): unquote, join the block lines
|
||||
with spaces, cut at the first sentence end (`. ` / `.` at end), then
|
||||
clean(…, 140); any failure → name only. Read at ask time, once (the key
|
||||
is asked at most once per session). The kind check lives in a
|
||||
skill-specific wrapper around `readCapped` (config reads untouched).
|
||||
- Agent row: the offer description goes through the SAME rule (sentence
|
||||
cut + clean 140) WHEN STORED at `agent.offer` (bounded cache
|
||||
`Map<type, {source, description}>`); absent → name only.
|
||||
- Phase (T3): "First use of orchestrate on the main loop — <about>; used by
|
||||
<n> rows[, the derived route of dispatches][, <k> prompt rule(s)]:
|
||||
claude-fable-5-1 at medium. OK?"; consumers derived: `orchestrate` →
|
||||
"the derived route of dispatches" (hardcoded, pushOrchestrate), prompt
|
||||
rules counted from `cfg.prompt.filter(r => r.phase === name)` (floor
|
||||
rules named "floor rule"). Options ["Later", "Keep"].
|
||||
- Row options: ["Later", "Keep", "Change"]. At EVERY step the `askOr` LATER
|
||||
(dismissed, headless) is checked first and is silent; any other
|
||||
non-label answer = Later + toast "answer not recognised, default kept"
|
||||
(no echo). `ALTS`, the alt slice in `optionsFor` and the free-text phase
|
||||
branch of `decide` are removed.
|
||||
- Main-row texts keep the real-decision rule (decideFor/mainEffort); spawn
|
||||
texts keep spawnTarget + explicit effort.
|
||||
|
||||
## Change flow (rows only)
|
||||
- ASK2 "Which model for `<name>`?": four FIXED labels from the default
|
||||
aliases, each with its tier role ("fable (best)", "opus (big)", "sonnet
|
||||
(work)", "haiku (cheap)"; role = the tier headed by the alias in
|
||||
`cfg.tiers`, "(custom)" when none); Other or an unknown label = Later +
|
||||
toast.
|
||||
- ASK3 "Which effort on <alias>?": the efforts of the phases (in
|
||||
`cfg.phases`) whose tier head is the chosen alias, deduplicated and
|
||||
sorted ASCENDING by LEVELS (fable: medium high xhigh max; opus: xhigh;
|
||||
sonnet: low medium high xhigh; haiku: low) → at most 4 labels (the
|
||||
first 4); exactly one → ASK3 skipped; zero → Later + toast "no phase
|
||||
uses <alias>"; Other = Later.
|
||||
- Target phase = the row's CURRENT phase when it matches (alias, effort),
|
||||
else the first matching phase in `cfg.phases` order. Same phase as now →
|
||||
treated as Keep (no `changed` entry).
|
||||
- ASK4 scope as W3-A: ["Everywhere", "This project only"], or ["Everywhere",
|
||||
"Later"] when there is no repo key.
|
||||
- After a CHANGE on a MAIN row (T1, never on Keep), computed in
|
||||
`confirmSkill` from `skillRow` + `decideFor`: toast "`/<name>` now runs
|
||||
<id> at <effort>" plus ", main moves up only: `/route switch on` allows
|
||||
a downgrade" when the chosen alias ranks below the session model and the
|
||||
switch is off; the new route is re-picked into the current decision
|
||||
(T1/T2 as W3-A).
|
||||
- Storage unchanged from W3-A: `changed.<kind>.<name> = {from (first), to}`,
|
||||
`confirmed.<kind>.<name> = to` (strings), `projects[key]` rows.
|
||||
|
||||
## `about`
|
||||
- routing.json: `phases.<name>.about` (≤ 120 chars, clean) on the 11
|
||||
phases; loaded into `mem.about: Record<phase, string>` (part of the
|
||||
memory rebuilt with the config, so it survives /clear with cfg; never on
|
||||
`Route`, never in DEFAULT_CONFIG; the census `code_phase` regex
|
||||
untouched); a non-string or overlong `about` is dropped + logged once;
|
||||
a phase without `about` → no parenthesis. acceptPhase ignores the key.
|
||||
|
||||
## Steps
|
||||
- [ ] S1 routing.json `about` ×11 (one line each from the W2 phase table);
|
||||
loader + map + validation.
|
||||
- [ ] S2 descriptions: offer cache `{source, description}`; `skillDesc`
|
||||
(allowlist, readCapped, kind check, YAML forms, sentence cut); `clean`.
|
||||
- [ ] S3 ASK1 texts + options Later/Keep/Change; Other → Later + toast;
|
||||
dead code removed (`ALTS`, alt slice, free-text branch).
|
||||
- [ ] S4 change flow ASK2 → ASK3 (derived efforts) → ASK4; target-phase
|
||||
rule; same-phase = Keep; main toast with the real decision.
|
||||
- [ ] S5 `lib/effort-shift.md` dialog lines (≤ 4 changed).
|
||||
- [ ] S6a migrate the W3-A dialog tests: every `asks([...])` answering a
|
||||
phase name at ASK1 becomes Keep or a Change chain (ASK1 → ASK2 →
|
||||
[ASK3] → ASK4; the `asked[n]` indexes shift accordingly); option-list
|
||||
assertions become Later/Keep/Change (T3 Later/Keep); the lead-text
|
||||
assertions (:2266 "First dispatch of feater", :2395 "First use of
|
||||
orchestrate on the main loop") stay valid by construction; :1914
|
||||
expects the new toast "answer not recognised, default kept"; the two
|
||||
free-text tests (:1871 "a free-text phase counts as an alt", :1878
|
||||
"equal to the current phase is a Keep") are DELETED (authorized:
|
||||
they test the removed branch); "a phase name answer is a Later" kept.
|
||||
World/helper extensions: `offerOf` takes a description; the World
|
||||
gains a per-path stat `kinds` override and a HOME override (unset).
|
||||
- [ ] S6b new tests, one per clause: skill text with each YAML form (plain,
|
||||
`|`, `>-`, 'sq' with `''`, "dq"), missing SKILL.md → name only, a bad
|
||||
name (`../x`) → name only, stat kind ≠ file → name only, a SKILL.md
|
||||
over the cap → name only, HOME unset → name only, a 500-char
|
||||
description with control, bidi and U+2028 chars → ≤ 140 clean code
|
||||
points ending in "…"; a Keep on a main row → no "now runs" toast;
|
||||
a Change → the toast with the switch hint; zero-effort alias → Later
|
||||
+ toast; dismissed ASK2 → silent Later; agent text from the offer
|
||||
description; T3 text with `about` and consumers; ASK2 labels; ASK3
|
||||
derived efforts per alias (fable 4, opus → skipped, sonnet 4, haiku →
|
||||
skipped); security-auditor Change → opus → (skipped) → Everywhere →
|
||||
row `judge`, spawn on claude-opus-5-5 at xhigh now; feater Change →
|
||||
sonnet → high → `write`; Explore Change → sonnet → medium → stays
|
||||
`explore` (current phase wins) → treated as Keep, no `changed`;
|
||||
`/feat` Change → sonnet → medium → `implement`, toast names "moves up
|
||||
only" with the switch off; Other at ASK1/ASK2/ASK3 → Later + toast,
|
||||
nothing written; project scope with the derived phase; `about`
|
||||
overlong → dropped + logged.
|
||||
- Disposition: honors BDR-116 rules (1), (3)-(6) unchanged (rows = phases,
|
||||
dialog never edits phases, writers/paths/caps as is); AMENDS its clause
|
||||
(2): options become Later/Keep/Change with a model-then-effort chain,
|
||||
Other = Later (successor amendment written at CAPITALIZE, append-only); BDR-115 (full ids from `cfg.models`),
|
||||
LRN-210 (one clause per test), LRN-212 (live mod: announce dialogs).
|
||||
- Docs (README/USAGE/CHANGELOG lines on the dialog) drift after this wave:
|
||||
STEP 6 doc-sync, out of this contract's scope.
|
||||
|
||||
## Challenge ledger (r1 → r2)
|
||||
- simp 1/2, rob 2/3/4/7/11/12, corr 1/2/3/9/10/12 (inline rows) → D1:
|
||||
rows stay phases; efforts derived from the alias's phases; target-phase
|
||||
rule with tie-break; same-phase = Keep.
|
||||
- simp 3, rob 1 BLOCKER/5/6/13, corr 4/5 (T3 phase edits) → D2: T3
|
||||
Later/Keep with context; BDR-116 unchanged.
|
||||
- simp 6, rob 8/9, corr 8 (descriptions) → D3 single rule, five YAML
|
||||
forms, hardened read, allowlist, clean() with Cc/Cf and code points.
|
||||
- simp 7, corr 14 (`about` on Route) → separate map, validated at load.
|
||||
- simp 8, corr 13 (ASK2 Other, aliases) → fixed four labels, Other = Later.
|
||||
- simp 9 (dead code) → S3 removal.
|
||||
- rob 10, corr 6 (main model pick is a placebo) → toast with the real
|
||||
decision and the switch hint.
|
||||
- corr 7 (W3-A tests) → S6a migration step.
|
||||
- corr 11 (no-key scope) → W3-A behaviour kept ([Everywhere, Later]).
|
||||
- corr 15 (docs, AC5 oracle) → doc-sync noted; contract AC5 reworded.
|
||||
|
||||
## Confirmation ledger (r2 → r3)
|
||||
- conf 1 (S6a under-listed) → leads kept by construction; toast text,
|
||||
deleted free-text tests, `asked[n]` shifts listed.
|
||||
- conf 2 (read-hardening clauses untested) → S6b tests + World extensions.
|
||||
- conf 3 (ASK3 order) → ascending by LEVELS; tie-break in `cfg.phases` order.
|
||||
- conf 4 (clean order, length) → fold first, strip, n-1 + "…".
|
||||
- conf 5 (dismissed ASK2/3 toasted) → LATER checked first, silent.
|
||||
- conf 6 (zero efforts, texts, echo) → Later + toast; ASK2/ASK3 texts; no echo.
|
||||
- conf 7 (toast on Keep) → Change only, computed in confirmSkill.
|
||||
- conf 8 (BDR-116 clause 2) → amendment at CAPITALIZE.
|
||||
- conf 9 (`about` across /clear) → in `mem`, rebuilt with cfg.
|
||||
- conf 10 (agents vs skills rule) → same rule at offer time, bounded.
|
||||
- conf 11 (hardcoded consumers) → derived from cfg.prompt.
|
||||
- conf 12 (kind check placement) → skill wrapper around readCapped.
|
||||
Reference in New Issue
Block a user