substrate-ai 0.20.8 → 0.20.12

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-DXW-E2vb.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-0jqPFBEL.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-Bz3dW9Wn.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-B6qbsy-F.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-Q8-69N9P.js");
3670
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-C401hYzi.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;
@@ -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-C3kaptjF.js"
5201
+ "../run-DEyd1p7U.js"
5200
5202
  );
5201
5203
  const runStoryFn = async (opts) => {
5202
5204
  const exitCode = await runPipeline({
@@ -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,
@@ -3442,21 +3442,52 @@ const RuntimeProbeListSchema = z.array(RuntimeProbeSchema);
3442
3442
  //#endregion
3443
3443
  //#region packages/sdlc/dist/verification/probes/parser.js
3444
3444
  const SECTION_HEADING = /^##\s+Runtime\s+Probes\s*$/i;
3445
+ const FENCE_DELIMITER = /^\s*```/;
3445
3446
  /**
3446
3447
  * Return the raw text of the story's `## Runtime Probes` section (excluding
3447
3448
  * the heading line itself), or `undefined` if the section is not present.
3448
3449
  *
3449
3450
  * The section ends at the next `##` heading or end-of-file. Sub-headings
3450
3451
  * (`###`, `####`) remain part of the section body.
3452
+ *
3453
+ * Story 58-4: the scan tracks code-fence depth so a `## Runtime Probes`
3454
+ * heading that appears *inside* an outer ``` block is ignored. Stories that
3455
+ * DOCUMENT probes in prose — regression fixtures, how-to-author docs, the
3456
+ * Epic 58 e2e test spec — contain illustrative `## Runtime Probes` examples
3457
+ * inside outer fences. Without fence-awareness the parser matches those
3458
+ * illustrations as the story's own section, fails to find a terminated
3459
+ * yaml block (the inner fences are typically escaped), and emits a spurious
3460
+ * `runtime-probe-parse-error`. Hit live during the Epic 58 substrate
3461
+ * dispatch on 58-3's artifact.
3451
3462
  */
3452
3463
  function extractRuntimeProbesSection(storyContent) {
3453
3464
  const lines = storyContent.split(/\r?\n/);
3454
- const start = lines.findIndex((line) => SECTION_HEADING.test(line.trim()));
3465
+ let inCodeFence = false;
3466
+ let start = -1;
3467
+ for (let i = 0; i < lines.length; i += 1) {
3468
+ const line = lines[i] ?? "";
3469
+ if (FENCE_DELIMITER.test(line)) {
3470
+ inCodeFence = !inCodeFence;
3471
+ continue;
3472
+ }
3473
+ if (!inCodeFence && SECTION_HEADING.test(line.trim())) {
3474
+ start = i;
3475
+ break;
3476
+ }
3477
+ }
3455
3478
  if (start === -1) return void 0;
3456
3479
  let end = lines.length;
3457
- for (let i = start + 1; i < lines.length; i += 1) if (/^##\s+\S/.test(lines[i] ?? "")) {
3458
- end = i;
3459
- break;
3480
+ inCodeFence = false;
3481
+ for (let i = start + 1; i < lines.length; i += 1) {
3482
+ const line = lines[i] ?? "";
3483
+ if (FENCE_DELIMITER.test(line)) {
3484
+ inCodeFence = !inCodeFence;
3485
+ continue;
3486
+ }
3487
+ if (!inCodeFence && /^##\s+\S/.test(line)) {
3488
+ end = i;
3489
+ break;
3490
+ }
3460
3491
  }
3461
3492
  return lines.slice(start + 1, end).join("\n");
3462
3493
  }
@@ -3732,6 +3763,113 @@ var RuntimeProbeCheck = class {
3732
3763
  }
3733
3764
  };
3734
3765
 
3766
+ //#endregion
3767
+ //#region packages/sdlc/dist/verification/source-ac-fidelity-check.js
3768
+ /**
3769
+ * Extract the story's section from the full epic content.
3770
+ *
3771
+ * Uses the same heading pattern as `isImplicitlyCovered` in the monolith:
3772
+ * `### Story <storyKey>:` or `### Story <storyKey> ` or `### Story <storyKey>\n`
3773
+ *
3774
+ * Returns the extracted section text (from the heading match through to the
3775
+ * next `### Story` heading or end of file), or the full content if no
3776
+ * matching heading is found.
3777
+ */
3778
+ function extractStorySection(epicContent, storyKey) {
3779
+ const escapedKey = storyKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3780
+ const headingPattern = new RegExp(`^###\\s+Story\\s+${escapedKey}[:\\s]`, "m");
3781
+ const match = headingPattern.exec(epicContent);
3782
+ if (!match) return epicContent;
3783
+ const start = match.index;
3784
+ const nextHeading = /\n### Story /m.exec(epicContent.slice(start + 1));
3785
+ if (nextHeading) return epicContent.slice(start, start + 1 + nextHeading.index);
3786
+ return epicContent.slice(start);
3787
+ }
3788
+ /**
3789
+ * Extract hard clauses from a story section of an epic file.
3790
+ *
3791
+ * Hard clauses:
3792
+ * 1. Lines containing MUST NOT / MUST / SHALL NOT / SHALL as standalone keywords (case-sensitive)
3793
+ * 2. Backtick-wrapped paths with at least one `/` (excludes bare filenames)
3794
+ * 3. The presence of `## Runtime Probes` heading followed by a fenced yaml block
3795
+ * (represented as a single "runtime-probes-section" clause)
3796
+ */
3797
+ function extractHardClauses(sectionContent) {
3798
+ const clauses = [];
3799
+ const mustPattern = /\b(MUST NOT|MUST|SHALL NOT|SHALL)\b/;
3800
+ const lines = sectionContent.split("\n");
3801
+ for (const line of lines) {
3802
+ const match = mustPattern.exec(line);
3803
+ if (match) {
3804
+ const keyword = match[1];
3805
+ clauses.push({
3806
+ type: keyword,
3807
+ text: line.trim()
3808
+ });
3809
+ }
3810
+ }
3811
+ const pathPattern = /`([a-zA-Z0-9_./-]+\/[a-zA-Z0-9_./-]+)`/g;
3812
+ let pathMatch;
3813
+ while ((pathMatch = pathPattern.exec(sectionContent)) !== null) clauses.push({
3814
+ type: "path",
3815
+ text: `\`${pathMatch[1]}\``
3816
+ });
3817
+ const probesPattern = /^##\s+Runtime Probes[\s\S]*?```yaml/m;
3818
+ if (probesPattern.test(sectionContent)) clauses.push({
3819
+ type: "runtime-probes-section",
3820
+ text: "## Runtime Probes"
3821
+ });
3822
+ return clauses;
3823
+ }
3824
+ var SourceAcFidelityCheck = class {
3825
+ name = "source-ac-fidelity";
3826
+ tier = "A";
3827
+ async run(context) {
3828
+ const start = Date.now();
3829
+ if (!context.sourceEpicContent) {
3830
+ const findings$1 = [{
3831
+ category: "source-ac-source-unavailable",
3832
+ severity: "warn",
3833
+ message: "source epic content unavailable — skipping fidelity check"
3834
+ }];
3835
+ return {
3836
+ status: "pass",
3837
+ details: renderFindings(findings$1),
3838
+ duration_ms: Date.now() - start,
3839
+ findings: findings$1
3840
+ };
3841
+ }
3842
+ const storySection = extractStorySection(context.sourceEpicContent, context.storyKey);
3843
+ const hardClauses = extractHardClauses(storySection);
3844
+ const findings = [];
3845
+ const storyContent = context.storyContent ?? "";
3846
+ for (const clause of hardClauses) if (clause.type === "runtime-probes-section") {
3847
+ if (!storyContent.includes("## Runtime Probes")) {
3848
+ const truncated = clause.text.length > 120 ? clause.text.slice(0, 120) : clause.text;
3849
+ findings.push({
3850
+ category: "source-ac-drift",
3851
+ severity: "error",
3852
+ message: `runtime-probes-section: "${truncated}" present in epics source but absent in story artifact`
3853
+ });
3854
+ }
3855
+ } else if (!storyContent.includes(clause.text)) {
3856
+ const truncated = clause.text.length > 120 ? clause.text.slice(0, 120) : clause.text;
3857
+ findings.push({
3858
+ category: "source-ac-drift",
3859
+ severity: "error",
3860
+ message: `${clause.type}: "${truncated}" present in epics source but absent in story artifact`
3861
+ });
3862
+ }
3863
+ const status = findings.some((f) => f.severity === "error") ? "fail" : "pass";
3864
+ return {
3865
+ status,
3866
+ details: findings.length > 0 ? renderFindings(findings) : `source-ac-fidelity: ${hardClauses.length} hard clause(s) verified — all present`,
3867
+ duration_ms: Date.now() - start,
3868
+ findings
3869
+ };
3870
+ }
3871
+ };
3872
+
3735
3873
  //#endregion
3736
3874
  //#region packages/sdlc/dist/verification/verification-pipeline.js
3737
3875
  /**
@@ -3839,12 +3977,14 @@ var VerificationPipeline = class {
3839
3977
  * Create a VerificationPipeline pre-loaded with the canonical check set.
3840
3978
  *
3841
3979
  * Canonical Tier A check order:
3842
- * 1. PhantomReviewCheck — story 51-2 (runs first: unreviewed stories skipped)
3843
- * 2. TrivialOutputCheck — story 51-3
3980
+ * 1. PhantomReviewCheck — story 51-2 (runs first: unreviewed stories skipped)
3981
+ * 2. TrivialOutputCheck — story 51-3
3844
3982
  * 3. AcceptanceCriteriaEvidenceCheck
3845
- * 4. BuildCheck — story 51-4
3846
- * 5. RuntimeProbeCheck — Epic 55 Phase 2: runtime behavior gate; runs last
3847
- * in Tier A because probes may depend on built artifacts
3983
+ * 4. BuildCheck — story 51-4
3984
+ * 5. RuntimeProbeCheck — Epic 55 Phase 2: runtime behavior gate; runs last
3985
+ * in Tier A because probes may depend on built artifacts
3986
+ * 6. SourceAcFidelityCheck — Story 58-2: cross-references rendered story artifact
3987
+ * against the source epic's hard clauses (MUST/SHALL/paths)
3848
3988
  *
3849
3989
  * @param bus Typed event bus for verification events.
3850
3990
  * @param config Optional config (used to forward threshold to TrivialOutputCheck).
@@ -3855,7 +3995,8 @@ function createDefaultVerificationPipeline(bus, config) {
3855
3995
  new TrivialOutputCheck(config),
3856
3996
  new AcceptanceCriteriaEvidenceCheck(),
3857
3997
  new BuildCheck(),
3858
- new RuntimeProbeCheck()
3998
+ new RuntimeProbeCheck(),
3999
+ new SourceAcFidelityCheck()
3859
4000
  ];
3860
4001
  return new VerificationPipeline(bus, checks);
3861
4002
  }
@@ -4185,6 +4326,11 @@ var RunManifest = class RunManifest {
4185
4326
  baseDir;
4186
4327
  /** Optional Dolt adapter for degraded-mode fallback on read. */
4187
4328
  doltAdapter;
4329
+ /**
4330
+ * Serializes all write operations on this instance to prevent lost-update races.
4331
+ * Initialized to a resolved promise; each enqueued operation chains off the tail.
4332
+ */
4333
+ _writeChain = Promise.resolve();
4188
4334
  constructor(runId, baseDir = defaultBaseDir(), doltAdapter = null) {
4189
4335
  this.runId = runId;
4190
4336
  this.baseDir = baseDir;
@@ -4231,7 +4377,22 @@ var RunManifest = class RunManifest {
4231
4377
  await this.write(merged);
4232
4378
  }
4233
4379
  /**
4234
- * Atomically write the manifest to disk.
4380
+ * Append `fn` to the per-instance write chain so all write operations execute
4381
+ * strictly sequentially, preventing lost-update races on concurrent callers.
4382
+ *
4383
+ * The chain itself never rejects (errors are swallowed on the chain side);
4384
+ * the returned promise resolves or rejects exactly when `fn` settles, so
4385
+ * fire-and-forget `.catch()` callers still receive failure signals.
4386
+ */
4387
+ _enqueue(fn) {
4388
+ const next = this._writeChain.then(() => fn());
4389
+ this._writeChain = next.then(() => void 0, () => void 0);
4390
+ return next;
4391
+ }
4392
+ /**
4393
+ * Raw implementation of the atomic manifest write.
4394
+ * Must only be called from within an `_enqueue`-d function to maintain
4395
+ * the serialization invariant.
4235
4396
  *
4236
4397
  * Sequence:
4237
4398
  * 1. Auto-increment `generation`, set `updated_at`
@@ -4241,7 +4402,7 @@ var RunManifest = class RunManifest {
4241
4402
  * 5. If primary exists, copy to `.bak`
4242
4403
  * 6. Rename `.tmp` → primary path
4243
4404
  */
4244
- async write(data) {
4405
+ async _writeImpl(data) {
4245
4406
  let currentGeneration = 0;
4246
4407
  const existing = await tryReadFile(this.primaryPath);
4247
4408
  if (existing !== null) currentGeneration = existing.generation;
@@ -4267,6 +4428,15 @@ var RunManifest = class RunManifest {
4267
4428
  await promises.rename(tmp, this.primaryPath);
4268
4429
  }
4269
4430
  /**
4431
+ * Atomically write the manifest to disk.
4432
+ *
4433
+ * Enqueues the write via `_enqueue` so concurrent calls are serialized.
4434
+ * The returned promise resolves when this call's write completes.
4435
+ */
4436
+ async write(data) {
4437
+ return this._enqueue(() => this._writeImpl(data));
4438
+ }
4439
+ /**
4270
4440
  * Return a bound `RunManifest` instance without performing any file I/O.
4271
4441
  *
4272
4442
  * Use `open()` when you want to call instance methods (`read()`, `patchCLIFlags()`)
@@ -4280,13 +4450,9 @@ var RunManifest = class RunManifest {
4280
4450
  return new RunManifest(runId, baseDir, doltAdapter);
4281
4451
  }
4282
4452
  /**
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.
4453
+ * Raw implementation must only be called from within `_enqueue`.
4288
4454
  */
4289
- async patchCLIFlags(flags) {
4455
+ async _patchCLIFlagsImpl(flags) {
4290
4456
  let existingData;
4291
4457
  try {
4292
4458
  const read = await RunManifest.read(this.runId, this.baseDir, this.doltAdapter);
@@ -4310,7 +4476,7 @@ var RunManifest = class RunManifest {
4310
4476
  created_at: now
4311
4477
  };
4312
4478
  }
4313
- await this.write({
4479
+ await this._writeImpl({
4314
4480
  ...existingData,
4315
4481
  cli_flags: {
4316
4482
  ...existingData.cli_flags,
@@ -4319,21 +4485,20 @@ var RunManifest = class RunManifest {
4319
4485
  });
4320
4486
  }
4321
4487
  /**
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.
4488
+ * Read the current manifest (or create a minimal default), merge the provided
4489
+ * CLI flags into `cli_flags`, and write the result atomically.
4332
4490
  *
4333
- * @param storyKey - Story key (e.g. '52-4')
4334
- * @param updates - Partial PerStoryState fields to merge
4491
+ * Enqueues the operation via `_enqueue` so concurrent calls are serialized.
4492
+ * Non-fatal: callers should wrap in try/catch and log a warning on failure.
4493
+ * The pipeline must not abort if manifest write fails.
4335
4494
  */
4336
- async patchStoryState(storyKey, updates) {
4495
+ async patchCLIFlags(flags) {
4496
+ return this._enqueue(() => this._patchCLIFlagsImpl(flags));
4497
+ }
4498
+ /**
4499
+ * Raw implementation — must only be called from within `_enqueue`.
4500
+ */
4501
+ async _patchStoryStateImpl(storyKey, updates) {
4337
4502
  let existingData;
4338
4503
  try {
4339
4504
  const read = await RunManifest.read(this.runId, this.baseDir, this.doltAdapter);
@@ -4365,7 +4530,7 @@ var RunManifest = class RunManifest {
4365
4530
  ...existing,
4366
4531
  ...updates
4367
4532
  };
4368
- await this.write({
4533
+ await this._writeImpl({
4369
4534
  ...existingData,
4370
4535
  per_story_state: {
4371
4536
  ...existingData.per_story_state,
@@ -4374,22 +4539,28 @@ var RunManifest = class RunManifest {
4374
4539
  });
4375
4540
  }
4376
4541
  /**
4377
- * Atomically append a recovery entry and update cost accumulation.
4542
+ * Atomically upsert the per-story lifecycle state for a single story key.
4378
4543
  *
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.
4544
+ * Reads the current manifest (or creates a minimal default if absent),
4545
+ * shallowly merges `updates` into `per_story_state[storyKey]`, and writes
4546
+ * the result atomically via a single `write()` call.
4547
+ *
4548
+ * Fields not included in `updates` on an existing entry are preserved unchanged.
4383
4549
  *
4550
+ * Enqueues the operation via `_enqueue` so concurrent calls are serialized.
4384
4551
  * Non-fatal: callers MUST wrap in `.catch((err) => logger.warn(...))`.
4385
4552
  * The pipeline must never abort due to a manifest write failure.
4386
4553
  *
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)
4554
+ * @param storyKey - Story key (e.g. '52-4')
4555
+ * @param updates - Partial PerStoryState fields to merge
4391
4556
  */
4392
- async appendRecoveryEntry(entry) {
4557
+ async patchStoryState(storyKey, updates) {
4558
+ return this._enqueue(() => this._patchStoryStateImpl(storyKey, updates));
4559
+ }
4560
+ /**
4561
+ * Raw implementation — must only be called from within `_enqueue`.
4562
+ */
4563
+ async _appendRecoveryEntryImpl(entry) {
4393
4564
  let existingData;
4394
4565
  try {
4395
4566
  const read = await RunManifest.read(this.runId, this.baseDir, this.doltAdapter);
@@ -4425,7 +4596,27 @@ var RunManifest = class RunManifest {
4425
4596
  run_total: existingData.cost_accumulation.run_total + entry.cost_usd
4426
4597
  }
4427
4598
  };
4428
- await this.write(updated);
4599
+ await this._writeImpl(updated);
4600
+ }
4601
+ /**
4602
+ * Atomically append a recovery entry and update cost accumulation.
4603
+ *
4604
+ * Reads the current manifest, appends `entry` to `recovery_history[]`,
4605
+ * increments `cost_accumulation.per_story[entry.story_key]` by `entry.cost_usd`,
4606
+ * increments `cost_accumulation.run_total` by `entry.cost_usd`, then writes
4607
+ * atomically via a single `write()` call.
4608
+ *
4609
+ * Enqueues the operation via `_enqueue` so concurrent calls are serialized.
4610
+ * Non-fatal: callers MUST wrap in `.catch((err) => logger.warn(...))`.
4611
+ * The pipeline must never abort due to a manifest write failure.
4612
+ *
4613
+ * `entry.cost_usd` is the cost of this single retry attempt only (NOT cumulative).
4614
+ * Cumulative per-story retry cost is tracked in `cost_accumulation.per_story`.
4615
+ *
4616
+ * @param entry - Recovery entry to append (attempt_number is 1-indexed)
4617
+ */
4618
+ async appendRecoveryEntry(entry) {
4619
+ return this._enqueue(() => this._appendRecoveryEntryImpl(entry));
4429
4620
  }
4430
4621
  /**
4431
4622
  * Create a new manifest with `generation: 0` and write it.
@@ -5286,4 +5477,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
5286
5477
 
5287
5478
  //#endregion
5288
5479
  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-DXW-E2vb.js.map
5480
+ //# sourceMappingURL=health-0jqPFBEL.js.map
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-DXW-E2vb.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-0jqPFBEL.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-DXW-E2vb.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-0jqPFBEL.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";
@@ -5911,13 +5911,14 @@ async function getImplementationDecisions(deps, pipelineRunId) {
5911
5911
  */
5912
5912
  function extractStorySection(shardContent, storyKey) {
5913
5913
  if (!shardContent || !storyKey) return null;
5914
- const escaped = storyKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5915
- const headingPattern = new RegExp(`(?:^#{2,4}\\s+Story\\s+${escaped}\\b|^Story\\s+${escaped}:|^\\*\\*${escaped}\\*\\*|^${escaped}:)`, "mi");
5914
+ const parts = storyKey.split(/[-._ ]/);
5915
+ const normalized = parts.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[-._ ]");
5916
+ const headingPattern = new RegExp(`(?:^#{2,4}\\s+Story\\s+${normalized}\\b|^Story\\s+${normalized}:|^\\*\\*${normalized}\\*\\*|^${normalized}:)`, "mi");
5916
5917
  const match$2 = headingPattern.exec(shardContent);
5917
5918
  if (!match$2) return null;
5918
5919
  const startIdx = match$2.index;
5919
5920
  const rest = shardContent.slice(startIdx + match$2[0].length);
5920
- const nextStoryPattern = new RegExp(`(?:^#{2,4}\\s+Story\\s+[\\d]|^Story\\s+[\\d][\\d-]*:|^\\*\\*[\\d][\\d-]*\\*\\*|^[\\d][\\d-]*:)`, "mi");
5921
+ const nextStoryPattern = new RegExp(`(?:^#{2,4}\\s+Story\\s+[\\d]|^Story\\s+[\\d][\\d.\\-_a-z]*:|^\\*\\*[\\d][\\d.\\-_a-z]*\\*\\*|^[\\d][\\d.\\-_a-z]*:)`, "mi");
5921
5922
  const nextMatch = nextStoryPattern.exec(rest);
5922
5923
  const endIdx = nextMatch !== null ? startIdx + match$2[0].length + nextMatch.index : shardContent.length;
5923
5924
  const section = shardContent.slice(startIdx, endIdx).trim();
@@ -10429,7 +10430,8 @@ function assembleVerificationContext(opts) {
10429
10430
  reviewResult: opts.reviewResult,
10430
10431
  storyContent: opts.storyContent,
10431
10432
  devStoryResult: opts.devStoryResult,
10432
- outputTokenCount: opts.outputTokenCount
10433
+ outputTokenCount: opts.outputTokenCount,
10434
+ sourceEpicContent: opts.sourceEpicContent
10433
10435
  };
10434
10436
  }
10435
10437
  /**
@@ -10462,20 +10464,23 @@ var VerificationStore = class {
10462
10464
  * verification results survive process crashes.
10463
10465
  *
10464
10466
  * 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.
10467
+ * - Non-fatal: the `.catch()` handler swallows any rejection and logs at warn.
10468
+ * - Returns a `Promise<void>` so callers can optionally `await` it to ensure
10469
+ * ordering (Story 57-2). Fire-and-forget callers that discard the return
10470
+ * value continue to compile and work correctly.
10467
10471
  * - Reuses the single RunManifest instance injected by the orchestrator to
10468
10472
  * avoid concurrent-write conflicts with the atomic-write lock.
10469
10473
  * - `runManifest` is optional (`undefined | null`) — callers from contexts
10470
- * where no manifest is configured pass `null` and this function is a no-op.
10474
+ * where no manifest is configured pass `null` and this function returns a
10475
+ * resolved promise (no-op).
10471
10476
  *
10472
10477
  * @param storyKey - Story key being verified (e.g. '52-7')
10473
10478
  * @param summary - VerificationSummary returned by VerificationPipeline.run()
10474
10479
  * @param runManifest - RunManifest instance to write to, or null/undefined to skip
10475
10480
  */
10476
10481
  function persistVerificationResult(storyKey, summary, runManifest) {
10477
- if (runManifest == null) return;
10478
- runManifest.patchStoryState(storyKey, { verification_result: summary }).catch((err) => _logger.warn({
10482
+ if (runManifest == null) return Promise.resolve();
10483
+ return runManifest.patchStoryState(storyKey, { verification_result: summary }).catch((err) => _logger.warn({
10479
10484
  err,
10480
10485
  storyKey
10481
10486
  }, "manifest verification_result write failed — pipeline continues"));
@@ -12072,21 +12077,17 @@ function createImplementationOrchestrator(deps) {
12072
12077
  phase: "IN_STORY_CREATION",
12073
12078
  result: createResult
12074
12079
  });
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
- }
12080
+ if (config.pipelineRunId !== void 0 && createResult.tokenUsage !== void 0) Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
12081
+ phase: "create-story",
12082
+ agent: "create-story",
12083
+ input_tokens: createResult.tokenUsage.input,
12084
+ output_tokens: createResult.tokenUsage.output,
12085
+ cost_usd: estimateDispatchCost(createResult.tokenUsage.input, createResult.tokenUsage.output),
12086
+ metadata: JSON.stringify({ storyKey })
12087
+ })).catch((tokenErr) => logger$24.warn({
12088
+ storyKey,
12089
+ err: tokenErr
12090
+ }, "Failed to record create-story token usage"));
12090
12091
  await persistState();
12091
12092
  if (createResult.result === "failed") {
12092
12093
  const errMsg = createResult.error ?? "create-story failed";
@@ -12256,21 +12257,17 @@ function createImplementationOrchestrator(deps) {
12256
12257
  }, "Test planning failed — proceeding to dev-story without test plan");
12257
12258
  }
12258
12259
  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
- }
12260
+ if (config.pipelineRunId !== void 0 && testPlanTokenUsage !== void 0) Promise.resolve().then(() => 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
+ })).catch((tokenErr) => logger$24.warn({
12268
+ storyKey,
12269
+ err: tokenErr
12270
+ }, "Failed to record test-plan token usage"));
12274
12271
  eventBus.emit("orchestrator:story-phase-complete", {
12275
12272
  storyKey,
12276
12273
  phase: "IN_TEST_PLANNING",
@@ -12419,28 +12416,24 @@ function createImplementationOrchestrator(deps) {
12419
12416
  batchIndex: batch.batchIndex,
12420
12417
  files: batchFilesModified
12421
12418
  });
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({
12419
+ if (config.pipelineRunId !== void 0 && batchResult.tokenUsage !== void 0) Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
12420
+ phase: "dev-story",
12421
+ agent: `batch-${batch.batchIndex}`,
12422
+ input_tokens: batchResult.tokenUsage.input,
12423
+ output_tokens: batchResult.tokenUsage.output,
12424
+ cost_usd: estimateDispatchCost(batchResult.tokenUsage.input, batchResult.tokenUsage.output),
12425
+ metadata: JSON.stringify({
12439
12426
  storyKey,
12440
12427
  batchIndex: batch.batchIndex,
12441
- err: tokenErr
12442
- }, "Failed to record batch token usage");
12443
- }
12428
+ taskIds: batch.taskIds,
12429
+ durationMs: batchDurationMs,
12430
+ result: batchMetrics.result
12431
+ })
12432
+ })).catch((tokenErr) => logger$24.warn({
12433
+ storyKey,
12434
+ batchIndex: batch.batchIndex,
12435
+ err: tokenErr
12436
+ }, "Failed to record batch token usage"));
12444
12437
  if (batchResult.tokenUsage?.output !== void 0) devOutputTokenCount = (devOutputTokenCount ?? 0) + batchResult.tokenUsage.output;
12445
12438
  if (batchResult.result === "failed") logger$24.warn({
12446
12439
  storyKey,
@@ -12478,21 +12471,17 @@ function createImplementationOrchestrator(deps) {
12478
12471
  });
12479
12472
  devFilesModified = devResult.files_modified ?? [];
12480
12473
  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
- }
12474
+ if (config.pipelineRunId !== void 0 && devResult.tokenUsage !== void 0) Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
12475
+ phase: "dev-story",
12476
+ agent: "dev-story",
12477
+ input_tokens: devResult.tokenUsage.input,
12478
+ output_tokens: devResult.tokenUsage.output,
12479
+ cost_usd: estimateDispatchCost(devResult.tokenUsage.input, devResult.tokenUsage.output),
12480
+ metadata: JSON.stringify({ storyKey })
12481
+ })).catch((tokenErr) => logger$24.warn({
12482
+ storyKey,
12483
+ err: tokenErr
12484
+ }, "Failed to record dev-story token usage"));
12496
12485
  eventBus.emit("orchestrator:story-phase-complete", {
12497
12486
  storyKey,
12498
12487
  phase: "IN_DEV",
@@ -13151,23 +13140,22 @@ function createImplementationOrchestrator(deps) {
13151
13140
  ...baselineHeadSha ? { baselineCommit: baselineHeadSha } : {}
13152
13141
  });
13153
13142
  }
13154
- if (config.pipelineRunId !== void 0 && reviewResult.tokenUsage !== void 0) try {
13155
- addTokenUsage(db, config.pipelineRunId, {
13143
+ if (config.pipelineRunId !== void 0 && reviewResult?.tokenUsage !== void 0) {
13144
+ const reviewTokens = reviewResult.tokenUsage;
13145
+ Promise.resolve().then(() => addTokenUsage(db, config.pipelineRunId, {
13156
13146
  phase: "code-review",
13157
13147
  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),
13148
+ input_tokens: reviewTokens.input,
13149
+ output_tokens: reviewTokens.output,
13150
+ cost_usd: estimateDispatchCost(reviewTokens.input, reviewTokens.output),
13161
13151
  metadata: JSON.stringify({
13162
13152
  storyKey,
13163
13153
  reviewCycle: reviewCycles
13164
13154
  })
13165
- });
13166
- } catch (tokenErr) {
13167
- logger$24.warn({
13155
+ })).catch((tokenErr) => logger$24.warn({
13168
13156
  storyKey,
13169
13157
  err: tokenErr
13170
- }, "Failed to record code-review token usage");
13158
+ }, "Failed to record code-review token usage"));
13171
13159
  }
13172
13160
  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
13161
  if (isPhantomReview && !timeoutRetried) {
@@ -13272,17 +13260,25 @@ function createImplementationOrchestrator(deps) {
13272
13260
  error: reviewResult.error,
13273
13261
  rawOutput: reviewResult.rawOutput
13274
13262
  } : void 0;
13263
+ let sourceEpicContent;
13264
+ const epicsPath1 = findEpicsFile(projectRoot ?? process.cwd());
13265
+ if (epicsPath1) try {
13266
+ const epicFull = readFileSync(epicsPath1, "utf-8");
13267
+ const section = extractStorySection(epicFull, storyKey);
13268
+ if (section) sourceEpicContent = section;
13269
+ } catch {}
13275
13270
  const verifContext = assembleVerificationContext({
13276
13271
  storyKey,
13277
13272
  workingDir: projectRoot ?? process.cwd(),
13278
13273
  reviewResult: latestReviewSignals,
13279
13274
  storyContent: storyContentForVerification,
13280
13275
  devStoryResult: devStorySignals,
13281
- outputTokenCount: devOutputTokenCount
13276
+ outputTokenCount: devOutputTokenCount,
13277
+ sourceEpicContent
13282
13278
  });
13283
13279
  const verifSummary = await verificationPipeline.run(verifContext, "A");
13284
13280
  verificationStore.set(storyKey, verifSummary);
13285
- persistVerificationResult(storyKey, verifSummary, runManifest);
13281
+ await persistVerificationResult(storyKey, verifSummary, runManifest);
13286
13282
  if (verifSummary.status === "fail") {
13287
13283
  updateStory(storyKey, {
13288
13284
  phase: "VERIFICATION_FAILED",
@@ -13301,6 +13297,12 @@ function createImplementationOrchestrator(deps) {
13301
13297
  phase: "COMPLETE",
13302
13298
  completedAt: new Date().toISOString()
13303
13299
  });
13300
+ if (config.skipVerification !== true && runManifest != null) Promise.resolve().then(() => runManifest.read()).then((manifest) => {
13301
+ if (manifest?.per_story_state?.[storyKey]?.verification_result == null) logger$24.warn({
13302
+ storyKey,
13303
+ category: "verification-result-missing"
13304
+ }, "post-COMPLETE invariant: verification_result absent in manifest");
13305
+ }).catch(() => {});
13304
13306
  const completedReviewCycles = reviewCycles + 1;
13305
13307
  await writeStoryMetricsBestEffort(storyKey, verdict, completedReviewCycles);
13306
13308
  await writeStoryOutcomeBestEffort(storyKey, "complete", completedReviewCycles);
@@ -13530,17 +13532,25 @@ function createImplementationOrchestrator(deps) {
13530
13532
  error: reviewResult.error,
13531
13533
  rawOutput: reviewResult.rawOutput
13532
13534
  } : void 0;
13535
+ let sourceEpicContent2;
13536
+ const epicsPath2 = findEpicsFile(projectRoot ?? process.cwd());
13537
+ if (epicsPath2) try {
13538
+ const epicFull2 = readFileSync(epicsPath2, "utf-8");
13539
+ const section2 = extractStorySection(epicFull2, storyKey);
13540
+ if (section2) sourceEpicContent2 = section2;
13541
+ } catch {}
13533
13542
  const verifContext = assembleVerificationContext({
13534
13543
  storyKey,
13535
13544
  workingDir: projectRoot ?? process.cwd(),
13536
13545
  reviewResult: latestReviewSignals,
13537
13546
  storyContent: storyContentForVerification,
13538
13547
  devStoryResult: devStorySignals,
13539
- outputTokenCount: devOutputTokenCount
13548
+ outputTokenCount: devOutputTokenCount,
13549
+ sourceEpicContent: sourceEpicContent2
13540
13550
  });
13541
13551
  const verifSummary = await verificationPipeline.run(verifContext, "A");
13542
13552
  verificationStore.set(storyKey, verifSummary);
13543
- persistVerificationResult(storyKey, verifSummary, runManifest);
13553
+ await persistVerificationResult(storyKey, verifSummary, runManifest);
13544
13554
  if (verifSummary.status === "fail") {
13545
13555
  updateStory(storyKey, {
13546
13556
  phase: "VERIFICATION_FAILED",
@@ -13568,6 +13578,12 @@ function createImplementationOrchestrator(deps) {
13568
13578
  reviewCycles: finalReviewCycles,
13569
13579
  completedAt: new Date().toISOString()
13570
13580
  });
13581
+ if (config.skipVerification !== true && runManifest != null) Promise.resolve().then(() => runManifest.read()).then((manifest) => {
13582
+ if (manifest?.per_story_state?.[storyKey]?.verification_result == null) logger$24.warn({
13583
+ storyKey,
13584
+ category: "verification-result-missing"
13585
+ }, "post-COMPLETE invariant: verification_result absent in manifest");
13586
+ }).catch(() => {});
13571
13587
  await writeStoryMetricsBestEffort(storyKey, verdict, finalReviewCycles);
13572
13588
  await writeStoryOutcomeBestEffort(storyKey, "complete", finalReviewCycles);
13573
13589
  eventBus.emit("orchestrator:story-complete", {
@@ -18477,7 +18493,8 @@ async function buildRunReportFromManifest(runId, runsDir, adapter, opts) {
18477
18493
  ...vr !== void 0 && {
18478
18494
  verificationStatus: vr.status,
18479
18495
  verificationChecks: vr.checks
18480
- }
18496
+ },
18497
+ verification_ran: state.verification_result !== void 0 && state.verification_result !== null
18481
18498
  });
18482
18499
  }
18483
18500
  const escalationFindings = Object.values(escalationDiagnoses).map((d) => d.finding);
@@ -18608,6 +18625,7 @@ async function buildRunReport(adapter, runId, opts) {
18608
18625
  verificationStatus: vr.status,
18609
18626
  verificationChecks: vr.checks
18610
18627
  },
18628
+ verification_ran: vr !== void 0,
18611
18629
  ...qualityScore !== void 0 && { qualityScore },
18612
18630
  ...s$1.primary_agent_id && { agentId: s$1.primary_agent_id },
18613
18631
  ...s$1.primary_model && { model: s$1.primary_model },
@@ -43840,4 +43858,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
43840
43858
 
43841
43859
  //#endregion
43842
43860
  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-Bz3dW9Wn.js.map
43861
+ //# sourceMappingURL=run-B6qbsy-F.js.map
@@ -1,8 +1,8 @@
1
- import "./health-DXW-E2vb.js";
1
+ import "./health-0jqPFBEL.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-Bz3dW9Wn.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-B6qbsy-F.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.8",
3
+ "version": "0.20.12",
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