substrate-ai 0.20.130 → 0.20.132

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-DAb4BgAO.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) {
@@ -1738,6 +1840,7 @@ function syncCommandsAsPrompts(commandsDir, promptsDir, ownershipPrefixes, nameP
1738
1840
  *
1739
1841
  * Returns the number of skill directories copied.
1740
1842
  */
1843
+ /** Exported for testing. */
1741
1844
  function syncSkillsToTarget(srcSkillsDir, destSkillsDir, ownershipPrefixes, namePrefix) {
1742
1845
  if (!existsSync$1(srcSkillsDir)) return 0;
1743
1846
  mkdirSync$1(destSkillsDir, { recursive: true });
@@ -1764,12 +1867,20 @@ function syncSkillsToTarget(srcSkillsDir, destSkillsDir, ownershipPrefixes, name
1764
1867
  for (const entry of sourceEntries) {
1765
1868
  const destName = namePrefix && !entry.name.startsWith(namePrefix) ? `${namePrefix}${entry.name}` : entry.name;
1766
1869
  const dest = join(destSkillsDir, destName);
1767
- rmSync$1(dest, {
1768
- recursive: true,
1769
- force: true
1770
- });
1771
- cpSync(join(srcSkillsDir, entry.name), dest, { recursive: true });
1772
- count++;
1870
+ try {
1871
+ rmSync$1(dest, {
1872
+ recursive: true,
1873
+ force: true
1874
+ });
1875
+ cpSync(join(srcSkillsDir, entry.name), dest, { recursive: true });
1876
+ count++;
1877
+ } catch (err) {
1878
+ logger$18.warn({
1879
+ skill: entry.name,
1880
+ dest,
1881
+ err: err instanceof Error ? err.message : String(err)
1882
+ }, "Skipped skill due to copy error; continuing with the rest");
1883
+ }
1773
1884
  }
1774
1885
  return count;
1775
1886
  }
@@ -1856,13 +1967,14 @@ const PROVIDER_KEY_ENV = {
1856
1967
  codex: "OPENAI_API_KEY",
1857
1968
  gemini: "GOOGLE_API_KEY"
1858
1969
  };
1970
+ /** Exported for testing. */
1859
1971
  function buildProviderConfig(adapterId, cliPath, subscriptionRouting) {
1860
1972
  const providerKey = ADAPTER_TO_PROVIDER[adapterId] ?? adapterId;
1861
1973
  const defaults = PROVIDER_DEFAULTS[providerKey];
1862
1974
  if (!defaults) throw new ConfigError(`Unknown provider: ${providerKey}`, { adapterId });
1863
1975
  return {
1864
1976
  ...defaults,
1865
- enabled: true,
1977
+ enabled: subscriptionRouting !== "disabled",
1866
1978
  cli_path: cliPath,
1867
1979
  subscription_routing: subscriptionRouting
1868
1980
  };
@@ -1993,12 +2105,18 @@ async function runInitAction(options) {
1993
2105
  providers: configProviders,
1994
2106
  telemetry: DEFAULT_CONFIG.telemetry
1995
2107
  };
1996
- const routingPolicy = structuredClone(DEFAULT_ROUTING_POLICY);
2108
+ const routingPolicy = deriveRoutingPolicy(DEFAULT_ROUTING_POLICY, configProviders);
1997
2109
  await mkdir(substrateDir, { recursive: true });
1998
2110
  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
- await writeFile(configPath, configHeader + yaml.dump(config), "utf-8");
2111
+ const configExists = existsSync$1(configPath);
2112
+ if (configExists && !force) {
2113
+ if (outputFormat !== "json") process.stdout.write(" .substrate/config.yaml already exists — preserving (use --force to overwrite)\n");
2114
+ } else await writeFile(configPath, configHeader + yaml.dump(config), "utf-8");
2000
2115
  const routingHeader = "# Substrate Routing Policy\n# Defines how tasks are routed to AI providers.\n# Customize rules to match your workflow and available agents.\n\n";
2001
- await writeFile(routingPolicyPath, routingHeader + yaml.dump(routingPolicy), "utf-8");
2116
+ const routingExists = existsSync$1(routingPolicyPath);
2117
+ if (routingExists && !force) {
2118
+ if (outputFormat !== "json") process.stdout.write(" .substrate/routing-policy.yaml already exists — preserving (use --force to overwrite)\n");
2119
+ } else await writeFile(routingPolicyPath, routingHeader + yaml.dump(routingPolicy), "utf-8");
2002
2120
  const projectProfilePath = join(substrateDir, "project-profile.yaml");
2003
2121
  let detectedProfile = null;
2004
2122
  let projectProfileWritten = false;
@@ -2080,27 +2198,12 @@ async function runInitAction(options) {
2080
2198
  else if (outputFormat !== "json") process.stderr.write("Warning: --install-user-scope requested but HOME/USERPROFILE is not set\n");
2081
2199
  }
2082
2200
  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
2201
  try {
2098
2202
  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");
2203
+ const { content, changed } = computeSubstrateGitignore(existing);
2204
+ if (changed) {
2205
+ writeFileSync$1(gitignorePath, content);
2206
+ logger$18.info("Updated .gitignore: track only .substrate/config.yaml");
2104
2207
  }
2105
2208
  } catch (err) {
2106
2209
  logger$18.debug({ err }, "Could not update .gitignore (non-fatal)");
@@ -6890,7 +6993,7 @@ async function runStatusAction(options) {
6890
6993
  logger$15.debug({ err }, "Work graph query failed, continuing without work graph data");
6891
6994
  }
6892
6995
  if (run === void 0) {
6893
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-AQjZv_aT.js");
6996
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-Cb4jPuRd.js");
6894
6997
  const substrateDirPath = join(projectRoot, ".substrate");
6895
6998
  const processInfo = inspectProcessTree$1({
6896
6999
  projectRoot,
@@ -7838,7 +7941,7 @@ function defaultSupervisorDeps() {
7838
7941
  if (cached === null) {
7839
7942
  const { AdapterRegistry: AR } = await import(
7840
7943
  /* @vite-ignore */
7841
- "../adapter-registry-B9G8dfn-.js"
7944
+ "../adapter-registry-D70o7g1c.js"
7842
7945
  );
7843
7946
  cached = new AR();
7844
7947
  await cached.discoverAndRegister();
@@ -8405,11 +8508,11 @@ async function runSupervisorAction(options, deps = {}) {
8405
8508
  try {
8406
8509
  const { createExperimenter } = await import(
8407
8510
  /* @vite-ignore */
8408
- "../experimenter-D9yPAcRD.js"
8511
+ "../experimenter-CpnxTV5m.js"
8409
8512
  );
8410
8513
  const { getLatestRun: getLatest } = await import(
8411
8514
  /* @vite-ignore */
8412
- "../decisions-B4A60aRy.js"
8515
+ "../decisions-DAuvU4q9.js"
8413
8516
  );
8414
8517
  const expAdapter = createDatabaseAdapter({
8415
8518
  backend: "auto",
@@ -8419,7 +8522,7 @@ async function runSupervisorAction(options, deps = {}) {
8419
8522
  await initSchema(expAdapter);
8420
8523
  const { runRunAction: runPipeline } = await import(
8421
8524
  /* @vite-ignore */
8422
- "../run-BsPkkWob.js"
8525
+ "../run-BMx6kY0E.js"
8423
8526
  );
8424
8527
  const runStoryFn = async (opts) => {
8425
8528
  const exitCode = await runPipeline({
@@ -8944,7 +9047,7 @@ async function runMetricsAction(options) {
8944
9047
  const routingConfigPath = join(dbDir, "routing.yml");
8945
9048
  let routingConfig = null;
8946
9049
  if (existsSync$1(routingConfigPath)) try {
8947
- const { loadModelRoutingConfig } = await import("../routing-DFwoxEpT.js");
9050
+ const { loadModelRoutingConfig } = await import("../routing-CEd36PVz.js");
8948
9051
  routingConfig = loadModelRoutingConfig(routingConfigPath);
8949
9052
  } catch {}
8950
9053
  if (routingConfig === null) routingConfig = {
@@ -11904,7 +12007,11 @@ var EpicParser = class {
11904
12007
  */
11905
12008
  parseStories(content) {
11906
12009
  const headingMatch = STORY_MAP_HEADING_RE.exec(content);
11907
- if (!headingMatch) throw new Error("No story map section found in document");
12010
+ if (!headingMatch) {
12011
+ const hasPerStoryHeadings = /^#{2,4}\s+Story\s+\d+[-._]\d+/im.test(content);
12012
+ 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." : "";
12013
+ 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);
12014
+ }
11908
12015
  const afterHeading = content.slice(headingMatch.index + headingMatch[0].length);
11909
12016
  const stories = [];
11910
12017
  let currentSprint = 0;
@@ -12733,13 +12840,24 @@ function wallClockMs(state) {
12733
12840
  const end = new Date(state.completed_at).getTime();
12734
12841
  return isNaN(start) || isNaN(end) ? void 0 : end - start;
12735
12842
  }
12843
+ /**
12844
+ * Compute the run verdict. Must honor the manifest's `run_status`, not only the
12845
+ * per-story counts: a run that failed before dispatching any story has empty
12846
+ * scope (total=0), and a counts-only verdict would vacuously report "ALL PASSED"
12847
+ * for a failed run. Exported for testing.
12848
+ */
12849
+ function computeReportVerdict(summary, runStatus) {
12850
+ if (summary.escalated > 0 || summary.failed > 0 || runStatus === "failed") return "NEEDS ATTENTION";
12851
+ if (summary.total === 0) return "NO STORIES RUN";
12852
+ return "ALL PASSED";
12853
+ }
12736
12854
  function renderHuman(output, manifest) {
12737
12855
  const lines = [];
12738
12856
  const { runId, summary, stories, escalations, cost, duration, halts } = output;
12739
12857
  const durationStr = duration.wall_clock_ms != null ? formatDurationMs(duration.wall_clock_ms) : "unknown";
12740
12858
  const costStr = `$${cost.spent.toFixed(4)}`;
12741
12859
  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";
12860
+ const verdict = computeReportVerdict(summary, manifest.run_status);
12743
12861
  lines.push(`══════════════════════════════════════════════════════════`);
12744
12862
  lines.push(` Run: ${runId}`);
12745
12863
  lines.push(` Duration: ${durationStr}`);
@@ -12747,6 +12865,10 @@ function renderHuman(output, manifest) {
12747
12865
  lines.push(` Verdict: ${verdict}`);
12748
12866
  lines.push(`══════════════════════════════════════════════════════════`);
12749
12867
  lines.push("");
12868
+ if (manifest.run_status === "failed" && summary.total === 0) {
12869
+ 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).");
12870
+ lines.push("");
12871
+ }
12750
12872
  lines.push(`${summary.verified} verified, ${summary.recovered} recovered, ${summary.escalated} escalated, ${summary.failed} failed of ${summary.total} total`);
12751
12873
  lines.push("");
12752
12874
  const COL_WIDTHS = [
@@ -13235,8 +13357,8 @@ async function createProgram() {
13235
13357
  /** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
13236
13358
  function checkForUpdatesInBackground(currentVersion) {
13237
13359
  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");
13360
+ import("../upgrade-D4Dj7rjQ.js").then(async () => {
13361
+ const { createVersionManager } = await import("../version-manager-impl-Bi05jQ9s.js");
13240
13362
  const vm = createVersionManager();
13241
13363
  const result = await vm.checkForUpdates();
13242
13364
  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,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-DAb4BgAO.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,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);
@@ -46117,7 +46265,7 @@ async function runRunAction(options) {
46117
46265
  })
46118
46266
  });
46119
46267
  try {
46120
- const runsDir = join(dbDir, "runs");
46268
+ const runsDir$1 = join(dbDir, "runs");
46121
46269
  const cliFlags = {
46122
46270
  ...parsedStoryKeys.length > 0 ? { stories: parsedStoryKeys } : {},
46123
46271
  halt_on: haltOn ?? "critical",
@@ -46128,7 +46276,7 @@ async function runRunAction(options) {
46128
46276
  ...nonInteractive === true ? { non_interactive: true } : {},
46129
46277
  ...noWorktree === true ? { no_worktree: true } : {}
46130
46278
  };
46131
- const manifest = RunManifest.open(pipelineRun.id, runsDir);
46279
+ const manifest = RunManifest.open(pipelineRun.id, runsDir$1);
46132
46280
  await manifest.patchCLIFlags(cliFlags);
46133
46281
  await manifest.update({ run_status: "running" });
46134
46282
  } catch (err) {
@@ -46258,6 +46406,16 @@ 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
+ }
46414
+ try {
46415
+ await updatePipelineRun(adapter, pipelineRun.id, { status: "completed" });
46416
+ } catch (err) {
46417
+ logger.debug({ err }, "Failed to finalize dry-run Dolt pipeline_runs (non-fatal)");
46418
+ }
46261
46419
  return 0;
46262
46420
  }
46263
46421
  const dispatcher = createDispatcher({
@@ -46646,11 +46804,16 @@ async function runRunAction(options) {
46646
46804
  } catch (metricsErr) {
46647
46805
  logger.warn({ err: metricsErr }, "Failed to write run metrics (best-effort)");
46648
46806
  }
46807
+ const runsDir = join(dbDir, "runs");
46808
+ const terminalStatus = failedKeys.length > 0 || escalatedKeys.length > 0 ? "failed" : "completed";
46649
46809
  try {
46650
- const runsDir = join(dbDir, "runs");
46651
- const terminalStatus = failedKeys.length > 0 || escalatedKeys.length > 0 ? "failed" : "completed";
46652
46810
  await RunManifest.open(pipelineRun.id, runsDir).update({ run_status: terminalStatus });
46653
46811
  } catch {}
46812
+ try {
46813
+ await updatePipelineRun(adapter, pipelineRun.id, { status: terminalStatus });
46814
+ } catch (err) {
46815
+ logger.debug({ err }, "Failed to finalize Dolt pipeline_runs status (non-fatal)");
46816
+ }
46654
46817
  if (progressRenderer !== void 0) progressRenderer.render({
46655
46818
  type: "pipeline:complete",
46656
46819
  ts: new Date().toISOString(),
@@ -46708,8 +46871,8 @@ async function runRunAction(options) {
46708
46871
  reason: "non-interactive: stdin prompt suppressed"
46709
46872
  });
46710
46873
  try {
46711
- const runsDir = join(dbDir, "runs");
46712
- const runManifestForHalt = RunManifest.open(pipelineRun.id, runsDir);
46874
+ const runsDir$1 = join(dbDir, "runs");
46875
+ const runManifestForHalt = RunManifest.open(pipelineRun.id, runsDir$1);
46713
46876
  await runManifestForHalt.update({ cli_flags: {
46714
46877
  halt_on: haltPolicy,
46715
46878
  halt_skipped: true,
@@ -46736,8 +46899,8 @@ async function runRunAction(options) {
46736
46899
  return derivedCode;
46737
46900
  }
46738
46901
  if (verifyAc === true) try {
46739
- const runsDir = join(dbDir, "runs");
46740
- const runManifestForAc = RunManifest.open(pipelineRun.id, runsDir);
46902
+ const runsDir$1 = join(dbDir, "runs");
46903
+ const runManifestForAc = RunManifest.open(pipelineRun.id, runsDir$1);
46741
46904
  const manifestData = await runManifestForAc.read();
46742
46905
  const artifactsDir = join(dbRoot, "_bmad-output", "implementation-artifacts");
46743
46906
  const acResults = {};
@@ -47278,7 +47441,7 @@ async function runFullPipeline(options) {
47278
47441
  */
47279
47442
  async function emitPreDispatchVersionAdvisory(currentVersion) {
47280
47443
  if (process.env["SUBSTRATE_NO_UPDATE_CHECK"] === "1") return;
47281
- const { createVersionManager } = await import("./version-manager-impl-BRVBwdma.js");
47444
+ const { createVersionManager } = await import("./version-manager-impl-Bi05jQ9s.js");
47282
47445
  const vm = createVersionManager();
47283
47446
  const result = await vm.checkForUpdates(true);
47284
47447
  const gap = classifyVersionGap(currentVersion, result.latestVersion);
@@ -47375,4 +47538,4 @@ function registerRunCommand(program, version = "0.0.0", projectRoot = process.cw
47375
47538
 
47376
47539
  //#endregion
47377
47540
  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
47541
+ //# sourceMappingURL=run-DAb4BgAO.js.map
@@ -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.132",
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 };