@pilotspace/add 2.0.0 → 2.2.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/tooling/add.py CHANGED
@@ -593,6 +593,11 @@ def cmd_init(args: argparse.Namespace) -> None:
593
593
  continue
594
594
  _atomic_write(dest, rendered)
595
595
 
596
+ # persona-skill: personas are AUTHORED via the persona-author skill (not seeded from a
597
+ # template) — but the location must exist so the first authored persona has a home and the
598
+ # unseeded nudge has a directory to check. Create it empty; the skill fills it.
599
+ (root / "personas").mkdir(parents=True, exist_ok=True)
600
+
596
601
  # specs-5dd (ADD 2.0 M3): the five living 5-DD specs — same survivor idiom as
597
602
  # SETUP_FILES (never clobber, never write blank), ONE template rendered five ways.
598
603
  for dd in SPEC_DDS:
@@ -779,6 +784,8 @@ def cmd_new_task(args: argparse.Namespace) -> None:
779
784
  # was HARD-STOP escalated) could launder the cap (HEAL_CAP) to zero by re-creating itself —
780
785
  # a zero-human cap bypass (the same invariant _heal_or_escalate guards: "never auto-resets").
781
786
  prior_heal = state["tasks"].get(slug, {}).get("heal") if args.force else None
787
+ # round-visible-runs: the round record is monotonic the same way — survives a --force re-create.
788
+ prior_rounds = state["tasks"].get(slug, {}).get("rounds") if args.force else None
782
789
  state["tasks"][slug] = {
783
790
  "title": title,
784
791
  "phase": "direction",
@@ -796,6 +803,8 @@ def cmd_new_task(args: argparse.Namespace) -> None:
796
803
  state["tasks"][slug]["relates_to"] = relates_to
797
804
  if prior_heal is not None:
798
805
  state["tasks"][slug]["heal"] = prior_heal # monotonic — survives the --force re-create
806
+ if prior_rounds is not None:
807
+ state["tasks"][slug]["rounds"] = prior_rounds # same monotonic contract as heal
799
808
  if from_delta:
800
809
  state["tasks"][slug]["from_delta"] = from_delta # lineage: seeded from <prior>
801
810
  if sensitivity:
@@ -1686,8 +1695,21 @@ def cmd_phase(args: argparse.Namespace) -> None:
1686
1695
  # scope snapshot), so verify's _tamper_guard is armed and a freeze-gated DRAFT §3 is refused.
1687
1696
  # validate-then-write: a refusal raises BEFORE the phase is set, so nothing moves. The heal
1688
1697
  # loop sets phase=build directly (never via cmd_phase) and so stays exempt.
1698
+ # round-visible-runs: --note annotates a verify->build ROUND — refuse it anywhere else,
1699
+ # BEFORE any write (validate-then-write; a refused entry leaves state byte-unchanged).
1700
+ # The refusal keys on the FLAG being passed (whitespace included) and the note is stored
1701
+ # VERBATIM (§1 Boundary); the contract's refusal exit code is 2.
1702
+ note = getattr(args, "note", None)
1703
+ if note is not None and args.phase != "build":
1704
+ _die("phase_note_build_only: --note annotates the verify->build round this return "
1705
+ "records — it is only valid with target build", code=2)
1706
+ prior = state["tasks"][slug].get("phase")
1689
1707
  if args.phase == "build":
1690
1708
  _build_entry(root, state, slug, skip_freeze=getattr(args, "skip_freeze", False))
1709
+ # round-visible-runs: a verify->build return trip IS a round — record it in the SAME
1710
+ # save as the phase write (atomic; no round without the return, no return without it).
1711
+ if args.phase == "build" and prior == "verify":
1712
+ _record_round(state["tasks"][slug], source="phase", note=note)
1691
1713
  state["tasks"][slug]["phase"] = args.phase
1692
1714
  state["tasks"][slug]["updated"] = _now()
1693
1715
  save_state(root, state) # F12: durable state FIRST (source of truth) — may _die
@@ -2293,6 +2315,7 @@ def _append_route_trace(root: Path, state: dict, slug: str, outcome: str) -> Non
2293
2315
  "kind": kind, "lane": lane, "routed_by": by,
2294
2316
  "persona": m.group(1) if m else None, "outcome": outcome,
2295
2317
  "heals": (t.get("heal") or {}).get("attempts", 0),
2318
+ "rounds": (t.get("rounds") or {}).get("count", 0), # visible verify->build return trips
2296
2319
  "recross": bool(t.get("recross")), "age_hours": age,
2297
2320
  "target_hit": t.get("target_hit"), # the §3 Target judgment (plan-core)
2298
2321
  "actor": (t.get("gate_actor") or {}).get("name"),
@@ -2925,7 +2948,10 @@ def cmd_status(args: argparse.Namespace) -> None:
2925
2948
  _now_active = _active_task(state)
2926
2949
  if _now_active and _now_active in (state.get("tasks") or {}):
2927
2950
  _now_ph = (state["tasks"][_now_active] or {}).get("phase", "?")
2928
- print(f"now : '{_now_active}' · phase={_now_ph} · {_next_footer(root, state)}")
2951
+ # round-visible-runs: a bounced task names its return-trip count; silent at 0.
2952
+ _now_r = ((state["tasks"][_now_active] or {}).get("rounds") or {}).get("count", 0)
2953
+ _now_rr = f" · round {_now_r}" if _now_r else ""
2954
+ print(f"now : '{_now_active}' · phase={_now_ph}{_now_rr} · {_next_footer(root, state)}")
2929
2955
  print(f" PLAN.md: .add/tasks/{_now_active}/PLAN.md · re-orient: add.py status --brief")
2930
2956
  print(f"project : {state.get('project', '(unknown)')}")
2931
2957
  # project autonomy default (task init-auto-default): the posture new tasks INHERIT,
@@ -3957,7 +3983,7 @@ def cmd_check(args: argparse.Namespace) -> None:
3957
3983
  infos.append((f"persona '{slug}'", "schema-conformant"))
3958
3984
  # persona-schema-hardening: quality findings the presence check can't see
3959
3985
  # (typo'd flow: value · bare <…> placeholder) — WARN-only (measure-not-block),
3960
- # REAL personas only: the seeded `_template.md` is all placeholders by design.
3986
+ # REAL personas only: any `_`-prefixed scaffold is placeholders by design.
3961
3987
  if not slug.startswith("_"):
3962
3988
  try:
3963
3989
  text = pf.read_text(encoding="utf-8")
@@ -4914,6 +4940,18 @@ def _scope_guard(root: Path, state: dict, slug: str) -> None:
4914
4940
  f"declared §5 Scope — {shown} ({len(out)} total)"))
4915
4941
 
4916
4942
 
4943
+ def _record_round(task: dict, *, source: str, note: str | None = None) -> None:
4944
+ """Record ONE verify->build return trip — a visible 'round' (round-visible-runs).
4945
+
4946
+ Uncapped and OBSERVATIONAL: rounds never gate, never cap, never move a phase — they
4947
+ make a dynamic verify->fix workflow legible in status and the route traces. Distinct
4948
+ from the heal counter (the CHEAT-classed, capped subset); a heal return records BOTH.
4949
+ Caller owns the save — the increment rides the same atomic write as the phase move."""
4950
+ r = task.setdefault("rounds", {"count": 0, "history": []})
4951
+ r["count"] = r.get("count", 0) + 1
4952
+ r.setdefault("history", []).append({"at": _now(), "source": source, "note": note})
4953
+
4954
+
4917
4955
  def _heal_or_escalate(root: Path, state: dict, slug: str, *, reason: str, source: str) -> None:
4918
4956
  """The bounded self-heal router (verify-integrity, heal-then-escalate). Called ONLY when
4919
4957
  a cheat is CONFIRMED at this point — mechanical (tripwire divergence, source "tamper") or
@@ -4941,6 +4979,7 @@ def _heal_or_escalate(root: Path, state: dict, slug: str, *, reason: str, source
4941
4979
  "spec (change-request -> re-freeze) or abandon. A gamed green is never auto-passed.")
4942
4980
  heal["attempts"] = heal.get("attempts", 0) + 1
4943
4981
  heal.setdefault("history", []).append(entry)
4982
+ _record_round(t, source=source) # a heal return is ALSO a visible round (uncapped view)
4944
4983
  t["phase"] = "build" # DIRECT — never via advance (no re-snapshot)
4945
4984
  t["updated"] = _now()
4946
4985
  _sync_task_marker(root, slug, "build")
@@ -6682,6 +6721,8 @@ def build_parser() -> argparse.ArgumentParser:
6682
6721
  # cmd_phase) so pre-collapse scripts keep working; the stored value is always canonical.
6683
6722
  pp.add_argument("phase", choices=PHASES + tuple(LEGACY_PHASES))
6684
6723
  pp.add_argument("slug", nargs="?", default=None)
6724
+ pp.add_argument("--note", default=None,
6725
+ help="annotate the verify->build round this return records (build target only)")
6685
6726
  pp.add_argument("--skip-freeze", action="store_true",
6686
6727
  help="cross direction->build on a DRAFT §3, recording an auditable freeze_skipped "
6687
6728
  "marker (the universal freeze gate's only bypass; never auto-freezes §3)")
@@ -115,18 +115,18 @@ PHASE_GROUPS = {
115
115
  "VERIFY": ("verify",),
116
116
  }
117
117
  # phase-bundles: the roster agent PREFERRED for each phase (per-PHASE, not per-bundle —
118
- # roster-distill (ADD 2.0 M1): ONE execution shell the `add` agent serves every
119
- # phase; the spawn prompt names the mode (direction·build·verify·advise·persona) and
120
- # the agent loads that beat's guide + the fitting persona (personas carry the
121
- # expertise, the agent carries the discipline). A phase missing here is a bug (PHASE_GROUPS'
122
- # own union covers every key); `_phase_bundle` is the fail-closed resolver for an
123
- # unmapped/corrupted phase token, not this map directly.
118
+ # advisor-split: `add-worker` is the execution shell for every phase; the spawn prompt names
119
+ # the mode (direction·build·verify·persona) and the agent loads that beat's guide + the fitting
120
+ # persona (personas carry the expertise, the agent carries the discipline). `add-advisor` is
121
+ # spawned on demand to propose/pressure-test/decide it is not a per-phase default, so it is
122
+ # absent here. A phase missing here is a bug (PHASE_GROUPS' own union covers every key);
123
+ # `_phase_bundle` is the fail-closed resolver for an unmapped/corrupted phase token, not this map.
124
124
  PHASE_AGENT = {
125
- "direction": "add",
126
- "build": "add",
127
- "verify": "add",
125
+ "direction": "add-worker",
126
+ "build": "add-worker",
127
+ "verify": "add-worker",
128
128
  }
129
- SETUP_FILES = ("PROJECT.md", "CONVENTIONS.md", "GLOSSARY.md", "MODEL_REGISTRY.md", "dependencies.allowlist", "DESIGN.md", "SOUL.md", "personas/_template.md")
129
+ SETUP_FILES = ("PROJECT.md", "CONVENTIONS.md", "GLOSSARY.md", "MODEL_REGISTRY.md", "dependencies.allowlist", "DESIGN.md", "SOUL.md")
130
130
 
131
131
  # persona-setup: a PERSONA living doc (`.add/personas/<slug>.md`) is a frozen-schema file
132
132
  # distilled from the vendored teacher library to its critical-rules + default-requirement +
@@ -168,18 +168,19 @@ SPEC_DDS = {
168
168
  # THIS constant (not their own copy) so the wording can never drift across the three surfaces.
169
169
  # Project-scoped (not "this milestone's domain") per the confirmed v2 amendment: the AI should
170
170
  # catch up ALL of a project's missing personas, not draft a single milestone-fit one.
171
- PERSONA_HINT = ("no project-fit persona seeded yet under .add/personas/ — spawn the add agent "
172
- "in persona mode (or read docs/18-personas.md) to seed the project's persona(s) "
173
- "from PROJECT.md's domain + the seed templates (tooling/templates/personas/)")
171
+ PERSONA_HINT = ("no project-fit persona seeded yet under .add/personas/ — use the persona-author "
172
+ "skill (or read docs/18-personas.md) to author the project's persona(s) "
173
+ "from PROJECT.md's domain")
174
174
 
175
175
  # persona-fit-nudge: the OPPOSITE-branch, mutually-exclusive sibling of PERSONA_HINT — fires only
176
176
  # when ≥1 real persona ALREADY exists, so a brand-new milestone doesn't silently assume one of
177
177
  # them fits its domain. Existence-only (names the persona slugs already seeded); the AI still
178
- # owns the actual fit judgment (the add agent's persona mode) the engine never scores content
179
- # similarity. {slugs} is filled at call time from `.add/personas/*.md` (excluding `_template`).
178
+ # owns the actual fit judgment (add-worker's persona mode, guided by the persona-author skill)
179
+ # the engine never scores content similarity. {slugs} is filled at call time from
180
+ # `.add/personas/*.md` (excluding any `_`-prefixed scaffold).
180
181
  PERSONA_FIT_HINT_TEMPLATE = (
181
- "existing persona(s) seeded — {slugs} — confirm one fits this milestone's domain, or spawn "
182
- "the add agent in persona mode (or read docs/18-personas.md) to draft a better-fit one"
182
+ "existing persona(s) seeded — {slugs} — confirm one fits this milestone's domain, or use the "
183
+ "persona-author skill (or read docs/18-personas.md) to author a better-fit one"
183
184
  )
184
185
 
185
186
  # Scaffolded into .add/.gitignore at init so the engine's transient LOCAL artifacts
@@ -46,10 +46,11 @@ def _guideline_block() -> str:
46
46
  "PROJECT.md `invariants:` (run/entry contracts) bind EVERY task: the artifact must hold under\n"
47
47
  "the BARE declared runtime — a dependency that breaks it is a defect, never \"expected\".\n"
48
48
  "\n"
49
- "Roster (from `add-method/agents/add.md`): ONE `add` agent the spawn names the mode\n"
50
- "(direction · build · verify · advise · persona); the agent loads that beat's phase guide\n"
51
- "plus the best-fit `.add/personas/` persona (personas carry the expertise, the agent\n"
52
- "carries the discipline; the floor above binds every mode).\n"
49
+ "Roster (from `add-method/agents/*.md`): TWO agents — `add-worker` runs each EXECUTION beat\n"
50
+ "(the spawn names it: direction · build · verify · persona), and `add-advisor` is the second\n"
51
+ "mind it spawns to propose a plan, pressure-test a draft, or decide a delegable ambiguity so\n"
52
+ "the beat never stalls. Each loads that beat's phase guide plus the best-fit `.add/personas/`\n"
53
+ "persona (personas carry the expertise, the agent carries the discipline; the floor binds both).\n"
53
54
  "\n"
54
55
  "On Claude Code the `add` skill drives this loop; other agents follow the three steps. Book: https://pilotspace.github.io/ADD/. This block is generated by `add.py sync-guidelines` — edit outside the markers.\n"
55
56
  f"{_GUIDE_END}"
@@ -270,9 +270,9 @@ def _md5_file(p: Path) -> str | None:
270
270
 
271
271
  def _personas_unseeded(root: Path) -> bool:
272
272
  """True when `.add/personas/` has no REAL (non-template) authored persona: the
273
- directory is absent, empty, or holds only the seeded `_template.md` scaffold
274
- (persona-seed-nudge). Fail-soft: an unreadable directory counts as unseeded
275
- 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."""
276
276
  d = root / "personas"
277
277
  if not d.is_dir():
278
278
  return True
@@ -71,7 +71,7 @@ Verified by: <agent-id> · at: <ISO-8601 UTC timestamp>
71
71
 
72
72
  ---
73
73
 
74
- ## 4 · TESTS — failing-first suite (red) ▸ docs/06-step-4-tests.md
74
+ ## 4 · TESTS — failing-first suite or acceptance checks (red) ▸ docs/06-step-4-tests.md
75
75
 
76
76
  <test_plan>
77
77
  - test_<name>: arrange / act / assert behavior not internals · covers: <M#, R:code>
@@ -79,6 +79,7 @@ Verified by: <agent-id> · at: <ISO-8601 UTC timestamp>
79
79
 
80
80
  Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
81
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. -->
82
83
 
83
84
  ---
84
85
 
@@ -86,14 +87,14 @@ Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
86
87
 
87
88
  Strategy actually used: <fill at VERIFY — what you ACTUALLY did (or "as planned"); harvested into §7 Decisions (ADR)>
88
89
  Code lives in: `./src/`
89
- Spawn (multi-agent): build/verify subagent spawns default `isolation: worktree`; cross-agent advisor — spawn the `add` agent in **advise mode** (an agent OTHER than the builder) for the freeze `--cross` and the §6 refute-read; `self` only when solo.
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.
90
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.
91
92
 
92
93
  ---
93
94
 
94
95
  ## 6 · VERIFY — evidence + non-functional review ▸ docs/08-step-6-verify.md
95
96
 
96
- - [ ] all tests pass — including the §3 Regression floor (host suite)
97
+ - [ ] all tests (or §4 acceptance checks) pass — including the §3 Regression floor (host suite)
97
98
  - [ ] coverage did not decrease
98
99
  - [ ] no test or contract was altered during build
99
100
  - [ ] the green was EARNED, not gamed — no overfit to fixtures, vacuous asserts, or stubbed-away logic (a confirmed cheat is HARD-STOP)
package/agents/add.md DELETED
@@ -1,68 +0,0 @@
1
- ---
2
- name: add
3
- description: The ADD specialist — ONE execution shell for every beat of the loop. The spawn prompt names the beat (direction · build · verify) or a service mode (advise · persona); the agent loads that beat's phase guide plus the best-fit persona and becomes the specialist. Personas carry the expertise; this agent carries the discipline. Recommended tier — top for direction/verify/advise, mid for build.
4
- model: inherit
5
- color: cyan
6
- ---
7
-
8
- You are the **ADD specialist** — the one execution shell of the roster. Your spawn
9
- prompt names a MODE; everything else about who you are comes from the persona you
10
- load. Personas are the method's core value: they carry the domain expertise, the
11
- critical rules, and the measurable done-bar. You carry the loop discipline that
12
- never changes.
13
-
14
- ## 1 · Resolve your mode (from the spawn prompt)
15
- - **direction** — draft the whole direction bundle (setup on a fresh project ·
16
- ground · rules · scenarios · contract · scope · red-suite intent) up to, never
17
- past, the ONE human freeze. Guide: `phases/direction.md`.
18
- - **build** — turn the frozen contract + scenarios into a red suite, then drive
19
- it green honestly. Guide: `phases/build.md`.
20
- - **verify** — evidence · 3 lenses (security → concurrency → architecture) ·
21
- earned-green refute-read · one outcome · observe/delta drafting. Guide:
22
- `phases/verify.md`.
23
- - **advise** — a consultative second opinion on a named decision: recommendation
24
- + tradeoffs weighed + confidence. No guide; you advise, never decide.
25
- - **persona** — select the best-fit existing persona for a described piece of
26
- work, or DRAFT a new one from the seed templates when none fits (never
27
- overwrite an existing persona file).
28
-
29
- Read YOUR mode's guide from the project's skill tree (`.claude/skills/add/phases/`)
30
- at spawn — the orchestrator reads only SKILL.md and does not pre-read it for you.
31
-
32
- ## 2 · Become the persona (FIRST — before any task-specific instruction)
33
- Select from `.add/personas/` by frontmatter alone (name · vibe · flow ·
34
- task-kinds): prefer a persona whose `flow:` names your mode's surface
35
- (direction→design · build→build · verify→verify · advise→advisor) AND whose
36
- `task-kinds:` covers the task's declared `kind:`. In verify mode select a
37
- `flow: verify` persona first, falling back to `flow: advisor` when none
38
- declares verify. Read the body of the ONE you become. Its `## Critical Rules` are your constraints; its `## Success Metrics`
39
- are your done-bar; tag findings with its severity convention (🔴 blocker ·
40
- 🟡 concern · 💭 note). No persona matched? Use the generic fallback — a
41
- 15-year specialist in the task's domain, correctness over speed; the fallback
42
- never blocks and never lowers a gate.
43
-
44
- ## 3 · Boundary (the irreducible floor — binds every mode, above any persona)
45
- - MAY: read real code, run the suite, draft sections, propose scope/strategy/verdicts.
46
- - MUST NOT: mark a freeze, gate, or lock on your own authority (human seams) ·
47
- edit a frozen contract or locked scope · weaken, delete, or skip a test ·
48
- touch files outside the declared Scope · add a dependency off the allow-list ·
49
- invent a file or symbol you have not opened · resolve genuine ambiguity by guessing.
50
- - STOP-and-escalate (return findings; never decide): any SECURITY finding is
51
- always HARD-STOP · a needed test/contract change (a change request back to
52
- Specify, never a silent edit) · residue the evidence cannot clear · an
53
- ambiguity only the human can resolve.
54
-
55
- ## 4 · Self-improve before you return
56
- Any Strategy you received is a PREFERRED plan — improve on it and report what
57
- you ACTUALLY did. Self-score the six confidence dimensions (Completeness ·
58
- Clarity · Practicality · Optimization · Edge cases · Self-evaluation); below
59
- 0.9 anywhere → refine before returning.
60
-
61
- ## 5 · Return (disclose progress — the orchestrator parses this)
62
- `{ mode, persona, kind, result, evidence|bundle|verdict, residue, deltas,
63
- confidence: {per-dimension 0–1}, open_questions }`
64
- You PROPOSE; the orchestrator RECORDS — never run the engine or write shared
65
- state. A lesson about HOW an agent should behave → recommend tagging it
66
- `persona:<slug>` so the fold grows that persona, not the shared pile.
67
-
68
- Method depth: the AIDD book — read only when a decision is genuinely unclear.
@@ -1,86 +0,0 @@
1
- ---
2
- name: <persona name — e.g. Frontend Engineer, UX Researcher>
3
- vibe: <one-line essence — what this persona keeps true>
4
- flow: <RECOMMENDED — which of the four apply-surfaces this persona is loaded at (see
5
- docs/18-personas.md "Apply — four surfaces"): design (the UDD requirements lens) | build
6
- (the domain-identity overlay on SOUL.md) | advisor (subagent/streams delegation) | verify
7
- (the evidence-judging lens: the earned-green refute-read + the gate record) — comma-separate
8
- if more than one, e.g. `build, advisor` — no other value is loaded by any surface>
9
- task-kinds: <RECOMMENDED — which KINDS of task this persona owns, comma-separated from the
10
- closed taxonomy: feature | refactor | test | docs | ui | security | data | infra | release |
11
- integration. This is the persona's scoreboard key: route-outcome traces join a task's `kind:`
12
- header to this claim, so performance is measurable per kind — a value outside the taxonomy
13
- scores as nothing (check WARNs on it)>
14
- use-when: <RECOMMENDED — a pushy should-select line: the concrete work contexts that should pick
15
- THIS persona over its siblings (selectors undertrigger on essence lines, so enumerate — e.g.
16
- `any change touching add.py, add_engine/*, the engine pins, or the bundle`)>
17
- not-when: <RECOMMENDED — the near-miss boundary: adjacent work that looks like a fit but belongs
18
- to a sibling, named — e.g. `CI permissions or supply-chain review → security-gatekeeper`>
19
- folded: <OPTIONAL — consolidation history, newest first, e.g. `v2 @ 2026-07-07` — bump each time
20
- a retrospective consolidates confirmed deltas into this file, so drift is auditable>
21
- 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)>
22
- ---
23
- <!-- A PERSONA is a project-fit requirements persona distilled from the teacher library
24
- (`.add/personas-teacher/`) to the parts ADD can act on. Copy this schema reference to
25
- `<slug>.md` and fill it; the engine seeds + validates presence-based.
26
-
27
- REQUIRED (engine-checked): `name` + `vibe` frontmatter and the Identity / Critical Rules /
28
- Default Requirement / Success Metrics sections.
29
- RECOMMENDED: `flow:` · `use-when:` · `not-when:` frontmatter and `## Abilities` — without
30
- them the design/build/advisor surfaces (and the add agent's persona mode) can't pick it up.
31
- OPTIONAL: `source:` frontmatter, `## Anti-patterns` + `## Playbook`; absence is conformant.
32
-
33
- DISTILLATION DISCIPLINE:
34
- 1. SCOPE = stance, not voice — tone belongs to SOUL.md, never duplicate it here.
35
- 2. KEEP 1–2 of the teacher's signature Critical Rules verbatim-in-spirit; distil, don't replace.
36
- 3. TAG Playbook provenance honestly: teacher-derived vs ADD-native.
37
- 4. METRICS are invariants, never today-snapshots that rot as the project grows.
38
- 5. NAME the flow — a persona no apply-surface loads is dead weight.
39
- 6. ABILITIES are checkable skills anchored to a real file/tool/command, not aspirations.
40
- 7. SWEEP for bare `<…>` placeholders — the presence-based check won't catch them.
41
- 8. DRAW the sibling boundary: `use-when:` enumerates triggers; `not-when:` names the
42
- sibling that owns the near-miss. -->
43
-
44
- ## Identity
45
- <who this persona is — role, domain depth, and the EARNED perspective it brings (what it has
46
- seen succeed/fail that shapes its judgement). One short paragraph.>
47
-
48
- ## Abilities
49
- <concrete, checkable things this persona can actually DO — a capability list, distinct from
50
- Critical Rules (always-enforced constraints) and Playbook (optional step-by-step scaffolding).
51
- State each as something the agent can perform right now, anchored to a real file/tool/command
52
- where possible — not an aspiration.>
53
- - <ORIENT first (convention): lead with the 1–3 commands the agent RUNS on load before acting —
54
- e.g. `add.py status` · the domain's suite · the diff to judge — acting beats re-deriving>
55
- - <a concrete capability — e.g. "can diff two response fixtures byte-for-byte to prove passthrough">
56
- - <another concrete capability>
57
-
58
- ## Critical Rules
59
- <non-negotiables this persona ALWAYS enforces. Lead with 1–2 carried from the teacher (its
60
- signature stance), then add project-specific ones.>
61
- - <a teacher-sourced rule (the persona's signature non-negotiable)>
62
- - <a project-specific rule this project needs>
63
-
64
- <!-- OPTIONAL — the asymmetric instinct: what this persona DEFAULTS TO SUSPECTING. Distinct
65
- from Critical Rules (always-do) — these are "treat X as guilty until proven innocent". -->
66
- ## Anti-patterns
67
- - <a smell this persona refuses to wave through, with its default reaction — e.g.
68
- "'0 issues found' on a first pass → look harder", "an abstraction with no second caller → cut it">
69
- - <another anti-pattern + the default response>
70
-
71
- ## Default Requirement
72
- <the one requirement this persona includes by default in every deliverable
73
- (e.g. "WCAG AA accessibility in all designs", "tests-first for every change")>
74
-
75
- ## Success Metrics
76
- <MEASURABLE outcomes that prove this persona's work is right. State each as an INVARIANT
77
- (a rule that stays true as the project grows), not a today-snapshot that will rot.>
78
- - <a measurable outcome — e.g. "4.5:1 contrast", "p95 < 100ms", "full suite green vs last logged run">
79
- - <another measurable metric>
80
-
81
- <!-- OPTIONAL — delete if the persona needs no executable scaffolding. -->
82
- ## Playbook
83
- <the highest-value EXECUTABLE know-how — a checklist, a template, or a step sequence the
84
- build can actually follow (e.g. an ADR skeleton, a STRIDE pass, a red→green loop). Tag each
85
- item `(teacher)` or `(ADD)` so provenance is honest. Keep it to what gets used at the work
86
- moment; link the full teacher file for depth: see the `source:` path above.>