@zigrivers/scaffold 3.32.1 → 3.33.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.
Files changed (119) hide show
  1. package/README.md +41 -18
  2. package/content/guides/knowledge-freshness/.diagrams/diagram-0.svg +1 -1
  3. package/content/guides/knowledge-freshness/.diagrams/manifest.json +1 -1
  4. package/content/guides/knowledge-freshness/index.html +9 -5
  5. package/content/guides/knowledge-freshness/index.md +5 -4
  6. package/content/guides/pipeline/index.html +2 -2
  7. package/content/guides/pipeline/index.md +2 -2
  8. package/content/knowledge/mcp-server/mcp-authentication.md +100 -0
  9. package/content/knowledge/mcp-server/mcp-deployment-patterns.md +119 -0
  10. package/content/knowledge/mcp-server/mcp-error-handling.md +131 -0
  11. package/content/knowledge/mcp-server/mcp-observability.md +125 -0
  12. package/content/knowledge/mcp-server/mcp-prompt-primitives.md +119 -0
  13. package/content/knowledge/mcp-server/mcp-protocol-fundamentals.md +130 -0
  14. package/content/knowledge/mcp-server/mcp-resource-design.md +111 -0
  15. package/content/knowledge/mcp-server/mcp-sdk-selection.md +136 -0
  16. package/content/knowledge/mcp-server/mcp-testing-strategies.md +127 -0
  17. package/content/knowledge/mcp-server/mcp-tool-design.md +125 -0
  18. package/content/knowledge/mcp-server/mcp-transport-patterns.md +122 -0
  19. package/content/knowledge/mcp-server/mcp-versioning.md +115 -0
  20. package/content/methodology/custom-defaults.yml +2 -0
  21. package/content/methodology/deep.yml +2 -0
  22. package/content/methodology/mcp-server-overlay.yml +88 -0
  23. package/content/methodology/mvp.yml +2 -0
  24. package/content/pipeline/build/multi-agent-resume.md +107 -11
  25. package/content/pipeline/build/multi-agent-start.md +104 -11
  26. package/content/pipeline/build/single-agent-resume.md +74 -8
  27. package/content/pipeline/build/single-agent-start.md +69 -12
  28. package/content/pipeline/finalization/materialize-plan-to-beads.md +473 -0
  29. package/content/pipeline/foundation/beads.md +6 -0
  30. package/content/pipeline/planning/implementation-plan-review.md +25 -0
  31. package/content/pipeline/planning/implementation-plan.md +75 -1
  32. package/content/pipeline/specification/mcp-tool-resource-contract.md +77 -0
  33. package/dist/cli/commands/adopt.d.ts.map +1 -1
  34. package/dist/cli/commands/adopt.js +33 -1
  35. package/dist/cli/commands/adopt.js.map +1 -1
  36. package/dist/cli/commands/init.d.ts +6 -0
  37. package/dist/cli/commands/init.d.ts.map +1 -1
  38. package/dist/cli/commands/init.js +46 -3
  39. package/dist/cli/commands/init.js.map +1 -1
  40. package/dist/cli/init-flag-families.d.ts +6 -1
  41. package/dist/cli/init-flag-families.d.ts.map +1 -1
  42. package/dist/cli/init-flag-families.js +59 -2
  43. package/dist/cli/init-flag-families.js.map +1 -1
  44. package/dist/cli/init-flag-families.test.js +86 -1
  45. package/dist/cli/init-flag-families.test.js.map +1 -1
  46. package/dist/config/schema.d.ts +2310 -126
  47. package/dist/config/schema.d.ts.map +1 -1
  48. package/dist/config/schema.js +26 -1
  49. package/dist/config/schema.js.map +1 -1
  50. package/dist/config/schema.test.js +75 -2
  51. package/dist/config/schema.test.js.map +1 -1
  52. package/dist/config/validators/index.d.ts.map +1 -1
  53. package/dist/config/validators/index.js +2 -0
  54. package/dist/config/validators/index.js.map +1 -1
  55. package/dist/config/validators/mcp-server.d.ts +4 -0
  56. package/dist/config/validators/mcp-server.d.ts.map +1 -0
  57. package/dist/config/validators/mcp-server.js +37 -0
  58. package/dist/config/validators/mcp-server.js.map +1 -0
  59. package/dist/config/validators/mcp-server.test.d.ts +2 -0
  60. package/dist/config/validators/mcp-server.test.d.ts.map +1 -0
  61. package/dist/config/validators/mcp-server.test.js +47 -0
  62. package/dist/config/validators/mcp-server.test.js.map +1 -0
  63. package/dist/core/assembly/materialize-plan-to-beads-assembly.test.d.ts +2 -0
  64. package/dist/core/assembly/materialize-plan-to-beads-assembly.test.d.ts.map +1 -0
  65. package/dist/core/assembly/materialize-plan-to-beads-assembly.test.js +75 -0
  66. package/dist/core/assembly/materialize-plan-to-beads-assembly.test.js.map +1 -0
  67. package/dist/e2e/project-type-overlays.test.js +83 -0
  68. package/dist/e2e/project-type-overlays.test.js.map +1 -1
  69. package/dist/project/adopt.d.ts.map +1 -1
  70. package/dist/project/adopt.js +3 -1
  71. package/dist/project/adopt.js.map +1 -1
  72. package/dist/project/detectors/coverage.test.js +1 -0
  73. package/dist/project/detectors/coverage.test.js.map +1 -1
  74. package/dist/project/detectors/disambiguate.d.ts.map +1 -1
  75. package/dist/project/detectors/disambiguate.js +6 -1
  76. package/dist/project/detectors/disambiguate.js.map +1 -1
  77. package/dist/project/detectors/disambiguate.test.js +18 -0
  78. package/dist/project/detectors/disambiguate.test.js.map +1 -1
  79. package/dist/project/detectors/index.d.ts.map +1 -1
  80. package/dist/project/detectors/index.js +2 -1
  81. package/dist/project/detectors/index.js.map +1 -1
  82. package/dist/project/detectors/mcp-server.d.ts +4 -0
  83. package/dist/project/detectors/mcp-server.d.ts.map +1 -0
  84. package/dist/project/detectors/mcp-server.js +91 -0
  85. package/dist/project/detectors/mcp-server.js.map +1 -0
  86. package/dist/project/detectors/mcp-server.test.d.ts +2 -0
  87. package/dist/project/detectors/mcp-server.test.d.ts.map +1 -0
  88. package/dist/project/detectors/mcp-server.test.js +115 -0
  89. package/dist/project/detectors/mcp-server.test.js.map +1 -0
  90. package/dist/project/detectors/types.d.ts +6 -2
  91. package/dist/project/detectors/types.d.ts.map +1 -1
  92. package/dist/project/detectors/types.js.map +1 -1
  93. package/dist/types/config.d.ts +8 -1
  94. package/dist/types/config.d.ts.map +1 -1
  95. package/dist/wizard/copy/core.d.ts.map +1 -1
  96. package/dist/wizard/copy/core.js +4 -0
  97. package/dist/wizard/copy/core.js.map +1 -1
  98. package/dist/wizard/copy/index.d.ts.map +1 -1
  99. package/dist/wizard/copy/index.js +2 -0
  100. package/dist/wizard/copy/index.js.map +1 -1
  101. package/dist/wizard/copy/mcp-server.d.ts +3 -0
  102. package/dist/wizard/copy/mcp-server.d.ts.map +1 -0
  103. package/dist/wizard/copy/mcp-server.js +40 -0
  104. package/dist/wizard/copy/mcp-server.js.map +1 -0
  105. package/dist/wizard/copy/types.d.ts +5 -1
  106. package/dist/wizard/copy/types.d.ts.map +1 -1
  107. package/dist/wizard/flags.d.ts +9 -1
  108. package/dist/wizard/flags.d.ts.map +1 -1
  109. package/dist/wizard/questions.d.ts +4 -2
  110. package/dist/wizard/questions.d.ts.map +1 -1
  111. package/dist/wizard/questions.js +37 -0
  112. package/dist/wizard/questions.js.map +1 -1
  113. package/dist/wizard/questions.test.js +107 -0
  114. package/dist/wizard/questions.test.js.map +1 -1
  115. package/dist/wizard/wizard.d.ts +3 -2
  116. package/dist/wizard/wizard.d.ts.map +1 -1
  117. package/dist/wizard/wizard.js +3 -1
  118. package/dist/wizard/wizard.js.map +1 -1
  119. package/package.json +1 -1
@@ -42,6 +42,7 @@ steps:
42
42
  database-schema: { enabled: true, conditional: "if-needed" }
43
43
  review-database: { enabled: true, conditional: "if-needed" }
44
44
  api-contracts: { enabled: true, conditional: "if-needed" }
45
+ mcp-tool-resource-contract: { enabled: false }
45
46
  review-api: { enabled: true, conditional: "if-needed" }
46
47
  ux-spec: { enabled: true, conditional: "if-needed" }
47
48
  review-ux: { enabled: true, conditional: "if-needed" }
@@ -73,6 +74,7 @@ steps:
73
74
  apply-fixes-and-freeze: { enabled: true }
74
75
  developer-onboarding-guide: { enabled: true }
75
76
  implementation-playbook: { enabled: true }
77
+ materialize-plan-to-beads: { enabled: true, conditional: "if-needed" }
76
78
  # Phase 15 — Build (build) — stateless, on-demand execution steps
77
79
  single-agent-start: { enabled: true }
78
80
  single-agent-resume: { enabled: true }
@@ -0,0 +1,88 @@
1
+ # methodology/mcp-server-overlay.yml
2
+ name: mcp-server
3
+ description: >
4
+ MCP Server overlay — an MCP server has no UI, so the design-system and UX
5
+ steps are disabled. database-schema/review-database stay available
6
+ (if-needed) for servers that persist resources/state. The
7
+ mcp-tool-resource-contract step is enabled (if-needed) for specifying MCP
8
+ tool/resource/prompt schemas. MCP domain knowledge (protocol, SDK, transport,
9
+ tool/resource design, auth, testing, deployment, observability, versioning)
10
+ is injected into the relevant pipeline steps.
11
+ project-type: mcp-server
12
+
13
+ step-overrides:
14
+ # No UI surface — disable design/UX steps entirely.
15
+ design-system: { enabled: false }
16
+ ux-spec: { enabled: false }
17
+ review-ux: { enabled: false }
18
+ # Stateful servers may persist resources; keep available but skippable.
19
+ database-schema: { enabled: true, conditional: "if-needed" }
20
+ review-database: { enabled: true, conditional: "if-needed" }
21
+ # MCP-specific spec step — enabled for mcp-server projects.
22
+ mcp-tool-resource-contract: { enabled: true, conditional: "if-needed" }
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # knowledge-overrides
26
+ # ---------------------------------------------------------------------------
27
+ # Inject MCP domain expertise into existing pipeline steps so that MCP
28
+ # protocol, SDK, transport, tool/resource design, auth, testing, deployment,
29
+ # observability, and versioning knowledge is available during prompt assembly.
30
+ knowledge-overrides:
31
+ # Foundation
32
+ tech-stack:
33
+ append: [mcp-protocol-fundamentals, mcp-sdk-selection, mcp-transport-patterns]
34
+ tdd:
35
+ append: [mcp-testing-strategies]
36
+
37
+ # Architecture & Decisions
38
+ system-architecture:
39
+ append: [mcp-protocol-fundamentals, mcp-transport-patterns, mcp-tool-design, mcp-resource-design]
40
+ adrs:
41
+ append: [mcp-sdk-selection, mcp-transport-patterns]
42
+
43
+ # Specifications
44
+ api-contracts:
45
+ append: [mcp-tool-design, mcp-resource-design, mcp-error-handling]
46
+ # mcp-tool-resource-contract knowledge is already declared in the step's own
47
+ # frontmatter knowledge-base ([mcp-tool-design, mcp-resource-design,
48
+ # mcp-prompt-primitives, mcp-error-handling]); no overlay override needed.
49
+ database-schema:
50
+ append: [mcp-resource-design]
51
+
52
+ # Testing
53
+ add-e2e-testing:
54
+ append: [mcp-testing-strategies]
55
+
56
+ # Quality Gates
57
+ security:
58
+ append: [mcp-authentication]
59
+ review-security:
60
+ append: [mcp-authentication]
61
+ operations:
62
+ append: [mcp-deployment-patterns, mcp-observability, mcp-versioning]
63
+ review-operations:
64
+ append: [mcp-deployment-patterns, mcp-observability, mcp-versioning]
65
+
66
+ # Reviews
67
+ review-architecture:
68
+ append: [mcp-transport-patterns, mcp-tool-design, mcp-resource-design]
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # reads-overrides
72
+ # ---------------------------------------------------------------------------
73
+ # Wire docs/mcp-contract.md (output of mcp-tool-resource-contract) into
74
+ # downstream steps that need MCP auth/validation/error contracts.
75
+ # Uses append-only so non-MCP steps are unaffected when the overlay is absent.
76
+ reads-overrides:
77
+ # Security review: MCP auth/error contracts are security-relevant
78
+ security:
79
+ append: [mcp-tool-resource-contract]
80
+ review-security:
81
+ append: [mcp-tool-resource-contract]
82
+ # Test generation: test the tools/resources against the contract
83
+ create-evals:
84
+ append: [mcp-tool-resource-contract]
85
+ story-tests:
86
+ append: [mcp-tool-resource-contract]
87
+ tdd:
88
+ append: [mcp-tool-resource-contract]
@@ -42,6 +42,7 @@ steps:
42
42
  database-schema: { enabled: false }
43
43
  review-database: { enabled: false }
44
44
  api-contracts: { enabled: false }
45
+ mcp-tool-resource-contract: { enabled: false }
45
46
  review-api: { enabled: false }
46
47
  ux-spec: { enabled: false }
47
48
  review-ux: { enabled: false }
@@ -73,6 +74,7 @@ steps:
73
74
  apply-fixes-and-freeze: { enabled: false }
74
75
  developer-onboarding-guide: { enabled: false }
75
76
  implementation-playbook: { enabled: true }
77
+ materialize-plan-to-beads: { enabled: false }
76
78
  # Phase 15 — Build (build) — stateless, on-demand execution steps
77
79
  single-agent-start: { enabled: true }
78
80
  single-agent-resume: { enabled: true }
@@ -119,17 +119,113 @@ Recover your context by checking the current state of work:
119
119
 
120
120
  ### Beads Recovery
121
121
 
122
- **If Beads is configured** (`.beads/` exists):
123
- - `bd list --assignee "$ARGUMENTS"` check for tasks with `in_progress` status owned by this agent
124
- - If a PR shows as merged, close the corresponding task: `bd close <id>`
125
- - If there is in-progress work, finish it (see "Resume In-Progress Work" below)
126
- - Otherwise, clean up and start fresh:
127
- - `git fetch origin --prune && git clean -fd`
128
- - Run the install command from CLAUDE.md Key Commands
129
- - Atomically claim the next ready task: `TASK=$(bd ready --claim --json | jq -r '.id')` (sets `assignee=$BEADS_ACTOR` + `status=in_progress`; no race window between agents).
130
- - Continue working until `bd ready --claim --json` returns no task.
131
-
132
- **Without Beads:**
122
+ The implementation plan is materialized into Beads issues by
123
+ `/scaffold:materialize-plan-to-beads` before the build phase. A resumed build
124
+ runs the **same defensive preflight** as the start prompt it never claims
125
+ against an empty or stale tracker. Under multi-agent concurrency,
126
+ materialization is **orchestrator-only** and runs **once per wave under a
127
+ merge-slot lock**; workers never materialize and must wait for a run-stamped
128
+ completion signal before their first claim.
129
+
130
+ **Step 1 compute `beads_usable`.** `beads_usable` is true only when **all**
131
+ hold: `.beads/` exists, `bd` is on `PATH`, `bd version` parses to **≥ 1.0.5**
132
+ (using a macOS/BSD-safe numeric compare — split major/minor/patch and compare
133
+ numerically, never rely on GNU `sort -V`), and `jq` is on `PATH`. Never write
134
+ `[ -d .beads ] && bd …` as a whole command — it returns exit 1 when `.beads/` is
135
+ absent and breaks callers under `set -e`; use an `if`.
136
+
137
+ **Step 2 — route on the decision table** (this is what prevents the "empty
138
+ tracker looks done" bug):
139
+
140
+ | Condition | Action |
141
+ |---|---|
142
+ | `.beads/` **absent** | Non-Beads project → drive the loop from the markdown playbook/plan (see "Markdown fallback" below). Do **not** call `bd`. |
143
+ | `.beads/` present but `beads_usable` is false (`bd`/`jq` missing or `bd` < 1.0.5) | **Fail closed.** Stop and tell the user to install/upgrade `bd` (≥ v1.0.5) and `jq`. Do **not** markdown-fall-back — Beads may already hold execution state. |
144
+ | `beads_usable`, plan has **no** stable IDs **and** Beads holds no plan-derived issues and no non-bootstrap claimed/closed work | Genuinely legacy plan → markdown loop, emit "re-run planning to assign stable task IDs". Do **not** claim. |
145
+ | `beads_usable`, plan has no stable IDs **but** Beads already holds plausible build work | **Fail closed** — markdown would bypass existing execution state. |
146
+ | `beads_usable`, contract **partially present or malformed** | **Fail closed.** Do **not** markdown-fall-back. Require planning to be re-run/fixed. |
147
+ | `beads_usable` **and a valid stable-ID contract** | **Resume your own task; orchestrator materializes once under the lock; workers wait; then claim** (see Step 3). |
148
+
149
+ **Step 3 — `beads_usable` + valid contract:**
150
+
151
+ **Two distinct identities.** The merge-slot needs a **per-process unique** holder
152
+ (e.g. `agent-$$` or a UUID) so two local agents sharing one `git user.name` don't
153
+ both think they hold the slot. The **claim/resume actor must stay stable** per
154
+ worktree/session — resolve `BEADS_ACTOR` → `git user.name` → `$USER` (never
155
+ empty). Use the unique value **only** for `bd merge-slot acquire/check/release`;
156
+ keep the stable `BEADS_ACTOR` for the resume lookup and `bd ready --claim`.
157
+
158
+ 1. **Resume the actor's own in-flight *plan* task first.** Before claiming
159
+ anything new — and using the **stable** claim actor, not the per-process lock
160
+ identity — check for a **plan-derived** task already `in_progress` assigned to
161
+ you, scoped exactly like claiming:
162
+ `bd list --status in_progress --assignee <actor> --has-metadata-key plan_task_id --json`.
163
+ If one exists, continue it (see "Resume In-Progress Work" below). Scoping to
164
+ `plan_task_id` prevents resuming onto an unrelated manual/bootstrap issue
165
+ assigned to the same actor; any such non-plan in-progress work is reported
166
+ separately, not resumed as build work.
167
+ 2. **Reconcile merged PRs.** If a PR shows as merged, close the corresponding
168
+ task: `bd close <id>`.
169
+ 3. **Orchestrator-only materialization under the merge-slot lock.** Only the wave
170
+ orchestrator (the first agent resuming the wave) runs the materializer;
171
+ workers skip to step 4. The orchestrator:
172
+ - **Clears/overwrites any stale completion signal before acquiring the lock**,
173
+ so a signal left from a previous pipeline run (or a pre-update plan) can't
174
+ let workers race ahead of a fresh re-materialization. The **completion
175
+ signal must be run-stamped** — carry a `run_id` or the current plan hash
176
+ (e.g. a metadata flag on the project merge-slot/bootstrap bead, or a
177
+ workspace marker recording `run_id` / `materialized_at`).
178
+ - **Acquires the lock with a real acquisition loop, not a status poll** — loop
179
+ on `bd merge-slot acquire` itself and re-verify ownership via
180
+ `bd merge-slot check --json` (a released slot is `holder: null` and never
181
+ auto-promotes a waiter). Guard the non-zero/queued return with `|| true` and
182
+ release via a `trap … EXIT INT TERM`.
183
+ - **Once ownership is confirmed, invokes `/scaffold:materialize-plan-to-beads`**
184
+ (the canonical procedure — do not duplicate the four-pass logic). It is
185
+ idempotent and a cheap no-op when in sync. If it returns non-zero, **fail
186
+ closed** (do not set the signal, do not claim, do not markdown-fall-back).
187
+ - On success, **sets the run-stamped completion signal**, then **releases**
188
+ the slot.
189
+ 4. **Workers block on the run-stamped completion signal before their first
190
+ claim.** A released slot (`holder: null`) does **not** prove the orchestrator
191
+ ran — blocking on slot release alone is insufficient (a worker could
192
+ acquire/release before the orchestrator started). Workers wait until a signal
193
+ matching **this run's** `run_id`/plan-hash is present, then proceed.
194
+ 5. **Clean up and run the scoped claim loop** (using the **stable** `BEADS_ACTOR`,
195
+ not the per-process lock identity):
196
+ - `git fetch origin --prune && git clean -fd`
197
+ - Run the install command from CLAUDE.md Key Commands
198
+ - Atomically claim the next ready **plan** task:
199
+ `TASK=$(bd ready --claim --has-metadata-key plan_task_id --json | jq -r '.id')`
200
+ - Scoping to `plan_task_id` keeps the loop from ever claiming the bootstrap
201
+ "initialize Beads" bead or a manually-created issue.
202
+ - This sets `assignee=$BEADS_ACTOR` + `status=in_progress` in a single
203
+ round-trip — no race window between agents.
204
+ 6. Continue until the scoped claim
205
+ (`bd ready --claim --has-metadata-key plan_task_id --json`) returns no ready
206
+ task, then run the **completion check**.
207
+
208
+ **Completion check (empty `bd ready` ≠ done).** An empty scoped-ready result does
209
+ **not** mean the build is finished. On an empty result, fetch all plan-derived
210
+ tasks (`bd list --all --limit 0 --has-metadata-key plan_task_id --json`) and
211
+ classify the remaining non-`closed` tasks:
212
+
213
+ - **All plan tasks `closed`** → genuinely **done**; exit gracefully.
214
+ - Otherwise classify **each** remaining non-`closed` task independently — do
215
+ **not** short-circuit on "any task is `in_progress`". Resolve blocker statuses
216
+ from an **unfiltered** `bd list --all --limit 0 --json` (manual blockers carry
217
+ no `plan_task_id`):
218
+ - **advancing** — the task is itself `in_progress`, **or** a **transitive**
219
+ blocker (walk the chain; bound the walk, reuse `bd dep cycles`) is
220
+ `in_progress`.
221
+ - **stalled** — not `in_progress` and no transitive blocker is `in_progress`.
222
+ - **All remaining advancing** → exit gracefully (normal multi-agent case). **Any
223
+ stalled** → **stop and report the stalled subset**, grouped by why (open
224
+ dependency, manual `blocked`, `deferred`). Unrelated global `in_progress` work
225
+ that blocks none of the stalled tasks does **not** suppress the report.
226
+
227
+ **Markdown fallback** (only when `.beads/` is **absent**, or for a genuinely
228
+ legacy plan per the table — never past existing Beads state):
133
229
  - Read `docs/implementation-playbook.md` as the primary task reference.
134
230
  Fall back to `docs/implementation-plan.md` when no playbook is present.
135
231
  - If a PR shows as merged, mark the corresponding task as complete in the plan/playbook
@@ -123,17 +123,110 @@ These rules are critical for multi-agent operation:
123
123
 
124
124
  ### Beads Detection
125
125
 
126
- **If Beads is configured** (`.beads/` exists):
127
- - Branch naming: `bd-<id>/<desc>`
128
- - Verify `$BEADS_ACTOR` is set per agent (echo it; bail if empty).
129
- - Atomically claim the next ready task: `TASK=$(bd ready --claim --json | jq -r '.id')`
130
- - This sets `assignee=$BEADS_ACTOR` and `status=in_progress` in a single round-trip eliminates the race window where two agents both see the same "ready" task.
131
- - If `bd ready --claim` returns no task, you're done exit the loop.
132
- - Implement following the TDD workflow below
133
- - After PR is merged: `bd close <id>`
134
- - Repeat (`bd ready --claim --json`) until no tasks remain
135
-
136
- **Without Beads:**
126
+ The implementation plan is materialized into Beads issues by
127
+ `/scaffold:materialize-plan-to-beads` before the build phase. This block is the
128
+ **defensive preflight** that guarantees the tracker is populated and current
129
+ before any agent claims work it never claims against an empty or stale tracker.
130
+ Under multi-agent concurrency, materialization is **orchestrator-only** and runs
131
+ **once per wave under a merge-slot lock**; workers never materialize and must
132
+ wait for a run-stamped completion signal before their first claim.
133
+
134
+ **Step 1 compute `beads_usable`.** `beads_usable` is true only when **all**
135
+ hold: `.beads/` exists, `bd` is on `PATH`, `bd version` parses to **≥ 1.0.5**
136
+ (using a macOS/BSD-safe numeric compare — split major/minor/patch and compare
137
+ numerically, never rely on GNU `sort -V`), and `jq` is on `PATH`. Never write
138
+ `[ -d .beads ] && bd …` as a whole command — it returns exit 1 when `.beads/` is
139
+ absent and breaks callers under `set -e`; use an `if`.
140
+
141
+ **Step 2 — route on the decision table** (this is what prevents the "empty
142
+ tracker looks done" bug):
143
+
144
+ | Condition | Action |
145
+ |---|---|
146
+ | `.beads/` **absent** | Non-Beads project → drive the loop from the markdown playbook/plan (see "Markdown fallback" below). Do **not** call `bd`. |
147
+ | `.beads/` present but `beads_usable` is false (`bd`/`jq` missing or `bd` < 1.0.5) | **Fail closed.** Stop and tell the user to install/upgrade `bd` (≥ v1.0.5) and `jq`. Do **not** markdown-fall-back — Beads may already hold execution state. |
148
+ | `beads_usable`, but the plan has **no** stable task IDs **and** Beads holds no plan-derived issues and no non-bootstrap claimed/closed work | Genuinely legacy plan → markdown loop, and emit "re-run planning to assign stable task IDs". Do **not** claim. |
149
+ | `beads_usable`, plan has no stable IDs **but** Beads already holds plausible build work (claimed/closed non-bootstrap issues) | **Fail closed** — markdown would bypass existing execution state. Require re-running planning + materialization. |
150
+ | `beads_usable`, contract **partially present or malformed** | **Fail closed.** Do **not** markdown-fall-back (would bypass existing plan-derived issues and diverge). Require planning to be re-run/fixed. |
151
+ | `beads_usable` **and a valid stable-ID contract** | **Orchestrator materializes once under the lock, then everyone claims** (see Step 3). |
152
+
153
+ **Step 3 — `beads_usable` + valid contract → orchestrator materializes, workers wait, then claim:**
154
+
155
+ Branch naming: `bd-<id>/<desc>`. Verify `$BEADS_ACTOR` is set per agent (echo it;
156
+ bail if empty).
157
+
158
+ **Two distinct identities.** The merge-slot needs a **per-process unique** holder
159
+ (e.g. `agent-$$` or a UUID) so two local agents sharing one `git user.name` don't
160
+ both think they hold the slot. The **claim/resume actor must stay stable** per
161
+ worktree/session — resolve `BEADS_ACTOR` → `git user.name` → `$USER` (never
162
+ empty). Use the unique value **only** for `bd merge-slot acquire/check/release`;
163
+ keep the stable `BEADS_ACTOR` for `bd ready --claim`. If you must override
164
+ `BEADS_ACTOR` for the lock, scope that override to the lock commands and restore
165
+ the stable actor before claiming.
166
+
167
+ 1. **Orchestrator-only materialization under the merge-slot lock.** Only the wave
168
+ orchestrator (the first agent) runs the materializer; workers skip to step 2.
169
+ The orchestrator:
170
+ - **Clears/overwrites any stale completion signal before acquiring the lock**,
171
+ so a signal left from a previous pipeline run (or a pre-update plan) can't
172
+ let workers race ahead of a fresh re-materialization. The **completion
173
+ signal must be run-stamped** — carry a `run_id` or the current plan hash
174
+ (e.g. a metadata flag on the project merge-slot/bootstrap bead, or a
175
+ workspace marker file recording `run_id` / `materialized_at`).
176
+ - **Acquires the lock with a real acquisition loop, not a status poll** — loop
177
+ on `bd merge-slot acquire` itself and re-verify ownership via
178
+ `bd merge-slot check --json` (a released slot is `holder: null` and never
179
+ auto-promotes a waiter, so a check-only loop deadlocks). Guard the
180
+ non-zero/queued return with `|| true` and release via a
181
+ `trap … EXIT INT TERM`.
182
+ - **Once ownership is confirmed, invokes `/scaffold:materialize-plan-to-beads`**
183
+ (the canonical procedure — do not duplicate the four-pass logic). It is
184
+ idempotent and a cheap no-op when already in sync. If it returns non-zero,
185
+ **fail closed** (do not set the signal, do not claim, do not markdown-fall-back).
186
+ - On success, **sets the run-stamped completion signal**, then **releases**
187
+ the slot.
188
+ 2. **Workers block on the run-stamped completion signal before their first
189
+ claim.** A released slot (`holder: null`) does **not** prove the orchestrator
190
+ ran — a worker could acquire/release before the orchestrator even started. So
191
+ workers wait until a signal matching **this run's** `run_id`/plan-hash is
192
+ present, then proceed. The lock serializes the *write*; the run-stamped signal
193
+ gates the *readers*.
194
+ 3. **Run the scoped claim loop** (using the **stable** `BEADS_ACTOR`, not the
195
+ per-process lock identity). Atomically claim the next ready **plan** task:
196
+ `TASK=$(bd ready --claim --has-metadata-key plan_task_id --json | jq -r '.id')`
197
+ - Scoping to `plan_task_id` keeps the loop from ever claiming the bootstrap
198
+ "initialize Beads" bead or a manually-created issue.
199
+ - This sets `assignee=$BEADS_ACTOR` and `status=in_progress` in a single
200
+ round-trip — eliminates the race window where two agents both see the same
201
+ "ready" task.
202
+ 4. Implement following the TDD workflow below.
203
+ 5. After the PR is merged: `bd close <id>`.
204
+ 6. Repeat the scoped claim (`bd ready --claim --has-metadata-key plan_task_id --json`)
205
+ until it returns no ready task, then run the **completion check**.
206
+
207
+ **Completion check (empty `bd ready` ≠ done).** An empty scoped-ready result does
208
+ **not** mean the build is finished. On an empty result, fetch all plan-derived
209
+ tasks (`bd list --all --limit 0 --has-metadata-key plan_task_id --json`) and
210
+ classify the remaining non-`closed` tasks:
211
+
212
+ - **All plan tasks `closed`** → genuinely **done**; exit gracefully.
213
+ - Otherwise classify **each** remaining non-`closed` task independently — do
214
+ **not** short-circuit on "any task is `in_progress`". Resolve blocker statuses
215
+ from an **unfiltered** `bd list --all --limit 0 --json` (manual blockers carry
216
+ no `plan_task_id`):
217
+ - **advancing** — the task is itself `in_progress`, **or** at least one of its
218
+ **transitive** blockers (walk the chain; bound the walk, reuse
219
+ `bd dep cycles`) is `in_progress`.
220
+ - **stalled** — not `in_progress` and **no** transitive blocker is
221
+ `in_progress`.
222
+ - **All remaining tasks advancing** → exit gracefully (normal multi-agent case;
223
+ other agents are still working).
224
+ - **Any task stalled** → **stop and report the stalled subset**, grouped by why
225
+ (open dependency, manual `blocked`, `deferred`). Unrelated global `in_progress`
226
+ work that blocks none of the stalled tasks does **not** suppress the report.
227
+
228
+ **Markdown fallback** (only when `.beads/` is **absent**, or for a genuinely
229
+ legacy plan per the table — never past existing Beads state):
137
230
  - Branch naming: `<type>/<desc>` (e.g., `feat/add-auth`)
138
231
  1. Read `docs/implementation-playbook.md` as the primary task execution reference.
139
232
  Fall back to `docs/implementation-plan.md` when no playbook is present.
@@ -96,14 +96,80 @@ Recover your context by checking the current state of work:
96
96
 
97
97
  ### Beads Recovery
98
98
 
99
- **If Beads is configured** (`.beads/` exists):
100
- - `bd list` check for tasks with `in_progress` status
101
- - If a PR shows as merged, close the corresponding task: `bd close <id>`
102
- - If there is in-progress work, finish it (see "Resume In-Progress Work" below)
103
- - Otherwise, atomically claim the next ready task: `TASK=$(bd ready --claim --json | jq -r '.id')` (sets `assignee=$BEADS_ACTOR` + `status=in_progress`; no race window).
104
- - Continue working until `bd ready --claim --json` returns no task.
105
-
106
- **Without Beads:**
99
+ The implementation plan is materialized into Beads issues by
100
+ `/scaffold:materialize-plan-to-beads` before the build phase. A resumed build
101
+ runs the **same defensive preflight** as the start prompt it never claims
102
+ against an empty or stale tracker.
103
+
104
+ **Step 1 compute `beads_usable`.** `beads_usable` is true only when **all**
105
+ hold: `.beads/` exists, `bd` is on `PATH`, `bd version` parses to **≥ 1.0.5**
106
+ (using a macOS/BSD-safe numeric compare — split major/minor/patch and compare
107
+ numerically, never rely on GNU `sort -V`), and `jq` is on `PATH`. Never write
108
+ `[ -d .beads ] && bd …` as a whole command — it returns exit 1 when `.beads/` is
109
+ absent and breaks callers under `set -e`; use an `if`.
110
+
111
+ **Step 2 — route on the decision table** (this is what prevents the "empty
112
+ tracker looks done" bug):
113
+
114
+ | Condition | Action |
115
+ |---|---|
116
+ | `.beads/` **absent** | Non-Beads project → drive the loop from the markdown playbook/plan (see "Markdown fallback" below). Do **not** call `bd`. |
117
+ | `.beads/` present but `beads_usable` is false (`bd`/`jq` missing or `bd` < 1.0.5) | **Fail closed.** Stop and tell the user to install/upgrade `bd` (≥ v1.0.5) and `jq`. Do **not** markdown-fall-back — Beads may already hold execution state. |
118
+ | `beads_usable`, plan has **no** stable IDs **and** Beads holds no plan-derived issues and no non-bootstrap claimed/closed work | Genuinely legacy plan → markdown loop, emit "re-run planning to assign stable task IDs". Do **not** claim. |
119
+ | `beads_usable`, plan has no stable IDs **but** Beads already holds plausible build work | **Fail closed** — markdown would bypass existing execution state. |
120
+ | `beads_usable`, contract **partially present or malformed** | **Fail closed.** Do **not** markdown-fall-back. Require planning to be re-run/fixed. |
121
+ | `beads_usable` **and a valid stable-ID contract** | **Resume your own task, materialize, then claim** (see Step 3). |
122
+
123
+ **Step 3 — `beads_usable` + valid contract:**
124
+
125
+ 1. **Resume the actor's own in-flight *plan* task first.** Before claiming
126
+ anything new — and using the **stable** claim actor (resolve `BEADS_ACTOR` →
127
+ `git user.name` → `$USER`, never empty) — check for a **plan-derived** task
128
+ already `in_progress` assigned to you, scoped exactly like claiming:
129
+ `bd list --status in_progress --assignee <actor> --has-metadata-key plan_task_id --json`.
130
+ If one exists, continue it (see "Resume In-Progress Work" below). Scoping to
131
+ `plan_task_id` prevents resuming onto an unrelated manual/bootstrap issue
132
+ assigned to the same actor; any such non-plan in-progress work is reported
133
+ separately, not resumed as build work.
134
+ 2. **Reconcile merged PRs.** If a PR shows as merged, close the corresponding
135
+ task: `bd close <id>`.
136
+ 3. **Always invoke the canonical materializer** before claiming new work:
137
+ `/scaffold:materialize-plan-to-beads`. Run it unconditionally — do **not**
138
+ gate it on a count or ID-set comparison. It is idempotent (a cheap no-op when
139
+ in sync) and is the single source of the four-pass reconcile logic — this
140
+ prompt **invokes** it, it does not duplicate it. If it returns non-zero,
141
+ **fail closed** — stop, surface the error, do **not** claim and do **not**
142
+ markdown-fall-back past existing Beads state.
143
+ 4. **Run the scoped claim loop.** Atomically claim the next ready **plan** task:
144
+ `TASK=$(bd ready --claim --has-metadata-key plan_task_id --json | jq -r '.id')`
145
+ - Scoping to `plan_task_id` keeps the loop from ever claiming the bootstrap
146
+ "initialize Beads" bead or a manually-created issue.
147
+ - This sets `assignee=$BEADS_ACTOR` + `status=in_progress` in a single
148
+ round-trip — no race window.
149
+ 5. Continue until the scoped claim
150
+ (`bd ready --claim --has-metadata-key plan_task_id --json`) returns no ready
151
+ task, then run the **completion check**.
152
+
153
+ **Completion check (empty `bd ready` ≠ done).** An empty scoped-ready result does
154
+ **not** mean the build is finished. On an empty result, fetch all plan-derived
155
+ tasks (`bd list --all --limit 0 --has-metadata-key plan_task_id --json`) and
156
+ classify the remaining non-`closed` tasks:
157
+
158
+ - **All plan tasks `closed`** → genuinely **done**; exit gracefully.
159
+ - Otherwise classify **each** remaining non-`closed` task independently — do
160
+ **not** short-circuit on "any task is `in_progress`". Resolve blocker statuses
161
+ from an **unfiltered** `bd list --all --limit 0 --json` (manual blockers carry
162
+ no `plan_task_id`):
163
+ - **advancing** — the task is itself `in_progress`, **or** a **transitive**
164
+ blocker (walk the chain; bound the walk, reuse `bd dep cycles`) is
165
+ `in_progress`.
166
+ - **stalled** — not `in_progress` and no transitive blocker is `in_progress`.
167
+ - **All remaining advancing** → exit gracefully. **Any stalled** → **stop and
168
+ report the stalled subset**, grouped by why (open dependency, manual `blocked`,
169
+ `deferred`).
170
+
171
+ **Markdown fallback** (only when `.beads/` is **absent**, or for a genuinely
172
+ legacy plan per the table — never past existing Beads state):
107
173
  - Read `docs/implementation-playbook.md` as the primary task reference.
108
174
  Fall back to `docs/implementation-plan.md` when no playbook is present.
109
175
  - If a PR shows as merged, mark the corresponding task as complete in the plan/playbook
@@ -98,18 +98,75 @@ Before writing any code, verify the environment is ready:
98
98
 
99
99
  ### Beads Detection
100
100
 
101
- Check if `.beads/` directory exists.
102
-
103
- **If Beads is configured:**
104
- - Atomically claim the next ready task: `TASK=$(bd ready --claim --json | jq -r '.id')`
105
- - This sets `assignee=$BEADS_ACTOR` (or your git user.name) and `status=in_progress` in a single round-trip — no race window vs other agents.
106
- - If you need a specific task by ID instead, use `bd update <id> --claim`.
107
- - If `bd ready --claim` returns no task, you're done exit the loop.
108
- - Implement following the TDD workflow below
109
- - After PR is merged, run `bd close <id>`
110
- - Repeat (`bd ready --claim --json`) until no tasks remain
111
-
112
- **Without Beads:**
101
+ The implementation plan is materialized into Beads issues by
102
+ `/scaffold:materialize-plan-to-beads` before the build phase. This block is the
103
+ **defensive preflight** that guarantees the tracker is populated and current
104
+ before any work is claimed it never claims against an empty or stale tracker.
105
+
106
+ **Step 1 compute `beads_usable`.** `beads_usable` is true only when **all**
107
+ hold: `.beads/` exists, `bd` is on `PATH`, `bd version` parses to **≥ 1.0.5**
108
+ (using a macOS/BSD-safe numeric compare split major/minor/patch and compare
109
+ numerically, never rely on GNU `sort -V`), and `jq` is on `PATH`. Never write
110
+ `[ -d .beads ] && bd …` as a whole command it returns exit 1 when `.beads/` is
111
+ absent and breaks callers under `set -e`; use an `if`.
112
+
113
+ **Step 2 — route on the decision table** (this is what prevents the "empty
114
+ tracker looks done" bug):
115
+
116
+ | Condition | Action |
117
+ |---|---|
118
+ | `.beads/` **absent** | Non-Beads project → drive the loop from the markdown playbook/plan (see "Markdown fallback" below). Do **not** call `bd`. |
119
+ | `.beads/` present but `beads_usable` is false (`bd`/`jq` missing or `bd` < 1.0.5) | **Fail closed.** Stop and tell the user to install/upgrade `bd` (≥ v1.0.5) and `jq`. Do **not** markdown-fall-back — Beads may already hold execution state, and a markdown re-run would re-execute completed work. |
120
+ | `beads_usable`, but the plan has **no** stable task IDs **and** Beads holds no plan-derived issues and no non-bootstrap claimed/closed work | Genuinely legacy plan → markdown loop, and emit "re-run planning to assign stable task IDs". Do **not** claim. |
121
+ | `beads_usable`, plan has no stable IDs **but** Beads already holds plausible build work (claimed/closed non-bootstrap issues) | **Fail closed** — markdown would bypass existing execution state. Require re-running planning + materialization. |
122
+ | `beads_usable`, contract **partially present or malformed** (some IDs present, or plan-derived issues already exist, but the contract doesn't fully parse) | **Fail closed.** Do **not** markdown-fall-back (would bypass existing plan-derived issues and diverge). Require planning to be re-run/fixed. |
123
+ | `beads_usable` **and a valid stable-ID contract** | **Always materialize first, then claim** (see Step 3). |
124
+
125
+ **Step 3 — `beads_usable` + valid contract → materialize, then claim:**
126
+
127
+ 1. **Always invoke the canonical materializer:** `/scaffold:materialize-plan-to-beads`.
128
+ Run it unconditionally — do **not** gate it on a count or ID-set comparison
129
+ (every such gate misses content-only edits, partial imports, or stale deps).
130
+ The materializer is idempotent and a cheap no-op when already in sync; it is
131
+ the single source of the four-pass reconcile logic — this prompt **invokes**
132
+ it, it does not duplicate it. If the materializer returns non-zero (mid-run
133
+ failure), **fail closed** — stop and surface the error; do **not** claim and
134
+ do **not** markdown-fall-back past existing Beads state.
135
+ 2. **Run the scoped claim loop.** Atomically claim the next ready **plan** task:
136
+ `TASK=$(bd ready --claim --has-metadata-key plan_task_id --json | jq -r '.id')`
137
+ - Scoping to `plan_task_id` keeps the loop from ever claiming the bootstrap
138
+ "initialize Beads" bead or a manually-created issue.
139
+ - This sets `assignee=$BEADS_ACTOR` (or your git user.name) and
140
+ `status=in_progress` in a single round-trip — no race window.
141
+ - If you need a specific task by ID instead, use `bd update <id> --claim`.
142
+ 3. Implement following the TDD workflow below.
143
+ 4. After the PR is merged, run `bd close <id>`.
144
+ 5. Repeat the scoped claim (`bd ready --claim --has-metadata-key plan_task_id --json`)
145
+ until it returns no ready task, then run the **completion check**.
146
+
147
+ **Completion check (empty `bd ready` ≠ done).** An empty scoped-ready result does
148
+ **not** mean the build is finished — every remaining task could be blocked. On an
149
+ empty result, fetch all plan-derived tasks
150
+ (`bd list --all --limit 0 --has-metadata-key plan_task_id --json`) and classify
151
+ the remaining non-`closed` tasks:
152
+
153
+ - **All plan tasks `closed`** → genuinely **done**; exit gracefully.
154
+ - Otherwise classify **each** remaining non-`closed` task independently — do
155
+ **not** short-circuit on "any task is `in_progress`". Resolve blocker statuses
156
+ from an **unfiltered** `bd list --all --limit 0 --json` (manual blockers carry
157
+ no `plan_task_id`):
158
+ - **advancing** — the task is itself `in_progress`, **or** at least one of its
159
+ **transitive** blockers (walk the chain; bound the walk and reuse
160
+ `bd dep cycles` as a guard) is `in_progress`.
161
+ - **stalled** — not `in_progress` and **no** transitive blocker is
162
+ `in_progress` (open-but-unready behind inactive blockers, manually `blocked`,
163
+ or `deferred`).
164
+ - **All remaining tasks advancing** → exit gracefully.
165
+ - **Any task stalled** → **stop and report the stalled subset**, grouped by why
166
+ (open dependency, manual `blocked`, `deferred`), so the user can unblock them.
167
+
168
+ **Markdown fallback** (only when `.beads/` is **absent**, or for a genuinely
169
+ legacy plan per the table — never past existing Beads state):
113
170
  1. Read `docs/implementation-playbook.md` as the primary task execution reference.
114
171
  Fall back to `docs/implementation-plan.md` when no playbook is present.
115
172
  2. Pick the first uncompleted task that has no unfinished dependencies.