substrate-ai 0.21.1 → 0.21.3

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.
Files changed (28) hide show
  1. package/README.md +32 -3
  2. package/dist/adapter-registry-BSig1zpx.js +4 -0
  3. package/dist/cli/index.js +114 -19
  4. package/dist/cli/templates/agents-md-substrate-section.md +12 -1
  5. package/dist/cli/templates/claude-md-substrate-section.md +6 -0
  6. package/dist/cli/templates/gemini-md-substrate-section.md +12 -1
  7. package/dist/{decisions-BPwrDCTx.js → decisions-lxU14pNh.js} +1 -1
  8. package/dist/{dist-BkhYgJWo.js → dist-CpnLJuVw.js} +13 -3
  9. package/dist/{errors-C7pY6_yQ.js → errors-8tVEwG6v.js} +2 -2
  10. package/dist/{experimenter-BYqALeaE.js → experimenter-C7swe92R.js} +1 -1
  11. package/dist/{health-Cfkv6XoQ.js → health-Dnzblihr.js} +3 -3
  12. package/dist/{health-Coh3BG8W.js → health-bhMCTYDO.js} +3 -3
  13. package/dist/{index-CnV5PcSR.d.ts → index-C31XtSMn.d.ts} +8 -1
  14. package/dist/index.d.ts +45 -2
  15. package/dist/index.js +2 -2
  16. package/dist/{interactive-prompt-BBfPSxyJ.js → interactive-prompt-BM5RaXYG.js} +2 -2
  17. package/dist/{manifest-read-BhNRIiqm.js → manifest-read-B3gE46DX.js} +369 -4
  18. package/dist/modules/interactive-prompt/index.js +3 -3
  19. package/dist/{routing-BS-Q8_2L.js → routing-CGO2UZyA.js} +1 -1
  20. package/dist/{run-CKM-5T34.js → run-ChZFveqv.js} +5 -5
  21. package/dist/{run-JrEnvaB0.js → run-ZxFHQyE_.js} +219 -8
  22. package/dist/src/modules/recovery-engine/index.d.ts +16 -2
  23. package/dist/{upgrade-S1oUMH5P.js → upgrade-BUSzXgnd.js} +2 -2
  24. package/dist/{upgrade-BOWLlLbD.js → upgrade-M0UkDCeg.js} +2 -2
  25. package/dist/{version-manager-impl-BVcsgUtN.js → version-manager-impl-DeEPhQlA.js} +1 -1
  26. package/package.json +1 -1
  27. package/packs/bmad/prompts/create-story.md +22 -0
  28. package/dist/adapter-registry-CZn__637.js +0 -4
@@ -1,11 +1,11 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, FileKvStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-Cfkv6XoQ.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, FileKvStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-Dnzblihr.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, BRANCH_PREFIX, CLAUDE_AUTH_FAILURE_HINT, CODEX_SANDBOX_BLOCK_HINT, 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, classifyVersionGap, createConfigSystem, createDatabaseAdapter$1, createDecision, createGitWorktreeManager, createPipelineRun, createRequirement, detectClaudeAuthFailure, detectCodexSandboxBlock, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, swallowDebug, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-BkhYgJWo.js";
5
- import { FindingsInjector, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, parseRuntimeProbes, renderFindings, resolveGraphPath, resolveMainRepoRoot, runAcTraceabilityCheck, runStaleVerificationRecovery } from "./manifest-read-BhNRIiqm.js";
4
+ import { ADVISORY_NOTES, BRANCH_PREFIX, CLAUDE_AUTH_FAILURE_HINT, CODEX_SANDBOX_BLOCK_HINT, 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, classifyVersionGap, createConfigSystem, createDatabaseAdapter$1, createDecision, createGitWorktreeManager, createPipelineRun, createRequirement, detectClaudeAuthFailure, detectCodexSandboxBlock, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, swallowDebug, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-CpnLJuVw.js";
5
+ import { FindingsInjector, JOURNEY_DEFERRALS_PATH, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, computeJourneyCoverage, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, loadJourneyDeferralsFromTrustedTree, loadJourneyRegistryFromTrustedTree, parseRuntimeProbes, parseStoryFrontmatter, renderFindings, resolveGraphPath, resolveMainRepoRoot, runAcTraceabilityCheck, runStaleVerificationRecovery, summarizeCoverage } from "./manifest-read-B3gE46DX.js";
6
6
  import { WorkGraphRepository, detectCycles } from "./work-graph-repository-DZyJv5pV.js";
7
7
  import { deriveExitCode, routeDecision } from "./decision-router-BAPpON_C.js";
8
- import { runInteractivePrompt } from "./interactive-prompt-BBfPSxyJ.js";
8
+ import { runInteractivePrompt } from "./interactive-prompt-BM5RaXYG.js";
9
9
  import { runRecoveryEngine } from "./recovery-engine-BKGBeBnW.js";
10
10
  import { basename, dirname, extname, join } from "path";
11
11
  import { access, readFile, readdir, stat } from "fs/promises";
@@ -933,6 +933,38 @@ const PIPELINE_EVENT_METADATA = [
933
933
  }
934
934
  ]
935
935
  },
936
+ {
937
+ type: "acceptance:coverage",
938
+ description: "Journey coverage audit result (acceptance gate, A0.3). Every registered journey in scope lands in exactly one state: walked-pass, walked-fail, deferred, unclaimed (NO story claims it — the never-wired-journey class), or unwalked (claimed but never walked). Advisory mode warns; blocking mode escalates the last story of the epic.",
939
+ when: "At each epic close and once at run end (scope: \"final\").",
940
+ fields: [
941
+ {
942
+ name: "ts",
943
+ type: "string",
944
+ description: "Timestamp."
945
+ },
946
+ {
947
+ name: "scope",
948
+ type: "string",
949
+ description: "Audited boundary: epic-<n> or final."
950
+ },
951
+ {
952
+ name: "mode",
953
+ type: "advisory|blocking",
954
+ description: "Audit posture (acceptance.mode config)."
955
+ },
956
+ {
957
+ name: "entries",
958
+ type: "array",
959
+ description: "Per-journey {journeyId, criticality, state, ownerStories}."
960
+ },
961
+ {
962
+ name: "summary",
963
+ type: "object",
964
+ description: "State counts across entries."
965
+ }
966
+ ]
967
+ },
936
968
  {
937
969
  type: "story:escalation",
938
970
  description: "Story escalated — either max review cycles exceeded or a precondition for SHIP_IT was not met.",
@@ -951,7 +983,7 @@ const PIPELINE_EVENT_METADATA = [
951
983
  {
952
984
  name: "reason",
953
985
  type: "string",
954
- description: "Escalation reason. Common values: review-cycles-exhausted (default review-loop max hit); create-story-no-file (create-story phase did not write a story artifact); dev-story-no-commit (substrate auto-commit found no committable changes — agent produced nothing or all changes were outside the worktree); dev-story-commit-failed (substrate auto-commit failed — typically a pre-commit hook rejected the commit; stderr captured in issues); merge-to-main-error (unexpected error in the merge-to-main phase); merge-conflict-detected (story branch could not be merged due to conflicts); parent-tree-dirtied-by-run (uncommitted parent changes intersect the story diff — merge refused, H3.3); ff-only-merge-not-possible (base moved and merge_strategy is ff-only — set finalization.merge_strategy: three-way or integrate manually, H3.3); epic-gate-failed (finalization.epic_gate_command exited non-zero before the last story of an epic integrated, H3.4); story-file-missing (story artifact gone from working tree AND branch HEAD — check wip/feat commits on the story branch, H5.5). v0.20.87+."
986
+ description: "Escalation reason. Common values: review-cycles-exhausted (default review-loop max hit); create-story-no-file (create-story phase did not write a story artifact); dev-story-no-commit (substrate auto-commit found no committable changes — agent produced nothing or all changes were outside the worktree); dev-story-commit-failed (substrate auto-commit failed — typically a pre-commit hook rejected the commit; stderr captured in issues); merge-to-main-error (unexpected error in the merge-to-main phase); merge-conflict-detected (story branch could not be merged due to conflicts); parent-tree-dirtied-by-run (uncommitted parent changes intersect the story diff — merge refused, H3.3); ff-only-merge-not-possible (base moved and merge_strategy is ff-only — set finalization.merge_strategy: three-way or integrate manually, H3.3); epic-gate-failed (finalization.epic_gate_command exited non-zero before the last story of an epic integrated, H3.4); story-file-missing (story artifact gone from working tree AND branch HEAD — check wip/feat commits on the story branch, H5.5); undisclosed-files-in-merge (a committed implementation file was never in the dev agent's files_modified, so no review cycle inspected it — inspect the branch, then re-dispatch with accurate files_modified or merge manually, H7); journey-unclaimed (acceptance.mode: blocking — a registered journey has NO story claiming it at epic close; wire it or defer via `substrate acceptance defer`, A0.3); journey-unwalked (acceptance.mode: blocking — a claimed journey was never walked by the acceptance gate at epic close, A0.3). v0.20.87+."
955
987
  },
956
988
  {
957
989
  name: "cycles",
@@ -6138,6 +6170,21 @@ async function runCreateStory(deps, params) {
6138
6170
  } catch {}
6139
6171
  const archConstraintsContent = await getArchConstraints$3(deps);
6140
6172
  const storyTemplateContent = await getStoryTemplate(deps);
6173
+ const trustedRoot = deps.parentProjectRoot ?? deps.projectRoot;
6174
+ let journeyRegistry;
6175
+ if (trustedRoot !== void 0) {
6176
+ const registryLoad = await loadJourneyRegistryFromTrustedTree(trustedRoot);
6177
+ if (registryLoad.status === "ok" && registryLoad.registry.journeys.length > 0) journeyRegistry = registryLoad.registry;
6178
+ else if (registryLoad.status === "invalid") logger$21.warn({
6179
+ storyKey,
6180
+ issues: registryLoad.issues
6181
+ }, "A0.2: journey registry present but invalid — stories will be untagged (registry validity is escalated at epic close)");
6182
+ else if (registryLoad.status === "error") logger$21.warn({
6183
+ storyKey,
6184
+ error: registryLoad.message
6185
+ }, "A0.2: journey registry read failed — stories will be untagged");
6186
+ }
6187
+ const journeyRegistryContent = journeyRegistry !== void 0 ? journeyRegistry.journeys.map((j$1) => `- ${j$1.id} [${j$1.criticality}]: ${j$1.title}`).join("\n") : "";
6141
6188
  const { prompt, tokenCount, truncated } = assemblePrompt(template, [
6142
6189
  {
6143
6190
  name: "story_key",
@@ -6182,6 +6229,15 @@ async function runCreateStory(deps, params) {
6182
6229
  name: "prior_drift_feedback",
6183
6230
  content: "",
6184
6231
  priority: "optional"
6232
+ }],
6233
+ ...journeyRegistryContent.length > 0 ? [{
6234
+ name: "journey_registry",
6235
+ content: journeyRegistryContent,
6236
+ priority: "required"
6237
+ }] : [{
6238
+ name: "journey_registry",
6239
+ content: "",
6240
+ priority: "optional"
6185
6241
  }]
6186
6242
  ], TOKEN_CEILING);
6187
6243
  logger$21.debug({
@@ -6295,6 +6351,42 @@ async function runCreateStory(deps, params) {
6295
6351
  };
6296
6352
  }
6297
6353
  const parsed = parseResult.data;
6354
+ let validatedJourneys;
6355
+ if (journeyRegistry !== void 0 && parsed.story_file !== void 0) {
6356
+ let artifactContent;
6357
+ try {
6358
+ artifactContent = await readFile$1(parsed.story_file, "utf-8");
6359
+ } catch {
6360
+ artifactContent = void 0;
6361
+ }
6362
+ if (artifactContent !== void 0) {
6363
+ const taggedJourneys = parseStoryFrontmatter(artifactContent).journeys;
6364
+ const knownIds = new Set(journeyRegistry.journeys.map((j$1) => j$1.id));
6365
+ const unknown = taggedJourneys.filter((id) => !knownIds.has(id));
6366
+ if (unknown.length > 0) {
6367
+ const details = `story artifact declares unknown journey id(s): ${unknown.join(", ")} — registry v${String(journeyRegistry.version)} defines: ${[...knownIds].join(", ")}`;
6368
+ logger$21.warn({
6369
+ epicId,
6370
+ storyKey,
6371
+ unknown
6372
+ }, "A0.2: journey tag validation failed");
6373
+ return {
6374
+ result: "failed",
6375
+ error: "schema_validation_failed",
6376
+ details,
6377
+ tokenUsage
6378
+ };
6379
+ }
6380
+ if (taggedJourneys.length > 0) {
6381
+ logger$21.info({
6382
+ epicId,
6383
+ storyKey,
6384
+ journeys: taggedJourneys
6385
+ }, "A0.2: story tagged with journeys");
6386
+ validatedJourneys = taggedJourneys;
6387
+ }
6388
+ }
6389
+ }
6298
6390
  logger$21.info({
6299
6391
  epicId,
6300
6392
  storyKey,
@@ -6307,6 +6399,7 @@ async function runCreateStory(deps, params) {
6307
6399
  story_key: parsed.story_key,
6308
6400
  story_title: parsed.story_title,
6309
6401
  tokenUsage,
6402
+ ...validatedJourneys !== void 0 ? { journeys: validatedJourneys } : {},
6310
6403
  ...dispatchResult.model !== void 0 ? { model: dispatchResult.model } : {}
6311
6404
  };
6312
6405
  }
@@ -13859,6 +13952,40 @@ function createImplementationOrchestrator(deps) {
13859
13952
  _lastProgressTs = Date.now();
13860
13953
  _stalledStories.clear();
13861
13954
  }
13955
+ async function auditJourneyCoverage(scope, opts) {
13956
+ const mode = config.acceptanceMode ?? "advisory";
13957
+ if (mode === "off" || projectRoot === void 0) return void 0;
13958
+ const registryLoad = await loadJourneyRegistryFromTrustedTree(projectRoot);
13959
+ if (registryLoad.status === "absent") return void 0;
13960
+ if (registryLoad.status !== "ok") {
13961
+ logger$27.warn({ detail: registryLoad.status === "invalid" ? registryLoad.issues : registryLoad.message }, "A0.3: journey registry unreadable/invalid — coverage audit skipped (loud escalation for this is A1 acceptance-unrunnable scope)");
13962
+ return void 0;
13963
+ }
13964
+ const deferralsLoad = await loadJourneyDeferralsFromTrustedTree(projectRoot);
13965
+ if (deferralsLoad.status !== "ok") logger$27.warn({ detail: deferralsLoad.status === "invalid" ? deferralsLoad.issues : deferralsLoad.message }, `A0.3: ${JOURNEY_DEFERRALS_PATH} unreadable/invalid — treating as no deferrals`);
13966
+ const deferredJourneyIds = deferralsLoad.status === "ok" ? deferralsLoad.deferrals.map((d) => d.journey) : [];
13967
+ const claims = [];
13968
+ for (const [key, st] of _stories.entries()) for (const journeyId of st.journeys ?? []) claims.push({
13969
+ journeyId,
13970
+ storyKey: key
13971
+ });
13972
+ const entries = computeJourneyCoverage({
13973
+ registry: registryLoad.registry,
13974
+ claims,
13975
+ verdicts: [],
13976
+ deferredJourneyIds,
13977
+ scope
13978
+ });
13979
+ const scopeLabel = "final" in scope ? "final" : `epic-${String(scope.epic)}`;
13980
+ eventBus.emit("orchestrator:acceptance-coverage", {
13981
+ scope: scopeLabel,
13982
+ mode,
13983
+ entries,
13984
+ summary: summarizeCoverage(entries)
13985
+ });
13986
+ if (opts?.persist === true && runManifest !== null) await runManifest.update({ journeys: entries }).catch((err) => logger$27.warn({ err }, "A0.3: manifest journeys ledger write failed — pipeline continues"));
13987
+ return entries;
13988
+ }
13862
13989
  function getStallThresholdMs(phase) {
13863
13990
  return phase === "IN_DEV" ? DEV_STORY_STALL_THRESHOLD_MS : DEFAULT_STALL_THRESHOLD_MS;
13864
13991
  }
@@ -14085,6 +14212,11 @@ function createImplementationOrchestrator(deps) {
14085
14212
  storyKey,
14086
14213
  storyFilePath
14087
14214
  }, "Found existing story file — skipping create-story");
14215
+ try {
14216
+ const reusedContent = await readFile$1(candidatePath, "utf-8");
14217
+ const reusedJourneys = parseStoryFrontmatter(reusedContent).journeys;
14218
+ if (reusedJourneys.length > 0) updateStory(storyKey, { journeys: reusedJourneys });
14219
+ } catch {}
14088
14220
  endPhase(storyKey, "create-story");
14089
14221
  eventBus.emit("orchestrator:story-phase-complete", {
14090
14222
  storyKey,
@@ -14379,6 +14511,13 @@ function createImplementationOrchestrator(deps) {
14379
14511
  }
14380
14512
  }
14381
14513
  storyFilePath = createResult.story_file;
14514
+ if (createResult.journeys !== void 0 && createResult.journeys.length > 0) {
14515
+ updateStory(storyKey, { journeys: createResult.journeys });
14516
+ if (runManifest !== null) runManifest.patchStoryState(storyKey, { journeys: createResult.journeys }).catch((err) => logger$27.warn({
14517
+ err,
14518
+ storyKey
14519
+ }, "A0.3: patchStoryState(journeys) failed — pipeline continues"));
14520
+ }
14382
14521
  _capturedStoryTitle = sanitizeStoryTitle(createResult.story_title);
14383
14522
  if (createResult.story_title) try {
14384
14523
  const epicId = storyKey.split("-")[0] ?? storyKey;
@@ -16231,6 +16370,53 @@ function createImplementationOrchestrator(deps) {
16231
16370
  }
16232
16371
  }
16233
16372
  const finalizationMode = config.finalizationMode ?? "merge";
16373
+ {
16374
+ const acceptanceModeCfg = config.acceptanceMode ?? "advisory";
16375
+ if (acceptanceModeCfg !== "off") {
16376
+ const epicOfA = (key) => key.includes("-") ? key.slice(0, key.lastIndexOf("-")) : key;
16377
+ const epicIdA = epicOfA(storyKey);
16378
+ const TERMINAL_A = ["COMPLETE", "ESCALATED"];
16379
+ const isLastOfEpicA = [..._stories.entries()].filter(([key]) => key !== storyKey && epicOfA(key) === epicIdA).every(([, st]) => TERMINAL_A.includes(st.phase));
16380
+ const epicNum = Number(epicIdA);
16381
+ if (isLastOfEpicA && Number.isInteger(epicNum)) {
16382
+ const entries = await auditJourneyCoverage({ epic: epicNum });
16383
+ const violations = (entries ?? []).filter((e) => e.state === "unclaimed" || e.state === "unwalked");
16384
+ if (violations.length > 0) {
16385
+ const issueLines = violations.map((v) => `journey ${v.journeyId} [${v.criticality}] "${v.title}" is ${v.state}` + (v.ownerStories.length > 0 ? ` (claimed by ${v.ownerStories.join(", ")} but never walked)` : " — NO story claims it"));
16386
+ if (acceptanceModeCfg === "blocking") {
16387
+ const verdict = violations.some((v) => v.state === "unclaimed") ? "journey-unclaimed" : "journey-unwalked";
16388
+ logger$27.error({
16389
+ storyKey,
16390
+ epicId: epicIdA,
16391
+ journeys: violations.map((v) => v.journeyId)
16392
+ }, "A0.3: journey coverage violation at epic close — blocking finalization; branch preserved");
16393
+ updateStory(storyKey, {
16394
+ phase: "ESCALATED",
16395
+ error: verdict,
16396
+ completedAt: new Date().toISOString()
16397
+ });
16398
+ await emitEscalation({
16399
+ storyKey,
16400
+ lastVerdict: verdict,
16401
+ reviewCycles: completedReviewCycles,
16402
+ issues: [
16403
+ `epic ${epicIdA} closes with ${String(violations.length)} journey coverage violation(s) — the never-wired-journey class:`,
16404
+ ...issueLines,
16405
+ "Wire the journey in a story tagged with its id, or defer it explicitly: `substrate acceptance defer <id> --reason \"<why>\"` (commit the deferral file)."
16406
+ ]
16407
+ });
16408
+ await persistState();
16409
+ return "terminal";
16410
+ }
16411
+ logger$27.warn({
16412
+ storyKey,
16413
+ epicId: epicIdA,
16414
+ violations: issueLines
16415
+ }, "A0.3 (advisory): journey coverage violations at epic close — not blocking (acceptance.mode: advisory)");
16416
+ }
16417
+ }
16418
+ }
16419
+ }
16234
16420
  const epicGateCommand = config.epicGateCommand;
16235
16421
  if (epicGateCommand !== void 0 && epicGateCommand.trim() !== "" && finalizationMode !== "branch") {
16236
16422
  const epicOf = (key) => key.includes("-") ? key.slice(0, key.lastIndexOf("-")) : key;
@@ -18008,6 +18194,11 @@ function createImplementationOrchestrator(deps) {
18008
18194
  } catch (err) {
18009
18195
  logger$27.debug({ err }, "Profile staleness check failed (best-effort)");
18010
18196
  }
18197
+ try {
18198
+ await auditJourneyCoverage({ final: true }, { persist: true });
18199
+ } catch (err) {
18200
+ logger$27.warn({ err }, "A0.3: run-end coverage sweep failed (best-effort)");
18201
+ }
18011
18202
  let completed = 0;
18012
18203
  let escalated = 0;
18013
18204
  let failed = 0;
@@ -46243,6 +46434,21 @@ function wireNdjsonEmitter(eventBus, ndjsonEmitter) {
46243
46434
  ...payload.prUrl !== void 0 ? { pr_url: payload.prUrl } : {}
46244
46435
  });
46245
46436
  });
46437
+ eventBus.on("orchestrator:acceptance-coverage", (payload) => {
46438
+ ndjsonEmitter.emit({
46439
+ type: "acceptance:coverage",
46440
+ ts: new Date().toISOString(),
46441
+ scope: payload.scope,
46442
+ mode: payload.mode,
46443
+ entries: payload.entries.map((e) => ({
46444
+ journeyId: e.journeyId,
46445
+ criticality: e.criticality,
46446
+ state: e.state,
46447
+ ownerStories: e.ownerStories
46448
+ })),
46449
+ summary: payload.summary
46450
+ });
46451
+ });
46246
46452
  eventBus.on("orchestrator:story-escalated", (payload) => {
46247
46453
  const rawIssues = Array.isArray(payload.issues) ? payload.issues : [];
46248
46454
  const issues = rawIssues.map((issue) => {
@@ -46575,6 +46781,7 @@ async function runRunAction(options) {
46575
46781
  let configFinalizationMode;
46576
46782
  let configMergeStrategy;
46577
46783
  let configEpicGateCommand;
46784
+ let configAcceptanceMode;
46578
46785
  let configPermissionProfile;
46579
46786
  let configWorktreeCopyFiles;
46580
46787
  try {
@@ -46605,6 +46812,7 @@ async function runRunAction(options) {
46605
46812
  if (cfg.finalization?.mode !== void 0) configFinalizationMode = cfg.finalization.mode;
46606
46813
  if (cfg.finalization?.merge_strategy !== void 0) configMergeStrategy = cfg.finalization.merge_strategy;
46607
46814
  if (cfg.finalization?.epic_gate_command !== void 0) configEpicGateCommand = cfg.finalization.epic_gate_command;
46815
+ if (cfg.acceptance?.mode !== void 0) configAcceptanceMode = cfg.acceptance.mode;
46608
46816
  if (cfg.dispatch?.permission_profile !== void 0) configPermissionProfile = cfg.dispatch.permission_profile;
46609
46817
  if (Array.isArray(cfg.worktree?.copy_files) && cfg.worktree.copy_files.length > 0) {
46610
46818
  configWorktreeCopyFiles = [...cfg.worktree.copy_files];
@@ -46708,6 +46916,7 @@ async function runRunAction(options) {
46708
46916
  ...finalization !== void 0 ? { finalizationMode: finalization } : configFinalizationMode !== void 0 ? { finalizationMode: configFinalizationMode } : {},
46709
46917
  ...configMergeStrategy !== void 0 ? { mergeStrategy: configMergeStrategy } : {},
46710
46918
  ...configEpicGateCommand !== void 0 ? { epicGateCommand: configEpicGateCommand } : {},
46919
+ ...configAcceptanceMode !== void 0 ? { acceptanceMode: configAcceptanceMode } : {},
46711
46920
  ...configPermissionProfile !== void 0 ? { permissionProfile: configPermissionProfile } : {},
46712
46921
  ...configWorktreeCopyFiles !== void 0 ? { worktreeCopyFiles: configWorktreeCopyFiles } : {}
46713
46922
  });
@@ -47276,6 +47485,7 @@ async function runRunAction(options) {
47276
47485
  ...finalization !== void 0 ? { finalizationMode: finalization } : configFinalizationMode !== void 0 ? { finalizationMode: configFinalizationMode } : {},
47277
47486
  ...configMergeStrategy !== void 0 ? { mergeStrategy: configMergeStrategy } : {},
47278
47487
  ...configEpicGateCommand !== void 0 ? { epicGateCommand: configEpicGateCommand } : {},
47488
+ ...configAcceptanceMode !== void 0 ? { acceptanceMode: configAcceptanceMode } : {},
47279
47489
  ...configWorktreeCopyFiles !== void 0 ? { worktreeCopyFiles: configWorktreeCopyFiles } : {}
47280
47490
  },
47281
47491
  projectRoot,
@@ -47499,7 +47709,7 @@ async function runRunAction(options) {
47499
47709
  }
47500
47710
  }
47501
47711
  async function runFullPipeline(options) {
47502
- const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, maxReviewCycles = 2, retryBudget, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId, meshUrl: fpMeshUrl, meshProjectId: fpMeshProjectId, engineType: fpEngineType, probeAuthor, probeAuthorStateIntegrating: fpProbeAuthorStateIntegrating, noWorktree, worktreeCopyFiles: fpWorktreeCopyFiles, finalizationMode: fpFinalizationMode, mergeStrategy: fpMergeStrategy, epicGateCommand: fpEpicGateCommand, permissionProfile: fpPermissionProfile } = options;
47712
+ const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, maxReviewCycles = 2, retryBudget, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId, meshUrl: fpMeshUrl, meshProjectId: fpMeshProjectId, engineType: fpEngineType, probeAuthor, probeAuthorStateIntegrating: fpProbeAuthorStateIntegrating, noWorktree, worktreeCopyFiles: fpWorktreeCopyFiles, finalizationMode: fpFinalizationMode, mergeStrategy: fpMergeStrategy, epicGateCommand: fpEpicGateCommand, acceptanceMode: fpAcceptanceMode, permissionProfile: fpPermissionProfile } = options;
47503
47713
  if (!existsSync$1(dbDir)) mkdirSync$1(dbDir, { recursive: true });
47504
47714
  let doltServerFull = null;
47505
47715
  try {
@@ -47815,6 +48025,7 @@ async function runFullPipeline(options) {
47815
48025
  ...fpFinalizationMode !== void 0 ? { finalizationMode: fpFinalizationMode } : {},
47816
48026
  ...fpMergeStrategy !== void 0 ? { mergeStrategy: fpMergeStrategy } : {},
47817
48027
  ...fpEpicGateCommand !== void 0 ? { epicGateCommand: fpEpicGateCommand } : {},
48028
+ ...fpAcceptanceMode !== void 0 ? { acceptanceMode: fpAcceptanceMode } : {},
47818
48029
  ...fpWorktreeCopyFiles !== void 0 ? { worktreeCopyFiles: fpWorktreeCopyFiles } : {}
47819
48030
  },
47820
48031
  projectRoot,
@@ -47985,7 +48196,7 @@ async function runFullPipeline(options) {
47985
48196
  */
47986
48197
  async function emitPreDispatchVersionAdvisory(currentVersion) {
47987
48198
  if (process.env["SUBSTRATE_NO_UPDATE_CHECK"] === "1") return;
47988
- const { createVersionManager } = await import("./version-manager-impl-BVcsgUtN.js");
48199
+ const { createVersionManager } = await import("./version-manager-impl-DeEPhQlA.js");
47989
48200
  const vm = createVersionManager();
47990
48201
  const result = await vm.checkForUpdates(true);
47991
48202
  const gap = classifyVersionGap(currentVersion, result.latestVersion);
@@ -48092,4 +48303,4 @@ function registerRunCommand(program, version = "0.0.0", projectRoot = process.cw
48092
48303
 
48093
48304
  //#endregion
48094
48305
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR$1 as GLOBSTAR, GitClient, GrammarLoader, Minimatch$1 as Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape$1 as escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runRunAction, runSolutioningPhase, unescape$1 as unescape, validateStopAfterFromConflict, wireNdjsonEmitter };
48095
- //# sourceMappingURL=run-JrEnvaB0.js.map
48306
+ //# sourceMappingURL=run-ZxFHQyE_.js.map
@@ -1,4 +1,4 @@
1
- import { CoreEvents, DatabaseAdapter, TypedEventBus } from "../../../index-CnV5PcSR.js";
1
+ import { CoreEvents, DatabaseAdapter, TypedEventBus } from "../../../index-C31XtSMn.js";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region packages/sdlc/dist/verification/findings.d.ts
@@ -582,6 +582,7 @@ declare const PerStoryStateSchema: z.ZodObject<{
582
582
  escalation_detail: z.ZodOptional<z.ZodString>;
583
583
  commit_sha: z.ZodOptional<z.ZodString>;
584
584
  baseline_sha: z.ZodOptional<z.ZodString>;
585
+ journeys: z.ZodOptional<z.ZodArray<z.ZodString>>;
585
586
  checkpoint_sha: z.ZodOptional<z.ZodString>;
586
587
  finalization: z.ZodOptional<z.ZodObject<{
587
588
  mode: z.ZodUnion<readonly [z.ZodLiteral<"merge">, z.ZodLiteral<"branch">, z.ZodLiteral<"pr">]>;
@@ -717,6 +718,19 @@ interface RunManifestData {
717
718
  * Optional for backward-compatibility with pre-75-2 manifests.
718
719
  */
719
720
  orchestrator_start_branch?: string;
721
+ /**
722
+ * A0.3 (acceptance-gate): journey coverage ledger from the most recent
723
+ * audit (epic-close or run-end). Absent on registry-less projects and
724
+ * pre-A0.3 manifests.
725
+ */
726
+ journeys?: {
727
+ journeyId: string;
728
+ title: string;
729
+ criticality: 'critical' | 'standard';
730
+ epic?: number;
731
+ state: 'walked-pass' | 'walked-fail' | 'deferred' | 'unclaimed' | 'unwalked';
732
+ ownerStories: string[];
733
+ }[];
720
734
  /**
721
735
  * Human-readable reason the run was stopped (e.g. 'killed_by_user').
722
736
  * Set by the SIGTERM/SIGINT handler (Story 58-7). Absent on pre-58-7 manifests.
@@ -751,8 +765,8 @@ declare const CliFlagsSchema: z.ZodObject<{
751
765
  stories: z.ZodOptional<z.ZodArray<z.ZodString>>;
752
766
  halt_on: z.ZodOptional<z.ZodEnum<{
753
767
  none: "none";
754
- all: "all";
755
768
  critical: "critical";
769
+ all: "all";
756
770
  }>>;
757
771
  cost_ceiling: z.ZodOptional<z.ZodNumber>;
758
772
  agent: z.ZodOptional<z.ZodString>;
@@ -1,4 +1,4 @@
1
- import { createVersionManager } from "./dist-BkhYgJWo.js";
1
+ import { createVersionManager } from "./dist-CpnLJuVw.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-S1oUMH5P.js.map
126
+ //# sourceMappingURL=upgrade-BUSzXgnd.js.map
@@ -1,5 +1,5 @@
1
- import "./dist-BkhYgJWo.js";
1
+ import "./dist-CpnLJuVw.js";
2
2
  import "./version-manager-impl-qFBiO4Eh.js";
3
- import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-S1oUMH5P.js";
3
+ import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-BUSzXgnd.js";
4
4
 
5
5
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
@@ -1,4 +1,4 @@
1
- import { VersionManagerImpl, createVersionManager } from "./dist-BkhYgJWo.js";
1
+ import { VersionManagerImpl, createVersionManager } from "./dist-CpnLJuVw.js";
2
2
  import "./version-manager-impl-qFBiO4Eh.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.21.1",
3
+ "version": "0.21.3",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -191,6 +191,28 @@ Suggested values (use as many as apply):
191
191
 
192
192
  **Omit the field (or leave it empty) only for purely-algorithmic modules** where you also omit `## Runtime Probes` — parse/format/sort/score/calculate transforms, type-only refactors, documentation edits, build-config changes, and unit-test-only stories.
193
193
 
194
+ ### Journey tags (acceptance gate)
195
+
196
+ This project's journey registry (empty when the project has none):
197
+
198
+ {{journey_registry}}
199
+
200
+ If the registry above is non-empty, decide which journeys this story's acceptance criteria deliver or materially advance, and declare them in the SAME YAML frontmatter block at the top of the story file:
201
+
202
+ ```text
203
+ ---
204
+ external_state_dependencies:
205
+ - subprocess
206
+ journeys:
207
+ - UJ-2
208
+ ---
209
+ ```
210
+
211
+ Rules:
212
+ - Use ONLY journey ids that appear in the registry above — an unknown id fails the story artifact's schema validation.
213
+ - Tag a journey when the story wires, extends, or completes any part of that journey's user-facing path — not merely because it touches nearby code.
214
+ - If no registered journey applies (or the registry above is empty), OMIT the `journeys:` field entirely. Untagged stories are legal; unclaimed journeys are audited separately at epic close.
215
+
194
216
  ### Probe YAML shape
195
217
 
196
218
  Declare probes as a YAML list inside a single fenced `yaml` block directly under the `## Runtime Probes` heading. Each entry has this shape:
@@ -1,4 +0,0 @@
1
- import { AdapterRegistry } from "./dist-BkhYgJWo.js";
2
- import "./adapter-registry-DXLMTmfD.js";
3
-
4
- export { AdapterRegistry };