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.
@@ -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.stack` (sweep hint), `payload.spec_folder` (the SHARED spec
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.md path — read it if it exists; record "no breadboard" if absent
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. Flag every place the
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 (+ sibling `breadboard.md` if present) |
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):** at GATE L0, before Step 2, dispatch
47
- an Agent (model: exec) that calls `Skill(shapeup-sdlc-plugin:translator) --check <intake>`.
48
- English → proceed as-is. Non-English → dispatch a second Agent (`--auto` under auto/unattended)
49
- and orchestrate against the produced `<name>.en.md`. The tech lead detects and sequences; it never
50
- translates itself.
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: path to a shaping.md / pitch.md (already shaped + bet by PO).
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) --check <intake>.
37
- English → use intake as-is.
38
- non-English → Agent (model: exec) → Skill(shapeup-sdlc-plugin:translator) <intake>
39
- (--auto under auto/unattended), then use the produced <name>.en.md as
40
- the ORIENT/MAP-SCOPES input. Log in ledger.
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: the code-surface headline (where it lands), the spiked area + result, the riskiest
171
- open unknowns going into mapping.
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). Any red → HARD STOP, past a 🔴 at the architect's own checkpoint.
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 B5). Two hard rules:
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
- ORIENT against the <name>.en.md copy.
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>", "t0_artifacts": [...]}'
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 B5 (≤9) | **normalizer / denominator** |
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 with an evaluate-r<n>.json result — the resumed run's round counter starts one past the maximum."
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
- // trace-lint stays advisory until `covers:` is populated; hill-derive is a projection.
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 a disjointness or size problem before BUILD: ${specLint.detail || `exit ${specLint.exit_code}`}`);
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 T0 artifact you re-hash yourself.",
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 || !ev.ok || !ev.overall) return diedAt("L3", nullFail(`verdict:r${round}`));
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
  }