@pilotspace/add 1.17.0 → 2.1.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 +193 -0
- package/GETTING-STARTED.md +20 -64
- package/README.md +138 -158
- package/agents/add-advisor.md +66 -21
- package/agents/add-worker.md +102 -0
- package/bin/cli.js +88 -21
- package/package.json +2 -3
- package/personas-teacher/VENDOR.md +6 -0
- package/skill/add/SKILL.md +100 -144
- package/skill/add/adopt.md +28 -0
- package/skill/add/beyond.md +37 -0
- package/skill/add/deltas.md +25 -2
- package/skill/add/design.md +56 -74
- package/skill/add/{report-template.md → gate-udd.md} +28 -38
- package/skill/add/intake.md +56 -25
- package/skill/add/loop.md +32 -19
- package/skill/add/persona-author/SKILL.md +116 -0
- package/skill/add/persona-author/assets/example-design-persona.md +55 -0
- package/skill/add/persona-author/assets/example-persona.md +57 -0
- package/skill/add/persona-author/references/contract.md +69 -0
- package/skill/add/persona-author/references/patterns.md +122 -0
- package/skill/add/persona-author/references/seeding.md +79 -0
- package/skill/add/phases/build.md +79 -0
- package/skill/add/phases/direction.md +287 -0
- package/skill/add/phases/verify.md +161 -0
- package/skill/add/run.md +16 -18
- package/skill/add/terms.md +18 -0
- package/tooling/add.py +2421 -3258
- package/tooling/add_engine/autonomy.py +2 -37
- package/tooling/add_engine/components.py +4 -87
- package/tooling/add_engine/constants.py +148 -93
- package/tooling/add_engine/guidelines.py +10 -11
- package/tooling/add_engine/io_state.py +72 -5
- package/tooling/add_engine/predicates.py +90 -2
- package/tooling/add_engine/search.py +6 -6
- package/tooling/add_engine/taskdoc.py +15 -10
- package/tooling/templates/CONVENTIONS.md.tmpl +1 -1
- package/tooling/templates/GLOSSARY.md.tmpl +10 -4
- package/tooling/templates/MILESTONE.md.tmpl +20 -16
- package/tooling/templates/PLAN.md.tmpl +129 -0
- package/tooling/templates/PROJECT.md.tmpl +6 -1
- package/tooling/templates/PROMPT.persona.md.tmpl +17 -21
- package/tooling/templates/personas/build-engineer.md.tmpl +45 -0
- package/tooling/templates/personas/data-steward.md.tmpl +45 -0
- package/tooling/templates/personas/evidence-verifier.md.tmpl +45 -0
- package/tooling/templates/personas/platform-engineer.md.tmpl +45 -0
- package/tooling/templates/personas/product-lead.md.tmpl +51 -0
- package/tooling/templates/personas/quality-auditor.md.tmpl +47 -0
- package/tooling/templates/personas/release-manager.md.tmpl +52 -0
- package/tooling/templates/personas/security-gatekeeper.md.tmpl +45 -0
- package/tooling/templates/personas/software-architect.md.tmpl +45 -0
- package/tooling/templates/personas/stream-orchestrator.md.tmpl +44 -0
- package/tooling/templates/personas/technical-writer.md.tmpl +49 -0
- package/tooling/templates/personas/ux-experience-lead.md.tmpl +48 -0
- package/tooling/templates/specs/SPEC.md.tmpl +18 -0
- package/tooling/templates/udd-wireframe.md +1 -1
- package/agents/add-build.md +0 -29
- package/agents/add-design.md +0 -32
- package/agents/add-persona.md +0 -33
- package/agents/add-verify.md +0 -36
- package/docs/00-introduction.md +0 -46
- package/docs/01-principles.md +0 -71
- package/docs/02-the-flow.md +0 -113
- package/docs/03-step-1-specify.md +0 -123
- package/docs/04-step-2-scenarios.md +0 -90
- package/docs/05-step-3-contract.md +0 -80
- package/docs/06-step-4-tests.md +0 -85
- package/docs/07-step-5-build.md +0 -82
- package/docs/08-step-6-verify.md +0 -105
- package/docs/09-the-loop.md +0 -75
- package/docs/10-setup-and-stages.md +0 -118
- package/docs/11-governance.md +0 -91
- package/docs/12-roles.md +0 -99
- package/docs/13-adoption.md +0 -67
- package/docs/14-foundation.md +0 -142
- package/docs/15-foundations-and-lineage.md +0 -106
- package/docs/16-releasing.md +0 -182
- package/docs/17-components.md +0 -143
- package/docs/18-personas.md +0 -80
- package/docs/README.md +0 -77
- package/docs/add-competencies.png +0 -0
- package/docs/add-flow.png +0 -0
- package/docs/add-foundation.png +0 -0
- package/docs/add-hierarchy.png +0 -0
- package/docs/add-milestone-task-lifecycle.png +0 -0
- package/docs/add-task-growth-wheel.png +0 -0
- package/docs/appendix-a-templates.md +0 -88
- package/docs/appendix-b-prompts.md +0 -154
- package/docs/appendix-c-glossary.md +0 -176
- package/docs/appendix-d-worked-example.md +0 -231
- package/docs/appendix-e-checklists.md +0 -92
- package/docs/appendix-f-requirements-matrix.md +0 -171
- package/docs/appendix-g-references.md +0 -106
- package/skill/add/advisor.md +0 -81
- package/skill/add/compact-foundation.md +0 -40
- package/skill/add/components.md +0 -52
- package/skill/add/confidence.md +0 -32
- package/skill/add/fold.md +0 -63
- package/skill/add/graduate.md +0 -39
- package/skill/add/phases/0-ground.md +0 -64
- package/skill/add/phases/0-setup.md +0 -103
- package/skill/add/phases/1-specify.md +0 -61
- package/skill/add/phases/2-scenarios.md +0 -48
- package/skill/add/phases/3-contract.md +0 -60
- package/skill/add/phases/4-tests.md +0 -61
- package/skill/add/phases/5-build.md +0 -62
- package/skill/add/phases/6-verify.md +0 -69
- package/skill/add/phases/7-observe.md +0 -45
- package/skill/add/phases/fast-lane.md +0 -34
- package/skill/add/release.md +0 -61
- package/skill/add/scope.md +0 -72
- package/skill/add/self-improve.md +0 -20
- package/skill/add/sensitivity.md +0 -51
- package/skill/add/setup-review.md +0 -48
- package/skill/add/soul.md +0 -58
- package/skill/add/streams.md +0 -269
- package/tooling/add_engine/release.py +0 -86
- package/tooling/templates/TASK.fast.md.tmpl +0 -78
- package/tooling/templates/TASK.md.tmpl +0 -181
- package/tooling/templates/personas/_template.md.tmpl +0 -82
|
@@ -117,17 +117,32 @@ def _atomic_write_many(writes: list[tuple[Path, str]]) -> None:
|
|
|
117
117
|
# --- root finding + state load/save + the shared error primitive ------------
|
|
118
118
|
|
|
119
119
|
def find_root(start: Path | None = None) -> Path | None:
|
|
120
|
-
"""Walk up from cwd to find a .add/ project root.
|
|
120
|
+
"""Walk up from cwd to find a .add/ project root.
|
|
121
|
+
|
|
122
|
+
Opt-in boundary: when `ADD_ROOT_CEILING` is set, the walk stops at that dir
|
|
123
|
+
(inclusive) and never ascends above it — so a workspace nested under an
|
|
124
|
+
ancestor project resolves ONLY its own root (or None before init), never the
|
|
125
|
+
parent's. Unset (every normal invocation) is the legacy walk to fs-root,
|
|
126
|
+
byte-identical. Both paths are resolved so a symlinked ceiling still matches.
|
|
127
|
+
"""
|
|
121
128
|
cur = (start or Path.cwd()).resolve()
|
|
129
|
+
_ceil = os.environ.get("ADD_ROOT_CEILING")
|
|
130
|
+
ceil = Path(_ceil).resolve() if _ceil else None
|
|
122
131
|
for d in (cur, *cur.parents):
|
|
123
132
|
if (d / ROOT_DIRNAME / STATE_FILE).exists():
|
|
124
133
|
return d / ROOT_DIRNAME
|
|
134
|
+
if ceil is not None and d == ceil:
|
|
135
|
+
break
|
|
125
136
|
return None
|
|
126
137
|
|
|
127
138
|
def _require_root() -> Path:
|
|
128
139
|
root = find_root()
|
|
129
140
|
if root is None:
|
|
130
|
-
|
|
141
|
+
# skip-error-ergonomics M2: hand the exact command with its flags — the
|
|
142
|
+
# bare "run init first" hint made every fresh headless agent read
|
|
143
|
+
# `init --help` before its first real call (LOOP-2 re-measure, all reps).
|
|
144
|
+
_die('no .add/ project found — run: add.py init --name "<project>" '
|
|
145
|
+
"--stage <prototype|poc|mvp|production>")
|
|
131
146
|
return root
|
|
132
147
|
|
|
133
148
|
def _migrate_state(state: dict) -> dict:
|
|
@@ -170,8 +185,60 @@ def _state_text_or_die(root: Path) -> str:
|
|
|
170
185
|
f"`git checkout --ours/--theirs {STATE_FILE}`), then run `add.py doctor` to verify")
|
|
171
186
|
return text
|
|
172
187
|
|
|
188
|
+
# kickoff-truth M3 (contract v2): the dup-failure short-circuit. The 2026-07-13
|
|
189
|
+
# transcript audit measured 12-21% of all engine calls as byte-identical repeats of
|
|
190
|
+
# a call that had just failed the same way — the agent re-issues the command
|
|
191
|
+
# unchanged instead of reading the error. The sig sidecar lives OUTSIDE the project
|
|
192
|
+
# tree (OS tmp dir, keyed by md5(root path)): the engine's reject-writes-nothing
|
|
193
|
+
# floor pins that a REJECTED command leaves the .add tree byte-identical, so the
|
|
194
|
+
# hint state is ephemeral per-project cache, never a tree write. add.main()
|
|
195
|
+
# registers each invocation and clears the sidecar on any successful exit, so only
|
|
196
|
+
# CONSECUTIVE identical failures short-circuit. Fail-open everywhere: no root /
|
|
197
|
+
# unreadable sidecar / any OSError -> no hint, never a block — a hint helper must
|
|
198
|
+
# never change an error's outcome.
|
|
199
|
+
_INVOCATION: list[str] | None = None # set by add.main() per dispatch
|
|
200
|
+
|
|
201
|
+
def _register_invocation(argv: list[str]) -> None:
|
|
202
|
+
global _INVOCATION
|
|
203
|
+
_INVOCATION = list(argv)
|
|
204
|
+
|
|
205
|
+
def _last_fail_path() -> Path | None:
|
|
206
|
+
root = find_root()
|
|
207
|
+
if root is None:
|
|
208
|
+
return None
|
|
209
|
+
key = hashlib.md5(str(root.resolve()).encode("utf-8")).hexdigest()[:12]
|
|
210
|
+
return Path(tempfile.gettempdir()) / f"add-last-fail-{key}.json"
|
|
211
|
+
|
|
212
|
+
def _clear_last_fail() -> None:
|
|
213
|
+
try:
|
|
214
|
+
side = _last_fail_path()
|
|
215
|
+
if side is not None:
|
|
216
|
+
side.unlink(missing_ok=True)
|
|
217
|
+
except OSError:
|
|
218
|
+
pass # hygiene only — success must never fail on it
|
|
219
|
+
|
|
220
|
+
def _dup_fail_hint(msg: str) -> str | None:
|
|
221
|
+
try:
|
|
222
|
+
side = _last_fail_path()
|
|
223
|
+
if side is None:
|
|
224
|
+
return None
|
|
225
|
+
argv = _INVOCATION if _INVOCATION is not None else sys.argv[1:]
|
|
226
|
+
code_head = msg.split(":", 1)[0].strip()
|
|
227
|
+
sig = hashlib.md5((" ".join(argv) + "\n" + code_head).encode("utf-8")).hexdigest()
|
|
228
|
+
hint = None
|
|
229
|
+
if side.exists() and json.loads(side.read_text(encoding="utf-8")).get("sig") == sig:
|
|
230
|
+
hint = ("hint: this exact call already failed with the same error — change "
|
|
231
|
+
"the command or input before retrying (add.py status shows the true state)")
|
|
232
|
+
side.write_text(json.dumps({"sig": sig}), encoding="utf-8")
|
|
233
|
+
return hint
|
|
234
|
+
except (OSError, ValueError):
|
|
235
|
+
return None
|
|
236
|
+
|
|
173
237
|
def _die(msg: str, code: int = 1) -> None:
|
|
238
|
+
hint = _dup_fail_hint(msg) # kickoff-truth M3 — fail-open, never raises
|
|
174
239
|
print(f"add: error: {msg}", file=sys.stderr)
|
|
240
|
+
if hint:
|
|
241
|
+
print(hint, file=sys.stderr)
|
|
175
242
|
raise SystemExit(code)
|
|
176
243
|
|
|
177
244
|
|
|
@@ -203,9 +270,9 @@ def _md5_file(p: Path) -> str | None:
|
|
|
203
270
|
|
|
204
271
|
def _personas_unseeded(root: Path) -> bool:
|
|
205
272
|
"""True when `.add/personas/` has no REAL (non-template) authored persona: the
|
|
206
|
-
directory is absent, empty, or holds only
|
|
207
|
-
|
|
208
|
-
rather than raising — this feeds a `note:`/INFO hint, never a gate."""
|
|
273
|
+
directory is absent, empty, or holds only a `_template.md` scaffold (personas are
|
|
274
|
+
authored via the persona-author skill, not seeded). Fail-soft: an unreadable directory
|
|
275
|
+
counts as unseeded rather than raising — this feeds a `note:`/INFO hint, never a gate."""
|
|
209
276
|
d = root / "personas"
|
|
210
277
|
if not d.is_dir():
|
|
211
278
|
return True
|
|
@@ -10,7 +10,8 @@ from __future__ import annotations
|
|
|
10
10
|
import re
|
|
11
11
|
|
|
12
12
|
from add_engine.constants import (
|
|
13
|
-
PHASE_OWNER, PERSONA_FRONTMATTER_KEYS, PERSONA_REQUIRED_SECTIONS,
|
|
13
|
+
PHASE_OWNER, PHASE_GROUPS, PERSONA_FLOW_VALUES, PERSONA_FRONTMATTER_KEYS, PERSONA_REQUIRED_SECTIONS,
|
|
14
|
+
TASK_KINDS,
|
|
14
15
|
_MUST_ID_RE, _REJECT_CODE_RE, _SCENARIO_TAG_RE, _COVERS_LINE_RE, _TAG_TOKEN_RE,
|
|
15
16
|
)
|
|
16
17
|
from add_engine.io_state import _die
|
|
@@ -23,6 +24,59 @@ def _phase_owner(phase: str) -> str:
|
|
|
23
24
|
_die("unmapped_phase")
|
|
24
25
|
return owner
|
|
25
26
|
|
|
27
|
+
def _phase_bundle(phase: str) -> str | None:
|
|
28
|
+
"""Map a phase to its PHASE_GROUPS bundle name (DIRECTION|BUILD|VERIFY); `None` for
|
|
29
|
+
the terminal "done" phase (a deliberate, documented non-crash — done is a human-led
|
|
30
|
+
terminal state, not work any of the three roster agents drive); `_die
|
|
31
|
+
("unmapped_phase_bundle")` for any other token absent from PHASE_GROUPS (fail closed,
|
|
32
|
+
mirrors `_phase_owner`'s exact idiom)."""
|
|
33
|
+
if phase == "done":
|
|
34
|
+
return None
|
|
35
|
+
for bundle, phases in PHASE_GROUPS.items():
|
|
36
|
+
if phase in phases:
|
|
37
|
+
return bundle
|
|
38
|
+
_die("unmapped_phase_bundle")
|
|
39
|
+
|
|
40
|
+
def _ai_freeze_allowed(gate_mode: str | None, sensitivity: str | None, autonomy: str) -> tuple[bool, str | None]:
|
|
41
|
+
"""The ai-plan-verify-gate predicate: may an AI agent perform the §3 contract freeze in
|
|
42
|
+
place of a human? PURE, fail-closed. A BLOCK-list (not an allow-list of one, human freeze
|
|
43
|
+
decision 2026-07-09): the human floor is exactly {security, data, architecture} plus a
|
|
44
|
+
malformed "?" token; undeclared sensitivity (None), the literal "mechanical" token, and any
|
|
45
|
+
other valid project-GLOSSARY class all qualify — the double opt-in (gate_mode: ai-plan-verify
|
|
46
|
+
AND autonomy: auto, both human-declared) IS the sign-off. Sibling of advisor-gate-relax (a
|
|
47
|
+
DIFFERENT, narrower allow-list-of-"mechanical" floor at the VERIFY/completion boundary) —
|
|
48
|
+
related but not identical floors, never interchangeable."""
|
|
49
|
+
if gate_mode != "ai-plan-verify":
|
|
50
|
+
return False, "ai_freeze_not_opted_in"
|
|
51
|
+
if autonomy != "auto":
|
|
52
|
+
return False, "ai_freeze_requires_auto"
|
|
53
|
+
if sensitivity in ("security", "data", "architecture"):
|
|
54
|
+
return False, "ai_freeze_blocked_sensitivity"
|
|
55
|
+
if sensitivity == "?":
|
|
56
|
+
return False, "ai_freeze_unknown_sensitivity"
|
|
57
|
+
return True, None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _skip_lane_eligible(fast: bool, oneshot: bool, benchmark_mode: bool) -> bool:
|
|
61
|
+
"""fast-lane-skips: may a task declare a scenarios/observe skip at all? PURE. True iff
|
|
62
|
+
ANY of the three independent triggers MILESTONE.md Scope(3) names is set — the existing
|
|
63
|
+
fast lane, the new --oneshot flag, or a project-wide benchmark_mode:true opt-in. Does not
|
|
64
|
+
itself validate the declared token set (see _skip_set_allowed) — single responsibility."""
|
|
65
|
+
return fast or oneshot or benchmark_mode
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _skip_set_allowed(skip_tokens: frozenset[str], eligible: bool) -> tuple[bool, str | None]:
|
|
69
|
+
"""fast-lane-skips: may THIS declared (already closed-set-validated) skip-set be honored?
|
|
70
|
+
PURE, fail-closed. `skip_tokens` is assumed already validated by _task_skip_set — this
|
|
71
|
+
predicate's single responsibility is the LANE-ELIGIBILITY axis, not set-membership (two
|
|
72
|
+
distinct error codes for two distinct failure shapes, mirrors _ai_freeze_allowed's split
|
|
73
|
+
from _task_gate_mode). An EMPTY skip-set is always permitted (nothing declared, nothing to
|
|
74
|
+
gate) regardless of eligibility."""
|
|
75
|
+
if skip_tokens and not eligible:
|
|
76
|
+
return False, "skip_lane_required"
|
|
77
|
+
return True, None
|
|
78
|
+
|
|
79
|
+
|
|
26
80
|
def _setup_locked(state: dict) -> bool:
|
|
27
81
|
"""True when the project's setup is locked — i.e. the build-boundary gate is OPEN.
|
|
28
82
|
|
|
@@ -48,7 +102,7 @@ def _section_unfilled(md_text: str, header: str) -> bool:
|
|
|
48
102
|
"""True iff the `header` section is PRESENT but UNFILLED — empty (no real bullet) or
|
|
49
103
|
still a `<…>` template placeholder. ABSENT section -> False (grandfathered legacy);
|
|
50
104
|
a filled section (>=1 real bullet, no `<…>`) -> False. Pure predicate — the shared
|
|
51
|
-
placeholder test the
|
|
105
|
+
placeholder test the contract-fill gate uses at confirm.
|
|
52
106
|
Angle brackets INSIDE a backtick code span are literal technical notation (`<persona>`,
|
|
53
107
|
`.add/personas/<slug>.md`), not a fill placeholder — only a BARE <…> counts as unfilled."""
|
|
54
108
|
body, in_sec, present = [], False, False
|
|
@@ -91,6 +145,40 @@ def _persona_missing(md_text: str) -> list[str]:
|
|
|
91
145
|
return missing
|
|
92
146
|
|
|
93
147
|
|
|
148
|
+
def _persona_quality_warnings(md_text: str) -> list[str]:
|
|
149
|
+
"""Quality findings the presence-based schema check can't see (persona-schema-hardening).
|
|
150
|
+
`[]` == clean. WARN-only at the caller (measure-not-block); never a gate. Two findings:
|
|
151
|
+
(A) a `flow:` frontmatter value outside PERSONA_FLOW_VALUES — no apply-surface loads an
|
|
152
|
+
unknown flow, so a typo otherwise fails silently; an ABSENT `flow:` line is conformant
|
|
153
|
+
(that is `_persona_missing` territory, and flow is only RECOMMENDED). (B) a bare `<…>`
|
|
154
|
+
placeholder outside backtick code spans and HTML comments — a half-filled template copy
|
|
155
|
+
passes the presence check. PURE; NO-EXEC — text in, list of strings out."""
|
|
156
|
+
findings: list[str] = []
|
|
157
|
+
fm = re.match(r"\s*---\s*\n(.*?)\n---\s*\n", md_text, re.S)
|
|
158
|
+
fm_body = fm.group(1) if fm else ""
|
|
159
|
+
m = re.search(r"(?m)^\s*flow\s*:\s*(.+)$", fm_body)
|
|
160
|
+
if m:
|
|
161
|
+
for v in (tok.strip() for tok in m.group(1).split(",")):
|
|
162
|
+
if v and v not in PERSONA_FLOW_VALUES:
|
|
163
|
+
findings.append(f"flow value '{v}' not one of " + "|".join(PERSONA_FLOW_VALUES))
|
|
164
|
+
# (C) persona-task-kinds: a `task-kinds:` value outside the closed taxonomy scores as
|
|
165
|
+
# NOTHING on the persona scoreboard (the trace join key never matches), so a typo would
|
|
166
|
+
# otherwise fail silently — same shape as Finding A; an ABSENT line is conformant.
|
|
167
|
+
m = re.search(r"(?m)^\s*task-kinds\s*:\s*(.+)$", fm_body)
|
|
168
|
+
if m:
|
|
169
|
+
for v in (tok.strip() for tok in m.group(1).split(",")):
|
|
170
|
+
if v and v not in TASK_KINDS:
|
|
171
|
+
findings.append(f"task-kinds value '{v}' not one of " + "|".join(TASK_KINDS))
|
|
172
|
+
# strip comments FIRST (a backtick inside a comment is already gone), then ```-fenced
|
|
173
|
+
# blocks (a Playbook skeleton legitimately carries <placeholder> lines), then inline spans
|
|
174
|
+
no_comments = re.sub(r"<!--.*?-->", "", md_text, flags=re.S)
|
|
175
|
+
no_fences = re.sub(r"(?ms)^```.*?^```\s*$", "", no_comments)
|
|
176
|
+
no_code = re.sub(r"`[^`\n]*`", "", no_fences)
|
|
177
|
+
for ph in re.findall(r"<[^>\n]+>", no_code):
|
|
178
|
+
findings.append(f"bare <…> placeholder remains: '{ph[:40]}'")
|
|
179
|
+
return findings
|
|
180
|
+
|
|
181
|
+
|
|
94
182
|
def _persona_slug_valid(slug: str) -> bool:
|
|
95
183
|
"""A persona file slug is valid iff non-empty and alphanumeric with `-`/`_` only
|
|
96
184
|
(mirrors new-task's slug rule). PURE; NO-EXEC."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""add_engine.search — keyword/substring search over the milestone/task corpus
|
|
2
2
|
(context-search, search-index task). A flat, keyword-only index over document
|
|
3
|
-
prose — title/goal/rationale (MILESTONE.md) or title/Feature (
|
|
3
|
+
prose — title/goal/rationale (MILESTONE.md) or title/Feature (PLAN.md) lines,
|
|
4
4
|
never full body, never graph/backlink traversal, never a persisted cache:
|
|
5
5
|
rebuilt fresh on every call (the milestone's own "rebuild-on-demand is enough"
|
|
6
6
|
scope decision). Deliberately distinct from a graph-query command over the
|
|
@@ -69,7 +69,7 @@ def _milestone_fields(text: str) -> dict[str, str]:
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def _task_fields(text: str) -> dict[str, str]:
|
|
72
|
-
"""{'title', 'feature'} from raw
|
|
72
|
+
"""{'title', 'feature'} from raw PLAN.md text — H1 title + §1 `Feature:`
|
|
73
73
|
line, continuation-folded; an unfilled `<name>`-style placeholder value
|
|
74
74
|
-> '' (never treated as matchable content). Dict INSERTION ORDER (title,
|
|
75
75
|
feature) is the snippet priority `_keyword_hit` reads. PURE; NO-EXEC."""
|
|
@@ -77,7 +77,7 @@ def _task_fields(text: str) -> dict[str, str]:
|
|
|
77
77
|
if _PLACEHOLDER_RE.match(feature.strip()):
|
|
78
78
|
feature = ""
|
|
79
79
|
return {
|
|
80
|
-
"title": _line_field(text, "#
|
|
80
|
+
"title": _line_field(text, "# PLAN:"),
|
|
81
81
|
"feature": feature,
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -108,13 +108,13 @@ def _iter_corpus(root: Path) -> Iterator[tuple[Path, str, bool]]:
|
|
|
108
108
|
"""Yield (path, kind, archived) over the 4 frozen corpus roots, in fixed
|
|
109
109
|
order: active milestones, active tasks, archived milestones, archived
|
|
110
110
|
tasks (nested under the owning milestone's compact bundle — never a flat
|
|
111
|
-
`archive/*/
|
|
111
|
+
`archive/*/PLAN.md`). A missing root dir yields nothing — `Path.glob` on
|
|
112
112
|
an absent directory is empty, never an OSError."""
|
|
113
113
|
for pattern, kind, archived in (
|
|
114
114
|
(f"milestones/*/{MILESTONE_FILE}", "milestone", False),
|
|
115
|
-
("tasks/*/
|
|
115
|
+
("tasks/*/PLAN.md", "task", False),
|
|
116
116
|
(f"archive/*/{MILESTONE_FILE}", "milestone", True),
|
|
117
|
-
("archive/*/tasks/*/
|
|
117
|
+
("archive/*/tasks/*/PLAN.md", "task", True),
|
|
118
118
|
):
|
|
119
119
|
for p in sorted(root.glob(pattern)):
|
|
120
120
|
yield p, kind, archived
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""add_engine.taskdoc —
|
|
1
|
+
"""add_engine.taskdoc — PLAN.md structural readers (engine-modularization 15/N).
|
|
2
2
|
|
|
3
3
|
Header, prose, test-count, phase-span, raw phase bodies, and the §7 spec-delta entries —
|
|
4
4
|
the pure parsers that read a task document's shape. A closed, unpatched cluster
|
|
@@ -14,12 +14,17 @@ from pathlib import Path
|
|
|
14
14
|
from add_engine.constants import _DELTA_RE, _EVIDENCE_RE, _SPEC_DELTA_RE
|
|
15
15
|
from add_engine.components import _confined
|
|
16
16
|
|
|
17
|
+
# The ONE canonical §-heading scanner (`^##\s*<n>\s*·`). Static pattern hoisted to
|
|
18
|
+
# module load — _phase_spans is on the busiest task-doc read path (check/status/gate),
|
|
19
|
+
# so it never re-compiles per call. Behaviour byte-identical.
|
|
20
|
+
_HEADING_RE = re.compile(r"^##\s*(\d+)\s*·")
|
|
21
|
+
|
|
17
22
|
|
|
18
23
|
def _task_header(root: Path, slug: str) -> str:
|
|
19
|
-
"""The
|
|
24
|
+
"""The PLAN.md header region — where declared tokens (risk · autonomy)
|
|
20
25
|
live — with HTML comments stripped. Missing file -> '' (no tokens)."""
|
|
21
26
|
try:
|
|
22
|
-
text = (root / "tasks" / slug / "
|
|
27
|
+
text = (root / "tasks" / slug / "PLAN.md").read_text(encoding="utf-8")
|
|
23
28
|
except OSError:
|
|
24
29
|
return ""
|
|
25
30
|
return re.sub(r"<!--.*?-->", "", text.split("\n## ", 1)[0], flags=re.S)
|
|
@@ -97,11 +102,11 @@ def _tests_info(root: Path, slug: str) -> tuple[int, bool]:
|
|
|
97
102
|
return (declared, True) if declared > 0 else (0, False)
|
|
98
103
|
|
|
99
104
|
def _task_prose(root: Path, slug: str) -> tuple[str, list[str]]:
|
|
100
|
-
"""(observe_delta, [delta lines]) from the task's
|
|
105
|
+
"""(observe_delta, [delta lines]) from the task's PLAN.md §7 — captured at FULL
|
|
101
106
|
fidelity: both fields wrap across physical lines in real files, so continuation
|
|
102
107
|
lines are JOINED. Scoped to the OBSERVE section so we read the FIELD, not §1 prose
|
|
103
108
|
that names it. Fail-closed to '(unknown)' on a missing file / `<...>` placeholder."""
|
|
104
|
-
f = root / "tasks" / slug / "
|
|
109
|
+
f = root / "tasks" / slug / "PLAN.md"
|
|
105
110
|
if not f.exists():
|
|
106
111
|
return "(unknown)", []
|
|
107
112
|
text = f.read_text(encoding="utf-8")
|
|
@@ -157,14 +162,14 @@ def _task_prose(root: Path, slug: str) -> tuple[str, list[str]]:
|
|
|
157
162
|
return observe, deltas
|
|
158
163
|
|
|
159
164
|
def _phase_spans(text: str) -> dict[int, str]:
|
|
160
|
-
"""Split a
|
|
165
|
+
"""Split a PLAN.md into RAW §1–§7 bodies keyed by section number — the ONE
|
|
161
166
|
canonical heading scan (`^##\\s*<n>\\s*·`, case/locale-proof); a body runs from
|
|
162
167
|
its heading to the next `## `/`---`/EOF. RAW = byte-faithful lines, no cleaning:
|
|
163
168
|
the decision-marker extractor (decide-digest) depends on byte-verbatim text.
|
|
164
169
|
KNOWN LIMIT: a §body containing a line-start `## ` or bare `---` truncates early —
|
|
165
|
-
today's
|
|
170
|
+
today's PLAN.md bodies don't (box-chars ─═, `### ` sub-heads)."""
|
|
166
171
|
lines = text.splitlines()
|
|
167
|
-
head =
|
|
172
|
+
head = _HEADING_RE
|
|
168
173
|
starts: dict[int, int] = {}
|
|
169
174
|
for idx, ln in enumerate(lines):
|
|
170
175
|
m = head.match(ln)
|
|
@@ -184,8 +189,8 @@ def _phase_spans(text: str) -> dict[int, str]:
|
|
|
184
189
|
|
|
185
190
|
def _raw_phase_bodies(root: Path, slug: str) -> dict[int, str]:
|
|
186
191
|
"""RAW §bodies for one task (byte-faithful, for marker extraction). PURE.
|
|
187
|
-
Missing/unreadable
|
|
188
|
-
f = root / "tasks" / slug / "
|
|
192
|
+
Missing/unreadable PLAN.md -> {} (fail-closed, like task_phases)."""
|
|
193
|
+
f = root / "tasks" / slug / "PLAN.md"
|
|
189
194
|
try:
|
|
190
195
|
return _phase_spans(f.read_text(encoding="utf-8"))
|
|
191
196
|
except OSError:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# CONVENTIONS (living documentation — set once, kept for the whole project)
|
|
2
2
|
|
|
3
3
|
Language/framework: <e.g. Python 3.12 / FastAPI>
|
|
4
|
-
Folders: .add/tasks/<slug>/{
|
|
4
|
+
Folders: .add/tasks/<slug>/{PLAN.md, tests/, src/}
|
|
5
5
|
Naming: <file case>, <type case>, verbs for functions
|
|
6
6
|
Lint/format: <tools>, enforced in CI
|
|
7
7
|
Errors: machine-readable error codes (string enums), never free text
|
|
@@ -9,6 +9,9 @@ DOMAIN classes (the AI keeps these current as the domain is learned — see the
|
|
|
9
9
|
|
|
10
10
|
Base (always apply): security · data · architecture · mechanical
|
|
11
11
|
|
|
12
|
+
Datetime, money, or timezone arithmetic ⇒ declare `data` (full lane) — value formats are the
|
|
13
|
+
risk surface (a naive-timestamp suite stays green through an aware-timestamp crash: bench wm2).
|
|
14
|
+
|
|
12
15
|
<!-- Domain classes — uncomment and add yours, one `- <token>: <definition>` bullet per line:
|
|
13
16
|
- pii: personally identifiable information; any task touching it escalates to human review
|
|
14
17
|
- payments: money movement; reconciliation + an audit trail are required
|
|
@@ -36,7 +39,7 @@ failing-first suite: the test suite written before code, confirmed red for the r
|
|
|
36
39
|
non-functional review: the deliberate post-evidence check of what tests rarely catch — concurrency, security, architecture (formerly "blind-spot checks").
|
|
37
40
|
change scope: the files a locked run may and may not touch (formerly "touch-boundary"; the <touch_boundary> XML prompt tag keeps its name).
|
|
38
41
|
automated quality gate: the evidence-based Verify resolver under autonomy auto — may auto-PASS on complete evidence; security always escalates (formerly "evidence auto-gate").
|
|
39
|
-
autonomy level: the explicit per-task Verify resolver setting on an ordered ladder — manual | conservative | auto (manual < conservative < auto): auto (the seeded default) 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); declared in the
|
|
42
|
+
autonomy level: the explicit per-task Verify resolver setting on an ordered ladder — manual | conservative | auto (manual < conservative < auto): auto (the seeded default) 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); declared in the PLAN.md header, human-reviewed at the freeze (formerly "autonomy dial").
|
|
40
43
|
auto-ready goal: a milestone goal whose every exit criterion CITES a verifier `(verify: <test|command|metric>)` — so the engine can self-verify the result against the goal without human judgement. This is the prerequisite by which autonomy is EARNED by goal-clarity (the autonomy level then governs who resolves Verify, but a clarified goal is what makes a self-verifying run meaningful). `add.py check` WARNs (never red) the active milestone until its goal is auto-ready (total >= 1 AND every criterion cited) and `status` surfaces it (`goal-ready: auto-ready ✓` / cited-of-total); a zero-criteria goal is NOT auto-ready and is milestone-shaping's nudge, not this WARN's. The lint forces a citation slot per criterion (raising the floor) but cannot PROVE the citation is real — the human still owns whether it is honest (citation-theater is the accepted, irreducible floor; the freeze gate and autonomy behavior are unchanged by it).
|
|
41
44
|
living documentation: the durable project artifacts — conventions, glossary, frozen contracts — that outlive any particular code (formerly "survivor layer").
|
|
42
45
|
scope level: the granularity a decision lives at — intake level (request -> versioned scope), milestone level, setup/foundation level, task level (formerly "altitude").
|
|
@@ -44,11 +47,14 @@ baseline approval: the one human gate that freezes the AI-drafted foundation, fi
|
|
|
44
47
|
lesson learned: a single learning a loop produces, tagged by the competency it improves — the `- [DDD · open]` grammar and deltas.md/`add.py deltas` machine names stay (formerly "competency delta").
|
|
45
48
|
lowest-confidence flag: the AI's ranked declaration of the 1–2 points most likely to be wrong in what a human is asked to approve — each with why + cost-if-wrong; the ⚠ glyph keeps its name as the machine marker (formerly "least-sure flag").
|
|
46
49
|
decision point: a stop for human judgment — the contract-freeze approval, an escalated verify gate, intake confirmation, milestone close; the machine names seam (--json owner enum, decide key) and seam-audit (CI job) keep their names (formerly "seam").
|
|
47
|
-
|
|
50
|
+
depends-on: a DECLARED, BLOCKING relation — a task/milestone that must be DONE before this one starts; drives the wave schedule (the only relation the scheduler reads). Declared via `new-task --depends-on` (task) or a MILESTONE.md header `depends-on:` line (milestone).
|
|
51
|
+
extends: a DECLARED, NON-BLOCKING relation — this task/milestone builds on a prior one's shipped surface; a legibility edge, never gates the schedule. Declared via `new-task --extends` (task) or a MILESTONE.md header `extends:` line (milestone).
|
|
52
|
+
relates-to: a DECLARED, NON-BLOCKING relation — this task/milestone shares context with another (informational); never gates the schedule. Declared via `new-task --relates-to` (task) or a MILESTONE.md header `relates-to:` line. All three relations are validated (dangling/self) by `add.py check` + `status`, never inferred.
|
|
53
|
+
retrospective consolidation: lessons learned land in-flight in the living specs (`add.py delta-append`); the human confirms what binds — never self-approved; the `folded` status and `add.py deltas` keep their names (formerly "the fold / fold ritual").
|
|
48
54
|
specification bundle: a task's spec, scenarios, contract, and failing tests drafted as one piece and approved by a person once at the contract freeze (formerly "the one-approval front").
|
|
49
|
-
foundation compaction: the retrospective shrink — collapse a
|
|
55
|
+
foundation compaction: the retrospective shrink — collapse a living spec's stable, shipped, zero-residue tail into one rolled-up settled line; the AI proposes and the human confirms; summarize and point, never delete.
|
|
50
56
|
rolled-up settled line: the single line left when a stable run is collapsed — lossy on prose, lossless on traceability (carries a `see git` pointer).
|
|
51
57
|
per-spec shape: each foundation spec's own tailored rolled-line format (PROJECT §Spec bullets · §Key-Decisions rows · CONVENTIONS learnings · GLOSSARY definition · MODEL_REGISTRY rows), all sharing one eligibility rule: shipped + zero open residues.
|
|
52
58
|
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.
|
|
53
59
|
ship review: the whole-milestone, cross-task evidence the AI fills at milestone close (ship-by-domain · cross-task evidence · goal-met map), read before the exit-criteria boxes are checked — evidence, not a new gate; the ritual lives in the `add` skill's loop.md.
|
|
54
|
-
release steps: the AI-defined, per-milestone ordered hints to ship a closed milestone, merge being one small step (a PR · an exported hand-off doc · a tag or publish are others); defined at close
|
|
60
|
+
release steps: the AI-defined, per-milestone ordered hints to ship a closed milestone, merge being one small step (a PR · an exported hand-off doc · a tag or publish are others); defined at close; the human runs the cut, never a second flow.
|
|
@@ -3,23 +3,29 @@
|
|
|
3
3
|
goal: {{goal}}
|
|
4
4
|
rationale: <why this scope — the confirmed intake classification (bucket + reason)>
|
|
5
5
|
stage: {{stage}} · status: active · created: {{date}}
|
|
6
|
-
|
|
6
|
+
relations: <cross-MILESTONE edges — `depends-on:` / `extends:` / `relates-to:` header lines, comma-sep slugs; omit if none; `add.py check` validates>
|
|
7
7
|
|
|
8
8
|
> SDD living doc for this milestone. Keep it THIN: breadth, shared decisions, and
|
|
9
|
-
> exit criteria only — per-task detail lives in each `.add/tasks/<slug>/
|
|
9
|
+
> exit criteria only — per-task detail lives in each `.add/tasks/<slug>/PLAN.md`,
|
|
10
10
|
> written just-in-time. Update this doc whenever a task reveals a milestone gap.
|
|
11
11
|
|
|
12
12
|
## Scope
|
|
13
13
|
In: <what this milestone delivers>
|
|
14
14
|
Out: <explicitly deferred — the anti-scope-creep list>
|
|
15
15
|
|
|
16
|
-
> UI/UX in scope? Name it precisely, not "make it nice" —
|
|
17
|
-
>
|
|
18
|
-
>
|
|
19
|
-
>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
> UI/UX in scope? Name it precisely, not "make it nice" — IA · interaction pattern ·
|
|
17
|
+
> visual hierarchy · tokens · component states · WCAG AA · breakpoints · user journey.
|
|
18
|
+
> Skip generic AI-design defaults; name ONE deliberate signature element. A UI feature
|
|
19
|
+
> also triggers DESIGN.md via the `add` skill's design.md.
|
|
20
|
+
|
|
21
|
+
## Ground (shared real-code context — gathered ONCE; every task's specify projects from this)
|
|
22
|
+
Touches (shared files · symbols): <the code every task in this milestone lands in — gathered once, task-delta>
|
|
23
|
+
Anchors: <the shared symbols tasks may cite — the floor each task's contract builds on>
|
|
24
|
+
Honors (conventions): <PROJECT.md · CONVENTIONS.md · SEAMS.md rules every task honors>
|
|
25
|
+
Issues/Risks (shared): <traps in the shared code that feed each task's §1 expectations>
|
|
26
|
+
|
|
27
|
+
> Gathered ONCE per milestone (`scope.md`); each task's specify PROJECTS its §1 from
|
|
28
|
+
> here + the specific request — light, never re-grounded per task.
|
|
23
29
|
|
|
24
30
|
## Shared decisions & glossary deltas (living — every task must honor these)
|
|
25
31
|
- <cross-cutting rule, named from GLOSSARY.md>
|
|
@@ -27,16 +33,15 @@ Out: <explicitly deferred — the anti-scope-creep list>
|
|
|
27
33
|
## Shared / risky contracts (freeze these first)
|
|
28
34
|
- <contract name> -> owning task <slug>
|
|
29
35
|
|
|
30
|
-
## Tasks (breadth-first decomposition; detail lives in each
|
|
36
|
+
## Tasks (breadth-first decomposition; detail lives in each PLAN.md)
|
|
31
37
|
- [ ] <slug> depends-on: none — <one line>
|
|
32
38
|
- [ ] <slug> depends-on: <slug> — <one line>
|
|
33
39
|
|
|
34
40
|
## Exit criteria (observable; map each to the task that delivers it)
|
|
35
|
-
- [ ] User can <observable behavior> (← <slug>)
|
|
41
|
+
- [ ] User can <observable behavior — the SEEN outcome only, NOT the task's plan line> (← <slug>)
|
|
36
42
|
|
|
37
43
|
## Close — ship review (AI fills when every task is done — the evidence behind the engine gate, read before the boxes are checked)
|
|
38
|
-
>
|
|
39
|
-
> gate (milestone-done / checking the Exit-criteria boxes) — NOT a new approval. Tool-agnostic.
|
|
44
|
+
> Cross-task review the AI fills — the evidence behind the EXISTING milestone-done gate, NOT a new approval.
|
|
40
45
|
|
|
41
46
|
### Ship by domain (what changed, per bounded context)
|
|
42
47
|
- tooling : <add.py / state.json / templates — what shipped, or "untouched">
|
|
@@ -51,8 +56,7 @@ Out: <explicitly deferred — the anti-scope-creep list>
|
|
|
51
56
|
- goal: <restate the milestone goal — and the one evidence line that proves the ship meets it>
|
|
52
57
|
|
|
53
58
|
## Release steps (AI-DEFINED — fill the ordered steps to ship this milestone; engine records, human gate)
|
|
54
|
-
>
|
|
55
|
-
> small step among them. These feed the release scope (release.md) when the cut is bundled.
|
|
59
|
+
> AI-written steps for THIS milestone (hints, not engine commands); MERGE is one small step; the human runs the cut.
|
|
56
60
|
- [ ] <step — e.g. open a PR from the Close ship-review above; the human reviews + merges>
|
|
57
61
|
- [ ] <step — e.g. export the ship-review to a hand-off doc, e.g. `pandoc CLOSE.md -o close.docx`>
|
|
58
|
-
- [ ] <step — e.g. tag / publish / deploy (human-run
|
|
62
|
+
- [ ] <step — e.g. tag / publish / deploy (human-run)>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# PLAN: {{title}}
|
|
2
|
+
|
|
3
|
+
slug: {{slug}} · created: {{date}} · stage: {{stage}}
|
|
4
|
+
milestone: {{milestone}}
|
|
5
|
+
autonomy: {{autonomy}} <!-- manual<conservative<auto — lower for high-risk (`add.py autonomy set`); a `component: <name>` line joins that root to §3 Scope; task edges: `--depends-on`/`--extends`/`--relates-to`; high-risk/method-defining? declare `risk: high` on the slug line; headless agent-crossed freeze? declare `gate_mode: ai-plan-verify` here (human floor: security|data|architecture never AI-frozen) -->
|
|
6
|
+
phase: direction <!-- direction→build→verify→done; direction drafts §1–§4 (rules · scenarios · change plan · red suite) to the ONE freeze -->
|
|
7
|
+
|
|
8
|
+
> One file = one task — an ATOMIC node: persist the interface (contract · red suite · scope · verdict); reason everything else in-context, don't write essays. The phase marker above is the single source of truth (`add.py phase`).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1 · SPECIFY — the rules ▸ docs/03-step-1-specify.md
|
|
13
|
+
|
|
14
|
+
Feature: <name — one-line description of the behavior this task ships>
|
|
15
|
+
Framings weighed: <chosen> (chosen — why) · <alternative>
|
|
16
|
+
Must:
|
|
17
|
+
<must>
|
|
18
|
+
- <required behavior>
|
|
19
|
+
</must>
|
|
20
|
+
Reject:
|
|
21
|
+
<reject>
|
|
22
|
+
- <bad input / situation> -> "<error_code>"
|
|
23
|
+
</reject>
|
|
24
|
+
After:
|
|
25
|
+
<after>
|
|
26
|
+
- <state that is true once it succeeds>
|
|
27
|
+
</after>
|
|
28
|
+
Boundary: <one format-variant per external input shape the tests must speak — e.g. aware vs naive timestamp · or "none — no external input">
|
|
29
|
+
<assumptions>
|
|
30
|
+
⚠ <the ONE assumption most likely to be wrong — if wrong: <cost>>
|
|
31
|
+
</assumptions>
|
|
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. -->
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3 · PLAN — the change plan: ground · contract · build-strategy ▸ docs/05-step-3-plan.md
|
|
44
|
+
|
|
45
|
+
### Contract (freeze the shape — the HARD, tamper-guarded core; ground it in the REAL code in-context, cite symbols not line numbers)
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
<METHOD> <path> body: { <fields> }
|
|
49
|
+
200 -> { <success fields> }
|
|
50
|
+
4xx -> { error: "<code>" | "<code>" }
|
|
51
|
+
Schema: <tables/fields touched, and access pattern>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Target (measurable): <the success bar §6 evidence must hit — numbers, not adjectives — judged at the gate via --target-hit; name any outcome tests can't show (boots · renders) + how it's confirmed>
|
|
55
|
+
Status: DRAFT
|
|
56
|
+
Reported: <yes — the freeze report (banner/ARC/SHAPE) rendered before this froze | no>
|
|
57
|
+
|
|
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>
|
|
62
|
+
|
|
63
|
+
### AI-verify record (required when gate_mode: ai-plan-verify)
|
|
64
|
+
- [ ] §3 PLAN grounding anchors resolve in the current tree
|
|
65
|
+
- [ ] §1 every Must + every Reject present, each Reject paired with an error code
|
|
66
|
+
- [ ] §3 Contract shape is concrete (no template placeholder text remains)
|
|
67
|
+
- [ ] Lowest-confidence flag surfaced and substantive (mirrors unflagged_freeze's own bar)
|
|
68
|
+
Verified by: <agent-id> · at: <ISO-8601 UTC timestamp>
|
|
69
|
+
|
|
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). -->
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 4 · TESTS — failing-first suite or acceptance checks (red) ▸ docs/06-step-4-tests.md
|
|
75
|
+
|
|
76
|
+
<test_plan>
|
|
77
|
+
- test_<name>: arrange / act / assert behavior not internals · covers: <M#, R:code>
|
|
78
|
+
</test_plan>
|
|
79
|
+
|
|
80
|
+
Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
|
|
81
|
+
<!-- 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
|
+
<!-- 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. -->
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 5 · BUILD — AI writes the code (execution) ▸ docs/07-step-5-build.md
|
|
87
|
+
|
|
88
|
+
Strategy actually used: <fill at VERIFY — what you ACTUALLY did (or "as planned"); harvested into §7 Decisions (ADR)>
|
|
89
|
+
Code lives in: `./src/`
|
|
90
|
+
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
|
+
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
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 6 · VERIFY — evidence + non-functional review ▸ docs/08-step-6-verify.md
|
|
96
|
+
|
|
97
|
+
- [ ] all tests (or §4 acceptance checks) pass — including the §3 Regression floor (host suite)
|
|
98
|
+
- [ ] coverage did not decrease
|
|
99
|
+
- [ ] no test or contract was altered during build
|
|
100
|
+
- [ ] the green was EARNED, not gamed — no overfit to fixtures, vacuous asserts, or stubbed-away logic (a confirmed cheat is HARD-STOP)
|
|
101
|
+
- [ ] concurrency / timing of the risky operation is safe
|
|
102
|
+
- [ ] no exposed secrets, injection openings, or unexpected dependencies
|
|
103
|
+
- [ ] layering & dependencies follow CONVENTIONS.md
|
|
104
|
+
- [ ] a person reviewed and approved the change
|
|
105
|
+
|
|
106
|
+
### Refute-read verdict — the earned-green check (record it; required for an auto-PASS)
|
|
107
|
+
Verdict: <EARNED | NOT-EARNED>
|
|
108
|
+
By: <self | agent-id> · adversarially checked: <what was probed>
|
|
109
|
+
|
|
110
|
+
### GATE RECORD
|
|
111
|
+
Reported: <yes — the gate report (banner/ARC) rendered before this outcome recorded | no>
|
|
112
|
+
Outcome: <PASS | RISK-ACCEPTED | HARD-STOP>
|
|
113
|
+
If RISK-ACCEPTED -> owner: <name> · ticket: <link> · expires: <date> (never for a security gap)
|
|
114
|
+
Reviewed by: <name> · date: <date>
|
|
115
|
+
|
|
116
|
+
<!-- Security is ALWAYS HARD-STOP; record exactly one outcome — no silent pass. The Refute-read verdict is recorded, never engine-blocked; a human spot-audit backstops anything unrecorded. -->
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 7 · OBSERVE — feed the next loop ▸ docs/09-the-loop.md
|
|
121
|
+
|
|
122
|
+
### Decisions (ADR)
|
|
123
|
+
<harvested at done from §1/§3/§5/§6 — do not hand-edit; one actor-tagged line per decision, refilled only while this placeholder stands>
|
|
124
|
+
|
|
125
|
+
### Spec delta
|
|
126
|
+
One line per forward change, tagged `[SPEC · open|seeded|dropped]` + evidence — each re-enters at Specify (`deltas.md`).
|
|
127
|
+
|
|
128
|
+
### Competency deltas
|
|
129
|
+
One lesson per line: `[DDD|SDD|UDD|TDD|ADD · open] the learning (evidence: …)` — see `deltas.md`.
|
|
@@ -7,13 +7,16 @@
|
|
|
7
7
|
> that is the re-entrant arrow from the engine down to the foundation.
|
|
8
8
|
|
|
9
9
|
slug: {{project}} · stage: {{stage}} · updated: {{date}}
|
|
10
|
-
autonomy: auto <!-- project default — new tasks inherit this rung (manual < conservative < auto); lower a single task in its
|
|
10
|
+
autonomy: auto <!-- project default — new tasks inherit this rung (manual < conservative < auto); lower a single task in its PLAN.md header when it needs a human gate. -->
|
|
11
11
|
goal:
|
|
12
12
|
<!-- set at setup — the outcome this project runs toward; status/guide surface it every session -->
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
## Domain (DDD) — the language and the boundaries
|
|
17
|
+
invariants:
|
|
18
|
+
<!-- never deferred — pin the run/entry contract + the one domain invariant at setup; every task §0 re-states these -->
|
|
19
|
+
<!-- living: fill on first touch — grown by milestones/tasks; lessons land in .add/specs/ via `add.py delta-append` -->
|
|
17
20
|
<!-- One name per concept (the GLOSSARY holds the full term list). What are the
|
|
18
21
|
core entities, the bounded contexts/modules, and the invariants that MUST
|
|
19
22
|
always hold? This is the ubiquitous language the spec and code share. -->
|
|
@@ -22,6 +25,7 @@ goal:
|
|
|
22
25
|
- Invariants that must always hold:
|
|
23
26
|
|
|
24
27
|
## Spec / Living Document (SDD) — what we are building, now
|
|
28
|
+
<!-- living: fill on first touch — grown by milestones/tasks; lessons land in .add/specs/ via `add.py delta-append` -->
|
|
25
29
|
<!-- The spec is a living document, not a frozen plan. This section POINTS to the
|
|
26
30
|
active milestone + frozen contracts; it does not duplicate them. -->
|
|
27
31
|
- Active milestone → `.add/milestones/<slug>/MILESTONE.md` (see `add.py status`)
|
|
@@ -29,6 +33,7 @@ goal:
|
|
|
29
33
|
- Settled vs still open: <frozen contracts · open questions>
|
|
30
34
|
|
|
31
35
|
## Users (UDD) — UI/UX: design before code
|
|
36
|
+
<!-- living: fill on first touch — grown by milestones/tasks; lessons land in .add/specs/ via `add.py delta-append` -->
|
|
32
37
|
<!-- UI/UX-Driven Development: users use the interface, not the spec. Capture the
|
|
33
38
|
experience BEFORE code. Per-feature design detail lives in a DESIGN.md /
|
|
34
39
|
clickable prototype; keep here the cross-cutting UX stance + the source of truth.
|