@smartmemory/compose 0.2.58-beta → 0.3.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.
- package/README.md +2 -2
- package/bin/compose.js +55 -54
- package/dist/assets/{App-s4CulNkO.js → App-DJ5xk_Wx.js} +218 -218
- package/dist/assets/{abnfDiagram-VRR7QNED-CDwzQ5wr.js → abnfDiagram-VRR7QNED-BPWGdCFx.js} +1 -1
- package/dist/assets/{arc-kIU8DDth.js → arc-DX5jqmcO.js} +1 -1
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-CleorO_0.js → architectureDiagram-ZJ3FMSHR-B6jmNVw7.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-NvEFfoMk.js → blockDiagram-677ZJIJ3-DAx2i-sB.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-BcDQdTVB.js → c4Diagram-LMCZKHZV-zmsZCplj.js} +1 -1
- package/dist/assets/channel-vsDnTvkh.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BseRkpm4.js → chunk-2Q5K7J3B-3QSh6sI7.js} +1 -1
- package/dist/assets/{chunk-32BRIVSS-DCHhvgFV.js → chunk-32BRIVSS-BiH5Di_y.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DcTpMdJ4.js → chunk-5VM5RSS4-C0EfjFLd.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-BdN8fvWs.js → chunk-EX3LRPZG-BGyfmVm-.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DmZJP-8E.js → chunk-JWPE2WC7-Ba0mrNlg.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-V74uYM17.js → chunk-MOJQB5TN-D3QO9EzH.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-CoXUXbn_.js → chunk-RYQCIY6F-D1uvNA6d.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DUscIydR.js → chunk-V7JOEXUC-fihlnodT.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-C4UDHq_a.js → chunk-VR4S4FIN-nmRUEo1H.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-DIH9i8mZ.js → chunk-XXDRQBXY-CM693yEg.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DO-wdSFZ.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-DO-wdSFZ.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-Dn6Gy6l1.js → cose-bilkent-JH36ORCC-CkacPn8W.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-C2LW5fzB.js → cynefin-VYW2F7L2-BiHaIptG.js} +1 -1
- package/dist/assets/{cynefinDiagram-TSTJHNR4-CGoAq7C9.js → cynefinDiagram-TSTJHNR4-CdKxMmiy.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-BaOKtuBE.js → dagre-VKFMJZFB-BQLY5y_W.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BdQwqt4x.js → diagram-FQU43EPY-D7uMBHvq.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-Dkcf0o5z.js → diagram-G47NLZAW-B3Z1cuH7.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-C_fDi59r.js → diagram-NH7WQ7WH-BZyRD45e.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-jaTQcOxt.js → diagram-OA4YK3LP-DcThOTt7.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-dEtqVj3s.js → diagram-WEI45ONY-BcgRAkqY.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-s8ImqsSs.js → ebnfDiagram-CCIWWBDH-CGwfO_xH.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-5Zy29-SA.js → erDiagram-Q63AITRT-pV58-Ncc.js} +1 -1
- package/dist/assets/{flowDiagram-23GEKE2U-DV74V9he.js → flowDiagram-23GEKE2U-DJ_SqE8h.js} +1 -1
- package/dist/assets/{ganttDiagram-NO4QXBWP-BND-CiLO.js → ganttDiagram-NO4QXBWP-Dgy0Iyss.js} +1 -1
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-B_-Mf9Jo.js → gitGraphDiagram-IHSO6WYX-CbiZw9fb.js} +1 -1
- package/dist/assets/{index-CBWbmG7W.js → index-DZTJEk-y.js} +2 -2
- package/dist/assets/{infoDiagram-FWYZ7A6U-Duyz8rZM.js → infoDiagram-FWYZ7A6U-CtsyyEc-.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-D2Qft0e5.js → ishikawaDiagram-FXEZZL3T-BJilNFkK.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-q6rG4Rig.js → journeyDiagram-5HDEW3XC-C2UCMP4t.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-CsPX7gBL.js → kanban-definition-HUTT4EX6-DmLDJBRy.js} +1 -1
- package/dist/assets/{linear-CiccpAAd.js → linear-C1paCqE7.js} +1 -1
- package/dist/assets/{mindmap-definition-LN4V7U3C-DRW2njpA.js → mindmap-definition-LN4V7U3C-CX-RxKVn.js} +1 -1
- package/dist/assets/{pegDiagram-2B236MQR-DoDgm2S_.js → pegDiagram-2B236MQR-CTU32H2W.js} +1 -1
- package/dist/assets/{pieDiagram-ENE6RG2P-CNSYLVAF.js → pieDiagram-ENE6RG2P-D8L1aYoo.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-8-Nd6ony.js → quadrantDiagram-ABIIQ3AL-hQ3bXosy.js} +1 -1
- package/dist/assets/{railroadDiagram-RFXS5EU6-bfdg6Bo4.js → railroadDiagram-RFXS5EU6--_vuYcda.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-slDkxCQy.js → requirementDiagram-TGXJPOKE-C6h_m3Az.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-CR3CboiK.js → sankeyDiagram-HTMAVEWB-Dpc7CfIQ.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BIXw1JS1.js → sequenceDiagram-DBY2YBRQ-CK5ZzbvJ.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-BdKI7JyF.js → sizeCapture-X5ZJPWSS-BAxVvM9G.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-ktVgpBZS.js → stateDiagram-2N3HPSRC-7j33_2PY.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-GviN0FpJ.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-CaXpC3FM.js → swimlanes-5IMT3BWC-CBG2yOob.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-D0wMoxl1.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-CKWSlRUl.js → timeline-definition-FHXFAJF6-BOnZkQvc.js} +1 -1
- package/dist/assets/{vennDiagram-L72KCM5P-CwOhxDyv.js → vennDiagram-L72KCM5P-D52NhIfj.js} +1 -1
- package/dist/assets/{wardleyDiagram-EHGQE667-DGavMpEB.js → wardleyDiagram-EHGQE667-YoPY2ZU-.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-CWmnmLZz.js → xychartDiagram-FW5EYKEG-BanNfgtO.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/build-all.js +0 -5
- package/lib/build-stream-schema.js +1 -1
- package/lib/build.js +1762 -2614
- package/lib/consumer-fanout.js +1317 -0
- package/lib/feature-validator.js +6 -4
- package/lib/flow-state.js +15 -14
- package/lib/gsd-budget.js +48 -10
- package/lib/gsd-prompt.js +3 -4
- package/lib/gsd-stuck.js +1 -1
- package/lib/gsd.js +303 -149
- package/lib/local-claude-connector.js +149 -0
- package/lib/new.js +162 -307
- package/lib/result-normalizer.js +233 -18
- package/lib/review-lenses.js +1 -1
- package/lib/step-prompt.js +41 -119
- package/lib/stratum-engine.js +297 -0
- package/lib/stratum-mcp-client.js +224 -213
- package/lib/vocabulary-compliance.js +268 -0
- package/lib/vocabulary-inject.js +1 -36
- package/package.json +2 -1
- package/pipelines/build-quick.stratum.yaml +5 -17
- package/pipelines/build.profiles.json +11 -0
- package/pipelines/build.stratum.yaml +288 -467
- package/pipelines/gsd.stratum.yaml +73 -125
- package/pipelines/new.stratum.yaml +68 -149
- package/server/build-routes.js +4 -1
- package/server/design-routes.js +37 -21
- package/server/index.js +13 -21
- package/server/lifecycle-guard.js +1 -1
- package/server/pipeline-routes.js +113 -31
- package/server/stratum-client.js +33 -47
- package/server/stratum-sync.js +3 -4
- package/server/vision-server.js +1 -1
- package/dist/assets/channel-DiJkE9og.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DS76VBEL.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-DS76VBEL.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CzPQnlan.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-rIDv6EuQ.js +0 -8
- package/lib/connector-factory-shim.js +0 -167
- package/server/agent-mcp.js +0 -10
|
@@ -1,149 +1,97 @@
|
|
|
1
1
|
# metadata:
|
|
2
2
|
# id: gsd
|
|
3
3
|
# label: "GSD — Per-Task Fresh-Context Dispatch"
|
|
4
|
-
# description: "
|
|
4
|
+
# description: "Decompose a blueprint and dispatch each task in a fresh worktree context"
|
|
5
5
|
# category: development
|
|
6
|
-
# steps:
|
|
6
|
+
# steps: 4
|
|
7
7
|
# estimated_minutes: 60
|
|
8
8
|
|
|
9
|
-
version:
|
|
10
|
-
|
|
11
|
-
workflow:
|
|
12
|
-
name: gsd
|
|
13
|
-
description: "Per-task fresh-context dispatch from existing blueprint+Boundary Map (COMP-GSD-2)"
|
|
14
|
-
input:
|
|
15
|
-
featureCode:
|
|
16
|
-
type: string
|
|
17
|
-
required: true
|
|
18
|
-
gateCommands:
|
|
19
|
-
type: array
|
|
20
|
-
required: true # injected by runGsd from loadProjectConfig() with default fallback
|
|
21
|
-
pre_merge_gate:
|
|
22
|
-
type: array
|
|
23
|
-
required: false # COMP-PAR-MERGE-QUEUE: fast per-task enforced gate (lint+build); injected by runGsd
|
|
9
|
+
version: 1
|
|
24
10
|
|
|
25
11
|
contracts:
|
|
26
12
|
PhaseResult:
|
|
27
|
-
phase:
|
|
28
|
-
artifact:
|
|
29
|
-
outcome:
|
|
30
|
-
summary:
|
|
13
|
+
phase: string
|
|
14
|
+
artifact: string
|
|
15
|
+
outcome: complete|skipped|failed
|
|
16
|
+
summary: string
|
|
17
|
+
files_changed: string[]?
|
|
18
|
+
commit_hash: string?
|
|
19
|
+
|
|
20
|
+
TaskGraph:
|
|
21
|
+
tasks: object[]
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
TaskResult:
|
|
24
|
+
outcome: string
|
|
25
|
+
summary: string
|
|
26
|
+
files_changed: string[]?
|
|
36
27
|
|
|
37
28
|
flows:
|
|
29
|
+
entry: gsd
|
|
38
30
|
gsd:
|
|
39
31
|
input:
|
|
40
|
-
featureCode:
|
|
41
|
-
gateCommands:
|
|
42
|
-
pre_merge_gate:
|
|
43
|
-
output:
|
|
32
|
+
featureCode: string
|
|
33
|
+
gateCommands: string[]
|
|
34
|
+
pre_merge_gate: string[]?
|
|
35
|
+
output:
|
|
36
|
+
from: "${ship_gsd.output}"
|
|
37
|
+
contract: PhaseResult
|
|
44
38
|
max_rounds: 10
|
|
45
39
|
steps:
|
|
46
|
-
# Phase: Decompose blueprint + Boundary Map into TaskGraphGsd
|
|
47
40
|
- id: decompose_gsd
|
|
48
|
-
type: decompose
|
|
49
41
|
agent: claude
|
|
50
|
-
|
|
51
|
-
Read docs/features
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- "Symbols you must produce:" + the matching Boundary Map slice's
|
|
60
|
-
produces entries (file → symbols (kind))
|
|
61
|
-
- "Symbols you may consume from upstream tasks:" + the slice's
|
|
62
|
-
consumes entries
|
|
63
|
-
- "Boundary Map slice (the contract for this task):" + the verbatim
|
|
64
|
-
slice block from blueprint.md
|
|
65
|
-
- "Upstream tasks (spec-level summary; their code lands at end-of-step
|
|
66
|
-
merge):" + one line per upstream task listing its declared produces
|
|
67
|
-
- "GATES — you MUST run each command and they MUST pass before
|
|
68
|
-
declaring done:" + each command from the pre_merge_gate array
|
|
69
|
-
- "Fix and re-run within this invocation. Do NOT declare done while
|
|
70
|
-
gates are red."
|
|
71
|
-
|
|
72
|
-
Stratum's parallel_dispatch only interpolates {task.id},
|
|
73
|
-
{task.description}, {task.files_owned}, {task.files_read},
|
|
74
|
-
{task.depends_on}, {task.index}, {input.<field>}. Pack everything else
|
|
75
|
-
into description.
|
|
76
|
-
|
|
77
|
-
Each Boundary Map slice maps to exactly one task whose files_owned ⊇
|
|
78
|
-
the slice's File Plan files. The decomposition is invalid if a slice
|
|
79
|
-
has no matching task or its File Plan files are split across
|
|
80
|
-
multiple tasks.
|
|
81
|
-
|
|
82
|
-
pre_merge_gate commands to embed (the fast per-task gate; the full
|
|
83
|
-
test suite runs once at ship): {input.pre_merge_gate}
|
|
84
|
-
inputs:
|
|
85
|
-
featureCode: "$.input.featureCode"
|
|
86
|
-
gateCommands: "$.input.gateCommands"
|
|
87
|
-
pre_merge_gate: "$.input.pre_merge_gate"
|
|
88
|
-
output_contract: TaskGraph
|
|
42
|
+
do: >
|
|
43
|
+
Read docs/features/${input.featureCode}/blueprint.md including its Boundary
|
|
44
|
+
Map. Decompose it into independent tasks. Each task must contain id,
|
|
45
|
+
description, files_owned, files_read, and depends_on. Put the matching
|
|
46
|
+
produces/consumes entries, verbatim Boundary Map slice, upstream summaries,
|
|
47
|
+
and required pre-merge commands ${input.pre_merge_gate} into description.
|
|
48
|
+
Each slice maps to exactly one task and its File Plan is owned by that task.
|
|
49
|
+
Reject file ownership conflicts and return {tasks}.
|
|
50
|
+
out: TaskGraph
|
|
89
51
|
ensure:
|
|
90
|
-
- "
|
|
91
|
-
|
|
92
|
-
retries: 2
|
|
52
|
+
- expr: "len(result.tasks) >= 1"
|
|
53
|
+
attempts: 2
|
|
93
54
|
|
|
94
|
-
#
|
|
55
|
+
# v0.3 parallel_dispatch -> TS v1 consumer fanout. Each item writes code,
|
|
56
|
+
# so Compose supplies an isolated worktree and retains its cumulative diff.
|
|
95
57
|
- id: execute
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
58
|
+
after: [decompose_gsd]
|
|
59
|
+
attempts: 2
|
|
60
|
+
fanout:
|
|
61
|
+
over: "${decompose_gsd.output.tasks}"
|
|
62
|
+
dispatch: consumer
|
|
63
|
+
concurrency: 1
|
|
64
|
+
isolation: worktree
|
|
65
|
+
require: all
|
|
66
|
+
merge: sequential
|
|
67
|
+
pre_merge: "$.input.pre_merge_gate"
|
|
68
|
+
steps:
|
|
69
|
+
- agent: claude
|
|
70
|
+
do: >
|
|
71
|
+
Implement the task described by ${item} using TDD. Honor its id,
|
|
72
|
+
files_owned, files_read, depends_on, and description fields. Write the
|
|
73
|
+
TaskResult JSON under .compose/gsd/${input.featureCode}/results/ using
|
|
74
|
+
the task id as its filename, and include it in your diff. Return
|
|
75
|
+
{outcome, summary, files_changed}.
|
|
76
|
+
out: TaskResult
|
|
77
|
+
|
|
78
|
+
- id: execute_merge
|
|
79
|
+
after: [execute]
|
|
80
|
+
gate:
|
|
81
|
+
on_approve: ship_gsd
|
|
82
|
+
on_revise: execute
|
|
83
|
+
on_kill: null
|
|
84
|
+
max_rounds: 10
|
|
115
85
|
|
|
116
|
-
When done, write TaskResult JSON to
|
|
117
|
-
.compose/gsd/{input.featureCode}/results/{task.id}.json
|
|
118
|
-
(schema: contracts/task-result.json) and INCLUDE that file in your diff.
|
|
119
|
-
depends_on: [decompose_gsd]
|
|
120
|
-
|
|
121
|
-
# Phase: Ship — verify, update docs, commit, push
|
|
122
86
|
- id: ship_gsd
|
|
123
|
-
agent:
|
|
124
|
-
|
|
125
|
-
Final verification and ship for
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
If this completes a parent feature, update the parent header.
|
|
132
|
-
4. Update CHANGELOG.md — add an entry under today's date with
|
|
133
|
-
COMP-GSD-2 and a bullet list of what was built. Read the
|
|
134
|
-
existing format first.
|
|
135
|
-
5. Update CLAUDE.md ONLY if a new convention/command/architecture
|
|
136
|
-
surface emerged. Skip otherwise.
|
|
137
|
-
6. Stage changed files explicitly (no worktrees, pycache, build
|
|
138
|
-
artifacts). Commit with message:
|
|
139
|
-
"feat(COMP-GSD-2): per-task fresh-context dispatch"
|
|
140
|
-
(Note: push is deferred to the user — `compose gsd` does not
|
|
141
|
-
auto-push, mirroring `compose build`'s ship behavior.)
|
|
142
|
-
7. Report commit hash and files_changed in result JSON.
|
|
143
|
-
inputs:
|
|
144
|
-
featureCode: "$.input.featureCode"
|
|
145
|
-
output_contract: PhaseResult
|
|
87
|
+
agent: claude
|
|
88
|
+
do: >
|
|
89
|
+
Final verification and ship for ${input.featureCode}. Run all gate commands
|
|
90
|
+
${input.gateCommands}; fix failures. Update ROADMAP.md and CHANGELOG.md, update
|
|
91
|
+
CLAUDE.md only for a new convention, stage only GSD-produced files, and commit
|
|
92
|
+
with message "feat(COMP-GSD-2): per-task fresh-context dispatch". Do not push.
|
|
93
|
+
Return PhaseResult with files_changed and commit_hash.
|
|
94
|
+
out: PhaseResult
|
|
146
95
|
ensure:
|
|
147
|
-
- "result.outcome == 'complete'"
|
|
148
|
-
|
|
149
|
-
depends_on: [execute]
|
|
96
|
+
- expr: "result.outcome == 'complete'"
|
|
97
|
+
attempts: 2
|
|
@@ -1,182 +1,101 @@
|
|
|
1
|
-
version:
|
|
2
|
-
|
|
3
|
-
workflow:
|
|
4
|
-
name: new
|
|
5
|
-
description: "Product kickoff — research, brainstorm, and scaffold a new project from intent"
|
|
6
|
-
input:
|
|
7
|
-
projectName:
|
|
8
|
-
type: string
|
|
9
|
-
required: true
|
|
10
|
-
intent:
|
|
11
|
-
type: string
|
|
12
|
-
required: true
|
|
1
|
+
version: 1
|
|
13
2
|
|
|
14
3
|
contracts:
|
|
15
4
|
ResearchResult:
|
|
16
|
-
priorArt:
|
|
17
|
-
patterns:
|
|
18
|
-
risks:
|
|
19
|
-
summary:
|
|
5
|
+
priorArt: array
|
|
6
|
+
patterns: array
|
|
7
|
+
risks: array
|
|
8
|
+
summary: string
|
|
20
9
|
|
|
21
10
|
BrainstormResult:
|
|
22
|
-
features:
|
|
23
|
-
userStories:
|
|
24
|
-
archOptions:
|
|
25
|
-
summary:
|
|
11
|
+
features: array
|
|
12
|
+
userStories: array
|
|
13
|
+
archOptions: array
|
|
14
|
+
summary: string
|
|
26
15
|
|
|
27
16
|
RoadmapResult:
|
|
28
|
-
phases:
|
|
29
|
-
features:
|
|
30
|
-
summary:
|
|
31
|
-
artifact:
|
|
17
|
+
phases: array
|
|
18
|
+
features: array
|
|
19
|
+
summary: string
|
|
20
|
+
artifact: string
|
|
32
21
|
|
|
33
22
|
ScaffoldResult:
|
|
34
|
-
created:
|
|
35
|
-
summary:
|
|
36
|
-
|
|
37
|
-
functions:
|
|
38
|
-
review_gate:
|
|
39
|
-
mode: gate
|
|
40
|
-
timeout: 7200
|
|
41
|
-
|
|
42
|
-
roadmap_gate:
|
|
43
|
-
mode: gate
|
|
44
|
-
timeout: 3600
|
|
23
|
+
created: array
|
|
24
|
+
summary: string
|
|
45
25
|
|
|
46
26
|
flows:
|
|
27
|
+
entry: new
|
|
28
|
+
|
|
47
29
|
new:
|
|
48
30
|
input:
|
|
49
|
-
projectName:
|
|
50
|
-
intent:
|
|
51
|
-
output:
|
|
31
|
+
projectName: string
|
|
32
|
+
intent: string
|
|
33
|
+
output:
|
|
34
|
+
from: "${scaffold.output}"
|
|
35
|
+
contract: ScaffoldResult
|
|
36
|
+
max_rounds: 10
|
|
52
37
|
steps:
|
|
53
|
-
# Phase: Research prior art
|
|
54
38
|
- id: research
|
|
55
39
|
agent: claude
|
|
56
|
-
|
|
57
|
-
Research prior art for
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Write your findings to docs/discovery/research.md.
|
|
63
|
-
If docs/discovery/research.md already exists, read it and build on it
|
|
64
|
-
rather than starting from scratch.
|
|
65
|
-
inputs:
|
|
66
|
-
intent: "$.input.intent"
|
|
67
|
-
output_contract: ResearchResult
|
|
40
|
+
do: >
|
|
41
|
+
Research prior art for ${input.intent}. Find existing tools, common
|
|
42
|
+
architectural patterns, known pitfalls, and risks. Write the findings
|
|
43
|
+
to docs/discovery/research.md, building on that file if it exists.
|
|
44
|
+
Return ResearchResult.
|
|
45
|
+
out: ResearchResult
|
|
68
46
|
ensure:
|
|
69
|
-
- "file_exists('docs/discovery/research.md')"
|
|
70
|
-
|
|
71
|
-
artifact: docs/discovery/research.md
|
|
72
|
-
criteria:
|
|
73
|
-
- "Contains at least 2 existing tools or prior art entries"
|
|
74
|
-
- "Mentions architectural patterns or common approaches"
|
|
75
|
-
- "Lists risks or pitfalls"
|
|
76
|
-
retries: 3
|
|
47
|
+
- expr: "file_exists('docs/discovery/research.md')"
|
|
48
|
+
attempts: 3
|
|
77
49
|
|
|
78
|
-
# Phase: Brainstorm features
|
|
79
50
|
- id: brainstorm
|
|
51
|
+
after: [research]
|
|
80
52
|
agent: claude
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
2. User stories — "As a <user>, I want <goal>, so that <benefit>"
|
|
88
|
-
3. Architecture options — 2-3 high-level approaches with trade-offs
|
|
89
|
-
|
|
90
|
-
If docs/discovery/research.md exists, read it first for prior-art
|
|
91
|
-
context. Otherwise proceed from the product intent alone — research
|
|
92
|
-
is an optional input, not a hard requirement (the questionnaire can
|
|
93
|
-
skip it).
|
|
94
|
-
|
|
95
|
-
Write the full brainstorm to docs/discovery/brainstorm.md.
|
|
96
|
-
If docs/discovery/brainstorm.md already exists, read it and refine it
|
|
97
|
-
rather than starting from scratch.
|
|
98
|
-
inputs:
|
|
99
|
-
intent: "$.input.intent"
|
|
100
|
-
research: "$.steps.research.output.summary" # may be null when research is skipped
|
|
101
|
-
output_contract: BrainstormResult
|
|
53
|
+
do: >
|
|
54
|
+
Brainstorm features, dependency-ordered feature codes, user stories,
|
|
55
|
+
and 2-3 architecture options with trade-offs for ${input.intent}.
|
|
56
|
+
Read docs/discovery/research.md first if it exists. Write the complete
|
|
57
|
+
result to docs/discovery/brainstorm.md and return BrainstormResult.
|
|
58
|
+
out: BrainstormResult
|
|
102
59
|
ensure:
|
|
103
|
-
- "file_exists('docs/discovery/brainstorm.md')"
|
|
104
|
-
|
|
105
|
-
artifact: docs/discovery/brainstorm.md
|
|
106
|
-
criteria:
|
|
107
|
-
- "Contains at least 3 features with short codes (e.g. LOG-1, LOG-2)"
|
|
108
|
-
- "Contains user stories in 'As a... I want... so that...' format"
|
|
109
|
-
- "Contains at least 2 architecture options with trade-offs"
|
|
110
|
-
retries: 3
|
|
111
|
-
depends_on: [research]
|
|
60
|
+
- expr: "file_exists('docs/discovery/brainstorm.md')"
|
|
61
|
+
attempts: 3
|
|
112
62
|
|
|
113
|
-
# Gate: Human reviews brainstorm
|
|
114
63
|
- id: review_gate
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
64
|
+
after: [brainstorm]
|
|
65
|
+
gate:
|
|
66
|
+
on_approve: roadmap
|
|
67
|
+
on_revise: brainstorm
|
|
68
|
+
on_kill: null
|
|
120
69
|
|
|
121
|
-
# Phase: Structure the roadmap
|
|
122
70
|
- id: roadmap
|
|
71
|
+
after: [review_gate]
|
|
123
72
|
agent: claude
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Write ROADMAP.md in the project root with:
|
|
131
|
-
1. Project name and description
|
|
132
|
-
2. Roadmap conventions (status values, numbering rules)
|
|
133
|
-
3. Phases grouping features by dependency order
|
|
134
|
-
4. Each feature as a numbered row with its code, description, and PLANNED status
|
|
135
|
-
|
|
136
|
-
Follow this table format for each phase:
|
|
137
|
-
| # | Feature | Item | Status |
|
|
138
|
-
|---|---------|------|--------|
|
|
139
|
-
| 1 | CODE-1 | Description | PLANNED |
|
|
140
|
-
inputs:
|
|
141
|
-
intent: "$.input.intent"
|
|
142
|
-
brainstorm: "$.steps.brainstorm.output.summary"
|
|
143
|
-
output_contract: RoadmapResult
|
|
73
|
+
do: >
|
|
74
|
+
Read the approved docs/discovery/brainstorm.md and write a phased
|
|
75
|
+
ROADMAP.md for ${input.projectName}. Include project conventions and
|
|
76
|
+
dependency-ordered feature tables with PLANNED status. Return
|
|
77
|
+
RoadmapResult with artifact set to ROADMAP.md.
|
|
78
|
+
out: RoadmapResult
|
|
144
79
|
ensure:
|
|
145
|
-
- "file_exists('ROADMAP.md')"
|
|
146
|
-
|
|
147
|
-
artifact: ROADMAP.md
|
|
148
|
-
criteria:
|
|
149
|
-
- "Contains a markdown table with feature codes and status columns"
|
|
150
|
-
- "Features are organized into phases"
|
|
151
|
-
- "All features have PLANNED status"
|
|
152
|
-
retries: 3
|
|
153
|
-
depends_on: [review_gate]
|
|
80
|
+
- expr: "file_exists('ROADMAP.md')"
|
|
81
|
+
attempts: 3
|
|
154
82
|
|
|
155
|
-
# Gate: Human approves the roadmap
|
|
156
83
|
- id: roadmap_gate
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
84
|
+
after: [roadmap]
|
|
85
|
+
gate:
|
|
86
|
+
on_approve: scaffold
|
|
87
|
+
on_revise: roadmap
|
|
88
|
+
on_kill: null
|
|
162
89
|
|
|
163
|
-
# Phase: Scaffold feature folders
|
|
164
90
|
- id: scaffold
|
|
91
|
+
after: [roadmap_gate]
|
|
165
92
|
agent: claude
|
|
166
|
-
|
|
167
|
-
Read the approved ROADMAP.md and create
|
|
168
|
-
feature
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- Created date
|
|
173
|
-
- Intent section with the feature description
|
|
174
|
-
- Notes section explaining this is a seed for compose build
|
|
175
|
-
inputs:
|
|
176
|
-
intent: "$.input.intent"
|
|
177
|
-
roadmap: "$.steps.roadmap.output.summary"
|
|
178
|
-
output_contract: ScaffoldResult
|
|
93
|
+
do: >
|
|
94
|
+
Read the approved ROADMAP.md and create docs/features/CODE/design.md
|
|
95
|
+
seed documents for every feature. Include title, PLANNED status,
|
|
96
|
+
created date, intent, and a note that compose build will refine it.
|
|
97
|
+
Return ScaffoldResult listing every created file.
|
|
98
|
+
out: ScaffoldResult
|
|
179
99
|
ensure:
|
|
180
|
-
- "len(result.created) > 0"
|
|
181
|
-
|
|
182
|
-
depends_on: [roadmap_gate]
|
|
100
|
+
- expr: "len(result.created) > 0"
|
|
101
|
+
attempts: 2
|
package/server/build-routes.js
CHANGED
|
@@ -144,7 +144,10 @@ export function attachBuildRoutes(app, deps = {}) {
|
|
|
144
144
|
return res.status(400).json({ error: 'featureCode required' });
|
|
145
145
|
}
|
|
146
146
|
try {
|
|
147
|
-
|
|
147
|
+
// C2: thread the ACTIVE PROJECT ROOT so abortBuild resolves the stratum
|
|
148
|
+
// engine from the project's capabilities (the route may have switched
|
|
149
|
+
// projects at runtime; process.cwd() is not the project root).
|
|
150
|
+
const result = await abortBuild(getDataDir(), featureCode, getTargetRoot());
|
|
148
151
|
res.json(result ?? { ok: true });
|
|
149
152
|
} catch (err) {
|
|
150
153
|
res.status(500).json({ error: err.message || String(err) });
|
package/server/design-routes.js
CHANGED
|
@@ -17,29 +17,45 @@ import { parseDecisionBlocks } from '../src/components/vision/designSessionState
|
|
|
17
17
|
import { StratumMcpClient } from '../lib/stratum-mcp-client.js';
|
|
18
18
|
import { KNOWN_VERSIONS } from '../lib/build-stream-schema.js';
|
|
19
19
|
import { getTargetRoot, resolveProjectPath } from './project-root.js';
|
|
20
|
+
import { resolveStratumMcpConnection } from '../lib/stratum-engine.js';
|
|
20
21
|
import { relForDisplay } from '../lib/project-paths.js';
|
|
21
22
|
|
|
22
|
-
// Lazy
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
// Lazy per-project-root singletons — design conversations share one Stratum TS
|
|
24
|
+
// MCP connection PER target project root. H2: keying on root (and threading the
|
|
25
|
+
// root into the connection) is load-bearing. `resolveStratumMcpConnection(root)`
|
|
26
|
+
// reads the project's `.compose/compose.json` engine pin and THROWS the
|
|
27
|
+
// python-legacy error for a python-pinned project; connecting without a cwd (the
|
|
28
|
+
// old behaviour) resolved the process cwd and silently handed a python project a
|
|
29
|
+
// TS engine, and the single cached client leaked across project switches. Keying
|
|
30
|
+
// the cache on root re-resolves the engine on every project switch.
|
|
31
|
+
const _stratumClients = new Map(); // root key -> connected client
|
|
32
|
+
const _stratumConnectPromises = new Map(); // root key -> in-flight connect promise
|
|
33
|
+
|
|
34
|
+
async function _getStratum(root = getTargetRoot(), { factory } = {}) {
|
|
35
|
+
const key = root || '';
|
|
36
|
+
if (_stratumClients.has(key)) return _stratumClients.get(key);
|
|
37
|
+
if (!_stratumConnectPromises.has(key)) {
|
|
38
|
+
// Throws synchronously for a python-pinned (or otherwise invalid) root, before
|
|
39
|
+
// any subprocess is spawned — the caller sees a rejected promise.
|
|
40
|
+
const connection = resolveStratumMcpConnection(root);
|
|
41
|
+
const c = factory ? factory() : new StratumMcpClient();
|
|
42
|
+
const p = c.connect(connection)
|
|
43
|
+
.then(() => { _stratumClients.set(key, c); return c; })
|
|
44
|
+
.catch((err) => { _stratumConnectPromises.delete(key); throw err; });
|
|
45
|
+
_stratumConnectPromises.set(key, p);
|
|
33
46
|
}
|
|
34
|
-
return
|
|
47
|
+
return _stratumConnectPromises.get(key);
|
|
35
48
|
}
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
// Test-only export: exercise the root-keyed connect/cache without a live server.
|
|
51
|
+
export { _getStratum as _getDesignStratumForTest };
|
|
52
|
+
|
|
53
|
+
/** Tear down every cached Stratum connection. Tests should call this in after(). */
|
|
38
54
|
export async function closeDesignStratum() {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
const clients = [..._stratumClients.values()];
|
|
56
|
+
_stratumClients.clear();
|
|
57
|
+
_stratumConnectPromises.clear();
|
|
58
|
+
for (const c of clients) {
|
|
43
59
|
try { await c.close(); } catch { /* ignore */ }
|
|
44
60
|
}
|
|
45
61
|
}
|
|
@@ -95,7 +111,7 @@ async function dispatchDesignAgent(sessionManager, projectRoot, scope, featureCo
|
|
|
95
111
|
const key = sessionKey(scope, featureCode, projectRoot);
|
|
96
112
|
if (_inFlight.has(key)) return; // already running
|
|
97
113
|
// Tests assert on the HTTP response only — skip the LLM round-trip to avoid
|
|
98
|
-
// keeping a
|
|
114
|
+
// keeping a Stratum subprocess alive past the test lifetime.
|
|
99
115
|
if (process.env.NODE_ENV === 'test' || process.env.COMPOSE_DESIGN_DISPATCH === '0') return;
|
|
100
116
|
_inFlight.add(key);
|
|
101
117
|
// Snapshot message count so we can detect new messages arriving during the run
|
|
@@ -142,7 +158,7 @@ ${formattedMessages}`;
|
|
|
142
158
|
let fullContent = '';
|
|
143
159
|
let toolUseCounter = 0;
|
|
144
160
|
|
|
145
|
-
const stratum = await _getStratum();
|
|
161
|
+
const stratum = await _getStratum(projectRoot);
|
|
146
162
|
const correlationId = randomUUID();
|
|
147
163
|
const subStepId = '_agent_run';
|
|
148
164
|
|
|
@@ -432,11 +448,11 @@ Write the design document in Markdown format. Include:
|
|
|
432
448
|
|
|
433
449
|
Output ONLY the Markdown content, no code fences.`;
|
|
434
450
|
|
|
435
|
-
// Generate the design doc via the persistent
|
|
451
|
+
// Generate the design doc via the persistent Stratum TS client.
|
|
436
452
|
// No streaming needed for the one-shot doc generation.
|
|
437
453
|
let docContent = '';
|
|
438
454
|
try {
|
|
439
|
-
const stratum = await _getStratum();
|
|
455
|
+
const stratum = await _getStratum(projectRoot);
|
|
440
456
|
const result = await stratum.runAgentText('claude', docPrompt, { cwd: projectRoot });
|
|
441
457
|
docContent = result || '';
|
|
442
458
|
} catch (err) {
|
package/server/index.js
CHANGED
|
@@ -2,8 +2,7 @@ import express from 'express';
|
|
|
2
2
|
import cors from 'cors';
|
|
3
3
|
import http from 'node:http';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import { existsSync, statSync, readFileSync, accessSync, constants as fsConstants } from 'node:fs';
|
|
5
|
+
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
7
6
|
import { FileWatcherServer } from './file-watcher.js';
|
|
8
7
|
import { VisionStore } from './vision-store.js';
|
|
9
8
|
import { VisionServer } from './vision-server.js';
|
|
@@ -15,6 +14,7 @@ import { attachGraphLayoutRoutes } from './graph-layout-routes.js';
|
|
|
15
14
|
import { createWorkspaceMiddleware } from './workspace-middleware.js';
|
|
16
15
|
import { getTargetRoot, getDataDir, ensureDataDir, loadProjectConfig, resolveProjectPath, switchProject, COMPOSE_HOME } from './project-root.js';
|
|
17
16
|
import { resolveStratumEngine } from './stratum-client.js';
|
|
17
|
+
import { probeStratumBin, resolveStratumBin } from '../lib/stratum-engine.js';
|
|
18
18
|
import { createAuthStore } from './auth-store.js';
|
|
19
19
|
import { createAuthGate, wsUpgradeTokenOk } from './auth-middleware.js';
|
|
20
20
|
import { attachAuthRoutes } from './auth-routes.js';
|
|
@@ -39,34 +39,26 @@ if (remoteMode && process.env.COMPOSE_REMOTE_AUTH !== 'enabled') {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Load project config and verify stratum capability matches reality.
|
|
42
|
-
// The probe targets the
|
|
43
|
-
// engine=ts the flow/gate seam runs without any Python install.
|
|
42
|
+
// The probe targets the TS engine binary (COMP-STRATUM-TS).
|
|
44
43
|
const projectConfig = loadProjectConfig();
|
|
45
44
|
if (projectConfig.capabilities.stratum) {
|
|
46
45
|
let stratumEngine;
|
|
46
|
+
let stratumBin;
|
|
47
47
|
try {
|
|
48
48
|
stratumEngine = resolveStratumEngine();
|
|
49
|
+
stratumBin = resolveStratumBin('cli', getTargetRoot());
|
|
49
50
|
} catch (err) {
|
|
50
51
|
console.error(`[compose] ${err.message}`);
|
|
51
52
|
process.exit(1);
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
accessSync(stratumBin, fsConstants.X_OK);
|
|
62
|
-
} else {
|
|
63
|
-
execFileSync('which', [stratumBin], { stdio: 'ignore' });
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
console.error(`[compose] ${stratumBin} (engine=${stratumEngine}) not found but capabilities.stratum=true`);
|
|
67
|
-
console.error(stratumEngine === 'ts'
|
|
68
|
-
? '[compose] Install @smartmemory/stratum or set COMPOSE_STRATUM_TS_BIN'
|
|
69
|
-
: '[compose] Run: compose init (will auto-install) or compose init --no-stratum');
|
|
54
|
+
// C1: existence alone is not enough — a bare `stratum` on $PATH can EXIST yet
|
|
55
|
+
// not speak the query contract (miniconda's CLI answers "Unknown command" and
|
|
56
|
+
// even exits 0), which used to half-enable the adapter with every call broken.
|
|
57
|
+
// The probe now verifies the binary actually returns the flows projection.
|
|
58
|
+
const probe = probeStratumBin(stratumBin);
|
|
59
|
+
if (!probe.ok) {
|
|
60
|
+
console.error(`[compose] stratum ${stratumEngine} binary is unusable but capabilities.stratum=true: ${probe.reason}`);
|
|
61
|
+
console.error('[compose] Install @smartmemory/stratum or set COMPOSE_STRATUM_TS_CLI_BIN to the live query/gate CLI');
|
|
70
62
|
projectConfig.capabilities.stratum = false;
|
|
71
63
|
}
|
|
72
64
|
}
|