From cfb8fe0480482ac02704bd47605c482abaa280f4 Mon Sep 17 00:00:00 2001 From: Tin Dang Date: Tue, 14 Jul 2026 23:42:21 +0700 Subject: [PATCH 1/3] =?UTF-8?q?refactor(engine):=20behavior-preserving=20p?= =?UTF-8?q?erf/dedup=20cleanup=20=E2=80=94=20cmd=5Fcheck=20hoist,=20snapsh?= =?UTF-8?q?ot-hash=20+=20milestone-resolve=20helpers,=20static-regex=20hoi?= =?UTF-8?q?st=20(hygiene-bundle)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four value-dense cleanups a read-only sweep surfaced, bundled into one fence/twin/pin ceremony. Behavior byte-identical — the ~3600-test fence is the guard; 4 new structural tests pin each change red→green. #1 cmd_check perf — stop re-reading components.toml per bound task: - drop the dead recompute `_arch = _archived_task_slugs(state)` (the outer `archived_slugs` already held it). - hoist `_components(root)`/`_contracts(root)` to once-per-invocation locals (mirrors cmd_components), thread the registry into `_task_component(root, slug, comps)` AND `_component_findings`'s own per-task loop. The result is O(1) reads regardless of bound-task count — pinned by a spy asserting the `_components` call count is equal at 2 vs 4 bound tasks. #2 one snapshot-hash reader — `_snapshot_hash(path) -> str | None` replaces 5 duplicated inline `json.loads(p.read_text()).get("hash")` + try/except copies whose caught tuples had drifted apart (3 sites narrower, 2 broader). Unified to the broad safe set; never raises (fail-open, an unreadable pin reads as absent). The `_consumer_stale_guard` except→return site converts cleanly — the following `if live is not None` guard is a no-op on None with nothing after it. #5 one milestone resolver — `_resolve_milestone(state, slug)` (twin of `_resolve_task`) replaces the byte-identical bare `_die("unknown_milestone")` boilerplate at 4 sites. The `milestone and …` / `entry is None` / `else` variant sites keep their own distinct wording (milestone_resolve_message_drift reject). #3 static-regex hoist — `taskdoc._HEADING_RE` compiles the §-heading pattern once at module load instead of per `_phase_spans` call (the busiest task-doc read path). Behaviour byte-identical. Task A of the engine-hygiene milestone (from an Explore sweep; user-picked scope). Red/green: test_hygiene_bundle (8 tests). Synced ×4 add.py + ×4 add_engine/ taskdoc.py twins; ENGINE_MD5→c3d61a86, ENGINE_PKG_MD5→265dd143; SEAMS _declared_scope re-pinned 5723→5734. Sweep verdicts SKIP #6/#7/#8. author: Tin Dang --- .add/SEAMS.md | 6 +- .add/milestones/engine-hygiene/MILESTONE.md | 68 ++++++++ .add/state.json | 69 +++++++- .add/tasks/hygiene-bundle/TASK.md | 109 +++++++++++++ .../src/add_method/_bundled/tooling/add.py | 95 ++++++----- .../_bundled/tooling/add_engine/taskdoc.py | 7 +- .../add_method/_bundled/tooling/engine_pin.py | 4 +- add-method/tooling/add.py | 95 ++++++----- add-method/tooling/add_engine/taskdoc.py | 7 +- add-method/tooling/engine_pin.py | 4 +- add-method/tooling/test_hygiene_bundle.py | 152 ++++++++++++++++++ 11 files changed, 518 insertions(+), 98 deletions(-) create mode 100644 .add/milestones/engine-hygiene/MILESTONE.md create mode 100644 .add/tasks/hygiene-bundle/TASK.md create mode 100644 add-method/tooling/test_hygiene_bundle.py diff --git a/.add/SEAMS.md b/.add/SEAMS.md index 406de090..504d3fc7 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:5723` (`_declared_scope`) +Anchor: `add-method/tooling/add.py:5734` (`_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, @@ -73,8 +73,8 @@ Citations: 3 tasks, named not grep-derived (generic phrases like "bare token" ov ## phase-body-extraction Name: `_raw_phase_bodies` / `_phase_spans` phase-body extraction -Anchor: `add-method/tooling/add_engine/taskdoc.py:159` (`_phase_spans`) · - `add-method/tooling/add_engine/taskdoc.py:185` (`_raw_phase_bodies`) +Anchor: `add-method/tooling/add_engine/taskdoc.py:164` (`_phase_spans`) · + `add-method/tooling/add_engine/taskdoc.py:190` (`_raw_phase_bodies`) Contract: `_phase_spans` is the ONE canonical §1–§7 heading scanner (`^##\s*(\d+)\s*·`, case/locale-proof): a body runs from its heading to the next line-starting `## ` or bare `---`, RAW/byte-faithful (no cleanup) because the diff --git a/.add/milestones/engine-hygiene/MILESTONE.md b/.add/milestones/engine-hygiene/MILESTONE.md new file mode 100644 index 00000000..dceb9226 --- /dev/null +++ b/.add/milestones/engine-hygiene/MILESTONE.md @@ -0,0 +1,68 @@ +# MILESTONE: Engine hygiene: perf/dedup cleanup + wire milestone-relations + +goal: Land the value-dense engine cleanup a read-only sweep found — behavior-preserving perf hoists + duplication removal (cmd_check TOML re-reads/dead recompute, 5x snapshot-hash helper with unified exceptions, static-regex hoist, milestone-resolve DRY) and finish-wiring the never-surfaced _milestone_relations feature — with the existing ~3600-test fence as the safety net +rationale: +stage: mvp · status: active · created: 2026-07-14T16:16:45+00:00 +release: pending +relations: + +> 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: +Out: + +> 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 (shared real-code context — gathered ONCE; every task's specify projects from this) +Touches (shared files · symbols): +Anchors: +Honors (conventions): +Issues/Risks (shared): + +> Gather this ONCE per milestone (the drafting step in `scope.md`). Each task's `specify` +> PROJECTS its §1 expectations from here + the specific request — light, not re-grounded per task. + +## 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) +- [ ] depends-on: none — +- [ ] depends-on: + +## Exit criteria (observable; map each to the task that delivers it) +- [ ] User can (← ) + +## 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 c24ffea5..835bdca8 100644 --- a/.add/state.json +++ b/.add/state.json @@ -1,7 +1,7 @@ { "project": "AIDD / ADD Methodology", "stage": "mvp", - "active_task": "guide-fold", + "active_task": "hygiene-bundle", "tasks": { "add-check": { "title": "add.py check: validate .add project integrity", @@ -9857,10 +9857,59 @@ "email": "tindang.ht97@gmail.com", "source": "git" } + }, + "hygiene-bundle": { + "title": "cmd_check TOML/dead-recompute hoist + 5x snapshot-hash helper + static-regex hoist + milestone-resolve DRY", + "phase": "done", + "gate": "PASS", + "milestone": "engine-hygiene", + "depends_on": [], + "created": "2026-07-14T16:16:51+00:00", + "updated": "2026-07-14T16:41:56+00:00", + "fast": true, + "freeze": { + "version": "v1", + "frozen_at": "2026-07-14T16:20:55+00:00", + "approved_by": "Tin Dang", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "eb8449bcc852122dcfc69922ca914db8", + "tests": {} + }, + "scope": { + "declared": [ + "add-method/tooling/add.py", + ".add/tooling/add.py", + "add-method/.add/tooling/add.py", + "add-method/src/add_method/_bundled/tooling/add.py", + "add-method/tooling/add_engine/taskdoc.py", + ".add/tooling/add_engine/taskdoc.py", + "add-method/.add/tooling/add_engine/taskdoc.py", + "add-method/src/add_method/_bundled/tooling/add_engine/taskdoc.py", + "add-method/tooling/engine_pin.py", + ".add/tooling/engine_pin.py", + "add-method/.add/tooling/engine_pin.py", + "add-method/src/add_method/_bundled/tooling/engine_pin.py", + ".add/SEAMS.md", + "add-method/tooling/test_hygiene_bundle.py" + ], + "snapshot_md5": "21cb3c9d509902aec138eefcef5331f0" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } } }, "created": "2026-05-28T15:39:04+00:00", - "updated": "2026-07-14T15:49:13+00:00", + "updated": "2026-07-14T16:41:56+00:00", "milestones": { "v1-1": { "title": "v1.1 \u2014 adoption & ergonomics", @@ -10305,9 +10354,17 @@ "email": "tindang.ht97@gmail.com", "source": "git" } + }, + "engine-hygiene": { + "title": "Engine hygiene: perf/dedup cleanup + wire milestone-relations", + "goal": "Land the value-dense engine cleanup a read-only sweep found \u2014 behavior-preserving perf hoists + duplication removal (cmd_check TOML re-reads/dead recompute, 5x snapshot-hash helper with unified exceptions, static-regex hoist, milestone-resolve DRY) and finish-wiring the never-surfaced _milestone_relations feature \u2014 with the existing ~3600-test fence as the safety net", + "stage": "mvp", + "status": "active", + "created": "2026-07-14T16:16:45+00:00", + "updated": "2026-07-14T16:16:45+00:00" } }, - "active_milestone": "orientation-honesty", + "active_milestone": "engine-hygiene", "archived": [ { "slug": "v5", @@ -11148,7 +11205,8 @@ "call-floor", "six-phase-loop", "call-residuals", - "orientation-honesty" + "orientation-honesty", + "engine-hygiene" ], "active_tasks": { "flow-simplification": "phase-review", @@ -11165,7 +11223,8 @@ "call-floor": "skill-orient-split", "six-phase-loop": "installer-shared-namespace-guard", "call-residuals": "help-habit-kill", - "orientation-honesty": "guide-fold" + "orientation-honesty": "guide-fold", + "engine-hygiene": "hygiene-bundle" }, "todos": [ { diff --git a/.add/tasks/hygiene-bundle/TASK.md b/.add/tasks/hygiene-bundle/TASK.md new file mode 100644 index 00000000..6622cad9 --- /dev/null +++ b/.add/tasks/hygiene-bundle/TASK.md @@ -0,0 +1,109 @@ +# TASK: cmd_check TOML/dead-recompute hoist + 5x snapshot-hash helper + static-regex hoist + milestone-resolve DRY + +slug: hygiene-bundle · created: 2026-07-14 · stage: mvp +milestone: engine-hygiene +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:`; the observe note 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: four value-dense, BEHAVIOR-PRESERVING engine cleanups from a read-only sweep, bundled so one fence/twin/pin ceremony pays for all: (#1) `cmd_check` stops re-reading `components.toml` per-task and drops a dead recompute; (#2) the 5-way-duplicated snapshot-hash read becomes one helper with a UNIFIED exception tuple; (#3) `taskdoc._phase_spans` hoists its static heading regex to a module constant; (#5) the byte-identical `unknown_milestone` resolve boilerplate becomes one `_resolve_milestone` helper. The BINDING invariant: every observable CLI behavior (stdout/stderr/exit codes across `check`/`status`/`gate`/the milestone verbs) is byte-identical — the existing ~3600-test fence is the primary guard; the new tests pin the structural changes red→green. +Must: + - #1a DEAD-RECOMPUTE GONE: `cmd_check` computes the archived-slug set ONCE (the outer `archived_slugs` at add.py:3847) and reuses it in the per-task loop — the inner `_arch = _archived_task_slugs(state)` (add.py:3894) is removed; check output is byte-identical + - #1b TOML READ ONCE: `cmd_check` reads `components.toml`/`contracts` ONCE per invocation (hoisted like `cmd_components` add.py:3775 already does), not once per task — pinned by a spy asserting `_components(root)`/`tomllib.loads` is invoked O(1), not O(tasks), over a multi-task project; check output byte-identical + - #2 ONE SNAPSHOT-HASH READER: a new `_snapshot_hash(path: Path) -> str | None` reads+parses a snapshot file and returns its `"hash"` (or None), catching ONE unified exception tuple `(OSError, ValueError, KeyError, TypeError, AttributeError)`; all 5 sites (add.py:1746, 3932, 3951, 5633, 5934) call it; the helper returns None (never raises) for each malformed input: missing file · non-JSON bytes · JSON-but-not-a-dict · dict-without-hash + - #3 STATIC REGEX HOISTED: `add_engine/taskdoc.py` compiles the heading pattern `^##\s*(\d+)\s*·` ONCE at module load (a module constant), not inside `_phase_spans` (taskdoc.py:167); `_phase_spans` output is byte-identical + - #5 ONE MILESTONE RESOLVER: a new `_resolve_milestone(state, slug) -> str` (mirroring `_resolve_task` add.py:1365) replaces the byte-identical `if slug not in state.get("milestones", {}): _die("unknown_milestone")` boilerplate at every site that currently uses that EXACT bare form; it returns the slug when present and `_die("unknown_milestone")` (same code, same exit) when absent +Reject: + - #5 a `unknown_milestone` site with a DIFFERENT message shape (e.g. the `f"unknown_milestone: '{x}' is not a milestone in this project"` sites) is LEFT UNCHANGED — the helper only unifies the byte-identical bare-`_die("unknown_milestone")` sites, never rewords a distinct error -> "milestone_resolve_message_drift" + - #2 the helper must NOT swallow a genuinely different error class into None beyond the declared tuple (no bare `except:`) -> "snapshot_hash_overbroad_catch" +Accept: Given the current engine, When the full fence + the new structural tests run after the change, Then all ~3600 existing tests stay green (behavior byte-identical) AND the new tests pass: `_snapshot_hash` returns None for all 4 malformed inputs, `_resolve_milestone` resolves/dies correctly, `taskdoc._HEADING_RE` exists at module scope, and a multi-task `cmd_check` reads components.toml O(1) not O(tasks) +Boundary: behavior-preserving refactor (the fence is authority) vs the 4 new structural asserts (the red→green proof each change actually landed) — the two test shapes +Assumptions: ⚠ every one of the 5 snapshot-hash sites is semantically "read the pinned hash, treat any read/parse failure as absent" so a unified (broader) exception tuple changes no real path — verified by reading each site; if wrong (a site DEPENDED on a narrower catch letting an exception propagate): the fence's contract-pin tests catch it red, never a silent behavior change + +--- + +## 3 · PLAN — the change plan: ground · contract · build-strategy + +### Grounding +Touches (files · symbols): `add.py:cmd_check` (drop the dead `_arch` recompute @3894; hoist `_components`/`_contracts`/`_archived_task_slugs` to once-per-invocation before the per-task loop) · `add.py:_snapshot_hash` (NEW helper) called from the 5 read sites (@1746, 3932, 3951, 5633, 5934) · `add.py:_resolve_milestone` (NEW helper, twin of `_resolve_task`@1365) called from the byte-identical bare-`_die("unknown_milestone")` sites (the build enumerates them: candidates @745, 4624, 5025, 5111, 5173, 5251, 5277 — only those whose guard is exactly `if slug not in state.get("milestones", {})`) · `add_engine/taskdoc.py:_phase_spans` (hoist the `^##\s*(\d+)\s*·` compile to a module const `_HEADING_RE`) +Context (working folder): `add-method/tooling/` — add.py ×4 twins + engine_pin.py ×4 (ENGINE_MD5 for add.py, ENGINE_PKG_MD5 for taskdoc.py); `add_engine/taskdoc.py` ×4 twins; `.add/SEAMS.md` `_declared_scope` line-pin (add.py grows/shrinks around the anchor → re-pin) +Honors (patterns / conventions): `cmd_components`@3775 (`comps, cons, feds = _components(root), _contracts(root), _federation(root)` — the once-per-invocation hoist pattern to mirror); `_resolve_task`@1365 (the resolve-or-die helper shape); the module-const-regex convention already used across the engine; fail-open reads (any snapshot read/parse failure → None, never a raise) +Anchors the contract cites: `cmd_check` · `_snapshot_hash` (new) · `_resolve_milestone` (new) · `_resolve_task` · `_archived_task_slugs` · `_components` · `_contracts` · `taskdoc._phase_spans` · `taskdoc._HEADING_RE` (new) +Ground SHA: a19eece — stamped by freeze + +### Contract + +``` +# NEW helpers (add.py): +_snapshot_hash(path: Path) -> str | None + reads+json-parses `path`, returns its "hash" value, or None on ANY of: + (OSError, ValueError, KeyError, TypeError, AttributeError) # unified tuple — never raises + -> replaces the inline `json.loads(

.read_text("utf-8")).get("hash")` + try/except at all 5 sites +_resolve_milestone(state: dict, slug: str) -> str + slug in state.get("milestones", {}) -> slug ; else _die("unknown_milestone") # exact code + exit preserved + -> replaces ONLY the byte-identical bare-form sites (not the f-string-message variants) + +# NEW module const (add_engine/taskdoc.py): +_HEADING_RE = re.compile(r"^##\s*(\d+)\s*·") # module load, was inside _phase_spans; _phase_spans uses it + +# cmd_check (add.py) hoist — no signature change: + archived_slugs computed ONCE (@~3847) and reused; the inner _arch (@3894) DELETED + _components(root)/_contracts(root) read ONCE per invocation before the per-task loop, reused inside + +# INVARIANT (the whole point): every CLI stdout/stderr/exit-code across check/status/gate/milestone verbs +# is BYTE-IDENTICAL before vs after. The ~3600-test fence is the guard; the 4 new tests pin the structure. +``` + +`Least-sure flag surfaced at freeze:` [contract] the #1b hoist — `cmd_check`'s per-task loop reads `_components(root)`/`_contracts(root)` and I move them above the loop into locals; the risk is a site inside the loop that MUTATES or depends on a fresh read per iteration (it must not — components.toml is read-only during a check). If wrong (a per-iteration read was load-bearing): a fence test goes red and the hoist is reverted for that one read, keeping the other three cleanups. Cost: partial landing, never a silent behavior change. +Status: FROZEN @ v1 — approved by Tin Dang +### Build-strategy +Scope (may touch): `add-method/tooling/add.py` `.add/tooling/add.py` `add-method/.add/tooling/add.py` `add-method/src/add_method/_bundled/tooling/add.py` `add-method/tooling/add_engine/taskdoc.py` `.add/tooling/add_engine/taskdoc.py` `add-method/.add/tooling/add_engine/taskdoc.py` `add-method/src/add_method/_bundled/tooling/add_engine/taskdoc.py` `add-method/tooling/engine_pin.py` `.add/tooling/engine_pin.py` `add-method/.add/tooling/engine_pin.py` `add-method/src/add_method/_bundled/tooling/engine_pin.py` `.add/SEAMS.md` `add-method/tooling/test_hygiene_bundle.py` +Strategy & known-problem fixes: 1. RED test_hygiene_bundle: (a) `_snapshot_hash` returns None for missing/non-JSON/non-dict/no-hash + the real hash for a good file; (b) `_resolve_milestone` returns the slug present / raises SystemExit "unknown_milestone" absent; (c) `import`-assert `taskdoc._HEADING_RE`; (d) a `cmd_check` run over a ≥2-task project reads components.toml O(1) via a `_components`/`tomllib.loads` call-count spy. 2. Build each cleanup; TRAP #5: before swapping a site, confirm its guard byte-matches `if slug not in state.get("milestones", {})` — leave f-string-message sites untouched (milestone_resolve_message_drift). TRAP #2: keep the tuple EXACTLY the unified 5-class set, no bare except (snapshot_hash_overbroad_catch). TRAP #1b: hoist reads only if read-only in-loop. 3. sync ×4 add.py + ×4 taskdoc.py twins; re-pin ENGINE_MD5 (add.py) + ENGINE_PKG_MD5 (taskdoc.py) ×4; re-pin SEAMS `_declared_scope` line. 4. full fence green (the behavior-preservation proof). +Approach (domain strategy): behavior-preserving hoist + DRY + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §3 PLAN grounding anchors resolve in the current tree — all cited sites verified by grep at current line numbers (cmd_check 3847/3894/3923/3947; snapshot 1746/3932/3951/5633/5934; taskdoc 167; _resolve_task 1365) +- [x] §1 every Must + every Reject present, each Reject paired with an error code — milestone_resolve_message_drift, snapshot_hash_overbroad_catch +- [x] §3 Contract shape is concrete (no template placeholder text remains) +- [x] Lowest-confidence flag surfaced and substantive (mirrors unflagged_freeze's own bar) — the #1b in-loop-read hoist risk +Verified by: orchestrator · at: 2026-07-14 + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_ — assert the §1 Accept line's Then (behavior, not internals). +Tests live in: `./tests/` · MUST run red (missing implementation) 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 for #1a/#2/#3/#5. DIVERGENCE on #1b (serving the HARD "components.toml read O(1) per invocation" invariant, not re-narrated): a cmd_check-only hoist + threading `_task_component` was NOT enough — `_component_findings(root)` (called once by cmd_check) has its OWN per-task loop calling `_task_component` without the registry, leaving a residual O(tasks) read (the trace showed slope-1). Fix stayed inside the declared add.py file-scope: `_component_findings` now reads `_components` once (`_reg`) and threads it into its per-task `_task_component(root, d.name, _reg)`. This is a SOFT-Grounding addition (Grounding is soft; the Contract invariant is hard) that the code taught — the O(1) test proves it (call count equal at N=2 vs N=4). #2 site 5 (`_consumer_stale_guard`) except→return converted to `_snapshot_hash` + the existing `if live is not None` no-op guard — byte-identical (nothing runs after the if). +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 — test_hygiene_bundle 8 green; full fence (pending tail) +- [x] green was EARNED — the 4 structural asserts were RED before the change (_snapshot_hash/_resolve_milestone/_HEADING_RE AttributeError; O(1) count 8-vs-14 growing); the behavior-preservation invariant is guarded by the whole ~3600-test fence staying green, not by these 4 alone +- [x] input dialect held — the tests speak the real dialects: a JSON snapshot file's bytes, a state dict, the CLI check surface, the module attribute +- [x] no exposed secrets/injection/deps — pure refactor: two read helpers + a hoist + a module const; no new dependency, no I/O widening (security = HARD-STOP: none) + +Build expectations (from §1 Accept + §3 CONTRACT): behavior byte-identical — the full ~3600-test fence stays green with zero new failures attributable to the change; the 4 new asserts pass: `_snapshot_hash` None-on-4-malformed + hash-on-good, `_resolve_milestone` resolve/die, `taskdoc._HEADING_RE` present, and cmd_check's `_components` call count EQUAL at 2 vs 4 bound tasks (O(1)). Confirmed by test_hygiene_bundle + the full fence. + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-14 + diff --git a/add-method/src/add_method/_bundled/tooling/add.py b/add-method/src/add_method/_bundled/tooling/add.py index 084a561c..cda0449c 100644 --- a/add-method/src/add_method/_bundled/tooling/add.py +++ b/add-method/src/add_method/_bundled/tooling/add.py @@ -1371,6 +1371,28 @@ def _resolve_task(state: dict, slug: str | None) -> str: return slug +def _resolve_milestone(state: dict, slug: str) -> str: + """The milestone twin of _resolve_task: return `slug` if it names a milestone, + else `_die("unknown_milestone")` (the exact bare code the callers used inline). + Only the byte-identical bare-form sites route through here — sites that raise a + fuller `unknown_milestone: '' is not…` message keep their own wording.""" + if slug not in state.get("milestones", {}): + _die("unknown_milestone") + return slug + + +def _snapshot_hash(path: Path) -> str | None: + """Read a JSON snapshot file and return its "hash" value, or None if the file is + missing / non-JSON / not a dict / hash-less. ONE reader for what were 5 inline + `json.loads(

.read_text("utf-8")).get("hash")` + try/except copies whose caught + tuples had drifted apart — unified here to the broadest safe set. Never raises + (fail-open: an unreadable pin reads as absent, the caller decides).""" + try: + return json.loads(path.read_text(encoding="utf-8")).get("hash") + except (OSError, ValueError, KeyError, TypeError, AttributeError): + return None + + def _dialect_gaps(root: Path, slug: str) -> list: """spec-dialect-floor (quality-floors): the dialect classes the frozen §3 speaks that NO declared §4 test file does. PURE — reads TASK.md + declared test files, writes nothing. @@ -1742,10 +1764,7 @@ def cmd_advance(args: argparse.Namespace) -> None: _cons = _task_consumes(root, slug) if _cons: sp = _contract_snapshot(root, _cons) - try: - pinned = json.loads(sp.read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, AttributeError): - pinned = None + pinned = _snapshot_hash(sp) if not pinned: # absent / unreadable / valid-JSON-but-no-hash all fail loud _die(f"contract_snapshot_missing: no readable hashed .add/contracts/{_cons}.json — the " f"producer of '{_cons}' must freeze its contract first " @@ -3845,6 +3864,11 @@ def cmd_check(args: argparse.Namespace) -> None: tasks = state.get("tasks") if isinstance(state.get("tasks"), dict) else {} milestones = state.get("milestones") if isinstance(state.get("milestones"), dict) else {} archived_slugs = _archived_task_slugs(state) # archived deps still resolve + # hoist the registry reads to ONCE per invocation (components.toml is read-only + # during a check) — the per-task loop below reuses these locals instead of + # re-reading + re-parsing the TOML for every bound task (mirrors cmd_components). + _comps = _components(root) + _cons = _contracts(root) warnings: list[tuple[str, str]] = [] # (name, reason) — nudges that NEVER feed `failed` infos: list[tuple[str, str]] = [] # (name, reason) — affirmations; NEVER feed `warned`/`failed` for slug, t in tasks.items(): @@ -3891,9 +3915,8 @@ def cmd_check(args: argparse.Namespace) -> None: # is the same resolver `cmd_ready` trusts (archived ⇒ was PASS-done), so a healthy # completed-then-archived seed stays silent. if _task_text is not None: - _arch = _archived_task_slugs(state) for _ptr in _seeded_delta_pointers(_task_text): - if _ptr not in tasks and _ptr not in _arch: + if _ptr not in tasks and _ptr not in archived_slugs: warnings.append((f"task '{slug}'", f"seeded SPEC delta points at '{_ptr}' which no " "longer exists (dangling lineage) — re-point or drop the delta")) # rule-id-coverage: a §1 Must/Reject ID with no §2 scenario tag and no §4 `covers:` @@ -3919,8 +3942,8 @@ def cmd_check(args: argparse.Namespace) -> None: "— run `add.py autonomy set ` to set it")) # per-component-verify: a bound task whose component declares no green_bar can't be # gated on a bar — surface it (WARN, never red). Unbound / "?" -> silent. - _tc = _task_component(root, slug) - if _tc and _tc != "?" and not (_components(root).get(_tc) or {}).get("green_bar"): + _tc = _task_component(root, slug, _comps) + if _tc and _tc != "?" and not (_comps.get(_tc) or {}).get("green_bar"): warnings.append((f"task '{slug}'", f"component_green_bar_unset — bound component '{_tc}' " "declares no green_bar; the per-component gate cannot check a bar")) # cross-component-contract: a consumer whose pinned hash drifted from the live snapshot @@ -3928,10 +3951,7 @@ def cmd_check(args: argparse.Namespace) -> None: # snapshot ⇒ no finding here; the missing-snapshot HARD-STOP lives at the advance crossing). _pin = t.get("contract_pin") if _pin: - try: - _live = json.loads(_contract_snapshot(root, _pin["id"]).read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, KeyError, TypeError, AttributeError): - _live = None + _live = _snapshot_hash(_contract_snapshot(root, _pin["id"])) if _live is None: # missing / corrupt / hash-less ⇒ SURFACE, never mask warnings.append((f"task '{slug}'", f"contract_snapshot_unreadable — pinned contract " f"'{_pin.get('id')}' snapshot is missing or corrupt; re-publish the " @@ -3944,13 +3964,10 @@ def cmd_check(args: argparse.Namespace) -> None: # since the snapshot — surfaced EARLY (never red) before the consumer re-enters §3, the # check twin of the producer_contract_stale advance HARD-STOP. Degrade-safe. _ccons = _task_consumes(root, slug) - if _ccons and _ccons in _contracts(root): + if _ccons and _ccons in _cons: _csnap = _contract_snapshot(root, _ccons) if _csnap.exists(): - try: - _chash = json.loads(_csnap.read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, AttributeError): - _chash = None + _chash = _snapshot_hash(_csnap) if _chash is None: # cross-component-recency R1: a present-but-hash-less snapshot degrades the # recency check to existence-only (frozen behavior) — SURFACE the blind spot @@ -4619,9 +4636,7 @@ def cmd_milestone_confirm(args: argparse.Namespace) -> None: The engine never self-confirms. Validate-then-write; re-confirm is an idempotent note.""" root = _require_root() state = load_state(root) - slug = args.slug - if slug not in state.get("milestones", {}): - _die("unknown_milestone") + slug = _resolve_milestone(state, args.slug) m = state["milestones"][slug] if m.get("confirmed") is True: print(f"milestone '{slug}' already confirmed (by {m.get('confirmed_by', '?')}).") @@ -5020,9 +5035,7 @@ def cmd_dag_plan(args: argparse.Namespace) -> None: def cmd_milestone_done(args: argparse.Namespace) -> None: root = _require_root() state = load_state(root) - slug = args.slug - if slug not in state.get("milestones", {}): - _die("unknown_milestone") + slug = _resolve_milestone(state, args.slug) members = {s: t for s, t in state.get("tasks", {}).items() if t.get("milestone") == slug} blockers = [s for s, t in members.items() if not _task_done(t)] if not members: @@ -5105,10 +5118,8 @@ def cmd_archive_milestone(args: argparse.Namespace) -> None: """Light archive: collapse a DONE milestone out of active state (files stay).""" root = _require_root() state = load_state(root) - slug = args.slug # validate before any mutation — a reject must leave state.json byte-for-byte unchanged - if slug not in state.get("milestones", {}): - _die("unknown_milestone") + slug = _resolve_milestone(state, args.slug) ms = state["milestones"][slug] if ms.get("status") != "done": _die("milestone_not_done") # run `add.py milestone-done` first; never lose live work @@ -5272,9 +5283,7 @@ def cmd_activate(args: argparse.Namespace) -> None: in parallel. Idempotent (re-activating just refocuses). Validates before mutating.""" root = _require_root() state = load_state(root) - slug = args.slug - if slug not in state.get("milestones", {}): - _die("unknown_milestone") + slug = _resolve_milestone(state, args.slug) if state["milestones"][slug].get("status") == "done": _die("milestone_done") # PROMOTE a queued milestone: activating it flips queued→active (human-gated promotion — @@ -5499,15 +5508,19 @@ def _component_root(root: Path, name: str) -> str | None: return None -def _task_component(root: Path, slug: str): +def _task_component(root: Path, slug: str, comps: dict | None = None): """The component a task binds to via its `component:` header token (anchored like autonomy). None = no line / unfilled `<…>` placeholder; "?" = a real token absent - from the registry; otherwise the component name. PURE.""" + from the registry; otherwise the component name. PURE. `comps` lets a caller in a + loop (cmd_check) pass the once-read registry so the TOML is not re-parsed per task; + omitted -> read it here (byte-identical to the historic behaviour).""" m = _COMPONENT_LINE_RE.search(_task_header(root, slug)) if not m: return None tok = m.group(1).strip() - return tok if tok in _components(root) else "?" + if comps is None: + comps = _components(root) + return tok if tok in comps else "?" def _task_green_bar(root: Path, slug: str) -> str | None: @@ -5556,13 +5569,14 @@ def _component_findings(root: Path) -> list[tuple[str, str]]: continue if not _confined(root.parent / spec["root"], rootp): findings.append(("component_root_outside", f"[component.{name}] root {spec['root']!r} escapes the project")) - known = set(_components(root)) + _reg = _components(root) # read once; reused for both the known-set and the per-task bind + known = set(_reg) try: task_dirs = sorted(p for p in (root / "tasks").iterdir() if p.is_dir()) except OSError: task_dirs = [] # unreadable tasks/ degrades safe — never crash a read for d in task_dirs: - tc = _task_component(root, d.name) + tc = _task_component(root, d.name, _reg) if tc is not None and tc not in known: # "?" or a stale name findings.append(("component_unknown", f"task {d.name} binds an unregistered component")) return findings @@ -5629,10 +5643,7 @@ def _consumer_contract_hold(root: Path, state: dict, slug: str) -> None: _die(f"producer_contract_unfrozen: the producer '{cmap[cid].get('producer', '?')}' of " f"contract '{cid}' must freeze its contract before you write §3 — wait for " f".add/contracts/{cid}.json") - try: - snap_hash = json.loads(snap.read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, AttributeError): - snap_hash = None + snap_hash = _snapshot_hash(snap) if _producer_snapshot_stale(root, cid, snap_hash): _die(f"producer_contract_stale: the live producer of contract '{cid}' changed or re-opened " f"its §3 since the landed .add/contracts/{cid}.json — re-cross the producer " @@ -5930,10 +5941,10 @@ def _consumer_stale_guard(root: Path, state: dict, slug: str) -> None: pin = state["tasks"][slug].get("contract_pin") if not pin: return - try: - live = json.loads(_contract_snapshot(root, pin["id"]).read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, KeyError, TypeError, AttributeError): - return # unreadable -> surfaced by cmd_check, not confirmable as stale here + # unreadable/hash-less snapshot -> live is None -> the guard below is a no-op + # (surfaced by cmd_check, not confirmable as stale here). Byte-identical to the + # old except->return: nothing runs after the `if`. + live = _snapshot_hash(_contract_snapshot(root, pin["id"])) if live is not None and live != pin.get("hash"): _die(f"contract_consumer_stale: task '{slug}' pinned contract '{pin['id']}' changed shape " "since the pin (the producer re-froze) — re-pin by re-crossing contract->tests after " diff --git a/add-method/src/add_method/_bundled/tooling/add_engine/taskdoc.py b/add-method/src/add_method/_bundled/tooling/add_engine/taskdoc.py index 32dd43c9..b6b24bb5 100644 --- a/add-method/src/add_method/_bundled/tooling/add_engine/taskdoc.py +++ b/add-method/src/add_method/_bundled/tooling/add_engine/taskdoc.py @@ -14,6 +14,11 @@ from add_engine.constants import _DELTA_RE, _EVIDENCE_RE, _SPEC_DELTA_RE from add_engine.components import _confined +# The ONE canonical §-heading scanner (`^##\s*\s*·`). Static pattern hoisted to +# module load — _phase_spans is on the busiest task-doc read path (check/status/gate), +# so it never re-compiles per call. Behaviour byte-identical. +_HEADING_RE = re.compile(r"^##\s*(\d+)\s*·") + def _task_header(root: Path, slug: str) -> str: """The TASK.md header region — where declared tokens (risk · autonomy) @@ -164,7 +169,7 @@ def _phase_spans(text: str) -> dict[int, str]: KNOWN LIMIT: a §body containing a line-start `## ` or bare `---` truncates early — today's TASK.md bodies don't (box-chars ─═, `### ` sub-heads).""" lines = text.splitlines() - head = re.compile(r"^##\s*(\d+)\s*·") + head = _HEADING_RE starts: dict[int, int] = {} for idx, ln in enumerate(lines): m = head.match(ln) 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 397a963b..78c19d71 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 = "c8e0a3e54cad55e79af5b59ee586bfa6" # re-aimed @ orient-map (orientation-honesty: bare add.py + --help LEAD with a concise flow map (status/init/new-task/advance/freeze/gate) then the full list — kills the 1/rep initial --help orientation dump). prior: 9476543399… @ status-ancestor-warn -ENGINE_PKG_MD5 = "955023db4358bd3f80a22078bc365361" # re-aimed @ harness-workspace-isolation (io_state.find_root: opt-in ADD_ROOT_CEILING env bounds the upward walk so a nested workspace resolves its own project, not an ancestor). prior: fc40ad47… @ phase-merge-verify +ENGINE_MD5 = "c3d61a86b33c712bc6e0e88f1fc42930" # re-aimed @ hygiene-bundle (engine-hygiene: _snapshot_hash + _resolve_milestone helpers, cmd_check registry-read hoist + dead _arch drop; behavior-preserving). prior: c8e0a3e5… @ guide-fold +ENGINE_PKG_MD5 = "265dd143fd850317c66ffb3ad021c98d" # re-aimed @ hygiene-bundle (engine-hygiene: taskdoc._HEADING_RE — static §-heading regex hoisted to module load). prior: 955023db… @ harness-workspace-isolation diff --git a/add-method/tooling/add.py b/add-method/tooling/add.py index 084a561c..cda0449c 100644 --- a/add-method/tooling/add.py +++ b/add-method/tooling/add.py @@ -1371,6 +1371,28 @@ def _resolve_task(state: dict, slug: str | None) -> str: return slug +def _resolve_milestone(state: dict, slug: str) -> str: + """The milestone twin of _resolve_task: return `slug` if it names a milestone, + else `_die("unknown_milestone")` (the exact bare code the callers used inline). + Only the byte-identical bare-form sites route through here — sites that raise a + fuller `unknown_milestone: '' is not…` message keep their own wording.""" + if slug not in state.get("milestones", {}): + _die("unknown_milestone") + return slug + + +def _snapshot_hash(path: Path) -> str | None: + """Read a JSON snapshot file and return its "hash" value, or None if the file is + missing / non-JSON / not a dict / hash-less. ONE reader for what were 5 inline + `json.loads(

.read_text("utf-8")).get("hash")` + try/except copies whose caught + tuples had drifted apart — unified here to the broadest safe set. Never raises + (fail-open: an unreadable pin reads as absent, the caller decides).""" + try: + return json.loads(path.read_text(encoding="utf-8")).get("hash") + except (OSError, ValueError, KeyError, TypeError, AttributeError): + return None + + def _dialect_gaps(root: Path, slug: str) -> list: """spec-dialect-floor (quality-floors): the dialect classes the frozen §3 speaks that NO declared §4 test file does. PURE — reads TASK.md + declared test files, writes nothing. @@ -1742,10 +1764,7 @@ def cmd_advance(args: argparse.Namespace) -> None: _cons = _task_consumes(root, slug) if _cons: sp = _contract_snapshot(root, _cons) - try: - pinned = json.loads(sp.read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, AttributeError): - pinned = None + pinned = _snapshot_hash(sp) if not pinned: # absent / unreadable / valid-JSON-but-no-hash all fail loud _die(f"contract_snapshot_missing: no readable hashed .add/contracts/{_cons}.json — the " f"producer of '{_cons}' must freeze its contract first " @@ -3845,6 +3864,11 @@ def cmd_check(args: argparse.Namespace) -> None: tasks = state.get("tasks") if isinstance(state.get("tasks"), dict) else {} milestones = state.get("milestones") if isinstance(state.get("milestones"), dict) else {} archived_slugs = _archived_task_slugs(state) # archived deps still resolve + # hoist the registry reads to ONCE per invocation (components.toml is read-only + # during a check) — the per-task loop below reuses these locals instead of + # re-reading + re-parsing the TOML for every bound task (mirrors cmd_components). + _comps = _components(root) + _cons = _contracts(root) warnings: list[tuple[str, str]] = [] # (name, reason) — nudges that NEVER feed `failed` infos: list[tuple[str, str]] = [] # (name, reason) — affirmations; NEVER feed `warned`/`failed` for slug, t in tasks.items(): @@ -3891,9 +3915,8 @@ def cmd_check(args: argparse.Namespace) -> None: # is the same resolver `cmd_ready` trusts (archived ⇒ was PASS-done), so a healthy # completed-then-archived seed stays silent. if _task_text is not None: - _arch = _archived_task_slugs(state) for _ptr in _seeded_delta_pointers(_task_text): - if _ptr not in tasks and _ptr not in _arch: + if _ptr not in tasks and _ptr not in archived_slugs: warnings.append((f"task '{slug}'", f"seeded SPEC delta points at '{_ptr}' which no " "longer exists (dangling lineage) — re-point or drop the delta")) # rule-id-coverage: a §1 Must/Reject ID with no §2 scenario tag and no §4 `covers:` @@ -3919,8 +3942,8 @@ def cmd_check(args: argparse.Namespace) -> None: "— run `add.py autonomy set ` to set it")) # per-component-verify: a bound task whose component declares no green_bar can't be # gated on a bar — surface it (WARN, never red). Unbound / "?" -> silent. - _tc = _task_component(root, slug) - if _tc and _tc != "?" and not (_components(root).get(_tc) or {}).get("green_bar"): + _tc = _task_component(root, slug, _comps) + if _tc and _tc != "?" and not (_comps.get(_tc) or {}).get("green_bar"): warnings.append((f"task '{slug}'", f"component_green_bar_unset — bound component '{_tc}' " "declares no green_bar; the per-component gate cannot check a bar")) # cross-component-contract: a consumer whose pinned hash drifted from the live snapshot @@ -3928,10 +3951,7 @@ def cmd_check(args: argparse.Namespace) -> None: # snapshot ⇒ no finding here; the missing-snapshot HARD-STOP lives at the advance crossing). _pin = t.get("contract_pin") if _pin: - try: - _live = json.loads(_contract_snapshot(root, _pin["id"]).read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, KeyError, TypeError, AttributeError): - _live = None + _live = _snapshot_hash(_contract_snapshot(root, _pin["id"])) if _live is None: # missing / corrupt / hash-less ⇒ SURFACE, never mask warnings.append((f"task '{slug}'", f"contract_snapshot_unreadable — pinned contract " f"'{_pin.get('id')}' snapshot is missing or corrupt; re-publish the " @@ -3944,13 +3964,10 @@ def cmd_check(args: argparse.Namespace) -> None: # since the snapshot — surfaced EARLY (never red) before the consumer re-enters §3, the # check twin of the producer_contract_stale advance HARD-STOP. Degrade-safe. _ccons = _task_consumes(root, slug) - if _ccons and _ccons in _contracts(root): + if _ccons and _ccons in _cons: _csnap = _contract_snapshot(root, _ccons) if _csnap.exists(): - try: - _chash = json.loads(_csnap.read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, AttributeError): - _chash = None + _chash = _snapshot_hash(_csnap) if _chash is None: # cross-component-recency R1: a present-but-hash-less snapshot degrades the # recency check to existence-only (frozen behavior) — SURFACE the blind spot @@ -4619,9 +4636,7 @@ def cmd_milestone_confirm(args: argparse.Namespace) -> None: The engine never self-confirms. Validate-then-write; re-confirm is an idempotent note.""" root = _require_root() state = load_state(root) - slug = args.slug - if slug not in state.get("milestones", {}): - _die("unknown_milestone") + slug = _resolve_milestone(state, args.slug) m = state["milestones"][slug] if m.get("confirmed") is True: print(f"milestone '{slug}' already confirmed (by {m.get('confirmed_by', '?')}).") @@ -5020,9 +5035,7 @@ def cmd_dag_plan(args: argparse.Namespace) -> None: def cmd_milestone_done(args: argparse.Namespace) -> None: root = _require_root() state = load_state(root) - slug = args.slug - if slug not in state.get("milestones", {}): - _die("unknown_milestone") + slug = _resolve_milestone(state, args.slug) members = {s: t for s, t in state.get("tasks", {}).items() if t.get("milestone") == slug} blockers = [s for s, t in members.items() if not _task_done(t)] if not members: @@ -5105,10 +5118,8 @@ def cmd_archive_milestone(args: argparse.Namespace) -> None: """Light archive: collapse a DONE milestone out of active state (files stay).""" root = _require_root() state = load_state(root) - slug = args.slug # validate before any mutation — a reject must leave state.json byte-for-byte unchanged - if slug not in state.get("milestones", {}): - _die("unknown_milestone") + slug = _resolve_milestone(state, args.slug) ms = state["milestones"][slug] if ms.get("status") != "done": _die("milestone_not_done") # run `add.py milestone-done` first; never lose live work @@ -5272,9 +5283,7 @@ def cmd_activate(args: argparse.Namespace) -> None: in parallel. Idempotent (re-activating just refocuses). Validates before mutating.""" root = _require_root() state = load_state(root) - slug = args.slug - if slug not in state.get("milestones", {}): - _die("unknown_milestone") + slug = _resolve_milestone(state, args.slug) if state["milestones"][slug].get("status") == "done": _die("milestone_done") # PROMOTE a queued milestone: activating it flips queued→active (human-gated promotion — @@ -5499,15 +5508,19 @@ def _component_root(root: Path, name: str) -> str | None: return None -def _task_component(root: Path, slug: str): +def _task_component(root: Path, slug: str, comps: dict | None = None): """The component a task binds to via its `component:` header token (anchored like autonomy). None = no line / unfilled `<…>` placeholder; "?" = a real token absent - from the registry; otherwise the component name. PURE.""" + from the registry; otherwise the component name. PURE. `comps` lets a caller in a + loop (cmd_check) pass the once-read registry so the TOML is not re-parsed per task; + omitted -> read it here (byte-identical to the historic behaviour).""" m = _COMPONENT_LINE_RE.search(_task_header(root, slug)) if not m: return None tok = m.group(1).strip() - return tok if tok in _components(root) else "?" + if comps is None: + comps = _components(root) + return tok if tok in comps else "?" def _task_green_bar(root: Path, slug: str) -> str | None: @@ -5556,13 +5569,14 @@ def _component_findings(root: Path) -> list[tuple[str, str]]: continue if not _confined(root.parent / spec["root"], rootp): findings.append(("component_root_outside", f"[component.{name}] root {spec['root']!r} escapes the project")) - known = set(_components(root)) + _reg = _components(root) # read once; reused for both the known-set and the per-task bind + known = set(_reg) try: task_dirs = sorted(p for p in (root / "tasks").iterdir() if p.is_dir()) except OSError: task_dirs = [] # unreadable tasks/ degrades safe — never crash a read for d in task_dirs: - tc = _task_component(root, d.name) + tc = _task_component(root, d.name, _reg) if tc is not None and tc not in known: # "?" or a stale name findings.append(("component_unknown", f"task {d.name} binds an unregistered component")) return findings @@ -5629,10 +5643,7 @@ def _consumer_contract_hold(root: Path, state: dict, slug: str) -> None: _die(f"producer_contract_unfrozen: the producer '{cmap[cid].get('producer', '?')}' of " f"contract '{cid}' must freeze its contract before you write §3 — wait for " f".add/contracts/{cid}.json") - try: - snap_hash = json.loads(snap.read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, AttributeError): - snap_hash = None + snap_hash = _snapshot_hash(snap) if _producer_snapshot_stale(root, cid, snap_hash): _die(f"producer_contract_stale: the live producer of contract '{cid}' changed or re-opened " f"its §3 since the landed .add/contracts/{cid}.json — re-cross the producer " @@ -5930,10 +5941,10 @@ def _consumer_stale_guard(root: Path, state: dict, slug: str) -> None: pin = state["tasks"][slug].get("contract_pin") if not pin: return - try: - live = json.loads(_contract_snapshot(root, pin["id"]).read_text(encoding="utf-8")).get("hash") - except (OSError, ValueError, KeyError, TypeError, AttributeError): - return # unreadable -> surfaced by cmd_check, not confirmable as stale here + # unreadable/hash-less snapshot -> live is None -> the guard below is a no-op + # (surfaced by cmd_check, not confirmable as stale here). Byte-identical to the + # old except->return: nothing runs after the `if`. + live = _snapshot_hash(_contract_snapshot(root, pin["id"])) if live is not None and live != pin.get("hash"): _die(f"contract_consumer_stale: task '{slug}' pinned contract '{pin['id']}' changed shape " "since the pin (the producer re-froze) — re-pin by re-crossing contract->tests after " diff --git a/add-method/tooling/add_engine/taskdoc.py b/add-method/tooling/add_engine/taskdoc.py index 32dd43c9..b6b24bb5 100644 --- a/add-method/tooling/add_engine/taskdoc.py +++ b/add-method/tooling/add_engine/taskdoc.py @@ -14,6 +14,11 @@ from add_engine.constants import _DELTA_RE, _EVIDENCE_RE, _SPEC_DELTA_RE from add_engine.components import _confined +# The ONE canonical §-heading scanner (`^##\s*\s*·`). Static pattern hoisted to +# module load — _phase_spans is on the busiest task-doc read path (check/status/gate), +# so it never re-compiles per call. Behaviour byte-identical. +_HEADING_RE = re.compile(r"^##\s*(\d+)\s*·") + def _task_header(root: Path, slug: str) -> str: """The TASK.md header region — where declared tokens (risk · autonomy) @@ -164,7 +169,7 @@ def _phase_spans(text: str) -> dict[int, str]: KNOWN LIMIT: a §body containing a line-start `## ` or bare `---` truncates early — today's TASK.md bodies don't (box-chars ─═, `### ` sub-heads).""" lines = text.splitlines() - head = re.compile(r"^##\s*(\d+)\s*·") + head = _HEADING_RE starts: dict[int, int] = {} for idx, ln in enumerate(lines): m = head.match(ln) diff --git a/add-method/tooling/engine_pin.py b/add-method/tooling/engine_pin.py index 397a963b..78c19d71 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 = "c8e0a3e54cad55e79af5b59ee586bfa6" # re-aimed @ orient-map (orientation-honesty: bare add.py + --help LEAD with a concise flow map (status/init/new-task/advance/freeze/gate) then the full list — kills the 1/rep initial --help orientation dump). prior: 9476543399… @ status-ancestor-warn -ENGINE_PKG_MD5 = "955023db4358bd3f80a22078bc365361" # re-aimed @ harness-workspace-isolation (io_state.find_root: opt-in ADD_ROOT_CEILING env bounds the upward walk so a nested workspace resolves its own project, not an ancestor). prior: fc40ad47… @ phase-merge-verify +ENGINE_MD5 = "c3d61a86b33c712bc6e0e88f1fc42930" # re-aimed @ hygiene-bundle (engine-hygiene: _snapshot_hash + _resolve_milestone helpers, cmd_check registry-read hoist + dead _arch drop; behavior-preserving). prior: c8e0a3e5… @ guide-fold +ENGINE_PKG_MD5 = "265dd143fd850317c66ffb3ad021c98d" # re-aimed @ hygiene-bundle (engine-hygiene: taskdoc._HEADING_RE — static §-heading regex hoisted to module load). prior: 955023db… @ harness-workspace-isolation diff --git a/add-method/tooling/test_hygiene_bundle.py b/add-method/tooling/test_hygiene_bundle.py new file mode 100644 index 00000000..d44e06a0 --- /dev/null +++ b/add-method/tooling/test_hygiene_bundle.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Red/green for hygiene-bundle (engine-hygiene, frozen §3 v1). + +Four behavior-preserving engine cleanups, pinned structurally (the ~3600-test +fence guards behavior; these pin that each change actually landed): + #2 _snapshot_hash(path) -> str | None — one reader, unified exception tuple + #5 _resolve_milestone(state, slug) -> str — one resolver (twin of _resolve_task) + #3 taskdoc._HEADING_RE — static heading regex hoisted to module + #1 cmd_check reads components.toml O(1), not O(tasks) (+ no dead _arch recompute) + +Run: python3 -m unittest test_hygiene_bundle -v +""" +import hashlib +import io +import json +import os +import shutil +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import add +import engine_pin +import engine_manifest +from add_engine import taskdoc + +HERE = Path(__file__).resolve().parent +ADD_METHOD = HERE.parent +REPO = ADD_METHOD.parent +BUNDLE = ADD_METHOD / "src" / "add_method" / "_bundled" +ADDPY_TRIO = (HERE / "add.py", REPO / ".add" / "tooling" / "add.py", + BUNDLE / "tooling" / "add.py") + + +class SnapshotHashTest(unittest.TestCase): + def setUp(self): + self.tmp = Path(tempfile.mkdtemp(prefix="add-snaphash-")).resolve() + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + + def test_good_file_returns_hash(self): + p = self.tmp / "good.json" + p.write_text(json.dumps({"id": "x", "hash": "abc123"}), encoding="utf-8") + self.assertEqual(add._snapshot_hash(p), "abc123") + + def test_all_malformed_return_none_never_raise(self): + missing = self.tmp / "nope.json" + nonjson = self.tmp / "nonjson.json"; nonjson.write_text("}{not json", encoding="utf-8") + nondict = self.tmp / "nondict.json"; nondict.write_text("[1, 2, 3]", encoding="utf-8") + nohash = self.tmp / "nohash.json"; nohash.write_text(json.dumps({"id": "x"}), encoding="utf-8") + for p in (missing, nonjson, nondict, nohash): + self.assertIsNone(add._snapshot_hash(p), f"{p.name} must degrade to None, never raise") + + +class ResolveMilestoneTest(unittest.TestCase): + def test_present_returns_slug(self): + state = {"milestones": {"v1": {}}} + self.assertEqual(add._resolve_milestone(state, "v1"), "v1") + + def test_absent_dies_unknown_milestone(self): + state = {"milestones": {"v1": {}}} + with self.assertRaises(SystemExit) as cm: + with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): + add._resolve_milestone(state, "ghost") + self.assertNotEqual(cm.exception.code, 0) + + +class TaskdocHeadingReTest(unittest.TestCase): + def test_module_constant_exists_and_matches(self): + self.assertTrue(hasattr(taskdoc, "_HEADING_RE"), + "the static heading regex must be a module-level constant (hoisted)") + m = taskdoc._HEADING_RE.match("## 3 · PLAN") + self.assertIsNotNone(m) + self.assertEqual(m.group(1), "3") + + +class CmdCheckComponentsOncePerInvocationTest(unittest.TestCase): + """#1b: cmd_check reads components.toml O(1) per invocation, not O(bound-tasks). + Proven by comparing the _components() call count across a 2-bound-task and a + 4-bound-task project — a hoisted read is INDEPENDENT of task count.""" + + def _silent(self, *argv): + with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): + try: + add.main(list(argv)) + except SystemExit: + pass + + def _project(self, n_tasks: int) -> Path: + d = Path(tempfile.mkdtemp(prefix="add-comp-")).resolve() + self.addCleanup(shutil.rmtree, d, ignore_errors=True) + prev = Path.cwd() + os.chdir(d) + try: + self._silent("init", "--name", "demo") + # a real component so a bound task's _tc != "?" (exercises the per-task read) + (d / ".add" / "components.toml").write_text( + '[component.api]\nroot = "api"\ngreen_bar = "tests pass"\n', encoding="utf-8") + for i in range(n_tasks): + slug = f"t{i}" + self._silent("new-task", slug) + p = d / ".add" / "tasks" / slug / "TASK.md" + txt = p.read_text(encoding="utf-8").replace( + "autonomy: auto", "autonomy: auto\ncomponent: api", 1) + p.write_text(txt, encoding="utf-8") + finally: + os.chdir(prev) + return d + + def _count_components_calls(self, project: Path) -> int: + real = add._components + n = {"c": 0} + def spy(root): + n["c"] += 1 + return real(root) + prev = Path.cwd() + os.chdir(project) + add._components = spy + try: + with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): + try: + add.main(["check"]) + except SystemExit: + pass + finally: + add._components = real + os.chdir(prev) + return n["c"] + + def test_components_read_is_independent_of_task_count(self): + c2 = self._count_components_calls(self._project(2)) + c4 = self._count_components_calls(self._project(4)) + self.assertEqual(c2, c4, + f"cmd_check must read components.toml O(1), not O(bound-tasks): " + f"2-task={c2} vs 4-task={c4} — a hoisted read is task-count-independent") + + +class EnginePinTest(unittest.TestCase): + def test_addpy_trio_matches_engine_md5(self): + digests = {hashlib.md5(p.read_bytes()).hexdigest() for p in ADDPY_TRIO} + self.assertEqual(len(digests), 1, "add.py trio diverged") + self.assertEqual(digests.pop(), engine_pin.ENGINE_MD5, + "engine_pin.ENGINE_MD5 must track the live engine") + + def test_pkg_digest_matches(self): + self.assertEqual(engine_manifest.package_digest(HERE), + engine_pin.ENGINE_PKG_MD5, + "engine_pin.ENGINE_PKG_MD5 must track the engine package modules") + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 0dddefd67aa1d0a400724155d71277e3a3433ce3 Mon Sep 17 00:00:00 2001 From: Tin Dang Date: Tue, 14 Jul 2026 23:54:56 +0700 Subject: [PATCH 2/3] feat(engine): wire milestone-relations health into check + status (wire-milestone-relations) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sweep found `_milestone_relations` parsed a MILESTONE.md's depends-on/extends/relates-to header edges but was called by NO command (only its test) — a half-built feature, while the task-level twin is fully wired. This finishes it. New `_milestone_relations_health(root, state)` mirrors `_relations_health`: returns `{mslug, relation, target, kind}` findings — `dangling` when an edge names an unknown milestone, `self_relation` when it names itself. All three edge kinds are validated (milestone edges are cross-milestone legibility, never a build DAG, so unlike the task twin's depends_on they don't belong to any schedule). Surfaced ADVISORY (never red, never blocks a gate): - cmd_check: one WARN per finding (feeds `warnings`, never `checks`/`failed`), reading the hyphen label the human wrote (`relates-to 'ghost' which is not a milestone (dangling)`). - cmd_status: a one-line count `milestone-relations: N dangling · M self — run add.py check` beside the existing task `relations:` line, silent when clean, human branch only (never --json/--brief). This also wires the cmd_check per-finding detail the status line's "run add.py check" already promised — so milestone-relations is now more completely surfaced than the task twin. Task B of the engine-hygiene milestone (#4 from the read-only sweep; user chose "wire it up"). Red/green: test_wire_milestone_relations (6 tests). Synced ×4 add.py twins; ENGINE_MD5→d7079f8d (no add_engine change → ENGINE_PKG_MD5 unchanged); SEAMS _declared_scope re-pinned 5734→5778. author: Tin Dang --- .add/SEAMS.md | 2 +- .add/state.json | 51 ++++++- .add/tasks/wire-milestone-relations/TASK.md | 105 ++++++++++++++ .../src/add_method/_bundled/tooling/add.py | 44 ++++++ .../add_method/_bundled/tooling/engine_pin.py | 2 +- add-method/tooling/add.py | 44 ++++++ add-method/tooling/engine_pin.py | 2 +- .../tooling/test_wire_milestone_relations.py | 131 ++++++++++++++++++ 8 files changed, 375 insertions(+), 6 deletions(-) create mode 100644 .add/tasks/wire-milestone-relations/TASK.md create mode 100644 add-method/tooling/test_wire_milestone_relations.py diff --git a/.add/SEAMS.md b/.add/SEAMS.md index 504d3fc7..25a932fa 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:5734` (`_declared_scope`) +Anchor: `add-method/tooling/add.py:5778` (`_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/state.json b/.add/state.json index 835bdca8..adb21ee3 100644 --- a/.add/state.json +++ b/.add/state.json @@ -1,7 +1,7 @@ { "project": "AIDD / ADD Methodology", "stage": "mvp", - "active_task": "hygiene-bundle", + "active_task": "wire-milestone-relations", "tasks": { "add-check": { "title": "add.py check: validate .add project integrity", @@ -9906,10 +9906,55 @@ "email": "tindang.ht97@gmail.com", "source": "git" } + }, + "wire-milestone-relations": { + "title": "wire _milestone_relations into cmd_check/status \u2014 dangling/self milestone-edge health (mirrors _relations_health)", + "phase": "done", + "gate": "PASS", + "milestone": "engine-hygiene", + "depends_on": [], + "created": "2026-07-14T16:44:46+00:00", + "updated": "2026-07-14T16:54:49+00:00", + "fast": true, + "freeze": { + "version": "v1", + "frozen_at": "2026-07-14T16:46:48+00:00", + "approved_by": "Tin Dang", + "actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } + }, + "flag_verified": true, + "tripwire": { + "contract_md5": "b203d7801087dd97d33518a784f5c15e", + "tests": {} + }, + "scope": { + "declared": [ + "add-method/tooling/add.py", + ".add/tooling/add.py", + "add-method/.add/tooling/add.py", + "add-method/src/add_method/_bundled/tooling/add.py", + "add-method/tooling/engine_pin.py", + ".add/tooling/engine_pin.py", + "add-method/.add/tooling/engine_pin.py", + "add-method/src/add_method/_bundled/tooling/engine_pin.py", + ".add/SEAMS.md", + "add-method/tooling/test_wire_milestone_relations.py" + ], + "snapshot_md5": "16707166c4bc4b9d7035ddd1c5662aeb" + }, + "gate_actor": { + "name": "Tin Dang", + "email": "tindang.ht97@gmail.com", + "source": "git" + } } }, "created": "2026-05-28T15:39:04+00:00", - "updated": "2026-07-14T16:41:56+00:00", + "updated": "2026-07-14T16:54:49+00:00", "milestones": { "v1-1": { "title": "v1.1 \u2014 adoption & ergonomics", @@ -11224,7 +11269,7 @@ "six-phase-loop": "installer-shared-namespace-guard", "call-residuals": "help-habit-kill", "orientation-honesty": "guide-fold", - "engine-hygiene": "hygiene-bundle" + "engine-hygiene": "wire-milestone-relations" }, "todos": [ { diff --git a/.add/tasks/wire-milestone-relations/TASK.md b/.add/tasks/wire-milestone-relations/TASK.md new file mode 100644 index 00000000..49c281ce --- /dev/null +++ b/.add/tasks/wire-milestone-relations/TASK.md @@ -0,0 +1,105 @@ +# TASK: wire _milestone_relations into cmd_check/status — dangling/self milestone-edge health (mirrors _relations_health) + +slug: wire-milestone-relations · created: 2026-07-14 · stage: mvp +milestone: engine-hygiene +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:`; the observe note 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: finish-wire the half-built milestone-relations feature (#4 from the sweep). `_milestone_relations(root, mslug)` parses a MILESTONE.md's `depends-on:`/`extends:`/`relates-to:` header edges but is called by NO command (only its test) — the task-level twin `_task_relations`→`_relations_health`→cmd_status/cmd_check IS wired. This adds the milestone-level twin: a new `_milestone_relations_health(root, state)` that validates every milestone's relation targets, surfaced ADVISORY (never red, never blocks) in cmd_check (per-finding warnings) and cmd_status (a one-line count), exactly mirroring the task surface. +Must: + - a new `_milestone_relations_health(root, state) -> list[dict]` returns findings `{mslug, relation, target, kind}` for every milestone in state: kind `self_relation` when an edge names its own milestone; kind `dangling` when a `depends_on`/`extends`/`relates_to` target is not a known milestone in state; clean → `[]`. PURE (reads MILESTONE.md via `_milestone_relations`, never writes/blocks) — mirrors `_relations_health` + - cmd_check surfaces each finding as a WARN (never red, warn-never-block, feeds `warnings` not `failed`): e.g. `milestone 'X' relates-to 'Y' which is not a milestone (dangling)` / `... names itself (self_relation)` + - cmd_status prints an advisory one-liner `milestone-relations: N dangling · M self — run add.py check` when findings exist, SILENT when clean, ONLY on the human-readable active-milestone surface (never the `--json`/`--brief` path) — mirroring the existing task `relations:` line + - depends_on milestone edges are validated for resolution here too, but this is ADVISORY legibility only — it does NOT enter any schedule/DAG or block a gate (milestone-level edges are cross-milestone context, not a build DAG) +Reject: + - a MILESTONE.md with no relation header lines / an old milestone / an unreadable doc → contributes NO findings (fail-safe, `_milestone_relations` already returns all-empty) — never a raise, never a spurious finding -> "milestone_relations_read_failsafe" + - the surface must NOT turn a dangling/self relation into a FAILED check (red) — advisory only, like every relation-health finding -> "milestone_relation_must_not_block" +Accept: Given a project with milestone A declaring `relates-to: ghost` (no such milestone) and milestone B declaring `depends-on: B` (itself), When `add.py check` runs, Then it WARNs one dangling (A→ghost) + one self (B→B) without failing the check; and `add.py status` prints `milestone-relations: 1 dangling · 1 self — run add.py check`; a clean project prints neither +Boundary: a resolvable milestone edge (known target, silent) vs a dangling target (unknown milestone) vs a self edge — the three the health check must distinguish +Assumptions: ⚠ milestone relation targets are milestone SLUGS (validated against `state["milestones"]`), exactly as task relation targets are task slugs — verified against `_relations_health`'s task-slug resolution; if wrong (targets were meant to be something else): the finding text is advisory so a mis-resolution mislabels a warning at worst, never blocks or crashes + +--- + +## 3 · PLAN — the change plan: ground · contract · build-strategy + +### Grounding +Touches (files · symbols): `add.py:_milestone_relations_health` (NEW, mirrors `_relations_health`@1343, uses the existing `_milestone_relations`@1323) · `add.py:cmd_check` (add a per-milestone warn loop feeding `warnings`, near the existing task dangling-lineage warns) · `add.py:cmd_status` (add the advisory one-liner near the existing task `relations:` line @2922, human-readable branch only) +Context (working folder): `add-method/tooling/` — add.py ×4 twins + engine_pin.py ×4 (ENGINE_MD5 only — no add_engine change); `.add/SEAMS.md` `_declared_scope` line-pin (new function above the anchor → re-pin) +Honors (patterns / conventions): `_relations_health`@1343 (finding shape `{slug/mslug, relation, target, kind}`, kinds `dangling`/`self_relation`, PURE never-blocks); cmd_status's task `relations:` one-liner @2922 (silent-when-clean, human-branch-only); cmd_check's warn-never-block idiom (findings feed `warnings`, never `checks`/`failed`) +Anchors the contract cites: `_milestone_relations_health` (new) · `_milestone_relations` · `_relations_health` · `cmd_check` · `cmd_status` +Ground SHA: cfb8fe0 — stamped by freeze + +### Contract + +``` +# NEW (add.py), the milestone twin of _relations_health: +_milestone_relations_health(root: Path, state: dict) -> list[dict] + for mslug in state.get("milestones", {}): + rel = _milestone_relations(root, mslug) # depends_on/extends/relates_to edge-lists + for rtype in ("depends_on", "extends", "relates_to"): + for target in rel[rtype]: + target == mslug -> {mslug, relation: rtype, target, kind: "self_relation"} + target not in state["milestones"] -> {mslug, relation: rtype, target, kind: "dangling"} + else -> (resolves — no finding) + -> [] when clean. PURE: reads MILESTONE.md headers via _milestone_relations; never writes, never blocks. + +# cmd_check: after computing findings, append one WARN per finding to `warnings` (never `checks`): +# f"milestone '{mslug}'" , f"{relation} '{target}' which is not a milestone (dangling)" | f"{relation} names itself (self_relation)" +# cmd_status (human branch only, near the task relations: line): when findings exist, print +# f"milestone-relations: {n_dang} dangling · {n_self} self — run add.py check" (silent when clean) +``` + +`Least-sure flag surfaced at freeze:` [contract] the relation-label wording in the finding text — `_milestone_relations` keys are `depends_on`/`extends`/`relates_to` (underscored) but the human MILESTONE.md labels are `depends-on`/`extends`/`relates-to` (hyphen). The warning should read the hyphen form the human wrote; if I emit the underscore key it's cosmetically off (not wrong). Cost: a wording nit caught by the test asserting the surfaced text — never a behavior/blocking issue. +Status: FROZEN @ v1 — approved by Tin Dang +### Build-strategy +Scope (may touch): `add-method/tooling/add.py` `.add/tooling/add.py` `add-method/.add/tooling/add.py` `add-method/src/add_method/_bundled/tooling/add.py` `add-method/tooling/engine_pin.py` `.add/tooling/engine_pin.py` `add-method/.add/tooling/engine_pin.py` `add-method/src/add_method/_bundled/tooling/engine_pin.py` `.add/SEAMS.md` `add-method/tooling/test_wire_milestone_relations.py` +Strategy & known-problem fixes: 1. RED test_wire_milestone_relations: build a project with milestone A `relates-to: ghost` + milestone B `depends-on: B`; assert `_milestone_relations_health` returns exactly the 1 dangling + 1 self finding; assert `check` WARNs both without a FAILED check (exit stays check-clean); assert `status` prints `milestone-relations: 1 dangling · 1 self`; assert a clean project → no finding, no status line. 2. add `_milestone_relations_health` (mirror `_relations_health`); wire the cmd_check warn loop + cmd_status one-liner. TRAP milestone_relation_must_not_block: findings feed `warnings`, NEVER `checks`/`failed`. TRAP: status line on the human branch only (guard like the task relations: line). 3. sync ×4 add.py twins, re-pin ENGINE_MD5 (no PKG — add_engine untouched), re-pin SEAMS `_declared_scope`. 4. full fence. +Approach (domain strategy): mirror the task-relations twin + +### AI-verify record (required when gate_mode: ai-plan-verify) +- [x] §3 PLAN grounding anchors resolve in the current tree — _milestone_relations@1323, _relations_health@1343, cmd_status relations line @2922 all verified +- [x] §1 every Must + every Reject present, each Reject paired with an error code — milestone_relations_read_failsafe, milestone_relation_must_not_block +- [x] §3 Contract shape is concrete (no template placeholder text remains) +- [x] Lowest-confidence flag surfaced and substantive (mirrors unflagged_freeze's own bar) — the hyphen-vs-underscore label wording +Verified by: orchestrator · at: 2026-07-14 + +--- + +## 4 · TESTS — failing-first (red) + +Plan: test_ — assert the §1 Accept line's Then (behavior, not internals). +Tests live in: `./tests/` · MUST run red (missing implementation) 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 — added `_milestone_relations_health` (mirrors `_relations_health`, validates all three edge kinds since milestone edges are advisory-not-DAG), the cmd_check warn loop (feeds `warnings`, never `checks`), and the cmd_status one-liner beside the task `relations:` line (human branch only). Least-sure flag resolved: emit the HYPHEN label form (`_mf['relation'].replace('_','-')`) so the warning reads `relates-to`/`depends-on` as the human wrote them. NOTE: the task twin only surfaces the status count (not a per-check detail) — this task ALSO wires the cmd_check per-finding detail the status line's `run add.py check` already promised, so milestone-relations is now MORE complete than the task twin. No divergence from the frozen contract. +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 — test_wire_milestone_relations 6 green; full fence (pending tail) +- [x] green was EARNED — the health/check/status asserts were RED before the wiring (_milestone_relations_health AttributeError, no 'ghost' in check, no 'milestone-relations:' in status); the clean-project asserts guard against spurious findings +- [x] input dialect held — the test speaks the real MILESTONE.md header-relation dialect (`relates-to:`/`depends-on:` lines) + the CLI check/status stdout +- [x] no exposed secrets/injection/deps — pure advisory read of MILESTONE.md headers; never writes, never blocks a gate (security = HARD-STOP: none) + +Build expectations (from §1 Accept + §3 CONTRACT): with milestone A `relates-to: ghost` + B `depends-on: b`, `add.py check` prints WARN lines naming `ghost` (dangling) and `names itself (self_relation)` while staying exit 0 (not FAILED); `add.py status` prints `milestone-relations: 1 dangling · 1 self — run add.py check`; a clean project prints neither. Confirmed by test_wire_milestone_relations (6 tests) + the full fence. + +### GATE RECORD +Outcome: PASS +Reviewed by: Tin Dang · date: 2026-07-14 + diff --git a/add-method/src/add_method/_bundled/tooling/add.py b/add-method/src/add_method/_bundled/tooling/add.py index cda0449c..7704b336 100644 --- a/add-method/src/add_method/_bundled/tooling/add.py +++ b/add-method/src/add_method/_bundled/tooling/add.py @@ -1362,6 +1362,29 @@ def _relations_health(root: Path, state: dict) -> list[dict]: return findings +def _milestone_relations_health(root: Path, state: dict) -> list[dict]: + """ADVISORY validate pass over every milestone's relation edges — the milestone twin of + _relations_health. Returns [{mslug, relation, target, kind}], kind in {'dangling', + 'self_relation'}: a depends_on/extends/relates_to target that is not a known milestone is + dangling; a self-edge is self_relation. Milestone edges are cross-milestone LEGIBILITY only + (never a build DAG, never blocking) so all three edge kinds are validated here — unlike the + task twin, whose depends_on IS the schedule DAG and is checked there. PURE: reads MILESTONE.md + headers via _milestone_relations, never writes, never blocks a gate.""" + milestones = state.get("milestones") or {} + findings: list[dict] = [] + for mslug in milestones: + rel = _milestone_relations(root, mslug) + for rtype in ("depends_on", "extends", "relates_to"): + for target in rel[rtype]: + if target == mslug: + findings.append({"mslug": mslug, "relation": rtype, + "target": target, "kind": "self_relation"}) + elif target not in milestones: + findings.append({"mslug": mslug, "relation": rtype, + "target": target, "kind": "dangling"}) + return findings + + def _resolve_task(state: dict, slug: str | None) -> str: slug = slug or _active_task(state) if not slug: @@ -2926,6 +2949,15 @@ def cmd_status(args: argparse.Namespace) -> None: _parts = [p for p in (f"{_n_dang} dangling" if _n_dang else "", f"{_n_self} self" if _n_self else "") if p] print(f"relations: {' · '.join(_parts)} — run add.py check") + # milestone-relations health (wire-milestone-relations): the milestone twin of the + # task relations: line above — one advisory count, silent when clean, human branch only. + _msrel_bad = _milestone_relations_health(root, state) + if _msrel_bad: + _ms_self = sum(1 for f in _msrel_bad if f["kind"] == "self_relation") + _ms_dang = sum(1 for f in _msrel_bad if f["kind"] == "dangling") + _ms_parts = [p for p in (f"{_ms_dang} dangling" if _ms_dang else "", + f"{_ms_self} self" if _ms_self else "") if p] + print(f"milestone-relations: {' · '.join(_ms_parts)} — run add.py check") # foundation pointer — read the cross-milestone context first (anti-rot) if (root / "PROJECT.md").exists(): print("context : .add/PROJECT.md (foundation: domain · spec · UI/UX — read first)") @@ -4241,6 +4273,18 @@ def cmd_check(args: argparse.Namespace) -> None: "add-*.md` files are installed — run `add.py update` (or re-run the " "CLI installer) to materialize them")) + # milestone-relations health (wire-milestone-relations): surface a milestone whose + # depends-on/extends/relates-to header edge names an unknown milestone (dangling) or + # itself (self). ADVISORY — feeds `warnings`, NEVER `checks`/`failed` (a cross-milestone + # legibility edge never blocks). The status one-liner counts the same findings. + for _mf in _milestone_relations_health(root, state): + _rlabel = _mf["relation"].replace("_", "-") + if _mf["kind"] == "self_relation": + warnings.append((f"milestone '{_mf['mslug']}'", f"{_rlabel} names itself (self_relation)")) + else: + warnings.append((f"milestone '{_mf['mslug']}'", + f"{_rlabel} '{_mf['target']}' which is not a milestone (dangling)")) + passed = sum(1 for ok, _, _ in checks if ok) failed = len(checks) - passed if as_json: 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 78c19d71..1dae8203 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 = "c3d61a86b33c712bc6e0e88f1fc42930" # re-aimed @ hygiene-bundle (engine-hygiene: _snapshot_hash + _resolve_milestone helpers, cmd_check registry-read hoist + dead _arch drop; behavior-preserving). prior: c8e0a3e5… @ guide-fold +ENGINE_MD5 = "d7079f8d9622da309286cec9951f8b91" # re-aimed @ wire-milestone-relations (engine-hygiene: _milestone_relations_health wired into cmd_check warns + cmd_status one-liner — the milestone twin of _relations_health). prior: c3d61a86… @ hygiene-bundle ENGINE_PKG_MD5 = "265dd143fd850317c66ffb3ad021c98d" # re-aimed @ hygiene-bundle (engine-hygiene: taskdoc._HEADING_RE — static §-heading regex hoisted to module load). prior: 955023db… @ harness-workspace-isolation diff --git a/add-method/tooling/add.py b/add-method/tooling/add.py index cda0449c..7704b336 100644 --- a/add-method/tooling/add.py +++ b/add-method/tooling/add.py @@ -1362,6 +1362,29 @@ def _relations_health(root: Path, state: dict) -> list[dict]: return findings +def _milestone_relations_health(root: Path, state: dict) -> list[dict]: + """ADVISORY validate pass over every milestone's relation edges — the milestone twin of + _relations_health. Returns [{mslug, relation, target, kind}], kind in {'dangling', + 'self_relation'}: a depends_on/extends/relates_to target that is not a known milestone is + dangling; a self-edge is self_relation. Milestone edges are cross-milestone LEGIBILITY only + (never a build DAG, never blocking) so all three edge kinds are validated here — unlike the + task twin, whose depends_on IS the schedule DAG and is checked there. PURE: reads MILESTONE.md + headers via _milestone_relations, never writes, never blocks a gate.""" + milestones = state.get("milestones") or {} + findings: list[dict] = [] + for mslug in milestones: + rel = _milestone_relations(root, mslug) + for rtype in ("depends_on", "extends", "relates_to"): + for target in rel[rtype]: + if target == mslug: + findings.append({"mslug": mslug, "relation": rtype, + "target": target, "kind": "self_relation"}) + elif target not in milestones: + findings.append({"mslug": mslug, "relation": rtype, + "target": target, "kind": "dangling"}) + return findings + + def _resolve_task(state: dict, slug: str | None) -> str: slug = slug or _active_task(state) if not slug: @@ -2926,6 +2949,15 @@ def cmd_status(args: argparse.Namespace) -> None: _parts = [p for p in (f"{_n_dang} dangling" if _n_dang else "", f"{_n_self} self" if _n_self else "") if p] print(f"relations: {' · '.join(_parts)} — run add.py check") + # milestone-relations health (wire-milestone-relations): the milestone twin of the + # task relations: line above — one advisory count, silent when clean, human branch only. + _msrel_bad = _milestone_relations_health(root, state) + if _msrel_bad: + _ms_self = sum(1 for f in _msrel_bad if f["kind"] == "self_relation") + _ms_dang = sum(1 for f in _msrel_bad if f["kind"] == "dangling") + _ms_parts = [p for p in (f"{_ms_dang} dangling" if _ms_dang else "", + f"{_ms_self} self" if _ms_self else "") if p] + print(f"milestone-relations: {' · '.join(_ms_parts)} — run add.py check") # foundation pointer — read the cross-milestone context first (anti-rot) if (root / "PROJECT.md").exists(): print("context : .add/PROJECT.md (foundation: domain · spec · UI/UX — read first)") @@ -4241,6 +4273,18 @@ def cmd_check(args: argparse.Namespace) -> None: "add-*.md` files are installed — run `add.py update` (or re-run the " "CLI installer) to materialize them")) + # milestone-relations health (wire-milestone-relations): surface a milestone whose + # depends-on/extends/relates-to header edge names an unknown milestone (dangling) or + # itself (self). ADVISORY — feeds `warnings`, NEVER `checks`/`failed` (a cross-milestone + # legibility edge never blocks). The status one-liner counts the same findings. + for _mf in _milestone_relations_health(root, state): + _rlabel = _mf["relation"].replace("_", "-") + if _mf["kind"] == "self_relation": + warnings.append((f"milestone '{_mf['mslug']}'", f"{_rlabel} names itself (self_relation)")) + else: + warnings.append((f"milestone '{_mf['mslug']}'", + f"{_rlabel} '{_mf['target']}' which is not a milestone (dangling)")) + passed = sum(1 for ok, _, _ in checks if ok) failed = len(checks) - passed if as_json: diff --git a/add-method/tooling/engine_pin.py b/add-method/tooling/engine_pin.py index 78c19d71..1dae8203 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 = "c3d61a86b33c712bc6e0e88f1fc42930" # re-aimed @ hygiene-bundle (engine-hygiene: _snapshot_hash + _resolve_milestone helpers, cmd_check registry-read hoist + dead _arch drop; behavior-preserving). prior: c8e0a3e5… @ guide-fold +ENGINE_MD5 = "d7079f8d9622da309286cec9951f8b91" # re-aimed @ wire-milestone-relations (engine-hygiene: _milestone_relations_health wired into cmd_check warns + cmd_status one-liner — the milestone twin of _relations_health). prior: c3d61a86… @ hygiene-bundle ENGINE_PKG_MD5 = "265dd143fd850317c66ffb3ad021c98d" # re-aimed @ hygiene-bundle (engine-hygiene: taskdoc._HEADING_RE — static §-heading regex hoisted to module load). prior: 955023db… @ harness-workspace-isolation diff --git a/add-method/tooling/test_wire_milestone_relations.py b/add-method/tooling/test_wire_milestone_relations.py new file mode 100644 index 00000000..9e637f43 --- /dev/null +++ b/add-method/tooling/test_wire_milestone_relations.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Red/green for wire-milestone-relations (engine-hygiene, frozen §3 v1). + +Finish-wires the parsed-but-unused `_milestone_relations` into a command surface: +a new `_milestone_relations_health(root, state)` validates every milestone's +relation edges (dangling target / self edge), surfaced ADVISORY (never red) in +cmd_check (per-finding warnings) and cmd_status (a one-line count) — mirroring the +task-level `_relations_health`. + +Run: python3 -m unittest test_wire_milestone_relations -v +""" +import hashlib +import io +import os +import shutil +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path + +import add +import engine_pin + +HERE = Path(__file__).resolve().parent +ADD_METHOD = HERE.parent +REPO = ADD_METHOD.parent +BUNDLE = ADD_METHOD / "src" / "add_method" / "_bundled" +ADDPY_TRIO = (HERE / "add.py", REPO / ".add" / "tooling" / "add.py", + BUNDLE / "tooling" / "add.py") + + +class _Board(unittest.TestCase): + def setUp(self): + self._cwd = Path.cwd() + self.tmp = Path(tempfile.mkdtemp(prefix="add-msrel-")).resolve() + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + self.addCleanup(os.chdir, os.getcwd()) + os.chdir(self.tmp) + self._silent("init", "--name", "demo") + + def tearDown(self): + os.chdir(self._cwd) + + def _silent(self, *argv): + with redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): + try: + add.main(list(argv)) + except SystemExit: + pass + + def _run(self, *argv): + buf, err = io.StringIO(), io.StringIO() + code = 0 + try: + with redirect_stdout(buf), redirect_stderr(err): + add.main(list(argv)) + except SystemExit as e: + code = e.code if isinstance(e.code, int) else 1 + return buf.getvalue(), err.getvalue(), code + + def _root(self): + return self.tmp / ".add" + + def _add_ms(self, slug, *header_rel_lines): + """Create milestone `slug`, then inject relation line(s) into its MILESTONE.md header.""" + self._silent("new-milestone", slug, "--title", slug.upper(), "--goal", "g") + if header_rel_lines: + p = self._root() / "milestones" / slug / "MILESTONE.md" + txt = p.read_text(encoding="utf-8") + # insert the relation lines right after the H1 title (still in the header, before any `## `) + lines = txt.splitlines() + lines.insert(1, "\n".join(header_rel_lines)) + p.write_text("\n".join(lines), encoding="utf-8") + + +class MilestoneRelationsHealthTest(_Board): + def test_health_flags_dangling_and_self(self): + self._add_ms("a", "relates-to: ghost") # ghost is no milestone -> dangling + self._add_ms("b", "depends-on: b") # names itself -> self + findings = add._milestone_relations_health(self._root(), add.load_state(self._root())) + kinds = sorted((f["mslug"], f["kind"]) for f in findings) + self.assertIn(("a", "dangling"), kinds, f"A→ghost must be dangling: {findings}") + self.assertIn(("b", "self_relation"), kinds, f"B→B must be self_relation: {findings}") + self.assertEqual(len(findings), 2, f"exactly the 2 findings, no more: {findings}") + + def test_clean_project_no_findings(self): + self._add_ms("a") + self._add_ms("b", "depends-on: a") # resolves to a known milestone -> silent + self.assertEqual(add._milestone_relations_health(self._root(), add.load_state(self._root())), [], + "resolvable + relation-less milestones yield no findings") + + +class SurfaceTest(_Board): + def test_check_warns_but_does_not_fail(self): + self._add_ms("a", "relates-to: ghost") + self._add_ms("b", "depends-on: b") + out, err, code = self._run("check") + blob = out + err + self.assertIn("ghost", blob, "check surfaces the dangling milestone edge") + self.assertIn("self_relation", blob.replace("self relation", "self_relation") + blob, + "check surfaces the self edge") + self.assertNotIn("check: FAIL", blob) + # advisory: dangling/self relations must never turn the check red (exit non-2 for content-fail) + # a clean-but-warned check still exits 0 + self.assertEqual(code, 0, f"a dangling/self milestone relation must NOT fail the check: {blob}") + + def test_status_prints_advisory_oneliner(self): + self._add_ms("a", "relates-to: ghost") + self._add_ms("b", "depends-on: b") + out, _, _ = self._run("status") + self.assertIn("milestone-relations:", out, f"status prints the advisory one-liner: {out}") + self.assertIn("1 dangling", out) + self.assertIn("1 self", out) + + def test_status_silent_when_clean(self): + self._add_ms("a") + out, _, _ = self._run("status") + self.assertNotIn("milestone-relations:", out, + "a clean project prints no milestone-relations line") + + +class EnginePinTest(unittest.TestCase): + def test_addpy_trio_matches_engine_md5(self): + digests = {hashlib.md5(p.read_bytes()).hexdigest() for p in ADDPY_TRIO} + self.assertEqual(len(digests), 1, "add.py trio diverged") + self.assertEqual(digests.pop(), engine_pin.ENGINE_MD5, + "engine_pin.ENGINE_MD5 must track the live engine") + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 36b5c15470d6b0051acb60c297962f997f7ca2cf Mon Sep 17 00:00:00 2001 From: Tin Dang Date: Tue, 14 Jul 2026 23:58:21 +0700 Subject: [PATCH 3/3] docs(method): fill engine-hygiene milestone close ship-review (2/2 done) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the MILESTONE.md scaffold placeholders with the real milestone record now that both tasks are built, gated PASS, and committed: - Scope: the confirmed #1+#2+#3+#5 behavior-preserving cleanup + #4 finish-wire, with the deferred #6/#7/#8 named as the anti-scope-creep Out list. - Tasks: hygiene-bundle + wire-milestone-relations, both checked done. - Exit criteria: the three observable outcomes (cmd_check O(1), shared snapshot-hash/milestone-resolve helpers, advisory milestone-relation surface). - Close ship-review: ship-by-domain (tooling only; skill/book untouched), the per-task gate/tests/fence evidence rows, and the goal-met mapping back to each exit criterion. Full fence last green at 3626 passed / 0 failed. - Release steps: commits landed; push + PR + merge-on-green next; the paid WM1 re-measure waits for merge (human-authorized "wait for engine-hygiene"). Doc-only — no engine change, so no ENGINE_MD5 / SEAMS re-pin. author: Tin Dang --- .add/milestones/engine-hygiene/MILESTONE.md | 36 ++++++++++++--------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/.add/milestones/engine-hygiene/MILESTONE.md b/.add/milestones/engine-hygiene/MILESTONE.md index dceb9226..938f94d2 100644 --- a/.add/milestones/engine-hygiene/MILESTONE.md +++ b/.add/milestones/engine-hygiene/MILESTONE.md @@ -1,7 +1,7 @@ # MILESTONE: Engine hygiene: perf/dedup cleanup + wire milestone-relations goal: Land the value-dense engine cleanup a read-only sweep found — behavior-preserving perf hoists + duplication removal (cmd_check TOML re-reads/dead recompute, 5x snapshot-hash helper with unified exceptions, static-regex hoist, milestone-resolve DRY) and finish-wiring the never-surfaced _milestone_relations feature — with the existing ~3600-test fence as the safety net -rationale: +rationale: sub-milestone — a read-only engine sweep (Explore agent, scratchpad/sweep_report.md) surfaced 8 findings; the user picked scope #1+#2+#3+#5 (behavior-preserving cleanup) + #4 (finish-wire the half-built feature) via AskUserQuestion. Value-dense, low-risk, one existing fence protects it. stage: mvp · status: active · created: 2026-07-14T16:16:45+00:00 release: pending relations: @@ -11,8 +11,8 @@ relations: written just-in-time. Update this doc whenever a task reveals a milestone gap. ## Scope -In: -Out: +In: behavior-preserving perf/dedup cleanup of the ADD engine (cmd_check TOML re-reads + dead recompute → O(1) hoist; 5× snapshot-hash try/except → `_snapshot_hash` with a unified except tuple; static `_HEADING_RE` regex → module-const hoist in taskdoc.py; 4× `unknown_milestone` boilerplate → `_resolve_milestone`) · finish-wiring the parsed-but-unused `_milestone_relations` feature into cmd_check (per-finding advisory warnings) + cmd_status (a one-line count). +Out: findings #6 (no_project boilerplate — too small standalone), #7 (long fns cmd_check/cmd_status/build_parser — verbose not broken, order-sensitive stdout tests), #8 (13 broad `except` — all intentional documented fail-open). No new CLI surface, no behavior change to any existing gate. > UI/UX in scope? Name it precisely, not "make it nice" — information architecture · > interaction pattern · visual hierarchy · design tokens · component states · @@ -38,31 +38,37 @@ Issues/Risks (shared): -> owning task ## Tasks (breadth-first decomposition; detail lives in each TASK.md) -- [ ] depends-on: none — -- [ ] depends-on: +- [x] hygiene-bundle depends-on: none — perf hoists + dedup helpers (#1+#2+#3+#5), behavior-preserving, ~3600-test fence as net +- [x] wire-milestone-relations depends-on: hygiene-bundle — finish-wire `_milestone_relations` into cmd_check + cmd_status (advisory) ## Exit criteria (observable; map each to the task that delivers it) -- [ ] User can (← ) +- [x] cmd_check reads components.toml + contracts O(1) per invocation (not per-task), with no observable output change (← hygiene-bundle) +- [x] every snapshot-hash read and every `unknown_milestone` resolve routes through one shared helper (← hygiene-bundle) +- [x] a MILESTONE.md relation edge to an unknown / self milestone surfaces as an advisory warning in `add.py check` and a one-line count in `add.py status` — never turning either red (← wire-milestone-relations) ## 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 : +- tooling : add.py — `_snapshot_hash` + `_resolve_milestone` helpers (5 + 4 call-sites deduped), cmd_check O(1) hoist (`_components`/`_contracts` lifted out of the per-task loop, dead `_arch` removed, `_task_component(comps=)` + `_component_findings(_reg)` threaded), new `_milestone_relations_health` wired into cmd_check warns + cmd_status one-liner. add_engine/taskdoc.py — static `_HEADING_RE` hoisted to module const. engine_pin.py — ENGINE_MD5 ×2 re-pins (→c3d61a86→d7079f8d), ENGINE_PKG_MD5 →265dd143. All synced ×4 engine twins byte-identically. +- skill : untouched. +- book : untouched. (`.add/SEAMS.md` line-pins migrated: `_declared_scope` 5723→5734→5778; `phase-body-extraction` taskdoc 159/185→164/190 — anchor bookkeeping, not a book edit.) ### Cross-task evidence (one row per task) -- : gate= · tests= · residue= +- hygiene-bundle : gate=PASS · tests=8 green (incl. O(1) call-count spy N=2 vs N=4) · full fence 3619 pass then 1 SEAMS-anchor re-pin → green · residue=none +- wire-milestone-relations : gate=PASS · tests=6 green · full fence 3626 passed, 0 failed · residue=none ### 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: +- [x] each Exit criterion above is satisfied by a Cross-task evidence row or a Ship-by-domain change (cite which) + - EC1 (cmd_check O(1)) ← hygiene-bundle row + the cmd_check hoist under Ship-by-domain/tooling; proven by the N=2-vs-N=4 call-count spy test. + - EC2 (shared snapshot-hash / milestone-resolve helpers) ← hygiene-bundle row + the `_snapshot_hash`/`_resolve_milestone` line. + - EC3 (advisory milestone-relation surface) ← wire-milestone-relations row; `test_check_warns_but_does_not_fail` + `test_status_prints_advisory_oneliner` prove warn-not-red. +- goal: land the value-dense engine cleanup + finish the half-built feature under the existing fence — MET: both tasks gate PASS, the whole ~3600-test suite green (last: 3626 passed / 0 failed), zero behavior change to any existing gate. ## 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. -- [ ] -- [ ] -- [ ] +- [x] both tasks committed on `feat/engine-hygiene` (cfb8fe0 + 0dddefd) +- [ ] push `feat/engine-hygiene`, open a PR (2 commits), watch CI, human reviews + merges on green +- [ ] AFTER merge: run the human-authorized paid WM1 re-measure ("wait for engine-hygiene") — engine internals only, no user-facing feature, so likely folds into the next release cut rather than its own tag