@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
@@ -0,0 +1,473 @@
1
+ ---
2
+ name: materialize-plan-to-beads
3
+ description: Materialize the implementation plan into Beads issues before the build phase
4
+ summary: "When Beads is enabled, converts docs/implementation-plan.md into Beads issues — creating, updating, and reconciling tasks/stories/epics and their dependencies idempotently — so the build phase has a populated tracker to claim from."
5
+ phase: "finalization"
6
+ order: 1440
7
+ dependencies: [implementation-playbook]
8
+ outputs: []
9
+ conditional: "if-needed"
10
+ stateless: true
11
+ category: pipeline
12
+ knowledge-base: [task-tracking]
13
+ ---
14
+
15
+ ## Purpose
16
+ Materialize the frozen implementation plan into Beads (`bd`) issues so the build
17
+ phase has a populated tracker to claim work from. Reads
18
+ `docs/implementation-plan.md` and creates, updates, and reconciles the
19
+ corresponding epics, stories, tasks, and dependency edges in Beads.
20
+
21
+ ## Inputs
22
+ - **`docs/implementation-plan.md`** (required) — the frozen plan emitting the
23
+ Plan Output Contract: stable task IDs (`T-001`…), stable container IDs
24
+ (`S-001`/`E-001`, deep only), and per-task / per-container metadata blocks
25
+ (`id`, `title`, `priority`, `wave`, `risk`, `story`/`epic` parent refs,
26
+ `depends_on`, `acceptance_criteria`). This is the design source of truth for
27
+ *what* the tasks are.
28
+ - **`docs/implementation-playbook.md`** (optional but expected) — wave ordering
29
+ and per-task context used to enrich issue descriptions and to compute the
30
+ wave-biased default priority.
31
+ - **`.beads/`** (required for materialization) — the Beads database initialized
32
+ by the foundation `beads.md` step. Its presence is the signal that this
33
+ project tracks work in Beads; its absence routes to the markdown fallback (see
34
+ Instructions → degradation split).
35
+ - **Scaffold state** (`.scaffold/config.yml` / methodology preset) — supplies
36
+ the methodology/depth that governs whether containers, waves, and risk
37
+ metadata are materialized.
38
+
39
+ ## Expected Outputs
40
+ - Beads issues for every plan task (and, deep only, every story/epic container),
41
+ joined to the plan by metadata keys `plan_task_id` / `plan_story_id` /
42
+ `plan_epic_id`, with `--external-ref "plan:<id>"` stamped for human
43
+ traceability.
44
+ - All plan dependency edges materialized via `bd dep add`, with the
45
+ materializer-owned blocker set recorded per issue in `plan_deps`.
46
+ - A reconciled tracker: not-started issues updated to match the plan, started
47
+ (`in_progress`/`closed`) issues preserved, removed-from-plan not-started issues
48
+ retired, and stale duplicates collapsed to one canonical issue per join key.
49
+ - A deterministic one-line **summary report** (`materialize: …`).
50
+ - On success, a **run-stamped materialization-complete signal** that build
51
+ workers wait on before claiming.
52
+ - This step writes **no markdown documents** (`outputs: []`); its product is the
53
+ state of the Beads database plus the printed summary.
54
+
55
+ ## Quality Criteria
56
+ - (mvp) Every plan task carrying a stable ID maps to exactly one not-started
57
+ Beads issue after a run (joined on `plan_task_id`).
58
+ - (mvp) The reconcile is idempotent — a second run over an unchanged plan
59
+ creates nothing and updates nothing (`materialize: 0 created, 0 updated, …`).
60
+ - (mvp) Only plan-derived issues are claimable: the build claim loop is scoped by
61
+ `--has-metadata-key plan_task_id`, so the bootstrap bead and manual issues are
62
+ never claimed.
63
+ - (mvp) Started issues (`in_progress`/`closed`) are never mutated except the two
64
+ narrow metadata-only exceptions (join-key cleanup, `ac_warn_hash`).
65
+ - (mvp) `bd dep cycles` reports no cycle after a run.
66
+ - (deep) Story/epic containers are created top-down (epics, then stories) with
67
+ correct `--parent` links; a depth-4 story with no epic parent is valid.
68
+ - (deep) Dependency edges reconcile to the plan: missing plan edges added, stale
69
+ `plan_deps`-owned edges removed, manual/external edges preserved.
70
+ - (deep) Tasks removed from the plan are retired when not-started; completed
71
+ (`closed`) issues stay linked for revert-safety.
72
+
73
+ ## Methodology Scaling
74
+ Structure is a function of **methodology/depth only** — read it from scaffold
75
+ state, never probe `bd` for type availability. Both `-t story` and `-t epic` are
76
+ usable directly on the supported `bd` (≥ v1.0.5); mvp stays flat as a deliberate
77
+ *simplicity* choice, not because the type is unavailable. **Dependencies are
78
+ materialized at every depth** (the plan is a DAG even at mvp); depth scales only
79
+ the container hierarchy and `wave`/`risk` metadata.
80
+
81
+ (Note: the **`mvp` methodology preset disables this step entirely** — it doesn't
82
+ use Beads. The "mvp" rules below therefore apply to a **custom** low-depth build
83
+ that has Beads enabled, not to the mvp preset.)
84
+
85
+ - **mvp** → flat `-t task` issues **plus dependencies**. No story/epic
86
+ containers, no `--parent`; skip Pass 0b. Priority defaults to `-p 2`.
87
+ - **deep** → full container hierarchy + `wave`/`risk` metadata + wave-biased
88
+ priority. At depth 4, tasks are parented to **stories** (`-t story`) and
89
+ stories have no epic parent; at depth 5, the full **epic → story → task**
90
+ hierarchy with stories carrying an epic `--parent`.
91
+ - **custom:depth(1-5)** → dials between the two:
92
+ - Depth 1: flat `-t task` issues + dependencies; skip Pass 0b. Priority `-p 2`.
93
+ - Depth 2: flat `-t task` issues + dependencies; skip Pass 0b (sizing detail
94
+ differs upstream only). Priority `-p 2`.
95
+ - Depth 3: flat `-t task` issues + dependencies; skip Pass 0b. Wave-biased
96
+ priority if the plan assigns waves.
97
+ - Depth 4: tasks under `-t story` parents + `wave` metadata; a missing epic ref
98
+ on a story is valid, not a dangling ref. Pass 0b creates stories.
99
+ - Depth 5: full epic → story → task hierarchy + `risk` metadata + full
100
+ traceability; stories carry an epic `--parent`.
101
+
102
+ **Priority is wave-biased** so `bd ready` surfaces work in playbook order:
103
+
104
+ 1. **Explicit plan priority wins**: `P0`→`-p 0`, `P1`→`-p 1`, `P2`→`-p 2`,
105
+ `P3`→`-p 3`.
106
+ 2. **Otherwise bias by wave**: Wave 1 → `-p 1`, Wave 2 → `-p 2`, Wave 3+ →
107
+ `-p 3` (clamp at 3). Dependencies still gate readiness; the bias only orders
108
+ *among* ready tasks.
109
+ 3. **No priority and no waves** (mvp) → default `-p 2`, rely on dependencies.
110
+
111
+ **Dependencies are always materialized**, at **every** depth (including mvp). The
112
+ plan is a DAG at every depth, and without the `bd dep add` edges the scoped
113
+ `bd ready --claim` would expose dependent tasks out of order. Depth governs only
114
+ **hierarchy** (story/epic parents), **wave**, and **risk** — never *whether*
115
+ dependencies exist.
116
+
117
+ ## Mode Detection
118
+ This step is **idempotent and re-runnable**: running it again reconciles the
119
+ current plan against existing Beads issues rather than duplicating them. There is
120
+ no separate "fresh" vs. "update" code path — the same four passes
121
+ (duplicate-guard → container upsert → task upsert → dependency reconcile → stale
122
+ reconcile) cover both. Detect which case you are in by the join-key fetch:
123
+
124
+ - **No plan-derived issues exist yet** (`bd list --all --limit 0
125
+ --has-metadata-key plan_task_id --json` returns `[]`) → effectively a fresh
126
+ import: every pass falls through to "create".
127
+ - **Plan-derived issues already exist** → reconciliation: each pass looks up by
128
+ join key and creates / updates / retires as needed.
129
+
130
+ Because the build preflight invokes this step **unconditionally** before every
131
+ claim loop, design every pass to be a cheap **lookup-and-reconcile no-op when
132
+ already in sync** (a second run over an unchanged plan reports `0 created,
133
+ 0 updated`).
134
+
135
+ ## Update Mode Specifics
136
+ Reconcile plan changes into Beads **one-way (plan → Beads)** without clobbering
137
+ started work:
138
+
139
+ - **Preserve started state.** Never mutate the content fields, execution status,
140
+ claims, or assignees of issues in a **started** status (`in_progress` or
141
+ `closed`). Freely update the content fields
142
+ (title/description/priority/parent/wave/risk) of **not-started**
143
+ (`open`/`blocked`/`deferred`) issues to match the plan.
144
+ - **Two narrow metadata-only exceptions** to "never touch started issues" — each
145
+ touches a single metadata tag, is reported, and changes no content field,
146
+ status, claim, or assignee:
147
+ 1. **Join-key cleanup (Pass 0a)** — a started *non-canonical duplicate* may have
148
+ its own join key `--unset-metadata`'d to restore the one-key invariant.
149
+ 2. **AC-drift bookkeeping (Pass 1)** — an `in_progress` issue whose plan AC
150
+ changed may get `--set-metadata ac_warn_hash=…` so the warning comment posts
151
+ once per distinct change, not every run.
152
+ - **Triggers handled:** new task IDs added (created), content-only edits
153
+ (title/AC/priority/parent/deps changed with no ID change → not-started issues
154
+ updated, edges reconciled), dependency add/remove, and tasks/containers removed
155
+ from the plan (retired if not-started, flagged if `in_progress`, left linked if
156
+ `closed`).
157
+ - A dependency-blocked task stays stored `open` — blockers affect *computed*
158
+ readiness, not stored status; a stored `blocked`/`deferred` is an explicit
159
+ signal that descriptive-field updates neither set nor clear.
160
+
161
+ ## Instructions
162
+
163
+ Execute the steps below in order. All `bd` commands use the verified v1.0.5
164
+ surface — do **not** invent flags (in particular, there is **no `bd list
165
+ --external-ref` filter**; join by metadata keys only).
166
+
167
+ ### Gate on `beads_usable` (version + tooling), then split degradation
168
+
169
+ Compute a single shared `beads_usable` check. It is true **only** when *all* of:
170
+
171
+ 1. `.beads/` exists.
172
+ 2. `bd` is on PATH (`command -v bd`).
173
+ 3. `bd version` parses to **≥ 1.0.5**, using a **macOS/BSD-portable** numeric
174
+ compare (do **not** depend on GNU `sort -V`). Split major/minor/patch and
175
+ compare numerically.
176
+ 4. `jq` is on PATH (`command -v jq`).
177
+
178
+ ```bash
179
+ beads_usable() {
180
+ [ -d .beads ] || return 1
181
+ command -v bd >/dev/null 2>&1 || return 1
182
+ command -v jq >/dev/null 2>&1 || return 1
183
+ # Portable >= 1.0.5 compare — no `sort -V` (absent on macOS/BSD).
184
+ local ver have_major have_minor have_patch
185
+ ver=$(bd version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
186
+ [ -n "$ver" ] || return 1
187
+ IFS=. read -r have_major have_minor have_patch <<EOF
188
+ $ver
189
+ EOF
190
+ # Compare against floor 1.0.5
191
+ if [ "$have_major" -gt 1 ]; then return 0; fi
192
+ if [ "$have_major" -lt 1 ]; then return 1; fi
193
+ if [ "$have_minor" -gt 0 ]; then return 0; fi
194
+ if [ "$have_minor" -lt 0 ]; then return 1; fi
195
+ [ "$have_patch" -ge 5 ]
196
+ }
197
+ ```
198
+
199
+ > **Never** write `[ -d .beads ] && bd …` as a whole command — it returns exit 1
200
+ > when `.beads/` is absent and breaks callers under `set -e`. Always branch on
201
+ > the function's return code with an explicit `if`.
202
+
203
+ **Degradation splits on whether `.beads/` exists** — markdown is safe *only* when
204
+ there is no Beads state to diverge from:
205
+
206
+ - **`.beads/` absent** → genuinely a non-Beads project. Stop materializing and
207
+ let the build drive from the markdown plan/playbook. (Nothing to do here; this
208
+ prompt only runs when Beads is enabled.)
209
+ - **`.beads/` present but `bd`/`jq` missing or too old** (`beads_usable` false)
210
+ → **FAIL CLOSED.** Do **not** markdown-fallback — the tracker may already hold
211
+ execution state (claimed/closed tasks) and the markdown loop would re-run
212
+ completed work. Stop and tell the user to install/upgrade `bd` (≥ v1.0.5) and
213
+ `jq`.
214
+
215
+ Once `beads_usable` is true, resolve **methodology/depth** from scaffold state
216
+ (see Methodology Scaling) and parse the plan's task / container / dependency
217
+ blocks per the Plan Output Contract. Then run the passes below.
218
+
219
+ ### The Retire convention
220
+
221
+ Whenever a pass removes an issue from the plan-derived set (Pass 0a duplicate
222
+ close, Pass 3 stale close), do it in **this exact order**, against the issue's
223
+ **own** join key (`plan_task_id` for a task, `plan_story_id` for a story,
224
+ `plan_epic_id` for an epic — resolved from the issue's type, never hardcoded):
225
+
226
+ 1. `bd label add <id> <stale-label>` — `stale:duplicate` or
227
+ `stale:removed-from-plan`. The **label is applied first** and is what marks the
228
+ issue as *retired-by-the-materializer* (vs. closed as completed work).
229
+ 2. `bd close <id> --reason "…"` — skipped if already `closed`.
230
+ 3. **then** `bd update <id> --unset-metadata <join-key>`.
231
+
232
+ The key is unset **last** so a failure at any step is **resumable**: the issue
233
+ keeps its join key (still found next run) and carries the `stale:*` label
234
+ (recognized as retire-pending, not completed work). Each run re-applies whatever
235
+ steps are missing for any join-keyed issue bearing a `stale:*` label. Because the
236
+ `stale:*` label distinguishes a retired close from a genuine completion, two later
237
+ rules key off it: (a) Pass 0a canonical selection **excludes any `stale:*`-labelled
238
+ issue**; and (b) Pass 3 leaves a completed `closed` issue linked **only when it
239
+ carries no `stale:*` label**.
240
+
241
+ ### Pass 0a — Duplicate guard (always)
242
+
243
+ Before any upsert, fetch the plan-derived issues once and group by each join key:
244
+
245
+ ```bash
246
+ bd list --all --limit 0 --has-metadata-key plan_task_id --json # tasks
247
+ bd list --all --limit 0 --has-metadata-key plan_story_id --json # stories (deep)
248
+ bd list --all --limit 0 --has-metadata-key plan_epic_id --json # epics (depth 5)
249
+ ```
250
+
251
+ For any join key held by **more than one** issue (failed/manual/concurrent prior
252
+ import), restore the **exactly-one-issue-per-key invariant**:
253
+
254
+ 1. **Pick the canonical issue.** First **exclude any issue already bearing a
255
+ `stale:*` label** (retire-pending leftovers can never win). Among the rest,
256
+ the ordering is total:
257
+ - if **exactly one** duplicate is `in_progress` → it is canonical (active work
258
+ wins over any `closed` or not-started copy);
259
+ - else if any is `closed` → canonical is the **oldest** `closed` one;
260
+ - else the **oldest not-started** issue (lowest `created_at`, ties by `id`).
261
+
262
+ The **only** unorderable case is **two or more `in_progress` duplicates** for
263
+ the same plan ID — there is no safe way to pick which active effort to detach,
264
+ so **FAIL CLOSED** and report. (An `in_progress` + `closed` mix is *not* a
265
+ conflict: `in_progress` wins.)
266
+ 2. **Not-started non-canonical duplicates** → retire them per the **Retire
267
+ convention** (`stale:duplicate` label → close → unset the issue's own
268
+ `<join-key>`), so they leave the plan-derived set and are never re-detected.
269
+ 3. **Started non-canonical duplicates** → do **not** close or mutate fields; only
270
+ `bd update <id> --unset-metadata <join-key>` to restore key uniqueness, and
271
+ **report** them for human review.
272
+
273
+ After Pass 0a, exactly one canonical issue retains each `plan_*_id`, so every
274
+ later bulk fetch, map, dep reconcile, stale pass, and the scoped claim loop can
275
+ rely on the one-key invariant.
276
+
277
+ ### Pass 0b — Container upsert (deep only), top-down
278
+
279
+ Skip entirely at mvp / depth 1–3. **Bulk-fetch containers once** (two queries),
280
+ build in-memory `plan_epic_id → id` and `plan_story_id → id` maps, then process
281
+ **epics first, then stories** so each child can reference its parent's resolved
282
+ Beads ID via `--parent`. Containers carry the **same fields as tasks** (title,
283
+ wave-biased `-p`, description/AC, and — for stories — `--parent`).
284
+
285
+ ```bash
286
+ EPIC_MAP_JSON=$(bd list --all --limit 0 --has-metadata-key plan_epic_id --json \
287
+ | jq 'map({ (.metadata.plan_epic_id): .id }) | add // {}')
288
+ STORY_MAP_JSON=$(bd list --all --limit 0 --has-metadata-key plan_story_id --json \
289
+ | jq 'map({ (.metadata.plan_story_id): .id }) | add // {}')
290
+
291
+ # Epic E-001 (depth 5): look up in the epic map; create if absent.
292
+ ID=$(printf '%s' "$EPIC_MAP_JSON" | jq -r '."E-001" // empty')
293
+ [ -z "$ID" ] && ID=$(bd create "<epic title>" -t epic -p <prio> \
294
+ --description "<epic body>" \
295
+ --metadata '{"plan_epic_id":"E-001","wave":"<n>","risk":"<type>"}' \
296
+ --external-ref "plan:E-001" --json | jq -r '.id')
297
+
298
+ # Story S-001: same lookup-or-create against the story map; wire --parent only
299
+ # when an epic ref is present (depth 4 stories have NO epic parent).
300
+ ID=$(printf '%s' "$STORY_MAP_JSON" | jq -r '."S-001" // empty')
301
+ [ -z "$ID" ] && ID=$(bd create "<story title>" -t story -p <prio> \
302
+ ${EPIC_PARENT_ID:+--parent "$EPIC_PARENT_ID"} \
303
+ --description "<story body>" \
304
+ --metadata '{"plan_story_id":"S-001","wave":"<n>","risk":"<type>"}' \
305
+ --external-ref "plan:S-001" --json | jq -r '.id')
306
+ ```
307
+
308
+ Container upsert obeys the **same not-started-vs-started rules as Pass 1**: a
309
+ not-started (`open`/`blocked`/`deferred`) epic/story is updated to match the plan
310
+ (`bd update <id> --title … -d … -p … --parent … --set-metadata wave=… --set-metadata
311
+ risk=…`); a started (`in_progress`/`closed`) container is left untouched.
312
+ Reparenting a not-started story uses `bd update <id> --parent <new>`.
313
+
314
+ ### Pass 1 — Task upsert
315
+
316
+ **Fetch existing plan-derived task issues once**, build an in-memory
317
+ `plan_task_id → issue` map (one `bd` process for the whole plan, not one per
318
+ task), then for each plan task (parent IDs already resolved by Pass 0b):
319
+
320
+ ```bash
321
+ TASK_MAP_JSON=$(bd list --all --limit 0 --has-metadata-key plan_task_id --json \
322
+ | jq 'map({ (.metadata.plan_task_id): . }) | add // {}')
323
+ EXISTING=$(printf '%s' "$TASK_MAP_JSON" | jq -r '."T-001" // empty')
324
+ ```
325
+
326
+ 1. **Create if absent:**
327
+ ```bash
328
+ bd create "<title>" -t task -p <prio> \
329
+ ${PARENT_ID:+--parent "$PARENT_ID"} \
330
+ --metadata '{"plan_task_id":"T-001","wave":"<n>","risk":"<type>"}' \
331
+ --external-ref "plan:T-001" \
332
+ --description "<body incl. acceptance criteria + traceability>"
333
+ ```
334
+ 2. **If present, branch on stored status** (not-started vs. started):
335
+ - **`open` / `blocked` / `deferred`** → **update fields to match the plan,
336
+ including wave/risk metadata** so nothing drifts:
337
+ ```bash
338
+ bd update <id> --title "<title>" -d "<body>" -p <prio> \
339
+ ${PARENT_ID:+--parent "$PARENT_ID"} \
340
+ --set-metadata wave=<n> --set-metadata risk=<type>
341
+ ```
342
+ These are not-started states; mutating their fields never changes execution
343
+ status or readiness, and never unblocks/disturbs a stored `blocked`/`deferred`
344
+ signal.
345
+ - **`in_progress`** → **do not mutate fields.** If the plan's AC/description
346
+ changed, post a warning **idempotently**: compute a hash of the current plan
347
+ AC text; if it differs from the issue's `ac_warn_hash` metadata, post one
348
+ `bd comment <id> "Plan AC changed since this task was started: …"` then record
349
+ the new hash with the targeted `bd update <id> --set-metadata
350
+ ac_warn_hash=<hash>`. Re-runs with the same AC post nothing.
351
+ - **`closed`** → leave **entirely untouched** (do not recreate, do not edit).
352
+
353
+ ### Pass 2 — Dependency reconcile
354
+
355
+ Run after all issues exist (forward references resolved). **Read existing edges
356
+ from a single bulk fetch**, not per task — `bd list --all --limit 0 --json`
357
+ carries an inline `dependencies` array (`depends_on_id`, `type`) on each issue.
358
+ Reconcile in-memory against the plan's `depends_on` lists; only the mutating
359
+ calls need per-edge invocations.
360
+
361
+ **Materializer-owned edges are tracked explicitly** in the `plan_deps` metadata
362
+ key (a sorted CSV of blocker task IDs, e.g. `"T-003,T-007"`) — *not* inferred
363
+ from endpoint types. "Both endpoints are plan-derived" is **not** proof of
364
+ ownership: an agent may add an execution blocker during the build, and deleting
365
+ it just because it is absent from the markdown plan would corrupt Beads-owned
366
+ state.
367
+
368
+ For each **`open` / `blocked` / `deferred`** dependent (never mutate
369
+ `in_progress`/`closed`), reconcile against the plan's `depends_on` list:
370
+
371
+ - **Add** plan edges not already present: `bd dep add <blocked-id> <blocker-id>`
372
+ (re-adding is a no-op).
373
+ - **Remove** an edge **only** when it is **in the prior `plan_deps`** (materializer
374
+ created it) **and absent from the current plan**: `bd dep remove <blocked-id>
375
+ <blocker-id>`. Edges **not** in `plan_deps` (manual/external blockers) are
376
+ preserved untouched; a manual edge that collides with a plan edge is kept and
377
+ noted in the summary, not deleted.
378
+ - **Rewrite `plan_deps` authoritatively** at the end of each issue's reconcile:
379
+
380
+ > `plan_deps' = (prior plan_deps ∩ current-plan deps) ∪ (edges this run added)`
381
+
382
+ Keep still-valid owned edges, add the ones just created, and **explicitly
383
+ exclude any pre-existing edge that was *not* already in `plan_deps`** — even if
384
+ it collides with a current plan dep (that edge stays manual-owned so a later
385
+ plan removal never deletes it). Write with `bd update <id> --set-metadata
386
+ plan_deps=<csv>` (or `bd update <id> --unset-metadata plan_deps` when empty).
387
+
388
+ A per-issue read, if ever needed, is `bd dep list <id> --direction down --json`
389
+ (verified: `down` = what `<id>` depends on, i.e. its blockers; `up` returns
390
+ dependents and is wrong here). No manual status reconciliation is needed after
391
+ add/remove — readiness is computed from open blockers.
392
+
393
+ **Detect cycles after applying:** `bd dep cycles` — surface any cycle as an error
394
+ (the plan DAG is validated upstream, but a manual edit could reintroduce one).
395
+
396
+ ### Pass 3 — Stale reconcile (tasks/containers removed from the plan)
397
+
398
+ List every plan-derived issue (the three `--has-metadata-key` queries) and diff
399
+ IDs against the current plan:
400
+
401
+ - ID no longer in the plan and issue **not started** (`open`/`blocked`/
402
+ `deferred`) → **retire it** per the Retire convention
403
+ (`stale:removed-from-plan` label → `bd close <id> --reason "Removed from
404
+ implementation plan (was <id>)"` → unset the issue's own `<join-key>`), so it
405
+ drops out of future queries and can never be misread as "started/dropped".
406
+ - Such an issue **`in_progress`** → **do not auto-close.** Report it in the
407
+ summary for human attention (it may be mid-flight work the plan dropped by
408
+ mistake). Leave its join key intact so it stays visible until a human decides.
409
+ - Such an issue **`closed` as genuinely-completed work** (no `stale:*` label) →
410
+ **leave it entirely untouched** (do **not** unset its join key). Plan IDs are
411
+ never reused, so a later revert restoring the task must let Pass 1 find this
412
+ issue by `plan_task_id` and recognize it as already `closed` — not recreate an
413
+ `open` issue and force redone work. (A `closed` issue that **does** carry
414
+ `stale:removed-from-plan` is a retire-pending leftover — the Retire convention
415
+ finishes unsetting its key.)
416
+
417
+ ### Summary report (deterministic)
418
+
419
+ Print exactly one line:
420
+
421
+ ```
422
+ materialize: C created, U updated, K unchanged,
423
+ S skipped (in_progress/closed — started, not mutated),
424
+ D deps added, R deps removed,
425
+ X stale closed, W stale flagged for review
426
+ ```
427
+
428
+ where each letter is the count accumulated across the passes. A re-run over an
429
+ unchanged plan prints `0 created, 0 updated` (proof of idempotency).
430
+
431
+ ### Run-stamped materialization-complete signal
432
+
433
+ On **success**, set a durable **materialization-complete signal** that build
434
+ workers wait on before their first claim. The signal **must be run-specific** —
435
+ carry a `run_id` or the current plan hash (e.g. `materialized_at=<run_id>` /
436
+ `plan_hash=<sha>` as metadata on the project merge-slot / bootstrap bead, or a
437
+ workspace marker file). **Clear/overwrite any prior signal *before* acquiring the
438
+ merge-slot lock**, so a stale signal from a previous pipeline run (or a pre-update
439
+ plan) can never let workers race ahead of a fresh re-materialization.
440
+
441
+ In the sequential finalization path there is no concurrency, but still set the
442
+ run-stamped signal so the multi-agent build preflight (which gates workers on a
443
+ signal matching **this run's** id/hash) is satisfied without re-running the
444
+ materializer. The multi-agent orchestrator runs this whole procedure **once per
445
+ wave under the merge-slot lock** (acquire via a real acquisition loop, verify
446
+ ownership with `bd merge-slot check --json`, run the idempotent materializer, set
447
+ the signal, then `bd merge-slot release` via a `trap … EXIT INT TERM`); workers
448
+ never materialize — they only wait for the signal, then claim.
449
+
450
+ ## After This Step
451
+
452
+ The implementation plan is now **materialized into Beads** — every plan task
453
+ (and, on deep builds, every story/epic and dependency edge) exists as a `bd`
454
+ issue joined to the plan by `plan_task_id` / `plan_story_id` / `plan_epic_id`.
455
+ Print the summary line and tell the user:
456
+
457
+ ---
458
+ **Plan materialized into Beads.** The tracker is populated and ready for the
459
+ build phase.
460
+
461
+ **Summary:** [paste the `materialize: …` line]
462
+
463
+ **Next:**
464
+ - **Single agent:** run `/scaffold:single-agent-start` to begin the scoped claim
465
+ loop (`bd ready --claim --has-metadata-key plan_task_id`).
466
+ - **Multiple agents in parallel:** run `/scaffold:multi-agent-start` — the
467
+ orchestrator re-runs this materializer once under the merge-slot lock before
468
+ fan-out, then workers wait for the completion signal and claim.
469
+
470
+ **Re-running:** this step is idempotent — after any plan update, re-run
471
+ `/scaffold:materialize-plan-to-beads` to reconcile new/changed/removed tasks and
472
+ dependencies into Beads without clobbering started work.
473
+ ---
@@ -178,6 +178,12 @@ Enable when: project uses Beads task tracking methodology (user selects Beads du
178
178
  setup), or user explicitly enables structured task management. Skip when: user prefers
179
179
  GitHub Issues, Linear, or another task tracker, or explicitly declines Beads setup.
180
180
 
181
+ Note: this step only initializes the tracker — it does **not** create your
182
+ implementation tasks. Beads stays empty of plan tasks until the finalization step
183
+ `/scaffold:materialize-plan-to-beads` converts `docs/implementation-plan.md` into
184
+ Beads issues just before the build phase. An empty `bd ready` right after this
185
+ step is therefore expected, not a problem.
186
+
181
187
  ## Mode Detection
182
188
  Update mode if `.beads/` contains a populated database (look for `.beads/embeddeddolt/`
183
189
  in the default embedded-Dolt layout, `.beads/dolt/` in server mode, or any `.beads/*.db`
@@ -46,8 +46,33 @@ and produce a structured coverage matrix and review summary.
46
46
  - (deep) Every task has verb-first description, >= 1 input file reference, >= 1 acceptance criterion, and defined output artifact
47
47
  - (mvp) Every task complies with agent executability rules (3-file, 150-line, single-concern, decision-free, test co-location)
48
48
  - (mvp) Tasks exceeding limits have explicit `<!-- agent-size-exception -->` justification
49
+ - (mvp) Plan Output Contract holds: every task and container has an ID; IDs are unique and stable; all parent and `depends_on` refs resolve; the dependency graph is acyclic; field blocks parse
49
50
  - (depth 4+) Independent model reviews completed and reconciled
50
51
 
52
+ ## Plan Output Contract Validation
53
+
54
+ The plan is consumed by an automated materializer that needs stable join keys
55
+ and a parseable structure (see the **Plan Output Contract** section in
56
+ `implementation-plan.md`). This review MUST verify that contract holds, rejecting
57
+ any plan that violates it:
58
+
59
+ - **ID presence** — every task has a `T-NNN` ID, and (deep) every story has an
60
+ `S-NNN` ID and every epic an `E-NNN` ID. Flag any item missing an ID.
61
+ - **Uniqueness and stability** — task, story, and epic IDs are each **unique**
62
+ and **stable** (no two items share an ID; no ID changed for an item that
63
+ already existed in a prior plan revision; IDs are never reused for a different
64
+ task/container).
65
+ - **Referential integrity (no dangling refs)** — every `story` / `epic` parent
66
+ reference **and every `depends_on` reference** **resolves** to a declared ID in
67
+ the plan. Report any **dangling** ref (a parent or `depends_on` entry that does
68
+ not resolve to a declared ID) as a blocking finding. A story with no `epic`
69
+ parent is valid, not dangling.
70
+ - **Acyclicity** — the dependency graph formed by all `depends_on` edges is a
71
+ **DAG**: there are **no cycles**. Walk the graph and report any cycle.
72
+ - **Parseability** — every per-task and per-container fenced metadata block
73
+ parses cleanly under the canonical serialization (well-formed key/value block
74
+ with the required fields present).
75
+
51
76
  ## Methodology Scaling
52
77
  - **deep**: Full multi-pass review with multi-model validation. AC coverage
53
78
  matrix. Independent Codex/Gemini dispatches. Detailed reconciliation report.
@@ -6,7 +6,7 @@ phase: "planning"
6
6
  order: 1210
7
7
  dependencies: [tdd, operations, security, review-architecture, create-evals]
8
8
  outputs: [docs/implementation-plan.md]
9
- reads: [create-prd, story-tests, database-schema, api-contracts, ux-spec]
9
+ reads: [create-prd, story-tests, database-schema, api-contracts, mcp-tool-resource-contract, ux-spec]
10
10
  conditional: null
11
11
  knowledge-base: [task-decomposition, system-architecture]
12
12
  ---
@@ -28,6 +28,7 @@ The primary mapping is Story → Task(s), with PRD as the traceability root.
28
28
  - docs/security-review.md (optional) — security requirements to incorporate into tasks
29
29
  - docs/database-schema.md (optional) — data layer tasks
30
30
  - docs/api-contracts.md (optional) — API implementation tasks
31
+ - docs/mcp-contract.md (optional) — MCP tool/resource/prompt implementation tasks
31
32
  - docs/ux-spec.md (optional) — frontend tasks
32
33
  - tests/acceptance/ (optional) — test skeletons to reference in task descriptions
33
34
  - docs/story-tests-map.md (optional) — AC-to-test mapping for task coverage verification
@@ -36,6 +37,79 @@ The primary mapping is Story → Task(s), with PRD as the traceability root.
36
37
  - docs/implementation-plan.md — task list with dependencies, sizing, and
37
38
  assignment recommendations
38
39
 
40
+ ## Plan Output Contract
41
+
42
+ The plan is consumed not only by humans but by an automated materializer that
43
+ upserts it into a task tracker (Beads). That tool needs **stable join keys** and
44
+ a **machine-readable structure** to parse and reconcile against. Every plan
45
+ therefore MUST satisfy the following contract. These rules apply at all depths
46
+ unless a clause is explicitly marked **(deep)** — container IDs, waves, and risk
47
+ are deep-only; stable task IDs, the per-task block, and referential integrity are
48
+ required at **every** depth.
49
+
50
+ 1. **Stable task IDs.** Every task carries a unique, format-defined ID of the
51
+ form `T-001`, `T-002`, … (zero-padded, monotonically increasing). IDs are
52
+ assigned fresh in initial mode and are **never reused** — once `T-007`
53
+ exists, that number is retired even if the task is later removed. In update
54
+ mode, existing task IDs are **preserved verbatim**; new tasks take the next
55
+ unused number. The ID is the stable join key the materializer uses to upsert
56
+ idempotently, so retitling or reordering a task must never change its ID.
57
+
58
+ 2. **Stable container IDs (deep).** Every story carries an ID of the form
59
+ `S-001`, `S-002`, … and every epic an ID of the form `E-001`, `E-002`, …,
60
+ under the same stability rules as task IDs (unique, monotonic, never reused,
61
+ preserved across update-mode runs). These become the `plan_story_id` /
62
+ `plan_epic_id` join keys so re-runs reconcile rather than duplicate parents.
63
+
64
+ 3. **Per-task field block.** Each task is serialized with a per-task heading
65
+ plus a fenced metadata block carrying these fields:
66
+ - `id` — the task's `T-NNN` ID
67
+ - `title` — a short verb-first task name
68
+ - `priority` — optional; one of `P0`–`P3`
69
+ - `wave` — (deep) integer wave number
70
+ - `risk` — (deep) short risk-type string
71
+ - `story` / `epic` — (deep) the parent container ID(s) this task belongs to
72
+ - `depends_on` — list of task IDs this task depends on (the DAG edges); empty
73
+ list if none
74
+ - `acceptance_criteria` — a delimited block copied verbatim into the tracker
75
+ issue body
76
+
77
+ 4. **Per-container field block (deep).** Each story and epic is serialized in
78
+ the same parseable form as tasks, with a per-container heading plus a fenced
79
+ metadata block carrying: `id` (its `S-NNN` / `E-NNN` ID), `title`, `priority`
80
+ (optional), `wave` / `risk` (if assigned), and a `description` / acceptance
81
+ block (the container body). A story's `epic` parent is **optional** — epics
82
+ appear only in the deepest hierarchy, so a plan with stories but no epics has
83
+ stories with no `epic` parent, which is valid (not a dangling ref).
84
+
85
+ 5. **Canonical serialization.** Use one unambiguous markdown shape for every
86
+ item so the materializer has a single parsing rule: a per-item heading
87
+ followed immediately by a fenced key/value (e.g. `yaml`) metadata block
88
+ containing the fields above. Apply the identical shape to tasks **and**
89
+ containers. Example task block:
90
+
91
+ ```yaml
92
+ id: T-001
93
+ title: Create users table migration
94
+ priority: P1
95
+ wave: 1
96
+ risk: data-loss
97
+ story: S-002
98
+ depends_on: []
99
+ acceptance_criteria: |
100
+ - Migration creates a `users` table with id, email, created_at
101
+ - Rolling the migration back drops the table cleanly
102
+ ```
103
+
104
+ 6. **Referential integrity.** Every `story` / `epic` parent reference (on a
105
+ task, and the optional `epic` parent on a story) **and every `depends_on`
106
+ entry** must **resolve to a declared ID** in this plan — there must be **no
107
+ dangling refs** in either the hierarchy or the dependency graph. A dangling
108
+ `depends_on` would let the materializer silently skip an edge and allow tasks
109
+ to be claimed out of order; a dangling parent ref would orphan a task. The
110
+ implementation-plan **review** step validates this referential integrity (see
111
+ `implementation-plan-review.md`).
112
+
39
113
  ## Quality Criteria
40
114
  - (deep) Every architecture component has implementation tasks
41
115
  - (mvp) Every user story has implementation tasks