substrate-ai 0.20.130 → 0.20.131

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-BUDAiEaH.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-C5qUaNup.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-BtNrnj3J.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-Bc0-6VcX.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-jVpLtRRy.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-BUDAiEaH.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-BodUSLte.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-D_HmoOcb.js";
9
- import "../errors-CwQM_6Yk.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-CKmTZKA5.js";
9
+ import "../errors-pJaYVCUJ.js";
10
10
  import "../routing-DFxoKHDt.js";
11
11
  import { WorkGraphRepository } from "../work-graph-repository-DZyJv5pV.js";
12
12
  import "../decisions-CzSIEeGP.js";
13
13
  import "../decision-router-DblHY8se.js";
14
- import "../interactive-prompt-y6udulxR.js";
14
+ import "../interactive-prompt-DKNFNQ7u.js";
15
15
  import "../recovery-engine-BKGBeBnW.js";
16
16
  import "../version-manager-impl-qFBiO4Eh.js";
17
- import { registerUpgradeCommand } from "../upgrade-BlBDCOfS.js";
17
+ import { registerUpgradeCommand } from "../upgrade-BApZ1sPK.js";
18
18
  import { Command } from "commander";
19
19
  import { fileURLToPath } from "url";
20
20
  import { dirname, join, resolve } from "path";
@@ -30,7 +30,7 @@ import { randomUUID } from "node:crypto";
30
30
  import { z } from "zod";
31
31
  import * as fs from "node:fs/promises";
32
32
  import { lstat, readFile as readFile$1, readdir as readdir$1, readlink, realpath } from "node:fs/promises";
33
- import { appendFileSync, chmodSync, cpSync, 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, rmSync as rmSync$1, statSync as statSync$1, unlinkSync as unlinkSync$1, writeFileSync as writeFileSync$1 } from "fs";
33
+ import { chmodSync, cpSync, 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, rmSync as rmSync$1, statSync as statSync$1, unlinkSync as unlinkSync$1, writeFileSync as writeFileSync$1 } from "fs";
34
34
  import { homedir } from "os";
35
35
  import { createRequire } from "node:module";
36
36
  import { fileURLToPath as fileURLToPath$1 } from "node:url";
@@ -516,6 +516,53 @@ const DEFAULT_ROUTING_POLICY = {
516
516
  ]
517
517
  };
518
518
 
519
+ //#endregion
520
+ //#region src/modules/config/derive-routing-policy.ts
521
+ /**
522
+ * Returns a copy of `basePolicy` filtered to the providers enabled in
523
+ * `providers`. Provider preference order is taken from `basePolicy` (the
524
+ * order each provider first appears as default/preferred/fallback), so output
525
+ * is deterministic regardless of the `providers` object key order.
526
+ *
527
+ * Edge case: if no providers are enabled (e.g. the no-selection init fallback
528
+ * where every provider defaults to `enabled: false`), the base policy is
529
+ * returned unchanged — there is nothing to derive from, and an empty policy
530
+ * would be invalid.
531
+ */
532
+ function deriveRoutingPolicy(basePolicy, providers) {
533
+ const enabled = Object.entries(providers).filter(([, cfg]) => cfg?.enabled === true).map(([key]) => key);
534
+ if (enabled.length === 0) return structuredClone(basePolicy);
535
+ const ranking = [];
536
+ const addRank = (p) => {
537
+ if (!ranking.includes(p)) ranking.push(p);
538
+ };
539
+ addRank(basePolicy.default_provider);
540
+ for (const rule of basePolicy.rules) {
541
+ addRank(rule.preferred_provider);
542
+ rule.fallback_providers.forEach(addRank);
543
+ }
544
+ const rankOf = (p) => {
545
+ const i = ranking.indexOf(p);
546
+ return i === -1 ? Number.MAX_SAFE_INTEGER : i;
547
+ };
548
+ const enabledRanked = [...enabled].sort((a, b) => rankOf(a) - rankOf(b));
549
+ const isEnabled = (p) => enabled.includes(p);
550
+ const defaultProvider = isEnabled(basePolicy.default_provider) ? basePolicy.default_provider : enabledRanked[0];
551
+ const rules = basePolicy.rules.map((rule) => {
552
+ const ordered = [rule.preferred_provider, ...rule.fallback_providers].filter(isEnabled);
553
+ for (const p of enabledRanked) if (!ordered.includes(p)) ordered.push(p);
554
+ return {
555
+ task_type: rule.task_type,
556
+ preferred_provider: ordered[0] ?? rule.preferred_provider,
557
+ fallback_providers: ordered.slice(1)
558
+ };
559
+ });
560
+ return {
561
+ default_provider: defaultProvider,
562
+ rules
563
+ };
564
+ }
565
+
519
566
  //#endregion
520
567
  //#region src/modules/config/config-schema.ts
521
568
  const RoutingRuleSchema = z.object({
@@ -562,7 +609,8 @@ const SubstrateConfigSchema = z.object({
562
609
  telemetry: TelemetryConfigSchema.optional(),
563
610
  default_agent: z.string().optional(),
564
611
  supervisor_poll_interval_seconds: z.number().int().positive().optional(),
565
- retry_budget: z.number().int().positive().optional()
612
+ retry_budget: z.number().int().positive().optional(),
613
+ epics_path: z.string().optional()
566
614
  }).strict();
567
615
  const PartialSubstrateConfigSchema = z.object({
568
616
  config_format_version: z.enum(["1"]).optional(),
@@ -580,9 +628,63 @@ const PartialSubstrateConfigSchema = z.object({
580
628
  telemetry: TelemetryConfigSchema.partial().optional(),
581
629
  default_agent: z.string().optional(),
582
630
  supervisor_poll_interval_seconds: z.number().int().positive().optional(),
583
- retry_budget: z.number().int().positive().optional()
631
+ retry_budget: z.number().int().positive().optional(),
632
+ epics_path: z.string().optional()
584
633
  }).strict();
585
634
 
635
+ //#endregion
636
+ //#region src/cli/commands/substrate-gitignore.ts
637
+ /**
638
+ * Compute the `.gitignore` content substrate should write so that everything
639
+ * under `.substrate/` is ignored EXCEPT the operator-shared `config.yaml`,
640
+ * consistent with the AGENTS.md/CLAUDE.md/GEMINI.md guidance.
641
+ *
642
+ * The previous init writer enumerated individual runtime files, which both
643
+ * (a) diverged from the documented negation pattern and (b) left a pre-existing
644
+ * wholesale `.substrate/` dir-ignore in place. A dir-ignore (`.substrate/` or
645
+ * `.substrate`) makes git skip the directory entirely, so `!.substrate/config.yaml`
646
+ * can NEVER re-include the file — the fix MUST convert it to `.substrate/*`
647
+ * (ignore the contents, not the directory) before the negation can work.
648
+ *
649
+ * Pure + exported for unit testing; init.ts is the only caller.
650
+ */
651
+ /** Wholesale `.substrate` dir-ignore forms that block `!config.yaml` re-inclusion. */
652
+ const WHOLESALE_SUBSTRATE_IGNORES = new Set([
653
+ ".substrate",
654
+ ".substrate/",
655
+ "/.substrate",
656
+ "/.substrate/"
657
+ ]);
658
+ const STAR = ".substrate/*";
659
+ const CONFIG_NEGATION = "!.substrate/config.yaml";
660
+ const CODEX_PROMPTS = ".codex/prompts/";
661
+ const CODEX_SKILLS = ".codex/skills/";
662
+ /**
663
+ * Returns the updated `.gitignore` content. Idempotent: running it on its own
664
+ * output returns `changed: false`.
665
+ */
666
+ function computeSubstrateGitignore(existing) {
667
+ const lines = existing.split("\n").map((line) => WHOLESALE_SUBSTRATE_IGNORES.has(line.trim()) ? STAR : line);
668
+ const trimmed = lines.map((l) => l.trim());
669
+ const append = [];
670
+ if (!trimmed.includes(STAR)) append.push(STAR);
671
+ const starIdx = trimmed.lastIndexOf(STAR);
672
+ const negIdx = trimmed.lastIndexOf(CONFIG_NEGATION);
673
+ const negationEffective = negIdx !== -1 && negIdx > starIdx;
674
+ if (!negationEffective) append.push(CONFIG_NEGATION);
675
+ if (!trimmed.includes(CODEX_PROMPTS)) append.push(CODEX_PROMPTS);
676
+ if (!trimmed.includes(CODEX_SKILLS)) append.push(CODEX_SKILLS);
677
+ let content = lines.join("\n");
678
+ if (append.length > 0) {
679
+ const needsNewline = content.length > 0 && !content.endsWith("\n");
680
+ content += (needsNewline ? "\n" : "") + "\n# Substrate state — track only the operator config\n" + append.join("\n") + "\n";
681
+ }
682
+ return {
683
+ content,
684
+ changed: content !== existing
685
+ };
686
+ }
687
+
586
688
  //#endregion
587
689
  //#region src/modules/project-profile/detect.ts
588
690
  function execFileAsync(cmd, args, opts) {
@@ -1856,13 +1958,14 @@ const PROVIDER_KEY_ENV = {
1856
1958
  codex: "OPENAI_API_KEY",
1857
1959
  gemini: "GOOGLE_API_KEY"
1858
1960
  };
1961
+ /** Exported for testing. */
1859
1962
  function buildProviderConfig(adapterId, cliPath, subscriptionRouting) {
1860
1963
  const providerKey = ADAPTER_TO_PROVIDER[adapterId] ?? adapterId;
1861
1964
  const defaults = PROVIDER_DEFAULTS[providerKey];
1862
1965
  if (!defaults) throw new ConfigError(`Unknown provider: ${providerKey}`, { adapterId });
1863
1966
  return {
1864
1967
  ...defaults,
1865
- enabled: true,
1968
+ enabled: subscriptionRouting !== "disabled",
1866
1969
  cli_path: cliPath,
1867
1970
  subscription_routing: subscriptionRouting
1868
1971
  };
@@ -1993,7 +2096,7 @@ async function runInitAction(options) {
1993
2096
  providers: configProviders,
1994
2097
  telemetry: DEFAULT_CONFIG.telemetry
1995
2098
  };
1996
- const routingPolicy = structuredClone(DEFAULT_ROUTING_POLICY);
2099
+ const routingPolicy = deriveRoutingPolicy(DEFAULT_ROUTING_POLICY, configProviders);
1997
2100
  await mkdir(substrateDir, { recursive: true });
1998
2101
  const configHeader = "# Substrate Configuration\n# Generated by `substrate init`\n# Edit this file to customize your AI agent orchestration settings.\n# API keys must be set as environment variables — never stored here.\n#\n# Provider API key env vars:\n" + Object.entries(PROVIDER_KEY_ENV).map(([p, env]) => `# ${p}: ${env}`).join("\n") + "\n\n";
1999
2102
  await writeFile(configPath, configHeader + yaml.dump(config), "utf-8");
@@ -2080,27 +2183,12 @@ async function runInitAction(options) {
2080
2183
  else if (outputFormat !== "json") process.stderr.write("Warning: --install-user-scope requested but HOME/USERPROFILE is not set\n");
2081
2184
  }
2082
2185
  const gitignorePath = join(projectRoot, ".gitignore");
2083
- const runtimeEntries = [
2084
- ".substrate/orchestrator.pid",
2085
- ".substrate/current-run-id",
2086
- ".substrate/scenarios/",
2087
- ".substrate/state/",
2088
- ".substrate/runs/",
2089
- ".substrate/notifications/",
2090
- ".substrate/kv-metrics.json",
2091
- ".substrate/latest-heartbeat-per-story-state.json",
2092
- ".substrate/substrate.db",
2093
- ".substrate/substrate.db-journal",
2094
- ".codex/prompts/",
2095
- ".codex/skills/"
2096
- ];
2097
2186
  try {
2098
2187
  const existing = existsSync$1(gitignorePath) ? readFileSync$1(gitignorePath, "utf-8") : "";
2099
- const missing = runtimeEntries.filter((e) => !existing.includes(e));
2100
- if (missing.length > 0) {
2101
- const block = "\n# Substrate runtime files\n" + missing.join("\n") + "\n";
2102
- appendFileSync(gitignorePath, block);
2103
- logger$18.info({ entries: missing }, "Added substrate runtime files to .gitignore");
2188
+ const { content, changed } = computeSubstrateGitignore(existing);
2189
+ if (changed) {
2190
+ writeFileSync$1(gitignorePath, content);
2191
+ logger$18.info("Updated .gitignore: track only .substrate/config.yaml");
2104
2192
  }
2105
2193
  } catch (err) {
2106
2194
  logger$18.debug({ err }, "Could not update .gitignore (non-fatal)");
@@ -6890,7 +6978,7 @@ async function runStatusAction(options) {
6890
6978
  logger$15.debug({ err }, "Work graph query failed, continuing without work graph data");
6891
6979
  }
6892
6980
  if (run === void 0) {
6893
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-AQjZv_aT.js");
6981
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-Cb4jPuRd.js");
6894
6982
  const substrateDirPath = join(projectRoot, ".substrate");
6895
6983
  const processInfo = inspectProcessTree$1({
6896
6984
  projectRoot,
@@ -7838,7 +7926,7 @@ function defaultSupervisorDeps() {
7838
7926
  if (cached === null) {
7839
7927
  const { AdapterRegistry: AR } = await import(
7840
7928
  /* @vite-ignore */
7841
- "../adapter-registry-B9G8dfn-.js"
7929
+ "../adapter-registry-D70o7g1c.js"
7842
7930
  );
7843
7931
  cached = new AR();
7844
7932
  await cached.discoverAndRegister();
@@ -8405,11 +8493,11 @@ async function runSupervisorAction(options, deps = {}) {
8405
8493
  try {
8406
8494
  const { createExperimenter } = await import(
8407
8495
  /* @vite-ignore */
8408
- "../experimenter-D9yPAcRD.js"
8496
+ "../experimenter-CpnxTV5m.js"
8409
8497
  );
8410
8498
  const { getLatestRun: getLatest } = await import(
8411
8499
  /* @vite-ignore */
8412
- "../decisions-B4A60aRy.js"
8500
+ "../decisions-DAuvU4q9.js"
8413
8501
  );
8414
8502
  const expAdapter = createDatabaseAdapter({
8415
8503
  backend: "auto",
@@ -8419,7 +8507,7 @@ async function runSupervisorAction(options, deps = {}) {
8419
8507
  await initSchema(expAdapter);
8420
8508
  const { runRunAction: runPipeline } = await import(
8421
8509
  /* @vite-ignore */
8422
- "../run-BsPkkWob.js"
8510
+ "../run-NJIX3S8-.js"
8423
8511
  );
8424
8512
  const runStoryFn = async (opts) => {
8425
8513
  const exitCode = await runPipeline({
@@ -8944,7 +9032,7 @@ async function runMetricsAction(options) {
8944
9032
  const routingConfigPath = join(dbDir, "routing.yml");
8945
9033
  let routingConfig = null;
8946
9034
  if (existsSync$1(routingConfigPath)) try {
8947
- const { loadModelRoutingConfig } = await import("../routing-DFwoxEpT.js");
9035
+ const { loadModelRoutingConfig } = await import("../routing-CEd36PVz.js");
8948
9036
  routingConfig = loadModelRoutingConfig(routingConfigPath);
8949
9037
  } catch {}
8950
9038
  if (routingConfig === null) routingConfig = {
@@ -11904,7 +11992,11 @@ var EpicParser = class {
11904
11992
  */
11905
11993
  parseStories(content) {
11906
11994
  const headingMatch = STORY_MAP_HEADING_RE.exec(content);
11907
- if (!headingMatch) throw new Error("No story map section found in document");
11995
+ if (!headingMatch) {
11996
+ const hasPerStoryHeadings = /^#{2,4}\s+Story\s+\d+[-._]\d+/im.test(content);
11997
+ const hint = hasPerStoryHeadings ? "\n\nThis document already has per-story headings (e.g. `### Story 7-7:` / `#### Story 7.7:`), which `substrate run --stories <key>` consumes directly — ingest-epic is not required to dispatch from it." : "";
11998
+ throw new Error("No story map section found in document. ingest-epic expects a heading containing \"Story Map\" followed by sprint blocks (`**Sprint N — Label:**`) and story lines (`- N-M: Title (P0, Medium)`)." + hint);
11999
+ }
11908
12000
  const afterHeading = content.slice(headingMatch.index + headingMatch[0].length);
11909
12001
  const stories = [];
11910
12002
  let currentSprint = 0;
@@ -12733,13 +12825,24 @@ function wallClockMs(state) {
12733
12825
  const end = new Date(state.completed_at).getTime();
12734
12826
  return isNaN(start) || isNaN(end) ? void 0 : end - start;
12735
12827
  }
12828
+ /**
12829
+ * Compute the run verdict. Must honor the manifest's `run_status`, not only the
12830
+ * per-story counts: a run that failed before dispatching any story has empty
12831
+ * scope (total=0), and a counts-only verdict would vacuously report "ALL PASSED"
12832
+ * for a failed run. Exported for testing.
12833
+ */
12834
+ function computeReportVerdict(summary, runStatus) {
12835
+ if (summary.escalated > 0 || summary.failed > 0 || runStatus === "failed") return "NEEDS ATTENTION";
12836
+ if (summary.total === 0) return "NO STORIES RUN";
12837
+ return "ALL PASSED";
12838
+ }
12736
12839
  function renderHuman(output, manifest) {
12737
12840
  const lines = [];
12738
12841
  const { runId, summary, stories, escalations, cost, duration, halts } = output;
12739
12842
  const durationStr = duration.wall_clock_ms != null ? formatDurationMs(duration.wall_clock_ms) : "unknown";
12740
12843
  const costStr = `$${cost.spent.toFixed(4)}`;
12741
12844
  const ceilingStr = cost.ceiling != null ? ` / $${cost.ceiling.toFixed(4)} ceiling (${cost.utilization ?? "?"}) ${cost.overCeiling ? "[OVER CEILING]" : ""}` : "";
12742
- const verdict = summary.escalated > 0 || summary.failed > 0 ? "NEEDS ATTENTION" : "ALL PASSED";
12845
+ const verdict = computeReportVerdict(summary, manifest.run_status);
12743
12846
  lines.push(`══════════════════════════════════════════════════════════`);
12744
12847
  lines.push(` Run: ${runId}`);
12745
12848
  lines.push(` Duration: ${durationStr}`);
@@ -12747,6 +12850,10 @@ function renderHuman(output, manifest) {
12747
12850
  lines.push(` Verdict: ${verdict}`);
12748
12851
  lines.push(`══════════════════════════════════════════════════════════`);
12749
12852
  lines.push("");
12853
+ if (manifest.run_status === "failed" && summary.total === 0) {
12854
+ lines.push(" ⚠ Run failed before any story was dispatched (run_status: failed, empty scope). Check the run manifest and prior output for the cause (e.g. worktree/setup error).");
12855
+ lines.push("");
12856
+ }
12750
12857
  lines.push(`${summary.verified} verified, ${summary.recovered} recovered, ${summary.escalated} escalated, ${summary.failed} failed of ${summary.total} total`);
12751
12858
  lines.push("");
12752
12859
  const COL_WIDTHS = [
@@ -13235,8 +13342,8 @@ async function createProgram() {
13235
13342
  /** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
13236
13343
  function checkForUpdatesInBackground(currentVersion) {
13237
13344
  if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
13238
- import("../upgrade-OE6hMPMW.js").then(async () => {
13239
- const { createVersionManager } = await import("../version-manager-impl-BRVBwdma.js");
13345
+ import("../upgrade-D4Dj7rjQ.js").then(async () => {
13346
+ const { createVersionManager } = await import("../version-manager-impl-Bi05jQ9s.js");
13240
13347
  const vm = createVersionManager();
13241
13348
  const result = await vm.checkForUpdates();
13242
13349
  if (result.updateAvailable) {
@@ -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-Bc0-6VcX.js";
1
+ import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-BUDAiEaH.js";
2
2
  import "./decisions-CzSIEeGP.js";
3
3
 
4
4
  export { getLatestRun };
@@ -9716,6 +9716,29 @@ function stripCodeFences$1(raw) {
9716
9716
  /** Codex default billing modes — subscription via `codex login`, or API key */
9717
9717
  const CODEX_BILLING_MODES = ["subscription", "api"];
9718
9718
  /**
9719
+ * Signatures emitted by `codex exec` when its sandbox/approval policy prevents
9720
+ * a file write or command in non-interactive mode. The first two are the
9721
+ * "exec can't service an approval" failures; the third is what an org policy
9722
+ * prints when it overrides the requested approval/sandbox configuration.
9723
+ */
9724
+ const CODEX_SANDBOX_BLOCK_SIGNATURES = [
9725
+ "approval is not supported in exec mode",
9726
+ "command execution approval is not supported",
9727
+ "file change approval is not supported",
9728
+ "disallowed by requirements"
9729
+ ];
9730
+ /** Human-readable explanation appended to escalations caused by a Codex write-block. */
9731
+ const CODEX_SANDBOX_BLOCK_HINT = "Likely cause: Codex could not write files. Substrate runs `codex exec --sandbox workspace-write --ask-for-approval never` (normal automation mode — not the org-blocked `--dangerously-bypass-approvals-and-sandbox`). If the run log shows \"disallowed by requirements\", your org policy forbids the `never` approval policy, so Codex cannot write non-interactively on this machine — an org constraint, not substrate. Workaround: dispatch with a provider that can write here (e.g. `--agent claude-code`), or have the Codex policy permit non-interactive workspace writes.";
9732
+ /**
9733
+ * Returns the Codex-write-block hint if `output` contains a sandbox/approval
9734
+ * block signature, else null. Pure + exported for diagnostics and testing.
9735
+ */
9736
+ function detectCodexSandboxBlock(output) {
9737
+ if (output === void 0 || output === null || output === "") return null;
9738
+ const lower = output.toLowerCase();
9739
+ return CODEX_SANDBOX_BLOCK_SIGNATURES.some((sig) => lower.includes(sig)) ? CODEX_SANDBOX_BLOCK_HINT : null;
9740
+ }
9741
+ /**
9719
9742
  * Adapter for the OpenAI Codex CLI agent.
9720
9743
  *
9721
9744
  * Codex CLI uses stdin for the prompt and outputs JSON when --json flag is used.
@@ -9761,12 +9784,28 @@ var CodexCLIAdapter = class {
9761
9784
  * Build spawn command for a coding task.
9762
9785
  * Uses: `codex exec` with prompt delivered via stdin.
9763
9786
  *
9787
+ * `--sandbox workspace-write --ask-for-approval never` is required so the
9788
+ * non-interactive `codex exec` can actually write files. Without flags, exec
9789
+ * defaults to a read-only sandbox + an approval policy that escalates to the
9790
+ * user — but exec has no one to approve, so file writes fail with
9791
+ * "file change approval is not supported in exec mode" (→ create-story-no-file).
9792
+ * `never` is exactly what Codex's own docs recommend for non-interactive runs,
9793
+ * and this is normal automation mode — NOT the
9794
+ * `--dangerously-bypass-approvals-and-sandbox` flag (which some org policies
9795
+ * forbid). The planning command stays read-only (it must not write).
9796
+ *
9764
9797
  * Do NOT use --json: it produces a JSONL event stream that prevents
9765
9798
  * extractYamlBlock from finding the structured result block in stdout.
9766
9799
  * Raw text output is required (same rationale as Claude adapter).
9767
9800
  */
9768
9801
  buildCommand(prompt, options) {
9769
- const args = ["exec"];
9802
+ const args = [
9803
+ "exec",
9804
+ "--sandbox",
9805
+ "workspace-write",
9806
+ "--ask-for-approval",
9807
+ "never"
9808
+ ];
9770
9809
  if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
9771
9810
  const envEntries = {};
9772
9811
  if (options.apiKey) envEntries.OPENAI_API_KEY = options.apiKey;
@@ -11279,5 +11318,5 @@ async function callLLM(params) {
11279
11318
  }
11280
11319
 
11281
11320
  //#endregion
11282
- export { ADVISORY_NOTES, AdapterRegistry, AdtError, BudgetConfigSchema, 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, 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 };
11283
- //# sourceMappingURL=dist-Bc0-6VcX.js.map
11321
+ 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 };
11322
+ //# sourceMappingURL=dist-BUDAiEaH.js.map
@@ -1,4 +1,4 @@
1
- import { AdtError } from "./dist-Bc0-6VcX.js";
1
+ import { AdtError } from "./dist-BUDAiEaH.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-CwQM_6Yk.js.map
74
+ //# sourceMappingURL=errors-pJaYVCUJ.js.map
@@ -1,3 +1,3 @@
1
- import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-Bc0-6VcX.js";
1
+ import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-BUDAiEaH.js";
2
2
 
3
3
  export { createExperimenter };
@@ -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-Bc0-6VcX.js";
3
- import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-D_HmoOcb.js";
2
+ import { DoltClient, DoltQueryError, createDatabaseAdapter$1 as createDatabaseAdapter, getLatestRun, getPipelineRunById, initSchema } from "./dist-BUDAiEaH.js";
3
+ import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-CKmTZKA5.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-C5qUaNup.js.map
1003
+ //# sourceMappingURL=health-BtNrnj3J.js.map
@@ -1,7 +1,7 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-C5qUaNup.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-BtNrnj3J.js";
2
2
  import "./logger-KeHncl-f.js";
3
- import "./dist-Bc0-6VcX.js";
4
- import "./manifest-read-D_HmoOcb.js";
3
+ import "./dist-BUDAiEaH.js";
4
+ import "./manifest-read-CKmTZKA5.js";
5
5
  import "./work-graph-repository-DZyJv5pV.js";
6
6
  import "./decisions-CzSIEeGP.js";
7
7
 
@@ -1333,6 +1333,16 @@ declare class CodexCLIAdapter implements WorkerAdapter {
1333
1333
  * Build spawn command for a coding task.
1334
1334
  * Uses: `codex exec` with prompt delivered via stdin.
1335
1335
  *
1336
+ * `--sandbox workspace-write --ask-for-approval never` is required so the
1337
+ * non-interactive `codex exec` can actually write files. Without flags, exec
1338
+ * defaults to a read-only sandbox + an approval policy that escalates to the
1339
+ * user — but exec has no one to approve, so file writes fail with
1340
+ * "file change approval is not supported in exec mode" (→ create-story-no-file).
1341
+ * `never` is exactly what Codex's own docs recommend for non-interactive runs,
1342
+ * and this is normal automation mode — NOT the
1343
+ * `--dangerously-bypass-approvals-and-sandbox` flag (which some org policies
1344
+ * forbid). The planning command stays read-only (it must not write).
1345
+ *
1336
1346
  * Do NOT use --json: it produces a JSONL event stream that prevents
1337
1347
  * extractYamlBlock from finding the structured result block in stdout.
1338
1348
  * Raw text output is required (same rationale as Claude adapter).
@@ -1439,4 +1449,4 @@ interface Recommendation {
1439
1449
 
1440
1450
  //#endregion
1441
1451
  export { AdapterDiscoveryResult, AdapterRegistry as AdapterRegistry$1, AdtError as AdtError$1, ClaudeCodeAdapter as ClaudeCodeAdapter$1, CodexCLIAdapter as CodexCLIAdapter$1, ConfigError as ConfigError$1, ConfigIncompatibleFormatError as ConfigIncompatibleFormatError$1, CoreEvents, DatabaseAdapter, DiscoveryReport, GeminiCLIAdapter as GeminiCLIAdapter$1, Recommendation, TypedEventBus };
1442
- //# sourceMappingURL=index-Ce8BVUmL.d.ts.map
1452
+ //# sourceMappingURL=index-CyQhVXLB.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AdapterDiscoveryResult, AdapterRegistry$1 as AdapterRegistry, AdtError$1 as AdtError, ClaudeCodeAdapter$1 as ClaudeCodeAdapter, CodexCLIAdapter$1 as CodexCLIAdapter, ConfigError$1 as ConfigError, ConfigIncompatibleFormatError$1 as ConfigIncompatibleFormatError, DiscoveryReport, GeminiCLIAdapter$1 as GeminiCLIAdapter, Recommendation, TypedEventBus } from "./index-Ce8BVUmL.js";
1
+ import { AdapterDiscoveryResult, AdapterRegistry$1 as AdapterRegistry, AdtError$1 as AdtError, ClaudeCodeAdapter$1 as ClaudeCodeAdapter, CodexCLIAdapter$1 as CodexCLIAdapter, ConfigError$1 as ConfigError, ConfigIncompatibleFormatError$1 as ConfigIncompatibleFormatError, DiscoveryReport, GeminiCLIAdapter$1 as GeminiCLIAdapter, Recommendation, TypedEventBus } from "./index-CyQhVXLB.js";
2
2
  import pino from "pino";
3
3
  import { z } from "zod";
4
4
  import { Readable, Writable } from "node:stream";
@@ -1014,6 +1014,7 @@ declare const SubstrateConfigSchema: z.ZodObject<{
1014
1014
  default_agent: z.ZodOptional<z.ZodString>;
1015
1015
  supervisor_poll_interval_seconds: z.ZodOptional<z.ZodNumber>;
1016
1016
  retry_budget: z.ZodOptional<z.ZodNumber>;
1017
+ epics_path: z.ZodOptional<z.ZodString>;
1017
1018
  }, z.core.$strict>;
1018
1019
  type SubstrateConfig = z.infer<typeof SubstrateConfigSchema>;
1019
1020
 
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-Bc0-6VcX.js";
3
+ import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-BUDAiEaH.js";
4
4
  import "./adapter-registry-DIcrxjH8.js";
5
- import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-CwQM_6Yk.js";
5
+ import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-pJaYVCUJ.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-D_HmoOcb.js";
2
+ import { readCurrentRunId, resolveMainRepoRoot } from "./manifest-read-CKmTZKA5.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-y6udulxR.js.map
183
+ //# sourceMappingURL=interactive-prompt-DKNFNQ7u.js.map
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { LEARNING_FINDING, createDecision, getDecisionsByCategory } from "./dist-Bc0-6VcX.js";
2
+ import { LEARNING_FINDING, createDecision, getDecisionsByCategory } from "./dist-BUDAiEaH.js";
3
3
  import * as path$1 from "path";
4
4
  import { join } from "path";
5
5
  import { readFile } from "fs/promises";
@@ -5851,4 +5851,4 @@ async function resolveRunManifest(dbRoot, runId) {
5851
5851
 
5852
5852
  //#endregion
5853
5853
  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 };
5854
- //# sourceMappingURL=manifest-read-D_HmoOcb.js.map
5854
+ //# sourceMappingURL=manifest-read-CKmTZKA5.js.map
@@ -1,6 +1,6 @@
1
1
  import "../../logger-KeHncl-f.js";
2
- import "../../dist-Bc0-6VcX.js";
3
- import "../../manifest-read-D_HmoOcb.js";
4
- import { runInteractivePrompt } from "../../interactive-prompt-y6udulxR.js";
2
+ import "../../dist-BUDAiEaH.js";
3
+ import "../../manifest-read-CKmTZKA5.js";
4
+ import { runInteractivePrompt } from "../../interactive-prompt-DKNFNQ7u.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-Bc0-6VcX.js";
1
+ import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-BUDAiEaH.js";
2
2
  import "./routing-DFxoKHDt.js";
3
3
 
4
4
  export { loadModelRoutingConfig };
@@ -1,15 +1,16 @@
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-C5qUaNup.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-BtNrnj3J.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, classifyVersionGap, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, createStderrLogger, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, swallowDebug, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-Bc0-6VcX.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-D_HmoOcb.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-BUDAiEaH.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-CKmTZKA5.js";
6
6
  import { WorkGraphRepository, detectCycles } from "./work-graph-repository-DZyJv5pV.js";
7
7
  import { deriveExitCode, routeDecision } from "./decision-router-DblHY8se.js";
8
- import { runInteractivePrompt } from "./interactive-prompt-y6udulxR.js";
8
+ import { runInteractivePrompt } from "./interactive-prompt-DKNFNQ7u.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";
12
12
  import { EventEmitter } from "node:events";
13
+ import * as yaml$1 from "js-yaml";
13
14
  import yaml from "js-yaml";
14
15
  import * as actualFS from "node:fs";
15
16
  import { accessSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
@@ -135,23 +136,29 @@ async function verifyGitVersion() {
135
136
  }
136
137
  }
137
138
  /**
138
- * Create a git worktree and its associated branch.
139
+ * Decide whether a registered worktree from a prior dispatch can be reclaimed
140
+ * for a fresh re-run. Safe only when there is nothing to lose: no uncommitted
141
+ * changes AND no commits on the branch beyond the base branch. A negative
142
+ * `commitsAhead` means the ahead-count could not be determined → not safe.
139
143
  *
140
- * Uses a single `git worktree add {worktreePath} -b {branchName} {baseBranch}`
141
- * command to create the worktree with a new branch in one step.
142
- *
143
- * @param projectRoot - Absolute path to the git repository root
144
- * @param taskId - Task identifier (used in path derivation)
145
- * @param branchName - Branch name to create (e.g., "substrate/story-abc123")
146
- * @param baseBranch - Branch to base the worktree on (e.g., "main")
147
- * @param copyFiles - v0.20.109: Optional list of files to copy from the
148
- * project root into the new worktree after creation
149
- * (e.g., `[".env", ".env.local"]`). Missing files are
150
- * skipped silently. Useful for gitignored files that
151
- * build tooling needs but `git worktree add` won't carry.
152
- * @returns - Object with the worktreePath
153
- * @throws - Error if git command fails
144
+ * Pure + exported for testing (the git I/O that produces the inputs lives in
145
+ * createWorktree).
154
146
  */
147
+ function decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch) {
148
+ if (hasUncommittedChanges) return {
149
+ safe: false,
150
+ reason: "it has uncommitted changes that are NOT on the branch"
151
+ };
152
+ if (commitsAhead > 0) return {
153
+ safe: false,
154
+ reason: `its branch has ${String(commitsAhead)} commit(s) beyond ${baseBranch}`
155
+ };
156
+ if (commitsAhead < 0) return {
157
+ safe: false,
158
+ reason: "its state could not be verified as safe to discard"
159
+ };
160
+ return { safe: true };
161
+ }
155
162
  async function createWorktree(projectRoot, taskId, branchName, baseBranch, copyFiles = []) {
156
163
  const worktreePath = path$5.join(projectRoot, ".substrate-worktrees", taskId);
157
164
  const worktreeExists = await access$1(worktreePath).then(() => true).catch((err) => {
@@ -170,7 +177,29 @@ async function createWorktree(projectRoot, taskId, branchName, baseBranch, copyF
170
177
  recursive: true,
171
178
  force: true
172
179
  });
173
- else throw new Error(`Worktree at ${worktreePath} is already registered (branch: ${branchName}).\nThis usually means a prior dispatch escalated and the worktree was preserved for inspection.\nIt may contain uncommitted changes that are NOT on the branch — inspect before removing.\n\nTo remove and re-dispatch:\n substrate worktrees --cleanup ${taskId}\n\nTo remove all substrate worktrees:\n substrate worktrees --cleanup`);
180
+ else {
181
+ const statusResult = await spawnGit(["status", "--porcelain"], { cwd: worktreePath });
182
+ const hasUncommittedChanges = statusResult.code === 0 && statusResult.stdout.trim().length > 0;
183
+ const aheadResult = await spawnGit([
184
+ "rev-list",
185
+ "--count",
186
+ `${baseBranch}..${branchName}`
187
+ ], { cwd: projectRoot });
188
+ const commitsAhead = aheadResult.code === 0 ? Number.parseInt(aheadResult.stdout.trim(), 10) || 0 : -1;
189
+ const decision = decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch);
190
+ if (!decision.safe) throw new Error(`Worktree at ${worktreePath} is already registered (branch: ${branchName}) and ${decision.reason}.\nIt was preserved from a prior dispatch for inspection — inspect before removing.\n\nTo remove and re-dispatch:\n substrate worktrees --cleanup ${taskId}\n\nTo remove all substrate worktrees:\n substrate worktrees --cleanup`);
191
+ await spawnGit([
192
+ "worktree",
193
+ "remove",
194
+ "--force",
195
+ worktreePath
196
+ ], { cwd: projectRoot });
197
+ await spawnGit([
198
+ "branch",
199
+ "-D",
200
+ branchName
201
+ ], { cwd: projectRoot });
202
+ }
174
203
  }
175
204
  const addResult = await spawnGit([
176
205
  "worktree",
@@ -6484,6 +6513,64 @@ const ProbeAuthorResultSchema = z.object({
6484
6513
  probes: InlineRuntimeProbeListSchema
6485
6514
  });
6486
6515
 
6516
+ //#endregion
6517
+ //#region src/modules/implementation-orchestrator/epic-paths.ts
6518
+ /** Default planning directories scanned for per-epic/glob files, priority order. */
6519
+ const DEFAULT_PLANNING_DIRS = [
6520
+ "_bmad-output/planning-artifacts",
6521
+ "_bmad-output",
6522
+ "docs/planning"
6523
+ ];
6524
+ /** Default consolidated-epics file candidates (relative), priority order. */
6525
+ const DEFAULT_EPICS_FILES = [
6526
+ "_bmad-output/planning-artifacts/epics.md",
6527
+ "_bmad-output/epics.md",
6528
+ "docs/planning/epics.md"
6529
+ ];
6530
+ function normalizeOverride(projectRoot, override) {
6531
+ if (override === void 0 || override.trim().length === 0) return void 0;
6532
+ const trimmed = override.trim();
6533
+ return isAbsolute(trimmed) ? trimmed : join$1(projectRoot, trimmed);
6534
+ }
6535
+ /**
6536
+ * Ordered absolute candidate paths for the consolidated epics file. An override
6537
+ * (if any) is highest priority, followed by the built-in defaults.
6538
+ */
6539
+ function buildEpicsFileCandidates(projectRoot, epicsPathOverride) {
6540
+ const out = [];
6541
+ const abs = normalizeOverride(projectRoot, epicsPathOverride);
6542
+ if (abs !== void 0) out.push(abs);
6543
+ for (const rel of DEFAULT_EPICS_FILES) out.push(join$1(projectRoot, rel));
6544
+ return out;
6545
+ }
6546
+ /**
6547
+ * Ordered absolute planning directories to scan for per-epic/glob files. When an
6548
+ * override file is given, its parent directory is searched first.
6549
+ */
6550
+ function buildPlanningDirs(projectRoot, epicsPathOverride) {
6551
+ const out = [];
6552
+ const abs = normalizeOverride(projectRoot, epicsPathOverride);
6553
+ if (abs !== void 0) out.push(dirname$1(abs));
6554
+ for (const rel of DEFAULT_PLANNING_DIRS) out.push(join$1(projectRoot, rel));
6555
+ return out;
6556
+ }
6557
+ /**
6558
+ * Best-effort read of `epics_path` from `<projectRoot>/.substrate/config.yaml`.
6559
+ * Returns undefined on any error (missing file, parse failure, wrong type) so
6560
+ * discovery silently falls back to the defaults.
6561
+ */
6562
+ function resolveEpicsPathOverride(projectRoot) {
6563
+ try {
6564
+ const configPath = join$1(projectRoot, ".substrate", "config.yaml");
6565
+ if (!existsSync(configPath)) return void 0;
6566
+ const parsed = yaml$1.load(readFileSync(configPath, "utf-8"));
6567
+ const value = parsed?.epics_path;
6568
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
6569
+ } catch {
6570
+ return void 0;
6571
+ }
6572
+ }
6573
+
6487
6574
  //#endregion
6488
6575
  //#region src/modules/compiled-workflows/token-ceiling.ts
6489
6576
  /**
@@ -7214,7 +7301,8 @@ async function getArchConstraints$3(deps) {
7214
7301
  */
7215
7302
  function readEpicShardFromFile(projectRoot, epicId, storyKey) {
7216
7303
  try {
7217
- const candidates = [join$1(projectRoot, "_bmad-output", "planning-artifacts", "epics.md"), join$1(projectRoot, "_bmad-output", "epics.md")];
7304
+ const override = resolveEpicsPathOverride(projectRoot);
7305
+ const candidates = buildEpicsFileCandidates(projectRoot, override);
7218
7306
  const epicsPath = candidates.find((p) => existsSync(p));
7219
7307
  const epicNum = epicId.replace(/^epic-/i, "");
7220
7308
  if (epicsPath) {
@@ -7232,25 +7320,27 @@ function readEpicShardFromFile(projectRoot, epicId, storyKey) {
7232
7320
  return content.slice(startIdx, endIdx).trim();
7233
7321
  }
7234
7322
  }
7235
- const planningDir = join$1(projectRoot, "_bmad-output", "planning-artifacts");
7236
- if (existsSync(planningDir)) try {
7237
- const entries = readdirSync(planningDir, { encoding: "utf-8" });
7238
- const perEpicPattern = new RegExp(`^epic-${epicNum}-.*\\.md$`);
7239
- const matches = entries.filter((e) => perEpicPattern.test(e)).sort();
7240
- if (matches.length > 0) {
7241
- let chosenIdx = 0;
7242
- if (storyKey !== void 0 && matches.length > 1) for (let i = 0; i < matches.length; i++) {
7243
- const candidateContent = readFileSync(join$1(planningDir, matches[i]), "utf-8");
7244
- if (extractStorySection(candidateContent, storyKey) !== null) {
7245
- chosenIdx = i;
7246
- break;
7323
+ const perEpicPattern = new RegExp(`^epic-${epicNum}-.*\\.md$`);
7324
+ for (const planningDir of buildPlanningDirs(projectRoot, override)) {
7325
+ if (!existsSync(planningDir)) continue;
7326
+ try {
7327
+ const entries = readdirSync(planningDir, { encoding: "utf-8" });
7328
+ const matches = entries.filter((e) => perEpicPattern.test(e)).sort();
7329
+ if (matches.length > 0) {
7330
+ let chosenIdx = 0;
7331
+ if (storyKey !== void 0 && matches.length > 1) for (let i = 0; i < matches.length; i++) {
7332
+ const candidateContent = readFileSync(join$1(planningDir, matches[i]), "utf-8");
7333
+ if (extractStorySection(candidateContent, storyKey) !== null) {
7334
+ chosenIdx = i;
7335
+ break;
7336
+ }
7247
7337
  }
7338
+ const perEpicPath = join$1(planningDir, matches[chosenIdx]);
7339
+ const content = readFileSync(perEpicPath, "utf-8");
7340
+ return content.trim();
7248
7341
  }
7249
- const perEpicPath = join$1(planningDir, matches[chosenIdx]);
7250
- const content = readFileSync(perEpicPath, "utf-8");
7251
- return content.trim();
7252
- }
7253
- } catch {}
7342
+ } catch {}
7343
+ }
7254
7344
  return "";
7255
7345
  } catch (err) {
7256
7346
  logger$21.warn({
@@ -12685,17 +12775,16 @@ function discoverPendingStoryKeys(projectRoot, epicNumber) {
12685
12775
  * For individual epic files, use findEpicFiles() instead.
12686
12776
  */
12687
12777
  function findEpicsFile(projectRoot) {
12688
- const candidates = ["_bmad-output/planning-artifacts/epics.md", "_bmad-output/epics.md"];
12689
- for (const candidate of candidates) {
12690
- const fullPath = join$1(projectRoot, candidate);
12691
- if (existsSync(fullPath)) return fullPath;
12778
+ const override = resolveEpicsPathOverride(projectRoot);
12779
+ for (const fullPath of buildEpicsFileCandidates(projectRoot, override)) if (existsSync(fullPath)) return fullPath;
12780
+ for (const planningDir of buildPlanningDirs(projectRoot, override)) {
12781
+ if (!existsSync(planningDir)) continue;
12782
+ try {
12783
+ const entries = readdirSync(planningDir, { encoding: "utf-8" });
12784
+ const match$2 = entries.filter((e) => /^epics[-.].*\.md$/i.test(e) && !/^epic-\d+/.test(e)).sort();
12785
+ if (match$2.length > 0) return join$1(planningDir, match$2[0]);
12786
+ } catch {}
12692
12787
  }
12693
- const planningDir = join$1(projectRoot, "_bmad-output", "planning-artifacts");
12694
- if (existsSync(planningDir)) try {
12695
- const entries = readdirSync(planningDir, { encoding: "utf-8" });
12696
- const match$2 = entries.filter((e) => /^epics[-.].*\.md$/i.test(e) && !/^epic-\d+/.test(e)).sort();
12697
- if (match$2.length > 0) return join$1(planningDir, match$2[0]);
12698
- } catch {}
12699
12788
  return void 0;
12700
12789
  }
12701
12790
  /**
@@ -12703,14 +12792,15 @@ function findEpicsFile(projectRoot) {
12703
12792
  * Returns paths sorted alphabetically.
12704
12793
  */
12705
12794
  function findEpicFiles(projectRoot) {
12706
- const planningDir = join$1(projectRoot, "_bmad-output", "planning-artifacts");
12707
- if (!existsSync(planningDir)) return [];
12708
- try {
12709
- const entries = readdirSync(planningDir, { encoding: "utf-8" });
12710
- return entries.filter((e) => /^epic-\d+.*\.md$/.test(e)).sort().map((e) => join$1(planningDir, e));
12711
- } catch {
12712
- return [];
12795
+ const out = [];
12796
+ for (const planningDir of buildPlanningDirs(projectRoot, resolveEpicsPathOverride(projectRoot))) {
12797
+ if (!existsSync(planningDir)) continue;
12798
+ try {
12799
+ const entries = readdirSync(planningDir, { encoding: "utf-8" });
12800
+ for (const e of entries.filter((x) => /^epic-\d+.*\.md$/.test(x)).sort()) out.push(join$1(planningDir, e));
12801
+ } catch {}
12713
12802
  }
12803
+ return out;
12714
12804
  }
12715
12805
  /**
12716
12806
  * Story 61-3: find the epic file relevant to a specific story.
@@ -12753,14 +12843,15 @@ function findEpicFileForStory(projectRoot, storyKey) {
12753
12843
  const epicNumMatch = /^(\d+)/.exec(storyKey);
12754
12844
  if (!epicNumMatch) return void 0;
12755
12845
  const epicNum = epicNumMatch[1];
12756
- const planningDir = join$1(projectRoot, "_bmad-output", "planning-artifacts");
12757
- if (!existsSync(planningDir)) return void 0;
12758
- try {
12759
- const entries = readdirSync(planningDir, { encoding: "utf-8" });
12760
- const perEpicPattern = new RegExp(`^epic-${epicNum}-.*\\.md$`);
12761
- const matches = entries.filter((e) => perEpicPattern.test(e)).sort();
12762
- if (matches.length > 0) return join$1(planningDir, matches[0]);
12763
- } catch {}
12846
+ const perEpicPattern = new RegExp(`^epic-${epicNum}-.*\\.md$`);
12847
+ for (const planningDir of buildPlanningDirs(projectRoot, resolveEpicsPathOverride(projectRoot))) {
12848
+ if (!existsSync(planningDir)) continue;
12849
+ try {
12850
+ const entries = readdirSync(planningDir, { encoding: "utf-8" });
12851
+ const matches = entries.filter((e) => perEpicPattern.test(e)).sort();
12852
+ if (matches.length > 0) return join$1(planningDir, matches[0]);
12853
+ } catch {}
12854
+ }
12764
12855
  return void 0;
12765
12856
  }
12766
12857
  /**
@@ -14244,11 +14335,12 @@ function createImplementationOrchestrator(deps) {
14244
14335
  completedAt: new Date().toISOString()
14245
14336
  });
14246
14337
  await writeStoryMetricsBestEffort(storyKey, "failed", 0);
14338
+ const codexHint = detectCodexSandboxBlock(errMsg);
14247
14339
  await emitEscalation({
14248
14340
  storyKey,
14249
14341
  lastVerdict: "create-story-failed",
14250
14342
  reviewCycles: 0,
14251
- issues: [errMsg]
14343
+ issues: codexHint !== null ? [errMsg, codexHint] : [errMsg]
14252
14344
  });
14253
14345
  await persistState();
14254
14346
  return;
@@ -14331,11 +14423,12 @@ function createImplementationOrchestrator(deps) {
14331
14423
  completedAt: new Date().toISOString()
14332
14424
  });
14333
14425
  await writeStoryMetricsBestEffort(storyKey, "failed", 0);
14426
+ const fraudIssues = deps.agentId === "codex" ? [errMsg, CODEX_SANDBOX_BLOCK_HINT] : [errMsg];
14334
14427
  await emitEscalation({
14335
14428
  storyKey,
14336
14429
  lastVerdict: "create-story-fraud-success",
14337
14430
  reviewCycles: 0,
14338
- issues: [errMsg]
14431
+ issues: fraudIssues
14339
14432
  });
14340
14433
  await persistState();
14341
14434
  return;
@@ -21476,6 +21569,47 @@ async function runResearchPhase(deps, params) {
21476
21569
  }
21477
21570
  }
21478
21571
 
21572
+ //#endregion
21573
+ //#region src/modules/config/resolve-default-agent.ts
21574
+ /**
21575
+ * Maps config provider keys to the dispatch agent ids the registry/orchestrator
21576
+ * use. The config keys (`claude`/`codex`/`gemini`) differ from the adapter ids
21577
+ * (`claude-code`/`codex`/`gemini`) only for Claude.
21578
+ */
21579
+ const PROVIDER_TO_AGENT = {
21580
+ claude: "claude-code",
21581
+ codex: "codex",
21582
+ gemini: "gemini"
21583
+ };
21584
+ /**
21585
+ * Precedence when more than one provider is enabled and no `--agent` is given.
21586
+ * Claude first (substrate's reference provider), then Codex, then Gemini.
21587
+ */
21588
+ const PROVIDER_PRECEDENCE = [
21589
+ "claude",
21590
+ "codex",
21591
+ "gemini"
21592
+ ];
21593
+ /**
21594
+ * Returns the dispatch agent id derived from the enabled providers:
21595
+ * - exactly one enabled → that provider's agent id
21596
+ * - several enabled → the highest-precedence enabled provider
21597
+ * - none enabled → `{ error }` (no agent can be chosen)
21598
+ *
21599
+ * Providers absent from the precedence list (future provider keys) sort after
21600
+ * the known ones but remain selectable.
21601
+ */
21602
+ function resolveDefaultAgentId(providers) {
21603
+ const enabled = Object.entries(providers).filter(([, cfg]) => cfg?.enabled === true).map(([key]) => key);
21604
+ if (enabled.length === 0) return { error: "No enabled providers in .substrate/config.yaml. Run `substrate init` and enable at least one CLI, or pass --agent <claude-code|codex|gemini>." };
21605
+ const rankOf = (p) => {
21606
+ const i = PROVIDER_PRECEDENCE.indexOf(p);
21607
+ return i === -1 ? Number.MAX_SAFE_INTEGER : i;
21608
+ };
21609
+ const chosen = [...enabled].sort((a, b) => rankOf(a) - rankOf(b))[0];
21610
+ return { agentId: PROVIDER_TO_AGENT[chosen] ?? chosen };
21611
+ }
21612
+
21479
21613
  //#endregion
21480
21614
  //#region src/modules/telemetry/mesh-reporter.ts
21481
21615
  const logger$2 = createLogger("mesh-reporter");
@@ -44658,10 +44792,10 @@ function createTwinManager(eventBus, options) {
44658
44792
  } catch {
44659
44793
  throw new TwinError("Docker not found — twins require Docker");
44660
44794
  }
44661
- const yaml$1 = generateComposeYaml(twins);
44795
+ const yaml$2 = generateComposeYaml(twins);
44662
44796
  const dir = join$1(tmpdir(), randomUUID());
44663
44797
  mkdirSync(dir, { recursive: true });
44664
- writeFileSync(join$1(dir, "docker-compose.yml"), yaml$1, "utf-8");
44798
+ writeFileSync(join$1(dir, "docker-compose.yml"), yaml$2, "utf-8");
44665
44799
  composeDir = dir;
44666
44800
  startedTwins = twins;
44667
44801
  try {
@@ -45800,7 +45934,7 @@ function resolveProbeAuthorStateIntegrating(cliFlag) {
45800
45934
  * substrate run --non-interactive --halt-on none --events --output-format json
45801
45935
  */
45802
45936
  async function runRunAction(options) {
45803
- 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, verifyAc, noWorktree } = options;
45937
+ 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: explicitAgentId, registry: injectedRegistry, haltOn: haltOnOpt, costCeiling, probeAuthor, probeAuthorStateIntegrating: probeAuthorStateIntegratingFlag, nonInteractive, verifyAc, noWorktree } = options;
45804
45938
  const haltOn = haltOnOpt;
45805
45939
  const VALID_PROBE_AUTHOR_MODES = [
45806
45940
  "enabled",
@@ -45845,7 +45979,8 @@ async function runRunAction(options) {
45845
45979
  else process.stderr.write(`Error: ${errorMsg}\n`);
45846
45980
  return 1;
45847
45981
  }
45848
- const effectiveMaxReviewCycles = resolveMaxReviewCycles(maxReviewCycles, agentId, injectedRegistry);
45982
+ let agentId = explicitAgentId;
45983
+ let agentResolutionError;
45849
45984
  if (startPhase !== void 0 && !VALID_PHASES.includes(startPhase)) {
45850
45985
  const errorMsg = `Invalid phase '${startPhase}'. Valid phases: ${VALID_PHASES.join(", ")}`;
45851
45986
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -45920,6 +46055,13 @@ async function runRunAction(options) {
45920
46055
  const configSystem = createConfigSystem({ projectConfigDir: dbDir });
45921
46056
  await configSystem.load();
45922
46057
  const cfg = configSystem.getConfig();
46058
+ if (agentId == null) {
46059
+ const resolved = resolveDefaultAgentId(cfg.providers);
46060
+ if (resolved.agentId != null) {
46061
+ agentId = resolved.agentId;
46062
+ logger.info({ agentId }, "Resolved default dispatch agent from enabled providers");
46063
+ } else agentResolutionError = resolved.error;
46064
+ }
45923
46065
  tokenCeilings = cfg.token_ceilings;
45924
46066
  if (cfg.dispatch_timeouts) {
45925
46067
  dispatchTimeouts = Object.fromEntries(Object.entries(cfg.dispatch_timeouts).filter(([, v]) => v !== void 0));
@@ -45941,6 +46083,12 @@ async function runRunAction(options) {
45941
46083
  } catch {
45942
46084
  logger.debug("Config loading skipped — using default token ceilings and telemetry settings");
45943
46085
  }
46086
+ if (agentResolutionError !== void 0) {
46087
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, agentResolutionError) + "\n");
46088
+ else process.stderr.write(`Error: ${agentResolutionError}\n`);
46089
+ return 1;
46090
+ }
46091
+ const effectiveMaxReviewCycles = resolveMaxReviewCycles(maxReviewCycles, agentId, injectedRegistry);
45944
46092
  let parsedStoryKeys = [];
45945
46093
  if (storiesArg !== void 0 && storiesArg !== "") {
45946
46094
  parsedStoryKeys = storiesArg.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
@@ -46258,6 +46406,11 @@ async function runRunAction(options) {
46258
46406
  process.stdout.write(sep$2 + "\n");
46259
46407
  for (const s$1 of storiesPreview) for (const p of s$1.phases) process.stdout.write(s$1.storyKey.padEnd(COL.story) + p.phase.padEnd(COL.phase) + p.model.padEnd(COL.model) + String(p.estimatedSymbolCount) + "\n");
46260
46408
  }
46409
+ try {
46410
+ await RunManifest.open(pipelineRun.id, join(dbDir, "runs")).update({ run_status: "completed" });
46411
+ } catch (err) {
46412
+ logger.debug({ err }, "Failed to finalize dry-run manifest (non-fatal)");
46413
+ }
46261
46414
  return 0;
46262
46415
  }
46263
46416
  const dispatcher = createDispatcher({
@@ -47278,7 +47431,7 @@ async function runFullPipeline(options) {
47278
47431
  */
47279
47432
  async function emitPreDispatchVersionAdvisory(currentVersion) {
47280
47433
  if (process.env["SUBSTRATE_NO_UPDATE_CHECK"] === "1") return;
47281
- const { createVersionManager } = await import("./version-manager-impl-BRVBwdma.js");
47434
+ const { createVersionManager } = await import("./version-manager-impl-Bi05jQ9s.js");
47282
47435
  const vm = createVersionManager();
47283
47436
  const result = await vm.checkForUpdates(true);
47284
47437
  const gap = classifyVersionGap(currentVersion, result.latestVersion);
@@ -47375,4 +47528,4 @@ function registerRunCommand(program, version = "0.0.0", projectRoot = process.cw
47375
47528
 
47376
47529
  //#endregion
47377
47530
  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 };
47378
- //# sourceMappingURL=run-jVpLtRRy.js.map
47531
+ //# sourceMappingURL=run-BodUSLte.js.map
@@ -1,14 +1,14 @@
1
- import "./health-C5qUaNup.js";
1
+ import "./health-BtNrnj3J.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
- import "./dist-Bc0-6VcX.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-jVpLtRRy.js";
6
- import "./manifest-read-D_HmoOcb.js";
4
+ import "./dist-BUDAiEaH.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-BodUSLte.js";
6
+ import "./manifest-read-CKmTZKA5.js";
7
7
  import "./routing-DFxoKHDt.js";
8
8
  import "./work-graph-repository-DZyJv5pV.js";
9
9
  import "./decisions-CzSIEeGP.js";
10
10
  import "./decision-router-DblHY8se.js";
11
- import "./interactive-prompt-y6udulxR.js";
11
+ import "./interactive-prompt-DKNFNQ7u.js";
12
12
  import "./recovery-engine-BKGBeBnW.js";
13
13
 
14
14
  export { runRunAction };
@@ -1,4 +1,4 @@
1
- import { CoreEvents, DatabaseAdapter, TypedEventBus } from "../../../index-Ce8BVUmL.js";
1
+ import { CoreEvents, DatabaseAdapter, TypedEventBus } from "../../../index-CyQhVXLB.js";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region packages/sdlc/dist/verification/findings.d.ts
@@ -1,4 +1,4 @@
1
- import { createVersionManager } from "./dist-Bc0-6VcX.js";
1
+ import { createVersionManager } from "./dist-BUDAiEaH.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-BlBDCOfS.js.map
126
+ //# sourceMappingURL=upgrade-BApZ1sPK.js.map
@@ -1,5 +1,5 @@
1
- import "./dist-Bc0-6VcX.js";
1
+ import "./dist-BUDAiEaH.js";
2
2
  import "./version-manager-impl-qFBiO4Eh.js";
3
- import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-BlBDCOfS.js";
3
+ import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-BApZ1sPK.js";
4
4
 
5
5
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
@@ -1,4 +1,4 @@
1
- import { VersionManagerImpl, createVersionManager } from "./dist-Bc0-6VcX.js";
1
+ import { VersionManagerImpl, createVersionManager } from "./dist-BUDAiEaH.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.130",
3
+ "version": "0.20.131",
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-Bc0-6VcX.js";
2
- import "./adapter-registry-DIcrxjH8.js";
3
-
4
- export { AdapterRegistry };