substrate-ai 0.20.4 → 0.20.6

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-CB48LF0t.js";
2
+ import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts } from "../health-BIS34IYK.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-BHKqyFFM.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-BAc1zfMQ.js";
8
8
  import "../errors-CSTQNabo.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -3619,9 +3619,11 @@ async function runStatusAction(options) {
3619
3619
  if (run === void 0) run = await getLatestRun(adapter);
3620
3620
  }
3621
3621
  let workGraph;
3622
+ let manifestPerStoryState;
3622
3623
  const { manifest: resolvedManifest } = await resolveRunManifest(dbRoot, run?.id);
3623
3624
  if (resolvedManifest !== null) try {
3624
3625
  const manifestData = await resolvedManifest.read();
3626
+ manifestPerStoryState = manifestData.per_story_state;
3625
3627
  workGraph = buildWorkGraphFromManifest(manifestData.per_story_state);
3626
3628
  logger$12.debug({ runId: run?.id }, "status: workGraph built from manifest per_story_state");
3627
3629
  } catch {
@@ -3665,7 +3667,7 @@ async function runStatusAction(options) {
3665
3667
  logger$12.debug({ err }, "Work graph query failed, continuing without work graph data");
3666
3668
  }
3667
3669
  if (run === void 0) {
3668
- const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-DIMI36KN.js");
3670
+ const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-3-qy8XEI.js");
3669
3671
  const substrateDirPath = join(projectRoot, ".substrate");
3670
3672
  const processInfo = inspectProcessTree$1({
3671
3673
  projectRoot,
@@ -3708,6 +3710,8 @@ async function runStatusAction(options) {
3708
3710
  for (const [phase, secs] of Object.entries(parsed)) phaseBreakdown[phase] = Math.round(secs * 1e3);
3709
3711
  }
3710
3712
  } catch {}
3713
+ const verificationResult = manifestPerStoryState?.[row.story_key]?.verification_result;
3714
+ const verificationFindings = rollupFindingCounts(verificationResult);
3711
3715
  return {
3712
3716
  story_key: row.story_key,
3713
3717
  result: row.result,
@@ -3718,7 +3722,8 @@ async function runStatusAction(options) {
3718
3722
  output: row.output_tokens ?? 0
3719
3723
  },
3720
3724
  review_cycles: row.review_cycles ?? 0,
3721
- dispatches: row.dispatches ?? 0
3725
+ dispatches: row.dispatches ?? 0,
3726
+ verification_findings: verificationFindings
3722
3727
  };
3723
3728
  });
3724
3729
  let pipelineWallClockMs = 0;
@@ -5191,7 +5196,7 @@ async function runSupervisorAction(options, deps = {}) {
5191
5196
  await initSchema(expAdapter);
5192
5197
  const { runRunAction: runPipeline } = await import(
5193
5198
  /* @vite-ignore */
5194
- "../run-l5fLWKtI.js"
5199
+ "../run-Dif8PJRd.js"
5195
5200
  );
5196
5201
  const runStoryFn = async (opts) => {
5197
5202
  const exitCode = await runPipeline({
@@ -5953,6 +5958,14 @@ async function runMetricsAction(options) {
5953
5958
  phaseBreakdownMap[run$1.run_id] = raw !== void 0 ? raw : null;
5954
5959
  }
5955
5960
  } catch {}
5961
+ const findingCountsByStoryRun = new Map();
5962
+ const uniqueRunIds = Array.from(new Set(storyMetrics.map((sm) => sm.run_id).filter((id) => id !== "")));
5963
+ for (const uniqueRunId of uniqueRunIds) try {
5964
+ const { manifest } = await resolveRunManifest(dbRoot, uniqueRunId);
5965
+ if (manifest === null) continue;
5966
+ const data = await manifest.read();
5967
+ for (const [storyKey, entry] of Object.entries(data.per_story_state)) findingCountsByStoryRun.set(`${storyKey}:${uniqueRunId}`, rollupFindingCounts(entry.verification_result));
5968
+ } catch {}
5956
5969
  let factoryRuns = [];
5957
5970
  try {
5958
5971
  factoryRuns = await getFactoryRunSummaries(adapter, limit);
@@ -5965,10 +5978,14 @@ async function runMetricsAction(options) {
5965
5978
  type: "sdlc",
5966
5979
  phase_token_breakdown: phaseBreakdownMap[run$1.run_id] ?? null
5967
5980
  }));
5981
+ const storyMetricsWithFindings = storyMetrics.map((sm) => ({
5982
+ ...sm,
5983
+ verification_findings: findingCountsByStoryRun.get(`${sm.story_key}:${sm.run_id}`) ?? { ...ZERO_FINDING_COUNTS }
5984
+ }));
5968
5985
  const jsonPayload = {
5969
5986
  runs: runsWithBreakdown,
5970
5987
  graph_runs: factoryRuns,
5971
- story_metrics: storyMetrics
5988
+ story_metrics: storyMetricsWithFindings
5972
5989
  };
5973
5990
  if (doltMetrics !== void 0) if (aggregate) {
5974
5991
  const aggregateResults = doltMetrics.map((m) => ({
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-CB48LF0t.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-BIS34IYK.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-srr3BfCc.js";
4
4
  import "./decisions-C0pz9Clx.js";
@@ -4,6 +4,7 @@ import { createRequire } from "module";
4
4
  import { dirname, join } from "path";
5
5
  import { readFile } from "fs/promises";
6
6
  import { EventEmitter } from "node:events";
7
+ import { YAMLException, load } from "js-yaml";
7
8
  import { existsSync, promises, readFileSync } from "node:fs";
8
9
  import { spawn, spawnSync } from "node:child_process";
9
10
  import { dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
@@ -3259,7 +3260,7 @@ const MAX_OUTPUT_CHARS = 2e3;
3259
3260
  /** Per-stream tail size cap for structured findings (story 55-1 convention). */
3260
3261
  const TAIL_BYTES = 4 * 1024;
3261
3262
  /** Return the last N bytes of a UTF-8 string, sliced by string length for simplicity. */
3262
- function tail(text, bytes = TAIL_BYTES) {
3263
+ function tail$1(text, bytes = TAIL_BYTES) {
3263
3264
  return text.length <= bytes ? text : text.slice(text.length - bytes);
3264
3265
  }
3265
3266
  /**
@@ -3351,8 +3352,8 @@ var BuildCheck = class {
3351
3352
  severity: "error",
3352
3353
  message: `command exceeded ${BUILD_CHECK_TIMEOUT_MS}ms`,
3353
3354
  command: cmd,
3354
- stdoutTail: tail(stdout),
3355
- stderrTail: tail(stderr),
3355
+ stdoutTail: tail$1(stdout),
3356
+ stderrTail: tail$1(stderr),
3356
3357
  durationMs: duration
3357
3358
  }];
3358
3359
  resolve$2({
@@ -3379,8 +3380,8 @@ var BuildCheck = class {
3379
3380
  message: `build failed (exit ${code}): ${truncated}`,
3380
3381
  command: cmd,
3381
3382
  ...code !== null ? { exitCode: code } : {},
3382
- stdoutTail: tail(stdout),
3383
- stderrTail: tail(stderr),
3383
+ stdoutTail: tail$1(stdout),
3384
+ stderrTail: tail$1(stderr),
3384
3385
  durationMs: duration
3385
3386
  }];
3386
3387
  resolve$2({
@@ -3395,6 +3396,342 @@ var BuildCheck = class {
3395
3396
  }
3396
3397
  };
3397
3398
 
3399
+ //#endregion
3400
+ //#region packages/sdlc/dist/verification/probes/types.js
3401
+ /**
3402
+ * Execution sandbox for a runtime probe.
3403
+ *
3404
+ * - `host`: probe runs directly on the operator's machine. Explicit opt-in.
3405
+ * Cheapest; most dangerous. Authors choosing `host` acknowledge the probe
3406
+ * may touch host state (ports, systemd units, filesystem) and take
3407
+ * responsibility for cleanup.
3408
+ * - `twin`: probe runs inside an ephemeral sandbox brokered by the Digital
3409
+ * Twin subsystem (Epic 47). Twin integration is **deferred to Phase 3** —
3410
+ * probes with `sandbox: twin` currently emit a `probe-deferred` warn
3411
+ * finding rather than executing. Authors can declare twin-scoped probes
3412
+ * today and they will execute transparently once Phase 3 lands.
3413
+ */
3414
+ const RuntimeProbeSandboxSchema = z.enum(["host", "twin"]);
3415
+ /**
3416
+ * Default per-probe timeout in milliseconds. Matches the existing
3417
+ * BuildCheck ceiling (60 s) — deliberate, so probe timeouts are bounded
3418
+ * by the same policy the pipeline already uses for long-running checks.
3419
+ */
3420
+ const DEFAULT_PROBE_TIMEOUT_MS = 6e4;
3421
+ /** Hard upper bound on per-probe stdout/stderr retention (≤ 4 KiB — the
3422
+ * same convention as VerificationFinding.{stdoutTail,stderrTail}). */
3423
+ const PROBE_TAIL_BYTES = 4 * 1024;
3424
+ /**
3425
+ * Zod schema for one runtime probe declared in a story's
3426
+ * `## Runtime Probes` section.
3427
+ *
3428
+ * Required fields (`name`, `sandbox`, `command`) force authors to make
3429
+ * intent explicit — no silent defaults that could mask a miswritten probe.
3430
+ * Optional fields cover operational knobs with sensible fallbacks.
3431
+ */
3432
+ const RuntimeProbeSchema = z.object({
3433
+ name: z.string().min(1, "probe name is required"),
3434
+ sandbox: RuntimeProbeSandboxSchema,
3435
+ command: z.string().min(1, "probe command is required"),
3436
+ timeout_ms: z.number().int().positive().optional(),
3437
+ description: z.string().optional()
3438
+ });
3439
+ /** Zod schema for the full list (wrapping the per-probe schema). */
3440
+ const RuntimeProbeListSchema = z.array(RuntimeProbeSchema);
3441
+
3442
+ //#endregion
3443
+ //#region packages/sdlc/dist/verification/probes/parser.js
3444
+ const SECTION_HEADING = /^##\s+Runtime\s+Probes\s*$/i;
3445
+ /**
3446
+ * Return the raw text of the story's `## Runtime Probes` section (excluding
3447
+ * the heading line itself), or `undefined` if the section is not present.
3448
+ *
3449
+ * The section ends at the next `##` heading or end-of-file. Sub-headings
3450
+ * (`###`, `####`) remain part of the section body.
3451
+ */
3452
+ function extractRuntimeProbesSection(storyContent) {
3453
+ const lines = storyContent.split(/\r?\n/);
3454
+ const start = lines.findIndex((line) => SECTION_HEADING.test(line.trim()));
3455
+ if (start === -1) return void 0;
3456
+ let end = lines.length;
3457
+ for (let i = start + 1; i < lines.length; i += 1) if (/^##\s+\S/.test(lines[i] ?? "")) {
3458
+ end = i;
3459
+ break;
3460
+ }
3461
+ return lines.slice(start + 1, end).join("\n");
3462
+ }
3463
+ /**
3464
+ * Extract the body of the first ```yaml (or ```yml) fenced block in the
3465
+ * given section text. Returns `undefined` if no yaml fence is present.
3466
+ *
3467
+ * The opening fence is recognized case-insensitively and may carry an
3468
+ * arbitrary trailing info string (e.g. ```yaml title=...). The closing
3469
+ * fence is any line whose first non-whitespace run is exactly three
3470
+ * backticks.
3471
+ */
3472
+ function extractYamlFence(section) {
3473
+ const lines = section.split(/\r?\n/);
3474
+ let inside = false;
3475
+ let collected;
3476
+ for (const line of lines) {
3477
+ if (!inside) {
3478
+ if (/^\s*```\s*(yaml|yml)\b/i.test(line)) {
3479
+ inside = true;
3480
+ collected = [];
3481
+ }
3482
+ continue;
3483
+ }
3484
+ if (/^\s*```\s*$/.test(line)) return (collected ?? []).join("\n");
3485
+ collected?.push(line);
3486
+ }
3487
+ return void 0;
3488
+ }
3489
+ /**
3490
+ * Parse the `## Runtime Probes` section of a story's markdown content.
3491
+ *
3492
+ * Outcomes:
3493
+ * - section missing → { kind: 'absent' }
3494
+ * - section present, no yaml fence → { kind: 'invalid' }
3495
+ * - section present, yaml fence malformed → { kind: 'invalid' }
3496
+ * - section present, yaml root is not a list → { kind: 'invalid' }
3497
+ * - section present, entry fails RuntimeProbeSchema → { kind: 'invalid' }
3498
+ * - section present, yaml valid, all entries valid → { kind: 'parsed' }
3499
+ *
3500
+ * Duplicate names within a single story are surfaced as `invalid` so that
3501
+ * finding messages can unambiguously reference a probe by name.
3502
+ */
3503
+ function parseRuntimeProbes(storyContent) {
3504
+ const section = extractRuntimeProbesSection(storyContent);
3505
+ if (section === void 0) return { kind: "absent" };
3506
+ const yamlBody = extractYamlFence(section);
3507
+ if (yamlBody === void 0) return {
3508
+ kind: "invalid",
3509
+ error: "## Runtime Probes section is present but contains no terminated ```yaml fenced block"
3510
+ };
3511
+ let parsed;
3512
+ try {
3513
+ parsed = load(yamlBody) ?? [];
3514
+ } catch (err) {
3515
+ const detail = err instanceof YAMLException ? err.message : String(err);
3516
+ return {
3517
+ kind: "invalid",
3518
+ error: `YAML parse error: ${detail}`
3519
+ };
3520
+ }
3521
+ if (!Array.isArray(parsed)) return {
3522
+ kind: "invalid",
3523
+ error: `probe block root must be a YAML list; got ${typeof parsed}`
3524
+ };
3525
+ const validation = RuntimeProbeListSchema.safeParse(parsed);
3526
+ if (!validation.success) {
3527
+ const first = validation.error.issues[0];
3528
+ const path$1 = first?.path.join(".") ?? "";
3529
+ const message = first?.message ?? "schema validation failed";
3530
+ return {
3531
+ kind: "invalid",
3532
+ error: `probe list is malformed at ${path$1 || "<root>"}: ${message}`
3533
+ };
3534
+ }
3535
+ const probes = validation.data;
3536
+ const seen = new Set();
3537
+ for (const probe of probes) {
3538
+ if (seen.has(probe.name)) return {
3539
+ kind: "invalid",
3540
+ error: `duplicate probe name: ${probe.name}`
3541
+ };
3542
+ seen.add(probe.name);
3543
+ }
3544
+ return {
3545
+ kind: "parsed",
3546
+ probes
3547
+ };
3548
+ }
3549
+
3550
+ //#endregion
3551
+ //#region packages/sdlc/dist/verification/probes/executor.js
3552
+ /** Return the last N bytes of a UTF-8 string (sliced by length for simplicity). */
3553
+ function tail(text, bytes = PROBE_TAIL_BYTES) {
3554
+ return text.length <= bytes ? text : text.slice(text.length - bytes);
3555
+ }
3556
+ /**
3557
+ * Execute one probe on the host and return a structured ProbeResult.
3558
+ *
3559
+ * Behavior notes:
3560
+ * - The shell used is `/bin/sh -c '<probe.command>'` inside a detached
3561
+ * process group (so the entire tree is killed on timeout).
3562
+ * - stdout and stderr are captured independently; each is returned
3563
+ * tailed to PROBE_TAIL_BYTES (≤ 4 KiB) so published tarballs of the
3564
+ * run manifest stay small.
3565
+ * - Timeout defaults to `probe.timeout_ms ?? DEFAULT_PROBE_TIMEOUT_MS`
3566
+ * (60 s). When the timeout fires, the process group is SIGKILL'd and
3567
+ * the returned result has `outcome: 'timeout'`, `exitCode` undefined.
3568
+ * - Never throws. Spawn errors (e.g. exec format error) are returned as
3569
+ * `outcome: 'fail'` with exitCode -1 and the error message captured on
3570
+ * stderrTail, so the caller can emit a deterministic finding.
3571
+ */
3572
+ function executeProbeOnHost(probe, options = {}) {
3573
+ const timeoutMs = probe.timeout_ms ?? DEFAULT_PROBE_TIMEOUT_MS;
3574
+ const cwd = options.cwd ?? process.cwd();
3575
+ const env = options.env ?? process.env;
3576
+ const start = Date.now();
3577
+ return new Promise((resolve$2) => {
3578
+ let stdout = "";
3579
+ let stderr = "";
3580
+ let settled = false;
3581
+ const child = spawn(probe.command, [], {
3582
+ cwd,
3583
+ env,
3584
+ detached: true,
3585
+ shell: true,
3586
+ stdio: [
3587
+ "ignore",
3588
+ "pipe",
3589
+ "pipe"
3590
+ ]
3591
+ });
3592
+ const finalize = (result) => {
3593
+ if (settled) return;
3594
+ settled = true;
3595
+ resolve$2(result);
3596
+ };
3597
+ child.on("error", (err) => {
3598
+ finalize({
3599
+ outcome: "fail",
3600
+ command: probe.command,
3601
+ exitCode: -1,
3602
+ stdoutTail: tail(stdout),
3603
+ stderrTail: tail(stderr + (stderr.length > 0 && !stderr.endsWith("\n") ? "\n" : "") + `spawn error: ${err.message}\n`),
3604
+ durationMs: Date.now() - start
3605
+ });
3606
+ });
3607
+ child.stdout?.on("data", (chunk) => {
3608
+ stdout += chunk.toString();
3609
+ });
3610
+ child.stderr?.on("data", (chunk) => {
3611
+ stderr += chunk.toString();
3612
+ });
3613
+ const timeoutHandle = setTimeout(() => {
3614
+ try {
3615
+ if (child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
3616
+ } catch {}
3617
+ finalize({
3618
+ outcome: "timeout",
3619
+ command: probe.command,
3620
+ stdoutTail: tail(stdout),
3621
+ stderrTail: tail(stderr),
3622
+ durationMs: Date.now() - start
3623
+ });
3624
+ }, timeoutMs);
3625
+ child.on("close", (code) => {
3626
+ clearTimeout(timeoutHandle);
3627
+ const duration = Date.now() - start;
3628
+ finalize({
3629
+ outcome: code === 0 ? "pass" : "fail",
3630
+ command: probe.command,
3631
+ ...code !== null ? { exitCode: code } : {},
3632
+ stdoutTail: tail(stdout),
3633
+ stderrTail: tail(stderr),
3634
+ durationMs: duration
3635
+ });
3636
+ });
3637
+ });
3638
+ }
3639
+
3640
+ //#endregion
3641
+ //#region packages/sdlc/dist/verification/checks/runtime-probe-check.js
3642
+ const CATEGORY_PARSE = "runtime-probe-parse-error";
3643
+ const CATEGORY_SKIP = "runtime-probe-skip";
3644
+ const CATEGORY_DEFERRED = "runtime-probe-deferred";
3645
+ const CATEGORY_FAIL = "runtime-probe-fail";
3646
+ const CATEGORY_TIMEOUT = "runtime-probe-timeout";
3647
+ const defaultExecutors = { host: (probe) => executeProbeOnHost(probe, { cwd: process.cwd() }) };
3648
+ var RuntimeProbeCheck = class {
3649
+ name = "runtime-probes";
3650
+ tier = "A";
3651
+ _executors;
3652
+ constructor(executors) {
3653
+ this._executors = {
3654
+ ...defaultExecutors,
3655
+ ...executors ?? {}
3656
+ };
3657
+ }
3658
+ async run(context) {
3659
+ const start = Date.now();
3660
+ if (context.storyContent === void 0) {
3661
+ const findings$1 = [{
3662
+ category: CATEGORY_SKIP,
3663
+ severity: "warn",
3664
+ message: "story content unavailable — skipping runtime probe check"
3665
+ }];
3666
+ return {
3667
+ status: "warn",
3668
+ details: renderFindings(findings$1),
3669
+ duration_ms: Date.now() - start,
3670
+ findings: findings$1
3671
+ };
3672
+ }
3673
+ const parsed = parseRuntimeProbes(context.storyContent);
3674
+ if (parsed.kind === "absent") return {
3675
+ status: "pass",
3676
+ details: "runtime-probes: no ## Runtime Probes section declared — skipping",
3677
+ duration_ms: Date.now() - start,
3678
+ findings: []
3679
+ };
3680
+ if (parsed.kind === "invalid") {
3681
+ const findings$1 = [{
3682
+ category: CATEGORY_PARSE,
3683
+ severity: "error",
3684
+ message: parsed.error
3685
+ }];
3686
+ return {
3687
+ status: "fail",
3688
+ details: renderFindings(findings$1),
3689
+ duration_ms: Date.now() - start,
3690
+ findings: findings$1
3691
+ };
3692
+ }
3693
+ if (parsed.probes.length === 0) return {
3694
+ status: "pass",
3695
+ details: "runtime-probes: 0 probes declared — skipping",
3696
+ duration_ms: Date.now() - start,
3697
+ findings: []
3698
+ };
3699
+ const findings = [];
3700
+ for (const probe of parsed.probes) {
3701
+ if (probe.sandbox === "twin") {
3702
+ findings.push({
3703
+ category: CATEGORY_DEFERRED,
3704
+ severity: "warn",
3705
+ message: `probe "${probe.name}" uses sandbox=twin which is deferred until Phase 3 (Digital Twin integration); skipping`
3706
+ });
3707
+ continue;
3708
+ }
3709
+ const result = await this._executors.host(probe);
3710
+ if (result.outcome === "pass") continue;
3711
+ const category = result.outcome === "timeout" ? CATEGORY_TIMEOUT : CATEGORY_FAIL;
3712
+ const descriptor = probe.description ? ` (${probe.description})` : "";
3713
+ const message = result.outcome === "timeout" ? `probe "${probe.name}"${descriptor} timed out after ${result.durationMs}ms` : `probe "${probe.name}"${descriptor} failed with exit ${result.exitCode ?? "unknown"}`;
3714
+ findings.push({
3715
+ category,
3716
+ severity: "error",
3717
+ message,
3718
+ command: result.command,
3719
+ ...result.exitCode !== void 0 ? { exitCode: result.exitCode } : {},
3720
+ stdoutTail: result.stdoutTail,
3721
+ stderrTail: result.stderrTail,
3722
+ durationMs: result.durationMs
3723
+ });
3724
+ }
3725
+ const status = findings.some((f) => f.severity === "error") ? "fail" : findings.some((f) => f.severity === "warn") ? "warn" : "pass";
3726
+ return {
3727
+ status,
3728
+ details: findings.length > 0 ? renderFindings(findings) : `runtime-probes: ${parsed.probes.length} probe(s) passed`,
3729
+ duration_ms: Date.now() - start,
3730
+ findings
3731
+ };
3732
+ }
3733
+ };
3734
+
3398
3735
  //#endregion
3399
3736
  //#region packages/sdlc/dist/verification/verification-pipeline.js
3400
3737
  /**
@@ -3460,7 +3797,8 @@ var VerificationPipeline = class {
3460
3797
  checkName: check.name,
3461
3798
  status: runResult.status,
3462
3799
  details: runResult.details,
3463
- duration_ms: runResult.duration_ms
3800
+ duration_ms: runResult.duration_ms,
3801
+ ...runResult.findings !== void 0 ? { findings: runResult.findings } : {}
3464
3802
  };
3465
3803
  } catch (err) {
3466
3804
  const elapsed = Date.now() - checkStart;
@@ -3470,7 +3808,12 @@ var VerificationPipeline = class {
3470
3808
  checkName: check.name,
3471
3809
  status: "warn",
3472
3810
  details: message,
3473
- duration_ms: elapsed
3811
+ duration_ms: elapsed,
3812
+ findings: [{
3813
+ category: "check-exception",
3814
+ severity: "warn",
3815
+ message
3816
+ }]
3474
3817
  };
3475
3818
  }
3476
3819
  checkResults.push(result);
@@ -3495,11 +3838,13 @@ var VerificationPipeline = class {
3495
3838
  /**
3496
3839
  * Create a VerificationPipeline pre-loaded with the canonical check set.
3497
3840
  *
3498
- * Canonical Tier A check order (architecture section 3.5):
3841
+ * Canonical Tier A check order:
3499
3842
  * 1. PhantomReviewCheck — story 51-2 (runs first: unreviewed stories skipped)
3500
3843
  * 2. TrivialOutputCheck — story 51-3
3501
3844
  * 3. AcceptanceCriteriaEvidenceCheck
3502
3845
  * 4. BuildCheck — story 51-4
3846
+ * 5. RuntimeProbeCheck — Epic 55 Phase 2: runtime behavior gate; runs last
3847
+ * in Tier A because probes may depend on built artifacts
3503
3848
  *
3504
3849
  * @param bus Typed event bus for verification events.
3505
3850
  * @param config Optional config (used to forward threshold to TrivialOutputCheck).
@@ -3509,7 +3854,8 @@ function createDefaultVerificationPipeline(bus, config) {
3509
3854
  new PhantomReviewCheck(),
3510
3855
  new TrivialOutputCheck(config),
3511
3856
  new AcceptanceCriteriaEvidenceCheck(),
3512
- new BuildCheck()
3857
+ new BuildCheck(),
3858
+ new RuntimeProbeCheck()
3513
3859
  ];
3514
3860
  return new VerificationPipeline(bus, checks);
3515
3861
  }
@@ -4140,6 +4486,61 @@ var RunManifest = class RunManifest {
4140
4486
  }
4141
4487
  };
4142
4488
 
4489
+ //#endregion
4490
+ //#region packages/sdlc/dist/run-model/verification-findings-counts.js
4491
+ /**
4492
+ * Verification finding count roll-up — Story 55-3b.
4493
+ *
4494
+ * Collapses every finding across every check in a StoredVerificationSummary
4495
+ * into a `{error, warn, info}` triple, suitable for per-story surfacing in
4496
+ * the status/metrics CLI JSON payloads.
4497
+ *
4498
+ * Intentionally pure: no I/O, no logger, no throw. Fits cleanly in the
4499
+ * run-model package so both the status and metrics commands (and any
4500
+ * future consumer) can share a single implementation and one set of tests.
4501
+ */
4502
+ /** Zero-counts object used as the default return value and as the identity
4503
+ * element in consumer-side accumulations. */
4504
+ const ZERO_FINDING_COUNTS = Object.freeze({
4505
+ error: 0,
4506
+ warn: 0,
4507
+ info: 0
4508
+ });
4509
+ /**
4510
+ * Sum findings across every check in the summary, grouped by severity.
4511
+ *
4512
+ * Backward-compatible — when the summary is `undefined`, or a check has no
4513
+ * `findings` field (legacy manifests written before Story 55-2 migrated the
4514
+ * checks), the absent arrays contribute 0 to every severity. No severity
4515
+ * ever reports undefined.
4516
+ */
4517
+ function rollupFindingCounts(summary) {
4518
+ if (summary === void 0 || summary === null) return { ...ZERO_FINDING_COUNTS };
4519
+ let error = 0;
4520
+ let warn = 0;
4521
+ let info = 0;
4522
+ for (const check of summary.checks) {
4523
+ const findings = check.findings;
4524
+ if (findings === void 0) continue;
4525
+ for (const finding of findings) switch (finding.severity) {
4526
+ case "error":
4527
+ error += 1;
4528
+ break;
4529
+ case "warn":
4530
+ warn += 1;
4531
+ break;
4532
+ case "info":
4533
+ info += 1;
4534
+ break;
4535
+ }
4536
+ }
4537
+ return {
4538
+ error,
4539
+ warn,
4540
+ info
4541
+ };
4542
+ }
4543
+
4143
4544
  //#endregion
4144
4545
  //#region packages/sdlc/dist/run-model/supervisor-lock.js
4145
4546
  const defaultLogger = console;
@@ -4884,5 +5285,5 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
4884
5285
  }
4885
5286
 
4886
5287
  //#endregion
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
5288
+ export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, runHealthAction, validateStoryKey };
5289
+ //# sourceMappingURL=health-BIS34IYK.js.map
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-CB48LF0t.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-BIS34IYK.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";
@@ -43840,4 +43840,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
43840
43840
 
43841
43841
  //#endregion
43842
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 };
43843
- //# sourceMappingURL=run-BHKqyFFM.js.map
43843
+ //# sourceMappingURL=run-BAc1zfMQ.js.map
@@ -1,8 +1,8 @@
1
- import "./health-CB48LF0t.js";
1
+ import "./health-BIS34IYK.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-BHKqyFFM.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-BAc1zfMQ.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.4",
3
+ "version": "0.20.6",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",