substrate-ai 0.20.28 → 0.20.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts } from "../health-Dx9hm9x1.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts } from "../health-B0cPyaYJ.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-CqtWS9wF.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-BmRu588B.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-C8IJQ5i5.js";
8
8
  import "../errors-1uLGqnvr.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -3667,7 +3667,7 @@ async function runStatusAction(options) {
3667
3667
  logger$12.debug({ err }, "Work graph query failed, continuing without work graph data");
3668
3668
  }
3669
3669
  if (run === void 0) {
3670
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-C6x1jDlX.js");
3670
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-C6pR6QvM.js");
3671
3671
  const substrateDirPath = join(projectRoot, ".substrate");
3672
3672
  const processInfo = inspectProcessTree$1({
3673
3673
  projectRoot,
@@ -5198,7 +5198,7 @@ async function runSupervisorAction(options, deps = {}) {
5198
5198
  await initSchema(expAdapter);
5199
5199
  const { runRunAction: runPipeline } = await import(
5200
5200
  /* @vite-ignore */
5201
- "../run-CV3EAUZK.js"
5201
+ "../run-Dt5fuOCt.js"
5202
5202
  );
5203
5203
  const runStoryFn = async (opts) => {
5204
5204
  const exitCode = await runPipeline({
@@ -3043,6 +3043,21 @@ var TrivialOutputCheck = class {
3043
3043
  }
3044
3044
  const count = context.outputTokenCount;
3045
3045
  if (count < this.threshold) {
3046
+ const devResult = context.devStoryResult;
3047
+ const recoveredAfterCheckpoint = devResult?.result === "success" && Array.isArray(devResult.files_modified) && devResult.files_modified.length > 0;
3048
+ if (recoveredAfterCheckpoint) {
3049
+ const findings$1 = [{
3050
+ category: "trivial-output",
3051
+ severity: "warn",
3052
+ message: `output token count ${count} is below threshold ${this.threshold} but dev-story signals success with ${devResult.files_modified.length} files modified — likely checkpoint-recovered dispatch (last dispatch was bookkeeping; earlier dispatches did the work). Verdict downgraded to warn so dispatches that legitimately recovered from checkpoint aren't blocked by the trivial-output gate.`
3053
+ }];
3054
+ return {
3055
+ status: "warn",
3056
+ details: renderFindings(findings$1),
3057
+ duration_ms: Date.now() - start,
3058
+ findings: findings$1
3059
+ };
3060
+ }
3046
3061
  const findings = [{
3047
3062
  category: "trivial-output",
3048
3063
  severity: "error",
@@ -3090,12 +3105,36 @@ function addExplicitAcRefs(text, ids) {
3090
3105
  }
3091
3106
  function extractAcceptanceSection(storyContent) {
3092
3107
  const lines = storyContent.split(/\r?\n/);
3093
- const start = lines.findIndex((line) => /^##\s+Acceptance Criteria\s*$/i.test(line.trim()));
3108
+ let mode;
3109
+ const start = lines.findIndex((line) => {
3110
+ const trimmed = line.trim();
3111
+ if (/^##\s+Acceptance Criteria\s*$/i.test(trimmed)) {
3112
+ mode = "heading";
3113
+ return true;
3114
+ }
3115
+ if (/^\*\*Acceptance Criteria\*\*:?/i.test(trimmed)) {
3116
+ mode = "bold";
3117
+ return true;
3118
+ }
3119
+ return false;
3120
+ });
3094
3121
  if (start === -1) return void 0;
3095
3122
  let end = lines.length;
3096
- for (let i = start + 1; i < lines.length; i += 1) if (/^##\s+\S/.test(lines[i] ?? "")) {
3097
- end = i;
3098
- break;
3123
+ const BOLD_PARA_BOUNDARY = /^\*\*[A-Za-z][A-Za-z\s]*\*\*:/;
3124
+ for (let i = start + 1; i < lines.length; i += 1) {
3125
+ const line = lines[i] ?? "";
3126
+ if (/^##\s+\S/.test(line)) {
3127
+ end = i;
3128
+ break;
3129
+ }
3130
+ if (/^###\s+Story\s+/i.test(line)) {
3131
+ end = i;
3132
+ break;
3133
+ }
3134
+ if (mode === "bold" && BOLD_PARA_BOUNDARY.test(line.trim())) {
3135
+ end = i;
3136
+ break;
3137
+ }
3099
3138
  }
3100
3139
  return lines.slice(start + 1, end).join("\n");
3101
3140
  }
@@ -3103,18 +3142,33 @@ function extractAcceptanceSection(storyContent) {
3103
3142
  * Extract normalized AC ids from story markdown.
3104
3143
  *
3105
3144
  * Supports the BMAD default format (`### AC1:`), explicit references such as
3106
- * `AC: #1`, and plain numbered criteria inside the Acceptance Criteria section.
3145
+ * `AC: #1`, plain numbered criteria inside the Acceptance Criteria section,
3146
+ * and (Story 61-4) bullet-format ACs where each bullet line under the
3147
+ * Acceptance Criteria section becomes an implicit AC numbered by position
3148
+ * (first bullet → AC1, second → AC2, etc.). Bullet-format inference fires
3149
+ * only when no numbered or explicit-ref ACs were found, so projects mixing
3150
+ * conventions favor the explicit signal.
3107
3151
  */
3108
3152
  function extractAcceptanceCriteriaIds(storyContent) {
3109
3153
  const ids = new Set();
3110
3154
  const acceptanceSection = extractAcceptanceSection(storyContent);
3111
3155
  const textToScan = acceptanceSection ?? storyContent;
3112
3156
  addExplicitAcRefs(textToScan, ids);
3113
- if (acceptanceSection !== void 0) for (const line of acceptanceSection.split(/\r?\n/)) {
3114
- const match = line.match(NUMBERED_CRITERION);
3115
- if (match?.[1] !== void 0) {
3116
- const id = normalizeAcId(match[1]);
3117
- if (id !== void 0) ids.add(id);
3157
+ if (acceptanceSection !== void 0) {
3158
+ for (const line of acceptanceSection.split(/\r?\n/)) {
3159
+ const match = line.match(NUMBERED_CRITERION);
3160
+ if (match?.[1] !== void 0) {
3161
+ const id = normalizeAcId(match[1]);
3162
+ if (id !== void 0) ids.add(id);
3163
+ }
3164
+ }
3165
+ if (ids.size === 0) {
3166
+ let bulletPosition = 0;
3167
+ for (const line of acceptanceSection.split(/\r?\n/)) if (/^\s*[-*]\s+\S/.test(line) && !NUMBERED_CRITERION.test(line)) {
3168
+ bulletPosition += 1;
3169
+ const id = normalizeAcId(String(bulletPosition));
3170
+ if (id !== void 0) ids.add(id);
3171
+ }
3118
3172
  }
3119
3173
  }
3120
3174
  return sortAcIds(ids);
@@ -6067,4 +6121,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
6067
6121
 
6068
6122
  //#endregion
6069
6123
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, runHealthAction, validateStoryKey };
6070
- //# sourceMappingURL=health-Dx9hm9x1.js.map
6124
+ //# sourceMappingURL=health-B0cPyaYJ.js.map
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-Dx9hm9x1.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-B0cPyaYJ.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-CqtWS9wF.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-Dx9hm9x1.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-B0cPyaYJ.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
4
  import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-CqtWS9wF.js";
@@ -6248,28 +6248,53 @@ async function getArchConstraints$3(deps) {
6248
6248
  }
6249
6249
  }
6250
6250
  /**
6251
- * File-based fallback: read epic shard from _bmad-output/epics.md.
6252
- * Extracts the section for the target epic (## Epic N or ## N.) using regex.
6253
- * Returns the matched section content, or empty string if not found.
6251
+ * File-based fallback: read epic shard from _bmad-output planning files.
6252
+ *
6253
+ * Lookup order:
6254
+ * 1. Consolidated `_bmad-output/planning-artifacts/epics.md` — the
6255
+ * multi-epic-per-file convention used by external projects (strata,
6256
+ * ynab, NextGen Ticketing). Section extracted via heading regex.
6257
+ * 2. Consolidated `_bmad-output/epics.md` — alternate location.
6258
+ * 3. Per-epic file `_bmad-output/planning-artifacts/epic-<epicNum>-*.md`
6259
+ * — the per-epic convention substrate's own planning artifacts use
6260
+ * (Story 61-1). Returns the entire file content as the shard;
6261
+ * downstream `extractStorySection` callers narrow to the per-story
6262
+ * section by `### Story X:` heading match.
6263
+ *
6264
+ * Returns the matched section content, or empty string if no path matches.
6254
6265
  */
6255
6266
  function readEpicShardFromFile(projectRoot, epicId) {
6256
6267
  try {
6257
6268
  const candidates = [join$1(projectRoot, "_bmad-output", "planning-artifacts", "epics.md"), join$1(projectRoot, "_bmad-output", "epics.md")];
6258
6269
  const epicsPath = candidates.find((p) => existsSync(p));
6259
- if (!epicsPath) return "";
6260
- const content = readFileSync(epicsPath, "utf-8");
6261
6270
  const epicNum = epicId.replace(/^epic-/i, "");
6262
- const headingPattern = new RegExp(`^(#{2,4})\\s+(?:Epic\\s+)?${epicNum}[.:\\s]`, "m");
6263
- const headingMatch = headingPattern.exec(content);
6264
- if (!headingMatch) return "";
6265
- const startIdx = headingMatch.index;
6266
- const headingLevel = headingMatch[1].length;
6267
- const hashes = "#".repeat(headingLevel);
6268
- const endPattern = new RegExp(`\\n${hashes}\\s`, "g");
6269
- endPattern.lastIndex = startIdx + headingMatch[0].length;
6270
- const endMatch = endPattern.exec(content);
6271
- const endIdx = endMatch ? endMatch.index : content.length;
6272
- return content.slice(startIdx, endIdx).trim();
6271
+ if (epicsPath) {
6272
+ const content = readFileSync(epicsPath, "utf-8");
6273
+ const headingPattern = new RegExp(`^(#{2,4})\\s+(?:Epic\\s+)?${epicNum}[.:\\s]`, "m");
6274
+ const headingMatch = headingPattern.exec(content);
6275
+ if (headingMatch) {
6276
+ const startIdx = headingMatch.index;
6277
+ const headingLevel = headingMatch[1].length;
6278
+ const hashes = "#".repeat(headingLevel);
6279
+ const endPattern = new RegExp(`\\n${hashes}\\s`, "g");
6280
+ endPattern.lastIndex = startIdx + headingMatch[0].length;
6281
+ const endMatch = endPattern.exec(content);
6282
+ const endIdx = endMatch ? endMatch.index : content.length;
6283
+ return content.slice(startIdx, endIdx).trim();
6284
+ }
6285
+ }
6286
+ const planningDir = join$1(projectRoot, "_bmad-output", "planning-artifacts");
6287
+ if (existsSync(planningDir)) try {
6288
+ const entries = readdirSync(planningDir, { encoding: "utf-8" });
6289
+ const perEpicPattern = new RegExp(`^epic-${epicNum}-.*\\.md$`);
6290
+ const matches = entries.filter((e) => perEpicPattern.test(e)).sort();
6291
+ if (matches.length > 0) {
6292
+ const perEpicPath = join$1(planningDir, matches[0]);
6293
+ const content = readFileSync(perEpicPath, "utf-8");
6294
+ return content.trim();
6295
+ }
6296
+ } catch {}
6297
+ return "";
6273
6298
  } catch (err) {
6274
6299
  logger$18.warn({
6275
6300
  epicId,
@@ -11078,6 +11103,76 @@ function findEpicFiles(projectRoot) {
11078
11103
  }
11079
11104
  }
11080
11105
  /**
11106
+ * Story 61-3: find the epic file relevant to a specific story.
11107
+ *
11108
+ * Sibling to `findEpicsFile` for the verification path
11109
+ * (`assembleVerificationContext` populates `sourceEpicContent` from this).
11110
+ * `findEpicsFile` only checks the consolidated convention (`epics.md`)
11111
+ * → returns undefined for projects using per-epic files (substrate's own
11112
+ * planning artifacts), causing `SourceAcFidelityCheck` to silently skip
11113
+ * with a `source-ac-source-unavailable` warn — exactly what happened on
11114
+ * the 60-12 redispatch (run 4700c6e8, 2026-04-27).
11115
+ *
11116
+ * Story 61-3 v2 (post-round-3): the v1 implementation honored
11117
+ * findEpicsFile's returned path without verifying it contained the
11118
+ * requested story. substrate's own findEpicsFile glob-matches
11119
+ * `epics-and-stories-*.md` files, so for projects with stale consolidated
11120
+ * files (substrate has `epics-and-stories-software-factory.md` for old
11121
+ * epics 40-50) the function returned that path → caller's
11122
+ * extractStorySection found nothing for new stories → sourceEpicContent
11123
+ * stayed undefined. This rev verifies file contains the story (via the
11124
+ * SAME `### Story X:` heading match the caller uses) before returning,
11125
+ * and falls through to per-epic search if not.
11126
+ *
11127
+ * Lookup order:
11128
+ * 1. Consolidated epics.md (existing findEpicsFile path) — return ONLY
11129
+ * if file content contains a `### Story <storyKey>:` heading.
11130
+ * 2. Per-epic file `epic-<epicNum>-*.md` derived from storyKey's first
11131
+ * numeric segment (e.g. storyKey '60-12' → epicNum '60' →
11132
+ * `epic-60-*.md`). Per-epic files contain the entire epic so a
11133
+ * filename match is sufficient (no content verification needed —
11134
+ * mirrors readEpicShardFromFile in create-story.ts).
11135
+ *
11136
+ * Returns the matched path, or undefined if no file contains the story.
11137
+ */
11138
+ function findEpicFileForStory(projectRoot, storyKey) {
11139
+ const consolidated = findEpicsFile(projectRoot);
11140
+ if (consolidated !== void 0) {
11141
+ if (fileContainsStory(consolidated, storyKey)) return consolidated;
11142
+ }
11143
+ const epicNumMatch = /^(\d+)/.exec(storyKey);
11144
+ if (!epicNumMatch) return void 0;
11145
+ const epicNum = epicNumMatch[1];
11146
+ const planningDir = join$1(projectRoot, "_bmad-output", "planning-artifacts");
11147
+ if (!existsSync(planningDir)) return void 0;
11148
+ try {
11149
+ const entries = readdirSync(planningDir, { encoding: "utf-8" });
11150
+ const perEpicPattern = new RegExp(`^epic-${epicNum}-.*\\.md$`);
11151
+ const matches = entries.filter((e) => perEpicPattern.test(e)).sort();
11152
+ if (matches.length > 0) return join$1(planningDir, matches[0]);
11153
+ } catch {}
11154
+ return void 0;
11155
+ }
11156
+ /**
11157
+ * Story 61-3 v2: check whether a file's content contains a story heading
11158
+ * matching the storyKey, with the same separator tolerance as
11159
+ * `extractStorySection` (Story 60-6) so both call sites agree.
11160
+ *
11161
+ * Cheap to call (one synchronous read, one regex test); gracefully
11162
+ * returns false on any I/O error.
11163
+ */
11164
+ function fileContainsStory(filePath, storyKey) {
11165
+ try {
11166
+ const content = readFileSync(filePath, "utf-8");
11167
+ const parts = storyKey.split(/[-._ ]/);
11168
+ const normalized = parts.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[-._ ]");
11169
+ const headingPattern = new RegExp(`^###\\s+Story\\s+${normalized}[:\\s]`, "m");
11170
+ return headingPattern.test(content);
11171
+ } catch {
11172
+ return false;
11173
+ }
11174
+ }
11175
+ /**
11081
11176
  * Collect story keys that already have implementation artifact files.
11082
11177
  * Scans _bmad-output/implementation-artifacts/ for files matching N-M-*.md.
11083
11178
  */
@@ -13831,7 +13926,7 @@ function createImplementationOrchestrator(deps) {
13831
13926
  rawOutput: reviewResult.rawOutput
13832
13927
  } : void 0;
13833
13928
  let sourceEpicContent;
13834
- const epicsPath1 = findEpicsFile(projectRoot ?? process.cwd());
13929
+ const epicsPath1 = findEpicFileForStory(projectRoot ?? process.cwd(), storyKey);
13835
13930
  if (epicsPath1) try {
13836
13931
  const epicFull = readFileSync(epicsPath1, "utf-8");
13837
13932
  const section = extractStorySection(epicFull, storyKey);
@@ -14104,7 +14199,7 @@ function createImplementationOrchestrator(deps) {
14104
14199
  rawOutput: reviewResult.rawOutput
14105
14200
  } : void 0;
14106
14201
  let sourceEpicContent2;
14107
- const epicsPath2 = findEpicsFile(projectRoot ?? process.cwd());
14202
+ const epicsPath2 = findEpicFileForStory(projectRoot ?? process.cwd(), storyKey);
14108
14203
  if (epicsPath2) try {
14109
14204
  const epicFull2 = readFileSync(epicsPath2, "utf-8");
14110
14205
  const section2 = extractStorySection(epicFull2, storyKey);
@@ -44488,4 +44583,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
44488
44583
 
44489
44584
  //#endregion
44490
44585
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict, wireNdjsonEmitter };
44491
- //# sourceMappingURL=run-BmRu588B.js.map
44586
+ //# sourceMappingURL=run-C8IJQ5i5.js.map
@@ -1,8 +1,8 @@
1
- import "./health-Dx9hm9x1.js";
1
+ import "./health-B0cPyaYJ.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-CqtWS9wF.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-BmRu588B.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-C8IJQ5i5.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.28",
3
+ "version": "0.20.31",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",