substrate-ai 0.21.3 → 0.21.5

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,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { FileKvStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createDatabaseAdapter, createDoltOperatorReader, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion } from "../health-Dnzblihr.js";
2
+ import { FileKvStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createDatabaseAdapter, createDoltOperatorReader, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion } from "../health-hYMxKqAH.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, InMemoryDatabaseAdapter, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createGitWorktreeManager, createPipelineRun, createStderrLogger, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initWorkGraphSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, swallowDebug, tagRunAsBaseline, updatePipelineRun } from "../dist-CpnLJuVw.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { JOURNEY_DEFERRALS_PATH, JOURNEY_REGISTRY_PATH, RunManifest, SupervisorLock, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, loadJourneyRegistryFromFile, loadJourneyRegistryFromTrustedTree, parseJourneyDeferrals, parseRuntimeProbes, readCurrentRunId, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runAcTraceabilityCheck } from "../manifest-read-B3gE46DX.js";
8
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-ZxFHQyE_.js";
7
+ import { ACCEPTANCE_CONTRACT_PROFILE_PATH, JOURNEY_DEFERRALS_PATH, JOURNEY_REGISTRY_PATH, RunManifest, SupervisorLock, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, loadAcceptanceContractFromTrustedTree, loadJourneyRegistryFromFile, loadJourneyRegistryFromTrustedTree, parseAcceptanceContract, parseJourneyDeferrals, parseRuntimeProbes, readCurrentRunId, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runAcTraceabilityCheck } from "../manifest-read-C0N7b9nH.js";
8
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR, GitClient, GrammarLoader, Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runSolutioningPhase, unescape, validateStopAfterFromConflict } from "../run-C7PLGWT0.js";
9
9
  import "../errors-8tVEwG6v.js";
10
10
  import "../routing-DFxoKHDt.js";
11
11
  import { WorkGraphRepository } from "../work-graph-repository-DZyJv5pV.js";
12
12
  import "../decisions-CzSIEeGP.js";
13
13
  import "../decision-router-BAPpON_C.js";
14
- import "../interactive-prompt-BM5RaXYG.js";
14
+ import "../interactive-prompt-BvY2FKsl.js";
15
15
  import "../recovery-engine-BKGBeBnW.js";
16
16
  import "../version-manager-impl-qFBiO4Eh.js";
17
17
  import { registerUpgradeCommand } from "../upgrade-BUSzXgnd.js";
@@ -7130,7 +7130,7 @@ async function runStatusAction(options) {
7130
7130
  logger$15.debug({ err }, "Work graph query failed, continuing without work graph data");
7131
7131
  }
7132
7132
  if (run === void 0) {
7133
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-bhMCTYDO.js");
7133
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-BdYZpCP1.js");
7134
7134
  const substrateDirPath = join(projectRoot, ".substrate");
7135
7135
  const processInfo = inspectProcessTree$1({
7136
7136
  projectRoot,
@@ -8659,7 +8659,7 @@ async function runSupervisorAction(options, deps = {}) {
8659
8659
  await initSchema(expAdapter);
8660
8660
  const { runRunAction: runPipeline } = await import(
8661
8661
  /* @vite-ignore */
8662
- "../run-ChZFveqv.js"
8662
+ "../run-CHMwDPRx.js"
8663
8663
  );
8664
8664
  const runStoryFn = async (opts) => {
8665
8665
  const exitCode = await runPipeline({
@@ -13474,15 +13474,33 @@ function registerAcceptanceCommand(program, version) {
13474
13474
  const projectRoot = process.cwd();
13475
13475
  const source = opts.ref !== void 0 ? `committed @ ${opts.ref}` : "working tree";
13476
13476
  const result = opts.ref !== void 0 ? await loadJourneyRegistryFromTrustedTree(projectRoot, opts.ref) : await loadJourneyRegistryFromFile(projectRoot);
13477
+ let contract;
13478
+ if (opts.ref !== void 0) contract = await loadAcceptanceContractFromTrustedTree(projectRoot, opts.ref);
13479
+ else try {
13480
+ const profileContent = await readFile$1(join$1(projectRoot, ACCEPTANCE_CONTRACT_PROFILE_PATH), "utf-8");
13481
+ contract = parseAcceptanceContract(profileContent);
13482
+ } catch {
13483
+ contract = { status: "absent" };
13484
+ }
13477
13485
  if (opts.outputFormat === "json") {
13478
13486
  const output = buildJsonOutput("substrate acceptance validate", {
13479
13487
  source,
13480
- ...result
13488
+ ...result,
13489
+ contract: { status: contract.status }
13481
13490
  }, version);
13482
13491
  process.stdout.write(JSON.stringify(output, null, 2) + "\n");
13483
13492
  process.exit(result.status === "ok" ? ACCEPTANCE_EXIT_SUCCESS : ACCEPTANCE_EXIT_ERROR);
13484
13493
  }
13485
- process.exit(renderHuman(result, source));
13494
+ const exitCode = renderHuman(result, source);
13495
+ if (contract.status === "ok") {
13496
+ const surfaces = Object.keys(contract.contract.surfaces).join(", ");
13497
+ process.stdout.write(`contract: OK — surfaces declared: ${surfaces}\n`);
13498
+ } else if (contract.status === "invalid") {
13499
+ process.stdout.write(`contract: INVALID (${ACCEPTANCE_CONTRACT_PROFILE_PATH} acceptance: block):\n`);
13500
+ for (const issue of contract.issues) process.stdout.write(` ${issue.path}: ${issue.message}\n`);
13501
+ } else if (contract.status === "absent") process.stdout.write(`contract: ABSENT — no acceptance: block in ${ACCEPTANCE_CONTRACT_PROFILE_PATH}. Claimed journeys can never be walked (acceptance-unrunnable in blocking mode).
13502
+ `);
13503
+ process.exit(exitCode);
13486
13504
  });
13487
13505
  acceptanceCmd.command("defer <journeyId>").description("Record an operator deferral for a journey — the coverage audit reports it `deferred` instead of unclaimed/unwalked. Writes to the working tree; COMMIT the file for the audit (trusted tree) to honor it.").requiredOption("--reason <text>", "why this journey is deferred (the operator ack is the point)").action(async (journeyId, opts) => {
13488
13506
  const projectRoot = process.cwd();
@@ -1,7 +1,7 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-Dnzblihr.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-hYMxKqAH.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-CpnLJuVw.js";
4
- import "./manifest-read-B3gE46DX.js";
4
+ import "./manifest-read-C0N7b9nH.js";
5
5
  import "./work-graph-repository-DZyJv5pV.js";
6
6
  import "./decisions-CzSIEeGP.js";
7
7
 
@@ -1,6 +1,6 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
2
  import { DoltClient, DoltQueryError, createDatabaseAdapter$1 as createDatabaseAdapter, getLatestRun, getPipelineRunById, initSchema } from "./dist-CpnLJuVw.js";
3
- import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-B3gE46DX.js";
3
+ import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-C0N7b9nH.js";
4
4
  import { createRequire } from "module";
5
5
  import { dirname, join } from "path";
6
6
  import { existsSync, readFileSync } from "node:fs";
@@ -1000,4 +1000,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
1000
1000
 
1001
1001
  //#endregion
1002
1002
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, FileKvStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDoltOperatorReader, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, runHealthAction, validateStoryKey };
1003
- //# sourceMappingURL=health-Dnzblihr.js.map
1003
+ //# sourceMappingURL=health-hYMxKqAH.js.map
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { readCurrentRunId, resolveMainRepoRoot } from "./manifest-read-B3gE46DX.js";
2
+ import { readCurrentRunId, resolveMainRepoRoot } from "./manifest-read-C0N7b9nH.js";
3
3
  import { join } from "node:path";
4
4
  import { mkdir, readFile, writeFile } from "node:fs/promises";
5
5
  import * as readline from "node:readline";
@@ -180,4 +180,4 @@ async function runInteractivePrompt(decisionContext) {
180
180
 
181
181
  //#endregion
182
182
  export { runInteractivePrompt };
183
- //# sourceMappingURL=interactive-prompt-BM5RaXYG.js.map
183
+ //# sourceMappingURL=interactive-prompt-BvY2FKsl.js.map
@@ -8,9 +8,10 @@ import { YAMLException, load } from "js-yaml";
8
8
  import { existsSync, promises, readFileSync, readdirSync, statSync } from "node:fs";
9
9
  import { execSync, spawn } from "node:child_process";
10
10
  import * as path$2 from "node:path";
11
- import { basename as basename$1, delimiter, dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
11
+ import { basename as basename$1, delimiter, dirname as dirname$1, join as join$1, relative, resolve as resolve$1 } from "node:path";
12
+ import { createHash } from "node:crypto";
12
13
  import { z } from "zod";
13
- import { mkdir as mkdir$1, open, readFile as readFile$1, unlink as unlink$1, writeFile as writeFile$1 } from "node:fs/promises";
14
+ import { mkdir as mkdir$1, open, readFile as readFile$1, readdir as readdir$1, unlink as unlink$1, writeFile as writeFile$1 } from "node:fs/promises";
14
15
  import * as fs from "fs";
15
16
  import { createRequire } from "node:module";
16
17
  import { fileURLToPath } from "node:url";
@@ -2612,13 +2613,13 @@ var ContaminationCheck = class {
2612
2613
  //#region packages/sdlc/dist/verification/checks/test-mutation-check.js
2613
2614
  /** Heuristic: is this path a test file? Covers the JS/TS/Python/Go idioms. */
2614
2615
  function isTestPath(file) {
2615
- const norm = file.replace(/\\/g, "/").toLowerCase();
2616
- const base = norm.split("/").pop() ?? "";
2617
- if (/(^|\/)(__tests__|tests?)\//.test(norm)) return true;
2616
+ const norm$1 = file.replace(/\\/g, "/").toLowerCase();
2617
+ const base = norm$1.split("/").pop() ?? "";
2618
+ if (/(^|\/)(__tests__|tests?)\//.test(norm$1)) return true;
2618
2619
  if (/\.(test|spec)\.[a-z]+$/.test(base)) return true;
2619
2620
  if (/^test_.*\.py$/.test(base) || /_test\.(py|go)$/.test(base)) return true;
2620
2621
  if (base === "conftest.py") return true;
2621
- if (/(^|\/)(fixtures|factories|testsupport|test-support|test_support|__mocks__|__fixtures__)\//.test(norm)) return true;
2622
+ if (/(^|\/)(fixtures|factories|testsupport|test-support|test_support|__mocks__|__fixtures__)\//.test(norm$1)) return true;
2622
2623
  return false;
2623
2624
  }
2624
2625
  var TestMutationCheck = class {
@@ -2648,6 +2649,351 @@ var TestMutationCheck = class {
2648
2649
  }
2649
2650
  };
2650
2651
 
2652
+ //#endregion
2653
+ //#region packages/sdlc/dist/acceptance/registry.js
2654
+ /** Repo-relative path of the registry — the single canonical location. */
2655
+ const JOURNEY_REGISTRY_PATH = ".substrate/acceptance/journeys.yaml";
2656
+ const JourneySurfaceSchema = z.enum([
2657
+ "email",
2658
+ "cli",
2659
+ "file",
2660
+ "web"
2661
+ ]);
2662
+ const JourneyEndStateSchema = z.object({
2663
+ id: z.string().min(1, "end-state id must be a non-empty string"),
2664
+ given: z.string().min(1, "given must be a non-empty string"),
2665
+ walk: z.string().min(1, "walk must be a non-empty string"),
2666
+ then: z.string().min(1, "then must be a non-empty string")
2667
+ });
2668
+ const JourneySchema = z.object({
2669
+ id: z.string().min(1, "journey id must be a non-empty string"),
2670
+ title: z.string().min(1, "title must be a non-empty string"),
2671
+ criticality: z.enum(["critical", "standard"]),
2672
+ surfaces: z.array(JourneySurfaceSchema).min(1, "a journey must declare at least one surface"),
2673
+ epic: z.number().int().positive().optional(),
2674
+ end_states: z.array(JourneyEndStateSchema).min(1, "a journey must declare at least one end_state — a journey with none is unjudgeable")
2675
+ });
2676
+ const JourneyRegistrySchema = z.object({
2677
+ version: z.number().int().positive("version must be a positive integer"),
2678
+ journeys: z.array(JourneySchema)
2679
+ }).superRefine((registry, ctx) => {
2680
+ const seenJourneyIds = new Map();
2681
+ registry.journeys.forEach((journey, i) => {
2682
+ const firstIndex = seenJourneyIds.get(journey.id);
2683
+ if (firstIndex !== void 0) ctx.addIssue({
2684
+ code: "custom",
2685
+ path: [
2686
+ "journeys",
2687
+ i,
2688
+ "id"
2689
+ ],
2690
+ message: `duplicate journey id "${journey.id}" (first declared at journeys[${firstIndex}])`
2691
+ });
2692
+ else seenJourneyIds.set(journey.id, i);
2693
+ const seenEndStateIds = new Map();
2694
+ journey.end_states.forEach((endState, j) => {
2695
+ const firstEs = seenEndStateIds.get(endState.id);
2696
+ if (firstEs !== void 0) ctx.addIssue({
2697
+ code: "custom",
2698
+ path: [
2699
+ "journeys",
2700
+ i,
2701
+ "end_states",
2702
+ j,
2703
+ "id"
2704
+ ],
2705
+ message: `duplicate end-state id "${endState.id}" in journey "${journey.id}" (first declared at end_states[${firstEs}])`
2706
+ });
2707
+ else seenEndStateIds.set(endState.id, j);
2708
+ });
2709
+ });
2710
+ });
2711
+ function zodErrorToValidationIssues(error) {
2712
+ return error.issues.map((issue) => ({
2713
+ path: issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)",
2714
+ message: issue.message
2715
+ }));
2716
+ }
2717
+ /**
2718
+ * Parse and validate registry YAML content.
2719
+ *
2720
+ * Never throws: malformed YAML, non-object documents, and schema violations
2721
+ * all come back as named, pathed issues.
2722
+ */
2723
+ function parseJourneyRegistry(yamlContent) {
2724
+ let doc;
2725
+ try {
2726
+ doc = load(yamlContent);
2727
+ } catch (err) {
2728
+ const message = err instanceof YAMLException ? err.message : String(err);
2729
+ return {
2730
+ ok: false,
2731
+ issues: [{
2732
+ path: "(root)",
2733
+ message: `malformed YAML: ${message}`
2734
+ }]
2735
+ };
2736
+ }
2737
+ if (doc === null || doc === void 0) return {
2738
+ ok: false,
2739
+ issues: [{
2740
+ path: "(root)",
2741
+ message: "registry file is empty"
2742
+ }]
2743
+ };
2744
+ const result = JourneyRegistrySchema.safeParse(doc);
2745
+ if (!result.success) return {
2746
+ ok: false,
2747
+ issues: zodErrorToValidationIssues(result.error)
2748
+ };
2749
+ return {
2750
+ ok: true,
2751
+ registry: result.data
2752
+ };
2753
+ }
2754
+
2755
+ //#endregion
2756
+ //#region packages/sdlc/dist/acceptance/coverage.js
2757
+ /**
2758
+ * Compute the coverage state of every journey audited at this scope.
2759
+ *
2760
+ * - `{epic: n}` audits journeys declaring `epic: n`.
2761
+ * - `{final: true}` audits the FULL registry (epicless journeys have no
2762
+ * earlier audit point; epic-scoped ones are re-audited — idempotent).
2763
+ *
2764
+ * State precedence: deferred > walked-fail > walked-pass > unwalked/unclaimed.
2765
+ * An operator deferral wins over everything (explicit scope decision); a fail
2766
+ * verdict wins over a pass (any failing end-state fails the journey).
2767
+ */
2768
+ function computeJourneyCoverage(input) {
2769
+ const { registry, claims, verdicts, deferredJourneyIds, scope } = input;
2770
+ const audited = registry.journeys.filter((j) => "final" in scope ? true : j.epic === scope.epic);
2771
+ const claimsByJourney = new Map();
2772
+ for (const claim of claims) {
2773
+ const owners = claimsByJourney.get(claim.journeyId) ?? [];
2774
+ if (!owners.includes(claim.storyKey)) owners.push(claim.storyKey);
2775
+ claimsByJourney.set(claim.journeyId, owners);
2776
+ }
2777
+ const deferred = new Set(deferredJourneyIds);
2778
+ const verdictByJourney = new Map();
2779
+ for (const v of verdicts) {
2780
+ const existing = verdictByJourney.get(v.journeyId);
2781
+ verdictByJourney.set(v.journeyId, existing === "fail" ? "fail" : v.verdict);
2782
+ }
2783
+ return audited.map((journey) => {
2784
+ const ownerStories = claimsByJourney.get(journey.id) ?? [];
2785
+ let state;
2786
+ if (deferred.has(journey.id)) state = "deferred";
2787
+ else {
2788
+ const verdict = verdictByJourney.get(journey.id);
2789
+ if (verdict === "fail") state = "walked-fail";
2790
+ else if (verdict === "pass") state = "walked-pass";
2791
+ else state = ownerStories.length > 0 ? "unwalked" : "unclaimed";
2792
+ }
2793
+ return {
2794
+ journeyId: journey.id,
2795
+ title: journey.title,
2796
+ criticality: journey.criticality,
2797
+ ...journey.epic !== void 0 ? { epic: journey.epic } : {},
2798
+ state,
2799
+ ownerStories
2800
+ };
2801
+ });
2802
+ }
2803
+ /** Aggregate counts for the `acceptance:coverage` event payload. */
2804
+ function summarizeCoverage(entries) {
2805
+ const summary = {
2806
+ "walked-pass": 0,
2807
+ "walked-fail": 0,
2808
+ deferred: 0,
2809
+ unclaimed: 0,
2810
+ unwalked: 0
2811
+ };
2812
+ for (const entry of entries) summary[entry.state] += 1;
2813
+ return summary;
2814
+ }
2815
+ /** Repo-relative path of the operator deferral file. */
2816
+ const JOURNEY_DEFERRALS_PATH = ".substrate/acceptance/deferrals.yaml";
2817
+ const JourneyDeferralSchema = z.object({
2818
+ journey: z.string().min(1),
2819
+ reason: z.string().min(1, "a deferral must carry an operator reason"),
2820
+ deferred_at: z.string().optional()
2821
+ });
2822
+ const JourneyDeferralsFileSchema = z.object({ deferrals: z.array(JourneyDeferralSchema) });
2823
+ /** Parse deferrals YAML. Never throws; malformed content comes back as issues. */
2824
+ function parseJourneyDeferrals(yamlContent) {
2825
+ let doc;
2826
+ try {
2827
+ doc = load(yamlContent);
2828
+ } catch (err) {
2829
+ const message = err instanceof YAMLException ? err.message : String(err);
2830
+ return {
2831
+ ok: false,
2832
+ issues: [{
2833
+ path: "(root)",
2834
+ message: `malformed YAML: ${message}`
2835
+ }]
2836
+ };
2837
+ }
2838
+ if (doc === null || doc === void 0) return {
2839
+ ok: true,
2840
+ deferrals: []
2841
+ };
2842
+ const result = JourneyDeferralsFileSchema.safeParse(doc);
2843
+ if (!result.success) return {
2844
+ ok: false,
2845
+ issues: result.error.issues.map((issue) => ({
2846
+ path: issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)",
2847
+ message: issue.message
2848
+ }))
2849
+ };
2850
+ return {
2851
+ ok: true,
2852
+ deferrals: result.data.deferrals
2853
+ };
2854
+ }
2855
+
2856
+ //#endregion
2857
+ //#region packages/sdlc/dist/acceptance/contract.js
2858
+ /** The acceptance contract lives inside the project profile. */
2859
+ const ACCEPTANCE_CONTRACT_PROFILE_PATH = ".substrate/project-profile.yaml";
2860
+ const RenderSurfaceSchema = z.object({ render: z.string().min(1) }).strict();
2861
+ const WebSurfaceSchema = z.object({
2862
+ serve: z.string().min(1),
2863
+ ready: z.string().optional()
2864
+ }).strict();
2865
+ const AcceptanceContractSchema = z.object({
2866
+ fixtures: z.string().optional(),
2867
+ surfaces: z.object({
2868
+ email: RenderSurfaceSchema.optional(),
2869
+ cli: RenderSurfaceSchema.optional(),
2870
+ file: RenderSurfaceSchema.optional(),
2871
+ web: WebSurfaceSchema.optional()
2872
+ }).strict()
2873
+ }).strict();
2874
+ /**
2875
+ * Extract + validate the `acceptance:` block from project-profile YAML
2876
+ * content. `absent` when the profile has no block (legal — the coverage
2877
+ * audit escalates registry-present+contract-absent as acceptance-unrunnable
2878
+ * in blocking mode; pure accounting still works without one).
2879
+ */
2880
+ function parseAcceptanceContract(profileYamlContent) {
2881
+ let doc;
2882
+ try {
2883
+ doc = load(profileYamlContent);
2884
+ } catch (err) {
2885
+ const message = err instanceof YAMLException ? err.message : String(err);
2886
+ return {
2887
+ status: "invalid",
2888
+ issues: [{
2889
+ path: "(root)",
2890
+ message: `malformed profile YAML: ${message}`
2891
+ }]
2892
+ };
2893
+ }
2894
+ if (doc === null || doc === void 0 || typeof doc !== "object") return { status: "absent" };
2895
+ const block = doc["acceptance"];
2896
+ if (block === void 0 || block === null) return { status: "absent" };
2897
+ const result = AcceptanceContractSchema.safeParse(block);
2898
+ if (!result.success) return {
2899
+ status: "invalid",
2900
+ issues: result.error.issues.map((issue) => ({
2901
+ path: issue.path.length > 0 ? `acceptance.${issue.path.map(String).join(".")}` : "acceptance",
2902
+ message: issue.message
2903
+ }))
2904
+ };
2905
+ return {
2906
+ status: "ok",
2907
+ contract: result.data
2908
+ };
2909
+ }
2910
+
2911
+ //#endregion
2912
+ //#region packages/sdlc/dist/verification/checks/acceptance-spec-check.js
2913
+ async function readWorktreeCopy(workingDir, relPath) {
2914
+ try {
2915
+ return await readFile$1(join$1(workingDir, relPath), "utf-8");
2916
+ } catch {
2917
+ return null;
2918
+ }
2919
+ }
2920
+ const norm = (s) => s === null ? null : s.replace(/\r\n/g, "\n").trim();
2921
+ /** Compare only the acceptance BLOCK of the profile (other profile keys are ContaminationCheck/H7 territory). */
2922
+ function acceptanceBlockFingerprint(profileContent) {
2923
+ if (profileContent === null) return "absent";
2924
+ const parsed = parseAcceptanceContract(profileContent);
2925
+ if (parsed.status === "absent") return "absent";
2926
+ if (parsed.status === "invalid") return "invalid:" + JSON.stringify(parsed.issues);
2927
+ return JSON.stringify(parsed.contract);
2928
+ }
2929
+ var AcceptanceSpecCheck = class {
2930
+ name = "acceptance-spec";
2931
+ tier = "A";
2932
+ async run(context) {
2933
+ const start = Date.now();
2934
+ const guard = context.acceptanceSpecGuard;
2935
+ if (guard === void 0) return {
2936
+ status: "pass",
2937
+ details: "no acceptance spec guard in context (acceptance not configured or non-worktree dispatch)",
2938
+ duration_ms: Date.now() - start,
2939
+ findings: []
2940
+ };
2941
+ const findings = [];
2942
+ const rawSpecs = [{
2943
+ path: JOURNEY_REGISTRY_PATH,
2944
+ trusted: guard.journeysTrusted
2945
+ }, {
2946
+ path: JOURNEY_DEFERRALS_PATH,
2947
+ trusted: guard.deferralsTrusted
2948
+ }];
2949
+ for (const spec of rawSpecs) {
2950
+ const worktree = await readWorktreeCopy(context.workingDir, spec.path);
2951
+ const trustedNorm = norm(spec.trusted);
2952
+ const worktreeNorm = norm(worktree);
2953
+ if (trustedNorm === worktreeNorm) continue;
2954
+ const shape = trustedNorm === null ? "INTRODUCED in the story worktree (acceptance specs are planning/operator-authored, never story output)" : worktreeNorm === null ? "DELETED in the story worktree" : "DIVERGES from the trusted copy";
2955
+ findings.push({
2956
+ category: "acceptance-spec-tampered",
2957
+ severity: "error",
2958
+ message: `${spec.path} ${shape}. The acceptance gate reads the trusted tree, so this edit cannot fool the current audit — but merging it would change every FUTURE audit. Revert it or make the change through planning (operator commit outside a story).`
2959
+ });
2960
+ }
2961
+ const worktreeProfile = await readWorktreeCopy(context.workingDir, ACCEPTANCE_CONTRACT_PROFILE_PATH);
2962
+ const trustedFp = acceptanceBlockFingerprint(guard.profileTrusted);
2963
+ const worktreeFp = acceptanceBlockFingerprint(worktreeProfile);
2964
+ if (trustedFp !== worktreeFp) findings.push({
2965
+ category: "acceptance-spec-tampered",
2966
+ severity: "error",
2967
+ message: `the acceptance: contract block in ${ACCEPTANCE_CONTRACT_PROFILE_PATH} differs between the story worktree and the trusted tree (render-command laundering shape). Revert the worktree edit.`
2968
+ });
2969
+ if (guard.fixturesPath !== void 0) {
2970
+ const prefix = guard.fixturesPath.replace(/\\/g, "/").replace(/\/+$/, "") + "/";
2971
+ const touched = (context.changedFiles ?? []).filter((f) => {
2972
+ const n = f.replace(/\\/g, "/");
2973
+ return n.startsWith(prefix) || n === prefix.slice(0, -1);
2974
+ });
2975
+ if (touched.length > 0) findings.push({
2976
+ category: "acceptance-fixture-mutation",
2977
+ severity: "warn",
2978
+ message: `story modified ${String(touched.length)} file(s) under the declared acceptance fixtures path (${guard.fixturesPath}): ${touched.slice(0, 10).join(", ")}` + (touched.length > 10 ? ` (+${String(touched.length - 10)} more)` : "") + `. The judge grounds verdicts in these fixtures — review whether the change tilts the walk.`
2979
+ });
2980
+ }
2981
+ if (findings.length === 0) return {
2982
+ status: "pass",
2983
+ details: "acceptance specs match the trusted tree; no fixture mutations",
2984
+ duration_ms: Date.now() - start,
2985
+ findings: []
2986
+ };
2987
+ const hasError = findings.some((f) => f.severity === "error");
2988
+ return {
2989
+ status: hasError ? "fail" : "warn",
2990
+ details: renderFindings(findings),
2991
+ duration_ms: Date.now() - start,
2992
+ findings
2993
+ };
2994
+ }
2995
+ };
2996
+
2651
2997
  //#endregion
2652
2998
  //#region packages/sdlc/dist/verification/probes/types.js
2653
2999
  /**
@@ -4603,6 +4949,7 @@ function createDefaultVerificationPipeline(bus, config) {
4603
4949
  new TestSuiteCheck(),
4604
4950
  new ContaminationCheck(),
4605
4951
  new TestMutationCheck(),
4952
+ new AcceptanceSpecCheck(),
4606
4953
  new RuntimeProbeCheck(),
4607
4954
  new SourceAcFidelityCheck(),
4608
4955
  new SourceAcShelloutCheck(),
@@ -4977,210 +5324,6 @@ async function runAcTraceabilityCheck(input) {
4977
5324
  };
4978
5325
  }
4979
5326
 
4980
- //#endregion
4981
- //#region packages/sdlc/dist/acceptance/registry.js
4982
- /** Repo-relative path of the registry — the single canonical location. */
4983
- const JOURNEY_REGISTRY_PATH = ".substrate/acceptance/journeys.yaml";
4984
- const JourneySurfaceSchema = z.enum([
4985
- "email",
4986
- "cli",
4987
- "file",
4988
- "web"
4989
- ]);
4990
- const JourneyEndStateSchema = z.object({
4991
- id: z.string().min(1, "end-state id must be a non-empty string"),
4992
- given: z.string().min(1, "given must be a non-empty string"),
4993
- walk: z.string().min(1, "walk must be a non-empty string"),
4994
- then: z.string().min(1, "then must be a non-empty string")
4995
- });
4996
- const JourneySchema = z.object({
4997
- id: z.string().min(1, "journey id must be a non-empty string"),
4998
- title: z.string().min(1, "title must be a non-empty string"),
4999
- criticality: z.enum(["critical", "standard"]),
5000
- surfaces: z.array(JourneySurfaceSchema).min(1, "a journey must declare at least one surface"),
5001
- epic: z.number().int().positive().optional(),
5002
- end_states: z.array(JourneyEndStateSchema).min(1, "a journey must declare at least one end_state — a journey with none is unjudgeable")
5003
- });
5004
- const JourneyRegistrySchema = z.object({
5005
- version: z.number().int().positive("version must be a positive integer"),
5006
- journeys: z.array(JourneySchema)
5007
- }).superRefine((registry, ctx) => {
5008
- const seenJourneyIds = new Map();
5009
- registry.journeys.forEach((journey, i) => {
5010
- const firstIndex = seenJourneyIds.get(journey.id);
5011
- if (firstIndex !== void 0) ctx.addIssue({
5012
- code: "custom",
5013
- path: [
5014
- "journeys",
5015
- i,
5016
- "id"
5017
- ],
5018
- message: `duplicate journey id "${journey.id}" (first declared at journeys[${firstIndex}])`
5019
- });
5020
- else seenJourneyIds.set(journey.id, i);
5021
- const seenEndStateIds = new Map();
5022
- journey.end_states.forEach((endState, j) => {
5023
- const firstEs = seenEndStateIds.get(endState.id);
5024
- if (firstEs !== void 0) ctx.addIssue({
5025
- code: "custom",
5026
- path: [
5027
- "journeys",
5028
- i,
5029
- "end_states",
5030
- j,
5031
- "id"
5032
- ],
5033
- message: `duplicate end-state id "${endState.id}" in journey "${journey.id}" (first declared at end_states[${firstEs}])`
5034
- });
5035
- else seenEndStateIds.set(endState.id, j);
5036
- });
5037
- });
5038
- });
5039
- function zodErrorToValidationIssues(error) {
5040
- return error.issues.map((issue) => ({
5041
- path: issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)",
5042
- message: issue.message
5043
- }));
5044
- }
5045
- /**
5046
- * Parse and validate registry YAML content.
5047
- *
5048
- * Never throws: malformed YAML, non-object documents, and schema violations
5049
- * all come back as named, pathed issues.
5050
- */
5051
- function parseJourneyRegistry(yamlContent) {
5052
- let doc;
5053
- try {
5054
- doc = load(yamlContent);
5055
- } catch (err) {
5056
- const message = err instanceof YAMLException ? err.message : String(err);
5057
- return {
5058
- ok: false,
5059
- issues: [{
5060
- path: "(root)",
5061
- message: `malformed YAML: ${message}`
5062
- }]
5063
- };
5064
- }
5065
- if (doc === null || doc === void 0) return {
5066
- ok: false,
5067
- issues: [{
5068
- path: "(root)",
5069
- message: "registry file is empty"
5070
- }]
5071
- };
5072
- const result = JourneyRegistrySchema.safeParse(doc);
5073
- if (!result.success) return {
5074
- ok: false,
5075
- issues: zodErrorToValidationIssues(result.error)
5076
- };
5077
- return {
5078
- ok: true,
5079
- registry: result.data
5080
- };
5081
- }
5082
-
5083
- //#endregion
5084
- //#region packages/sdlc/dist/acceptance/coverage.js
5085
- /**
5086
- * Compute the coverage state of every journey audited at this scope.
5087
- *
5088
- * - `{epic: n}` audits journeys declaring `epic: n`.
5089
- * - `{final: true}` audits the FULL registry (epicless journeys have no
5090
- * earlier audit point; epic-scoped ones are re-audited — idempotent).
5091
- *
5092
- * State precedence: deferred > walked-fail > walked-pass > unwalked/unclaimed.
5093
- * An operator deferral wins over everything (explicit scope decision); a fail
5094
- * verdict wins over a pass (any failing end-state fails the journey).
5095
- */
5096
- function computeJourneyCoverage(input) {
5097
- const { registry, claims, verdicts, deferredJourneyIds, scope } = input;
5098
- const audited = registry.journeys.filter((j) => "final" in scope ? true : j.epic === scope.epic);
5099
- const claimsByJourney = new Map();
5100
- for (const claim of claims) {
5101
- const owners = claimsByJourney.get(claim.journeyId) ?? [];
5102
- if (!owners.includes(claim.storyKey)) owners.push(claim.storyKey);
5103
- claimsByJourney.set(claim.journeyId, owners);
5104
- }
5105
- const deferred = new Set(deferredJourneyIds);
5106
- const verdictByJourney = new Map();
5107
- for (const v of verdicts) {
5108
- const existing = verdictByJourney.get(v.journeyId);
5109
- verdictByJourney.set(v.journeyId, existing === "fail" ? "fail" : v.verdict);
5110
- }
5111
- return audited.map((journey) => {
5112
- const ownerStories = claimsByJourney.get(journey.id) ?? [];
5113
- let state;
5114
- if (deferred.has(journey.id)) state = "deferred";
5115
- else {
5116
- const verdict = verdictByJourney.get(journey.id);
5117
- if (verdict === "fail") state = "walked-fail";
5118
- else if (verdict === "pass") state = "walked-pass";
5119
- else state = ownerStories.length > 0 ? "unwalked" : "unclaimed";
5120
- }
5121
- return {
5122
- journeyId: journey.id,
5123
- title: journey.title,
5124
- criticality: journey.criticality,
5125
- ...journey.epic !== void 0 ? { epic: journey.epic } : {},
5126
- state,
5127
- ownerStories
5128
- };
5129
- });
5130
- }
5131
- /** Aggregate counts for the `acceptance:coverage` event payload. */
5132
- function summarizeCoverage(entries) {
5133
- const summary = {
5134
- "walked-pass": 0,
5135
- "walked-fail": 0,
5136
- deferred: 0,
5137
- unclaimed: 0,
5138
- unwalked: 0
5139
- };
5140
- for (const entry of entries) summary[entry.state] += 1;
5141
- return summary;
5142
- }
5143
- /** Repo-relative path of the operator deferral file. */
5144
- const JOURNEY_DEFERRALS_PATH = ".substrate/acceptance/deferrals.yaml";
5145
- const JourneyDeferralSchema = z.object({
5146
- journey: z.string().min(1),
5147
- reason: z.string().min(1, "a deferral must carry an operator reason"),
5148
- deferred_at: z.string().optional()
5149
- });
5150
- const JourneyDeferralsFileSchema = z.object({ deferrals: z.array(JourneyDeferralSchema) });
5151
- /** Parse deferrals YAML. Never throws; malformed content comes back as issues. */
5152
- function parseJourneyDeferrals(yamlContent) {
5153
- let doc;
5154
- try {
5155
- doc = load(yamlContent);
5156
- } catch (err) {
5157
- const message = err instanceof YAMLException ? err.message : String(err);
5158
- return {
5159
- ok: false,
5160
- issues: [{
5161
- path: "(root)",
5162
- message: `malformed YAML: ${message}`
5163
- }]
5164
- };
5165
- }
5166
- if (doc === null || doc === void 0) return {
5167
- ok: true,
5168
- deferrals: []
5169
- };
5170
- const result = JourneyDeferralsFileSchema.safeParse(doc);
5171
- if (!result.success) return {
5172
- ok: false,
5173
- issues: result.error.issues.map((issue) => ({
5174
- path: issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)",
5175
- message: issue.message
5176
- }))
5177
- };
5178
- return {
5179
- ok: true,
5180
- deferrals: result.data.deferrals
5181
- };
5182
- }
5183
-
5184
5327
  //#endregion
5185
5328
  //#region packages/sdlc/dist/acceptance/loader.js
5186
5329
  /** stderr shapes that mean "the path is not in the tree at this ref" (absent, not an error). */
@@ -5286,6 +5429,26 @@ async function loadJourneyDeferralsFromTrustedTree(repoRoot, ref = "HEAD") {
5286
5429
  };
5287
5430
  }
5288
5431
  /**
5432
+ * Load the acceptance contract (the `acceptance:` block of the project
5433
+ * profile) from the trusted tree at `ref` (A1.1). Same H7 posture as the
5434
+ * registry: the agent-writable worktree copy of the profile is never read.
5435
+ */
5436
+ async function loadAcceptanceContractFromTrustedTree(repoRoot, ref = "HEAD") {
5437
+ const result = await runGitShow(repoRoot, ref, ACCEPTANCE_CONTRACT_PROFILE_PATH);
5438
+ if (result.spawnError !== void 0) return {
5439
+ status: "error",
5440
+ message: `git show could not be spawned: ${result.spawnError}`
5441
+ };
5442
+ if (result.code !== 0) {
5443
+ if (GIT_SHOW_ABSENT_PATTERNS.some((p) => p.test(result.stderr))) return { status: "absent" };
5444
+ return {
5445
+ status: "error",
5446
+ message: `git show ${ref}:${ACCEPTANCE_CONTRACT_PROFILE_PATH} failed (exit ${String(result.code)}): ${result.stderr.trim()}`
5447
+ };
5448
+ }
5449
+ return parseAcceptanceContract(result.stdout);
5450
+ }
5451
+ /**
5289
5452
  * Filesystem read for OPERATOR LINT ONLY (`substrate acceptance validate`).
5290
5453
  * Gate/judge code paths must use `loadJourneyRegistryFromTrustedTree`.
5291
5454
  */
@@ -6746,5 +6909,5 @@ async function resolveRunManifest(dbRoot, runId) {
6746
6909
  }
6747
6910
 
6748
6911
  //#endregion
6749
- export { FindingsInjector, JOURNEY_DEFERRALS_PATH, JOURNEY_REGISTRY_PATH, RunManifest, RuntimeProbeListSchema, SupervisorLock, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, applyConfigToGraph, computeJourneyCoverage, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, loadJourneyDeferralsFromTrustedTree, loadJourneyRegistryFromFile, loadJourneyRegistryFromTrustedTree, parseJourneyDeferrals, parseRuntimeProbes, parseStoryFrontmatter, readCurrentRunId, renderFindings, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runAcTraceabilityCheck, runStaleVerificationRecovery, summarizeCoverage };
6750
- //# sourceMappingURL=manifest-read-B3gE46DX.js.map
6912
+ export { ACCEPTANCE_CONTRACT_PROFILE_PATH, FindingsInjector, JOURNEY_DEFERRALS_PATH, JOURNEY_REGISTRY_PATH, RunManifest, RuntimeProbeListSchema, SupervisorLock, ZERO_FINDINGS_BY_AUTHOR, ZERO_FINDING_COUNTS, ZERO_PROBE_AUTHOR_METRICS, aggregateProbeAuthorMetrics, applyConfigToGraph, computeJourneyCoverage, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, loadAcceptanceContractFromTrustedTree, loadJourneyDeferralsFromTrustedTree, loadJourneyRegistryFromFile, loadJourneyRegistryFromTrustedTree, parseAcceptanceContract, parseJourneyDeferrals, parseRuntimeProbes, parseStoryFrontmatter, readCurrentRunId, renderFindings, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, rollupFindingsByAuthor, rollupProbeAuthorByClass, rollupProbeAuthorMetrics, runAcTraceabilityCheck, runStaleVerificationRecovery, summarizeCoverage };
6913
+ //# sourceMappingURL=manifest-read-C0N7b9nH.js.map
@@ -1,6 +1,6 @@
1
1
  import "../../logger-KeHncl-f.js";
2
2
  import "../../dist-CpnLJuVw.js";
3
- import "../../manifest-read-B3gE46DX.js";
4
- import { runInteractivePrompt } from "../../interactive-prompt-BM5RaXYG.js";
3
+ import "../../manifest-read-C0N7b9nH.js";
4
+ import { runInteractivePrompt } from "../../interactive-prompt-BvY2FKsl.js";
5
5
 
6
6
  export { runInteractivePrompt };
@@ -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-Dnzblihr.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-hYMxKqAH.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, 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";
5
+ import { ACCEPTANCE_CONTRACT_PROFILE_PATH, FindingsInjector, JOURNEY_DEFERRALS_PATH, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, computeJourneyCoverage, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, loadAcceptanceContractFromTrustedTree, loadJourneyDeferralsFromTrustedTree, loadJourneyRegistryFromTrustedTree, parseRuntimeProbes, parseStoryFrontmatter, renderFindings, resolveGraphPath, resolveMainRepoRoot, runAcTraceabilityCheck, runStaleVerificationRecovery, summarizeCoverage } from "./manifest-read-C0N7b9nH.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-BM5RaXYG.js";
8
+ import { runInteractivePrompt } from "./interactive-prompt-BvY2FKsl.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";
@@ -983,7 +983,7 @@ const PIPELINE_EVENT_METADATA = [
983
983
  {
984
984
  name: "reason",
985
985
  type: "string",
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+."
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); acceptance-unrunnable (acceptance.mode: blocking — a journey registry exists but the committed acceptance contract/registry is absent or invalid, so claimed journeys can never be walked; declare an acceptance: block in the project profile, A1.1). v0.20.87+."
987
987
  },
988
988
  {
989
989
  name: "cycles",
@@ -12202,6 +12202,28 @@ function assembleVerificationContext(opts) {
12202
12202
  const profileBuildCommand = readProfileKey(trustedDir, "buildCommand");
12203
12203
  const profileTestCommand = readProfileKey(trustedDir, "testCommand");
12204
12204
  const trustedLanguages = readProfileLanguagesLocal(trustedDir);
12205
+ let acceptanceSpecGuard;
12206
+ if (opts.trustedProfileDir !== void 0) {
12207
+ const readOrNull = (rel) => {
12208
+ try {
12209
+ const content = readFileSync(join$1(trustedDir, rel), "utf-8");
12210
+ return content.trim().length > 0 ? content : null;
12211
+ } catch {
12212
+ return null;
12213
+ }
12214
+ };
12215
+ const journeysTrusted = readOrNull(".substrate/acceptance/journeys.yaml");
12216
+ if (journeysTrusted !== null) {
12217
+ const profileTrusted = readOrNull(".substrate/project-profile.yaml");
12218
+ const fixturesPath = readAcceptanceFixturesPathLocal(profileTrusted);
12219
+ acceptanceSpecGuard = {
12220
+ journeysTrusted,
12221
+ deferralsTrusted: readOrNull(".substrate/acceptance/deferrals.yaml"),
12222
+ profileTrusted,
12223
+ ...fixturesPath !== void 0 ? { fixturesPath } : {}
12224
+ };
12225
+ }
12226
+ }
12205
12227
  return {
12206
12228
  storyKey: opts.storyKey,
12207
12229
  workingDir: opts.workingDir,
@@ -12217,10 +12239,32 @@ function assembleVerificationContext(opts) {
12217
12239
  ...opts.modifiedTrackedFiles !== void 0 ? { modifiedTrackedFiles: opts.modifiedTrackedFiles } : {},
12218
12240
  ...profileBuildCommand !== void 0 ? { buildCommand: profileBuildCommand } : {},
12219
12241
  ...profileTestCommand !== void 0 ? { testCommand: profileTestCommand } : {},
12220
- ...trustedLanguages.length > 0 ? { trustedLanguages } : {}
12242
+ ...trustedLanguages.length > 0 ? { trustedLanguages } : {},
12243
+ ...acceptanceSpecGuard !== void 0 ? { acceptanceSpecGuard } : {}
12221
12244
  };
12222
12245
  }
12223
12246
  /**
12247
+ * A1.3: extract `fixtures:` from the `acceptance:` block of profile content.
12248
+ * Line-based (no yaml dependency, no sdlc value import) — tracks entry/exit
12249
+ * of the top-level acceptance block by indentation.
12250
+ */
12251
+ function readAcceptanceFixturesPathLocal(profileContent) {
12252
+ if (profileContent === null) return void 0;
12253
+ let inAcceptance = false;
12254
+ for (const line of profileContent.split("\n")) {
12255
+ if (/^acceptance:\s*$/.test(line)) {
12256
+ inAcceptance = true;
12257
+ continue;
12258
+ }
12259
+ if (inAcceptance && /^\S/.test(line)) inAcceptance = false;
12260
+ if (inAcceptance) {
12261
+ const m = /^\s+fixtures:\s*['"]?([^'"\n#]+?)['"]?\s*$/.exec(line);
12262
+ if (m?.[1] !== void 0) return m[1].trim();
12263
+ }
12264
+ }
12265
+ return void 0;
12266
+ }
12267
+ /**
12224
12268
  * Read a single `<key>: value` line from `.substrate/project-profile.yaml`
12225
12269
  * under `workingDir` (H1.2). Line-based parse — no yaml dependency.
12226
12270
  */
@@ -13957,10 +14001,19 @@ function createImplementationOrchestrator(deps) {
13957
14001
  if (mode === "off" || projectRoot === void 0) return void 0;
13958
14002
  const registryLoad = await loadJourneyRegistryFromTrustedTree(projectRoot);
13959
14003
  if (registryLoad.status === "absent") return void 0;
14004
+ if (registryLoad.status === "invalid") return {
14005
+ entries: [],
14006
+ unrunnable: "journey registry is INVALID: " + registryLoad.issues.map((i) => `${i.path}: ${i.message}`).join("; ") + " — fix it (substrate acceptance validate) or the audit cannot run"
14007
+ };
13960
14008
  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)");
14009
+ logger$27.warn({ detail: registryLoad.message }, "A0.3: journey registry read failed — coverage audit skipped (environmental)");
13962
14010
  return void 0;
13963
14011
  }
14012
+ let unrunnable;
14013
+ const contractLoad = await loadAcceptanceContractFromTrustedTree(projectRoot);
14014
+ if (contractLoad.status === "absent") unrunnable = `journey registry exists but the committed project profile has no acceptance: contract block (${ACCEPTANCE_CONTRACT_PROFILE_PATH}) — claimed journeys can never be walked. Declare render commands per surface.`;
14015
+ else if (contractLoad.status === "invalid") unrunnable = "acceptance: contract block is INVALID: " + contractLoad.issues.map((i) => `${i.path}: ${i.message}`).join("; ");
14016
+ else if (contractLoad.status === "error") logger$27.warn({ detail: contractLoad.message }, "A1.1: acceptance contract read failed (environmental) — treating as absent for this audit");
13964
14017
  const deferralsLoad = await loadJourneyDeferralsFromTrustedTree(projectRoot);
13965
14018
  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
14019
  const deferredJourneyIds = deferralsLoad.status === "ok" ? deferralsLoad.deferrals.map((d) => d.journey) : [];
@@ -13983,8 +14036,12 @@ function createImplementationOrchestrator(deps) {
13983
14036
  entries,
13984
14037
  summary: summarizeCoverage(entries)
13985
14038
  });
14039
+ if (unrunnable !== void 0) logger$27.warn({ scope: scopeLabel }, `A1.1: acceptance gate unrunnable — ${unrunnable}`);
13986
14040
  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;
14041
+ return unrunnable !== void 0 ? {
14042
+ entries,
14043
+ unrunnable
14044
+ } : { entries };
13988
14045
  }
13989
14046
  function getStallThresholdMs(phase) {
13990
14047
  return phase === "IN_DEV" ? DEV_STORY_STALL_THRESHOLD_MS : DEFAULT_STALL_THRESHOLD_MS;
@@ -16379,17 +16436,19 @@ function createImplementationOrchestrator(deps) {
16379
16436
  const isLastOfEpicA = [..._stories.entries()].filter(([key]) => key !== storyKey && epicOfA(key) === epicIdA).every(([, st]) => TERMINAL_A.includes(st.phase));
16380
16437
  const epicNum = Number(epicIdA);
16381
16438
  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) {
16439
+ const audit = await auditJourneyCoverage({ epic: epicNum });
16440
+ const violations = (audit?.entries ?? []).filter((e) => e.state === "unclaimed" || e.state === "unwalked");
16441
+ if (violations.length > 0 || audit?.unrunnable !== void 0) {
16385
16442
  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"));
16443
+ if (audit?.unrunnable !== void 0) issueLines.push(audit.unrunnable);
16386
16444
  if (acceptanceModeCfg === "blocking") {
16387
- const verdict = violations.some((v) => v.state === "unclaimed") ? "journey-unclaimed" : "journey-unwalked";
16445
+ const verdict = violations.some((v) => v.state === "unclaimed") ? "journey-unclaimed" : audit?.unrunnable !== void 0 ? "acceptance-unrunnable" : "journey-unwalked";
16388
16446
  logger$27.error({
16389
16447
  storyKey,
16390
16448
  epicId: epicIdA,
16449
+ verdict,
16391
16450
  journeys: violations.map((v) => v.journeyId)
16392
- }, "A0.3: journey coverage violation at epic close — blocking finalization; branch preserved");
16451
+ }, "A0.3/A1.1: journey coverage violation at epic close — blocking finalization; branch preserved");
16393
16452
  updateStory(storyKey, {
16394
16453
  phase: "ESCALATED",
16395
16454
  error: verdict,
@@ -48303,4 +48362,4 @@ function registerRunCommand(program, version = "0.0.0", projectRoot = process.cw
48303
48362
 
48304
48363
  //#endregion
48305
48364
  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 };
48306
- //# sourceMappingURL=run-ZxFHQyE_.js.map
48365
+ //# sourceMappingURL=run-C7PLGWT0.js.map
@@ -1,14 +1,14 @@
1
- import "./health-Dnzblihr.js";
1
+ import "./health-hYMxKqAH.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-CpnLJuVw.js";
5
- import "./manifest-read-B3gE46DX.js";
6
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-ZxFHQyE_.js";
5
+ import "./manifest-read-C0N7b9nH.js";
6
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-C7PLGWT0.js";
7
7
  import "./routing-DFxoKHDt.js";
8
8
  import "./work-graph-repository-DZyJv5pV.js";
9
9
  import "./decisions-CzSIEeGP.js";
10
10
  import "./decision-router-BAPpON_C.js";
11
- import "./interactive-prompt-BM5RaXYG.js";
11
+ import "./interactive-prompt-BvY2FKsl.js";
12
12
  import "./recovery-engine-BKGBeBnW.js";
13
13
 
14
14
  export { runRunAction };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.21.3",
3
+ "version": "0.21.5",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",