substrate-ai 0.20.65 → 0.20.66

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,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion } from "../health-DC3y-sR6.js";
2
+ import { FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion } from "../health-D6MKxV46.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry$1 as AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError$1 as 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-K_RRWnBX.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { RunManifest, SupervisorLock, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, parseRuntimeProbes, readCurrentRunId, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics } from "../manifest-read-DDkXC3L_.js";
8
- 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-DzB4rgkj.js";
7
+ 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-g4zt3DXJ.js";
8
+ 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-7h2-DIjt.js";
9
9
  import "../errors-pSiZbn6e.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-C7wpE4z4.js";
14
+ import "../interactive-prompt-kzkG24Rn.js";
15
15
  import "../recovery-engine-BKGBeBnW.js";
16
16
  import "../version-manager-impl-qFBiO4Eh.js";
17
17
  import { registerUpgradeCommand } from "../upgrade-MP9XzrI6.js";
@@ -7491,7 +7491,7 @@ async function runStatusAction(options) {
7491
7491
  logger$15.debug({ err }, "Work graph query failed, continuing without work graph data");
7492
7492
  }
7493
7493
  if (run === void 0) {
7494
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-qhtWYh49.js");
7494
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-rGXaJvYJ.js");
7495
7495
  const substrateDirPath = join(projectRoot, ".substrate");
7496
7496
  const processInfo = inspectProcessTree$1({
7497
7497
  projectRoot,
@@ -9037,7 +9037,7 @@ async function runSupervisorAction(options, deps = {}) {
9037
9037
  await initSchema(expAdapter);
9038
9038
  const { runRunAction: runPipeline } = await import(
9039
9039
  /* @vite-ignore */
9040
- "../run-DX95j4_D.js"
9040
+ "../run-DB9P6m_P.js"
9041
9041
  );
9042
9042
  const runStoryFn = async (opts) => {
9043
9043
  const exitCode = await runPipeline({
@@ -13787,6 +13787,44 @@ function renderHuman(output, manifest) {
13787
13787
  }
13788
13788
  lines.push("");
13789
13789
  }
13790
+ if (output.ac_traceability && Object.keys(output.ac_traceability).length > 0) {
13791
+ lines.push("──── AC Traceability (approximate) ────");
13792
+ for (const [storyKey, traceability] of Object.entries(output.ac_traceability)) {
13793
+ lines.push("");
13794
+ lines.push(` Story: ${storyKey}`);
13795
+ if (traceability.matrix.length === 0) {
13796
+ lines.push(" (no acceptance criteria found)");
13797
+ continue;
13798
+ }
13799
+ const AC_COL = 60;
13800
+ const MATCH_COL = 9;
13801
+ const TEST_COL = 50;
13802
+ lines.push(" " + formatRow([
13803
+ "AC",
13804
+ "Matched",
13805
+ "Test Name"
13806
+ ], [
13807
+ AC_COL,
13808
+ MATCH_COL,
13809
+ TEST_COL
13810
+ ]));
13811
+ lines.push(" " + [
13812
+ AC_COL,
13813
+ MATCH_COL,
13814
+ TEST_COL
13815
+ ].map((w) => "-".repeat(w)).join("-+-"));
13816
+ for (const row of traceability.matrix) lines.push(" " + formatRow([
13817
+ row.acText,
13818
+ row.matched ? "✓" : "✗",
13819
+ row.testName ?? "—"
13820
+ ], [
13821
+ AC_COL,
13822
+ MATCH_COL,
13823
+ TEST_COL
13824
+ ]));
13825
+ }
13826
+ lines.push("");
13827
+ }
13790
13828
  return lines.join("\n");
13791
13829
  }
13792
13830
  function renderJson(output) {
@@ -13926,7 +13964,7 @@ async function resolveLatestRunId(dbRoot) {
13926
13964
  }
13927
13965
  }
13928
13966
  async function runReportAction(options) {
13929
- const { run: runArg, outputFormat, projectRoot, _dbRoot } = options;
13967
+ const { run: runArg, outputFormat, projectRoot, _dbRoot, verifyAc } = options;
13930
13968
  const effectiveProjectRoot = process.env["SUBSTRATE_PROJECT_ROOT"] ?? projectRoot;
13931
13969
  const dbRoot = _dbRoot ?? await resolveMainRepoRoot(effectiveProjectRoot);
13932
13970
  const runsDir = join(dbRoot, ".substrate", "runs");
@@ -14005,6 +14043,45 @@ async function runReportAction(options) {
14005
14043
  return [];
14006
14044
  });
14007
14045
  const output = assembleReport(resolvedRunId, manifest, halts);
14046
+ if (verifyAc === true) {
14047
+ const artifactsDir = join(dbRoot, "_bmad-output", "implementation-artifacts");
14048
+ const acTraceability = {};
14049
+ let acPerStoryState = manifest.per_story_state;
14050
+ try {
14051
+ const rmForAc = RunManifest.open(resolvedRunId, runsDir);
14052
+ const rmData = await rmForAc.read();
14053
+ acPerStoryState = rmData.per_story_state;
14054
+ } catch {
14055
+ logger$1.debug({ runId: resolvedRunId }, "RunManifest.read() for --verify-ac failed — using raw manifest fallback");
14056
+ }
14057
+ const storyEntries = Object.entries(acPerStoryState);
14058
+ for (const [storyKey, state] of storyEntries) {
14059
+ const filesModified = state.dev_story_signals?.files_modified ?? [];
14060
+ let storyContent = "";
14061
+ try {
14062
+ const artifactFiles = await readdir(artifactsDir).catch(() => []);
14063
+ const matchingFile = artifactFiles.find((f) => (f.startsWith(`${storyKey}-`) || f === `${storyKey}.md`) && f.endsWith(".md"));
14064
+ if (matchingFile) storyContent = await readFile(join(artifactsDir, matchingFile), "utf-8");
14065
+ } catch {}
14066
+ try {
14067
+ const result = await runAcTraceabilityCheck({
14068
+ storyKey,
14069
+ storyContent,
14070
+ filesModified
14071
+ });
14072
+ acTraceability[storyKey] = {
14073
+ matrix: result.matrix,
14074
+ confidence: result.confidence
14075
+ };
14076
+ } catch (err) {
14077
+ logger$1.debug({
14078
+ err,
14079
+ storyKey
14080
+ }, "ac traceability check failed for story (skipping)");
14081
+ }
14082
+ }
14083
+ output.ac_traceability = acTraceability;
14084
+ }
14008
14085
  if (outputFormat === "json") process.stdout.write(renderJson(output) + "\n");
14009
14086
  else process.stdout.write(renderHuman(output, manifest));
14010
14087
  return 0;
@@ -14017,13 +14094,14 @@ async function runReportAction(options) {
14017
14094
  * uniformity even though this command does not use it.
14018
14095
  */
14019
14096
  function registerReportCommand(program, _version = "0.0.0", projectRoot = process.cwd(), _registry) {
14020
- program.command("report").description("Read run manifest and produce a structured completion report").option("--run <id|latest>", "Run ID to report on, or \"latest\" (default: current-run-id file, then Dolt getLatestRun fallback)").option("--output-format <format>", "Output format: human (default) or json", "human").option("--basePath <path>", "Base path override for .substrate directory (used by probes and tests)").action(async (opts) => {
14097
+ program.command("report").description("Read run manifest and produce a structured completion report").option("--run <id|latest>", "Run ID to report on, or \"latest\" (default: current-run-id file, then Dolt getLatestRun fallback)").option("--output-format <format>", "Output format: human (default) or json", "human").option("--basePath <path>", "Base path override for .substrate directory (used by probes and tests)").option("--verify-ac", "Run AC-to-test traceability heuristic for each story and append results to report (Story 74-1)").action(async (opts) => {
14021
14098
  const outputFormat = opts.outputFormat === "json" ? "json" : "human";
14022
14099
  const exitCode = await runReportAction({
14023
14100
  run: opts.run,
14024
14101
  outputFormat,
14025
14102
  projectRoot,
14026
- _dbRoot: opts.basePath
14103
+ _dbRoot: opts.basePath,
14104
+ verifyAc: opts.verifyAc
14027
14105
  });
14028
14106
  process.exitCode = exitCode;
14029
14107
  });
@@ -1,6 +1,6 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
2
  import { DoltClient, DoltQueryError, createDatabaseAdapter$1 as createDatabaseAdapter, getLatestRun, getPipelineRunById, initSchema } from "./dist-K_RRWnBX.js";
3
- import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-DDkXC3L_.js";
3
+ import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-g4zt3DXJ.js";
4
4
  import { createRequire } from "module";
5
5
  import { dirname, join } from "path";
6
6
  import { existsSync, readFileSync } from "node:fs";
@@ -1712,4 +1712,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
1712
1712
 
1713
1713
  //#endregion
1714
1714
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, runHealthAction, validateStoryKey };
1715
- //# sourceMappingURL=health-DC3y-sR6.js.map
1715
+ //# sourceMappingURL=health-D6MKxV46.js.map
@@ -1,7 +1,7 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-DC3y-sR6.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-D6MKxV46.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-K_RRWnBX.js";
4
- import "./manifest-read-DDkXC3L_.js";
4
+ import "./manifest-read-g4zt3DXJ.js";
5
5
  import "./work-graph-repository-DZyJv5pV.js";
6
6
  import "./decisions-CzSIEeGP.js";
7
7
 
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { readCurrentRunId, resolveMainRepoRoot } from "./manifest-read-DDkXC3L_.js";
2
+ import { readCurrentRunId, resolveMainRepoRoot } from "./manifest-read-g4zt3DXJ.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-C7wpE4z4.js.map
183
+ //# sourceMappingURL=interactive-prompt-kzkG24Rn.js.map
@@ -310,7 +310,11 @@ const RootCauseCategorySchema = z.enum([
310
310
  "test-failure",
311
311
  "resource-exhaustion",
312
312
  "infrastructure",
313
- "unclassified"
313
+ "unclassified",
314
+ "ac-missing-evidence",
315
+ "runtime-probe-fail",
316
+ "source-ac-drift",
317
+ "cross-story-concurrent-modification"
314
318
  ]);
315
319
  const FindingSchema = z.object({
316
320
  id: z.string().uuid(),
@@ -350,7 +354,11 @@ const CATEGORY_DESCRIPTIONS = {
350
354
  "test-failure": "Tests failed after story implementation",
351
355
  "resource-exhaustion": "Story produced fewer than 100 output tokens (resource exhaustion suspected)",
352
356
  "infrastructure": "System-level infrastructure error (OOM, disk, permissions, or SIGKILL)",
353
- "unclassified": "No error text available"
357
+ "unclassified": "No error text available",
358
+ "ac-missing-evidence": "Acceptance criteria evidence check found missing or insufficient AC coverage",
359
+ "runtime-probe-fail": "Runtime probe verification failed (probe execution or assertion failure)",
360
+ "source-ac-drift": "Source AC fidelity check detected drift between story ACs and source epic",
361
+ "cross-story-concurrent-modification": "Cross-story consistency check detected concurrent file modification conflict"
354
362
  };
355
363
  /**
356
364
  * Construct a validated Finding from a StoryFailureContext and classification result.
@@ -4060,6 +4068,11 @@ var VerificationPipeline = class {
4060
4068
  * 6. SourceAcFidelityCheck — Story 58-2: cross-references rendered story artifact
4061
4069
  * against the source epic's hard clauses (MUST/SHALL/paths)
4062
4070
  *
4071
+ * On-demand check (NOT in default pipeline):
4072
+ * - AcTraceabilityCheck — Story 74-1: heuristic AC-to-test traceability.
4073
+ * Invoked only via `--verify-ac` on `substrate report` and `substrate run`.
4074
+ * Excluded from default Tier A/B to avoid paying the cost on every run.
4075
+ *
4063
4076
  * @param bus Typed event bus for verification events.
4064
4077
  * @param config Optional config (used to forward threshold to TrivialOutputCheck).
4065
4078
  */
@@ -4298,6 +4311,151 @@ async function runStaleVerificationRecovery(input) {
4298
4311
  };
4299
4312
  }
4300
4313
 
4314
+ //#endregion
4315
+ //#region packages/sdlc/dist/verification/checks/ac-traceability-check.js
4316
+ /**
4317
+ * Tokenize a string to a deduplicated set of lowercase alphanumeric tokens.
4318
+ * Non-alphanumeric characters are replaced with spaces before splitting.
4319
+ */
4320
+ function tokenize(s) {
4321
+ const words = s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter(Boolean);
4322
+ return new Set(words);
4323
+ }
4324
+ /**
4325
+ * Compute Jaccard-based word overlap between two strings.
4326
+ * Returns 0 when either string has no tokens.
4327
+ */
4328
+ function wordOverlap(a, b) {
4329
+ const setA = tokenize(a);
4330
+ const setB = tokenize(b);
4331
+ if (setA.size === 0 || setB.size === 0) return 0;
4332
+ let intersection = 0;
4333
+ for (const w of setA) if (setB.has(w)) intersection++;
4334
+ const union = new Set([...setA, ...setB]).size;
4335
+ return intersection / union;
4336
+ }
4337
+ /**
4338
+ * Extract numbered AC items from the `## Acceptance Criteria` section of a
4339
+ * story spec. Handles both plain `## Acceptance Criteria` headings and the
4340
+ * bold-wrapped variant `**Acceptance Criteria:**`.
4341
+ *
4342
+ * Stops scanning at the next `##` heading (or end-of-file).
4343
+ *
4344
+ * Returns an array of trimmed AC text strings (numbering stripped).
4345
+ */
4346
+ function parseAcList(storyContent) {
4347
+ const lines = storyContent.split("\n");
4348
+ const items = [];
4349
+ let inAcSection = false;
4350
+ for (const line of lines) {
4351
+ if (/^##\s+Acceptance Criteria/i.test(line) || /\*\*Acceptance Criteria:\*\*/i.test(line)) {
4352
+ inAcSection = true;
4353
+ continue;
4354
+ }
4355
+ if (inAcSection && /^##\s/.test(line)) break;
4356
+ if (!inAcSection) continue;
4357
+ const numbered = line.match(/^\s*(?:\d+[.):]|AC\d+[.):])\s*(.+)/);
4358
+ if (numbered) {
4359
+ const text = (numbered[1] ?? "").trim();
4360
+ if (text.length > 0) items.push(text);
4361
+ }
4362
+ }
4363
+ return items;
4364
+ }
4365
+ const TEST_FILE_PATTERNS = [
4366
+ /\.test\.ts$/,
4367
+ /\.test\.js$/,
4368
+ /test/i
4369
+ ];
4370
+ /** Returns true when the path matches a known test file pattern. */
4371
+ function isTestFile(path$3) {
4372
+ return TEST_FILE_PATTERNS.some((p) => p.test(path$3));
4373
+ }
4374
+ const TEST_DESC_RE = /(?:describe|it|test)\s*\(\s*['"`]([^'"`]+)['"`]/g;
4375
+ /**
4376
+ * Extract all string literals passed to `describe(`, `it(`, or `test(` in the
4377
+ * provided source text.
4378
+ */
4379
+ function extractTestDescriptions(source) {
4380
+ const results = [];
4381
+ let m;
4382
+ TEST_DESC_RE.lastIndex = 0;
4383
+ while ((m = TEST_DESC_RE.exec(source)) !== null) {
4384
+ const desc = m[1];
4385
+ if (desc !== void 0 && desc.trim().length > 0) results.push(desc.trim());
4386
+ }
4387
+ return results;
4388
+ }
4389
+ /**
4390
+ * Run the AC-to-test traceability heuristic.
4391
+ *
4392
+ * Steps:
4393
+ * 1. Parse AC list from `input.storyContent`.
4394
+ * 2. Filter `input.filesModified` to test files.
4395
+ * 3. Read each test file and extract describe/it/test descriptions.
4396
+ * 4. For each AC × each test description, compute word-overlap score.
4397
+ * 5. A score ≥ 0.4 marks the AC as "matched".
4398
+ * 6. Return the matrix with `confidence: 'approximate'`.
4399
+ *
4400
+ * File I/O errors are silently ignored per file (the file is simply skipped).
4401
+ * When no test files are found, all ACs are "not matched" and a warning is emitted.
4402
+ */
4403
+ async function runAcTraceabilityCheck(input) {
4404
+ const { storyKey, storyContent, filesModified } = input;
4405
+ const fileReader = input._readFile ?? ((p) => readFile(p, "utf-8"));
4406
+ const warnings = [];
4407
+ const acList = parseAcList(storyContent);
4408
+ const testFiles = filesModified.filter(isTestFile);
4409
+ if (testFiles.length === 0) {
4410
+ warnings.push(`No test files found in filesModified for story ${storyKey}. All ACs marked as unmatched.`);
4411
+ const matrix$1 = acList.map((acText) => ({
4412
+ acText,
4413
+ matched: false,
4414
+ testName: null,
4415
+ score: 0
4416
+ }));
4417
+ return {
4418
+ storyKey,
4419
+ matrix: matrix$1,
4420
+ confidence: "approximate",
4421
+ warnings
4422
+ };
4423
+ }
4424
+ const allTestDescriptions = [];
4425
+ for (const filePath of testFiles) try {
4426
+ const source = await fileReader(filePath);
4427
+ if (source != null) {
4428
+ const descs = extractTestDescriptions(source);
4429
+ allTestDescriptions.push(...descs);
4430
+ }
4431
+ } catch {}
4432
+ const MATCH_THRESHOLD = .4;
4433
+ const matrix = acList.map((acText) => {
4434
+ let bestScore = 0;
4435
+ let bestTestName = null;
4436
+ for (const desc of allTestDescriptions) {
4437
+ const score = wordOverlap(acText, desc);
4438
+ if (score > bestScore) {
4439
+ bestScore = score;
4440
+ bestTestName = desc;
4441
+ }
4442
+ }
4443
+ const matched = bestScore >= MATCH_THRESHOLD;
4444
+ return {
4445
+ acText,
4446
+ matched,
4447
+ testName: matched ? bestTestName : null,
4448
+ score: bestScore
4449
+ };
4450
+ });
4451
+ return {
4452
+ storyKey,
4453
+ matrix,
4454
+ confidence: "approximate",
4455
+ warnings
4456
+ };
4457
+ }
4458
+
4301
4459
  //#endregion
4302
4460
  //#region packages/sdlc/dist/run-model/cli-flags.js
4303
4461
  /**
@@ -5680,5 +5838,5 @@ async function resolveRunManifest(dbRoot, runId) {
5680
5838
  }
5681
5839
 
5682
5840
  //#endregion
5683
- 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, runStaleVerificationRecovery };
5684
- //# sourceMappingURL=manifest-read-DDkXC3L_.js.map
5841
+ 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 };
5842
+ //# sourceMappingURL=manifest-read-g4zt3DXJ.js.map
@@ -1,6 +1,6 @@
1
1
  import "../../logger-KeHncl-f.js";
2
2
  import "../../dist-K_RRWnBX.js";
3
- import "../../manifest-read-DDkXC3L_.js";
4
- import { runInteractivePrompt } from "../../interactive-prompt-C7wpE4z4.js";
3
+ import "../../manifest-read-g4zt3DXJ.js";
4
+ import { runInteractivePrompt } from "../../interactive-prompt-kzkG24Rn.js";
5
5
 
6
6
  export { runInteractivePrompt };
@@ -1,11 +1,11 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-DC3y-sR6.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-D6MKxV46.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-K_RRWnBX.js";
5
- import { FindingsInjector, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, renderFindings, resolveGraphPath, resolveMainRepoRoot, runStaleVerificationRecovery } from "./manifest-read-DDkXC3L_.js";
5
+ import { FindingsInjector, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, renderFindings, resolveGraphPath, resolveMainRepoRoot, runAcTraceabilityCheck, runStaleVerificationRecovery } from "./manifest-read-g4zt3DXJ.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-C7wpE4z4.js";
8
+ import { runInteractivePrompt } from "./interactive-prompt-kzkG24Rn.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";
@@ -11313,7 +11313,8 @@ function assembleVerificationContext(opts) {
11313
11313
  storyContent: opts.storyContent,
11314
11314
  devStoryResult: opts.devStoryResult,
11315
11315
  outputTokenCount: opts.outputTokenCount,
11316
- sourceEpicContent: opts.sourceEpicContent
11316
+ sourceEpicContent: opts.sourceEpicContent,
11317
+ runId: opts.runId
11317
11318
  };
11318
11319
  }
11319
11320
  /**
@@ -12360,7 +12361,7 @@ function createImplementationOrchestrator(deps) {
12360
12361
  let _otlpEndpoint;
12361
12362
  let _probeAuthorEffectiveMode = "enabled";
12362
12363
  const verificationStore = new VerificationStore();
12363
- const verificationPipeline = createDefaultVerificationPipeline(toSdlcEventBus(eventBus));
12364
+ const verificationPipeline = createDefaultVerificationPipeline(toSdlcEventBus(eventBus), void 0);
12364
12365
  const _stateStoreCache = new Map();
12365
12366
  const _checkpoints = new Map();
12366
12367
  const MEMORY_PRESSURE_BACKOFF_MS = [
@@ -14462,7 +14463,8 @@ function createImplementationOrchestrator(deps) {
14462
14463
  storyContent: storyContentForVerification,
14463
14464
  devStoryResult: devStorySignals,
14464
14465
  outputTokenCount: devOutputTokenCount,
14465
- sourceEpicContent
14466
+ sourceEpicContent,
14467
+ runId: config.pipelineRunId
14466
14468
  });
14467
14469
  const verifSummary = await verificationPipeline.run(verifContext, "A");
14468
14470
  verificationStore.set(storyKey, verifSummary);
@@ -14536,7 +14538,8 @@ function createImplementationOrchestrator(deps) {
14536
14538
  storyContent: storyContentForVerification,
14537
14539
  devStoryResult: devStorySignals,
14538
14540
  outputTokenCount: devOutputTokenCount,
14539
- sourceEpicContent
14541
+ sourceEpicContent,
14542
+ runId: config.pipelineRunId
14540
14543
  });
14541
14544
  const retryVerifSummary = await verificationPipeline.run(retryVerifContext, "A");
14542
14545
  verificationStore.set(storyKey, retryVerifSummary);
@@ -44515,7 +44518,7 @@ function resolveProbeAuthorStateIntegrating(cliFlag) {
44515
44518
  * substrate run --non-interactive --halt-on none --events --output-format json
44516
44519
  */
44517
44520
  async function runRunAction(options) {
44518
- 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 } = options;
44521
+ 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 } = options;
44519
44522
  const haltOn = haltOnOpt;
44520
44523
  const VALID_PROBE_AUTHOR_MODES = [
44521
44524
  "enabled",
@@ -45441,6 +45444,54 @@ async function runRunAction(options) {
45441
45444
  });
45442
45445
  return derivedCode;
45443
45446
  }
45447
+ if (verifyAc === true) try {
45448
+ const runsDir = join(dbDir, "runs");
45449
+ const runManifestForAc = RunManifest.open(pipelineRun.id, runsDir);
45450
+ const manifestData = await runManifestForAc.read();
45451
+ const artifactsDir = join(dbRoot, "_bmad-output", "implementation-artifacts");
45452
+ const acResults = {};
45453
+ for (const [sk, state] of Object.entries(manifestData.per_story_state)) {
45454
+ const filesModified = state.dev_story_signals?.files_modified ?? [];
45455
+ let storyContent = "";
45456
+ try {
45457
+ const artifactFiles = await readdir(artifactsDir, { encoding: "utf-8" }).catch(() => []);
45458
+ const matchingFile = artifactFiles.find((f$1) => (f$1.startsWith(`${sk}-`) || f$1 === `${sk}.md`) && f$1.endsWith(".md"));
45459
+ if (matchingFile) storyContent = await readFile(join(artifactsDir, matchingFile), "utf-8");
45460
+ } catch {}
45461
+ try {
45462
+ const result = await runAcTraceabilityCheck({
45463
+ storyKey: sk,
45464
+ storyContent,
45465
+ filesModified
45466
+ });
45467
+ acResults[sk] = {
45468
+ matrix: result.matrix,
45469
+ confidence: result.confidence
45470
+ };
45471
+ } catch (acErr) {
45472
+ logger.debug({
45473
+ err: acErr,
45474
+ storyKey: sk
45475
+ }, "ac traceability check failed for story");
45476
+ }
45477
+ }
45478
+ if (outputFormat === "json" || ndjsonEmitter !== void 0) process.stdout.write(JSON.stringify({
45479
+ type: "pipeline:ac-traceability",
45480
+ ts: new Date().toISOString(),
45481
+ run_id: pipelineRun.id,
45482
+ ac_traceability: acResults
45483
+ }) + "\n");
45484
+ else {
45485
+ process.stdout.write("\n── AC Traceability (approximate) ──\n");
45486
+ for (const [sk, result] of Object.entries(acResults)) {
45487
+ const matrix = result.matrix;
45488
+ const matchCount = matrix.filter((r) => r.matched).length;
45489
+ process.stdout.write(` ${sk}: ${matchCount}/${matrix.length} ACs matched\n`);
45490
+ }
45491
+ }
45492
+ } catch (acRunErr) {
45493
+ logger.warn({ err: acRunErr }, "AC traceability post-run check failed (best-effort)");
45494
+ }
45444
45495
  return 0;
45445
45496
  } catch (err) {
45446
45497
  const msg = err instanceof Error ? err.message : String(err);
@@ -45936,7 +45987,7 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
45936
45987
  "",
45937
45988
  "--halt-on defaults to critical. Skipped halt decisions are recorded as",
45938
45989
  "decision:halt-skipped-non-interactive events in --non-interactive mode."
45939
- ].join("\n ")).action(async (opts) => {
45990
+ ].join("\n ")).option("--verify-ac", "Run AC-to-test traceability heuristic after the pipeline completes (Story 74-1)").action(async (opts) => {
45940
45991
  if (opts.helpAgent) {
45941
45992
  process.exitCode = await runHelpAgent();
45942
45993
  return;
@@ -45981,7 +46032,8 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
45981
46032
  costCeiling: opts.costCeiling,
45982
46033
  probeAuthor: opts.probeAuthor,
45983
46034
  probeAuthorStateIntegrating: opts.probeAuthorStateIntegrating,
45984
- nonInteractive: opts.nonInteractive
46035
+ nonInteractive: opts.nonInteractive,
46036
+ verifyAc: opts.verifyAc
45985
46037
  });
45986
46038
  if (opts.nonInteractive === true) process.exit(exitCode);
45987
46039
  process.exitCode = exitCode;
@@ -45990,4 +46042,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
45990
46042
 
45991
46043
  //#endregion
45992
46044
  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 };
45993
- //# sourceMappingURL=run-DzB4rgkj.js.map
46045
+ //# sourceMappingURL=run-7h2-DIjt.js.map
@@ -1,14 +1,14 @@
1
- import "./health-DC3y-sR6.js";
1
+ import "./health-D6MKxV46.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-K_RRWnBX.js";
5
- import "./manifest-read-DDkXC3L_.js";
6
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-DzB4rgkj.js";
5
+ import "./manifest-read-g4zt3DXJ.js";
6
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-7h2-DIjt.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-C7wpE4z4.js";
11
+ import "./interactive-prompt-kzkG24Rn.js";
12
12
  import "./recovery-engine-BKGBeBnW.js";
13
13
 
14
14
  export { runRunAction };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.65",
3
+ "version": "0.20.66",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",