substrate-ai 0.20.16 → 0.20.18

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
@@ -4,7 +4,7 @@ import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-CqtWS9wF.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-DRYcfcJG.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-DgaY0RP2.js";
8
8
  import "../errors-1uLGqnvr.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -5198,7 +5198,7 @@ async function runSupervisorAction(options, deps = {}) {
5198
5198
  await initSchema(expAdapter);
5199
5199
  const { runRunAction: runPipeline } = await import(
5200
5200
  /* @vite-ignore */
5201
- "../run-B7d0fB8W.js"
5201
+ "../run-Ds4q0vfb.js"
5202
5202
  );
5203
5203
  const runStoryFn = async (opts) => {
5204
5204
  const exitCode = await runPipeline({
@@ -7,7 +7,7 @@ import { access, readFile, readdir, stat } from "fs/promises";
7
7
  import { EventEmitter } from "node:events";
8
8
  import yaml from "js-yaml";
9
9
  import * as actualFS from "node:fs";
10
- import { accessSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
10
+ import { accessSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
11
11
  import { exec, execFile, execFileSync, execSync, spawn } from "node:child_process";
12
12
  import path, { basename as basename$1, dirname as dirname$1, extname as extname$1, isAbsolute, join as join$1, posix, resolve as resolve$1, win32 } from "node:path";
13
13
  import { tmpdir } from "node:os";
@@ -5968,27 +5968,24 @@ function getEpicShard(decisions, epicId, projectRoot, storyKey) {
5968
5968
  }
5969
5969
  const epicShard = decisions.find((d) => d.category === "epic-shard" && d.key === epicId);
5970
5970
  const shardContent = epicShard?.value;
5971
- if (shardContent) {
5972
- if (storyKey) {
5973
- const storySection = extractStorySection(shardContent, storyKey);
5974
- if (storySection) {
5975
- logger$18.debug({
5976
- epicId,
5977
- storyKey
5978
- }, "Extracted per-story section from epic shard (pre-37-0 fallback)");
5979
- return storySection;
5980
- }
5971
+ if (shardContent && storyKey) {
5972
+ const storySection = extractStorySection(shardContent, storyKey);
5973
+ if (storySection) {
5981
5974
  logger$18.debug({
5982
5975
  epicId,
5983
5976
  storyKey
5984
- }, "No matching story section found using full epic shard");
5977
+ }, "Extracted per-story section from epic shard (pre-37-0 fallback)");
5978
+ return storySection;
5985
5979
  }
5986
- return shardContent;
5980
+ logger$18.info({
5981
+ epicId,
5982
+ storyKey
5983
+ }, "Story section absent in decisions-store shard — attempting file-based fallback before returning stale shard");
5987
5984
  }
5988
5985
  if (projectRoot) {
5989
5986
  const fallback = readEpicShardFromFile(projectRoot, epicId);
5990
5987
  if (fallback) {
5991
- logger$18.info({ epicId }, "Using file-based fallback for epic shard (decisions table empty)");
5988
+ logger$18.info({ epicId }, "Using file-based fallback for epic shard");
5992
5989
  if (storyKey) {
5993
5990
  const storySection = extractStorySection(fallback, storyKey);
5994
5991
  if (storySection) {
@@ -6002,6 +5999,7 @@ function getEpicShard(decisions, epicId, projectRoot, storyKey) {
6002
5999
  return fallback;
6003
6000
  }
6004
6001
  }
6002
+ if (shardContent) return shardContent;
6005
6003
  return "";
6006
6004
  } catch (err) {
6007
6005
  logger$18.warn({
@@ -12034,7 +12032,8 @@ function createImplementationOrchestrator(deps) {
12034
12032
  const artifactsDir = projectRoot ? join$1(projectRoot, "_bmad-output", "implementation-artifacts") : void 0;
12035
12033
  if (artifactsDir && existsSync(artifactsDir)) try {
12036
12034
  const files = readdirSync(artifactsDir);
12037
- const match$2 = files.find((f$1) => f$1.startsWith(`${storyKey}-`) && f$1.endsWith(".md"));
12035
+ const STALE_SUFFIX = /\.stale-\d+\.md$/;
12036
+ const match$2 = files.find((f$1) => f$1.startsWith(`${storyKey}-`) && f$1.endsWith(".md") && !STALE_SUFFIX.test(f$1));
12038
12037
  if (match$2) {
12039
12038
  const candidatePath = join$1(artifactsDir, match$2);
12040
12039
  const validation = await isValidStoryFile(candidatePath);
@@ -12089,6 +12088,20 @@ function createImplementationOrchestrator(deps) {
12089
12088
  }
12090
12089
  });
12091
12090
  await persistState();
12091
+ } else try {
12092
+ const ts = Date.now();
12093
+ const staleName = match$2.replace(/\.md$/, `.stale-${ts}.md`);
12094
+ const stalePath = join$1(artifactsDir, staleName);
12095
+ renameSync(candidatePath, stalePath);
12096
+ logger$24.info({
12097
+ storyKey,
12098
+ staleName
12099
+ }, `[orchestrator] story ${storyKey}: renamed drifted artifact to ${staleName} before re-dispatch`);
12100
+ } catch (renameErr) {
12101
+ logger$24.warn({
12102
+ storyKey,
12103
+ err: renameErr
12104
+ }, "Failed to rename stale artifact before create-story re-dispatch; relying on 58-9d fraud-guard");
12092
12105
  }
12093
12106
  }
12094
12107
  }
@@ -14432,8 +14445,7 @@ function createImplementationOrchestrator(deps) {
14432
14445
  let escalated = 0;
14433
14446
  let failed = 0;
14434
14447
  for (const s$1 of _stories.values()) if (s$1.phase === "COMPLETE") completed++;
14435
- else if (s$1.phase === "ESCALATED") if (s$1.error !== void 0) failed++;
14436
- else escalated++;
14448
+ else if (s$1.phase === "ESCALATED") escalated++;
14437
14449
  else if (s$1.phase === "VERIFICATION_FAILED") failed++;
14438
14450
  eventBus.emit("orchestrator:complete", {
14439
14451
  totalStories: storyKeys.length,
@@ -43277,11 +43289,11 @@ async function runRunAction(options) {
43277
43289
  });
43278
43290
  process.on("SIGINT", () => {
43279
43291
  ingestionServer.stop();
43280
- process.exit(130);
43292
+ setTimeout(() => process.exit(130), 6e3).unref();
43281
43293
  });
43282
43294
  process.on("SIGTERM", () => {
43283
43295
  ingestionServer.stop();
43284
- process.exit(143);
43296
+ setTimeout(() => process.exit(143), 6e3).unref();
43285
43297
  });
43286
43298
  }
43287
43299
  if (telemetryPersistence !== void 0) {
@@ -43435,8 +43447,7 @@ async function runRunAction(options) {
43435
43447
  const failedKeys = [];
43436
43448
  const escalatedKeys = [];
43437
43449
  for (const [key, s$1] of Object.entries(status.stories)) if (s$1.phase === "COMPLETE") succeededKeys.push(key);
43438
- else if (s$1.phase === "ESCALATED") if (s$1.error !== void 0) failedKeys.push(key);
43439
- else escalatedKeys.push(key);
43450
+ else if (s$1.phase === "ESCALATED") escalatedKeys.push(key);
43440
43451
  else failedKeys.push(key);
43441
43452
  try {
43442
43453
  const runEndMs = Date.now();
@@ -43469,7 +43480,7 @@ async function runRunAction(options) {
43469
43480
  }
43470
43481
  try {
43471
43482
  const runsDir = join(dbDir, "runs");
43472
- const terminalStatus = failedKeys.length > 0 ? "failed" : "completed";
43483
+ const terminalStatus = failedKeys.length > 0 || escalatedKeys.length > 0 ? "failed" : "completed";
43473
43484
  await RunManifest.open(pipelineRun.id, runsDir).update({ run_status: terminalStatus });
43474
43485
  } catch {}
43475
43486
  if (progressRenderer !== void 0) progressRenderer.render({
@@ -43812,11 +43823,11 @@ async function runFullPipeline(options) {
43812
43823
  });
43813
43824
  process.on("SIGINT", () => {
43814
43825
  fpIngestionServer.stop();
43815
- process.exit(130);
43826
+ setTimeout(() => process.exit(130), 6e3).unref();
43816
43827
  });
43817
43828
  process.on("SIGTERM", () => {
43818
43829
  fpIngestionServer.stop();
43819
- process.exit(143);
43830
+ setTimeout(() => process.exit(143), 6e3).unref();
43820
43831
  });
43821
43832
  }
43822
43833
  const fpTelemetryPersistence = fullTelemetryEnabled ? new AdapterTelemetryPersistence(adapter) : void 0;
@@ -43880,8 +43891,7 @@ async function runFullPipeline(options) {
43880
43891
  const implStatus = await orchestrator.run(storyKeys);
43881
43892
  if (outputFormat === "human") process.stdout.write("[IMPLEMENTATION] Complete\n");
43882
43893
  for (const [key, s$1] of Object.entries(implStatus.stories)) if (s$1.phase === "COMPLETE") fpSucceededKeys.push(key);
43883
- else if (s$1.phase === "ESCALATED") if (s$1.error !== void 0) fpFailedKeys.push(key);
43884
- else fpEscalatedKeys.push(key);
43894
+ else if (s$1.phase === "ESCALATED") fpEscalatedKeys.push(key);
43885
43895
  else fpFailedKeys.push(key);
43886
43896
  }
43887
43897
  if (fullPipelineNdjsonEmitter !== void 0) fullPipelineNdjsonEmitter.emit({
@@ -44040,4 +44050,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
44040
44050
 
44041
44051
  //#endregion
44042
44052
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict, wireNdjsonEmitter };
44043
- //# sourceMappingURL=run-DRYcfcJG.js.map
44053
+ //# sourceMappingURL=run-DgaY0RP2.js.map
@@ -2,7 +2,7 @@ import "./health-ZGa9E0D2.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-CqtWS9wF.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-DRYcfcJG.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-DgaY0RP2.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.16",
3
+ "version": "0.20.18",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,6 +29,24 @@ Using the context above, write a complete, implementation-ready story file for s
29
29
  Use the title, description, and acceptance criteria from the Story Definition — do NOT substitute
30
30
  a different story from the epic scope. The story key, title, and core scope are non-negotiable.
31
31
 
32
+ ## Input Validation (fail-loud)
33
+
34
+ Before anything else, verify the input contains the source Acceptance Criteria for story `{{story_key}}`. Scan `Epic Scope` and `Story Definition` for BOTH:
35
+
36
+ - A heading matching `Story {{story_key}}` (separators: `-`, `.`, `_`, space).
37
+ - An AC-bearing block within that section (`## Acceptance Criteria`, `### Acceptance Criteria`, `**Acceptance Criteria:**`, etc.).
38
+
39
+ If either is missing — shard truncated, context about other stories only — **do not infer, guess, or hallucinate an AC from the story key or domain priors**. A prior substrate session recorded a shape-specific drift exactly here: no source AC for a "graph builder" story → the agent invented a LanceDB+class-based spec, contradicting the author's explicit "plain JSON adjacency list" directive, purely from a trained pattern.
40
+
41
+ Instead, emit immediately per the Output Contract below:
42
+
43
+ ```yaml
44
+ result: failure
45
+ error: source-ac-content-missing
46
+ ```
47
+
48
+ Do NOT write a partial story file. Do NOT paraphrase surrounding context. Do NOT dispatch Write. The orchestrator treats this as terminal — the correct outcome when the input pipeline has degraded.
49
+
32
50
  ## Instructions
33
51
 
34
52
  1. **Use the Story Definition as your primary input** — it specifies exactly what this story builds. The epic scope provides surrounding context only.