substrate-ai 0.20.62 → 0.20.64

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,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, parseRuntimeProbes, readCurrentRunId, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics } from "../health-BoXxsFSF.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, parseRuntimeProbes, readCurrentRunId, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics } from "../health-DudlnqXd.js";
3
3
  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, InMemoryDatabaseAdapter, 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-W2emvN3F.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-CRz08RrU.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-CCxsv-9M.js";
8
8
  import "../errors-CKFu8YI9.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
11
- import "../version-manager-impl-BmOWu8ml.js";
12
- import { registerUpgradeCommand } from "../upgrade-CAqLkNUP.js";
11
+ import "../decision-router-BA__VYIp.js";
12
+ import "../version-manager-impl-FH4TTnXm.js";
13
+ import { registerUpgradeCommand } from "../upgrade-aW7GYL2F.js";
13
14
  import { Command } from "commander";
14
15
  import { fileURLToPath } from "url";
15
16
  import { dirname, join, resolve } from "path";
@@ -7486,7 +7487,7 @@ async function runStatusAction(options) {
7486
7487
  logger$15.debug({ err }, "Work graph query failed, continuing without work graph data");
7487
7488
  }
7488
7489
  if (run === void 0) {
7489
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-FZVOBYND.js");
7490
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-CLNmnZiw.js");
7490
7491
  const substrateDirPath = join(projectRoot, ".substrate");
7491
7492
  const processInfo = inspectProcessTree$1({
7492
7493
  projectRoot,
@@ -9018,7 +9019,7 @@ async function runSupervisorAction(options, deps = {}) {
9018
9019
  try {
9019
9020
  const { createExperimenter } = await import(
9020
9021
  /* @vite-ignore */
9021
- "../experimenter-DxxwicpK.js"
9022
+ "../experimenter-BgpUcUaW.js"
9022
9023
  );
9023
9024
  const { getLatestRun: getLatest } = await import(
9024
9025
  /* @vite-ignore */
@@ -9032,7 +9033,7 @@ async function runSupervisorAction(options, deps = {}) {
9032
9033
  await initSchema(expAdapter);
9033
9034
  const { runRunAction: runPipeline } = await import(
9034
9035
  /* @vite-ignore */
9035
- "../run-BxfeSz6G.js"
9036
+ "../run-ChxsPICN.js"
9036
9037
  );
9037
9038
  const runStoryFn = async (opts) => {
9038
9039
  const exitCode = await runPipeline({
@@ -14035,8 +14036,8 @@ async function createProgram() {
14035
14036
  /** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
14036
14037
  function checkForUpdatesInBackground(currentVersion) {
14037
14038
  if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
14038
- import("../upgrade-C8LAnB6W.js").then(async () => {
14039
- const { createVersionManager } = await import("../version-manager-impl-DaA_ALYK.js");
14039
+ import("../upgrade-OFeC_NIx.js").then(async () => {
14040
+ const { createVersionManager } = await import("../version-manager-impl-BCSf5E3j.js");
14040
14041
  const vm = createVersionManager();
14041
14042
  const result = await vm.checkForUpdates();
14042
14043
  if (result.updateAvailable) {
@@ -0,0 +1,97 @@
1
+ //#region src/modules/decision-router/index.ts
2
+ /**
3
+ * Maps each known decision type to its severity level.
4
+ *
5
+ * Epic 70: cross-story-race-recovered (info — log only, no halt) and
6
+ * cross-story-race-still-failed (critical — recovery exhausted, halt for operator)
7
+ * motivate the registry pattern. Epic 73 (Recovery Engine) will register
8
+ * additional decision types here.
9
+ */
10
+ const DECISION_SEVERITY_MAP = {
11
+ "cost-ceiling-exhausted": "critical",
12
+ "build-verification-failure": "critical",
13
+ "recovery-retry-attempt": "info",
14
+ "re-scope-proposal": "warning",
15
+ "scope-violation": "fatal",
16
+ "cross-story-race-recovered": "info",
17
+ "cross-story-race-still-failed": "critical",
18
+ "pipeline-escalation": "warning"
19
+ };
20
+ /**
21
+ * Default action to apply when a decision does NOT trigger a halt.
22
+ * Caller invokes the returned defaultAction string autonomously.
23
+ */
24
+ const DEFAULT_ACTION_MAP = {
25
+ "cost-ceiling-exhausted": "skip-remaining",
26
+ "build-verification-failure": "escalate-without-halt",
27
+ "recovery-retry-attempt": "continue-autonomous",
28
+ "re-scope-proposal": "escalate-without-halt",
29
+ "scope-violation": "abort-pipeline",
30
+ "cross-story-race-recovered": "continue-autonomous",
31
+ "cross-story-race-still-failed": "escalate-without-halt",
32
+ "pipeline-escalation": "escalate-without-halt"
33
+ };
34
+ /** Fallback default action for unknown decision types. */
35
+ const DEFAULT_DEFAULT_ACTION = "escalate-without-halt";
36
+ /**
37
+ * Route a halt-able decision through the autonomy policy.
38
+ *
39
+ * Pure function — no I/O, no side effects. All orchestrator state interactions
40
+ * remain in orchestrator-impl.ts.
41
+ *
42
+ * Halt policy logic (AC4):
43
+ * - 'all': halts on info | warning | critical | fatal
44
+ * - 'critical': halts on critical | fatal (default)
45
+ * - 'none': halts ONLY on fatal (scope violations bypass the autonomy-gradient
46
+ * policy — they are always halts regardless of the chosen policy)
47
+ *
48
+ * Fatal always halts regardless of policy — hard safety invariant, not configurable.
49
+ *
50
+ * Unknown decision types default to severity 'critical' (safe default, AC9e).
51
+ *
52
+ * @param decision - The decision type string to route
53
+ * @param policy - The autonomy policy from the --halt-on CLI flag
54
+ * @returns { halt: boolean, defaultAction: string, severity: Severity }
55
+ */
56
+ function routeDecision(decision, policy) {
57
+ const severity = DECISION_SEVERITY_MAP[decision] ?? "critical";
58
+ let halt;
59
+ switch (policy) {
60
+ case "all":
61
+ halt = true;
62
+ break;
63
+ case "critical":
64
+ halt = severity === "critical" || severity === "fatal";
65
+ break;
66
+ case "none":
67
+ halt = severity === "fatal";
68
+ break;
69
+ default: halt = severity === "critical" || severity === "fatal";
70
+ }
71
+ if (severity === "fatal") halt = true;
72
+ const defaultAction = DEFAULT_ACTION_MAP[decision] ?? DEFAULT_DEFAULT_ACTION;
73
+ return {
74
+ halt,
75
+ defaultAction,
76
+ severity
77
+ };
78
+ }
79
+ /**
80
+ * Derive the machine-readable exit code from pipeline completion results.
81
+ *
82
+ * - Exit `0` when all stories succeeded (or recovered cleanly)
83
+ * - Exit `1` when some stories escalated; run completed
84
+ * - Exit `2` when run-level failure (cost ceiling, fatal halt, orchestrator died, stories failed)
85
+ *
86
+ * @param outcome - Pipeline completion outcome data
87
+ * @returns 0 (success), 1 (escalated), or 2 (failure)
88
+ */
89
+ function deriveExitCode(outcome) {
90
+ if (outcome.failed.length > 0 || outcome.costCeilingExhausted === true || outcome.fatalHaltReached === true || outcome.orchestratorDied === true) return 2;
91
+ if (outcome.escalated.length > 0 && outcome.failed.length === 0) return 1;
92
+ return 0;
93
+ }
94
+
95
+ //#endregion
96
+ export { DECISION_SEVERITY_MAP, deriveExitCode, routeDecision };
97
+ //# sourceMappingURL=decision-router-BA__VYIp.js.map
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-BoXxsFSF.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-DudlnqXd.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-W2emvN3F.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -7,7 +7,7 @@ import { readFile } from "fs/promises";
7
7
  import { EventEmitter } from "node:events";
8
8
  import { YAMLException, load } from "js-yaml";
9
9
  import { existsSync, promises, readFileSync, readdirSync, statSync } from "node:fs";
10
- import { spawn, spawnSync } from "node:child_process";
10
+ import { execSync, spawn, spawnSync } from "node:child_process";
11
11
  import * as path$2 from "node:path";
12
12
  import { basename as basename$1, dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
13
13
  import { z } from "zod";
@@ -5548,6 +5548,227 @@ function createDefaultVerificationPipeline(bus, config) {
5548
5548
  return new VerificationPipeline(bus, checks);
5549
5549
  }
5550
5550
 
5551
+ //#endregion
5552
+ //#region packages/sdlc/dist/verification/cross-story-race-recovery.js
5553
+ /**
5554
+ * Detect stories whose verification results were recorded before concurrent
5555
+ * story commits landed.
5556
+ *
5557
+ * Stale-verification boundary condition:
5558
+ * t.committedAt > s.verifiedAt
5559
+ * AND
5560
+ * t.modifiedFiles ∩ (s.modifiedFiles ∪ s.testFiles) ≠ ∅
5561
+ *
5562
+ * Where:
5563
+ * s = the story we're checking for staleness
5564
+ * t = another story in the batch that may have committed after s verified
5565
+ *
5566
+ * Pure function — all data must be pre-resolved in the `batch` entries.
5567
+ * Returns empty array when no stale stories are detected (idempotent, no-op).
5568
+ *
5569
+ * @param batch Pre-resolved story data for all stories in the concurrent batch.
5570
+ * @param _manifest Manifest instance (accepted for API symmetry; not used in pure logic).
5571
+ * @returns Array of story keys that have stale verification results.
5572
+ */
5573
+ function detectStaleVerifications(batch, _manifest) {
5574
+ if (batch.length < 2) return [];
5575
+ const staleKeys = [];
5576
+ for (const s of batch) {
5577
+ const sVerifiedAt = s.verifiedAt;
5578
+ if (sVerifiedAt === void 0) continue;
5579
+ const sPrimaryFiles = s.modifiedFiles ?? s.verificationResultFiles ?? [];
5580
+ const sTestFiles = s.testFiles ?? [];
5581
+ const sAllFiles = new Set([...sPrimaryFiles, ...sTestFiles]);
5582
+ if (sAllFiles.size === 0) continue;
5583
+ for (const t of batch) {
5584
+ if (t.storyKey === s.storyKey) continue;
5585
+ const tCommittedAt = t.committedAt;
5586
+ if (tCommittedAt === void 0) continue;
5587
+ let tTime, sTime;
5588
+ try {
5589
+ tTime = new Date(tCommittedAt).getTime();
5590
+ sTime = new Date(sVerifiedAt).getTime();
5591
+ } catch {
5592
+ continue;
5593
+ }
5594
+ if (isNaN(tTime) || isNaN(sTime)) continue;
5595
+ if (tTime <= sTime) continue;
5596
+ const tFiles = t.modifiedFiles ?? [];
5597
+ const hasOverlap = tFiles.some((f) => sAllFiles.has(f));
5598
+ if (hasOverlap) {
5599
+ staleKeys.push(s.storyKey);
5600
+ break;
5601
+ }
5602
+ }
5603
+ }
5604
+ return staleKeys;
5605
+ }
5606
+ /**
5607
+ * Resolves the commit timestamp for a story's implementation commit.
5608
+ *
5609
+ * Uses the canonical `feat(story-<storyKey>):` commit message pattern to
5610
+ * locate the story's auto-commit in git history.
5611
+ *
5612
+ * @param storyKey Story key (e.g. "70-1")
5613
+ * @param workingDir Absolute path to the git repo root
5614
+ * @returns ISO-8601 commit timestamp string, or `undefined` if not found
5615
+ */
5616
+ function CommittedAtResolver(storyKey, workingDir) {
5617
+ try {
5618
+ const result = execSync(`git log --format=%cI --grep="feat(story-${storyKey}):" -1`, {
5619
+ cwd: workingDir,
5620
+ encoding: "utf-8",
5621
+ stdio: [
5622
+ "ignore",
5623
+ "pipe",
5624
+ "pipe"
5625
+ ]
5626
+ }).trim();
5627
+ return result.length > 0 ? result : void 0;
5628
+ } catch {
5629
+ return void 0;
5630
+ }
5631
+ }
5632
+ /**
5633
+ * Cross-story race recovery action handler.
5634
+ *
5635
+ * Orchestrates the full recovery arc for stories with stale verification
5636
+ * results caused by concurrent story commits landing after verification ran:
5637
+ *
5638
+ * 1. Resolves `committedAt` for each batch story via git log.
5639
+ * 2. Enriches BatchEntry[] from manifest per_story_state when fields absent.
5640
+ * 3. Calls `detectStaleVerifications` to identify stale story keys.
5641
+ * 4. Returns `{ noStale: true }` immediately when none are stale (idempotent).
5642
+ * 5. For each stale story:
5643
+ * a. Transitions status to `verification-stale` in manifest.
5644
+ * b. Re-runs `createDefaultVerificationPipeline` against current tree.
5645
+ * c. On pass: transitions to `complete`, emits `pipeline:cross-story-race-recovered`.
5646
+ * d. On fail: transitions to `failed` with `verification_re_run: true`,
5647
+ * emits `pipeline:cross-story-race-still-failed`.
5648
+ *
5649
+ * Implements AC3 canonical-helper discipline: all state reads use RunManifest,
5650
+ * all persistence uses the injected adapter. No direct file reads of manifest.json.
5651
+ *
5652
+ * @param input Recovery input (see StaleVerificationRecoveryInput)
5653
+ * @returns Recovery summary
5654
+ */
5655
+ async function runStaleVerificationRecovery(input) {
5656
+ const { runId, batch, workingDir, bus, manifest } = input;
5657
+ const enrichedBatch = [];
5658
+ let manifestData = null;
5659
+ try {
5660
+ manifestData = await manifest.read();
5661
+ } catch {}
5662
+ for (const entry of batch) {
5663
+ const committedAt = CommittedAtResolver(entry.storyKey, workingDir);
5664
+ let verifiedAt = entry.verifiedAt;
5665
+ let modifiedFiles = entry.modifiedFiles;
5666
+ let testFiles = entry.testFiles;
5667
+ let verificationResultFiles = entry.verificationResultFiles;
5668
+ if (manifestData !== null) {
5669
+ const perStory = manifestData.per_story_state[entry.storyKey];
5670
+ if (perStory !== void 0) {
5671
+ if (verifiedAt === void 0) verifiedAt = perStory.completed_at;
5672
+ if (modifiedFiles === void 0 && perStory.dev_story_signals?.files_modified !== void 0) modifiedFiles = perStory.dev_story_signals.files_modified;
5673
+ if (verificationResultFiles === void 0 && perStory.dev_story_signals?.files_modified !== void 0) verificationResultFiles = perStory.dev_story_signals.files_modified;
5674
+ }
5675
+ }
5676
+ const enriched = { storyKey: entry.storyKey };
5677
+ if (committedAt !== void 0) enriched.committedAt = committedAt;
5678
+ if (verifiedAt !== void 0) enriched.verifiedAt = verifiedAt;
5679
+ if (modifiedFiles !== void 0) enriched.modifiedFiles = modifiedFiles;
5680
+ if (testFiles !== void 0) enriched.testFiles = testFiles;
5681
+ if (verificationResultFiles !== void 0) enriched.verificationResultFiles = verificationResultFiles;
5682
+ enrichedBatch.push(enriched);
5683
+ }
5684
+ const staleKeys = detectStaleVerifications(enrichedBatch, manifest);
5685
+ if (staleKeys.length === 0) return {
5686
+ recovered: [],
5687
+ stillFailed: [],
5688
+ noStale: true
5689
+ };
5690
+ const recovered = [];
5691
+ const stillFailed = [];
5692
+ const verificationPipeline = createDefaultVerificationPipeline(bus);
5693
+ for (const storyKey of staleKeys) {
5694
+ const recoveryStart = Date.now();
5695
+ let originalFindings = [];
5696
+ if (manifestData !== null) {
5697
+ const perStory = manifestData.per_story_state[storyKey];
5698
+ if (perStory?.verification_result !== void 0) originalFindings = (perStory.verification_result.checks ?? []).flatMap((c) => c.findings ?? []);
5699
+ }
5700
+ await manifest.patchStoryState(storyKey, { status: "verification-stale" }).catch(() => {});
5701
+ let commitSha;
5702
+ try {
5703
+ commitSha = execSync("git rev-parse HEAD", {
5704
+ cwd: workingDir,
5705
+ encoding: "utf-8",
5706
+ stdio: [
5707
+ "ignore",
5708
+ "pipe",
5709
+ "pipe"
5710
+ ]
5711
+ }).trim();
5712
+ } catch {
5713
+ commitSha = "unknown";
5714
+ }
5715
+ let storyContent;
5716
+ try {
5717
+ const artifactsDir = `${workingDir}/_bmad-output/implementation-artifacts`;
5718
+ if (existsSync(artifactsDir)) {
5719
+ const STALE_SUFFIX = /\.stale-\d+\.md$/;
5720
+ const files = readdirSync(artifactsDir);
5721
+ const match = files.find((f) => f.startsWith(`${storyKey}-`) && f.endsWith(".md") && !STALE_SUFFIX.test(f));
5722
+ if (match !== void 0) storyContent = readFileSync(`${artifactsDir}/${match}`, "utf-8");
5723
+ }
5724
+ } catch {}
5725
+ const verifContext = {
5726
+ storyKey,
5727
+ workingDir,
5728
+ commitSha,
5729
+ timeout: 6e4,
5730
+ ...storyContent !== void 0 ? { storyContent } : {}
5731
+ };
5732
+ const freshSummary = await verificationPipeline.run(verifContext, "A");
5733
+ const freshFindings = (freshSummary.checks ?? []).flatMap((c) => c.findings ?? []);
5734
+ const recoveryDurationMs = Date.now() - recoveryStart;
5735
+ if (freshSummary.status === "pass" || freshSummary.status === "warn") {
5736
+ await manifest.patchStoryState(storyKey, {
5737
+ status: "complete",
5738
+ verification_result: freshSummary,
5739
+ completed_at: new Date().toISOString()
5740
+ }).catch(() => {});
5741
+ bus.emit("pipeline:cross-story-race-recovered", {
5742
+ runId,
5743
+ storyKey,
5744
+ originalFindings,
5745
+ freshFindings,
5746
+ recoveryDurationMs
5747
+ });
5748
+ recovered.push(storyKey);
5749
+ } else {
5750
+ await manifest.patchStoryState(storyKey, {
5751
+ status: "failed",
5752
+ verification_result: freshSummary,
5753
+ verification_re_run: true,
5754
+ completed_at: new Date().toISOString()
5755
+ }).catch(() => {});
5756
+ bus.emit("pipeline:cross-story-race-still-failed", {
5757
+ runId,
5758
+ storyKey,
5759
+ freshFindings,
5760
+ recoveryDurationMs
5761
+ });
5762
+ stillFailed.push(storyKey);
5763
+ }
5764
+ }
5765
+ return {
5766
+ recovered,
5767
+ stillFailed,
5768
+ noStale: false
5769
+ };
5770
+ }
5771
+
5551
5772
  //#endregion
5552
5773
  //#region packages/sdlc/dist/run-model/cli-flags.js
5553
5774
  /**
@@ -5566,7 +5787,16 @@ const CliFlagsSchema = z.object({
5566
5787
  cost_ceiling: z.number().positive().optional(),
5567
5788
  agent: z.string().optional(),
5568
5789
  skip_verification: z.boolean().optional(),
5569
- events: z.boolean().optional()
5790
+ events: z.boolean().optional(),
5791
+ non_interactive: z.boolean().optional(),
5792
+ halt_skipped: z.boolean().optional(),
5793
+ halt_skipped_decisions: z.array(z.object({
5794
+ decisionType: z.string(),
5795
+ severity: z.string(),
5796
+ defaultAction: z.string(),
5797
+ reason: z.string(),
5798
+ skippedAt: z.string()
5799
+ })).optional()
5570
5800
  });
5571
5801
 
5572
5802
  //#endregion
@@ -5704,6 +5934,7 @@ const PerStoryStatusSchema = z.union([
5704
5934
  z.literal("verification-failed"),
5705
5935
  z.literal("gated"),
5706
5936
  z.literal("skipped"),
5937
+ z.literal("verification-stale"),
5707
5938
  z.string()
5708
5939
  ]);
5709
5940
  /**
@@ -5735,7 +5966,8 @@ const PerStoryStateSchema = z.object({
5735
5966
  z.literal("state-integrating"),
5736
5967
  z.literal("both"),
5737
5968
  z.string()
5738
- ]).optional()
5969
+ ]).optional(),
5970
+ verification_re_run: z.boolean().optional()
5739
5971
  });
5740
5972
 
5741
5973
  //#endregion
@@ -7340,5 +7572,5 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
7340
7572
  }
7341
7573
 
7342
7574
  //#endregion
7343
- export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, __commonJS, __require, __toESM, aggregateProbeAuthorMetrics, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, parseRuntimeProbes, readCurrentRunId, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runHealthAction, validateStoryKey };
7344
- //# sourceMappingURL=health-BoXxsFSF.js.map
7575
+ export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, __commonJS, __require, __toESM, aggregateProbeAuthorMetrics, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, parseRuntimeProbes, readCurrentRunId, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runHealthAction, runStaleVerificationRecovery, validateStoryKey };
7576
+ //# sourceMappingURL=health-DudlnqXd.js.map
package/dist/index.d.ts CHANGED
@@ -732,6 +732,28 @@ interface CostCeilingReachedEvent {
732
732
  /** 'critical' when halt_on is 'all' or 'critical'; absent when 'none' */
733
733
  severity?: string;
734
734
  }
735
+ /**
736
+ * Emitted when --non-interactive mode suppresses a halt decision and applies
737
+ * the default action autonomously instead of prompting the operator.
738
+ *
739
+ * Story 72-2: closes the NDJSON protocol gap — operators can see which halts
740
+ * were auto-skipped and what actions were applied via `substrate report`.
741
+ */
742
+ interface DecisionHaltSkippedNonInteractiveEvent {
743
+ type: 'decision:halt-skipped-non-interactive';
744
+ /** ISO-8601 timestamp generated at emit time */
745
+ ts: string;
746
+ /** Pipeline run ID */
747
+ run_id: string;
748
+ /** Halt decision type that was skipped (e.g., 'halt:escalation', 'cost-ceiling-exhausted') */
749
+ decision_type: string;
750
+ /** Severity of the skipped halt (e.g., 'critical') */
751
+ severity: string;
752
+ /** Action that was applied in place of the operator prompt (e.g., 'continue-autonomous') */
753
+ default_action: string;
754
+ /** Human-readable reason for skipping */
755
+ reason: string;
756
+ }
735
757
  /**
736
758
  * Discriminated union of all pipeline event types.
737
759
  *
@@ -744,7 +766,7 @@ interface CostCeilingReachedEvent {
744
766
  * }
745
767
  * ```
746
768
  */
747
- type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | PipelinePreFlightFailureEvent | PipelineProfileStaleEvent | PipelineContractMismatchEvent | PipelineContractVerificationSummaryEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | StoryInterfaceChangeWarningEvent | StoryMetricsEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent | RoutingModelSelectedEvent | PipelinePhaseStartEvent | PipelinePhaseCompleteEvent | StoryAutoApprovedEvent | VerificationCheckCompleteEvent | VerificationStoryCompleteEvent | CostWarningEvent | CostCeilingReachedEvent; //#endregion
769
+ type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | PipelinePreFlightFailureEvent | PipelineProfileStaleEvent | PipelineContractMismatchEvent | PipelineContractVerificationSummaryEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | StoryInterfaceChangeWarningEvent | StoryMetricsEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent | RoutingModelSelectedEvent | PipelinePhaseStartEvent | PipelinePhaseCompleteEvent | StoryAutoApprovedEvent | VerificationCheckCompleteEvent | VerificationStoryCompleteEvent | CostWarningEvent | CostCeilingReachedEvent | DecisionHaltSkippedNonInteractiveEvent; //#endregion
748
770
  //#region packages/core/dist/types.d.ts
749
771
 
750
772
  /**
@@ -2479,6 +2501,87 @@ interface OrchestratorEvents {
2479
2501
  /** Hash computed from the current source epic's AC section */
2480
2502
  currentHash: string;
2481
2503
  };
2504
+ /**
2505
+ * Cross-story race recovery succeeded: fresh verification passed for a story
2506
+ * whose original result was stale due to a concurrent story committing after
2507
+ * the original verification ran.
2508
+ *
2509
+ * Story 70-1. Motivating incidents: Epic 66 (run a832487a), Epic 67 (run a59e4c96).
2510
+ * Mirror of CoreEvents['pipeline:cross-story-race-recovered']; both must stay in sync.
2511
+ */
2512
+ 'pipeline:cross-story-race-recovered': {
2513
+ runId: string;
2514
+ storyKey: string;
2515
+ originalFindings: unknown[];
2516
+ freshFindings: unknown[];
2517
+ recoveryDurationMs: number;
2518
+ };
2519
+ /**
2520
+ * Cross-story race recovery completed but fresh verification still failed:
2521
+ * the story genuinely has issues that are not attributable to the race condition.
2522
+ *
2523
+ * Story 70-1. Motivating incidents: Epic 66 (run a832487a), Epic 67 (run a59e4c96).
2524
+ * Mirror of CoreEvents['pipeline:cross-story-race-still-failed']; both must stay in sync.
2525
+ */
2526
+ 'pipeline:cross-story-race-still-failed': {
2527
+ runId: string;
2528
+ storyKey: string;
2529
+ freshFindings: unknown[];
2530
+ recoveryDurationMs: number;
2531
+ };
2532
+ /**
2533
+ * Story 72-2: A critical halt decision was skipped under --non-interactive mode.
2534
+ *
2535
+ * Emitted when the pipeline would have prompted the operator (interactive stdin
2536
+ * read) but --non-interactive suppressed stdin and auto-applied the default
2537
+ * action instead. Operators reviewing the run via `substrate report` can see
2538
+ * which halts were auto-skipped and what actions were applied.
2539
+ *
2540
+ * Mirror of CoreEvents['decision:halt-skipped-non-interactive']; both must stay
2541
+ * in sync.
2542
+ */
2543
+ 'decision:halt-skipped-non-interactive': {
2544
+ runId: string;
2545
+ /** Halt decision type that was skipped (e.g., 'halt:escalation', 'halt:critical'). */
2546
+ decisionType: string;
2547
+ /** Severity of the skipped halt (e.g., 'critical'). */
2548
+ severity: string;
2549
+ /** Action that was applied in place of the operator prompt (e.g., 'continue'). */
2550
+ defaultAction: string;
2551
+ /** Human-readable reason for skipping (e.g., 'non-interactive: stdin prompt suppressed'). */
2552
+ reason: string;
2553
+ };
2554
+ /**
2555
+ * Story 72-1: A halt-able decision was routed and the policy requires halting.
2556
+ * Emitted when routeDecision() returns halt=true for a given policy.
2557
+ * Mirror of CoreEvents['decision:halt']; both must stay in sync.
2558
+ */
2559
+ 'decision:halt': {
2560
+ runId: string;
2561
+ /** The decision type that triggered the halt (e.g., 'cost-ceiling-exhausted'). */
2562
+ decisionType: string;
2563
+ /** Severity of the decision (e.g., 'critical', 'fatal'). */
2564
+ severity: string;
2565
+ /** Human-readable reason for the halt. */
2566
+ reason: string;
2567
+ };
2568
+ /**
2569
+ * Story 72-1: A halt-able decision was routed and the policy allows autonomous action.
2570
+ * Emitted when routeDecision() returns halt=false for a given policy.
2571
+ * Caller applies defaultAction without operator intervention.
2572
+ * Mirror of CoreEvents['decision:autonomous']; both must stay in sync.
2573
+ */
2574
+ 'decision:autonomous': {
2575
+ runId: string;
2576
+ /** The decision type that was routed autonomously (e.g., 'build-verification-failure'). */
2577
+ decisionType: string;
2578
+ /** Severity of the decision (e.g., 'info', 'warning', 'critical'). */
2579
+ severity: string;
2580
+ /** The action applied autonomously (e.g., 'escalate-without-halt', 'continue-autonomous'). */
2581
+ defaultAction: string;
2582
+ /** Human-readable reason for autonomous action. */
2583
+ reason: string;
2584
+ };
2482
2585
  }
2483
2586
 
2484
2587
  //#endregion
@@ -1,7 +1,8 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-BoXxsFSF.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, runStaleVerificationRecovery, validateStoryKey } from "./health-DudlnqXd.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
4
  import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-W2emvN3F.js";
5
+ import { deriveExitCode, routeDecision } from "./decision-router-BA__VYIp.js";
5
6
  import { basename, dirname, extname, join } from "path";
6
7
  import { access, readFile, readdir, stat } from "fs/promises";
7
8
  import { EventEmitter } from "node:events";
@@ -1764,7 +1765,7 @@ const PIPELINE_EVENT_METADATA = [
1764
1765
  {
1765
1766
  name: "action",
1766
1767
  type: "string",
1767
- description: "Action taken (always stopped in this story; interactive prompt is Epic 54 scope)."
1768
+ description: "Action taken stopped when policy requires halt, or the defaultAction when proceeding autonomously."
1768
1769
  },
1769
1770
  {
1770
1771
  name: "skipped_stories",
@@ -1774,10 +1775,47 @@ const PIPELINE_EVENT_METADATA = [
1774
1775
  {
1775
1776
  name: "severity",
1776
1777
  type: "string",
1777
- description: "critical when halt_on is all or critical; absent when none.",
1778
+ description: "Severity from routeDecision (critical for cost-ceiling-exhausted).",
1778
1779
  optional: true
1779
1780
  }
1780
1781
  ]
1782
+ },
1783
+ {
1784
+ type: "decision:halt-skipped-non-interactive",
1785
+ description: "A critical halt decision was skipped under --non-interactive mode; default action was applied autonomously.",
1786
+ when: "Emitted when --non-interactive suppresses an operator prompt and applies the default action. Story 72-2.",
1787
+ fields: [
1788
+ {
1789
+ name: "ts",
1790
+ type: "string",
1791
+ description: "Timestamp."
1792
+ },
1793
+ {
1794
+ name: "run_id",
1795
+ type: "string",
1796
+ description: "Pipeline run ID."
1797
+ },
1798
+ {
1799
+ name: "decision_type",
1800
+ type: "string",
1801
+ description: "Halt decision type that was skipped (e.g., halt:escalation)."
1802
+ },
1803
+ {
1804
+ name: "severity",
1805
+ type: "string",
1806
+ description: "Severity of the skipped halt (e.g., critical)."
1807
+ },
1808
+ {
1809
+ name: "default_action",
1810
+ type: "string",
1811
+ description: "Action applied in place of the operator prompt."
1812
+ },
1813
+ {
1814
+ name: "reason",
1815
+ type: "string",
1816
+ description: "Human-readable reason for skipping."
1817
+ }
1818
+ ]
1781
1819
  }
1782
1820
  ];
1783
1821
  /**
@@ -14284,6 +14322,27 @@ function createImplementationOrchestrator(deps) {
14284
14322
  }, "Build-fix dispatch failed — escalating");
14285
14323
  }
14286
14324
  if (!buildFixPassed) {
14325
+ let buildHaltPolicy = "critical";
14326
+ if (runManifest !== null && runManifest !== void 0) try {
14327
+ const manifestData = await runManifest.read();
14328
+ buildHaltPolicy = manifestData.cli_flags.halt_on ?? "critical";
14329
+ } catch {}
14330
+ const buildRouteResult = routeDecision("build-verification-failure", buildHaltPolicy);
14331
+ const buildRunId = config.pipelineRunId ?? "unknown";
14332
+ const buildReason = `build verification failed for story ${storyKey}`;
14333
+ if (buildRouteResult.halt) eventBus.emit("decision:halt", {
14334
+ runId: buildRunId,
14335
+ decisionType: "build-verification-failure",
14336
+ severity: buildRouteResult.severity,
14337
+ reason: buildReason
14338
+ });
14339
+ else eventBus.emit("decision:autonomous", {
14340
+ runId: buildRunId,
14341
+ decisionType: "build-verification-failure",
14342
+ severity: buildRouteResult.severity,
14343
+ defaultAction: buildRouteResult.defaultAction,
14344
+ reason: buildReason
14345
+ });
14287
14346
  eventBus.emit("story:build-verification-failed", {
14288
14347
  storyKey,
14289
14348
  exitCode: buildVerifyResult.exitCode ?? 1,
@@ -14292,7 +14351,9 @@ function createImplementationOrchestrator(deps) {
14292
14351
  logger$26.warn({
14293
14352
  storyKey,
14294
14353
  reason,
14295
- exitCode: buildVerifyResult.exitCode
14354
+ exitCode: buildVerifyResult.exitCode,
14355
+ routedHalt: buildRouteResult.halt,
14356
+ defaultAction: buildRouteResult.defaultAction
14296
14357
  }, "Build verification failed — escalating story");
14297
14358
  updateStory(storyKey, {
14298
14359
  phase: "ESCALATED",
@@ -15234,7 +15295,23 @@ function createImplementationOrchestrator(deps) {
15234
15295
  * @param manifest - The current run manifest data
15235
15296
  */
15236
15297
  async function handleCeilingExceeded(triggeredStoryKey, remainingInGroup, result, manifest) {
15237
- const haltOn = manifest.cli_flags.halt_on ?? "none";
15298
+ const haltPolicy = manifest.cli_flags.halt_on ?? "critical";
15299
+ const routeResult = routeDecision("cost-ceiling-exhausted", haltPolicy);
15300
+ const runId = config.pipelineRunId ?? "unknown";
15301
+ const reason = `cost ceiling exceeded: ${result.cumulative.toFixed(4)} USD >= ${result.ceiling} USD`;
15302
+ if (routeResult.halt) eventBus.emit("decision:halt", {
15303
+ runId,
15304
+ decisionType: "cost-ceiling-exhausted",
15305
+ severity: routeResult.severity,
15306
+ reason
15307
+ });
15308
+ else eventBus.emit("decision:autonomous", {
15309
+ runId,
15310
+ decisionType: "cost-ceiling-exhausted",
15311
+ severity: routeResult.severity,
15312
+ defaultAction: routeResult.defaultAction,
15313
+ reason
15314
+ });
15238
15315
  const allSkipped = [triggeredStoryKey, ...remainingInGroup];
15239
15316
  for (const [key, state] of _stories) if (state.phase === "PENDING" && !allSkipped.includes(key)) allSkipped.push(key);
15240
15317
  for (const key of allSkipped) {
@@ -15248,16 +15325,18 @@ function createImplementationOrchestrator(deps) {
15248
15325
  eventBus.emit("cost:ceiling-reached", {
15249
15326
  cumulative_cost: result.cumulative,
15250
15327
  ceiling: result.ceiling,
15251
- halt_on: haltOn,
15252
- action: "stopped",
15328
+ halt_on: haltPolicy,
15329
+ action: routeResult.halt ? "stopped" : routeResult.defaultAction,
15253
15330
  skipped_stories: allSkipped,
15254
- ...haltOn !== "none" ? { severity: "critical" } : {}
15331
+ severity: routeResult.severity
15255
15332
  });
15256
15333
  _budgetExhausted = true;
15257
15334
  logger$26.warn({
15258
15335
  skipped: allSkipped.length,
15259
15336
  cumulative: result.cumulative,
15260
- ceiling: result.ceiling
15337
+ ceiling: result.ceiling,
15338
+ routedHalt: routeResult.halt,
15339
+ defaultAction: routeResult.defaultAction
15261
15340
  }, "Cost ceiling reached — stopping dispatch");
15262
15341
  }
15263
15342
  /**
@@ -15745,7 +15824,36 @@ function createImplementationOrchestrator(deps) {
15745
15824
  try {
15746
15825
  for (const rawBatchGroups of batches) {
15747
15826
  const batchGroups = detectAndSerializeConcurrentFileCollisions(rawBatchGroups);
15827
+ let collisionFired = false;
15828
+ const collisionListener = (_evt) => {
15829
+ collisionFired = true;
15830
+ };
15831
+ eventBus.on("dispatch:cross-story-file-collision", collisionListener);
15748
15832
  await runWithConcurrency(batchGroups, config.maxConcurrency);
15833
+ eventBus.off("dispatch:cross-story-file-collision", collisionListener);
15834
+ if (collisionFired && runManifest !== null && runManifest !== void 0) {
15835
+ const batchStoryKeys = batchGroups.flat();
15836
+ const batchEntries = batchStoryKeys.map((key) => ({ storyKey: key }));
15837
+ const runId = config.pipelineRunId ?? "unknown";
15838
+ try {
15839
+ const recoveryResult = await runStaleVerificationRecovery({
15840
+ runId,
15841
+ batch: batchEntries,
15842
+ workingDir: projectRoot ?? process.cwd(),
15843
+ bus: toSdlcEventBus(eventBus),
15844
+ manifest: runManifest,
15845
+ adapter: db
15846
+ });
15847
+ if (!recoveryResult.noStale) logger$26.info({
15848
+ recovered: recoveryResult.recovered,
15849
+ stillFailed: recoveryResult.stillFailed,
15850
+ recoveredCount: recoveryResult.recovered.length,
15851
+ stillFailedCount: recoveryResult.stillFailed.length
15852
+ }, "Cross-story race recovery complete");
15853
+ } catch (recoveryErr) {
15854
+ logger$26.warn({ err: recoveryErr }, "Cross-story race recovery failed (non-fatal) — pipeline continues");
15855
+ }
15856
+ }
15749
15857
  }
15750
15858
  } catch (err) {
15751
15859
  stopHeartbeat();
@@ -44175,6 +44283,17 @@ function wireNdjsonEmitter(eventBus, ndjsonEmitter) {
44175
44283
  ...payload.severity !== void 0 ? { severity: payload.severity } : {}
44176
44284
  });
44177
44285
  });
44286
+ eventBus.on("decision:halt-skipped-non-interactive", (payload) => {
44287
+ ndjsonEmitter.emit({
44288
+ type: "decision:halt-skipped-non-interactive",
44289
+ ts: new Date().toISOString(),
44290
+ run_id: payload.runId,
44291
+ decision_type: payload.decisionType,
44292
+ severity: payload.severity,
44293
+ default_action: payload.defaultAction,
44294
+ reason: payload.reason
44295
+ });
44296
+ });
44178
44297
  }
44179
44298
  /**
44180
44299
  * Resolve the `probeAuthorStateIntegrating` boolean from CLI flag and env var.
@@ -44191,8 +44310,20 @@ function resolveProbeAuthorStateIntegrating(cliFlag) {
44191
44310
  if (envVal !== void 0) return envVal === "on";
44192
44311
  return true;
44193
44312
  }
44313
+ /**
44314
+ * Story 72-2: --non-interactive + Machine-Readable Exit Codes.
44315
+ *
44316
+ * Phase D Story 54-6 (2026-04-05): original headless CI/CD spec.
44317
+ * Story 72-1: Decision Router providing routeDecision defaultAction authority.
44318
+ * Story 72-2 (this code): --non-interactive flag that suppresses stdin reads,
44319
+ * auto-applies default actions via routeDecision, and returns exit codes 0/1/2.
44320
+ *
44321
+ * Enables strata + agent-mesh cross-project CI/CD invocation:
44322
+ * substrate run --non-interactive --halt-on none --events --output-format json
44323
+ */
44194
44324
  async function runRunAction(options) {
44195
- const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, epic: epicNumber, dryRun, maxReviewCycles = 2, engine, agent: agentId, registry: injectedRegistry, haltOn, costCeiling, probeAuthor, probeAuthorStateIntegrating: probeAuthorStateIntegratingFlag } = options;
44325
+ const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, epic: epicNumber, dryRun, maxReviewCycles = 2, engine, agent: agentId, registry: injectedRegistry, haltOn: haltOnOpt, costCeiling, probeAuthor, probeAuthorStateIntegrating: probeAuthorStateIntegratingFlag, nonInteractive } = options;
44326
+ const haltOn = haltOnOpt;
44196
44327
  const VALID_PROBE_AUTHOR_MODES = [
44197
44328
  "enabled",
44198
44329
  "disabled",
@@ -44505,11 +44636,12 @@ async function runRunAction(options) {
44505
44636
  const runsDir = join(dbDir, "runs");
44506
44637
  const cliFlags = {
44507
44638
  ...parsedStoryKeys.length > 0 ? { stories: parsedStoryKeys } : {},
44508
- halt_on: haltOn ?? "none",
44639
+ halt_on: haltOn ?? "critical",
44509
44640
  ...costCeiling !== void 0 ? { cost_ceiling: costCeiling } : {},
44510
44641
  ...agentId !== void 0 ? { agent: agentId } : {},
44511
44642
  ...skipVerification === true ? { skip_verification: true } : {},
44512
- ...eventsFlag === true ? { events: true } : {}
44643
+ ...eventsFlag === true ? { events: true } : {},
44644
+ ...nonInteractive === true ? { non_interactive: true } : {}
44513
44645
  };
44514
44646
  const manifest = RunManifest.open(pipelineRun.id, runsDir);
44515
44647
  await manifest.patchCLIFlags(cliFlags);
@@ -44536,6 +44668,14 @@ async function runRunAction(options) {
44536
44668
  });
44537
44669
  } catch {}
44538
44670
  const eventBus = createEventBus();
44671
+ let _costCeilingExhausted = false;
44672
+ let _fatalHaltReached = false;
44673
+ eventBus.on("cost:ceiling-reached", () => {
44674
+ _costCeilingExhausted = true;
44675
+ });
44676
+ eventBus.on("decision:halt", (payload) => {
44677
+ if (payload.severity === "fatal") _fatalHaltReached = true;
44678
+ });
44539
44679
  const contextCompiler = createContextCompiler({ db: adapter });
44540
44680
  if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
44541
44681
  const routingConfigPath = join(projectRoot, "substrate.routing.yml");
@@ -44676,7 +44816,7 @@ async function runRunAction(options) {
44676
44816
  if (tuiFlag === true && !isTuiCapable()) printNonTtyWarning();
44677
44817
  if (verboseFlag !== true && eventsFlag !== true) process.env.LOG_LEVEL = "silent";
44678
44818
  let tuiApp;
44679
- if (tuiFlag === true && isTuiCapable() && eventsFlag !== true && outputFormat === "human") {
44819
+ if (tuiFlag === true && nonInteractive !== true && isTuiCapable() && eventsFlag !== true && outputFormat === "human") {
44680
44820
  tuiApp = createTuiApp(process.stdout, process.stdin);
44681
44821
  tuiApp.handleEvent({
44682
44822
  type: "pipeline:start",
@@ -45069,13 +45209,52 @@ async function runRunAction(options) {
45069
45209
  engineType: resolvedEngine,
45070
45210
  concurrency
45071
45211
  });
45212
+ if (nonInteractive === true) {
45213
+ if (escalatedKeys.length > 0 || _costCeilingExhausted || _fatalHaltReached) {
45214
+ const haltPolicy = haltOn ?? "critical";
45215
+ const routeResult = routeDecision("pipeline-escalation", haltPolicy);
45216
+ eventBus.emit("decision:halt-skipped-non-interactive", {
45217
+ runId: pipelineRun.id,
45218
+ decisionType: "pipeline-escalation",
45219
+ severity: routeResult.severity,
45220
+ defaultAction: routeResult.defaultAction,
45221
+ reason: "non-interactive: stdin prompt suppressed"
45222
+ });
45223
+ try {
45224
+ const runsDir = join(dbDir, "runs");
45225
+ const runManifestForHalt = RunManifest.open(pipelineRun.id, runsDir);
45226
+ await runManifestForHalt.update({ cli_flags: {
45227
+ halt_on: haltPolicy,
45228
+ halt_skipped: true,
45229
+ halt_skipped_decisions: [{
45230
+ decisionType: "pipeline-escalation",
45231
+ severity: routeResult.severity,
45232
+ defaultAction: routeResult.defaultAction,
45233
+ reason: "non-interactive: stdin prompt suppressed",
45234
+ skippedAt: new Date().toISOString()
45235
+ }]
45236
+ } });
45237
+ } catch (haltManifestErr) {
45238
+ logger.warn({ err: haltManifestErr }, "Failed to write halt-skipped to manifest (best-effort)");
45239
+ }
45240
+ }
45241
+ const derivedCode = deriveExitCode({
45242
+ succeeded: succeededKeys,
45243
+ escalated: escalatedKeys,
45244
+ failed: failedKeys,
45245
+ total: storyKeys.length,
45246
+ costCeilingExhausted: _costCeilingExhausted,
45247
+ fatalHaltReached: _fatalHaltReached
45248
+ });
45249
+ return derivedCode;
45250
+ }
45072
45251
  return 0;
45073
45252
  } catch (err) {
45074
45253
  const msg = err instanceof Error ? err.message : String(err);
45075
45254
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
45076
45255
  else process.stderr.write(`Error: ${msg}\n`);
45077
45256
  logger.error({ err }, "run failed");
45078
- return 1;
45257
+ return nonInteractive === true ? 2 : 1;
45079
45258
  } finally {
45080
45259
  try {
45081
45260
  await adapter.close();
@@ -45549,7 +45728,22 @@ async function runFullPipeline(options) {
45549
45728
  }
45550
45729
  }
45551
45730
  function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
45552
- program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").option("--skip-verification", "Skip the post-dispatch verification pipeline (Story 51-5)").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").option("--engine <type>", "Execution engine: linear (default) or graph").option("--agent <id>", "Agent backend: claude-code (default), codex, or gemini").option("--halt-on <severity>", "Halt pipeline on escalation severity: all | critical | none (default: none)", "none").option("--cost-ceiling <amount>", "Maximum cost ceiling in USD (positive number); halts pipeline when exceeded", parseFloat).option("--probe-author <mode>", "probe-author phase mode: enabled | disabled | auto (default: auto = SUBSTRATE_PROBE_AUTHOR_ENABLED env, default true) (Story 60-14)", "auto").option("--probe-author-state-integrating <value>", "Disable probe-author dispatch for state-integrating ACs (Phase 3). Use to ramp DOWN if catch rate drops below the GREEN threshold. Values: on | off (default: on)").action(async (opts) => {
45731
+ program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").option("--skip-verification", "Skip the post-dispatch verification pipeline (Story 51-5)").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").option("--engine <type>", "Execution engine: linear (default) or graph").option("--agent <id>", "Agent backend: claude-code (default), codex, or gemini").option("--halt-on <severity>", "Halt pipeline on escalation severity: all | critical | none (default: critical)", "critical").option("--cost-ceiling <amount>", "Maximum cost ceiling in USD (positive number); halts pipeline when exceeded", parseFloat).option("--probe-author <mode>", "probe-author phase mode: enabled | disabled | auto (default: auto = SUBSTRATE_PROBE_AUTHOR_ENABLED env, default true) (Story 60-14)", "auto").option("--probe-author-state-integrating <value>", "Disable probe-author dispatch for state-integrating ACs (Phase 3). Use to ramp DOWN if catch rate drops below the GREEN threshold. Values: on | off (default: on)").option("--non-interactive", [
45732
+ "Run without operator prompts (CI/CD mode). Suppresses all stdin reads,",
45733
+ "auto-applies default actions for halt decisions, and returns machine-readable",
45734
+ "exit codes: 0=all stories succeeded, 1=some escalated, 2=run-level failure.",
45735
+ "",
45736
+ "Canonical CI/CD invocation:",
45737
+ " substrate run --non-interactive --halt-on none --events --output-format json",
45738
+ "",
45739
+ "Exit code semantics:",
45740
+ " 0 All stories succeeded (or recovered cleanly)",
45741
+ " 1 Some stories escalated; run completed",
45742
+ " 2 Run-level failure (cost ceiling exhausted, fatal halt, orchestrator died)",
45743
+ "",
45744
+ "--halt-on defaults to critical. Skipped halt decisions are recorded as",
45745
+ "decision:halt-skipped-non-interactive events in --non-interactive mode."
45746
+ ].join("\n ")).action(async (opts) => {
45553
45747
  if (opts.helpAgent) {
45554
45748
  process.exitCode = await runHelpAgent();
45555
45749
  return;
@@ -45593,12 +45787,14 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
45593
45787
  haltOn: opts.haltOn,
45594
45788
  costCeiling: opts.costCeiling,
45595
45789
  probeAuthor: opts.probeAuthor,
45596
- probeAuthorStateIntegrating: opts.probeAuthorStateIntegrating
45790
+ probeAuthorStateIntegrating: opts.probeAuthorStateIntegrating,
45791
+ nonInteractive: opts.nonInteractive
45597
45792
  });
45793
+ if (opts.nonInteractive === true) process.exit(exitCode);
45598
45794
  process.exitCode = exitCode;
45599
45795
  });
45600
45796
  }
45601
45797
 
45602
45798
  //#endregion
45603
45799
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR$1 as GLOBSTAR, GitClient, GrammarLoader, Minimatch$1 as Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape$1 as escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runRunAction, runSolutioningPhase, unescape$1 as unescape, validateStopAfterFromConflict, wireNdjsonEmitter };
45604
- //# sourceMappingURL=run-CRz08RrU.js.map
45800
+ //# sourceMappingURL=run-CCxsv-9M.js.map
@@ -1,9 +1,10 @@
1
- import "./health-BoXxsFSF.js";
1
+ import "./health-DudlnqXd.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-W2emvN3F.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-CRz08RrU.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-CCxsv-9M.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
+ import "./decision-router-BA__VYIp.js";
8
9
 
9
10
  export { runRunAction };
@@ -0,0 +1,88 @@
1
+ //#region src/modules/decision-router/index.d.ts
2
+ /**
3
+ * Decision Router — Phase D Story 54-2 (2026-04-05): original autonomy-gradient spec.
4
+ * Story 72-1: --halt-on flag + Decision Router (this file).
5
+ * Classifies every halt-able decision by severity and enforces the chosen autonomy policy.
6
+ * Exports routeDecision(decision, policy) pure function for orchestrator consumption.
7
+ * Epic 70: cross-story-race decision types (cross-story-race-recovered,
8
+ * cross-story-race-still-failed) motivate the registry pattern — different halt semantics
9
+ * based on severity (info vs critical).
10
+ * Epic 73 (Recovery Engine) will register additional decision types via DECISION_SEVERITY_MAP.
11
+ *
12
+ * Story 72-2: --non-interactive flag consuming routeDecision for stdin suppression.
13
+ * Enables strata + agent-mesh cross-project CI/CD invocation.
14
+ */
15
+ /**
16
+ * Severity of a halt-able decision.
17
+ * Controls which autonomy policies trigger a halt.
18
+ */
19
+ type Severity = 'info' | 'warning' | 'critical' | 'fatal';
20
+ /**
21
+ * All known halt-able decision types (AC1).
22
+ * Epic 73 (Recovery Engine) will extend this union with additional types.
23
+ */
24
+ type DecisionType = 'cost-ceiling-exhausted' | 'build-verification-failure' | 'recovery-retry-attempt' | 're-scope-proposal' | 'scope-violation' | 'cross-story-race-recovered' | 'cross-story-race-still-failed' | 'pipeline-escalation';
25
+ /**
26
+ * Maps each known decision type to its severity level.
27
+ *
28
+ * Epic 70: cross-story-race-recovered (info — log only, no halt) and
29
+ * cross-story-race-still-failed (critical — recovery exhausted, halt for operator)
30
+ * motivate the registry pattern. Epic 73 (Recovery Engine) will register
31
+ * additional decision types here.
32
+ */
33
+ declare const DECISION_SEVERITY_MAP: Record<DecisionType, Severity>;
34
+ /**
35
+ * Route a halt-able decision through the autonomy policy.
36
+ *
37
+ * Pure function — no I/O, no side effects. All orchestrator state interactions
38
+ * remain in orchestrator-impl.ts.
39
+ *
40
+ * Halt policy logic (AC4):
41
+ * - 'all': halts on info | warning | critical | fatal
42
+ * - 'critical': halts on critical | fatal (default)
43
+ * - 'none': halts ONLY on fatal (scope violations bypass the autonomy-gradient
44
+ * policy — they are always halts regardless of the chosen policy)
45
+ *
46
+ * Fatal always halts regardless of policy — hard safety invariant, not configurable.
47
+ *
48
+ * Unknown decision types default to severity 'critical' (safe default, AC9e).
49
+ *
50
+ * @param decision - The decision type string to route
51
+ * @param policy - The autonomy policy from the --halt-on CLI flag
52
+ * @returns { halt: boolean, defaultAction: string, severity: Severity }
53
+ */
54
+ declare function routeDecision(decision: string, policy: 'all' | 'critical' | 'none'): {
55
+ halt: boolean;
56
+ defaultAction: string;
57
+ severity: Severity;
58
+ };
59
+ /**
60
+ * Inputs for exit code derivation from pipeline completion results.
61
+ * Used by the CLI layer to derive the machine-readable exit code (0/1/2)
62
+ * for non-interactive CI/CD invocations.
63
+ */
64
+ interface PipelineOutcome {
65
+ succeeded: string[];
66
+ recovered?: string[];
67
+ escalated: string[];
68
+ failed: string[];
69
+ total: number;
70
+ costCeilingExhausted?: boolean;
71
+ fatalHaltReached?: boolean;
72
+ orchestratorDied?: boolean;
73
+ }
74
+ /**
75
+ * Derive the machine-readable exit code from pipeline completion results.
76
+ *
77
+ * - Exit `0` when all stories succeeded (or recovered cleanly)
78
+ * - Exit `1` when some stories escalated; run completed
79
+ * - Exit `2` when run-level failure (cost ceiling, fatal halt, orchestrator died, stories failed)
80
+ *
81
+ * @param outcome - Pipeline completion outcome data
82
+ * @returns 0 (success), 1 (escalated), or 2 (failure)
83
+ */
84
+ declare function deriveExitCode(outcome: PipelineOutcome): 0 | 1 | 2; //#endregion
85
+
86
+ //# sourceMappingURL=index.d.ts.map
87
+ export { DECISION_SEVERITY_MAP, DecisionType, PipelineOutcome, Severity, deriveExitCode, routeDecision };
88
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ import { DECISION_SEVERITY_MAP, deriveExitCode, routeDecision } from "../../../decision-router-BA__VYIp.js";
2
+
3
+ export { DECISION_SEVERITY_MAP, deriveExitCode, routeDecision };
@@ -1,5 +1,5 @@
1
1
  import "./dist-W2emvN3F.js";
2
- import "./version-manager-impl-BmOWu8ml.js";
3
- import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-CAqLkNUP.js";
2
+ import "./version-manager-impl-FH4TTnXm.js";
3
+ import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-aW7GYL2F.js";
4
4
 
5
5
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
@@ -123,4 +123,4 @@ function registerUpgradeCommand(program) {
123
123
 
124
124
  //#endregion
125
125
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
126
- //# sourceMappingURL=upgrade-CAqLkNUP.js.map
126
+ //# sourceMappingURL=upgrade-aW7GYL2F.js.map
@@ -1,4 +1,4 @@
1
1
  import { VersionManagerImpl, createVersionManager } from "./dist-W2emvN3F.js";
2
- import "./version-manager-impl-BmOWu8ml.js";
2
+ import "./version-manager-impl-FH4TTnXm.js";
3
3
 
4
4
  export { createVersionManager };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.62",
3
+ "version": "0.20.64",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",