@smartmemory/compose 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1014 -0
  3. package/bin/compose.js +1515 -0
  4. package/dist/assets/_baseUniq-CQwX6VLz.js +1 -0
  5. package/dist/assets/arc-SxJ2J1sh.js +1 -0
  6. package/dist/assets/architectureDiagram-Q4EWVU46-BykunY1F.js +36 -0
  7. package/dist/assets/blockDiagram-DXYQGD6D-ohAKBOUw.js +132 -0
  8. package/dist/assets/c4Diagram-AHTNJAMY-DBDC3ENB.js +10 -0
  9. package/dist/assets/channel-DGElom1e.js +1 -0
  10. package/dist/assets/chunk-4BX2VUAB-Cv93Z7uM.js +1 -0
  11. package/dist/assets/chunk-4TB4RGXK-DE0WBDkj.js +206 -0
  12. package/dist/assets/chunk-55IACEB6-CE1EXenG.js +1 -0
  13. package/dist/assets/chunk-EDXVE4YY-DA7Ana6H.js +1 -0
  14. package/dist/assets/chunk-FMBD7UC4-CTDIPA3p.js +15 -0
  15. package/dist/assets/chunk-OYMX7WX6-uGBaPaTX.js +231 -0
  16. package/dist/assets/chunk-QZHKN3VN-CYlnXuUO.js +1 -0
  17. package/dist/assets/chunk-YZCP3GAM-ojGkzcZK.js +1 -0
  18. package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +1 -0
  19. package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +1 -0
  20. package/dist/assets/clone-DUJKJXd7.js +1 -0
  21. package/dist/assets/cose-bilkent-S5V4N54A-Bktn9hL-.js +1 -0
  22. package/dist/assets/dagre-KV5264BT-DFaSzuRF.js +4 -0
  23. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  24. package/dist/assets/diagram-5BDNPKRD-DnfmDzEm.js +10 -0
  25. package/dist/assets/diagram-G4DWMVQ6-Bm8W9YnG.js +24 -0
  26. package/dist/assets/diagram-MMDJMWI5-B5-TSKvp.js +43 -0
  27. package/dist/assets/diagram-TYMM5635-ls4rqlky.js +24 -0
  28. package/dist/assets/erDiagram-SMLLAGMA-giG6WO-r.js +85 -0
  29. package/dist/assets/flowDiagram-DWJPFMVM-XvlUuz-7.js +162 -0
  30. package/dist/assets/ganttDiagram-T4ZO3ILL-hLBV57oV.js +292 -0
  31. package/dist/assets/gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js +106 -0
  32. package/dist/assets/graph-D0Cfv00Y.js +1 -0
  33. package/dist/assets/index-CUd6pFGF.css +1 -0
  34. package/dist/assets/index-DReRlzZI.js +1144 -0
  35. package/dist/assets/infoDiagram-42DDH7IO-DbqRsOo3.js +2 -0
  36. package/dist/assets/init-Gi6I4Gst.js +1 -0
  37. package/dist/assets/ishikawaDiagram-UXIWVN3A-DnCdx7zb.js +70 -0
  38. package/dist/assets/journeyDiagram-VCZTEJTY-CfD7eNcP.js +139 -0
  39. package/dist/assets/kanban-definition-6JOO6SKY-BYaO9-mK.js +89 -0
  40. package/dist/assets/katex-DkKDou_j.js +257 -0
  41. package/dist/assets/layout-Bj72wOEB.js +1 -0
  42. package/dist/assets/linear-BRFo114D.js +1 -0
  43. package/dist/assets/min-GCHnKlJS.js +1 -0
  44. package/dist/assets/mindmap-definition-QFDTVHPH-n0PMebY4.js +96 -0
  45. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  46. package/dist/assets/pieDiagram-DEJITSTG-pN4CljHF.js +30 -0
  47. package/dist/assets/quadrantDiagram-34T5L4WZ-DNoAy8-D.js +7 -0
  48. package/dist/assets/requirementDiagram-MS252O5E-BhtY05PT.js +84 -0
  49. package/dist/assets/sankeyDiagram-XADWPNL6-B6AD-16A.js +10 -0
  50. package/dist/assets/sequenceDiagram-FGHM5R23-DShHM-uk.js +157 -0
  51. package/dist/assets/stateDiagram-FHFEXIEX-DMxn7HTo.js +1 -0
  52. package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +1 -0
  53. package/dist/assets/timeline-definition-GMOUNBTQ-Cdu6uq52.js +120 -0
  54. package/dist/assets/vennDiagram-DHZGUBPP-CpK29iRe.js +34 -0
  55. package/dist/assets/wardley-RL74JXVD-BQgSkdcO.js +162 -0
  56. package/dist/assets/wardleyDiagram-NUSXRM2D-DJHYev6O.js +20 -0
  57. package/dist/assets/xychartDiagram-5P7HB3ND-1d75pbaO.js +7 -0
  58. package/dist/index.html +30 -0
  59. package/lib/agent-chains.js +65 -0
  60. package/lib/agent-string.js +86 -0
  61. package/lib/budget-ledger.js +86 -0
  62. package/lib/build-all.js +162 -0
  63. package/lib/build-dag.js +120 -0
  64. package/lib/build-stream-writer.js +190 -0
  65. package/lib/build.js +2997 -0
  66. package/lib/capability-checker.js +53 -0
  67. package/lib/cert-inject.js +38 -0
  68. package/lib/cli-progress.js +483 -0
  69. package/lib/constants.js +69 -0
  70. package/lib/cross-layer-audit.js +84 -0
  71. package/lib/debug-discipline.js +173 -0
  72. package/lib/feature-json.js +106 -0
  73. package/lib/gate-prompt.js +291 -0
  74. package/lib/gate-tiers.js +194 -0
  75. package/lib/health-history.js +119 -0
  76. package/lib/health-score.js +227 -0
  77. package/lib/ideabox.js +570 -0
  78. package/lib/import.js +244 -0
  79. package/lib/migrate-roadmap.js +94 -0
  80. package/lib/model-pricing.js +67 -0
  81. package/lib/new.js +413 -0
  82. package/lib/pipeline-cli.js +489 -0
  83. package/lib/plan-parser.js +103 -0
  84. package/lib/qa-scoping.js +474 -0
  85. package/lib/questionnaire.js +200 -0
  86. package/lib/resolve-port.js +7 -0
  87. package/lib/result-normalizer.js +349 -0
  88. package/lib/review-lenses.js +166 -0
  89. package/lib/roadmap-gen.js +210 -0
  90. package/lib/roadmap-parser.js +176 -0
  91. package/lib/server-probe.js +23 -0
  92. package/lib/staleness.js +87 -0
  93. package/lib/step-prompt.js +260 -0
  94. package/lib/step-validator.js +49 -0
  95. package/lib/stratum-mcp-client.js +365 -0
  96. package/lib/team-flag.js +46 -0
  97. package/lib/test-bootstrap.js +401 -0
  98. package/lib/triage.js +274 -0
  99. package/lib/vision-writer.js +391 -0
  100. package/package.json +111 -0
  101. package/pipelines/bug-fix.stratum.yaml +230 -0
  102. package/pipelines/build.stratum.yaml +498 -0
  103. package/pipelines/content.stratum.yaml +112 -0
  104. package/pipelines/coverage-sweep.stratum.yaml +52 -0
  105. package/pipelines/refactor.stratum.yaml +169 -0
  106. package/pipelines/research.stratum.yaml +88 -0
  107. package/pipelines/review-fix.stratum.yaml +109 -0
  108. package/presets/team-feature.stratum.yaml +105 -0
  109. package/presets/team-research.stratum.yaml +108 -0
  110. package/presets/team-review.stratum.yaml +106 -0
  111. package/scripts/agent-activity-hook.sh +31 -0
  112. package/scripts/agent-error-hook.sh +28 -0
  113. package/scripts/analyze-orphans.mjs +50 -0
  114. package/scripts/find-orphans.mjs +26 -0
  115. package/scripts/fix-phases.mjs +49 -0
  116. package/scripts/generate-stratum-spec.mjs +137 -0
  117. package/scripts/import-roadmap.mjs +116 -0
  118. package/scripts/phase-audit.mjs +33 -0
  119. package/scripts/run-pipeline.mjs +314 -0
  120. package/scripts/session-end-hook.sh +18 -0
  121. package/scripts/session-start-hook.sh +38 -0
  122. package/scripts/vision-hook.sh +104 -0
  123. package/scripts/vision-track.mjs +554 -0
  124. package/scripts/wire-all-orphans.mjs +108 -0
  125. package/scripts/wire-orphans.mjs +164 -0
  126. package/server/activity-routes.js +123 -0
  127. package/server/agent-health.js +197 -0
  128. package/server/agent-hooks.js +102 -0
  129. package/server/agent-mcp.js +10 -0
  130. package/server/agent-registry.js +95 -0
  131. package/server/agent-server.js +290 -0
  132. package/server/agent-spawn.js +251 -0
  133. package/server/agent-templates.js +77 -0
  134. package/server/artifact-manager.js +247 -0
  135. package/server/artifact-templates/architecture.md +28 -0
  136. package/server/artifact-templates/blueprint.md +21 -0
  137. package/server/artifact-templates/design.md +36 -0
  138. package/server/artifact-templates/plan.md +25 -0
  139. package/server/artifact-templates/prd.md +43 -0
  140. package/server/artifact-templates/report.md +40 -0
  141. package/server/block-tracker.js +90 -0
  142. package/server/build-stream-bridge.js +502 -0
  143. package/server/coalescing-buffer.js +46 -0
  144. package/server/compose-mcp-tools.js +479 -0
  145. package/server/compose-mcp.js +324 -0
  146. package/server/connectors/agent-connector.js +78 -0
  147. package/server/connectors/claude-sdk-connector.js +198 -0
  148. package/server/connectors/codex-connector.js +240 -0
  149. package/server/connectors/connector-discovery.js +18 -0
  150. package/server/connectors/connector-runtime.js +13 -0
  151. package/server/connectors/opencode-connector.js +200 -0
  152. package/server/design-routes.js +540 -0
  153. package/server/design-session.js +161 -0
  154. package/server/feature-scan.js +593 -0
  155. package/server/file-watcher.js +284 -0
  156. package/server/find-root.js +29 -0
  157. package/server/graph-export.js +343 -0
  158. package/server/ideabox-cache.js +77 -0
  159. package/server/ideabox-routes.js +294 -0
  160. package/server/index.js +156 -0
  161. package/server/model-tiers.js +49 -0
  162. package/server/pipeline-routes.js +288 -0
  163. package/server/policy-evaluator.js +36 -0
  164. package/server/project-root.js +122 -0
  165. package/server/security.js +23 -0
  166. package/server/session-manager.js +403 -0
  167. package/server/session-routes.js +190 -0
  168. package/server/session-store.js +107 -0
  169. package/server/settings-routes.js +35 -0
  170. package/server/settings-store.js +234 -0
  171. package/server/stratum-api.js +102 -0
  172. package/server/stratum-client.js +192 -0
  173. package/server/stratum-sync.js +193 -0
  174. package/server/summarizer.js +139 -0
  175. package/server/supervisor.js +196 -0
  176. package/server/vision-routes.js +668 -0
  177. package/server/vision-server.js +393 -0
  178. package/server/vision-store.js +360 -0
  179. package/server/vision-utils.js +179 -0
  180. package/server/worktree-gc.js +137 -0
  181. package/templates/ROADMAP.md +46 -0
@@ -0,0 +1,46 @@
1
+ /**
2
+ * team-flag.js — --team CLI flag parsing for COMP-TEAMS.
3
+ *
4
+ * Extracted from compose.js for testability (compose.js has top-level
5
+ * side effects that prevent clean import in test files).
6
+ */
7
+
8
+ export const KNOWN_TEAMS = ['review', 'research', 'feature'];
9
+
10
+ /**
11
+ * Parse and validate the --team flag from CLI args.
12
+ * Rewrites --team <name> into a template name and strips it from args.
13
+ *
14
+ * @param {string[]} args - CLI arguments (after subcommand is stripped)
15
+ * @returns {{ template: string|null, args: string[] }}
16
+ * @throws {Error} On invalid usage
17
+ */
18
+ export function parseTeamFlag(args) {
19
+ const teamIdx = args.indexOf('--team');
20
+ if (teamIdx === -1) return { template: null, args: [...args] };
21
+
22
+ const teamName = args[teamIdx + 1];
23
+ if (!teamName || teamName.startsWith('-')) {
24
+ throw new Error('--team requires a team name (available: ' + KNOWN_TEAMS.join(', ') + ')');
25
+ }
26
+
27
+ if (!KNOWN_TEAMS.includes(teamName)) {
28
+ throw new Error(`Unknown team "${teamName}". Available: ${KNOWN_TEAMS.join(', ')}`);
29
+ }
30
+
31
+ if (args.includes('--template')) {
32
+ throw new Error('--team cannot be used with --template');
33
+ }
34
+
35
+ const cleaned = args.filter((_, i) => i !== teamIdx && i !== teamIdx + 1);
36
+
37
+ if (cleaned.includes('--all')) {
38
+ throw new Error('--team cannot be used with batch builds (--all or multiple features)');
39
+ }
40
+ const featureCodes = cleaned.filter(a => !a.startsWith('-'));
41
+ if (featureCodes.length > 1) {
42
+ throw new Error('--team cannot be used with batch builds (--all or multiple features)');
43
+ }
44
+
45
+ return { template: `team-${teamName}`, args: cleaned };
46
+ }
@@ -0,0 +1,401 @@
1
+ /**
2
+ * test-bootstrap.js — Framework detection and test scaffold generation.
3
+ *
4
+ * Item 125: detectTestFramework(cwd) — inspect config files, package.json devDeps,
5
+ * and pyproject.toml to identify the active test framework.
6
+ *
7
+ * Item 126: scaffoldTestFramework(cwd, language) — create minimal test scaffolding
8
+ * when no framework is detected.
9
+ */
10
+
11
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Internal helpers
16
+ // ---------------------------------------------------------------------------
17
+
18
+ function _readJSON(filePath) {
19
+ try {
20
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ function _readText(filePath) {
27
+ try {
28
+ return readFileSync(filePath, 'utf-8');
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Item 125: detectTestFramework
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Detect which test framework is configured in the given directory.
40
+ *
41
+ * Checks (in order):
42
+ * 1. Well-known config file names on disk
43
+ * 2. package.json devDependencies / dependencies
44
+ * 3. pyproject.toml [tool.pytest]
45
+ *
46
+ * @param {string} cwd - Directory to inspect
47
+ * @returns {{ framework: string, runner: string, configFile: string|null, command: string }|null}
48
+ */
49
+ export function detectTestFramework(cwd) {
50
+ // --- Config-file checks ---
51
+
52
+ const configChecks = [
53
+ // Vitest
54
+ {
55
+ files: ['vitest.config.js', 'vitest.config.ts', 'vitest.config.mjs', 'vitest.config.cjs'],
56
+ framework: 'vitest',
57
+ runner: 'vitest',
58
+ command: 'npx vitest run',
59
+ },
60
+ // Jest
61
+ {
62
+ files: [
63
+ 'jest.config.js', 'jest.config.ts', 'jest.config.mjs', 'jest.config.cjs',
64
+ 'jest.config.json',
65
+ ],
66
+ framework: 'jest',
67
+ runner: 'jest',
68
+ command: 'npx jest',
69
+ },
70
+ // Mocha
71
+ {
72
+ files: ['.mocharc.js', '.mocharc.cjs', '.mocharc.yaml', '.mocharc.yml', '.mocharc.json'],
73
+ framework: 'mocha',
74
+ runner: 'mocha',
75
+ command: 'npx mocha',
76
+ },
77
+ // pytest — config file forms
78
+ {
79
+ files: ['pytest.ini', 'conftest.py'],
80
+ framework: 'pytest',
81
+ runner: 'pytest',
82
+ command: 'pytest',
83
+ },
84
+ // setup.cfg with [tool:pytest]
85
+ {
86
+ files: ['setup.cfg'],
87
+ framework: 'pytest',
88
+ runner: 'pytest',
89
+ command: 'pytest',
90
+ // validated below — only if [tool:pytest] section present
91
+ contentCheck: (content) => content.includes('[tool:pytest]'),
92
+ },
93
+ ];
94
+
95
+ for (const check of configChecks) {
96
+ for (const filename of check.files) {
97
+ const fullPath = join(cwd, filename);
98
+ if (!existsSync(fullPath)) continue;
99
+
100
+ if (check.contentCheck) {
101
+ const content = _readText(fullPath);
102
+ if (!content || !check.contentCheck(content)) continue;
103
+ }
104
+
105
+ return {
106
+ framework: check.framework,
107
+ runner: check.runner,
108
+ configFile: filename,
109
+ command: check.command,
110
+ };
111
+ }
112
+ }
113
+
114
+ // --- Go: *_test.go files ---
115
+ {
116
+ const goMod = join(cwd, 'go.mod');
117
+ if (existsSync(goMod)) {
118
+ return {
119
+ framework: 'go-test',
120
+ runner: 'go',
121
+ configFile: 'go.mod',
122
+ command: 'go test ./...',
123
+ };
124
+ }
125
+ }
126
+
127
+ // --- Rust: Cargo.toml ---
128
+ {
129
+ const cargoToml = join(cwd, 'Cargo.toml');
130
+ if (existsSync(cargoToml)) {
131
+ const content = _readText(cargoToml);
132
+ if (content) {
133
+ return {
134
+ framework: 'cargo-test',
135
+ runner: 'cargo',
136
+ configFile: 'Cargo.toml',
137
+ command: 'cargo test',
138
+ };
139
+ }
140
+ }
141
+ }
142
+
143
+ // --- pyproject.toml with [tool.pytest] ---
144
+ {
145
+ const pyproject = join(cwd, 'pyproject.toml');
146
+ const content = _readText(pyproject);
147
+ if (content && content.includes('[tool.pytest')) {
148
+ return {
149
+ framework: 'pytest',
150
+ runner: 'pytest',
151
+ configFile: 'pyproject.toml',
152
+ command: 'pytest',
153
+ };
154
+ }
155
+ }
156
+
157
+ // --- package.json devDependencies / dependencies ---
158
+ {
159
+ const pkgPath = join(cwd, 'package.json');
160
+ const pkg = _readJSON(pkgPath);
161
+ if (pkg) {
162
+ const allDeps = {
163
+ ...(pkg.dependencies ?? {}),
164
+ ...(pkg.devDependencies ?? {}),
165
+ };
166
+
167
+ // Ordered: prefer vitest > jest > mocha > ava > tap
168
+ if (allDeps['vitest']) {
169
+ return {
170
+ framework: 'vitest',
171
+ runner: 'vitest',
172
+ configFile: 'package.json',
173
+ command: 'npx vitest run',
174
+ };
175
+ }
176
+ if (allDeps['jest'] || allDeps['ts-jest'] || allDeps['babel-jest']) {
177
+ return {
178
+ framework: 'jest',
179
+ runner: 'jest',
180
+ configFile: 'package.json',
181
+ command: 'npx jest',
182
+ };
183
+ }
184
+ if (allDeps['mocha']) {
185
+ return {
186
+ framework: 'mocha',
187
+ runner: 'mocha',
188
+ configFile: 'package.json',
189
+ command: 'npx mocha',
190
+ };
191
+ }
192
+ if (allDeps['ava']) {
193
+ return {
194
+ framework: 'ava',
195
+ runner: 'ava',
196
+ configFile: 'package.json',
197
+ command: 'npx ava',
198
+ };
199
+ }
200
+ if (allDeps['tap']) {
201
+ return {
202
+ framework: 'tap',
203
+ runner: 'tap',
204
+ configFile: 'package.json',
205
+ command: 'npx tap',
206
+ };
207
+ }
208
+ }
209
+ }
210
+
211
+ return null;
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Item 126: scaffoldTestFramework
216
+ // ---------------------------------------------------------------------------
217
+
218
+ /**
219
+ * Create a minimal test scaffold if no framework is detected.
220
+ * Only runs when detectTestFramework(cwd) returns null.
221
+ *
222
+ * @param {string} cwd - Project root directory
223
+ * @param {string} language - 'node' | 'python' | 'go' | 'rust'
224
+ * @returns {{ framework: string, configFile: string, testDir: string, command: string }}
225
+ */
226
+ export function scaffoldTestFramework(cwd, language) {
227
+ const existing = detectTestFramework(cwd);
228
+ if (existing) return existing;
229
+
230
+ switch (language) {
231
+ case 'python': {
232
+ const testDir = join(cwd, 'tests');
233
+ mkdirSync(testDir, { recursive: true });
234
+
235
+ const conftest = join(cwd, 'conftest.py');
236
+ if (!existsSync(conftest)) {
237
+ writeFileSync(conftest, [
238
+ '# conftest.py — pytest configuration and shared fixtures',
239
+ 'import pytest',
240
+ '',
241
+ '# Add project root to sys.path so tests can import the package.',
242
+ 'import sys, os',
243
+ 'sys.path.insert(0, os.path.dirname(__file__))',
244
+ '',
245
+ ].join('\n'), 'utf-8');
246
+ }
247
+
248
+ const initTest = join(testDir, 'test_golden.py');
249
+ if (!existsSync(initTest)) {
250
+ writeFileSync(initTest, [
251
+ '"""Golden flow placeholder — replace with real lifecycle tests."""',
252
+ '',
253
+ 'def test_placeholder():',
254
+ ' """Minimal placeholder that passes until real tests are written."""',
255
+ ' assert True',
256
+ '',
257
+ ].join('\n'), 'utf-8');
258
+ }
259
+
260
+ return {
261
+ framework: 'pytest',
262
+ configFile: 'conftest.py',
263
+ testDir: 'tests',
264
+ command: 'pytest',
265
+ };
266
+ }
267
+
268
+ case 'go': {
269
+ // Find first .go file to derive the package name, defaulting to 'main'
270
+ let packageName = 'main';
271
+ try {
272
+ const goFiles = readdirSync(cwd).filter(
273
+ f => f.endsWith('.go') && !f.endsWith('_test.go')
274
+ );
275
+ if (goFiles.length > 0) {
276
+ const src = _readText(join(cwd, goFiles[0]));
277
+ const m = src?.match(/^package\s+(\w+)/m);
278
+ if (m) packageName = m[1];
279
+ }
280
+ } catch { /* use default */ }
281
+
282
+ const testFile = join(cwd, 'golden_test.go');
283
+ if (!existsSync(testFile)) {
284
+ writeFileSync(testFile, [
285
+ `package ${packageName}`,
286
+ '',
287
+ 'import "testing"',
288
+ '',
289
+ '// TestGoldenFlow is a placeholder — replace with real lifecycle tests.',
290
+ 'func TestGoldenFlow(t *testing.T) {',
291
+ ' // TODO: implement golden flow test',
292
+ '}',
293
+ '',
294
+ ].join('\n'), 'utf-8');
295
+ }
296
+
297
+ return {
298
+ framework: 'go-test',
299
+ configFile: 'golden_test.go',
300
+ testDir: '.',
301
+ command: 'go test ./...',
302
+ };
303
+ }
304
+
305
+ case 'rust': {
306
+ const testDir = join(cwd, 'tests');
307
+ mkdirSync(testDir, { recursive: true });
308
+
309
+ const testFile = join(testDir, 'golden.rs');
310
+ if (!existsSync(testFile)) {
311
+ writeFileSync(testFile, [
312
+ '//! Golden flow placeholder — replace with real lifecycle tests.',
313
+ '',
314
+ '#[test]',
315
+ 'fn placeholder() {',
316
+ ' // TODO: implement golden flow test',
317
+ '}',
318
+ '',
319
+ ].join('\n'), 'utf-8');
320
+ }
321
+
322
+ return {
323
+ framework: 'cargo-test',
324
+ configFile: 'tests/golden.rs',
325
+ testDir: 'tests',
326
+ command: 'cargo test',
327
+ };
328
+ }
329
+
330
+ case 'node':
331
+ default: {
332
+ const testDir = join(cwd, 'test');
333
+ mkdirSync(testDir, { recursive: true });
334
+
335
+ // Prefer vitest if package.json exists; otherwise scaffold bare vitest config
336
+ const pkgPath = join(cwd, 'package.json');
337
+ const pkg = _readJSON(pkgPath);
338
+ const useVitest = !pkg?.devDependencies?.jest && !pkg?.dependencies?.jest;
339
+
340
+ if (useVitest) {
341
+ const configPath = join(cwd, 'vitest.config.js');
342
+ if (!existsSync(configPath)) {
343
+ writeFileSync(configPath, [
344
+ '// vitest.config.js — generated by Compose test-bootstrap',
345
+ "import { defineConfig } from 'vitest/config';",
346
+ '',
347
+ 'export default defineConfig({',
348
+ ' test: {',
349
+ " include: ['test/**/*.test.{js,ts}'],",
350
+ ' },',
351
+ '});',
352
+ '',
353
+ ].join('\n'), 'utf-8');
354
+ }
355
+
356
+ const setupPath = join(testDir, 'setup.js');
357
+ if (!existsSync(setupPath)) {
358
+ writeFileSync(setupPath, [
359
+ '// test/setup.js — shared test helpers',
360
+ "// Import this in tests via: import './setup.js'",
361
+ '',
362
+ '// Example: assert helpers, fixtures, shared state',
363
+ '',
364
+ ].join('\n'), 'utf-8');
365
+ }
366
+
367
+ return {
368
+ framework: 'vitest',
369
+ configFile: 'vitest.config.js',
370
+ testDir: 'test',
371
+ command: 'npx vitest run',
372
+ };
373
+ }
374
+
375
+ // Jest fallback
376
+ const configPath = join(cwd, 'jest.config.js');
377
+ if (!existsSync(configPath)) {
378
+ writeFileSync(configPath, [
379
+ '// jest.config.js — generated by Compose test-bootstrap',
380
+ "export default { testMatch: ['**/test/**/*.test.js'] };",
381
+ '',
382
+ ].join('\n'), 'utf-8');
383
+ }
384
+
385
+ const setupPath = join(testDir, 'setup.js');
386
+ if (!existsSync(setupPath)) {
387
+ writeFileSync(setupPath, [
388
+ '// test/setup.js — shared test helpers',
389
+ '',
390
+ ].join('\n'), 'utf-8');
391
+ }
392
+
393
+ return {
394
+ framework: 'jest',
395
+ configFile: 'jest.config.js',
396
+ testDir: 'test',
397
+ command: 'npx jest',
398
+ };
399
+ }
400
+ }
401
+ }