@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.
@@ -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.15.0",
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__",
@@ -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 (when to spawn, the prompt
124
- template, the tier) `advisor.md`. Self-score a draft (0–1 across six dimensions, refine if any < 0.9)
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 ·
@@ -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) and
54
- render as a guided choice (recommended next move + described alternatives).
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: it NEVER skips the human baseline approval — `lock` stays the one decision.
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), recording the teacher in `source:` and carrying its top `## Playbook` (both optional). Covered by the **baseline approval**; `add.py check` validates; never clobber.
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 to the baseline approval; ambient mid-stream agreement is not a confirmation. On that recorded confirmation, you run the lock:
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 + described alternatives). See `run.md`. The approval also freezes the §5 **Scope (may touch)** allowlist + Strategy declarations.
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 as a guided choice, and reconcile FLAGS with `add.py report --decide`'s open-item count.
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;
@@ -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
- Honest visibility for three verify guarantees; NEVER a finding (audit stays exit 0). PURE — reads
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
- body6 = _raw_phase_bodies(root, slug).get(6, "")
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.>