@smartmemory/compose 0.2.57-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.
Files changed (104) hide show
  1. package/README.md +2 -2
  2. package/bin/compose.js +55 -54
  3. package/contracts/feature-json.schema.json +16 -0
  4. package/dist/assets/{App-BmhlHOXF.js → App-DJ5xk_Wx.js} +219 -219
  5. package/dist/assets/{abnfDiagram-VRR7QNED-CMOTYAnt.js → abnfDiagram-VRR7QNED-BPWGdCFx.js} +1 -1
  6. package/dist/assets/{arc-BJAv_6dL.js → arc-DX5jqmcO.js} +1 -1
  7. package/dist/assets/{architectureDiagram-ZJ3FMSHR-xa4DLUcp.js → architectureDiagram-ZJ3FMSHR-B6jmNVw7.js} +1 -1
  8. package/dist/assets/{blockDiagram-677ZJIJ3-BdkDqeQF.js → blockDiagram-677ZJIJ3-DAx2i-sB.js} +1 -1
  9. package/dist/assets/{c4Diagram-LMCZKHZV-DiNrLIuL.js → c4Diagram-LMCZKHZV-zmsZCplj.js} +1 -1
  10. package/dist/assets/channel-vsDnTvkh.js +1 -0
  11. package/dist/assets/{chunk-2Q5K7J3B-BsjtgrSL.js → chunk-2Q5K7J3B-3QSh6sI7.js} +1 -1
  12. package/dist/assets/{chunk-32BRIVSS-DpImULWy.js → chunk-32BRIVSS-BiH5Di_y.js} +1 -1
  13. package/dist/assets/{chunk-5VM5RSS4-DTdG5JnR.js → chunk-5VM5RSS4-C0EfjFLd.js} +1 -1
  14. package/dist/assets/{chunk-EX3LRPZG-Cj1jNuKY.js → chunk-EX3LRPZG-BGyfmVm-.js} +1 -1
  15. package/dist/assets/{chunk-JWPE2WC7-DwQ0wwWn.js → chunk-JWPE2WC7-Ba0mrNlg.js} +1 -1
  16. package/dist/assets/{chunk-MOJQB5TN-DZZa7unc.js → chunk-MOJQB5TN-D3QO9EzH.js} +1 -1
  17. package/dist/assets/{chunk-RYQCIY6F-CgdjqYQj.js → chunk-RYQCIY6F-D1uvNA6d.js} +1 -1
  18. package/dist/assets/{chunk-V7JOEXUC-DrPIj6XI.js → chunk-V7JOEXUC-fihlnodT.js} +1 -1
  19. package/dist/assets/{chunk-VR4S4FIN-BBUOVC5W.js → chunk-VR4S4FIN-nmRUEo1H.js} +1 -1
  20. package/dist/assets/{chunk-XXDRQBXY-BEIiZ6gS.js → chunk-XXDRQBXY-CM693yEg.js} +1 -1
  21. package/dist/assets/classDiagram-OUVF2IWQ-DO-wdSFZ.js +1 -0
  22. package/dist/assets/classDiagram-v2-EOCWNBFH-DO-wdSFZ.js +1 -0
  23. package/dist/assets/{cose-bilkent-JH36ORCC-ClFTQEyD.js → cose-bilkent-JH36ORCC-CkacPn8W.js} +1 -1
  24. package/dist/assets/{cynefin-VYW2F7L2-5Bp7xFO0.js → cynefin-VYW2F7L2-BiHaIptG.js} +1 -1
  25. package/dist/assets/{cynefinDiagram-TSTJHNR4-C-0QlSda.js → cynefinDiagram-TSTJHNR4-CdKxMmiy.js} +1 -1
  26. package/dist/assets/{dagre-VKFMJZFB-DLkqsfBy.js → dagre-VKFMJZFB-BQLY5y_W.js} +1 -1
  27. package/dist/assets/{diagram-FQU43EPY-Cwh_tJ_r.js → diagram-FQU43EPY-D7uMBHvq.js} +1 -1
  28. package/dist/assets/{diagram-G47NLZAW-CvvAjulf.js → diagram-G47NLZAW-B3Z1cuH7.js} +1 -1
  29. package/dist/assets/{diagram-NH7WQ7WH-CCoyx8_e.js → diagram-NH7WQ7WH-BZyRD45e.js} +1 -1
  30. package/dist/assets/{diagram-OA4YK3LP-DfjhKYCp.js → diagram-OA4YK3LP-DcThOTt7.js} +1 -1
  31. package/dist/assets/{diagram-WEI45ONY-Vjz0ygfB.js → diagram-WEI45ONY-BcgRAkqY.js} +1 -1
  32. package/dist/assets/{ebnfDiagram-CCIWWBDH-GsA8c6g2.js → ebnfDiagram-CCIWWBDH-CGwfO_xH.js} +1 -1
  33. package/dist/assets/{erDiagram-Q63AITRT-Cc0lJ9sQ.js → erDiagram-Q63AITRT-pV58-Ncc.js} +1 -1
  34. package/dist/assets/{flowDiagram-23GEKE2U-C_sotK-k.js → flowDiagram-23GEKE2U-DJ_SqE8h.js} +1 -1
  35. package/dist/assets/{ganttDiagram-NO4QXBWP-BBtu_WUe.js → ganttDiagram-NO4QXBWP-Dgy0Iyss.js} +1 -1
  36. package/dist/assets/{gitGraphDiagram-IHSO6WYX-BV_aSB8-.js → gitGraphDiagram-IHSO6WYX-CbiZw9fb.js} +1 -1
  37. package/dist/assets/{index-CKfOVv2N.js → index-DZTJEk-y.js} +2 -2
  38. package/dist/assets/{infoDiagram-FWYZ7A6U-DMAvNGtX.js → infoDiagram-FWYZ7A6U-CtsyyEc-.js} +1 -1
  39. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DfQB91MZ.js → ishikawaDiagram-FXEZZL3T-BJilNFkK.js} +1 -1
  40. package/dist/assets/{journeyDiagram-5HDEW3XC-Bq4Llm9o.js → journeyDiagram-5HDEW3XC-C2UCMP4t.js} +1 -1
  41. package/dist/assets/{kanban-definition-HUTT4EX6-DTXHiDiE.js → kanban-definition-HUTT4EX6-DmLDJBRy.js} +1 -1
  42. package/dist/assets/{linear-BQSYZcVg.js → linear-C1paCqE7.js} +1 -1
  43. package/dist/assets/{mindmap-definition-LN4V7U3C-KuH2NIj0.js → mindmap-definition-LN4V7U3C-CX-RxKVn.js} +1 -1
  44. package/dist/assets/{pegDiagram-2B236MQR-BKoNGQFr.js → pegDiagram-2B236MQR-CTU32H2W.js} +1 -1
  45. package/dist/assets/{pieDiagram-ENE6RG2P-EpYv162Y.js → pieDiagram-ENE6RG2P-D8L1aYoo.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-ABIIQ3AL-C9deUjQ2.js → quadrantDiagram-ABIIQ3AL-hQ3bXosy.js} +1 -1
  47. package/dist/assets/{railroadDiagram-RFXS5EU6-Csqw2hK9.js → railroadDiagram-RFXS5EU6--_vuYcda.js} +1 -1
  48. package/dist/assets/{requirementDiagram-TGXJPOKE-DCuz3emr.js → requirementDiagram-TGXJPOKE-C6h_m3Az.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-HTMAVEWB-BTeTL24f.js → sankeyDiagram-HTMAVEWB-Dpc7CfIQ.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-DBY2YBRQ-Cf1ZkTwO.js → sequenceDiagram-DBY2YBRQ-CK5ZzbvJ.js} +1 -1
  51. package/dist/assets/{sizeCapture-X5ZJPWSS-D3Lbewzg.js → sizeCapture-X5ZJPWSS-BAxVvM9G.js} +1 -1
  52. package/dist/assets/{stateDiagram-2N3HPSRC-O8l37yJI.js → stateDiagram-2N3HPSRC-7j33_2PY.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-6OUMAXLB-GviN0FpJ.js +1 -0
  54. package/dist/assets/{swimlanes-5IMT3BWC-BikmTh8b.js → swimlanes-5IMT3BWC-CBG2yOob.js} +2 -2
  55. package/dist/assets/swimlanesDiagram-G3AALYLV-D0wMoxl1.js +8 -0
  56. package/dist/assets/{timeline-definition-FHXFAJF6-BOxSosG0.js → timeline-definition-FHXFAJF6-BOnZkQvc.js} +1 -1
  57. package/dist/assets/{vennDiagram-L72KCM5P-BSClOErZ.js → vennDiagram-L72KCM5P-D52NhIfj.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-EHGQE667-DHFRjOK0.js → wardleyDiagram-EHGQE667-YoPY2ZU-.js} +1 -1
  59. package/dist/assets/{xychartDiagram-FW5EYKEG-Dh1v4x-O.js → xychartDiagram-FW5EYKEG-BanNfgtO.js} +1 -1
  60. package/dist/index.html +1 -1
  61. package/lib/build-all.js +0 -5
  62. package/lib/build-stream-schema.js +1 -1
  63. package/lib/build.js +1809 -2646
  64. package/lib/consumer-fanout.js +1317 -0
  65. package/lib/escalation.js +69 -0
  66. package/lib/feature-validator.js +6 -4
  67. package/lib/feature-writer.js +58 -3
  68. package/lib/flow-state.js +15 -14
  69. package/lib/gsd-budget.js +48 -10
  70. package/lib/gsd-prompt.js +3 -4
  71. package/lib/gsd-stuck.js +1 -1
  72. package/lib/gsd.js +303 -149
  73. package/lib/lane-gate.js +200 -0
  74. package/lib/local-claude-connector.js +149 -0
  75. package/lib/new.js +162 -307
  76. package/lib/result-normalizer.js +233 -18
  77. package/lib/review-lenses.js +1 -1
  78. package/lib/step-prompt.js +41 -119
  79. package/lib/stratum-engine.js +297 -0
  80. package/lib/stratum-mcp-client.js +224 -213
  81. package/lib/triage.js +144 -0
  82. package/lib/vocabulary-compliance.js +268 -0
  83. package/lib/vocabulary-inject.js +1 -36
  84. package/package.json +2 -1
  85. package/pipelines/build-quick.stratum.yaml +5 -17
  86. package/pipelines/build.profiles.json +11 -0
  87. package/pipelines/build.stratum.yaml +288 -467
  88. package/pipelines/gsd.stratum.yaml +73 -125
  89. package/pipelines/new.stratum.yaml +68 -149
  90. package/server/build-routes.js +4 -1
  91. package/server/design-routes.js +37 -21
  92. package/server/index.js +13 -21
  93. package/server/lifecycle-guard.js +1 -1
  94. package/server/pipeline-routes.js +113 -31
  95. package/server/stratum-client.js +33 -47
  96. package/server/stratum-sync.js +3 -4
  97. package/server/vision-server.js +1 -1
  98. package/dist/assets/channel-CYErfopw.js +0 -1
  99. package/dist/assets/classDiagram-OUVF2IWQ-qCC_hnXu.js +0 -1
  100. package/dist/assets/classDiagram-v2-EOCWNBFH-qCC_hnXu.js +0 -1
  101. package/dist/assets/stateDiagram-v2-6OUMAXLB-BWqk_9py.js +0 -1
  102. package/dist/assets/swimlanesDiagram-G3AALYLV-C60ICdks.js +0 -8
  103. package/lib/connector-factory-shim.js +0 -167
  104. package/server/agent-mcp.js +0 -10
package/lib/new.js CHANGED
@@ -1,391 +1,246 @@
1
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.
2
+ * Product kickoff runner for `compose new` using the Stratum TS ready loop.
8
3
  */
9
4
 
10
5
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
11
6
  import { join, basename } from 'node:path';
7
+ import YAML from 'yaml';
12
8
 
13
9
  import { StratumMcpClient } from './stratum-mcp-client.js';
14
10
  import { runAndNormalize } from './result-normalizer.js';
15
- import { installFactoryShim } from './connector-factory-shim.js';
16
- import { buildStepPrompt, buildRetryPrompt, buildGateContext } from './step-prompt.js';
11
+ import { buildStepPrompt, buildGateContext } from './step-prompt.js';
17
12
  import { promptGate } from './gate-prompt.js';
18
13
  import { VisionWriter } from './vision-writer.js';
19
14
  import { readFlowRound } from './flow-state.js';
20
-
21
15
  import { validateStep } from './step-validator.js';
22
16
 
23
- // STRAT-DEDUP-AGENTRUN-V3: connectors live in stratum-mcp; runAndNormalize
24
- // dispatches via stratum.agentRun(...) directly.
17
+ const KICKOFF_VALIDATION = new Map([
18
+ ['research', {
19
+ artifact: 'docs/discovery/research.md',
20
+ criteria: [
21
+ 'Contains at least 2 existing tools or prior art entries',
22
+ 'Mentions architectural patterns or common approaches',
23
+ 'Lists risks or pitfalls',
24
+ ],
25
+ }],
26
+ ['brainstorm', {
27
+ artifact: 'docs/discovery/brainstorm.md',
28
+ criteria: [
29
+ 'Contains at least 3 features with short codes',
30
+ 'Contains user stories in As a, I want, so that format',
31
+ 'Contains at least 2 architecture options with trade-offs',
32
+ ],
33
+ }],
34
+ ['roadmap', {
35
+ artifact: 'ROADMAP.md',
36
+ criteria: [
37
+ 'Contains a markdown table with feature codes and status columns',
38
+ 'Features are organized into phases',
39
+ 'All features have PLANNED status',
40
+ ],
41
+ }],
42
+ ]);
43
+
44
+ function outputContract(spec, stepId) {
45
+ const step = spec.flows?.new?.steps?.find((candidate) => candidate.id === stepId);
46
+ const fields = typeof step?.out === 'string' ? spec.contracts?.[step.out] : null;
47
+ return fields && typeof fields === 'object' ? fields : {};
48
+ }
25
49
 
26
- /**
27
- * Run the product kickoff pipeline.
28
- *
29
- * @param {string} intent - Product description / intent
30
- * @param {object} opts
31
- * @param {string} [opts.cwd] - Working directory (default: process.cwd())
32
- * @param {string} [opts.projectName] - Project name override
33
- * @param {boolean} [opts.skipResearch] - Skip the research step
34
- * @param {object} [opts.gateOpts] - Options for gate prompt (input/output streams)
35
- */
50
+ function waitingGate(audit) {
51
+ return Object.entries(audit?.steps ?? {})
52
+ .find(([, state]) => state?.status === 'waiting_gate');
53
+ }
54
+
55
+ function gateDispatch(spec, stepId) {
56
+ const step = spec.flows?.new?.steps?.find((candidate) => candidate.id === stepId);
57
+ if (!step?.gate) throw new Error(`Kickoff gate ${stepId} is missing from the local TS spec`);
58
+ return { step_id: stepId, ...step.gate };
59
+ }
60
+
61
+ /** Run the product kickoff pipeline. */
36
62
  export async function runNew(intent, opts = {}) {
37
63
  const cwd = opts.cwd ?? process.cwd();
38
64
  const projectName = opts.projectName ?? basename(cwd);
39
- const skipResearch = opts.skipResearch ?? false;
40
65
 
41
- // Check for existing design doc — use as enriched intent if present
42
66
  const designDocPath = join(cwd, 'docs', 'design.md');
43
67
  if (existsSync(designDocPath)) {
44
- const designDoc = readFileSync(designDocPath, 'utf-8');
45
68
  console.log('Found design doc at docs/design.md — using as enriched intent');
46
- intent = `${intent}\n\n## Design Document\n${designDoc}`;
69
+ intent = `${intent}\n\n## Design Document\n${readFileSync(designDocPath, 'utf-8')}`;
47
70
  }
48
71
 
49
- // Resolve project paths
50
72
  const composeDir = join(cwd, '.compose');
51
73
  const dataDir = join(composeDir, 'data');
52
-
53
- // Ensure compose is initialized
54
- const configPath = join(composeDir, 'compose.json');
55
- if (!existsSync(configPath)) {
74
+ if (!existsSync(join(composeDir, 'compose.json'))) {
56
75
  throw new Error(`No .compose/compose.json found at ${cwd}. Run 'compose init' first.`);
57
76
  }
58
-
59
- // Ensure discovery dir exists for brainstorm output
60
77
  mkdirSync(join(cwd, 'docs', 'discovery'), { recursive: true });
61
78
 
62
- // Load kickoff spec
63
79
  const specPath = join(cwd, 'pipelines', 'new.stratum.yaml');
64
80
  if (!existsSync(specPath)) {
65
81
  throw new Error(`Kickoff spec not found: ${specPath}. Run 'compose init' to get default pipelines.`);
66
82
  }
67
83
 
68
- // If skipResearch, temporarily add skip_if to research step
69
- if (skipResearch) {
70
- const { parse, stringify } = await import('yaml');
71
- const spec = parse(readFileSync(specPath, 'utf-8'));
72
- const researchStep = spec.flows?.new?.steps?.find(s => s.id === 'research');
73
- if (researchStep && !researchStep.skip_if) {
74
- researchStep.skip_if = 'true';
75
- researchStep.skip_reason = 'Skipped by user (questionnaire)';
76
- writeFileSync(specPath, stringify(spec, { lineWidth: 120 }));
77
- console.log('Skipping research step (per questionnaire).\n');
78
- }
84
+ const spec = YAML.parse(readFileSync(specPath, 'utf-8'));
85
+ if (opts.skipResearch) {
86
+ const research = spec.flows?.new?.steps?.find((step) => step.id === 'research');
87
+ if (research) research.when = 'false';
88
+ console.log('Skipping research step (per questionnaire).\n');
79
89
  }
80
90
 
81
- const specYaml = readFileSync(specPath, 'utf-8');
91
+ const validateConfigs = KICKOFF_VALIDATION;
92
+ const cleanSpecYaml = YAML.stringify(spec, { lineWidth: 120 });
82
93
 
83
- // Parse spec to extract validate configs per step
84
- const { parse: parseYaml, stringify: stringifyYaml } = await import('yaml');
85
- const specObj = parseYaml(specYaml);
86
- const validateConfigs = new Map();
87
- for (const step of specObj.flows?.new?.steps ?? []) {
88
- if (step.validate) {
89
- validateConfigs.set(step.id, step.validate);
90
- delete step.validate; // strip before sending to stratum
91
- }
92
- }
93
- // Re-serialize with validate fields stripped
94
- const cleanSpecYaml = stringifyYaml(specObj, { lineWidth: 120 });
95
-
96
- // Vision writer
97
94
  const visionWriter = new VisionWriter(dataDir);
98
95
  const itemId = await visionWriter.ensureFeatureItem(projectName, projectName);
99
-
100
- // Stratum MCP client (test override permitted via opts.stratum)
101
96
  const stratum = opts.stratum ?? new StratumMcpClient();
102
97
  if (!opts.stratum) await stratum.connect({ cwd });
103
98
 
104
- if (opts.connectorFactory && !opts.stratum) {
105
- installFactoryShim(stratum, opts.connectorFactory, cwd);
106
- }
107
-
108
99
  try {
109
100
  console.log(`Starting product kickoff for "${projectName}"...`);
110
101
  console.log(`Intent: ${intent}\n`);
111
-
112
- let response = await stratum.plan(cleanSpecYaml, 'new', { projectName, intent });
113
-
102
+ let response = await stratum.plan(
103
+ cleanSpecYaml,
104
+ 'new',
105
+ { projectName, intent },
106
+ { workspaceRoot: cwd },
107
+ );
108
+ const flowId = response.runId;
114
109
  await visionWriter.updateItemStatus(itemId, 'in_progress');
115
-
116
- const context = { cwd, featureCode: projectName, projectName, intent };
117
-
118
- // Dispatch loop same pattern as build.js
119
- while (response.status !== 'complete' && response.status !== 'killed') {
120
- const stepId = response.step_id;
121
- const flowId = response.flow_id;
122
- const stepNum = response.step_number ?? '?';
123
- const totalSteps = response.total_steps ?? '?';
124
-
125
- if (response.status === 'execute_step') {
126
- console.log(`[${stepNum}/${totalSteps}] ${stepId}...`);
127
-
128
- await visionWriter.updateItemPhase(itemId, stepId);
129
-
130
- const agentType = response.agent ?? 'claude';
131
- const prompt = buildStepPrompt(response, context);
132
- const { result } = await runAndNormalize(null, prompt, response, { stratum, cwd });
133
-
134
- // Agent-as-validator: if step has validate config, check the artifact
135
- const valConfig = validateConfigs.get(stepId);
136
- if (valConfig) {
137
- console.log(` ✓ Validating ${stepId}...`);
138
- const { valid, issues } = await validateStep({
139
- artifact: valConfig.artifact,
140
- criteria: valConfig.criteria,
141
- stepId,
110
+ const context = { cwd, featureCode: projectName, projectName, intent, stepHistory: [] };
111
+
112
+ while (!['completed', 'failed', 'budget_exhausted'].includes(response.status)) {
113
+ if (response.status === 'ready') {
114
+ const step = response.ready?.[0];
115
+ if (!step) throw new Error('Stratum returned ready without a kickoff step');
116
+ const dispatch = {
117
+ ...step,
118
+ step_id: step.id,
119
+ flow_id: response.runId,
120
+ intent: step.do,
121
+ output_fields: outputContract(spec, step.id),
122
+ has_out_contract: true,
123
+ };
124
+ console.log(`${step.id}...`);
125
+ await visionWriter.updateItemPhase(itemId, step.id);
126
+ const prompt = buildStepPrompt(dispatch, context);
127
+ const { result } = await runAndNormalize(null, prompt, dispatch, { stratum, cwd });
128
+
129
+ const validation = validateConfigs.get(step.id);
130
+ if (validation) {
131
+ console.log(` ✓ Validating ${step.id}...`);
132
+ const checked = await validateStep({
133
+ artifact: validation.artifact,
134
+ criteria: validation.criteria,
135
+ stepId: step.id,
142
136
  stratum,
143
137
  cwd,
144
138
  });
145
- if (!valid) {
146
- console.log(` ✗ Validation failed:`);
147
- for (const issue of issues) console.log(` - ${issue}`);
148
- console.log(` ↻ Fix (claude) for ${stepId}`);
139
+ if (!checked.valid) {
140
+ console.log(' ✗ Validation failed:');
141
+ for (const issue of checked.issues) console.log(` - ${issue}`);
149
142
  const fixPrompt =
150
- `Read "${valConfig.artifact}" and fix these issues:\n` +
151
- issues.map(i => `- ${i}`).join('\n') + '\n\n' +
152
- `Update the file in place. Do not skip any issue.\n\n` +
143
+ `Read "${validation.artifact}" and fix these issues:\n` +
144
+ checked.issues.map((issue) => `- ${issue}`).join('\n') +
145
+ `\n\nUpdate the file in place. Do not skip any issue.\n\n` +
153
146
  `## Context\nWorking directory: ${cwd}\nProject: ${projectName}`;
154
- const fixDispatch = { ...response, agent: 'claude' };
155
- await runAndNormalize(null, fixPrompt, fixDispatch, { stratum, cwd });
147
+ await runAndNormalize(null, fixPrompt, { ...dispatch, agent: 'claude' }, { stratum, cwd });
156
148
  }
157
149
  }
158
150
 
159
- // Print step summary
160
- const valConfig2 = validateConfigs.get(stepId);
161
- const artifactPath = valConfig2?.artifact;
162
- if (result?.summary) {
163
- console.log(` ✓ ${result.summary}`);
164
- } else if (artifactPath && existsSync(join(cwd, artifactPath))) {
165
- // Summarize from artifact first few lines
166
- const content = readFileSync(join(cwd, artifactPath), 'utf-8');
167
- const heading = content.split('\n').find(l => l.startsWith('# '));
168
- const lineCount = content.split('\n').length;
169
- console.log(` ✓ Wrote ${artifactPath} (${lineCount} lines)${heading ? ' — ' + heading.replace(/^#+\s*/, '') : ''}`);
170
- } else {
171
- console.log(` ✓ ${stepId} complete`);
172
- }
173
-
174
- response = await stratum.stepDone(flowId, stepId, result ?? { summary: 'Step complete' });
151
+ if (result?.summary) console.log(` ✓ ${result.summary}`);
152
+ else console.log(` ✓ ${step.id} complete`);
153
+ context.stepHistory.push({ stepId: step.id, summary: result?.summary ?? 'Step complete' });
154
+ response = await stratum.stepDone(
155
+ response.runId,
156
+ step.id,
157
+ { output: result ?? { summary: 'Step complete' } },
158
+ step.dispatchToken,
159
+ );
160
+ continue;
161
+ }
175
162
 
176
- } else if (response.status === 'await_gate') {
163
+ if (response.status === 'running') {
164
+ const audit = await stratum.audit(response.runId);
165
+ const waiting = waitingGate(audit);
166
+ if (!waiting) throw new Error('Kickoff flow is running without ready work or a waiting gate');
167
+ const [stepId, state] = waiting;
168
+ const dispatch = gateDispatch(spec, stepId);
177
169
  console.log(`\nGate: ${stepId}`);
178
170
 
179
- // Show the artifact that's being gated so user can make an informed decision
180
- // Try multiple sources for the prior step: gate's on_revise, depends_on, or spec lookup
181
- const priorStepId = response.on_revise ?? response.depends_on?.[0]
182
- ?? specObj.flows?.new?.steps?.find(s => s.id === stepId)?.on_revise;
183
- const priorValConfig = priorStepId ? validateConfigs.get(priorStepId) : null;
184
- if (priorValConfig?.artifact) {
185
- const artPath = join(cwd, priorValConfig.artifact);
186
- if (existsSync(artPath)) {
187
- const content = readFileSync(artPath, 'utf-8');
188
- console.log(`\n--- ${priorValConfig.artifact} ---`);
189
- // Show full content (it's a discovery doc, should be readable)
190
- const lines = content.split('\n');
191
- if (lines.length <= 80) {
192
- console.log(content);
193
- } else {
194
- console.log(lines.slice(0, 60).join('\n'));
195
- console.log(`\n... (${lines.length - 60} more lines — see ${priorValConfig.artifact})`);
196
- }
197
- console.log(`--- end ---\n`);
198
- }
171
+ const priorStepId = dispatch.on_revise;
172
+ const priorValidation = validateConfigs.get(priorStepId);
173
+ const artifact = priorValidation?.artifact ? join(cwd, priorValidation.artifact) : null;
174
+ if (artifact && existsSync(artifact)) {
175
+ const content = readFileSync(artifact, 'utf-8');
176
+ console.log(`\n--- ${priorValidation.artifact} ---`);
177
+ const lines = content.split('\n');
178
+ console.log(lines.length <= 80 ? content : `${lines.slice(0, 60).join('\n')}\n\n... (${lines.length - 60} more lines)`);
179
+ console.log('--- end ---\n');
199
180
  }
200
181
 
201
- // COMP-PLAN-GATE-LOOP: round-aware gate id so a revise re-entry mints a
202
- // fresh gate instead of reusing the prior resolved one.
203
- const gateId = await visionWriter.createGate(flowId, stepId, itemId, { round: readFlowRound(flowId) });
204
-
205
- // Resolve artifact path for this gate
206
- const gateArtifact = priorValConfig?.artifact
207
- ? join(cwd, priorValConfig.artifact)
208
- : null;
209
-
210
- // Agent Q&A callback for interactive gate — with full workflow context
211
- const gatePreamble = buildGateContext(response, context, null);
182
+ const gateId = await visionWriter.createGate(
183
+ response.runId,
184
+ stepId,
185
+ itemId,
186
+ { round: readFlowRound(response.runId) },
187
+ );
188
+ const preamble = buildGateContext(dispatch, context, null);
212
189
  const askAgent = async (question, artifactPath) => {
213
190
  const fileRef = artifactPath
214
191
  ? `Read the file "${artifactPath}" and answer`
215
- : `Look at the project files in the working directory and answer`;
216
- const qaPrompt =
217
- `${gatePreamble}\n\n---\n\n` +
218
- `${fileRef} this question concisely:\n\n` +
219
- `${question}\n\n` +
220
- `Keep your answer brief — 2-3 sentences max.`;
221
- const text = await stratum.runAgentText('claude', qaPrompt, { cwd });
222
- return text || '(no answer)';
192
+ : 'Look at the project files in the working directory and answer';
193
+ return stratum.runAgentText(
194
+ 'claude',
195
+ `${preamble}\n\n---\n\n${fileRef} this question concisely:\n\n${question}`,
196
+ { cwd },
197
+ );
223
198
  };
224
-
225
- const { outcome, rationale } = await promptGate(response, {
199
+ const decision = await promptGate(dispatch, {
226
200
  ...(opts.gateOpts ?? {}),
227
- artifact: gateArtifact ?? cwd,
201
+ artifact: artifact ?? cwd,
228
202
  askAgent,
229
203
  });
230
- await visionWriter.resolveGate(gateId, outcome);
231
-
232
- // Inject gate decision into context so the re-run step sees reviewer feedback
233
- if (!context.stepHistory) context.stepHistory = [];
204
+ await visionWriter.resolveGate(gateId, decision.outcome);
234
205
  context.stepHistory.push({
235
206
  stepId,
236
- artifact: null,
237
- summary: `Gate ${outcome}${rationale ? ': ' + rationale : ''}`,
238
- outcome,
207
+ summary: `Gate ${decision.outcome}${decision.rationale ? `: ${decision.rationale}` : ''}`,
208
+ outcome: decision.outcome,
239
209
  });
240
-
241
- response = await stratum.gateResolve(flowId, stepId, outcome, rationale, 'human');
242
-
243
- } else if (response.status === 'execute_flow') {
244
- const parentFlowId = response.parent_flow_id;
245
- const parentStepId = response.parent_step_id;
246
- const childFlowName = response.child_flow_name ?? 'sub-flow';
247
- console.log(`[sub-flow] ${childFlowName}...`);
248
-
249
- const childResult = await executeChildFlow(
250
- response, stratum, context,
251
- visionWriter, itemId, opts.gateOpts ?? {}
252
- );
253
-
254
- response = await stratum.stepDone(parentFlowId, parentStepId, childResult);
255
-
256
- } else if (response.status === 'ensure_failed' || response.status === 'schema_failed') {
257
- console.log(` ↻ Retrying ${stepId} (postconditions failed)`);
258
- const violations = response.violations ?? [];
259
- const agentType = response.agent ?? 'claude';
260
-
261
- // Fix pass before retry
262
- const fixAgent = agentType === 'codex' ? 'claude' : agentType;
263
- console.log(` ↻ Fix (${fixAgent}) for ${stepId}`);
264
- const fixPrompt =
265
- `Fix step "${stepId}" — postconditions failed:\n` +
266
- violations.map(v => `- ${v}`).join('\n') + '\n\n' +
267
- `Fix every issue. Do not skip any.\n\n` +
268
- `## Context\nWorking directory: ${cwd}\nProject: ${projectName}`;
269
- const fixDispatch = { ...response, agent: fixAgent };
270
- await runAndNormalize(null, fixPrompt, fixDispatch, { stratum, cwd });
271
-
272
- console.log(` ↻ Retrying ${stepId} (${agentType})`);
273
- const prompt = buildRetryPrompt(response, violations, context);
274
- const { result } = await runAndNormalize(null, prompt, response, { stratum, cwd });
275
-
276
- response = await stratum.stepDone(
277
- response.flow_id, response.step_id,
278
- result ?? { summary: 'Retry complete' }
210
+ response = await stratum.gateResolve(
211
+ response.runId,
212
+ stepId,
213
+ decision.outcome,
214
+ decision.rationale,
215
+ 'human',
216
+ state.gateToken,
279
217
  );
280
-
281
- } else {
282
- console.warn(`Unknown dispatch status: ${response.status}`);
283
- break;
218
+ continue;
284
219
  }
220
+
221
+ throw new Error(`Unknown TS kickoff status: ${response.status}`);
285
222
  }
286
223
 
287
- // Flow complete
288
- if (response.status === 'complete') {
224
+ if (response.status === 'completed') {
289
225
  console.log('\nProduct kickoff complete.');
290
226
  await visionWriter.updateItemStatus(itemId, 'complete');
291
- } else if (response.status === 'killed') {
292
- console.log('\nProduct kickoff killed.');
293
- await visionWriter.updateItemStatus(itemId, 'killed');
294
- }
295
-
296
- // Write audit trace
297
- if (response.trace) {
298
- try {
299
- const auditPath = join(cwd, 'docs', 'discovery', 'kickoff-audit.json');
300
- writeFileSync(auditPath, JSON.stringify(response, null, 2));
301
- console.log(`Audit trace written to docs/discovery/kickoff-audit.json`);
302
- } catch (err) {
303
- console.warn(`Warning: could not write audit trace: ${err.message}`);
304
- }
305
- }
306
-
307
- // Summary
308
- if (response.status === 'complete') {
309
227
  console.log('\nNext steps:');
310
228
  console.log(' compose pipeline show # review the build pipeline');
311
229
  console.log(' compose build <FEATURE-CODE> # build the first feature');
230
+ } else {
231
+ await visionWriter.updateItemStatus(itemId, 'failed');
312
232
  }
313
233
 
234
+ try {
235
+ const audit = await stratum.audit(flowId);
236
+ const auditPath = join(cwd, 'docs', 'discovery', 'kickoff-audit.json');
237
+ writeFileSync(auditPath, JSON.stringify(audit, null, 2));
238
+ console.log('Audit trace written to docs/discovery/kickoff-audit.json');
239
+ } catch (err) {
240
+ console.warn(`Warning: could not write audit trace: ${err.message}`);
241
+ }
242
+ return response;
314
243
  } finally {
315
244
  await stratum.close();
316
245
  }
317
246
  }
318
-
319
- // ---------------------------------------------------------------------------
320
- // Child flow execution (simplified from build.js — no active-build tracking)
321
- // ---------------------------------------------------------------------------
322
-
323
- async function executeChildFlow(
324
- flowDispatch, stratum, context,
325
- visionWriter, itemId, gateOpts
326
- ) {
327
- let resp = flowDispatch.child_step;
328
- const childFlowId = flowDispatch.child_flow_id;
329
- const childFlowName = flowDispatch.child_flow_name ?? 'sub-flow';
330
-
331
- while (resp.status !== 'complete' && resp.status !== 'killed') {
332
- if (resp.status === 'execute_step') {
333
- console.log(` [${childFlowName}] ${resp.step_id}...`);
334
- await visionWriter.updateItemPhase(itemId, `${childFlowName}:${resp.step_id}`);
335
-
336
- const prompt = buildStepPrompt(resp, context);
337
- const { result } = await runAndNormalize(null, prompt, resp, { stratum, cwd: context.cwd });
338
-
339
- resp = await stratum.stepDone(
340
- childFlowId, resp.step_id,
341
- result ?? { summary: 'Step complete' }
342
- );
343
-
344
- } else if (resp.status === 'await_gate') {
345
- console.log(` [${childFlowName}] Gate: ${resp.step_id}`);
346
- // COMP-PLAN-GATE-LOOP: round-aware gate id (child flow has its own round).
347
- const gateId = await visionWriter.createGate(childFlowId, resp.step_id, itemId, { round: readFlowRound(childFlowId) });
348
- const { outcome, rationale } = await promptGate(resp, gateOpts);
349
- await visionWriter.resolveGate(gateId, outcome);
350
- resp = await stratum.gateResolve(childFlowId, resp.step_id, outcome, rationale, 'human');
351
-
352
- } else if (resp.status === 'ensure_failed' || resp.status === 'schema_failed') {
353
- const violations = resp.violations ?? [];
354
- const stepAgent = resp.agent ?? 'claude';
355
- const fixAgent = stepAgent === 'codex' ? 'claude' : stepAgent;
356
-
357
- console.log(` [${childFlowName}] ↻ Fix (${fixAgent}) for ${resp.step_id}`);
358
- const fixPrompt =
359
- `Fix step "${resp.step_id}" — postconditions failed:\n` +
360
- violations.map(v => `- ${v}`).join('\n') + '\n\n' +
361
- `Fix every issue.\n\n` +
362
- `## Context\nWorking directory: ${context.cwd}\nProject: ${context.projectName}`;
363
- const fixDispatch = { ...resp, agent: fixAgent };
364
- await runAndNormalize(null, fixPrompt, fixDispatch, { stratum, cwd: context.cwd });
365
-
366
- console.log(` [${childFlowName}] ↻ Retrying ${resp.step_id} (${stepAgent})`);
367
- const prompt = buildRetryPrompt(resp, violations, context);
368
- const { result } = await runAndNormalize(null, prompt, resp, { stratum, cwd: context.cwd });
369
-
370
- resp = await stratum.stepDone(
371
- resp.flow_id ?? childFlowId, resp.step_id,
372
- result ?? { summary: 'Retry complete' }
373
- );
374
-
375
- } else if (resp.status === 'execute_flow') {
376
- const nestedParentFlowId = resp.parent_flow_id;
377
- const nestedParentStepId = resp.parent_step_id;
378
- const nestedResult = await executeChildFlow(
379
- resp, stratum, context,
380
- visionWriter, itemId, gateOpts
381
- );
382
- resp = await stratum.stepDone(nestedParentFlowId, nestedParentStepId, nestedResult);
383
-
384
- } else {
385
- console.warn(` [${childFlowName}] Unknown status: ${resp.status}`);
386
- break;
387
- }
388
- }
389
-
390
- return resp;
391
- }