@pilotspace/add 2.1.0 → 2.3.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.
@@ -2,10 +2,9 @@
2
2
 
3
3
  Pure module-level constants. add.py re-exports these so `import add; add.STAGES`
4
4
  (and the 6 _-prefixed names) still resolve. `__all__` lists the public names so
5
- `from add_engine.constants import *` brings exactly them (not the Path import).
5
+ `from add_engine.constants import *` brings exactly them.
6
6
  """
7
7
  import re
8
- from pathlib import Path
9
8
 
10
9
  __all__ = [
11
10
  "BOOK_URL",
@@ -32,8 +31,6 @@ __all__ = [
32
31
  "PERSONA_HINT",
33
32
  "PERSONA_FIT_HINT_TEMPLATE",
34
33
  "GUIDELINE_FILES",
35
- "RULES_FILE_REL",
36
- "WORKFLOW_HEADINGS",
37
34
  "_GATE_MODES",
38
35
  "_SKIPPABLE_PHASES",
39
36
  "_DIALECT_CLASSES",
@@ -223,15 +220,14 @@ GUIDELINE_FILES = ("AGENTS.md", "CLAUDE.md", ".clinerules")
223
220
  _GUIDE_BEGIN = "<!-- ADD:BEGIN — managed by `add.py sync-guidelines`; do not edit inside -->"
224
221
  _GUIDE_END = "<!-- ADD:END -->"
225
222
 
226
- # Rule-file mode (ccsk-style projects): instead of inlining the block into CLAUDE.md,
227
- # write it to a dedicated rule file under .claude/rules/ and leave a one-line reference
228
- # in CLAUDE.md's Workflows section. .claude/rules/ is a CLAUDE-only convention, so this
229
- # mode only ever relocates CLAUDE.md AGENTS.md/.clinerules keep the inline block.
230
- RULES_FILE_REL = Path(".claude") / "rules" / "add-workflows.md"
231
- # Headings (most→least specific) a project may already use to group rule/workflow links.
232
- # Match is case-insensitive on the heading TEXT, at any `#` level.
233
- WORKFLOW_HEADINGS = ("Rules & Workflows", "Workflows", "Rules")
234
- _RULE_REF_LINE = "- ADD (AI-Driven Development) Workflows rules: ./.claude/rules/add-workflows.md"
223
+ # PROJECT.md specs-pointer markers (foundation-specs-refs). PROJECT.md stays the thin
224
+ # read-first index; this managed, SPEC_DDS-driven block routes to the standing 5-DD picture
225
+ # in `.add/specs/`. `init` scaffolds it, `migrate` wires it into a pre-pointer PROJECT.md;
226
+ # both refresh it idempotently in place. Same never-change rule as the guideline markers:
227
+ # a re-run finds the old block by exact match, so changing these would orphan every block a
228
+ # prior version wrote.
229
+ _SPECS_BEGIN = "<!-- ADD:SPECS the 5-DD standing picture; managed by add.py — do not edit inside -->"
230
+ _SPECS_END = "<!-- /ADD:SPECS -->"
235
231
 
236
232
  # Minimal embedded fallback so the tool still works if templates/ is missing
237
233
  # (circuit breaker: never hard-fail just because a template file was deleted).
@@ -252,14 +248,13 @@ Boundary:
252
248
  ⚠ <the ONE assumption most likely to be wrong — if wrong: <cost>>
253
249
  </assumptions>
254
250
 
255
- ## 2 · SCENARIOS
256
251
  ## 3 · PLAN
257
252
  ### Contract
258
253
  Status: DRAFT
259
254
  ### Build-strategy
260
255
  Scope (may touch):
261
256
  Regression floor:
262
- ## 4 · TESTS
257
+ ## 4 · TESTS & SCENARIOS
263
258
  ## 5 · BUILD
264
259
  ## 6 · VERIFY
265
260
  ### GATE RECORD
@@ -3,7 +3,7 @@
3
3
  Inject one stable, marker-delimited ADD block into every GUIDELINE_FILES target under the
4
4
  project root (AGENTS.md, CLAUDE.md, .clinerules). DYNAMIC-BY-REFERENCE: the block tells the
5
5
  agent to run `add.py status` and read PROJECT.md — it never embeds live state. A closed,
6
- self-contained cluster (8 fns + the cluster-private _INIT_EXCLUDE); transitive-closure AST
6
+ self-contained cluster (4 fns + the cluster-private _INIT_EXCLUDE); transitive-closure AST
7
7
  scan: ZERO outbound calls to non-cluster add fns. Deps: constants + _atomic_write (io_state)
8
8
  + stdlib. None patched.
9
9
  """
@@ -15,8 +15,12 @@ import sys
15
15
  from pathlib import Path
16
16
 
17
17
  from add_engine.constants import (
18
- GUIDELINE_FILES, RULES_FILE_REL, WORKFLOW_HEADINGS,
19
- _GUIDE_BEGIN, _GUIDE_END, _RULE_REF_LINE,
18
+ GUIDELINE_FILES,
19
+ SPEC_DDS,
20
+ _GUIDE_BEGIN,
21
+ _GUIDE_END,
22
+ _SPECS_BEGIN,
23
+ _SPECS_END,
20
24
  )
21
25
  from add_engine.io_state import _atomic_write
22
26
 
@@ -31,28 +35,28 @@ def _guideline_block() -> str:
31
35
  f"{_GUIDE_BEGIN}\n"
32
36
  "## ADD — how to work in this repo\n"
33
37
  "\n"
34
- "This project uses **ADD (AI-Driven Development)**: you, the AI, drive the build;\n"
35
- "the human owns direction and verification. The loop below works for any agent —\n"
36
- "Claude, Cursor, Copilot, Codex — through the CLI alone. Before you change code:\n"
38
+ "**ADD (AI-Driven Development)** you, the AI, drive the build; the human owns direction\n"
39
+ "and verification. Works for any agent (Claude · Cursor · Copilot · Codex) through the CLI\n"
40
+ "alone. Before you touch code, orient:\n"
37
41
  "\n"
38
- "1. `python3 .add/tooling/add.py status` — the resume point; read it first each session.\n"
39
- "2. `.add/PROJECT.md` — the foundation (domain · spec · UI/UX) every task builds on.\n"
40
- "3. `python3 .add/tooling/add.py guide` — it names the phase + its guide file; work ONLY that phase.\n"
42
+ "1. `python3 .add/tooling/add.py status` — your resume point; read it first each session.\n"
43
+ "2. `.add/PROJECT.md` — the thin read-first index (goal · `invariants:` · pointers into\n"
44
+ " `.add/specs/`, the 5-DD standing picture); drill into a spec on demand.\n"
45
+ "3. `python3 .add/tooling/add.py guide` — names the current phase + its guide; work ONLY that phase.\n"
41
46
  "\n"
42
- "The flow: INTAKE sizes a request into a milestone; each task runs the **specification bundle**\n"
43
- "(Spec+Scenarios+Contract+Tests) — ONE human approval at the frozen contract then a\n"
44
- "self-driving build→verify run. Non-negotiable for every agent: Never weaken a test or edit a frozen contract\n"
45
- "to make a build pass; a security finding is always HARD-STOP — never auto-passed.\n"
46
- "PROJECT.md `invariants:` (run/entry contracts) bind EVERY task: the artifact must hold under\n"
47
- "the BARE declared runtime — a dependency that breaks it is a defect, never \"expected\".\n"
47
+ "Each task drafts the **specification bundle** (Spec · Contract · Tests & Scenarios) —\n"
48
+ "ONE human approval at the frozen contract, then a self-driving build→verify run.\n"
49
+ "Non-negotiable: Never weaken a test or edit a frozen contract to pass a build; a\n"
50
+ "security finding is always HARD-STOP. PROJECT.md `invariants:` bind EVERY task the\n"
51
+ "artifact must hold under the BARE declared runtime.\n"
48
52
  "\n"
49
- "Roster (from `add-method/agents/*.md`): TWO agents — `add-worker` runs each EXECUTION beat\n"
50
- "(the spawn names it: direction · build · verify · persona), and `add-advisor` is the second\n"
51
- "mind it spawns to propose a plan, pressure-test a draft, or decide a delegable ambiguity so\n"
52
- "the beat never stalls. Each loads that beat's phase guide plus the best-fit `.add/personas/`\n"
53
- "persona (personas carry the expertise, the agent carries the discipline; the floor binds both).\n"
53
+ "Roster (`agents/*.md`): `add-worker` runs each beat (direction · build · verify · persona);\n"
54
+ "`add-advisor` is the second mind it spawns to plan, pressure-test, or resolve a delegable\n"
55
+ "ambiguity. Each loads the beat's guide + the best-fit `.add/personas/` persona personas\n"
56
+ "carry the expertise, the agent carries the discipline.\n"
54
57
  "\n"
55
- "On Claude Code the `add` skill drives this loop; other agents follow the three steps. Book: https://pilotspace.github.io/ADD/. This block is generated by `add.py sync-guidelines` — edit outside the markers.\n"
58
+ "On Claude Code the `add` skill drives this loop; other agents follow the three steps.\n"
59
+ "Book: https://pilotspace.github.io/ADD/. Generated by `add.py sync-guidelines` — edit outside the markers.\n"
56
60
  f"{_GUIDE_END}"
57
61
  )
58
62
 
@@ -89,131 +93,17 @@ def _inject_block(path: Path) -> str:
89
93
  return "created"
90
94
 
91
95
 
92
- def _rule_file_mode(project_root: Path, flag: bool = False) -> bool:
93
- """True when the ADD block should live in .claude/rules/add-workflows.md (referenced
94
- from CLAUDE.md) instead of inline. Re-derived from disk EACH phase — no persisted
95
- state — so an explicit `--rule-file` at install carries into init via the rule file it
96
- leaves behind. Three triggers: the explicit flag, a ccsk project (.ccsk/ sibling to
97
- .claude/), or a rule file already written by a prior run. Pure + fail-soft."""
98
- if flag:
99
- return True
100
- try:
101
- if (project_root / ".ccsk").is_dir():
102
- return True
103
- if (project_root / RULES_FILE_REL).exists():
104
- return True
105
- except OSError:
106
- pass
107
- return False
108
-
109
-
110
- def _strip_inline_block(text: str) -> str:
111
- """Remove an inline ADD:BEGIN..ADD:END region (migration to rule-file mode), collapsing
112
- the blank-line gap it leaves behind. An unterminated BEGIN (no END) is left as-is rather
113
- than eating the rest of the file (design-for-failure)."""
114
- begin = text.find(_GUIDE_BEGIN)
115
- if begin == -1:
116
- return text
117
- end = text.find(_GUIDE_END, begin)
118
- if end == -1:
119
- return text
120
- end += len(_GUIDE_END)
121
- head = text[:begin].rstrip("\n")
122
- tail = text[end:].lstrip("\n")
123
- if head and tail:
124
- return head + "\n\n" + tail
125
- return head or tail
126
-
127
-
128
- def _insert_rule_reference(text: str) -> str:
129
- """Insert the ADD rule-file bullet under an existing Workflows/Rules heading, or append a
130
- fresh '## Workflows' section when none is found. Caller guarantees the bullet is absent."""
131
- lines = text.split("\n")
132
- heading_idx = -1
133
- for i, line in enumerate(lines):
134
- m = re.match(r"^(#{1,6})\s+(.*?)\s*$", line)
135
- if m and any(m.group(2).strip().lower() == h.lower() for h in WORKFLOW_HEADINGS):
136
- heading_idx = i
137
- break
138
- if heading_idx == -1: # no section — append a fresh one
139
- body = text.rstrip("\n")
140
- sep = "\n\n" if body else ""
141
- return f"{body}{sep}## Workflows\n\n{_RULE_REF_LINE}\n"
142
- level = len(re.match(r"^(#{1,6})", lines[heading_idx]).group(1))
143
- end = len(lines) # section ends at next same/higher heading or EOF
144
- for j in range(heading_idx + 1, len(lines)):
145
- m = re.match(r"^(#{1,6})\s+", lines[j])
146
- if m and len(m.group(1)) <= level:
147
- end = j
148
- break
149
- insert_at = heading_idx + 1 # after the last non-blank line in the section
150
- for j in range(heading_idx + 1, end):
151
- if lines[j].strip():
152
- insert_at = j + 1
153
- lines.insert(insert_at, _RULE_REF_LINE)
154
- return "\n".join(lines)
155
-
156
-
157
- def _ensure_claude_reference(claude_md: Path) -> str:
158
- """Make CLAUDE.md reference the ADD rule file under a Workflows/Rules heading, migrating
159
- any prior inline ADD block out. Returns created|updated|unchanged.
160
-
161
- - Strips a prior inline ADD:BEGIN..END block (rule-file mode supersedes inline).
162
- - If a reference to add-workflows.md already exists -> no bullet change.
163
- - Else inserts the bullet into the first matching section, or appends '## Workflows'.
164
- .bak on change; idempotent. User content outside the touched region is preserved.
165
- """
166
- existed = claude_md.exists()
167
- current = claude_md.read_text(encoding="utf-8") if existed else ""
168
- new = _strip_inline_block(current)
169
- if "add-workflows.md" not in new:
170
- new = _insert_rule_reference(new)
171
- if not new.endswith("\n"):
172
- new += "\n"
173
- if new == current:
174
- return "unchanged"
175
- if existed:
176
- _atomic_write(Path(str(claude_md) + ".bak"), current) # rollback path before mutate
177
- _atomic_write(claude_md, new)
178
- return "updated" if existed else "created"
179
-
180
-
181
- def _inject_guidelines(project_root: Path, rule_file: bool = False) -> list[tuple[str, str]]:
96
+ def _inject_guidelines(project_root: Path) -> list[tuple[str, str]]:
182
97
  """Inject the block into each guideline file under `project_root`.
183
98
 
184
99
  Symlink-dedup: targets resolving (os.path.realpath) to the same inode are
185
100
  written once, against the REAL file (never replacing the symlink with a
186
101
  regular file). Per-target OSError is isolated (warn+skip) so one unwritable
187
102
  file never aborts the run or `init`.
188
-
189
- Rule-file mode (ccsk projects / `--rule-file`): CLAUDE.md's full block is relocated
190
- to .claude/rules/add-workflows.md and CLAUDE.md keeps only a reference bullet. This is
191
- CLAUDE-only — AGENTS.md (and any other guideline file) keeps the inline block.
192
103
  """
193
104
  results: list[tuple[str, str]] = []
194
105
  seen: set[str] = set()
195
- mode = _rule_file_mode(project_root, rule_file)
196
106
  for name in GUIDELINE_FILES:
197
- if name == "CLAUDE.md" and mode:
198
- rules_path = project_root / RULES_FILE_REL
199
- real = os.path.realpath(rules_path)
200
- if real not in seen:
201
- seen.add(real)
202
- try:
203
- action = _inject_block(rules_path)
204
- except (OSError, UnicodeDecodeError) as exc:
205
- print(f"add: warning: could not sync {RULES_FILE_REL} — {exc}; skipped",
206
- file=sys.stderr)
207
- action = "skipped"
208
- results.append((str(RULES_FILE_REL), action))
209
- try:
210
- ref_action = _ensure_claude_reference(project_root / "CLAUDE.md")
211
- except (OSError, UnicodeDecodeError) as exc:
212
- print(f"add: warning: could not sync CLAUDE.md — {exc}; skipped",
213
- file=sys.stderr)
214
- ref_action = "skipped"
215
- results.append(("CLAUDE.md", ref_action))
216
- continue
217
107
  target = project_root / name
218
108
  real = os.path.realpath(target)
219
109
  if real in seen:
@@ -233,6 +123,66 @@ def _inject_guidelines(project_root: Path, rule_file: bool = False) -> list[tupl
233
123
  return results
234
124
 
235
125
 
126
+ # --- PROJECT.md specs pointers -----------------------------------------------
127
+
128
+ def _specs_pointer_block() -> str:
129
+ """The managed ADD:SPECS block (markers + body), driven entirely by SPEC_DDS.
130
+
131
+ PROJECT.md is the thin read-first index; this block routes to the standing
132
+ 5-DD picture in `.add/specs/` — one line per lens, so the picture can never
133
+ drift from the closed 5-DD map."""
134
+ lines = [
135
+ _SPECS_BEGIN,
136
+ "## Foundation specs — the standing 5-DD picture",
137
+ "",
138
+ "This file is the thin index; the detail lives in the specs. Lessons land there via "
139
+ "`add.py delta-append <dd>`.",
140
+ "",
141
+ ]
142
+ for _dd, (fname, title, lens) in SPEC_DDS.items():
143
+ lines.append(f"- **{title}** → `.add/specs/{fname}` — {lens}")
144
+ lines.append(_SPECS_END)
145
+ return "\n".join(lines)
146
+
147
+
148
+ def _inject_specs_pointers(project_md: Path) -> str:
149
+ """Inject/refresh the ADD:SPECS pointer block in PROJECT.md. Idempotent.
150
+
151
+ Returns created|updated|unchanged|absent:
152
+ - absent: PROJECT.md does not exist -> no-op (nothing to wire).
153
+ - unchanged: on-disk block already matches -> no write.
154
+ - updated: markers present -> replace only the marked region (refresh).
155
+ - created: markers absent -> insert before the first '## ' section
156
+ (keeping the header/goal above it); append if no section exists.
157
+ Content outside the markers is always preserved."""
158
+ if not project_md.exists():
159
+ return "absent"
160
+ current = project_md.read_text(encoding="utf-8")
161
+ block = _specs_pointer_block()
162
+ begin = current.find(_SPECS_BEGIN)
163
+ if begin != -1:
164
+ end = current.find(_SPECS_END, begin)
165
+ if end != -1: # replace only the marked region
166
+ end += len(_SPECS_END)
167
+ new = current[:begin] + block + current[end:]
168
+ else: # begin without end: corrupt — append fresh
169
+ print(f"add: warning: {project_md.name}: found an ADD:SPECS with no close "
170
+ "— appending a fresh block; review the result", file=sys.stderr)
171
+ new = current.rstrip("\n") + "\n\n" + block + "\n"
172
+ action = "updated"
173
+ else: # no block yet — insert above the first section
174
+ m = re.search(r"(?m)^## ", current)
175
+ if m:
176
+ new = current[:m.start()] + block + "\n\n" + current[m.start():]
177
+ else:
178
+ new = current.rstrip("\n") + "\n\n" + block + "\n"
179
+ action = "created"
180
+ if new == current:
181
+ return "unchanged"
182
+ _atomic_write(project_md, new)
183
+ return action
184
+
185
+
236
186
  # --- commands ----------------------------------------------------------------
237
187
 
238
188
  _INIT_EXCLUDE = {
@@ -40,6 +40,15 @@ Issues/Risks (shared): <traps in the shared code that feed each task's §1 expec
40
40
  ## Exit criteria (observable; map each to the task that delivers it)
41
41
  - [ ] User can <observable behavior — the SEEN outcome only, NOT the task's plan line> (← <slug>)
42
42
 
43
+ ## Strategy (AI-drafted WITH the human — the optimized task plan; SOFT/advisory like a task's Build-strategy; drafted-blank for a micro/--fast milestone)
44
+ > The persona-led strategy over THIS milestone's tasks — sequencing, freeze-first contracts,
45
+ > parallel waves, the first unblocking slice, tradeoffs named. SOFT: the preferred plan; the
46
+ > loop may deviate and records what it did. Drafted-blank is valid (risk-proportional).
47
+ - Approach (sequencing): <risk-first | dependency-first | first-slice-unblocks — and WHY>
48
+ - Freeze-first: <the shared/risky contract to freeze before the rest>
49
+ - Waves (parallel): <task slugs that can run concurrently behind frozen contracts — or "sequential">
50
+ - Tradeoffs weighed: <alternative decompositions considered + why this one>
51
+
43
52
  ## Close — ship review (AI fills when every task is done — the evidence behind the engine gate, read before the boxes are checked)
44
53
  > Cross-task review the AI fills — the evidence behind the EXISTING milestone-done gate, NOT a new approval.
45
54
 
@@ -30,13 +30,7 @@ Boundary: <one format-variant per external input shape the tests must speak —
30
30
  ⚠ <the ONE assumption most likely to be wrong — if wrong: <cost>>
31
31
  </assumptions>
32
32
 
33
- ---
34
-
35
- ## 2 · SCENARIOS — pass/fail cases ▸ docs/04-step-2-scenarios.md
36
-
37
- <scenarios>
38
- </scenarios>
39
- <!-- The §4 test_plan is the canonical encoding of every scenario (one test per Must/Reject). Add gherkin above ONLY when a human stakeholder needs readable cases — never as ceremony. -->
33
+ <!-- §2 (the old standalone SCENARIOS section) was RETIRED — pass/fail cases now live with the tests in §4 · TESTS & SCENARIOS. The §3–§7 numbers are unchanged so the freeze parser and every §-reference keep working; the jump from §1 to §3 is intentional. -->
40
34
 
41
35
  ---
42
36
 
@@ -55,10 +49,12 @@ Target (measurable): <the success bar §6 evidence must hit — numbers, not adj
55
49
  Status: DRAFT
56
50
  Reported: <yes — the freeze report (banner/ARC/SHAPE) rendered before this froze | no>
57
51
 
58
- ### Build-strategy (SOFT: preferred; the builder self-improves and records actual at verify)
59
- Scope (may touch): `./src/` <fill before the freeze the file write-set, single source of truth; every file the build may write; scope-lock source>
60
- Regression floor: <the existing suite(s) that must stay green the host repo's own tests are ALWAYS a floor; run them before the gate — or "none — greenfield">
61
- Persona (required): <persona file under `.add/personas/` this build embodies — advisory, never lowers a gate; "generic" if none fits>
52
+ ### Build-strategy — Scope (may touch) is HARD scope-lock; the rest is SOFT (the builder self-improves and records actual at verify)
53
+ Scope (may touch): `src/` <HARD — fill before the freeze; the file write-set, single source of truth; every file the build may write. Token grammar (backtick each): name/ = project root · ./… = THIS task's dir (rarely what a build writes) · a directory covers its whole subtree>
54
+ Regression floor: <optional — the existing suite(s) that must stay green; the host repo's own tests are a floor when present; run them before the gate — or omit / "none — greenfield">
55
+ Persona (optional): <persona file under `.add/personas/` this build embodies — advisory, never lowers a gate; omit or "generic" if none fits>
56
+
57
+ Least-sure flag surfaced at freeze: [spec|scenario|contract|test] <the ONE part you trust least + why — REQUIRED at the freeze (unflagged_freeze refuses without it); pick ONE part tag from the menu; §1's top ⚠ usually feeds it>
62
58
 
63
59
  ### AI-verify record (required when gate_mode: ai-plan-verify)
64
60
  - [ ] §3 PLAN grounding anchors resolve in the current tree
@@ -67,16 +63,18 @@ Persona (required): <persona file under `.add/personas/` this build embodies —
67
63
  - [ ] Lowest-confidence flag surfaced and substantive (mirrors unflagged_freeze's own bar)
68
64
  Verified by: <agent-id> · at: <ISO-8601 UTC timestamp>
69
65
 
70
- <!-- The freeze IS the one approval, led by the bundle's lowest-confidence flag — Contract = HARD (tamper-guarded); Build-strategy = SOFT. Approved -> Status: FROZEN @ vN — approved by <name>; changing a frozen Contract = change request back to SPECIFY. Scope tokens, backticked: `./…` = this task dir · a "/" token = project root · a bare name = sibling of the previous token's dir · a directory covers its whole subtree · outside-root drops fail-closed · absent line = UNDECLARED (grandfathered, never retro-red). -->
66
+ <!-- The freeze IS the one approval, led by the bundle's lowest-confidence flag — Contract + Scope (may touch) = HARD (tamper-guarded); Strategy · Regression floor · Persona = SOFT/optional. Approved -> Status: FROZEN @ vN — approved by <name>; changing a frozen Contract = change request back to SPECIFY. Scope tokens, backticked: `./…` = this task dir · a "/" token = project root · a bare name = sibling of the previous token's dir · a directory covers its whole subtree · outside-root drops fail-closed · absent line = UNDECLARED (grandfathered, never retro-red). -->
71
67
 
72
68
  ---
73
69
 
74
- ## 4 · TESTS — failing-first suite or acceptance checks (red) ▸ docs/06-step-4-tests.md
70
+ ## 4 · TESTS & SCENARIOS — failing-first suite or acceptance checks (red) ▸ docs/06-step-4-tests.md
75
71
 
76
72
  <test_plan>
77
73
  - test_<name>: arrange / act / assert behavior not internals · covers: <M#, R:code>
78
74
  </test_plan>
79
75
 
76
+ Rigor: one red test per §1 Must/Reject — the PRIMARY cases + primary edge cases — is the gated floor. Minor/secondary behaviors are DESCRIBED in prose below as build-guidance — no `covers:` tag, no red test, not gated. Add a Given/When/Then line inline ONLY when a human stakeholder needs a readable case — never as ceremony; the test_plan is the canonical encoding of every scenario.
77
+
80
78
  Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
81
79
  <!-- declare paths as backticked tokens on this line: `./…` = this task dir · a token with "/" = the project root · a bare name = a sibling of the previous token's dir · a directory counts its *.py files (non-recursive) · declared counts marked † · outside the project root counts 0. The test_plan bullets' `covers:` tails are machine-read too: `add.py locate path::test_name` resolves a failing test to the frozen §3 clause it proves -->
82
80
  <!-- NON-CODING task (kind: docs · release · infra, or a non-coding project)? §4 is a failing-first ACCEPTANCE CHECK, not a script — verifiable pass/fail evidence (mkdocs build succeeds · §X covers A/B/C · every internal link resolves), red before the artifact exists and green after. Set `Tests live in: evidence` (no `./tests/`). The red→green discipline holds; only the must-be-executable-code requirement is lifted. -->
@@ -86,7 +84,7 @@ Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
86
84
  ## 5 · BUILD — AI writes the code (execution) ▸ docs/07-step-5-build.md
87
85
 
88
86
  Strategy actually used: <fill at VERIFY — what you ACTUALLY did (or "as planned"); harvested into §7 Decisions (ADR)>
89
- Code lives in: `./src/`
87
+ Code lives in: `src/`
90
88
  Spawn (multi-agent): build/verify subagent spawns default `isolation: worktree`; cross-agent advisor — spawn `add-advisor` (an agent OTHER than the builder) for the freeze `--cross` and the §6 refute-read; `self` only when solo.
91
89
  Constraints: do NOT change any test or the frozen §3 contract; stay inside §3 Scope (an out-of-scope build fails the gate: scope_violation); keep the §3 Regression floor green; allow-list packages only; ask if unclear.
92
90