substrate-ai 0.3.2 → 0.3.3

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, DoltNotInstalled, FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, TelemetryPersistence, VALID_PHASES, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDispatcher, createDoltClient, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initializeDolt, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-Fzhz3-mv.js";
2
+ import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltNotInstalled, FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, TelemetryPersistence, VALID_PHASES, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDispatcher, createDoltClient, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initializeDolt, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-DI9s014E.js";
3
3
  import { createLogger } from "../logger-D2fS2ccL.js";
4
4
  import { AdapterRegistry } from "../adapter-registry-DHl0W-YB.js";
5
5
  import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-CQmBdKeG.js";
@@ -2554,6 +2554,7 @@ async function runSupervisorAction(options, deps = {}) {
2554
2554
  runId,
2555
2555
  restartCount: 0
2556
2556
  };
2557
+ let maxRestartsExhausted = false;
2557
2558
  const startTime = Date.now();
2558
2559
  function emitEvent(event) {
2559
2560
  if (outputFormat === "json") {
@@ -2664,7 +2665,7 @@ async function runSupervisorAction(options, deps = {}) {
2664
2665
  const expDb = expDbWrapper.db;
2665
2666
  const { runRunAction: runPipeline } = await import(
2666
2667
  /* @vite-ignore */
2667
- "../run-BJ5z_b2J.js"
2668
+ "../run-33J0SBp1.js"
2668
2669
  );
2669
2670
  const runStoryFn = async (opts) => {
2670
2671
  const exitCode = await runPipeline({
@@ -2729,6 +2730,7 @@ async function runSupervisorAction(options, deps = {}) {
2729
2730
  }
2730
2731
  return summary.failed.length > 0 || summary.escalated.length > 0 ? 1 : 0;
2731
2732
  }
2733
+ if (maxRestartsExhausted) return 2;
2732
2734
  const stallResult = await handleStallRecovery(health, state, {
2733
2735
  stallThreshold,
2734
2736
  maxRestarts,
@@ -2745,10 +2747,8 @@ async function runSupervisorAction(options, deps = {}) {
2745
2747
  emitEvent,
2746
2748
  log
2747
2749
  });
2748
- if (stallResult !== null) {
2749
- if (stallResult.maxRestartsExceeded) return 2;
2750
- state = stallResult.state;
2751
- }
2750
+ if (stallResult !== null) if (stallResult.maxRestartsExceeded) maxRestartsExhausted = true;
2751
+ else state = stallResult.state;
2752
2752
  await sleep(pollInterval * 1e3);
2753
2753
  }
2754
2754
  }
@@ -2778,6 +2778,7 @@ async function runMultiProjectSupervisor(options, deps = {}) {
2778
2778
  }]));
2779
2779
  const doneProjects = new Set();
2780
2780
  const projectExitCodes = new Map();
2781
+ const maxRestartsExhaustedProjects = new Set();
2781
2782
  const startTime = Date.now();
2782
2783
  function emitEvent(event) {
2783
2784
  if (outputFormat === "json") {
@@ -2834,6 +2835,11 @@ async function runMultiProjectSupervisor(options, deps = {}) {
2834
2835
  projectExitCodes.set(projectRoot, summary.failed.length > 0 || summary.escalated.length > 0 ? 1 : 0);
2835
2836
  continue;
2836
2837
  }
2838
+ if (maxRestartsExhaustedProjects.has(projectRoot)) {
2839
+ doneProjects.add(projectRoot);
2840
+ projectExitCodes.set(projectRoot, 2);
2841
+ continue;
2842
+ }
2837
2843
  const stallResult = await handleStallRecovery(health, state, {
2838
2844
  stallThreshold,
2839
2845
  maxRestarts,
@@ -2846,10 +2852,8 @@ async function runMultiProjectSupervisor(options, deps = {}) {
2846
2852
  }),
2847
2853
  log: (msg) => log(`[${projectRoot}] ${msg}`)
2848
2854
  });
2849
- if (stallResult !== null) if (stallResult.maxRestartsExceeded) {
2850
- doneProjects.add(projectRoot);
2851
- projectExitCodes.set(projectRoot, 2);
2852
- } else states.set(projectRoot, stallResult.state);
2855
+ if (stallResult !== null) if (stallResult.maxRestartsExceeded) maxRestartsExhaustedProjects.add(projectRoot);
2856
+ else states.set(projectRoot, stallResult.state);
2853
2857
  }
2854
2858
  if (doneProjects.size >= projects.length) {
2855
2859
  const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
@@ -1,4 +1,4 @@
1
- import { registerRunCommand, runRunAction } from "./run-Fzhz3-mv.js";
1
+ import { registerRunCommand, runRunAction } from "./run-DI9s014E.js";
2
2
  import "./logger-D2fS2ccL.js";
3
3
  import "./config-migrator-CQmBdKeG.js";
4
4
  import "./helpers-RL22dYtn.js";
@@ -8155,6 +8155,8 @@ async function getAutoHealthData(options) {
8155
8155
  const initialized = existsSync$1(doltDirPath);
8156
8156
  let responsive = false;
8157
8157
  let version;
8158
+ let branches;
8159
+ let currentBranch;
8158
8160
  try {
8159
8161
  await stateStore.getHistory(1);
8160
8162
  responsive = true;
@@ -8166,13 +8168,30 @@ async function getAutoHealthData(options) {
8166
8168
  const match = stdout.match(/dolt version (\S+)/);
8167
8169
  if (match) version = match[1];
8168
8170
  } catch {}
8171
+ try {
8172
+ const { execFile: ef } = await import("node:child_process");
8173
+ const { promisify: p } = await import("node:util");
8174
+ const execFileAsync = p(ef);
8175
+ const { stdout } = await execFileAsync("dolt", ["branch", "--list"], { cwd: repoPath });
8176
+ const lines = stdout.split("\n").filter((l) => l.trim().length > 0);
8177
+ branches = lines.map((l) => {
8178
+ const trimmed = l.trim();
8179
+ if (trimmed.startsWith("* ")) {
8180
+ currentBranch = trimmed.slice(2).trim();
8181
+ return currentBranch;
8182
+ }
8183
+ return trimmed;
8184
+ });
8185
+ } catch {}
8169
8186
  } catch {
8170
8187
  responsive = false;
8171
8188
  }
8172
8189
  doltStateInfo = {
8173
8190
  initialized,
8174
8191
  responsive,
8175
- ...version !== void 0 ? { version } : {}
8192
+ ...version !== void 0 ? { version } : {},
8193
+ ...branches !== void 0 ? { branches } : {},
8194
+ ...currentBranch !== void 0 ? { current_branch: currentBranch } : {}
8176
8195
  };
8177
8196
  }
8178
8197
  const NO_PIPELINE = {
@@ -10660,6 +10679,21 @@ function createImplementationOrchestrator(deps) {
10660
10679
  phase: "IN_STORY_CREATION",
10661
10680
  result: createResult
10662
10681
  });
10682
+ if (config.pipelineRunId !== void 0 && createResult.tokenUsage !== void 0) try {
10683
+ addTokenUsage(db, config.pipelineRunId, {
10684
+ phase: "create-story",
10685
+ agent: "create-story",
10686
+ input_tokens: createResult.tokenUsage.input,
10687
+ output_tokens: createResult.tokenUsage.output,
10688
+ cost_usd: 0,
10689
+ metadata: JSON.stringify({ storyKey })
10690
+ });
10691
+ } catch (tokenErr) {
10692
+ logger$26.warn({
10693
+ storyKey,
10694
+ err: tokenErr
10695
+ }, "Failed to record create-story token usage");
10696
+ }
10663
10697
  persistState();
10664
10698
  if (createResult.result === "failed") {
10665
10699
  const errMsg = createResult.error ?? "create-story failed";
@@ -10757,6 +10791,7 @@ function createImplementationOrchestrator(deps) {
10757
10791
  updateStory(storyKey, { phase: "IN_TEST_PLANNING" });
10758
10792
  persistState();
10759
10793
  let testPlanPhaseResult = "failed";
10794
+ let testPlanTokenUsage;
10760
10795
  try {
10761
10796
  const testPlanResult = await runTestPlan({
10762
10797
  db,
@@ -10772,6 +10807,7 @@ function createImplementationOrchestrator(deps) {
10772
10807
  pipelineRunId: config.pipelineRunId ?? ""
10773
10808
  });
10774
10809
  testPlanPhaseResult = testPlanResult.result;
10810
+ testPlanTokenUsage = testPlanResult.tokenUsage;
10775
10811
  if (testPlanResult.result === "success") logger$26.info({ storyKey }, "Test plan generated successfully");
10776
10812
  else logger$26.warn({ storyKey }, "Test planning returned failed result — proceeding to dev-story without test plan");
10777
10813
  } catch (err) {
@@ -10781,6 +10817,21 @@ function createImplementationOrchestrator(deps) {
10781
10817
  }, "Test planning failed — proceeding to dev-story without test plan");
10782
10818
  }
10783
10819
  endPhase(storyKey, "test-plan");
10820
+ if (config.pipelineRunId !== void 0 && testPlanTokenUsage !== void 0) try {
10821
+ addTokenUsage(db, config.pipelineRunId, {
10822
+ phase: "test-plan",
10823
+ agent: "test-plan",
10824
+ input_tokens: testPlanTokenUsage.input,
10825
+ output_tokens: testPlanTokenUsage.output,
10826
+ cost_usd: 0,
10827
+ metadata: JSON.stringify({ storyKey })
10828
+ });
10829
+ } catch (tokenErr) {
10830
+ logger$26.warn({
10831
+ storyKey,
10832
+ err: tokenErr
10833
+ }, "Failed to record test-plan token usage");
10834
+ }
10784
10835
  eventBus.emit("orchestrator:story-phase-complete", {
10785
10836
  storyKey,
10786
10837
  phase: "IN_TEST_PLANNING",
@@ -10929,6 +10980,21 @@ function createImplementationOrchestrator(deps) {
10929
10980
  pipelineRunId: config.pipelineRunId
10930
10981
  });
10931
10982
  devFilesModified = devResult.files_modified ?? [];
10983
+ if (config.pipelineRunId !== void 0 && devResult.tokenUsage !== void 0) try {
10984
+ addTokenUsage(db, config.pipelineRunId, {
10985
+ phase: "dev-story",
10986
+ agent: "dev-story",
10987
+ input_tokens: devResult.tokenUsage.input,
10988
+ output_tokens: devResult.tokenUsage.output,
10989
+ cost_usd: 0,
10990
+ metadata: JSON.stringify({ storyKey })
10991
+ });
10992
+ } catch (tokenErr) {
10993
+ logger$26.warn({
10994
+ storyKey,
10995
+ err: tokenErr
10996
+ }, "Failed to record dev-story token usage");
10997
+ }
10932
10998
  eventBus.emit("orchestrator:story-phase-complete", {
10933
10999
  storyKey,
10934
11000
  phase: "IN_DEV",
@@ -11144,6 +11210,24 @@ function createImplementationOrchestrator(deps) {
11144
11210
  ...previousIssueList.length > 0 ? { previousIssues: previousIssueList } : {}
11145
11211
  });
11146
11212
  }
11213
+ if (config.pipelineRunId !== void 0 && reviewResult.tokenUsage !== void 0) try {
11214
+ addTokenUsage(db, config.pipelineRunId, {
11215
+ phase: "code-review",
11216
+ agent: useBatchedReview ? "code-review-batched" : "code-review",
11217
+ input_tokens: reviewResult.tokenUsage.input,
11218
+ output_tokens: reviewResult.tokenUsage.output,
11219
+ cost_usd: 0,
11220
+ metadata: JSON.stringify({
11221
+ storyKey,
11222
+ reviewCycle: reviewCycles
11223
+ })
11224
+ });
11225
+ } catch (tokenErr) {
11226
+ logger$26.warn({
11227
+ storyKey,
11228
+ err: tokenErr
11229
+ }, "Failed to record code-review token usage");
11230
+ }
11147
11231
  const isPhantomReview = reviewResult.dispatchFailed === true || reviewResult.verdict !== "SHIP_IT" && reviewResult.verdict !== "LGTM_WITH_NOTES" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
11148
11232
  if (isPhantomReview && !timeoutRetried) {
11149
11233
  timeoutRetried = true;
@@ -11717,16 +11801,22 @@ function createImplementationOrchestrator(deps) {
11717
11801
  persistState();
11718
11802
  recordProgress();
11719
11803
  if (config.enableHeartbeat) startHeartbeat();
11804
+ const _startupTimings = {};
11720
11805
  if (projectRoot !== void 0) {
11806
+ const seedStart = Date.now();
11721
11807
  const seedResult = seedMethodologyContext(db, projectRoot);
11808
+ _startupTimings.seedMethodologyMs = Date.now() - seedStart;
11722
11809
  if (seedResult.decisionsCreated > 0) logger$26.info({
11723
11810
  decisionsCreated: seedResult.decisionsCreated,
11724
- skippedCategories: seedResult.skippedCategories
11811
+ skippedCategories: seedResult.skippedCategories,
11812
+ durationMs: _startupTimings.seedMethodologyMs
11725
11813
  }, "Methodology context seeded from planning artifacts");
11726
11814
  }
11727
11815
  try {
11728
11816
  if (stateStore !== void 0) {
11817
+ const stateStoreInitStart = Date.now();
11729
11818
  await stateStore.initialize();
11819
+ _startupTimings.stateStoreInitMs = Date.now() - stateStoreInitStart;
11730
11820
  for (const key of storyKeys) {
11731
11821
  const pendingState = _stories.get(key);
11732
11822
  if (pendingState !== void 0) persistStoryState(key, pendingState).catch((err) => logger$26.warn({
@@ -11735,7 +11825,9 @@ function createImplementationOrchestrator(deps) {
11735
11825
  }, "StateStore write failed during PENDING init"));
11736
11826
  }
11737
11827
  try {
11828
+ const queryStoriesStart = Date.now();
11738
11829
  const existingRecords = await stateStore.queryStories({});
11830
+ _startupTimings.queryStoriesMs = Date.now() - queryStoriesStart;
11739
11831
  for (const record of existingRecords) _stateStoreCache.set(record.storyKey, record);
11740
11832
  } catch (err) {
11741
11833
  logger$26.warn({ err }, "StateStore.queryStories() failed during init — status merge will be empty (best-effort)");
@@ -11750,7 +11842,9 @@ function createImplementationOrchestrator(deps) {
11750
11842
  }
11751
11843
  let contractDeclarations = [];
11752
11844
  if (stateStore !== void 0) {
11845
+ const queryContractsStart = Date.now();
11753
11846
  const allContractRecords = await stateStore.queryContracts();
11847
+ _startupTimings.queryContractsMs = Date.now() - queryContractsStart;
11754
11848
  contractDeclarations = allContractRecords.map((r) => ({
11755
11849
  storyKey: r.storyKey,
11756
11850
  contractName: r.contractName,
@@ -11780,7 +11874,9 @@ function createImplementationOrchestrator(deps) {
11780
11874
  }
11781
11875
  }).filter((d) => d !== null);
11782
11876
  }
11877
+ const conflictDetectStart = Date.now();
11783
11878
  const { batches, edges: contractEdges } = detectConflictGroupsWithContracts(storyKeys, { moduleMap: pack.manifest.conflictGroups }, contractDeclarations);
11879
+ _startupTimings.conflictDetectMs = Date.now() - conflictDetectStart;
11784
11880
  if (contractEdges.length > 0) logger$26.info({
11785
11881
  contractEdges,
11786
11882
  edgeCount: contractEdges.length
@@ -11792,11 +11888,13 @@ function createImplementationOrchestrator(deps) {
11792
11888
  maxConcurrency: config.maxConcurrency
11793
11889
  }, "Orchestrator starting");
11794
11890
  if (config.skipPreflight !== true) {
11891
+ const preflightStart = Date.now();
11795
11892
  const preFlightResult = runBuildVerification({
11796
11893
  verifyCommand: pack.manifest.verifyCommand,
11797
11894
  verifyTimeoutMs: pack.manifest.verifyTimeoutMs,
11798
11895
  projectRoot: projectRoot ?? process.cwd()
11799
11896
  });
11897
+ _startupTimings.preflightMs = Date.now() - preflightStart;
11800
11898
  if (preFlightResult.status === "failed" || preFlightResult.status === "timeout") {
11801
11899
  stopHeartbeat();
11802
11900
  const truncatedOutput = (preFlightResult.output ?? "").slice(0, 2e3);
@@ -11816,6 +11914,7 @@ function createImplementationOrchestrator(deps) {
11816
11914
  }
11817
11915
  if (preFlightResult.status !== "skipped") logger$26.info("Pre-flight build check passed");
11818
11916
  }
11917
+ logger$26.info(_startupTimings, "Orchestrator startup timings (ms)");
11819
11918
  try {
11820
11919
  for (const batchGroups of batches) await runWithConcurrency(batchGroups, config.maxConcurrency);
11821
11920
  } catch (err) {
@@ -12162,14 +12261,17 @@ const PHASE_ARTIFACTS = [
12162
12261
  * 4. The first required phase WITHOUT an artifact is where we start
12163
12262
  * 5. If nothing exists → analysis (needs concept)
12164
12263
  */
12165
- function detectStartPhase(db, projectRoot) {
12264
+ function detectStartPhase(db, projectRoot, epicNumber) {
12166
12265
  try {
12167
- const storyKeys = resolveStoryKeys(db, projectRoot);
12168
- if (storyKeys.length > 0) return {
12169
- phase: "implementation",
12170
- reason: `${storyKeys.length} stories ready for implementation`,
12171
- needsConcept: false
12172
- };
12266
+ const storyKeys = resolveStoryKeys(db, projectRoot, { epicNumber });
12267
+ if (storyKeys.length > 0) {
12268
+ const scopeLabel = epicNumber !== void 0 ? ` (epic ${epicNumber})` : "";
12269
+ return {
12270
+ phase: "implementation",
12271
+ reason: `${storyKeys.length} stories ready for implementation${scopeLabel}`,
12272
+ needsConcept: false
12273
+ };
12274
+ }
12173
12275
  } catch {}
12174
12276
  let lastCompletedPhase;
12175
12277
  try {
@@ -16221,7 +16323,7 @@ async function runRunAction(options) {
16221
16323
  try {
16222
16324
  detectDb.open();
16223
16325
  runMigrations(detectDb.db);
16224
- const detection = detectStartPhase(detectDb.db, projectRoot);
16326
+ const detection = detectStartPhase(detectDb.db, projectRoot, epicNumber);
16225
16327
  if (detection.phase !== "implementation") {
16226
16328
  effectiveStartPhase = detection.phase;
16227
16329
  if (outputFormat === "human") process.stdout.write(`[AUTO-DETECT] ${detection.reason}\n`);
@@ -16700,7 +16802,7 @@ async function runRunAction(options) {
16700
16802
  writeRunMetrics(db, {
16701
16803
  run_id: pipelineRun.id,
16702
16804
  methodology: pack.manifest.name,
16703
- status: failedKeys.length > 0 || escalatedKeys.length > 0 ? "failed" : "completed",
16805
+ status: failedKeys.length > 0 ? "failed" : escalatedKeys.length > 0 ? "completed_with_escalations" : "completed",
16704
16806
  started_at: pipelineRun.created_at ?? "",
16705
16807
  completed_at: new Date().toISOString(),
16706
16808
  wall_clock_seconds: Math.round((runEndMs - runStartMs) / 1e3),
@@ -17119,4 +17221,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
17119
17221
 
17120
17222
  //#endregion
17121
17223
  export { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltNotInstalled, FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, TelemetryPersistence, VALID_PHASES, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDispatcher, createDoltClient, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initializeDolt, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
17122
- //# sourceMappingURL=run-Fzhz3-mv.js.map
17224
+ //# sourceMappingURL=run-DI9s014E.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",