substrate-ai 0.20.7 → 0.20.10

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.
@@ -0,0 +1,4 @@
1
+ import { AdapterRegistry } from "./dist-CqtWS9wF.js";
2
+ import "./adapter-registry-DXLMTmfD.js";
3
+
4
+ export { AdapterRegistry };
package/dist/cli/index.js CHANGED
@@ -1,15 +1,15 @@
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-BIS34IYK.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-C6Up5GCr.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
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-srr3BfCc.js";
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-BAc1zfMQ.js";
8
- import "../errors-CSTQNabo.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-BcwaSYTg.js";
8
+ import "../errors-1uLGqnvr.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
11
11
  import "../version-manager-impl-BmOWu8ml.js";
12
- import { registerUpgradeCommand } from "../upgrade-iTr8nbbE.js";
12
+ import { registerUpgradeCommand } from "../upgrade-k15NGTno.js";
13
13
  import { Command } from "commander";
14
14
  import { fileURLToPath } from "url";
15
15
  import { dirname, join, resolve } from "path";
@@ -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-3-qy8XEI.js");
3670
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-C73H5CUZ.js");
3671
3671
  const substrateDirPath = join(projectRoot, ".substrate");
3672
3672
  const processInfo = inspectProcessTree$1({
3673
3673
  projectRoot,
@@ -3712,6 +3712,7 @@ async function runStatusAction(options) {
3712
3712
  } catch {}
3713
3713
  const verificationResult = manifestPerStoryState?.[row.story_key]?.verification_result;
3714
3714
  const verificationFindings = rollupFindingCounts(verificationResult);
3715
+ const verificationRan = verificationResult !== void 0 && verificationResult !== null;
3715
3716
  return {
3716
3717
  story_key: row.story_key,
3717
3718
  result: row.result,
@@ -3723,7 +3724,8 @@ async function runStatusAction(options) {
3723
3724
  },
3724
3725
  review_cycles: row.review_cycles ?? 0,
3725
3726
  dispatches: row.dispatches ?? 0,
3726
- verification_findings: verificationFindings
3727
+ verification_findings: verificationFindings,
3728
+ verification_ran: verificationRan
3727
3729
  };
3728
3730
  });
3729
3731
  let pipelineWallClockMs = 0;
@@ -4615,7 +4617,7 @@ function defaultSupervisorDeps() {
4615
4617
  if (cached === null) {
4616
4618
  const { AdapterRegistry: AR } = await import(
4617
4619
  /* @vite-ignore */
4618
- "../adapter-registry-CwjKI1sO.js"
4620
+ "../adapter-registry-N0Klnk7Y.js"
4619
4621
  );
4620
4622
  cached = new AR();
4621
4623
  await cached.discoverAndRegister();
@@ -5182,11 +5184,11 @@ async function runSupervisorAction(options, deps = {}) {
5182
5184
  try {
5183
5185
  const { createExperimenter } = await import(
5184
5186
  /* @vite-ignore */
5185
- "../experimenter-DaXUHfJO.js"
5187
+ "../experimenter-VEJHtfLh.js"
5186
5188
  );
5187
5189
  const { getLatestRun: getLatest } = await import(
5188
5190
  /* @vite-ignore */
5189
- "../decisions-Ca77UwMA.js"
5191
+ "../decisions-7UJcRETq.js"
5190
5192
  );
5191
5193
  const expAdapter = createDatabaseAdapter({
5192
5194
  backend: "auto",
@@ -5196,7 +5198,7 @@ async function runSupervisorAction(options, deps = {}) {
5196
5198
  await initSchema(expAdapter);
5197
5199
  const { runRunAction: runPipeline } = await import(
5198
5200
  /* @vite-ignore */
5199
- "../run-Dif8PJRd.js"
5201
+ "../run-CvCQpsy0.js"
5200
5202
  );
5201
5203
  const runStoryFn = async (opts) => {
5202
5204
  const exitCode = await runPipeline({
@@ -5728,7 +5730,7 @@ async function runMetricsAction(options) {
5728
5730
  const routingConfigPath = join(dbDir, "routing.yml");
5729
5731
  let routingConfig = null;
5730
5732
  if (existsSync$1(routingConfigPath)) try {
5731
- const { loadModelRoutingConfig } = await import("../routing-BX6ObE8S.js");
5733
+ const { loadModelRoutingConfig } = await import("../routing-Ccpb_syA.js");
5732
5734
  routingConfig = loadModelRoutingConfig(routingConfigPath);
5733
5735
  } catch {}
5734
5736
  if (routingConfig === null) routingConfig = {
@@ -5959,12 +5961,17 @@ async function runMetricsAction(options) {
5959
5961
  }
5960
5962
  } catch {}
5961
5963
  const findingCountsByStoryRun = new Map();
5964
+ const verificationRanByStoryRun = new Map();
5962
5965
  const uniqueRunIds = Array.from(new Set(storyMetrics.map((sm) => sm.run_id).filter((id) => id !== "")));
5963
5966
  for (const uniqueRunId of uniqueRunIds) try {
5964
5967
  const { manifest } = await resolveRunManifest(dbRoot, uniqueRunId);
5965
5968
  if (manifest === null) continue;
5966
5969
  const data = await manifest.read();
5967
- for (const [storyKey, entry] of Object.entries(data.per_story_state)) findingCountsByStoryRun.set(`${storyKey}:${uniqueRunId}`, rollupFindingCounts(entry.verification_result));
5970
+ for (const [storyKey, entry] of Object.entries(data.per_story_state)) {
5971
+ const key = `${storyKey}:${uniqueRunId}`;
5972
+ findingCountsByStoryRun.set(key, rollupFindingCounts(entry.verification_result));
5973
+ verificationRanByStoryRun.set(key, entry.verification_result !== void 0 && entry.verification_result !== null);
5974
+ }
5968
5975
  } catch {}
5969
5976
  let factoryRuns = [];
5970
5977
  try {
@@ -5980,7 +5987,8 @@ async function runMetricsAction(options) {
5980
5987
  }));
5981
5988
  const storyMetricsWithFindings = storyMetrics.map((sm) => ({
5982
5989
  ...sm,
5983
- verification_findings: findingCountsByStoryRun.get(`${sm.story_key}:${sm.run_id}`) ?? { ...ZERO_FINDING_COUNTS }
5990
+ verification_findings: findingCountsByStoryRun.get(`${sm.story_key}:${sm.run_id}`) ?? { ...ZERO_FINDING_COUNTS },
5991
+ verification_ran: verificationRanByStoryRun.get(`${sm.story_key}:${sm.run_id}`) ?? false
5984
5992
  }));
5985
5993
  const jsonPayload = {
5986
5994
  runs: runsWithBreakdown,
@@ -8995,8 +9003,8 @@ async function createProgram() {
8995
9003
  /** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
8996
9004
  function checkForUpdatesInBackground(currentVersion) {
8997
9005
  if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
8998
- import("../upgrade-Cm8B3cXL.js").then(async () => {
8999
- const { createVersionManager } = await import("../version-manager-impl-XiJHzIbR.js");
9006
+ import("../upgrade-CVaD7iUR.js").then(async () => {
9007
+ const { createVersionManager } = await import("../version-manager-impl-CEiZEsmi.js");
9000
9008
  const vm = createVersionManager();
9001
9009
  const result = await vm.checkForUpdates();
9002
9010
  if (result.updateAvailable) {
@@ -1,4 +1,4 @@
1
- import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-srr3BfCc.js";
1
+ import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-CqtWS9wF.js";
2
2
  import "./decisions-C0pz9Clx.js";
3
3
 
4
4
  export { getLatestRun };
@@ -4044,15 +4044,27 @@ async function aggregateTokenUsageForStory(adapter, runId, storyKey) {
4044
4044
  //#endregion
4045
4045
  //#region packages/core/dist/persistence/queries/retry-escalated.js
4046
4046
  /**
4047
+ * Pipeline-run statuses that make escalation-diagnosis decisions non-actionable.
4048
+ * Runs in these states are terminated (abandoned / stopped manually) and their
4049
+ * per-story escalations are historical noise — the work either shipped in a
4050
+ * later run or was never going to be retried in this run's lifetime.
4051
+ */
4052
+ const TERMINAL_RUN_STATUSES = ["failed", "stopped"];
4053
+ /**
4047
4054
  * Query the decision store for escalation-diagnosis decisions and classify
4048
4055
  * each story key as retryable or skipped.
4049
4056
  *
4050
4057
  * Key format in the DB: `{storyKey}:{runId}`
4051
4058
  *
4059
+ * Scoping:
4052
4060
  * - When `runId` is provided, only decisions whose key contains that runId
4053
- * are considered (AC5 scoping).
4054
- * - When `runId` is omitted, the runId of the last (most recently created)
4055
- * escalation-diagnosis decision is used as the default (AC1 defaulting).
4061
+ * are considered (AC5). The caller is explicit; no status filter is applied
4062
+ * so a user can still inspect diagnoses from a terminal run by naming it.
4063
+ * - When `runId` is omitted, the latest run whose `pipeline_runs.status` is
4064
+ * NOT terminal (`failed` / `stopped`) is used. Terminal runs are abandoned
4065
+ * or manually stopped — their per-story diagnoses are historical noise and
4066
+ * would generate false-positive retry proposals for work that either
4067
+ * shipped in a later run or will never be retried in that run's lifetime.
4056
4068
  *
4057
4069
  * @param adapter The database adapter
4058
4070
  * @param runId Optional run ID to scope the query
@@ -4083,7 +4095,13 @@ async function getRetryableEscalations(adapter, runId) {
4083
4095
  });
4084
4096
  }
4085
4097
  if (parsed.length === 0) return result;
4086
- const effectiveRunId = runId ?? parsed[parsed.length - 1].decisionRunId;
4098
+ let effectiveRunId;
4099
+ if (runId !== void 0) effectiveRunId = runId;
4100
+ else {
4101
+ const candidateRunId = await pickLatestNonTerminalRunId(adapter, parsed);
4102
+ if (candidateRunId === void 0) return result;
4103
+ effectiveRunId = candidateRunId;
4104
+ }
4087
4105
  const lastEntryByKey = new Map();
4088
4106
  for (const entry of parsed) {
4089
4107
  if (entry.decisionRunId !== effectiveRunId) continue;
@@ -4103,6 +4121,34 @@ async function getRetryableEscalations(adapter, runId) {
4103
4121
  }
4104
4122
  return result;
4105
4123
  }
4124
+ /**
4125
+ * Walk the parsed decision list newest-last (as supplied), map distinct runIds
4126
+ * to their pipeline-run status, and return the runId of the newest decision
4127
+ * whose run is NOT in a terminal status.
4128
+ *
4129
+ * Returns `undefined` when every referenced run is terminal or missing —
4130
+ * signalling "nothing retryable" to the caller.
4131
+ *
4132
+ * The adapter query is a single SELECT against `pipeline_runs` restricted to
4133
+ * the distinct runIds we actually care about; keeps the cost O(unique-runs)
4134
+ * rather than O(decisions).
4135
+ */
4136
+ async function pickLatestNonTerminalRunId(adapter, parsed) {
4137
+ const uniqueRunIds = Array.from(new Set(parsed.map((p) => p.decisionRunId)));
4138
+ if (uniqueRunIds.length === 0) return void 0;
4139
+ const statusByRunId = new Map();
4140
+ for (const id of uniqueRunIds) {
4141
+ const rows = await adapter.query("SELECT id, status FROM pipeline_runs WHERE id = ?", [id]);
4142
+ if (rows.length > 0 && rows[0]) statusByRunId.set(rows[0].id, rows[0].status);
4143
+ }
4144
+ for (let i = parsed.length - 1; i >= 0; i -= 1) {
4145
+ const entry = parsed[i];
4146
+ const status = statusByRunId.get(entry.decisionRunId);
4147
+ if (status !== void 0 && TERMINAL_RUN_STATUSES.includes(status)) continue;
4148
+ return entry.decisionRunId;
4149
+ }
4150
+ return void 0;
4151
+ }
4106
4152
 
4107
4153
  //#endregion
4108
4154
  //#region packages/core/dist/persistence/monitor-database.js
@@ -10894,4 +10940,4 @@ async function callLLM(params) {
10894
10940
 
10895
10941
  //#endregion
10896
10942
  export { ADVISORY_NOTES, AdapterRegistry, AdtError, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, Categorizer, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, ConsumerAnalyzer, CostTrackerConfigSchema, DEFAULT_CONFIG, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, DoltNotInstalled, DoltQueryError, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, GeminiCLIAdapter, GlobalSettingsSchema, IngestionServer, LEARNING_FINDING, LogTurnAnalyzer, ModelRoutingConfigSchema, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProviderPolicySchema, ProvidersSchema, Recommender, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TASK_TYPE_PHASE_MAP, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryConfigSchema, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, VersionManagerImpl, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, callLLM, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDatabaseAdapter as createDatabaseAdapter$1, createDecision, createDoltClient, createExperimenter, createPipelineRun, createRequirement, createVersionManager, detectInterfaceChanges, determineVerdict, getActiveDecisions, getAllCostEntriesFiltered, getArtifactByTypeForRun, getArtifactsByRun, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getModelTier, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRequirements, listRunMetrics, loadModelRoutingConfig, loadParentRunDecisions, registerArtifact, resolvePromptFile, supersedeDecision, tagRunAsBaseline, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics };
10897
- //# sourceMappingURL=dist-srr3BfCc.js.map
10943
+ //# sourceMappingURL=dist-CqtWS9wF.js.map
@@ -1,4 +1,4 @@
1
- import { AdtError } from "./dist-srr3BfCc.js";
1
+ import { AdtError } from "./dist-CqtWS9wF.js";
2
2
 
3
3
  //#region src/core/errors.ts
4
4
  /** Error thrown when task configuration is invalid */
@@ -71,4 +71,4 @@ var TaskGraphIncompatibleFormatError = class extends AdtError {
71
71
 
72
72
  //#endregion
73
73
  export { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError };
74
- //# sourceMappingURL=errors-CSTQNabo.js.map
74
+ //# sourceMappingURL=errors-1uLGqnvr.js.map
@@ -1,3 +1,3 @@
1
- import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-srr3BfCc.js";
1
+ import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-CqtWS9wF.js";
2
2
 
3
3
  export { createExperimenter };
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { DoltClient, DoltQueryError, LEARNING_FINDING, createDatabaseAdapter$1 as createDatabaseAdapter, createDecision, getDecisionsByCategory, getLatestRun, getPipelineRunById, initSchema } from "./dist-srr3BfCc.js";
2
+ import { DoltClient, DoltQueryError, LEARNING_FINDING, createDatabaseAdapter$1 as createDatabaseAdapter, createDecision, getDecisionsByCategory, getLatestRun, getPipelineRunById, initSchema } from "./dist-CqtWS9wF.js";
3
3
  import { createRequire } from "module";
4
4
  import { dirname, join } from "path";
5
5
  import { readFile } from "fs/promises";
@@ -4185,6 +4185,11 @@ var RunManifest = class RunManifest {
4185
4185
  baseDir;
4186
4186
  /** Optional Dolt adapter for degraded-mode fallback on read. */
4187
4187
  doltAdapter;
4188
+ /**
4189
+ * Serializes all write operations on this instance to prevent lost-update races.
4190
+ * Initialized to a resolved promise; each enqueued operation chains off the tail.
4191
+ */
4192
+ _writeChain = Promise.resolve();
4188
4193
  constructor(runId, baseDir = defaultBaseDir(), doltAdapter = null) {
4189
4194
  this.runId = runId;
4190
4195
  this.baseDir = baseDir;
@@ -4231,7 +4236,22 @@ var RunManifest = class RunManifest {
4231
4236
  await this.write(merged);
4232
4237
  }
4233
4238
  /**
4234
- * Atomically write the manifest to disk.
4239
+ * Append `fn` to the per-instance write chain so all write operations execute
4240
+ * strictly sequentially, preventing lost-update races on concurrent callers.
4241
+ *
4242
+ * The chain itself never rejects (errors are swallowed on the chain side);
4243
+ * the returned promise resolves or rejects exactly when `fn` settles, so
4244
+ * fire-and-forget `.catch()` callers still receive failure signals.
4245
+ */
4246
+ _enqueue(fn) {
4247
+ const next = this._writeChain.then(() => fn());
4248
+ this._writeChain = next.then(() => void 0, () => void 0);
4249
+ return next;
4250
+ }
4251
+ /**
4252
+ * Raw implementation of the atomic manifest write.
4253
+ * Must only be called from within an `_enqueue`-d function to maintain
4254
+ * the serialization invariant.
4235
4255
  *
4236
4256
  * Sequence:
4237
4257
  * 1. Auto-increment `generation`, set `updated_at`
@@ -4241,7 +4261,7 @@ var RunManifest = class RunManifest {
4241
4261
  * 5. If primary exists, copy to `.bak`
4242
4262
  * 6. Rename `.tmp` → primary path
4243
4263
  */
4244
- async write(data) {
4264
+ async _writeImpl(data) {
4245
4265
  let currentGeneration = 0;
4246
4266
  const existing = await tryReadFile(this.primaryPath);
4247
4267
  if (existing !== null) currentGeneration = existing.generation;
@@ -4267,6 +4287,15 @@ var RunManifest = class RunManifest {
4267
4287
  await promises.rename(tmp, this.primaryPath);
4268
4288
  }
4269
4289
  /**
4290
+ * Atomically write the manifest to disk.
4291
+ *
4292
+ * Enqueues the write via `_enqueue` so concurrent calls are serialized.
4293
+ * The returned promise resolves when this call's write completes.
4294
+ */
4295
+ async write(data) {
4296
+ return this._enqueue(() => this._writeImpl(data));
4297
+ }
4298
+ /**
4270
4299
  * Return a bound `RunManifest` instance without performing any file I/O.
4271
4300
  *
4272
4301
  * Use `open()` when you want to call instance methods (`read()`, `patchCLIFlags()`)
@@ -4280,13 +4309,9 @@ var RunManifest = class RunManifest {
4280
4309
  return new RunManifest(runId, baseDir, doltAdapter);
4281
4310
  }
4282
4311
  /**
4283
- * Read the current manifest (or create a minimal default), merge the provided
4284
- * CLI flags into `cli_flags`, and write the result atomically.
4285
- *
4286
- * Non-fatal: callers should wrap in try/catch and log a warning on failure.
4287
- * The pipeline must not abort if manifest write fails.
4312
+ * Raw implementation must only be called from within `_enqueue`.
4288
4313
  */
4289
- async patchCLIFlags(flags) {
4314
+ async _patchCLIFlagsImpl(flags) {
4290
4315
  let existingData;
4291
4316
  try {
4292
4317
  const read = await RunManifest.read(this.runId, this.baseDir, this.doltAdapter);
@@ -4310,7 +4335,7 @@ var RunManifest = class RunManifest {
4310
4335
  created_at: now
4311
4336
  };
4312
4337
  }
4313
- await this.write({
4338
+ await this._writeImpl({
4314
4339
  ...existingData,
4315
4340
  cli_flags: {
4316
4341
  ...existingData.cli_flags,
@@ -4319,21 +4344,20 @@ var RunManifest = class RunManifest {
4319
4344
  });
4320
4345
  }
4321
4346
  /**
4322
- * Atomically upsert the per-story lifecycle state for a single story key.
4323
- *
4324
- * Reads the current manifest (or creates a minimal default if absent),
4325
- * shallowly merges `updates` into `per_story_state[storyKey]`, and writes
4326
- * the result atomically via a single `write()` call.
4327
- *
4328
- * Fields not included in `updates` on an existing entry are preserved unchanged.
4329
- *
4330
- * Non-fatal: callers MUST wrap in `.catch((err) => logger.warn(...))`.
4331
- * The pipeline must never abort due to a manifest write failure.
4347
+ * Read the current manifest (or create a minimal default), merge the provided
4348
+ * CLI flags into `cli_flags`, and write the result atomically.
4332
4349
  *
4333
- * @param storyKey - Story key (e.g. '52-4')
4334
- * @param updates - Partial PerStoryState fields to merge
4350
+ * Enqueues the operation via `_enqueue` so concurrent calls are serialized.
4351
+ * Non-fatal: callers should wrap in try/catch and log a warning on failure.
4352
+ * The pipeline must not abort if manifest write fails.
4335
4353
  */
4336
- async patchStoryState(storyKey, updates) {
4354
+ async patchCLIFlags(flags) {
4355
+ return this._enqueue(() => this._patchCLIFlagsImpl(flags));
4356
+ }
4357
+ /**
4358
+ * Raw implementation — must only be called from within `_enqueue`.
4359
+ */
4360
+ async _patchStoryStateImpl(storyKey, updates) {
4337
4361
  let existingData;
4338
4362
  try {
4339
4363
  const read = await RunManifest.read(this.runId, this.baseDir, this.doltAdapter);
@@ -4365,7 +4389,7 @@ var RunManifest = class RunManifest {
4365
4389
  ...existing,
4366
4390
  ...updates
4367
4391
  };
4368
- await this.write({
4392
+ await this._writeImpl({
4369
4393
  ...existingData,
4370
4394
  per_story_state: {
4371
4395
  ...existingData.per_story_state,
@@ -4374,22 +4398,28 @@ var RunManifest = class RunManifest {
4374
4398
  });
4375
4399
  }
4376
4400
  /**
4377
- * Atomically append a recovery entry and update cost accumulation.
4401
+ * Atomically upsert the per-story lifecycle state for a single story key.
4378
4402
  *
4379
- * Reads the current manifest, appends `entry` to `recovery_history[]`,
4380
- * increments `cost_accumulation.per_story[entry.story_key]` by `entry.cost_usd`,
4381
- * increments `cost_accumulation.run_total` by `entry.cost_usd`, then writes
4382
- * atomically via a single `write()` call.
4403
+ * Reads the current manifest (or creates a minimal default if absent),
4404
+ * shallowly merges `updates` into `per_story_state[storyKey]`, and writes
4405
+ * the result atomically via a single `write()` call.
4406
+ *
4407
+ * Fields not included in `updates` on an existing entry are preserved unchanged.
4383
4408
  *
4409
+ * Enqueues the operation via `_enqueue` so concurrent calls are serialized.
4384
4410
  * Non-fatal: callers MUST wrap in `.catch((err) => logger.warn(...))`.
4385
4411
  * The pipeline must never abort due to a manifest write failure.
4386
4412
  *
4387
- * `entry.cost_usd` is the cost of this single retry attempt only (NOT cumulative).
4388
- * Cumulative per-story retry cost is tracked in `cost_accumulation.per_story`.
4389
- *
4390
- * @param entry - Recovery entry to append (attempt_number is 1-indexed)
4413
+ * @param storyKey - Story key (e.g. '52-4')
4414
+ * @param updates - Partial PerStoryState fields to merge
4391
4415
  */
4392
- async appendRecoveryEntry(entry) {
4416
+ async patchStoryState(storyKey, updates) {
4417
+ return this._enqueue(() => this._patchStoryStateImpl(storyKey, updates));
4418
+ }
4419
+ /**
4420
+ * Raw implementation — must only be called from within `_enqueue`.
4421
+ */
4422
+ async _appendRecoveryEntryImpl(entry) {
4393
4423
  let existingData;
4394
4424
  try {
4395
4425
  const read = await RunManifest.read(this.runId, this.baseDir, this.doltAdapter);
@@ -4425,7 +4455,27 @@ var RunManifest = class RunManifest {
4425
4455
  run_total: existingData.cost_accumulation.run_total + entry.cost_usd
4426
4456
  }
4427
4457
  };
4428
- await this.write(updated);
4458
+ await this._writeImpl(updated);
4459
+ }
4460
+ /**
4461
+ * Atomically append a recovery entry and update cost accumulation.
4462
+ *
4463
+ * Reads the current manifest, appends `entry` to `recovery_history[]`,
4464
+ * increments `cost_accumulation.per_story[entry.story_key]` by `entry.cost_usd`,
4465
+ * increments `cost_accumulation.run_total` by `entry.cost_usd`, then writes
4466
+ * atomically via a single `write()` call.
4467
+ *
4468
+ * Enqueues the operation via `_enqueue` so concurrent calls are serialized.
4469
+ * Non-fatal: callers MUST wrap in `.catch((err) => logger.warn(...))`.
4470
+ * The pipeline must never abort due to a manifest write failure.
4471
+ *
4472
+ * `entry.cost_usd` is the cost of this single retry attempt only (NOT cumulative).
4473
+ * Cumulative per-story retry cost is tracked in `cost_accumulation.per_story`.
4474
+ *
4475
+ * @param entry - Recovery entry to append (attempt_number is 1-indexed)
4476
+ */
4477
+ async appendRecoveryEntry(entry) {
4478
+ return this._enqueue(() => this._appendRecoveryEntryImpl(entry));
4429
4479
  }
4430
4480
  /**
4431
4481
  * Create a new manifest with `generation: 0` and write it.
@@ -5286,4 +5336,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
5286
5336
 
5287
5337
  //#endregion
5288
5338
  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 };
5289
- //# sourceMappingURL=health-BIS34IYK.js.map
5339
+ //# sourceMappingURL=health-C6Up5GCr.js.map
@@ -1,6 +1,6 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-BIS34IYK.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-C6Up5GCr.js";
2
2
  import "./logger-KeHncl-f.js";
3
- import "./dist-srr3BfCc.js";
3
+ import "./dist-CqtWS9wF.js";
4
4
  import "./decisions-C0pz9Clx.js";
5
5
 
6
6
  export { inspectProcessTree };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { childLogger, createLogger, logger } from "./logger-KeHncl-f.js";
2
2
  import { assertDefined, createEventBus, createTuiApp, deepClone, formatDuration, generateId, isPlainObject, isTuiCapable, printNonTtyWarning, sleep, withRetry } from "./helpers-CElYrONe.js";
3
- import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-srr3BfCc.js";
3
+ import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-CqtWS9wF.js";
4
4
  import "./adapter-registry-DXLMTmfD.js";
5
- import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-CSTQNabo.js";
5
+ import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-1uLGqnvr.js";
6
6
 
7
7
  //#region src/core/di.ts
8
8
  /**
@@ -1,4 +1,4 @@
1
- import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-srr3BfCc.js";
1
+ import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-CqtWS9wF.js";
2
2
  import "./routing-CcBOCuC9.js";
3
3
 
4
4
  export { loadModelRoutingConfig };
@@ -1,7 +1,7 @@
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-BIS34IYK.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-C6Up5GCr.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
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-srr3BfCc.js";
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";
5
5
  import { basename, dirname, extname, join } from "path";
6
6
  import { access, readFile, readdir, stat } from "fs/promises";
7
7
  import { EventEmitter } from "node:events";
@@ -10462,20 +10462,23 @@ var VerificationStore = class {
10462
10462
  * verification results survive process crashes.
10463
10463
  *
10464
10464
  * Design notes:
10465
- * - Non-fatal: the returned promise is wrapped in `.catch()` at the call site
10466
- * so a manifest write failure never aborts the pipeline.
10465
+ * - Non-fatal: the `.catch()` handler swallows any rejection and logs at warn.
10466
+ * - Returns a `Promise<void>` so callers can optionally `await` it to ensure
10467
+ * ordering (Story 57-2). Fire-and-forget callers that discard the return
10468
+ * value continue to compile and work correctly.
10467
10469
  * - Reuses the single RunManifest instance injected by the orchestrator to
10468
10470
  * avoid concurrent-write conflicts with the atomic-write lock.
10469
10471
  * - `runManifest` is optional (`undefined | null`) — callers from contexts
10470
- * where no manifest is configured pass `null` and this function is a no-op.
10472
+ * where no manifest is configured pass `null` and this function returns a
10473
+ * resolved promise (no-op).
10471
10474
  *
10472
10475
  * @param storyKey - Story key being verified (e.g. '52-7')
10473
10476
  * @param summary - VerificationSummary returned by VerificationPipeline.run()
10474
10477
  * @param runManifest - RunManifest instance to write to, or null/undefined to skip
10475
10478
  */
10476
10479
  function persistVerificationResult(storyKey, summary, runManifest) {
10477
- if (runManifest == null) return;
10478
- runManifest.patchStoryState(storyKey, { verification_result: summary }).catch((err) => _logger.warn({
10480
+ if (runManifest == null) return Promise.resolve();
10481
+ return runManifest.patchStoryState(storyKey, { verification_result: summary }).catch((err) => _logger.warn({
10479
10482
  err,
10480
10483
  storyKey
10481
10484
  }, "manifest verification_result write failed — pipeline continues"));
@@ -12072,21 +12075,17 @@ function createImplementationOrchestrator(deps) {
12072
12075
  phase: "IN_STORY_CREATION",
12073
12076
  result: createResult
12074
12077
  });
12075
- if (config.pipelineRunId !== void 0 && createResult.tokenUsage !== void 0) try {
12076
- addTokenUsage(db, config.pipelineRunId, {
12077
- phase: "create-story",
12078
- agent: "create-story",
12079
- input_tokens: createResult.tokenUsage.input,
12080
- output_tokens: createResult.tokenUsage.output,
12081
- cost_usd: estimateDispatchCost(createResult.tokenUsage.input, createResult.tokenUsage.output),
12082
- metadata: JSON.stringify({ storyKey })
12083
- });
12084
- } catch (tokenErr) {
12085
- logger$24.warn({
12086
- storyKey,
12087
- err: tokenErr
12088
- }, "Failed to record create-story token usage");
12089
- }
12078
+ if (config.pipelineRunId !== void 0 && createResult.tokenUsage !== void 0) Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
12079
+ phase: "create-story",
12080
+ agent: "create-story",
12081
+ input_tokens: createResult.tokenUsage.input,
12082
+ output_tokens: createResult.tokenUsage.output,
12083
+ cost_usd: estimateDispatchCost(createResult.tokenUsage.input, createResult.tokenUsage.output),
12084
+ metadata: JSON.stringify({ storyKey })
12085
+ })).catch((tokenErr) => logger$24.warn({
12086
+ storyKey,
12087
+ err: tokenErr
12088
+ }, "Failed to record create-story token usage"));
12090
12089
  await persistState();
12091
12090
  if (createResult.result === "failed") {
12092
12091
  const errMsg = createResult.error ?? "create-story failed";
@@ -12256,21 +12255,17 @@ function createImplementationOrchestrator(deps) {
12256
12255
  }, "Test planning failed — proceeding to dev-story without test plan");
12257
12256
  }
12258
12257
  endPhase(storyKey, "test-plan");
12259
- if (config.pipelineRunId !== void 0 && testPlanTokenUsage !== void 0) try {
12260
- addTokenUsage(db, config.pipelineRunId, {
12261
- phase: "test-plan",
12262
- agent: "test-plan",
12263
- input_tokens: testPlanTokenUsage.input,
12264
- output_tokens: testPlanTokenUsage.output,
12265
- cost_usd: estimateDispatchCost(testPlanTokenUsage.input, testPlanTokenUsage.output),
12266
- metadata: JSON.stringify({ storyKey })
12267
- });
12268
- } catch (tokenErr) {
12269
- logger$24.warn({
12270
- storyKey,
12271
- err: tokenErr
12272
- }, "Failed to record test-plan token usage");
12273
- }
12258
+ if (config.pipelineRunId !== void 0 && testPlanTokenUsage !== void 0) Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
12259
+ phase: "test-plan",
12260
+ agent: "test-plan",
12261
+ input_tokens: testPlanTokenUsage.input,
12262
+ output_tokens: testPlanTokenUsage.output,
12263
+ cost_usd: estimateDispatchCost(testPlanTokenUsage.input, testPlanTokenUsage.output),
12264
+ metadata: JSON.stringify({ storyKey })
12265
+ })).catch((tokenErr) => logger$24.warn({
12266
+ storyKey,
12267
+ err: tokenErr
12268
+ }, "Failed to record test-plan token usage"));
12274
12269
  eventBus.emit("orchestrator:story-phase-complete", {
12275
12270
  storyKey,
12276
12271
  phase: "IN_TEST_PLANNING",
@@ -12419,28 +12414,24 @@ function createImplementationOrchestrator(deps) {
12419
12414
  batchIndex: batch.batchIndex,
12420
12415
  files: batchFilesModified
12421
12416
  });
12422
- if (config.pipelineRunId !== void 0 && batchResult.tokenUsage !== void 0) try {
12423
- addTokenUsage(db, config.pipelineRunId, {
12424
- phase: "dev-story",
12425
- agent: `batch-${batch.batchIndex}`,
12426
- input_tokens: batchResult.tokenUsage.input,
12427
- output_tokens: batchResult.tokenUsage.output,
12428
- cost_usd: estimateDispatchCost(batchResult.tokenUsage.input, batchResult.tokenUsage.output),
12429
- metadata: JSON.stringify({
12430
- storyKey,
12431
- batchIndex: batch.batchIndex,
12432
- taskIds: batch.taskIds,
12433
- durationMs: batchDurationMs,
12434
- result: batchMetrics.result
12435
- })
12436
- });
12437
- } catch (tokenErr) {
12438
- logger$24.warn({
12417
+ if (config.pipelineRunId !== void 0 && batchResult.tokenUsage !== void 0) Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
12418
+ phase: "dev-story",
12419
+ agent: `batch-${batch.batchIndex}`,
12420
+ input_tokens: batchResult.tokenUsage.input,
12421
+ output_tokens: batchResult.tokenUsage.output,
12422
+ cost_usd: estimateDispatchCost(batchResult.tokenUsage.input, batchResult.tokenUsage.output),
12423
+ metadata: JSON.stringify({
12439
12424
  storyKey,
12440
12425
  batchIndex: batch.batchIndex,
12441
- err: tokenErr
12442
- }, "Failed to record batch token usage");
12443
- }
12426
+ taskIds: batch.taskIds,
12427
+ durationMs: batchDurationMs,
12428
+ result: batchMetrics.result
12429
+ })
12430
+ })).catch((tokenErr) => logger$24.warn({
12431
+ storyKey,
12432
+ batchIndex: batch.batchIndex,
12433
+ err: tokenErr
12434
+ }, "Failed to record batch token usage"));
12444
12435
  if (batchResult.tokenUsage?.output !== void 0) devOutputTokenCount = (devOutputTokenCount ?? 0) + batchResult.tokenUsage.output;
12445
12436
  if (batchResult.result === "failed") logger$24.warn({
12446
12437
  storyKey,
@@ -12478,21 +12469,17 @@ function createImplementationOrchestrator(deps) {
12478
12469
  });
12479
12470
  devFilesModified = devResult.files_modified ?? [];
12480
12471
  devOutputTokenCount = devResult.tokenUsage?.output ?? void 0;
12481
- if (config.pipelineRunId !== void 0 && devResult.tokenUsage !== void 0) try {
12482
- addTokenUsage(db, config.pipelineRunId, {
12483
- phase: "dev-story",
12484
- agent: "dev-story",
12485
- input_tokens: devResult.tokenUsage.input,
12486
- output_tokens: devResult.tokenUsage.output,
12487
- cost_usd: estimateDispatchCost(devResult.tokenUsage.input, devResult.tokenUsage.output),
12488
- metadata: JSON.stringify({ storyKey })
12489
- });
12490
- } catch (tokenErr) {
12491
- logger$24.warn({
12492
- storyKey,
12493
- err: tokenErr
12494
- }, "Failed to record dev-story token usage");
12495
- }
12472
+ if (config.pipelineRunId !== void 0 && devResult.tokenUsage !== void 0) Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
12473
+ phase: "dev-story",
12474
+ agent: "dev-story",
12475
+ input_tokens: devResult.tokenUsage.input,
12476
+ output_tokens: devResult.tokenUsage.output,
12477
+ cost_usd: estimateDispatchCost(devResult.tokenUsage.input, devResult.tokenUsage.output),
12478
+ metadata: JSON.stringify({ storyKey })
12479
+ })).catch((tokenErr) => logger$24.warn({
12480
+ storyKey,
12481
+ err: tokenErr
12482
+ }, "Failed to record dev-story token usage"));
12496
12483
  eventBus.emit("orchestrator:story-phase-complete", {
12497
12484
  storyKey,
12498
12485
  phase: "IN_DEV",
@@ -13151,23 +13138,22 @@ function createImplementationOrchestrator(deps) {
13151
13138
  ...baselineHeadSha ? { baselineCommit: baselineHeadSha } : {}
13152
13139
  });
13153
13140
  }
13154
- if (config.pipelineRunId !== void 0 && reviewResult.tokenUsage !== void 0) try {
13155
- addTokenUsage(db, config.pipelineRunId, {
13141
+ if (config.pipelineRunId !== void 0 && reviewResult?.tokenUsage !== void 0) {
13142
+ const reviewTokens = reviewResult.tokenUsage;
13143
+ Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
13156
13144
  phase: "code-review",
13157
13145
  agent: useBatchedReview ? "code-review-batched" : "code-review",
13158
- input_tokens: reviewResult.tokenUsage.input,
13159
- output_tokens: reviewResult.tokenUsage.output,
13160
- cost_usd: estimateDispatchCost(reviewResult.tokenUsage.input, reviewResult.tokenUsage.output),
13146
+ input_tokens: reviewTokens.input,
13147
+ output_tokens: reviewTokens.output,
13148
+ cost_usd: estimateDispatchCost(reviewTokens.input, reviewTokens.output),
13161
13149
  metadata: JSON.stringify({
13162
13150
  storyKey,
13163
13151
  reviewCycle: reviewCycles
13164
13152
  })
13165
- });
13166
- } catch (tokenErr) {
13167
- logger$24.warn({
13153
+ })).catch((tokenErr) => logger$24.warn({
13168
13154
  storyKey,
13169
13155
  err: tokenErr
13170
- }, "Failed to record code-review token usage");
13156
+ }, "Failed to record code-review token usage"));
13171
13157
  }
13172
13158
  const isPhantomReview = reviewResult.dispatchFailed === true || reviewResult.verdict !== "SHIP_IT" && reviewResult.verdict !== "LGTM_WITH_NOTES" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
13173
13159
  if (isPhantomReview && !timeoutRetried) {
@@ -13282,7 +13268,7 @@ function createImplementationOrchestrator(deps) {
13282
13268
  });
13283
13269
  const verifSummary = await verificationPipeline.run(verifContext, "A");
13284
13270
  verificationStore.set(storyKey, verifSummary);
13285
- persistVerificationResult(storyKey, verifSummary, runManifest);
13271
+ await persistVerificationResult(storyKey, verifSummary, runManifest);
13286
13272
  if (verifSummary.status === "fail") {
13287
13273
  updateStory(storyKey, {
13288
13274
  phase: "VERIFICATION_FAILED",
@@ -13301,6 +13287,12 @@ function createImplementationOrchestrator(deps) {
13301
13287
  phase: "COMPLETE",
13302
13288
  completedAt: new Date().toISOString()
13303
13289
  });
13290
+ if (config.skipVerification !== true && runManifest != null) Promise.resolve().then(() => runManifest.read()).then((manifest) => {
13291
+ if (manifest?.per_story_state?.[storyKey]?.verification_result == null) logger$24.warn({
13292
+ storyKey,
13293
+ category: "verification-result-missing"
13294
+ }, "post-COMPLETE invariant: verification_result absent in manifest");
13295
+ }).catch(() => {});
13304
13296
  const completedReviewCycles = reviewCycles + 1;
13305
13297
  await writeStoryMetricsBestEffort(storyKey, verdict, completedReviewCycles);
13306
13298
  await writeStoryOutcomeBestEffort(storyKey, "complete", completedReviewCycles);
@@ -13540,7 +13532,7 @@ function createImplementationOrchestrator(deps) {
13540
13532
  });
13541
13533
  const verifSummary = await verificationPipeline.run(verifContext, "A");
13542
13534
  verificationStore.set(storyKey, verifSummary);
13543
- persistVerificationResult(storyKey, verifSummary, runManifest);
13535
+ await persistVerificationResult(storyKey, verifSummary, runManifest);
13544
13536
  if (verifSummary.status === "fail") {
13545
13537
  updateStory(storyKey, {
13546
13538
  phase: "VERIFICATION_FAILED",
@@ -13568,6 +13560,12 @@ function createImplementationOrchestrator(deps) {
13568
13560
  reviewCycles: finalReviewCycles,
13569
13561
  completedAt: new Date().toISOString()
13570
13562
  });
13563
+ if (config.skipVerification !== true && runManifest != null) Promise.resolve().then(() => runManifest.read()).then((manifest) => {
13564
+ if (manifest?.per_story_state?.[storyKey]?.verification_result == null) logger$24.warn({
13565
+ storyKey,
13566
+ category: "verification-result-missing"
13567
+ }, "post-COMPLETE invariant: verification_result absent in manifest");
13568
+ }).catch(() => {});
13571
13569
  await writeStoryMetricsBestEffort(storyKey, verdict, finalReviewCycles);
13572
13570
  await writeStoryOutcomeBestEffort(storyKey, "complete", finalReviewCycles);
13573
13571
  eventBus.emit("orchestrator:story-complete", {
@@ -18477,7 +18475,8 @@ async function buildRunReportFromManifest(runId, runsDir, adapter, opts) {
18477
18475
  ...vr !== void 0 && {
18478
18476
  verificationStatus: vr.status,
18479
18477
  verificationChecks: vr.checks
18480
- }
18478
+ },
18479
+ verification_ran: state.verification_result !== void 0 && state.verification_result !== null
18481
18480
  });
18482
18481
  }
18483
18482
  const escalationFindings = Object.values(escalationDiagnoses).map((d) => d.finding);
@@ -18608,6 +18607,7 @@ async function buildRunReport(adapter, runId, opts) {
18608
18607
  verificationStatus: vr.status,
18609
18608
  verificationChecks: vr.checks
18610
18609
  },
18610
+ verification_ran: vr !== void 0,
18611
18611
  ...qualityScore !== void 0 && { qualityScore },
18612
18612
  ...s$1.primary_agent_id && { agentId: s$1.primary_agent_id },
18613
18613
  ...s$1.primary_model && { model: s$1.primary_model },
@@ -43840,4 +43840,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
43840
43840
 
43841
43841
  //#endregion
43842
43842
  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 };
43843
- //# sourceMappingURL=run-BAc1zfMQ.js.map
43843
+ //# sourceMappingURL=run-BcwaSYTg.js.map
@@ -1,8 +1,8 @@
1
- import "./health-BIS34IYK.js";
1
+ import "./health-C6Up5GCr.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
- import "./dist-srr3BfCc.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-BAc1zfMQ.js";
4
+ import "./dist-CqtWS9wF.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-BcwaSYTg.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
@@ -1,5 +1,5 @@
1
- import "./dist-srr3BfCc.js";
1
+ import "./dist-CqtWS9wF.js";
2
2
  import "./version-manager-impl-BmOWu8ml.js";
3
- import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-iTr8nbbE.js";
3
+ import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-k15NGTno.js";
4
4
 
5
5
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
@@ -1,4 +1,4 @@
1
- import { createVersionManager } from "./dist-srr3BfCc.js";
1
+ import { createVersionManager } from "./dist-CqtWS9wF.js";
2
2
  import { execSync, spawn } from "child_process";
3
3
  import * as readline from "readline";
4
4
 
@@ -123,4 +123,4 @@ function registerUpgradeCommand(program) {
123
123
 
124
124
  //#endregion
125
125
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
126
- //# sourceMappingURL=upgrade-iTr8nbbE.js.map
126
+ //# sourceMappingURL=upgrade-k15NGTno.js.map
@@ -1,4 +1,4 @@
1
- import { VersionManagerImpl, createVersionManager } from "./dist-srr3BfCc.js";
1
+ import { VersionManagerImpl, createVersionManager } from "./dist-CqtWS9wF.js";
2
2
  import "./version-manager-impl-BmOWu8ml.js";
3
3
 
4
4
  export { createVersionManager };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.7",
3
+ "version": "0.20.10",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,15 +32,16 @@ a different story from the epic scope. The story key, title, and core scope are
32
32
  ## Instructions
33
33
 
34
34
  1. **Use the Story Definition as your primary input** — it specifies exactly what this story builds. The epic scope provides surrounding context only.
35
- 2. **Apply architecture constraints** every constraint listed above is mandatory (file paths, import style, test framework, etc.)
36
- 3. **Use previous dev notes** as guardrails don't repeat mistakes, build on patterns that worked
37
- 4. **Fill out the story template** with:
35
+ 2. **Acceptance-criteria text from the Story Definition is read-only input.** Any AC clause from the source that contains `MUST`, `MUST NOT`, `SHALL`, `SHALL NOT`, enumerated file or directory paths, or explicit technology / storage / data-format choices (e.g., "plain JSON file", "LanceDB table", "systemd unit") must appear in the rendered story artifact's Acceptance Criteria section **verbatim**. Never soften, abstract, or paraphrase a hard clause — reshaping "MUST remove X" into "consider deprecating X" silently strips the requirement and ships code that violates its own spec. When in doubt, copy the source clause literally and add any clarifying BDD phrasing alongside it, not in place of it.
36
+ 3. **Apply architecture constraints** every constraint listed above is mandatory (file paths, import style, test framework, etc.)
37
+ 4. **Use previous dev notes** as guardrails — don't repeat mistakes, build on patterns that worked
38
+ 5. **Fill out the story template** with:
38
39
  - A clear user story (As a / I want / So that)
39
- - Acceptance criteria in BDD Given/When/Then format (minimum 3, maximum 8)
40
+ - Acceptance criteria: preserve the hard-clause text from the Story Definition verbatim. BDD Given/When/Then phrasing is **optional** — permitted for behavior-oriented criteria where it adds clarity, not mandatory. Never let BDD reshape a MUST / MUST NOT / SHALL clause; copy those clauses literally and, if BDD adds clarity, append the Given/When/Then alongside the original clause rather than replacing it. Aim for the same number of ACs as the source — do not condense clauses into fewer items to hit a target count.
40
41
  - Concrete tasks broken into 2–4 hour subtasks, each tied to specific ACs
41
42
  - Dev Notes with file paths, import patterns, testing requirements
42
- 5. **Apply the scope cap** — see Scope Cap Guidance below
43
- 6. **Write the story file** to: `_bmad-output/implementation-artifacts/{{story_key}}-<kebab-title>.md`
43
+ 6. **Apply the scope cap** — see Scope Cap Guidance below
44
+ 7. **Write the story file** to: `_bmad-output/implementation-artifacts/{{story_key}}-<kebab-title>.md`
44
45
  - Do NOT add a `Status:` field to the story file — story status is managed exclusively by the Dolt work graph (`wg_stories` table)
45
46
  - Dev Agent Record section must be present but left blank (to be filled by dev agent)
46
47
 
@@ -66,7 +67,9 @@ Use this exact format for each item:
66
67
 
67
68
  ## Runtime Verification Guidance
68
69
 
69
- **Decide whether this story's artifact is runtime-dependent.** An artifact is runtime-dependent if correctness depends on execution systemd units, container definitions (Podman Quadlet, Docker Compose), install scripts, migration runners, anything whose behavior is only observable by running it against a real host or ephemeral sandbox.
70
+ **If the Story Definition already contains a `## Runtime Probes` section, transfer it verbatim** including every probe entry, YAML fenced block, and surrounding prose into the rendered story artifact. Do not independently re-evaluate whether the story is runtime-dependent; the epic author already decided when they authored probes in the source. Adding, removing, renaming, or reshaping a source-declared probe silently subverts the author's runtime contract.
71
+
72
+ **If the Story Definition has no `## Runtime Probes` section, decide whether this story's artifact is runtime-dependent.** An artifact is runtime-dependent if correctness depends on execution — systemd units, container definitions (Podman Quadlet, Docker Compose), install scripts, migration runners, anything whose behavior is only observable by running it against a real host or ephemeral sandbox.
70
73
 
71
74
  If the artifact is runtime-dependent, add a `## Runtime Probes` section to the story file. Each probe is a short shell command whose exit status answers "does this artifact actually work?".
72
75
 
@@ -156,11 +159,13 @@ Treat the probes you draft as a **first pass** the human author will refine. Pro
156
159
 
157
160
  ## Scope Cap Guidance
158
161
 
159
- **Aim for 6-7 acceptance criteria and 7-8 tasks per story.**
162
+ **Aim for 6-7 acceptance criteria and 7-8 tasks per story** when you are authoring ACs from scratch.
160
163
 
161
164
  Each story will be implemented by an AI agent in a single pass. Stories with more than 7 ACs tend to exceed agent capabilities and require decomposition, adding latency and complexity to the pipeline.
162
165
 
163
- If the scope requires more than 7 ACs, split into multiple sequential stories (e.g., `7-1a: Core Setup`, `7-1b: Advanced Features`). Splitting is preferable to cramming too much scope into a single story.
166
+ **The scope cap does NOT license condensing source ACs.** If the Story Definition supplies more ACs than the guidance target, preserve them all verbatim never collapse hard clauses (MUST / MUST NOT / SHALL / enumerated paths) into fewer items just to hit a count. If the source scope is too large for a single story, surface that as a failure (`result: failure`, `error: source scope exceeds single-story capacity — split upstream`) rather than silently dropping ACs.
167
+
168
+ If the scope *you are authoring from scratch* requires more than 7 ACs, split into multiple sequential stories (e.g., `7-1a: Core Setup`, `7-1b: Advanced Features`). Splitting is preferable to cramming too much scope into a single story.
164
169
 
165
170
  This is guidance, not enforcement — if the scope genuinely fits in a slightly larger story, use your judgment. The goal is to avoid stories that will predictably fail during implementation.
166
171
 
@@ -1,4 +0,0 @@
1
- import { AdapterRegistry } from "./dist-srr3BfCc.js";
2
- import "./adapter-registry-DXLMTmfD.js";
3
-
4
- export { AdapterRegistry };