substrate-ai 0.2.35 → 0.2.38

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/dist/cli/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createConfigSystem, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-XNRFAHEx.js";
2
+ import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createConfigSystem, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-RJ0EHbfM.js";
3
3
  import { createLogger } from "../logger-D2fS2ccL.js";
4
4
  import { AdapterRegistry } from "../adapter-registry-PsWhP_1Q.js";
5
5
  import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-DSi8KhQC.js";
@@ -1201,12 +1201,8 @@ async function runFullPipelineFromPhase(options) {
1201
1201
  logger$14.warn({ err }, "Failed to record token usage");
1202
1202
  }
1203
1203
  });
1204
- const storyDecisions = db.prepare(`SELECT description FROM requirements WHERE pipeline_run_id = ? AND source = 'solutioning-phase'`).all(runId);
1205
- const storyKeys = [];
1206
- for (const req of storyDecisions) {
1207
- const keyMatch = /^(\d+-\d+):/.exec(req.description);
1208
- if (keyMatch) storyKeys.push(keyMatch[1]);
1209
- }
1204
+ const storyKeys = resolveStoryKeys(db, projectRoot, { pipelineRunId: runId });
1205
+ if (storyKeys.length === 0 && outputFormat === "human") process.stdout.write("[IMPLEMENTATION] No stories found for this run. Check solutioning phase output.\n");
1210
1206
  await orchestrator.run(storyKeys);
1211
1207
  if (outputFormat === "human") process.stdout.write("[IMPLEMENTATION] Complete\n");
1212
1208
  }
@@ -2585,7 +2581,7 @@ async function runSupervisorAction(options, deps = {}) {
2585
2581
  const expDb = expDbWrapper.db;
2586
2582
  const { runRunAction: runPipeline } = await import(
2587
2583
  /* @vite-ignore */
2588
- "../run-Cd7sfXzo.js"
2584
+ "../run-Q-rgJf4a.js"
2589
2585
  );
2590
2586
  const runStoryFn = async (opts) => {
2591
2587
  const exitCode = await runPipeline({
@@ -1,20 +1,23 @@
1
1
  <!-- substrate:start -->
2
2
  ## Substrate Pipeline
3
3
 
4
- This project uses Substrate for automated implementation pipelines. Substrate runs are long-running (5–40 minutes). Plan accordingly.
4
+ This project uses Substrate for automated implementation pipelines. **When the user asks you to implement, build, or run the pipeline — go straight to running substrate. Do NOT explore the codebase, read source files, or plan the implementation yourself.** Substrate orchestrates sub-agents that handle all of that.
5
5
 
6
6
  ### Running the Pipeline
7
7
 
8
- **Preferred Supervisor mode** (handles stalls, auto-restarts, post-run analysis):
8
+ **Just run it.** Substrate auto-detects which pipeline phase to start from (analysis planning → solutioning → implementation) and auto-discovers pending stories. You do not need to determine the phase or find story keys manually.
9
+
9
10
  ```
10
- substrate supervisor --output-format json --stories 7-1,7-2
11
+ substrate supervisor --output-format json
11
12
  ```
12
13
 
13
- **Direct mode** (simpler, no auto-recovery):
14
+ To target specific stories (if the user names them):
14
15
  ```
15
- substrate run --events --stories 7-1,7-2
16
+ substrate supervisor --output-format json --stories 1-1,1-2,1-3
16
17
  ```
17
18
 
19
+ If substrate needs input it can't auto-detect (e.g., a project concept for analysis), it will exit with a clear error message telling you what to provide.
20
+
18
21
  **CRITICAL execution rules:**
19
22
  - Pipeline runs take **5–40 minutes**. You MUST use `run_in_background: true` or `timeout: 600000` (10 min) when invoking via Bash tool. Default 2-minute timeout WILL kill the pipeline.
20
23
  - **NEVER pipe substrate output** to `head`, `tail`, `grep`, or any command that may close the pipe early — this causes EPIPE stalls that hang the process.
@@ -1,4 +1,4 @@
1
- import { registerRunCommand, runRunAction } from "./run-XNRFAHEx.js";
1
+ import { registerRunCommand, runRunAction } from "./run-RJ0EHbfM.js";
2
2
  import "./logger-D2fS2ccL.js";
3
3
  import "./config-migrator-DSi8KhQC.js";
4
4
  import "./helpers-RL22dYtn.js";
@@ -9404,6 +9404,44 @@ function createImplementationOrchestrator(deps) {
9404
9404
  //#endregion
9405
9405
  //#region src/modules/implementation-orchestrator/story-discovery.ts
9406
9406
  /**
9407
+ * Unified story key resolution with a 4-level fallback chain.
9408
+ *
9409
+ * 1. Explicit keys (from --stories flag) — returned as-is
9410
+ * 2. Decisions table (category='stories', phase='solutioning')
9411
+ * 3. Epic shard decisions (category='epic-shard') — parsed with parseStoryKeysFromEpics
9412
+ * 4. epics.md file on disk (via discoverPendingStoryKeys)
9413
+ *
9414
+ * Optionally filters out completed stories when filterCompleted is set.
9415
+ *
9416
+ * @returns Sorted, deduplicated array of story keys in "N-M" format
9417
+ */
9418
+ function resolveStoryKeys(db, projectRoot, opts) {
9419
+ if (opts?.explicit !== void 0 && opts.explicit.length > 0) return opts.explicit;
9420
+ let keys = [];
9421
+ try {
9422
+ const query = opts?.pipelineRunId !== void 0 ? `SELECT key FROM decisions WHERE phase = 'solutioning' AND category = 'stories' AND pipeline_run_id = ? ORDER BY created_at ASC` : `SELECT key FROM decisions WHERE phase = 'solutioning' AND category = 'stories' ORDER BY created_at ASC`;
9423
+ const params = opts?.pipelineRunId !== void 0 ? [opts.pipelineRunId] : [];
9424
+ const rows = db.prepare(query).all(...params);
9425
+ for (const row of rows) if (/^\d+-\d+/.test(row.key)) {
9426
+ const match = /^(\d+-\d+)/.exec(row.key);
9427
+ if (match !== null) keys.push(match[1]);
9428
+ }
9429
+ } catch {}
9430
+ if (keys.length === 0) try {
9431
+ const query = opts?.pipelineRunId !== void 0 ? `SELECT value FROM decisions WHERE category = 'epic-shard' AND pipeline_run_id = ? ORDER BY created_at ASC` : `SELECT value FROM decisions WHERE category = 'epic-shard' ORDER BY created_at ASC`;
9432
+ const params = opts?.pipelineRunId !== void 0 ? [opts.pipelineRunId] : [];
9433
+ const shardRows = db.prepare(query).all(...params);
9434
+ const allContent = shardRows.map((r) => r.value).join("\n");
9435
+ if (allContent.length > 0) keys = parseStoryKeysFromEpics(allContent);
9436
+ } catch {}
9437
+ if (keys.length === 0) keys = discoverPendingStoryKeys(projectRoot);
9438
+ if (opts?.filterCompleted === true && keys.length > 0) {
9439
+ const completedKeys = getCompletedStoryKeys(db);
9440
+ keys = keys.filter((k) => !completedKeys.has(k));
9441
+ }
9442
+ return sortStoryKeys([...new Set(keys)]);
9443
+ }
9444
+ /**
9407
9445
  * Extract all story keys (N-M format) from epics.md content.
9408
9446
  *
9409
9447
  * Supports three extraction patterns found in real epics.md files:
@@ -9490,6 +9528,24 @@ function collectExistingStoryKeys(projectRoot) {
9490
9528
  return existing;
9491
9529
  }
9492
9530
  /**
9531
+ * Collect story keys already completed in previous pipeline runs.
9532
+ * Scans pipeline_runs with status='completed' and extracts story keys
9533
+ * with phase='COMPLETE' from their token_usage_json state.
9534
+ */
9535
+ function getCompletedStoryKeys(db) {
9536
+ const completed = new Set();
9537
+ try {
9538
+ const rows = db.prepare(`SELECT token_usage_json FROM pipeline_runs WHERE status = 'completed' AND token_usage_json IS NOT NULL`).all();
9539
+ for (const row of rows) try {
9540
+ const state = JSON.parse(row.token_usage_json);
9541
+ if (state.stories !== void 0) {
9542
+ for (const [key, s] of Object.entries(state.stories)) if (s.phase === "COMPLETE") completed.add(key);
9543
+ }
9544
+ } catch {}
9545
+ } catch {}
9546
+ return completed;
9547
+ }
9548
+ /**
9493
9549
  * Sort story keys numerically by epic number (primary) then story number (secondary).
9494
9550
  * E.g. ["10-1", "1-2", "2-1"] → ["1-2", "2-1", "10-1"]
9495
9551
  */
@@ -9503,6 +9559,83 @@ function sortStoryKeys(keys) {
9503
9559
  });
9504
9560
  }
9505
9561
 
9562
+ //#endregion
9563
+ //#region src/modules/phase-orchestrator/phase-detection.ts
9564
+ const PHASE_ARTIFACTS = [
9565
+ {
9566
+ phase: "research",
9567
+ type: "research-findings",
9568
+ optional: true
9569
+ },
9570
+ {
9571
+ phase: "analysis",
9572
+ type: "product-brief",
9573
+ optional: false
9574
+ },
9575
+ {
9576
+ phase: "planning",
9577
+ type: "prd",
9578
+ optional: false
9579
+ },
9580
+ {
9581
+ phase: "solutioning",
9582
+ type: "stories",
9583
+ optional: false
9584
+ }
9585
+ ];
9586
+ /**
9587
+ * Detect the next phase to run based on DB state.
9588
+ *
9589
+ * Detection logic:
9590
+ * 1. If stories exist (decisions/epics.md) → implementation
9591
+ * 2. Walk forward through phases checking for completion artifacts
9592
+ * 3. Skip optional phases (research) if no artifact found
9593
+ * 4. The first required phase WITHOUT an artifact is where we start
9594
+ * 5. If nothing exists → analysis (needs concept)
9595
+ */
9596
+ function detectStartPhase(db, projectRoot) {
9597
+ try {
9598
+ const storyKeys = resolveStoryKeys(db, projectRoot);
9599
+ if (storyKeys.length > 0) return {
9600
+ phase: "implementation",
9601
+ reason: `${storyKeys.length} stories ready for implementation`,
9602
+ needsConcept: false
9603
+ };
9604
+ } catch {}
9605
+ let lastCompletedPhase;
9606
+ try {
9607
+ for (const entry of PHASE_ARTIFACTS) {
9608
+ const row = db.prepare("SELECT id FROM artifacts WHERE phase = ? AND type = ? LIMIT 1").get(entry.phase, entry.type);
9609
+ if (row !== void 0) lastCompletedPhase = entry.phase;
9610
+ else if (!entry.optional) {
9611
+ const needsConcept = entry.phase === "analysis";
9612
+ const reason = lastCompletedPhase !== void 0 ? `${lastCompletedPhase} phase complete — continuing with ${entry.phase}` : "No pipeline state found — starting from the beginning";
9613
+ return {
9614
+ phase: entry.phase,
9615
+ needsConcept,
9616
+ reason
9617
+ };
9618
+ }
9619
+ }
9620
+ } catch {
9621
+ return {
9622
+ phase: "analysis",
9623
+ reason: "No pipeline state found — starting from the beginning",
9624
+ needsConcept: true
9625
+ };
9626
+ }
9627
+ if (lastCompletedPhase !== void 0) return {
9628
+ phase: "solutioning",
9629
+ reason: "All phases completed but no stories found — re-running solutioning",
9630
+ needsConcept: false
9631
+ };
9632
+ return {
9633
+ phase: "analysis",
9634
+ reason: "No pipeline state found — starting from the beginning",
9635
+ needsConcept: true
9636
+ };
9637
+ }
9638
+
9506
9639
  //#endregion
9507
9640
  //#region src/modules/phase-orchestrator/built-in-phases.ts
9508
9641
  function logPhase(message) {
@@ -12341,7 +12474,7 @@ async function runStoryGeneration(deps, params, gapAnalysis) {
12341
12474
  pipeline_run_id: runId,
12342
12475
  source: "solutioning-phase",
12343
12476
  type: "functional",
12344
- description: `${story.title}: ${story.description}`,
12477
+ description: `${story.key}: ${story.title}: ${story.description}`,
12345
12478
  priority: story.priority
12346
12479
  });
12347
12480
  const totalStories = epics.reduce((sum, epic) => sum + epic.stories.length, 0);
@@ -12696,7 +12829,7 @@ async function runStoryGenerationMultiStep(deps, params) {
12696
12829
  pipeline_run_id: params.runId,
12697
12830
  source: "solutioning-phase",
12698
12831
  type: "functional",
12699
- description: `${story.title}: ${story.description}`,
12832
+ description: `${story.key}: ${story.title}: ${story.description}`,
12700
12833
  priority: story.priority
12701
12834
  });
12702
12835
  const artifactId = storyStep?.artifactId ?? "";
@@ -13494,18 +13627,54 @@ async function runRunAction(options) {
13494
13627
  } catch {
13495
13628
  logger.debug("Config loading skipped — using default token ceilings");
13496
13629
  }
13497
- if (startPhase !== void 0) return runFullPipeline({
13630
+ let parsedStoryKeys = [];
13631
+ if (storiesArg !== void 0 && storiesArg !== "") {
13632
+ parsedStoryKeys = storiesArg.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
13633
+ for (const key of parsedStoryKeys) if (!validateStoryKey(key)) {
13634
+ const errorMsg = `Story key '${key}' is not a valid format. Expected: <epic>-<story> (e.g., 10-1)`;
13635
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
13636
+ else process.stderr.write(`Error: ${errorMsg}\n`);
13637
+ return 1;
13638
+ }
13639
+ }
13640
+ let effectiveStartPhase = startPhase;
13641
+ if (effectiveStartPhase === void 0) {
13642
+ mkdirSync(dbDir, { recursive: true });
13643
+ try {
13644
+ const detectDb = new DatabaseWrapper(dbPath);
13645
+ try {
13646
+ detectDb.open();
13647
+ runMigrations(detectDb.db);
13648
+ const detection = detectStartPhase(detectDb.db, projectRoot);
13649
+ if (detection.phase !== "implementation") {
13650
+ effectiveStartPhase = detection.phase;
13651
+ if (outputFormat === "human") process.stdout.write(`[AUTO-DETECT] ${detection.reason}\n`);
13652
+ if (detection.needsConcept && concept === void 0) {
13653
+ const errorMsg = `Pipeline needs to start from ${detection.phase} phase, which requires a concept.\nProvide --concept "your idea" or --concept-file path/to/brief.md`;
13654
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
13655
+ else process.stderr.write(`Error: ${errorMsg}\n`);
13656
+ detectDb.close();
13657
+ return 1;
13658
+ }
13659
+ } else if (outputFormat === "human") process.stdout.write(`[AUTO-DETECT] ${detection.reason}\n`);
13660
+ } finally {
13661
+ detectDb.close();
13662
+ }
13663
+ } catch {}
13664
+ }
13665
+ if (effectiveStartPhase !== void 0) return runFullPipeline({
13498
13666
  packName,
13499
13667
  packPath,
13500
13668
  dbDir,
13501
13669
  dbPath,
13502
- startPhase,
13670
+ startPhase: effectiveStartPhase,
13503
13671
  stopAfter,
13504
13672
  concept,
13505
13673
  concurrency,
13506
13674
  outputFormat,
13507
13675
  projectRoot,
13508
13676
  tokenCeilings,
13677
+ ...parsedStoryKeys.length > 0 ? { stories: parsedStoryKeys } : {},
13509
13678
  ...eventsFlag === true ? { events: true } : {},
13510
13679
  ...skipUx === true ? { skipUx: true } : {},
13511
13680
  ...researchFlag === true ? { research: true } : {},
@@ -13513,16 +13682,7 @@ async function runRunAction(options) {
13513
13682
  ...skipPreflight === true ? { skipPreflight: true } : {},
13514
13683
  ...injectedRegistry !== void 0 ? { registry: injectedRegistry } : {}
13515
13684
  });
13516
- let storyKeys = [];
13517
- if (storiesArg !== void 0 && storiesArg !== "") {
13518
- storyKeys = storiesArg.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
13519
- for (const key of storyKeys) if (!validateStoryKey(key)) {
13520
- const errorMsg = `Story key '${key}' is not a valid format. Expected: <epic>-<story> (e.g., 10-1)`;
13521
- if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
13522
- else process.stderr.write(`Error: ${errorMsg}\n`);
13523
- return 1;
13524
- }
13525
- }
13685
+ let storyKeys = [...parsedStoryKeys];
13526
13686
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
13527
13687
  const dbWrapper = new DatabaseWrapper(dbPath);
13528
13688
  try {
@@ -14029,7 +14189,7 @@ async function runRunAction(options) {
14029
14189
  }
14030
14190
  }
14031
14191
  async function runFullPipeline(options) {
14032
- const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, registry: injectedRegistry, tokenCeilings } = options;
14192
+ const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, registry: injectedRegistry, tokenCeilings, stories: explicitStories } = options;
14033
14193
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
14034
14194
  const dbWrapper = new DatabaseWrapper(dbPath);
14035
14195
  try {
@@ -14267,12 +14427,8 @@ async function runFullPipeline(options) {
14267
14427
  process.stdout.write(` [ESCALATED] ${payload.storyKey}: ${payload.lastVerdict}\n`);
14268
14428
  });
14269
14429
  }
14270
- const storyDecisions = db.prepare(`SELECT description FROM requirements WHERE status = 'active' AND source = 'solutioning-phase'`).all();
14271
- const storyKeys = [];
14272
- for (const req of storyDecisions) {
14273
- const keyMatch = /^(\d+-\d+):/.exec(req.description);
14274
- if (keyMatch) storyKeys.push(keyMatch[1]);
14275
- }
14430
+ const storyKeys = resolveStoryKeys(db, projectRoot, { explicit: explicitStories });
14431
+ if (storyKeys.length === 0 && outputFormat === "human") process.stdout.write("[IMPLEMENTATION] No stories found. Run solutioning first or pass --stories.\n");
14276
14432
  if (outputFormat === "human") process.stdout.write(`[IMPLEMENTATION] Starting ${storyKeys.length} stories with concurrency=${concurrency}\n`);
14277
14433
  await orchestrator.run(storyKeys);
14278
14434
  if (outputFormat === "human") process.stdout.write("[IMPLEMENTATION] Complete\n");
@@ -14376,5 +14532,5 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
14376
14532
  }
14377
14533
 
14378
14534
  //#endregion
14379
- export { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createConfigSystem, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
14380
- //# sourceMappingURL=run-XNRFAHEx.js.map
14535
+ export { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createConfigSystem, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
14536
+ //# sourceMappingURL=run-RJ0EHbfM.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.2.35",
3
+ "version": "0.2.38",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",