@pilotspace/add 1.8.0 → 1.10.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 +109 -0
- package/bin/cli.js +137 -2
- package/docs/16-releasing.md +2 -2
- package/docs/17-components.md +125 -0
- package/docs/appendix-c-glossary.md +8 -0
- package/package.json +2 -2
- package/skill/add/SKILL.md +90 -116
- package/skill/add/adopt.md +13 -34
- package/skill/add/advisor.md +9 -10
- package/skill/add/compact-foundation.md +21 -34
- package/skill/add/components.md +54 -0
- package/skill/add/confidence.md +9 -25
- package/skill/add/deltas.md +12 -28
- package/skill/add/design.md +26 -32
- package/skill/add/fold.md +26 -51
- package/skill/add/graduate.md +18 -54
- package/skill/add/intake.md +30 -34
- package/skill/add/loop.md +30 -34
- package/skill/add/phases/0-ground.md +6 -14
- package/skill/add/phases/0-setup.md +30 -81
- package/skill/add/phases/1-specify.md +7 -22
- package/skill/add/phases/2-scenarios.md +1 -3
- package/skill/add/phases/3-contract.md +8 -22
- package/skill/add/phases/4-tests.md +3 -6
- package/skill/add/phases/5-build.md +8 -22
- package/skill/add/phases/6-verify.md +14 -49
- package/skill/add/phases/7-observe.md +1 -3
- package/skill/add/phases/fast-lane.md +31 -0
- package/skill/add/release.md +36 -90
- package/skill/add/report-template.md +41 -126
- package/skill/add/run.md +89 -159
- package/skill/add/scope.md +40 -89
- package/skill/add/setup-review.md +10 -27
- package/skill/add/soul.md +17 -36
- package/skill/add/streams.md +90 -149
- package/tooling/add.py +804 -14
- package/tooling/templates/TASK.fast.md.tmpl +72 -0
- package/tooling/templates/TASK.md.tmpl +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,115 @@ All notable changes to the ADD method (`@pilotspace/add` on npm,
|
|
|
4
4
|
`pilotspace-add` on PyPI) are documented here. The format follows
|
|
5
5
|
[Keep a Changelog](https://keepachangelog.com/); versions follow semver.
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.10.0] — 2026-06-25
|
|
10
|
+
|
|
11
|
+
Component-aware major — ADD now models every codebase as a **graph of components**,
|
|
12
|
+
each owning a source root, its own green bar, and the contracts it produces or
|
|
13
|
+
consumes, so **one milestone can ship a vertical slice across components** in a
|
|
14
|
+
monorepo or across repos. Bundles the closed `component-aware-add` major plus the
|
|
15
|
+
`docs-site` (the book as a live MkDocs site) and `loop-steering` milestones, and the
|
|
16
|
+
ccsk `--rule-file` mode. Every new gate is **opt-in or grandfathered** — a project
|
|
17
|
+
that declares no components is byte-identical to 1.9.0.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **Component registry (`component-aware-add`)** — declare parts of a multi-part
|
|
21
|
+
codebase in `.add/components.toml` under `[component.<name>]` (a `root` + a
|
|
22
|
+
`green-bar`). A task binds to one with a `component:` header line, which anchors its
|
|
23
|
+
§5 Scope to that component's root. Declared, never inferred; zero components ⇒
|
|
24
|
+
byte-identical to today.
|
|
25
|
+
- **Per-component verify** — a bound task is held to ITS component's green-bar at the
|
|
26
|
+
verify gate (the cite-gate refuses `component_green_bar_uncited`), so two tasks in
|
|
27
|
+
one milestone can pass on two different toolchains (e.g. `pytest` and `vitest`). The
|
|
28
|
+
engine still never runs a suite — the AI runs it, the gate checks the right bar was
|
|
29
|
+
cited.
|
|
30
|
+
- **Cross-component contracts (`produces:` / `consumes:`)** — declare `[contract.<id>]`
|
|
31
|
+
(producer + consumers). A producer's §3 freeze writes an immutable snapshot at
|
|
32
|
+
`.add/contracts/<id>.json`; a consumer pins its hash; a changed re-freeze flags every
|
|
33
|
+
consumer stale. A missing/malformed snapshot HARD-STOPS — never build against a
|
|
34
|
+
guessed shape.
|
|
35
|
+
- **One milestone, full-stack slice** — a `consumes:` task is HELD from entering §3
|
|
36
|
+
(`producer_contract_unfrozen`) until its producer's contract is frozen, so a BE→FE
|
|
37
|
+
vertical slice ships in one milestone, ordered by the frozen contract.
|
|
38
|
+
- **Multi-repo federation (`add.py federate pull <id>`)** — a consumer repo declares
|
|
39
|
+
`[federation.<id>]` (a `source` path + optional `pin`) and pulls a byte-for-byte copy
|
|
40
|
+
of a producer repo's published snapshot into its local `.add/contracts/`, where the
|
|
41
|
+
rest of ADD treats mono- and multi-repo identically. Fail-loud: unknown id /
|
|
42
|
+
unreadable source / invalid snapshot / version mismatch each HARD-STOPS and lands
|
|
43
|
+
nothing.
|
|
44
|
+
- **Component pillar docs** — a new book chapter `17 · Components`, a skill guide
|
|
45
|
+
(`components.md`), and glossary terms (Component · Cross-component contract ·
|
|
46
|
+
Federation) teach the whole loop.
|
|
47
|
+
- **The book as a live site (`docs-site`)** — the AIDD book ships to GitHub Pages as a
|
|
48
|
+
MkDocs-Material site (`mkdocs.yml` + `requirements-docs.txt`, build-time only — the
|
|
49
|
+
published packages stay zero-dependency); `mkdocs build --strict` fails the deploy on
|
|
50
|
+
a broken intra-book link.
|
|
51
|
+
- **Rule-file mode for ccsk projects (`--rule-file`)** — when a project keeps its
|
|
52
|
+
workflow rules under `.claude/rules/` (the ccsk convention, detected by a `.ccsk/`
|
|
53
|
+
dir), ADD relocates CLAUDE.md's block to `.claude/rules/add-workflows.md` and leaves a
|
|
54
|
+
single reference bullet, instead of inlining. Triggers three ways (the explicit
|
|
55
|
+
`--rule-file` flag, a `.ccsk/` directory, or a rule file already present),
|
|
56
|
+
**CLAUDE-only**, migrates a prior inline block out (`.bak` on change), idempotent and
|
|
57
|
+
fail-soft. Mirrored across all three installers (engine `add.py`, pip `_installer.py`,
|
|
58
|
+
npm `cli.js`).
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
- **Guided dynamic loop (`loop-steering`)** — `status` and `guide` now STEER into the
|
|
62
|
+
build→verify loop at the loop juncture rather than only reporting it, so an agent is
|
|
63
|
+
pointed at the next loop step instead of having to infer it.
|
|
64
|
+
|
|
65
|
+
### Compatibility
|
|
66
|
+
- Python 3.11+ is required to USE the component pillar (it parses `components.toml` with
|
|
67
|
+
the stdlib `tomllib`); on 3.10 the engine runs unchanged but a declared
|
|
68
|
+
`components.toml` fails loud (`components_malformed`). The published packages remain
|
|
69
|
+
zero-dependency.
|
|
70
|
+
|
|
71
|
+
## [1.9.0] — 2026-06-24
|
|
72
|
+
|
|
73
|
+
Lean-pass major — make ADD's own surface (skill · flow · engine) the most-effective
|
|
74
|
+
prompt at optimized token cost, *not* token-golf. Bundles four closed milestones:
|
|
75
|
+
`skill-effectiveness` (M1), `flow-simplification` (M3), `flow-enforcement` (M4), and
|
|
76
|
+
the `fast-lane` sub-milestone. The skill tree routes identically but loads lighter;
|
|
77
|
+
the method's three fill-seams are now engine-enforced rather than convention; and a
|
|
78
|
+
new opt-in **fast lane** collapses ceremony for small tasks without lowering the trust
|
|
79
|
+
floor. Every new gate is opt-in or grandfathered — existing flows are byte-unchanged.
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
- **Fast lane (`fast-lane`)** — `add.py new-task <slug> --fast` runs a small task
|
|
83
|
+
through a minimal `TASK.fast.md` (sections {0,1,3,4,5,6}) that still freezes a
|
|
84
|
+
contract, proves a red→green, and reads back cold in a later session. The lane is
|
|
85
|
+
*collapse-never-skip*: a `--fast` task is freeze-gated under ANY milestone (the floor
|
|
86
|
+
fires on `_optin OR fast`). Human-triggered only — the engine never auto-classifies a
|
|
87
|
+
task as "small". New `phases/fast-lane.md` guide + a SKILL.md quick-ref document it.
|
|
88
|
+
- **Freeze-before-build gate (`fast-lane`)** — crossing tests→build now refuses
|
|
89
|
+
`contract_not_frozen` when §3 is still DRAFT and the task is opted-in or fast, closing
|
|
90
|
+
the gap where a task could reach `gate=PASS` without an approved, frozen contract.
|
|
91
|
+
- **Contract-fill + build-expectations gates (opt-in, `flow-enforcement`)** — under
|
|
92
|
+
`new-milestone --await-confirm`, the engine HOLDS a task whose §3 CONTRACT or §6
|
|
93
|
+
Build-expectations block is still a placeholder, so the method's fill-seams are
|
|
94
|
+
enforced rather than trusted. `await_confirm` is the master switch; without it no key
|
|
95
|
+
is written → grandfathered → byte-unchanged.
|
|
96
|
+
- **Gate-record write-back (`flow-enforcement`)** — recording a verify gate now stamps
|
|
97
|
+
the verdict into the §6 GATE RECORD of the TASK.md for ALL tasks (grandfathered
|
|
98
|
+
backfill), so the file itself carries the outcome the Seam audit reads.
|
|
99
|
+
- **Confirm-parent gate (opt-in, `flow-simplification`)** — `new-milestone <slug>
|
|
100
|
+
--await-confirm` seeds the milestone *unconfirmed*, so `new-task` is HELD (reject
|
|
101
|
+
`milestone_unconfirmed`) until you show the filled `MILESTONE.md`, get the human's go,
|
|
102
|
+
and run the new `add.py milestone-confirm <slug>`. Closes the gap where the AI would
|
|
103
|
+
detail a task's §0–§5 before the human had agreed the parent milestone. Mirrors
|
|
104
|
+
`init --await-lock` one level down.
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
- **Skill tree 25% lighter (`skill-effectiveness`)** — the on-demand skill guides were
|
|
108
|
+
compacted tree-wide (164,333 → ~123,000 bytes) with zero routing, gate, reject-code,
|
|
109
|
+
threshold, or rule lost; an independent adversarial review confirmed every operative
|
|
110
|
+
element preserved (it flagged 6 dropped nuances → all restored). Same flow an agent
|
|
111
|
+
reads, fewer tokens to load.
|
|
112
|
+
- **One home for the worker-spawn model tiers (`spawn-fold`)** — the tier→model mapping
|
|
113
|
+
(mid→sonnet / top→opus) now lives only in `streams.md`; `advisor.md` points at it
|
|
114
|
+
instead of copying. Advisor keeps its own advisory template.
|
|
115
|
+
|
|
7
116
|
## [1.8.0] — 2026-06-23
|
|
8
117
|
|
|
9
118
|
Team collaboration: ADD becomes git-native and multi-user, with N
|
package/bin/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ function parseArgs(argv) {
|
|
|
39
39
|
// defaults the stage and infers the name from the folder, so the manual-init
|
|
40
40
|
// hint only echoes flags the user actually chose (shortest true command).
|
|
41
41
|
const args = { _: [], force: false, check: false, noSkill: false, stage: null, name: null,
|
|
42
|
-
yes: false, nonInteractive: false, global: false, globalData: false };
|
|
42
|
+
yes: false, nonInteractive: false, global: false, globalData: false, ruleFile: false };
|
|
43
43
|
for (let i = 0; i < argv.length; i++) {
|
|
44
44
|
const a = argv[i];
|
|
45
45
|
if (a === "--force") args.force = true;
|
|
@@ -58,6 +58,9 @@ function parseArgs(argv) {
|
|
|
58
58
|
// already provides the `add` skill, so a plugin bootstrap uses this to materialize
|
|
59
59
|
// .add/tooling/ + .add/docs/ into the project without a duplicate .claude/skills/add.
|
|
60
60
|
else if (a === "--no-skill") args.noSkill = true;
|
|
61
|
+
// --rule-file: write the ADD block to .claude/rules/add-workflows.md + reference it from
|
|
62
|
+
// CLAUDE.md instead of inlining (auto-on for ccsk projects with a .ccsk/ dir).
|
|
63
|
+
else if (a === "--rule-file") args.ruleFile = true;
|
|
61
64
|
else if (a === "--stage" || a === "--name") {
|
|
62
65
|
const v = argv[++i];
|
|
63
66
|
// fail loudly on a trailing/abutting flag — never silently drop a value
|
|
@@ -249,6 +252,126 @@ function writeAgentPointer(target, profile) {
|
|
|
249
252
|
}
|
|
250
253
|
}
|
|
251
254
|
|
|
255
|
+
// --- rule-file mode: ccsk-style relocation of the CLAUDE.md block ---------------------
|
|
256
|
+
// Mirror of add.py / _installer.py rule-file logic (twins by duplication). For ccsk projects
|
|
257
|
+
// (.ccsk/ dir) or an explicit --rule-file, the ADD pointer goes to .claude/rules/add-workflows.md
|
|
258
|
+
// and CLAUDE.md keeps only a reference bullet. CLAUDE-only — AGENTS.md/.clinerules stay inline.
|
|
259
|
+
const RULES_FILE_REL = path.join(".claude", "rules", "add-workflows.md");
|
|
260
|
+
const WORKFLOW_HEADINGS = ["Rules & Workflows", "Workflows", "Rules"];
|
|
261
|
+
const RULE_REF_LINE = "- ADD (AI-Driven Development) Workflows rules: ./.claude/rules/add-workflows.md";
|
|
262
|
+
|
|
263
|
+
// True when the ADD block belongs in .claude/rules/add-workflows.md instead of inline.
|
|
264
|
+
// Re-derived from disk: explicit flag, a ccsk project (.ccsk/), or a rule file already present.
|
|
265
|
+
function ruleFileMode(root, flag) {
|
|
266
|
+
if (flag) return true;
|
|
267
|
+
try {
|
|
268
|
+
if (fs.existsSync(path.join(root, ".ccsk")) &&
|
|
269
|
+
fs.statSync(path.join(root, ".ccsk")).isDirectory()) return true;
|
|
270
|
+
if (fs.existsSync(path.join(root, RULES_FILE_REL))) return true;
|
|
271
|
+
} catch (_e) { /* fail-soft */ }
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Remove an inline ADD:BEGIN..END region (migration), collapsing the gap. An unterminated
|
|
276
|
+
// BEGIN is left as-is rather than eating the rest of the file.
|
|
277
|
+
function stripInlineBlock(text) {
|
|
278
|
+
const begin = text.indexOf(GUIDE_BEGIN);
|
|
279
|
+
if (begin === -1) return text;
|
|
280
|
+
let end = text.indexOf(GUIDE_END, begin);
|
|
281
|
+
if (end === -1) return text;
|
|
282
|
+
end += GUIDE_END.length;
|
|
283
|
+
const head = text.slice(0, begin).replace(/\n+$/, "");
|
|
284
|
+
const tail = text.slice(end).replace(/^\n+/, "");
|
|
285
|
+
if (head && tail) return head + "\n\n" + tail;
|
|
286
|
+
return head || tail;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Insert the ADD rule-file bullet under an existing Workflows/Rules heading, or append a fresh
|
|
290
|
+
// '## Workflows' section. Caller guarantees the bullet is absent.
|
|
291
|
+
function insertRuleReference(text) {
|
|
292
|
+
const lines = text.split("\n");
|
|
293
|
+
let headingIdx = -1;
|
|
294
|
+
for (let i = 0; i < lines.length; i++) {
|
|
295
|
+
const m = lines[i].match(/^(#{1,6})\s+(.*?)\s*$/);
|
|
296
|
+
if (m && WORKFLOW_HEADINGS.some((h) => m[2].trim().toLowerCase() === h.toLowerCase())) {
|
|
297
|
+
headingIdx = i;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (headingIdx === -1) {
|
|
302
|
+
const body = text.replace(/\n+$/, "");
|
|
303
|
+
const sep = body ? "\n\n" : "";
|
|
304
|
+
return body + sep + "## Workflows\n\n" + RULE_REF_LINE + "\n";
|
|
305
|
+
}
|
|
306
|
+
const level = lines[headingIdx].match(/^(#{1,6})/)[1].length;
|
|
307
|
+
let end = lines.length;
|
|
308
|
+
for (let j = headingIdx + 1; j < lines.length; j++) {
|
|
309
|
+
const m = lines[j].match(/^(#{1,6})\s+/);
|
|
310
|
+
if (m && m[1].length <= level) { end = j; break; }
|
|
311
|
+
}
|
|
312
|
+
let insertAt = headingIdx + 1;
|
|
313
|
+
for (let j = headingIdx + 1; j < end; j++) {
|
|
314
|
+
if (lines[j].trim()) insertAt = j + 1;
|
|
315
|
+
}
|
|
316
|
+
lines.splice(insertAt, 0, RULE_REF_LINE);
|
|
317
|
+
return lines.join("\n");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Make CLAUDE.md reference the ADD rule file under a Workflows/Rules heading, migrating any prior
|
|
321
|
+
// inline ADD block out. created|updated|unchanged|skipped; .bak on change; fail-soft.
|
|
322
|
+
function ensureClaudeReference(claudeMd) {
|
|
323
|
+
try {
|
|
324
|
+
const existed = fs.existsSync(claudeMd);
|
|
325
|
+
const current = existed ? fs.readFileSync(claudeMd, "utf8") : "";
|
|
326
|
+
let next = stripInlineBlock(current);
|
|
327
|
+
if (next.indexOf("add-workflows.md") === -1) next = insertRuleReference(next);
|
|
328
|
+
if (!next.endsWith("\n")) next += "\n";
|
|
329
|
+
if (next === current) return "unchanged";
|
|
330
|
+
if (existed) fs.writeFileSync(claudeMd + ".bak", current);
|
|
331
|
+
fs.writeFileSync(claudeMd, next);
|
|
332
|
+
return existed ? "updated" : "created";
|
|
333
|
+
} catch (e) {
|
|
334
|
+
warn("could not write CLAUDE.md — " + (e && e.message ? e.message : e) + "; skipped");
|
|
335
|
+
return "skipped";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Rule-file variant of writeAgentPointer for the Claude profile: write the ADD pointer block to
|
|
340
|
+
// .claude/rules/add-workflows.md and leave a reference in CLAUDE.md. Fail-soft.
|
|
341
|
+
function writeRuleFilePointer(target, profile) {
|
|
342
|
+
const rulesPath = path.join(target, RULES_FILE_REL);
|
|
343
|
+
const block = agentPointerBlock(profile);
|
|
344
|
+
try {
|
|
345
|
+
if (fs.existsSync(rulesPath)) {
|
|
346
|
+
const current = fs.readFileSync(rulesPath, "utf8");
|
|
347
|
+
const begin = current.indexOf(GUIDE_BEGIN);
|
|
348
|
+
let next;
|
|
349
|
+
if (begin !== -1) {
|
|
350
|
+
const endIdx = current.indexOf(GUIDE_END, begin);
|
|
351
|
+
if (endIdx !== -1) {
|
|
352
|
+
next = current.slice(0, begin) + block + current.slice(endIdx + GUIDE_END.length);
|
|
353
|
+
} else {
|
|
354
|
+
next = current.replace(/\n+$/, "") + "\n\n" + block + "\n";
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
next = current.replace(/\n+$/, "") + "\n\n" + block + "\n";
|
|
358
|
+
}
|
|
359
|
+
if (next !== current) {
|
|
360
|
+
fs.writeFileSync(rulesPath + ".bak", current);
|
|
361
|
+
fs.writeFileSync(rulesPath, next);
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
fs.mkdirSync(path.dirname(rulesPath), { recursive: true });
|
|
365
|
+
fs.writeFileSync(rulesPath, block + "\n");
|
|
366
|
+
}
|
|
367
|
+
} catch (e) {
|
|
368
|
+
warn("could not write " + RULES_FILE_REL + " — " + (e && e.message ? e.message : e) + "; skipped");
|
|
369
|
+
return "skipped";
|
|
370
|
+
}
|
|
371
|
+
ensureClaudeReference(path.join(target, "CLAUDE.md"));
|
|
372
|
+
return "ok";
|
|
373
|
+
}
|
|
374
|
+
|
|
252
375
|
// --- interactive layer (clack on a real TTY; plain text everywhere else) -----
|
|
253
376
|
// Designed-for-failure: any doubt (non-TTY, CI, --yes, a failed import, an
|
|
254
377
|
// un-promptable stream) degrades to the EXACT plain-text path below. The clack
|
|
@@ -450,7 +573,16 @@ function dropFiles(args, target, profile, intent) {
|
|
|
450
573
|
// Agent detection: write THE detected agent's integration file (a marker-delimited
|
|
451
574
|
// pointer init's sync-guidelines later supersedes) + tailor the closing next-step.
|
|
452
575
|
// Best-effort + fail-soft — never aborts the successful drop above.
|
|
453
|
-
|
|
576
|
+
// Rule-file mode (ccsk projects / --rule-file) relocates the CLAUDE.md pointer to
|
|
577
|
+
// .claude/rules/add-workflows.md + a reference — CLAUDE-only; other agents stay inline.
|
|
578
|
+
if (profile.integration_file === "CLAUDE.md" && ruleFileMode(target, args.ruleFile)) {
|
|
579
|
+
if (fs.existsSync(path.join(target, ".ccsk"))) {
|
|
580
|
+
log(" ccsk detected — ADD rules go to .claude/rules/add-workflows.md");
|
|
581
|
+
}
|
|
582
|
+
writeRuleFilePointer(target, profile);
|
|
583
|
+
} else {
|
|
584
|
+
writeAgentPointer(target, profile);
|
|
585
|
+
}
|
|
454
586
|
|
|
455
587
|
// Gemini CLI auto-loads GEMINI.md, not AGENTS.md — so for the gemini profile we ALSO merge
|
|
456
588
|
// .gemini/settings.json (context.fileName) to load the AGENTS.md pointer. Fail-soft + idempotent.
|
|
@@ -847,4 +979,7 @@ module.exports = {
|
|
|
847
979
|
scopeOptions: scopeOptions,
|
|
848
980
|
writeIntentNote: writeIntentNote,
|
|
849
981
|
writeGeminiSettings: writeGeminiSettings,
|
|
982
|
+
ruleFileMode: ruleFileMode,
|
|
983
|
+
ensureClaudeReference: ensureClaudeReference,
|
|
984
|
+
writeRuleFilePointer: writeRuleFilePointer,
|
|
850
985
|
};
|
package/docs/16-releasing.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 16 · Releasing
|
|
2
2
|
|
|
3
|
-
[← 15 Foundations & Lineage](./15-foundations-and-lineage.md) · [Contents](./README.md) · Next: [
|
|
3
|
+
[← 15 Foundations & Lineage](./15-foundations-and-lineage.md) · [Contents](./README.md) · Next: [17 Components →](./17-components.md)
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -179,4 +179,4 @@ the risk, record the marker, and let a person make the outward call.
|
|
|
179
179
|
|
|
180
180
|
---
|
|
181
181
|
|
|
182
|
-
[← 15 Foundations & Lineage](./15-foundations-and-lineage.md) · [Contents](./README.md) · Next: [
|
|
182
|
+
[← 15 Foundations & Lineage](./15-foundations-and-lineage.md) · [Contents](./README.md) · Next: [17 Components →](./17-components.md)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# 17 · Components — monorepo and multi-repo
|
|
2
|
+
|
|
3
|
+
[← 16 Releasing](./16-releasing.md) · [Contents](./README.md) · Next: [Appendix A Templates →](./appendix-a-templates.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Most of this book treats a project as one codebase with one green bar. Real
|
|
8
|
+
systems are rarely that tidy: a backend and a frontend, a shared library and two
|
|
9
|
+
apps, or three services across three repos. ADD models all of these the same
|
|
10
|
+
way — as a **graph of components**. A component owns a source root, its own
|
|
11
|
+
green bar, and the contracts it produces or consumes. With that, **one milestone
|
|
12
|
+
can ship a vertical slice across components** — a backend endpoint and the
|
|
13
|
+
frontend that calls it — instead of splitting the slice across milestones.
|
|
14
|
+
|
|
15
|
+
This pillar is **opt-in and additive**: a project that declares no components
|
|
16
|
+
behaves exactly as the rest of the book describes. You reach for it only when a
|
|
17
|
+
milestone genuinely spans more than one green bar.
|
|
18
|
+
|
|
19
|
+
## Declare the components
|
|
20
|
+
|
|
21
|
+
Components are **declared, never inferred** — ADD does not scan `apps/*` and
|
|
22
|
+
guess. You name them in `.add/components.toml`:
|
|
23
|
+
|
|
24
|
+
```toml
|
|
25
|
+
[component.gateway]
|
|
26
|
+
root = "apps/gateway"
|
|
27
|
+
green-bar = "pytest + pyright"
|
|
28
|
+
|
|
29
|
+
[component.dashboard]
|
|
30
|
+
root = "apps/web"
|
|
31
|
+
green-bar = "vitest + a11y"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Each component owns a **root** (the source subtree it governs) and a
|
|
35
|
+
**green-bar** (the suite + checks that prove *that* component healthy). A task
|
|
36
|
+
binds to a component with a `component:` header line; the component's root is
|
|
37
|
+
then added to the task's §5 Scope automatically. A project with no
|
|
38
|
+
`components.toml`, or a task with no `component:` line, is byte-identical to a
|
|
39
|
+
single-component project.
|
|
40
|
+
|
|
41
|
+
## Verify each task against its own green bar
|
|
42
|
+
|
|
43
|
+
In a mixed milestone, a backend task and a frontend task pass on **different
|
|
44
|
+
toolchains**. The verify gate enforces this per component: a task bound to
|
|
45
|
+
`gateway` must cite its component's green-bar (`pytest + pyright`) in the §6
|
|
46
|
+
Build-expectations evidence, or the gate refuses `component_green_bar_uncited`.
|
|
47
|
+
The engine never *runs* the suite — that invariant holds here too. The AI runs
|
|
48
|
+
the right suite for the bound component; the gate checks the **right bar was
|
|
49
|
+
cited** in the evidence. Two tasks, one milestone, two green bars — each held to
|
|
50
|
+
its own.
|
|
51
|
+
|
|
52
|
+
## Freeze a contract between components
|
|
53
|
+
|
|
54
|
+
When one component produces an interface another consumes, that boundary needs a
|
|
55
|
+
**frozen, machine-checkable contract**. Declare it, name its producer and
|
|
56
|
+
consumers:
|
|
57
|
+
|
|
58
|
+
```toml
|
|
59
|
+
[contract.gateway-api]
|
|
60
|
+
producer = "gateway"
|
|
61
|
+
consumers = ["dashboard"]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
A task declares its role with a header line — `produces: gateway-api` or
|
|
65
|
+
`consumes: gateway-api`. When the **producer** task freezes its §3 and crosses
|
|
66
|
+
contract→tests, the engine writes an immutable snapshot at
|
|
67
|
+
`.add/contracts/gateway-api.json` (id, producer, version, frozen date, and a
|
|
68
|
+
hash over the frozen §3 shape). When a **consumer** task crosses contract→tests,
|
|
69
|
+
it **pins** that live hash. If the producer later re-freezes a *changed* shape,
|
|
70
|
+
`add.py check` flags every consumer `contract_consumer_stale` — a §7 delta to
|
|
71
|
+
re-pin against the new shape. A missing or malformed snapshot is a HARD-STOP, not
|
|
72
|
+
a guess: the consumer never builds against a shape that was never frozen.
|
|
73
|
+
|
|
74
|
+
## One milestone, a full-stack slice
|
|
75
|
+
|
|
76
|
+
The reason to put a producer and a consumer in the *same* milestone is to ship a
|
|
77
|
+
vertical slice — but the frontend must not commit to an endpoint the backend has
|
|
78
|
+
not frozen yet. ADD enforces that ordering with a **hold**: a `consumes:` task
|
|
79
|
+
cannot advance scenarios→contract (it cannot write its §3) while its producer's
|
|
80
|
+
snapshot does not yet exist. The engine refuses `producer_contract_unfrozen` and
|
|
81
|
+
the task stays at `scenarios`. Once the backend freezes its contract, the
|
|
82
|
+
frontend proceeds and pins it. The slice is **ordered by the frozen contract**,
|
|
83
|
+
all inside one milestone — the FE stays downstream of the BE endpoint, not split
|
|
84
|
+
into a later milestone.
|
|
85
|
+
|
|
86
|
+
## Across repositories: federation
|
|
87
|
+
|
|
88
|
+
Components in separate repositories work the same way; only the
|
|
89
|
+
**snapshot transport** differs. A consumer repo declares where a producer repo
|
|
90
|
+
publishes its frozen contract:
|
|
91
|
+
|
|
92
|
+
```toml
|
|
93
|
+
[federation.gateway-api]
|
|
94
|
+
source = "../gateway/.add/contracts/gateway-api.json"
|
|
95
|
+
pin = "v1" # optional — the version this repo expects
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`add.py federate pull gateway-api` reads that source, validates it (valid JSON,
|
|
99
|
+
matching id, a hash, and — if `pin` is set — a matching version), and lands a
|
|
100
|
+
**byte-for-byte copy** at the local `.add/contracts/gateway-api.json`. From
|
|
101
|
+
there, the consuming repo's task holds and pins exactly as in a monorepo. The
|
|
102
|
+
pull is **fail-loud by design**: an unknown id, an unreadable source, an invalid
|
|
103
|
+
snapshot, or a version mismatch each HARD-STOPS and lands nothing — federation
|
|
104
|
+
never builds an FE against a guessed or stale endpoint. The producer's snapshot
|
|
105
|
+
is the published artifact; "publishing" is committing that file in the producer
|
|
106
|
+
repo. Each repo keeps its own git-native `state.json`; federation transports only
|
|
107
|
+
the immutable frozen shape, never shared mutable state.
|
|
108
|
+
|
|
109
|
+
## What this pillar is not
|
|
110
|
+
|
|
111
|
+
- **Not auto-discovery.** Components are declared in `components.toml`, not
|
|
112
|
+
inferred from the directory tree.
|
|
113
|
+
- **Not a central server.** Federation copies an immutable snapshot between
|
|
114
|
+
repos; there is no shared service and no shared mutable state.
|
|
115
|
+
- **Not a new approval.** The component machinery rides the existing six-step
|
|
116
|
+
flow and its single contract-freeze approval — it adds gates the engine
|
|
117
|
+
enforces, not human checkpoints.
|
|
118
|
+
|
|
119
|
+
The whole pillar is structure, not policy: who *owns* a component and how
|
|
120
|
+
autonomy is set per component is the identity/governance story (chapters 11–12),
|
|
121
|
+
layered on top of this graph.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
[← 16 Releasing](./16-releasing.md) · [Contents](./README.md) · Next: [Appendix A Templates →](./appendix-a-templates.md)
|
|
@@ -16,12 +16,20 @@
|
|
|
16
16
|
|
|
17
17
|
**Co-specification** — how a spec is made in ADD: the AI and the human **brainstorm the shape together** (diverge), the AI **drafts** it, and the human **validates with the AI's advice** (validate). The AI's decisive advice is the *lowest-confidence flag*. It replaces dictation-by-one-side — the human owns the decision, the AI owns surfacing what it does not yet know. See [03 Specify](./03-step-1-specify.md).
|
|
18
18
|
|
|
19
|
+
**Component** — a declared part of a multi-part codebase that owns a source `root` and its own `green-bar` (suite + checks), named in `.add/components.toml` under `[component.<name>]`. A task binds to one with a `component:` header line, which adds that root to its §5 Scope and holds it to that component's green bar at verify. Declared, never inferred; a project with no components is byte-identical to a single-codebase project. See [17 Components](./17-components.md).
|
|
20
|
+
|
|
21
|
+
**Cross-component contract** — the frozen, machine-checkable interface between a producer component and its consumers, declared under `[contract.<id>]` (producer + consumers). A task names its role with a `produces: <id>` or `consumes: <id>` header. On the producer's freeze the engine writes an immutable snapshot at `.add/contracts/<id>.json`; a consumer pins its hash and is flagged `contract_consumer_stale` if the producer later re-freezes a changed shape. Inside one milestone a `consumes:` task is HELD from writing its §3 until the producer's snapshot exists — the intra-milestone BE→FE ordering. See [17 Components](./17-components.md).
|
|
22
|
+
|
|
23
|
+
**Federation (multi-repo)** — the transport that carries a frozen cross-component contract between separate repositories. A consumer repo declares `[federation.<id>]` with a `source` (and optional `pin`); `add.py federate pull <id>` validates the producer repo's published snapshot and lands a byte-for-byte copy locally, where it behaves exactly as in a monorepo. Fail-loud: an unknown id, unreadable source, invalid snapshot, or version mismatch HARD-STOPS and lands nothing. Each repo keeps its own git-native `state.json`; only the immutable snapshot crosses. See [17 Components](./17-components.md).
|
|
24
|
+
|
|
19
25
|
**Disposable code** — the view that code is one regenerable implementation of the artifacts, not a durable asset to be preserved.
|
|
20
26
|
|
|
21
27
|
**Evidence bundle** — the proof attached to a change (passing tests, clean security scan, no coverage loss) that justifies trusting it and may unlock more AI autonomy.
|
|
22
28
|
|
|
23
29
|
**Foundation version** — a monotonic integer marker in `PROJECT.md` that advances by one each time confirmed lessons learned are consolidated into the foundation. It makes the living documentation's evolution auditable: a rising version with fewer new deltas per milestone is the signal that a competency is converging rather than drifting. Bumped only by the retrospective consolidation (see the `add` skill's `fold.md`).
|
|
24
30
|
|
|
31
|
+
**Fast lane** — the collapsed, opt-in task path for small, low-risk work. `add.py new-task <slug> --fast` scaffolds a minimal `TASK.fast.md` (sections {0,1,3,4,5,6}; scenarios and observe dropped) and the specification bundle is approved in one freeze. It runs the SAME flow with fewer sections — it never drops the trust floor: a `--fast` task is held to a frozen contract, a red test before build, and a recorded verify gate under any milestone. Collapse, never skip; the human opts in (the engine never auto-classifies a task as small). See the `add` skill's `phases/fast-lane.md`.
|
|
32
|
+
|
|
25
33
|
**Gate** — a checkpoint with an explicit pass/fail exit. Its outcome is `PASS`, `RISK-ACCEPTED`, or `HARD-STOP`.
|
|
26
34
|
|
|
27
35
|
**Ground (phase-0 preamble)** — the per-task phase *before* Specify in which the AI gathers the real current codebase the task touches — files, symbols, signatures, patterns, conventions — into a lean **grounding map**, surfacing the **anchors** the frozen contract will cite. It is AI-owned and adds no approval (the one approval stays at the contract freeze); it precedes the seven steps as step 0 so the contract, tests, and build are grounded in the code as it actually is, not in assumption. Lives in the `add` skill's `phases/0-ground.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilotspace/add",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.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"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"type": "git",
|
|
47
47
|
"url": "git+https://github.com/pilotspace/ADD.git"
|
|
48
48
|
},
|
|
49
|
-
"homepage": "https://github.
|
|
49
|
+
"homepage": "https://pilotspace.github.io/ADD/",
|
|
50
50
|
"bugs": {
|
|
51
51
|
"url": "https://github.com/pilotspace/ADD/issues"
|
|
52
52
|
}
|