substrate-ai 0.20.2 → 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.
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { DoltClient, DoltQueryError, LEARNING_FINDING, createDatabaseAdapter$1 as createDatabaseAdapter, createDecision, getDecisionsByCategory, getLatestRun, getPipelineRunById, initSchema } from "./dist-sNh9XQ6V.js";
2
+ import { DoltClient, DoltQueryError, LEARNING_FINDING, createDatabaseAdapter$1 as createDatabaseAdapter, createDecision, getDecisionsByCategory, getLatestRun, getPipelineRunById, initSchema } from "./dist-srr3BfCc.js";
3
3
  import { createRequire } from "module";
4
4
  import { dirname, join } from "path";
5
5
  import { readFile } from "fs/promises";
@@ -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-DrZiqv4h.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,6 +1,6 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-DrZiqv4h.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
- import "./dist-sNh9XQ6V.js";
3
+ import "./dist-srr3BfCc.js";
4
4
  import "./decisions-C0pz9Clx.js";
5
5
 
6
6
  export { inspectProcessTree };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { childLogger, createLogger, logger } from "./logger-KeHncl-f.js";
2
2
  import { assertDefined, createEventBus, createTuiApp, deepClone, formatDuration, generateId, isPlainObject, isTuiCapable, printNonTtyWarning, sleep, withRetry } from "./helpers-CElYrONe.js";
3
- import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-sNh9XQ6V.js";
3
+ import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-srr3BfCc.js";
4
4
  import "./adapter-registry-DXLMTmfD.js";
5
- import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-RupuC-ES.js";
5
+ import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-CSTQNabo.js";
6
6
 
7
7
  //#region src/core/di.ts
8
8
  /**
@@ -1,4 +1,4 @@
1
- import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-sNh9XQ6V.js";
1
+ import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-srr3BfCc.js";
2
2
  import "./routing-CcBOCuC9.js";
3
3
 
4
4
  export { loadModelRoutingConfig };