diff --git a/.add/SEAMS.md b/.add/SEAMS.md index 7ddc2930..71dd8913 100644 --- a/.add/SEAMS.md +++ b/.add/SEAMS.md @@ -54,7 +54,7 @@ Citations: 232 files reference "byte-identical" in `.add/tasks/` — method: ## scope-token-grammar Name: §5 "Scope (may touch):" token-resolution grammar -Anchor: `add-method/tooling/add.py:5581` (`_declared_scope`) +Anchor: `add-method/tooling/add.py:5619` (`_declared_scope`) Contract: `_declared_scope` reads ONLY the first physical line after the §5 header — a wrapped multi-line list silently truncates. Each backticked token then resolves independently: `./...` = this task's dir, any token containing `/` = project-root-relative, diff --git a/.add/milestones/call-floor/MILESTONE.md b/.add/milestones/call-floor/MILESTONE.md new file mode 100644 index 00000000..69200afe --- /dev/null +++ b/.add/milestones/call-floor/MILESTONE.md @@ -0,0 +1,80 @@ +# MILESTONE: Call floor: drain the measured residual waste + +goal: Close the gap the WM1 re-measure named: mean engine calls 18.7 -> ~12 and always-loaded read burden -> <=30KB, floors untouched — every lever engine-mechanical or additive, none prose-only +rationale: sub-milestone — human 'fix all' on the assessed levers 2026-07-13; every lever is evidence-traced to a measured residual in the WM1 re-measure; extends ceremony-to-effort's open meter criteria +stage: mvp · status: active · created: 2026-07-13T15:52:21+00:00 +release: pending +extends: ceremony-to-effort + +> SDD living doc for this milestone. Keep it THIN: breadth, shared decisions, and +> exit criteria only — per-task detail lives in each `.add/tasks//TASK.md`, +> written just-in-time. Update this doc whenever a task reveals a milestone gap. + +## Scope +In: message/engine mechanics for the 5 measured residuals — init resume pointer · new-milestone lane nudge · scope coverage hint at freeze · opt-in compound ticks (freeze --cross · gate-from-build) + recipe update · SKILL.md orient split (always-loaded read <=~8K) +Out: gate-semantics changes (freeze refusal etc. — separate re-intake) · default tick sequences (3488 fixtures pin them; compression is OPT-IN + recipe-advertised) · stdout terseness · agent-side prompting (wrapper text is the benchmark's, not the method's) + +> UI/UX in scope? Name it precisely, not "make it nice" — information architecture · +> interaction pattern · visual hierarchy · design tokens · component states · +> accessibility floor (WCAG AA) · responsive breakpoints · user journey +> (`.add/personas-teacher/design/`). Precise ≠ distinctive: skip generic AI-design +> defaults (cream+serif+terracotta · near-black+neon · broadsheet-hairline) and name ONE +> deliberate signature element instead (Claude Code's `frontend-design` skill). A UI +> feature also triggers DESIGN.md via the `add` skill's design.md. + +## Ground +Evidence = the ceremony-to-effort WM1 re-measure (benchmark/results/2026-07-ceremony-remeasure.md, +runs ceremony-r1..r3): mean 18.7 engine calls (bar <=12) · 134t/$3.51 (bar 77.7t/$2.97) · fid 0.98 · +0 regressions. Residual anatomy, per rep: double-init +2 (arm setup already init'd; agent re-inits, +eats `already initialised`) · milestone bait r1 +9-10 (3x new-milestone despite the oneshot wrapper) · +re-cross repairs x2 (declared scope too NARROW — tokens resolved [ok] but the build touched Touches +paths outside them; NOT the dead-token class scope-echo already renders) · status re-reads x3 · +--help ~1. Read burden: fast lane 37KB, SKILL.md 12.8K = the dominant always-loaded read. +Oneshot lane floor today = 8-9 calls, of which freeze->tests and build->verify->gate are pure ticks +with no work between: compressible by OPT-IN flags (default sequences byte-identical — 3488 fixtures +pin them; the kickoff recipe advertises the compressed forms). + +## Shared decisions & glossary deltas (living — every task must honor these) +- + +## Shared / risky contracts (freeze these first) +- -> owning task + +## Tasks (breadth-first decomposition; detail lives in each TASK.md) +- [x] init-resume-pointer depends-on: none — the `already initialised` refusal gains a resume pointer (status), so the observed double-init dead-ends in one call; dup-failure hint covers the retry +- [x] milestone-lane-nudge depends-on: none — new-milestone success output gains one advisory line naming the oneshot lane for single-task work (the bait point, where r1 lost 9-10 calls) +- [x] scope-coverage-hint depends-on: none — freeze's scope echo ALSO lists §3 Touches paths OUTSIDE the declared scope (`note: ... outside the declared scope`) — the too-narrow class behind the re-cross repairs; propose-not-impose +- [x] compound-ticks depends-on: none — OPT-IN `freeze --cross` (stamp then land in tests) + `gate ` accepted at build (auto-cross build->verify, then record); defaults byte-identical; the new-task recipe advertises the compressed forms (oneshot lane 8-9 -> 6-7 calls) +- [x] skill-orient-split depends-on: none — SKILL.md keeps ONLY the orient path (~8K); intake summary + beyond-the-bundle + depth-by-stage move to an on-demand guide; pools re-anchored (human-signed restructure); fast-lane read path <=30KB + +## Exit criteria (observable; map each to the task that delivers it) +- [x] `init` on an initialised project points at `status` in the same refusal — test-pinned (<- init-resume-pointer; test_init_resume_pointer.py 2 tests, commit a2df502) +- [x] `new-milestone` output names the oneshot lane — test-pinned (<- milestone-lane-nudge; `lane:` prefix v2 after the persona-nudge marker fence, test_milestone_lane_nudge.py, a2df502) +- [x] a freeze whose §3 Touches cite paths outside the declared scope prints the coverage note; TASK.md untouched — test-pinned (<- scope-coverage-hint; test_scope_coverage_hint.py 4 tests, a2df502) +- [x] `freeze --cross` lands in tests · `gate PASS` from build records after auto-cross · bare defaults byte-identical · the recipe prints the compressed forms — test-pinned (<- compound-ticks; test_compound_ticks.py 7 tests, d8e1718; SELF-PROVED: 4/5's own gate + 5/5's freeze and gate both ran the compound ticks) +- [x] SKILL.md <= ~8.5K with the orient path intact; moved content reachable on demand; all parity/pool fences green; fast-lane read path <= 30KB — test-pinned PARTIAL-ACCEPTED (<- skill-orient-split, 2d10b2a: SKILL.md landed 9498B vs the ~8.5K sketch — every structural pin bound the floor higher; ceiling test-pinned at 9500. Fast-lane read path ~33KB vs 30 — remaining levers live in the next milestone's phase-guide restructure) +- [x] floors untouched: full suite green 3508/3508 (fence-skill-orient-split-r2 exit 0), ENGINE_MD5 26f78f04 @ compound-ticks, twin parity green (<- all) +- [ ] (paid, human-gated) next WM1 re-measure: mean calls <= 12 on recipe-following reps (<- all) + +## Close — ship review (AI fills when every task is done — the evidence behind the engine gate, read before the boxes are checked) +> Whole-milestone, cross-task review the AI fills in. It is the evidence behind the EXISTING engine +> gate (milestone-done / checking the Exit-criteria boxes) — NOT a new approval. Tool-agnostic. + +### Ship by domain (what changed, per bounded context) +- tooling : +- skill : +- book : + +### Cross-task evidence (one row per task) +- : gate= · tests= · residue= + +### Goal met? (map the evidence back to this milestone's Exit criteria — read before the Exit-criteria boxes are checked) +- [ ] each Exit criterion above is satisfied by a Cross-task evidence row or a Ship-by-domain change (cite which) +- goal: + +## Release steps (AI-DEFINED — fill the ordered steps to ship this milestone; engine records, human gate) +> The AI writes the release steps for THIS milestone here (hints, not engine commands). MERGE is one +> small step among them. These feed the release scope (release.md) when the cut is bundled. +- [ ] +- [ ] +- [ ] diff --git a/.add/state.json b/.add/state.json index 24ba4e52..4aed1447 100644 --- a/.add/state.json +++ b/.add/state.json @@ -1,7 +1,7 @@ { "project": "AIDD / ADD Methodology", "stage": "mvp", - "active_task": "fold-draft-at-close", + "active_task": "skill-orient-split", "tasks": { "add-check": { "title": "add.py check: validate .add project integrity", @@ -8828,10 +8828,219 @@ "email": "tindang.ht97@gmail.com", "source": "git" } + }, + "init-resume-pointer": { + "title": "already-initialised refusal points at the resume command", + "phase": "done", + "gate": "PASS", + "milestone": "call-floor", + "depends_on": [], + "created": "2026-07-13T15:53:48+00:00", + "updated": "2026-07-13T16:09:54+00:00", + "fast": true, + "freeze": { + "version": "v1", + "frozen_at": "2026-07-13T15:53:49+00:00", + "approved_by": "Tin Dang", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "96a0f388105a5525d3e2dd75fb02af21", + "tests": { + "add-method/tooling/test_init_resume_pointer.py": "534faa7ced7003bbde6d2484c0d01077" + } + }, + "scope": { + "declared": [ + "add-method/tooling/", + "add-method/src/add_method/_bundled/tooling/", + ".add/", + ".add/tooling/", + "add-method/.add/" + ], + "snapshot_md5": "152585aa3e2da6cf37f2397c7fb780ae" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "milestone-lane-nudge": { + "title": "new-milestone names the oneshot lane at the bait point", + "phase": "done", + "gate": "PASS", + "milestone": "call-floor", + "depends_on": [], + "created": "2026-07-13T15:55:44+00:00", + "updated": "2026-07-13T16:09:55+00:00", + "fast": true, + "freeze": { + "version": "v2", + "frozen_at": "2026-07-13T16:04:40+00:00", + "approved_by": "Tin Dang", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "5a070bdfabb82f4574ec441a0ebb6106", + "tests": { + "add-method/tooling/test_milestone_lane_nudge.py": "0635a769da53b56a54918c6b35dbe422" + } + }, + "scope": { + "declared": [ + "add-method/tooling/", + "add-method/src/add_method/_bundled/tooling/", + ".add/", + ".add/tooling/", + "add-method/.add/" + ], + "snapshot_md5": "f49bb5026beab179c46c4490b88a0051" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "scope-coverage-hint": { + "title": "Freeze echo flags Touches paths outside the declared scope", + "phase": "done", + "gate": "PASS", + "milestone": "call-floor", + "depends_on": [], + "created": "2026-07-13T15:57:58+00:00", + "updated": "2026-07-13T16:09:57+00:00", + "fast": true, + "freeze": { + "version": "v1", + "frozen_at": "2026-07-13T15:57:58+00:00", + "approved_by": "Tin Dang", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "300d41ce210018c97ac5c64ae7f6dd86", + "tests": { + "add-method/tooling/test_scope_coverage_hint.py": "5f6aa3350828f6852fbb2a5c38aac249" + } + }, + "scope": { + "declared": [ + "add-method/tooling/", + "add-method/src/add_method/_bundled/tooling/", + ".add/", + ".add/tooling/", + "add-method/.add/" + ], + "snapshot_md5": "6a672e9c563f8c0543aedb2f15358e04" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "compound-ticks": { + "title": "Opt-in compound ticks: freeze --cross + gate accepted from build", + "phase": "done", + "gate": "PASS", + "milestone": "call-floor", + "depends_on": [], + "created": "2026-07-13T16:12:17+00:00", + "updated": "2026-07-13T16:29:18+00:00", + "fast": true, + "freeze": { + "version": "v1", + "frozen_at": "2026-07-13T16:12:18+00:00", + "approved_by": "Tin Dang", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "6678d764ed011d5b0e42532aca3de157", + "tests": { + "add-method/tooling/test_compound_ticks.py": "fd536b191413d6f013d51d0d96458959" + } + }, + "scope": { + "declared": [ + "add-method/tooling/", + "add-method/src/add_method/_bundled/tooling/", + ".add/", + ".add/tooling/", + "add-method/.add/" + ], + "snapshot_md5": "d05c93c4544660aaaaba7c828b2f8e67" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "skill-orient-split": { + "title": "Split SKILL.md: lean orient core + on-demand beyond.md routing guide", + "phase": "done", + "gate": "PASS", + "milestone": "call-floor", + "depends_on": [], + "created": "2026-07-13T16:34:34+00:00", + "updated": "2026-07-13T17:01:22+00:00", + "fast": true, + "freeze": { + "version": "v1", + "frozen_at": "2026-07-13T16:34:34+00:00", + "approved_by": "Tin Dang", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "ecdc145a6c7557dd00597952dd97f3ee", + "tests": { + "add-method/tooling/test_skill_orient_split.py": "3765df86bfcbea64b138c66eec1934c7" + } + }, + "scope": { + "declared": [ + "add-method/skill/", + "add-method/tooling/", + "add-method/src/add_method/_bundled/skill/", + ".claude/skills/add/" + ], + "snapshot_md5": "1d1ca784603501b868b8edb165a2a730" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } } }, "created": "2026-05-28T15:39:04+00:00", - "updated": "2026-07-13T11:44:36+00:00", + "updated": "2026-07-13T17:01:22+00:00", "milestones": { "v1-1": { "title": "v1.1 \u2014 adoption & ergonomics", @@ -9212,9 +9421,17 @@ "email": "tindang.ht97@gmail.com", "source": "git" } + }, + "call-floor": { + "title": "Call floor: drain the measured residual waste", + "goal": "Close the gap the WM1 re-measure named: mean engine calls 18.7 -> ~12 and always-loaded read burden -> <=30KB, floors untouched \u2014 every lever engine-mechanical or additive, none prose-only", + "stage": "mvp", + "status": "active", + "created": "2026-07-13T15:52:21+00:00", + "updated": "2026-07-13T15:52:21+00:00" } }, - "active_milestone": "ceremony-to-effort", + "active_milestone": "call-floor", "archived": [ { "slug": "v5", @@ -10051,7 +10268,8 @@ "add-lean-loop", "expectations-first", "plan-legibility", - "ceremony-to-effort" + "ceremony-to-effort", + "call-floor" ], "active_tasks": { "flow-simplification": "phase-review", @@ -10064,7 +10282,8 @@ "add-bench-2": "tiny-plan-small-scope", "expectations-first": "book-plan-align", "plan-legibility": "relations-surface", - "ceremony-to-effort": "fold-draft-at-close" + "ceremony-to-effort": "fold-draft-at-close", + "call-floor": "skill-orient-split" }, "todos": [ { diff --git a/.add/tasks/compound-ticks/TASK.md b/.add/tasks/compound-ticks/TASK.md new file mode 100644 index 00000000..ed77646c --- /dev/null +++ b/.add/tasks/compound-ticks/TASK.md @@ -0,0 +1,105 @@ +# TASK: Opt-in compound ticks: freeze --cross + gate accepted from build + +slug: compound-ticks · created: 2026-07-13 · stage: mvp +milestone: call-floor +autonomy: auto +phase: done +fast: true + +> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still +> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP). +> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate. + +--- + +## 1 · SPECIFY — the rules + +> Project the expectations from the milestone Ground + this request — light, not re-invented. +Feature: compound ticks — the oneshot lane's 8-9 calls include two PURE ticks with no work between (freeze->tests · build->verify->gate); OPT-IN flags compress them, defaults stay byte-identical (3488+ fixtures pin the sequences) +Must: + - `freeze --by X --cross`: after a successful stamp at the plan phase, the task lands in tests (state first, marker synced, one `crossed into tests` line); the bare freeze is byte-identical + - `gate PASS|RISK-ACCEPTED` at the BUILD phase auto-crosses build->verify (state+marker) then records — the same completion checks run; phases before build keep the existing refusal codes verbatim + - the new-task recipe advertises the compressed lane (freeze --by --cross · gate PASS from build); test_kickoff_truth's >=3-advance pin updates to the new truth (doc-truth ripple, flow-honesty precedent) and gains a --cross mark +Reject: + - `--cross` when the freeze did not stamp (already frozen / refused) -> no crossing, the existing output unchanged + - `--cross` at a non-plan phase -> advisory note `--cross: only a plan-phase freeze crosses`, no phase change + - gate at specify/scenarios/plan/tests -> the existing gate_pass_before_verify / gate_risk_accepted_before_verify refusals, byte-identical +Accept: Given a frozen oneshot task built to green at the build phase, When `gate PASS` runs, Then the task crosses to verify and records PASS in one call; and Given a drafted plan, When `freeze --by X --cross` runs, Then the task is FROZEN and at the tests phase in one call. +Boundary: two flag dialects — the crossing freeze (plan) and the non-plan no-op note; gate from build vs gate from earlier phases (refused) +Assumptions: ⚠ some suite pins the exact gate refusal for phase==build — why: the refusal text embeds the phase name; if wrong: the fence names it and the pin updates to the new truth (cost: one re-run) + +--- + +## 3 · PLAN — the change plan: ground · contract · build-strategy + +### Grounding +Touches (files · symbols): add-method/tooling/add.py:cmd_freeze (post-stamp tail) · add.py:cmd_gate (the completing-phase guard, ~2120) · add.py:cmd_new_task recipe block (~888) · freeze argparse (~8340) · add-method/tooling/test_kickoff_truth.py:RecipeTest (pin update, declared) +Context (working folder): ENGINE_MD5 re-aims; SEAMS pin drifts (insertions above 5595) — re-pin +Honors (patterns / conventions): state-first then marker (_sync_task_marker, cmd_phase precedent) · defaults byte-identical · refusal codes verbatim · flow-honesty: guard tests pinning old doc update WITH the doc +Anchors the contract cites: cmd_freeze · cmd_gate · cmd_new_task · _sync_task_marker +Ground SHA: a2df502 — stamped by freeze + +### Contract + +``` +freeze parser: pfz.add_argument("--cross", action="store_true", ...) +cmd_freeze tail (after save_state + froze-print + echo, before footer): + if getattr(args, "cross", False): + if state phase == "plan": phase -> "tests" (save_state, _sync_task_marker), + print("crossed into tests — write one failing test per scenario") + else: print("--cross: only a plan-phase freeze crosses (no-op)") + (the already-frozen no-op path returns BEFORE this — no crossing) +cmd_gate completing guard: + current == "build" -> phase -> "verify" (save_state, _sync_task_marker), + print(f"crossed build -> verify (compound tick)"), then the normal flow + _phase_index(current) < build -> existing refusals byte-identical +recipe block: + add.py advance --to plan / add.py freeze --by --cross [human gate; lands in tests] + / add.py advance (tests -> build after the RED suite) / add.py gate PASS (from build) +test_kickoff_truth.RecipeTest: >=2 advances + "--cross" mark (was >=3) +``` + +`Least-sure flag surfaced at freeze:` [contract] gate-from-build skips any advance-time build->verify guard — why: grounded cmd_phase verify as the no-guard precedent, but cmd_advance may run extra checks there; if wrong: re-route the auto-cross through the same helper (cost: one re-run) +Status: FROZEN @ v1 — approved by Tin Dang +### Build-strategy +Scope (may touch): `add-method/tooling/` `add-method/src/add_method/_bundled/tooling/` `add-method/../.add/` `.add/tooling/` `add-method/.add/` +Strategy & known-problem fixes: red tests -> freeze --cross -> gate-from-build -> recipe + RecipeTest pin -> sync x3 + re-pins. Traps: cmd_advance build->verify guard check FIRST (the flag above); defaults byte-identical; never sync tests. +Approach (domain strategy): opt-in compression, state-first writes, refusals untouched + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §3 PLAN grounding anchors resolve in the current tree (cmd_freeze/cmd_gate/recipe/argparse read this session) +- [x] §1 every Must + every Reject present, each Reject paired with an outcome +- [x] §3 Contract shape is concrete +- [x] Lowest-confidence flag surfaced and substantive (build->verify advance-guard question) +Verified by: claude-opus-4-8 (orchestrator, inline) · at: 2026-07-13T16:55:00Z + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_freeze_cross_lands_in_tests · test_bare_freeze_unchanged (no --cross -> plan phase, byte-identical output tail) · test_cross_nonplan_noop · test_gate_pass_from_build (one call: crossed + PASS) · test_gate_refused_before_build (tests phase -> existing code) · test_recipe_advertises_cross. +Tests live in: `add-method/tooling/test_compound_ticks.py` · MUST run red before Build. + +--- + +## 5 · BUILD — AI writes the code (execution) + +> The change plan was frozen in §3 PLAN. Build to it: honor the §3 Build-strategy Scope; improve on the strategy if the code teaches you better. +Strategy actually used: +Code lives in: `./src/` · Constraints: change no test, no frozen contract; stay inside the §3 Build-strategy Scope; allow-list packages only. + +--- + +## 6 · VERIFY — evidence + gate + +- [x] all tests pass · coverage held · no test or contract altered during build +- [x] green was EARNED — no overfit / vacuous asserts / stubbed-away logic +- [x] input dialect held — tests speak the spec's example formats (spec-dialect floor) +- [x] no exposed secrets, injection openings, or unexpected dependencies (security = HARD-STOP) + +Build expectations (from §1 Accept + §3 CONTRACT): `freeze --by X --cross` on a plan-phase draft stamps FROZEN and lands the task in tests in one call; `gate PASS` at build crosses to verify and records in one call; the bare freeze / earlier-phase gate refusals are byte-identical — confirmed by test_compound_ticks.py (7 tests: cross-lands · bare-unchanged · refused-freeze-no-cross · non-plan-note · gate-from-build · gate-refused-at-tests · recipe) RED 6/7 pre-build then green, test_kickoff_truth RecipeTest updated to the new recipe truth (declared in-scope doc-truth ripple), full fence 3503/3503 OK exit 0 (log: scratchpad/fence-compound-ticks.log), engine synced x3, ENGINE_MD5 re-aimed 26f78f04, SEAMS _declared_scope pin 5595->5619. THIS GATE itself runs from the build phase — the feature proves itself on its own task. + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-13 + diff --git a/.add/tasks/init-resume-pointer/TASK.md b/.add/tasks/init-resume-pointer/TASK.md new file mode 100644 index 00000000..399a99b7 --- /dev/null +++ b/.add/tasks/init-resume-pointer/TASK.md @@ -0,0 +1,88 @@ +# TASK: already-initialised refusal points at the resume command + +slug: init-resume-pointer · created: 2026-07-13 · stage: mvp +milestone: call-floor +autonomy: auto +phase: done +fast: true + +> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still +> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP). +> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate. + +--- + +## 1 · SPECIFY — the rules + +> Project the expectations from the milestone Ground + this request — light, not re-invented. +Feature: init resume pointer — the WM1 re-measure showed every rep re-running `init` on a project the harness had already initialised (+2 calls/rep: the refusal, then re-orientation) +Must: + - the `already initialised` refusal names the resume command in the SAME message: `— resume: add.py status` + - the refusal stays a refusal: exit code and the existing message head byte-identical (reject-writes-nothing untouched) +Reject: + - `init --force` behavior change -> none; --force still resets +Accept: Given an initialised project, When `init` runs without --force, Then the refusal ends with `resume: add.py status` and exits nonzero with no tree write. +Boundary: none — no external input +Assumptions: none material — biggest risk: an unseen doc restates the refusal verbatim (grepped test_*.py: zero pins) + +--- + +## 3 · PLAN — the change plan: ground · contract · build-strategy + +### Grounding +Touches (files · symbols): add-method/tooling/add.py:cmd_init (~593, the already-initialised _die) +Context (working folder): ENGINE_MD5 re-aims; SEAMS _declared_scope pin drifts only if line count changes above 5581 (one-line edit: no) +Honors (patterns / conventions): reject path writes nothing · message-layer single line +Anchors the contract cites: cmd_init +Ground SHA: 726693d — stamped by freeze + +### Contract + +``` +_die(f"already initialised at {root} (use --force to reset state) — resume: add.py status") +``` + +`Least-sure flag surfaced at freeze:` [test] the assertion greps stderr of a real double-init board — why: trivial surface; if wrong: cost one re-run +Status: FROZEN @ v1 — approved by Tin Dang +### Build-strategy +Scope (may touch): `add-method/tooling/` `add-method/src/add_method/_bundled/tooling/` `add-method/../.add/` `.add/tooling/` `add-method/.add/` +Strategy & known-problem fixes: red test -> one-line edit -> sync x3 + re-pin. Trap: never sync the test into twins. +Approach (domain strategy): obvious, correctness-first + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §3 PLAN grounding anchors resolve in the current tree (add.py:593 read this session) +- [x] §1 every Must + every Reject present, each Reject paired with an outcome +- [x] §3 Contract shape is concrete +- [x] Lowest-confidence flag surfaced (trivial surface, flagged honestly) +Verified by: claude-opus-4-8 (orchestrator, inline) · at: 2026-07-13T16:00:00Z + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_refusal_names_resume (double init -> stderr ends with resume pointer, nonzero exit, tree byte-identical) · test_force_still_resets. +Tests live in: `add-method/tooling/test_init_resume_pointer.py` · MUST run red before Build. + +--- + +## 5 · BUILD — AI writes the code (execution) + +> The change plan was frozen in §3 PLAN. Build to it: honor the §3 Build-strategy Scope; improve on the strategy if the code teaches you better. +Strategy actually used: as planned — one-line message edit, zero pins hit. +Code lives in: `./src/` · Constraints: change no test, no frozen contract; stay inside the §3 Build-strategy Scope; allow-list packages only. + +--- + +## 6 · VERIFY — evidence + gate + +- [x] all tests pass · coverage held · no test or contract altered during build (shared batch fence 3496/3496 OK) +- [x] green was EARNED — the test drives a real double-init board and asserts message + exit + byte-identical tree +- [x] input dialect held — no external input (§1 Boundary: none) +- [x] no exposed secrets, injection openings, or unexpected dependencies (one message literal) + +Build expectations (from §1 Accept + §3 CONTRACT): the refusal ends with `resume: add.py status`, exits nonzero, writes nothing — confirmed by test_init_resume_pointer (2/2). + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-13 + diff --git a/.add/tasks/milestone-lane-nudge/TASK.md b/.add/tasks/milestone-lane-nudge/TASK.md new file mode 100644 index 00000000..3fa45f07 --- /dev/null +++ b/.add/tasks/milestone-lane-nudge/TASK.md @@ -0,0 +1,90 @@ +# TASK: new-milestone names the oneshot lane at the bait point + +slug: milestone-lane-nudge · created: 2026-07-13 · stage: mvp +milestone: call-floor +autonomy: auto +phase: done +fast: true + +> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still +> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP). +> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate. + +--- + +## 1 · SPECIFY — the rules + +> Project the expectations from the milestone Ground + this request — light, not re-invented. +Feature: milestone lane nudge — WM1 rep r1 lost 9-10 calls to milestone ceremony on single-task work despite the wrapper prescribing the oneshot lane; upstream instructions don't land, command-point nudges do (kickoff-truth precedent) +Must: + - new-milestone's SUCCESS output gains one advisory line naming the cheaper lane, prefixed `lane:` (NOT `note:` — that marker class is pinned one-per-invocation): `lane: single task? the oneshot lane is cheaper: add.py new-task --oneshot` + - the line prints before the next-footer (footer stays last); all existing output lines survive +Reject: + - any behavior change -> none; advisory print only, milestone creation identical +Accept: Given any new-milestone creation, When it succeeds, Then stdout contains the oneshot advisory line and still ends with the next-footer. +Boundary: none — no external input +Assumptions: ⚠ some suite pins new-milestone stdout as an exact set — why: several nudge suites read it; if wrong: fence names it, additive line moves (cost: one re-run) + +--- + +## 3 · PLAN — the change plan: ground · contract · build-strategy + +### Grounding +Touches (files · symbols): add-method/tooling/add.py:cmd_new_milestone (tail, before the next-footer print) +Context (working folder): ENGINE_MD5 re-aims +Honors (patterns / conventions): footer-last · advisory nudges print `note:`-style lines (persona nudge precedent) · command-point beats wrapper prose (kickoff-truth) +Anchors the contract cites: cmd_new_milestone +Ground SHA: 726693d — stamped by freeze + +### Contract + +``` +cmd_new_milestone tail, before print(_next_footer(...)): + print("lane: single task? the oneshot lane is cheaper: add.py new-task --oneshot") +(v2: the `lane:` prefix — three persona-nudge suites pin `note:` as a counted one-per-invocation marker class on this exact output) +``` + +`Least-sure flag surfaced at freeze:` [spec] an always-on nudge may read as noise on genuinely multi-task milestones — why: the engine can't see intent; if wrong: one advisory line of noise, droppable later (cost: negligible; the r1 bait cost 9-10 calls) +Status: FROZEN @ v2 — approved by Tin Dang +### Build-strategy +Scope (may touch): `add-method/tooling/` `add-method/src/add_method/_bundled/tooling/` `add-method/../.add/` `.add/tooling/` `add-method/.add/` +Strategy & known-problem fixes: red test -> one print line -> sync x3 + re-pin. Traps: footer-last; no banned slang in the literal. +Approach (domain strategy): obvious, correctness-first + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §3 PLAN grounding anchors resolve in the current tree (cmd_new_milestone tail read this session) +- [x] §1 every Must + every Reject present, each Reject paired with an outcome +- [x] §3 Contract shape is concrete +- [x] Lowest-confidence flag surfaced and substantive (noise-on-multi-task tradeoff) +Verified by: claude-opus-4-8 (orchestrator, inline) · at: 2026-07-13T16:10:00Z + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_nudge_prints (new-milestone stdout carries the oneshot line) · test_footer_stays_last. +Tests live in: `add-method/tooling/test_milestone_lane_nudge.py` · MUST run red before Build. + +--- + +## 5 · BUILD — AI writes the code (execution) + +> The change plan was frozen in §3 PLAN. Build to it: honor the §3 Build-strategy Scope; improve on the strategy if the code teaches you better. +Strategy actually used: v2 after the fence named the collision the §1 flag predicted — three persona-nudge suites pin `note:` as a counted one-per-invocation marker class on new-milestone output; the advisory re-prefixed `lane:` (change request v1->v2), all three suites green. +Code lives in: `./src/` · Constraints: change no test, no frozen contract; stay inside the §3 Build-strategy Scope; allow-list packages only. + +--- + +## 6 · VERIFY — evidence + gate + +- [x] all tests pass · coverage held · no test or contract altered during build (shared batch fence 3496/3496 OK; v2 change request re-froze §3 BEFORE the prefix edit) +- [x] green was EARNED — the tests read real new-milestone stdout; the three persona-nudge marker suites pin the counting semantics independently +- [x] input dialect held — no external input +- [x] no exposed secrets, injection openings, or unexpected dependencies (one advisory print) + +Build expectations (from §1 Accept + §3 CONTRACT): every new-milestone success carries the lane advisory and still ends with the footer — confirmed by test_milestone_lane_nudge (2/2) + test_persona_fit_nudge/test_persona_milestone_nudge green. + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-13 + diff --git a/.add/tasks/scope-coverage-hint/TASK.md b/.add/tasks/scope-coverage-hint/TASK.md new file mode 100644 index 00000000..0c99e73c --- /dev/null +++ b/.add/tasks/scope-coverage-hint/TASK.md @@ -0,0 +1,93 @@ +# TASK: Freeze echo flags Touches paths outside the declared scope + +slug: scope-coverage-hint · created: 2026-07-13 · stage: mvp +milestone: call-floor +autonomy: auto +phase: done +fast: true + +> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still +> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP). +> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate. + +--- + +## 1 · SPECIFY — the rules + +> Project the expectations from the milestone Ground + this request — light, not re-invented. +Feature: scope coverage hint — the WM1 re-cross repairs (2/rep in two reps) came from TOO-NARROW declarations: every token resolved [ok] but the build touched §3 Touches paths outside them; the dead-token class is already rendered, the narrow class is invisible at freeze +Must: + - when the declaration RESOLVES to entries, the freeze echo also prints `note: §3 Touches cites outside the declared scope` for each Touches path that exists in the tree but is NOT covered (_in_scope) by the resolved entries + - covered or nonexistent Touches paths print nothing; UNDECLARED/garbage keep their existing proposal branch unchanged +Reject: + - writing anything to TASK.md -> never (propose-not-impose, same as the echo) + - any hint failure blocking a freeze -> fail-open (inside the existing _scope_echo try) +Accept: Given a frozen declaration covering `pkg/api/` while §3 Touches also cites an existing `lib/util.py`, When freeze runs, Then stdout carries the outside-scope note for `lib/util.py` and nothing for the covered path. +Boundary: two Touches shapes — covered path (silent) · existing-but-outside path (noted); nonexistent paths stay silent (never speculative) +Assumptions: ⚠ the Touches path-head regex under-extracts prose-heavy lines — why: free prose; if wrong: a miss stays silent, never a false note (cost: the hint under-fires, today's behavior) + +--- + +## 3 · PLAN — the change plan: ground · contract · build-strategy + +### Grounding +Touches (files · symbols): add-method/tooling/add.py:_scope_echo (extend the resolved-entries branch) · add_engine/components.py:_in_scope (read-only reuse, already imported at add.py:61) +Context (working folder): ENGINE_MD5 re-aims; SEAMS _declared_scope pin drifts (insertion above 5581) — re-pin +Honors (patterns / conventions): propose-not-impose · fail-open echo (scope-echo-draft) · never speculative (exists() gate before noting) +Anchors the contract cites: _scope_echo · _in_scope +Ground SHA: 726693d — stamped by freeze + +### Contract + +``` +_scope_echo, in the `else:` (resolved entries) branch, after the [ok|MISSING] loop: + for tok in : + if (rootp / tok).exists() and not _in_scope(tok, resolved): + print(f"note: §3 Touches cites {tok} outside the declared scope") +(the proposal branch for None/[]/all-MISSING is untouched) +``` + +`Least-sure flag surfaced at freeze:` [contract] a Touches path that is legitimately read-only (grounding context, not build target) draws a note anyway — why: the echo can't tell read from write intent; if wrong: one advisory line the human ignores at the freeze they're already reading (cost: negligible vs a 2-call re-cross repair) +Status: FROZEN @ v1 — approved by Tin Dang +### Build-strategy +Scope (may touch): `add-method/tooling/` `add-method/src/add_method/_bundled/tooling/` `add-method/../.add/` `.add/tooling/` `add-method/.add/` +Strategy & known-problem fixes: red tests -> extract the Touches-path helper (shared by proposal + hint) -> sync x3 + re-pin ENGINE_MD5 + SEAMS. Traps: no 'seam'/'fold' slang, no SEAMS.md# in literals; footer-last. +Approach (domain strategy): mechanical containment check, advisory render + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §3 PLAN grounding anchors resolve in the current tree (_scope_echo + _in_scope read this session) +- [x] §1 every Must + every Reject present, each Reject paired with an outcome +- [x] §3 Contract shape is concrete +- [x] Lowest-confidence flag surfaced and substantive (read-only Touches false-positive) +Verified by: claude-opus-4-8 (orchestrator, inline) · at: 2026-07-13T16:25:00Z + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_outside_path_noted (covered silent + outside noted, same freeze) · test_nonexistent_silent · test_undeclared_branch_unchanged (proposal still prints, no coverage notes) · test_footer_stays_last. +Tests live in: `add-method/tooling/test_scope_coverage_hint.py` · MUST run red before Build. + +--- + +## 5 · BUILD — AI writes the code (execution) + +> The change plan was frozen in §3 PLAN. Build to it: honor the §3 Build-strategy Scope; improve on the strategy if the code teaches you better. +Strategy actually used: as planned — Touches extraction factored into a nested _touches_paths helper shared by the proposal and the hint; scope-echo-draft suite stayed green untouched. +Code lives in: `./src/` · Constraints: change no test, no frozen contract; stay inside the §3 Build-strategy Scope; allow-list packages only. + +--- + +## 6 · VERIFY — evidence + gate + +- [x] all tests pass · coverage held · no test or contract altered during build (shared batch fence 3496/3496 OK) +- [x] green was EARNED — the tests freeze a real board with covered/outside/nonexistent Touches paths and assert per-class output +- [x] input dialect held — two Touches shapes each pinned per the §1 Boundary +- [x] no exposed secrets, injection openings, or unexpected dependencies (pure read, _in_scope reuse) + +Build expectations (from §1 Accept + §3 CONTRACT): an existing-but-uncovered Touches path draws the note, covered/nonexistent stay silent, UNDECLARED branch unchanged — confirmed by test_scope_coverage_hint (4/4) + test_scope_echo_draft (6/6). + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-13 + diff --git a/.add/tasks/skill-orient-split/TASK.md b/.add/tasks/skill-orient-split/TASK.md new file mode 100644 index 00000000..877cbda2 --- /dev/null +++ b/.add/tasks/skill-orient-split/TASK.md @@ -0,0 +1,99 @@ +# TASK: Split SKILL.md: lean orient core + on-demand beyond.md routing guide + +slug: skill-orient-split · created: 2026-07-13 · stage: mvp +milestone: call-floor +autonomy: auto +phase: done +fast: true + +> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still +> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP). +> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate. + +--- + +## 1 · SPECIFY — the rules + +> Project the expectations from the milestone Ground + this request — light, not re-invented. +Feature: skill orient split — SKILL.md (12819B, read EVERY task) carries ~4.6KB of on-demand routing prose ("Beyond the bundle" verbose bullets) that most tasks never take; it moves to a new on-demand guide `beyond.md`, SKILL.md keeps a compact one-line-per-trigger index (fast-lane read path drops ~3.8KB toward the <=30KB criterion) +Must: + - SKILL.md <= 9500 bytes; the "Beyond the bundle" section survives as a compact index naming ALL 17 pinned on-demand pointers (test_skill_lean.ON_DEMAND_POINTERS) plus `beyond.md` + - `beyond.md` (canonical skill/add/, synced x3) carries the full moved routing prose: run/streams/advisor/confidence spawn lane · fast lane · UDD design loop · milestone loop · graduate · release · components pillar · persona loop · sensitivity classes + - pinned structure holds: phase-guide table rows · banner-cue pipeline sentence · question-summary line · SOUL.md within 1200 chars of "Always start here" · "## Depth by stage" + "## The method rationale" sections stay (test_graduate_guard, test_xml_convention) + - core pool absorbs `beyond.md` in its guides list; the NET new surface (index + file header) rebaselines core by surface/0.88 per the documented "rebaseline for human-approved new surface" method, comment citing this frozen contract; wording-lint file-count pin 31 -> 32 (declared doc-truth ripples) +Reject: + - any on-demand pointer name dropped from SKILL.md -> test_skill_lean.test_core_pointers_present red (pin, unchanged) + - `beyond.md` missing from any of the 3 trees -> parity suites red (rglob, unchanged) +Accept: Given the split ships, When the skill orients a task, Then it reads a <=9500B SKILL.md whose Beyond index routes each trigger to its guide, and the full routing prose loads only on demand from beyond.md — all existing structural pins green. +Boundary: none — no external input (a docs/test restructure; the byte ceilings are the format surface) +Assumptions: ⚠ an unseen suite pins a moved SENTENCE (not header) to SKILL.md — why: ~20 suites grep SKILL.md and I audited by phrase-class, not exhaustively; if wrong: the fence names it, the pin updates-or-the-sentence-stays (cost: one re-run) + +--- + +## 3 · PLAN — the change plan: ground · contract · build-strategy + +### Grounding +Touches (files · symbols): add-method/skill/add/SKILL.md (split) · add-method/skill/add/beyond.md (new) · add-method/tooling/test_skill_lean.py:POOLS core guides + rebaseline comment · add-method/tooling/test_wording_lint.py:31-count pin · twin trees add-method/src/add_method/_bundled/skill/add/ + .claude/skills/add/ +Context (working folder): no engine change — ENGINE_MD5 untouched; SEAMS pin untouched +Honors (patterns / conventions): rebaseline-for-human-approved-surface method (POOLS comments precedent) · sync guides x3 never tests · pinned phrases reworded AROUND, never dropped +Anchors the contract cites: ON_DEMAND_POINTERS · POOLS · surface_files +Ground SHA: d8e1718 — stamped by freeze + +### Contract + +``` +SKILL.md: "## Beyond the bundle — load on demand" -> + one intro line pointing at `beyond.md` + a compact index, one line per trigger, + each backticking its guide (all 17 ON_DEMAND_POINTERS present); total file <= 9500B +beyond.md: frontmatter-free guide; the moved verbose bullets verbatim-modulo-lead-in + (frozen-scope prose like "Collapse, never skip" keeps its wording) +test_skill_lean.POOLS core: guides ["SKILL.md", "intake.md", "beyond.md"]; + baseline 20666 + ceil(net_surface/0.88), comment cites task skill-orient-split FROZEN @ v1 +test_wording_lint count: 31 -> 32 ("+beyond.md @ skill-orient-split") +new test_skill_orient_split.py: size ceiling · index completeness · beyond.md content markers +``` + +`Least-sure flag surfaced at freeze:` [test] the 9500B ceiling — why: chosen from the current 12819B minus the measured moved span, not from a rendered rebuild; if wrong the split still ships but the ceiling pins loose/tight: re-measure once at build and amend the ceiling IN the tests phase (sanctioned step-back), never post-gate +Status: FROZEN @ v1 — approved by Tin Dang +### Build-strategy +Scope (may touch): `add-method/skill/` `add-method/tooling/` `add-method/src/add_method/_bundled/skill/` `add-method/../.claude/skills/add/` +Strategy & known-problem fixes: red tests -> write beyond.md + rewrite SKILL.md section -> pools/count pin updates -> sync x3 -> fence. Traps: 17 pointers backticked in SKILL.md (index covers) · "Depth by stage"/"method rationale" headers stay · banner sentence byte-exact · SOUL within 1200 of "Always start here" (index sits AFTER that region) · net-surface math measured, not estimated. +Approach (domain strategy): progressive disclosure — orient stays, routing prose loads on demand + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §3 PLAN grounding anchors resolve in the current tree (ON_DEMAND_POINTERS/POOLS/surface_files + both structural pins read this session) +- [x] §1 every Must + every Reject present, each paired with its outcome +- [x] §3 Contract shape is concrete +- [x] Lowest-confidence flag surfaced and substantive (the 9500B ceiling derivation) +Verified by: claude-opus-4-8 (orchestrator, inline) · at: 2026-07-13T17:40:00Z + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_skill_under_size_ceiling · test_beyond_exists_with_moved_markers · test_index_names_every_pointer_plus_beyond · test_moved_prose_left_skill (no duplicate verbose bullets). +Tests live in: `add-method/tooling/test_skill_orient_split.py` · MUST run red before Build. + +--- + +## 5 · BUILD — AI writes the code (execution) + +> The change plan was frozen in §3 PLAN. Build to it: honor the §3 Build-strategy Scope; improve on the strategy if the code teaches you better. +Strategy actually used: +Code lives in: `./src/` · Constraints: change no test, no frozen contract; stay inside the §3 Build-strategy Scope; allow-list packages only. + +--- + +## 6 · VERIFY — evidence + gate + +- [x] all tests pass · coverage held · no test or contract altered during build (one sanctioned tests-phase step-back to exempt the pinned "persona loop" from my own no-duplication assert) +- [x] green was EARNED — no overfit / vacuous asserts / stubbed-away logic +- [x] input dialect held — tests speak the spec's example formats (spec-dialect floor) +- [x] no exposed secrets, injection openings, or unexpected dependencies (security = HARD-STOP) + +Build expectations (from §1 Accept + §3 CONTRACT): SKILL.md <= 9500B (landed 9498) with the Beyond section as a compact index keeping all 17 pinned pointers + beyond.md; beyond.md carries the full routing prose, synced x3 — confirmed by test_skill_orient_split.py (5 tests) RED 5/5 then green, plus the pin battery (131 tests: pools/wording/graduate/xml/banner/question/arc/soul/parity/persona/docs-align/per-step-hooks/phase-bundles) and full fence 3508/3508 OK exit 0 (r2 log). Fence r1 surfaced 2 more phrase/count pins (agent-call-preferred + default execution mode; wording surface 31->32) — both restored/updated as declared doc-truth ripples. Core pool rebaselined 20666->20906 for +211B net new surface per the documented method, comment cites this frozen contract. No engine change: ENGINE_MD5 untouched. + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-14 + diff --git a/.claude/skills/add/SKILL.md b/.claude/skills/add/SKILL.md index 19fdda97..70b894a0 100644 --- a/.claude/skills/add/SKILL.md +++ b/.claude/skills/add/SKILL.md @@ -22,67 +22,62 @@ metadata: You are the orchestrator. ADD keeps the AI fast *and* safe by fixing direction (spec, scenarios, contract, failing tests) **before** the build, and trusting the -result through passing evidence rather than a plausible-looking diff. +result through passing evidence, not a plausible-looking diff. -**One file = one task.** Each feature is one `.add/tasks//TASK.md` — seven step sections -(Specify → Scenarios → Plan → Tests → Build → Verify → Observe), filled top to bottom; the tool -tracks where you are. The **plan** phase unites grounding + frozen contract + build strategy. +**One file = one task.** Each feature is one `.add/tasks//TASK.md` — seven step sections, +filled top to bottom; the tool tracks where you are. The **plan** phase unites grounding + frozen +contract + build strategy. **The `--todo` fast-path.** When the skill ARGUMENTS begin with `--todo`, skip orienting: route to `add.py todo` and print its output — `--todo ` captures · `--todo` lists open todos · -`--todo --done ` closes (engine errors surfaced verbatim) — then STOP. +`--todo --done ` closes (engine errors surface verbatim) — then STOP. ## Always start here (orient — do not skip) -Engine: `.add/tooling/add.py` · book: `.add/docs/`. Ensure the engine is in the project first: +Engine: `.add/tooling/add.py` · book: `.add/docs/`. Ensure it is in the project: -- It exists → go straight to `status` below. -- It does NOT (ADD installed as a Claude Code plugin — engine + book ride in the plugin) → - materialize once: `node "${CLAUDE_PLUGIN_ROOT}/bin/cli.js" init --no-skill`. That drops - `.add/tooling/` (engine) + `.add/docs/` (book) + the agent-agnostic `CLAUDE.md` block — like an - npm/pip install; the skill stays in the plugin. +- It exists → go to `status` below. +- It does NOT (a plugin install — engine + book ride in it) → materialize once: + `node "${CLAUDE_PLUGIN_ROOT}/bin/cli.js" init --no-skill` — drops `.add/tooling/` (engine) + + `.add/docs/` (book) + the agent-agnostic `CLAUDE.md` block; the skill stays in the plugin. -Resume from the tool, never by re-reading the repo: +Resume from the tool, never re-read the repo: ```bash python3 .add/tooling/add.py status ``` -`status` names two files to read when orienting: `.add/PROJECT.md` (the foundation) and `.add/SOUL.md` -(your **voice** — what keeps the human's trust; read each session; human-owned, -self-improving — `soul.md`). Then branch on state: +`status` names two orient files: `.add/PROJECT.md` (the foundation) and `.add/SOUL.md` +(your **voice** — read each session; human-owned, self-improving — `soul.md`). Then branch on state: -- **No `.add/state.json` yet** (`status` says `no .add/ project found`) → **autonomous setup**: if - `.add/.intent` exists, read it — the installer's one-line first-build intent (a NOTE, never an init - trigger) — to seed your kickoff; then YOU run init — `add.py init --name "" --stage - --await-lock` — and read `phases/0-setup.md` to draft the foundation + §1–§3 through to the human - baseline approval. +- **No `.add/state.json` yet** (`status` says `no .add/ project found`) → **autonomous setup**: read + `.add/.intent` if present (the installer's first-build intent — a NOTE, never an init trigger), then + YOU run `add.py init --name "" --stage --await-lock` and read `phases/0-setup.md` + to draft the foundation + §1–§3 to the human baseline approval. - **A task is active** → open its `.add/tasks//TASK.md`, read the `phase:` marker, load the matching `phases/-.md`. Work *only* that phase. - **No active task** → first SIZE the request (Intake below), then `add.py new-task --title "..."`. -**Quick ref** — `status --brief` resume · `advance --fill ` write+continue · `status --section ` read one §body · `gate PASS` at verify. +**Quick ref** — `status --brief` resume · `advance --fill ` write+continue · `status --section ` one §body · `gate PASS` at verify. **Flag mode** — two human-owned settings (never auto-picked): **fast** (task) · **auto** (mode). -- **fast** — `new-task --fast`: minimal template, freeze-gated; a milestone-free `--fast` task is a - blessed low-ceremony lane. Jot ideas with `add.py todo ""` (then `todo` to - list · `todo --done `). -- **auto** — `autonomy: auto` (default) auto-gates verify on evidence; lower the autonomy level with - `add.py autonomy set conservative|manual` for a human gate · `new-milestone --await-confirm` - confirm-gates a milestone's tasks. +- **fast** — `new-task --fast`: minimal template, freeze-gated; a milestone-free `--fast` task is + the blessed low-ceremony lane. Jot ideas: `add.py todo ""` · `todo` lists · `todo --done `. +- **auto** — `autonomy: auto` (default) auto-gates verify on evidence; `add.py autonomy set + conservative|manual` restores a human gate · `new-milestone --await-confirm` confirm-gates + a milestone's tasks. ## Intake — size a request before creating scope -Classify a raw request BEFORE any milestone or task: read `intake.md`, place it in one bucket — -`new-major` · `sub-milestone` · `task` · `change-request` — propose `{ bucket, rationale, command }`; -the human confirms. A question or unsharp intent? **Interview before you size** (`intake.md`). Once -`new-major`/`sub-milestone`, draft the `MILESTONE.md` (goal · scope · exit criteria · breadth-first -tasks) — read `scope.md`. Create it `new-milestone --await-confirm`, then `milestone-confirm ` -gates `new-task` until the parent is agreed. For `task`/`change-request`: `add.py new-task` then the -first phase guide. +Classify a raw request BEFORE any scope: read `intake.md`, place it in one bucket — `new-major` · +`sub-milestone` · `task` · `change-request` — propose `{ bucket, rationale, command }`; the human +confirms. Unsharp intent? **Interview before you size** (`intake.md`). For a milestone bucket draft +`MILESTONE.md` (goal · scope · exit criteria · breadth-first tasks) — read `scope.md` — then +`new-milestone --await-confirm` + `milestone-confirm ` (gates `new-task` until agreed). For +`task`/`change-request`: `add.py new-task` then the first phase guide. ## The flow and which file to load -Load the phase guide **only for the phase you are in** (progressive disclosure): +Load **only the phase you are in** (progressive disclosure): | Phase | Guide | Produces (TASK.md section) | Who leads | Bundle | |-------|-------|----------------------------|-----------|--------| @@ -96,10 +91,10 @@ Load the phase guide **only for the phase you are in** (progressive disclosure): | observe | `phases/7-observe.md` | §7 spec delta | human + AI | VERIFY | † **The specification bundle (v7).** §1–§4 are one bundle; the human gives **one approval at the -contract freeze**, lowest-confidence-first — see `run.md`. +contract freeze**, lowest-confidence-first — `run.md`. ‡ **Verify auto-gate (v6–v7).** Under `autonomy: auto` (default) a run may auto-PASS on complete evidence (*auto-resolved* — an explicit PASS, not a skip). **Security always escalates** (HARD-STOP); -so do concurrency / architecture residue and a lowered autonomy level (`conservative` / `manual`) — `run.md`. +so do concurrency / architecture residue and a lowered autonomy level — `run.md`. At every human decision point (intake · bundle approval · gate · milestone close) follow `report-template.md`: open with the banner then the ARC (goal · done · plan, engine-sourced), then PLAN/SHAPE → SUMMARY → @@ -107,47 +102,23 @@ FLAGS → DECIDED → EVIDENCE → APPROVE → NEXT; show-before-ask; never pre- Read `report-template.md`/`run.md` at most once per session — each phase guide carries its gate card. In **observe**, emit **lessons learned** tagged by which of the five (`DDD · SDD · UDD · TDD · ADD`) -they improve (write them `open`; the human consolidates into `PROJECT.md`) — grammar + lifecycle in -`deltas.md`. At milestone close (or on demand) the retrospective consolidation gathers confirmed deltas -into a versioned foundation — `fold.md`; then (separately, after) compact each foundation spec's stable -tail — `compact-foundation.md`. Observe also tunes your voice: propose a confirmable voice delta that, -once the human confirms, rewrites `SOUL.md` (the human is the only writer) — `soul.md`. +they improve (written `open`; the human consolidates into `PROJECT.md`) — grammar + lifecycle in +`deltas.md`. At milestone close the retrospective consolidation gathers confirmed deltas into a versioned +foundation — `fold.md`; then compact each spec's stable tail — `compact-foundation.md`. Observe also +tunes your voice: a confirmable delta the human confirms rewrites `SOUL.md` (the human is the only +writer) — `soul.md`. ## Beyond the bundle — load on demand -- **§3 CONTRACT FROZEN** → build→verify is a dynamic, auto-gated run (`autonomy: auto` default; lower to - `conservative`/`manual` for a human gate) — `run.md`. Pipeline ready tasks behind frozen - contracts — `streams.md`. Delegate one piece of your plan to a subagent — the named roster - (`add-design`/`add-build`/`add-verify`/`add-persona`/`add-advisor`) is agent-call-preferred, the - default execution mode over an ad-hoc spawn; when to spawn, the prompt template, the tier — - `advisor.md`. Self-score a draft (0–1 across six dimensions, refine if any < 0.9) — `confidence.md`. - Both advisory; the engine never spawns. -- **Small, low-risk task**, less ceremony → the **fast lane**: `new-task --fast` scaffolds the minimal - `TASK.fast.md`, bundle approved in one freeze — `phases/fast-lane.md`. Floor held (frozen contract · - red test · verify gate; freeze-gated under any milestone). Collapse, never skip; opt-in. -- **UI feature** at specify → the **design-definition loop** (UDD): intake the design axes → review the - domain → research and reuse components → wireframe → a captured screen the human confirms **before** build — `design.md`. - Tool-agnostic; the engine never renders. -- Tasks all done but the milestone **goal** unmet → `milestone-done` holds it open; the loop turns open - deltas + extras into the next tasks until the goal is met — `loop.md`. -- `status` prints **`MVP covered → propose graduation`** (every milestone done AND stage criteria all - `[x]`) → `graduate.md`: `graduation-report` → co-specify interview → draft ≥1 production milestone → - human confirm → then `stage production`. Guarded (`stage_no_roadmap`); the FINAL step, never a bare flip. -- `status` prints **`→ releasable: N milestone(s) closed since last release`** → `release.md` (the 5th - scope level): `release-report` → draft notes from the consolidated deltas → meet the readiness floor - (security HARD-STOP is un-forceable) → human confirms → `add.py release ` records the cut - (CHANGELOG + `RELEASES.md` ledger + milestone attribution). The engine records; the human runs the - tag/publish/deploy. A release bundles ≥1 milestone, orthogonal to stage. -- **Monorepo / multi-repo** — a milestone spans more than one green bar (a BE + its FE) → the - **component pillar**: declare components in `.add/components.toml`, gate each task on its component's - green-bar, freeze cross-component contracts (`produces:`/`consumes:`), hold the FE until the BE - freezes, `federate pull` across repos — `components.md`. Opt-in; no components = today. -- **Project-fit personas** — the **persona loop** seeds `.add/personas/.md`, grows them via - observe→delta→consolidate, applies them in design/streams/advisor/build (advisory; never lowers a gate) — `docs/18-personas.md`. -- **Risk-class of a task** — declare `sensitivity:` in the TASK header (base `security|data|architecture| - mechanical`, always valid). EXTEND it with your project's domain classes in `GLOSSARY.md`'s `## Sensitivity - classes` section; freeze/status/check read base ∪ project. The AI keeps the domain vocabulary current — - `sensitivity.md`. Security is a human floor in every tier; only `mechanical` is advisor-gatable — see `advisor-gate-relax` in `run.md`. +One trigger = one guide — full prose: `beyond.md`; load only when a trigger fires: + +- §3 FROZEN → auto-gated run `run.md` · pipelines `streams.md` · subagent roster `advisor.md` (agent-call-preferred, the default execution mode) · + self-score `confidence.md` +- small low-risk task → fast lane `phases/fast-lane.md` · UI feature → UDD loop `design.md` +- milestone goal unmet at `milestone-done` → `loop.md` +- status cues: `MVP covered` → `graduate.md` · closed-milestone cut → `release.md` +- monorepo green-bars → `components.md` · the persona loop (`.add/personas/`) → `docs/18-personas.md` · + `sensitivity:` risk classes + `advisor-gate-relax` → `sensitivity.md` ## Non-negotiable rules (from the method) @@ -165,26 +136,25 @@ once the human confirms, rewrites `SOUL.md` (the human is the only writer) — ` ## Advancing -After a phase's exit gate is met, advance the state (this also syncs the TASK.md marker): +Exit gate met → advance (also syncs the TASK.md marker): ```bash python3 .add/tooling/add.py advance # next phase of the active task python3 .add/tooling/add.py gate PASS # at verify: records PASS, marks done -python3 .add/tooling/add.py use # switch the active task (e.g. across parallel streams) +python3 .add/tooling/add.py use # switch the active task ``` ## Depth by stage -The steps never change; their depth does (read the stage from `add.py status`): +The steps never change; their depth does (stage from `add.py status`): -- **prototype** — run light; code is throwaway; design/experience is the point. -- **poc** — run contract/tests/build deeply on the single riskiest slice only. +- **prototype** — light; throwaway code; design/experience is the point. +- **poc** — contract/tests/build deep on the single riskiest slice only. - **mvp** — full flow, narrow scope, light observation. -- **production** — every step at full rigor + the observe loop. Reach it via the `graduate.md` - orchestration when status shows `MVP covered → propose graduation`, never a bare `stage production` flip. +- **production** — full rigor + the observe loop; reached via the `graduate.md` orchestration + (`MVP covered → propose graduation`), never a bare `stage production` flip. ## The method rationale -The full method (the *why* behind every rule) is the AIDD book in `.add/docs/`. When a phase decision is -genuinely unclear, read the linked chapter — each phase guide points to its chapter. Do not duplicate -the book here; load it on demand. +The full method (the *why*) is the AIDD book in `.add/docs/`; each phase guide +points to its chapter. Read it only when a decision is genuinely unclear — never duplicate it here. diff --git a/.claude/skills/add/beyond.md b/.claude/skills/add/beyond.md new file mode 100644 index 00000000..4f36c00d --- /dev/null +++ b/.claude/skills/add/beyond.md @@ -0,0 +1,38 @@ +# Beyond the bundle — the full routing prose + +SKILL.md's compact index names each trigger; this guide carries the full routing +prose. Load it (or the one guide you need) only when a trigger fires. + +- **§3 CONTRACT FROZEN** → build→verify is a dynamic, auto-gated run (`autonomy: auto` default; lower to + `conservative`/`manual` for a human gate) — `run.md`. Pipeline ready tasks behind frozen + contracts — `streams.md`. Delegate one piece of your plan to a subagent — the named roster + (`add-design`/`add-build`/`add-verify`/`add-persona`/`add-advisor`) is agent-call-preferred, the + default execution mode over an ad-hoc spawn; when to spawn, the prompt template, the tier — + `advisor.md`. Self-score a draft (0–1 across six dimensions, refine if any < 0.9) — `confidence.md`. + Both advisory; the engine never spawns. +- **Small, low-risk task**, less ceremony → the **fast lane**: `new-task --fast` scaffolds the minimal + `TASK.fast.md`, bundle approved in one freeze — `phases/fast-lane.md`. Floor held (frozen contract · + red test · verify gate; freeze-gated under any milestone). Collapse, never skip; opt-in. +- **UI feature** at specify → the **design-definition loop** (UDD): intake the design axes → review the + domain → research and reuse components → wireframe → a captured screen the human confirms **before** build — `design.md`. + Tool-agnostic; the engine never renders. +- Tasks all done but the milestone **goal** unmet → `milestone-done` holds it open; the loop turns open + deltas + extras into the next tasks until the goal is met — `loop.md`. +- `status` prints **`MVP covered → propose graduation`** (every milestone done AND stage criteria all + `[x]`) → `graduate.md`: `graduation-report` → co-specify interview → draft ≥1 production milestone → + human confirm → then `stage production`. Guarded (`stage_no_roadmap`); the FINAL step, never a bare flip. +- `status` prints **`→ releasable: N milestone(s) closed since last release`** → `release.md` (the 5th + scope level): `release-report` → draft notes from the consolidated deltas → meet the readiness floor + (security HARD-STOP is un-forceable) → human confirms → `add.py release ` records the cut + (CHANGELOG + `RELEASES.md` ledger + milestone attribution). The engine records; the human runs the + tag/publish/deploy. A release bundles ≥1 milestone, orthogonal to stage. +- **Monorepo / multi-repo** — a milestone spans more than one green bar (a BE + its FE) → the + **component pillar**: declare components in `.add/components.toml`, gate each task on its component's + green-bar, freeze cross-component contracts (`produces:`/`consumes:`), hold the FE until the BE + freezes, `federate pull` across repos — `components.md`. Opt-in; no components = today. +- **Project-fit personas** — the **persona loop** seeds `.add/personas/.md`, grows them via + observe→delta→consolidate, applies them in design/streams/advisor/build (advisory; never lowers a gate) — `docs/18-personas.md`. +- **Risk-class of a task** — declare `sensitivity:` in the TASK header (base `security|data|architecture| + mechanical`, always valid). EXTEND it with your project's domain classes in `GLOSSARY.md`'s `## Sensitivity + classes` section; freeze/status/check read base ∪ project. The AI keeps the domain vocabulary current — + `sensitivity.md`. Security is a human floor in every tier; only `mechanical` is advisor-gatable — see `advisor-gate-relax` in `run.md`. diff --git a/add-method/skill/add/SKILL.md b/add-method/skill/add/SKILL.md index 19fdda97..70b894a0 100644 --- a/add-method/skill/add/SKILL.md +++ b/add-method/skill/add/SKILL.md @@ -22,67 +22,62 @@ metadata: You are the orchestrator. ADD keeps the AI fast *and* safe by fixing direction (spec, scenarios, contract, failing tests) **before** the build, and trusting the -result through passing evidence rather than a plausible-looking diff. +result through passing evidence, not a plausible-looking diff. -**One file = one task.** Each feature is one `.add/tasks//TASK.md` — seven step sections -(Specify → Scenarios → Plan → Tests → Build → Verify → Observe), filled top to bottom; the tool -tracks where you are. The **plan** phase unites grounding + frozen contract + build strategy. +**One file = one task.** Each feature is one `.add/tasks//TASK.md` — seven step sections, +filled top to bottom; the tool tracks where you are. The **plan** phase unites grounding + frozen +contract + build strategy. **The `--todo` fast-path.** When the skill ARGUMENTS begin with `--todo`, skip orienting: route to `add.py todo` and print its output — `--todo ` captures · `--todo` lists open todos · -`--todo --done ` closes (engine errors surfaced verbatim) — then STOP. +`--todo --done ` closes (engine errors surface verbatim) — then STOP. ## Always start here (orient — do not skip) -Engine: `.add/tooling/add.py` · book: `.add/docs/`. Ensure the engine is in the project first: +Engine: `.add/tooling/add.py` · book: `.add/docs/`. Ensure it is in the project: -- It exists → go straight to `status` below. -- It does NOT (ADD installed as a Claude Code plugin — engine + book ride in the plugin) → - materialize once: `node "${CLAUDE_PLUGIN_ROOT}/bin/cli.js" init --no-skill`. That drops - `.add/tooling/` (engine) + `.add/docs/` (book) + the agent-agnostic `CLAUDE.md` block — like an - npm/pip install; the skill stays in the plugin. +- It exists → go to `status` below. +- It does NOT (a plugin install — engine + book ride in it) → materialize once: + `node "${CLAUDE_PLUGIN_ROOT}/bin/cli.js" init --no-skill` — drops `.add/tooling/` (engine) + + `.add/docs/` (book) + the agent-agnostic `CLAUDE.md` block; the skill stays in the plugin. -Resume from the tool, never by re-reading the repo: +Resume from the tool, never re-read the repo: ```bash python3 .add/tooling/add.py status ``` -`status` names two files to read when orienting: `.add/PROJECT.md` (the foundation) and `.add/SOUL.md` -(your **voice** — what keeps the human's trust; read each session; human-owned, -self-improving — `soul.md`). Then branch on state: +`status` names two orient files: `.add/PROJECT.md` (the foundation) and `.add/SOUL.md` +(your **voice** — read each session; human-owned, self-improving — `soul.md`). Then branch on state: -- **No `.add/state.json` yet** (`status` says `no .add/ project found`) → **autonomous setup**: if - `.add/.intent` exists, read it — the installer's one-line first-build intent (a NOTE, never an init - trigger) — to seed your kickoff; then YOU run init — `add.py init --name "" --stage - --await-lock` — and read `phases/0-setup.md` to draft the foundation + §1–§3 through to the human - baseline approval. +- **No `.add/state.json` yet** (`status` says `no .add/ project found`) → **autonomous setup**: read + `.add/.intent` if present (the installer's first-build intent — a NOTE, never an init trigger), then + YOU run `add.py init --name "" --stage --await-lock` and read `phases/0-setup.md` + to draft the foundation + §1–§3 to the human baseline approval. - **A task is active** → open its `.add/tasks//TASK.md`, read the `phase:` marker, load the matching `phases/-.md`. Work *only* that phase. - **No active task** → first SIZE the request (Intake below), then `add.py new-task --title "..."`. -**Quick ref** — `status --brief` resume · `advance --fill ` write+continue · `status --section ` read one §body · `gate PASS` at verify. +**Quick ref** — `status --brief` resume · `advance --fill ` write+continue · `status --section ` one §body · `gate PASS` at verify. **Flag mode** — two human-owned settings (never auto-picked): **fast** (task) · **auto** (mode). -- **fast** — `new-task --fast`: minimal template, freeze-gated; a milestone-free `--fast` task is a - blessed low-ceremony lane. Jot ideas with `add.py todo ""` (then `todo` to - list · `todo --done `). -- **auto** — `autonomy: auto` (default) auto-gates verify on evidence; lower the autonomy level with - `add.py autonomy set conservative|manual` for a human gate · `new-milestone --await-confirm` - confirm-gates a milestone's tasks. +- **fast** — `new-task --fast`: minimal template, freeze-gated; a milestone-free `--fast` task is + the blessed low-ceremony lane. Jot ideas: `add.py todo ""` · `todo` lists · `todo --done `. +- **auto** — `autonomy: auto` (default) auto-gates verify on evidence; `add.py autonomy set + conservative|manual` restores a human gate · `new-milestone --await-confirm` confirm-gates + a milestone's tasks. ## Intake — size a request before creating scope -Classify a raw request BEFORE any milestone or task: read `intake.md`, place it in one bucket — -`new-major` · `sub-milestone` · `task` · `change-request` — propose `{ bucket, rationale, command }`; -the human confirms. A question or unsharp intent? **Interview before you size** (`intake.md`). Once -`new-major`/`sub-milestone`, draft the `MILESTONE.md` (goal · scope · exit criteria · breadth-first -tasks) — read `scope.md`. Create it `new-milestone --await-confirm`, then `milestone-confirm ` -gates `new-task` until the parent is agreed. For `task`/`change-request`: `add.py new-task` then the -first phase guide. +Classify a raw request BEFORE any scope: read `intake.md`, place it in one bucket — `new-major` · +`sub-milestone` · `task` · `change-request` — propose `{ bucket, rationale, command }`; the human +confirms. Unsharp intent? **Interview before you size** (`intake.md`). For a milestone bucket draft +`MILESTONE.md` (goal · scope · exit criteria · breadth-first tasks) — read `scope.md` — then +`new-milestone --await-confirm` + `milestone-confirm ` (gates `new-task` until agreed). For +`task`/`change-request`: `add.py new-task` then the first phase guide. ## The flow and which file to load -Load the phase guide **only for the phase you are in** (progressive disclosure): +Load **only the phase you are in** (progressive disclosure): | Phase | Guide | Produces (TASK.md section) | Who leads | Bundle | |-------|-------|----------------------------|-----------|--------| @@ -96,10 +91,10 @@ Load the phase guide **only for the phase you are in** (progressive disclosure): | observe | `phases/7-observe.md` | §7 spec delta | human + AI | VERIFY | † **The specification bundle (v7).** §1–§4 are one bundle; the human gives **one approval at the -contract freeze**, lowest-confidence-first — see `run.md`. +contract freeze**, lowest-confidence-first — `run.md`. ‡ **Verify auto-gate (v6–v7).** Under `autonomy: auto` (default) a run may auto-PASS on complete evidence (*auto-resolved* — an explicit PASS, not a skip). **Security always escalates** (HARD-STOP); -so do concurrency / architecture residue and a lowered autonomy level (`conservative` / `manual`) — `run.md`. +so do concurrency / architecture residue and a lowered autonomy level — `run.md`. At every human decision point (intake · bundle approval · gate · milestone close) follow `report-template.md`: open with the banner then the ARC (goal · done · plan, engine-sourced), then PLAN/SHAPE → SUMMARY → @@ -107,47 +102,23 @@ FLAGS → DECIDED → EVIDENCE → APPROVE → NEXT; show-before-ask; never pre- Read `report-template.md`/`run.md` at most once per session — each phase guide carries its gate card. In **observe**, emit **lessons learned** tagged by which of the five (`DDD · SDD · UDD · TDD · ADD`) -they improve (write them `open`; the human consolidates into `PROJECT.md`) — grammar + lifecycle in -`deltas.md`. At milestone close (or on demand) the retrospective consolidation gathers confirmed deltas -into a versioned foundation — `fold.md`; then (separately, after) compact each foundation spec's stable -tail — `compact-foundation.md`. Observe also tunes your voice: propose a confirmable voice delta that, -once the human confirms, rewrites `SOUL.md` (the human is the only writer) — `soul.md`. +they improve (written `open`; the human consolidates into `PROJECT.md`) — grammar + lifecycle in +`deltas.md`. At milestone close the retrospective consolidation gathers confirmed deltas into a versioned +foundation — `fold.md`; then compact each spec's stable tail — `compact-foundation.md`. Observe also +tunes your voice: a confirmable delta the human confirms rewrites `SOUL.md` (the human is the only +writer) — `soul.md`. ## Beyond the bundle — load on demand -- **§3 CONTRACT FROZEN** → build→verify is a dynamic, auto-gated run (`autonomy: auto` default; lower to - `conservative`/`manual` for a human gate) — `run.md`. Pipeline ready tasks behind frozen - contracts — `streams.md`. Delegate one piece of your plan to a subagent — the named roster - (`add-design`/`add-build`/`add-verify`/`add-persona`/`add-advisor`) is agent-call-preferred, the - default execution mode over an ad-hoc spawn; when to spawn, the prompt template, the tier — - `advisor.md`. Self-score a draft (0–1 across six dimensions, refine if any < 0.9) — `confidence.md`. - Both advisory; the engine never spawns. -- **Small, low-risk task**, less ceremony → the **fast lane**: `new-task --fast` scaffolds the minimal - `TASK.fast.md`, bundle approved in one freeze — `phases/fast-lane.md`. Floor held (frozen contract · - red test · verify gate; freeze-gated under any milestone). Collapse, never skip; opt-in. -- **UI feature** at specify → the **design-definition loop** (UDD): intake the design axes → review the - domain → research and reuse components → wireframe → a captured screen the human confirms **before** build — `design.md`. - Tool-agnostic; the engine never renders. -- Tasks all done but the milestone **goal** unmet → `milestone-done` holds it open; the loop turns open - deltas + extras into the next tasks until the goal is met — `loop.md`. -- `status` prints **`MVP covered → propose graduation`** (every milestone done AND stage criteria all - `[x]`) → `graduate.md`: `graduation-report` → co-specify interview → draft ≥1 production milestone → - human confirm → then `stage production`. Guarded (`stage_no_roadmap`); the FINAL step, never a bare flip. -- `status` prints **`→ releasable: N milestone(s) closed since last release`** → `release.md` (the 5th - scope level): `release-report` → draft notes from the consolidated deltas → meet the readiness floor - (security HARD-STOP is un-forceable) → human confirms → `add.py release ` records the cut - (CHANGELOG + `RELEASES.md` ledger + milestone attribution). The engine records; the human runs the - tag/publish/deploy. A release bundles ≥1 milestone, orthogonal to stage. -- **Monorepo / multi-repo** — a milestone spans more than one green bar (a BE + its FE) → the - **component pillar**: declare components in `.add/components.toml`, gate each task on its component's - green-bar, freeze cross-component contracts (`produces:`/`consumes:`), hold the FE until the BE - freezes, `federate pull` across repos — `components.md`. Opt-in; no components = today. -- **Project-fit personas** — the **persona loop** seeds `.add/personas/.md`, grows them via - observe→delta→consolidate, applies them in design/streams/advisor/build (advisory; never lowers a gate) — `docs/18-personas.md`. -- **Risk-class of a task** — declare `sensitivity:` in the TASK header (base `security|data|architecture| - mechanical`, always valid). EXTEND it with your project's domain classes in `GLOSSARY.md`'s `## Sensitivity - classes` section; freeze/status/check read base ∪ project. The AI keeps the domain vocabulary current — - `sensitivity.md`. Security is a human floor in every tier; only `mechanical` is advisor-gatable — see `advisor-gate-relax` in `run.md`. +One trigger = one guide — full prose: `beyond.md`; load only when a trigger fires: + +- §3 FROZEN → auto-gated run `run.md` · pipelines `streams.md` · subagent roster `advisor.md` (agent-call-preferred, the default execution mode) · + self-score `confidence.md` +- small low-risk task → fast lane `phases/fast-lane.md` · UI feature → UDD loop `design.md` +- milestone goal unmet at `milestone-done` → `loop.md` +- status cues: `MVP covered` → `graduate.md` · closed-milestone cut → `release.md` +- monorepo green-bars → `components.md` · the persona loop (`.add/personas/`) → `docs/18-personas.md` · + `sensitivity:` risk classes + `advisor-gate-relax` → `sensitivity.md` ## Non-negotiable rules (from the method) @@ -165,26 +136,25 @@ once the human confirms, rewrites `SOUL.md` (the human is the only writer) — ` ## Advancing -After a phase's exit gate is met, advance the state (this also syncs the TASK.md marker): +Exit gate met → advance (also syncs the TASK.md marker): ```bash python3 .add/tooling/add.py advance # next phase of the active task python3 .add/tooling/add.py gate PASS # at verify: records PASS, marks done -python3 .add/tooling/add.py use # switch the active task (e.g. across parallel streams) +python3 .add/tooling/add.py use # switch the active task ``` ## Depth by stage -The steps never change; their depth does (read the stage from `add.py status`): +The steps never change; their depth does (stage from `add.py status`): -- **prototype** — run light; code is throwaway; design/experience is the point. -- **poc** — run contract/tests/build deeply on the single riskiest slice only. +- **prototype** — light; throwaway code; design/experience is the point. +- **poc** — contract/tests/build deep on the single riskiest slice only. - **mvp** — full flow, narrow scope, light observation. -- **production** — every step at full rigor + the observe loop. Reach it via the `graduate.md` - orchestration when status shows `MVP covered → propose graduation`, never a bare `stage production` flip. +- **production** — full rigor + the observe loop; reached via the `graduate.md` orchestration + (`MVP covered → propose graduation`), never a bare `stage production` flip. ## The method rationale -The full method (the *why* behind every rule) is the AIDD book in `.add/docs/`. When a phase decision is -genuinely unclear, read the linked chapter — each phase guide points to its chapter. Do not duplicate -the book here; load it on demand. +The full method (the *why*) is the AIDD book in `.add/docs/`; each phase guide +points to its chapter. Read it only when a decision is genuinely unclear — never duplicate it here. diff --git a/add-method/skill/add/beyond.md b/add-method/skill/add/beyond.md new file mode 100644 index 00000000..4f36c00d --- /dev/null +++ b/add-method/skill/add/beyond.md @@ -0,0 +1,38 @@ +# Beyond the bundle — the full routing prose + +SKILL.md's compact index names each trigger; this guide carries the full routing +prose. Load it (or the one guide you need) only when a trigger fires. + +- **§3 CONTRACT FROZEN** → build→verify is a dynamic, auto-gated run (`autonomy: auto` default; lower to + `conservative`/`manual` for a human gate) — `run.md`. Pipeline ready tasks behind frozen + contracts — `streams.md`. Delegate one piece of your plan to a subagent — the named roster + (`add-design`/`add-build`/`add-verify`/`add-persona`/`add-advisor`) is agent-call-preferred, the + default execution mode over an ad-hoc spawn; when to spawn, the prompt template, the tier — + `advisor.md`. Self-score a draft (0–1 across six dimensions, refine if any < 0.9) — `confidence.md`. + Both advisory; the engine never spawns. +- **Small, low-risk task**, less ceremony → the **fast lane**: `new-task --fast` scaffolds the minimal + `TASK.fast.md`, bundle approved in one freeze — `phases/fast-lane.md`. Floor held (frozen contract · + red test · verify gate; freeze-gated under any milestone). Collapse, never skip; opt-in. +- **UI feature** at specify → the **design-definition loop** (UDD): intake the design axes → review the + domain → research and reuse components → wireframe → a captured screen the human confirms **before** build — `design.md`. + Tool-agnostic; the engine never renders. +- Tasks all done but the milestone **goal** unmet → `milestone-done` holds it open; the loop turns open + deltas + extras into the next tasks until the goal is met — `loop.md`. +- `status` prints **`MVP covered → propose graduation`** (every milestone done AND stage criteria all + `[x]`) → `graduate.md`: `graduation-report` → co-specify interview → draft ≥1 production milestone → + human confirm → then `stage production`. Guarded (`stage_no_roadmap`); the FINAL step, never a bare flip. +- `status` prints **`→ releasable: N milestone(s) closed since last release`** → `release.md` (the 5th + scope level): `release-report` → draft notes from the consolidated deltas → meet the readiness floor + (security HARD-STOP is un-forceable) → human confirms → `add.py release ` records the cut + (CHANGELOG + `RELEASES.md` ledger + milestone attribution). The engine records; the human runs the + tag/publish/deploy. A release bundles ≥1 milestone, orthogonal to stage. +- **Monorepo / multi-repo** — a milestone spans more than one green bar (a BE + its FE) → the + **component pillar**: declare components in `.add/components.toml`, gate each task on its component's + green-bar, freeze cross-component contracts (`produces:`/`consumes:`), hold the FE until the BE + freezes, `federate pull` across repos — `components.md`. Opt-in; no components = today. +- **Project-fit personas** — the **persona loop** seeds `.add/personas/.md`, grows them via + observe→delta→consolidate, applies them in design/streams/advisor/build (advisory; never lowers a gate) — `docs/18-personas.md`. +- **Risk-class of a task** — declare `sensitivity:` in the TASK header (base `security|data|architecture| + mechanical`, always valid). EXTEND it with your project's domain classes in `GLOSSARY.md`'s `## Sensitivity + classes` section; freeze/status/check read base ∪ project. The AI keeps the domain vocabulary current — + `sensitivity.md`. Security is a human floor in every tier; only `mechanical` is advisor-gatable — see `advisor-gate-relax` in `run.md`. diff --git a/add-method/src/add_method/_bundled/skill/add/SKILL.md b/add-method/src/add_method/_bundled/skill/add/SKILL.md index 19fdda97..70b894a0 100644 --- a/add-method/src/add_method/_bundled/skill/add/SKILL.md +++ b/add-method/src/add_method/_bundled/skill/add/SKILL.md @@ -22,67 +22,62 @@ metadata: You are the orchestrator. ADD keeps the AI fast *and* safe by fixing direction (spec, scenarios, contract, failing tests) **before** the build, and trusting the -result through passing evidence rather than a plausible-looking diff. +result through passing evidence, not a plausible-looking diff. -**One file = one task.** Each feature is one `.add/tasks//TASK.md` — seven step sections -(Specify → Scenarios → Plan → Tests → Build → Verify → Observe), filled top to bottom; the tool -tracks where you are. The **plan** phase unites grounding + frozen contract + build strategy. +**One file = one task.** Each feature is one `.add/tasks//TASK.md` — seven step sections, +filled top to bottom; the tool tracks where you are. The **plan** phase unites grounding + frozen +contract + build strategy. **The `--todo` fast-path.** When the skill ARGUMENTS begin with `--todo`, skip orienting: route to `add.py todo` and print its output — `--todo ` captures · `--todo` lists open todos · -`--todo --done ` closes (engine errors surfaced verbatim) — then STOP. +`--todo --done ` closes (engine errors surface verbatim) — then STOP. ## Always start here (orient — do not skip) -Engine: `.add/tooling/add.py` · book: `.add/docs/`. Ensure the engine is in the project first: +Engine: `.add/tooling/add.py` · book: `.add/docs/`. Ensure it is in the project: -- It exists → go straight to `status` below. -- It does NOT (ADD installed as a Claude Code plugin — engine + book ride in the plugin) → - materialize once: `node "${CLAUDE_PLUGIN_ROOT}/bin/cli.js" init --no-skill`. That drops - `.add/tooling/` (engine) + `.add/docs/` (book) + the agent-agnostic `CLAUDE.md` block — like an - npm/pip install; the skill stays in the plugin. +- It exists → go to `status` below. +- It does NOT (a plugin install — engine + book ride in it) → materialize once: + `node "${CLAUDE_PLUGIN_ROOT}/bin/cli.js" init --no-skill` — drops `.add/tooling/` (engine) + + `.add/docs/` (book) + the agent-agnostic `CLAUDE.md` block; the skill stays in the plugin. -Resume from the tool, never by re-reading the repo: +Resume from the tool, never re-read the repo: ```bash python3 .add/tooling/add.py status ``` -`status` names two files to read when orienting: `.add/PROJECT.md` (the foundation) and `.add/SOUL.md` -(your **voice** — what keeps the human's trust; read each session; human-owned, -self-improving — `soul.md`). Then branch on state: +`status` names two orient files: `.add/PROJECT.md` (the foundation) and `.add/SOUL.md` +(your **voice** — read each session; human-owned, self-improving — `soul.md`). Then branch on state: -- **No `.add/state.json` yet** (`status` says `no .add/ project found`) → **autonomous setup**: if - `.add/.intent` exists, read it — the installer's one-line first-build intent (a NOTE, never an init - trigger) — to seed your kickoff; then YOU run init — `add.py init --name "" --stage - --await-lock` — and read `phases/0-setup.md` to draft the foundation + §1–§3 through to the human - baseline approval. +- **No `.add/state.json` yet** (`status` says `no .add/ project found`) → **autonomous setup**: read + `.add/.intent` if present (the installer's first-build intent — a NOTE, never an init trigger), then + YOU run `add.py init --name "" --stage --await-lock` and read `phases/0-setup.md` + to draft the foundation + §1–§3 to the human baseline approval. - **A task is active** → open its `.add/tasks//TASK.md`, read the `phase:` marker, load the matching `phases/-.md`. Work *only* that phase. - **No active task** → first SIZE the request (Intake below), then `add.py new-task --title "..."`. -**Quick ref** — `status --brief` resume · `advance --fill ` write+continue · `status --section ` read one §body · `gate PASS` at verify. +**Quick ref** — `status --brief` resume · `advance --fill ` write+continue · `status --section ` one §body · `gate PASS` at verify. **Flag mode** — two human-owned settings (never auto-picked): **fast** (task) · **auto** (mode). -- **fast** — `new-task --fast`: minimal template, freeze-gated; a milestone-free `--fast` task is a - blessed low-ceremony lane. Jot ideas with `add.py todo ""` (then `todo` to - list · `todo --done `). -- **auto** — `autonomy: auto` (default) auto-gates verify on evidence; lower the autonomy level with - `add.py autonomy set conservative|manual` for a human gate · `new-milestone --await-confirm` - confirm-gates a milestone's tasks. +- **fast** — `new-task --fast`: minimal template, freeze-gated; a milestone-free `--fast` task is + the blessed low-ceremony lane. Jot ideas: `add.py todo ""` · `todo` lists · `todo --done `. +- **auto** — `autonomy: auto` (default) auto-gates verify on evidence; `add.py autonomy set + conservative|manual` restores a human gate · `new-milestone --await-confirm` confirm-gates + a milestone's tasks. ## Intake — size a request before creating scope -Classify a raw request BEFORE any milestone or task: read `intake.md`, place it in one bucket — -`new-major` · `sub-milestone` · `task` · `change-request` — propose `{ bucket, rationale, command }`; -the human confirms. A question or unsharp intent? **Interview before you size** (`intake.md`). Once -`new-major`/`sub-milestone`, draft the `MILESTONE.md` (goal · scope · exit criteria · breadth-first -tasks) — read `scope.md`. Create it `new-milestone --await-confirm`, then `milestone-confirm ` -gates `new-task` until the parent is agreed. For `task`/`change-request`: `add.py new-task` then the -first phase guide. +Classify a raw request BEFORE any scope: read `intake.md`, place it in one bucket — `new-major` · +`sub-milestone` · `task` · `change-request` — propose `{ bucket, rationale, command }`; the human +confirms. Unsharp intent? **Interview before you size** (`intake.md`). For a milestone bucket draft +`MILESTONE.md` (goal · scope · exit criteria · breadth-first tasks) — read `scope.md` — then +`new-milestone --await-confirm` + `milestone-confirm ` (gates `new-task` until agreed). For +`task`/`change-request`: `add.py new-task` then the first phase guide. ## The flow and which file to load -Load the phase guide **only for the phase you are in** (progressive disclosure): +Load **only the phase you are in** (progressive disclosure): | Phase | Guide | Produces (TASK.md section) | Who leads | Bundle | |-------|-------|----------------------------|-----------|--------| @@ -96,10 +91,10 @@ Load the phase guide **only for the phase you are in** (progressive disclosure): | observe | `phases/7-observe.md` | §7 spec delta | human + AI | VERIFY | † **The specification bundle (v7).** §1–§4 are one bundle; the human gives **one approval at the -contract freeze**, lowest-confidence-first — see `run.md`. +contract freeze**, lowest-confidence-first — `run.md`. ‡ **Verify auto-gate (v6–v7).** Under `autonomy: auto` (default) a run may auto-PASS on complete evidence (*auto-resolved* — an explicit PASS, not a skip). **Security always escalates** (HARD-STOP); -so do concurrency / architecture residue and a lowered autonomy level (`conservative` / `manual`) — `run.md`. +so do concurrency / architecture residue and a lowered autonomy level — `run.md`. At every human decision point (intake · bundle approval · gate · milestone close) follow `report-template.md`: open with the banner then the ARC (goal · done · plan, engine-sourced), then PLAN/SHAPE → SUMMARY → @@ -107,47 +102,23 @@ FLAGS → DECIDED → EVIDENCE → APPROVE → NEXT; show-before-ask; never pre- Read `report-template.md`/`run.md` at most once per session — each phase guide carries its gate card. In **observe**, emit **lessons learned** tagged by which of the five (`DDD · SDD · UDD · TDD · ADD`) -they improve (write them `open`; the human consolidates into `PROJECT.md`) — grammar + lifecycle in -`deltas.md`. At milestone close (or on demand) the retrospective consolidation gathers confirmed deltas -into a versioned foundation — `fold.md`; then (separately, after) compact each foundation spec's stable -tail — `compact-foundation.md`. Observe also tunes your voice: propose a confirmable voice delta that, -once the human confirms, rewrites `SOUL.md` (the human is the only writer) — `soul.md`. +they improve (written `open`; the human consolidates into `PROJECT.md`) — grammar + lifecycle in +`deltas.md`. At milestone close the retrospective consolidation gathers confirmed deltas into a versioned +foundation — `fold.md`; then compact each spec's stable tail — `compact-foundation.md`. Observe also +tunes your voice: a confirmable delta the human confirms rewrites `SOUL.md` (the human is the only +writer) — `soul.md`. ## Beyond the bundle — load on demand -- **§3 CONTRACT FROZEN** → build→verify is a dynamic, auto-gated run (`autonomy: auto` default; lower to - `conservative`/`manual` for a human gate) — `run.md`. Pipeline ready tasks behind frozen - contracts — `streams.md`. Delegate one piece of your plan to a subagent — the named roster - (`add-design`/`add-build`/`add-verify`/`add-persona`/`add-advisor`) is agent-call-preferred, the - default execution mode over an ad-hoc spawn; when to spawn, the prompt template, the tier — - `advisor.md`. Self-score a draft (0–1 across six dimensions, refine if any < 0.9) — `confidence.md`. - Both advisory; the engine never spawns. -- **Small, low-risk task**, less ceremony → the **fast lane**: `new-task --fast` scaffolds the minimal - `TASK.fast.md`, bundle approved in one freeze — `phases/fast-lane.md`. Floor held (frozen contract · - red test · verify gate; freeze-gated under any milestone). Collapse, never skip; opt-in. -- **UI feature** at specify → the **design-definition loop** (UDD): intake the design axes → review the - domain → research and reuse components → wireframe → a captured screen the human confirms **before** build — `design.md`. - Tool-agnostic; the engine never renders. -- Tasks all done but the milestone **goal** unmet → `milestone-done` holds it open; the loop turns open - deltas + extras into the next tasks until the goal is met — `loop.md`. -- `status` prints **`MVP covered → propose graduation`** (every milestone done AND stage criteria all - `[x]`) → `graduate.md`: `graduation-report` → co-specify interview → draft ≥1 production milestone → - human confirm → then `stage production`. Guarded (`stage_no_roadmap`); the FINAL step, never a bare flip. -- `status` prints **`→ releasable: N milestone(s) closed since last release`** → `release.md` (the 5th - scope level): `release-report` → draft notes from the consolidated deltas → meet the readiness floor - (security HARD-STOP is un-forceable) → human confirms → `add.py release ` records the cut - (CHANGELOG + `RELEASES.md` ledger + milestone attribution). The engine records; the human runs the - tag/publish/deploy. A release bundles ≥1 milestone, orthogonal to stage. -- **Monorepo / multi-repo** — a milestone spans more than one green bar (a BE + its FE) → the - **component pillar**: declare components in `.add/components.toml`, gate each task on its component's - green-bar, freeze cross-component contracts (`produces:`/`consumes:`), hold the FE until the BE - freezes, `federate pull` across repos — `components.md`. Opt-in; no components = today. -- **Project-fit personas** — the **persona loop** seeds `.add/personas/.md`, grows them via - observe→delta→consolidate, applies them in design/streams/advisor/build (advisory; never lowers a gate) — `docs/18-personas.md`. -- **Risk-class of a task** — declare `sensitivity:` in the TASK header (base `security|data|architecture| - mechanical`, always valid). EXTEND it with your project's domain classes in `GLOSSARY.md`'s `## Sensitivity - classes` section; freeze/status/check read base ∪ project. The AI keeps the domain vocabulary current — - `sensitivity.md`. Security is a human floor in every tier; only `mechanical` is advisor-gatable — see `advisor-gate-relax` in `run.md`. +One trigger = one guide — full prose: `beyond.md`; load only when a trigger fires: + +- §3 FROZEN → auto-gated run `run.md` · pipelines `streams.md` · subagent roster `advisor.md` (agent-call-preferred, the default execution mode) · + self-score `confidence.md` +- small low-risk task → fast lane `phases/fast-lane.md` · UI feature → UDD loop `design.md` +- milestone goal unmet at `milestone-done` → `loop.md` +- status cues: `MVP covered` → `graduate.md` · closed-milestone cut → `release.md` +- monorepo green-bars → `components.md` · the persona loop (`.add/personas/`) → `docs/18-personas.md` · + `sensitivity:` risk classes + `advisor-gate-relax` → `sensitivity.md` ## Non-negotiable rules (from the method) @@ -165,26 +136,25 @@ once the human confirms, rewrites `SOUL.md` (the human is the only writer) — ` ## Advancing -After a phase's exit gate is met, advance the state (this also syncs the TASK.md marker): +Exit gate met → advance (also syncs the TASK.md marker): ```bash python3 .add/tooling/add.py advance # next phase of the active task python3 .add/tooling/add.py gate PASS # at verify: records PASS, marks done -python3 .add/tooling/add.py use # switch the active task (e.g. across parallel streams) +python3 .add/tooling/add.py use # switch the active task ``` ## Depth by stage -The steps never change; their depth does (read the stage from `add.py status`): +The steps never change; their depth does (stage from `add.py status`): -- **prototype** — run light; code is throwaway; design/experience is the point. -- **poc** — run contract/tests/build deeply on the single riskiest slice only. +- **prototype** — light; throwaway code; design/experience is the point. +- **poc** — contract/tests/build deep on the single riskiest slice only. - **mvp** — full flow, narrow scope, light observation. -- **production** — every step at full rigor + the observe loop. Reach it via the `graduate.md` - orchestration when status shows `MVP covered → propose graduation`, never a bare `stage production` flip. +- **production** — full rigor + the observe loop; reached via the `graduate.md` orchestration + (`MVP covered → propose graduation`), never a bare `stage production` flip. ## The method rationale -The full method (the *why* behind every rule) is the AIDD book in `.add/docs/`. When a phase decision is -genuinely unclear, read the linked chapter — each phase guide points to its chapter. Do not duplicate -the book here; load it on demand. +The full method (the *why*) is the AIDD book in `.add/docs/`; each phase guide +points to its chapter. Read it only when a decision is genuinely unclear — never duplicate it here. diff --git a/add-method/src/add_method/_bundled/skill/add/beyond.md b/add-method/src/add_method/_bundled/skill/add/beyond.md new file mode 100644 index 00000000..4f36c00d --- /dev/null +++ b/add-method/src/add_method/_bundled/skill/add/beyond.md @@ -0,0 +1,38 @@ +# Beyond the bundle — the full routing prose + +SKILL.md's compact index names each trigger; this guide carries the full routing +prose. Load it (or the one guide you need) only when a trigger fires. + +- **§3 CONTRACT FROZEN** → build→verify is a dynamic, auto-gated run (`autonomy: auto` default; lower to + `conservative`/`manual` for a human gate) — `run.md`. Pipeline ready tasks behind frozen + contracts — `streams.md`. Delegate one piece of your plan to a subagent — the named roster + (`add-design`/`add-build`/`add-verify`/`add-persona`/`add-advisor`) is agent-call-preferred, the + default execution mode over an ad-hoc spawn; when to spawn, the prompt template, the tier — + `advisor.md`. Self-score a draft (0–1 across six dimensions, refine if any < 0.9) — `confidence.md`. + Both advisory; the engine never spawns. +- **Small, low-risk task**, less ceremony → the **fast lane**: `new-task --fast` scaffolds the minimal + `TASK.fast.md`, bundle approved in one freeze — `phases/fast-lane.md`. Floor held (frozen contract · + red test · verify gate; freeze-gated under any milestone). Collapse, never skip; opt-in. +- **UI feature** at specify → the **design-definition loop** (UDD): intake the design axes → review the + domain → research and reuse components → wireframe → a captured screen the human confirms **before** build — `design.md`. + Tool-agnostic; the engine never renders. +- Tasks all done but the milestone **goal** unmet → `milestone-done` holds it open; the loop turns open + deltas + extras into the next tasks until the goal is met — `loop.md`. +- `status` prints **`MVP covered → propose graduation`** (every milestone done AND stage criteria all + `[x]`) → `graduate.md`: `graduation-report` → co-specify interview → draft ≥1 production milestone → + human confirm → then `stage production`. Guarded (`stage_no_roadmap`); the FINAL step, never a bare flip. +- `status` prints **`→ releasable: N milestone(s) closed since last release`** → `release.md` (the 5th + scope level): `release-report` → draft notes from the consolidated deltas → meet the readiness floor + (security HARD-STOP is un-forceable) → human confirms → `add.py release ` records the cut + (CHANGELOG + `RELEASES.md` ledger + milestone attribution). The engine records; the human runs the + tag/publish/deploy. A release bundles ≥1 milestone, orthogonal to stage. +- **Monorepo / multi-repo** — a milestone spans more than one green bar (a BE + its FE) → the + **component pillar**: declare components in `.add/components.toml`, gate each task on its component's + green-bar, freeze cross-component contracts (`produces:`/`consumes:`), hold the FE until the BE + freezes, `federate pull` across repos — `components.md`. Opt-in; no components = today. +- **Project-fit personas** — the **persona loop** seeds `.add/personas/.md`, grows them via + observe→delta→consolidate, applies them in design/streams/advisor/build (advisory; never lowers a gate) — `docs/18-personas.md`. +- **Risk-class of a task** — declare `sensitivity:` in the TASK header (base `security|data|architecture| + mechanical`, always valid). EXTEND it with your project's domain classes in `GLOSSARY.md`'s `## Sensitivity + classes` section; freeze/status/check read base ∪ project. The AI keeps the domain vocabulary current — + `sensitivity.md`. Security is a human floor in every tier; only `mechanical` is advisor-gatable — see `advisor-gate-relax` in `run.md`. diff --git a/add-method/src/add_method/_bundled/tooling/add.py b/add-method/src/add_method/_bundled/tooling/add.py index c9be1ece..4c5793c4 100644 --- a/add-method/src/add_method/_bundled/tooling/add.py +++ b/add-method/src/add_method/_bundled/tooling/add.py @@ -590,7 +590,7 @@ def cmd_init(args: argparse.Namespace) -> None: root = base / ROOT_DIRNAME state_path = root / STATE_FILE if state_path.exists() and not args.force: - _die(f"already initialised at {root} (use --force to reset state)") + _die(f"already initialised at {root} (use --force to reset state) — resume: add.py status") (root / "tasks").mkdir(parents=True, exist_ok=True) # Keep the engine's transient local artifacts out of git. Never-clobber: a @@ -887,11 +887,10 @@ def cmd_new_task(args: argparse.Namespace) -> None: # the same in every lane); the agent scripts ahead instead of rediscovering. print("recipe — this task's remaining engine calls:") print(" add.py advance --to plan (write the section rules first)") - print(" add.py freeze --by [human gate — approves the whole plan]") - print(" add.py advance (plan -> tests: write the RED suite)") - print(" add.py advance (tests -> build: make it green)") - print(" add.py advance (build -> verify: gather evidence)") - print(" add.py gate PASS (record the verify outcome)") + print(" add.py freeze --by --cross [human gate — approves the whole plan; " + "--cross lands in tests]") + print(" add.py advance (after the RED suite: tests -> build, make it green)") + print(" add.py gate PASS (from build — crosses to verify and records the outcome)") def _delta_task_md(root: Path, state: dict, raw_slug: str | None) -> tuple[str, Path, bool]: @@ -1035,6 +1034,19 @@ class (three tasks independently rediscovered it; the shared-rules ledger has th never written — the agent/human re-drafts and re-freezes deliberately.""" resolved = _declared_scope(root, slug) rootp = root.parent + + def _touches_paths() -> list[str]: + # path-heads on the §3 Touches lines that exist in the tree (never speculative) + paths: list[str] = [] + body = _raw_phase_bodies(root, slug).get(3, "") + for line in body.splitlines(): + if not line.lstrip().startswith("Touches"): + continue + for tok in re.findall(r"([\w.-]+(?:/[\w.-]+)+):", line): + if tok not in paths and (rootp / tok).exists(): + paths.append(tok) + return paths + missing_all = False if resolved is None: print("scope: UNDECLARED (grandfathered)") @@ -1045,15 +1057,13 @@ class (three tasks independently rediscovered it; the shared-rules ledger has th for rel, ok in marks: print(f"scope: {rel} [{'ok' if ok else 'MISSING'}]") missing_all = not any(ok for _, ok in marks) + # scope-coverage-hint: the too-narrow class behind the measured re-cross + # repairs — tokens resolve [ok] yet the build's real targets sit outside them. + for tok in _touches_paths(): + if not _in_scope(tok, resolved): + print(f"note: §3 Touches cites {tok} outside the declared scope") if resolved is None or not resolved or missing_all: - paths: list[str] = [] - body = _raw_phase_bodies(root, slug).get(3, "") - for line in body.splitlines(): - if not line.lstrip().startswith("Touches"): - continue - for tok in re.findall(r"([\w.-]+(?:/[\w.-]+)+):", line): - if tok not in paths and (rootp / tok).exists(): - paths.append(tok) + paths = _touches_paths() if paths: print("scope (proposed from §3 Touches): " + " ".join(f"`{p}`" for p in paths)) @@ -1196,6 +1206,20 @@ def cmd_freeze(args: argparse.Namespace) -> None: _scope_echo(root, slug) except Exception: pass + # compound-ticks: `--cross` compresses the pure freeze->tests tick into this same + # call — OPT-IN (the bare freeze is byte-identical), and only a freeze that STAMPED + # at the plan phase crosses (the already-frozen no-op returned above; a change-request + # re-freeze mid-build reaches here at a non-plan phase and gets the note instead). + if getattr(args, "cross", False): + cur = state["tasks"][slug]["phase"] + if cur == "plan": + state["tasks"][slug]["phase"] = "tests" + state["tasks"][slug]["updated"] = _now() + save_state(root, state) # durable state FIRST + _sync_task_marker(root, slug, "tests") # then the TASK.md mirror + print("crossed into tests — write one failing test per scenario (compound tick)") + else: + print(f"--cross: only a plan-phase freeze crosses (task is at '{cur}' — no-op)") print(_next_footer(root, state)) @@ -2108,6 +2132,17 @@ def cmd_gate(args: argparse.Namespace) -> None: completing = args.outcome in ("PASS", "RISK-ACCEPTED") if completing: current = state["tasks"][slug]["phase"] + # compound-ticks: a completing verdict at BUILD auto-crosses build->verify in + # the same call (the tick between them is pure — no work happens on it), then + # runs every completion check below unchanged. Phases before build keep their + # refusal verbatim; HARD-STOP never reaches this branch (recordable anywhere). + if current == "build": + state["tasks"][slug]["phase"] = "verify" + state["tasks"][slug]["updated"] = _now() + save_state(root, state) # durable state FIRST + _sync_task_marker(root, slug, "verify") # then the TASK.md mirror + print(f"crossed build -> verify (compound tick) — recording {args.outcome}") + current = "verify" if _phase_index(current) < _phase_index("verify"): code = ("gate_pass_before_verify" if args.outcome == "PASS" else "gate_risk_accepted_before_verify") @@ -4468,6 +4503,9 @@ def cmd_new_milestone(args: argparse.Namespace) -> None: # exclusive with the note above (same predicate, opposite branch — never both). slugs = ", ".join(_real_persona_slugs(root)) print(f"persona-fit: {PERSONA_FIT_HINT_TEMPLATE.format(slugs=slugs)}") + # milestone-lane-nudge: the WM1 bait point — a single-task request routed here costs + # ~10 calls of milestone ceremony. Advisory only; the command-point beats wrapper prose. + print("lane: single task? the oneshot lane is cheaper: add.py new-task --oneshot") print(_next_footer(root, state)) # converges the old "Decompose it into tasks: …" hint @@ -8330,6 +8368,9 @@ def build_parser() -> argparse.ArgumentParser: help="task to freeze (default: the active task)") pfz.add_argument("--by", default=None, help="approver name (default: the resolved actor); " "REQUIRED (an agent id) with --ai-plan-verify") + pfz.add_argument("--cross", action="store_true", + help="compound tick: after a plan-phase freeze stamps, land in tests " + "in the same call (opt-in; a non-plan or refused freeze never crosses)") pfz.add_argument("--ai-plan-verify", action="store_true", dest="ai_plan_verify", help="AI-plan-verify-gate: let an AI agent (--by AGENT_ID) perform this " "freeze in place of a human — refused unless gate_mode: ai-plan-verify, " diff --git a/add-method/src/add_method/_bundled/tooling/engine_pin.py b/add-method/src/add_method/_bundled/tooling/engine_pin.py index 1324ca86..84c86fde 100644 --- a/add-method/src/add_method/_bundled/tooling/engine_pin.py +++ b/add-method/src/add_method/_bundled/tooling/engine_pin.py @@ -17,5 +17,5 @@ this file only ever holds the newest pointer. """ -ENGINE_MD5 = "8aef02aed5d34c80210866852cc996da" # re-aimed @ fold-draft-at-close (ceremony-to-effort 7/7: cmd_milestone_done's SPEC nudge grows a mechanical fold draft — seed when a cited path resolves, drop? when none does, seed-by-default when pathless; stdout only, fail-open). prior: 80b8121a… @ scope-echo-draft +ENGINE_MD5 = "26f78f0480771939d4604d21f62546f6" # re-aimed @ compound-ticks (call-floor 4/5: opt-in freeze --cross lands a plan freeze in tests; a completing gate at build auto-crosses to verify; the new-task recipe advertises the compressed lane). prior: 6f4e791f… @ scope-coverage-hint ENGINE_PKG_MD5 = "d83fc67fc7979adde37358a29d5e3f46" # re-aimed @ kickoff-truth v2 (io_state.py: _die gains the dup-failure short-circuit — _register_invocation/_clear_last_fail/_dup_fail_hint; the sig sidecar lives in the OS tmp dir keyed by md5(root), NEVER in the .add tree — the reject-writes-nothing floor; constants.py untouched at v2). prior: 28212a55… @ guides-and-skill diff --git a/add-method/tooling/add.py b/add-method/tooling/add.py index c9be1ece..4c5793c4 100644 --- a/add-method/tooling/add.py +++ b/add-method/tooling/add.py @@ -590,7 +590,7 @@ def cmd_init(args: argparse.Namespace) -> None: root = base / ROOT_DIRNAME state_path = root / STATE_FILE if state_path.exists() and not args.force: - _die(f"already initialised at {root} (use --force to reset state)") + _die(f"already initialised at {root} (use --force to reset state) — resume: add.py status") (root / "tasks").mkdir(parents=True, exist_ok=True) # Keep the engine's transient local artifacts out of git. Never-clobber: a @@ -887,11 +887,10 @@ def cmd_new_task(args: argparse.Namespace) -> None: # the same in every lane); the agent scripts ahead instead of rediscovering. print("recipe — this task's remaining engine calls:") print(" add.py advance --to plan (write the section rules first)") - print(" add.py freeze --by [human gate — approves the whole plan]") - print(" add.py advance (plan -> tests: write the RED suite)") - print(" add.py advance (tests -> build: make it green)") - print(" add.py advance (build -> verify: gather evidence)") - print(" add.py gate PASS (record the verify outcome)") + print(" add.py freeze --by --cross [human gate — approves the whole plan; " + "--cross lands in tests]") + print(" add.py advance (after the RED suite: tests -> build, make it green)") + print(" add.py gate PASS (from build — crosses to verify and records the outcome)") def _delta_task_md(root: Path, state: dict, raw_slug: str | None) -> tuple[str, Path, bool]: @@ -1035,6 +1034,19 @@ class (three tasks independently rediscovered it; the shared-rules ledger has th never written — the agent/human re-drafts and re-freezes deliberately.""" resolved = _declared_scope(root, slug) rootp = root.parent + + def _touches_paths() -> list[str]: + # path-heads on the §3 Touches lines that exist in the tree (never speculative) + paths: list[str] = [] + body = _raw_phase_bodies(root, slug).get(3, "") + for line in body.splitlines(): + if not line.lstrip().startswith("Touches"): + continue + for tok in re.findall(r"([\w.-]+(?:/[\w.-]+)+):", line): + if tok not in paths and (rootp / tok).exists(): + paths.append(tok) + return paths + missing_all = False if resolved is None: print("scope: UNDECLARED (grandfathered)") @@ -1045,15 +1057,13 @@ class (three tasks independently rediscovered it; the shared-rules ledger has th for rel, ok in marks: print(f"scope: {rel} [{'ok' if ok else 'MISSING'}]") missing_all = not any(ok for _, ok in marks) + # scope-coverage-hint: the too-narrow class behind the measured re-cross + # repairs — tokens resolve [ok] yet the build's real targets sit outside them. + for tok in _touches_paths(): + if not _in_scope(tok, resolved): + print(f"note: §3 Touches cites {tok} outside the declared scope") if resolved is None or not resolved or missing_all: - paths: list[str] = [] - body = _raw_phase_bodies(root, slug).get(3, "") - for line in body.splitlines(): - if not line.lstrip().startswith("Touches"): - continue - for tok in re.findall(r"([\w.-]+(?:/[\w.-]+)+):", line): - if tok not in paths and (rootp / tok).exists(): - paths.append(tok) + paths = _touches_paths() if paths: print("scope (proposed from §3 Touches): " + " ".join(f"`{p}`" for p in paths)) @@ -1196,6 +1206,20 @@ def cmd_freeze(args: argparse.Namespace) -> None: _scope_echo(root, slug) except Exception: pass + # compound-ticks: `--cross` compresses the pure freeze->tests tick into this same + # call — OPT-IN (the bare freeze is byte-identical), and only a freeze that STAMPED + # at the plan phase crosses (the already-frozen no-op returned above; a change-request + # re-freeze mid-build reaches here at a non-plan phase and gets the note instead). + if getattr(args, "cross", False): + cur = state["tasks"][slug]["phase"] + if cur == "plan": + state["tasks"][slug]["phase"] = "tests" + state["tasks"][slug]["updated"] = _now() + save_state(root, state) # durable state FIRST + _sync_task_marker(root, slug, "tests") # then the TASK.md mirror + print("crossed into tests — write one failing test per scenario (compound tick)") + else: + print(f"--cross: only a plan-phase freeze crosses (task is at '{cur}' — no-op)") print(_next_footer(root, state)) @@ -2108,6 +2132,17 @@ def cmd_gate(args: argparse.Namespace) -> None: completing = args.outcome in ("PASS", "RISK-ACCEPTED") if completing: current = state["tasks"][slug]["phase"] + # compound-ticks: a completing verdict at BUILD auto-crosses build->verify in + # the same call (the tick between them is pure — no work happens on it), then + # runs every completion check below unchanged. Phases before build keep their + # refusal verbatim; HARD-STOP never reaches this branch (recordable anywhere). + if current == "build": + state["tasks"][slug]["phase"] = "verify" + state["tasks"][slug]["updated"] = _now() + save_state(root, state) # durable state FIRST + _sync_task_marker(root, slug, "verify") # then the TASK.md mirror + print(f"crossed build -> verify (compound tick) — recording {args.outcome}") + current = "verify" if _phase_index(current) < _phase_index("verify"): code = ("gate_pass_before_verify" if args.outcome == "PASS" else "gate_risk_accepted_before_verify") @@ -4468,6 +4503,9 @@ def cmd_new_milestone(args: argparse.Namespace) -> None: # exclusive with the note above (same predicate, opposite branch — never both). slugs = ", ".join(_real_persona_slugs(root)) print(f"persona-fit: {PERSONA_FIT_HINT_TEMPLATE.format(slugs=slugs)}") + # milestone-lane-nudge: the WM1 bait point — a single-task request routed here costs + # ~10 calls of milestone ceremony. Advisory only; the command-point beats wrapper prose. + print("lane: single task? the oneshot lane is cheaper: add.py new-task --oneshot") print(_next_footer(root, state)) # converges the old "Decompose it into tasks: …" hint @@ -8330,6 +8368,9 @@ def build_parser() -> argparse.ArgumentParser: help="task to freeze (default: the active task)") pfz.add_argument("--by", default=None, help="approver name (default: the resolved actor); " "REQUIRED (an agent id) with --ai-plan-verify") + pfz.add_argument("--cross", action="store_true", + help="compound tick: after a plan-phase freeze stamps, land in tests " + "in the same call (opt-in; a non-plan or refused freeze never crosses)") pfz.add_argument("--ai-plan-verify", action="store_true", dest="ai_plan_verify", help="AI-plan-verify-gate: let an AI agent (--by AGENT_ID) perform this " "freeze in place of a human — refused unless gate_mode: ai-plan-verify, " diff --git a/add-method/tooling/engine_pin.py b/add-method/tooling/engine_pin.py index 1324ca86..84c86fde 100644 --- a/add-method/tooling/engine_pin.py +++ b/add-method/tooling/engine_pin.py @@ -17,5 +17,5 @@ this file only ever holds the newest pointer. """ -ENGINE_MD5 = "8aef02aed5d34c80210866852cc996da" # re-aimed @ fold-draft-at-close (ceremony-to-effort 7/7: cmd_milestone_done's SPEC nudge grows a mechanical fold draft — seed when a cited path resolves, drop? when none does, seed-by-default when pathless; stdout only, fail-open). prior: 80b8121a… @ scope-echo-draft +ENGINE_MD5 = "26f78f0480771939d4604d21f62546f6" # re-aimed @ compound-ticks (call-floor 4/5: opt-in freeze --cross lands a plan freeze in tests; a completing gate at build auto-crosses to verify; the new-task recipe advertises the compressed lane). prior: 6f4e791f… @ scope-coverage-hint ENGINE_PKG_MD5 = "d83fc67fc7979adde37358a29d5e3f46" # re-aimed @ kickoff-truth v2 (io_state.py: _die gains the dup-failure short-circuit — _register_invocation/_clear_last_fail/_dup_fail_hint; the sig sidecar lives in the OS tmp dir keyed by md5(root), NEVER in the .add tree — the reject-writes-nothing floor; constants.py untouched at v2). prior: 28212a55… @ guides-and-skill diff --git a/add-method/tooling/test_compound_ticks.py b/add-method/tooling/test_compound_ticks.py new file mode 100644 index 00000000..8a526801 --- /dev/null +++ b/add-method/tooling/test_compound_ticks.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +"""Red/green tests for compound ticks (task compound-ticks, frozen v1): the oneshot +lane's 8-9 calls include two PURE ticks with no work between them (freeze->tests · +build->verify->gate). Two OPT-IN compressions remove them: `freeze --cross` lands a +plan-phase freeze in tests, and a completing gate at the BUILD phase auto-crosses +build->verify before recording. Defaults stay byte-identical; refusals verbatim. + +Run: python3 -m unittest test_compound_ticks -v +""" +import io +import json +import os +import re +import shutil +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import add + +_SEC3 = """### Grounding +Touches (files · symbols): pkg/api/handler.py:handle — the endpoint +Anchors the contract cites: handle +Ground SHA: hand1234 — hand-grounded + +### Contract + +``` +GET /w -> ok +``` + +`Least-sure flag surfaced at freeze:` + ⚠ [contract] the shape is the least-sure part — cost if wrong: a reparse. +Status: DRAFT + +### Build-strategy +Scope (may touch): `pkg/api/` +""" + + +class _Harness(unittest.TestCase): + """A live board arranged through the real CLI (mirrors test_scope_coverage_hint's + harness — duplicated per this repo's one-harness-per-file norm).""" + + def setUp(self): + self._cwd = Path.cwd() + self.tmp = Path(tempfile.mkdtemp(prefix="add-cti-")).resolve() + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + self.addCleanup(os.chdir, self._cwd) + os.chdir(self.tmp) + + def _run(self, *argv): + out, err = io.StringIO(), io.StringIO() + code = 0 + try: + with redirect_stdout(out), redirect_stderr(err): + add.main(list(argv)) + except SystemExit as e: + code = e.code if isinstance(e.code, int) else 1 + return out.getvalue() + err.getvalue(), code + + def _ok(self, *argv): + text, code = self._run(*argv) + self.assertEqual(code, 0, f"{argv} exited {code}: {text}") + return text + + def _phase(self, slug="t"): + state = json.loads((self.tmp / ".add" / "state.json").read_text(encoding="utf-8")) + return state["tasks"][slug]["phase"] + + def _planned_board(self): + """A oneshot task at the plan phase with a freezeable §3.""" + self._ok("init", "--name", "demo", "--stage", "mvp") + self._ok("lock", "--force") + self._ok("new-task", "t", "--title", "T", "--oneshot") + self._ok("advance", "--to", "plan") + p = self.tmp / ".add" / "tasks" / "t" / "TASK.md" + text = p.read_text(encoding="utf-8") + new = re.sub(r"(?ms)(^### Grounding.*?)(?=^---)", _SEC3 + "\n", text, count=1) + self.assertNotEqual(new, text, "fixture §3 replacement failed") + new = re.sub(r"(?m)^Boundary:.*$", "Boundary: none — no external input", new, count=1) + p.write_text(new, encoding="utf-8") + return p + + +class FreezeCrossTest(_Harness): + def test_cross_lands_in_tests(self): # M1 + Accept + self._planned_board() + out = self._ok("freeze", "--by", "Tester", "--cross") + self.assertIn("froze §3 of t @ v1", out) + self.assertIn("crossed into tests", out) + self.assertEqual(self._phase(), "tests", + "--cross must land a plan-phase freeze in tests") + + def test_bare_freeze_unchanged(self): # M1 (default) + self._planned_board() + out = self._ok("freeze", "--by", "Tester") + self.assertNotIn("crossed", out, "the bare freeze must not cross") + self.assertNotIn("--cross", out.replace("freeze --by", ""), + "the bare freeze output must not advertise the flag") + self.assertEqual(self._phase(), "plan", "the bare freeze stays at plan") + + def test_cross_on_refused_freeze_no_crossing(self): # R1 + self._planned_board() + self._ok("freeze", "--by", "Tester") + out = self._ok("freeze", "--by", "Tester", "--cross") # benign no-op path + self.assertIn("already frozen", out) + self.assertNotIn("crossed", out, + "a freeze that did not stamp must never cross") + self.assertEqual(self._phase(), "plan") + + def test_cross_nonplan_note(self): # R2 + Boundary + p = self._planned_board() + self._ok("freeze", "--by", "Tester", "--cross") # now at tests + text = p.read_text(encoding="utf-8") + new = re.sub(r"(?m)^Status: FROZEN @ v1.*$", "Status: DRAFT", text, count=1) + self.assertNotEqual(new, text, "change-request DRAFT flip failed") + p.write_text(new, encoding="utf-8") + out = self._ok("freeze", "--by", "Tester", "--cross") # v2, phase != plan + self.assertIn("froze §3 of t @ v2", out) + self.assertIn("only a plan-phase freeze crosses", out) + self.assertEqual(self._phase(), "tests", "a non-plan --cross must not move the phase") + + +class GateFromBuildTest(_Harness): + def test_gate_pass_from_build(self): # M2 + Accept + self._planned_board() + self._ok("freeze", "--by", "Tester") + self._ok("phase", "build", "t") # the logged override + out = self._ok("gate", "PASS", "t") + self.assertIn("crossed build -> verify", out) + self.assertEqual(self._phase(), "done", + "gate PASS from build must cross and record in one call") + + def test_gate_refused_before_build(self): # R3 (verbatim refusal) + self._planned_board() + self._ok("freeze", "--by", "Tester", "--cross") # at tests + out, code = self._run("gate", "PASS", "t") + self.assertNotEqual(code, 0, "gate PASS from tests must still refuse") + self.assertIn("gate_pass_before_verify", out) + self.assertNotIn("crossed", out) + self.assertEqual(self._phase(), "tests") + + +class RecipeCrossTest(_Harness): + def test_recipe_advertises_compressed_lane(self): # M3 + self._ok("init", "--name", "demo", "--stage", "mvp") + self._ok("lock", "--force") + out = self._ok("new-task", "t", "--title", "T", "--oneshot") + tail = out[out.lower().index("recipe"):] + self.assertIn("--cross", tail, "the recipe must advertise freeze --cross") + self.assertIn("gate PASS", tail) + self.assertIn("from build", tail, + "the recipe must say the gate records from build") + + +if __name__ == "__main__": + unittest.main() diff --git a/add-method/tooling/test_init_resume_pointer.py b/add-method/tooling/test_init_resume_pointer.py new file mode 100644 index 00000000..66cca3c6 --- /dev/null +++ b/add-method/tooling/test_init_resume_pointer.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Red/green tests for the init resume pointer (task init-resume-pointer, frozen v1): +the WM1 re-measure showed every rep re-running `init` on an already-initialised +project (+2 calls/rep). The refusal now names the resume command in the same +message; it stays a refusal (nonzero exit, no tree write). + +Run: python3 -m unittest test_init_resume_pointer -v +""" +import io +import os +import shutil +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import add + + +class _Harness(unittest.TestCase): + def setUp(self): + self._cwd = Path.cwd() + self.tmp = Path(tempfile.mkdtemp(prefix="add-irp-")).resolve() + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + self.addCleanup(os.chdir, self._cwd) + os.chdir(self.tmp) + + def _run(self, *argv): + out, err = io.StringIO(), io.StringIO() + code = 0 + try: + with redirect_stdout(out), redirect_stderr(err): + add.main(list(argv)) + except SystemExit as e: + code = e.code if isinstance(e.code, int) else 1 + return out.getvalue() + err.getvalue(), code + + def _snapshot(self): + return {p: p.read_bytes() for p in (self.tmp / ".add").rglob("*") if p.is_file()} + + +class ResumePointerTest(_Harness): + def test_refusal_names_resume(self): # M1+M2 + out, code = self._run("init", "--name", "demo", "--stage", "mvp") + self.assertEqual(code, 0, out) + before = self._snapshot() + out, code = self._run("init", "--name", "demo", "--stage", "mvp") + self.assertNotEqual(code, 0, "the second init must still refuse") + self.assertIn("already initialised", out, "the existing message head survives") + self.assertIn("resume: add.py status", out, + "the refusal must name the resume command") + self.assertEqual(before, self._snapshot(), + "a refused init must write nothing") + + def test_force_still_resets(self): # R1 + self._run("init", "--name", "demo", "--stage", "mvp") + out, code = self._run("init", "--name", "demo", "--stage", "mvp", "--force") + self.assertEqual(code, 0, f"--force must still reset: {out}") + + +if __name__ == "__main__": + unittest.main() diff --git a/add-method/tooling/test_kickoff_truth.py b/add-method/tooling/test_kickoff_truth.py index 5e0bceda..831dde99 100644 --- a/add-method/tooling/test_kickoff_truth.py +++ b/add-method/tooling/test_kickoff_truth.py @@ -81,15 +81,16 @@ def test_init_kickoff_names_single_task_lane_first(self): class RecipeTest(_Harness): """M2 — new-task hands the full remaining call recipe, every lane.""" - _MARKS = ("advance --to plan", "freeze --by", "gate PASS") + _MARKS = ("advance --to plan", "freeze --by", "--cross", "gate PASS") def _assert_recipe(self, out, lane): self.assertIn("recipe", out.lower(), f"{lane}: no recipe block in new-task stdout") for mark in self._MARKS: self.assertIn(mark, out, f"{lane}: recipe is missing `{mark}`") - # the three post-freeze advances (plan->tests, tests->build, build->verify) + # the compressed lane (compound-ticks): freeze --cross absorbs plan->tests and + # gate-from-build absorbs build->verify, so two advances remain in the recipe tail = out[out.lower().index("recipe"):] - self.assertGreaterEqual(tail.count("add.py advance"), 3, + self.assertGreaterEqual(tail.count("add.py advance"), 2, f"{lane}: recipe must list every remaining advance") def test_new_task_emits_full_recipe_milestone_lane(self): diff --git a/add-method/tooling/test_milestone_lane_nudge.py b/add-method/tooling/test_milestone_lane_nudge.py new file mode 100644 index 00000000..d973713d --- /dev/null +++ b/add-method/tooling/test_milestone_lane_nudge.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Red/green tests for the milestone lane nudge (task milestone-lane-nudge, frozen v1): +WM1 rep r1 lost 9-10 calls to milestone ceremony on single-task work despite the +wrapper prescribing the oneshot lane. Upstream prose doesn't land; a nudge at the +command point does (kickoff-truth precedent) — new-milestone now names the lane. + +Run: python3 -m unittest test_milestone_lane_nudge -v +""" +import io +import os +import shutil +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import add + + +class _Harness(unittest.TestCase): + def setUp(self): + self._cwd = Path.cwd() + self.tmp = Path(tempfile.mkdtemp(prefix="add-mln-")).resolve() + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + self.addCleanup(os.chdir, self._cwd) + os.chdir(self.tmp) + + def _ok(self, *argv): + out, err = io.StringIO(), io.StringIO() + code = 0 + try: + with redirect_stdout(out), redirect_stderr(err): + add.main(list(argv)) + except SystemExit as e: + code = e.code if isinstance(e.code, int) else 1 + text = out.getvalue() + err.getvalue() + self.assertEqual(code, 0, f"{argv} exited {code}: {text}") + return text + + +class LaneNudgeTest(_Harness): + def _create(self): + self._ok("init", "--name", "demo", "--stage", "mvp") + self._ok("lock", "--force") + return self._ok("new-milestone", "m", "--title", "M", "--goal", "g") + + def test_nudge_prints(self): # M1 + out = self._create() + self.assertIn("oneshot lane is cheaper", out) + self.assertIn("--oneshot", out) + + def test_footer_stays_last(self): # M2 + out = self._create() + last = [ln for ln in out.splitlines() if ln.strip()][-1] + self.assertTrue(last.startswith("next:"), + f"the next-footer must stay the last line, got: {last!r}") + + +if __name__ == "__main__": + unittest.main() diff --git a/add-method/tooling/test_per_step_hooks.py b/add-method/tooling/test_per_step_hooks.py index a1c31a86..d7edc266 100644 --- a/add-method/tooling/test_per_step_hooks.py +++ b/add-method/tooling/test_per_step_hooks.py @@ -71,9 +71,10 @@ def test_skill_cross_ref(self) -> None: self.assertIn("confidence.md", self.skill, "SKILL.md must cross-ref confidence.md") def test_wording_surface_count_unchanged(self) -> None: - self.assertEqual(len(_wl.surface_files()), 31, + self.assertEqual(len(_wl.surface_files()), 32, "editing guides must not change the wording-lint surface count " - "(31: 0-ground.md + 3-contract.md merged into 3-plan.md @ guides-and-skill, net -1; " + "(32: +beyond.md @ skill-orient-split; " + "0-ground.md + 3-contract.md merged into 3-plan.md @ guides-and-skill, net -1; " "+fast-lane.md @ fast-lane, +components.md @ component-method-docs, " "+sensitivity.md @ sensitivity-glossary, +self-improve.md @ self-improving-guide)") diff --git a/add-method/tooling/test_scope_coverage_hint.py b/add-method/tooling/test_scope_coverage_hint.py new file mode 100644 index 00000000..11745f0a --- /dev/null +++ b/add-method/tooling/test_scope_coverage_hint.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""Red/green tests for the scope coverage hint (task scope-coverage-hint, frozen v1): +the WM1 re-cross repairs came from TOO-NARROW declarations — every token resolved +[ok] but the build touched §3 Touches paths outside them. The freeze echo now notes +each existing Touches path not covered by the resolved scope. Propose-not-impose; +never speculative (nonexistent paths stay silent); fail-open. + +Run: python3 -m unittest test_scope_coverage_hint -v +""" +import io +import os +import re +import shutil +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import add + +_SEC3 = """### Grounding +Touches (files · symbols): pkg/api/handler.py:handle — covered · lib/util.py:helper — outside · ghost/nowhere.py:phantom — nonexistent +Anchors the contract cites: handle +Ground SHA: hand1234 — hand-grounded + +### Contract + +``` +GET /w -> ok +``` + +`Least-sure flag surfaced at freeze:` + ⚠ [contract] the shape is the least-sure part — cost if wrong: a reparse. +Status: DRAFT + +### Build-strategy +Scope (may touch): `pkg/api/` +""" + + +class _Harness(unittest.TestCase): + """A live board arranged through the real CLI (mirrors test_scope_echo_draft's + harness — duplicated per this repo's one-harness-per-file norm).""" + + def setUp(self): + self._cwd = Path.cwd() + self.tmp = Path(tempfile.mkdtemp(prefix="add-sch-")).resolve() + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + self.addCleanup(os.chdir, self._cwd) + os.chdir(self.tmp) + + def _ok(self, *argv): + out, err = io.StringIO(), io.StringIO() + code = 0 + try: + with redirect_stdout(out), redirect_stderr(err): + add.main(list(argv)) + except SystemExit as e: + code = e.code if isinstance(e.code, int) else 1 + text = out.getvalue() + err.getvalue() + self.assertEqual(code, 0, f"{argv} exited {code}: {text}") + return text + + def _frozen_board(self, drop_scope_line: bool = False): + self._ok("init", "--name", "demo", "--stage", "mvp") + self._ok("lock", "--force") + self._ok("new-task", "t", "--title", "T", "--oneshot") + self._ok("advance", "--to", "plan") + (self.tmp / "pkg" / "api").mkdir(parents=True) + (self.tmp / "pkg" / "api" / "handler.py").write_text("x = 1\n", encoding="utf-8") + (self.tmp / "lib").mkdir() + (self.tmp / "lib" / "util.py").write_text("y = 2\n", encoding="utf-8") + p = self.tmp / ".add" / "tasks" / "t" / "TASK.md" + text = p.read_text(encoding="utf-8") + sec3 = _SEC3 + if drop_scope_line: + sec3 = "\n".join(ln for ln in sec3.splitlines() + if not ln.startswith("Scope (may touch):")) + "\n" + new = re.sub(r"(?ms)(^### Grounding.*?)(?=^---)", sec3 + "\n", text, count=1) + self.assertNotEqual(new, text, "fixture §3 replacement failed") + new = re.sub(r"(?m)^Boundary:.*$", "Boundary: none — no external input", new, count=1) + p.write_text(new, encoding="utf-8") + return self._ok("freeze", "--by", "Tester") + + +class CoverageHintTest(_Harness): + def test_outside_path_noted(self): # M1 + Boundary + out = self._frozen_board() + self.assertIn("note: §3 Touches cites lib/util.py outside the declared scope", out) + self.assertNotIn("handler.py outside", out, + "a covered Touches path must stay silent") + + def test_nonexistent_silent(self): # M2 + out = self._frozen_board() + self.assertNotIn("ghost/nowhere.py", out, + "a nonexistent Touches path must never draw a note (not speculative)") + + def test_undeclared_branch_unchanged(self): # M2/R + out = self._frozen_board(drop_scope_line=True) + self.assertIn("scope: UNDECLARED (grandfathered)", out) + self.assertIn("scope (proposed from §3 Touches):", out) + self.assertNotIn("outside the declared scope", out, + "no coverage notes without a declaration to be outside of") + + def test_footer_stays_last(self): # R + out = self._frozen_board() + last = [ln for ln in out.splitlines() if ln.strip()][-1] + self.assertTrue(last.startswith("next:")) + + +if __name__ == "__main__": + unittest.main() diff --git a/add-method/tooling/test_skill_lean.py b/add-method/tooling/test_skill_lean.py index 5856a221..c468c4c5 100644 --- a/add-method/tooling/test_skill_lean.py +++ b/add-method/tooling/test_skill_lean.py @@ -78,8 +78,15 @@ # `add.py search [ ...]` as the first action — +140 B human-approved surface # (contract FROZEN @ v1). RATIO 0.88 kept EXACTLY; baseline grows by surface ÷ ratio (+⌈140/0.88⌉=160). # The won compaction is untouched. - {"name": "core", "ratio": 0.88, "baseline": 20666, - "guides": ["SKILL.md", "intake.md"]}, + # core 20666 → 20906 @ skill-orient-split (same "rebaseline for human-approved new surface" + # method): SKILL.md (read EVERY task) splits — the verbose "Beyond the bundle" routing prose + # moves to the NEW on-demand guide beyond.md (which joins this pool; it carries core's moved + # bytes) and SKILL.md keeps a compact one-line-per-trigger index; the always-read orient core + # drops 12819→9498B. Net new surface = the index + the new file's header = +211 B + # human-approved (task skill-orient-split, contract FROZEN @ v1). RATIO 0.88 kept EXACTLY; + # baseline grows by surface ÷ ratio (+⌈211/0.88⌉=240). The won compaction is untouched. + {"name": "core", "ratio": 0.88, "baseline": 20906, + "guides": ["SKILL.md", "intake.md", "beyond.md"]}, # orchestration 50098 → 51732 @ design-intake-beat (same "rebaseline for human-approved new surface" # method): design.md's UDD loop gains a NEW front beat `### 0 · design-intake` (the four design axes # FIDELITY·CONCEPT·LAYOUT·VISUAL DESIGN) + a hard rule — +1225 B human-approved surface (milestone diff --git a/add-method/tooling/test_skill_orient_split.py b/add-method/tooling/test_skill_orient_split.py new file mode 100644 index 00000000..b1d6698f --- /dev/null +++ b/add-method/tooling/test_skill_orient_split.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Red/green tests for the skill orient split (task skill-orient-split, frozen v1): +SKILL.md is read on EVERY task but carried ~4.6KB of on-demand routing prose +("Beyond the bundle" verbose bullets) most tasks never take. That prose moves to +the on-demand guide `beyond.md`; SKILL.md keeps a compact one-line-per-trigger +index. Structural pins (pointer census, phase table, banner cue, Depth-by-stage) +are guarded by their own suites and stay green — this suite pins the SPLIT itself. + +Run: python3 -m unittest test_skill_orient_split -v +""" +import unittest +from pathlib import Path + +from test_skill_lean import ON_DEMAND_POINTERS + +_CANON = Path(__file__).resolve().parent.parent / "skill" / "add" +_SKILL = _CANON / "SKILL.md" +_BEYOND = _CANON / "beyond.md" + +# One marker per moved routing bullet — the prose must live on in beyond.md. +_MOVED_MARKERS = [ + "Pipeline ready tasks behind frozen", # run/streams spawn lane + "Collapse, never skip", # fast lane (frozen-scope wording) + "design-definition loop", # UDD + "holds it open", # milestone loop + "propose graduation", # graduate + "releasable:", # release + "component pillar", # components + "persona loop", # personas + "Risk-class of a task", # sensitivity +] + + +class OrientSplitTest(unittest.TestCase): + def test_skill_under_size_ceiling(self): # M1 + self.assertTrue(_SKILL.exists()) + size = len(_SKILL.read_bytes()) + self.assertLessEqual(size, 9500, + f"SKILL.md is {size}B; the orient core must stay <= 9500B " + f"(the split moved the routing prose to beyond.md)") + + def test_beyond_exists_with_moved_markers(self): # M2 + self.assertTrue(_BEYOND.exists(), "beyond.md (the on-demand routing guide) is missing") + text = _BEYOND.read_text(encoding="utf-8") + missing = [m for m in _MOVED_MARKERS if m not in text] + self.assertEqual(missing, [], + f"beyond.md must carry the moved routing prose; missing: {missing}") + + def test_index_names_every_pointer_plus_beyond(self): # M1 + R1 + text = _SKILL.read_text(encoding="utf-8") + self.assertIn("`beyond.md`", text, + "SKILL.md must route to the on-demand guide it spun off") + missing = [p for p in ON_DEMAND_POINTERS if f"`{p}`" not in text] + self.assertEqual(missing, [], + f"the compact index must keep every pinned pointer: {missing}") + + def test_moved_prose_left_skill(self): # M1 (no duplication) + text = _SKILL.read_text(encoding="utf-8") + exempt = ("propose graduation", "persona loop") + # "propose graduation" also lives in Depth-by-stage's production line (pinned by + # test_graduate_guard); "persona loop" is pinned INTO SKILL.md by + # test_persona_method_docs — every OTHER moved bullet appears only in beyond.md. + still_there = [m for m in _MOVED_MARKERS if m in text and m not in exempt] + self.assertEqual(still_there, [], + f"moved routing prose still duplicated in SKILL.md: {still_there}") + + def test_beyond_synced_across_trees(self): # M2 (x3 parity) + repo = _CANON.parent.parent.parent + twins = [ + repo / "add-method" / "src" / "add_method" / "_bundled" / "skill" / "add" / "beyond.md", + repo / ".claude" / "skills" / "add" / "beyond.md", + ] + canon = _BEYOND.read_bytes() if _BEYOND.exists() else b"" + for t in twins: + self.assertTrue(t.exists(), f"beyond.md missing from twin: {t}") + self.assertEqual(t.read_bytes(), canon, f"twin drifted: {t}") + + +if __name__ == "__main__": + unittest.main() diff --git a/add-method/tooling/test_wording_lint.py b/add-method/tooling/test_wording_lint.py index bcca483e..00d56765 100644 --- a/add-method/tooling/test_wording_lint.py +++ b/add-method/tooling/test_wording_lint.py @@ -166,7 +166,7 @@ def test_enforced_banned_green_over_live_surface(self) -> None: self.assertEqual([f for f in live if f.code == "banned_idiom_present"], []) def test_surface_files_cover_the_contract(self) -> None: - """The surface is skill/add (29 files: +loop.md @ v20, +graduate.md @ v22, +confidence.md +advisor.md @ advisor-context, +compact-foundation.md @ foundation-compaction, +soul.md @ soul-self-improve, +design.md @ udd-design-loop, +release.md @ release-altitude, +phases/fast-lane.md @ fast-lane, +components.md @ component-method-docs) + docs/appendix-b-prompts.md, per §3.""" + """The surface is skill/add (30 files: +beyond.md @ skill-orient-split, +loop.md @ v20, +graduate.md @ v22, +confidence.md +advisor.md @ advisor-context, +compact-foundation.md @ foundation-compaction, +soul.md @ soul-self-improve, +design.md @ udd-design-loop, +release.md @ release-altitude, +phases/fast-lane.md @ fast-lane, +components.md @ component-method-docs) + docs/appendix-b-prompts.md, per §3.""" files = wl.surface_files() names = {p.name for p in files} self.assertIn("SKILL.md", names) @@ -183,7 +183,7 @@ def test_surface_files_cover_the_contract(self) -> None: self.assertIn("sensitivity.md", names) self.assertIn("appendix-b-prompts.md", names) self.assertIn("self-improve.md", names) - self.assertEqual(len(files), 31, f"expected 30 skill files + appendix-b, got {len(files)} " + self.assertEqual(len(files), 32, f"expected 31 skill files + appendix-b, got {len(files)} " "(guides-and-skill merged 0-ground.md + 3-contract.md into 3-plan.md, net -1)")