@smartmemory/compose 0.2.58-beta → 0.3.1-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 (100) hide show
  1. package/README.md +2 -2
  2. package/bin/compose.js +55 -54
  3. package/dist/assets/{App-s4CulNkO.js → App-DR9pCiNE.js} +290 -290
  4. package/dist/assets/{abnfDiagram-VRR7QNED-CDwzQ5wr.js → abnfDiagram-VRR7QNED-CZ6p2bRe.js} +1 -1
  5. package/dist/assets/{arc-kIU8DDth.js → arc-6j04LBqn.js} +1 -1
  6. package/dist/assets/{architectureDiagram-ZJ3FMSHR-CleorO_0.js → architectureDiagram-ZJ3FMSHR-00QM3pmc.js} +1 -1
  7. package/dist/assets/{blockDiagram-677ZJIJ3-NvEFfoMk.js → blockDiagram-677ZJIJ3-BUb9Kjfd.js} +1 -1
  8. package/dist/assets/{c4Diagram-LMCZKHZV-BcDQdTVB.js → c4Diagram-LMCZKHZV-D1PkhmSm.js} +1 -1
  9. package/dist/assets/channel-BcWZM2KA.js +1 -0
  10. package/dist/assets/{chunk-2Q5K7J3B-BseRkpm4.js → chunk-2Q5K7J3B-CduDdMf4.js} +1 -1
  11. package/dist/assets/{chunk-32BRIVSS-DCHhvgFV.js → chunk-32BRIVSS-CH8Kj5rh.js} +1 -1
  12. package/dist/assets/{chunk-5VM5RSS4-DcTpMdJ4.js → chunk-5VM5RSS4-Cfts6Lix.js} +1 -1
  13. package/dist/assets/{chunk-EX3LRPZG-BdN8fvWs.js → chunk-EX3LRPZG-Dguk4yKZ.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-DmZJP-8E.js → chunk-JWPE2WC7-DbCJp6t9.js} +1 -1
  15. package/dist/assets/{chunk-MOJQB5TN-V74uYM17.js → chunk-MOJQB5TN-CNjM_9_U.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-CoXUXbn_.js → chunk-RYQCIY6F-D4k-BUbh.js} +1 -1
  17. package/dist/assets/{chunk-V7JOEXUC-DUscIydR.js → chunk-V7JOEXUC-O1sFoHf1.js} +1 -1
  18. package/dist/assets/{chunk-VR4S4FIN-C4UDHq_a.js → chunk-VR4S4FIN-CGbSXVPL.js} +1 -1
  19. package/dist/assets/{chunk-XXDRQBXY-DIH9i8mZ.js → chunk-XXDRQBXY-iIelV7SO.js} +1 -1
  20. package/dist/assets/classDiagram-OUVF2IWQ-Ce7YjlIK.js +1 -0
  21. package/dist/assets/classDiagram-v2-EOCWNBFH-Ce7YjlIK.js +1 -0
  22. package/dist/assets/{cose-bilkent-JH36ORCC-Dn6Gy6l1.js → cose-bilkent-JH36ORCC-BKmPLgw2.js} +1 -1
  23. package/dist/assets/{cynefin-VYW2F7L2-C2LW5fzB.js → cynefin-VYW2F7L2-CmT6ieC5.js} +1 -1
  24. package/dist/assets/{cynefinDiagram-TSTJHNR4-CGoAq7C9.js → cynefinDiagram-TSTJHNR4-Hn2vSVOw.js} +1 -1
  25. package/dist/assets/{dagre-VKFMJZFB-BaOKtuBE.js → dagre-VKFMJZFB-B0ifIwlU.js} +1 -1
  26. package/dist/assets/{diagram-FQU43EPY-BdQwqt4x.js → diagram-FQU43EPY-BuCDB-JG.js} +1 -1
  27. package/dist/assets/{diagram-G47NLZAW-Dkcf0o5z.js → diagram-G47NLZAW-D5avTZAQ.js} +1 -1
  28. package/dist/assets/{diagram-NH7WQ7WH-C_fDi59r.js → diagram-NH7WQ7WH-24QCIgpj.js} +1 -1
  29. package/dist/assets/{diagram-OA4YK3LP-jaTQcOxt.js → diagram-OA4YK3LP-A-bbUTK3.js} +1 -1
  30. package/dist/assets/{diagram-WEI45ONY-dEtqVj3s.js → diagram-WEI45ONY-BIMDm-Kj.js} +1 -1
  31. package/dist/assets/{ebnfDiagram-CCIWWBDH-s8ImqsSs.js → ebnfDiagram-CCIWWBDH-DCXMfG-M.js} +1 -1
  32. package/dist/assets/{erDiagram-Q63AITRT-5Zy29-SA.js → erDiagram-Q63AITRT-BlDrG0Ev.js} +1 -1
  33. package/dist/assets/{flowDiagram-23GEKE2U-DV74V9he.js → flowDiagram-23GEKE2U-aTyazY6P.js} +1 -1
  34. package/dist/assets/{ganttDiagram-NO4QXBWP-BND-CiLO.js → ganttDiagram-NO4QXBWP-Bxo-a80f.js} +1 -1
  35. package/dist/assets/{gitGraphDiagram-IHSO6WYX-B_-Mf9Jo.js → gitGraphDiagram-IHSO6WYX-COhKb9wR.js} +1 -1
  36. package/dist/assets/index-BcmsQZX-.js +119 -0
  37. package/dist/assets/{infoDiagram-FWYZ7A6U-Duyz8rZM.js → infoDiagram-FWYZ7A6U-DLCbpinV.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-FXEZZL3T-D2Qft0e5.js → ishikawaDiagram-FXEZZL3T-eyHF7GIj.js} +1 -1
  39. package/dist/assets/{journeyDiagram-5HDEW3XC-q6rG4Rig.js → journeyDiagram-5HDEW3XC-BsVocmlw.js} +1 -1
  40. package/dist/assets/{kanban-definition-HUTT4EX6-CsPX7gBL.js → kanban-definition-HUTT4EX6-CaFPSZxY.js} +1 -1
  41. package/dist/assets/{linear-CiccpAAd.js → linear-cqnI_wgW.js} +1 -1
  42. package/dist/assets/{mindmap-definition-LN4V7U3C-DRW2njpA.js → mindmap-definition-LN4V7U3C-B66BGqKn.js} +1 -1
  43. package/dist/assets/{pegDiagram-2B236MQR-DoDgm2S_.js → pegDiagram-2B236MQR-F6IN0_L5.js} +1 -1
  44. package/dist/assets/{pieDiagram-ENE6RG2P-CNSYLVAF.js → pieDiagram-ENE6RG2P-D5nOQxpo.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-ABIIQ3AL-8-Nd6ony.js → quadrantDiagram-ABIIQ3AL-DwUAYbMO.js} +1 -1
  46. package/dist/assets/{railroadDiagram-RFXS5EU6-bfdg6Bo4.js → railroadDiagram-RFXS5EU6-Cvmlah5P.js} +1 -1
  47. package/dist/assets/{requirementDiagram-TGXJPOKE-slDkxCQy.js → requirementDiagram-TGXJPOKE-DoUtsFnL.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-HTMAVEWB-CR3CboiK.js → sankeyDiagram-HTMAVEWB-D4XTwiKp.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BIXw1JS1.js → sequenceDiagram-DBY2YBRQ-CiG9yRIR.js} +1 -1
  50. package/dist/assets/{sizeCapture-X5ZJPWSS-BdKI7JyF.js → sizeCapture-X5ZJPWSS-zd7ydQ_Y.js} +1 -1
  51. package/dist/assets/{stateDiagram-2N3HPSRC-ktVgpBZS.js → stateDiagram-2N3HPSRC-BZesBHQ4.js} +1 -1
  52. package/dist/assets/stateDiagram-v2-6OUMAXLB-BnlLOJzB.js +1 -0
  53. package/dist/assets/{swimlanes-5IMT3BWC-CaXpC3FM.js → swimlanes-5IMT3BWC-Bg8exSE7.js} +2 -2
  54. package/dist/assets/swimlanesDiagram-G3AALYLV-DZMVxqbs.js +8 -0
  55. package/dist/assets/{timeline-definition-FHXFAJF6-CKWSlRUl.js → timeline-definition-FHXFAJF6-TKj_HY9k.js} +1 -1
  56. package/dist/assets/{vennDiagram-L72KCM5P-CwOhxDyv.js → vennDiagram-L72KCM5P-CJf-bqn0.js} +1 -1
  57. package/dist/assets/{wardleyDiagram-EHGQE667-DGavMpEB.js → wardleyDiagram-EHGQE667-CqizSfCL.js} +1 -1
  58. package/dist/assets/{xychartDiagram-FW5EYKEG-CWmnmLZz.js → xychartDiagram-FW5EYKEG-CK-OErMM.js} +1 -1
  59. package/dist/index.html +1 -1
  60. package/lib/build-all.js +0 -5
  61. package/lib/build-stream-schema.js +1 -1
  62. package/lib/build.js +1762 -2614
  63. package/lib/consumer-fanout.js +1317 -0
  64. package/lib/feature-validator.js +6 -4
  65. package/lib/flow-state.js +15 -14
  66. package/lib/gsd-budget.js +48 -10
  67. package/lib/gsd-prompt.js +3 -4
  68. package/lib/gsd-stuck.js +1 -1
  69. package/lib/gsd.js +303 -149
  70. package/lib/local-claude-connector.js +149 -0
  71. package/lib/new.js +162 -307
  72. package/lib/result-normalizer.js +233 -18
  73. package/lib/review-lenses.js +1 -1
  74. package/lib/step-prompt.js +41 -119
  75. package/lib/stratum-engine.js +297 -0
  76. package/lib/stratum-mcp-client.js +224 -213
  77. package/lib/vocabulary-compliance.js +268 -0
  78. package/lib/vocabulary-inject.js +1 -36
  79. package/package.json +6 -2
  80. package/pipelines/build-quick.stratum.yaml +5 -17
  81. package/pipelines/build.profiles.json +11 -0
  82. package/pipelines/build.stratum.yaml +288 -467
  83. package/pipelines/gsd.stratum.yaml +73 -125
  84. package/pipelines/new.stratum.yaml +68 -149
  85. package/server/build-routes.js +4 -1
  86. package/server/design-routes.js +37 -21
  87. package/server/index.js +13 -21
  88. package/server/lifecycle-guard.js +1 -1
  89. package/server/pipeline-routes.js +113 -31
  90. package/server/stratum-client.js +33 -47
  91. package/server/stratum-sync.js +3 -4
  92. package/server/vision-server.js +1 -1
  93. package/dist/assets/channel-DiJkE9og.js +0 -1
  94. package/dist/assets/classDiagram-OUVF2IWQ-DS76VBEL.js +0 -1
  95. package/dist/assets/classDiagram-v2-EOCWNBFH-DS76VBEL.js +0 -1
  96. package/dist/assets/index-CBWbmG7W.js +0 -119
  97. package/dist/assets/stateDiagram-v2-6OUMAXLB-CzPQnlan.js +0 -1
  98. package/dist/assets/swimlanesDiagram-G3AALYLV-rIDv6EuQ.js +0 -8
  99. package/lib/connector-factory-shim.js +0 -167
  100. 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: "Long-run autonomous mode. Decompose blueprint+Boundary Map into tasks, dispatch each as a fresh-context worktree-isolated agent, sequential by default. Reads existing blueprint.md."
4
+ # description: "Decompose a blueprint and dispatch each task in a fresh worktree context"
5
5
  # category: development
6
- # steps: 3
6
+ # steps: 4
7
7
  # estimated_minutes: 60
8
8
 
9
- version: "0.3"
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: {type: string}
28
- artifact: {type: string}
29
- outcome: {type: string, values: [complete, skipped, failed]}
30
- summary: {type: string}
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
- # TaskGraph is a v0.3 built-in contract — must NOT be redeclared.
33
- # The richer TaskGraphGsd shape (per-task produces/consumes) is enforced
34
- # post-decompose by lib/gsd-decompose-enrich.js + contracts/taskgraph-gsd.json
35
- # rather than via Stratum's contract layer.
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: {type: string}
41
- gateCommands: {type: array}
42
- pre_merge_gate: {type: array} # COMP-PAR-MERGE-QUEUE
43
- output: PhaseResult
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
- intent: >
51
- Read docs/features/{input.featureCode}/blueprint.md including its
52
- ## Boundary Map section. Decompose into independent tasks.
53
-
54
- For each task emit: id, files_owned, files_read, depends_on, AND a
55
- rich `description` string that bundles all the spec context Stratum's
56
- parallel_dispatch interpolation cannot carry on its own.
57
-
58
- Each task's `description` MUST include these labeled sections:
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
- - "no_file_conflicts(result.tasks)"
91
- - "len(result.tasks) >= 1"
92
- retries: 2
52
+ - expr: "len(result.tasks) >= 1"
53
+ attempts: 2
93
54
 
94
- # Phase: Execute sequential per-task fresh-context dispatch
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
- type: parallel_dispatch
97
- source: "$.steps.decompose_gsd.output.tasks"
98
- agent: claude
99
- max_concurrent: 1
100
- isolation: worktree
101
- capture_diff: true
102
- defer_advance: true # COMP-PAR-MERGE-QUEUE: consumer merges + reports status, enabling conflict-bounce
103
- pre_merge_verify: "$.input.pre_merge_gate" # COMP-PAR-MERGE-QUEUE: enforced fast gate per task worktree
104
- require: all
105
- merge: sequential_apply
106
- retries: 2
107
- intent_template: >
108
- Implement task {task.id} using TDD.
109
-
110
- Files you own (write): {task.files_owned}
111
- Files you may read: {task.files_read}
112
- Depends on: {task.depends_on}
113
-
114
- {task.description}
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: "claude::critical"
124
- intent: >
125
- Final verification and ship for feature {input.featureCode}.
126
-
127
- You MUST:
128
- 1. Run the project test suite. If tests fail, fix them.
129
- 2. Run the project build. Must pass.
130
- 3. Update ROADMAP.md — find COMP-GSD-2's row and mark it COMPLETE.
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
- retries: 2
149
- depends_on: [execute]
96
+ - expr: "result.outcome == 'complete'"
97
+ attempts: 2
@@ -1,182 +1,101 @@
1
- version: "0.2"
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: {type: array}
17
- patterns: {type: array}
18
- risks: {type: array}
19
- summary: {type: string}
5
+ priorArt: array
6
+ patterns: array
7
+ risks: array
8
+ summary: string
20
9
 
21
10
  BrainstormResult:
22
- features: {type: array}
23
- userStories: {type: array}
24
- archOptions: {type: array}
25
- summary: {type: string}
11
+ features: array
12
+ userStories: array
13
+ archOptions: array
14
+ summary: string
26
15
 
27
16
  RoadmapResult:
28
- phases: {type: array}
29
- features: {type: array}
30
- summary: {type: string}
31
- artifact: {type: string}
17
+ phases: array
18
+ features: array
19
+ summary: string
20
+ artifact: string
32
21
 
33
22
  ScaffoldResult:
34
- created: {type: array}
35
- summary: {type: string}
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: {type: string}
50
- intent: {type: string}
51
- output: ScaffoldResult
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
- intent: >
57
- Research prior art for this product idea. Search the web for:
58
- 1. Existing tools that solve the same or similar problems
59
- 2. Common architectural patterns used in this domain
60
- 3. Known pitfalls and risks
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
- validate:
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
- intent: >
82
- Given the product intent (and any available research findings),
83
- brainstorm the product. Generate:
84
- 1. A feature list discrete capabilities the product should have,
85
- each with a short code (e.g. LOG-1, LOG-2) and description.
86
- Order features by dependency: foundational first, advanced later.
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
- validate:
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
- function: review_gate
116
- on_approve: roadmap
117
- on_revise: brainstorm
118
- on_kill: null
119
- depends_on: [brainstorm]
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
- intent: >
125
- Take the approved brainstorm and structure it into a phased ROADMAP.
126
-
127
- First read docs/discovery/brainstorm.md for the full brainstorm.
128
- If ROADMAP.md already exists, read it and update it rather than overwriting.
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
- validate:
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
- function: roadmap_gate
158
- on_approve: scaffold
159
- on_revise: roadmap
160
- on_kill: null
161
- depends_on: [roadmap]
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
- intent: >
167
- Read the approved ROADMAP.md and create a feature folder for each
168
- feature listed. For each feature:
169
- 1. Create docs/features/<CODE>/design.md with a seed design doc containing:
170
- - Title: "<CODE>: <description>"
171
- - Status: PLANNED
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
- retries: 2
182
- depends_on: [roadmap_gate]
100
+ - expr: "len(result.created) > 0"
101
+ attempts: 2
@@ -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
- const result = await abortBuild(getDataDir(), featureCode);
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) });
@@ -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 singleton — design conversations share one stratum-mcp connection
23
- // across the server process lifetime. Concurrent runs are correlation-id scoped.
24
- let _stratumClient = null;
25
- let _stratumConnectPromise = null;
26
- async function _getStratum() {
27
- if (_stratumClient) return _stratumClient;
28
- if (!_stratumConnectPromise) {
29
- const c = new StratumMcpClient();
30
- _stratumConnectPromise = c.connect()
31
- .then(() => { _stratumClient = c; return c; })
32
- .catch((err) => { _stratumConnectPromise = null; throw err; });
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 _stratumConnectPromise;
47
+ return _stratumConnectPromises.get(key);
35
48
  }
36
49
 
37
- /** Tear down the cached stratum-mcp connection. Tests should call this in after(). */
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 c = _stratumClient;
40
- _stratumClient = null;
41
- _stratumConnectPromise = null;
42
- if (c) {
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 stratum-mcp subprocess alive past the test lifetime.
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 stratum-mcp client.
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 { execFileSync } from 'node:child_process';
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 SELECTED engine's binary (COMP-STRATUM-TS): under
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
- const stratumBin = stratumEngine === 'ts'
54
- ? (process.env.COMPOSE_STRATUM_TS_BIN || 'stratum')
55
- : 'stratum-mcp';
56
- try {
57
- if (stratumBin.includes('/')) {
58
- // A configured path must be an executable regular file — a directory or
59
- // mode-644 file would pass a bare stat and fail later at spawn time.
60
- if (!statSync(stratumBin).isFile()) throw new Error('not a regular file');
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
  }