shapeup-sdlc 3.1.1 → 3.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/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +2 -2
- package/commands/ship.md +5 -0
- package/hooks/gate-intake.mjs +1 -1
- package/kernel/compile.mjs +51 -2
- package/kernel/init/run.mjs +122 -6
- package/kernel/lib/breadboard.mjs +165 -0
- package/kernel/lib/paths.mjs +3 -1
- package/kernel/probe/eval.mjs +83 -12
- package/kernel/probe/resume.mjs +15 -2
- package/kernel/probe/t0.mjs +26 -3
- package/kernel/reduce/ingest.mjs +15 -0
- package/kernel/verify/spec.mjs +190 -2
- package/package.json +1 -1
- package/skills/ba-pitch-analyzer/SKILL.md +11 -5
- package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +2 -1
- package/skills/ba-pitch-analyzer/assets/templates/ux-behavior.tmpl.md +12 -2
- package/skills/ba-pitch-analyzer/references/doc-schemas.md +3 -0
- package/skills/ba-pitch-analyzer/references/ux-behavior-patterns.md +9 -0
- package/skills/orient/SKILL.md +7 -4
- package/skills/scope-architect/SKILL.md +4 -0
- package/skills/solution-architect/SKILL.md +4 -1
- package/skills/spec-evaluator/SKILL.md +1 -1
- package/skills/tech-lead/SKILL.md +6 -6
- package/skills/tech-lead/references/gates.md +26 -12
- package/skills/tech-lead/references/protocol.md +13 -8
- package/skills/tech-lead/schemas/domain.schema.json +29 -3
- package/skills/tech-lead/workflows/shapeup-run.js +27 -10
package/skills/orient/SKILL.md
CHANGED
|
@@ -42,7 +42,8 @@ from `tech-lead`; it never reads or writes a shared run-state file.
|
|
|
42
42
|
## Input contract (pure worker)
|
|
43
43
|
|
|
44
44
|
Orchestrated, you are invoked as `--order <path>` (a WorkOrder): `payload.pitch` (the
|
|
45
|
-
kicked-off pitch path), `payload.
|
|
45
|
+
kicked-off pitch path), `payload.breadboard` (the pitch's breadboard — Places, affordances, slices;
|
|
46
|
+
absent = none separate), `payload.stack` (sweep hint), `payload.spec_folder` (the SHARED spec
|
|
46
47
|
deliverable dir) and `payload.feature` (the run slug), plus `substrate.allowed` naming your one
|
|
47
48
|
write surface — the orient output dir. Anything absent = unknown: confirm at GATE O-A
|
|
48
49
|
(standalone) or report it in the result's `deviations`, never guess. Standalone, the
|
|
@@ -104,7 +105,8 @@ Confirm (do not guess):
|
|
|
104
105
|
Shaped signal: frontmatter status: shaped AND bet: <S1|S2|...> (or equivalent).
|
|
105
106
|
If the pitch lacks appetite AND solution boundaries → STOP and tell tech-lead:
|
|
106
107
|
"Orient runs on a kicked-off pitch, not a raw idea. Shape/bet first (PO upstream)."
|
|
107
|
-
- breadboard
|
|
108
|
+
- breadboard — read `payload.breadboard` when present; absent means the pitch has no separate
|
|
109
|
+
breadboard — look for Places and affordance tables in the pitch itself
|
|
108
110
|
- spec folder target (create orient/ if absent)
|
|
109
111
|
- codebase root
|
|
110
112
|
```
|
|
@@ -136,7 +138,8 @@ Useful sweeps (adapt to the stack arg):
|
|
|
136
138
|
```
|
|
137
139
|
|
|
138
140
|
Write `code-surface.md`: one row per pitch element → `file:line` it touches (or "NEW — no
|
|
139
|
-
existing home"), the seam it extends, and whether it's new vs. existing.
|
|
141
|
+
existing home"), the seam it extends, and whether it's new vs. existing. With a breadboard, each row
|
|
142
|
+
carries the P#, U# or N# of the element it locates — a Place with no existing home is a NEW screen. Flag every place the
|
|
140
143
|
map is uncertain — uncertainty is signal for Phase 3, not something to hide.
|
|
141
144
|
|
|
142
145
|
> **Output location.** All four orient artifacts are run-trace (recon scratch), so they
|
|
@@ -257,7 +260,7 @@ Tech-lead uses this to render the GATE L1a Hill and confirm the spike before han
|
|
|
257
260
|
### Flags
|
|
258
261
|
| Flag | Effect |
|
|
259
262
|
|------|--------|
|
|
260
|
-
| `--pitch <path>` | The kicked-off pitch
|
|
263
|
+
| `--pitch <path>` | The kicked-off pitch. Read `payload.breadboard` when present; absent means the pitch has no separate breadboard — look for Places and affordance tables in the pitch itself |
|
|
261
264
|
| `--spec <path>` | SHARED spec deliverable dir (shapeup/<feat>/spec/); orient *artifacts* are written to the LOCAL root `.shapeup/<feat>/orient/` |
|
|
262
265
|
| `--stack <hint>` | Stack hint to aim the code-surface sweeps |
|
|
263
266
|
| `--auto` | Auto-confirm O-A and O-B; run straight through |
|
|
@@ -21,6 +21,7 @@ the ship report's census table.
|
|
|
21
21
|
|---|---|
|
|
22
22
|
| `operation` | `map-scopes` — the only operation this skill has. It covers first slicing after the board exists, folding discovered items in, and re-slicing a stuck scope; the payload says which of those you are doing |
|
|
23
23
|
| `payload.feature` / `payload.spec_folder` | Slug + committed spec (read ux-behavior.md for manifests; usecases for flows) |
|
|
24
|
+
| `payload.breadboard` | When present, every U# the spec places is one manifest entry's `source`; record which scopes deliver each V# slice in `scope-board.md` (your write surface — `scope-summary.md` is the planner's) |
|
|
24
25
|
| `payload.tasks[]` | The board's tasks with their touched files — the slicing INPUT only. Each carries `use_case_refs`; those UC ids are what you write into the contract. Never copy a task id into a contract |
|
|
25
26
|
| `substrate.allowed` | `scopes/*.md` + `scope-board.md` — your ONLY write surface |
|
|
26
27
|
|
|
@@ -65,6 +66,8 @@ the ship report's census table.
|
|
|
65
66
|
element as {test_id, role} +
|
|
66
67
|
required_states [idle, loading,
|
|
67
68
|
success, error, empty]
|
|
69
|
+
+ `source` — the U# the
|
|
70
|
+
ux-behavior row cites
|
|
68
71
|
e2e_verification_fixtures[] — the command(s)/spec file(s)
|
|
69
72
|
that drive this scope
|
|
70
73
|
end-to-end (T0 layer); too
|
|
@@ -134,6 +137,7 @@ territory — and any lint warn left standing, with why). You never touch task f
|
|
|
134
137
|
- [ ] Every scope that consumes another's output declares it in `depends_on`
|
|
135
138
|
- [ ] Substrates disjoint except declared shared_substrate (DISJOINT = 0 red)
|
|
136
139
|
- [ ] Every interactive element in scope screens appears in exactly one affordance_manifest
|
|
140
|
+
- [ ] Every U# the spec places is some manifest entry's `source`
|
|
137
141
|
- [ ] Every scope has fixtures or an explicit TBD flag
|
|
138
142
|
- [ ] Every hill_phase written is UPHILL_UNKNOWN; superseded contracts kept
|
|
139
143
|
- [ ] The WorkResult validates against `work-result.schema.json`
|
|
@@ -42,6 +42,7 @@ return as a WorkResult.
|
|
|
42
42
|
|---|---|
|
|
43
43
|
| `operation` | `wire` (author/refresh the wiring map after `analyze`, before `map-scopes`) |
|
|
44
44
|
| `payload.feature` / `payload.spec_folder` | Slug + committed spec — read `usecases/` for the UCs and the engine each one needs, `domain-model.md`/`synthesis.md` for the module surface |
|
|
45
|
+
| `payload.breadboard` | When present, name each UC's `affordance` by its U# and Place |
|
|
45
46
|
| `payload.project_profile` | Path to the SHARED `project-profile.md`. Its `entry_point` is the composition root every engine must attach to — **archetype-specific** (a client-only game's `main.js` is not a web-service's `src/server.ts`). Read it; never guess the entry point |
|
|
46
47
|
| `substrate.allowed` | `wiring-map.md` — your ONLY write surface (the spec core, scopes, and the profile are frozen) |
|
|
47
48
|
|
|
@@ -69,7 +70,9 @@ guessed `main.js` would make the later oracle certify nothing.
|
|
|
69
70
|
file:line is a build-time fact (the oracle proves reachability by
|
|
70
71
|
the import graph, it does not parse this field)
|
|
71
72
|
affordance the player-visible thing this UC exposes once wired (the human
|
|
72
|
-
end of the chain — what a user can DO, not an internal call)
|
|
73
|
+
end of the chain — what a user can DO, not an internal call);
|
|
74
|
+
with a breadboard, name it by U# and Place —
|
|
75
|
+
`U1 Pay (P1) → P2 Payment Sheet`
|
|
73
76
|
3 WRITE shapeup/<slug>/wiring-map.md (WiringMap): frontmatter for schema_version, feature
|
|
74
77
|
and entry_point (echo of the profile), then entries[] as ONE MARKDOWN TABLE under a
|
|
75
78
|
`## Wiring` heading — this exact shape, because it is the only one the reader parses:
|
|
@@ -37,7 +37,7 @@ Invoked as `--order <path>`. Fields you may rely on (absent = unknown, never inf
|
|
|
37
37
|
| `payload.feature` | Feature slug — scopes the probe and names the report |
|
|
38
38
|
| `payload.dimensions[]` | The active dimension set (the caller resolved precedence). Absent → `[spec-conformance]` + the auto-enable rules below |
|
|
39
39
|
| `payload.run_cmd` | How to start the running app. Absent standalone → ask; absent orchestrated → ESCALATE, do not guess |
|
|
40
|
-
| `payload.t0_artifacts[]` | Per-scope T0 verdict paths for this round (scoped specs). An artifact listed but missing/red on disk, or a scoped spec with none listed → the round is NOT gradeable: return `status: failed` naming the scope — a structural precondition, not a criterion |
|
|
40
|
+
| `payload.t0_artifacts[]` | Per-scope T0 verdict paths for this round (scoped specs), compiled from each scope's green verdict. An artifact listed but missing/red on disk, or a scoped spec with none listed → the round is NOT gradeable: return `status: failed` with the reason, naming the scope, as your FIRST deviation — a structural precondition, not a criterion |
|
|
41
41
|
| `payload.browser` | `cli` (default, ~4x cheaper) \| `mcp` \| `none` |
|
|
42
42
|
| `payload.tasks[]` | Traceability only (which UCs a task claims): NEVER a grading source — the committed UC text is the criterion, a paraphrase mismatch is a finding |
|
|
43
43
|
| `substrate.allowed` | Your only write surface: `.shapeup/<slug>/evaluation/**` (the report + evidence) |
|
|
@@ -23,7 +23,7 @@ turns fighting shell quoting):
|
|
|
23
23
|
node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" init run \
|
|
24
24
|
--slug <slug-from-the-request> --intake-file <path/to/the/requirement.md> \
|
|
25
25
|
--auto-level <interactive|auto|unattended> \
|
|
26
|
-
[--dimensions <a,b>] [--gate-answers <ci|guarded|path.json>] [--wall-clock-budget <seconds>] [--max-rounds 3]
|
|
26
|
+
[--dimensions <a,b>] [--gate-answers <ci|guarded|path.json>] [--wall-clock-budget <seconds>] [--max-rounds 3] [--breadboard <path>]
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
**After a compaction, or in a fresh session over an open run, re-derive before you act.** One
|
|
@@ -43,11 +43,11 @@ counts.
|
|
|
43
43
|
plugin and need a one-time permission grant (`npx shapeup-sdlc init` writes it). Do not route
|
|
44
44
|
around it, and do not silently hand-build the feature instead.
|
|
45
45
|
|
|
46
|
-
**Language gate (delegated to `translator`, not this skill):**
|
|
47
|
-
an Agent (model: exec)
|
|
48
|
-
English → proceed as-is. Non-English →
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
**Language gate (delegated to `translator`, not this skill):** before Step 1 opens the run, dispatch
|
|
47
|
+
an Agent (model: exec) calling `Skill(shapeup-sdlc-plugin:translator) --check` on the pitch *and* its
|
|
48
|
+
breadboard. English → proceed as-is. Non-English → a second Agent translates both (`--auto` under
|
|
49
|
+
auto/unattended); Step 1 then names the `.en.md` files (a run already open on the original: re-open
|
|
50
|
+
it with `--force` — nothing is dispatched yet). The tech lead detects and sequences; never translates.
|
|
51
51
|
|
|
52
52
|
**Step 2 — pin GATE L0, then launch.** Collect the L0.1–L0.9 config (spec folder, lens, stack,
|
|
53
53
|
eval dims, max_rounds, the model/budget matrix — see `references/gates.md` GATE L0 for the full
|
|
@@ -31,13 +31,19 @@ escalates, writes nothing, and every relaunch re-dispatches it.
|
|
|
31
31
|
|
|
32
32
|
```
|
|
33
33
|
Collect (explicit — never inferred):
|
|
34
|
-
L0.1 Kicked-off pitch source:
|
|
34
|
+
L0.1 Kicked-off pitch source: `shaping.md` — and its `breadboard.md`, which the run finds
|
|
35
|
+
beside it (or in `shaping/`) or takes from `--breadboard`; a `pitch.md` may carry the
|
|
36
|
+
breadboard inline. Already shaped + bet by PO.
|
|
35
37
|
Not a raw idea — shaping (1-4) / betting (5) / kick-off (6) are PO-personal, upstream.
|
|
36
|
-
L0.1a Language gate: Agent (model: exec) → Skill(shapeup-sdlc-plugin:translator)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
L0.1a Language gate, BEFORE init run: Agent (model: exec) → Skill(shapeup-sdlc-plugin:translator)
|
|
39
|
+
--check over the pitch AND its breadboard.
|
|
40
|
+
English → use both as-is.
|
|
41
|
+
non-English → Agent (model: exec) → Skill(shapeup-sdlc-plugin:translator) <pitch> <breadboard>
|
|
42
|
+
(--auto under auto/unattended), then open the run on the produced
|
|
43
|
+
<name>.en.md — init run prefers a breadboard.en.md beside it, and warns
|
|
44
|
+
when it can find only the untranslated breadboard. The run's intake is
|
|
45
|
+
what every planning worker reads, so a translation made after the run
|
|
46
|
+
opened reaches nobody: re-open with --force naming the .en.md. Log in ledger.
|
|
41
47
|
L0.1b Appetite: read the `appetite` field from the pitch's YAML frontmatter (set by /shapeup).
|
|
42
48
|
Surface it in the gate output. Use it to:
|
|
43
49
|
- Contextualise the scope at L1b (right-size cuts to the budget).
|
|
@@ -167,8 +173,9 @@ committing to a scope map. This is the first Hill read (area-level — slices do
|
|
|
167
173
|
```
|
|
168
174
|
Read .shapeup/<slug>/orient/. Render the 🗻 Hill from hill-signal.md (see protocol.md "Hill report"):
|
|
169
175
|
- each suspected area → uphill (open unknowns) | crest (approach proven by the spike) | downhill
|
|
170
|
-
Print:
|
|
171
|
-
|
|
176
|
+
Print: `Breadboard: <source> | none` (how init run found the pitch's breadboard — flag, sibling,
|
|
177
|
+
shaping-dir, shared-root, embedded — or none), the code-surface headline (where it lands),
|
|
178
|
+
the spiked area + result, the riskiest open unknowns going into mapping.
|
|
172
179
|
Ask (max 2): is the riskiest area the right one to have spiked? any unknown that must be
|
|
173
180
|
resolved (another spike) before we map scopes?
|
|
174
181
|
```
|
|
@@ -186,7 +193,7 @@ Do NOT enter MAP SCOPES until Orient is accepted.
|
|
|
186
193
|
mobile|library|data-pipeline}; entry_point is the reachability seam (a game's main.js is NOT a
|
|
187
194
|
service's src/server.ts). Validate the enum — a typo must fail, not silently disable the check.
|
|
188
195
|
2. WIRE — compile-order --operation wire --slug <slug> (worker→solution-architect), payload
|
|
189
|
-
{project_profile}. Sole writer of committed wiring-map.md (per-UC engine → seam → entry-point
|
|
196
|
+
{project_profile, breadboard?}. Sole writer of committed wiring-map.md (per-UC engine → seam → entry-point
|
|
190
197
|
call site → affordance). ⏸ GATE L1a.5: confirm each UC has a declared seam before slicing.
|
|
191
198
|
⟐ PRECONDITION: MAP SCOPES step 1 (ANALYZE) has already run and usecases/ is
|
|
192
199
|
populated. WIRE writes one entry per use case, so dispatching it against an empty spec folder
|
|
@@ -212,7 +219,7 @@ against the seams WIRE declared. Sequence: ORIENT → L1a → **ANALYZE** → **
|
|
|
212
219
|
```
|
|
213
220
|
Two orders, two workers, one step (both model: exec — see references/protocol.md):
|
|
214
221
|
1. ANALYZE + BOARD — compile-order --operation analyze --slug <slug> --worker ba-pitch-analyzer
|
|
215
|
-
--payload '{"pitch": "<path>", "lens": "<lens>", "orient_dir": ".shapeup/<slug>/orient/"}'
|
|
222
|
+
--payload '{"pitch": "<path>", "breadboard": "<the path init run printed, when it printed one>", "lens": "<lens>", "orient_dir": ".shapeup/<slug>/orient/"}'
|
|
216
223
|
dispatch: Skill(shapeup-sdlc-plugin:ba-pitch-analyzer) --order <path>. The order hands it
|
|
217
224
|
code-surface.md (Phase-1 ingest consumes the map, does not re-scan), discovered-seed.md
|
|
218
225
|
(task gen starts from reality), spike-<area>.md (feasibility/contracts).
|
|
@@ -265,6 +272,8 @@ Scope contracts present:
|
|
|
265
272
|
- scope board: scope_id, topology_type, substrate file count (scopes/*.md / scope-board.md)
|
|
266
273
|
- any SPIKE blockers (scope-summary.md)
|
|
267
274
|
- scope-summary "Done when" headline statements
|
|
275
|
+
- the Deferred Places from ux-behavior.md (breadboard Places this shape will not build) —
|
|
276
|
+
each one needs the PO's yes; a rejected deferral goes back to the planner as a screen
|
|
268
277
|
No scope contracts (pre-v0.3.0, unchanged from v0.2.6):
|
|
269
278
|
Read tasks/_index.md (LOCAL root). Print:
|
|
270
279
|
- task count by package/variant (.shared / .be / .web / .mobile / .e2e)
|
|
@@ -280,7 +289,11 @@ this is the orchestrator's own re-confirmation before committing to a build sequ
|
|
|
280
289
|
waiting to happen), PA1 (directory-aligned scope), PA2 (size cap), SCOPE-ANCHOR (a scope
|
|
281
290
|
naming no committed use case, or one that does not resolve), TIER-DIRECTION (a committed
|
|
282
291
|
contract naming LOCAL task ids), SCOPE-DEPS (a build-order id naming a scope that is not
|
|
283
|
-
in this run)
|
|
292
|
+
in this run), BREADBOARD-PLACE (a breadboard Place with UI affordances has no
|
|
293
|
+
`## Screen: … (P#)` in ux-behavior.md and is not deferred), BREADBOARD-UI (a U# not
|
|
294
|
+
specified on a screen of its own Place). Any red → HARD STOP, past a 🔴 at the
|
|
295
|
+
architect's own checkpoint. The breadboard reds are the planner's to fix — add the screen
|
|
296
|
+
or defer the Place; never fold it into another screen.
|
|
284
297
|
- Lock the build SEQUENCE riskiest-first: order scopes by open-unknowns count (from
|
|
285
298
|
hill/<scope-id>.yml if present, else the orient hill signal), not by file count or
|
|
286
299
|
alphabetical — Shape Up's "solve in the right sequence" (step 10).
|
|
@@ -414,7 +427,8 @@ S.6 Harvest one signal row → append to `.shapeup/metrics/<machine-id>.jsonl`
|
|
|
414
427
|
deliberately without colliding on one filename. The read plane is
|
|
415
428
|
`harness probe stats`, or `cat .shapeup/metrics/*.jsonl`).
|
|
416
429
|
Copy fields that ALREADY exist as structured output (run-state, final EVAL report,
|
|
417
|
-
discovery ledger, qa/hunt-report, breadboard
|
|
430
|
+
discovery ledger, qa/hunt-report, and the receipt's `breadboard.ids.V` for `slice_count` —
|
|
431
|
+
the slices init run counted in the staged breadboard, never a hand copy). Two hard rules:
|
|
418
432
|
1. Harvest only fields that already exist at ship time — never evaluate something new.
|
|
419
433
|
2. Record facts, never compute a new verdict (no `run_quality_score` — that would be
|
|
420
434
|
a second judge behind spec-evaluator). The eval suite interprets; harvest records.
|
|
@@ -311,14 +311,15 @@ workers keep only their own product-idempotency key and emit domain artifacts.
|
|
|
311
311
|
|
|
312
312
|
## 0. LANGUAGE GATE → translator (GATE L0, only if non-English)
|
|
313
313
|
```
|
|
314
|
-
Invoke via Agent (model: exec): Skill(shapeup-sdlc-plugin:translator) --check "<intake path>"
|
|
314
|
+
Invoke via Agent (model: exec), BEFORE init run: Skill(shapeup-sdlc-plugin:translator) --check "<intake path>" "<breadboard path>"
|
|
315
315
|
# detect-only, writes nothing
|
|
316
316
|
English → skip; ORIENT against the original.
|
|
317
|
-
non-English → Agent (model: exec): Skill(shapeup-sdlc-plugin:translator) "<intake path>" [--auto]
|
|
318
|
-
# full pass
|
|
319
|
-
Writes: <name>.en.md (English copy; original untouched) + glossary.md
|
|
317
|
+
non-English → Agent (model: exec): Skill(shapeup-sdlc-plugin:translator) "<intake path>" "<breadboard path>" [--auto]
|
|
318
|
+
# full pass — the pitch AND its breadboard, before init run
|
|
319
|
+
Writes: <name>.en.md per file (English copy; original untouched) + glossary.md
|
|
320
320
|
+ translation-report.md.
|
|
321
|
-
|
|
321
|
+
Open the run on the .en.md pitch: init run stages breadboard.en.md beside it,
|
|
322
|
+
and every planning worker reads what init run pinned — never a later copy.
|
|
322
323
|
Read back: the detect table (--check) / the .en.md path + residual scan result (full pass).
|
|
323
324
|
Authority: translator normalizes language only — it does not orient/plan/build/judge. The tech
|
|
324
325
|
lead never translates itself; it only detects and sequences this step before ORIENT.
|
|
@@ -340,7 +341,7 @@ Authority: pure worker — no code, no board, no run-state, no reporting.
|
|
|
340
341
|
```
|
|
341
342
|
Order A (the spec tree + board):
|
|
342
343
|
compile-order --operation analyze --slug <slug> --worker ba-pitch-analyzer
|
|
343
|
-
--payload '{"pitch": "<path>", "lens": "<lens>", "orient_dir": ".shapeup/<slug>/orient/"}'
|
|
344
|
+
--payload '{"pitch": "<path>", "breadboard": "<the path init run printed, when it printed one>", "lens": "<lens>", "orient_dir": ".shapeup/<slug>/orient/"}'
|
|
344
345
|
Agent (model: exec): Skill(shapeup-sdlc-plugin:ba-pitch-analyzer) --order <path>
|
|
345
346
|
The order hands it code-surface.md (Phase-1 ingest, no re-scan), discovered-seed.md (task
|
|
346
347
|
gen from reality), spike-<area>.md (feasibility/contracts).
|
|
@@ -465,7 +466,9 @@ Read back: the stdout JSON — {path, sha256, trial, overall, regression, score,
|
|
|
465
466
|
## 4. EVAL → spec-evaluator (once per round)
|
|
466
467
|
```
|
|
467
468
|
compile-order --operation evaluate --slug <slug> --worker spec-evaluator --round <r>
|
|
468
|
-
--payload '{"dimensions": ["spec-conformance"], "run_cmd": "<cmd>"
|
|
469
|
+
--payload '{"dimensions": ["spec-conformance"], "run_cmd": "<cmd>"}'
|
|
470
|
+
t0_artifacts is compiled from each scope's green T0 verdict for round <r> — pass it only to
|
|
471
|
+
override. A scope with no green verdict is named on stderr: the judge has nothing to cite for it.
|
|
469
472
|
Invoke via Agent (model: eval), ONCE, after GATE L2:
|
|
470
473
|
Skill(shapeup-sdlc-plugin:spec-evaluator) --order <path>
|
|
471
474
|
Effect: one feature-level pass over the running app against all AC + Done-when; writes
|
|
@@ -473,6 +476,8 @@ Effect: one feature-level pass over the running app against all AC + Done-when;
|
|
|
473
476
|
verdicts, refuted boxes, T0 citations). It touches NO task file and NO board.
|
|
474
477
|
ingest-result <results/evaluate-r<r>.json>: appends the .verdicts JSONL ledger, un-ticks the
|
|
475
478
|
refuted AC boxes, sets eval_verdict frontmatter — the judge returns data, ingest writes.
|
|
479
|
+
A verdict on a scoped spec that cites no T0 artifact is refused and the round stays
|
|
480
|
+
open: re-dispatch the evaluator, do not advance the round.
|
|
476
481
|
Read back: EVAL-FEATURE-<slug>.md → verdict (pass|fail) + the bug list (each bug has
|
|
477
482
|
task ref, severity, file:line, expected vs actual).
|
|
478
483
|
```
|
|
@@ -822,7 +827,7 @@ fixtures run in isolation and do not consume it.
|
|
|
822
827
|
| `spike_unresolved_count` | `SPIKE-UNRESOLVED` markers at bet | shaping quality — open risk into bet |
|
|
823
828
|
| `scope_cut_count` | `~` items cut at SHIP S.0 | appetite pressure / scope hammer |
|
|
824
829
|
| `qa_findings` | `.shapeup/<slug>/qa/hunt-report.md` + triage → `{total, promoted, held}` | edge quality |
|
|
825
|
-
| `slice_count` | breadboard
|
|
830
|
+
| `slice_count` | `receipt.json` → `breadboard.ids.V` (the V# slices init run counted in the staged breadboard; omit the field when `breadboard` is null) | **normalizer / denominator** |
|
|
826
831
|
| `sources` | path to each **SHARED** source artifact — never a LOCAL `.shapeup/` path (the run-trace is superseded run by run, so a LOCAL path dangles by the time anyone reads the row; SHARED paths resolve on any clone — tier-direction rule) | auditability |
|
|
827
832
|
|
|
828
833
|
- `slice_count` is the **denominator**: `round_count=4` on a 2-slice feature is alarming,
|
|
@@ -314,6 +314,7 @@
|
|
|
314
314
|
],
|
|
315
315
|
"ba-pitch-analyzer": [
|
|
316
316
|
"pitch",
|
|
317
|
+
"breadboard",
|
|
317
318
|
"lens",
|
|
318
319
|
"orient_dir",
|
|
319
320
|
"spec_folder",
|
|
@@ -324,12 +325,14 @@
|
|
|
324
325
|
"scope-architect": [
|
|
325
326
|
"feature",
|
|
326
327
|
"spec_folder",
|
|
327
|
-
"tasks"
|
|
328
|
+
"tasks",
|
|
329
|
+
"breadboard"
|
|
328
330
|
],
|
|
329
331
|
"solution-architect": [
|
|
330
332
|
"feature",
|
|
331
333
|
"spec_folder",
|
|
332
|
-
"project_profile"
|
|
334
|
+
"project_profile",
|
|
335
|
+
"breadboard"
|
|
333
336
|
],
|
|
334
337
|
"spec-evaluator": [
|
|
335
338
|
"spec_folder",
|
|
@@ -342,6 +345,7 @@
|
|
|
342
345
|
],
|
|
343
346
|
"orient": [
|
|
344
347
|
"pitch",
|
|
348
|
+
"breadboard",
|
|
345
349
|
"stack",
|
|
346
350
|
"spec_folder",
|
|
347
351
|
"feature"
|
|
@@ -709,6 +713,10 @@
|
|
|
709
713
|
"type": "string"
|
|
710
714
|
},
|
|
711
715
|
"description": "Subset of [idle, loading, success, error, empty] the element must express via data-state."
|
|
716
|
+
},
|
|
717
|
+
"source": {
|
|
718
|
+
"type": "string",
|
|
719
|
+
"description": "The breadboard UI affordance (U#) this element implements; absent without a breadboard."
|
|
712
720
|
}
|
|
713
721
|
}
|
|
714
722
|
},
|
|
@@ -2248,6 +2256,10 @@
|
|
|
2248
2256
|
"type": "string",
|
|
2249
2257
|
"description": "ba-pitch-analyzer (analyze) / orient: the kicked-off pitch path (shaped + bet; frontmatter appetite/status/bet)."
|
|
2250
2258
|
},
|
|
2259
|
+
"breadboard": {
|
|
2260
|
+
"type": "string",
|
|
2261
|
+
"description": "orient / ba-pitch-analyzer (analyze) / solution-architect / scope-architect: the run's staged breadboard — Places (P#), UI and code affordances (U#, N#), stores (S#), slices (V#). Absent = the pitch has no separate breadboard (it may carry one inline); never inferred from the pitch's folder."
|
|
2262
|
+
},
|
|
2251
2263
|
"lens": {
|
|
2252
2264
|
"type": "string",
|
|
2253
2265
|
"enum": [
|
|
@@ -2379,6 +2391,20 @@
|
|
|
2379
2391
|
"type": "string",
|
|
2380
2392
|
"description": "Resolved path to the run's intake — the pitch a fresh ORIENT dispatch is compiled against."
|
|
2381
2393
|
},
|
|
2394
|
+
"breadboard_path": {
|
|
2395
|
+
"type": [
|
|
2396
|
+
"string",
|
|
2397
|
+
"null"
|
|
2398
|
+
],
|
|
2399
|
+
"description": "Resolved path to the run's staged breadboard (`.shapeup/<slug>/breadboard.md`), the pitch's second half, handed to the four planning dispatches as payload.breadboard. Null when the pitch had no separate breadboard file."
|
|
2400
|
+
},
|
|
2401
|
+
"breadboard_source": {
|
|
2402
|
+
"type": [
|
|
2403
|
+
"string",
|
|
2404
|
+
"null"
|
|
2405
|
+
],
|
|
2406
|
+
"description": "How `init run` found the breadboard — flag | sibling | shaping-dir | shared-root | embedded — read from the receipt. Null when there was none or the receipt is unreadable."
|
|
2407
|
+
},
|
|
2382
2408
|
"spec_folder": {
|
|
2383
2409
|
"type": [
|
|
2384
2410
|
"string",
|
|
@@ -2512,7 +2538,7 @@
|
|
|
2512
2538
|
"items": {
|
|
2513
2539
|
"type": "integer"
|
|
2514
2540
|
},
|
|
2515
|
-
"description": "Round numbers
|
|
2541
|
+
"description": "Round numbers whose evaluate-r<n>.json holds a verdict the run may act on (PASS/FAIL, citing T0 artifacts when the spec is scoped) — the resumed run's round counter starts one past the maximum. A refused or uncited round is not done: the relaunch re-enters it."
|
|
2516
2542
|
},
|
|
2517
2543
|
"next_phase": {
|
|
2518
2544
|
"type": "string",
|
|
@@ -400,6 +400,7 @@ const RESUME = {
|
|
|
400
400
|
type: "object",
|
|
401
401
|
properties: {
|
|
402
402
|
intake_path: nullable("string"), spec_folder: nullable("string"), orient_dir: nullable("string"),
|
|
403
|
+
breadboard_path: nullable("string"), breadboard_source: nullable("string"),
|
|
403
404
|
project_profile_path: nullable("string"), status: nullable("string"),
|
|
404
405
|
lens: nullable("string"), stack: nullable("string"),
|
|
405
406
|
run_cmd: nullable("string"), app_url: nullable("string"),
|
|
@@ -560,6 +561,10 @@ const EVAL_VERDICT = {
|
|
|
560
561
|
bug_count: nullable("integer"),
|
|
561
562
|
report_path: nullable("string"),
|
|
562
563
|
round: { type: "integer" },
|
|
564
|
+
// Why the round holds no verdict it may act on — the evaluator's own first deviation when it
|
|
565
|
+
// refused, or what is structurally wrong with the verdict it returned. Null when `ok`.
|
|
566
|
+
status: nullable("string"),
|
|
567
|
+
reason: nullable("string"),
|
|
563
568
|
},
|
|
564
569
|
required: ["ok", "round"],
|
|
565
570
|
};
|
|
@@ -968,7 +973,7 @@ if (!rs.has_orient_artifacts) {
|
|
|
968
973
|
await setRunStatus("orienting", "Orient");
|
|
969
974
|
const o = await worker({
|
|
970
975
|
skill: "orient", operation: "orient", schema: ORIENT, phase: "Orient", label: "orient",
|
|
971
|
-
payload: { pitch: rs.intake_path, spec_folder: specFolder, feature: slug, stack: rs.stack },
|
|
976
|
+
payload: { pitch: rs.intake_path, breadboard: rs.breadboard_path, spec_folder: specFolder, feature: slug, stack: rs.stack },
|
|
972
977
|
// NAME THE FILES. "write the orient/ artifacts" was the whole instruction, while completion is
|
|
973
978
|
// decided by four exact filenames — so a leg that did the work and called its output
|
|
974
979
|
// `code-surface-map.md` and `discovered-tasks.md` aborted the run at the post-condition, having
|
|
@@ -994,8 +999,10 @@ if (!rs.has_orient_artifacts) {
|
|
|
994
999
|
}
|
|
995
1000
|
|
|
996
1001
|
{
|
|
1002
|
+
// `breadboard` travels in the block because a missing one is invisible anywhere later: every
|
|
1003
|
+
// downstream artifact reads the same whether or not the pitch's second half reached the run.
|
|
997
1004
|
const g = await crossGate("L1a", "Orient", ["proceed", "ask", "abort"],
|
|
998
|
-
{ spiked_area: spikedArea, spike_result: spikeResult, riskiest_unknowns: riskiest });
|
|
1005
|
+
{ breadboard: rs.breadboard_source ?? "none", spiked_area: spikedArea, spike_result: spikeResult, riskiest_unknowns: riskiest });
|
|
999
1006
|
if (g.stop) return withWarnings(g.stop);
|
|
1000
1007
|
}
|
|
1001
1008
|
|
|
@@ -1011,7 +1018,7 @@ if (!rs.has_spec_tree) {
|
|
|
1011
1018
|
await setRunStatus("mapping", "Analyze");
|
|
1012
1019
|
const a = await worker({
|
|
1013
1020
|
skill: "ba-pitch-analyzer", operation: "analyze", schema: PHASE_OK, phase: "Analyze", label: "analyze",
|
|
1014
|
-
payload: { pitch: rs.intake_path, spec_folder: specFolder, feature: slug, lens: rs.lens, orient_dir: rs.orient_dir },
|
|
1021
|
+
payload: { pitch: rs.intake_path, breadboard: rs.breadboard_path, spec_folder: specFolder, feature: slug, lens: rs.lens, orient_dir: rs.orient_dir },
|
|
1015
1022
|
extra: "Write the spec tree and the board from the orient artifacts — do not re-scan the code.",
|
|
1016
1023
|
});
|
|
1017
1024
|
if (a.__failed) return diedAt("ANALYZE", a);
|
|
@@ -1043,7 +1050,7 @@ if (!rs.has_wiring_map) {
|
|
|
1043
1050
|
log(`WIRE — dispatching (slug ${slug})`);
|
|
1044
1051
|
const w = await worker({
|
|
1045
1052
|
skill: "solution-architect", operation: "wire", schema: PHASE_OK, phase: "Wire", label: "wire",
|
|
1046
|
-
payload: { feature: slug, spec_folder: specFolder, project_profile: rs.project_profile_path },
|
|
1053
|
+
payload: { feature: slug, spec_folder: specFolder, project_profile: rs.project_profile_path, breadboard: rs.breadboard_path },
|
|
1047
1054
|
extra: "Write the wiring map: per use case, engine → seam → entry-point call site → affordance.",
|
|
1048
1055
|
});
|
|
1049
1056
|
if (w.__failed) return diedAt("WIRE", w);
|
|
@@ -1074,7 +1081,7 @@ if (scopes.length === 0) {
|
|
|
1074
1081
|
log(`MAP SCOPES — dispatching (slug ${slug})`);
|
|
1075
1082
|
const m = await worker({
|
|
1076
1083
|
skill: "scope-architect", operation: "map-scopes", schema: MAPSCOPES, phase: "MapScopes", label: "map-scopes",
|
|
1077
|
-
payload: { feature: slug },
|
|
1084
|
+
payload: { feature: slug, breadboard: rs.breadboard_path },
|
|
1078
1085
|
// SAY THE PASS RULE, for the same reason ORIENT's filenames are named above: the rule lives in
|
|
1079
1086
|
// `verify t0` (a fixture passes iff it exits 0) and the architect never saw it. Given a contract
|
|
1080
1087
|
// that said only "commands that drive this scope end-to-end", it wrote the scope's error paths
|
|
@@ -1159,11 +1166,13 @@ if (waves.length > 1 || excluded.added || ceiling < maxParallelScopes) {
|
|
|
1159
1166
|
`${ceiling < maxParallelScopes ? ` (the window is ${maxParallelScopes}; the substrate the contracts declared is what caps it, not the dial)` : ""}`);
|
|
1160
1167
|
}
|
|
1161
1168
|
|
|
1162
|
-
// Advisory lints at L1b. spec-lint is hard — a substrate overlap makes parallel builds unsafe
|
|
1163
|
-
//
|
|
1169
|
+
// Advisory lints at L1b. spec-lint is hard — a substrate overlap makes parallel builds unsafe, and
|
|
1170
|
+
// a breadboard Place with no screen builds the wrong thing; trace-lint stays advisory until
|
|
1171
|
+
// `covers:` is populated; hill-derive is a projection. The abort names no cause of its own: spec-lint
|
|
1172
|
+
// has more than one kind of red, and the detail says which.
|
|
1164
1173
|
const specLint = await cmd(`verify spec --slug ${slug}`, "MapScopes", "spec-lint");
|
|
1165
1174
|
if (!specLint.ok) {
|
|
1166
|
-
return aborted("L1b", `spec-lint reported
|
|
1175
|
+
return aborted("L1b", `spec-lint reported red findings before BUILD: ${specLint.detail || `exit ${specLint.exit_code}`}`);
|
|
1167
1176
|
}
|
|
1168
1177
|
await advisory(`verify trace --slug ${slug} --quiet`, "MapScopes", "trace-lint");
|
|
1169
1178
|
await advisory(`reduce hill --slug ${slug}`, "MapScopes", "hill-derive");
|
|
@@ -1358,14 +1367,22 @@ while (verdict !== "pass" && round <= maxRounds) {
|
|
|
1358
1367
|
const e = await worker({
|
|
1359
1368
|
skill: "spec-evaluator", operation: "evaluate", schema: EVAL, phase: "Eval", label: `eval:r${round}`,
|
|
1360
1369
|
model: evalModel, round,
|
|
1370
|
+
// No `t0_artifacts` here, deliberately: `harness compile` derives them from the round's green
|
|
1371
|
+
// T0 verdicts on disk, for every lane — this script could only name paths it was told about.
|
|
1361
1372
|
payload: { dimensions: evalDims, run_cmd: rs.run_cmd, round },
|
|
1362
|
-
extra: "Evaluate the running feature against every acceptance criterion and Done-when. One feature-level pass; cite the
|
|
1373
|
+
extra: "Evaluate the running feature against every acceptance criterion and Done-when. One feature-level pass; cite every artifact the order lists under t0_artifacts, re-hashing each yourself.",
|
|
1363
1374
|
});
|
|
1364
1375
|
if (e.__failed) return diedAt("L3", e);
|
|
1365
1376
|
// The pass/fail branch is decided from the WorkResult on disk, not from the dispatching
|
|
1366
1377
|
// agent's own summary of it (`e.overall`) — see EVAL_VERDICT's comment for why.
|
|
1367
1378
|
const ev = await query(`probe eval --slug ${slug} --round ${round}`, EVAL_VERDICT, "Eval", `verdict:r${round}`);
|
|
1368
|
-
if (!ev
|
|
1379
|
+
if (!ev) return diedAt("L3", nullFail(`verdict:r${round}`));
|
|
1380
|
+
// A round with no verdict to act on is NOT a dead worker. An evaluator that refused the round
|
|
1381
|
+
// wrote a result saying why, and `probe eval` carries it as `reason`; reported as "died after
|
|
1382
|
+
// retries", the one sentence naming the cause stayed in a file nobody was pointed at.
|
|
1383
|
+
if (!ev.ok || !ev.overall) {
|
|
1384
|
+
return diedAt("L3", { __failed: `verdict:r${round}: no verdict this round can act on — ${ev.reason || `status ${ev.status || "unknown"}`}` });
|
|
1385
|
+
}
|
|
1369
1386
|
verdict = ev.overall === "PASS" ? "pass" : "fail";
|
|
1370
1387
|
findings = e.findings || [];
|
|
1371
1388
|
}
|