feat: add test-free verify:fast (typecheck + build + boot-smoke)

Adds scripts/verify-fast.mjs + root `pnpm verify:fast`, an opt-in, flake-free
verification path that runs typecheck + build scoped to the changed packages
(reusing test-changed.mjs git-diff / changed-package resolution) plus the
existing boot smoke once, with no test suite. Each step is bounded by the
shared runWithWatchdog (class "changed"); exits nonzero on the first failure.
No default changed: pnpm test, the merge gate, and CI are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-25 17:06:08 -07:00
parent a0954c7f27
commit f342761f1b
6 changed files with 438 additions and 4 deletions

View File

@@ -107,6 +107,7 @@ The merge gate is thin and trusted: CI blocks PRs on exactly Lint, Typecheck, Bu
pnpm test # gate suite + changed-only affected tests (bounded; never full-suite)
pnpm test:gate # the merge gate: curated engine-core suite + CI-shape test
pnpm smoke:boot # boot smoke: CLI --help + real serve /api/health
pnpm verify:fast # TEST-FREE verification: typecheck + build (scoped to changed packages) + boot smoke; recommended non-test verification/testCommand. Additive — changes no default
pnpm test:velocity # weekly report-only test velocity baseline; use -- --measure --write-report to refresh
pnpm test:full # full workspace suite — explicit opt-in only
pnpm lint
@@ -114,6 +115,8 @@ pnpm build
pnpm verify:workspace # deep opt-in verification (lint -> test:full -> build); NOT the merge gate
```
`pnpm verify:fast` is the recommended **test-free verification** path: typecheck + build scoped to the changed packages (it reuses `pnpm test`'s changed-package resolution) plus the boot smoke once, with **no test run**. It is deterministic and flake-free, suitable as a project `testCommand`/verification command when you want non-test verification; the full suite stays available and runs non-blocking. It is additive and does not change `pnpm test`, the gate, or CI. See `docs/testing.md`.
### Standing Rule: Flaky Tests Are Quarantined on Sight (Deletion Ratchet)
- A test observed failing without a corresponding real bug in the change is QUARANTINED ON SIGHT: add an entry to `scripts/lib/test-quarantine.json` (`file`, `reason` with a link to the failing run, `quarantinedAt`) AND a matching one-line `exclude` in that package's vitest config, in the same commit.

View File

@@ -32,12 +32,16 @@ Use the narrowest command that exercises the behavior you changed, then broaden
pnpm test # gate suite + changed-only affected tests (bounded; never full-suite)
pnpm test:gate # the merge gate: curated engine-core suite + CI-shape test
pnpm smoke:boot # boot smoke: CLI --help + real serve /api/health
pnpm verify:fast # TEST-FREE verification: typecheck + build (scoped to changed packages) + boot smoke
pnpm test:full # full workspace suite — explicit opt-in only
pnpm lint # lint all packages
pnpm build # build workspace packages (excludes desktop/mobile)
pnpm verify:workspace # deep opt-in verification: lint -> test:full -> build (NOT the merge gate)
```
<!-- FNXC:TestInfrastructure 2026-06-25-00:00: verify:fast is the opt-in test-free verification path. docs/testing.md observes the broad test gate caught no recalled real bugs while consuming ~70% of shipping time in flake triage; typecheck+build+boot-smoke gives deterministic, flake-free signal without running tests. It changes no default — pnpm test, the merge gate, and CI are untouched; the full suite stays available and runs non-blocking. -->
`pnpm verify:fast` (`scripts/verify-fast.mjs`) is the recommended **test-free verification** command: it runs **typecheck + build scoped to the changed packages** (reusing the same git-diff / changed-package resolution as `pnpm test`) followed by the existing **boot smoke** once — and runs **no test suite**. It gives deterministic, flake-free signal in seconds, so it is a sound project `testCommand`/verification command when you want non-test verification. With no affected package (root/docs-only diff) it runs the boot smoke only. Each step is bounded by the shared `runWithWatchdog` (class `changed`) so a hang fails fast, and it exits nonzero on the first failing step. This is purely additive: it does not change `pnpm test`, the merge gate, or CI, and the full suite stays available (`pnpm test:full`, non-blocking on push to main).
`pnpm test:full` runs each package's default test script with capped worker fanout (`FUSION_TEST_TOTAL_WORKERS=4 FUSION_TEST_CONCURRENCY=2 pnpm -r --workspace-concurrency=2 test`). Do not casually raise worker counts; dashboard/jsdom and integration-heavy packages destabilize when oversubscribed. Use `VITEST_MAX_WORKERS=<n>` only for targeted package-level investigation.
<!-- FNXC:CustomWorkflowReliability 2026-06-19-00:00: FN-6694 adds an executable custom-workflow reliability release-check lane for QA signoff, but it must stay out of the merge gate so reliability evidence does not inflate every PR's wall-time. -->

View File

@@ -33,6 +33,7 @@
"build:exe": "pnpm build && pnpm --filter @runfusion/fusion build:exe",
"build:exe:all": "pnpm build && pnpm --filter @runfusion/fusion build:exe:all",
"test": "node scripts/test-changed.mjs",
"verify:fast": "node scripts/verify-fast.mjs",
"test:scripts": "node scripts/run-script-tests.mjs",
"test:workflow-release-check": "node scripts/workflow-reliability-release-check.mjs",
"fn:cache-stats": "node scripts/cache-stats.mjs",

View File

@@ -0,0 +1,155 @@
/**
* Unit tests for scripts/verify-fast.mjs
*
* Runner: node --test scripts/__tests__/verify-fast.test.mjs
*
* These exercise the PURE planning / arg-construction logic only. They never
* spawn real tsc / build / vitest — the test-free verification command's value
* is its deterministic plan, so that is what we pin.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
buildTypecheckStep,
buildBuildStep,
buildBootSmokeStep,
buildVerifyPlan,
VERIFY_EXCLUDED_PACKAGES,
} from "../verify-fast.mjs";
import { resolveAffectedPackages } from "../test-changed.mjs";
const SMOKE = "/repo/scripts/boot-smoke.mjs";
const NODE = "/usr/bin/node";
function stepIds(plan) {
return plan.steps.map((s) => s.id);
}
function stepByKind(plan, kind) {
return plan.steps.filter((s) => s.kind === kind);
}
// ---------------------------------------------------------------------------
// buildTypecheckStep
// ---------------------------------------------------------------------------
test("buildTypecheckStep: uses the package's typecheck script when present", () => {
const step = buildTypecheckStep("@fusion/engine", { hasTypecheck: true });
assert.equal(step.command, "pnpm");
assert.deepEqual(step.args, ["--filter", "@fusion/engine", "typecheck"]);
assert.equal(step.klass, "changed");
});
test("buildTypecheckStep: falls back to scoped tsc --noEmit when no typecheck script", () => {
const step = buildTypecheckStep("@fusion/widget", { hasTypecheck: false });
assert.deepEqual(step.args, ["--filter", "@fusion/widget", "exec", "tsc", "--noEmit", "-p", "."]);
});
test("buildTypecheckStep: defaults to the tsc fallback when meta omitted", () => {
const step = buildTypecheckStep("@fusion/widget");
assert.deepEqual(step.args, ["--filter", "@fusion/widget", "exec", "tsc", "--noEmit", "-p", "."]);
});
// ---------------------------------------------------------------------------
// buildBuildStep / buildBootSmokeStep
// ---------------------------------------------------------------------------
test("buildBuildStep: scoped pnpm build for the package", () => {
const step = buildBuildStep("@fusion/cli");
assert.deepEqual(step.args, ["--filter", "@fusion/cli", "build"]);
assert.equal(step.kind, "build");
});
test("buildBootSmokeStep: runs the boot-smoke script via node", () => {
const step = buildBootSmokeStep(SMOKE, NODE);
assert.equal(step.command, NODE);
assert.deepEqual(step.args, [SMOKE]);
assert.equal(step.kind, "boot-smoke");
});
// ---------------------------------------------------------------------------
// buildVerifyPlan
// ---------------------------------------------------------------------------
test("buildVerifyPlan: no packages -> boot smoke only", () => {
const plan = buildVerifyPlan({ packages: [], bootSmokeScriptPath: SMOKE, nodeBin: NODE });
assert.deepEqual(stepIds(plan), ["boot-smoke"]);
assert.deepEqual(plan.eligiblePackages, []);
});
test("buildVerifyPlan: typecheck for all eligible, then builds, then boot smoke (ordered)", () => {
const packageMeta = new Map([
["@fusion/engine", { hasTypecheck: true, hasBuild: true }],
["@fusion/core", { hasTypecheck: true, hasBuild: true }],
]);
const plan = buildVerifyPlan({ packages: ["@fusion/engine", "@fusion/core"], packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
assert.deepEqual(stepIds(plan), [
"typecheck:@fusion/engine",
"typecheck:@fusion/core",
"build:@fusion/engine",
"build:@fusion/core",
"boot-smoke",
]);
});
test("buildVerifyPlan: a package without a build script gets a typecheck step but no build step", () => {
const packageMeta = new Map([
["@fusion/engine", { hasTypecheck: true, hasBuild: true }],
["@fusion/test-only", { hasTypecheck: false, hasBuild: false }],
]);
const plan = buildVerifyPlan({ packages: ["@fusion/engine", "@fusion/test-only"], packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
assert.deepEqual(stepIds(plan), [
"typecheck:@fusion/engine",
"typecheck:@fusion/test-only",
"build:@fusion/engine",
"boot-smoke",
]);
// The test-only package's typecheck uses the tsc fallback (no typecheck script).
const tc = stepByKind(plan, "typecheck").find((s) => s.pkg === "@fusion/test-only");
assert.deepEqual(tc.args, ["--filter", "@fusion/test-only", "exec", "tsc", "--noEmit", "-p", "."]);
});
test("buildVerifyPlan: desktop/mobile are excluded from scoped steps but boot smoke still runs", () => {
const packageMeta = new Map([
["@fusion/engine", { hasTypecheck: true, hasBuild: true }],
["@fusion/desktop", { hasTypecheck: true, hasBuild: true }],
["@fusion/mobile", { hasTypecheck: true, hasBuild: true }],
]);
const plan = buildVerifyPlan({
packages: ["@fusion/engine", "@fusion/desktop", "@fusion/mobile"],
packageMeta,
bootSmokeScriptPath: SMOKE,
nodeBin: NODE,
});
assert.deepEqual(plan.eligiblePackages, ["@fusion/engine"]);
assert.deepEqual(plan.excludedPackages.sort(), ["@fusion/desktop", "@fusion/mobile"]);
assert.deepEqual(stepIds(plan), ["typecheck:@fusion/engine", "build:@fusion/engine", "boot-smoke"]);
});
test("VERIFY_EXCLUDED_PACKAGES mirrors the root build/typecheck exclusions", () => {
assert.ok(VERIFY_EXCLUDED_PACKAGES.has("@fusion/desktop"));
assert.ok(VERIFY_EXCLUDED_PACKAGES.has("@fusion/mobile"));
});
// ---------------------------------------------------------------------------
// Integration: reuse test-changed's resolveAffectedPackages to scope the plan
// ---------------------------------------------------------------------------
test("buildVerifyPlan: scopes to exactly the packages resolveAffectedPackages selects", () => {
// packageNameByDir as test-changed builds it (dir -> name, with a bare alias).
const packageNameByDir = new Map([
["packages/engine", "@fusion/engine"],
["engine", "@fusion/engine"],
["packages/dashboard", "@fusion/dashboard"],
["dashboard", "@fusion/dashboard"],
]);
const changedFiles = ["packages/engine/src/merger.ts", "docs/testing.md"];
const affected = resolveAffectedPackages(changedFiles, packageNameByDir);
assert.deepEqual(affected, ["@fusion/engine"]); // docs/ change does not add a package
const packageMeta = new Map([["@fusion/engine", { hasTypecheck: true, hasBuild: true }]]);
const plan = buildVerifyPlan({ packages: affected, packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
assert.deepEqual(stepIds(plan), ["typecheck:@fusion/engine", "build:@fusion/engine", "boot-smoke"]);
});

View File

@@ -407,7 +407,13 @@ function gitOutput(gitArgs) {
return result.stdout.trim();
}
function getBaseBranch() {
// FNXC:TestInfrastructure 2026-06-25-00:00:
// Exported so the test-free verification path (scripts/verify-fast.mjs) reuses
// the SAME base-branch / comparison-base / changed-file / workspace-resolution
// logic instead of reinventing git-diff. verify:fast runs typecheck + build +
// boot-smoke (no test suite) and must scope to exactly the packages a
// changed-only run would scope to.
export function getBaseBranch() {
const changesetConfigPath = path.join(rootDir, ".changeset", "config.json");
const changesetConfig = JSON.parse(readFileSync(changesetConfigPath, "utf8"));
return changesetConfig.baseBranch || "main";
@@ -497,7 +503,7 @@ export function listWorkspacePackageInfos({ projectRoot = rootDir } = {}) {
.sort((a, b) => a.dir.localeCompare(b.dir));
}
function listWorkspacePackages(workspacePackages = listWorkspacePackageInfos()) {
export function listWorkspacePackages(workspacePackages = listWorkspacePackageInfos()) {
const packageNameByDir = new Map();
for (const workspacePackage of workspacePackages) {
packageNameByDir.set(workspacePackage.dir, workspacePackage.name);
@@ -665,7 +671,7 @@ export function isSharedInfraChange(changedFiles) {
});
}
function detectComparisonBase(baseBranch) {
export function detectComparisonBase(baseBranch) {
const candidates = [
`origin/${baseBranch}`,
`refs/remotes/origin/${baseBranch}`,
@@ -682,7 +688,7 @@ function detectComparisonBase(baseBranch) {
return null;
}
function changedFilesSince(baseSha) {
export function changedFilesSince(baseSha) {
const diff = gitOutput(["diff", "--name-only", `${baseSha}...HEAD`]);
if (diff === null) {
return null;

265
scripts/verify-fast.mjs Normal file
View File

@@ -0,0 +1,265 @@
#!/usr/bin/env node
/*
FNXC:TestInfrastructure 2026-06-25-00:00:
verify:fast is the opt-in, TEST-FREE verification command. It gives deterministic,
flake-free signal in seconds without running the test suite, by doing exactly:
1. typecheck — scoped to the changed packages (their `typecheck` script, or
`pnpm --filter <pkg> exec tsc --noEmit -p .` when none exists).
2. build — scoped to the changed packages (`pnpm --filter <pkg> build`).
3. boot smoke — once (scripts/boot-smoke.mjs: CLI --help + real serve /api/health).
Rationale: docs/testing.md observes the broad test gate "caught no recalled real
bugs while consuming ~70% of shipping time in flake triage." typecheck+build+boot
is fast and never flakes, so it is a sound project `testCommand`/verification
command when you want non-test verification. This command changes NO default —
`pnpm test`, the merge gate, and CI are untouched. The full suite stays available
(`pnpm test:full`) and runs non-blocking on push to main.
Change-detection REUSES scripts/test-changed.mjs (getBaseBranch /
detectComparisonBase / changedFilesSince / resolveAffectedPackages / workspace
resolution) so verify:fast scopes to exactly the packages a changed-only test run
would, instead of reinventing git-diff. Each step is bounded by the existing
`runWithWatchdog` (class "changed") so a hung tsc/build/serve fails fast instead
of blocking forever, and we exit nonzero on the first failing step.
*/
import path from "node:path";
import { readFileSync } from "node:fs";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import {
getBaseBranch,
detectComparisonBase,
changedFilesSince,
listWorkspacePackageInfos,
listWorkspacePackages,
buildPackageDirByName,
resolveAffectedPackages,
} from "./test-changed.mjs";
import { deriveBudgetMs, runWithWatchdog } from "./lib/run-vitest-watchdog.mjs";
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptDir, "..");
const bootSmokeScriptPath = path.join(scriptDir, "boot-smoke.mjs");
/*
FNXC:TestInfrastructure 2026-06-25-00:00:
@fusion/desktop and @fusion/mobile are excluded from the root `build`/`typecheck`
scripts (heavy native/electron + RN toolchains), so verify:fast mirrors that
policy and skips them with a note rather than failing on an unbuildable filter.
*/
export const VERIFY_EXCLUDED_PACKAGES = new Set(["@fusion/desktop", "@fusion/mobile"]);
/**
* Build the scoped typecheck step for a package. Prefers the package's own
* `typecheck` script (e.g. dashboard runs two tsc passes); falls back to a plain
* project tsc --noEmit when the package declares no typecheck script.
*
* @param {string} pkg workspace package name (e.g. "@fusion/engine")
* @param {{ hasTypecheck?: boolean }} [meta]
* @returns {{ id: string, kind: string, pkg: string, label: string, command: string, args: string[], klass: string }}
*/
export function buildTypecheckStep(pkg, meta = {}) {
const args = meta.hasTypecheck
? ["--filter", pkg, "typecheck"]
: ["--filter", pkg, "exec", "tsc", "--noEmit", "-p", "."];
return { id: `typecheck:${pkg}`, kind: "typecheck", pkg, label: `typecheck ${pkg}`, command: "pnpm", args, klass: "changed" };
}
/**
* Build the scoped build step for a package.
*
* @param {string} pkg
* @returns {{ id: string, kind: string, pkg: string, label: string, command: string, args: string[], klass: string }}
*/
export function buildBuildStep(pkg) {
return { id: `build:${pkg}`, kind: "build", pkg, label: `build ${pkg}`, command: "pnpm", args: ["--filter", pkg, "build"], klass: "changed" };
}
/**
* Build the single boot-smoke step (always last, after any builds, so it runs
* against freshly built artifacts).
*
* @param {string} smokeScriptPath
* @param {string} [nodeBin]
*/
export function buildBootSmokeStep(smokeScriptPath, nodeBin = process.execPath) {
return {
id: "boot-smoke",
kind: "boot-smoke",
pkg: null,
label: "boot smoke (CLI --help + real serve /api/health)",
command: nodeBin,
args: [smokeScriptPath],
klass: "changed",
};
}
/**
* Pure planner: turn the affected package set into an ordered step list.
* typecheck (all eligible) → build (eligible with a build script) → boot smoke.
* With no eligible packages this is just the boot-smoke step, satisfying the
* "no packages changed ⇒ boot smoke only" contract.
*
* @param {object} opts
* @param {string[]} [opts.packages] affected package names
* @param {Map<string, { dir?: string, hasTypecheck?: boolean, hasBuild?: boolean }>} [opts.packageMeta]
* @param {string} opts.bootSmokeScriptPath
* @param {string} [opts.nodeBin]
* @returns {{ eligiblePackages: string[], excludedPackages: string[], steps: object[] }}
*/
export function buildVerifyPlan({ packages = [], packageMeta = new Map(), bootSmokeScriptPath: smokeScriptPath, nodeBin = process.execPath } = {}) {
const eligiblePackages = packages.filter((pkg) => !VERIFY_EXCLUDED_PACKAGES.has(pkg));
const excludedPackages = packages.filter((pkg) => VERIFY_EXCLUDED_PACKAGES.has(pkg));
const steps = [];
for (const pkg of eligiblePackages) {
steps.push(buildTypecheckStep(pkg, packageMeta.get(pkg) ?? {}));
}
for (const pkg of eligiblePackages) {
const meta = packageMeta.get(pkg) ?? {};
// Only build packages that declare a build script; pure test/config packages
// have nothing to emit and a `pnpm --filter <pkg> build` would error.
if (meta.hasBuild !== false) steps.push(buildBuildStep(pkg));
}
steps.push(buildBootSmokeStep(smokeScriptPath, nodeBin));
return { eligiblePackages, excludedPackages, steps };
}
/**
* Read each affected package's package.json to learn which scripts it declares.
*
* @param {string[]} packages
* @param {Map<string, string>} packageDirByName pkg name → repo-relative dir
* @param {string} [root]
* @returns {Map<string, { dir: string, hasTypecheck: boolean, hasBuild: boolean }>}
*/
export function readPackageMeta(packages, packageDirByName, root = repoRoot) {
const meta = new Map();
for (const pkg of packages) {
const dir = packageDirByName.get(pkg);
let scripts = {};
if (dir) {
try {
const pkgJson = JSON.parse(readFileSync(path.join(root, dir, "package.json"), "utf8"));
scripts = pkgJson.scripts ?? {};
} catch {
// Missing/unreadable package.json: fall back to tsc default + attempt build.
}
}
meta.set(pkg, {
dir: dir ?? null,
hasTypecheck: typeof scripts.typecheck === "string",
hasBuild: typeof scripts.build === "string",
});
}
return meta;
}
/**
* Resolve the affected package set for the current working tree, reusing
* test-changed's git-diff + workspace resolution. Returns both the package list
* and a human note describing why the set is what it is (no base, no changes,
* unmappable path, etc.) so the CLI can explain a boot-smoke-only run.
*
* @returns {{ packages: string[], packageDirByName: Map<string,string>, note: string }}
*/
export function resolveAffectedForVerify() {
const baseBranch = getBaseBranch();
const comparisonBase = detectComparisonBase(baseBranch);
const workspacePackages = listWorkspacePackageInfos();
const packageNameByDir = listWorkspacePackages(workspacePackages);
const packageDirByName = buildPackageDirByName(workspacePackages);
if (!comparisonBase) {
return { packages: [], packageDirByName, note: `could not resolve merge-base with ${baseBranch}; running boot smoke only` };
}
const changedFiles = changedFilesSince(comparisonBase);
if (changedFiles === null) {
return { packages: [], packageDirByName, note: "failed to read git diff; running boot smoke only" };
}
if (changedFiles.length === 0) {
return { packages: [], packageDirByName, note: "no changes detected against base; running boot smoke only" };
}
const affected = resolveAffectedPackages(changedFiles, packageNameByDir);
if (affected === null) {
return { packages: [], packageDirByName, note: "changed file did not map to a workspace package; running boot smoke only" };
}
if (affected.length === 0) {
return { packages: [], packageDirByName, note: "no affected workspace package (root/docs-only changes); running boot smoke only" };
}
return { packages: affected, packageDirByName, note: `affected packages: ${affected.join(", ")}` };
}
/**
* Run one step under the wall-clock watchdog (class "changed"). Streams the
* child's output (stdio inherit) and throws with an `.exitCode` on the first
* failure/timeout/signal so the caller exits nonzero immediately.
*/
export async function runStep(step, { spawnFn = spawn, log = console.log, errLog = console.error } = {}) {
const budgetMs = deriveBudgetMs({ klass: step.klass ?? "changed" });
log(`\n[verify:fast] -> ${step.label}`);
log(`[verify:fast] ${step.command} ${step.args.join(" ")} (budget ${Math.round(budgetMs / 1000)}s)`);
const startedAt = Date.now();
const { code, signal, timedOut } = await runWithWatchdog({
command: step.command,
args: step.args,
env: process.env,
cwd: repoRoot,
budgetMs,
label: step.label,
log: errLog,
spawn: spawnFn,
});
const elapsedS = ((Date.now() - startedAt) / 1000).toFixed(1);
if (timedOut || signal || code !== 0) {
const reason = timedOut ? `watchdog timeout (${budgetMs}ms)` : signal ? `signal ${signal}` : `exit code ${code}`;
const error = new Error(`[verify:fast] FAILED: ${step.label} (${reason}) after ${elapsedS}s`);
error.exitCode = timedOut ? 124 : signal ? 1 : code ?? 1;
throw error;
}
log(`[verify:fast] OK ${step.label} (${elapsedS}s)`);
}
export async function main() {
const overallStart = Date.now();
console.log("[verify:fast] test-free verification: typecheck + build (scoped to changed packages) + boot smoke.");
const { packages, packageDirByName, note } = resolveAffectedForVerify();
console.log(`[verify:fast] ${note}`);
const packageMeta = readPackageMeta(packages, packageDirByName);
const { eligiblePackages, excludedPackages, steps } = buildVerifyPlan({
packages,
packageMeta,
bootSmokeScriptPath,
});
if (excludedPackages.length > 0) {
console.log(`[verify:fast] skipping excluded packages (also excluded from root build/typecheck): ${excludedPackages.join(", ")}`);
}
if (eligiblePackages.length === 0) {
console.log("[verify:fast] no scoped packages to verify; running boot smoke only.");
} else {
console.log(`[verify:fast] scoped to: ${eligiblePackages.join(", ")}`);
}
console.log(`[verify:fast] plan: ${steps.map((s) => s.id).join(" -> ")}`);
for (const step of steps) {
await runStep(step);
}
const elapsedS = ((Date.now() - overallStart) / 1000).toFixed(1);
console.log(`\n[verify:fast] PASS — ${steps.length} step(s) green in ${elapsedS}s (no tests run).`);
}
const currentFilePath = fileURLToPath(import.meta.url);
if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) {
main().catch((error) => {
if (error?.message) console.error(error.message);
if (error?.exitCode) process.exit(error.exitCode);
console.error(error);
process.exit(1);
});
}