@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.
- package/.claude/skills/compose/SKILL.md +0 -8
- package/README.md +39 -0
- package/bin/compose.js +378 -3
- package/dist/assets/App-D_tvTs_n.js +896 -0
- package/dist/assets/abnfDiagram-VRR7QNED-Dhb7O5rp.js +1 -0
- package/dist/assets/arc-C4oVdnk2.js +1 -0
- package/dist/assets/architectureDiagram-ZJ3FMSHR-DqHrb5_F.js +36 -0
- package/dist/assets/blockDiagram-677ZJIJ3-DUyBayYK.js +132 -0
- package/dist/assets/{browser-CnKiSnlr.js → browser-Cr0recrN.js} +6 -6
- package/dist/assets/{c4Diagram-AHTNJAMY-DtDSNlOX.js → c4Diagram-LMCZKHZV-CxlkWMRK.js} +1 -1
- package/dist/assets/channel-xb4mQfuL.js +1 -0
- package/dist/assets/{chunk-QZHKN3VN-BMWFLZ8t.js → chunk-2Q5K7J3B-C6QJsyOQ.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-CqjNXooj.js → chunk-32BRIVSS-D9NgYu9W.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BAa7Edp6.js → chunk-5VM5RSS4-DbD86rhK.js} +1 -1
- package/dist/assets/chunk-EX3LRPZG-E4m4xNMw.js +231 -0
- package/dist/assets/{chunk-4BX2VUAB-oXU2npoL.js → chunk-JWPE2WC7-DHGlRWbw.js} +1 -1
- package/dist/assets/chunk-MOJQB5TN-BblYnFUq.js +88 -0
- package/dist/assets/chunk-RYQCIY6F-CGT-88vL.js +1 -0
- package/dist/assets/chunk-V7JOEXUC-BJJmX0mc.js +206 -0
- package/dist/assets/{chunk-EDXVE4YY-Bse2kdRh.js → chunk-VR4S4FIN-B4ygduCD.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-BEEk2FdH.js → chunk-XXDRQBXY-7APXuI2s.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BIvgPTgh.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BIvgPTgh.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-NYnH3mVW.js → cose-bilkent-JH36ORCC-DnVcT6R6.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-C9qiUSfX.js +178 -0
- package/dist/assets/cynefinDiagram-TSTJHNR4-BowkQhEu.js +62 -0
- package/dist/assets/dagre-VKFMJZFB-Y5V-7U3p.js +4 -0
- package/dist/assets/diagram-FQU43EPY-I7wDE-Xp.js +3 -0
- package/dist/assets/diagram-G47NLZAW-DapGa1HI.js +24 -0
- package/dist/assets/diagram-NH7WQ7WH-CHO1JntO.js +24 -0
- package/dist/assets/diagram-OA4YK3LP-mBWP2D1G.js +30 -0
- package/dist/assets/diagram-WEI45ONY-C8KN8YnF.js +41 -0
- package/dist/assets/ebnfDiagram-CCIWWBDH-ZkJG9NOL.js +1 -0
- package/dist/assets/erDiagram-Q63AITRT-DGnL3kW1.js +85 -0
- package/dist/assets/flowDiagram-23GEKE2U-DHYupfOu.js +156 -0
- package/dist/assets/ganttDiagram-NO4QXBWP-B1B2zpwR.js +292 -0
- package/dist/assets/gitGraphDiagram-IHSO6WYX-B9_OP4Fs.js +106 -0
- package/dist/assets/graph-C9eacEi8.js +1 -0
- package/dist/assets/graph-xkel59g2.js +331 -0
- package/dist/assets/index-0Hq9436Q.js +119 -0
- package/dist/assets/index-LIwREYgH.css +1 -0
- package/dist/assets/infoDiagram-FWYZ7A6U-D-a34TqE.js +2 -0
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-CK2IFFAP.js → ishikawaDiagram-FXEZZL3T-h0bUvXGV.js} +5 -5
- package/dist/assets/{journeyDiagram-VCZTEJTY-DAH5Stkf.js → journeyDiagram-5HDEW3XC-B4lm0RBa.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-Bi0aCqkv.js → kanban-definition-HUTT4EX6-msvfouRX.js} +7 -7
- package/dist/assets/katex-C5jXJg4s.js +257 -0
- package/dist/assets/layout-DEXfKzaS.js +1 -0
- package/dist/assets/{linear-ClGEGlS2.js → linear-B0trv2xN.js} +1 -1
- package/dist/assets/map-Czzmt4hB.js +1 -0
- package/dist/assets/{mindmap-definition-QFDTVHPH-Wjr-PgC6.js → mindmap-definition-LN4V7U3C-CvswbRgd.js} +7 -7
- package/dist/assets/{mobile-Chw8RWyH.js → mobile-CNLMhdFP.js} +2 -2
- package/dist/assets/pegDiagram-2B236MQR-DZEmIYC7.js +1 -0
- package/dist/assets/pieDiagram-ENE6RG2P-1ZfqxHkL.js +39 -0
- package/dist/assets/quadrantDiagram-ABIIQ3AL-Owx49uiJ.js +7 -0
- package/dist/assets/railroadDiagram-RFXS5EU6-B7RiaIGf.js +1 -0
- package/dist/assets/{requirementDiagram-MS252O5E-CU9Rlyq5.js → requirementDiagram-TGXJPOKE-fqmHs_5o.js} +3 -3
- package/dist/assets/sankeyDiagram-HTMAVEWB-D0Q3FeKV.js +40 -0
- package/dist/assets/sequenceDiagram-DBY2YBRQ-CnPuB3fy.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-CsHoR37V.js +1 -0
- package/dist/assets/stateDiagram-2N3HPSRC-GVwpflU5.js +1 -0
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DCGIHFu9.js +1 -0
- package/dist/assets/swimlanes-5IMT3BWC-DRR41dsQ.js +2 -0
- package/dist/assets/swimlanesDiagram-G3AALYLV-C4RhLkZo.js +8 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-C8SZmaLp.js → timeline-definition-FHXFAJF6-ePaEemL7.js} +3 -3
- package/dist/assets/vennDiagram-L72KCM5P-BAYP8dNF.js +34 -0
- package/dist/assets/wardleyDiagram-EHGQE667-jNVH5Adm.js +78 -0
- package/dist/assets/xychartDiagram-FW5EYKEG-BYDWGXbZ.js +7 -0
- package/dist/index.html +3 -3
- package/lib/agent-string.js +34 -0
- package/lib/build.js +542 -148
- package/lib/experiment-judge.js +145 -0
- package/lib/experiment-metrics.js +265 -0
- package/lib/experiment-pricing.js +60 -0
- package/lib/experiment-report.js +306 -0
- package/lib/experiment-sandbox.js +147 -0
- package/lib/experiment.js +539 -0
- package/lib/feature-events.js +10 -0
- package/lib/feature-json.js +1 -1
- package/lib/feature-writer.js +30 -0
- package/lib/flow-state.js +36 -0
- package/lib/gate-prompt.js +66 -6
- package/lib/lifecycle-modes.js +213 -0
- package/lib/new.js +6 -2
- package/lib/roadmap-graph/index.js +26 -28
- package/lib/roadmap-graph/vision-adapter.js +126 -0
- package/lib/smartmemory-client.js +139 -0
- package/lib/smartmemory-config.js +50 -0
- package/lib/smartmemory-ingest.js +115 -0
- package/lib/smartmemory-sync.js +275 -0
- package/lib/stratum-mcp-client.js +9 -0
- package/lib/triage.js +7 -1
- package/lib/vision-writer.js +30 -6
- package/package.json +1 -1
- package/pipelines/plan.stratum.yaml +161 -0
- package/server/artifact-manager.js +30 -3
- package/server/compose-mcp-tools.js +11 -9
- package/server/compose-mcp.js +4 -0
- package/server/feature-scan.js +87 -173
- package/server/gate-log-store.js +11 -1
- package/server/graph-export.js +0 -0
- package/server/index.js +3 -5
- package/server/lifecycle-guard.js +62 -25
- package/server/roadmap-graph-vision.js +138 -0
- package/server/smartmemory-routes.js +113 -0
- package/server/status-snapshot.js +8 -7
- package/server/vision-routes.js +64 -33
- package/server/vision-server.js +6 -0
- package/server/vision-store.js +13 -4
- package/.claude/skills/compose/references/hermes-tools.md +0 -80
- package/dist/assets/App-Ddhfx18X.js +0 -889
- package/dist/assets/_baseUniq-CoEVVYyC.js +0 -1
- package/dist/assets/arc-DFnGxuCB.js +0 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-66rx01Tn.js +0 -36
- package/dist/assets/blockDiagram-DXYQGD6D-AE5PKSak.js +0 -132
- package/dist/assets/channel-CScyWprq.js +0 -1
- package/dist/assets/chunk-4TB4RGXK-5w2lZAjW.js +0 -206
- package/dist/assets/chunk-OYMX7WX6-CGr_YiAc.js +0 -231
- package/dist/assets/classDiagram-6PBFFD2Q-DibVYYdr.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-DibVYYdr.js +0 -1
- package/dist/assets/clone-ZMALIFmw.js +0 -1
- package/dist/assets/dagre-KV5264BT--mrfaTHR.js +0 -4
- package/dist/assets/diagram-5BDNPKRD-DjXvyFzh.js +0 -10
- package/dist/assets/diagram-G4DWMVQ6-BrrexEvm.js +0 -24
- package/dist/assets/diagram-MMDJMWI5-D3Q0OT2M.js +0 -43
- package/dist/assets/diagram-TYMM5635-DKTntmxE.js +0 -24
- package/dist/assets/erDiagram-SMLLAGMA-BWbR0dPj.js +0 -85
- package/dist/assets/flowDiagram-DWJPFMVM-Dj5M5XaY.js +0 -162
- package/dist/assets/ganttDiagram-T4ZO3ILL-DKppFh_5.js +0 -292
- package/dist/assets/gitGraphDiagram-UUTBAWPF-9xQ9O25V.js +0 -106
- package/dist/assets/graph-Cgmhvu1T.js +0 -1
- package/dist/assets/graph-DZe55uk8.js +0 -331
- package/dist/assets/index-BxRamj_i.css +0 -1
- package/dist/assets/index-D8uDfn8y.js +0 -123
- package/dist/assets/infoDiagram-42DDH7IO-DQYWVzVN.js +0 -2
- package/dist/assets/katex-DkKDou_j.js +0 -257
- package/dist/assets/layout-DN13RkA1.js +0 -1
- package/dist/assets/min-CXr-fUFS.js +0 -1
- package/dist/assets/pieDiagram-DEJITSTG-CVipbIlr.js +0 -30
- package/dist/assets/quadrantDiagram-34T5L4WZ-CHJFyhm-.js +0 -7
- package/dist/assets/sankeyDiagram-XADWPNL6-BSULeqST.js +0 -10
- package/dist/assets/sequenceDiagram-FGHM5R23-CCOKXvjj.js +0 -157
- package/dist/assets/stateDiagram-FHFEXIEX-B91gKA7Q.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-Dc8ur9ta.js +0 -1
- package/dist/assets/vennDiagram-DHZGUBPP-YvEPDlsM.js +0 -34
- package/dist/assets/wardley-RL74JXVD-TMBUq_w1.js +0 -162
- package/dist/assets/wardleyDiagram-NUSXRM2D-DzEkPrri.js +0 -20
- package/dist/assets/xychartDiagram-5P7HB3ND-BIiCeiYk.js +0 -7
- package/lib/roadmap-graph/collect.js +0 -178
package/lib/vision-writer.js
CHANGED
|
@@ -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 = {
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
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('
|
|
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('
|
|
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
|
|
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
|
|
package/server/compose-mcp.js
CHANGED
|
@@ -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
|
},
|