substrate-ai 0.20.58 → 0.20.59

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_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, parseRuntimeProbes, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics } from "../health-CNqQFdaT.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, parseRuntimeProbes, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics } from "../health-CzYD6ghE.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, InMemoryDatabaseAdapter, 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-W2emvN3F.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-DE5xoB9U.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-BGXvVIwO.js";
8
8
  import "../errors-CKFu8YI9.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -7484,7 +7484,7 @@ async function runStatusAction(options) {
7484
7484
  logger$13.debug({ err }, "Work graph query failed, continuing without work graph data");
7485
7485
  }
7486
7486
  if (run === void 0) {
7487
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-BX84L5Qe.js");
7487
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-CsE9INpp.js");
7488
7488
  const substrateDirPath = join(projectRoot, ".substrate");
7489
7489
  const processInfo = inspectProcessTree$1({
7490
7490
  projectRoot,
@@ -9030,7 +9030,7 @@ async function runSupervisorAction(options, deps = {}) {
9030
9030
  await initSchema(expAdapter);
9031
9031
  const { runRunAction: runPipeline } = await import(
9032
9032
  /* @vite-ignore */
9033
- "../run-ChqBlPYZ.js"
9033
+ "../run-BupqUzci.js"
9034
9034
  );
9035
9035
  const runStoryFn = async (opts) => {
9036
9036
  const exitCode = await runPipeline({
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-CNqQFdaT.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-CzYD6ghE.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-W2emvN3F.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -2941,6 +2941,22 @@ const SEVERITY_PREFIX = {
2941
2941
  */
2942
2942
  const CATEGORY_SHELLOUT_NPX_FALLBACK = "source-ac-shellout-npx-fallback";
2943
2943
  /**
2944
+ * cross-story-concurrent-modification — Story 68-1, Epic 66/67 cross-story-interaction fix.
2945
+ *
2946
+ * Severity: warn (defensive rollout per Story 60-16 pattern). Emitted by
2947
+ * CrossStoryConsistencyCheck (Layer 2) when post-completion analysis shows two
2948
+ * concurrent stories modified the same file AND interface signatures differ
2949
+ * between their commits.
2950
+ *
2951
+ * Motivating incidents: Epic 66 run a832487a (66-1+66-2+66-7 concurrent dispatch)
2952
+ * + Epic 67 run a59e4c96 (67-1+67-2 methodology-pack.test.ts budget constant race).
2953
+ *
2954
+ * NOTE: Promotion to 'error' is deferred pending empirical low-false-positive
2955
+ * validation across multiple substrate-on-substrate dispatch runs. Do NOT change
2956
+ * severity to 'error' until at least 3 consecutive runs with zero false positives.
2957
+ */
2958
+ const CATEGORY_CROSS_STORY_CONCURRENT_MODIFICATION = "cross-story-concurrent-modification";
2959
+ /**
2944
2960
  * Render a list of findings into the multi-line human-readable string that
2945
2961
  * populates VerificationResult.details. One line per finding:
2946
2962
  *
@@ -5253,6 +5269,152 @@ var SourceAcShelloutCheck = class {
5253
5269
  }
5254
5270
  };
5255
5271
 
5272
+ //#endregion
5273
+ //#region packages/sdlc/dist/verification/checks/cross-story-consistency-check.js
5274
+ /**
5275
+ * Matches added/removed export interface or type declarations.
5276
+ * Example: `+export interface Foo {` or `-export type Bar =`
5277
+ */
5278
+ const INTERFACE_CHANGE_PATTERN = /^[+-]\s*(export\s+(?:interface|type)\s+\w+)/;
5279
+ /**
5280
+ * Matches added/removed constant assignments.
5281
+ * Example: `+const BUDGET_LIMIT = 32000` or `-const BUDGET_LIMIT = 30000`
5282
+ * Also matches `export const`, `let`, `var`.
5283
+ */
5284
+ const CONST_CHANGE_PATTERN = /^[+-]\s*(?:export\s+)?(?:const|let|var)\s+\w+\s*=/;
5285
+ /**
5286
+ * Compute the set of file paths that collide between the current story's
5287
+ * modified files and the prior stories' modified files.
5288
+ *
5289
+ * Uses `context._crossStoryConflictingFiles` as a direct override when
5290
+ * supplied (test-hook / runtime-probe path). Otherwise computes the
5291
+ * intersection of `devStoryResult.files_modified` ∩ `priorStoryFiles`.
5292
+ */
5293
+ function computeCollisionPaths(context) {
5294
+ if (context._crossStoryConflictingFiles !== void 0 && context._crossStoryConflictingFiles.length > 0) return context._crossStoryConflictingFiles;
5295
+ const currentFiles = context.devStoryResult?.files_modified ?? [];
5296
+ const priorFiles = context.priorStoryFiles ?? [];
5297
+ if (currentFiles.length === 0 || priorFiles.length === 0) return [];
5298
+ const priorSet = new Set(priorFiles);
5299
+ return currentFiles.filter((f) => priorSet.has(f));
5300
+ }
5301
+ /**
5302
+ * Parse a unified diff text for type signature changes or constant reassignments.
5303
+ *
5304
+ * Returns `true` when the diff contains any added or removed export
5305
+ * interface/type declaration OR any added/removed constant assignment —
5306
+ * indicating a potential interface-level change that concurrent story authors
5307
+ * should review.
5308
+ */
5309
+ function diffContainsInterfaceOrConstChange(diffText) {
5310
+ const lines = diffText.split("\n");
5311
+ for (const line of lines) {
5312
+ if (INTERFACE_CHANGE_PATTERN.test(line)) return true;
5313
+ if (CONST_CHANGE_PATTERN.test(line)) return true;
5314
+ }
5315
+ return false;
5316
+ }
5317
+ /**
5318
+ * Run `git diff --no-renames <commitSha>^...<commitSha> -- <file>` to get
5319
+ * the per-file diff for the story's commit.
5320
+ *
5321
+ * Returns `null` when git is unavailable or the file wasn't part of the commit.
5322
+ */
5323
+ function getDiffForFile(workingDir, commitSha, filePath) {
5324
+ try {
5325
+ return execSync$1(`git diff --no-renames ${commitSha}~1 ${commitSha} -- ${filePath}`, {
5326
+ cwd: workingDir,
5327
+ encoding: "utf-8",
5328
+ stdio: [
5329
+ "ignore",
5330
+ "pipe",
5331
+ "pipe"
5332
+ ]
5333
+ });
5334
+ } catch {
5335
+ return null;
5336
+ }
5337
+ }
5338
+ /**
5339
+ * Get numstat diff for a story commit to confirm a file was modified.
5340
+ * Returns lines like: `5\t3\tsrc/foo.ts`
5341
+ */
5342
+ function getNumstatDiff(workingDir, commitSha) {
5343
+ try {
5344
+ return execSync$1(`git diff --no-renames --numstat ${commitSha}~1 ${commitSha}`, {
5345
+ cwd: workingDir,
5346
+ encoding: "utf-8",
5347
+ stdio: [
5348
+ "ignore",
5349
+ "pipe",
5350
+ "pipe"
5351
+ ]
5352
+ });
5353
+ } catch {
5354
+ return null;
5355
+ }
5356
+ }
5357
+ /**
5358
+ * Standalone function implementing the cross-story consistency check logic.
5359
+ * Exported separately so tests can call it directly without instantiating the class.
5360
+ */
5361
+ async function runCrossStoryConsistencyCheck(context) {
5362
+ const start = Date.now();
5363
+ const findings = [];
5364
+ if ((context.priorStoryFiles === void 0 || context.priorStoryFiles.length === 0) && (context._crossStoryConflictingFiles === void 0 || context._crossStoryConflictingFiles.length === 0)) return {
5365
+ status: "pass",
5366
+ details: "cross-story-consistency: no Tier B context (priorStoryFiles absent) — skipping check",
5367
+ duration_ms: Date.now() - start,
5368
+ findings: []
5369
+ };
5370
+ const collisionPaths = computeCollisionPaths(context);
5371
+ if (collisionPaths.length > 0) findings.push({
5372
+ category: "cross-story-file-collision",
5373
+ severity: "warn",
5374
+ message: `Layer 1 collision: story "${context.storyKey}" shares ${collisionPaths.length} file(s) with concurrent stories: ${collisionPaths.join(", ")}. Recommended action: serialize these stories to avoid race conditions. Motivating incidents: Epic 66 (a832487a), Epic 67 (a59e4c96).`
5375
+ });
5376
+ const shouldRunLayer2 = context.buildCheckPassed !== false && collisionPaths.length > 0;
5377
+ if (shouldRunLayer2) {
5378
+ const numstat = getNumstatDiff(context.workingDir, context.commitSha);
5379
+ const binaryFiles = new Set();
5380
+ if (numstat !== null) for (const line of numstat.split("\n")) {
5381
+ const binMatch = /^-\t-\t(.+)$/.exec(line.trim());
5382
+ if (binMatch?.[1]) binaryFiles.add(binMatch[1]);
5383
+ }
5384
+ for (const filePath of collisionPaths) {
5385
+ if (binaryFiles.has(filePath)) continue;
5386
+ const normalizedPath = filePath.replace(/\\/g, "/");
5387
+ const diffText = getDiffForFile(context.workingDir, context.commitSha, normalizedPath);
5388
+ if (diffText === null) continue;
5389
+ if (diffContainsInterfaceOrConstChange(diffText)) findings.push({
5390
+ category: CATEGORY_CROSS_STORY_CONCURRENT_MODIFICATION,
5391
+ severity: "warn",
5392
+ message: `Layer 2 interface/constant change in shared file "${filePath}": this story's commit modified export signatures or constants that may conflict with concurrent story changes. Manual review recommended. (Epic 66/67 reconciliation pattern: verify working-tree coherence via build + tests before treating pipeline outcome as definitive.)`
5393
+ });
5394
+ }
5395
+ }
5396
+ const status = findings.some((f) => f.severity === "error") ? "fail" : findings.some((f) => f.severity === "warn") ? "warn" : "pass";
5397
+ return {
5398
+ status,
5399
+ details: findings.length > 0 ? renderFindings(findings) : "cross-story-consistency: no file collisions detected between concurrent stories",
5400
+ duration_ms: Date.now() - start,
5401
+ findings
5402
+ };
5403
+ }
5404
+ /**
5405
+ * VerificationCheck class for cross-story consistency analysis.
5406
+ *
5407
+ * name = 'cross-story-consistency'
5408
+ * tier = 'B' (requires cross-story context; skipped for single-story runs)
5409
+ */
5410
+ var CrossStoryConsistencyCheck = class {
5411
+ name = "cross-story-consistency";
5412
+ tier = "B";
5413
+ async run(context) {
5414
+ return runCrossStoryConsistencyCheck(context);
5415
+ }
5416
+ };
5417
+
5256
5418
  //#endregion
5257
5419
  //#region packages/sdlc/dist/verification/verification-pipeline.js
5258
5420
  /**
@@ -5380,7 +5542,8 @@ function createDefaultVerificationPipeline(bus, config) {
5380
5542
  new BuildCheck(),
5381
5543
  new RuntimeProbeCheck(),
5382
5544
  new SourceAcFidelityCheck(),
5383
- new SourceAcShelloutCheck()
5545
+ new SourceAcShelloutCheck(),
5546
+ new CrossStoryConsistencyCheck()
5384
5547
  ];
5385
5548
  return new VerificationPipeline(bus, checks);
5386
5549
  }
@@ -7178,4 +7341,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
7178
7341
 
7179
7342
  //#endregion
7180
7343
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, __commonJS, __require, __toESM, aggregateProbeAuthorMetrics, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, parseRuntimeProbes, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runHealthAction, validateStoryKey };
7181
- //# sourceMappingURL=health-CNqQFdaT.js.map
7344
+ //# sourceMappingURL=health-CzYD6ghE.js.map
package/dist/index.d.ts CHANGED
@@ -2263,6 +2263,16 @@ interface OrchestratorEvents {
2263
2263
  /** Tail of subprocess stdout captured at kill time (~64KB max, UTF-8) */
2264
2264
  stdoutTail?: string;
2265
2265
  };
2266
+ /**
2267
+ * Two or more concurrent stories have overlapping target file paths.
2268
+ * Story 68-1: closes Epic 66 (a832487a) + Epic 67 (a59e4c96) cross-story-interaction class.
2269
+ * Mirror of CoreEvents['dispatch:cross-story-file-collision']; both must stay in sync.
2270
+ */
2271
+ 'dispatch:cross-story-file-collision': {
2272
+ storyKeys: string[];
2273
+ collisionPaths: string[];
2274
+ recommendedAction: 'serialize' | 'warn';
2275
+ };
2266
2276
  /** Watchdog detected no progress for an extended period */
2267
2277
  'orchestrator:stall': {
2268
2278
  runId: string;
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-CNqQFdaT.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, RuntimeProbeListSchema, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-CzYD6ghE.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-W2emvN3F.js";
@@ -15326,6 +15326,132 @@ function createImplementationOrchestrator(deps) {
15326
15326
  await sleep(gcPauseMs);
15327
15327
  }
15328
15328
  }
15329
+ function extractFilePathsFromStoryContent(content) {
15330
+ const paths = new Set();
15331
+ const atPattern = /@\s+([a-zA-Z][^\s`()\n]+\.[a-zA-Z0-9]+)/g;
15332
+ let m;
15333
+ while ((m = atPattern.exec(content)) !== null) {
15334
+ const p = m[1]?.trim();
15335
+ if (p !== void 0 && p.includes("/") && !p.startsWith("http")) paths.add(p.replace(/[.)]+$/, ""));
15336
+ }
15337
+ const backtickPattern = /`([a-zA-Z][^`\n ]+\.[a-zA-Z0-9]{1,6})`/g;
15338
+ while ((m = backtickPattern.exec(content)) !== null) {
15339
+ const p = m[1]?.trim();
15340
+ if (p !== void 0 && p.includes("/") && !p.includes(" ") && !p.startsWith("http")) paths.add(p);
15341
+ }
15342
+ return paths;
15343
+ }
15344
+ /**
15345
+ * Story 68-1: Pre-dispatch cross-story file collision detection.
15346
+ *
15347
+ * Before dispatching a batch with multiple concurrent groups, checks whether
15348
+ * any two stories from different groups share file paths in their story specs.
15349
+ * When collisions are found:
15350
+ * 1. Emits `dispatch:cross-story-file-collision` event for operator visibility.
15351
+ * 2. Merges colliding groups so they execute sequentially.
15352
+ *
15353
+ * Best-effort: if story files are missing, unreadable, or contain no parseable
15354
+ * paths, the original groups are returned unchanged.
15355
+ */
15356
+ function detectAndSerializeConcurrentFileCollisions(batchGroups) {
15357
+ if (batchGroups.length <= 1) return batchGroups;
15358
+ const root = projectRoot ?? process.cwd();
15359
+ const artifactsDir = join$1(root, "_bmad-output", "implementation-artifacts");
15360
+ if (!existsSync(artifactsDir)) return batchGroups;
15361
+ const storyFileMap = new Map();
15362
+ const allStoryKeys = batchGroups.flat();
15363
+ let artifactFiles;
15364
+ try {
15365
+ artifactFiles = readdirSync(artifactsDir);
15366
+ } catch {
15367
+ return batchGroups;
15368
+ }
15369
+ const STALE_SUFFIX = /\.stale-\d+\.md$/;
15370
+ for (const storyKey of allStoryKeys) try {
15371
+ const match$2 = artifactFiles.find((f$1) => f$1.startsWith(`${storyKey}-`) && f$1.endsWith(".md") && !STALE_SUFFIX.test(f$1));
15372
+ if (!match$2) continue;
15373
+ const content = readFileSync(join$1(artifactsDir, match$2), "utf-8");
15374
+ const paths = extractFilePathsFromStoryContent(content);
15375
+ if (paths.size > 0) storyFileMap.set(storyKey, paths);
15376
+ } catch {}
15377
+ if (storyFileMap.size === 0) return batchGroups;
15378
+ const parent = Array.from({ length: batchGroups.length }, (_, i) => i);
15379
+ function find(x) {
15380
+ while (parent[x] !== x) {
15381
+ parent[x] = parent[parent[x]];
15382
+ x = parent[x];
15383
+ }
15384
+ return x;
15385
+ }
15386
+ function union(x, y) {
15387
+ const rx = find(x);
15388
+ const ry = find(y);
15389
+ if (rx !== ry) parent[rx] = ry;
15390
+ }
15391
+ let collisionCount = 0;
15392
+ const collisionEvents = [];
15393
+ for (let i = 0; i < batchGroups.length; i++) for (let j$1 = i + 1; j$1 < batchGroups.length; j$1++) {
15394
+ if (find(i) === find(j$1)) continue;
15395
+ const groupA = batchGroups[i] ?? [];
15396
+ const groupB = batchGroups[j$1] ?? [];
15397
+ const filesA = new Set();
15398
+ const filesB = new Set();
15399
+ const keysA = [];
15400
+ const keysB = [];
15401
+ for (const key of groupA) {
15402
+ const files = storyFileMap.get(key);
15403
+ if (files !== void 0) {
15404
+ keysA.push(key);
15405
+ for (const f$1 of files) filesA.add(f$1);
15406
+ }
15407
+ }
15408
+ for (const key of groupB) {
15409
+ const files = storyFileMap.get(key);
15410
+ if (files !== void 0) {
15411
+ keysB.push(key);
15412
+ for (const f$1 of files) filesB.add(f$1);
15413
+ }
15414
+ }
15415
+ if (keysA.length === 0 || keysB.length === 0) continue;
15416
+ const collisionPaths = [...filesA].filter((f$1) => filesB.has(f$1));
15417
+ if (collisionPaths.length > 0) {
15418
+ union(i, j$1);
15419
+ collisionCount++;
15420
+ collisionEvents.push({
15421
+ storyKeys: [...keysA, ...keysB],
15422
+ collisionPaths
15423
+ });
15424
+ }
15425
+ }
15426
+ if (collisionCount === 0) return batchGroups;
15427
+ for (const evt of collisionEvents) {
15428
+ try {
15429
+ eventBus.emit("dispatch:cross-story-file-collision", {
15430
+ storyKeys: evt.storyKeys,
15431
+ collisionPaths: evt.collisionPaths,
15432
+ recommendedAction: "serialize"
15433
+ });
15434
+ } catch {}
15435
+ logger$26.info({
15436
+ storyKeys: evt.storyKeys,
15437
+ collisionPaths: evt.collisionPaths
15438
+ }, "Cross-story file collision detected — serializing affected groups to prevent race conditions");
15439
+ }
15440
+ const mergedGroupMap = new Map();
15441
+ for (let i = 0; i < batchGroups.length; i++) {
15442
+ const group = batchGroups[i] ?? [];
15443
+ const root2 = find(i);
15444
+ const existing = mergedGroupMap.get(root2) ?? [];
15445
+ mergedGroupMap.set(root2, [...existing, ...group]);
15446
+ }
15447
+ const result = [...mergedGroupMap.values()];
15448
+ logger$26.info({
15449
+ originalGroupCount: batchGroups.length,
15450
+ mergedGroupCount: result.length,
15451
+ collisionCount
15452
+ }, "Story groups re-arranged after cross-story collision detection");
15453
+ return result;
15454
+ }
15329
15455
  /**
15330
15456
  * Promise pool: run up to maxConcurrency groups at a time.
15331
15457
  *
@@ -15617,7 +15743,10 @@ function createImplementationOrchestrator(deps) {
15617
15743
  logger$26.warn({ err: snapErr }, "Failed to capture package snapshot — continuing without protection");
15618
15744
  }
15619
15745
  try {
15620
- for (const batchGroups of batches) await runWithConcurrency(batchGroups, config.maxConcurrency);
15746
+ for (const rawBatchGroups of batches) {
15747
+ const batchGroups = detectAndSerializeConcurrentFileCollisions(rawBatchGroups);
15748
+ await runWithConcurrency(batchGroups, config.maxConcurrency);
15749
+ }
15621
15750
  } catch (err) {
15622
15751
  stopHeartbeat();
15623
15752
  _state = "FAILED";
@@ -45472,4 +45601,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
45472
45601
 
45473
45602
  //#endregion
45474
45603
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR$1 as GLOBSTAR, GitClient, GrammarLoader, Minimatch$1 as Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape$1 as escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runRunAction, runSolutioningPhase, unescape$1 as unescape, validateStopAfterFromConflict, wireNdjsonEmitter };
45475
- //# sourceMappingURL=run-DE5xoB9U.js.map
45604
+ //# sourceMappingURL=run-BGXvVIwO.js.map
@@ -1,8 +1,8 @@
1
- import "./health-CNqQFdaT.js";
1
+ import "./health-CzYD6ghE.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-W2emvN3F.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-DE5xoB9U.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-BGXvVIwO.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.58",
3
+ "version": "0.20.59",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",