@smartmemory/compose 0.2.52-beta → 0.2.54-beta

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 (148) hide show
  1. package/.claude/skills/compose/SKILL.md +0 -8
  2. package/README.md +39 -0
  3. package/bin/compose.js +378 -3
  4. package/dist/assets/App-D_tvTs_n.js +896 -0
  5. package/dist/assets/abnfDiagram-VRR7QNED-Dhb7O5rp.js +1 -0
  6. package/dist/assets/arc-C4oVdnk2.js +1 -0
  7. package/dist/assets/architectureDiagram-ZJ3FMSHR-DqHrb5_F.js +36 -0
  8. package/dist/assets/blockDiagram-677ZJIJ3-DUyBayYK.js +132 -0
  9. package/dist/assets/{browser-CnKiSnlr.js → browser-Cr0recrN.js} +6 -6
  10. package/dist/assets/{c4Diagram-AHTNJAMY-DtDSNlOX.js → c4Diagram-LMCZKHZV-CxlkWMRK.js} +1 -1
  11. package/dist/assets/channel-xb4mQfuL.js +1 -0
  12. package/dist/assets/{chunk-QZHKN3VN-BMWFLZ8t.js → chunk-2Q5K7J3B-C6QJsyOQ.js} +1 -1
  13. package/dist/assets/{chunk-YZCP3GAM-CqjNXooj.js → chunk-32BRIVSS-D9NgYu9W.js} +1 -1
  14. package/dist/assets/{chunk-FMBD7UC4-BAa7Edp6.js → chunk-5VM5RSS4-DbD86rhK.js} +1 -1
  15. package/dist/assets/chunk-EX3LRPZG-E4m4xNMw.js +231 -0
  16. package/dist/assets/{chunk-4BX2VUAB-oXU2npoL.js → chunk-JWPE2WC7-DHGlRWbw.js} +1 -1
  17. package/dist/assets/chunk-MOJQB5TN-BblYnFUq.js +88 -0
  18. package/dist/assets/chunk-RYQCIY6F-CGT-88vL.js +1 -0
  19. package/dist/assets/chunk-V7JOEXUC-BJJmX0mc.js +206 -0
  20. package/dist/assets/{chunk-EDXVE4YY-Bse2kdRh.js → chunk-VR4S4FIN-B4ygduCD.js} +1 -1
  21. package/dist/assets/{chunk-55IACEB6-BEEk2FdH.js → chunk-XXDRQBXY-7APXuI2s.js} +1 -1
  22. package/dist/assets/classDiagram-OUVF2IWQ-BIvgPTgh.js +1 -0
  23. package/dist/assets/classDiagram-v2-EOCWNBFH-BIvgPTgh.js +1 -0
  24. package/dist/assets/{cose-bilkent-S5V4N54A-NYnH3mVW.js → cose-bilkent-JH36ORCC-DnVcT6R6.js} +1 -1
  25. package/dist/assets/cynefin-VYW2F7L2-C9qiUSfX.js +178 -0
  26. package/dist/assets/cynefinDiagram-TSTJHNR4-BowkQhEu.js +62 -0
  27. package/dist/assets/dagre-VKFMJZFB-Y5V-7U3p.js +4 -0
  28. package/dist/assets/diagram-FQU43EPY-I7wDE-Xp.js +3 -0
  29. package/dist/assets/diagram-G47NLZAW-DapGa1HI.js +24 -0
  30. package/dist/assets/diagram-NH7WQ7WH-CHO1JntO.js +24 -0
  31. package/dist/assets/diagram-OA4YK3LP-mBWP2D1G.js +30 -0
  32. package/dist/assets/diagram-WEI45ONY-C8KN8YnF.js +41 -0
  33. package/dist/assets/ebnfDiagram-CCIWWBDH-ZkJG9NOL.js +1 -0
  34. package/dist/assets/erDiagram-Q63AITRT-DGnL3kW1.js +85 -0
  35. package/dist/assets/flowDiagram-23GEKE2U-DHYupfOu.js +156 -0
  36. package/dist/assets/ganttDiagram-NO4QXBWP-B1B2zpwR.js +292 -0
  37. package/dist/assets/gitGraphDiagram-IHSO6WYX-B9_OP4Fs.js +106 -0
  38. package/dist/assets/graph-C9eacEi8.js +1 -0
  39. package/dist/assets/graph-xkel59g2.js +331 -0
  40. package/dist/assets/index-0Hq9436Q.js +119 -0
  41. package/dist/assets/index-LIwREYgH.css +1 -0
  42. package/dist/assets/infoDiagram-FWYZ7A6U-D-a34TqE.js +2 -0
  43. package/dist/assets/{ishikawaDiagram-UXIWVN3A-CK2IFFAP.js → ishikawaDiagram-FXEZZL3T-h0bUvXGV.js} +5 -5
  44. package/dist/assets/{journeyDiagram-VCZTEJTY-DAH5Stkf.js → journeyDiagram-5HDEW3XC-B4lm0RBa.js} +1 -1
  45. package/dist/assets/{kanban-definition-6JOO6SKY-Bi0aCqkv.js → kanban-definition-HUTT4EX6-msvfouRX.js} +7 -7
  46. package/dist/assets/katex-C5jXJg4s.js +257 -0
  47. package/dist/assets/layout-DEXfKzaS.js +1 -0
  48. package/dist/assets/{linear-ClGEGlS2.js → linear-B0trv2xN.js} +1 -1
  49. package/dist/assets/map-Czzmt4hB.js +1 -0
  50. package/dist/assets/{mindmap-definition-QFDTVHPH-Wjr-PgC6.js → mindmap-definition-LN4V7U3C-CvswbRgd.js} +7 -7
  51. package/dist/assets/{mobile-Chw8RWyH.js → mobile-CNLMhdFP.js} +2 -2
  52. package/dist/assets/pegDiagram-2B236MQR-DZEmIYC7.js +1 -0
  53. package/dist/assets/pieDiagram-ENE6RG2P-1ZfqxHkL.js +39 -0
  54. package/dist/assets/quadrantDiagram-ABIIQ3AL-Owx49uiJ.js +7 -0
  55. package/dist/assets/railroadDiagram-RFXS5EU6-B7RiaIGf.js +1 -0
  56. package/dist/assets/{requirementDiagram-MS252O5E-CU9Rlyq5.js → requirementDiagram-TGXJPOKE-fqmHs_5o.js} +3 -3
  57. package/dist/assets/sankeyDiagram-HTMAVEWB-D0Q3FeKV.js +40 -0
  58. package/dist/assets/sequenceDiagram-DBY2YBRQ-CnPuB3fy.js +162 -0
  59. package/dist/assets/sizeCapture-X5ZJPWSS-CsHoR37V.js +1 -0
  60. package/dist/assets/stateDiagram-2N3HPSRC-GVwpflU5.js +1 -0
  61. package/dist/assets/stateDiagram-v2-6OUMAXLB-DCGIHFu9.js +1 -0
  62. package/dist/assets/swimlanes-5IMT3BWC-DRR41dsQ.js +2 -0
  63. package/dist/assets/swimlanesDiagram-G3AALYLV-C4RhLkZo.js +8 -0
  64. package/dist/assets/{timeline-definition-GMOUNBTQ-C8SZmaLp.js → timeline-definition-FHXFAJF6-ePaEemL7.js} +3 -3
  65. package/dist/assets/vennDiagram-L72KCM5P-BAYP8dNF.js +34 -0
  66. package/dist/assets/wardleyDiagram-EHGQE667-jNVH5Adm.js +78 -0
  67. package/dist/assets/xychartDiagram-FW5EYKEG-BYDWGXbZ.js +7 -0
  68. package/dist/index.html +3 -3
  69. package/lib/agent-string.js +34 -0
  70. package/lib/build.js +542 -148
  71. package/lib/experiment-judge.js +145 -0
  72. package/lib/experiment-metrics.js +265 -0
  73. package/lib/experiment-pricing.js +60 -0
  74. package/lib/experiment-report.js +306 -0
  75. package/lib/experiment-sandbox.js +147 -0
  76. package/lib/experiment.js +539 -0
  77. package/lib/feature-events.js +10 -0
  78. package/lib/feature-json.js +1 -1
  79. package/lib/feature-writer.js +30 -0
  80. package/lib/flow-state.js +36 -0
  81. package/lib/gate-prompt.js +66 -6
  82. package/lib/lifecycle-modes.js +213 -0
  83. package/lib/new.js +6 -2
  84. package/lib/roadmap-graph/index.js +26 -28
  85. package/lib/roadmap-graph/vision-adapter.js +126 -0
  86. package/lib/smartmemory-client.js +139 -0
  87. package/lib/smartmemory-config.js +50 -0
  88. package/lib/smartmemory-ingest.js +115 -0
  89. package/lib/smartmemory-sync.js +275 -0
  90. package/lib/stratum-mcp-client.js +9 -0
  91. package/lib/triage.js +7 -1
  92. package/lib/vision-writer.js +30 -6
  93. package/package.json +1 -1
  94. package/pipelines/plan.stratum.yaml +161 -0
  95. package/server/artifact-manager.js +30 -3
  96. package/server/compose-mcp-tools.js +11 -9
  97. package/server/compose-mcp.js +4 -0
  98. package/server/feature-scan.js +87 -173
  99. package/server/gate-log-store.js +11 -1
  100. package/server/graph-export.js +0 -0
  101. package/server/index.js +3 -5
  102. package/server/lifecycle-guard.js +62 -25
  103. package/server/roadmap-graph-vision.js +138 -0
  104. package/server/smartmemory-routes.js +113 -0
  105. package/server/status-snapshot.js +8 -7
  106. package/server/vision-routes.js +64 -33
  107. package/server/vision-server.js +6 -0
  108. package/server/vision-store.js +13 -4
  109. package/.claude/skills/compose/references/hermes-tools.md +0 -80
  110. package/dist/assets/App-Ddhfx18X.js +0 -889
  111. package/dist/assets/_baseUniq-CoEVVYyC.js +0 -1
  112. package/dist/assets/arc-DFnGxuCB.js +0 -1
  113. package/dist/assets/architectureDiagram-Q4EWVU46-66rx01Tn.js +0 -36
  114. package/dist/assets/blockDiagram-DXYQGD6D-AE5PKSak.js +0 -132
  115. package/dist/assets/channel-CScyWprq.js +0 -1
  116. package/dist/assets/chunk-4TB4RGXK-5w2lZAjW.js +0 -206
  117. package/dist/assets/chunk-OYMX7WX6-CGr_YiAc.js +0 -231
  118. package/dist/assets/classDiagram-6PBFFD2Q-DibVYYdr.js +0 -1
  119. package/dist/assets/classDiagram-v2-HSJHXN6E-DibVYYdr.js +0 -1
  120. package/dist/assets/clone-ZMALIFmw.js +0 -1
  121. package/dist/assets/dagre-KV5264BT--mrfaTHR.js +0 -4
  122. package/dist/assets/diagram-5BDNPKRD-DjXvyFzh.js +0 -10
  123. package/dist/assets/diagram-G4DWMVQ6-BrrexEvm.js +0 -24
  124. package/dist/assets/diagram-MMDJMWI5-D3Q0OT2M.js +0 -43
  125. package/dist/assets/diagram-TYMM5635-DKTntmxE.js +0 -24
  126. package/dist/assets/erDiagram-SMLLAGMA-BWbR0dPj.js +0 -85
  127. package/dist/assets/flowDiagram-DWJPFMVM-Dj5M5XaY.js +0 -162
  128. package/dist/assets/ganttDiagram-T4ZO3ILL-DKppFh_5.js +0 -292
  129. package/dist/assets/gitGraphDiagram-UUTBAWPF-9xQ9O25V.js +0 -106
  130. package/dist/assets/graph-Cgmhvu1T.js +0 -1
  131. package/dist/assets/graph-DZe55uk8.js +0 -331
  132. package/dist/assets/index-BxRamj_i.css +0 -1
  133. package/dist/assets/index-D8uDfn8y.js +0 -123
  134. package/dist/assets/infoDiagram-42DDH7IO-DQYWVzVN.js +0 -2
  135. package/dist/assets/katex-DkKDou_j.js +0 -257
  136. package/dist/assets/layout-DN13RkA1.js +0 -1
  137. package/dist/assets/min-CXr-fUFS.js +0 -1
  138. package/dist/assets/pieDiagram-DEJITSTG-CVipbIlr.js +0 -30
  139. package/dist/assets/quadrantDiagram-34T5L4WZ-CHJFyhm-.js +0 -7
  140. package/dist/assets/sankeyDiagram-XADWPNL6-BSULeqST.js +0 -10
  141. package/dist/assets/sequenceDiagram-FGHM5R23-CCOKXvjj.js +0 -157
  142. package/dist/assets/stateDiagram-FHFEXIEX-B91gKA7Q.js +0 -1
  143. package/dist/assets/stateDiagram-v2-QKLJ7IA2-Dc8ur9ta.js +0 -1
  144. package/dist/assets/vennDiagram-DHZGUBPP-YvEPDlsM.js +0 -34
  145. package/dist/assets/wardley-RL74JXVD-TMBUq_w1.js +0 -162
  146. package/dist/assets/wardleyDiagram-NUSXRM2D-DzEkPrri.js +0 -20
  147. package/dist/assets/xychartDiagram-5P7HB3ND-BIiCeiYk.js +0 -7
  148. package/lib/roadmap-graph/collect.js +0 -178
@@ -14,6 +14,7 @@ import crypto from 'node:crypto';
14
14
  import { resolvePort } from './resolve-port.js';
15
15
  import { probeServer } from './server-probe.js';
16
16
  import { migrateVisionItemFeatureCode, normalizeGateOutcome } from './state-migrations.js';
17
+ import { resolveMode } from './lifecycle-modes.js';
17
18
 
18
19
  const EMPTY_STATE = () => ({ items: [], connections: [], gates: [] });
19
20
 
@@ -171,9 +172,12 @@ export class VisionWriter {
171
172
  // featureCode) but no lifecycle — start lifecycle so the next CLI run
172
173
  // finds it by lifecycle.featureCode (#31).
173
174
  if (!existing.lifecycle?.featureCode) {
175
+ // COMP-ROADMAP-PLAN: forward `mode` so a repaired (e.g. UI-created) plan
176
+ // item starts as plan, not build. store.createItem drops unknown fields,
177
+ // so mode must travel on the start body (not item-create).
174
178
  await this._fetch(`/api/vision/items/${existing.id}/lifecycle/start`, {
175
179
  method: 'POST',
176
- body: JSON.stringify({ featureCode }),
180
+ body: JSON.stringify({ featureCode, mode }),
177
181
  });
178
182
  }
179
183
  return existing.id;
@@ -188,9 +192,12 @@ export class VisionWriter {
188
192
  phase: 'planning',
189
193
  }),
190
194
  });
195
+ // COMP-ROADMAP-PLAN: forward `mode` on the post-create start so the lifecycle
196
+ // is stamped with the right mode (the item-create body can't carry it —
197
+ // store.createItem has no `mode` field).
191
198
  await this._fetch(`/api/vision/items/${item.id}/lifecycle/start`, {
192
199
  method: 'POST',
193
- body: JSON.stringify({ featureCode }),
200
+ body: JSON.stringify({ featureCode, mode }),
194
201
  });
195
202
  return item.id;
196
203
  }
@@ -261,7 +268,16 @@ export class VisionWriter {
261
268
  // lifecycle yet — seed lifecycle.featureCode so the next CLI run binds by
262
269
  // the canonical key (#31).
263
270
  if (!existing.lifecycle?.featureCode) {
264
- existing.lifecycle = { ...(existing.lifecycle || {}), featureCode, currentPhase: existing.lifecycle?.currentPhase || 'explore_design' };
271
+ existing.lifecycle = {
272
+ ...(existing.lifecycle || {}),
273
+ featureCode,
274
+ // currentPhase stays 'explore_design' (byte-identical to pre-MODES for
275
+ // build AND bug; it is transient — the runner overwrites it with the
276
+ // first stepId). Per-mode genesis-at-creation is deferred with the
277
+ // fix/plan UI phase wiring.
278
+ currentPhase: existing.lifecycle?.currentPhase || 'explore_design',
279
+ mode: existing.lifecycle?.mode || resolveMode(mode),
280
+ };
265
281
  this._atomicWrite(state);
266
282
  }
267
283
  return existing.id;
@@ -278,7 +294,10 @@ export class VisionWriter {
278
294
  status: 'planned',
279
295
  phase: 'planning',
280
296
  group,
281
- lifecycle: { featureCode, currentPhase: 'explore_design' },
297
+ // currentPhase stays 'explore_design' (byte-identical for build AND bug;
298
+ // transient — overwritten by the runner's first stepId). mode is the new
299
+ // additive field; per-mode genesis-at-creation is a deferred follow-up.
300
+ lifecycle: { featureCode, currentPhase: 'explore_design', mode: resolveMode(mode) },
282
301
  slug: featureCode.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
283
302
  confidence: null,
284
303
  createdAt: new Date().toISOString(),
@@ -327,10 +346,15 @@ export class VisionWriter {
327
346
  status: 'pending',
328
347
  createdAt: new Date().toISOString(),
329
348
  };
330
- // Dedupe by full gate ID or by pending itemId+stepId to prevent duplicates
349
+ // Dedupe by full gate ID, or by a pending gate for the same item+step
350
+ // WITHIN THE SAME FLOW. The flowId guard is essential: without it, a fresh
351
+ // build (new flowId) would reuse a stale pending gate left behind by a prior
352
+ // crashed/aborted run for the same feature — that gate is never resolved, so
353
+ // the new build polls it until the 30-min server TTL expires it and then
354
+ // errors. Scoping to flowId mints a fresh per-run gate instead.
331
355
  const existingIdx = state.gates.findIndex(g =>
332
356
  g.id === gate.id ||
333
- (g.itemId === gate.itemId && g.stepId === gate.stepId && g.status === 'pending')
357
+ (g.flowId === gate.flowId && g.itemId === gate.itemId && g.stepId === gate.stepId && g.status === 'pending')
334
358
  );
335
359
  if (existingIdx !== -1) {
336
360
  return state.gates[existingIdx].id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartmemory/compose",
3
- "version": "0.2.52-beta",
3
+ "version": "0.2.54-beta",
4
4
  "description": "Structured AI dev pipeline — goal-to-product orchestration with gates, iteration loops, and feature lifecycle management.",
5
5
  "author": "SmartMemory",
6
6
  "license": "MIT",
@@ -0,0 +1,161 @@
1
+ version: "0.3"
2
+
3
+ # COMP-ROADMAP-PLAN — the `plan` product-planning lifecycle (peer to build/fix).
4
+ # Front half of goal-to-product: frame → research → ideate → converge → estimate
5
+ # → build-handshake. Top-level step IDs MUST equal the plan-mode phaseOrder
6
+ # (explore_design / plan / ship) because phase tracking keys off the raw step_id
7
+ # (lib/build.js). Flow input is {projectName, intent} to match the plan-mode
8
+ # planInputs envelope (lib/lifecycle-modes.js plan.runner.planInputs == 'plan').
9
+ # Absorbs `compose new`: ideation routes to the ideabox, output is canonical
10
+ # feature.json (not hand-authored ROADMAP rows or design.md-only seeds).
11
+
12
+ workflow:
13
+ name: plan
14
+ description: "Product-planning lifecycle — frame, research, ideate, converge to build-ready feature specs"
15
+ input:
16
+ projectName:
17
+ type: string
18
+ required: true
19
+ intent:
20
+ type: string
21
+ required: true
22
+
23
+ contracts:
24
+ PlanDesignResult:
25
+ artifact: {type: string}
26
+ features: {type: array}
27
+ summary: {type: string}
28
+
29
+ PlanConvergeResult:
30
+ artifact: {type: string}
31
+ created: {type: array}
32
+ summary: {type: string}
33
+
34
+ PlanHandoffResult:
35
+ handed_off: {type: array}
36
+ summary: {type: string}
37
+
38
+ functions:
39
+ plan_design_gate:
40
+ mode: gate
41
+ timeout: 7200
42
+
43
+ plan_converge_gate:
44
+ mode: gate
45
+ timeout: 7200
46
+
47
+ flows:
48
+ plan:
49
+ input:
50
+ projectName: {type: string}
51
+ intent: {type: string}
52
+ output: PlanHandoffResult
53
+ steps:
54
+ # Phase: explore_design — frame, research, ideate
55
+ - id: explore_design
56
+ agent: claude
57
+ intent: >
58
+ You are running the PRODUCT-PLANNING front half for this intent: {intent}
59
+
60
+ Do three things, then write the result to docs/plans/{projectName}/design.md:
61
+ 1. FRAME — restate the goal, the problem it solves, who it is for, and the
62
+ scope boundaries (what is in v1, what is explicitly out).
63
+ 2. RESEARCH — search the web and the codebase for prior art, existing
64
+ tools, architectural patterns, and risks. Summarize what is relevant.
65
+ 3. IDEATE — propose candidate features (each with a short code and a
66
+ one-line description, ordered by dependency). Capture each idea in the
67
+ ideabox funnel (append to docs/product/ideabox.md, or run
68
+ `compose ideabox add`) so ideation lives in the funnel, not here. Then
69
+ shortlist the features you recommend building first.
70
+
71
+ Write docs/plans/{projectName}/design.md containing FRAME, RESEARCH, and
72
+ the shortlist (with feature codes). Return its path in "artifact" and the
73
+ shortlisted feature codes in "features".
74
+ inputs:
75
+ projectName: "$.input.projectName"
76
+ intent: "$.input.intent"
77
+ output_contract: PlanDesignResult
78
+ ensure:
79
+ - "file_exists(result.artifact)"
80
+ - "len(result.features) > 0"
81
+ retries: 2
82
+
83
+ # Gate: human reviews the framing + research + shortlist
84
+ - id: plan_design_gate
85
+ function: plan_design_gate
86
+ on_approve: plan
87
+ on_revise: explore_design
88
+ on_kill: null
89
+ depends_on: [explore_design]
90
+
91
+ # Phase: plan — converge, estimate, write build-ready specs
92
+ - id: plan
93
+ agent: claude
94
+ intent: >
95
+ Converge the shortlist in docs/plans/{projectName}/design.md into
96
+ build-ready feature specs. For EACH feature you commit to building:
97
+ 1. ESTIMATE — assign a complexity (S | M | L | XL) and a triage build
98
+ profile (which lifecycle phases it needs, e.g.
99
+ {{"needs_prd": false, "needs_architecture": false}}).
100
+ 2. SPEC — first write the per-feature design to <CODE>/design.md inside the
101
+ project features dir (docs/features/<CODE>/design.md by default, or the
102
+ paths.features root in .compose/compose.json). This is a real
103
+ product/feature design the build lifecycle will ratify. THEN create the
104
+ canonical feature.json by RUNNING this command from the project root
105
+ (it writes feature.json via the canonical writer, regenerates ROADMAP.md,
106
+ and auto-stamps triageTimestamp — works from any agent runtime, no MCP
107
+ needed):
108
+
109
+ compose roadmap add <CODE> --description "<one-line>" --phase "<phase>" \
110
+ --status PLANNED --complexity <S|M|L|XL> \
111
+ --profile '{"needs_prd":<bool>,"needs_architecture":<bool>,"needs_verification":<bool>,"needs_report":<bool>}' \
112
+ --planned-by "{projectName}" [--impact <low|medium|high>]
113
+
114
+ Write design.md BEFORE running it (the command verifies design.md exists
115
+ and refuses otherwise). Do NOT hand-author ROADMAP rows or write
116
+ feature.json by hand.
117
+
118
+ Then write the planning narrative (decisions, estimates, sequencing) to
119
+ docs/plans/{projectName}/plan.md. Return plan.md in "artifact" and the
120
+ created feature codes in "created".
121
+ inputs:
122
+ projectName: "$.input.projectName"
123
+ intent: "$.input.intent"
124
+ output_contract: PlanConvergeResult
125
+ ensure:
126
+ - "file_exists(result.artifact)"
127
+ - "len(result.created) > 0"
128
+ retries: 2
129
+ depends_on: [plan_design_gate]
130
+
131
+ # Gate: human approves the converged plan + build-ready specs
132
+ - id: plan_converge_gate
133
+ function: plan_converge_gate
134
+ on_approve: ship
135
+ on_revise: plan
136
+ on_kill: null
137
+ depends_on: [plan]
138
+
139
+ # Phase: ship — handoff / verify build-readiness (plan mode does NOT git-commit
140
+ # here; runBuild's ship interception is gated off for mode=plan).
141
+ - id: ship
142
+ agent: claude
143
+ intent: >
144
+ Finalize the plan→build handoff. For EACH feature code in the provided
145
+ "created" list (the features the plan phase produced), verify its folder
146
+ under the project features dir (docs/features/<CODE>/ by default, or the
147
+ paths.features root in .compose/compose.json) has a feature.json (status
148
+ PLANNED, with plannedBy "{projectName}", complexity, profile,
149
+ triageTimestamp) and a design.md. Produce a short handoff summary listing
150
+ each build-ready feature and the exact `compose build <CODE>` command to
151
+ start it. Do NOT git-commit here — just verify and summarize. Return the
152
+ verified feature codes in "handed_off".
153
+ inputs:
154
+ projectName: "$.input.projectName"
155
+ intent: "$.input.intent"
156
+ created: "$.steps.plan.output.created"
157
+ output_contract: PlanHandoffResult
158
+ ensure:
159
+ - "len(result.handed_off) > 0"
160
+ retries: 1
161
+ depends_on: [plan_converge_gate]
@@ -9,6 +9,7 @@
9
9
  import fs from 'node:fs';
10
10
  import path from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
+ import { resolveMode, artifactsOf } from '../lib/lifecycle-modes.js';
12
13
  /** Phases that produce named artifacts (derived from contracts/lifecycle.json). */
13
14
  const PHASE_ARTIFACTS = {
14
15
  explore_design: 'design.md',
@@ -139,20 +140,46 @@ function _matchSections(foundHeadings, schema) {
139
140
  return { found, missing, optional };
140
141
  }
141
142
 
143
+ // ---------------------------------------------------------------------------
144
+ // Mode-scoped artifact key set (shared by ArtifactManager + the MCP tools so
145
+ // the populated and empty-root paths can never diverge). A mode that declares a
146
+ // non-empty phaseArtifacts is scoped to that subset (global key order preserved);
147
+ // a mode that declares none falls back to ALL keys (today's build/bug behavior).
148
+ // ---------------------------------------------------------------------------
149
+
150
+ export function artifactKeysForMode(mode = 'build') {
151
+ const all = Object.keys(ARTIFACT_SCHEMAS);
152
+ const wanted = artifactsOf(resolveMode(mode));
153
+ return wanted && wanted.length ? all.filter((k) => wanted.includes(k)) : all;
154
+ }
155
+
142
156
  // ---------------------------------------------------------------------------
143
157
  // ArtifactManager
144
158
  // ---------------------------------------------------------------------------
145
159
 
146
160
  export class ArtifactManager {
147
161
  #featureRoot;
162
+ #mode;
148
163
 
149
- constructor(featureRoot) {
164
+ constructor(featureRoot, mode = 'build') {
150
165
  this.#featureRoot = featureRoot;
166
+ this.#mode = resolveMode(mode);
167
+ }
168
+
169
+ /**
170
+ * The artifact filenames this mode assesses/scaffolds. A mode that declares a
171
+ * non-empty `phaseArtifacts` is scoped to that subset (intersected with the
172
+ * global schema map, preserving global key order); a mode that declares NONE
173
+ * (build's full set is explicit; fix declares []) falls back to ALL global
174
+ * keys — which is exactly today's behavior for build and bug items.
175
+ */
176
+ #artifactKeys() {
177
+ return artifactKeysForMode(this.#mode);
151
178
  }
152
179
 
153
180
  assess(featureCode) {
154
181
  const artifacts = {};
155
- for (const filename of Object.keys(ARTIFACT_SCHEMAS)) {
182
+ for (const filename of this.#artifactKeys()) {
156
183
  artifacts[filename] = this.assessOne(featureCode, filename);
157
184
  }
158
185
  return { artifacts };
@@ -214,7 +241,7 @@ export class ArtifactManager {
214
241
  fs.mkdirSync(dir, { recursive: true });
215
242
  fs.mkdirSync(path.join(dir, 'sessions'), { recursive: true });
216
243
 
217
- for (const filename of Object.keys(ARTIFACT_SCHEMAS)) {
244
+ for (const filename of this.#artifactKeys()) {
218
245
  if (options?.only && !options.only.includes(filename)) continue;
219
246
 
220
247
  const filePath = path.join(dir, filename);
@@ -8,7 +8,7 @@
8
8
  import fs from 'node:fs';
9
9
  import http from 'node:http';
10
10
  import path from 'node:path';
11
- import { ArtifactManager, ARTIFACT_SCHEMAS } from './artifact-manager.js';
11
+ import { ArtifactManager, artifactKeysForMode } from './artifact-manager.js';
12
12
  import { getTargetRoot, getDataDir, resolveProjectPath, switchProject, setCurrentWorkspaceId, loadProjectConfig, isLifecycleEnabled } from './project-root.js';
13
13
  import { resolveProfile, isToolAllowed } from './mcp-tool-policy.js';
14
14
  import { getRoadmap } from '../lib/get-roadmap.js';
@@ -549,7 +549,7 @@ export async function toolValidateProject(args = {}) {
549
549
  // Returns small summaries only (counts + warning/dangling lists) — never the
550
550
  // HTML body, which would blow the MCP response token cap.
551
551
  export async function toolRoadmapGraph({ project, out } = {}) {
552
- const { generateRoadmapGraph } = await import('../lib/roadmap-graph/index.js');
552
+ const { generateRoadmapGraph } = await import('./roadmap-graph-vision.js');
553
553
  const cwd = project || getTargetRoot();
554
554
  try {
555
555
  const r = generateRoadmapGraph(cwd, { out });
@@ -582,7 +582,7 @@ export async function toolRoadmapXrefPush({ project, apply } = {}) {
582
582
  }
583
583
 
584
584
  export async function toolRoadmapGraphCheck({ project, out } = {}) {
585
- const { checkRoadmapGraph } = await import('../lib/roadmap-graph/index.js');
585
+ const { checkRoadmapGraph } = await import('./roadmap-graph-vision.js');
586
586
  const cwd = project || getTargetRoot();
587
587
  try {
588
588
  const r = checkRoadmapGraph(cwd, { out });
@@ -741,23 +741,25 @@ export async function toolIterationAbort({ id, reason }) {
741
741
  // Artifact tools — read/write directly (no REST delegation needed)
742
742
  // ---------------------------------------------------------------------------
743
743
 
744
- export function toolAssessFeatureArtifacts({ featureCode }) {
744
+ export function toolAssessFeatureArtifacts({ featureCode, mode = 'build' }) {
745
745
  const featureRoot = resolveProjectPath('features');
746
746
  if (!fs.existsSync(featureRoot)) {
747
- // Return empty assessments — feature root hasn't been created yet
747
+ // Return empty assessments — feature root hasn't been created yet. Iterate
748
+ // the SAME mode-scoped key set the populated path would, so the two paths
749
+ // never return a different artifact key set for the same mode.
748
750
  const empty = {};
749
- for (const filename of Object.keys(ARTIFACT_SCHEMAS)) {
751
+ for (const filename of artifactKeysForMode(mode)) {
750
752
  empty[filename] = { exists: false, wordCount: 0, meetsMinWordCount: false, sections: { found: [], missing: [], optional: [] }, completeness: 0, lastModified: null };
751
753
  }
752
754
  return { artifacts: empty };
753
755
  }
754
- const manager = new ArtifactManager(featureRoot);
756
+ const manager = new ArtifactManager(featureRoot, mode);
755
757
  return manager.assess(featureCode);
756
758
  }
757
759
 
758
- export function toolScaffoldFeature({ featureCode, only }) {
760
+ export function toolScaffoldFeature({ featureCode, only, mode = 'build' }) {
759
761
  const featureRoot = resolveProjectPath('features');
760
- const manager = new ArtifactManager(featureRoot);
762
+ const manager = new ArtifactManager(featureRoot, mode);
761
763
  return manager.scaffold(featureCode, only ? { only } : undefined);
762
764
  }
763
765
 
@@ -329,6 +329,10 @@ const TOOLS = [
329
329
  position: { type: 'number', description: 'Sort order within phase' },
330
330
  parent: { type: 'string', description: 'Parent feature code, for cross-references' },
331
331
  tags: { type: 'array', items: { type: 'string' } },
332
+ profile: { type: 'object', description: 'Triage build profile (needs_prd/needs_architecture/etc.). Set by the plan lifecycle so build can skip fresh triage.' },
333
+ triageTimestamp: { type: 'string', description: 'Triage cache stamp (ISO 8601). Set AFTER design.md is written so build treats triage as a cached no-op.' },
334
+ plannedBy: { type: 'string', description: 'Originating plan session code (e.g. "PLAN-WIDGET"). Marks a plan-authored, build-ready feature.' },
335
+ impact: { type: 'string', enum: ['low', 'medium', 'high'], description: 'Estimated impact, carried from the plan/ideabox estimation step.' },
332
336
  force: { type: 'boolean', description: 'Bypass the pre-commit roundtrip guard (commit even if ROADMAP.md would not be a generation fixed point).' },
333
337
  idempotency_key: { type: 'string', description: 'Optional caller-provided key. Same key replays return the cached result without re-mutating.' },
334
338
  },