shapeup-sdlc 3.4.0 → 3.5.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/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +4 -2
- package/README.md +6 -2
- package/SECURITY.md +1 -1
- package/hooks/sandbox-guard.mjs +69 -6
- package/kernel/compile.mjs +15 -6
- package/kernel/harness.mjs +7 -2
- package/kernel/lib/contract.mjs +68 -1
- package/kernel/probe/requirements.mjs +296 -0
- package/kernel/probe/resume.mjs +7 -1
- package/kernel/reduce/graph.mjs +5 -2
- package/kernel/reduce/ingest.mjs +16 -3
- package/kernel/reduce/ship.mjs +37 -1
- package/kernel/verify/spec.mjs +130 -4
- package/kernel/verify/trace.mjs +16 -7
- package/package.json +1 -1
- package/skills/ba-pitch-analyzer/SKILL.md +16 -1
- package/skills/scope-architect/SKILL.md +16 -1
- package/skills/scope-hammer/SKILL.md +10 -1
- package/skills/spec-evaluator/SKILL.md +12 -1
- package/skills/tech-lead/references/gates.md +22 -1
- package/skills/tech-lead/schemas/domain.schema.json +5 -0
- package/skills/tech-lead/workflows/shapeup-run.js +44 -2
package/kernel/verify/trace.mjs
CHANGED
|
@@ -211,8 +211,16 @@ export function traceLint(slug, { cwd, gate = false }) {
|
|
|
211
211
|
const findings = [];
|
|
212
212
|
|
|
213
213
|
// 1. Covers-closure.
|
|
214
|
+
//
|
|
215
|
+
// THE WHOLE ARM IS GATED ON THE REGISTRY EXISTING — both halves of it, and the second half is the
|
|
216
|
+
// one that was missing. With no `requirements.md` there are no clauses, so `REQ-UNCOVERED` cannot
|
|
217
|
+
// fire; but `dangling` is derived from the BOARD, which needs no registry to carry a `covers:`
|
|
218
|
+
// clause, so a tree with no registry reported "covers-closure not applicable" in the same breath
|
|
219
|
+
// as a red finding for every `covers:` on the board. An arm that reports itself skipped and emits
|
|
220
|
+
// findings anyway is not skipped, and the report says the opposite of what the findings do.
|
|
214
221
|
const reqPath = join(shared, "requirements.md");
|
|
215
|
-
const
|
|
222
|
+
const closureChecked = existsSync(reqPath);
|
|
223
|
+
const clauses = closureChecked ? parseRequirements(readFileSync(reqPath, "utf8")) : [];
|
|
216
224
|
const board = readBoard(cwd, slug);
|
|
217
225
|
const covered = coveredReqIds(board);
|
|
218
226
|
const knownIds = new Set(clauses.map((c) => c.id));
|
|
@@ -227,12 +235,13 @@ export function traceLint(slug, { cwd, gate = false }) {
|
|
|
227
235
|
findings.push({ severity: "red", code: "REQ-UNCOVERED", req: id,
|
|
228
236
|
message: `${id} (status: covered) is named by no AC's covers: — the clause "${(c?.clause || "").slice(0, 60)}" would silently vanish. Cover it with an AC, or mark it CUT (PO-approved).` });
|
|
229
237
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
238
|
+
if (closureChecked) {
|
|
239
|
+
for (const id of dangling) {
|
|
240
|
+
findings.push({ severity: "red", code: "COVERS-DANGLING", req: id,
|
|
241
|
+
message: `an AC declares (covers: ${id}) but ${id} is not in requirements.md — a covers: link must resolve to a registered REQ.` });
|
|
242
|
+
}
|
|
233
243
|
}
|
|
234
244
|
|
|
235
|
-
const closureChecked = existsSync(reqPath);
|
|
236
245
|
const coversClosure = {
|
|
237
246
|
checked: closureChecked,
|
|
238
247
|
requirements_total: clauses.length,
|
|
@@ -240,8 +249,8 @@ export function traceLint(slug, { cwd, gate = false }) {
|
|
|
240
249
|
cut_status: cut.length,
|
|
241
250
|
covered_by_ac: [...covered].filter((id) => knownIds.has(id)).length,
|
|
242
251
|
uncovered,
|
|
243
|
-
dangling_covers: dangling,
|
|
244
|
-
pass: uncovered.length === 0 && dangling.length === 0,
|
|
252
|
+
dangling_covers: closureChecked ? dangling : [],
|
|
253
|
+
pass: uncovered.length === 0 && (!closureChecked || dangling.length === 0),
|
|
245
254
|
skipped_reason: closureChecked ? null : "no requirements.md registry — covers-closure not applicable (non-regression on pre-spine specs).",
|
|
246
255
|
};
|
|
247
256
|
|
package/package.json
CHANGED
|
@@ -98,6 +98,21 @@ its phase; templates live in `assets/templates/`.
|
|
|
98
98
|
(red). An invariant-backed regression task still anchors to its owning UC — there is no
|
|
99
99
|
second path to green.
|
|
100
100
|
|
|
101
|
+
**The requirement edge is written on the AC line, or it does not exist.** An acceptance
|
|
102
|
+
criterion that grades a registry requirement ends with `(covers: REQ-…)` — the trailing clause,
|
|
103
|
+
in the checkbox text, not a mention in prose. Measured on two runs of one pitch: every
|
|
104
|
+
requirement had an acceptance criterion somewhere on the board and only half reached a criterion
|
|
105
|
+
the judge grades, because a board AC reaches the judge through the refuted list alone — it can
|
|
106
|
+
yield a FAIL and can never yield a PASS. An AC nothing cites by id produces no evidence for the
|
|
107
|
+
requirement it was written for.
|
|
108
|
+
|
|
109
|
+
**A requirement with no natural use-case home still becomes a task.** Contrast, localisation, a
|
|
110
|
+
performance ceiling, a test surface — a non-functional clause has no actor+action and so no UC of
|
|
111
|
+
its own, and the habit is to record it in the risk register, where nothing grades it. Give it a
|
|
112
|
+
task whose AC reaches the committed spec (the invariant, the contract field or the Test Surface
|
|
113
|
+
row that states it) and carries its `(covers: REQ-…)`. A line in the risk table is a note; a
|
|
114
|
+
covered AC is a requirement the run can be measured against.
|
|
115
|
+
|
|
101
116
|
---
|
|
102
117
|
|
|
103
118
|
## The other three operations — same craft, different payload + whitelist
|
|
@@ -106,7 +121,7 @@ second path to green.
|
|
|
106
121
|
|---|---|---|
|
|
107
122
|
| `reconcile` | Verify `ledger.feature == payload.feature` (mismatch → STOP). Map each `[+]` Keep item → its owning UC; new task continues numbering (never renumber); `~`/Cut → synthesis "Hammered Out" row, no file. A Keep item asserting a new invariant → APPEND `[INV-NN]` + TS-INV row to that UC (append-only sections in your substrate). A new actor/action with no UC → `status: "escalated"` + a `deviations[]` spec-ambiguity entry: spawning a UC mid-cycle is silent re-shaping, the PO decides. Finish with board-derive (appetite overflow → report) + spec-lint | re-run phases 1–5; edit UC Steps; resolve the appetite HAMMER yourself |
|
|
108
123
|
| `retrofit-surface` | Append `## Test Surface` (derived rows only, after Error Cases) to each UC of a pre-surface spec; an all-sources-empty UC gets the explicit empty-sources line | touch anything else — append-only substrate |
|
|
109
|
-
| `coverage` | Extract **atomic** customer requirement clauses from `payload.requirements` (default: the pitch) and write the SHARED `shapeup/<slug>/requirements.md` registry: one `\| REQ-id \| clause (verbatim) \| source \| status \| note \|` row per clause. Split compound sentences into one testable clause each — a clause lost *inside* a bigger sentence is a requirement nothing can be traced to. **Assign REQ-ids ONCE and freeze them** (they behave like scope_id, never TASK-NNN — every `covers:` link rots otherwise): re-running, append new clauses with fresh ids, mark a removed clause `CUT (PO-approved)`, never renumber or delete. Status starts `covered` (a live requirement); only the PO sets `CUT`. The REQ source itself is frozen — the registry is a separate derived file | edit the REQ source; renumber existing REQ-ids; delete a dropped clause instead of marking it CUT; invent a requirement not in the source |
|
|
124
|
+
| `coverage` | Extract **atomic** customer requirement clauses from `payload.requirements` (default: the pitch) and write the SHARED `shapeup/<slug>/requirements.md` registry: one `\| REQ-id \| clause (verbatim) \| source \| status \| note \|` row per clause. Split compound sentences into one testable clause each — a clause lost *inside* a bigger sentence is a requirement nothing can be traced to. **Assign REQ-ids ONCE and freeze them** (they behave like scope_id, never TASK-NNN — every `covers:` link rots otherwise): re-running, append new clauses with fresh ids, mark a removed clause `CUT (PO-approved)`, never renumber or delete. Status starts `covered` (a live requirement); only the PO sets `CUT`. The REQ source itself is frozen — the registry is a separate derived file. **Numbering.** A source clause already carrying an `R<n>` keeps its number — `R12` → `REQ-12` — and its `source` cell records where it came from verbatim (`shaping.md R12`), because that cell is the only thing that survives a re-run. A clause with no R-id takes the next free number ABOVE the highest `R<n>` in the source, so it can never collide with one added later. Splitting a compound clause keeps `REQ-12` for the first atomic part and records `shaping.md R12 (split 2/3)` for the rest — a requirement graded in parts is why splitting matters at all. On a re-run, match an existing id by its frozen `source` cell and clause text, **never** by re-deriving the number from the source's current order | edit the REQ source; renumber existing REQ-ids; delete a dropped clause instead of marking it CUT; invent a requirement not in the source; re-point an existing REQ-id because the source's R-numbers shifted |
|
|
110
125
|
---
|
|
111
126
|
|
|
112
127
|
## Anti-rationalization table
|
|
@@ -41,6 +41,16 @@ the ship report's census table.
|
|
|
41
41
|
scalars and [a, b] lists, a `## Affordances` table for affordance_manifest, and a
|
|
42
42
|
short `## Why this slice` paragraph. A reviewer must be able to read the substrate
|
|
43
43
|
in a PR; regeneration preserves prose under headings you do not own.
|
|
44
|
+
► affordance_manifest lives in the TABLE and NOWHERE ELSE. Do not also write it in
|
|
45
|
+
the frontmatter: nothing reads it there, so the copy is discarded — and a run has
|
|
46
|
+
been lost to exactly that. The frontmatter copy said required_states: [idle], the
|
|
47
|
+
table cell said a bare idle, the table won, the value was a string where an array
|
|
48
|
+
was required, and four scopes were never dispatched — the board green, the contract
|
|
49
|
+
lint-clean, each leg reporting done with no error, every round, until EVAL refused
|
|
50
|
+
to grade a round whose scopes had never run. A contract declaring no affordances
|
|
51
|
+
writes `affordance_manifest: []` in frontmatter and no table.
|
|
52
|
+
► A LIST INSIDE A TABLE CELL IS WRITTEN `[a, b]`, brackets and all — `[idle]`, never
|
|
53
|
+
`idle`. A bare word in that cell is a string, and required_states is an array.
|
|
44
54
|
scope_id, topology_type — the stable join key is the scope
|
|
45
55
|
use_cases[] — the UC ids this scope implements.
|
|
46
56
|
THE ONLY LINK YOU WRITE TO THE
|
|
@@ -53,7 +63,12 @@ the ship report's census table.
|
|
|
53
63
|
the board's own use_case_refs
|
|
54
64
|
covers[] — optional REQ-ids from
|
|
55
65
|
requirements.md this scope answers
|
|
56
|
-
for; stable, never renumbered
|
|
66
|
+
for; stable, never renumbered.
|
|
67
|
+
WRITE THE REGISTRY'S OWN KEY:
|
|
68
|
+
`REQ-12`, not the pitch's `R12`.
|
|
69
|
+
Both resolve — readers normalise —
|
|
70
|
+
but one spelling in the committed
|
|
71
|
+
contract is one thing to read
|
|
57
72
|
depends_on[] — scope_ids this scope builds AFTER.
|
|
58
73
|
This is the build ORDER — declare
|
|
59
74
|
it whenever one scope consumes
|
|
@@ -72,6 +72,14 @@ H0.1 Unresolved scopes (breaker cases only):
|
|
|
72
72
|
H0.2 QA findings (qa-edge-hunter's hunt-report.md, when present) — all `~` by default.
|
|
73
73
|
H0.3 Discovered-task ledger entries still open (discovery/ledger.md, `[+]`/`~` unresolved).
|
|
74
74
|
H0.4 Attempt-budget hammer proposals (scopes that exhausted their T0 attempts during BUILD).
|
|
75
|
+
H0.4b Requirements with no PASS evidence — the pitch clauses the run never showed working. Run
|
|
76
|
+
node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" probe requirements --slug <slug> --format table
|
|
77
|
+
and take its `no evidence` rows; cite the row, the same way H0.0 cites ownership. Each is a
|
|
78
|
+
census item carrying its source clause (`REQ-12 ← shaping.md R12`). A `cut` row is an answer
|
|
79
|
+
the PO already gave — not an item. An inconsistency row (a criterion anchored to a
|
|
80
|
+
requirement no acceptance criterion covers) is reported to the PO as a reconciliation, never
|
|
81
|
+
counted as evidence and never promoted as a finding. No registry on disk → this input is
|
|
82
|
+
empty and the census is unchanged (absent artifact ⇒ arm skipped).
|
|
75
83
|
H0.5 Classify every item: MUST-HAVE (the pitch's core problem is unsolved without it) vs
|
|
76
84
|
NICE-TO-HAVE (`~`, improves but doesn't block the core promise). Default to NICE-TO-HAVE
|
|
77
85
|
unless the item traces directly to a pitch boundary or a scope's business_goal — a
|
|
@@ -81,7 +89,8 @@ H0.5 Classify every item: MUST-HAVE (the pitch's core problem is unsolved witho
|
|
|
81
89
|
**GATE H0 Output:**
|
|
82
90
|
```
|
|
83
91
|
⏸ GATE H0 — Census
|
|
84
|
-
Must-have (unresolved) : [N] — [list, each with source: scope | QA | discovered |
|
|
92
|
+
Must-have (unresolved) : [N] — [list, each with source: scope | QA | discovered | requirement |
|
|
93
|
+
advisor-overflow]
|
|
85
94
|
Nice-to-have (~) : [M]
|
|
86
95
|
Carry candidates : [scopes still uphill/downhill, or exhausted attempt budget]
|
|
87
96
|
```
|
|
@@ -152,13 +152,23 @@ round. Write it so someone without your context can answer it in one reply.
|
|
|
152
152
|
"t0_citations": [ { "scope_id": "cart", "path": "…/t0/verdicts/r2-a3.json", "sha256": "…" } ],
|
|
153
153
|
"criteria": [ { "criterion": "UC-01 step 3", "dimension": "spec-conformance",
|
|
154
154
|
"verdict": "FAIL", "confidence": "high", "reprobed": true,
|
|
155
|
-
"evidence": "Pay click throws — apps/web/checkout/Pay.tsx:84"
|
|
155
|
+
"evidence": "Pay click throws — apps/web/checkout/Pay.tsx:84",
|
|
156
|
+
"traces_to": ["REQ-4"] } ],
|
|
156
157
|
"refuted": [ { "task_id": "TASK-007", "ac": "<the checkbox text your evidence disproves>" } ],
|
|
157
158
|
"bugs": [ /* report-schema bug entries */ ]
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
```
|
|
161
162
|
|
|
163
|
+
**`traces_to` is copied, not invented.** Fill it from the `(covers: REQ-…)` clause of the
|
|
164
|
+
acceptance criteria your criterion grades: the AC already carries the link, written when the plan
|
|
165
|
+
was reviewed, and you record which requirement your criterion maps back to. An AC with no `covers:`
|
|
166
|
+
clause yields no anchor — leave the array empty rather than guessing, and never read the pitch to
|
|
167
|
+
supply one. This changes nothing you grade: the anchor is a navigation path, never a grading input,
|
|
168
|
+
and a criterion passes or fails on its evidence exactly as before. It matters downstream because
|
|
169
|
+
the requirement matrix at GATE L4 and the census at GATE H are projected from these anchors; a
|
|
170
|
+
verdict that drops them grades the build and says nothing about what the pitch asked for.
|
|
171
|
+
|
|
162
172
|
**Every FAIL criterion's `evidence` MUST carry a `file:line` locator** — schema-enforced, not
|
|
163
173
|
advice: the envelope is validated against `work-result.schema.json` at ingest and a locatorless
|
|
164
174
|
FAIL is rejected before any write. A PASS may cite plain output. (Observed, not theorized: a
|
|
@@ -175,6 +185,7 @@ separation is the whole point of the architecture.
|
|
|
175
185
|
## Verification checklist
|
|
176
186
|
|
|
177
187
|
- [ ] Every criterion traces to committed spec text (UC/domain-model/contract/Done-when/Non-Go)
|
|
188
|
+
- [ ] `traces_to` copied from the graded ACs' `covers:` clauses — empty where they carry none
|
|
178
189
|
- [ ] Every PASS cites a confirming probe; every FAIL cites evidence or "NO EVIDENCE"
|
|
179
190
|
- [ ] Every FAIL was re-probed once; confidence assigned per the ledger rule
|
|
180
191
|
- [ ] Scoped spec → T0 citations present with recomputed sha256 (else the run returned `failed`)
|
|
@@ -297,6 +297,12 @@ Scope contracts present:
|
|
|
297
297
|
- scope-summary "Done when" headline statements
|
|
298
298
|
- the Deferred Places from ux-behavior.md (breadboard Places this shape will not build) —
|
|
299
299
|
each one needs the PO's yes; a rejected deferral goes back to the planner as a screen
|
|
300
|
+
- the REQ → AC table from requirements.md, one row per registered requirement: REQ-id, the
|
|
301
|
+
source clause it came from (`REQ-12 ← shaping.md R12`), and the acceptance criterion that
|
|
302
|
+
grades it — or the scope that claims it, or CUT (PO-approved). Omitted entirely when the run
|
|
303
|
+
has no registry. A requirement with none of the three is already a red below; this table is
|
|
304
|
+
what the PO reads to answer it — cover it, or cut it on the record. Printed, never asked:
|
|
305
|
+
the table decides nothing at this gate
|
|
300
306
|
No scope contracts (pre-v0.3.0, unchanged from v0.2.6):
|
|
301
307
|
Read tasks/_index.md (LOCAL root). Print:
|
|
302
308
|
- task count by package/variant (.shared / .be / .web / .mobile / .e2e)
|
|
@@ -312,7 +318,15 @@ this is the orchestrator's own re-confirmation before committing to a build sequ
|
|
|
312
318
|
waiting to happen), PA1 (directory-aligned scope), PA2 (size cap), SCOPE-ANCHOR (a scope
|
|
313
319
|
naming no committed use case, or one that does not resolve), TIER-DIRECTION (a committed
|
|
314
320
|
contract naming LOCAL task ids), SCOPE-DEPS (a build-order id naming a scope that is not
|
|
315
|
-
in this run),
|
|
321
|
+
in this run), REQ-UNCOVERED (a requirement in requirements.md that no acceptance criterion
|
|
322
|
+
grades and no scope claims — the PO's two ways out are an AC carrying `(covers: REQ-…)` or
|
|
323
|
+
`CUT (PO-approved)` in the registry; silent on a run with no registry),
|
|
324
|
+
CONTRACT-SCHEMA (a scope contract that parses but not into the shape a WorkOrder carries —
|
|
325
|
+
most often a list written bare in a table cell where the dialect wants `[a, b]`; without
|
|
326
|
+
this the compiler refuses the order later and the scope is never dispatched at all, with
|
|
327
|
+
the board green and the leg reporting done), CONTRACT-UNREADABLE (a table the parser could
|
|
328
|
+
not see, or a table field also declared in frontmatter where nothing reads it),
|
|
329
|
+
BREADBOARD-PLACE (a breadboard Place with UI affordances has no
|
|
316
330
|
`## Screen: … (P#)` in ux-behavior.md and is not deferred), BREADBOARD-UI (a U# not
|
|
317
331
|
specified on a screen of its own Place). Any red → HARD STOP, past a 🔴 at the
|
|
318
332
|
architect's own checkpoint. The breadboard reds are the planner's to fix — add the screen
|
|
@@ -513,8 +527,15 @@ Feature : [slug] — [SHIPPED (deployed) | BUILT & VERIFIED — deploy pending
|
|
|
513
527
|
Rounds : [r] (build+eval cycles)
|
|
514
528
|
Verdict : PASS (dims: [spec-conformance]; not evaluated: [security, performance])
|
|
515
529
|
QA : [hunt done — N findings, M promoted+fixed, rest ~ | skipped (--no-qa) | n/a (pre-QA spec)]
|
|
530
|
+
Requirements: [15/17 PASS · 1 CUT (PO) · 1 no evidence (REQ-12 ← R12) | n/a (no registry)]
|
|
516
531
|
Ledger : harness-run.md
|
|
517
532
|
```
|
|
533
|
+
The Requirements line is TRANSCRIBED, never composed — run
|
|
534
|
+
`node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" probe requirements --slug <slug> --format table`
|
|
535
|
+
and copy its `Requirements:` summary. It joins each registered clause to the acceptance criterion
|
|
536
|
+
that covers it and to the criterion the judge graded, over the run named in its own output. It
|
|
537
|
+
decides nothing here: a requirement with no PASS evidence is a fact GATE H's census and the
|
|
538
|
+
baseline comparison weigh, not a ship blocker.
|
|
518
539
|
Question (max 1): "Anything to record before I close the run? (y/n) or provide feedback for the next sprint."
|
|
519
540
|
On confirm:
|
|
520
541
|
- If the PO provides substantive feedback (not just 'y' or empty) → automatically delegate via Agent (model: exec — see references/protocol.md "Invocation mechanism"): Skill(shapeup-sdlc-plugin:coach) with the provided feedback for RLHF. The coach runs its own GATE COACH-1 to have the PO categorize each rule, then files it under the responsible skill in `shapeup/knowledge-base/<skill>.md` (committed → team-shared). Coachable: `task-executor`, `ba-pitch-analyzer`, `qa-edge-hunter`, `orient`, `scope-architect`, `solution-architect` (each reads its own file at the top of its next run) and `tech-lead` (workflow guidance, read at the next GATE L0). Guidance never decides a gate: a filed rule may add a question or a check to a gate block, never an answer. The tech lead does not categorize the feedback itself — that is the coach's gate, by design (no assumptions).
|
|
@@ -341,6 +341,7 @@
|
|
|
341
341
|
"spec_folder",
|
|
342
342
|
"feature",
|
|
343
343
|
"discovered_ledger",
|
|
344
|
+
"requirements",
|
|
344
345
|
"kb_rules_path"
|
|
345
346
|
],
|
|
346
347
|
"scope-architect": [
|
|
@@ -2611,6 +2612,10 @@
|
|
|
2611
2612
|
"type": "boolean",
|
|
2612
2613
|
"description": "ANALYZE finished: the spec folder's usecases/ carries at least one use case that is not _index.md. WIRE reads these — one wiring-map entry per use case — which is why ANALYZE precedes WIRE in the phase chain: dispatched against an empty spec folder, WIRE escalates on every launch."
|
|
2613
2614
|
},
|
|
2615
|
+
"has_requirements": {
|
|
2616
|
+
"type": "boolean",
|
|
2617
|
+
"description": "The requirements registry is on disk: shapeup/<slug>/requirements.md exists. A PLAIN FACT, not a phase — the orchestrator guards its single `coverage` dispatch on this boolean, and it is deliberately absent from kernel/probe/resume.mjs's PHASE_ARTIFACT map, which doubles as nextPhase()'s ordered list: an entry there would fast-forward every run recorded before the registry existed to the registry instead of to build."
|
|
2618
|
+
},
|
|
2614
2619
|
"has_wiring_map": {
|
|
2615
2620
|
"type": "boolean",
|
|
2616
2621
|
"description": "WIRE finished: shapeup/<slug>/wiring-map.md exists."
|
|
@@ -407,6 +407,9 @@ const RESUME = {
|
|
|
407
407
|
eval_dimensions: { type: "array", items: { type: "string" } },
|
|
408
408
|
has_orient_artifacts: { type: "boolean" },
|
|
409
409
|
has_spec_tree: { type: "boolean" },
|
|
410
|
+
// The requirements registry — a fact, not a phase. See the COVERAGE block below for why it is
|
|
411
|
+
// guarded on this bare boolean and never asked about through `probe resume --require`.
|
|
412
|
+
has_requirements: { type: "boolean" },
|
|
410
413
|
has_wiring_map: { type: "boolean" },
|
|
411
414
|
has_project_profile: { type: "boolean" },
|
|
412
415
|
// THE SHAPE THE KERNEL WRITES, not a convenient one. `probe resume` emits `{scope_id, path}` per
|
|
@@ -488,7 +491,7 @@ const ORIENT = {
|
|
|
488
491
|
required: ["ok", "artifact_written", "spiked_area", "spike_result"],
|
|
489
492
|
};
|
|
490
493
|
|
|
491
|
-
/** analyze / wire — "did the artifact land?" is all a gate needs from them. */
|
|
494
|
+
/** coverage / analyze / wire — "did the artifact land?" is all a gate needs from them. */
|
|
492
495
|
const PHASE_OK = {
|
|
493
496
|
type: "object",
|
|
494
497
|
properties: { ok: { type: "boolean" }, artifact_written: { type: "boolean" }, detail: { type: "string" } },
|
|
@@ -1006,8 +1009,47 @@ if (!rs.has_orient_artifacts) {
|
|
|
1006
1009
|
if (g.stop) return withWarnings(g.stop);
|
|
1007
1010
|
}
|
|
1008
1011
|
|
|
1009
|
-
// ----
|
|
1012
|
+
// ---- COVERAGE (the requirements registry) — ahead of ANALYZE, whose ACs cite its ids ----------
|
|
1013
|
+
//
|
|
1014
|
+
// WHY IT RUNS AT ALL, and why here. The pitch's own requirement list is the one statement of what
|
|
1015
|
+
// the run was asked for, and until it is extracted into `shapeup/<slug>/requirements.md` there is
|
|
1016
|
+
// no stable key an acceptance criterion, a scope contract or a verdict can point back to. Measured
|
|
1017
|
+
// on a full run: the planner produced the requirement edge on the board and the judge never saw
|
|
1018
|
+
// it, because nothing on either side shared a key space. So the registry is written BEFORE the
|
|
1019
|
+
// board, not beside it — ANALYZE's acceptance criteria cite `REQ-<n>` ids, which have to exist
|
|
1020
|
+
// before they can be cited.
|
|
1021
|
+
//
|
|
1022
|
+
// IT IS NOT A PHASE, AND THAT IS THE WHOLE DESIGN OF THIS BLOCK.
|
|
1023
|
+
// · No `phase("Coverage")`: the dispatch belongs to the planning stretch the Analyze group
|
|
1024
|
+
// already covers (`setRunStatus("mapping")` spans it), so it never renders as an empty group
|
|
1025
|
+
// on a relaunch — the failure a per-phase progress box would otherwise have to pay a leg to
|
|
1026
|
+
// avoid, and there is no leg to spend here (see the next point).
|
|
1027
|
+
// · No `requirePhase()` / no `fastForward()`: both route to `probe resume --require`, whose
|
|
1028
|
+
// `--require` is an ENUM over `PHASE_ARTIFACT`'s keys. `coverage` is not one, so the call exits
|
|
1029
|
+
// 2, and this file reads any exit other than 6 as "the predicate was never asked" — a dispatch
|
|
1030
|
+
// that worked would abort the run. The skip is therefore narrated, and guarded on the bare
|
|
1031
|
+
// `has_requirements` boolean the resume state carries.
|
|
1032
|
+
// · Not in `PHASE_ARTIFACT` either: that map is also `nextPhase()`'s ordered list, so adding it
|
|
1033
|
+
// would fast-forward every pre-registry run to the registry instead of to `build`.
|
|
1010
1034
|
phase("Analyze");
|
|
1035
|
+
if (!rs.has_requirements) {
|
|
1036
|
+
log(`COVERAGE — dispatching (slug ${slug})`);
|
|
1037
|
+
await setRunStatus("mapping", "Analyze");
|
|
1038
|
+
const c = await worker({
|
|
1039
|
+
skill: "ba-pitch-analyzer", operation: "coverage", schema: PHASE_OK, phase: "Analyze", label: "coverage",
|
|
1040
|
+
payload: { requirements: rs.intake_path, feature: slug },
|
|
1041
|
+
extra:
|
|
1042
|
+
"Extract the pitch's requirement clauses into the SHARED requirements registry, one atomic " +
|
|
1043
|
+
"clause per row. A clause carrying an R-id keeps its number as REQ-<n> and records the R-id " +
|
|
1044
|
+
"verbatim in its source cell; ids are assigned once and never renumbered.",
|
|
1045
|
+
});
|
|
1046
|
+
if (c.__failed) return diedAt("COVERAGE", c);
|
|
1047
|
+
await advisory(`reduce graph --slug ${slug}`, "Analyze", "graph:coverage");
|
|
1048
|
+
} else {
|
|
1049
|
+
log(`COVERAGE — a requirements registry is already on disk; not re-dispatching it`);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// ---- ANALYZE (spec tree + board) — ahead of WIRE, which reads its use cases -------------------
|
|
1011
1053
|
if (!rs.has_spec_tree) {
|
|
1012
1054
|
log(`ANALYZE — dispatching (slug ${slug})`);
|
|
1013
1055
|
// "mapping", not "analyzing": the kernel's RUN_STATUSES enum is deliberately COARSER than this
|