@pilotspace/add 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +34 -1
- package/bin/cli.js +449 -58
- package/docs/02-the-flow.md +1 -1
- package/docs/04-step-2-scenarios.md +6 -0
- package/docs/09-the-loop.md +2 -0
- package/docs/16-releasing.md +182 -0
- package/docs/README.md +3 -0
- package/docs/appendix-c-glossary.md +19 -1
- package/package.json +4 -1
- package/skill/add/SKILL.md +33 -0
- package/skill/add/fold.md +53 -35
- package/skill/add/graduate.md +2 -1
- package/skill/add/intake.md +1 -0
- package/skill/add/loop.md +18 -4
- package/skill/add/phases/0-setup.md +1 -1
- package/skill/add/phases/2-scenarios.md +2 -0
- package/skill/add/phases/3-contract.md +1 -1
- package/skill/add/phases/6-verify.md +1 -1
- package/skill/add/release.md +115 -0
- package/skill/add/report-template.md +42 -6
- package/skill/add/scope.md +1 -0
- package/tooling/add.py +862 -91
- package/tooling/templates/GLOSSARY.md.tmpl +2 -0
- package/tooling/templates/MILESTONE.md.tmpl +23 -0
- package/tooling/templates/TASK.md.tmpl +5 -1
package/docs/09-the-loop.md
CHANGED
|
@@ -47,6 +47,8 @@ A spec delta feeds the *next feature*. But a loop also teaches the **method itse
|
|
|
47
47
|
|
|
48
48
|
Each delta is one tagged entry — `- [COMPETENCY · status] the learning (evidence: a pointer)` — and the evidence is **required**: a failing scenario, a production signal, a review note. No evidence means it is an opinion, not a delta. The AI **emits** deltas as `open`; it never consolidates its own. Consolidation is judgment, and judgment is the human's — the same verify/observe decision point that keeps the AI from grading its own work.
|
|
49
49
|
|
|
50
|
+
**The ship review.** Before the consolidation, once a milestone's goal is met, the AI fills the milestone's `## Close — ship review` — the whole-milestone, cross-task evidence a person reads *before* checking the exit-criteria boxes (it is evidence, **not a new gate**). It has three parts: **ship by domain** — what changed in each bounded context (`tooling` · `skill` · `book`); **cross-task evidence** — one row per task (its gate, its tests, any residue); and a **goal-met map** — each exit criterion tied to the evidence that satisfies it. The AI then defines the milestone's **release steps**: the ordered, tool-agnostic hints to ship the milestone, of which `merge` is one small step (a pull request, an exported hand-off document, a tag or a publish are others). Those steps **feed** the release scope, not a second flow. The ritual itself lives in the `add` skill's `loop.md`; this chapter only names it.
|
|
51
|
+
|
|
50
52
|
**The consolidation.** At milestone close (or on demand, when open deltas pile up), a person runs the retrospective consolidation: **gather** every `open` delta across the milestone's tasks, **group** them by competency, **propose** the exact foundation edit for each, **confirm** with the human one by one, then **write** — append-only, newest-first (the newest record prepended at the top) — flipping each delta to `folded` (merged) or `rejected` (considered and deliberately not merged, left in place so the trail survives), and bumping the `foundation-version:` marker. `DDD`/`SDD`/`UDD` deltas consolidate into the matching section of `PROJECT.md`; `TDD`/`ADD` consolidate into `CONVENTIONS.md` (they sharpen the engine, not the product); and **every** consolidation also prepends one row at the top of `PROJECT.md` §Key Decisions — the universal, auditable record of what the foundation learned.
|
|
51
53
|
|
|
52
54
|
**Tooling.** `add.py deltas` lists every open delta across the project (so nothing waiting to be consolidated is invisible); `add.py check` lints each delta's well-formedness — known competency tag, valid status, non-empty evidence. There is deliberately **no `add.py fold`**: the engine stays judgment-free, and the ritual lives with the human who owns it.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# 16 · Releasing
|
|
2
|
+
|
|
3
|
+
[← 15 Foundations & Lineage](./15-foundations-and-lineage.md) · [Contents](./README.md) · Next: [Appendix A Templates →](./appendix-a-templates.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The flow chapters ([03](./03-step-1-specify.md)–[08](./08-step-6-verify.md)) take one feature from
|
|
8
|
+
spec to verified. The loop chapter ([09](./09-the-loop.md)) keeps a milestone going until its goal
|
|
9
|
+
is met. The stages chapter ([10](./10-setup-and-stages.md)) graduates the project's rigor. None of
|
|
10
|
+
them *ship*. This chapter names the act every project eventually performs and that the method, until
|
|
11
|
+
now, never formalized: bundling closed milestones into a versioned, user-facing release whose notes
|
|
12
|
+
are evidence-backed, whose risk is disclosed, and whose behaviour is then watched.
|
|
13
|
+
|
|
14
|
+
Releasing is the **fifth scope level** — after the task, the milestone, the foundation/setup level,
|
|
15
|
+
and stage graduation. Like every scope level it runs the same shape: **gather → propose → the human
|
|
16
|
+
confirms → the engine records and enforces a floor.** And like graduation, it ends with an outward
|
|
17
|
+
act the human owns. The operational recipe lives in the `release.md` skill guide; this chapter is the
|
|
18
|
+
*why* behind it.
|
|
19
|
+
|
|
20
|
+
## 16.1 · Why release is its own scope level
|
|
21
|
+
|
|
22
|
+
A number bump is not a release. A release is the moment one or more **closed milestones** become a
|
|
23
|
+
versioned cut that real users can run. Three distinctions make it its own scope level:
|
|
24
|
+
|
|
25
|
+
- **Milestone ≠ release.** A milestone is *feature-complete and consolidated* — its goal is met and
|
|
26
|
+
its lessons are gathered into the foundation (see [14 · The foundation](./14-foundation.md) and the
|
|
27
|
+
`fold.md` retrospective consolidation). A release is *shipped and watched*. The first is an internal
|
|
28
|
+
state; the second faces outward.
|
|
29
|
+
- **Graduation ≠ release.** Stage graduation moves the project's *rigor* (mvp → production); a release
|
|
30
|
+
ships a *version*. The two axes are orthogonal: you cut a prototype preview, an mvp beta, and a
|
|
31
|
+
production GA, each at its own stage. You release at every stage, not only at the end.
|
|
32
|
+
- **A release bundles; it does not equal.** One version may attribute several milestones — "we shipped
|
|
33
|
+
after a couple of milestones closed" is the normal case, not the exception. Forcing one release per
|
|
34
|
+
milestone is the anti-pattern; the decoupling is the whole point.
|
|
35
|
+
|
|
36
|
+
So release sits beside the other scope levels rather than inside any of them. The granularity ladder
|
|
37
|
+
is now complete: intake → milestone → task, with stage graduation and release as the two cross-cutting
|
|
38
|
+
levels that change *rigor* and ship *versions* respectively (see the **Scope level** entry in
|
|
39
|
+
[Appendix C](./appendix-c-glossary.md)).
|
|
40
|
+
|
|
41
|
+
## 16.2 · The cue and the inventory — gather, never judge
|
|
42
|
+
|
|
43
|
+
The trigger is a status line. When at least one milestone is `done`, archived, and not yet attributed
|
|
44
|
+
to any release, `add.py status` prints:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
→ releasable: N milestone(s) closed since last release
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That line is a **tally**, never a verdict. It counts archived-but-unreleased milestones; it is silent
|
|
51
|
+
for a project that has never released or has already shipped everything (grandfathered — zero change).
|
|
52
|
+
It says *there is something to consider*, not *you are ready*.
|
|
53
|
+
|
|
54
|
+
To gather the cut's evidence, run `add.py release-report`. It clusters five labeled record-sets: the
|
|
55
|
+
closed milestones since the last release · their **consolidated deltas** (the "what changed" record) ·
|
|
56
|
+
the open `RISK-ACCEPTED` waivers riding into the release · any open security `HARD-STOP` (a blocker) ·
|
|
57
|
+
the §2 scenarios to take live as monitors. The report **gathers; it does not judge** — there is no
|
|
58
|
+
readiness score to read off, because a tally that pretended to be a verdict would invite reading the
|
|
59
|
+
number instead of the evidence.
|
|
60
|
+
|
|
61
|
+
The reuse claim, stated plainly: **the consolidated deltas are the changelog source.** You do not write
|
|
62
|
+
release notes from memory. The foundation already recorded what changed when each milestone was
|
|
63
|
+
consolidated (`fold.md`); the release surfaces it. This is why release runs *after* consolidation, not
|
|
64
|
+
before. The lifecycle order is one line:
|
|
65
|
+
`milestone-done → fold → compact → archive → (repeat ≥1×) → release → watch`.
|
|
66
|
+
|
|
67
|
+
## 16.3 · Drafting the notes and the version — the proposal
|
|
68
|
+
|
|
69
|
+
From those record-sets you draft a [Keep a Changelog](https://keepachangelog.com/) entry: group the
|
|
70
|
+
changes under Added / Changed / Fixed and name the headline capabilities concretely, in the user's
|
|
71
|
+
language, not the commit's. Each bundled milestone's goal anchors one or more entries.
|
|
72
|
+
|
|
73
|
+
Then propose the version. Semver is a decision the evidence informs but does not make for you: a
|
|
74
|
+
breaking change is a MAJOR, a new capability a MINOR, a fix-only cut a PATCH. You propose the bump; the
|
|
75
|
+
**human confirms it** — the version is a judgement, not a default the tool fills in.
|
|
76
|
+
|
|
77
|
+
Both the notes and the version are *shown before they are asked about*. Present the drafted entry, the
|
|
78
|
+
proposed version, and the waivers shipping in this cut via the report template — opening with the ARC
|
|
79
|
+
(goal · done · plan) — and let the human approve once. The question is a summary to decide on; the
|
|
80
|
+
artifact itself is rendered first, never pre-stamped.
|
|
81
|
+
|
|
82
|
+
## 16.4 · The floor — what the engine enforces
|
|
83
|
+
|
|
84
|
+
`add.py release <version>` is **guarded**. Before it records anything it enforces a readiness floor,
|
|
85
|
+
refusing with a non-zero exit and leaving every file byte-unchanged on any of four conditions:
|
|
86
|
+
|
|
87
|
+
- `release_security_open` — an open security `HARD-STOP` exists. This is the non-negotiable one: a
|
|
88
|
+
security finding is never shipped. Resolve it first, as a change request back to Specify.
|
|
89
|
+
- `release_tests_red` — the suite is not green. A release ships on evidence, not on a plausible diff.
|
|
90
|
+
- `release_no_closed_milestone` — nothing new since the last release. The cut would be a no-op; do not
|
|
91
|
+
bump a version to mark time.
|
|
92
|
+
- `release_undisclosed_waiver` — a `RISK-ACCEPTED` waiver rides into the release but is missing from the
|
|
93
|
+
notes. Disclosure *is* the floor: a shipped risk the user cannot read about is a hidden risk.
|
|
94
|
+
|
|
95
|
+
The security stop is **un-forceable.** `--force` exists for grandfathered and brownfield first-cuts —
|
|
96
|
+
the same authority valve as `stage --force` ([10 · Setup and stages](./10-setup-and-stages.md)) — and
|
|
97
|
+
it can override the other three rejects, but it can never override `release_security_open`. This mirrors
|
|
98
|
+
the verify gate exactly ([08 · Verify](./08-step-6-verify.md)) and the governance ceiling that no
|
|
99
|
+
autonomy level may lift ([11 · Governance](./11-governance.md)): a security `HARD-STOP` is the one
|
|
100
|
+
outcome the method refuses to auto-pass, at verify and again at the cut.
|
|
101
|
+
|
|
102
|
+
## 16.5 · The cut versus the ship — the engine records, the human ships
|
|
103
|
+
|
|
104
|
+
Only after the human confirms do you run the cut. And here is the line that keeps releasing honest:
|
|
105
|
+
**the engine records; the human ships.**
|
|
106
|
+
|
|
107
|
+
`add.py release <version>` **records** the marker. It prepends the entry to `CHANGELOG.md`, stamps one
|
|
108
|
+
append-only row (newest-first, like the §Key Decisions log) into `RELEASES.md` — date · version ·
|
|
109
|
+
milestones · waivers shipped · evidence — and attributes the bundled milestones to this version, so the
|
|
110
|
+
cue stops firing for them. The ledger is the attribution source: a milestone is "released" because a
|
|
111
|
+
`RELEASES.md` row says so, never because a file was edited to claim it.
|
|
112
|
+
|
|
113
|
+
What the engine does **not** do is act outward. It **never tags, publishes, or deploys.** The outward
|
|
114
|
+
act — `git tag`, `npm publish`, the deploy pipeline — is the human's, tool-agnostic, exactly as the
|
|
115
|
+
method tool "never renders" in design and "never spawns" a subagent. Design-for-failure — timeouts,
|
|
116
|
+
retries, rollback, a tested revert path — belongs in the pipeline the human owns, not in a method tool
|
|
117
|
+
that has no business holding deploy credentials. The tag is the human-gated trigger; the record is the
|
|
118
|
+
engine's receipt that the floor was met.
|
|
119
|
+
|
|
120
|
+
> **A caveat worth one paragraph.** `add.py release` writes `CHANGELOG.md` at the **project root**. That
|
|
121
|
+
> is the right default for most repositories. But a repo with a different changelog convention — for
|
|
122
|
+
> instance a **nested-package** layout whose root `CHANGELOG.md` is a deliberate pointer to a package's
|
|
123
|
+
> own changelog — will get release blocks *prepended above* its existing content rather than replacing
|
|
124
|
+
> it (the writer preserves what is there; it does not clobber). Reconcile per repo: either let the root
|
|
125
|
+
> file carry the canonical log, or point the human's publish step at the package changelog the team
|
|
126
|
+
> treats as the source of truth.
|
|
127
|
+
|
|
128
|
+
## 16.6 · Watch and the hotfix path — re-entering observe
|
|
129
|
+
|
|
130
|
+
A release is not the finish line; it is where the most reliable information finally appears. The §2
|
|
131
|
+
scenarios that were pass/fail cases at build time become **live monitors** for the released version, and
|
|
132
|
+
error-budget burn feeds the next loop. Live-registry and deploy confirmation are post-cut *evidence*,
|
|
133
|
+
gathered after the tag — not unit tests pretending to be one.
|
|
134
|
+
|
|
135
|
+
The unhappy path is first-class. A regression found in the wild re-enters at Specify as a **change
|
|
136
|
+
request**, which narrows to a **hotfix release** — the same seven-step flow, scoped to the fix, cut as a
|
|
137
|
+
PATCH. Releasing does not have a separate emergency mode; it has the ordinary flow at a tighter scope.
|
|
138
|
+
|
|
139
|
+
Depth follows the stage, as everywhere in the method:
|
|
140
|
+
|
|
141
|
+
- **prototype / poc** — a one-line preview note and a tag; no deploy ceremony. The point is feedback.
|
|
142
|
+
- **mvp** — full notes, a tag, a guarded publish; watch the headline scenarios.
|
|
143
|
+
- **production** — every step at full rigor: notes, tag, a deploy behind a rollback-tested pipeline,
|
|
144
|
+
live scenario monitors, and error-budget watch. The hotfix path is a routine capability here, not a
|
|
145
|
+
fire drill.
|
|
146
|
+
|
|
147
|
+
## 16.7 · The flow, in one arc
|
|
148
|
+
|
|
149
|
+
One arc, seven steps:
|
|
150
|
+
|
|
151
|
+
**cue → gather → draft notes → readiness floor → human confirms → cut → watch**
|
|
152
|
+
|
|
153
|
+
1. **cue** — `add.py status` prints `→ releasable: N` when an archived milestone is unreleased.
|
|
154
|
+
2. **gather** — `add.py release-report` clusters the five record-sets (gather, never judge).
|
|
155
|
+
3. **draft notes** — a Keep-a-Changelog entry drawn from the consolidated deltas; propose the version.
|
|
156
|
+
4. **readiness floor** — the four guarded rejects; the security stop is un-forceable.
|
|
157
|
+
5. **human confirms** — the notes, version, and waivers are shown, then approved once.
|
|
158
|
+
6. **cut** — `add.py release <version>` records the CHANGELOG block + the `RELEASES.md` row + attribution.
|
|
159
|
+
7. **watch** — the scenarios become monitors; a wild regression becomes a PATCH hotfix release.
|
|
160
|
+
|
|
161
|
+
The recipe for each step — flags, the report's `--json` shape, the exact ledger row — lives in the
|
|
162
|
+
`release.md` skill guide. This chapter is the reasoning; the guide is the procedure.
|
|
163
|
+
|
|
164
|
+
## 16.8 · Worked example — this method's own 1.5.0
|
|
165
|
+
|
|
166
|
+
The repository already runs this by hand, which is the best evidence the flow is real. The
|
|
167
|
+
`udd-design-loop` milestone closed (4/4) and consolidated into `foundation-version 33`. From those
|
|
168
|
+
deltas the human drafted the `## [1.5.0]` changelog entry, bumped the three version sources in
|
|
169
|
+
lockstep, and a forward-pinned `test_release_1_5_0.py` asserted in-repo readiness: the versions agree,
|
|
170
|
+
the changelog lineage survives, the feature anchors are named, and the engine is untouched by the
|
|
171
|
+
release. The cut itself — the `git tag` that triggers the npm and PyPI publish — stayed human-gated,
|
|
172
|
+
and the live-registry confirmation was gathered *after* the tag as verify evidence, never as a unit
|
|
173
|
+
test.
|
|
174
|
+
|
|
175
|
+
That ritual is what this chapter formalizes. `release-report` gathers the inventory, the floor enforces
|
|
176
|
+
the security stop and the disclosures, `add.py release` records the cut, and the human still owns the
|
|
177
|
+
tag. The method releases itself the way it asks every project to release: gather the evidence, disclose
|
|
178
|
+
the risk, record the marker, and let a person make the outward call.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
[← 15 Foundations & Lineage](./15-foundations-and-lineage.md) · [Contents](./README.md) · Next: [Appendix A Templates →](./appendix-a-templates.md)
|
package/docs/README.md
CHANGED
|
@@ -54,6 +54,9 @@ For every feature, before AI writes any code, you write four short artifacts in
|
|
|
54
54
|
**Lineage**
|
|
55
55
|
- [15 · Foundations & Lineage](./15-foundations-and-lineage.md)
|
|
56
56
|
|
|
57
|
+
**Releasing**
|
|
58
|
+
- [16 · Releasing](./16-releasing.md)
|
|
59
|
+
|
|
57
60
|
**Part IV — Reference**
|
|
58
61
|
- [Appendix A · Templates](./appendix-a-templates.md)
|
|
59
62
|
- [Appendix B · Prompt library](./appendix-b-prompts.md)
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
|
|
43
43
|
**The decision arc** — the three engine-sourced lines a gate report opens with at every **decision point**: `goal:` the milestone goal the work serves · `done:` the achievement, the proven progress toward it (the gate reports render this line as `done`) · `plan:` what comes next. What `done` reports adapts per gate (verify: tests + evidence · milestone close: exit-criteria met · intake: the request sized) while the three-part shape stays constant. Rendered first, above the report's summary, so the human confirms with sight of the whole trajectory, not a local snapshot. Engine-sourced like all evidence — goal · done · plan are pulled from `add.py` output, never re-typed. Presentation only: it never adds a gate or changes a `PASS` / `RISK-ACCEPTED` / `HARD-STOP` / freeze outcome. The report it opens is the chat report a person reads at a decision point — distinct from the three Test/Quality/Risk reports a verify gate produces ([11 Governance](./11-governance.md)). See the `add` skill's `report-template.md`.
|
|
44
44
|
|
|
45
|
+
**Guided decision** — a **decision point** presented not as a bare next-step line but as one highlighted **recommended pick** plus its real, described alternatives (each with its one-line consequence), so the human chooses with the recommendation and what each option costs already in view. It refines the report's DECISION block — composing with **the decision arc**, never adding a gate — and fires at human gates only (never at an autonomous `[you drive]` step). The sibling of the decision arc: both are what the human sees when it is their turn. See the `add` skill's `report-template.md` for the convention itself.
|
|
46
|
+
|
|
47
|
+
**Recommended pick** — the one option a **guided decision** highlights with the `▶ … (recommended)` marker: exactly one, never zero and never two. The AI's confidence self-score informs the pick; the human overrides it freely. See **Guided decision**.
|
|
48
|
+
|
|
45
49
|
**Specification bundle** (formerly "the one-approval front") — §1–§4 of a task (spec · scenarios · contract · failing tests) drafted by the AI as one piece and approved by a person **once**, at the contract freeze. Rejecting any part returns the whole bundle to draft. The single approval it carries is the bundle approval.
|
|
46
50
|
|
|
47
51
|
**Retrospective consolidation** (formerly "the fold / fold ritual") — the milestone-close (or on-demand) step where a person gathers `open` lessons learned, confirms each, and the AI writes them append-only into the versioned foundation, bumping `foundation-version:`. The AI never self-approves a consolidation. The machine names keep their names: `fold.md`, the `folded` delta status, and `add.py deltas`.
|
|
@@ -72,10 +76,24 @@
|
|
|
72
76
|
|
|
73
77
|
**Baseline approval** (formerly "the lock-down") — the single human gate ending autonomous setup: an explicit yes that freezes the foundation, first scope, and first contract together; runs as `add.py lock --by <name>`.
|
|
74
78
|
|
|
75
|
-
**Scope level** (formerly "altitude") — the granularity a decision lives at: intake level (request → versioned scope) · milestone level · setup/foundation level · task level. (A cross-stage decision lives one level out, at the **stage-graduation** loop — which `graduate.md` also numbers as a scope level; see **Stage graduation**.) One ⚠-assumption notation is shared across every scope level.
|
|
79
|
+
**Scope level** (formerly "altitude") — the granularity a decision lives at: intake level (request → versioned scope) · milestone level · setup/foundation level · task level · release level (≥1 closed milestone → a versioned, watched cut; see **Release scope level**). (A cross-stage decision lives one level out, at the **stage-graduation** loop — which `graduate.md` also numbers as a scope level; see **Stage graduation**.) One ⚠-assumption notation is shared across every scope level.
|
|
76
80
|
|
|
77
81
|
**Autonomy level** (formerly "autonomy dial") — the explicit per-task setting (`autonomy: manual | conservative | auto`, an ordered ladder manual < conservative < auto) choosing who resolves Verify: `auto` auto-PASSes on complete evidence, `conservative` keeps a human at the gate, `manual` is the strict floor (the human owns the gate; nothing auto-resolves). A high-risk scope refuses an unguarded `auto` — it must be lowered to `manual` or `conservative`. New tasks seed a visible, overridable `autonomy: auto`; a live task with no level warns (`implicit_autonomy`), a token outside the set is rejected (`unknown_autonomy_level`).
|
|
78
82
|
|
|
83
|
+
**Release** — a versioned, user-facing cut that bundles one or more closed milestones into something real users can run; its notes are evidence-backed, its risk is disclosed, and its behaviour is then watched. Recorded with `add.py release <version>`, which writes the changelog block and the ledger row but never tags, publishes, or deploys — the outward act stays human-owned. See [16 · Releasing](./16-releasing.md).
|
|
84
|
+
|
|
85
|
+
**Release scope level** — the fifth scope level: releasing as its own granularity, orthogonal to the stage. A release bundles ≥1 closed milestone (never forced one-per-milestone) and may be cut at any stage — prototype preview, mvp beta, production GA. Distinct from milestone-close (feature-complete + consolidated) and from stage graduation (which changes rigor, not version). See **Scope level** and [16 · Releasing](./16-releasing.md).
|
|
86
|
+
|
|
87
|
+
**Ship review** — the whole-milestone, cross-task evidence the AI fills at milestone close in the `## Close — ship review` section: ship-by-domain (what changed per bounded context), cross-task evidence (one row per task: gate · tests · residue), and a goal-met map (each exit criterion tied to its evidence). A person reads it *before* checking the exit-criteria boxes — evidence, not a new gate. The ritual lives in the `add` skill's `loop.md`. See [09 · The Loop](./09-the-loop.md).
|
|
88
|
+
|
|
89
|
+
**Release steps** — the AI-defined, per-milestone ordered hints to ship a closed milestone, of which `merge` is one small step (a pull request, an exported hand-off document, a tag or a publish are others). Defined at close and **fed** into the release scope (see **Release**), never a second flow.
|
|
90
|
+
|
|
91
|
+
**Readiness floor** — the engine-enforced pre-cut gate `add.py release` applies before it records anything: a green suite, zero open security `HARD-STOP`, a closed-and-unreleased milestone to bundle, and every riding `RISK-ACCEPTED` waiver disclosed in the notes. Its four rejects are `release_security_open` (un-forceable) · `release_tests_red` · `release_no_closed_milestone` · `release_undisclosed_waiver`; `--force` may override every reject except the security stop.
|
|
92
|
+
|
|
93
|
+
**RELEASES.md ledger** — the append-only, newest-first trail of release rows at the project root (date · version · milestones · waivers shipped · evidence). Like the §Key Decisions log it is never rewritten; a superseded or yanked version is recorded as a new row. The ledger is the attribution source — a milestone is "released" because a row says so — which is why the `→ releasable` cue never has to read a milestone file.
|
|
94
|
+
|
|
95
|
+
**Hotfix release** — a narrowed PATCH cut that re-enters at Specify as a change request when a regression is found in a released version. It runs the same seven-step release flow scoped to the fix; releasing has no separate emergency mode, only the ordinary flow at a tighter scope.
|
|
96
|
+
|
|
79
97
|
**Auto-ready goal** — a milestone goal whose every exit criterion **cites a verifier** (`(verify: <test|command|metric>)`), so the engine can self-verify the result against the goal without human judgment. It is the prerequisite by which **autonomy is earned by goal-clarity**: the **autonomy level** governs *who* resolves Verify, but a clarified, machine-checkable goal is what makes a self-verifying run meaningful. `add.py check` raises a `goal_not_auto_ready` **WARN** (never red) for the active milestone until it has an auto-ready goal (≥1 exit criterion and every one cited), and `status` surfaces it (`goal-ready: auto-ready ✓` / cited-of-total); a zero-criteria goal reads not-auto-ready and is milestone-shaping's nudge, not this warning's. The lint forces a citation *slot* per criterion — it raises the floor but **cannot prove the citation is real** (a human can write `(verify: it works)`): citation-theater is the accepted irreducible floor, and the freeze gate and autonomy behavior are unchanged by it.
|
|
80
98
|
|
|
81
99
|
**Automated quality gate** (formerly "evidence auto-gate") — the Verify resolver under `autonomy: auto`: a run may auto-PASS on complete evidence, recorded as *auto-resolved*; a security finding always escalates (`HARD-STOP`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilotspace/add",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "ADD (AI-Driven Development) — a minimal, state-tracked Claude Code skill that drives every feature through Specify → Scenarios → Contract → Tests → Build → Verify → Observe. Ships the AIDD book as its trust layer.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"add": "bin/cli.js"
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"publishConfig": {
|
|
9
9
|
"access": "public"
|
|
10
10
|
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@clack/prompts": "^1.5.1"
|
|
13
|
+
},
|
|
11
14
|
"scripts": {
|
|
12
15
|
"test": "python3 -m unittest discover -s tooling -p 'test_*.py'",
|
|
13
16
|
"prepublishOnly": "python3 -m unittest discover -s tooling -p 'test_packaging.py'"
|
package/skill/add/SKILL.md
CHANGED
|
@@ -11,6 +11,15 @@ description: >-
|
|
|
11
11
|
new feature and you want spec/tests-first discipline instead of vague-prompt
|
|
12
12
|
coding. Also use it to resume work across sessions (it reads `.add/state.json`
|
|
13
13
|
so you never re-read the whole repo).
|
|
14
|
+
user-invocable: true
|
|
15
|
+
when_to_use: "Invoke in any repo with a `.add/` directory, or when the user wants spec/tests-first feature work, resumes ADD work, or asks to start/advance a task."
|
|
16
|
+
category: workflows
|
|
17
|
+
keywords: [add, aidd, ai-driven-development, spec-first, tdd, contract, scenarios, verify, milestone, task-orchestration]
|
|
18
|
+
argument-hint: "[describe your goals] OR [describe what is your expectation] (e.g., implement authentication flows for both server api and client side to handle email + Google OAuth login, session refresh, RBAC guards, and a profile page that survives page reloads)"
|
|
19
|
+
license: MIT
|
|
20
|
+
metadata:
|
|
21
|
+
author: add
|
|
22
|
+
version: "1.0.0"
|
|
14
23
|
---
|
|
15
24
|
|
|
16
25
|
# ADD — the orchestration engine
|
|
@@ -25,6 +34,23 @@ you are so context never rots across sessions.
|
|
|
25
34
|
|
|
26
35
|
## Always start here (orient — do not skip)
|
|
27
36
|
|
|
37
|
+
The engine lives at `.add/tooling/add.py` and the book at `.add/docs/`. Make sure the
|
|
38
|
+
engine is in this project before anything else:
|
|
39
|
+
|
|
40
|
+
- If `.add/tooling/add.py` exists, go straight to `status` below.
|
|
41
|
+
- If it does NOT exist, ADD was installed as a Claude Code plugin — the engine and the
|
|
42
|
+
book ride in the plugin, not the project. Materialize them into the project once, before
|
|
43
|
+
any other step:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
node "${CLAUDE_PLUGIN_ROOT}/bin/cli.js" init --no-skill
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That drops `.add/tooling/` (the engine) and `.add/docs/` (the book) here, so the engine,
|
|
50
|
+
the book, and the agent-agnostic guideline block in `CLAUDE.md` all work for every agent
|
|
51
|
+
and for a human at the shell — exactly like an npm or pip install. The skill itself stays
|
|
52
|
+
in the plugin, so nothing is duplicated.
|
|
53
|
+
|
|
28
54
|
Run the tool to find the resume point instead of re-reading the repo:
|
|
29
55
|
|
|
30
56
|
```bash
|
|
@@ -121,6 +147,13 @@ orchestration: gather `graduation-report` analytics → co-specify interview →
|
|
|
121
147
|
milestone → human confirm → then (and only then) `stage production`. The flip is guarded
|
|
122
148
|
(`stage_no_roadmap`) and is the FINAL step — never a bare label change.
|
|
123
149
|
|
|
150
|
+
When one or more milestones have closed since the last release, `add.py status` prints
|
|
151
|
+
**`→ releasable: N milestone(s) closed since last release`** — read `release.md` for the 5th scope
|
|
152
|
+
level: gather `release-report` → draft notes from the consolidated deltas → meet the readiness floor
|
|
153
|
+
(security HARD-STOP is un-forceable) → human confirms → `add.py release <version>` records the cut
|
|
154
|
+
(CHANGELOG + `RELEASES.md` ledger + milestone attribution) → watch. The engine records; the human runs
|
|
155
|
+
the tag / publish / deploy. A release bundles ≥1 milestone and is orthogonal to stage.
|
|
156
|
+
|
|
124
157
|
## Non-negotiable rules (from the method)
|
|
125
158
|
|
|
126
159
|
<constraints>
|
package/skill/add/fold.md
CHANGED
|
@@ -1,60 +1,77 @@
|
|
|
1
|
-
# Consolidating
|
|
1
|
+
# Consolidating lessons — how the foundation self-improves
|
|
2
2
|
|
|
3
|
-
This **closes the loop**. `deltas.md` lets a task EMIT
|
|
4
|
-
OBSERVE phase); the retrospective consolidation gathers the confirmed ones and writes them into a
|
|
5
|
-
so `DDD · SDD · UDD · TDD · ADD` sharpen across milestones instead of drifting.
|
|
3
|
+
This **closes the loop**. `deltas.md` lets a task EMIT lessons (`open` lessons learned in its
|
|
4
|
+
OBSERVE phase); the retrospective consolidation gathers the confirmed ones and writes them into a
|
|
5
|
+
**versioned foundation**, so `DDD · SDD · UDD · TDD · ADD` sharpen across milestones instead of drifting.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Since v3 this is mechanized by a command — `add.py fold` — but the command stays **judgment-free**:
|
|
8
|
+
it only ever TRANSCRIBES each lesson's OWN captured text into its routed home and bumps the version.
|
|
9
|
+
It NEVER composes or merges prose, and it never **self-approves** a consolidation — running the
|
|
10
|
+
command records the human's confirmation, it does not replace their judgment. Deciding WHICH lessons
|
|
11
|
+
to keep, and later polishing the raw transcribed bullets into lean one-screen prose, remain the
|
|
12
|
+
human's work — the latter via the **compaction door** (`compact-foundation.md`).
|
|
9
13
|
|
|
10
14
|
## When to consolidate
|
|
11
15
|
|
|
12
16
|
At **milestone close** (the natural "version bump to the foundation"), or **on demand** when open
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
lessons have piled up. One run of `add.py fold` is ONE consolidation session: it bumps
|
|
18
|
+
`foundation-version` exactly once and stamps every lesson it resolves with that one new version.
|
|
15
19
|
|
|
16
20
|
## The ritual
|
|
17
21
|
|
|
18
|
-
1. **Gather** —
|
|
19
|
-
2. **
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
1. **Gather** — `add.py deltas` reads every task's OBSERVE block for lessons still `open` (by the machine heading).
|
|
23
|
+
2. **Confirm** — decide which to keep; a lesson you do NOT want is marked `rejected` and left in place
|
|
24
|
+
(see below). Running `add.py fold` over the rest IS your confirmation — the command needs no separate flag.
|
|
25
|
+
3. **Write** — `add.py fold [--task <slug>] [--comp <TAG>]` performs the mechanical write atomically:
|
|
26
|
+
- flips each selected `open` lesson to `folded` and stamps it `[folded foundation-version N]`;
|
|
27
|
+
- transcribes each lesson VERBATIM as one bullet at the TOP (newest-first) of its routed section (below);
|
|
28
|
+
- prepends one row to §Key Decisions (date · what · why · outcome);
|
|
29
|
+
- bumps `foundation-version` by one.
|
|
30
|
+
Validate-all-then-write: if any precondition fails the command writes NOTHING (the tree stays byte-identical).
|
|
31
|
+
4. **Propose & polish** — the transcribed bullets are RAW; afterward you consolidate/merge them into
|
|
32
|
+
lean prose (append-only, newest-first) via the compaction door. The engine never does this editorial step.
|
|
23
33
|
|
|
24
34
|
## Consolidation routing (every competency has a home)
|
|
25
35
|
|
|
26
36
|
| competency | consolidates into | how |
|
|
27
37
|
|------------|-----------|-----|
|
|
28
|
-
| `DDD` | `PROJECT.md` §Domain
|
|
29
|
-
| `SDD` | `PROJECT.md` §Spec
|
|
30
|
-
| `UDD` | `PROJECT.md` §Users
|
|
31
|
-
| `TDD` | `CONVENTIONS.md` |
|
|
32
|
-
| `ADD` | `CONVENTIONS.md` |
|
|
38
|
+
| `DDD` | `PROJECT.md` §Domain | a transcribed bullet at the top |
|
|
39
|
+
| `SDD` | `PROJECT.md` §Spec | a transcribed bullet at the top |
|
|
40
|
+
| `UDD` | `PROJECT.md` §Users | a transcribed bullet at the top |
|
|
41
|
+
| `TDD` | `CONVENTIONS.md` §Method learnings | a transcribed bullet (no PROJECT.md section — it is the engine) |
|
|
42
|
+
| `ADD` | `CONVENTIONS.md` §Method learnings | a transcribed bullet (likewise the engine) |
|
|
33
43
|
|
|
34
|
-
**Every** consolidation — whatever the competency — ALSO prepends one row at the TOP of `PROJECT.md`
|
|
35
|
-
(date · decision · why · outcome): the universal, auditable trail of
|
|
44
|
+
**Every** consolidation — whatever the competency — ALSO prepends one row at the TOP of `PROJECT.md`
|
|
45
|
+
**§Key Decisions** (newest-first) (date · decision · why · outcome): the universal, auditable trail of
|
|
46
|
+
what the foundation learned.
|
|
36
47
|
|
|
37
48
|
## Status transitions & version
|
|
38
49
|
|
|
39
|
-
- on **confirm**: the
|
|
40
|
-
- on **decline**: the
|
|
50
|
+
- on **confirm**: the lesson moves `open` → `folded` (its text transcribed at the top of the routed target, newest-first).
|
|
51
|
+
- on **decline**: the lesson moves `open` → `rejected` and is **left in place** — never deleted —
|
|
41
52
|
so "we considered this and chose not to act" stays auditable.
|
|
42
|
-
- a consolidation is **append-only (newest-first)**: it PREPENDS new bullets/rows at the top and never
|
|
43
|
-
|
|
53
|
+
- a consolidation is **append-only (newest-first)**: it PREPENDS new bullets/rows at the top and never
|
|
54
|
+
silently rewrites existing foundation text — EXCEPT via the recorded **compaction door** (`compact-foundation.md`):
|
|
55
|
+
eligible (shipped + zero open residues) stable entries collapse upward into a rolled-up settled line at the tail.
|
|
56
|
+
- each `add.py fold` run **bumps** the `foundation-version:` marker in `PROJECT.md` by one (monotonic int).
|
|
44
57
|
|
|
45
|
-
## Reject codes (the
|
|
58
|
+
## Reject codes (the command is fail-closed; validate-all-then-write)
|
|
46
59
|
|
|
47
60
|
<reject_codes>
|
|
48
|
-
- `no_open_deltas` — nothing is `open`
|
|
49
|
-
- `
|
|
50
|
-
|
|
51
|
-
- `
|
|
52
|
-
delta (it is malformed per `deltas.md`) before consolidating.
|
|
61
|
+
- `no_open_deltas` — nothing is `open` in the selected scope. The session is a no-op; the version is NOT bumped.
|
|
62
|
+
- `missing_route_section` — a lesson routes to a foundation section that does not exist (e.g. a `UDD`
|
|
63
|
+
lesson with no `## Users` section). Fail-closed: add the section header, then re-run. Nothing is written.
|
|
64
|
+
- `no_foundation_version` — `PROJECT.md` carries no parseable `foundation-version:` marker to bump.
|
|
53
65
|
</reject_codes>
|
|
54
66
|
|
|
67
|
+
The convention-era codes `unconfirmed_fold` and `unroutable_delta` are **retired** (bridged here, not
|
|
68
|
+
silently dropped): invoking `add.py fold` IS the confirmation, so `unconfirmed_fold` no longer applies;
|
|
69
|
+
and routing is total over the five competencies, so a missing destination surfaces as the more precise
|
|
70
|
+
`missing_route_section` rather than `unroutable_delta`.
|
|
71
|
+
|
|
55
72
|
## Worked example (from this repo's own history)
|
|
56
73
|
|
|
57
|
-
The `competency-deltas` task closed its OBSERVE with two
|
|
74
|
+
The `competency-deltas` task closed its OBSERVE with two lessons — the homeless ones, `TDD`/`ADD`,
|
|
58
75
|
which have no PROJECT.md section:
|
|
59
76
|
|
|
60
77
|
```
|
|
@@ -62,7 +79,8 @@ which have no PROJECT.md section:
|
|
|
62
79
|
- [TDD · open] structural tests guard canonical artifacts but not their dogfood twins (evidence: scope-loop note + this build)
|
|
63
80
|
```
|
|
64
81
|
|
|
65
|
-
At the next consolidation the human
|
|
66
|
-
|
|
67
|
-
and bumps `foundation-version` 1 → 2
|
|
68
|
-
have a home — which is exactly why v5
|
|
82
|
+
At the next consolidation the human keeps both and runs `add.py fold`. Routing transcribes each into
|
|
83
|
+
`CONVENTIONS.md` §Method learnings, prepends a §Key Decisions row, flips them to `folded` with a
|
|
84
|
+
`[folded foundation-version N]` stamp, and bumps `foundation-version` 1 → 2 — all in one atomic write.
|
|
85
|
+
The two competencies the foundation never tracked before now have a home — which is exactly why v5
|
|
86
|
+
routes `TDD`/`ADD` to `CONVENTIONS.md`.
|
package/skill/add/graduate.md
CHANGED
|
@@ -40,7 +40,8 @@ That line is the trigger. Before both tallies complete, status is silent and not
|
|
|
40
40
|
is what these milestones *contain*; this guide proposes them, it does not do them.
|
|
41
41
|
4. **Human confirms** — present the roadmap via `report-template.md`, opening with the ARC
|
|
42
42
|
(goal · done · plan): the stage-graduation goal, the MVP coverage that earns the move, and the
|
|
43
|
-
plan the production milestones lay out.
|
|
43
|
+
plan the production milestones lay out. Render the roadmap as a guided choice — the recommended path
|
|
44
|
+
+ its described alternatives (per `report-template.md`). The human accepts, edits, or declines each drafted
|
|
44
45
|
milestone. No milestone is created without this; nothing advances on a draft the human has not confirmed.
|
|
45
46
|
5. **Flip — the final step** — only now run `add.py stage production`. Because ≥1 production milestone
|
|
46
47
|
now exists, the guard passes and the transition is recorded. This is the orchestration's last act.
|
package/skill/add/intake.md
CHANGED
|
@@ -35,6 +35,7 @@ First ask "does this change already-frozen scope?" → if yes, it is a `change-r
|
|
|
35
35
|
|
|
36
36
|
Present the proposal to the human via `report-template.md` — open with the ARC (goal · done ·
|
|
37
37
|
plan): the goal this request serves, what is already covered, and the plan the chosen bucket sets up.
|
|
38
|
+
Render the proposal as a guided choice — the recommended bucket + its described alternatives (per `report-template.md`).
|
|
38
39
|
|
|
39
40
|
For every request, emit ONE of:
|
|
40
41
|
|
package/skill/add/loop.md
CHANGED
|
@@ -37,10 +37,24 @@ When every task is done but the goal is not, `add.py status` shows
|
|
|
37
37
|
4. **Create** each accepted task — `add.py new-task <slug> --title "..."` — and run it through
|
|
38
38
|
the normal flow (specify → … → verify).
|
|
39
39
|
5. **Repeat** until the work the goal needs is done.
|
|
40
|
-
6. **Close** — when the goal is genuinely met,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
6. **Close** — when the goal is genuinely met, run the **ship review** before you close:
|
|
41
|
+
- **Fill the ship review first** — write the milestone's `## Close — ship review` section (the
|
|
42
|
+
scaffold ships in `MILESTONE.md`): **Ship by domain** — what changed per bounded context
|
|
43
|
+
(`tooling` · `skill` · `book`, or "untouched"); **Cross-task evidence** — one row per task
|
|
44
|
+
(`gate` · `tests` · `residue`); and the **Goal met?** map — each exit criterion tied to the
|
|
45
|
+
evidence that satisfies it. This is the whole-milestone, cross-task evidence the human READS;
|
|
46
|
+
it is evidence, **not a new gate**.
|
|
47
|
+
- **Check the boxes** — read that evidence, then check the exit-criteria boxes in `MILESTONE.md`
|
|
48
|
+
(the single affirmation — the same gate as ever), and `add.py milestone-done <slug>` succeeds
|
|
49
|
+
(then consolidate the open deltas and archive — the `milestone-done → fold → compact → archive`
|
|
50
|
+
lifecycle, per `fold.md` · `compact-foundation.md`).
|
|
51
|
+
- **Define the release steps** — write the milestone's `## Release steps` (merge is one small step
|
|
52
|
+
among them; PR, asset export, tag/publish are others — tool-agnostic hints the human runs).
|
|
53
|
+
These **feed** the release scope — read `release.md` for the cut; loop.md never re-specifies it.
|
|
54
|
+
Present the close via `report-template.md` — open with the ARC (goal · done · plan): the milestone
|
|
55
|
+
goal, the exit-criteria met (with the ship-review evidence) that prove it, and the plan beyond the
|
|
56
|
+
close. Render the close as a guided choice — the recommended next move + its described alternatives
|
|
57
|
+
(per `report-template.md`).
|
|
44
58
|
|
|
45
59
|
## Reopen is the verb; this loop is the trigger
|
|
46
60
|
|
|
@@ -118,7 +118,7 @@ high-risk task still refuses `auto` and forces a lowered rung (run.md guard).
|
|
|
118
118
|
|
|
119
119
|
## 4 · The one human gate — the baseline approval
|
|
120
120
|
|
|
121
|
-
Open the report with the ARC (goal · done · plan) per `report-template.md`, then present
|
|
121
|
+
Open the report with the ARC (goal · done · plan) per `report-template.md`, render the baseline-lock DECISION as a guided choice (the recommended pick + described alternatives), then present
|
|
122
122
|
`SETUP-REVIEW.md` lowest-confidence-first (the `guessed` rows are what the human must actually check). They
|
|
123
123
|
confirm **once** — an explicit yes to the baseline approval itself, in conversation; ambient agreement mid-stream is
|
|
124
124
|
not a confirmation. On that recorded confirmation, you run the lock with their name:
|
|
@@ -28,6 +28,7 @@ Objective: one scenario per Must and per Reject rule, each result specific and o
|
|
|
28
28
|
Steps:
|
|
29
29
|
1. Write one scenario per Must rule and one per Reject rule.
|
|
30
30
|
2. For every rejection add an And-clause asserting what must NOT change.
|
|
31
|
+
3. Sweep for the edge cases the spec omits — boundary, duplicate, partial failure, concurrency, malformed input — add one per case that applies, or rule it out on purpose.
|
|
31
32
|
Never: settle for a vague result ("then it works") — results must be specific and observable.
|
|
32
33
|
</prompt>
|
|
33
34
|
|
|
@@ -36,6 +37,7 @@ Never: settle for a vague result ("then it works") — results must be specific
|
|
|
36
37
|
<exit_gate>
|
|
37
38
|
- [ ] One scenario per Must rule.
|
|
38
39
|
- [ ] One scenario per Reject rule.
|
|
40
|
+
- [ ] Edge cases the spec omits are covered (boundary · duplicate · partial failure · …) or ruled out on purpose.
|
|
39
41
|
- [ ] Each result is a specific, observable fact.
|
|
40
42
|
- [ ] Every rejection asserts what stays unchanged.
|
|
41
43
|
</exit_gate>
|
|
@@ -20,7 +20,7 @@ work can start before the real code exists.
|
|
|
20
20
|
**The freeze is the one approval.** This decision point is where the single human approval lands, over the
|
|
21
21
|
whole bundle (§1–§4). Before asking for it, present the bundle **lowest-confidence first**: the 1–2 points
|
|
22
22
|
most likely wrong (`⚠ [spec|scenario|contract|test] … — because …; if wrong: …`) — aim the human's
|
|
23
|
-
eye before they freeze. Open that report with the ARC (goal · done · plan) per `report-template.md
|
|
23
|
+
eye before they freeze. Open that report with the ARC (goal · done · plan) per `report-template.md`, rendering the freeze DECISION as a guided choice (the recommended pick + described alternatives), so the
|
|
24
24
|
human sees the goal this freeze serves and the plan beyond it, not just the bundle. See `run.md`.
|
|
25
25
|
The approval also freezes the §5 Scope (may touch) + Strategy declarations — the bundle covers them.
|
|
26
26
|
|
|
@@ -66,7 +66,7 @@ redo, and only after the loop's cap does it HARD-STOP to the human (the loop liv
|
|
|
66
66
|
## Record exactly one outcome (no silent pass)
|
|
67
67
|
|
|
68
68
|
When you present this gate to the human, open with the ARC (goal · done · plan) per
|
|
69
|
-
`report-template.md`, and reconcile its FLAGS with `add.py report --decide`'s open-item count
|
|
69
|
+
`report-template.md`, render the gate DECISION as a guided choice (the recommended pick + described alternatives), and reconcile its FLAGS with `add.py report --decide`'s open-item count
|
|
70
70
|
before the ask — per that file's reconcile rule (verify is where a flag-vs-digest mismatch bites).
|
|
71
71
|
|
|
72
72
|
| Outcome | When |
|