substrate-ai 0.20.21 → 0.20.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts } from "../health-ZGa9E0D2.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts } from "../health-Cq8K_jrJ.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-CqtWS9wF.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-DrJWCS63.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-B3e4O0Rk.js";
8
8
  import "../errors-1uLGqnvr.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -3667,7 +3667,7 @@ async function runStatusAction(options) {
3667
3667
  logger$12.debug({ err }, "Work graph query failed, continuing without work graph data");
3668
3668
  }
3669
3669
  if (run === void 0) {
3670
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-CeVz5k99.js");
3670
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-CsRLsKgu.js");
3671
3671
  const substrateDirPath = join(projectRoot, ".substrate");
3672
3672
  const processInfo = inspectProcessTree$1({
3673
3673
  projectRoot,
@@ -5198,7 +5198,7 @@ async function runSupervisorAction(options, deps = {}) {
5198
5198
  await initSchema(expAdapter);
5199
5199
  const { runRunAction: runPipeline } = await import(
5200
5200
  /* @vite-ignore */
5201
- "../run-C46Xfny6.js"
5201
+ "../run-DEeTPCdU.js"
5202
5202
  );
5203
5203
  const runStoryFn = async (opts) => {
5204
5204
  const exitCode = await runPipeline({
@@ -3847,6 +3847,51 @@ function pathSatisfiedByCode(workingDir, pathClause) {
3847
3847
  return false;
3848
3848
  }
3849
3849
  /**
3850
+ * Story 60-3 (Sprint 11B): check whether the path clause is referenced from
3851
+ * THIS story's modified files. Strata obs_2026-04-25_011 surfaced a case where
3852
+ * `pathSatisfiedByCode` returned true (path exists in repo) but the story's
3853
+ * own code did not actually use the path — the directory was created earlier
3854
+ * by a different story (1-17 in strata's case) and 1-10's `packages/memory-mcp/`
3855
+ * code never imported `packages/mesh-agent`. The fidelity check then annotated
3856
+ * the missing path as "stylistic drift — code satisfies it", obscuring real
3857
+ * under-delivery.
3858
+ *
3859
+ * This check closes that gap: when path exists in repo AND a list of
3860
+ * modified files is available, scan those modified files for an import /
3861
+ * require / use reference to the path's basename. If references exist, the
3862
+ * story's code does use the path → genuinely stylistic drift. If references
3863
+ * are absent, the story's code does not use the path → architectural
3864
+ * under-delivery (downgrade severity from warn to error).
3865
+ *
3866
+ * Conservative behavior: when modifiedFiles is empty (dev didn't report a
3867
+ * file list, or a Tier B re-verification context), preserves the existing
3868
+ * "code satisfies → warn" behavior. Only TIGHTENS when authoritative
3869
+ * file-list signal is present.
3870
+ */
3871
+ function pathReferencedInModifiedFiles(workingDir, pathClause, modifiedFiles) {
3872
+ if (modifiedFiles.length === 0) return true;
3873
+ const raw = pathClause.replace(/^`/, "").replace(/`$/, "");
3874
+ const baseWithExt = basename$1(raw);
3875
+ const token = baseWithExt.replace(/\.[a-z]+$/i, "");
3876
+ if (token.length < 3) return true;
3877
+ const escapedSeparatorTolerant = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/[-_]/g, "[-_]");
3878
+ const importPattern = new RegExp(`^\\s*(?:import|from|require|use|mod)\\b[^\\n]*\\b${escapedSeparatorTolerant}\\b`, "mi");
3879
+ const barePattern = new RegExp(`\\b${escapedSeparatorTolerant}\\b`, "i");
3880
+ for (const filePath of modifiedFiles) {
3881
+ let content;
3882
+ try {
3883
+ content = readFileSync(join$1(workingDir, filePath), "utf-8");
3884
+ } catch {
3885
+ continue;
3886
+ }
3887
+ if (importPattern.test(content)) return true;
3888
+ if (filePath.endsWith("package.json") || filePath.endsWith(".yaml") || filePath.endsWith(".yml") || filePath.endsWith(".toml")) {
3889
+ if (barePattern.test(content)) return true;
3890
+ }
3891
+ }
3892
+ return false;
3893
+ }
3894
+ /**
3850
3895
  * Extract the story's section from the full epic content.
3851
3896
  *
3852
3897
  * Uses the same heading pattern as `isImplicitlyCovered` in the monolith:
@@ -3937,10 +3982,24 @@ var SourceAcFidelityCheck = class {
3937
3982
  const truncated = clause.text.length > 120 ? clause.text.slice(0, 120) : clause.text;
3938
3983
  if (clause.type === "path") {
3939
3984
  const existsInCode = pathSatisfiedByCode(context.workingDir, clause.text);
3985
+ const modifiedFiles = context.devStoryResult?.files_modified ?? [];
3986
+ const referencedByStory = pathReferencedInModifiedFiles(context.workingDir, clause.text, modifiedFiles);
3987
+ let severity;
3988
+ let driftMessage;
3989
+ if (!existsInCode) {
3990
+ severity = "error";
3991
+ driftMessage = `${clause.type}: "${truncated}" present in epics source but absent in story artifact AND missing from code (architectural drift)`;
3992
+ } else if (!referencedByStory) {
3993
+ severity = "error";
3994
+ driftMessage = `${clause.type}: "${truncated}" present in epics source but absent in story artifact AND code path exists in repo but THIS story's modified files do not reference it (under-delivery — code path was created by a different story; this story did not wire it in)`;
3995
+ } else {
3996
+ severity = "warn";
3997
+ driftMessage = `${clause.type}: "${truncated}" present in epics source but absent in story artifact (code satisfies it — stylistic drift)`;
3998
+ }
3940
3999
  findings.push({
3941
4000
  category: "source-ac-drift",
3942
- severity: existsInCode ? "warn" : "error",
3943
- message: existsInCode ? `${clause.type}: "${truncated}" present in epics source but absent in story artifact (code satisfies it — stylistic drift)` : `${clause.type}: "${truncated}" present in epics source but absent in story artifact AND missing from code (architectural drift)`
4001
+ severity,
4002
+ message: driftMessage
3944
4003
  });
3945
4004
  } else findings.push({
3946
4005
  category: "source-ac-drift",
@@ -5622,4 +5681,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
5622
5681
 
5623
5682
  //#endregion
5624
5683
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, runHealthAction, validateStoryKey };
5625
- //# sourceMappingURL=health-ZGa9E0D2.js.map
5684
+ //# sourceMappingURL=health-Cq8K_jrJ.js.map
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-ZGa9E0D2.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-Cq8K_jrJ.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-CqtWS9wF.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-ZGa9E0D2.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-Cq8K_jrJ.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
4
  import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-CqtWS9wF.js";
@@ -6030,6 +6030,95 @@ function computeStoryFileFidelity(storyFileContent, namedPaths) {
6030
6030
  drift: missing.length / namedPaths.length
6031
6031
  };
6032
6032
  }
6033
+ const WORD_TO_NUMBER = {
6034
+ one: 1,
6035
+ two: 2,
6036
+ three: 3,
6037
+ four: 4,
6038
+ five: 5,
6039
+ six: 6,
6040
+ seven: 7,
6041
+ eight: 8,
6042
+ nine: 9,
6043
+ ten: 10
6044
+ };
6045
+ function extractBehavioralAssertions(content) {
6046
+ if (content.length === 0) return {
6047
+ whenClauseCount: 0,
6048
+ whenOrAcCount: 0,
6049
+ numericQuantifiers: []
6050
+ };
6051
+ const whenMatches = content.match(/\*\*When\*\*/gi);
6052
+ const whenClauseCount = whenMatches?.length ?? 0;
6053
+ const acHeadings = content.match(/^#{2,4}\s+AC\d+\b/gim);
6054
+ const acCount = acHeadings?.length ?? 0;
6055
+ const whenOrAcCount = Math.max(whenClauseCount, acCount);
6056
+ const numericQuantifiers = [];
6057
+ const seen = new Set();
6058
+ const wordNum = "(?:one|two|three|four|five|six|seven|eight|nine|ten)";
6059
+ const pattern = new RegExp(`\\b(exactly|all|both)\\s+(?:(${wordNum}|\\d+)\\s+)?(?:[a-z][a-z_-]*\\s+){0,2}([a-z][a-z_-]+s)\\b`, "gi");
6060
+ let match$2;
6061
+ while ((match$2 = pattern.exec(content)) !== null) {
6062
+ const determiner = match$2[1]?.toLowerCase() ?? "";
6063
+ const numStr = match$2[2]?.toLowerCase() ?? "";
6064
+ const noun = match$2[3]?.toLowerCase() ?? "";
6065
+ let count;
6066
+ if (numStr === "") if (determiner === "both") count = 2;
6067
+ else continue;
6068
+ else if (WORD_TO_NUMBER[numStr] !== void 0) count = WORD_TO_NUMBER[numStr];
6069
+ else {
6070
+ const parsed = Number.parseInt(numStr, 10);
6071
+ if (Number.isNaN(parsed)) continue;
6072
+ count = parsed;
6073
+ }
6074
+ if (noun.length < 3) continue;
6075
+ const phrase = `${determiner} ${numStr || (determiner === "both" ? "" : "")} ${noun}`.trim().replace(/\s+/g, " ");
6076
+ const dedupKey = `${count}|${noun}`;
6077
+ if (seen.has(dedupKey)) continue;
6078
+ seen.add(dedupKey);
6079
+ numericQuantifiers.push({
6080
+ phrase,
6081
+ count,
6082
+ noun
6083
+ });
6084
+ }
6085
+ return {
6086
+ whenClauseCount,
6087
+ whenOrAcCount,
6088
+ numericQuantifiers
6089
+ };
6090
+ }
6091
+ function computeClauseFidelity(storyFileContent, sourceContent) {
6092
+ const sourceSignals = extractBehavioralAssertions(sourceContent);
6093
+ const renderedSignals = extractBehavioralAssertions(storyFileContent);
6094
+ const sourceCount = sourceSignals.whenOrAcCount;
6095
+ const renderedCount = renderedSignals.whenOrAcCount;
6096
+ const clauseRatio = sourceCount === 0 ? 1 : Math.min(1, renderedCount / sourceCount);
6097
+ const sourceNounCounts = new Map();
6098
+ for (const q of sourceSignals.numericQuantifiers) sourceNounCounts.set(q.noun, Math.max(sourceNounCounts.get(q.noun) ?? 0, q.count));
6099
+ const renderedNounCounts = new Map();
6100
+ for (const q of renderedSignals.numericQuantifiers) renderedNounCounts.set(q.noun, Math.max(renderedNounCounts.get(q.noun) ?? 0, q.count));
6101
+ const numericMismatches = [];
6102
+ for (const [noun, sourceCnt] of sourceNounCounts.entries()) {
6103
+ const renderedCnt = renderedNounCounts.get(noun) ?? 0;
6104
+ if (renderedCnt < sourceCnt) numericMismatches.push({
6105
+ noun,
6106
+ sourceCount: sourceCnt,
6107
+ renderedCount: renderedCnt
6108
+ });
6109
+ }
6110
+ const numericDriftComponent = numericMismatches.length > 0 ? 1 : 0;
6111
+ const CLAUSE_RATIO_FLOOR = .7;
6112
+ const clauseDriftComponent = clauseRatio >= CLAUSE_RATIO_FLOOR ? 0 : Math.min(1, (CLAUSE_RATIO_FLOOR - clauseRatio) / CLAUSE_RATIO_FLOOR);
6113
+ const drift = Math.max(numericDriftComponent, clauseDriftComponent);
6114
+ return {
6115
+ clauseRatio,
6116
+ sourceClauseCount: sourceCount,
6117
+ renderedClauseCount: renderedCount,
6118
+ numericMismatches,
6119
+ drift
6120
+ };
6121
+ }
6033
6122
  /**
6034
6123
  * Retrieve the epic shard from the pre-fetched implementation decisions.
6035
6124
  *
@@ -12520,78 +12609,114 @@ function createImplementationOrchestrator(deps) {
12520
12609
  }
12521
12610
  if (fidelitySourceContent !== void 0) {
12522
12611
  const namedPaths = extractNamedPathsFromSource(fidelitySourceContent);
12523
- if (namedPaths.length >= MIN_NAMED_PATHS_FOR_FIDELITY_GATE) {
12524
- const storyContentForFidelity = await readFile$1(storyFilePath, "utf-8");
12525
- const fidelity = computeStoryFileFidelity(storyContentForFidelity, namedPaths);
12526
- logger$24.debug({
12527
- storyKey,
12528
- drift: fidelity.drift,
12529
- missing: fidelity.missing,
12530
- presentCount: fidelity.present.length,
12531
- namedPathsCount: namedPaths.length
12532
- }, "create-story output fidelity check");
12533
- if (fidelity.drift > FIDELITY_DRIFT_THRESHOLD) {
12534
- fidelityRetries++;
12535
- if (fidelityRetries <= MAX_FIDELITY_RETRIES) {
12536
- const stalePath = storyFilePath.replace(/\.md$/, `.stale-${Date.now()}.md`);
12537
- try {
12538
- renameSync(storyFilePath, stalePath);
12539
- const driftPct = Math.round(fidelity.drift * 100);
12540
- logger$24.warn({
12541
- storyKey,
12542
- drift: fidelity.drift,
12543
- missing: fidelity.missing,
12544
- retries: fidelityRetries,
12545
- stalePath
12546
- }, `create-story output drifted from source AC (${driftPct}% of ${namedPaths.length} named paths missing); renamed to ${stalePath} and retrying (${fidelityRetries}/${MAX_FIDELITY_RETRIES})`);
12547
- eventBus.emit("orchestrator:story-warn", {
12548
- storyKey,
12549
- msg: `create-story drift detected (${fidelity.missing.length}/${namedPaths.length} named paths missing); retry ${fidelityRetries}/${MAX_FIDELITY_RETRIES}`
12550
- });
12551
- priorDriftFeedback = [
12552
- `### Prior Dispatch Drift Detected (retry ${fidelityRetries}/${MAX_FIDELITY_RETRIES})`,
12553
- "",
12554
- `A previous create-story dispatch for this story produced an artifact that omitted ${fidelity.missing.length} of ${namedPaths.length} named files/paths from the source AC. The previous artifact has been moved to \`${stalePath}\` and you are being re-dispatched to produce a corrected artifact.`,
12555
- "",
12556
- "**Named paths from the source AC that were missing in the prior dispatch:**",
12557
- "",
12558
- ...fidelity.missing.map((p) => `- \`${p}\``),
12559
- "",
12560
- "These names appear in the Epic Scope above and are part of the source AC contract. Preserve them verbatim in your rendered artifact (file lists, paths, named identifiers in Tasks/Subtasks). Do not substitute alternative names from training priors. If the source AC says `adjacency-store.ts`, the rendered story file says `adjacency-store.ts` — not `LinkStore`, `AdjacencyManager`, or any other re-conceptualization."
12561
- ].join("\n");
12562
- storyFilePath = void 0;
12563
- continue;
12564
- } catch (renameErr) {
12565
- logger$24.warn({
12566
- storyKey,
12567
- err: renameErr,
12568
- stalePath
12569
- }, "failed to rename drifting artifact for retry; proceeding with current artifact");
12570
- }
12571
- } else {
12572
- const errMsg = `create-story output drifted from source AC after ${MAX_FIDELITY_RETRIES} retries; ${fidelity.missing.length} of ${namedPaths.length} named paths missing: ` + fidelity.missing.join(", ");
12573
- logger$24.error({
12612
+ const storyContentForFidelity = await readFile$1(storyFilePath, "utf-8");
12613
+ const pathFidelity = namedPaths.length >= MIN_NAMED_PATHS_FOR_FIDELITY_GATE ? computeStoryFileFidelity(storyContentForFidelity, namedPaths) : null;
12614
+ const clauseFidelity = computeClauseFidelity(storyContentForFidelity, fidelitySourceContent);
12615
+ const pathDrift = pathFidelity?.drift ?? 0;
12616
+ const clauseDrift = clauseFidelity.drift;
12617
+ const overallDrift = Math.max(pathDrift, clauseDrift);
12618
+ logger$24.debug({
12619
+ storyKey,
12620
+ pathDrift,
12621
+ clauseDrift,
12622
+ overallDrift,
12623
+ pathMissing: pathFidelity?.missing ?? [],
12624
+ numericMismatches: clauseFidelity.numericMismatches,
12625
+ clauseRatio: clauseFidelity.clauseRatio
12626
+ }, "create-story output fidelity check (path + clause)");
12627
+ if (overallDrift > FIDELITY_DRIFT_THRESHOLD) {
12628
+ fidelityRetries++;
12629
+ if (fidelityRetries <= MAX_FIDELITY_RETRIES) {
12630
+ const stalePath = storyFilePath.replace(/\.md$/, `.stale-${Date.now()}.md`);
12631
+ try {
12632
+ renameSync(storyFilePath, stalePath);
12633
+ const driftPct = Math.round(overallDrift * 100);
12634
+ const pathMissing = pathFidelity?.missing ?? [];
12635
+ const numericMismatches = clauseFidelity.numericMismatches;
12636
+ const reasons = [];
12637
+ if (pathMissing.length > 0) reasons.push(`${pathMissing.length} named path(s) missing`);
12638
+ if (numericMismatches.length > 0) reasons.push(`${numericMismatches.length} numeric quantifier mismatch(es) (e.g., "${numericMismatches[0].noun}" source=${numericMismatches[0].sourceCount} rendered=${numericMismatches[0].renderedCount})`);
12639
+ if (clauseFidelity.clauseRatio < .7) reasons.push(`clause shortfall (rendered ${clauseFidelity.renderedClauseCount}/${clauseFidelity.sourceClauseCount} = ${Math.round(clauseFidelity.clauseRatio * 100)}%)`);
12640
+ logger$24.warn({
12574
12641
  storyKey,
12575
- drift: fidelity.drift,
12576
- missing: fidelity.missing,
12577
- namedPaths
12578
- }, errMsg);
12579
- endPhase(storyKey, "create-story");
12580
- updateStory(storyKey, {
12581
- phase: "ESCALATED",
12582
- error: errMsg,
12583
- completedAt: new Date().toISOString()
12584
- });
12585
- await writeStoryMetricsBestEffort(storyKey, "failed", 0);
12586
- await emitEscalation({
12642
+ pathDrift,
12643
+ clauseDrift,
12644
+ pathMissing,
12645
+ numericMismatches,
12646
+ clauseRatio: clauseFidelity.clauseRatio,
12647
+ retries: fidelityRetries,
12648
+ stalePath
12649
+ }, `create-story output drifted from source AC (${driftPct}% drift, ${reasons.join("; ")}); renamed to ${stalePath} and retrying (${fidelityRetries}/${MAX_FIDELITY_RETRIES})`);
12650
+ eventBus.emit("orchestrator:story-warn", {
12587
12651
  storyKey,
12588
- lastVerdict: "create-story-source-ac-drift",
12589
- reviewCycles: 0,
12590
- issues: [errMsg]
12652
+ msg: `create-story drift detected (${reasons.join("; ")}); retry ${fidelityRetries}/${MAX_FIDELITY_RETRIES}`
12591
12653
  });
12592
- await persistState();
12593
- return;
12654
+ const feedbackParts = [
12655
+ `### Prior Dispatch Drift Detected (retry ${fidelityRetries}/${MAX_FIDELITY_RETRIES})`,
12656
+ "",
12657
+ `A previous create-story dispatch produced an artifact that drifted from the source AC. The previous artifact has been moved to \`${stalePath}\`.`,
12658
+ "",
12659
+ "**Specific drift findings:**",
12660
+ ""
12661
+ ];
12662
+ if (pathMissing.length > 0) {
12663
+ feedbackParts.push("Named paths/files from source AC that were missing in the prior dispatch:");
12664
+ feedbackParts.push("");
12665
+ feedbackParts.push(...pathMissing.map((p) => `- \`${p}\``));
12666
+ feedbackParts.push("");
12667
+ }
12668
+ if (numericMismatches.length > 0) {
12669
+ feedbackParts.push("Numeric quantifiers from source AC that were reduced in the prior dispatch:");
12670
+ feedbackParts.push("");
12671
+ feedbackParts.push(...numericMismatches.map((m) => `- source AC says "**${m.sourceCount}** ${m.noun}"; rendered artifact says "${m.renderedCount}" — restore the original count and the named items`));
12672
+ feedbackParts.push("");
12673
+ }
12674
+ if (clauseFidelity.clauseRatio < .7) {
12675
+ feedbackParts.push(`The source AC has ${clauseFidelity.sourceClauseCount} behavioral clauses (Given/When/Then triples or numbered ACs); the prior rendered artifact had only ${clauseFidelity.renderedClauseCount}. You dropped clauses without authorization. Either preserve all source clauses verbatim, OR if the scope is genuinely too large for a single story, emit \`result: failure, error: source scope exceeds single-story capacity — split upstream\` per the prompt's Scope Cap Guidance — silently reducing scope is forbidden.`);
12676
+ feedbackParts.push("");
12677
+ }
12678
+ feedbackParts.push("Preserve the source AC contract verbatim: every named file/path, every numeric quantifier (`exactly N`, `all N`, `both`), and every behavioral clause. Do not substitute names from training priors. Do not silently reduce scope. If the scope cannot fit, emit `result: failure, error: source scope exceeds single-story capacity — split upstream` instead of partially rendering.");
12679
+ priorDriftFeedback = feedbackParts.join("\n");
12680
+ storyFilePath = void 0;
12681
+ continue;
12682
+ } catch (renameErr) {
12683
+ logger$24.warn({
12684
+ storyKey,
12685
+ err: renameErr,
12686
+ stalePath
12687
+ }, "failed to rename drifting artifact for retry; proceeding with current artifact");
12594
12688
  }
12689
+ } else {
12690
+ const pathMissing = pathFidelity?.missing ?? [];
12691
+ const numericMismatches = clauseFidelity.numericMismatches;
12692
+ const reasons = [];
12693
+ if (pathMissing.length > 0) reasons.push(`paths missing: ${pathMissing.join(", ")}`);
12694
+ if (numericMismatches.length > 0) reasons.push(`numeric mismatches: ${numericMismatches.map((m) => `${m.noun} (source=${m.sourceCount}, rendered=${m.renderedCount})`).join("; ")}`);
12695
+ if (clauseFidelity.clauseRatio < .7) reasons.push(`clause shortfall: source=${clauseFidelity.sourceClauseCount}, rendered=${clauseFidelity.renderedClauseCount}`);
12696
+ const errMsg = `create-story output drifted from source AC after ${MAX_FIDELITY_RETRIES} retries; ` + reasons.join("; ");
12697
+ logger$24.error({
12698
+ storyKey,
12699
+ pathDrift,
12700
+ clauseDrift,
12701
+ pathMissing,
12702
+ numericMismatches,
12703
+ clauseRatio: clauseFidelity.clauseRatio
12704
+ }, errMsg);
12705
+ endPhase(storyKey, "create-story");
12706
+ updateStory(storyKey, {
12707
+ phase: "ESCALATED",
12708
+ error: errMsg,
12709
+ completedAt: new Date().toISOString()
12710
+ });
12711
+ await writeStoryMetricsBestEffort(storyKey, "failed", 0);
12712
+ await emitEscalation({
12713
+ storyKey,
12714
+ lastVerdict: "create-story-source-ac-drift",
12715
+ reviewCycles: 0,
12716
+ issues: [errMsg]
12717
+ });
12718
+ await persistState();
12719
+ return;
12595
12720
  }
12596
12721
  }
12597
12722
  }
@@ -44331,4 +44456,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
44331
44456
 
44332
44457
  //#endregion
44333
44458
  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 };
44334
- //# sourceMappingURL=run-DrJWCS63.js.map
44459
+ //# sourceMappingURL=run-B3e4O0Rk.js.map
@@ -1,8 +1,8 @@
1
- import "./health-ZGa9E0D2.js";
1
+ import "./health-Cq8K_jrJ.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-CqtWS9wF.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-DrJWCS63.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-B3e4O0Rk.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.21",
3
+ "version": "0.20.23",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",