substrate-ai 0.20.139 → 0.20.142

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.
@@ -0,0 +1,4 @@
1
+ import { AdapterRegistry } from "./dist-DiNK6QOD.js";
2
+ import "./adapter-registry-DIcrxjH8.js";
3
+
4
+ export { AdapterRegistry };
package/dist/cli/index.js CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import { FileKvStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createDatabaseAdapter, createDoltOperatorReader, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion } from "../health-BXVyiLiL.js";
2
+ import { FileKvStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createDatabaseAdapter, createDoltOperatorReader, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion } from "../health-DTJaiJOd.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
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, createPipelineRun, createStderrLogger, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initWorkGraphSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, swallowDebug, tagRunAsBaseline, updatePipelineRun } from "../dist-BWCudDcl.js";
6
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createGitWorktreeManager, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-eaZG_3rN.js";
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, createPipelineRun, createStderrLogger, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initWorkGraphSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, swallowDebug, tagRunAsBaseline, updatePipelineRun } from "../dist-DiNK6QOD.js";
6
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createGitWorktreeManager, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-DHHryElL.js";
7
7
  import "../adapter-registry-DIcrxjH8.js";
8
- import { RunManifest, SupervisorLock, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, parseRuntimeProbes, readCurrentRunId, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runAcTraceabilityCheck } from "../manifest-read-IvpNQ6Yy.js";
9
- import "../errors-rZ-lz8U0.js";
8
+ import { RunManifest, SupervisorLock, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, parseRuntimeProbes, readCurrentRunId, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runAcTraceabilityCheck } from "../manifest-read-BgZ6mR5R.js";
9
+ import "../errors-B21JUhcc.js";
10
10
  import "../routing-DFxoKHDt.js";
11
11
  import { WorkGraphRepository } from "../work-graph-repository-DZyJv5pV.js";
12
12
  import "../decisions-CzSIEeGP.js";
13
- import "../decision-router-DblHY8se.js";
14
- import "../interactive-prompt-BypNgFHL.js";
13
+ import "../decision-router-BAPpON_C.js";
14
+ import "../interactive-prompt-OMlqP7Lm.js";
15
15
  import "../recovery-engine-BKGBeBnW.js";
16
16
  import "../version-manager-impl-qFBiO4Eh.js";
17
- import { registerUpgradeCommand } from "../upgrade-3NTcs1t7.js";
17
+ import { registerUpgradeCommand } from "../upgrade-DB7KzD9c.js";
18
18
  import { Command } from "commander";
19
19
  import { fileURLToPath } from "url";
20
20
  import { dirname, join, resolve } from "path";
@@ -670,6 +670,7 @@ const WHOLESALE_SUBSTRATE_IGNORES = new Set([
670
670
  ]);
671
671
  const STAR = ".substrate/*";
672
672
  const CONFIG_NEGATION = "!.substrate/config.yaml";
673
+ const PROFILE_NEGATION = "!.substrate/project-profile.yaml";
673
674
  const CODEX_PROMPTS = ".codex/prompts/";
674
675
  const CODEX_SKILLS = ".codex/skills/";
675
676
  /**
@@ -685,6 +686,9 @@ function computeSubstrateGitignore(existing) {
685
686
  const negIdx = trimmed.lastIndexOf(CONFIG_NEGATION);
686
687
  const negationEffective = negIdx !== -1 && negIdx > starIdx;
687
688
  if (!negationEffective) append.push(CONFIG_NEGATION);
689
+ const profileNegIdx = trimmed.lastIndexOf(PROFILE_NEGATION);
690
+ const profileNegationEffective = profileNegIdx !== -1 && profileNegIdx > starIdx;
691
+ if (!profileNegationEffective) append.push(PROFILE_NEGATION);
688
692
  if (!trimmed.includes(CODEX_PROMPTS)) append.push(CODEX_PROMPTS);
689
693
  if (!trimmed.includes(CODEX_SKILLS)) append.push(CODEX_SKILLS);
690
694
  let content = lines.join("\n");
@@ -925,6 +929,18 @@ async function detectSingleProjectStack(dir) {
925
929
  break;
926
930
  }
927
931
  if (marker.file === "pyproject.toml") {
932
+ const hasUv = await fileExists(path$1.join(dir, "uv.lock"));
933
+ if (hasUv) {
934
+ baseEntry = {
935
+ path: dir,
936
+ language: "python",
937
+ buildTool: "uv",
938
+ buildCommand: "uv sync",
939
+ testCommand: "uv run pytest",
940
+ installCommand: "uv add <package>"
941
+ };
942
+ break;
943
+ }
928
944
  const hasPoetry = await fileExists(path$1.join(dir, "poetry.lock"));
929
945
  if (hasPoetry) {
930
946
  baseEntry = {
@@ -7036,7 +7052,7 @@ async function runStatusAction(options) {
7036
7052
  logger$15.debug({ err }, "Work graph query failed, continuing without work graph data");
7037
7053
  }
7038
7054
  if (run === void 0) {
7039
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-D27XuFLF.js");
7055
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-Cf5RjwHi.js");
7040
7056
  const substrateDirPath = join(projectRoot, ".substrate");
7041
7057
  const processInfo = inspectProcessTree$1({
7042
7058
  projectRoot,
@@ -7984,7 +8000,7 @@ function defaultSupervisorDeps() {
7984
8000
  if (cached === null) {
7985
8001
  const { AdapterRegistry: AR } = await import(
7986
8002
  /* @vite-ignore */
7987
- "../adapter-registry-CXIG-OQI.js"
8003
+ "../adapter-registry-qU0IkbbQ.js"
7988
8004
  );
7989
8005
  cached = new AR();
7990
8006
  await cached.discoverAndRegister();
@@ -8551,11 +8567,11 @@ async function runSupervisorAction(options, deps = {}) {
8551
8567
  try {
8552
8568
  const { createExperimenter } = await import(
8553
8569
  /* @vite-ignore */
8554
- "../experimenter-Dn0gGOYl.js"
8570
+ "../experimenter-D55L011w.js"
8555
8571
  );
8556
8572
  const { getLatestRun: getLatest } = await import(
8557
8573
  /* @vite-ignore */
8558
- "../decisions-EQtHqewP.js"
8574
+ "../decisions-Dvw5HhyM.js"
8559
8575
  );
8560
8576
  const expAdapter = createDatabaseAdapter({
8561
8577
  backend: "auto",
@@ -8565,7 +8581,7 @@ async function runSupervisorAction(options, deps = {}) {
8565
8581
  await initSchema(expAdapter);
8566
8582
  const { runRunAction: runPipeline } = await import(
8567
8583
  /* @vite-ignore */
8568
- "../run-Cp19x4Xv.js"
8584
+ "../run-DStT56J1.js"
8569
8585
  );
8570
8586
  const runStoryFn = async (opts) => {
8571
8587
  const exitCode = await runPipeline({
@@ -9090,7 +9106,7 @@ async function runMetricsAction(options) {
9090
9106
  const routingConfigPath = join(dbDir, "routing.yml");
9091
9107
  let routingConfig = null;
9092
9108
  if (existsSync$1(routingConfigPath)) try {
9093
- const { loadModelRoutingConfig } = await import("../routing-C0f2cYja.js");
9109
+ const { loadModelRoutingConfig } = await import("../routing-_PGfe_cD.js");
9094
9110
  routingConfig = loadModelRoutingConfig(routingConfigPath);
9095
9111
  } catch {}
9096
9112
  if (routingConfig === null) routingConfig = {
@@ -10505,7 +10521,7 @@ async function listWorktreesAction(options) {
10505
10521
  * @returns - Exit code (0 = success, 1 = error)
10506
10522
  */
10507
10523
  async function cleanupWorktreesAction(options) {
10508
- const { taskId, projectRoot } = options;
10524
+ const { taskId, projectRoot, force } = options;
10509
10525
  try {
10510
10526
  const eventBus = createEventBus();
10511
10527
  const manager = createGitWorktreeManager({
@@ -10513,7 +10529,7 @@ async function cleanupWorktreesAction(options) {
10513
10529
  projectRoot
10514
10530
  });
10515
10531
  if (taskId !== void 0) try {
10516
- await manager.cleanupWorktree(taskId);
10532
+ await manager.cleanupWorktree(taskId, { force: force === true });
10517
10533
  process.stdout.write(`Cleaned up worktree for task "${taskId}"\n`);
10518
10534
  return WORKTREES_EXIT_SUCCESS;
10519
10535
  } catch (err) {
@@ -10526,8 +10542,9 @@ async function cleanupWorktreesAction(options) {
10526
10542
  return WORKTREES_EXIT_ERROR;
10527
10543
  }
10528
10544
  try {
10529
- const cleaned = await manager.cleanupAllWorktrees();
10545
+ const cleaned = await manager.cleanupAllWorktrees({ force: force === true });
10530
10546
  process.stdout.write(`Cleaned up ${cleaned} worktree${cleaned === 1 ? "" : "s"}\n`);
10547
+ if (force !== true) process.stdout.write("Worktrees with uncommitted work or unmerged branch commits were preserved — re-run with --force to discard them.\n");
10531
10548
  return WORKTREES_EXIT_SUCCESS;
10532
10549
  } catch (err) {
10533
10550
  const message = err instanceof Error ? err.message : String(err);
@@ -10550,12 +10567,13 @@ async function cleanupWorktreesAction(options) {
10550
10567
  * @param projectRoot - Project root directory (defaults to process.cwd())
10551
10568
  */
10552
10569
  function registerWorktreesCommand(program, version = "0.0.0", projectRoot = process.cwd()) {
10553
- program.command("worktrees").description("List or clean up substrate git worktrees and their task branches").option("--output-format <format>", "Output format: table (default) or json", "table").option("--json", "Output JSON (shorthand for --output-format json)", false).option("--status <status>", `Filter by task status: ${VALID_STATUSES.join(", ")}`).option("--sort <key>", `Sort by: ${VALID_SORT_KEYS.join(", ")} (default: created)`, "created").option("--cleanup [task-id]", "Remove substrate worktree(s) and branches. Pass a task id to remove just one; omit the arg to remove all.").action(async (opts) => {
10570
+ program.command("worktrees").description("List or clean up substrate git worktrees and their task branches").option("--output-format <format>", "Output format: table (default) or json", "table").option("--json", "Output JSON (shorthand for --output-format json)", false).option("--status <status>", `Filter by task status: ${VALID_STATUSES.join(", ")}`).option("--sort <key>", `Sort by: ${VALID_SORT_KEYS.join(", ")} (default: created)`, "created").option("--cleanup [task-id]", "Remove substrate worktree(s) and branches. Pass a task id to remove just one; omit the arg to remove all.").option("--force", "Discard worktrees even when they hold uncommitted work or unmerged branch commits.", false).action(async (opts) => {
10554
10571
  if (opts.cleanup !== void 0 && opts.cleanup !== false) {
10555
10572
  const taskId = typeof opts.cleanup === "string" && opts.cleanup.trim().length > 0 ? opts.cleanup.trim() : void 0;
10556
10573
  const cleanupExitCode = await cleanupWorktreesAction({
10557
10574
  ...taskId !== void 0 ? { taskId } : {},
10558
- projectRoot
10575
+ projectRoot,
10576
+ force: opts.force
10559
10577
  });
10560
10578
  process.exitCode = cleanupExitCode;
10561
10579
  return;
@@ -13402,8 +13420,8 @@ async function createProgram() {
13402
13420
  /** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
13403
13421
  function checkForUpdatesInBackground(currentVersion) {
13404
13422
  if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
13405
- import("../upgrade-DHHSy4fQ.js").then(async () => {
13406
- const { createVersionManager } = await import("../version-manager-impl-CfzcfFwo.js");
13423
+ import("../upgrade-CTj4hi8J.js").then(async () => {
13424
+ const { createVersionManager } = await import("../version-manager-impl-HJMonRJi.js");
13407
13425
  const vm = createVersionManager();
13408
13426
  const result = await vm.checkForUpdates();
13409
13427
  if (result.updateAvailable) {
@@ -15,6 +15,7 @@ const DECISION_SEVERITY_MAP = {
15
15
  "scope-violation": "fatal",
16
16
  "cross-story-race-recovered": "info",
17
17
  "cross-story-race-still-failed": "critical",
18
+ "auth-failure": "fatal",
18
19
  "pipeline-escalation": "warning"
19
20
  };
20
21
  /**
@@ -29,7 +30,8 @@ const DEFAULT_ACTION_MAP = {
29
30
  "scope-violation": "abort-pipeline",
30
31
  "cross-story-race-recovered": "continue-autonomous",
31
32
  "cross-story-race-still-failed": "escalate-without-halt",
32
- "pipeline-escalation": "escalate-without-halt"
33
+ "pipeline-escalation": "escalate-without-halt",
34
+ "auth-failure": "abort-pipeline"
33
35
  };
34
36
  /** Fallback default action for unknown decision types. */
35
37
  const DEFAULT_DEFAULT_ACTION = "escalate-without-halt";
@@ -94,4 +96,4 @@ function deriveExitCode(outcome) {
94
96
 
95
97
  //#endregion
96
98
  export { DECISION_SEVERITY_MAP, deriveExitCode, routeDecision };
97
- //# sourceMappingURL=decision-router-DblHY8se.js.map
99
+ //# sourceMappingURL=decision-router-BAPpON_C.js.map
@@ -1,4 +1,4 @@
1
- import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-BWCudDcl.js";
1
+ import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-DiNK6QOD.js";
2
2
  import "./decisions-CzSIEeGP.js";
3
3
 
4
4
  export { getLatestRun };
@@ -9462,6 +9462,39 @@ function checkAdapterVersionCompat(adapterName, actualVersion, tested) {
9462
9462
  const execAsync$2 = promisify(exec);
9463
9463
  /** Default model used when none is specified */
9464
9464
  const DEFAULT_MODEL$1 = "claude-sonnet-4-6";
9465
+ /**
9466
+ * Signatures the Claude Code CLI emits when a dispatch dies on authentication
9467
+ * rather than doing any work (H0.4, field finding #10). Matched
9468
+ * case-insensitively against the dispatch's combined output.
9469
+ *
9470
+ * Field-verified: a stale `ANTHROPIC_API_KEY` in the operator's shell made
9471
+ * spawned CLIs reject with "auth source takes precedence over claude.ai
9472
+ * login · Invalid API key" — surfaced as `create-story-no-file`
9473
+ * (qualityScore 40) or a full 600s timeout, never as an auth error, burning
9474
+ * ~25 minutes and two runs before diagnosis.
9475
+ */
9476
+ const CLAUDE_AUTH_FAILURE_SIGNATURES = [
9477
+ "invalid api key",
9478
+ "auth source takes precedence",
9479
+ "please run /login",
9480
+ "oauth token has expired",
9481
+ "oauth token is invalid",
9482
+ "oauth token revoked",
9483
+ "authentication_error",
9484
+ "credit balance is too low"
9485
+ ];
9486
+ /** Operator-facing remediation appended to auth-failure escalations. */
9487
+ const CLAUDE_AUTH_FAILURE_HINT = "The Claude CLI rejected authentication before doing any work. Every subsequent dispatch in this run would fail identically, so the run was halted. Likely causes, in order: (1) a stale ANTHROPIC_API_KEY exported in the environment that spawned substrate — the CLI prefers it over claude.ai subscription login and rejects it if invalid (substrate scrubs the var on coding dispatches, but keys configured inside Claude Code settings/apiKeyHelper are outside substrate's control; verify with `env -u ANTHROPIC_API_KEY claude -p \"ok\"`); (2) an expired claude.ai session — run `claude login`; (3) an exhausted API credit balance when api_billing is enabled. Fix the credential, then re-run — completed stories are preserved.";
9488
+ /**
9489
+ * Returns the matched auth-failure signature if `output` shows the Claude CLI
9490
+ * dying on authentication, else null. Pure + exported for diagnostics and
9491
+ * testing (mirrors detectCodexSandboxBlock).
9492
+ */
9493
+ function detectClaudeAuthFailure(output) {
9494
+ if (output === void 0 || output === null || output === "") return null;
9495
+ const lower = output.toLowerCase();
9496
+ return CLAUDE_AUTH_FAILURE_SIGNATURES.find((sig) => lower.includes(sig)) ?? null;
9497
+ }
9465
9498
  /** Approximate characters per token for estimation */
9466
9499
  const CHARS_PER_TOKEN$2 = 3;
9467
9500
  /** Estimated output token multiplier relative to input */
@@ -9655,10 +9688,13 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
9655
9688
  if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
9656
9689
  const envEntries = {};
9657
9690
  if (options.billingMode === "api" && options.apiKey) envEntries.ANTHROPIC_API_KEY = options.apiKey;
9691
+ const unsetEnvKeys = ["CLAUDECODE"];
9692
+ if (options.billingMode !== "api") unsetEnvKeys.push("ANTHROPIC_API_KEY");
9658
9693
  return {
9659
9694
  binary: "claude",
9660
9695
  args,
9661
9696
  env: envEntries,
9697
+ unsetEnvKeys,
9662
9698
  cwd: options.worktreePath,
9663
9699
  stdin: planningPrompt
9664
9700
  };
@@ -11483,5 +11519,5 @@ async function callLLM(params) {
11483
11519
  }
11484
11520
 
11485
11521
  //#endregion
11486
- export { ADVISORY_NOTES, AdapterRegistry, AdtError, BudgetConfigSchema, CODEX_SANDBOX_BLOCK_HINT, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, Categorizer, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, ConsumerAnalyzer, CostTrackerConfigSchema, DEFAULT_CONFIG, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, DoltNotInstalled, DoltQueryError, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, GeminiCLIAdapter, GlobalSettingsSchema, InMemoryDatabaseAdapter, IngestionServer, LEARNING_FINDING, LogTurnAnalyzer, ModelRoutingConfigSchema, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProviderPolicySchema, ProvidersSchema, Recommender, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TASK_TYPE_PHASE_MAP, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryConfigSchema, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, VersionManagerImpl, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, callLLM, checkDoltInstalled, classifyVersionGap, compareRunMetrics, createAmendmentRun, createConfigSystem, createDatabaseAdapter as createDatabaseAdapter$1, createDecision, createExperimenter, createPipelineRun, createRequirement, createStderrLogger, createVersionManager, detectCodexSandboxBlock, detectInterfaceChanges, determineVerdict, getActiveDecisions, getAllCostEntriesFiltered, getArtifactByTypeForRun, getArtifactsByRun, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getModelTier, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initWorkGraphSchema, initializeDolt, listRequirements, listRunMetrics, loadModelRoutingConfig, loadParentRunDecisions, registerArtifact, resolvePromptFile, supersedeDecision, swallowDebug, tagRunAsBaseline, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics };
11487
- //# sourceMappingURL=dist-BWCudDcl.js.map
11522
+ export { ADVISORY_NOTES, AdapterRegistry, AdtError, BudgetConfigSchema, CLAUDE_AUTH_FAILURE_HINT, CODEX_SANDBOX_BLOCK_HINT, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, Categorizer, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, ConsumerAnalyzer, CostTrackerConfigSchema, DEFAULT_CONFIG, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, DoltNotInstalled, DoltQueryError, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, GeminiCLIAdapter, GlobalSettingsSchema, InMemoryDatabaseAdapter, IngestionServer, LEARNING_FINDING, LogTurnAnalyzer, ModelRoutingConfigSchema, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProviderPolicySchema, ProvidersSchema, Recommender, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TASK_TYPE_PHASE_MAP, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryConfigSchema, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, VersionManagerImpl, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, callLLM, checkDoltInstalled, classifyVersionGap, compareRunMetrics, createAmendmentRun, createConfigSystem, createDatabaseAdapter as createDatabaseAdapter$1, createDecision, createExperimenter, createPipelineRun, createRequirement, createStderrLogger, createVersionManager, detectClaudeAuthFailure, detectCodexSandboxBlock, detectInterfaceChanges, determineVerdict, getActiveDecisions, getAllCostEntriesFiltered, getArtifactByTypeForRun, getArtifactsByRun, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getModelTier, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initWorkGraphSchema, initializeDolt, listRequirements, listRunMetrics, loadModelRoutingConfig, loadParentRunDecisions, registerArtifact, resolvePromptFile, supersedeDecision, swallowDebug, tagRunAsBaseline, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics };
11523
+ //# sourceMappingURL=dist-DiNK6QOD.js.map
@@ -1,4 +1,4 @@
1
- import { AdtError } from "./dist-BWCudDcl.js";
1
+ import { AdtError } from "./dist-DiNK6QOD.js";
2
2
 
3
3
  //#region src/core/errors.ts
4
4
  /** Error thrown when task configuration is invalid */
@@ -71,4 +71,4 @@ var TaskGraphIncompatibleFormatError = class extends AdtError {
71
71
 
72
72
  //#endregion
73
73
  export { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError };
74
- //# sourceMappingURL=errors-rZ-lz8U0.js.map
74
+ //# sourceMappingURL=errors-B21JUhcc.js.map
@@ -1,3 +1,3 @@
1
- import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-BWCudDcl.js";
1
+ import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-DiNK6QOD.js";
2
2
 
3
3
  export { createExperimenter };
@@ -1,7 +1,7 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-BXVyiLiL.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-DTJaiJOd.js";
2
2
  import "./logger-KeHncl-f.js";
3
- import "./dist-BWCudDcl.js";
4
- import "./manifest-read-IvpNQ6Yy.js";
3
+ import "./dist-DiNK6QOD.js";
4
+ import "./manifest-read-BgZ6mR5R.js";
5
5
  import "./work-graph-repository-DZyJv5pV.js";
6
6
  import "./decisions-CzSIEeGP.js";
7
7
 
@@ -1,6 +1,6 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { DoltClient, DoltQueryError, createDatabaseAdapter$1 as createDatabaseAdapter, getLatestRun, getPipelineRunById, initSchema } from "./dist-BWCudDcl.js";
3
- import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-IvpNQ6Yy.js";
2
+ import { DoltClient, DoltQueryError, createDatabaseAdapter$1 as createDatabaseAdapter, getLatestRun, getPipelineRunById, initSchema } from "./dist-DiNK6QOD.js";
3
+ import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-BgZ6mR5R.js";
4
4
  import { createRequire } from "module";
5
5
  import { dirname, join } from "path";
6
6
  import { existsSync, readFileSync } from "node:fs";
@@ -1000,4 +1000,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
1000
1000
 
1001
1001
  //#endregion
1002
1002
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, FileKvStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDoltOperatorReader, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, runHealthAction, validateStoryKey };
1003
- //# sourceMappingURL=health-BXVyiLiL.js.map
1003
+ //# sourceMappingURL=health-DTJaiJOd.js.map
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { childLogger, createLogger, logger } from "./logger-KeHncl-f.js";
2
2
  import { assertDefined, createEventBus, createTuiApp, deepClone, formatDuration, generateId, isPlainObject, isTuiCapable, printNonTtyWarning, sleep, withRetry } from "./helpers-CElYrONe.js";
3
- import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-BWCudDcl.js";
3
+ import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-DiNK6QOD.js";
4
4
  import "./adapter-registry-DIcrxjH8.js";
5
- import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-rZ-lz8U0.js";
5
+ import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-B21JUhcc.js";
6
6
 
7
7
  //#region src/core/di.ts
8
8
  /**
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { readCurrentRunId, resolveMainRepoRoot } from "./manifest-read-IvpNQ6Yy.js";
2
+ import { readCurrentRunId, resolveMainRepoRoot } from "./manifest-read-BgZ6mR5R.js";
3
3
  import { join } from "node:path";
4
4
  import { mkdir, readFile, writeFile } from "node:fs/promises";
5
5
  import * as readline from "node:readline";
@@ -180,4 +180,4 @@ async function runInteractivePrompt(decisionContext) {
180
180
 
181
181
  //#endregion
182
182
  export { runInteractivePrompt };
183
- //# sourceMappingURL=interactive-prompt-BypNgFHL.js.map
183
+ //# sourceMappingURL=interactive-prompt-OMlqP7Lm.js.map
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { LEARNING_FINDING, createDecision, getDecisionsByCategory } from "./dist-BWCudDcl.js";
2
+ import { LEARNING_FINDING, createDecision, getDecisionsByCategory } from "./dist-DiNK6QOD.js";
3
3
  import * as path$1 from "path";
4
4
  import { join } from "path";
5
5
  import { readFile } from "fs/promises";
@@ -2043,11 +2043,11 @@ function findCodeEvidence(opts) {
2043
2043
  /** Hard timeout for the build command in milliseconds (FR-V11). */
2044
2044
  const BUILD_CHECK_TIMEOUT_MS = 6e4;
2045
2045
  /** Maximum characters to include in details string from build output. */
2046
- const MAX_OUTPUT_CHARS = 2e3;
2046
+ const MAX_OUTPUT_CHARS$1 = 2e3;
2047
2047
  /** Per-stream tail size cap for structured findings (story 55-1 convention). */
2048
2048
  const TAIL_BYTES = 4 * 1024;
2049
2049
  /** Return the last N bytes of a UTF-8 string, sliced by string length for simplicity. */
2050
- function tail$1(text, bytes = TAIL_BYTES) {
2050
+ function tail$2(text, bytes = TAIL_BYTES) {
2051
2051
  return text.length <= bytes ? text : text.slice(text.length - bytes);
2052
2052
  }
2053
2053
  /**
@@ -2061,11 +2061,9 @@ function tail$1(text, bytes = TAIL_BYTES) {
2061
2061
  * This function inlines the detection logic independently.
2062
2062
  */
2063
2063
  function detectBuildCommand(workingDir) {
2064
+ const profileCommand = readProfileBuildCommand(workingDir);
2065
+ if (profileCommand !== void 0) return profileCommand;
2064
2066
  if (existsSync(join$1(workingDir, "turbo.json"))) return "turbo build";
2065
- if (existsSync(join$1(workingDir, "pnpm-lock.yaml"))) return "pnpm run build";
2066
- if (existsSync(join$1(workingDir, "yarn.lock"))) return "yarn build";
2067
- if (existsSync(join$1(workingDir, "bun.lockb"))) return "bun run build";
2068
- if (existsSync(join$1(workingDir, "package.json"))) return "npm run build";
2069
2067
  const nonNodeMarkers = [
2070
2068
  "pyproject.toml",
2071
2069
  "poetry.lock",
@@ -2074,9 +2072,29 @@ function detectBuildCommand(workingDir) {
2074
2072
  "go.mod"
2075
2073
  ];
2076
2074
  for (const marker of nonNodeMarkers) if (existsSync(join$1(workingDir, marker))) return "";
2075
+ if (existsSync(join$1(workingDir, "pnpm-lock.yaml"))) return "pnpm run build";
2076
+ if (existsSync(join$1(workingDir, "yarn.lock"))) return "yarn build";
2077
+ if (existsSync(join$1(workingDir, "bun.lockb"))) return "bun run build";
2078
+ if (existsSync(join$1(workingDir, "package.json"))) return "npm run build";
2077
2079
  return "";
2078
2080
  }
2079
2081
  /**
2082
+ * Read `project.buildCommand` from `.substrate/project-profile.yaml` under
2083
+ * `workingDir`. Line-based parse (same approach as the monolith's
2084
+ * resolveInstallCommand) to avoid a yaml dependency. Returns undefined when
2085
+ * the profile is absent, unreadable, or has no buildCommand.
2086
+ */
2087
+ function readProfileBuildCommand(workingDir) {
2088
+ const profilePath = join$1(workingDir, ".substrate", "project-profile.yaml");
2089
+ if (!existsSync(profilePath)) return void 0;
2090
+ try {
2091
+ const content = readFileSync(profilePath, "utf-8");
2092
+ const match = content.match(/^\s*buildCommand:\s*['"]?(.+?)['"]?\s*$/m);
2093
+ if (match?.[1] !== void 0 && match[1].length > 0) return match[1];
2094
+ } catch {}
2095
+ return void 0;
2096
+ }
2097
+ /**
2080
2098
  * Runs the project's build command and returns pass/warn/fail based on exit code.
2081
2099
  *
2082
2100
  * AC1: exit code 0 → pass
@@ -2139,8 +2157,8 @@ var BuildCheck = class {
2139
2157
  severity: "error",
2140
2158
  message: `command exceeded ${BUILD_CHECK_TIMEOUT_MS}ms`,
2141
2159
  command: cmd,
2142
- stdoutTail: tail$1(stdout),
2143
- stderrTail: tail$1(stderr),
2160
+ stdoutTail: tail$2(stdout),
2161
+ stderrTail: tail$2(stderr),
2144
2162
  durationMs: duration
2145
2163
  }];
2146
2164
  resolve$2({
@@ -2160,13 +2178,169 @@ var BuildCheck = class {
2160
2178
  findings: []
2161
2179
  });
2162
2180
  else {
2163
- const truncated = output.length > MAX_OUTPUT_CHARS ? output.slice(0, MAX_OUTPUT_CHARS) + "... (truncated)" : output;
2181
+ const truncated = output.length > MAX_OUTPUT_CHARS$1 ? output.slice(0, MAX_OUTPUT_CHARS$1) + "... (truncated)" : output;
2164
2182
  const findings = [{
2165
2183
  category: "build-error",
2166
2184
  severity: "error",
2167
2185
  message: `build failed (exit ${code}): ${truncated}`,
2168
2186
  command: cmd,
2169
2187
  ...code !== null ? { exitCode: code } : {},
2188
+ stdoutTail: tail$2(stdout),
2189
+ stderrTail: tail$2(stderr),
2190
+ durationMs: duration
2191
+ }];
2192
+ resolve$2({
2193
+ status: "fail",
2194
+ details: renderFindings(findings),
2195
+ duration_ms: duration,
2196
+ findings
2197
+ });
2198
+ }
2199
+ });
2200
+ });
2201
+ }
2202
+ };
2203
+
2204
+ //#endregion
2205
+ //#region packages/sdlc/dist/verification/checks/test-suite-check.js
2206
+ /** Hard ceiling for the suite run (ms). Suites are slower than builds. */
2207
+ const TEST_SUITE_CHECK_TIMEOUT_MS = 3e5;
2208
+ /** Cap on captured output embedded in the finding. */
2209
+ const MAX_OUTPUT_CHARS = 4e3;
2210
+ function tail$1(s, n = 2e3) {
2211
+ return s.length > n ? s.slice(-n) : s;
2212
+ }
2213
+ /**
2214
+ * Read `project.testCommand` from `.substrate/project-profile.yaml` under
2215
+ * `workingDir`. Line-based parse (no yaml dependency; same pattern as
2216
+ * detectBuildCommand's profile read). Returns undefined when absent.
2217
+ */
2218
+ function detectTestCommand(workingDir) {
2219
+ const profilePath = join$1(workingDir, ".substrate", "project-profile.yaml");
2220
+ if (!existsSync(profilePath)) return void 0;
2221
+ try {
2222
+ const content = readFileSync(profilePath, "utf-8");
2223
+ const match = content.match(/^\s*testCommand:\s*['"]?(.+?)['"]?\s*$/m);
2224
+ if (match?.[1] !== void 0 && match[1].length > 0) return match[1];
2225
+ } catch {}
2226
+ return void 0;
2227
+ }
2228
+ var TestSuiteCheck = class {
2229
+ name = "test-suite";
2230
+ tier = "A";
2231
+ async run(context) {
2232
+ const start = Date.now();
2233
+ const explicitOverride = context.testCommand !== void 0;
2234
+ const cmd = explicitOverride ? context.testCommand : detectTestCommand(context.workingDir);
2235
+ if (!explicitOverride && process.env.VITEST !== void 0 && process.env.SUBSTRATE_ALLOW_NESTED_TESTS !== "1") {
2236
+ const findings = [{
2237
+ category: "test-suite-skip",
2238
+ severity: "warn",
2239
+ message: "recursion guard: refusing to run an auto-detected test suite from inside a test runner (VITEST is set). Pass an explicit testCommand, or set SUBSTRATE_ALLOW_NESTED_TESTS=1 in a harness that deliberately nests suite execution."
2240
+ }];
2241
+ return {
2242
+ status: "warn",
2243
+ details: renderFindings(findings),
2244
+ duration_ms: Date.now() - start,
2245
+ findings
2246
+ };
2247
+ }
2248
+ if (cmd === void 0 || cmd === "") {
2249
+ const findings = [{
2250
+ category: "test-suite-skip",
2251
+ severity: "warn",
2252
+ message: `no test command configured for project at ${context.workingDir} — set project.testCommand in .substrate/project-profile.yaml (substrate init detects it) so verification runs the real suite`
2253
+ }];
2254
+ return {
2255
+ status: "warn",
2256
+ details: renderFindings(findings),
2257
+ duration_ms: Date.now() - start,
2258
+ findings
2259
+ };
2260
+ }
2261
+ return new Promise((resolve$2) => {
2262
+ const env = { ...process.env };
2263
+ const parentNodeOpts = env["NODE_OPTIONS"] ?? "";
2264
+ if (!parentNodeOpts.includes("--max-old-space-size")) env["NODE_OPTIONS"] = `${parentNodeOpts} --max-old-space-size=2048`.trim();
2265
+ const child = spawn(cmd, [], {
2266
+ cwd: context.workingDir,
2267
+ detached: true,
2268
+ shell: true,
2269
+ env,
2270
+ stdio: [
2271
+ "ignore",
2272
+ "pipe",
2273
+ "pipe"
2274
+ ]
2275
+ });
2276
+ let stdout = "";
2277
+ let stderr = "";
2278
+ let output = "";
2279
+ child.stdout?.on("data", (chunk) => {
2280
+ const s = chunk.toString();
2281
+ stdout += s;
2282
+ output += s;
2283
+ });
2284
+ child.stderr?.on("data", (chunk) => {
2285
+ const s = chunk.toString();
2286
+ stderr += s;
2287
+ output += s;
2288
+ });
2289
+ const timeoutHandle = setTimeout(() => {
2290
+ try {
2291
+ process.kill(-child.pid, "SIGKILL");
2292
+ } catch {}
2293
+ const duration = Date.now() - start;
2294
+ const findings = [{
2295
+ category: "test-suite-timeout",
2296
+ severity: "error",
2297
+ message: `test suite exceeded ${TEST_SUITE_CHECK_TIMEOUT_MS}ms`,
2298
+ command: cmd,
2299
+ stdoutTail: tail$1(stdout),
2300
+ stderrTail: tail$1(stderr),
2301
+ durationMs: duration
2302
+ }];
2303
+ resolve$2({
2304
+ status: "fail",
2305
+ details: renderFindings(findings),
2306
+ duration_ms: duration,
2307
+ findings
2308
+ });
2309
+ }, TEST_SUITE_CHECK_TIMEOUT_MS);
2310
+ child.on("error", (err) => {
2311
+ clearTimeout(timeoutHandle);
2312
+ const duration = Date.now() - start;
2313
+ const findings = [{
2314
+ category: "test-suite-error",
2315
+ severity: "error",
2316
+ message: `test command could not be spawned: ${err.message}`,
2317
+ command: cmd,
2318
+ durationMs: duration
2319
+ }];
2320
+ resolve$2({
2321
+ status: "fail",
2322
+ details: renderFindings(findings),
2323
+ duration_ms: duration,
2324
+ findings
2325
+ });
2326
+ });
2327
+ child.on("close", (code) => {
2328
+ clearTimeout(timeoutHandle);
2329
+ const duration = Date.now() - start;
2330
+ if (code === 0) resolve$2({
2331
+ status: "pass",
2332
+ details: `test suite passed (${cmd})`,
2333
+ duration_ms: duration,
2334
+ findings: []
2335
+ });
2336
+ else {
2337
+ const truncated = output.length > MAX_OUTPUT_CHARS ? output.slice(0, MAX_OUTPUT_CHARS) + "... (truncated)" : output;
2338
+ const findings = [{
2339
+ category: "test-suite-fail",
2340
+ severity: "error",
2341
+ message: `test suite failed (exit ${String(code)}): ${truncated}`,
2342
+ command: cmd,
2343
+ ...code !== null ? { exitCode: code } : {},
2170
2344
  stdoutTail: tail$1(stdout),
2171
2345
  stderrTail: tail$1(stderr),
2172
2346
  durationMs: duration
@@ -4082,6 +4256,7 @@ function createDefaultVerificationPipeline(bus, config) {
4082
4256
  new TrivialOutputCheck(config),
4083
4257
  new AcceptanceCriteriaEvidenceCheck(),
4084
4258
  new BuildCheck(),
4259
+ new TestSuiteCheck(),
4085
4260
  new RuntimeProbeCheck(),
4086
4261
  new SourceAcFidelityCheck(),
4087
4262
  new SourceAcShelloutCheck(),
@@ -5865,4 +6040,4 @@ async function resolveRunManifest(dbRoot, runId) {
5865
6040
 
5866
6041
  //#endregion
5867
6042
  export { FindingsInjector, RunManifest, RuntimeProbeListSchema, SupervisorLock, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, parseRuntimeProbes, readCurrentRunId, renderFindings, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runAcTraceabilityCheck, runStaleVerificationRecovery };
5868
- //# sourceMappingURL=manifest-read-IvpNQ6Yy.js.map
6043
+ //# sourceMappingURL=manifest-read-BgZ6mR5R.js.map
@@ -1,6 +1,6 @@
1
1
  import "../../logger-KeHncl-f.js";
2
- import "../../dist-BWCudDcl.js";
3
- import "../../manifest-read-IvpNQ6Yy.js";
4
- import { runInteractivePrompt } from "../../interactive-prompt-BypNgFHL.js";
2
+ import "../../dist-DiNK6QOD.js";
3
+ import "../../manifest-read-BgZ6mR5R.js";
4
+ import { runInteractivePrompt } from "../../interactive-prompt-OMlqP7Lm.js";
5
5
 
6
6
  export { runInteractivePrompt };
@@ -1,4 +1,4 @@
1
- import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-BWCudDcl.js";
1
+ import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-DiNK6QOD.js";
2
2
  import "./routing-DFxoKHDt.js";
3
3
 
4
4
  export { loadModelRoutingConfig };
@@ -1,11 +1,11 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, FileKvStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-BXVyiLiL.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, FileKvStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-DTJaiJOd.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, CODEX_SANDBOX_BLOCK_HINT, 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, classifyVersionGap, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, createStderrLogger, detectCodexSandboxBlock, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, swallowDebug, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-BWCudDcl.js";
5
- import { FindingsInjector, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, parseRuntimeProbes, renderFindings, resolveGraphPath, resolveMainRepoRoot, runAcTraceabilityCheck, runStaleVerificationRecovery } from "./manifest-read-IvpNQ6Yy.js";
4
+ import { ADVISORY_NOTES, CLAUDE_AUTH_FAILURE_HINT, CODEX_SANDBOX_BLOCK_HINT, 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, classifyVersionGap, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, createStderrLogger, detectClaudeAuthFailure, detectCodexSandboxBlock, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, swallowDebug, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-DiNK6QOD.js";
5
+ import { FindingsInjector, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, parseRuntimeProbes, renderFindings, resolveGraphPath, resolveMainRepoRoot, runAcTraceabilityCheck, runStaleVerificationRecovery } from "./manifest-read-BgZ6mR5R.js";
6
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-BypNgFHL.js";
7
+ import { deriveExitCode, routeDecision } from "./decision-router-BAPpON_C.js";
8
+ import { runInteractivePrompt } from "./interactive-prompt-OMlqP7Lm.js";
9
9
  import { runRecoveryEngine } from "./recovery-engine-BKGBeBnW.js";
10
10
  import { basename, dirname, extname, join } from "path";
11
11
  import { access, readFile, readdir, stat } from "fs/promises";
@@ -159,6 +159,66 @@ function decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch)
159
159
  };
160
160
  return { safe: true };
161
161
  }
162
+ /**
163
+ * Decide whether removing a story worktree AND deleting its branch is safe
164
+ * (H0.3, field findings #17/#19). Unsafe when:
165
+ * - the worktree has uncommitted changes (removal destroys the only copy), or
166
+ * - the branch carries commits not reachable from the project's current HEAD
167
+ * (branch -D destroys them — this is where H0.1's wip checkpoints live), or
168
+ * - either state could not be determined (negative unmergedCommits).
169
+ *
170
+ * Pure + exported for testing (mirrors decideWorktreeReclaim; the git I/O that
171
+ * produces the inputs lives in inspectWorktreeRemovalSafety).
172
+ */
173
+ function decideWorktreeRemoval(hasUncommittedChanges, uncommittedFiles, unmergedCommits, branchName) {
174
+ const reasons = [];
175
+ if (hasUncommittedChanges) {
176
+ const preview = uncommittedFiles.slice(0, 10).join(", ");
177
+ const more = uncommittedFiles.length > 10 ? ` (+${String(uncommittedFiles.length - 10)} more)` : "";
178
+ reasons.push(`the worktree has ${String(uncommittedFiles.length)} uncommitted change(s) that removal would destroy` + (preview.length > 0 ? `: ${preview}${more}` : ""));
179
+ }
180
+ if (unmergedCommits > 0) reasons.push(`branch ${branchName} carries ${String(unmergedCommits)} commit(s) not reachable from the current HEAD — deleting the branch would destroy them`);
181
+ if (unmergedCommits < 0) reasons.push("the branch state could not be verified as safe to discard");
182
+ return {
183
+ safe: reasons.length === 0,
184
+ reasons
185
+ };
186
+ }
187
+ /**
188
+ * Gather the inputs for `decideWorktreeRemoval` from git. Thin I/O wrapper:
189
+ * - `git status --porcelain` inside the worktree (skipped when the directory
190
+ * is missing — nothing on disk to lose)
191
+ * - `git rev-list --count HEAD..<branch>` in the project root (commits the
192
+ * branch has that the current checkout does not; 0 when the branch is
193
+ * merged or absent, -1 when the count could not be determined)
194
+ */
195
+ async function inspectWorktreeRemovalSafety(worktreePath, projectRoot, branchName) {
196
+ let hasUncommittedChanges = false;
197
+ let uncommittedFiles = [];
198
+ const worktreeOnDisk = await access$1(worktreePath).then(() => true).catch(() => false);
199
+ if (worktreeOnDisk) {
200
+ const statusResult = await spawnGit(["status", "--porcelain"], { cwd: worktreePath });
201
+ if (statusResult.code === 0) {
202
+ uncommittedFiles = statusResult.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => line.slice(3).trim());
203
+ hasUncommittedChanges = uncommittedFiles.length > 0;
204
+ } else return decideWorktreeRemoval(false, [], -1, branchName);
205
+ }
206
+ const branchExists = (await spawnGit([
207
+ "rev-parse",
208
+ "--verify",
209
+ branchName
210
+ ], { cwd: projectRoot })).code === 0;
211
+ let unmergedCommits = 0;
212
+ if (branchExists) {
213
+ const aheadResult = await spawnGit([
214
+ "rev-list",
215
+ "--count",
216
+ `HEAD..${branchName}`
217
+ ], { cwd: projectRoot });
218
+ unmergedCommits = aheadResult.code === 0 ? Number.parseInt(aheadResult.stdout.trim(), 10) || 0 : -1;
219
+ }
220
+ return decideWorktreeRemoval(hasUncommittedChanges, uncommittedFiles, unmergedCommits, branchName);
221
+ }
162
222
  async function createWorktree(projectRoot, taskId, branchName, baseBranch, copyFiles = []) {
163
223
  const worktreePath = path$5.join(projectRoot, ".substrate-worktrees", taskId);
164
224
  const worktreeExists = await access$1(worktreePath).then(() => true).catch((err) => {
@@ -497,7 +557,8 @@ var GitWorktreeManagerImpl = class {
497
557
  baseBranch,
498
558
  copyFiles: this._copyFiles
499
559
  }, "createWorktree");
500
- await createWorktree(this._projectRoot, taskId, branchName, baseBranch, this._copyFiles);
560
+ const copyFiles = this._copyFiles.includes(".substrate/project-profile.yaml") ? this._copyFiles : [...this._copyFiles, ".substrate/project-profile.yaml"];
561
+ await createWorktree(this._projectRoot, taskId, branchName, baseBranch, copyFiles);
501
562
  const createdAt = new Date();
502
563
  this._eventBus.emit("worktree:created", {
503
564
  taskId,
@@ -518,14 +579,19 @@ var GitWorktreeManagerImpl = class {
518
579
  }, "Worktree created");
519
580
  return info;
520
581
  }
521
- async cleanupWorktree(taskId) {
582
+ async cleanupWorktree(taskId, opts) {
522
583
  const branchName = BRANCH_PREFIX + taskId;
523
584
  const worktreePath = this.getWorktreePath(taskId);
524
585
  this._logger.debug({
525
586
  taskId,
526
587
  branchName,
527
- worktreePath
588
+ worktreePath,
589
+ force: opts?.force === true
528
590
  }, "cleanupWorktree");
591
+ if (opts?.force !== true) {
592
+ const decision = await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchName);
593
+ if (!decision.safe) throw new Error(`refusing to clean up worktree for "${taskId}": ${decision.reasons.join("; ")}.\nInspect first (git -C ${worktreePath} status; git log ${branchName} --oneline) or re-run with --force to discard.`);
594
+ }
529
595
  let worktreeExists = false;
530
596
  try {
531
597
  await access$1(worktreePath);
@@ -563,12 +629,28 @@ var GitWorktreeManagerImpl = class {
563
629
  branchName
564
630
  }, "Worktree cleaned up");
565
631
  }
566
- async cleanupAllWorktrees() {
567
- this._logger.debug({ projectRoot: this._projectRoot }, "cleanupAllWorktrees");
632
+ async cleanupAllWorktrees(opts) {
633
+ this._logger.debug({
634
+ projectRoot: this._projectRoot,
635
+ force: opts?.force === true
636
+ }, "cleanupAllWorktrees");
568
637
  const orphanedPaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
569
638
  let cleaned = 0;
570
639
  for (const worktreePath of orphanedPaths) {
571
640
  const taskId = path$4.basename(worktreePath);
641
+ const branchGuardName = BRANCH_PREFIX + taskId;
642
+ const decision = opts?.force === true ? {
643
+ safe: true,
644
+ reasons: []
645
+ } : await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchGuardName);
646
+ if (!decision.safe) {
647
+ this._logger.warn({
648
+ taskId,
649
+ worktreePath,
650
+ reasons: decision.reasons
651
+ }, "cleanupAllWorktrees: preserving worktree — removal would destroy work (use `substrate worktrees cleanup --force` to discard)");
652
+ continue;
653
+ }
572
654
  let worktreeRemoved = false;
573
655
  try {
574
656
  await removeWorktree(worktreePath, this._projectRoot);
@@ -6813,6 +6895,21 @@ async function runCreateStory(deps, params) {
6813
6895
  if (dispatchResult.parsed === null) {
6814
6896
  const details = dispatchResult.parseError ?? "No YAML block found in output";
6815
6897
  const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
6898
+ const authSignature = detectClaudeAuthFailure(dispatchResult.output);
6899
+ if (authSignature !== null) {
6900
+ logger$21.error({
6901
+ epicId,
6902
+ storyKey,
6903
+ authSignature,
6904
+ rawOutputSnippet: rawSnippet
6905
+ }, "Create-story dispatch died on authentication");
6906
+ return {
6907
+ result: "failed",
6908
+ error: "auth-failure",
6909
+ details: `Claude CLI authentication failure (matched: "${authSignature}"). Agent output: ${rawSnippet}`,
6910
+ tokenUsage
6911
+ };
6912
+ }
6816
6913
  logger$21.warn({
6817
6914
  epicId,
6818
6915
  storyKey,
@@ -8157,6 +8254,22 @@ function resolveDefaultTestPatterns(projectRoot) {
8157
8254
  //#endregion
8158
8255
  //#region src/modules/compiled-workflows/install-command.ts
8159
8256
  /**
8257
+ * Read a single `<key>: value` line from the project profile under
8258
+ * `projectRoot` (H1.2). Line-based parse, no yaml dependency. Returns
8259
+ * undefined when the profile or key is absent.
8260
+ */
8261
+ function resolveProfileCommand(projectRoot, key) {
8262
+ if (!projectRoot) return void 0;
8263
+ const profilePath = join$1(projectRoot, ".substrate", "project-profile.yaml");
8264
+ if (!existsSync(profilePath)) return void 0;
8265
+ try {
8266
+ const content = readFileSync(profilePath, "utf-8");
8267
+ const match$2 = content.match(new RegExp(`^\\s*${key}:\\s*['"]?(.+?)['"]?\\s*$`, "m"));
8268
+ if (match$2?.[1] && match$2[1].length > 0) return match$2[1];
8269
+ } catch {}
8270
+ return void 0;
8271
+ }
8272
+ /**
8160
8273
  * Returns the install command string for the current project.
8161
8274
  * Used as a template variable ({{install_command}}) in the dev-story prompt.
8162
8275
  */
@@ -8180,6 +8293,7 @@ function resolveInstallCommand(projectRoot) {
8180
8293
  cargo: "cargo add <package>",
8181
8294
  pip: "pip install <package>",
8182
8295
  poetry: "poetry add <package>",
8296
+ uv: "uv add <package>",
8183
8297
  gradle: "add dependency to build.gradle",
8184
8298
  maven: "add dependency to pom.xml"
8185
8299
  };
@@ -8420,7 +8534,7 @@ async function runDevStory(deps, params) {
8420
8534
  },
8421
8535
  {
8422
8536
  name: "verify_command",
8423
- content: deps.pack.manifest.verifyCommand !== false ? deps.pack.manifest.verifyCommand ?? "npx turbo build" : "",
8537
+ content: deps.pack.manifest.verifyCommand !== false ? deps.pack.manifest.verifyCommand ?? resolveProfileCommand(deps.projectRoot, "buildCommand") ?? "" : "",
8424
8538
  priority: "optional"
8425
8539
  },
8426
8540
  {
@@ -11644,6 +11758,8 @@ function shouldRunTscCheck(projectRoot) {
11644
11758
  if (!project) return true;
11645
11759
  const packages = project["packages"];
11646
11760
  if (Array.isArray(packages) && packages.length > 0) return packages.some((p) => p.language === "typescript" || p.language === "javascript");
11761
+ const language = project["language"];
11762
+ if (typeof language === "string" && language.length > 0) return language === "typescript" || language === "javascript";
11647
11763
  const buildCommand = project["buildCommand"];
11648
11764
  if (typeof buildCommand === "string" && buildCommand.length > 0) {
11649
11765
  const tsIndicators = [
@@ -12490,6 +12606,8 @@ function assembleVerificationContext(opts) {
12490
12606
  } catch {
12491
12607
  commitSha = "unknown";
12492
12608
  }
12609
+ const profileBuildCommand = readProfileKey(opts.workingDir, "buildCommand");
12610
+ const profileTestCommand = readProfileKey(opts.workingDir, "testCommand");
12493
12611
  return {
12494
12612
  storyKey: opts.storyKey,
12495
12613
  workingDir: opts.workingDir,
@@ -12500,10 +12618,26 @@ function assembleVerificationContext(opts) {
12500
12618
  devStoryResult: opts.devStoryResult,
12501
12619
  outputTokenCount: opts.outputTokenCount,
12502
12620
  sourceEpicContent: opts.sourceEpicContent,
12503
- runId: opts.runId
12621
+ runId: opts.runId,
12622
+ ...profileBuildCommand !== void 0 ? { buildCommand: profileBuildCommand } : {},
12623
+ ...profileTestCommand !== void 0 ? { testCommand: profileTestCommand } : {}
12504
12624
  };
12505
12625
  }
12506
12626
  /**
12627
+ * Read a single `<key>: value` line from `.substrate/project-profile.yaml`
12628
+ * under `workingDir` (H1.2). Line-based parse — no yaml dependency.
12629
+ */
12630
+ function readProfileKey(workingDir, key) {
12631
+ const profilePath = join$1(workingDir, ".substrate", "project-profile.yaml");
12632
+ if (!existsSync(profilePath)) return void 0;
12633
+ try {
12634
+ const content = readFileSync(profilePath, "utf-8");
12635
+ const match$2 = content.match(new RegExp(`^\\s*${key}:\\s*['"]?(.+?)['"]?\\s*$`, "m"));
12636
+ if (match$2?.[1] && match$2[1].length > 0) return match$2[1];
12637
+ } catch {}
12638
+ return void 0;
12639
+ }
12640
+ /**
12507
12641
  * In-memory store for VerificationSummary results, keyed by story key.
12508
12642
  *
12509
12643
  * Persists for the lifetime of the orchestrator instance. Does NOT write
@@ -13695,6 +13829,7 @@ function createImplementationOrchestrator(deps) {
13695
13829
  const _costChecker = new CostGovernanceChecker();
13696
13830
  let _costWarningEmitted = false;
13697
13831
  let _budgetExhausted = false;
13832
+ let _authFailureHalted = false;
13698
13833
  let _shutdownRequested = false;
13699
13834
  let _inFlightCount = 0;
13700
13835
  let _drainResolve = null;
@@ -14018,6 +14153,21 @@ function createImplementationOrchestrator(deps) {
14018
14153
  stderr: cp.stderr.slice(0, 500)
14019
14154
  }, "escalation checkpoint failed — worktree state remains uncommitted (inspect before any cleanup)");
14020
14155
  }
14156
+ if (escalationWorktree !== void 0 && projectRoot !== void 0) {
14157
+ const leakedFiles = detectWorkOutsideWorktree(escalationWorktree, projectRoot, checkGitDiffFiles);
14158
+ if (leakedFiles.length > 0) {
14159
+ const leakMsg = `PARENT-TREE LEAK: the MAIN checkout at ${projectRoot} carries ${String(leakedFiles.length)} uncommitted change(s) during this story's escalation (e.g. ${leakedFiles.slice(0, 10).join(", ")}). Story output may have landed OUTSIDE the worktree — inspect \`git -C ${projectRoot} status\` BEFORE any cleanup; reconcile-from-disk inspects the branch, not main, and will not see these files.`;
14160
+ payload.issues = [...payload.issues, leakMsg];
14161
+ eventBus.emit("orchestrator:story-warn", {
14162
+ storyKey: payload.storyKey,
14163
+ msg: leakMsg
14164
+ });
14165
+ logger$27.warn({
14166
+ storyKey: payload.storyKey,
14167
+ leakedFileCount: leakedFiles.length
14168
+ }, "escalation parent-tree leak detected");
14169
+ }
14170
+ }
14021
14171
  const diagnosis = generateEscalationDiagnosis(payload.issues, payload.reviewCycles, payload.lastVerdict);
14022
14172
  eventBus.emit("orchestrator:story-escalated", {
14023
14173
  ...payload,
@@ -14494,6 +14644,20 @@ function createImplementationOrchestrator(deps) {
14494
14644
  completedAt: new Date().toISOString()
14495
14645
  });
14496
14646
  await writeStoryMetricsBestEffort(storyKey, "failed", 0);
14647
+ const authSignature = createResult.error === "auth-failure" ? createResult.details ?? "auth-failure" : detectClaudeAuthFailure(errMsg);
14648
+ if (authSignature !== null) {
14649
+ const authDetail = createResult.details ?? errMsg;
14650
+ await emitEscalation({
14651
+ storyKey,
14652
+ lastVerdict: "auth-failure",
14653
+ reviewCycles: 0,
14654
+ issues: [authDetail, CLAUDE_AUTH_FAILURE_HINT],
14655
+ escalationReason: "auth-failure"
14656
+ });
14657
+ await triggerAuthFailureHalt(storyKey, authSignature.slice(0, 200));
14658
+ await persistState();
14659
+ return;
14660
+ }
14497
14661
  const codexHint = detectCodexSandboxBlock(errMsg);
14498
14662
  await emitEscalation({
14499
14663
  storyKey,
@@ -15445,6 +15609,19 @@ function createImplementationOrchestrator(deps) {
15445
15609
  completedAt: new Date().toISOString()
15446
15610
  });
15447
15611
  await writeStoryMetricsBestEffort(storyKey, "failed", 0);
15612
+ const devAuthSignature = detectClaudeAuthFailure(errMsg);
15613
+ if (devAuthSignature !== null) {
15614
+ await emitEscalation({
15615
+ storyKey,
15616
+ lastVerdict: "auth-failure",
15617
+ reviewCycles: 0,
15618
+ issues: [errMsg, CLAUDE_AUTH_FAILURE_HINT],
15619
+ escalationReason: "auth-failure"
15620
+ });
15621
+ await triggerAuthFailureHalt(storyKey, devAuthSignature);
15622
+ await persistState();
15623
+ return;
15624
+ }
15448
15625
  await emitEscalation({
15449
15626
  storyKey,
15450
15627
  lastVerdict: "dev-story-exception",
@@ -17193,6 +17370,64 @@ function createImplementationOrchestrator(deps) {
17193
17370
  * @param result - The ceiling check result
17194
17371
  * @param manifest - The current run manifest data
17195
17372
  */
17373
+ /**
17374
+ * H0.4 (field finding #10): halt the entire run when a dispatch dies on
17375
+ * authentication. Auth failures are environmental, not story-specific —
17376
+ * every subsequent dispatch fails identically, so continuing burns the
17377
+ * whole batch (the field run lost ~25 minutes and two runs to this).
17378
+ * Mirrors handleCeilingExceeded: routes through the Decision Router
17379
+ * (severity fatal → halts under every --halt-on policy), sweeps PENDING
17380
+ * stories to ESCALATED with a named reason, and reuses _budgetExhausted
17381
+ * to stop the dispatch loop.
17382
+ */
17383
+ async function triggerAuthFailureHalt(triggeredStoryKey, matchedSignature) {
17384
+ if (_authFailureHalted) return;
17385
+ _authFailureHalted = true;
17386
+ _budgetExhausted = true;
17387
+ const runId = config.pipelineRunId ?? "unknown";
17388
+ const reason = `agent authentication failure on story ${triggeredStoryKey} (matched: "${matchedSignature}") — all subsequent dispatches would fail identically`;
17389
+ let haltPolicy = "critical";
17390
+ if (runManifest !== null) try {
17391
+ const manifest = await runManifest.read();
17392
+ haltPolicy = manifest.cli_flags.halt_on ?? "critical";
17393
+ } catch {}
17394
+ const routeResult = routeDecision("auth-failure", haltPolicy);
17395
+ eventBus.emit("decision:halt", {
17396
+ runId,
17397
+ decisionType: "auth-failure",
17398
+ severity: routeResult.severity,
17399
+ reason
17400
+ });
17401
+ await runInteractivePrompt({
17402
+ runId,
17403
+ decisionType: "auth-failure",
17404
+ severity: routeResult.severity,
17405
+ summary: reason,
17406
+ defaultAction: routeResult.defaultAction,
17407
+ choices: ["abort-run"],
17408
+ onHaltSkipped: (payload) => {
17409
+ eventBus.emit("decision:halt-skipped-non-interactive", payload);
17410
+ }
17411
+ }).catch((err) => {
17412
+ logger$27.warn({ err }, "interactive prompt failed during auth-failure halt — halting anyway");
17413
+ });
17414
+ for (const [key, state] of _stories) if (state.phase === "PENDING" && key !== triggeredStoryKey) {
17415
+ updateStory(key, {
17416
+ phase: "ESCALATED",
17417
+ error: "auth-failure-halt",
17418
+ completedAt: new Date().toISOString()
17419
+ });
17420
+ if (runManifest !== null && runManifest !== void 0) runManifest.patchStoryState(key, {
17421
+ status: "escalated",
17422
+ escalation_reason: "auth-failure-halt"
17423
+ }).catch(() => {});
17424
+ }
17425
+ logger$27.error({
17426
+ runId,
17427
+ triggeredStoryKey,
17428
+ matchedSignature
17429
+ }, "RUN HALTED: agent authentication failure — fix credentials and re-run");
17430
+ }
17196
17431
  async function handleCeilingExceeded(triggeredStoryKey, remainingInGroup, result, manifest) {
17197
17432
  const haltPolicy = manifest.cli_flags.halt_on ?? "critical";
17198
17433
  const routeResult = routeDecision("cost-ceiling-exhausted", haltPolicy);
@@ -47736,7 +47971,7 @@ async function runFullPipeline(options) {
47736
47971
  */
47737
47972
  async function emitPreDispatchVersionAdvisory(currentVersion) {
47738
47973
  if (process.env["SUBSTRATE_NO_UPDATE_CHECK"] === "1") return;
47739
- const { createVersionManager } = await import("./version-manager-impl-CfzcfFwo.js");
47974
+ const { createVersionManager } = await import("./version-manager-impl-HJMonRJi.js");
47740
47975
  const vm = createVersionManager();
47741
47976
  const result = await vm.checkForUpdates(true);
47742
47977
  const gap = classifyVersionGap(currentVersion, result.latestVersion);
@@ -47833,4 +48068,4 @@ function registerRunCommand(program, version = "0.0.0", projectRoot = process.cw
47833
48068
 
47834
48069
  //#endregion
47835
48070
  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, createGitWorktreeManager, 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 };
47836
- //# sourceMappingURL=run-eaZG_3rN.js.map
48071
+ //# sourceMappingURL=run-DHHryElL.js.map
@@ -1,14 +1,14 @@
1
- import "./health-BXVyiLiL.js";
1
+ import "./health-DTJaiJOd.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
- import "./dist-BWCudDcl.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-eaZG_3rN.js";
6
- import "./manifest-read-IvpNQ6Yy.js";
4
+ import "./dist-DiNK6QOD.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-DHHryElL.js";
6
+ import "./manifest-read-BgZ6mR5R.js";
7
7
  import "./routing-DFxoKHDt.js";
8
8
  import "./work-graph-repository-DZyJv5pV.js";
9
9
  import "./decisions-CzSIEeGP.js";
10
- import "./decision-router-DblHY8se.js";
11
- import "./interactive-prompt-BypNgFHL.js";
10
+ import "./decision-router-BAPpON_C.js";
11
+ import "./interactive-prompt-OMlqP7Lm.js";
12
12
  import "./recovery-engine-BKGBeBnW.js";
13
13
 
14
14
  export { runRunAction };
@@ -21,7 +21,7 @@ type Severity = 'info' | 'warning' | 'critical' | 'fatal';
21
21
  * All known halt-able decision types (AC1).
22
22
  * Epic 73 (Recovery Engine) will extend this union with additional types.
23
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';
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' | 'auth-failure';
25
25
  /**
26
26
  * Maps each known decision type to its severity level.
27
27
  *
@@ -1,3 +1,3 @@
1
- import { DECISION_SEVERITY_MAP, deriveExitCode, routeDecision } from "../../../decision-router-DblHY8se.js";
1
+ import { DECISION_SEVERITY_MAP, deriveExitCode, routeDecision } from "../../../decision-router-BAPpON_C.js";
2
2
 
3
3
  export { DECISION_SEVERITY_MAP, deriveExitCode, routeDecision };
@@ -1,5 +1,5 @@
1
- import "./dist-BWCudDcl.js";
1
+ import "./dist-DiNK6QOD.js";
2
2
  import "./version-manager-impl-qFBiO4Eh.js";
3
- import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-3NTcs1t7.js";
3
+ import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-DB7KzD9c.js";
4
4
 
5
5
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
@@ -1,4 +1,4 @@
1
- import { createVersionManager } from "./dist-BWCudDcl.js";
1
+ import { createVersionManager } from "./dist-DiNK6QOD.js";
2
2
  import { execSync, spawn } from "child_process";
3
3
  import * as readline from "readline";
4
4
 
@@ -123,4 +123,4 @@ function registerUpgradeCommand(program) {
123
123
 
124
124
  //#endregion
125
125
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
126
- //# sourceMappingURL=upgrade-3NTcs1t7.js.map
126
+ //# sourceMappingURL=upgrade-DB7KzD9c.js.map
@@ -1,4 +1,4 @@
1
- import { VersionManagerImpl, createVersionManager } from "./dist-BWCudDcl.js";
1
+ import { VersionManagerImpl, createVersionManager } from "./dist-DiNK6QOD.js";
2
2
  import "./version-manager-impl-qFBiO4Eh.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.139",
3
+ "version": "0.20.142",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,4 +0,0 @@
1
- import { AdapterRegistry } from "./dist-BWCudDcl.js";
2
- import "./adapter-registry-DIcrxjH8.js";
3
-
4
- export { AdapterRegistry };