@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.
- package/LICENSE +21 -0
- package/README.md +1014 -0
- package/bin/compose.js +1515 -0
- package/dist/assets/_baseUniq-CQwX6VLz.js +1 -0
- package/dist/assets/arc-SxJ2J1sh.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-BykunY1F.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-ohAKBOUw.js +132 -0
- package/dist/assets/c4Diagram-AHTNJAMY-DBDC3ENB.js +10 -0
- package/dist/assets/channel-DGElom1e.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-Cv93Z7uM.js +1 -0
- package/dist/assets/chunk-4TB4RGXK-DE0WBDkj.js +206 -0
- package/dist/assets/chunk-55IACEB6-CE1EXenG.js +1 -0
- package/dist/assets/chunk-EDXVE4YY-DA7Ana6H.js +1 -0
- package/dist/assets/chunk-FMBD7UC4-CTDIPA3p.js +15 -0
- package/dist/assets/chunk-OYMX7WX6-uGBaPaTX.js +231 -0
- package/dist/assets/chunk-QZHKN3VN-CYlnXuUO.js +1 -0
- package/dist/assets/chunk-YZCP3GAM-ojGkzcZK.js +1 -0
- package/dist/assets/classDiagram-6PBFFD2Q-KqWP9wWZ.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-KqWP9wWZ.js +1 -0
- package/dist/assets/clone-DUJKJXd7.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-Bktn9hL-.js +1 -0
- package/dist/assets/dagre-KV5264BT-DFaSzuRF.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-5BDNPKRD-DnfmDzEm.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Bm8W9YnG.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-B5-TSKvp.js +43 -0
- package/dist/assets/diagram-TYMM5635-ls4rqlky.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-giG6WO-r.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-XvlUuz-7.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-hLBV57oV.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-BHu3s_Gn.js +106 -0
- package/dist/assets/graph-D0Cfv00Y.js +1 -0
- package/dist/assets/index-CUd6pFGF.css +1 -0
- package/dist/assets/index-DReRlzZI.js +1144 -0
- package/dist/assets/infoDiagram-42DDH7IO-DbqRsOo3.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-UXIWVN3A-DnCdx7zb.js +70 -0
- package/dist/assets/journeyDiagram-VCZTEJTY-CfD7eNcP.js +139 -0
- package/dist/assets/kanban-definition-6JOO6SKY-BYaO9-mK.js +89 -0
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-Bj72wOEB.js +1 -0
- package/dist/assets/linear-BRFo114D.js +1 -0
- package/dist/assets/min-GCHnKlJS.js +1 -0
- package/dist/assets/mindmap-definition-QFDTVHPH-n0PMebY4.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pieDiagram-DEJITSTG-pN4CljHF.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-DNoAy8-D.js +7 -0
- package/dist/assets/requirementDiagram-MS252O5E-BhtY05PT.js +84 -0
- package/dist/assets/sankeyDiagram-XADWPNL6-B6AD-16A.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-DShHM-uk.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-DMxn7HTo.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-o6PnCs4e.js +1 -0
- package/dist/assets/timeline-definition-GMOUNBTQ-Cdu6uq52.js +120 -0
- package/dist/assets/vennDiagram-DHZGUBPP-CpK29iRe.js +34 -0
- package/dist/assets/wardley-RL74JXVD-BQgSkdcO.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-DJHYev6O.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-1d75pbaO.js +7 -0
- package/dist/index.html +30 -0
- package/lib/agent-chains.js +65 -0
- package/lib/agent-string.js +86 -0
- package/lib/budget-ledger.js +86 -0
- package/lib/build-all.js +162 -0
- package/lib/build-dag.js +120 -0
- package/lib/build-stream-writer.js +190 -0
- package/lib/build.js +2997 -0
- package/lib/capability-checker.js +53 -0
- package/lib/cert-inject.js +38 -0
- package/lib/cli-progress.js +483 -0
- package/lib/constants.js +69 -0
- package/lib/cross-layer-audit.js +84 -0
- package/lib/debug-discipline.js +173 -0
- package/lib/feature-json.js +106 -0
- package/lib/gate-prompt.js +291 -0
- package/lib/gate-tiers.js +194 -0
- package/lib/health-history.js +119 -0
- package/lib/health-score.js +227 -0
- package/lib/ideabox.js +570 -0
- package/lib/import.js +244 -0
- package/lib/migrate-roadmap.js +94 -0
- package/lib/model-pricing.js +67 -0
- package/lib/new.js +413 -0
- package/lib/pipeline-cli.js +489 -0
- package/lib/plan-parser.js +103 -0
- package/lib/qa-scoping.js +474 -0
- package/lib/questionnaire.js +200 -0
- package/lib/resolve-port.js +7 -0
- package/lib/result-normalizer.js +349 -0
- package/lib/review-lenses.js +166 -0
- package/lib/roadmap-gen.js +210 -0
- package/lib/roadmap-parser.js +176 -0
- package/lib/server-probe.js +23 -0
- package/lib/staleness.js +87 -0
- package/lib/step-prompt.js +260 -0
- package/lib/step-validator.js +49 -0
- package/lib/stratum-mcp-client.js +365 -0
- package/lib/team-flag.js +46 -0
- package/lib/test-bootstrap.js +401 -0
- package/lib/triage.js +274 -0
- package/lib/vision-writer.js +391 -0
- package/package.json +111 -0
- package/pipelines/bug-fix.stratum.yaml +230 -0
- package/pipelines/build.stratum.yaml +498 -0
- package/pipelines/content.stratum.yaml +112 -0
- package/pipelines/coverage-sweep.stratum.yaml +52 -0
- package/pipelines/refactor.stratum.yaml +169 -0
- package/pipelines/research.stratum.yaml +88 -0
- package/pipelines/review-fix.stratum.yaml +109 -0
- package/presets/team-feature.stratum.yaml +105 -0
- package/presets/team-research.stratum.yaml +108 -0
- package/presets/team-review.stratum.yaml +106 -0
- package/scripts/agent-activity-hook.sh +31 -0
- package/scripts/agent-error-hook.sh +28 -0
- package/scripts/analyze-orphans.mjs +50 -0
- package/scripts/find-orphans.mjs +26 -0
- package/scripts/fix-phases.mjs +49 -0
- package/scripts/generate-stratum-spec.mjs +137 -0
- package/scripts/import-roadmap.mjs +116 -0
- package/scripts/phase-audit.mjs +33 -0
- package/scripts/run-pipeline.mjs +314 -0
- package/scripts/session-end-hook.sh +18 -0
- package/scripts/session-start-hook.sh +38 -0
- package/scripts/vision-hook.sh +104 -0
- package/scripts/vision-track.mjs +554 -0
- package/scripts/wire-all-orphans.mjs +108 -0
- package/scripts/wire-orphans.mjs +164 -0
- package/server/activity-routes.js +123 -0
- package/server/agent-health.js +197 -0
- package/server/agent-hooks.js +102 -0
- package/server/agent-mcp.js +10 -0
- package/server/agent-registry.js +95 -0
- package/server/agent-server.js +290 -0
- package/server/agent-spawn.js +251 -0
- package/server/agent-templates.js +77 -0
- package/server/artifact-manager.js +247 -0
- package/server/artifact-templates/architecture.md +28 -0
- package/server/artifact-templates/blueprint.md +21 -0
- package/server/artifact-templates/design.md +36 -0
- package/server/artifact-templates/plan.md +25 -0
- package/server/artifact-templates/prd.md +43 -0
- package/server/artifact-templates/report.md +40 -0
- package/server/block-tracker.js +90 -0
- package/server/build-stream-bridge.js +502 -0
- package/server/coalescing-buffer.js +46 -0
- package/server/compose-mcp-tools.js +479 -0
- package/server/compose-mcp.js +324 -0
- package/server/connectors/agent-connector.js +78 -0
- package/server/connectors/claude-sdk-connector.js +198 -0
- package/server/connectors/codex-connector.js +240 -0
- package/server/connectors/connector-discovery.js +18 -0
- package/server/connectors/connector-runtime.js +13 -0
- package/server/connectors/opencode-connector.js +200 -0
- package/server/design-routes.js +540 -0
- package/server/design-session.js +161 -0
- package/server/feature-scan.js +593 -0
- package/server/file-watcher.js +284 -0
- package/server/find-root.js +29 -0
- package/server/graph-export.js +343 -0
- package/server/ideabox-cache.js +77 -0
- package/server/ideabox-routes.js +294 -0
- package/server/index.js +156 -0
- package/server/model-tiers.js +49 -0
- package/server/pipeline-routes.js +288 -0
- package/server/policy-evaluator.js +36 -0
- package/server/project-root.js +122 -0
- package/server/security.js +23 -0
- package/server/session-manager.js +403 -0
- package/server/session-routes.js +190 -0
- package/server/session-store.js +107 -0
- package/server/settings-routes.js +35 -0
- package/server/settings-store.js +234 -0
- package/server/stratum-api.js +102 -0
- package/server/stratum-client.js +192 -0
- package/server/stratum-sync.js +193 -0
- package/server/summarizer.js +139 -0
- package/server/supervisor.js +196 -0
- package/server/vision-routes.js +668 -0
- package/server/vision-server.js +393 -0
- package/server/vision-store.js +360 -0
- package/server/vision-utils.js +179 -0
- package/server/worktree-gc.js +137 -0
- 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
|
+
}
|