substrate-ai 0.20.3 → 0.20.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest } from "../health-BfeoutPu.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest } from "../health-CB48LF0t.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-srr3BfCc.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-8UnjRlkK.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-BHKqyFFM.js";
8
8
  import "../errors-CSTQNabo.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -3665,7 +3665,7 @@ async function runStatusAction(options) {
3665
3665
  logger$12.debug({ err }, "Work graph query failed, continuing without work graph data");
3666
3666
  }
3667
3667
  if (run === void 0) {
3668
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-Cs2x975P.js");
3668
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-DIMI36KN.js");
3669
3669
  const substrateDirPath = join(projectRoot, ".substrate");
3670
3670
  const processInfo = inspectProcessTree$1({
3671
3671
  projectRoot,
@@ -5191,7 +5191,7 @@ async function runSupervisorAction(options, deps = {}) {
5191
5191
  await initSchema(expAdapter);
5192
5192
  const { runRunAction: runPipeline } = await import(
5193
5193
  /* @vite-ignore */
5194
- "../run-COw1z5NJ.js"
5194
+ "../run-l5fLWKtI.js"
5195
5195
  );
5196
5196
  const runStoryFn = async (opts) => {
5197
5197
  const exitCode = await runPipeline({
@@ -2904,6 +2904,616 @@ function applyConfigToGraph(graph, options) {
2904
2904
  graph.defaultMaxRetries = options.maxReviewCycles;
2905
2905
  }
2906
2906
 
2907
+ //#endregion
2908
+ //#region packages/sdlc/dist/verification/findings.js
2909
+ /**
2910
+ * VerificationFinding — structured per-issue payload emitted by verification checks.
2911
+ *
2912
+ * Replaces the ad-hoc "stuff everything into VerificationResult.details" pattern
2913
+ * that preceded it: every downstream consumer (retry prompts, run manifest,
2914
+ * post-run analysis) used to string-parse a free-form blob that the emitting
2915
+ * check never promised a schema for. With findings, each issue is an
2916
+ * addressable record the pipeline can act on individually.
2917
+ *
2918
+ * The {command, exitCode, stdoutTail, stderrTail} optional fields are reserved
2919
+ * primarily for Phase 2 runtime probes — they cost nothing on the current four
2920
+ * Tier A checks (which leave them undefined) but let probe output flow through
2921
+ * the same shape without a second refactor.
2922
+ */
2923
+ const SEVERITY_PREFIX = {
2924
+ error: "ERROR",
2925
+ warn: "WARN",
2926
+ info: "INFO"
2927
+ };
2928
+ /**
2929
+ * Render a list of findings into the multi-line human-readable string that
2930
+ * populates VerificationResult.details. One line per finding:
2931
+ *
2932
+ * `${PREFIX} [${category}] ${message}`
2933
+ *
2934
+ * Checks that migrate to the findings-first pattern call this helper to derive
2935
+ * `details` from the findings they emit, guaranteeing the two stay in sync.
2936
+ */
2937
+ function renderFindings(findings) {
2938
+ if (findings.length === 0) return "";
2939
+ return findings.map((f) => `${SEVERITY_PREFIX[f.severity]} [${f.category}] ${f.message}`).join("\n");
2940
+ }
2941
+
2942
+ //#endregion
2943
+ //#region packages/sdlc/dist/verification/checks/phantom-review-check.js
2944
+ /**
2945
+ * Detects phantom reviews — dispatches that failed or produced no output but
2946
+ * were recorded as passing verdicts.
2947
+ *
2948
+ * AC1: dispatch failed (non-zero exit, timeout, crash) → fail
2949
+ * AC2: empty or null rawOutput → fail
2950
+ * AC3: schema_validation_failed error → fail
2951
+ * AC5: valid review (non-empty rawOutput, no dispatchFailed) → pass
2952
+ * AC6: name='phantom-review', tier='A'
2953
+ */
2954
+ var PhantomReviewCheck = class {
2955
+ name = "phantom-review";
2956
+ tier = "A";
2957
+ async run(context) {
2958
+ const start = Date.now();
2959
+ const review = context.reviewResult;
2960
+ if (!review) return {
2961
+ status: "pass",
2962
+ details: "phantom-review: no review result in context — skipping check",
2963
+ duration_ms: Date.now() - start,
2964
+ findings: []
2965
+ };
2966
+ if (review.dispatchFailed === true) {
2967
+ const reason = review.error === "schema_validation_failed" ? "schema validation failed" : `dispatch failed${review.error ? ` — ${review.error}` : ""}`;
2968
+ const findings = [{
2969
+ category: "phantom-review",
2970
+ severity: "error",
2971
+ message: reason
2972
+ }];
2973
+ return {
2974
+ status: "fail",
2975
+ details: renderFindings(findings),
2976
+ duration_ms: Date.now() - start,
2977
+ findings
2978
+ };
2979
+ }
2980
+ if (review.rawOutput !== void 0 && review.rawOutput.trim().length === 0) {
2981
+ const findings = [{
2982
+ category: "phantom-review",
2983
+ severity: "error",
2984
+ message: "empty review output"
2985
+ }];
2986
+ return {
2987
+ status: "fail",
2988
+ details: renderFindings(findings),
2989
+ duration_ms: Date.now() - start,
2990
+ findings
2991
+ };
2992
+ }
2993
+ return {
2994
+ status: "pass",
2995
+ details: "phantom-review: review output is valid",
2996
+ duration_ms: Date.now() - start,
2997
+ findings: []
2998
+ };
2999
+ }
3000
+ };
3001
+
3002
+ //#endregion
3003
+ //#region packages/sdlc/dist/verification/checks/trivial-output-check.js
3004
+ /**
3005
+ * Default minimum output-token count a story must produce to be
3006
+ * considered non-trivial. Configurable via trivialOutputThreshold config field.
3007
+ */
3008
+ const DEFAULT_TRIVIAL_OUTPUT_THRESHOLD = 100;
3009
+ /**
3010
+ * Checks that a completed story dispatch produced at least `threshold` output
3011
+ * tokens. Dispatches that produced fewer tokens are flagged as failures with
3012
+ * an actionable suggestion to re-run with increased maxTurns.
3013
+ *
3014
+ * AC1: fail when outputTokenCount < threshold.
3015
+ * AC2: details string includes "Re-run with increased maxTurns".
3016
+ * AC3: pass when outputTokenCount >= threshold.
3017
+ * AC4: threshold is configurable via trivialOutputThreshold config field.
3018
+ * AC5: warn (not fail) when outputTokenCount is undefined.
3019
+ * AC6: implements VerificationCheck with name='trivial-output', tier='A'.
3020
+ */
3021
+ var TrivialOutputCheck = class {
3022
+ name = "trivial-output";
3023
+ tier = "A";
3024
+ threshold;
3025
+ constructor(config) {
3026
+ this.threshold = config?.trivialOutputThreshold ?? DEFAULT_TRIVIAL_OUTPUT_THRESHOLD;
3027
+ }
3028
+ async run(context) {
3029
+ const start = Date.now();
3030
+ if (context.outputTokenCount === void 0) {
3031
+ const findings = [{
3032
+ category: "trivial-output",
3033
+ severity: "warn",
3034
+ message: "output token count unavailable — skipping check"
3035
+ }];
3036
+ return {
3037
+ status: "warn",
3038
+ details: renderFindings(findings),
3039
+ duration_ms: Date.now() - start,
3040
+ findings
3041
+ };
3042
+ }
3043
+ const count = context.outputTokenCount;
3044
+ if (count < this.threshold) {
3045
+ const findings = [{
3046
+ category: "trivial-output",
3047
+ severity: "error",
3048
+ message: `output token count ${count} is below threshold ${this.threshold} — Re-run with increased maxTurns`
3049
+ }];
3050
+ return {
3051
+ status: "fail",
3052
+ details: renderFindings(findings),
3053
+ duration_ms: Date.now() - start,
3054
+ findings
3055
+ };
3056
+ }
3057
+ return {
3058
+ status: "pass",
3059
+ details: `output token count ${count} meets threshold ${this.threshold}`,
3060
+ duration_ms: Date.now() - start,
3061
+ findings: []
3062
+ };
3063
+ }
3064
+ };
3065
+
3066
+ //#endregion
3067
+ //#region packages/sdlc/dist/verification/checks/acceptance-criteria-evidence-check.js
3068
+ const EXPLICIT_AC_REF = /\bAC\s*:?\s*#?\s*(\d+)\b/gi;
3069
+ const NUMBERED_CRITERION = /^\s*(?:[-*]\s*)?(?:\[[ xX]\]\s*)?(\d+)[.)]\s+\S/;
3070
+ function normalizeAcId(value) {
3071
+ const parsed = Number.parseInt(value, 10);
3072
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
3073
+ return `AC${parsed}`;
3074
+ }
3075
+ function sortAcIds(ids) {
3076
+ return Array.from(ids).sort((a, b) => {
3077
+ const aNum = Number.parseInt(a.replace(/^AC/i, ""), 10);
3078
+ const bNum = Number.parseInt(b.replace(/^AC/i, ""), 10);
3079
+ return aNum - bNum;
3080
+ });
3081
+ }
3082
+ function addExplicitAcRefs(text, ids) {
3083
+ EXPLICIT_AC_REF.lastIndex = 0;
3084
+ let match;
3085
+ while ((match = EXPLICIT_AC_REF.exec(text)) !== null) {
3086
+ const id = normalizeAcId(match[1] ?? "");
3087
+ if (id !== void 0) ids.add(id);
3088
+ }
3089
+ }
3090
+ function extractAcceptanceSection(storyContent) {
3091
+ const lines = storyContent.split(/\r?\n/);
3092
+ const start = lines.findIndex((line) => /^##\s+Acceptance Criteria\s*$/i.test(line.trim()));
3093
+ if (start === -1) return void 0;
3094
+ let end = lines.length;
3095
+ for (let i = start + 1; i < lines.length; i += 1) if (/^##\s+\S/.test(lines[i] ?? "")) {
3096
+ end = i;
3097
+ break;
3098
+ }
3099
+ return lines.slice(start + 1, end).join("\n");
3100
+ }
3101
+ /**
3102
+ * Extract normalized AC ids from story markdown.
3103
+ *
3104
+ * Supports the BMAD default format (`### AC1:`), explicit references such as
3105
+ * `AC: #1`, and plain numbered criteria inside the Acceptance Criteria section.
3106
+ */
3107
+ function extractAcceptanceCriteriaIds(storyContent) {
3108
+ const ids = new Set();
3109
+ const acceptanceSection = extractAcceptanceSection(storyContent);
3110
+ const textToScan = acceptanceSection ?? storyContent;
3111
+ addExplicitAcRefs(textToScan, ids);
3112
+ if (acceptanceSection !== void 0) for (const line of acceptanceSection.split(/\r?\n/)) {
3113
+ const match = line.match(NUMBERED_CRITERION);
3114
+ if (match?.[1] !== void 0) {
3115
+ const id = normalizeAcId(match[1]);
3116
+ if (id !== void 0) ids.add(id);
3117
+ }
3118
+ }
3119
+ return sortAcIds(ids);
3120
+ }
3121
+ function extractClaimedAcceptanceCriteriaIds(values) {
3122
+ const ids = new Set();
3123
+ for (const value of values ?? []) {
3124
+ addExplicitAcRefs(value, ids);
3125
+ const bareNumber = value.trim().match(/^#?(\d+)\b/);
3126
+ if (bareNumber?.[1] !== void 0) {
3127
+ const id = normalizeAcId(bareNumber[1]);
3128
+ if (id !== void 0) ids.add(id);
3129
+ }
3130
+ }
3131
+ return sortAcIds(ids);
3132
+ }
3133
+ function normalizeTestOutcome(value) {
3134
+ if (value === void 0) return void 0;
3135
+ return value.toLowerCase().includes("fail") ? "fail" : "pass";
3136
+ }
3137
+ function formatIds(ids) {
3138
+ return ids.join(", ");
3139
+ }
3140
+ var AcceptanceCriteriaEvidenceCheck = class {
3141
+ name = "acceptance-criteria-evidence";
3142
+ tier = "A";
3143
+ async run(context) {
3144
+ const start = Date.now();
3145
+ const storyContent = context.storyContent?.trim();
3146
+ if (!storyContent) {
3147
+ const findings = [{
3148
+ category: "ac-context-missing",
3149
+ severity: "warn",
3150
+ message: "story content unavailable - skipping AC evidence check"
3151
+ }];
3152
+ return {
3153
+ status: "warn",
3154
+ details: renderFindings(findings),
3155
+ duration_ms: Date.now() - start,
3156
+ findings
3157
+ };
3158
+ }
3159
+ const expectedIds = extractAcceptanceCriteriaIds(storyContent);
3160
+ if (expectedIds.length === 0) {
3161
+ const findings = [{
3162
+ category: "ac-context-missing",
3163
+ severity: "warn",
3164
+ message: "no numbered acceptance criteria found in story"
3165
+ }];
3166
+ return {
3167
+ status: "warn",
3168
+ details: renderFindings(findings),
3169
+ duration_ms: Date.now() - start,
3170
+ findings
3171
+ };
3172
+ }
3173
+ const devResult = context.devStoryResult;
3174
+ if (devResult === void 0) {
3175
+ const findings = [{
3176
+ category: "ac-context-missing",
3177
+ severity: "warn",
3178
+ message: `dev-story result unavailable for ${formatIds(expectedIds)}`
3179
+ }];
3180
+ return {
3181
+ status: "warn",
3182
+ details: renderFindings(findings),
3183
+ duration_ms: Date.now() - start,
3184
+ findings
3185
+ };
3186
+ }
3187
+ const acFailures = devResult.ac_failures ?? [];
3188
+ if (acFailures.length > 0) {
3189
+ const findings = acFailures.map((failure) => ({
3190
+ category: "ac-explicit-failure",
3191
+ severity: "error",
3192
+ message: `dev-story reported AC failure: ${failure}`
3193
+ }));
3194
+ return {
3195
+ status: "fail",
3196
+ details: renderFindings(findings),
3197
+ duration_ms: Date.now() - start,
3198
+ findings
3199
+ };
3200
+ }
3201
+ const testOutcome = normalizeTestOutcome(devResult.tests);
3202
+ if (testOutcome === "fail") {
3203
+ const findings = [{
3204
+ category: "ac-test-failure",
3205
+ severity: "error",
3206
+ message: "dev-story reported failing tests"
3207
+ }];
3208
+ return {
3209
+ status: "fail",
3210
+ details: renderFindings(findings),
3211
+ duration_ms: Date.now() - start,
3212
+ findings
3213
+ };
3214
+ }
3215
+ const claimedIds = new Set(extractClaimedAcceptanceCriteriaIds(devResult.ac_met));
3216
+ const missingIds = expectedIds.filter((id) => !claimedIds.has(id));
3217
+ if (missingIds.length > 0) {
3218
+ const claimedSummary = formatIds(sortAcIds(claimedIds)) || "none";
3219
+ const findings = missingIds.map((id) => ({
3220
+ category: "ac-missing-evidence",
3221
+ severity: "error",
3222
+ message: `missing dev-story AC evidence for ${id} (expected ${formatIds(expectedIds)}, claimed ${claimedSummary})`
3223
+ }));
3224
+ return {
3225
+ status: "fail",
3226
+ details: renderFindings(findings),
3227
+ duration_ms: Date.now() - start,
3228
+ findings
3229
+ };
3230
+ }
3231
+ if (testOutcome === void 0) {
3232
+ const findings = [{
3233
+ category: "ac-test-outcome-missing",
3234
+ severity: "warn",
3235
+ message: `AC evidence covers ${formatIds(expectedIds)} but test outcome is unavailable`
3236
+ }];
3237
+ return {
3238
+ status: "warn",
3239
+ details: renderFindings(findings),
3240
+ duration_ms: Date.now() - start,
3241
+ findings
3242
+ };
3243
+ }
3244
+ return {
3245
+ status: "pass",
3246
+ details: `acceptance-criteria-evidence: AC evidence covers ${formatIds(expectedIds)}; tests=${testOutcome}`,
3247
+ duration_ms: Date.now() - start,
3248
+ findings: []
3249
+ };
3250
+ }
3251
+ };
3252
+
3253
+ //#endregion
3254
+ //#region packages/sdlc/dist/verification/checks/build-check.js
3255
+ /** Hard timeout for the build command in milliseconds (FR-V11). */
3256
+ const BUILD_CHECK_TIMEOUT_MS = 6e4;
3257
+ /** Maximum characters to include in details string from build output. */
3258
+ const MAX_OUTPUT_CHARS = 2e3;
3259
+ /** Per-stream tail size cap for structured findings (story 55-1 convention). */
3260
+ const TAIL_BYTES = 4 * 1024;
3261
+ /** Return the last N bytes of a UTF-8 string, sliced by string length for simplicity. */
3262
+ function tail(text, bytes = TAIL_BYTES) {
3263
+ return text.length <= bytes ? text : text.slice(text.length - bytes);
3264
+ }
3265
+ /**
3266
+ * Detect the build command for a project based on files present in `workingDir`.
3267
+ *
3268
+ * Returns an empty string when no recognized build system is found, which
3269
+ * causes BuildCheck to return a 'warn' result without blocking the pipeline.
3270
+ *
3271
+ * NOTE: Do NOT import from src/modules/agent-dispatch/dispatcher-impl.ts —
3272
+ * that would create a circular dependency from packages/sdlc/ → monolith src/.
3273
+ * This function inlines the detection logic independently.
3274
+ */
3275
+ function detectBuildCommand(workingDir) {
3276
+ if (existsSync(join$1(workingDir, "turbo.json"))) return "turbo build";
3277
+ if (existsSync(join$1(workingDir, "pnpm-lock.yaml"))) return "pnpm run build";
3278
+ if (existsSync(join$1(workingDir, "yarn.lock"))) return "yarn build";
3279
+ if (existsSync(join$1(workingDir, "bun.lockb"))) return "bun run build";
3280
+ if (existsSync(join$1(workingDir, "package.json"))) return "npm run build";
3281
+ const nonNodeMarkers = [
3282
+ "pyproject.toml",
3283
+ "poetry.lock",
3284
+ "setup.py",
3285
+ "Cargo.toml",
3286
+ "go.mod"
3287
+ ];
3288
+ for (const marker of nonNodeMarkers) if (existsSync(join$1(workingDir, marker))) return "";
3289
+ return "";
3290
+ }
3291
+ /**
3292
+ * Runs the project's build command and returns pass/warn/fail based on exit code.
3293
+ *
3294
+ * AC1: exit code 0 → pass
3295
+ * AC2: non-zero exit code → fail with truncated output in details
3296
+ * AC3: timeout → kill process group, return fail with timeout message
3297
+ * AC4: no recognized build system → warn without blocking
3298
+ * AC5: explicit buildCommand override respected; empty string → warn (skip)
3299
+ * AC6: name === 'build', tier === 'A'
3300
+ */
3301
+ var BuildCheck = class {
3302
+ name = "build";
3303
+ tier = "A";
3304
+ async run(context) {
3305
+ const start = Date.now();
3306
+ const cmd = context.buildCommand !== void 0 ? context.buildCommand : detectBuildCommand(context.workingDir);
3307
+ if (cmd === "") {
3308
+ const findings = [{
3309
+ category: "build-skip",
3310
+ severity: "warn",
3311
+ message: `no build command detected for project at ${context.workingDir}`
3312
+ }];
3313
+ return {
3314
+ status: "warn",
3315
+ details: renderFindings(findings),
3316
+ duration_ms: Date.now() - start,
3317
+ findings
3318
+ };
3319
+ }
3320
+ return new Promise((resolve$2) => {
3321
+ const child = spawn(cmd, [], {
3322
+ cwd: context.workingDir,
3323
+ detached: true,
3324
+ shell: true,
3325
+ stdio: [
3326
+ "ignore",
3327
+ "pipe",
3328
+ "pipe"
3329
+ ]
3330
+ });
3331
+ let stdout = "";
3332
+ let stderr = "";
3333
+ let output = "";
3334
+ child.stdout?.on("data", (chunk) => {
3335
+ const s = chunk.toString();
3336
+ stdout += s;
3337
+ output += s;
3338
+ });
3339
+ child.stderr?.on("data", (chunk) => {
3340
+ const s = chunk.toString();
3341
+ stderr += s;
3342
+ output += s;
3343
+ });
3344
+ const timeoutHandle = setTimeout(() => {
3345
+ try {
3346
+ process.kill(-child.pid, "SIGKILL");
3347
+ } catch {}
3348
+ const duration = Date.now() - start;
3349
+ const findings = [{
3350
+ category: "build-timeout",
3351
+ severity: "error",
3352
+ message: `command exceeded ${BUILD_CHECK_TIMEOUT_MS}ms`,
3353
+ command: cmd,
3354
+ stdoutTail: tail(stdout),
3355
+ stderrTail: tail(stderr),
3356
+ durationMs: duration
3357
+ }];
3358
+ resolve$2({
3359
+ status: "fail",
3360
+ details: renderFindings(findings),
3361
+ duration_ms: duration,
3362
+ findings
3363
+ });
3364
+ }, BUILD_CHECK_TIMEOUT_MS);
3365
+ child.on("close", (code) => {
3366
+ clearTimeout(timeoutHandle);
3367
+ const duration = Date.now() - start;
3368
+ if (code === 0) resolve$2({
3369
+ status: "pass",
3370
+ details: "build passed",
3371
+ duration_ms: duration,
3372
+ findings: []
3373
+ });
3374
+ else {
3375
+ const truncated = output.length > MAX_OUTPUT_CHARS ? output.slice(0, MAX_OUTPUT_CHARS) + "... (truncated)" : output;
3376
+ const findings = [{
3377
+ category: "build-error",
3378
+ severity: "error",
3379
+ message: `build failed (exit ${code}): ${truncated}`,
3380
+ command: cmd,
3381
+ ...code !== null ? { exitCode: code } : {},
3382
+ stdoutTail: tail(stdout),
3383
+ stderrTail: tail(stderr),
3384
+ durationMs: duration
3385
+ }];
3386
+ resolve$2({
3387
+ status: "fail",
3388
+ details: renderFindings(findings),
3389
+ duration_ms: duration,
3390
+ findings
3391
+ });
3392
+ }
3393
+ });
3394
+ });
3395
+ }
3396
+ };
3397
+
3398
+ //#endregion
3399
+ //#region packages/sdlc/dist/verification/verification-pipeline.js
3400
+ /**
3401
+ * Compute the worst-case aggregate status across a list of check results.
3402
+ * Precedence: fail > warn > pass.
3403
+ */
3404
+ function aggregateStatus(checks) {
3405
+ let result = "pass";
3406
+ for (const c of checks) {
3407
+ if (c.status === "fail") return "fail";
3408
+ if (c.status === "warn") result = "warn";
3409
+ }
3410
+ return result;
3411
+ }
3412
+ /**
3413
+ * Runs an ordered chain of VerificationCheck implementations after each story dispatch.
3414
+ *
3415
+ * Checks are stored in registration order. When `run()` is called with `tier: 'A'`
3416
+ * only Tier A checks execute; when called with `tier: 'B'` only Tier B checks execute.
3417
+ * (Story 51-5 will invoke both tiers at the appropriate orchestration points.)
3418
+ */
3419
+ var VerificationPipeline = class {
3420
+ _bus;
3421
+ _checks = [];
3422
+ /**
3423
+ * @param bus Typed event bus for emitting verification events.
3424
+ * @param checks Optional initial list of checks to register at construction time.
3425
+ */
3426
+ constructor(bus, checks = []) {
3427
+ this._bus = bus;
3428
+ for (const check of checks) this.register(check);
3429
+ }
3430
+ /**
3431
+ * Register a VerificationCheck.
3432
+ *
3433
+ * Checks are stored in insertion order within their tier.
3434
+ * Tier A checks always run before Tier B checks regardless of registration order.
3435
+ */
3436
+ register(check) {
3437
+ this._checks.push(check);
3438
+ }
3439
+ /**
3440
+ * Execute all checks matching the specified tier sequentially.
3441
+ *
3442
+ * AC2: Tier A checks execute in registration order.
3443
+ * AC4: Results are aggregated into a VerificationSummary.
3444
+ * AC5: verification:check-complete and verification:story-complete events are emitted.
3445
+ * AC6: Unhandled exceptions are caught and recorded as warn.
3446
+ *
3447
+ * @param context Verification context for the story being verified.
3448
+ * @param tier Which tier of checks to execute ('A' | 'B'). Defaults to 'A'.
3449
+ */
3450
+ async run(context, tier = "A") {
3451
+ const pipelineStart = Date.now();
3452
+ const checks = this._checks.filter((c) => c.tier === tier);
3453
+ const checkResults = [];
3454
+ for (const check of checks) {
3455
+ const checkStart = Date.now();
3456
+ let result;
3457
+ try {
3458
+ const runResult = await check.run(context);
3459
+ result = {
3460
+ checkName: check.name,
3461
+ status: runResult.status,
3462
+ details: runResult.details,
3463
+ duration_ms: runResult.duration_ms
3464
+ };
3465
+ } catch (err) {
3466
+ const elapsed = Date.now() - checkStart;
3467
+ const message = err instanceof Error ? err.message : String(err);
3468
+ process.stderr.write(`[verification-pipeline] check "${check.name}" threw an unhandled exception: ${message}\n`);
3469
+ result = {
3470
+ checkName: check.name,
3471
+ status: "warn",
3472
+ details: message,
3473
+ duration_ms: elapsed
3474
+ };
3475
+ }
3476
+ checkResults.push(result);
3477
+ this._bus.emit("verification:check-complete", {
3478
+ storyKey: context.storyKey,
3479
+ checkName: result.checkName,
3480
+ status: result.status,
3481
+ details: result.details,
3482
+ duration_ms: result.duration_ms
3483
+ });
3484
+ }
3485
+ const summary = {
3486
+ storyKey: context.storyKey,
3487
+ checks: checkResults,
3488
+ status: aggregateStatus(checkResults),
3489
+ duration_ms: Date.now() - pipelineStart
3490
+ };
3491
+ this._bus.emit("verification:story-complete", summary);
3492
+ return summary;
3493
+ }
3494
+ };
3495
+ /**
3496
+ * Create a VerificationPipeline pre-loaded with the canonical check set.
3497
+ *
3498
+ * Canonical Tier A check order (architecture section 3.5):
3499
+ * 1. PhantomReviewCheck — story 51-2 (runs first: unreviewed stories skipped)
3500
+ * 2. TrivialOutputCheck — story 51-3
3501
+ * 3. AcceptanceCriteriaEvidenceCheck
3502
+ * 4. BuildCheck — story 51-4
3503
+ *
3504
+ * @param bus Typed event bus for verification events.
3505
+ * @param config Optional config (used to forward threshold to TrivialOutputCheck).
3506
+ */
3507
+ function createDefaultVerificationPipeline(bus, config) {
3508
+ const checks = [
3509
+ new PhantomReviewCheck(),
3510
+ new TrivialOutputCheck(config),
3511
+ new AcceptanceCriteriaEvidenceCheck(),
3512
+ new BuildCheck()
3513
+ ];
3514
+ return new VerificationPipeline(bus, checks);
3515
+ }
3516
+
2907
3517
  //#endregion
2908
3518
  //#region packages/sdlc/dist/run-model/cli-flags.js
2909
3519
  /**
@@ -2928,6 +3538,27 @@ const CliFlagsSchema = z.object({
2928
3538
  //#endregion
2929
3539
  //#region packages/sdlc/dist/run-model/verification-result.js
2930
3540
  /**
3541
+ * Schema for a single structured verification finding (story 55-1 / 55-3).
3542
+ *
3543
+ * Mirrors the VerificationFinding interface in
3544
+ * packages/sdlc/src/verification/findings.ts without importing from that
3545
+ * module to keep run-model free of a dependency on verification.
3546
+ */
3547
+ const StoredVerificationFindingSchema = z.object({
3548
+ category: z.string(),
3549
+ severity: z.enum([
3550
+ "error",
3551
+ "warn",
3552
+ "info"
3553
+ ]),
3554
+ message: z.string(),
3555
+ command: z.string().optional(),
3556
+ exitCode: z.number().int().optional(),
3557
+ stdoutTail: z.string().optional(),
3558
+ stderrTail: z.string().optional(),
3559
+ durationMs: z.number().nonnegative().optional()
3560
+ });
3561
+ /**
2931
3562
  * Schema for a single per-check verification result stored in the manifest.
2932
3563
  *
2933
3564
  * Mirrors VerificationCheckResult from packages/sdlc/src/verification/types.ts
@@ -2941,7 +3572,8 @@ const StoredVerificationCheckResultSchema = z.object({
2941
3572
  "fail"
2942
3573
  ]),
2943
3574
  details: z.string(),
2944
- duration_ms: z.number().nonnegative()
3575
+ duration_ms: z.number().nonnegative(),
3576
+ findings: z.array(StoredVerificationFindingSchema).optional()
2945
3577
  });
2946
3578
  /**
2947
3579
  * Schema for the aggregated verification pipeline summary stored in the manifest.
@@ -4252,5 +4884,5 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
4252
4884
  }
4253
4885
 
4254
4886
  //#endregion
4255
- export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, runHealthAction, validateStoryKey };
4256
- //# sourceMappingURL=health-BfeoutPu.js.map
4887
+ export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, runHealthAction, validateStoryKey };
4888
+ //# sourceMappingURL=health-CB48LF0t.js.map
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-BfeoutPu.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-CB48LF0t.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-srr3BfCc.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-BfeoutPu.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-CB48LF0t.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
4
  import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-srr3BfCc.js";
@@ -6386,478 +6386,6 @@ async function runGitCommand(args, cwd, logLabel) {
6386
6386
  });
6387
6387
  }
6388
6388
 
6389
- //#endregion
6390
- //#region packages/sdlc/dist/verification/checks/phantom-review-check.js
6391
- /**
6392
- * PhantomReviewCheck — Story 51-2.
6393
- *
6394
- * Tier A verification check that detects when a code review dispatch failed
6395
- * but was recorded as a passing verdict. Stories that were never actually
6396
- * reviewed should not be counted as verified.
6397
- *
6398
- * Architecture constraints (FR-V9):
6399
- * - No LLM calls.
6400
- * - No shell invocations — pure static signal inspection over VerificationContext fields.
6401
- * - Runs first in Tier A (before TrivialOutputCheck, before BuildCheck).
6402
- */
6403
- /**
6404
- * Detects phantom reviews — dispatches that failed or produced no output but
6405
- * were recorded as passing verdicts.
6406
- *
6407
- * AC1: dispatch failed (non-zero exit, timeout, crash) → fail
6408
- * AC2: empty or null rawOutput → fail
6409
- * AC3: schema_validation_failed error → fail
6410
- * AC5: valid review (non-empty rawOutput, no dispatchFailed) → pass
6411
- * AC6: name='phantom-review', tier='A'
6412
- */
6413
- var PhantomReviewCheck = class {
6414
- name = "phantom-review";
6415
- tier = "A";
6416
- async run(context) {
6417
- const start = Date.now();
6418
- const review = context.reviewResult;
6419
- if (!review) return {
6420
- status: "pass",
6421
- details: "phantom-review: no review result in context — skipping check",
6422
- duration_ms: Date.now() - start
6423
- };
6424
- if (review.dispatchFailed === true) {
6425
- const reason = review.error === "schema_validation_failed" ? "schema validation failed" : `dispatch failed${review.error ? ` — ${review.error}` : ""}`;
6426
- return {
6427
- status: "fail",
6428
- details: `phantom-review: ${reason}`,
6429
- duration_ms: Date.now() - start
6430
- };
6431
- }
6432
- if (review.rawOutput !== void 0 && review.rawOutput.trim().length === 0) return {
6433
- status: "fail",
6434
- details: "phantom-review: empty review output",
6435
- duration_ms: Date.now() - start
6436
- };
6437
- return {
6438
- status: "pass",
6439
- details: "phantom-review: review output is valid",
6440
- duration_ms: Date.now() - start
6441
- };
6442
- }
6443
- };
6444
-
6445
- //#endregion
6446
- //#region packages/sdlc/dist/verification/checks/trivial-output-check.js
6447
- /**
6448
- * TrivialOutputCheck — Story 51-3.
6449
- *
6450
- * Tier A verification check that flags story dispatches which produced
6451
- * fewer output tokens than the configured threshold. A very low output
6452
- * token count is a strong signal that the agent exited early (e.g. hit a
6453
- * maxTurns limit, encountered a fatal error, or did no real work).
6454
- *
6455
- * Architecture constraints (DC-6, FR-V9):
6456
- * - No LLM calls.
6457
- * - No shell invocations — pure in-process computation.
6458
- * - Runs in Tier A: before BuildCheck, after PhantomReviewCheck.
6459
- */
6460
- /**
6461
- * Default minimum output-token count a story must produce to be
6462
- * considered non-trivial. Configurable via trivialOutputThreshold config field.
6463
- */
6464
- const DEFAULT_TRIVIAL_OUTPUT_THRESHOLD = 100;
6465
- /**
6466
- * Checks that a completed story dispatch produced at least `threshold` output
6467
- * tokens. Dispatches that produced fewer tokens are flagged as failures with
6468
- * an actionable suggestion to re-run with increased maxTurns.
6469
- *
6470
- * AC1: fail when outputTokenCount < threshold.
6471
- * AC2: details string includes "Re-run with increased maxTurns".
6472
- * AC3: pass when outputTokenCount >= threshold.
6473
- * AC4: threshold is configurable via trivialOutputThreshold config field.
6474
- * AC5: warn (not fail) when outputTokenCount is undefined.
6475
- * AC6: implements VerificationCheck with name='trivial-output', tier='A'.
6476
- */
6477
- var TrivialOutputCheck = class {
6478
- name = "trivial-output";
6479
- tier = "A";
6480
- threshold;
6481
- constructor(config) {
6482
- this.threshold = config?.trivialOutputThreshold ?? DEFAULT_TRIVIAL_OUTPUT_THRESHOLD;
6483
- }
6484
- async run(context) {
6485
- const start = Date.now();
6486
- if (context.outputTokenCount === void 0) return {
6487
- status: "warn",
6488
- details: "trivial-output: output token count unavailable — skipping check",
6489
- duration_ms: Date.now() - start
6490
- };
6491
- const count = context.outputTokenCount;
6492
- if (count < this.threshold) return {
6493
- status: "fail",
6494
- details: `trivial-output: output token count ${count} is below threshold ${this.threshold} — Re-run with increased maxTurns`,
6495
- duration_ms: Date.now() - start
6496
- };
6497
- return {
6498
- status: "pass",
6499
- details: `output token count ${count} meets threshold ${this.threshold}`,
6500
- duration_ms: Date.now() - start
6501
- };
6502
- }
6503
- };
6504
-
6505
- //#endregion
6506
- //#region packages/sdlc/dist/verification/checks/acceptance-criteria-evidence-check.js
6507
- /**
6508
- * AcceptanceCriteriaEvidenceCheck.
6509
- *
6510
- * Tier A verification check that compares a story's declared acceptance
6511
- * criteria against structured dev-story output. The check is intentionally
6512
- * deterministic: no LLM calls, no shell commands, no repository inspection.
6513
- */
6514
- const EXPLICIT_AC_REF = /\bAC\s*:?\s*#?\s*(\d+)\b/gi;
6515
- const NUMBERED_CRITERION = /^\s*(?:[-*]\s*)?(?:\[[ xX]\]\s*)?(\d+)[.)]\s+\S/;
6516
- function normalizeAcId(value) {
6517
- const parsed = Number.parseInt(value, 10);
6518
- if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
6519
- return `AC${parsed}`;
6520
- }
6521
- function sortAcIds(ids) {
6522
- return Array.from(ids).sort((a, b) => {
6523
- const aNum = Number.parseInt(a.replace(/^AC/i, ""), 10);
6524
- const bNum = Number.parseInt(b.replace(/^AC/i, ""), 10);
6525
- return aNum - bNum;
6526
- });
6527
- }
6528
- function addExplicitAcRefs(text, ids) {
6529
- EXPLICIT_AC_REF.lastIndex = 0;
6530
- let match$2;
6531
- while ((match$2 = EXPLICIT_AC_REF.exec(text)) !== null) {
6532
- const id = normalizeAcId(match$2[1] ?? "");
6533
- if (id !== void 0) ids.add(id);
6534
- }
6535
- }
6536
- function extractAcceptanceSection(storyContent) {
6537
- const lines = storyContent.split(/\r?\n/);
6538
- const start = lines.findIndex((line) => /^##\s+Acceptance Criteria\s*$/i.test(line.trim()));
6539
- if (start === -1) return void 0;
6540
- let end = lines.length;
6541
- for (let i = start + 1; i < lines.length; i += 1) if (/^##\s+\S/.test(lines[i] ?? "")) {
6542
- end = i;
6543
- break;
6544
- }
6545
- return lines.slice(start + 1, end).join("\n");
6546
- }
6547
- /**
6548
- * Extract normalized AC ids from story markdown.
6549
- *
6550
- * Supports the BMAD default format (`### AC1:`), explicit references such as
6551
- * `AC: #1`, and plain numbered criteria inside the Acceptance Criteria section.
6552
- */
6553
- function extractAcceptanceCriteriaIds(storyContent) {
6554
- const ids = new Set();
6555
- const acceptanceSection = extractAcceptanceSection(storyContent);
6556
- const textToScan = acceptanceSection ?? storyContent;
6557
- addExplicitAcRefs(textToScan, ids);
6558
- if (acceptanceSection !== void 0) for (const line of acceptanceSection.split(/\r?\n/)) {
6559
- const match$2 = line.match(NUMBERED_CRITERION);
6560
- if (match$2?.[1] !== void 0) {
6561
- const id = normalizeAcId(match$2[1]);
6562
- if (id !== void 0) ids.add(id);
6563
- }
6564
- }
6565
- return sortAcIds(ids);
6566
- }
6567
- function extractClaimedAcceptanceCriteriaIds(values) {
6568
- const ids = new Set();
6569
- for (const value of values ?? []) {
6570
- addExplicitAcRefs(value, ids);
6571
- const bareNumber = value.trim().match(/^#?(\d+)\b/);
6572
- if (bareNumber?.[1] !== void 0) {
6573
- const id = normalizeAcId(bareNumber[1]);
6574
- if (id !== void 0) ids.add(id);
6575
- }
6576
- }
6577
- return sortAcIds(ids);
6578
- }
6579
- function normalizeTestOutcome(value) {
6580
- if (value === void 0) return void 0;
6581
- return value.toLowerCase().includes("fail") ? "fail" : "pass";
6582
- }
6583
- function formatIds(ids) {
6584
- return ids.join(", ");
6585
- }
6586
- var AcceptanceCriteriaEvidenceCheck = class {
6587
- name = "acceptance-criteria-evidence";
6588
- tier = "A";
6589
- async run(context) {
6590
- const start = Date.now();
6591
- const storyContent = context.storyContent?.trim();
6592
- if (!storyContent) return {
6593
- status: "warn",
6594
- details: "acceptance-criteria-evidence: story content unavailable - skipping AC evidence check",
6595
- duration_ms: Date.now() - start
6596
- };
6597
- const expectedIds = extractAcceptanceCriteriaIds(storyContent);
6598
- if (expectedIds.length === 0) return {
6599
- status: "warn",
6600
- details: "acceptance-criteria-evidence: no numbered acceptance criteria found in story",
6601
- duration_ms: Date.now() - start
6602
- };
6603
- const devResult = context.devStoryResult;
6604
- if (devResult === void 0) return {
6605
- status: "warn",
6606
- details: `acceptance-criteria-evidence: dev-story result unavailable for ${formatIds(expectedIds)}`,
6607
- duration_ms: Date.now() - start
6608
- };
6609
- const acFailures = devResult.ac_failures ?? [];
6610
- if (acFailures.length > 0) return {
6611
- status: "fail",
6612
- details: `acceptance-criteria-evidence: dev-story reported AC failures: ${acFailures.join("; ")}`,
6613
- duration_ms: Date.now() - start
6614
- };
6615
- const testOutcome = normalizeTestOutcome(devResult.tests);
6616
- if (testOutcome === "fail") return {
6617
- status: "fail",
6618
- details: "acceptance-criteria-evidence: dev-story reported failing tests",
6619
- duration_ms: Date.now() - start
6620
- };
6621
- const claimedIds = new Set(extractClaimedAcceptanceCriteriaIds(devResult.ac_met));
6622
- const missingIds = expectedIds.filter((id) => !claimedIds.has(id));
6623
- if (missingIds.length > 0) return {
6624
- status: "fail",
6625
- details: `acceptance-criteria-evidence: missing dev-story AC evidence for ${formatIds(missingIds)}; expected ${formatIds(expectedIds)}, claimed ${formatIds(sortAcIds(claimedIds)) || "none"}`,
6626
- duration_ms: Date.now() - start
6627
- };
6628
- if (testOutcome === void 0) return {
6629
- status: "warn",
6630
- details: `acceptance-criteria-evidence: AC evidence covers ${formatIds(expectedIds)} but test outcome is unavailable`,
6631
- duration_ms: Date.now() - start
6632
- };
6633
- return {
6634
- status: "pass",
6635
- details: `acceptance-criteria-evidence: AC evidence covers ${formatIds(expectedIds)}; tests=${testOutcome}`,
6636
- duration_ms: Date.now() - start
6637
- };
6638
- }
6639
- };
6640
-
6641
- //#endregion
6642
- //#region packages/sdlc/dist/verification/checks/build-check.js
6643
- /** Hard timeout for the build command in milliseconds (FR-V11). */
6644
- const BUILD_CHECK_TIMEOUT_MS = 6e4;
6645
- /** Maximum characters to include in details string from build output. */
6646
- const MAX_OUTPUT_CHARS = 2e3;
6647
- /**
6648
- * Detect the build command for a project based on files present in `workingDir`.
6649
- *
6650
- * Returns an empty string when no recognized build system is found, which
6651
- * causes BuildCheck to return a 'warn' result without blocking the pipeline.
6652
- *
6653
- * NOTE: Do NOT import from src/modules/agent-dispatch/dispatcher-impl.ts —
6654
- * that would create a circular dependency from packages/sdlc/ → monolith src/.
6655
- * This function inlines the detection logic independently.
6656
- */
6657
- function detectBuildCommand(workingDir) {
6658
- if (existsSync(join$1(workingDir, "turbo.json"))) return "turbo build";
6659
- if (existsSync(join$1(workingDir, "pnpm-lock.yaml"))) return "pnpm run build";
6660
- if (existsSync(join$1(workingDir, "yarn.lock"))) return "yarn build";
6661
- if (existsSync(join$1(workingDir, "bun.lockb"))) return "bun run build";
6662
- if (existsSync(join$1(workingDir, "package.json"))) return "npm run build";
6663
- const nonNodeMarkers = [
6664
- "pyproject.toml",
6665
- "poetry.lock",
6666
- "setup.py",
6667
- "Cargo.toml",
6668
- "go.mod"
6669
- ];
6670
- for (const marker of nonNodeMarkers) if (existsSync(join$1(workingDir, marker))) return "";
6671
- return "";
6672
- }
6673
- /**
6674
- * Runs the project's build command and returns pass/warn/fail based on exit code.
6675
- *
6676
- * AC1: exit code 0 → pass
6677
- * AC2: non-zero exit code → fail with truncated output in details
6678
- * AC3: timeout → kill process group, return fail with timeout message
6679
- * AC4: no recognized build system → warn without blocking
6680
- * AC5: explicit buildCommand override respected; empty string → warn (skip)
6681
- * AC6: name === 'build', tier === 'A'
6682
- */
6683
- var BuildCheck = class {
6684
- name = "build";
6685
- tier = "A";
6686
- async run(context) {
6687
- const start = Date.now();
6688
- const cmd = context.buildCommand !== void 0 ? context.buildCommand : detectBuildCommand(context.workingDir);
6689
- if (cmd === "") return {
6690
- status: "warn",
6691
- details: `build-skip: no build command detected for project at ${context.workingDir}`,
6692
- duration_ms: Date.now() - start
6693
- };
6694
- return new Promise((resolve$6) => {
6695
- const child = spawn(cmd, [], {
6696
- cwd: context.workingDir,
6697
- detached: true,
6698
- shell: true,
6699
- stdio: [
6700
- "ignore",
6701
- "pipe",
6702
- "pipe"
6703
- ]
6704
- });
6705
- let output = "";
6706
- child.stdout?.on("data", (chunk) => {
6707
- output += chunk.toString();
6708
- });
6709
- child.stderr?.on("data", (chunk) => {
6710
- output += chunk.toString();
6711
- });
6712
- const timeoutHandle = setTimeout(() => {
6713
- try {
6714
- process.kill(-child.pid, "SIGKILL");
6715
- } catch {}
6716
- resolve$6({
6717
- status: "fail",
6718
- details: `build-timeout: command exceeded ${BUILD_CHECK_TIMEOUT_MS}ms`,
6719
- duration_ms: Date.now() - start
6720
- });
6721
- }, BUILD_CHECK_TIMEOUT_MS);
6722
- child.on("close", (code) => {
6723
- clearTimeout(timeoutHandle);
6724
- if (code === 0) resolve$6({
6725
- status: "pass",
6726
- details: "build passed",
6727
- duration_ms: Date.now() - start
6728
- });
6729
- else {
6730
- const truncated = output.length > MAX_OUTPUT_CHARS ? output.slice(0, MAX_OUTPUT_CHARS) + "... (truncated)" : output;
6731
- resolve$6({
6732
- status: "fail",
6733
- details: `build failed (exit ${code}): ${truncated}`,
6734
- duration_ms: Date.now() - start
6735
- });
6736
- }
6737
- });
6738
- });
6739
- }
6740
- };
6741
-
6742
- //#endregion
6743
- //#region packages/sdlc/dist/verification/verification-pipeline.js
6744
- /**
6745
- * Compute the worst-case aggregate status across a list of check results.
6746
- * Precedence: fail > warn > pass.
6747
- */
6748
- function aggregateStatus(checks) {
6749
- let result = "pass";
6750
- for (const c of checks) {
6751
- if (c.status === "fail") return "fail";
6752
- if (c.status === "warn") result = "warn";
6753
- }
6754
- return result;
6755
- }
6756
- /**
6757
- * Runs an ordered chain of VerificationCheck implementations after each story dispatch.
6758
- *
6759
- * Checks are stored in registration order. When `run()` is called with `tier: 'A'`
6760
- * only Tier A checks execute; when called with `tier: 'B'` only Tier B checks execute.
6761
- * (Story 51-5 will invoke both tiers at the appropriate orchestration points.)
6762
- */
6763
- var VerificationPipeline = class {
6764
- _bus;
6765
- _checks = [];
6766
- /**
6767
- * @param bus Typed event bus for emitting verification events.
6768
- * @param checks Optional initial list of checks to register at construction time.
6769
- */
6770
- constructor(bus, checks = []) {
6771
- this._bus = bus;
6772
- for (const check of checks) this.register(check);
6773
- }
6774
- /**
6775
- * Register a VerificationCheck.
6776
- *
6777
- * Checks are stored in insertion order within their tier.
6778
- * Tier A checks always run before Tier B checks regardless of registration order.
6779
- */
6780
- register(check) {
6781
- this._checks.push(check);
6782
- }
6783
- /**
6784
- * Execute all checks matching the specified tier sequentially.
6785
- *
6786
- * AC2: Tier A checks execute in registration order.
6787
- * AC4: Results are aggregated into a VerificationSummary.
6788
- * AC5: verification:check-complete and verification:story-complete events are emitted.
6789
- * AC6: Unhandled exceptions are caught and recorded as warn.
6790
- *
6791
- * @param context Verification context for the story being verified.
6792
- * @param tier Which tier of checks to execute ('A' | 'B'). Defaults to 'A'.
6793
- */
6794
- async run(context, tier = "A") {
6795
- const pipelineStart = Date.now();
6796
- const checks = this._checks.filter((c) => c.tier === tier);
6797
- const checkResults = [];
6798
- for (const check of checks) {
6799
- const checkStart = Date.now();
6800
- let result;
6801
- try {
6802
- const runResult = await check.run(context);
6803
- result = {
6804
- checkName: check.name,
6805
- status: runResult.status,
6806
- details: runResult.details,
6807
- duration_ms: runResult.duration_ms
6808
- };
6809
- } catch (err) {
6810
- const elapsed = Date.now() - checkStart;
6811
- const message = err instanceof Error ? err.message : String(err);
6812
- process.stderr.write(`[verification-pipeline] check "${check.name}" threw an unhandled exception: ${message}\n`);
6813
- result = {
6814
- checkName: check.name,
6815
- status: "warn",
6816
- details: message,
6817
- duration_ms: elapsed
6818
- };
6819
- }
6820
- checkResults.push(result);
6821
- this._bus.emit("verification:check-complete", {
6822
- storyKey: context.storyKey,
6823
- checkName: result.checkName,
6824
- status: result.status,
6825
- details: result.details,
6826
- duration_ms: result.duration_ms
6827
- });
6828
- }
6829
- const summary = {
6830
- storyKey: context.storyKey,
6831
- checks: checkResults,
6832
- status: aggregateStatus(checkResults),
6833
- duration_ms: Date.now() - pipelineStart
6834
- };
6835
- this._bus.emit("verification:story-complete", summary);
6836
- return summary;
6837
- }
6838
- };
6839
- /**
6840
- * Create a VerificationPipeline pre-loaded with the canonical check set.
6841
- *
6842
- * Canonical Tier A check order (architecture section 3.5):
6843
- * 1. PhantomReviewCheck — story 51-2 (runs first: unreviewed stories skipped)
6844
- * 2. TrivialOutputCheck — story 51-3
6845
- * 3. AcceptanceCriteriaEvidenceCheck
6846
- * 4. BuildCheck — story 51-4
6847
- *
6848
- * @param bus Typed event bus for verification events.
6849
- * @param config Optional config (used to forward threshold to TrivialOutputCheck).
6850
- */
6851
- function createDefaultVerificationPipeline(bus, config) {
6852
- const checks = [
6853
- new PhantomReviewCheck(),
6854
- new TrivialOutputCheck(config),
6855
- new AcceptanceCriteriaEvidenceCheck(),
6856
- new BuildCheck()
6857
- ];
6858
- return new VerificationPipeline(bus, checks);
6859
- }
6860
-
6861
6389
  //#endregion
6862
6390
  //#region src/modules/compiled-workflows/story-complexity.ts
6863
6391
  const logger$16 = createLogger("compiled-workflows:story-complexity");
@@ -10952,6 +10480,29 @@ function persistVerificationResult(storyKey, summary, runManifest) {
10952
10480
  storyKey
10953
10481
  }, "manifest verification_result write failed — pipeline continues"));
10954
10482
  }
10483
+ /**
10484
+ * Flatten every finding from a VerificationSummary's checks into a single
10485
+ * prompt-ready string. Returns '' when the summary is undefined, contains
10486
+ * no checks, or every check emits zero findings (e.g. every check passed).
10487
+ *
10488
+ * The output is intended for direct injection into retry/rework/fix
10489
+ * prompt templates via a `{{verification_findings}}` section — kept
10490
+ * human-readable and minimal. Each finding is rendered as a single
10491
+ * `ERROR [category] message` / `WARN [...]` / `INFO [...]` line via the
10492
+ * renderFindings helper from the verification module; lines are grouped
10493
+ * by check name for readability.
10494
+ */
10495
+ function renderVerificationFindingsForPrompt(summary) {
10496
+ if (!summary) return "";
10497
+ const blocks = [];
10498
+ for (const check of summary.checks) {
10499
+ const findings = check.findings ?? [];
10500
+ if (findings.length === 0) continue;
10501
+ const rendered = renderFindings(findings);
10502
+ blocks.push(`- ${check.checkName}:\n${rendered.replace(/^/gm, " ")}`);
10503
+ }
10504
+ return blocks.join("\n");
10505
+ }
10955
10506
 
10956
10507
  //#endregion
10957
10508
  //#region src/modules/implementation-orchestrator/cost-governance.ts
@@ -13912,6 +13463,7 @@ function createImplementationOrchestrator(deps) {
13912
13463
  archConstraints = constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
13913
13464
  } catch {}
13914
13465
  const targetedFilesContent = buildTargetedFilesContent(issueList);
13466
+ const verificationFindingsContent = renderVerificationFindingsForPrompt(verificationStore.get(storyKey));
13915
13467
  const sections = [
13916
13468
  {
13917
13469
  name: "story_content",
@@ -13932,6 +13484,11 @@ function createImplementationOrchestrator(deps) {
13932
13484
  name: "targeted_files",
13933
13485
  content: targetedFilesContent,
13934
13486
  priority: "important"
13487
+ }] : [],
13488
+ ...verificationFindingsContent ? [{
13489
+ name: "verification_findings",
13490
+ content: verificationFindingsContent,
13491
+ priority: "important"
13935
13492
  }] : []
13936
13493
  ];
13937
13494
  const assembled = assemblePrompt(fixTemplate, sections, 24e3);
@@ -14100,6 +13657,7 @@ function createImplementationOrchestrator(deps) {
14100
13657
  const findings = await getProjectFindings(db);
14101
13658
  if (findings !== "") priorFindingsContent = "Prior pipeline findings — avoid repeating these patterns:\n\n" + findings;
14102
13659
  } catch {}
13660
+ const verificationFindingsContent = renderVerificationFindingsForPrompt(verificationStore.get(storyKey));
14103
13661
  const sections = isMajorRework ? [
14104
13662
  {
14105
13663
  name: "story_content",
@@ -14125,7 +13683,12 @@ function createImplementationOrchestrator(deps) {
14125
13683
  name: "prior_findings",
14126
13684
  content: priorFindingsContent,
14127
13685
  priority: "optional"
14128
- }
13686
+ },
13687
+ ...verificationFindingsContent ? [{
13688
+ name: "verification_findings",
13689
+ content: verificationFindingsContent,
13690
+ priority: "important"
13691
+ }] : []
14129
13692
  ] : (() => {
14130
13693
  const targetedFilesContent = buildTargetedFilesContent(issueList);
14131
13694
  return [
@@ -14153,7 +13716,12 @@ function createImplementationOrchestrator(deps) {
14153
13716
  name: "prior_findings",
14154
13717
  content: priorFindingsContent,
14155
13718
  priority: "optional"
14156
- }
13719
+ },
13720
+ ...verificationFindingsContent ? [{
13721
+ name: "verification_findings",
13722
+ content: verificationFindingsContent,
13723
+ priority: "important"
13724
+ }] : []
14157
13725
  ];
14158
13726
  })();
14159
13727
  const assembled = assemblePrompt(fixTemplate, sections, 24e3);
@@ -44272,4 +43840,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
44272
43840
 
44273
43841
  //#endregion
44274
43842
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict, wireNdjsonEmitter };
44275
- //# sourceMappingURL=run-8UnjRlkK.js.map
43843
+ //# sourceMappingURL=run-BHKqyFFM.js.map
@@ -1,8 +1,8 @@
1
- import "./health-BfeoutPu.js";
1
+ import "./health-CB48LF0t.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-srr3BfCc.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-8UnjRlkK.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-BHKqyFFM.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.3",
3
+ "version": "0.20.4",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,6 +17,9 @@
17
17
  ### Prior Pipeline Findings
18
18
  {{prior_findings}}
19
19
 
20
+ ### Automated Verification Findings
21
+ {{verification_findings}}
22
+
20
23
  ---
21
24
 
22
25
  ## Mission
@@ -17,6 +17,9 @@
17
17
  ### Prior Pipeline Findings
18
18
  {{prior_findings}}
19
19
 
20
+ ### Automated Verification Findings
21
+ {{verification_findings}}
22
+
20
23
  ---
21
24
 
22
25
  ## Mission