substrate-ai 0.20.63 → 0.20.65

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.
Files changed (36) hide show
  1. package/dist/adapter-registry-BbVWH3Yv.js +4 -0
  2. package/dist/cli/index.js +93 -23
  3. package/dist/decision-router-DblHY8se.js +97 -0
  4. package/dist/{decisions-4F91LrVD.js → decisions-DilHo99V.js} +2 -2
  5. package/dist/{dist-W2emvN3F.js → dist-K_RRWnBX.js} +2 -2
  6. package/dist/{errors-CKFu8YI9.js → errors-pSiZbn6e.js} +2 -2
  7. package/dist/{experimenter-DxxwicpK.js → experimenter-DT9v2Pto.js} +1 -1
  8. package/dist/health-DC3y-sR6.js +1715 -0
  9. package/dist/health-qhtWYh49.js +8 -0
  10. package/dist/index-c924O9mj.d.ts +1432 -0
  11. package/dist/index.d.ts +132 -736
  12. package/dist/index.js +2 -2
  13. package/dist/interactive-prompt-C7wpE4z4.js +183 -0
  14. package/dist/{health-C-KOZrFJ.js → manifest-read-DDkXC3L_.js} +130 -2013
  15. package/dist/modules/interactive-prompt/index.d.ts +86 -0
  16. package/dist/modules/interactive-prompt/index.js +6 -0
  17. package/dist/recovery-engine-BKGBeBnW.js +281 -0
  18. package/dist/{routing-0ykvBl_4.js → routing-CzF0p6lI.js} +2 -2
  19. package/dist/run-DX95j4_D.js +14 -0
  20. package/dist/{run-CHUFlRbH.js → run-DzB4rgkj.js} +391 -31
  21. package/dist/src/modules/decision-router/index.d.ts +88 -0
  22. package/dist/src/modules/decision-router/index.js +3 -0
  23. package/dist/src/modules/recovery-engine/index.d.ts +1101 -0
  24. package/dist/src/modules/recovery-engine/index.js +5 -0
  25. package/dist/{upgrade-C8LAnB6W.js → upgrade-DxzQ1nss.js} +3 -3
  26. package/dist/{upgrade-CAqLkNUP.js → upgrade-MP9XzrI6.js} +2 -2
  27. package/dist/version-manager-impl-GZDUBt0Q.js +4 -0
  28. package/dist/work-graph-repository-DZyJv5pV.js +265 -0
  29. package/package.json +1 -1
  30. package/dist/adapter-registry-k7ZX3Bz6.js +0 -4
  31. package/dist/health-CJqd1FzY.js +0 -6
  32. package/dist/run-Z_-caE_i.js +0 -9
  33. package/dist/version-manager-impl-DaA_ALYK.js +0 -4
  34. /package/dist/{decisions-C0pz9Clx.js → decisions-CzSIEeGP.js} +0 -0
  35. /package/dist/{routing-CcBOCuC9.js → routing-DFxoKHDt.js} +0 -0
  36. /package/dist/{version-manager-impl-BmOWu8ml.js → version-manager-impl-qFBiO4Eh.js} +0 -0
@@ -1,7 +1,12 @@
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-C-KOZrFJ.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-DC3y-sR6.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
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";
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-K_RRWnBX.js";
5
+ import { FindingsInjector, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, renderFindings, resolveGraphPath, resolveMainRepoRoot, runStaleVerificationRecovery } from "./manifest-read-DDkXC3L_.js";
6
+ import { WorkGraphRepository, detectCycles } from "./work-graph-repository-DZyJv5pV.js";
7
+ import { deriveExitCode, routeDecision } from "./decision-router-DblHY8se.js";
8
+ import { runInteractivePrompt } from "./interactive-prompt-C7wpE4z4.js";
9
+ import { runRecoveryEngine } from "./recovery-engine-BKGBeBnW.js";
5
10
  import { basename, dirname, extname, join } from "path";
6
11
  import { access, readFile, readdir, stat } from "fs/promises";
7
12
  import { EventEmitter } from "node:events";
@@ -14,7 +19,7 @@ import path, { basename as basename$1, dirname as dirname$1, extname as extname$
14
19
  import { tmpdir } from "node:os";
15
20
  import { createHash, randomUUID } from "node:crypto";
16
21
  import { z } from "zod";
17
- import { access as access$1, lstat, mkdir as mkdir$1, readFile as readFile$1, readdir as readdir$1, readlink, realpath, rename, stat as stat$1, unlink, writeFile as writeFile$1 } from "node:fs/promises";
22
+ import { access as access$1, lstat, mkdir as mkdir$1, readFile as readFile$1, readdir as readdir$1, readlink, realpath, rename, stat as stat$1, unlink as unlink$1, writeFile as writeFile$1 } from "node:fs/promises";
18
23
  import { existsSync as existsSync$1, lstatSync, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdir as readdir$2, readdirSync as readdirSync$1, readlinkSync, realpathSync as realpathSync$1, unlinkSync as unlinkSync$1, writeFileSync as writeFileSync$1 } from "fs";
19
24
  import { promisify } from "node:util";
20
25
  import { fileURLToPath } from "node:url";
@@ -1764,7 +1769,7 @@ const PIPELINE_EVENT_METADATA = [
1764
1769
  {
1765
1770
  name: "action",
1766
1771
  type: "string",
1767
- description: "Action taken (always stopped in this story; interactive prompt is Epic 54 scope)."
1772
+ description: "Action taken stopped when policy requires halt, or the defaultAction when proceeding autonomously."
1768
1773
  },
1769
1774
  {
1770
1775
  name: "skipped_stories",
@@ -1774,10 +1779,47 @@ const PIPELINE_EVENT_METADATA = [
1774
1779
  {
1775
1780
  name: "severity",
1776
1781
  type: "string",
1777
- description: "critical when halt_on is all or critical; absent when none.",
1782
+ description: "Severity from routeDecision (critical for cost-ceiling-exhausted).",
1778
1783
  optional: true
1779
1784
  }
1780
1785
  ]
1786
+ },
1787
+ {
1788
+ type: "decision:halt-skipped-non-interactive",
1789
+ description: "A critical halt decision was skipped under --non-interactive mode; default action was applied autonomously.",
1790
+ when: "Emitted when --non-interactive suppresses an operator prompt and applies the default action. Story 72-2.",
1791
+ fields: [
1792
+ {
1793
+ name: "ts",
1794
+ type: "string",
1795
+ description: "Timestamp."
1796
+ },
1797
+ {
1798
+ name: "run_id",
1799
+ type: "string",
1800
+ description: "Pipeline run ID."
1801
+ },
1802
+ {
1803
+ name: "decision_type",
1804
+ type: "string",
1805
+ description: "Halt decision type that was skipped (e.g., halt:escalation)."
1806
+ },
1807
+ {
1808
+ name: "severity",
1809
+ type: "string",
1810
+ description: "Severity of the skipped halt (e.g., critical)."
1811
+ },
1812
+ {
1813
+ name: "default_action",
1814
+ type: "string",
1815
+ description: "Action applied in place of the operator prompt."
1816
+ },
1817
+ {
1818
+ name: "reason",
1819
+ type: "string",
1820
+ description: "Human-readable reason for skipping."
1821
+ }
1822
+ ]
1781
1823
  }
1782
1824
  ];
1783
1825
  /**
@@ -14284,6 +14326,49 @@ function createImplementationOrchestrator(deps) {
14284
14326
  }, "Build-fix dispatch failed — escalating");
14285
14327
  }
14286
14328
  if (!buildFixPassed) {
14329
+ let buildHaltPolicy = "critical";
14330
+ if (runManifest !== null && runManifest !== void 0) try {
14331
+ const manifestData = await runManifest.read();
14332
+ buildHaltPolicy = manifestData.cli_flags.halt_on ?? "critical";
14333
+ } catch {}
14334
+ const buildRouteResult = routeDecision("build-verification-failure", buildHaltPolicy);
14335
+ const buildRunId = config.pipelineRunId ?? "unknown";
14336
+ const buildReason = `build verification failed for story ${storyKey}`;
14337
+ if (buildRouteResult.halt) {
14338
+ eventBus.emit("decision:halt", {
14339
+ runId: buildRunId,
14340
+ decisionType: "build-verification-failure",
14341
+ severity: buildRouteResult.severity,
14342
+ reason: buildReason
14343
+ });
14344
+ await runInteractivePrompt({
14345
+ runId: buildRunId,
14346
+ decisionType: "build-verification-failure",
14347
+ severity: buildRouteResult.severity,
14348
+ summary: buildReason,
14349
+ defaultAction: buildRouteResult.defaultAction,
14350
+ choices: [
14351
+ "escalate-without-halt",
14352
+ "retry-with-custom-context",
14353
+ "propose-re-scope",
14354
+ "abort-run"
14355
+ ],
14356
+ onHaltSkipped: (payload) => {
14357
+ eventBus.emit("decision:halt-skipped-non-interactive", payload);
14358
+ }
14359
+ }).catch((err) => {
14360
+ logger$26.warn({
14361
+ err,
14362
+ storyKey
14363
+ }, "interactive prompt failed — continuing with default action");
14364
+ });
14365
+ } else eventBus.emit("decision:autonomous", {
14366
+ runId: buildRunId,
14367
+ decisionType: "build-verification-failure",
14368
+ severity: buildRouteResult.severity,
14369
+ defaultAction: buildRouteResult.defaultAction,
14370
+ reason: buildReason
14371
+ });
14287
14372
  eventBus.emit("story:build-verification-failed", {
14288
14373
  storyKey,
14289
14374
  exitCode: buildVerifyResult.exitCode ?? 1,
@@ -14292,7 +14377,9 @@ function createImplementationOrchestrator(deps) {
14292
14377
  logger$26.warn({
14293
14378
  storyKey,
14294
14379
  reason,
14295
- exitCode: buildVerifyResult.exitCode
14380
+ exitCode: buildVerifyResult.exitCode,
14381
+ routedHalt: buildRouteResult.halt,
14382
+ defaultAction: buildRouteResult.defaultAction
14296
14383
  }, "Build verification failed — escalating story");
14297
14384
  updateStory(storyKey, {
14298
14385
  phase: "ESCALATED",
@@ -14381,17 +14468,165 @@ function createImplementationOrchestrator(deps) {
14381
14468
  verificationStore.set(storyKey, verifSummary);
14382
14469
  await persistVerificationResult(storyKey, verifSummary, runManifest);
14383
14470
  if (verifSummary.status === "fail") {
14384
- updateStory(storyKey, {
14385
- phase: "VERIFICATION_FAILED",
14386
- completedAt: new Date().toISOString()
14387
- });
14388
- persistStoryState(storyKey, _stories.get(storyKey)).catch((err) => logger$26.warn({
14389
- err,
14390
- storyKey
14391
- }, "StateStore write failed after verification-failed"));
14392
- await writeStoryMetricsBestEffort(storyKey, "verification-failed", finalReviewCycles);
14393
- await persistState();
14394
- return "verification-failed";
14471
+ let shouldFallThroughToComplete = false;
14472
+ if (runManifest != null) {
14473
+ const failFindings = (verifSummary.checks ?? []).flatMap((c) => c.findings ?? []);
14474
+ const hasBuildFail = (verifSummary.checks ?? []).some((c) => (c.checkName === "build" || c.checkName === "typecheck") && c.status === "fail");
14475
+ const recoveryRootCause = hasBuildFail ? "build-failure" : "ac-missing-evidence";
14476
+ const recoveryBudget = {
14477
+ max: config.maxReviewCycles,
14478
+ remaining: Math.max(0, config.maxReviewCycles - finalReviewCycles)
14479
+ };
14480
+ const recoveryResult = await runRecoveryEngine({
14481
+ runId: config.pipelineRunId ?? storyKey,
14482
+ storyKey,
14483
+ failure: {
14484
+ rootCause: recoveryRootCause,
14485
+ findings: failFindings
14486
+ },
14487
+ budget: recoveryBudget,
14488
+ bus: toSdlcEventBus(eventBus),
14489
+ manifest: runManifest,
14490
+ adapter: db,
14491
+ engine: "linear"
14492
+ }).catch((recoveryErr) => {
14493
+ logger$26.warn({
14494
+ storyKey,
14495
+ err: recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr)
14496
+ }, "Recovery Engine invocation failed — falling through to VERIFICATION_FAILED (best-effort)");
14497
+ return null;
14498
+ });
14499
+ if (recoveryResult?.action === "halt-entire-run") {
14500
+ logger$26.error({
14501
+ storyKey,
14502
+ pendingProposalsCount: recoveryResult.pendingProposalsCount
14503
+ }, "Recovery Engine safety valve: halting entire run due to >= 5 pending proposals");
14504
+ _budgetExhausted = true;
14505
+ } else if (recoveryResult?.action === "retry") {
14506
+ logger$26.info({
14507
+ storyKey,
14508
+ attempt: recoveryResult.attempt,
14509
+ retryBudgetRemaining: recoveryResult.retryBudgetRemaining
14510
+ }, "Recovery Engine Tier A: re-dispatching dev-story with enriched prompt");
14511
+ try {
14512
+ incrementDispatches(storyKey);
14513
+ const retryDevResult = await runDevStory({
14514
+ db,
14515
+ pack,
14516
+ contextCompiler,
14517
+ dispatcher,
14518
+ projectRoot,
14519
+ tokenCeilings,
14520
+ otlpEndpoint: _otlpEndpoint,
14521
+ repoMapInjector,
14522
+ maxRepoMapTokens,
14523
+ agentId
14524
+ }, {
14525
+ storyKey,
14526
+ storyFilePath: storyFilePath ?? "",
14527
+ pipelineRunId: config.pipelineRunId,
14528
+ findingsPrompt: recoveryResult.enrichedPrompt
14529
+ });
14530
+ replaceDevStorySignals(retryDevResult);
14531
+ await persistDevStorySignals(storyKey, devStorySignals, runManifest);
14532
+ const retryVerifContext = assembleVerificationContext({
14533
+ storyKey,
14534
+ workingDir: projectRoot ?? process.cwd(),
14535
+ reviewResult: latestReviewSignals,
14536
+ storyContent: storyContentForVerification,
14537
+ devStoryResult: devStorySignals,
14538
+ outputTokenCount: devOutputTokenCount,
14539
+ sourceEpicContent
14540
+ });
14541
+ const retryVerifSummary = await verificationPipeline.run(retryVerifContext, "A");
14542
+ verificationStore.set(storyKey, retryVerifSummary);
14543
+ await persistVerificationResult(storyKey, retryVerifSummary, runManifest);
14544
+ if (retryVerifSummary.status !== "fail") {
14545
+ logger$26.info({ storyKey }, "Recovery Engine Tier A retry succeeded — story proceeding to COMPLETE");
14546
+ shouldFallThroughToComplete = true;
14547
+ } else logger$26.warn({ storyKey }, "Recovery Engine Tier A retry still failed — falling through to VERIFICATION_FAILED");
14548
+ } catch (retryErr) {
14549
+ logger$26.warn({
14550
+ storyKey,
14551
+ err: retryErr instanceof Error ? retryErr.message : String(retryErr)
14552
+ }, "Recovery Engine Tier A re-dispatch threw — falling through to VERIFICATION_FAILED");
14553
+ }
14554
+ } else if (recoveryResult?.action === "propose") {
14555
+ logger$26.info({ storyKey }, "Recovery Engine Tier B: proposal appended — marking story ESCALATED for operator re-scope");
14556
+ updateStory(storyKey, {
14557
+ phase: "ESCALATED",
14558
+ completedAt: new Date().toISOString(),
14559
+ error: "recovery-engine-propose"
14560
+ });
14561
+ persistStoryState(storyKey, _stories.get(storyKey)).catch((err) => logger$26.warn({
14562
+ err,
14563
+ storyKey
14564
+ }, "StateStore write failed after recovery-propose"));
14565
+ await emitEscalation({
14566
+ storyKey,
14567
+ lastVerdict: "recovery-propose",
14568
+ reviewCycles: finalReviewCycles,
14569
+ issues: failFindings
14570
+ });
14571
+ await writeStoryMetricsBestEffort(storyKey, "escalated", finalReviewCycles);
14572
+ await persistState();
14573
+ return "verification-failed";
14574
+ } else if (recoveryResult?.action === "halt") {
14575
+ logger$26.warn({ storyKey }, "Recovery Engine Tier C: halt — invoking interactive prompt for operator decision");
14576
+ const haltRunId = config.pipelineRunId ?? storyKey;
14577
+ await runInteractivePrompt({
14578
+ runId: haltRunId,
14579
+ decisionType: "verification-failure",
14580
+ severity: "critical",
14581
+ summary: `Verification failed (Tier C halt) on story ${storyKey}: ${recoveryRootCause}`,
14582
+ defaultAction: "escalate",
14583
+ choices: [
14584
+ "escalate-without-halt",
14585
+ "propose-re-scope",
14586
+ "abort-run"
14587
+ ],
14588
+ onHaltSkipped: (haltPayload) => {
14589
+ eventBus.emit("decision:halt-skipped-non-interactive", haltPayload);
14590
+ }
14591
+ }).catch((err) => {
14592
+ logger$26.warn({
14593
+ err,
14594
+ storyKey
14595
+ }, "Recovery Engine Tier C: interactive prompt failed — escalating anyway");
14596
+ });
14597
+ updateStory(storyKey, {
14598
+ phase: "ESCALATED",
14599
+ completedAt: new Date().toISOString(),
14600
+ error: "recovery-engine-halt"
14601
+ });
14602
+ persistStoryState(storyKey, _stories.get(storyKey)).catch((err) => logger$26.warn({
14603
+ err,
14604
+ storyKey
14605
+ }, "StateStore write failed after recovery-halt"));
14606
+ await emitEscalation({
14607
+ storyKey,
14608
+ lastVerdict: "recovery-halt",
14609
+ reviewCycles: finalReviewCycles,
14610
+ issues: failFindings
14611
+ });
14612
+ await writeStoryMetricsBestEffort(storyKey, "escalated", finalReviewCycles);
14613
+ await persistState();
14614
+ return "verification-failed";
14615
+ }
14616
+ }
14617
+ if (!shouldFallThroughToComplete) {
14618
+ updateStory(storyKey, {
14619
+ phase: "VERIFICATION_FAILED",
14620
+ completedAt: new Date().toISOString()
14621
+ });
14622
+ persistStoryState(storyKey, _stories.get(storyKey)).catch((err) => logger$26.warn({
14623
+ err,
14624
+ storyKey
14625
+ }, "StateStore write failed after verification-failed"));
14626
+ await writeStoryMetricsBestEffort(storyKey, "verification-failed", finalReviewCycles);
14627
+ await persistState();
14628
+ return "verification-failed";
14629
+ }
14395
14630
  }
14396
14631
  }
14397
14632
  if (autoApprove !== void 0) eventBus.emit("story:auto-approved", {
@@ -15234,7 +15469,42 @@ function createImplementationOrchestrator(deps) {
15234
15469
  * @param manifest - The current run manifest data
15235
15470
  */
15236
15471
  async function handleCeilingExceeded(triggeredStoryKey, remainingInGroup, result, manifest) {
15237
- const haltOn = manifest.cli_flags.halt_on ?? "none";
15472
+ const haltPolicy = manifest.cli_flags.halt_on ?? "critical";
15473
+ const routeResult = routeDecision("cost-ceiling-exhausted", haltPolicy);
15474
+ const runId = config.pipelineRunId ?? "unknown";
15475
+ const reason = `cost ceiling exceeded: ${result.cumulative.toFixed(4)} USD >= ${result.ceiling} USD`;
15476
+ if (routeResult.halt) {
15477
+ eventBus.emit("decision:halt", {
15478
+ runId,
15479
+ decisionType: "cost-ceiling-exhausted",
15480
+ severity: routeResult.severity,
15481
+ reason
15482
+ });
15483
+ await runInteractivePrompt({
15484
+ runId,
15485
+ decisionType: "cost-ceiling-exhausted",
15486
+ severity: routeResult.severity,
15487
+ summary: reason,
15488
+ defaultAction: routeResult.defaultAction,
15489
+ choices: [
15490
+ "skip-remaining",
15491
+ "retry-with-custom-context",
15492
+ "propose-re-scope",
15493
+ "abort-run"
15494
+ ],
15495
+ onHaltSkipped: (payload) => {
15496
+ eventBus.emit("decision:halt-skipped-non-interactive", payload);
15497
+ }
15498
+ }).catch((err) => {
15499
+ logger$26.warn({ err }, "interactive prompt failed during cost-ceiling halt — continuing with default action");
15500
+ });
15501
+ } else eventBus.emit("decision:autonomous", {
15502
+ runId,
15503
+ decisionType: "cost-ceiling-exhausted",
15504
+ severity: routeResult.severity,
15505
+ defaultAction: routeResult.defaultAction,
15506
+ reason
15507
+ });
15238
15508
  const allSkipped = [triggeredStoryKey, ...remainingInGroup];
15239
15509
  for (const [key, state] of _stories) if (state.phase === "PENDING" && !allSkipped.includes(key)) allSkipped.push(key);
15240
15510
  for (const key of allSkipped) {
@@ -15248,16 +15518,18 @@ function createImplementationOrchestrator(deps) {
15248
15518
  eventBus.emit("cost:ceiling-reached", {
15249
15519
  cumulative_cost: result.cumulative,
15250
15520
  ceiling: result.ceiling,
15251
- halt_on: haltOn,
15252
- action: "stopped",
15521
+ halt_on: haltPolicy,
15522
+ action: routeResult.halt ? "stopped" : routeResult.defaultAction,
15253
15523
  skipped_stories: allSkipped,
15254
- ...haltOn !== "none" ? { severity: "critical" } : {}
15524
+ severity: routeResult.severity
15255
15525
  });
15256
15526
  _budgetExhausted = true;
15257
15527
  logger$26.warn({
15258
15528
  skipped: allSkipped.length,
15259
15529
  cumulative: result.cumulative,
15260
- ceiling: result.ceiling
15530
+ ceiling: result.ceiling,
15531
+ routedHalt: routeResult.halt,
15532
+ defaultAction: routeResult.defaultAction
15261
15533
  }, "Cost ceiling reached — stopping dispatch");
15262
15534
  }
15263
15535
  /**
@@ -43246,7 +43518,7 @@ async function writeRunState(projectDir, state) {
43246
43518
  async function clearRunState(projectDir) {
43247
43519
  const filePath = runStatePath(projectDir);
43248
43520
  try {
43249
- await unlink(filePath);
43521
+ await unlink$1(filePath);
43250
43522
  } catch (err) {
43251
43523
  if (err.code !== "ENOENT") throw err;
43252
43524
  }
@@ -44204,6 +44476,17 @@ function wireNdjsonEmitter(eventBus, ndjsonEmitter) {
44204
44476
  ...payload.severity !== void 0 ? { severity: payload.severity } : {}
44205
44477
  });
44206
44478
  });
44479
+ eventBus.on("decision:halt-skipped-non-interactive", (payload) => {
44480
+ ndjsonEmitter.emit({
44481
+ type: "decision:halt-skipped-non-interactive",
44482
+ ts: new Date().toISOString(),
44483
+ run_id: payload.runId,
44484
+ decision_type: payload.decisionType,
44485
+ severity: payload.severity,
44486
+ default_action: payload.defaultAction,
44487
+ reason: payload.reason
44488
+ });
44489
+ });
44207
44490
  }
44208
44491
  /**
44209
44492
  * Resolve the `probeAuthorStateIntegrating` boolean from CLI flag and env var.
@@ -44220,8 +44503,20 @@ function resolveProbeAuthorStateIntegrating(cliFlag) {
44220
44503
  if (envVal !== void 0) return envVal === "on";
44221
44504
  return true;
44222
44505
  }
44506
+ /**
44507
+ * Story 72-2: --non-interactive + Machine-Readable Exit Codes.
44508
+ *
44509
+ * Phase D Story 54-6 (2026-04-05): original headless CI/CD spec.
44510
+ * Story 72-1: Decision Router providing routeDecision defaultAction authority.
44511
+ * Story 72-2 (this code): --non-interactive flag that suppresses stdin reads,
44512
+ * auto-applies default actions via routeDecision, and returns exit codes 0/1/2.
44513
+ *
44514
+ * Enables strata + agent-mesh cross-project CI/CD invocation:
44515
+ * substrate run --non-interactive --halt-on none --events --output-format json
44516
+ */
44223
44517
  async function runRunAction(options) {
44224
- 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;
44518
+ 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;
44519
+ const haltOn = haltOnOpt;
44225
44520
  const VALID_PROBE_AUTHOR_MODES = [
44226
44521
  "enabled",
44227
44522
  "disabled",
@@ -44534,11 +44829,12 @@ async function runRunAction(options) {
44534
44829
  const runsDir = join(dbDir, "runs");
44535
44830
  const cliFlags = {
44536
44831
  ...parsedStoryKeys.length > 0 ? { stories: parsedStoryKeys } : {},
44537
- halt_on: haltOn ?? "none",
44832
+ halt_on: haltOn ?? "critical",
44538
44833
  ...costCeiling !== void 0 ? { cost_ceiling: costCeiling } : {},
44539
44834
  ...agentId !== void 0 ? { agent: agentId } : {},
44540
44835
  ...skipVerification === true ? { skip_verification: true } : {},
44541
- ...eventsFlag === true ? { events: true } : {}
44836
+ ...eventsFlag === true ? { events: true } : {},
44837
+ ...nonInteractive === true ? { non_interactive: true } : {}
44542
44838
  };
44543
44839
  const manifest = RunManifest.open(pipelineRun.id, runsDir);
44544
44840
  await manifest.patchCLIFlags(cliFlags);
@@ -44565,6 +44861,14 @@ async function runRunAction(options) {
44565
44861
  });
44566
44862
  } catch {}
44567
44863
  const eventBus = createEventBus();
44864
+ let _costCeilingExhausted = false;
44865
+ let _fatalHaltReached = false;
44866
+ eventBus.on("cost:ceiling-reached", () => {
44867
+ _costCeilingExhausted = true;
44868
+ });
44869
+ eventBus.on("decision:halt", (payload) => {
44870
+ if (payload.severity === "fatal") _fatalHaltReached = true;
44871
+ });
44568
44872
  const contextCompiler = createContextCompiler({ db: adapter });
44569
44873
  if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
44570
44874
  const routingConfigPath = join(projectRoot, "substrate.routing.yml");
@@ -44705,7 +45009,7 @@ async function runRunAction(options) {
44705
45009
  if (tuiFlag === true && !isTuiCapable()) printNonTtyWarning();
44706
45010
  if (verboseFlag !== true && eventsFlag !== true) process.env.LOG_LEVEL = "silent";
44707
45011
  let tuiApp;
44708
- if (tuiFlag === true && isTuiCapable() && eventsFlag !== true && outputFormat === "human") {
45012
+ if (tuiFlag === true && nonInteractive !== true && isTuiCapable() && eventsFlag !== true && outputFormat === "human") {
44709
45013
  tuiApp = createTuiApp(process.stdout, process.stdin);
44710
45014
  tuiApp.handleEvent({
44711
45015
  type: "pipeline:start",
@@ -45098,13 +45402,52 @@ async function runRunAction(options) {
45098
45402
  engineType: resolvedEngine,
45099
45403
  concurrency
45100
45404
  });
45405
+ if (nonInteractive === true) {
45406
+ if (escalatedKeys.length > 0 || _costCeilingExhausted || _fatalHaltReached) {
45407
+ const haltPolicy = haltOn ?? "critical";
45408
+ const routeResult = routeDecision("pipeline-escalation", haltPolicy);
45409
+ eventBus.emit("decision:halt-skipped-non-interactive", {
45410
+ runId: pipelineRun.id,
45411
+ decisionType: "pipeline-escalation",
45412
+ severity: routeResult.severity,
45413
+ defaultAction: routeResult.defaultAction,
45414
+ reason: "non-interactive: stdin prompt suppressed"
45415
+ });
45416
+ try {
45417
+ const runsDir = join(dbDir, "runs");
45418
+ const runManifestForHalt = RunManifest.open(pipelineRun.id, runsDir);
45419
+ await runManifestForHalt.update({ cli_flags: {
45420
+ halt_on: haltPolicy,
45421
+ halt_skipped: true,
45422
+ halt_skipped_decisions: [{
45423
+ decisionType: "pipeline-escalation",
45424
+ severity: routeResult.severity,
45425
+ defaultAction: routeResult.defaultAction,
45426
+ reason: "non-interactive: stdin prompt suppressed",
45427
+ skippedAt: new Date().toISOString()
45428
+ }]
45429
+ } });
45430
+ } catch (haltManifestErr) {
45431
+ logger.warn({ err: haltManifestErr }, "Failed to write halt-skipped to manifest (best-effort)");
45432
+ }
45433
+ }
45434
+ const derivedCode = deriveExitCode({
45435
+ succeeded: succeededKeys,
45436
+ escalated: escalatedKeys,
45437
+ failed: failedKeys,
45438
+ total: storyKeys.length,
45439
+ costCeilingExhausted: _costCeilingExhausted,
45440
+ fatalHaltReached: _fatalHaltReached
45441
+ });
45442
+ return derivedCode;
45443
+ }
45101
45444
  return 0;
45102
45445
  } catch (err) {
45103
45446
  const msg = err instanceof Error ? err.message : String(err);
45104
45447
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
45105
45448
  else process.stderr.write(`Error: ${msg}\n`);
45106
45449
  logger.error({ err }, "run failed");
45107
- return 1;
45450
+ return nonInteractive === true ? 2 : 1;
45108
45451
  } finally {
45109
45452
  try {
45110
45453
  await adapter.close();
@@ -45578,7 +45921,22 @@ async function runFullPipeline(options) {
45578
45921
  }
45579
45922
  }
45580
45923
  function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
45581
- 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) => {
45924
+ 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", [
45925
+ "Run without operator prompts (CI/CD mode). Suppresses all stdin reads,",
45926
+ "auto-applies default actions for halt decisions, and returns machine-readable",
45927
+ "exit codes: 0=all stories succeeded, 1=some escalated, 2=run-level failure.",
45928
+ "",
45929
+ "Canonical CI/CD invocation:",
45930
+ " substrate run --non-interactive --halt-on none --events --output-format json",
45931
+ "",
45932
+ "Exit code semantics:",
45933
+ " 0 All stories succeeded (or recovered cleanly)",
45934
+ " 1 Some stories escalated; run completed",
45935
+ " 2 Run-level failure (cost ceiling exhausted, fatal halt, orchestrator died)",
45936
+ "",
45937
+ "--halt-on defaults to critical. Skipped halt decisions are recorded as",
45938
+ "decision:halt-skipped-non-interactive events in --non-interactive mode."
45939
+ ].join("\n ")).action(async (opts) => {
45582
45940
  if (opts.helpAgent) {
45583
45941
  process.exitCode = await runHelpAgent();
45584
45942
  return;
@@ -45622,12 +45980,14 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
45622
45980
  haltOn: opts.haltOn,
45623
45981
  costCeiling: opts.costCeiling,
45624
45982
  probeAuthor: opts.probeAuthor,
45625
- probeAuthorStateIntegrating: opts.probeAuthorStateIntegrating
45983
+ probeAuthorStateIntegrating: opts.probeAuthorStateIntegrating,
45984
+ nonInteractive: opts.nonInteractive
45626
45985
  });
45986
+ if (opts.nonInteractive === true) process.exit(exitCode);
45627
45987
  process.exitCode = exitCode;
45628
45988
  });
45629
45989
  }
45630
45990
 
45631
45991
  //#endregion
45632
45992
  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 };
45633
- //# sourceMappingURL=run-CHUFlRbH.js.map
45993
+ //# sourceMappingURL=run-DzB4rgkj.js.map
@@ -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