@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
package/lib/new.js ADDED
@@ -0,0 +1,413 @@
1
+ /**
2
+ * new.js — Product kickoff runner for `compose new`.
3
+ *
4
+ * Orchestrates project creation through a Stratum workflow:
5
+ * research → brainstorm → gate → roadmap → gate → scaffold.
6
+ *
7
+ * Reuses the same dispatch loop pattern as build.js.
8
+ */
9
+
10
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
11
+ import { join, basename } from 'node:path';
12
+
13
+ import { StratumMcpClient } from './stratum-mcp-client.js';
14
+ import { runAndNormalize } from './result-normalizer.js';
15
+ import { buildStepPrompt, buildRetryPrompt, buildGateContext } from './step-prompt.js';
16
+ import { promptGate } from './gate-prompt.js';
17
+ import { VisionWriter } from './vision-writer.js';
18
+
19
+ import { validateStep } from './step-validator.js';
20
+
21
+ import { ClaudeSDKConnector } from '../server/connectors/claude-sdk-connector.js';
22
+ import { CodexConnector } from '../server/connectors/codex-connector.js';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Agent registry (same as build.js)
26
+ // ---------------------------------------------------------------------------
27
+
28
+ const DEFAULT_AGENTS = new Map([
29
+ ['claude', (opts) => new ClaudeSDKConnector(opts)],
30
+ ['codex', (opts) => new CodexConnector(opts)],
31
+ ]);
32
+
33
+ function defaultConnectorFactory(agentType, opts) {
34
+ const factory = DEFAULT_AGENTS.get(agentType);
35
+ if (!factory) throw new Error(`Unknown agent type: ${agentType}`);
36
+ return factory(opts);
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Main
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Run the product kickoff pipeline.
45
+ *
46
+ * @param {string} intent - Product description / intent
47
+ * @param {object} opts
48
+ * @param {string} [opts.cwd] - Working directory (default: process.cwd())
49
+ * @param {string} [opts.projectName] - Project name override
50
+ * @param {boolean} [opts.skipResearch] - Skip the research step
51
+ * @param {Function} [opts.connectorFactory] - Override agent connector creation (for testing)
52
+ * @param {object} [opts.gateOpts] - Options for gate prompt (input/output streams)
53
+ */
54
+ export async function runNew(intent, opts = {}) {
55
+ const cwd = opts.cwd ?? process.cwd();
56
+ const getConnector = opts.connectorFactory ?? defaultConnectorFactory;
57
+ const projectName = opts.projectName ?? basename(cwd);
58
+ const skipResearch = opts.skipResearch ?? false;
59
+
60
+ // Check for existing design doc — use as enriched intent if present
61
+ const designDocPath = join(cwd, 'docs', 'design.md');
62
+ if (existsSync(designDocPath)) {
63
+ const designDoc = readFileSync(designDocPath, 'utf-8');
64
+ console.log('Found design doc at docs/design.md — using as enriched intent');
65
+ intent = `${intent}\n\n## Design Document\n${designDoc}`;
66
+ }
67
+
68
+ // Resolve project paths
69
+ const composeDir = join(cwd, '.compose');
70
+ const dataDir = join(composeDir, 'data');
71
+
72
+ // Ensure compose is initialized
73
+ const configPath = join(composeDir, 'compose.json');
74
+ if (!existsSync(configPath)) {
75
+ throw new Error(`No .compose/compose.json found at ${cwd}. Run 'compose init' first.`);
76
+ }
77
+
78
+ // Ensure discovery dir exists for brainstorm output
79
+ mkdirSync(join(cwd, 'docs', 'discovery'), { recursive: true });
80
+
81
+ // Load kickoff spec
82
+ const specPath = join(cwd, 'pipelines', 'new.stratum.yaml');
83
+ if (!existsSync(specPath)) {
84
+ throw new Error(`Kickoff spec not found: ${specPath}. Run 'compose init' to get default pipelines.`);
85
+ }
86
+
87
+ // If skipResearch, temporarily add skip_if to research step
88
+ if (skipResearch) {
89
+ const { parse, stringify } = await import('yaml');
90
+ const spec = parse(readFileSync(specPath, 'utf-8'));
91
+ const researchStep = spec.flows?.new?.steps?.find(s => s.id === 'research');
92
+ if (researchStep && !researchStep.skip_if) {
93
+ researchStep.skip_if = 'true';
94
+ researchStep.skip_reason = 'Skipped by user (questionnaire)';
95
+ writeFileSync(specPath, stringify(spec, { lineWidth: 120 }));
96
+ console.log('Skipping research step (per questionnaire).\n');
97
+ }
98
+ }
99
+
100
+ const specYaml = readFileSync(specPath, 'utf-8');
101
+
102
+ // Parse spec to extract validate configs per step
103
+ const { parse: parseYaml, stringify: stringifyYaml } = await import('yaml');
104
+ const specObj = parseYaml(specYaml);
105
+ const validateConfigs = new Map();
106
+ for (const step of specObj.flows?.new?.steps ?? []) {
107
+ if (step.validate) {
108
+ validateConfigs.set(step.id, step.validate);
109
+ delete step.validate; // strip before sending to stratum
110
+ }
111
+ }
112
+ // Re-serialize with validate fields stripped
113
+ const cleanSpecYaml = stringifyYaml(specObj, { lineWidth: 120 });
114
+
115
+ // Vision writer
116
+ const visionWriter = new VisionWriter(dataDir);
117
+ const itemId = await visionWriter.ensureFeatureItem(projectName, projectName);
118
+
119
+ // Stratum MCP client
120
+ const stratum = new StratumMcpClient();
121
+ await stratum.connect({ cwd });
122
+
123
+ try {
124
+ console.log(`Starting product kickoff for "${projectName}"...`);
125
+ console.log(`Intent: ${intent}\n`);
126
+
127
+ let response = await stratum.plan(cleanSpecYaml, 'new', { projectName, intent });
128
+
129
+ await visionWriter.updateItemStatus(itemId, 'in_progress');
130
+
131
+ const context = { cwd, featureCode: projectName, projectName, intent };
132
+
133
+ // Dispatch loop — same pattern as build.js
134
+ while (response.status !== 'complete' && response.status !== 'killed') {
135
+ const stepId = response.step_id;
136
+ const flowId = response.flow_id;
137
+ const stepNum = response.step_number ?? '?';
138
+ const totalSteps = response.total_steps ?? '?';
139
+
140
+ if (response.status === 'execute_step') {
141
+ console.log(`[${stepNum}/${totalSteps}] ${stepId}...`);
142
+
143
+ await visionWriter.updateItemPhase(itemId, stepId);
144
+
145
+ const agentType = response.agent ?? 'claude';
146
+ const prompt = buildStepPrompt(response, context);
147
+ const connector = getConnector(agentType, { cwd });
148
+ const { result } = await runAndNormalize(connector, prompt, response);
149
+
150
+ // Agent-as-validator: if step has validate config, check the artifact
151
+ const valConfig = validateConfigs.get(stepId);
152
+ if (valConfig) {
153
+ console.log(` ✓ Validating ${stepId}...`);
154
+ const valConnector = getConnector('claude', { cwd });
155
+ const { valid, issues } = await validateStep({
156
+ artifact: valConfig.artifact,
157
+ criteria: valConfig.criteria,
158
+ stepId,
159
+ connector: valConnector,
160
+ });
161
+ if (!valid) {
162
+ console.log(` ✗ Validation failed:`);
163
+ for (const issue of issues) console.log(` - ${issue}`);
164
+ console.log(` ↻ Fix (claude) for ${stepId}`);
165
+ const fixPrompt =
166
+ `Read "${valConfig.artifact}" and fix these issues:\n` +
167
+ issues.map(i => `- ${i}`).join('\n') + '\n\n' +
168
+ `Update the file in place. Do not skip any issue.\n\n` +
169
+ `## Context\nWorking directory: ${cwd}\nProject: ${projectName}`;
170
+ const fixConnector = getConnector('claude', { cwd });
171
+ await runAndNormalize(fixConnector, fixPrompt, response);
172
+ }
173
+ }
174
+
175
+ // Print step summary
176
+ const valConfig2 = validateConfigs.get(stepId);
177
+ const artifactPath = valConfig2?.artifact;
178
+ if (result?.summary) {
179
+ console.log(` ✓ ${result.summary}`);
180
+ } else if (artifactPath && existsSync(join(cwd, artifactPath))) {
181
+ // Summarize from artifact — first few lines
182
+ const content = readFileSync(join(cwd, artifactPath), 'utf-8');
183
+ const heading = content.split('\n').find(l => l.startsWith('# '));
184
+ const lineCount = content.split('\n').length;
185
+ console.log(` ✓ Wrote ${artifactPath} (${lineCount} lines)${heading ? ' — ' + heading.replace(/^#+\s*/, '') : ''}`);
186
+ } else {
187
+ console.log(` ✓ ${stepId} complete`);
188
+ }
189
+
190
+ response = await stratum.stepDone(flowId, stepId, result ?? { summary: 'Step complete' });
191
+
192
+ } else if (response.status === 'await_gate') {
193
+ console.log(`\nGate: ${stepId}`);
194
+
195
+ // Show the artifact that's being gated so user can make an informed decision
196
+ // Try multiple sources for the prior step: gate's on_revise, depends_on, or spec lookup
197
+ const priorStepId = response.on_revise ?? response.depends_on?.[0]
198
+ ?? specObj.flows?.new?.steps?.find(s => s.id === stepId)?.on_revise;
199
+ const priorValConfig = priorStepId ? validateConfigs.get(priorStepId) : null;
200
+ if (priorValConfig?.artifact) {
201
+ const artPath = join(cwd, priorValConfig.artifact);
202
+ if (existsSync(artPath)) {
203
+ const content = readFileSync(artPath, 'utf-8');
204
+ console.log(`\n--- ${priorValConfig.artifact} ---`);
205
+ // Show full content (it's a discovery doc, should be readable)
206
+ const lines = content.split('\n');
207
+ if (lines.length <= 80) {
208
+ console.log(content);
209
+ } else {
210
+ console.log(lines.slice(0, 60).join('\n'));
211
+ console.log(`\n... (${lines.length - 60} more lines — see ${priorValConfig.artifact})`);
212
+ }
213
+ console.log(`--- end ---\n`);
214
+ }
215
+ }
216
+
217
+ const gateId = await visionWriter.createGate(flowId, stepId, itemId);
218
+
219
+ // Resolve artifact path for this gate
220
+ const gateArtifact = priorValConfig?.artifact
221
+ ? join(cwd, priorValConfig.artifact)
222
+ : null;
223
+
224
+ // Agent Q&A callback for interactive gate — with full workflow context
225
+ const gatePreamble = buildGateContext(response, context, null);
226
+ const askAgent = async (question, artifactPath) => {
227
+ const connector = getConnector('claude', { cwd });
228
+ const fileRef = artifactPath
229
+ ? `Read the file "${artifactPath}" and answer`
230
+ : `Look at the project files in the working directory and answer`;
231
+ const qaPrompt =
232
+ `${gatePreamble}\n\n---\n\n` +
233
+ `${fileRef} this question concisely:\n\n` +
234
+ `${question}\n\n` +
235
+ `Keep your answer brief — 2-3 sentences max.`;
236
+ const parts = [];
237
+ for await (const event of connector.run(qaPrompt, {})) {
238
+ if (event.type === 'assistant' && event.content) parts.push(event.content);
239
+ if (event.type === 'result' && event.content && parts.length === 0) parts.push(event.content);
240
+ }
241
+ return parts.join('') || '(no answer)';
242
+ };
243
+
244
+ const { outcome, rationale } = await promptGate(response, {
245
+ ...(opts.gateOpts ?? {}),
246
+ artifact: gateArtifact ?? cwd,
247
+ askAgent,
248
+ });
249
+ await visionWriter.resolveGate(gateId, outcome);
250
+
251
+ // Inject gate decision into context so the re-run step sees reviewer feedback
252
+ if (!context.stepHistory) context.stepHistory = [];
253
+ context.stepHistory.push({
254
+ stepId,
255
+ artifact: null,
256
+ summary: `Gate ${outcome}${rationale ? ': ' + rationale : ''}`,
257
+ outcome,
258
+ });
259
+
260
+ response = await stratum.gateResolve(flowId, stepId, outcome, rationale, 'human');
261
+
262
+ } else if (response.status === 'execute_flow') {
263
+ const parentFlowId = response.parent_flow_id;
264
+ const parentStepId = response.parent_step_id;
265
+ const childFlowName = response.child_flow_name ?? 'sub-flow';
266
+ console.log(`[sub-flow] ${childFlowName}...`);
267
+
268
+ const childResult = await executeChildFlow(
269
+ response, stratum, getConnector, context,
270
+ visionWriter, itemId, opts.gateOpts ?? {}
271
+ );
272
+
273
+ response = await stratum.stepDone(parentFlowId, parentStepId, childResult);
274
+
275
+ } else if (response.status === 'ensure_failed' || response.status === 'schema_failed') {
276
+ console.log(` ↻ Retrying ${stepId} (postconditions failed)`);
277
+ const violations = response.violations ?? [];
278
+ const agentType = response.agent ?? 'claude';
279
+
280
+ // Fix pass before retry
281
+ const fixAgent = agentType === 'codex' ? 'claude' : agentType;
282
+ console.log(` ↻ Fix (${fixAgent}) for ${stepId}`);
283
+ const fixPrompt =
284
+ `Fix step "${stepId}" — postconditions failed:\n` +
285
+ violations.map(v => `- ${v}`).join('\n') + '\n\n' +
286
+ `Fix every issue. Do not skip any.\n\n` +
287
+ `## Context\nWorking directory: ${cwd}\nProject: ${projectName}`;
288
+ const fixConnector = getConnector(fixAgent, { cwd });
289
+ await runAndNormalize(fixConnector, fixPrompt, response);
290
+
291
+ console.log(` ↻ Retrying ${stepId} (${agentType})`);
292
+ const prompt = buildRetryPrompt(response, violations, context);
293
+ const connector = getConnector(agentType, { cwd });
294
+ const { result } = await runAndNormalize(connector, prompt, response);
295
+
296
+ response = await stratum.stepDone(
297
+ response.flow_id, response.step_id,
298
+ result ?? { summary: 'Retry complete' }
299
+ );
300
+
301
+ } else {
302
+ console.warn(`Unknown dispatch status: ${response.status}`);
303
+ break;
304
+ }
305
+ }
306
+
307
+ // Flow complete
308
+ if (response.status === 'complete') {
309
+ console.log('\nProduct kickoff complete.');
310
+ await visionWriter.updateItemStatus(itemId, 'complete');
311
+ } else if (response.status === 'killed') {
312
+ console.log('\nProduct kickoff killed.');
313
+ await visionWriter.updateItemStatus(itemId, 'killed');
314
+ }
315
+
316
+ // Write audit trace
317
+ if (response.trace) {
318
+ try {
319
+ const auditPath = join(cwd, 'docs', 'discovery', 'kickoff-audit.json');
320
+ writeFileSync(auditPath, JSON.stringify(response, null, 2));
321
+ console.log(`Audit trace written to docs/discovery/kickoff-audit.json`);
322
+ } catch (err) {
323
+ console.warn(`Warning: could not write audit trace: ${err.message}`);
324
+ }
325
+ }
326
+
327
+ // Summary
328
+ if (response.status === 'complete') {
329
+ console.log('\nNext steps:');
330
+ console.log(' compose pipeline show # review the build pipeline');
331
+ console.log(' compose build <FEATURE-CODE> # build the first feature');
332
+ }
333
+
334
+ } finally {
335
+ await stratum.close();
336
+ }
337
+ }
338
+
339
+ // ---------------------------------------------------------------------------
340
+ // Child flow execution (simplified from build.js — no active-build tracking)
341
+ // ---------------------------------------------------------------------------
342
+
343
+ async function executeChildFlow(
344
+ flowDispatch, stratum, getConnector, context,
345
+ visionWriter, itemId, gateOpts
346
+ ) {
347
+ let resp = flowDispatch.child_step;
348
+ const childFlowId = flowDispatch.child_flow_id;
349
+ const childFlowName = flowDispatch.child_flow_name ?? 'sub-flow';
350
+
351
+ while (resp.status !== 'complete' && resp.status !== 'killed') {
352
+ if (resp.status === 'execute_step') {
353
+ console.log(` [${childFlowName}] ${resp.step_id}...`);
354
+ await visionWriter.updateItemPhase(itemId, `${childFlowName}:${resp.step_id}`);
355
+
356
+ const agentType = resp.agent ?? 'claude';
357
+ const prompt = buildStepPrompt(resp, context);
358
+ const connector = getConnector(agentType, { cwd: context.cwd });
359
+ const { result } = await runAndNormalize(connector, prompt, resp);
360
+
361
+ resp = await stratum.stepDone(
362
+ childFlowId, resp.step_id,
363
+ result ?? { summary: 'Step complete' }
364
+ );
365
+
366
+ } else if (resp.status === 'await_gate') {
367
+ console.log(` [${childFlowName}] Gate: ${resp.step_id}`);
368
+ const gateId = await visionWriter.createGate(childFlowId, resp.step_id, itemId);
369
+ const { outcome, rationale } = await promptGate(resp, gateOpts);
370
+ await visionWriter.resolveGate(gateId, outcome);
371
+ resp = await stratum.gateResolve(childFlowId, resp.step_id, outcome, rationale, 'human');
372
+
373
+ } else if (resp.status === 'ensure_failed' || resp.status === 'schema_failed') {
374
+ const violations = resp.violations ?? [];
375
+ const stepAgent = resp.agent ?? 'claude';
376
+ const fixAgent = stepAgent === 'codex' ? 'claude' : stepAgent;
377
+
378
+ console.log(` [${childFlowName}] ↻ Fix (${fixAgent}) for ${resp.step_id}`);
379
+ const fixPrompt =
380
+ `Fix step "${resp.step_id}" — postconditions failed:\n` +
381
+ violations.map(v => `- ${v}`).join('\n') + '\n\n' +
382
+ `Fix every issue.\n\n` +
383
+ `## Context\nWorking directory: ${context.cwd}\nProject: ${context.projectName}`;
384
+ const fixConnector = getConnector(fixAgent, { cwd: context.cwd });
385
+ await runAndNormalize(fixConnector, fixPrompt, resp);
386
+
387
+ console.log(` [${childFlowName}] ↻ Retrying ${resp.step_id} (${stepAgent})`);
388
+ const prompt = buildRetryPrompt(resp, violations, context);
389
+ const connector = getConnector(stepAgent, { cwd: context.cwd });
390
+ const { result } = await runAndNormalize(connector, prompt, resp);
391
+
392
+ resp = await stratum.stepDone(
393
+ resp.flow_id ?? childFlowId, resp.step_id,
394
+ result ?? { summary: 'Retry complete' }
395
+ );
396
+
397
+ } else if (resp.status === 'execute_flow') {
398
+ const nestedParentFlowId = resp.parent_flow_id;
399
+ const nestedParentStepId = resp.parent_step_id;
400
+ const nestedResult = await executeChildFlow(
401
+ resp, stratum, getConnector, context,
402
+ visionWriter, itemId, gateOpts
403
+ );
404
+ resp = await stratum.stepDone(nestedParentFlowId, nestedParentStepId, nestedResult);
405
+
406
+ } else {
407
+ console.warn(` [${childFlowName}] Unknown status: ${resp.status}`);
408
+ break;
409
+ }
410
+ }
411
+
412
+ return resp;
413
+ }