substrate-ai 0.19.33 → 0.19.35

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, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest } from "../health-q4LAKfYz.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest } from "../health-Ckl2VOPW.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-6ETBV23_.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-nz9xWO1t.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-BG1vEtOA.js";
8
8
  import "../errors-QZ2_FzOB.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -3307,7 +3307,7 @@ async function runStatusAction(options) {
3307
3307
  logger$12.debug({ err }, "Work graph query failed, continuing without work graph data");
3308
3308
  }
3309
3309
  if (run === void 0) {
3310
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-BMz_Vxme.js");
3310
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-B28gVggH.js");
3311
3311
  const substrateDirPath = join(projectRoot, ".substrate");
3312
3312
  const processInfo = inspectProcessTree$1({
3313
3313
  projectRoot,
@@ -4833,7 +4833,7 @@ async function runSupervisorAction(options, deps = {}) {
4833
4833
  await initSchema(expAdapter);
4834
4834
  const { runRunAction: runPipeline } = await import(
4835
4835
  /* @vite-ignore */
4836
- "../run-Cv8E1iib.js"
4836
+ "../run-DgayZ4Aq.js"
4837
4837
  );
4838
4838
  const runStoryFn = async (opts) => {
4839
4839
  const exitCode = await runPipeline({
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-q4LAKfYz.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-Ckl2VOPW.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-6ETBV23_.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -2715,7 +2715,7 @@ function createSdlcEventBridge(opts) {
2715
2715
  const { finalOutcome } = data;
2716
2716
  if (finalOutcome.status === "SUCCESS") sdlcBus.emit("orchestrator:story-complete", {
2717
2717
  storyKey,
2718
- reviewCycles: devStoryRetries
2718
+ reviewCycles: devStoryRetries + 1
2719
2719
  });
2720
2720
  };
2721
2721
  const onGoalGateUnsatisfied = (data) => {
@@ -4253,4 +4253,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
4253
4253
 
4254
4254
  //#endregion
4255
4255
  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, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, runHealthAction, validateStoryKey };
4256
- //# sourceMappingURL=health-q4LAKfYz.js.map
4256
+ //# sourceMappingURL=health-Ckl2VOPW.js.map
@@ -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, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-q4LAKfYz.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, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-Ckl2VOPW.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-6ETBV23_.js";
@@ -7372,6 +7372,191 @@ function extractFilesInScope(storyContent) {
7372
7372
  ].join("\n");
7373
7373
  }
7374
7374
 
7375
+ //#endregion
7376
+ //#region src/modules/compiled-workflows/scope-guardrail.ts
7377
+ /**
7378
+ * ScopeGuardrail — utility for detecting out-of-scope file modifications in code review.
7379
+ *
7380
+ * Parses the expected file set from a story spec and compares it against the
7381
+ * actual files modified by the dev agent. Returns a pre-computed scope analysis
7382
+ * markdown string that can be injected into the code-review prompt so the LLM
7383
+ * reviewer does not need to re-parse the spec sections manually.
7384
+ *
7385
+ * Architecture constraints:
7386
+ * - Pure utility: no imports from packages/sdlc/, packages/core/, or persistence layer
7387
+ * - Takes plain strings in, returns plain strings out
7388
+ * - Test-file exemption patterns must match countTestMetrics() in code-review.ts
7389
+ */
7390
+ /**
7391
+ * File extensions that qualify a token as a "path-like" string in task bullets.
7392
+ * Any token containing `/` and one of these extensions is treated as a file path.
7393
+ */
7394
+ const RECOGNIZED_EXTENSIONS = [
7395
+ ".ts",
7396
+ ".js",
7397
+ ".tsx",
7398
+ ".jsx",
7399
+ ".md",
7400
+ ".json",
7401
+ ".yaml",
7402
+ ".yml",
7403
+ ".py",
7404
+ ".go",
7405
+ ".java",
7406
+ ".rb",
7407
+ ".rs",
7408
+ ".sh",
7409
+ ".css",
7410
+ ".scss",
7411
+ ".html"
7412
+ ];
7413
+ /**
7414
+ * Section headings that explicitly list expected file paths.
7415
+ */
7416
+ const FILE_PATH_SECTION_PATTERNS = [
7417
+ /^#{1,4}\s*(file paths to create|files? to create)/i,
7418
+ /^#{1,4}\s*(file paths to modify|files? to modify)/i,
7419
+ /^#{1,4}\s*(key file paths?)/i
7420
+ ];
7421
+ /**
7422
+ * The heading that marks the start of tasks/subtasks section.
7423
+ */
7424
+ const TASKS_SECTION_PATTERN = /^#{1,4}\s*(tasks?\s*\/?\s*subtasks?|tasks?)/i;
7425
+ /**
7426
+ * Returns true if the given file path is a test file.
7427
+ * Patterns must be consistent with countTestMetrics() in code-review.ts,
7428
+ * which checks for `.test.`, `.spec.`, and `__tests__`. We also add `__mocks__/`.
7429
+ */
7430
+ function isTestFile(filePath) {
7431
+ return filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("__tests__/") || filePath.includes("__tests__\\") || filePath.includes("/__tests__") || filePath.includes("\\__tests__") || filePath.includes("__mocks__/") || filePath.includes("__mocks__\\") || filePath.includes("/__mocks__") || filePath.includes("\\__mocks__");
7432
+ }
7433
+ var ScopeGuardrail = class ScopeGuardrail {
7434
+ /**
7435
+ * Extract expected file paths from a story spec's raw text content.
7436
+ *
7437
+ * Scans for paths under sections:
7438
+ * - "### File Paths to Create" / "### Files to Create"
7439
+ * - "### File Paths to Modify" / "### Files to Modify"
7440
+ * - "### Key File Paths"
7441
+ * - Path-like tokens in "### Tasks / Subtasks" bullets
7442
+ *
7443
+ * A "path-like" token is any token containing `/` and a recognized extension.
7444
+ * Backtick wrappers and leading `- ` list markers are stripped.
7445
+ *
7446
+ * @param storyContent - Raw story spec markdown text
7447
+ * @returns Set of file path strings extracted from the spec
7448
+ */
7449
+ static parseExpectedFiles(storyContent) {
7450
+ const paths = new Set();
7451
+ const lines = storyContent.split("\n");
7452
+ let mode = "none";
7453
+ for (const rawLine of lines) {
7454
+ const line = rawLine.trimEnd();
7455
+ if (/^#{1,4}\s/.test(line)) {
7456
+ mode = "none";
7457
+ for (const pattern of FILE_PATH_SECTION_PATTERNS) if (pattern.test(line)) {
7458
+ mode = "file-list";
7459
+ break;
7460
+ }
7461
+ if (mode === "none" && TASKS_SECTION_PATTERN.test(line)) mode = "tasks";
7462
+ continue;
7463
+ }
7464
+ if (mode === "file-list") {
7465
+ const extracted = extractPathsFromLine(line);
7466
+ for (const p of extracted) paths.add(p);
7467
+ } else if (mode === "tasks") {
7468
+ const extracted = extractPathLikeTokens(line);
7469
+ for (const p of extracted) paths.add(p);
7470
+ }
7471
+ }
7472
+ return paths;
7473
+ }
7474
+ /**
7475
+ * Build a pre-computed scope analysis markdown string for injection into
7476
+ * the code-review prompt context.
7477
+ *
7478
+ * Returns empty string if no out-of-scope violations are detected (so the
7479
+ * assemblePrompt optional-section drop behavior applies automatically).
7480
+ *
7481
+ * @param storyContent - Raw story spec markdown text
7482
+ * @param filesModified - List of file paths from the git diff
7483
+ * @returns Markdown string listing expected/actual/delta file sets,
7484
+ * or empty string if no violations found
7485
+ */
7486
+ static buildAnalysis(storyContent, filesModified) {
7487
+ const expectedFiles = ScopeGuardrail.parseExpectedFiles(storyContent);
7488
+ const nonTestFiles = filesModified.filter((f$1) => !isTestFile(f$1));
7489
+ const outOfScope = nonTestFiles.filter((f$1) => !expectedFiles.has(f$1));
7490
+ if (outOfScope.length === 0) return "";
7491
+ const expectedList = expectedFiles.size > 0 ? Array.from(expectedFiles).map((f$1) => ` - ${f$1}`).join("\n") : " (none specified)";
7492
+ const actualList = nonTestFiles.length > 0 ? nonTestFiles.map((f$1) => ` - ${f$1}`).join("\n") : " (none)";
7493
+ const deltaList = outOfScope.map((f$1) => ` - ${f$1}`).join("\n");
7494
+ return [
7495
+ "## Pre-Computed Scope Analysis",
7496
+ "",
7497
+ "Expected files (from spec):",
7498
+ expectedList,
7499
+ "",
7500
+ "Actual files (from diff):",
7501
+ actualList,
7502
+ "",
7503
+ "Out-of-scope files (excluding tests):",
7504
+ deltaList,
7505
+ "",
7506
+ "Note: Test files (*.test.ts, *.spec.ts, __tests__/, __mocks__/) are excluded from scope checking."
7507
+ ].join("\n");
7508
+ }
7509
+ };
7510
+ /**
7511
+ * Extract all path-like strings from a line (for file-list sections).
7512
+ * Handles both backtick-wrapped paths and plain paths.
7513
+ * Strips leading `- ` list markers.
7514
+ */
7515
+ function extractPathsFromLine(line) {
7516
+ const paths = [];
7517
+ const trimmed = line.trim().replace(/^[-*]\s+/, "");
7518
+ if (!trimmed) return paths;
7519
+ const backtickMatches = trimmed.matchAll(/`([^`]+)`/g);
7520
+ for (const match$1 of backtickMatches) {
7521
+ const candidate = match$1[1].trim();
7522
+ if (isFilePath(candidate)) paths.push(candidate);
7523
+ }
7524
+ if (paths.length > 0) return paths;
7525
+ const noSpaces = trimmed.split(/\s/)[0];
7526
+ if (noSpaces && isFilePath(noSpaces)) paths.push(noSpaces);
7527
+ return paths;
7528
+ }
7529
+ /**
7530
+ * Extract path-like tokens from a line (for tasks/subtasks sections).
7531
+ * A path-like token contains `/` and a recognized extension.
7532
+ */
7533
+ function extractPathLikeTokens(line) {
7534
+ const paths = [];
7535
+ const backtickMatches = line.matchAll(/`([^`]+)`/g);
7536
+ for (const match$1 of backtickMatches) {
7537
+ const candidate = match$1[1].trim();
7538
+ if (isFilePath(candidate)) paths.push(candidate);
7539
+ }
7540
+ const withoutBackticks = line.replace(/`[^`]+`/g, " ");
7541
+ const tokens = withoutBackticks.split(/\s+/);
7542
+ for (const token of tokens) {
7543
+ const clean = token.replace(/[,;:()\[\]{}'"]+$/g, "").replace(/^[,;:()\[\]{}'"]+/g, "");
7544
+ if (clean && isFilePath(clean)) paths.push(clean);
7545
+ }
7546
+ return paths;
7547
+ }
7548
+ /**
7549
+ * Returns true if the candidate string looks like a file path:
7550
+ * - Contains at least one `/`
7551
+ * - Has a recognized file extension
7552
+ * - Does not contain spaces
7553
+ */
7554
+ function isFilePath(candidate) {
7555
+ if (!candidate.includes("/")) return false;
7556
+ if (/\s/.test(candidate)) return false;
7557
+ return RECOGNIZED_EXTENSIONS.some((ext$1) => candidate.endsWith(ext$1));
7558
+ }
7559
+
7375
7560
  //#endregion
7376
7561
  //#region src/modules/compiled-workflows/code-review.ts
7377
7562
  const logger$14 = createLogger("compiled-workflows:code-review");
@@ -7568,6 +7753,8 @@ async function runCodeReview(deps, params) {
7568
7753
  } catch {}
7569
7754
  const testMetricsContent = await countTestMetrics(filesModified, cwd);
7570
7755
  if (testMetricsContent) logger$14.debug({ storyKey }, "Injecting verified test-count metrics into code-review context");
7756
+ const scopeAnalysisContent = storyContent && filesModified ? ScopeGuardrail.buildAnalysis(storyContent, filesModified) : "";
7757
+ if (scopeAnalysisContent) logger$14.debug({ storyKey }, "Scope analysis detected out-of-scope files");
7571
7758
  const buildStatusPrefix = buildPassed === true ? "BUILD STATUS: PASSED — code compiles and passes build verification. Focus on logic correctness, style, and acceptance criteria rather than compilation errors.\n\n" : "";
7572
7759
  const sections = [
7573
7760
  {
@@ -7585,6 +7772,11 @@ async function runCodeReview(deps, params) {
7585
7772
  content: testMetricsContent,
7586
7773
  priority: "important"
7587
7774
  },
7775
+ {
7776
+ name: "scope_analysis",
7777
+ content: scopeAnalysisContent,
7778
+ priority: "optional"
7779
+ },
7588
7780
  {
7589
7781
  name: "previous_findings",
7590
7782
  content: previousFindingsContent,
@@ -9478,20 +9670,21 @@ function verifyContracts(declarations, projectRoot) {
9478
9670
  const mismatches = [];
9479
9671
  for (const exp of exports$1) {
9480
9672
  if (!exp.filePath) continue;
9481
- const absPath = join$1(projectRoot, exp.filePath);
9673
+ const cleanPath = exp.filePath.replace(/`/g, "");
9674
+ const absPath = join$1(projectRoot, cleanPath);
9482
9675
  if (!existsSync(absPath)) {
9483
9676
  const importers = imports.filter((i) => i.contractName === exp.contractName);
9484
9677
  if (importers.length > 0) for (const imp of importers) mismatches.push({
9485
9678
  exporter: exp.storyKey,
9486
9679
  importer: imp.storyKey,
9487
9680
  contractName: exp.contractName,
9488
- mismatchDescription: `Exported file not found: ${exp.filePath}`
9681
+ mismatchDescription: `Exported file not found: ${cleanPath}`
9489
9682
  });
9490
9683
  else mismatches.push({
9491
9684
  exporter: exp.storyKey,
9492
9685
  importer: null,
9493
9686
  contractName: exp.contractName,
9494
- mismatchDescription: `Exported file not found: ${exp.filePath}`
9687
+ mismatchDescription: `Exported file not found: ${cleanPath}`
9495
9688
  });
9496
9689
  }
9497
9690
  }
@@ -12914,6 +13107,16 @@ function createImplementationOrchestrator(deps) {
12914
13107
  modifiedInterfaces: icResult.modifiedInterfaces,
12915
13108
  potentiallyAffectedTests: icResult.potentiallyAffectedTests
12916
13109
  });
13110
+ if (config.pipelineRunId !== void 0) createDecision(db, {
13111
+ pipeline_run_id: config.pipelineRunId,
13112
+ phase: "implementation",
13113
+ category: "INTERFACE_WARNING",
13114
+ key: `${storyKey}:${config.pipelineRunId}`,
13115
+ value: JSON.stringify({
13116
+ modifiedInterfaces: icResult.modifiedInterfaces,
13117
+ potentiallyAffectedTests: icResult.potentiallyAffectedTests
13118
+ })
13119
+ }).catch(() => {});
12917
13120
  }
12918
13121
  }
12919
13122
  } catch {}
@@ -13197,11 +13400,12 @@ function createImplementationOrchestrator(deps) {
13197
13400
  phase: "COMPLETE",
13198
13401
  completedAt: new Date().toISOString()
13199
13402
  });
13200
- await writeStoryMetricsBestEffort(storyKey, verdict, reviewCycles + 1);
13201
- await writeStoryOutcomeBestEffort(storyKey, "complete", reviewCycles + 1);
13403
+ const completedReviewCycles = reviewCycles + 1;
13404
+ await writeStoryMetricsBestEffort(storyKey, verdict, completedReviewCycles);
13405
+ await writeStoryOutcomeBestEffort(storyKey, "complete", completedReviewCycles);
13202
13406
  eventBus.emit("orchestrator:story-complete", {
13203
13407
  storyKey,
13204
- reviewCycles
13408
+ reviewCycles: completedReviewCycles
13205
13409
  });
13206
13410
  await persistState();
13207
13411
  if (verdict === "LGTM_WITH_NOTES" && reviewResult.notes && config.pipelineRunId) try {
@@ -18134,6 +18338,7 @@ async function runResearchPhase(deps, params) {
18134
18338
 
18135
18339
  //#endregion
18136
18340
  //#region src/modules/telemetry/mesh-reporter.ts
18341
+ const logger$2 = createLogger("mesh-reporter");
18137
18342
  /** Lazy-loaded substrate version (avoids module-load-time side effects that break test mocking). */
18138
18343
  let _cachedVersion;
18139
18344
  function getSubstrateVersion() {
@@ -18147,7 +18352,68 @@ function getSubstrateVersion() {
18147
18352
  }
18148
18353
  return _cachedVersion;
18149
18354
  }
18150
- const logger$2 = createLogger("mesh-reporter");
18355
+ async function loadVerificationResults(runId, runsDir) {
18356
+ const results = {};
18357
+ try {
18358
+ const manifest = RunManifest.open(runId, runsDir);
18359
+ const data = await manifest.read();
18360
+ if (data?.per_story_state) for (const [storyKey, state] of Object.entries(data.per_story_state)) {
18361
+ const vr = state["verification_result"];
18362
+ if (vr) results[storyKey] = {
18363
+ status: vr.status ?? "pass",
18364
+ checks: (vr.checks ?? []).map((c) => ({
18365
+ checkName: c.checkName,
18366
+ status: c.status,
18367
+ ...c.details !== void 0 && { details: c.details },
18368
+ ...c.duration_ms !== void 0 && { durationMs: c.duration_ms }
18369
+ }))
18370
+ };
18371
+ }
18372
+ } catch {
18373
+ logger$2.debug({ runId }, "Could not read RunManifest for verification results — skipping");
18374
+ }
18375
+ return results;
18376
+ }
18377
+ async function loadEfficiencyScores(adapter, storyKeys) {
18378
+ const scores = {};
18379
+ if (storyKeys.length === 0) return scores;
18380
+ try {
18381
+ for (const key of storyKeys) {
18382
+ const rows = await adapter.query("SELECT composite_score FROM efficiency_scores WHERE story_key = ? ORDER BY timestamp DESC LIMIT 1", [key]);
18383
+ if (rows.length > 0 && rows[0] !== void 0) scores[key] = rows[0].composite_score;
18384
+ }
18385
+ } catch {
18386
+ logger$2.debug("Could not query efficiency_scores table — skipping");
18387
+ }
18388
+ return scores;
18389
+ }
18390
+ async function loadContractVerification(adapter, runId) {
18391
+ try {
18392
+ const rows = await adapter.query(`SELECT key, value FROM decisions WHERE pipeline_run_id = ? AND category = 'interface-contract'`, [runId]);
18393
+ if (rows.length === 0) return void 0;
18394
+ const mismatches = [];
18395
+ let verified = 0;
18396
+ for (const row of rows) try {
18397
+ const parsed = JSON.parse(row.value);
18398
+ if (parsed.verdict === "fail" && parsed.contractName && parsed.mismatchDescription) mismatches.push({
18399
+ exporter: parsed.exporter ?? row.key,
18400
+ importer: parsed.importer ?? null,
18401
+ contractName: parsed.contractName,
18402
+ mismatchDescription: parsed.mismatchDescription
18403
+ });
18404
+ else verified++;
18405
+ } catch {}
18406
+ return {
18407
+ verified,
18408
+ mismatches: mismatches.length,
18409
+ verdict: mismatches.length > 0 ? "fail" : "pass",
18410
+ ...mismatches.length > 0 && { details: mismatches }
18411
+ };
18412
+ } catch {
18413
+ logger$2.debug("Could not query contract verification decisions — skipping");
18414
+ return void 0;
18415
+ }
18416
+ }
18151
18417
  async function buildRunReport(adapter, runId, opts) {
18152
18418
  const runMetrics = await getRunMetrics(adapter, runId);
18153
18419
  if (!runMetrics) {
@@ -18155,11 +18421,46 @@ async function buildRunReport(adapter, runId, opts) {
18155
18421
  return null;
18156
18422
  }
18157
18423
  const storyMetrics = await getStoryMetricsForRun(adapter, runId);
18424
+ const storyKeys = storyMetrics.map((s$1) => s$1.story_key);
18425
+ const dbDir = opts.projectRoot ? join$1(opts.projectRoot, ".substrate") : ".substrate";
18426
+ const runsDir = join$1(dbDir, "runs");
18427
+ const verificationResults = await loadVerificationResults(runId, runsDir);
18428
+ const efficiencyScores = await loadEfficiencyScores(adapter, storyKeys);
18429
+ const contractVerification = await loadContractVerification(adapter, runId);
18430
+ const warnings = [];
18431
+ try {
18432
+ const warnRows = await adapter.query(`SELECT key, value FROM decisions WHERE pipeline_run_id = ? AND category = 'INTERFACE_WARNING'`, [runId]);
18433
+ for (const row of warnRows) try {
18434
+ const parsed = JSON.parse(row.value);
18435
+ const storyKey = row.key.split(":")[0] ?? "unknown";
18436
+ const ifaces = parsed.modifiedInterfaces?.join(", ") ?? "";
18437
+ warnings.push(`${storyKey}: modified interfaces [${ifaces}]`);
18438
+ } catch {}
18439
+ } catch {
18440
+ logger$2.debug("Could not query INTERFACE_WARNING decisions — skipping");
18441
+ }
18442
+ const testExpansions = {};
18443
+ try {
18444
+ const expRows = await adapter.query(`SELECT key, value FROM decisions WHERE pipeline_run_id = ? AND category = 'TEST_EXPANSION_FINDING'`, [runId]);
18445
+ for (const row of expRows) try {
18446
+ const parsed = JSON.parse(row.value);
18447
+ const storyKey = row.key.split(":")[0] ?? "unknown";
18448
+ testExpansions[storyKey] = {
18449
+ priority: parsed.expansion_priority ?? "unknown",
18450
+ gapCount: parsed.coverage_gaps?.length ?? 0
18451
+ };
18452
+ } catch {}
18453
+ } catch {
18454
+ logger$2.debug("Could not query TEST_EXPANSION_FINDING decisions — skipping");
18455
+ }
18456
+ for (const [storyKey, exp] of Object.entries(testExpansions)) if (exp.gapCount > 0) warnings.push(`${storyKey}: test expansion found ${exp.gapCount} coverage gap(s), priority=${exp.priority}`);
18158
18457
  const stories = storyMetrics.map((s$1) => {
18159
18458
  let phaseDurations;
18160
18459
  if (s$1.phase_durations_json) try {
18161
18460
  phaseDurations = JSON.parse(s$1.phase_durations_json);
18162
18461
  } catch {}
18462
+ const vr = verificationResults[s$1.story_key];
18463
+ const qualityScore = efficiencyScores[s$1.story_key];
18163
18464
  return {
18164
18465
  storyKey: s$1.story_key,
18165
18466
  result: s$1.result,
@@ -18169,9 +18470,16 @@ async function buildRunReport(adapter, runId, opts) {
18169
18470
  costUsd: s$1.cost_usd,
18170
18471
  reviewCycles: s$1.review_cycles,
18171
18472
  dispatches: s$1.dispatches,
18172
- ...phaseDurations !== void 0 && { phaseDurations }
18473
+ ...phaseDurations !== void 0 && { phaseDurations },
18474
+ ...vr !== void 0 && {
18475
+ verificationStatus: vr.status,
18476
+ verificationChecks: vr.checks
18477
+ },
18478
+ ...qualityScore !== void 0 && { qualityScore }
18173
18479
  };
18174
18480
  });
18481
+ const storyScores = stories.map((s$1) => s$1.qualityScore).filter((s$1) => s$1 !== void 0);
18482
+ const avgEfficiencyScore = storyScores.length > 0 ? Math.round(storyScores.reduce((a, b) => a + b, 0) / storyScores.length) : void 0;
18175
18483
  const rawStatus = runMetrics.status.toLowerCase();
18176
18484
  const status = rawStatus === "completed" ? "completed" : rawStatus === "failed" ? "failed" : "partial";
18177
18485
  const projectId = opts.projectId ?? (opts.projectRoot ? basename$1(opts.projectRoot) : "unknown");
@@ -18193,6 +18501,9 @@ async function buildRunReport(adapter, runId, opts) {
18193
18501
  totalDispatches: runMetrics.total_dispatches,
18194
18502
  restarts: runMetrics.restarts,
18195
18503
  stories,
18504
+ ...contractVerification !== void 0 && { contractVerification },
18505
+ ...warnings.length > 0 && { warnings },
18506
+ ...avgEfficiencyScore !== void 0 && { efficiencyScore: avgEfficiencyScore },
18196
18507
  agentBackend: opts.agentBackend ?? "claude-code",
18197
18508
  engineType: opts.engineType ?? "linear",
18198
18509
  concurrency: opts.concurrency ?? runMetrics.concurrency_setting
@@ -42266,6 +42577,13 @@ async function runFullPipeline(options) {
42266
42577
  runId
42267
42578
  });
42268
42579
  process.stdout.write(summary + "\n");
42580
+ if (fpMeshUrl !== void 0) await reportToMesh(adapter, runId, fpMeshUrl, {
42581
+ projectId: fpMeshProjectId,
42582
+ projectRoot,
42583
+ agentBackend: agentId ?? "claude-code",
42584
+ engineType: fpEngineType ?? "linear",
42585
+ concurrency
42586
+ });
42269
42587
  return 0;
42270
42588
  }
42271
42589
  }
@@ -42375,4 +42693,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
42375
42693
 
42376
42694
  //#endregion
42377
42695
  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 };
42378
- //# sourceMappingURL=run-nz9xWO1t.js.map
42696
+ //# sourceMappingURL=run-BG1vEtOA.js.map
@@ -1,8 +1,8 @@
1
- import "./health-q4LAKfYz.js";
1
+ import "./health-Ckl2VOPW.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-6ETBV23_.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-nz9xWO1t.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-BG1vEtOA.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.19.33",
3
+ "version": "0.19.35",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,6 +19,9 @@
19
19
  ### Prior Run Findings
20
20
  {{prior_findings}}
21
21
 
22
+ <!-- scope_analysis -->
23
+ {{scope_analysis}}
24
+
22
25
  ---
23
26
 
24
27
  ## Mission
@@ -39,12 +42,13 @@ Adversarial code review. Find what's wrong. Validate story claims against actual
39
42
 
40
43
  3. **Build AC Checklist** — For each acceptance criterion (AC1, AC2, ...) in the story, determine: `met` (code implements it), `not_met` (code does not implement it), or `partial` (partially implemented). Cite the specific file and function as evidence.
41
44
 
42
- 4. **Execute adversarial review** across 5 dimensions:
45
+ 4. **Execute adversarial review** across 6 dimensions:
43
46
  - **AC Validation** — Is each acceptance criterion implemented?
44
47
  - **AC-to-Test Traceability** — For each AC, identify the specific test file and test function that validates it. If an AC has no corresponding test evidence, flag it as a major issue: "AC{N} has no test evidence". A test "covers" an AC if it directly exercises the behavior described in the criterion — tangential tests do not count.
45
48
  - **Task Audit** — Tasks marked `[x]` that aren't done are BLOCKER issues
46
49
  - **Code Quality** — Security, error handling, edge cases, maintainability
47
50
  - **Test Quality** — Real assertions, not placeholders or skipped tests
51
+ - **Scope Compliance** — Compare files in the git diff against the expected file set from the story spec's "Key File Paths", "File Paths to Create", "File Paths to Modify", and "Tasks / Subtasks" sections. Any non-test file created or modified that does not appear in that expected set should be recorded as a `scope-creep` finding with `category: scope-creep` and `severity: minor`. **Test files (paths containing `.test.ts`, `.spec.ts`, `__tests__/`, or `__mocks__/`) are always exempt from scope checking — do not flag them, regardless of whether they appear in the story spec.** If a pre-computed scope analysis is provided in the `scope_analysis` section above, use it as ground truth — do not re-parse the story spec manually. **ADVISORY ONLY: Scope-creep findings are informational. If the only issues found are `scope-creep` entries, the verdict must be SHIP_IT or LGTM_WITH_NOTES — scope-creep findings do not independently trigger NEEDS_MINOR_FIXES or NEEDS_MAJOR_REWORK.**
48
52
 
49
53
  5. **Severity classification:**
50
54
  - **blocker** — Task `[x]` but not implemented; security vulnerability; data loss risk
@@ -85,6 +89,10 @@ issue_list:
85
89
  description: "Variable name `d` should be more descriptive"
86
90
  file: "src/modules/foo/foo.ts"
87
91
  line: 15
92
+ - severity: minor
93
+ category: scope-creep
94
+ description: "File src/modules/foo/extra.ts was created but not listed in the story spec's expected file set"
95
+ file: "src/modules/foo/extra.ts"
88
96
  ac_checklist:
89
97
  - ac_id: AC1
90
98
  status: met