@pilotspace/add 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,46 @@ All notable changes to the ADD method (`@pilotspace/add` on npm,
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.11.0] — 2026-06-26
10
+
11
+ Engine-modularization + audit-hardening release — the 7k-line monolith becomes a
12
+ navigable package, the verify/atomicity/coverage gates get five fixes, and the
13
+ standalone (milestone-free) lane becomes first-class. **No behavior change** to the
14
+ CLI, the flow, or any output: this is an internal architecture + hardening release.
15
+
16
+ ### Changed
17
+ - **The engine is now a focused package.** `add-method/tooling/add.py` (7049 → 5640
18
+ lines, −20%) split into **13 `add_engine/*.py` modules** — `constants`, `io_state`,
19
+ `accessors`, `predicates`, `identity`, `guidelines`, `render`, `milestones`,
20
+ `components`, `version`, `release`, `taskdoc`, `autonomy` — behind a stable
21
+ re-export surface (`import add; add.<name>` still resolves, public AND `_`-prefixed,
22
+ so every existing test is untouched). `add.py` stays the runnable **orchestrator
23
+ entry** (the `load_state`/`save_state`/`report_data`/`cmd_*`/`main` spine). Each of
24
+ the 16 extractions was its own CI-green PR, proven verbatim by an AST source-segment
25
+ diff and gated by the full suite (1815 → 1959 green throughout, no test weakened).
26
+ - **Two-pin integrity model.** Alongside `ENGINE_MD5` (md5 of `add.py`),
27
+ `ENGINE_PKG_MD5` is a manifest digest over every `add_engine/*.py`; both are literal
28
+ pins (never self-hashed) and are checked byte-identical across the 3-tree mirror
29
+ (canonical · `.add` · `_bundled`). `prepare_bundle` + both installers + the `.add`
30
+ mirror now ship the whole `add_engine/` package.
31
+
32
+ ### Fixed (audit-hardening)
33
+ - Five gate/atomicity/coverage fixes: a phase-build guard, hardened save-state
34
+ atomicity, force-preserve self-healing, setup-tests-before-build ordering, and a
35
+ consumer-stale gate. A security finding remains an un-forceable HARD-STOP.
36
+
37
+ ### Added (the standalone / lightweight lane)
38
+ - **`add.py todo`** — capture a milestone-free backlog item without scaffolding a task.
39
+ - **`loose tasks:` release attribution** — a release now attributes *any* done,
40
+ milestone-free task on its `RELEASES.md` row (not only milestone membership), with a
41
+ separate status cue for releasable loose work.
42
+ - **`fast` (task) + `auto` (mode) flag modes** — the standalone lane is first-class:
43
+ a minimal `TASK.fast.md`, freeze-gated under any milestone, with a flag-mode quick
44
+ reference.
45
+
46
+ This release bundles **2 closed milestones** (`audit-hardening`,
47
+ `engine-modularization`) and **27 loose tasks** since 1.10.0.
48
+
9
49
  ## [1.10.0] — 2026-06-25
10
50
 
11
51
  Component-aware major — ADD now models every codebase as a **graph of components**,
package/bin/cli.js CHANGED
@@ -562,6 +562,57 @@ function writeGeminiSettings(target) {
562
562
  }
563
563
  }
564
564
 
565
+ // Seed .add/SOUL.md from the bundled template if it does not yet exist. Mirror of
566
+ // _installer.py:_seed_soul_md (npm <-> pip parity): skip-if-exists (SOUL.md is
567
+ // user-owned — never clobber); fail-soft (warn + return, never abort install/update).
568
+ function seedSoulMd(target) {
569
+ const dest = path.join(target, ".add", "SOUL.md");
570
+ if (fs.existsSync(dest)) return; // skip-if-exists (never clobber)
571
+ const source = path.join(PKG_ROOT, "tooling", "templates", "SOUL.md.tmpl");
572
+ if (!fs.existsSync(source)) {
573
+ warn("soul_seed_skipped: SOUL.md.tmpl not found in bundled tooling/templates/");
574
+ return;
575
+ }
576
+ try {
577
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
578
+ fs.writeFileSync(dest, fs.readFileSync(source, "utf8"));
579
+ } catch (e) {
580
+ warn("soul_seed_skipped: could not write .add/SOUL.md — " + (e && e.message ? e.message : e));
581
+ }
582
+ }
583
+
584
+ // Ensure .add/.gitignore lists the engine's transient artifacts. Seed it from the bundled
585
+ // tooling/templates/gitignore.tmpl if absent; else APPEND-IF-ABSENT each pattern line the
586
+ // template carries that the file lacks — additive only, never reorders/removes user lines,
587
+ // idempotent; comment/blank lines are not appended to an existing file. Fail-soft. Twin of
588
+ // _installer.py:_seed_gitignore.
589
+ function seedGitignore(target) {
590
+ const source = path.join(PKG_ROOT, "tooling", "templates", "gitignore.tmpl");
591
+ if (!fs.existsSync(source)) {
592
+ warn("gitignore_seed_skipped: gitignore.tmpl not found in bundled tooling/templates/");
593
+ return;
594
+ }
595
+ const dest = path.join(target, ".add", ".gitignore");
596
+ try {
597
+ const body = fs.readFileSync(source, "utf8");
598
+ if (!fs.existsSync(dest)) {
599
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
600
+ fs.writeFileSync(dest, body); // seed-if-missing
601
+ return;
602
+ }
603
+ const current = fs.readFileSync(dest, "utf8");
604
+ const have = new Set(current.split("\n").map((l) => l.trim()));
605
+ const missing = body.split("\n").filter(
606
+ (l) => l.trim() && !l.trim().startsWith("#") && !have.has(l.trim())
607
+ );
608
+ if (missing.length === 0) return; // idempotent — nothing to add
609
+ const suffix = current === "" || current.endsWith("\n") ? "" : "\n";
610
+ fs.writeFileSync(dest, current + suffix + missing.join("\n") + "\n");
611
+ } catch (e) {
612
+ warn("gitignore_seed_skipped: could not update .add/.gitignore — " + (e && e.message ? e.message : e));
613
+ }
614
+ }
615
+
565
616
  // The drop — now a RECONCILE: restore missing managed trees + refresh present ones
566
617
  // (sweep orphans) + report per-tree status. Byte-compatible handoff with the prior
567
618
  // installer. The interactive path resolves a target then calls straight into this.
@@ -569,6 +620,8 @@ function dropFiles(args, target, profile, intent) {
569
620
  profile = profile || detectAgent(process.env);
570
621
  log("Installing ADD into " + target);
571
622
  reconcile(args, target);
623
+ seedSoulMd(target); // pip parity: re-seed a missing user-owned SOUL.md (never clobber)
624
+ seedGitignore(target); // pip parity: seed/append-if-absent the engine-transient ignore lines
572
625
 
573
626
  // Agent detection: write THE detected agent's integration file (a marker-delimited
574
627
  // pointer init's sync-guidelines later supersedes) + tailor the closing next-step.
@@ -932,6 +985,8 @@ function cmdUpdate(args) {
932
985
  fs.copyFileSync(stateFile, path.join(addDir, "pre-update-state.bak.json"));
933
986
  }
934
987
  reconcile(args, target);
988
+ seedSoulMd(target); // pip parity: re-seed a missing user-owned SOUL.md (never clobber)
989
+ seedGitignore(target); // pip parity: seed/append-if-absent the engine-transient ignore lines
935
990
  writeStamp(addDir, version);
936
991
  log("ADD updated " + (cur || "(unstamped)") + " -> " + version +
937
992
  " · managed layer reconciled · your project state untouched.");
@@ -51,7 +51,7 @@ Each delta is one tagged entry — `- [COMPETENCY · status] the learning (evide
51
51
 
52
52
  **The consolidation.** At milestone close (or on demand, when open deltas pile up), a person runs the retrospective consolidation: **gather** every `open` delta across the milestone's tasks, **group** them by competency, **propose** the exact foundation edit for each, **confirm** with the human one by one, then **write** — append-only, newest-first (the newest record prepended at the top) — flipping each delta to `folded` (merged) or `rejected` (considered and deliberately not merged, left in place so the trail survives), and bumping the `foundation-version:` marker. `DDD`/`SDD`/`UDD` deltas consolidate into the matching section of `PROJECT.md`; `TDD`/`ADD` consolidate into `CONVENTIONS.md` (they sharpen the engine, not the product); and **every** consolidation also prepends one row at the top of `PROJECT.md` §Key Decisions — the universal, auditable record of what the foundation learned.
53
53
 
54
- **Tooling.** `add.py deltas` lists every open delta across the project (so nothing waiting to be consolidated is invisible); `add.py check` lints each delta's well-formedness — known competency tag, valid status, non-empty evidence. There is deliberately **no `add.py fold`**: the engine stays judgment-free, and the ritual lives with the human who owns it.
54
+ **Tooling.** `add.py deltas` lists every open delta across the project (so nothing waiting to be consolidated is invisible); `add.py check` lints each delta's well-formedness — known competency tag, valid status, non-empty evidence. `add.py fold [--task <slug>] [--comp <TAG>]` mechanizes the consolidation flip + transcribe + route + bump, validate-all-then-write so a reject leaves the tree byte-unchanged; running it **is** the human's confirmation, and the engine never self-approves *which* lessons to keep.
55
55
 
56
56
  **Foundation compaction.** The consolidation *prepends* new learnings; **foundation compaction** is the separate, later step that *shrinks* the result. At milestone close (or on demand, once a foundation spec grows past one screen), the AI proposes collapsing the stable, shipped, zero-residue tail of each foundation spec into ONE per-spec **rolled-up settled line** at the bottom — and the human confirms, one line at a time, exactly as with the consolidation. It never deletes: a settled line summarizes the prose and keeps a `see git` pointer, so the record is lossy on wording but lossless on traceability, and any OPEN residue always stays live. Each spec collapses in its own **per-spec shape** — `PROJECT.md` §Spec version bullets, §Key-Decisions rows, `CONVENTIONS.md` learnings, the `GLOSSARY.md` definition, the `MODEL_REGISTRY.md` rows — under one shared eligibility rule. Because every append-only sequence is **newest-first** (newest record on top), compaction collapses *upward from the bottom*: the settled line anchors at the tail (the oldest end). Like the consolidation it is convention-guided — there is deliberately no `add.py` command for it (read `compact-foundation.md`) — and it is distinct from the engine's `add.py compact <slug>`, which archives a finished milestone's files rather than shrinking a living spec.
57
57
 
@@ -31,7 +31,7 @@ Every checkpoint produces three short reports — **Test** (does it pass?), **Qu
31
31
 
32
32
  - **`PASS`** — criteria met; proceed.
33
33
  - **`RISK-ACCEPTED`** — proceed with a signed waiver carrying a named owner, a linked ticket, and an expiry. Allowed for non-security gaps only.
34
- - **`HARD-STOP`** — cannot proceed. Triggered by any failing test or any security finding; overridable only by the most senior accountable owner, and never for security.
34
+ - **`HARD-STOP`** — cannot proceed. Triggered by any failing test or any security finding. A non-security limitation may proceed only with a signed `RISK-ACCEPTED` record carrying an owner and an expiry; security is never waved through.
35
35
 
36
36
  The rule behind the protocol is *no silent skips.* A report nobody is accountable for approving is just a document; an outcome with an owner is governance.
37
37
 
@@ -56,7 +56,7 @@ then render a real screen and **capture** it. That capture is the **design-confi
56
56
  evidence — a real image the person approves *before* implementation, so the build
57
57
  matches the layout instead of discovering it. The book keeps the *why*; the
58
58
  operational recipe (the wireframe format, the token-bound mock, the capture engines)
59
- lives in the `add` skill's `design.md` and `udd-wireframe.md`.
59
+ lives in the `add` skill's `design.md` and the `udd-wireframe.md` template (`tooling/templates/`).
60
60
 
61
61
  These three foundation competencies, together with the **TDD ⇄ ADD** engine of
62
62
  [Part II](./02-the-flow.md), are ADD's five. The first four feed context to the
@@ -11,8 +11,8 @@ them *ship*. This chapter names the act every project eventually performs and th
11
11
  now, never formalized: bundling closed milestones into a versioned, user-facing release whose notes
12
12
  are evidence-backed, whose risk is disclosed, and whose behaviour is then watched.
13
13
 
14
- Releasing is the **fifth scope level** — after the task, the milestone, the foundation/setup level,
15
- and stage graduation. Like every scope level it runs the same shape: **gather → propose → the human
14
+ Releasing is the **fifth scope level** — after setup, intake, the milestone loop, and stage graduation.
15
+ Like every scope level it runs the same shape: **gather → propose → the human
16
16
  confirms → the engine records and enforces a floor.** And like graduation, it ends with an outward
17
17
  act the human owns. The operational recipe lives in the `release.md` skill guide; this chapter is the
18
18
  *why* behind it.
@@ -84,7 +84,7 @@
84
84
 
85
85
  **Baseline approval** (formerly "the lock-down") — the single human gate ending autonomous setup: an explicit yes that freezes the foundation, first scope, and first contract together; runs as `add.py lock --by <name>`.
86
86
 
87
- **Scope level** (formerly "altitude") — the granularity a decision lives at: intake level (request → versioned scope) · milestone level · setup/foundation level · task level · release level (≥1 closed milestone → a versioned, watched cut; see **Release scope level**). (A cross-stage decision lives one level out, at the **stage-graduation** loop — which `graduate.md` also numbers as a scope level; see **Stage graduation**.) One ⚠-assumption notation is shared across every scope level.
87
+ **Scope level** (formerly "altitude") — the granularity a decision lives at, as one ordered ladder of five: **setup/foundation** · **intake** (request → versioned scope) · **the milestone loop** (the task is its inner unit) · **stage graduation** (changes rigor, not version; see **Stage graduation**) · **release** (≥1 closed milestone → a versioned, watched cut; see **Release scope level**). One ⚠-assumption notation is shared across every scope level.
88
88
 
89
89
  **Autonomy level** (formerly "autonomy dial") — the explicit per-task setting (`autonomy: manual | conservative | auto`, an ordered ladder manual < conservative < auto) choosing who resolves Verify: `auto` auto-PASSes on complete evidence, `conservative` keeps a human at the gate, `manual` is the strict floor (the human owns the gate; nothing auto-resolves). A high-risk scope refuses an unguarded `auto` — it must be lowered to `manual` or `conservative`. New tasks seed a visible, overridable `autonomy: auto`; a live task with no level warns (`implicit_autonomy`), a token outside the set is rejected (`unknown_autonomy_level`).
90
90
 
@@ -134,9 +134,9 @@
134
134
 
135
135
  **Newest-first append-only** — every append-only foundation sequence prepends the newest record at the top; the rolled-up settled line anchors at the bottom (the oldest end), so compaction collapses upward.
136
136
 
137
- **Wireframe** — the Stage-A low-fidelity, *structural* map of one screen: its regions and the component **slots** inside them, derived from `prototypes/<name>.json` *before* any color, type, or spacing — it answers "what goes where", not "what it looks like". Beat 3 of the UDD **design-definition loop**; the low-fi half of the two-stage fidelity that ends in a confirmed capture. See the `add` skill's `udd-wireframe.md` (Stage A).
137
+ **Wireframe** — the Stage-A low-fidelity, *structural* map of one screen: its regions and the component **slots** inside them, derived from `prototypes/<name>.json` *before* any color, type, or spacing — it answers "what goes where", not "what it looks like". Beat 3 of the UDD **design-definition loop**; the low-fi half of the two-stage fidelity that ends in a confirmed capture. See the `udd-wireframe.md` template (`tooling/templates/`, Stage A).
138
138
 
139
- **Design mock** — the Stage-B high-fidelity, **self-contained** HTML render of a screen: the `catalog.json` components as a reusable token-bound kit, bound to `tokens.json` and populated with mock data, openable offline and screenshot-able. The human-facing *visible* evidence the human confirms (the frozen `prototypes/<name>.json` tree is its machine-checkable twin). Beat 4's hi-fi artifact; the recipe lives in the `add` skill's `udd-wireframe.md` (Stage B).
139
+ **Design mock** — the Stage-B high-fidelity, **self-contained** HTML render of a screen: the `catalog.json` components as a reusable token-bound kit, bound to `tokens.json` and populated with mock data, openable offline and screenshot-able. The human-facing *visible* evidence the human confirms (the frozen `prototypes/<name>.json` tree is its machine-checkable twin). Beat 4's hi-fi artifact; the recipe lives in the `udd-wireframe.md` template (`tooling/templates/`, Stage B).
140
140
 
141
141
  **Capture** — the real rendered image (PNG/SVG) of a design mock: the **design-confirm evidence** artifact. Captures live at `.add/design/captures/<name>.<ext>` (one per prototype) and are attached or mentioned in the feature's `TASK.md`; `@json-render/image` (Satori → PNG/SVG, no browser) is the named default capture engine, otherwise the self-contained mock is screenshot headless. The engine never renders — it only MEASURES presence: `add.py check` raises a never-red `missing_capture` WARN for a prototype with no capture.
142
142
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pilotspace/add",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "ADD (AI-Driven Development). One skill. Eight steps. Five disciplines. Every feature ships through the loop — a minimal, state-tracked Claude Code skill that ships the AIDD book as its trust layer.",
5
5
  "bin": {
6
6
  "add": "bin/cli.js"
@@ -19,6 +19,9 @@
19
19
  "bin/",
20
20
  "skill/",
21
21
  "tooling/add.py",
22
+ "tooling/add_engine/",
23
+ "!tooling/add_engine/__pycache__",
24
+ "!**/*.pyc",
22
25
  "tooling/templates/",
23
26
  "docs/",
24
27
  "README.md",
@@ -62,7 +62,13 @@ self-improving via the `soul-self-improve` path). Then branch on state:
62
62
  - **No active task** → first SIZE the request (Intake below), then `add.py new-task <slug> --title "..."`.
63
63
 
64
64
  **Quick ref** — `status` resume · `init` bootstrap · `advance` continue · `gate PASS` at verify.
65
- **Opt-in flags** (human picks, never auto): `new-task --fast` = fast lane (minimal template, freeze-gated) · `new-milestone --await-confirm` = confirm-gate the milestone's tasks.
65
+ **Flag mode** — two human-owned settings (never auto-picked): **fast** (task) · **auto** (mode).
66
+ - **fast** — `new-task --fast`: minimal template, freeze-gated; a milestone-free `--fast` task is a
67
+ blessed standalone low-ceremony lane. Jot ideas first with `add.py todo "<text>"` (then `todo` to
68
+ list · `todo --done <id>`).
69
+ - **auto** — `autonomy: auto` (default) auto-gates verify on evidence; lower the autonomy level with
70
+ `add.py autonomy set conservative|manual` for a human gate · `new-milestone --await-confirm`
71
+ confirm-gates a milestone's tasks.
66
72
 
67
73
  ## Intake — size a request before creating scope
68
74
 
@@ -47,11 +47,17 @@ Never: invent a file, symbol, or signature you have not opened.
47
47
  ## Exit gate
48
48
 
49
49
  <exit_gate>
50
- - [ ] The real files/symbols the task touches are named (from the code, not assumed).
51
- - [ ] The conventions to honor are cited (task-delta only; no architecture re-scan).
52
- - [ ] The anchors §3 will cite are listed §3 names only anchors that exist here.
50
+ - [ ] **Touches** — the real files/symbols the task touches are named (from the code, not assumed).
51
+ - [ ] **Context** (working folder) the non-code artifacts the task touches (docs · todos · config · data) are named, task-delta only.
52
+ - [ ] **Honors** the patterns/conventions to honor are cited (task-delta only; no architecture re-scan).
53
+ - [ ] **Anchors** — the anchors §3 will cite are listed — §3 names only anchors that exist here.
53
54
  </exit_gate>
54
55
 
56
+ **Grounding is complete when** all four fields are filled from real assets: a STRONG grounding cites
57
+ actual files/symbols/docs/conventions you opened; a WEAK one leaves a `<…>` placeholder or names what you
58
+ assume. All four are non-optional — skipping **Context** (the working folder beyond code) is the usual
59
+ silent gap. §3 may cite only anchors that appear here.
60
+
55
61
  > **Advisor · Confidence** — a broad sweep is the canonical spawn case (advisor.md); self-score your grounding before you specify against it (confidence.md).
56
62
 
57
63
  ## Next
@@ -57,7 +57,7 @@ Capture each surfaced decision as an **ADR** into `PROJECT.md` **Key Decisions**
57
57
  ```bash
58
58
  python3 .add/tooling/add.py new-task <slug> --title "<first feature>"
59
59
  ```
60
- Draft §1 · §2 · §3. **Leave §3 `Status: DRAFT`** — the lock is its approval. You MAY `advance` pre-lock, but the engine **refuses crossing into build** until you `lock` (`setup_unlocked`). Sequence: bundle → lock → build.
60
+ Draft the full bundle **§1–§4** incl. the **§4 red suite** (`phases/4-tests.md`); the lock approves it whole. **Leave §3 `Status: DRAFT`** — the lock is its approval. You MAY `advance` pre-lock, but the engine **refuses crossing into build** until you `lock` (`setup_unlocked`). Sequence: **bundle (§1–§4, tests RED) → lock → build** — the red suite must FAIL **before build** (never start Build until §1–§4 exist and tests are red).
61
61
  4. **Write `.add/SETUP-REVIEW.md`** per `setup-review.md`: every drafted decision, **lowest-confidence-first**, tagged `guessed` | `evidence-grounded`.
62
62
 
63
63
  ## Run mode — how the build will be driven (propose parallel + auto; confirm to keep)
@@ -93,7 +93,7 @@ Typing it themselves stays the **escape hatch** — the decision is always the h
93
93
  <exit_gate>
94
94
  - [ ] `.add/state.json` exists; setup was seeded unlocked (`--await-lock`) then locked.
95
95
  - [ ] Living docs filled (brownfield: from code, tagged evidence-grounded; greenfield: from the interview).
96
- - [ ] First task created; §1–§3 drafted; `.add/SETUP-REVIEW.md` written lowest-confidence-first.
96
+ - [ ] First task created; **§1–§4 drafted — the red suite (per `phases/4-tests.md`) runs RED before build opens**; `.add/SETUP-REVIEW.md` written lowest-confidence-first.
97
97
  - [ ] Human confirmed the baseline approval and `add.py lock --by` ran with their name; first task §3 `FROZEN @ v1`; build open.
98
98
  </exit_gate>
99
99
 
@@ -19,7 +19,7 @@
19
19
 
20
20
  Before drafting the goal sentence, position the request in what already exists — distinct from intake's classification, not redundant with it.
21
21
 
22
- 1. **Ground in current assets.** Read the goal against `PROJECT.md` (domain · spec · UI/UX), the code it touches, and existing docs the goal must reflect what the project already is.
22
+ 1. **Ground in current assets.** Read the goal against what exists — the goal must reflect what the project already is. Ground as rigorously as a task's §0 (`phases/0-ground.md`), using the **same four fields** at milestone scope: **Touches** (the subsystems/files the milestone spans) · **Context** (the docs · todos · config · data it works against) · **Honors** (the `PROJECT.md` / `CONVENTIONS.md` invariants it must respect) · **Anchors** (the existing contracts/symbols its tasks will cite). Grounding is complete when each is named from real assets, not assumed.
23
23
  2. **Relate to the milestone map.** Read every existing goal — `.add/milestones/*/MILESTONE.md` and `.add/archive/*` — and name THIS request's relationship: *extends* X · *depends-on* Y · *overlaps* Z. Record in the `rationale` line.
24
24
  3. **If the goal is already delivered** by an existing milestone, reject `duplicate_goal` and route as `task` or `change-request`.
25
25