@pilotspace/add 1.15.0 → 1.16.1
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 +64 -0
- package/README.md +71 -4
- package/agents/add-advisor.md +31 -0
- package/agents/add-build.md +29 -0
- package/agents/add-design.md +32 -0
- package/agents/add-persona.md +31 -0
- package/agents/add-verify.md +34 -0
- package/bin/cli.js +581 -46
- package/docs/08-step-6-verify.md +7 -0
- package/package.json +2 -1
- package/skill/add/SKILL.md +3 -2
- package/skill/add/advisor.md +2 -0
- package/skill/add/loop.md +2 -2
- package/skill/add/phases/0-setup.md +3 -3
- package/skill/add/phases/3-contract.md +1 -1
- package/skill/add/phases/6-verify.md +3 -1
- package/skill/add/report-template.md +1 -0
- package/skill/add/run.md +1 -0
- package/skill/add/streams.md +7 -0
- package/tooling/add.py +103 -4
- package/tooling/add_engine/constants.py +9 -0
- package/tooling/add_engine/io_state.py +14 -0
- package/tooling/templates/TASK.md.tmpl +3 -0
- package/tooling/templates/personas/_template.md.tmpl +22 -2
package/docs/08-step-6-verify.md
CHANGED
|
@@ -31,9 +31,15 @@ Before the build, the task pre-declares its **build expectations** — the obser
|
|
|
31
31
|
- [ ] Coverage did not decrease.
|
|
32
32
|
- [ ] No test or contract was altered during the build.
|
|
33
33
|
- [ ] Every pre-declared build expectation is confirmed by real evidence (not merely a green test).
|
|
34
|
+
- [ ] Every §1 Must/Reject rule still traces to a §2 scenario tag or a §4 `covers:` line.
|
|
35
|
+
- [ ] Every symbol §3's contract cites still resolves in the *current* tree, not just at Ground SHA.
|
|
34
36
|
|
|
35
37
|
If any of these is false, stop here and return to the build; there is nothing to verify yet.
|
|
36
38
|
|
|
39
|
+
A green suite proves the tests you wrote pass — it says nothing about a rule you never wired a scenario or test to at all. That is a different failure than the ones Part four covers: not a cheated pass, but a rule with no trace of ever being checked. `add.py check` scans every task project-wide for exactly this — a §1 Must or Reject with no §2 scenario tag and no §4 `covers:` line — but that sweep is easy to forget to run. `add.py audit`'s `rule_coverage_gap` notice surfaces the SAME check for the task actually at this gate, the moment it reaches Verify (a task that never adopted the tag convention at all is silently grandfathered, never retro-flagged). Neither blocks the gate; both make an already-real gap impossible to miss.
|
|
40
|
+
|
|
41
|
+
The second check guards against a quieter kind of drift. §0's Ground SHA anchors the symbols §3's contract cites to the commit they were read at — but code moves *during* the build that follows. Before this gate, re-resolve every one of those symbols against the tree as it stands now, not the Ground SHA snapshot, and record it in §6's **Live-verify evidence** block. A moved or renamed anchor caught here is a one-line note; the same drift left silent is a future reader chasing a line number that no longer means what it did at Ground.
|
|
42
|
+
|
|
37
43
|
## Part two — check what tests miss
|
|
38
44
|
|
|
39
45
|
Automated tests are excellent at behavior on defined inputs and poor at a few specific things. Check those by hand, every time:
|
|
@@ -77,6 +83,7 @@ A security finding is always a `HARD-STOP`; it is never waved through with a wai
|
|
|
77
83
|
|
|
78
84
|
- [ ] All tests pass (the evidence).
|
|
79
85
|
- [ ] Every pre-declared build expectation is confirmed by observable evidence.
|
|
86
|
+
- [ ] Every §1 rule traces to §2/§4; every §3-cited symbol still resolves in the current tree.
|
|
80
87
|
- [ ] Concurrency/timing of the risky operation is safe.
|
|
81
88
|
- [ ] No exposed secrets, injection openings, or unexpected dependencies.
|
|
82
89
|
- [ ] Layering and dependencies follow `CONVENTIONS.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilotspace/add",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"description": "ADD (AI-Driven Development). One skill. Eight steps. Five disciplines. Every feature ships through the loop — a minimal, state-tracked Claude Code skill that ships the AIDD book as its trust layer.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"add": "bin/cli.js"
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"bin/",
|
|
20
20
|
"skill/",
|
|
21
|
+
"agents/",
|
|
21
22
|
"tooling/add.py",
|
|
22
23
|
"tooling/add_engine/",
|
|
23
24
|
"!tooling/add_engine/__pycache__",
|
package/skill/add/SKILL.md
CHANGED
|
@@ -120,8 +120,9 @@ once the human confirms, rewrites `SOUL.md` (the human is the only writer) — `
|
|
|
120
120
|
|
|
121
121
|
- **§3 CONTRACT FROZEN** → build→verify is a dynamic, auto-gated run (`autonomy: auto` default; lower to
|
|
122
122
|
`conservative`/`manual` for a human gate) — `run.md`. Pipeline ready tasks behind frozen
|
|
123
|
-
contracts — `streams.md`. Delegate one piece of your plan to a subagent
|
|
124
|
-
|
|
123
|
+
contracts — `streams.md`. Delegate one piece of your plan to a subagent — prefer the named roster
|
|
124
|
+
(`add-design`/`add-build`/`add-verify`/`add-persona`/`add-advisor`) over an ad-hoc spawn; when to
|
|
125
|
+
spawn, the prompt template, the tier — `advisor.md`. Self-score a draft (0–1 across six dimensions, refine if any < 0.9)
|
|
125
126
|
— `confidence.md`. Both advisory; the engine never spawns.
|
|
126
127
|
- **Small, low-risk task**, less ceremony → the **fast lane**: `new-task --fast` scaffolds the minimal
|
|
127
128
|
`TASK.fast.md`, bundle approved in one freeze — `phases/fast-lane.md`. Floor held (frozen contract ·
|
package/skill/add/advisor.md
CHANGED
|
@@ -11,6 +11,8 @@ Spawn when the piece is **separable and worth the round-trip**: a broad sweep; a
|
|
|
11
11
|
|
|
12
12
|
Do **not** spawn for narrow, cheap work — pay the round-trip only when the piece is big or independent enough. When in doubt, do it in-context.
|
|
13
13
|
|
|
14
|
+
**Prefer the named roster.** Check first whether the piece fits one of ADD's five phase specialists — `add-design` (setup–contract) · `add-build` (tests·build) · `add-verify` (verify·observe) · `add-persona` (fit-persona selection) · `add-advisor` (a consultative second opinion) — before an ad-hoc spawn; each already carries the worker contract below.
|
|
15
|
+
|
|
14
16
|
## The 3-lens sequential checklist at verify
|
|
15
17
|
|
|
16
18
|
At Verify, sweep security → concurrency → architecture in order. **Security HARD-STOP ends the checklist** (leave the rest blank). Each lens returns: **CLEAR** · **HARD-STOP** (security only) · **RESIDUE** (concurrency/architecture).
|
package/skill/add/loop.md
CHANGED
|
@@ -50,8 +50,8 @@ When every task is done but the goal is not, `add.py status` shows
|
|
|
50
50
|
- **Define the release steps** — write the milestone's `## Release steps` (merge is one small
|
|
51
51
|
step among them; PR, asset export, tag/publish are others). These **feed** the release scope —
|
|
52
52
|
read `release.md` for the cut; loop.md never re-specifies it.
|
|
53
|
-
Present the close via `report-template.md` — open with the ARC (goal · done · plan)
|
|
54
|
-
render as a guided choice
|
|
53
|
+
Present the close via `report-template.md` — open with the ARC (goal · done · plan),
|
|
54
|
+
render as a guided choice — **before `milestone-done`/`archive-milestone` run, not after.**
|
|
55
55
|
|
|
56
56
|
## Reopen is the verb; this loop is the trigger
|
|
57
57
|
|
|
@@ -47,12 +47,12 @@ Ask only the live ones. Rank: `⚠ <assumption> — lowest confidence because <w
|
|
|
47
47
|
|
|
48
48
|
Capture each surfaced decision as an **ADR** in `PROJECT.md` **Key Decisions** as it lands.
|
|
49
49
|
|
|
50
|
-
**Under `autonomy: auto`, auto-complete all four drives in one pass** — lowest-confidence-first. This deepens **drafting**, never the gate
|
|
50
|
+
**Under `autonomy: auto`, auto-complete all four drives in one pass** — lowest-confidence-first. This deepens **drafting**, never the gate — `lock` stays the one decision.
|
|
51
51
|
|
|
52
52
|
## 3 · Draft to the lock (both paths)
|
|
53
53
|
|
|
54
54
|
1. **Fill the living documentation**: `.add/PROJECT.md` (Domain · Spec · UI/UX · Key Decisions), `CONVENTIONS.md`, `GLOSSARY.md`, `MODEL_REGISTRY.md`, `dependencies.allowlist`, and — for a UI project — `DESIGN.md` (delete if no UI; `design.md`). Brownfield: from code. Greenfield: from interview, gaps flagged `guessed`.
|
|
55
|
-
- **Seed personas** (`.add/personas/`): `init` scaffolds `_template.md` (the schema). Author one per role from PROJECT.md + the vendored teacher library `.add/personas-teacher/` (read off-build; engine never fetches)
|
|
55
|
+
- **Seed personas** (`.add/personas/`): `init` scaffolds `_template.md` (the schema). **Author one per role** from PROJECT.md + the vendored teacher library `.add/personas-teacher/` (read off-build; engine never fetches) — citing the teacher in `source:` and carrying its top `## Playbook` are the two optional parts, not the authoring. Covered by the **baseline approval**; `add.py check` validates; never clobber. Still unseeded later? `status`/`check`/`new-milestone`'s hint means: return here, draft every missing project persona — not just one.
|
|
56
56
|
2. **Propose, then size it.** Float a **kickoff suggestion** for the first milestone: a **goal** (one sentence), a **flow** (task order), **scenarios** (examples of what ships). Not the frozen `MILESTONE.md`. On their reaction, draft `MILESTONE.md` (read `scope.md`).
|
|
57
57
|
3. **Create the first task and draft its candidate specification bundle.** `new-task` is allowed pre-lock:
|
|
58
58
|
```bash
|
|
@@ -76,7 +76,7 @@ Floor: **one human approval per contract**.
|
|
|
76
76
|
|
|
77
77
|
## 4 · The one human gate — the baseline approval
|
|
78
78
|
|
|
79
|
-
Open the report with the ARC per `report-template.md`, render SHAPE then APPROVE as a guided choice, then present `SETUP-REVIEW.md` lowest-confidence-first. They confirm **once** — an explicit yes
|
|
79
|
+
Open the report with the ARC per `report-template.md`, render SHAPE then APPROVE as a guided choice, then present `SETUP-REVIEW.md` lowest-confidence-first. They confirm **once** — an explicit yes; ambient agreement is not a confirmation. **Never self-stamp a timeout — hold, or re-ask.** On that recorded confirmation, you run the lock:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
python3 .add/tooling/add.py lock --by "<name>"
|
|
@@ -14,7 +14,7 @@ Then mark `Status: FROZEN @ v1`. Generate a mock + contract tests so dependent
|
|
|
14
14
|
work can start before the real code exists.
|
|
15
15
|
</output_format>
|
|
16
16
|
|
|
17
|
-
**The freeze is the one approval.** Present the bundle **lowest-confidence first**: the 1–2 points most likely wrong (`⚠ [spec|scenario|contract|test] … — because …; if wrong: …`). Open with the ARC per `report-template.md`, rendering SHAPE then the freeze APPROVE as a guided choice (recommended pick +
|
|
17
|
+
**The freeze is the one approval.** Present the bundle **lowest-confidence first**: the 1–2 points most likely wrong (`⚠ [spec|scenario|contract|test] … — because …; if wrong: …`). Open with the ARC per `report-template.md`, rendering SHAPE then the freeze APPROVE as a guided choice (recommended pick + alternatives) — **render before `FROZEN`, then record `Reported: yes` in §3; never on a timeout.** See `run.md`. The approval also freezes §5 **Scope (may touch)** + Strategy.
|
|
18
18
|
|
|
19
19
|
## The freeze review checklist
|
|
20
20
|
|
|
@@ -19,6 +19,8 @@ Fill the §6 **Build expectations** block BEFORE Build: OBSERVABLE outcomes deri
|
|
|
19
19
|
- [ ] Coverage did not decrease.
|
|
20
20
|
- [ ] No test or contract was altered during build.
|
|
21
21
|
- [ ] Every §6 Build expectation is confirmed by real evidence (not just a green test).
|
|
22
|
+
- [ ] §1 rules trace to §2/§4 — an untraced rule is a coverage gap (`add.py audit`'s `rule_coverage_gap`; `check` has detail).
|
|
23
|
+
- [ ] every §3-cited symbol still resolves in the CURRENT tree, not just Ground SHA (§6 Live-verify evidence catches a stale/moved anchor here, not later).
|
|
22
24
|
|
|
23
25
|
If any is false, stop and return to Build.
|
|
24
26
|
|
|
@@ -42,7 +44,7 @@ A green suite proves tests pass — not that the build EARNED them. Three judgme
|
|
|
42
44
|
|
|
43
45
|
## Record exactly one outcome (no silent pass)
|
|
44
46
|
|
|
45
|
-
Present this gate via `report-template.md`'s ARC, render APPROVE
|
|
47
|
+
Present this gate via `report-template.md`'s ARC, render APPROVE, and reconcile FLAGS with `add.py report --decide`'s open-item count. **Human-led: render before `gate` and record `Reported: yes` in §6, never self-stamp.**
|
|
46
48
|
|
|
47
49
|
| Outcome | When |
|
|
48
50
|
|---------|------|
|
|
@@ -125,4 +125,5 @@ APPROVE <the question>
|
|
|
125
125
|
- **The question is a summary, never the artifact.** A compact SUMMARY · FLAGS block sits in chat immediately before the ask; the question text itself is two lines at most — intent + what "yes" means + flag count — pointing at the report above.
|
|
126
126
|
- **NEXT is not a second gate.** The single decision stays in APPROVE; NEXT is ranked recommendations only.
|
|
127
127
|
- **DECIDED never holds a gate-class call.** Security / residue / lowered-autonomy calls escalate in APPROVE.
|
|
128
|
+
- **Recorded, not just performed.** Rendering this template at a gate is recorded, not assumed — TASK.md's `Reported: yes` (§3/§6) is the mechanical trace; `add.py audit` surfaces an unrecorded one (`contract_report_unrecorded` / `verify_report_unrecorded`), a spot-audit the backstop.
|
|
128
129
|
</constraints>
|
package/skill/add/run.md
CHANGED
|
@@ -70,6 +70,7 @@ recorded pass is an explicit pass, not a skip).
|
|
|
70
70
|
- **The deep check (every gate, do not skim).** If the task produced code, record that every new symbol is referenced (wiring) and that no new dead/unused code was introduced. If it produced prose or non-code, record a semantic read. An unfilled deep check is a **shallow verify**, not an auto-PASS.
|
|
71
71
|
- **The recorded refute-read (under `auto`).** The earned-green refute-read (`6-verify.md`) is not just run — its **verdict is recorded** in §6 (`EARNED | NOT-EARNED`); `add.py audit` surfaces an unrecorded one as `refute_unrecorded` — one of three shape lints it lists (with `shallow_deep_check` + `risk_unset`) — and a human spot-audit is the backstop. NOT-EARNED routes to `add.py heal`, never an auto-PASS.
|
|
72
72
|
- **The recorded Advisor 3-lens verdict (under `auto`).** The Advisor 3-lens sweep (security → concurrency → architecture, `6-verify.md`) is recorded in §6 `### Advisor 3-lens verdict`; `add.py audit` surfaces an unfilled block as `advisor_verdict_unrecorded` — a shape lint alongside `refute_unrecorded`.
|
|
73
|
+
- **The rendered gate report (§3/§6).** Report-template.md's ceremony is recorded, not just performed — a `Reported: yes` line in §3/§6; `add.py audit` surfaces an unrecorded one as `contract_report_unrecorded`/`verify_report_unrecorded`; a human spot-audit is the backstop.
|
|
73
74
|
- **The `advisor-gate-relax` pathway.** A `risk: high` + `sensitivity: mechanical` task whose §6 Advisor 3-lens verdict records Verdict `PASS` and Residue `none` may auto-complete via `add.py gate PASS` **without** a lowered autonomy level. Security and every non-mechanical sensitivity class are never relaxed by this pathway — the high-risk guard still applies.
|
|
74
75
|
- **Always escalates to a human (never auto-passed):** any **security** finding (HARD-STOP, always);
|
|
75
76
|
a **concurrency**/timing risk the tests cannot exercise; an **architecture**/layering violation;
|
package/skill/add/streams.md
CHANGED
|
@@ -55,6 +55,9 @@ tier hint: top → dag-scheduler, setup-run-mode; mid → the rest
|
|
|
55
55
|
- **Spend your strongest model on the critical path.** Critical-path tasks gate the most
|
|
56
56
|
downstream work; off-path tasks take **mid**. The tier hint is advisory — override when you
|
|
57
57
|
know a task is harder than its position suggests.
|
|
58
|
+
- **Prefer the named roster per worker** — `add-build` for a tests/build-phase worker, `add-design`
|
|
59
|
+
for design-phase, `add-verify` for verify-phase (full roster + when-to-spawn in advisor.md) — over
|
|
60
|
+
a generic ad-hoc spawn.
|
|
58
61
|
- **`--json`** (`{ milestone, waves, critical_path, critical_path_len, tiers, blocked }`) feeds
|
|
59
62
|
a runner that spawns programmatically. `blocked` lists tasks whose dep cannot be satisfied
|
|
60
63
|
within this milestone; a `dependency_cycle` is refused with the offending members named.
|
|
@@ -95,6 +98,10 @@ floor never drops to zero (`run.md:22`). Do not engineer around it.
|
|
|
95
98
|
gates this (`engine-merge-base-enforcement`): `add.py wave-verify` before the first merge-back
|
|
96
99
|
refuses a mismatched/pending echo (`unverified_fork_base`) or off-template ledger
|
|
97
100
|
(`wave_ledger_malformed`); `add.py check` is the standing monitor.
|
|
101
|
+
- **Materialize gitignored engine content** — `git worktree add` checks out TRACKED files only;
|
|
102
|
+
`.add/tooling` (engine) and `.add/docs` (book) are gitignored and will be ABSENT even when the
|
|
103
|
+
worktree's HEAD matches — copy them in before the worker's first `add.py` call, or its
|
|
104
|
+
`phase`/`advance` commands have no engine to run at all (confirmed 3-for-3 this session).
|
|
98
105
|
- **Lease + timeout** — record which worker holds which task (wave ledger); a dead worker releases
|
|
99
106
|
its claim back to READY.
|
|
100
107
|
- **Failure isolates** — a worker's STOP-and-escalate blocks only its own task; siblings run on, the
|
package/tooling/add.py
CHANGED
|
@@ -100,6 +100,7 @@ from add_engine.io_state import ( # re-exported as module globals: callers use
|
|
|
100
100
|
_CONFLICT_MARKER_RE, # conflict-marker re
|
|
101
101
|
_load_state_for_json, # --json state loader
|
|
102
102
|
_md5_text, _md5_file, # md5 hashing helpers
|
|
103
|
+
_personas_unseeded, # persona-seed-nudge predicate
|
|
103
104
|
)
|
|
104
105
|
|
|
105
106
|
|
|
@@ -1886,6 +1887,11 @@ def cmd_status(args: argparse.Namespace) -> None:
|
|
|
1886
1887
|
# Existence-only: no open/parse, so the pointer adds no IO failure path (a non-file is no voice).
|
|
1887
1888
|
if (root / "SOUL.md").exists():
|
|
1888
1889
|
print("voice : .add/SOUL.md (how I sound & what keeps your trust — read each session)")
|
|
1890
|
+
# persona pointer (persona-seed-nudge v2): project-wide, read every session like context/voice
|
|
1891
|
+
# above — fires until >=1 REAL persona is seeded, self-clears once one lands. Advisory only;
|
|
1892
|
+
# never gates, never touches the --json branch (human-readable orientation surface only).
|
|
1893
|
+
if _personas_unseeded(root):
|
|
1894
|
+
print(f"persona : {PERSONA_HINT}")
|
|
1889
1895
|
# wave resume hint — a live ledger outranks memory (streams.md "Wave ledger").
|
|
1890
1896
|
# Existence-only: no open/read/parse, so the hint adds no IO failure path; a
|
|
1891
1897
|
# non-file at the path is not a ledger. One line PER live ledger — more than
|
|
@@ -2930,6 +2936,11 @@ def cmd_check(args: argparse.Namespace) -> None:
|
|
|
2930
2936
|
"persona_schema_incomplete: missing " + ", ".join(missing)))
|
|
2931
2937
|
else:
|
|
2932
2938
|
infos.append((f"persona '{slug}'", "schema-conformant"))
|
|
2939
|
+
# persona-seed-nudge: surface the SAME "no real persona" gap `new-milestone` nudges on, so
|
|
2940
|
+
# it is also visible on a plain `check`/`status` sweep — an INFO affirmation-of-absence,
|
|
2941
|
+
# never a WARN (measure-not-block; a project with no personas behaves exactly as before).
|
|
2942
|
+
if _personas_unseeded(root):
|
|
2943
|
+
infos.append(("personas", f"unseeded — {PERSONA_HINT}"))
|
|
2933
2944
|
|
|
2934
2945
|
# drift: a done milestone must have no unfinished tasks
|
|
2935
2946
|
for mslug, m in milestones.items():
|
|
@@ -3055,6 +3066,30 @@ def cmd_check(args: argparse.Namespace) -> None:
|
|
|
3055
3066
|
f".add/design/captures/{_pname}.<png|svg|…> — render + confirm it "
|
|
3056
3067
|
"before build (design.md beat 4)"))
|
|
3057
3068
|
|
|
3069
|
+
# roster-uninstalled (roster-install-drift): the ADD-managed guideline block cites the agent
|
|
3070
|
+
# roster ("agents/*.md" tail — matches both the shipped `add-method/agents/*.md` attribution
|
|
3071
|
+
# citation and any older phrasing) but the project may have no roster installed at all — never
|
|
3072
|
+
# shipped in the package, synced before this fix, or from a build that regressed the agents/
|
|
3073
|
+
# tree — a dead reference with no signal anywhere. WARN, never red (measure-not-block);
|
|
3074
|
+
# presence-gated on the citation itself — a project whose guideline files don't cite a roster
|
|
3075
|
+
# at all is silently exempt, never retro-flagged.
|
|
3076
|
+
_project_root = root.parent
|
|
3077
|
+
_cites_roster = False
|
|
3078
|
+
for _gname in GUIDELINE_FILES:
|
|
3079
|
+
try:
|
|
3080
|
+
if "agents/*.md" in (_project_root / _gname).read_text(encoding="utf-8"):
|
|
3081
|
+
_cites_roster = True
|
|
3082
|
+
break
|
|
3083
|
+
except OSError:
|
|
3084
|
+
pass
|
|
3085
|
+
if _cites_roster:
|
|
3086
|
+
_agents_dir = _project_root / ".claude" / "agents"
|
|
3087
|
+
if not (_agents_dir.is_dir() and any(_agents_dir.glob("add-*.md"))):
|
|
3088
|
+
warnings.append(("roster_uninstalled",
|
|
3089
|
+
"guideline file(s) cite the agent roster but no `.claude/agents/"
|
|
3090
|
+
"add-*.md` files are installed — run `add.py update` (or re-run the "
|
|
3091
|
+
"CLI installer) to materialize them"))
|
|
3092
|
+
|
|
3058
3093
|
passed = sum(1 for ok, _, _ in checks if ok)
|
|
3059
3094
|
failed = len(checks) - passed
|
|
3060
3095
|
if as_json:
|
|
@@ -3407,6 +3442,12 @@ def cmd_new_milestone(args: argparse.Namespace) -> None:
|
|
|
3407
3442
|
else:
|
|
3408
3443
|
print("active milestone set." + ("" if not await_confirm else
|
|
3409
3444
|
" (unconfirmed — show the MILESTONE.md, then: add.py milestone-confirm " + slug + ")"))
|
|
3445
|
+
# persona-seed-nudge: a non-blocking hint (never a gate) when this project has no
|
|
3446
|
+
# REAL project-fit persona yet — points at the cross-cutting selection/drafting
|
|
3447
|
+
# service rather than inventing a second mechanism. Fires only on the ACTIVE arm
|
|
3448
|
+
# (a queued milestone isn't yet in flight, so the nudge would be premature there).
|
|
3449
|
+
if _personas_unseeded(root):
|
|
3450
|
+
print(f"note: {PERSONA_HINT}")
|
|
3410
3451
|
print(_next_footer(root, state)) # converges the old "Decompose it into tasks: …" hint
|
|
3411
3452
|
|
|
3412
3453
|
|
|
@@ -6037,6 +6078,21 @@ def _freeze_skip_notices(state: dict) -> list[dict]:
|
|
|
6037
6078
|
# region only (mirrors _RISK_HIGH_RE's anchoring so a title substring can never look like one).
|
|
6038
6079
|
_RISK_ANY_RE = re.compile(r"(?:^|·)[ \t]*risk:[ \t]*\S", re.MULTILINE)
|
|
6039
6080
|
|
|
6081
|
+
# A single `Reported:` line (report-rendered-trace) — scoped to ONE phase body at a time by the
|
|
6082
|
+
# caller (bodies.get(3, "")/bodies.get(6, "")), so §3 and §6 never cross-contaminate each other.
|
|
6083
|
+
_REPORTED_LINE_RE = re.compile(r"(?m)^Reported:[ \t]*(.*)$")
|
|
6084
|
+
|
|
6085
|
+
|
|
6086
|
+
def _reported_unrecorded(body_text: str) -> bool:
|
|
6087
|
+
"""True iff a 'Reported:' line is PRESENT but still an unfilled `<...>` placeholder or blank.
|
|
6088
|
+
ABSENT line -> False (grandfathered — a pre-existing task's TASK.md predates this template
|
|
6089
|
+
field), mirroring _section_unfilled's absent-block convention."""
|
|
6090
|
+
m = _REPORTED_LINE_RE.search(body_text)
|
|
6091
|
+
if m is None:
|
|
6092
|
+
return False
|
|
6093
|
+
val = m.group(1).strip()
|
|
6094
|
+
return (not val) or bool(re.fullmatch(r"<[^>\n]*>", val))
|
|
6095
|
+
|
|
6040
6096
|
|
|
6041
6097
|
def _guarantee_lint_notices(root: Path, state: dict) -> dict:
|
|
6042
6098
|
"""PRESENCE-ONLY, MEASURE-NOT-BLOCK lints SURFACED (never failed-on) by `add.py audit`
|
|
@@ -6049,16 +6105,31 @@ def _guarantee_lint_notices(root: Path, state: dict) -> dict:
|
|
|
6049
6105
|
refute-record, M4) — the earned-green verdict the AI must record under
|
|
6050
6106
|
`auto`; ABSENT block grandfathers exactly like shallow. MEASURE-NOT-BLOCK:
|
|
6051
6107
|
never auto-blocks a gate, only surfaced here for review + a human spot-audit.
|
|
6052
|
-
|
|
6108
|
+
rule_coverage_gap[]= this task's OWN §1 Must/Reject IDs have >=1 rule with no §2 scenario tag
|
|
6109
|
+
and no §4 `covers:` line (_rule_coverage_gaps — same opt-in-by-tag-presence
|
|
6110
|
+
grandfather as `add.py check`'s whole-project sweep) — surfaced the moment
|
|
6111
|
+
THIS task reaches verify, not only via a separate `check` invocation someone
|
|
6112
|
+
has to remember to run; `check` still owns the per-rule detail.
|
|
6113
|
+
contract_report_unrecorded[] = §3's `Reported:` line present-but-unfilled (report-rendered-
|
|
6114
|
+
trace) — the freeze report (banner/ARC/SHAPE) was cited by the guide but
|
|
6115
|
+
never recorded as rendered; ABSENT line grandfathers a pre-field task.
|
|
6116
|
+
verify_report_unrecorded[] = §6's `Reported:` line present-but-unfilled — the gate report
|
|
6117
|
+
(banner/ARC) was never recorded as rendered; same grandfather rule.
|
|
6118
|
+
Honest visibility for six verify guarantees; NEVER a finding (audit stays exit 0). PURE — reads
|
|
6053
6119
|
TASK.md + state only, writes nothing."""
|
|
6054
6120
|
shallow, risk_unset, refute_unrecorded, sensitivity_unset = [], [], [], []
|
|
6055
6121
|
advisor_verdict_unrecorded = []
|
|
6056
6122
|
advisor_reviewer_is_author = []
|
|
6057
6123
|
advisor_residue_on_mechanical_mis_tier = []
|
|
6124
|
+
rule_coverage_gap = []
|
|
6125
|
+
contract_report_unrecorded = []
|
|
6126
|
+
verify_report_unrecorded = []
|
|
6058
6127
|
for slug in sorted(state.get("tasks") or {}):
|
|
6059
6128
|
if (state["tasks"][slug] or {}).get("phase") not in ("verify", "observe", "done"):
|
|
6060
6129
|
continue
|
|
6061
|
-
|
|
6130
|
+
bodies = _raw_phase_bodies(root, slug)
|
|
6131
|
+
body3 = bodies.get(3, "")
|
|
6132
|
+
body6 = bodies.get(6, "")
|
|
6062
6133
|
hdr = _task_header(root, slug)
|
|
6063
6134
|
if _section_unfilled(body6, "### Deep checks"):
|
|
6064
6135
|
shallow.append(slug)
|
|
@@ -6066,12 +6137,21 @@ def _guarantee_lint_notices(root: Path, state: dict) -> dict:
|
|
|
6066
6137
|
refute_unrecorded.append(slug)
|
|
6067
6138
|
if _section_unfilled(body6, "### Advisor 3-lens verdict"):
|
|
6068
6139
|
advisor_verdict_unrecorded.append(slug)
|
|
6140
|
+
if _reported_unrecorded(body3):
|
|
6141
|
+
contract_report_unrecorded.append(slug)
|
|
6142
|
+
if _reported_unrecorded(body6):
|
|
6143
|
+
verify_report_unrecorded.append(slug)
|
|
6069
6144
|
if not _RISK_ANY_RE.search(hdr):
|
|
6070
6145
|
risk_unset.append(slug)
|
|
6071
6146
|
# sensitivity_unset (risk-sensitivity-taxonomy): a verify-reached task with no
|
|
6072
6147
|
# human-declared sensitivity — MEASURE-NOT-BLOCK, same class as risk_unset.
|
|
6073
6148
|
if _task_sensitivity(hdr) is None:
|
|
6074
6149
|
sensitivity_unset.append(slug)
|
|
6150
|
+
# rule_coverage_gap (verify-traceability-glint): this task's own §1 Must/Reject vs
|
|
6151
|
+
# §2/§4 tags — the SAME predicate `check` uses project-wide, scoped to just this task
|
|
6152
|
+
# and surfaced right at verify, not only via a separate whole-project sweep.
|
|
6153
|
+
if _rule_coverage_gaps(bodies.get(1, ""), bodies.get(2, ""), bodies.get(4, "")):
|
|
6154
|
+
rule_coverage_gap.append(slug)
|
|
6075
6155
|
# advisor_reviewer_is_author / advisor_residue_on_mechanical_mis_tier
|
|
6076
6156
|
# (advisor-verdict-audit): MEASURE-NOT-BLOCK lints on the filled advisor block.
|
|
6077
6157
|
# Both require the block to be PRESENT AND FILLED (not just unfilled).
|
|
@@ -6105,7 +6185,10 @@ def _guarantee_lint_notices(root: Path, state: dict) -> dict:
|
|
|
6105
6185
|
"sensitivity_unset": sensitivity_unset,
|
|
6106
6186
|
"advisor_verdict_unrecorded": advisor_verdict_unrecorded,
|
|
6107
6187
|
"advisor_reviewer_is_author": advisor_reviewer_is_author,
|
|
6108
|
-
"advisor_residue_on_mechanical_mis_tier": advisor_residue_on_mechanical_mis_tier
|
|
6188
|
+
"advisor_residue_on_mechanical_mis_tier": advisor_residue_on_mechanical_mis_tier,
|
|
6189
|
+
"rule_coverage_gap": rule_coverage_gap,
|
|
6190
|
+
"contract_report_unrecorded": contract_report_unrecorded,
|
|
6191
|
+
"verify_report_unrecorded": verify_report_unrecorded}
|
|
6109
6192
|
|
|
6110
6193
|
|
|
6111
6194
|
def cmd_audit(args: argparse.Namespace) -> None:
|
|
@@ -6142,6 +6225,11 @@ def cmd_audit(args: argparse.Namespace) -> None:
|
|
|
6142
6225
|
av = glints["advisor_verdict_unrecorded"]
|
|
6143
6226
|
print(f"audit: advisor_verdict_unrecorded — {len(av)} task(s): {', '.join(av)} "
|
|
6144
6227
|
f"— record the 3-lens advisor verdict (§6); a spot-audit is the backstop")
|
|
6228
|
+
if glints["rule_coverage_gap"]:
|
|
6229
|
+
rc = glints["rule_coverage_gap"]
|
|
6230
|
+
print(f"audit: rule_coverage_gap — {len(rc)} task(s): {', '.join(rc)} "
|
|
6231
|
+
f"— a §1 Must/Reject has no §2 scenario tag or §4 covers: line "
|
|
6232
|
+
f"(run `add.py check` for the per-rule detail)")
|
|
6145
6233
|
if glints["sensitivity_unset"]:
|
|
6146
6234
|
su = glints["sensitivity_unset"]
|
|
6147
6235
|
print(f"audit: sensitivity_unset — {len(su)} task(s) reached verify with no "
|
|
@@ -6155,11 +6243,22 @@ def cmd_audit(args: argparse.Namespace) -> None:
|
|
|
6155
6243
|
print(f"audit: advisor_residue_on_mechanical_mis_tier — {len(am)} task(s): "
|
|
6156
6244
|
f"{', '.join(am)} — mechanical tier with non-none residue and PASS verdict "
|
|
6157
6245
|
f"is incoherent; consider re-tiering")
|
|
6246
|
+
if glints["contract_report_unrecorded"]:
|
|
6247
|
+
cr = glints["contract_report_unrecorded"]
|
|
6248
|
+
print(f"audit: contract_report_unrecorded — {len(cr)} task(s): {', '.join(cr)} "
|
|
6249
|
+
f"— record the rendered freeze report (§3 `Reported: yes`); a spot-audit is the backstop")
|
|
6250
|
+
if glints["verify_report_unrecorded"]:
|
|
6251
|
+
vr = glints["verify_report_unrecorded"]
|
|
6252
|
+
print(f"audit: verify_report_unrecorded — {len(vr)} task(s): {', '.join(vr)} "
|
|
6253
|
+
f"— record the rendered gate report (§6 `Reported: yes`); a spot-audit is the backstop")
|
|
6158
6254
|
if not findings and not skips and not glints["shallow"] and not glints["risk_unset"] \
|
|
6159
6255
|
and not glints["refute_unrecorded"] and not glints["advisor_verdict_unrecorded"] \
|
|
6160
6256
|
and not glints["sensitivity_unset"] \
|
|
6161
6257
|
and not glints["advisor_reviewer_is_author"] \
|
|
6162
|
-
and not glints["advisor_residue_on_mechanical_mis_tier"]
|
|
6258
|
+
and not glints["advisor_residue_on_mechanical_mis_tier"] \
|
|
6259
|
+
and not glints["rule_coverage_gap"] \
|
|
6260
|
+
and not glints["contract_report_unrecorded"] \
|
|
6261
|
+
and not glints["verify_report_unrecorded"]:
|
|
6163
6262
|
print(f"audit: clean ({checked} tasks checked)")
|
|
6164
6263
|
# MEASURE-NOT-BLOCK: only real findings raise the exit code; notices never do.
|
|
6165
6264
|
if findings:
|
|
@@ -24,6 +24,7 @@ __all__ = [
|
|
|
24
24
|
"SETUP_FILES",
|
|
25
25
|
"PERSONA_FRONTMATTER_KEYS",
|
|
26
26
|
"PERSONA_REQUIRED_SECTIONS",
|
|
27
|
+
"PERSONA_HINT",
|
|
27
28
|
"GUIDELINE_FILES",
|
|
28
29
|
"RULES_FILE_REL",
|
|
29
30
|
"WORKFLOW_HEADINGS",
|
|
@@ -99,6 +100,14 @@ SETUP_FILES = ("PROJECT.md", "CONVENTIONS.md", "GLOSSARY.md", "MODEL_REGISTRY.md
|
|
|
99
100
|
PERSONA_FRONTMATTER_KEYS = ("name", "vibe")
|
|
100
101
|
PERSONA_REQUIRED_SECTIONS = ("## Identity", "## Critical Rules", "## Default Requirement", "## Success Metrics")
|
|
101
102
|
|
|
103
|
+
# persona-seed-nudge v2: ONE hint, single-sourced — `new-milestone`/`check`/`status` all print
|
|
104
|
+
# THIS constant (not their own copy) so the wording can never drift across the three surfaces.
|
|
105
|
+
# Project-scoped (not "this milestone's domain") per the confirmed v2 amendment: the AI should
|
|
106
|
+
# catch up ALL of a project's missing personas, not draft a single milestone-fit one.
|
|
107
|
+
PERSONA_HINT = ("no project-fit persona seeded yet under .add/personas/ — spawn the add-persona "
|
|
108
|
+
"agent (or read docs/18-personas.md) to seed the project's persona(s) from "
|
|
109
|
+
"PROJECT.md's domain")
|
|
110
|
+
|
|
102
111
|
# Scaffolded into .add/.gitignore at init so the engine's transient LOCAL artifacts
|
|
103
112
|
# never reach git. Bare-filename patterns match at any depth under .add/ (tasks/,
|
|
104
113
|
# milestones/, archive/). These are working state, not records: scope-snapshot.json
|
|
@@ -199,3 +199,17 @@ def _md5_file(p: Path) -> str | None:
|
|
|
199
199
|
return hashlib.md5(p.read_bytes()).hexdigest()
|
|
200
200
|
except OSError:
|
|
201
201
|
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _personas_unseeded(root: Path) -> bool:
|
|
205
|
+
"""True when `.add/personas/` has no REAL (non-template) authored persona: the
|
|
206
|
+
directory is absent, empty, or holds only the seeded `_template.md` scaffold
|
|
207
|
+
(persona-seed-nudge). Fail-soft: an unreadable directory counts as unseeded
|
|
208
|
+
rather than raising — this feeds a `note:`/INFO hint, never a gate."""
|
|
209
|
+
d = root / "personas"
|
|
210
|
+
if not d.is_dir():
|
|
211
|
+
return True
|
|
212
|
+
try:
|
|
213
|
+
return not any(p.stem != "_template" for p in d.glob("*.md"))
|
|
214
|
+
except OSError:
|
|
215
|
+
return True
|
|
@@ -82,6 +82,7 @@ Schema: <tables/fields touched, and access pattern>
|
|
|
82
82
|
|
|
83
83
|
Glossary deltas: <new domain term(s) this task introduces, `Term: definition` — or "none">
|
|
84
84
|
Status: DRAFT
|
|
85
|
+
Reported: <yes — the freeze report (banner/ARC/SHAPE) rendered before this froze | no>
|
|
85
86
|
<!-- The freeze IS the one approval — lead it with the bundle's lowest-confidence flag: the 1–2
|
|
86
87
|
points most likely wrong across the whole bundle, tagged [spec|scenario|contract|test], each
|
|
87
88
|
with why + cost (the §1 ⚠ assumptions feed it; a flag may point at a scenario or the contract
|
|
@@ -117,6 +118,7 @@ Scope (may touch): `./src/` <fill before the §3 freeze — every file the bui
|
|
|
117
118
|
Strategy (ordered batches): <1. … 2. … — the planned build order; guidance, not enforced; preferred architecture/pattern strategies; advise solution/method to resolve issues/implement features>
|
|
118
119
|
|
|
119
120
|
Persona (optional): <name the persona file under `.add/personas/` this build embodies as a domain stance atop SOUL.md — advisory, never lowers a gate; absent = generic>
|
|
121
|
+
Spawn isolation (default): <prefer isolation: "worktree" for any subagent build/verify spawn, not only explicit parallel mode; shared-tree needs a stated reason — see worktree-isolated-spawn-default>
|
|
120
122
|
Known-problem fixes: <trap → planned fix — the failure modes this build must dodge; guidance, not enforced>
|
|
121
123
|
Strategy actually used: <fill at VERIFY — the strategy you ACTUALLY used (or "as planned"); harvested into the §7 Decisions (ADR) block as the [AI] build decision>
|
|
122
124
|
Safety rule (feature-specific): <e.g. debit+credit in one atomic transaction>
|
|
@@ -187,6 +189,7 @@ Residue: <none | summary>
|
|
|
187
189
|
Binding: <yes — mechanical | advisory — <sensitivity>>
|
|
188
190
|
|
|
189
191
|
### GATE RECORD
|
|
192
|
+
Reported: <yes — the gate report (banner/ARC) rendered before this outcome recorded | no>
|
|
190
193
|
Outcome: <PASS | RISK-ACCEPTED | HARD-STOP>
|
|
191
194
|
If RISK-ACCEPTED -> owner: <name> · ticket: <link> · expires: <date> (never for a security gap)
|
|
192
195
|
Reviewed by: <name> · date: <date>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: <persona name — e.g. Frontend Engineer, UX Researcher>
|
|
3
3
|
vibe: <one-line essence — what this persona keeps true>
|
|
4
|
+
flow: <RECOMMENDED — which of the three apply-surfaces this persona is loaded at (see
|
|
5
|
+
docs/18-personas.md "Apply — three surfaces"): design (the UDD requirements lens) | build
|
|
6
|
+
(the domain-identity overlay on SOUL.md) | advisor (subagent/streams delegation, incl. the
|
|
7
|
+
verify refute-read) — comma-separate if more than one, e.g. `build, advisor`>
|
|
4
8
|
source: <OPTIONAL — the teacher file(s) this was distilled from, e.g. `.add/personas-teacher/engineering/engineering-software-architect.md` (provenance; omit if hand-authored)>
|
|
5
9
|
---
|
|
6
10
|
<!-- A PERSONA is a project-fit requirements persona, distilled from the vendored teacher
|
|
@@ -9,8 +13,11 @@ source: <OPTIONAL — the teacher file(s) this was distilled from, e.g. `.add/pe
|
|
|
9
13
|
the teacher; the engine only seeds + validates this schema (presence-based). This
|
|
10
14
|
`_template.md` is the schema reference — copy it to `<slug>.md` and fill it.
|
|
11
15
|
|
|
12
|
-
REQUIRED: `name` + `vibe` frontmatter and the Identity / Critical Rules /
|
|
16
|
+
REQUIRED (engine-checked): `name` + `vibe` frontmatter and the Identity / Critical Rules /
|
|
13
17
|
Default Requirement / Success Metrics sections.
|
|
18
|
+
RECOMMENDED (not engine-checked, but expected on every persona for consistency): `flow:`
|
|
19
|
+
frontmatter and the `## Abilities` section — a persona with no stated flow or abilities is
|
|
20
|
+
hard for the design/build/advisor surfaces to actually pick up and use.
|
|
14
21
|
OPTIONAL (recommended for a faithful distillation): `source:` frontmatter, and the
|
|
15
22
|
`## Anti-patterns` + `## Playbook` sections. The engine never requires the optional
|
|
16
23
|
parts; absence is conformant.
|
|
@@ -23,12 +30,25 @@ source: <OPTIONAL — the teacher file(s) this was distilled from, e.g. `.add/pe
|
|
|
23
30
|
3. TAG provenance honestly. In `## Playbook`, mark each item teacher-derived vs
|
|
24
31
|
ADD-native. Never credit home-grown project scaffolding to the teacher.
|
|
25
32
|
4. METRICS are rules, not snapshots. Prefer an invariant ("suite matches the last
|
|
26
|
-
green run") over a volatile literal ("2491/0") that rots as the project grows.
|
|
33
|
+
green run") over a volatile literal ("2491/0") that rots as the project grows.
|
|
34
|
+
5. NAME the flow. State which apply-surface(s) actually load this persona — a persona
|
|
35
|
+
that fits none of design/build/advisor is dead weight nobody will ever pick up.
|
|
36
|
+
6. ABILITIES are checkable skills, not aspirations. Anchor each to a real file, tool, or
|
|
37
|
+
command this project already has — the same anchoring discipline as Critical Rules
|
|
38
|
+
and Success Metrics, applied to "what this persona can concretely do." -->
|
|
27
39
|
|
|
28
40
|
## Identity
|
|
29
41
|
<who this persona is — role, domain depth, and the EARNED perspective it brings (what it has
|
|
30
42
|
seen succeed/fail that shapes its judgement). One short paragraph.>
|
|
31
43
|
|
|
44
|
+
## Abilities
|
|
45
|
+
<concrete, checkable things this persona can actually DO — a capability list, distinct from
|
|
46
|
+
Critical Rules (always-enforced constraints) and Playbook (optional step-by-step scaffolding).
|
|
47
|
+
State each as something the agent can perform right now, anchored to a real file/tool/command
|
|
48
|
+
where possible — not an aspiration.>
|
|
49
|
+
- <a concrete capability — e.g. "can diff two response fixtures byte-for-byte to prove passthrough">
|
|
50
|
+
- <another concrete capability>
|
|
51
|
+
|
|
32
52
|
## Critical Rules
|
|
33
53
|
<non-negotiables this persona ALWAYS enforces. Lead with 1–2 carried from the teacher (its
|
|
34
54
|
signature stance), then add project-specific ones.>
|