@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.
- package/CHANGELOG.md +82 -0
- package/agents/add-advisor.md +10 -2
- package/agents/add-worker.md +2 -0
- package/bin/cli.js +2 -137
- package/package.json +1 -1
- package/skill/add/SKILL.md +7 -7
- package/skill/add/deltas.md +1 -1
- package/skill/add/design.md +1 -1
- package/skill/add/phases/build.md +23 -1
- package/skill/add/phases/direction.md +75 -17
- package/skill/add/phases/verify.md +25 -4
- package/tooling/add.py +407 -33
- package/tooling/add_engine/constants.py +10 -15
- package/tooling/add_engine/guidelines.py +86 -136
- package/tooling/templates/MILESTONE.md.tmpl +9 -0
- package/tooling/templates/PLAN.md.tmpl +12 -14
|
@@ -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
|
|
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
|
-
#
|
|
227
|
-
#
|
|
228
|
-
# in
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
#
|
|
232
|
-
|
|
233
|
-
|
|
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 (
|
|
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,
|
|
19
|
-
|
|
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
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
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` —
|
|
39
|
-
"2. `.add/PROJECT.md` — the
|
|
40
|
-
"
|
|
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
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
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 (
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"the
|
|
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
|
|
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
|
|
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 (
|
|
59
|
-
Scope (may touch):
|
|
60
|
-
Regression floor: <the existing suite(s) that must stay green
|
|
61
|
-
Persona (
|
|
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);
|
|
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:
|
|
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
|
|