Skip to content

Commit 4dd5041

Browse files
claude[bot]claude
andauthored
tooling(scripts): assertion floors for the batch 6a self-tests — sink repair plus one hoisted battery (#13799 batch 6a) (#15250)
* tooling(scripts): assertion floor for check-declaration-mirrors' self-test `cases.filter((c) => !c.cond)` was this self-test's only success condition, so "every case held" and "the cases never ran" printed the same line (#13489). Class-1 sink repair, the PR #15156 shape: the concise-arrow `ok` sink gains a block body that calls `registerCase()` before the unchanged `cases.push`. No assertion condition is inverted or rewritten. Class-2 roster, the PR #15217 shape: ONE battery hoisted to the top of the self-test body, floor at the measured 23, `SELF_TEST_BATTERIES` size pinned at 1. No comment was promoted to a section head. Case count before == after, measured by pinning the roster to an unreachable value and reading the breach line: 23. `--self-test` stdout and stderr are byte-identical to the base tree's, exit 0 on both. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * tooling(scripts): assertion floor for check-docs-single-h1' self-test `cases.filter((c) => !c.ok)` was this self-test's only success condition, so "every case held" and "the cases never ran" printed the same line (#13489). Class-1 sink repair, the PR #15156 shape: the concise-arrow `t` sink gains a block body that calls `registerCase()` before the unchanged `cases.push`. No assertion condition is inverted or rewritten. Class-2 roster, the PR #15217 shape: ONE battery hoisted to the top of the self-test body, floor at the measured 20, `SELF_TEST_BATTERIES` size pinned at 1. The file's single named section banner is NOT split on, and no comment was promoted to a section head. Case count before == after, measured by pinning the roster to an unreachable value and reading the breach line: 20 — which agrees with the count the existing verdict line prints. `--self-test` stdout and stderr are byte-identical to the base tree's, exit 0 on both. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * tooling(scripts): assertion floors for three counter-sink self-tests `problems.length` / `failed` were these self-tests' only success condition, so "every case held" and "the cases never ran" printed the same line (#13489). Class-1 sink repair via the THUNK route PR #15198 measured, because each sink write is failure-only: routing failure-only writes through a helper would floor zero cases on a green run, which is no floor at all. `check(() => { ... })` registers the case and then runs the existing site VERBATIM, so registration happens whether or not the site fires and no assertion condition is inverted or rewritten. Per-iteration registration through a block-bodied helper is the landed shape (check-doc-frontmatter, check-test-source-alias both call their helper from inside a `for`). Class-2 roster, the PR #15217 shape: ONE battery hoisted to the top of each self-test body, floor at the measured count, roster size pinned at 1. No comment was promoted to a section head; a single banner was not split on. Floors, each measured by pinning the roster to an unreachable value and reading the breach line -- never transcribed: check-meta-type-normalized 4 (4 thunked sites) check-org-identifier 31 (agrees with its printed `cases.length + 1`) check-error-code-casing 51 (agrees with its printed 46 + 5) `--self-test` stdout and stderr byte-identical to the base tree's on all three, exit 0 both sides; normal mode also byte-identical where the gate has one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * tooling(scripts): assertion floors for three more counter-sink self-tests Same repair as the previous commit: the THUNK route PR #15198 measured, because every sink write in these three is failure-only, plus PR #15217's single hoisted battery with the roster size pinned at 1. Floors, each measured by pinning the roster to an unreachable value and reading the breach line -- never transcribed: check-console-intercept-disarm 11 (its printed 10 cases + the real-tree floor) check-examples-live-imports 36 (agrees with its printed `cases.length`) check-optional-error-sink-contract 66 (its printed 19 + 9x4 spellings + the reject side + 9 derivation + the `run` reference pin) check-examples-live-imports' one sink site is an if/ELSE, so it is wrapped by hand rather than by a brace matcher that would stop at the `if` block's own closing brace; the branches are reindented, not rewritten. ⛔ check-optional-error-sink-contract's two assertions over `baseline.entries` are deliberately left UNREGISTERED and say so in place: they run one-per-row of a shrink-only ledger, and a floor moving with that list would red every legitimate removal (#13797's ruling, carried forward by PR #15217's check-whole-set-label-write). `LOG_CHANNELS` is not that -- it is the contract's own vocabulary, not a list meant to shrink -- so its loops do register. `--self-test` stdout and stderr byte-identical to the base tree's on all three, exit 0 both sides; normal mode byte-identical where the gate has one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cc2e39e commit 4dd5041

8 files changed

Lines changed: 937 additions & 68 deletions

scripts/check-console-intercept-disarm.mjs

Lines changed: 119 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,64 @@ export default defineConfig({ test: { disableConsoleIntercept: true } });
367367
// as one that passed (#13798).
368368
const SELF_TEST_VERDICT = 'check-console-intercept-disarm self-test reached its verdict';
369369

370+
// ── The self-test's own battery roster and floor (#13489) ──────────────────
371+
//
372+
// `failures` used to be this self-test's ONLY success condition, so "every case
373+
// held" and "the cases never ran" printed the same line. Closed the way
374+
// PR #13487 validated on check-doc-authoring: what is pinned is the registered
375+
// NAMES, not a number. The floor requires the OPENED set to equal the DECLARED
376+
// set with each battery at or above its own count.
377+
//
378+
// This file declares ONE battery, opened at the top of the self-test body. Its
379+
// blocks are headed by unmarked prose comments, so it carries fewer than the two
380+
// named section banners the sectioning criterion needs, and ⛔ a comment is NOT
381+
// promoted to a section head — that is a judgement per comment this transplant
382+
// does not make. The hoisted single battery is the shape PR #14896, PR #15003
383+
// and PR #15217 landed for exactly this case.
384+
//
385+
// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3
386+
// keeps a total "right" the moment a sibling grows.
387+
//
388+
// The count is a FLOOR, not an equality — adding cases is ordinary work and must
389+
// not red. A battery BELOW its floor means cases stopped running; the remedy is
390+
// to find what stopped registering.
391+
const SELF_TEST_BATTERIES = Object.freeze({
392+
'check-console-intercept-disarm self-test': 11,
393+
});
394+
395+
// DELETING an entry silences that battery's floor exactly as effectively as
396+
// zeroing it, so the roster's own size is pinned too.
397+
const SELF_TEST_BATTERY_FLOOR = 1;
398+
399+
// The key an assertion is filed under when no battery is open. It is not a
400+
// declared battery, so it reds by the same set difference rather than silently
401+
// inflating whichever battery happened to run last.
402+
const UNATTRIBUTED_BATTERY = '(no battery open)';
403+
370404
function selfTest() {
405+
// The battery ledger this self-test's floor is evaluated against (#13489).
406+
// `battery()` opens a battery; every assertion below is attributed to the one
407+
// most recently opened, so a section that stops running stops registering and
408+
// names ITSELF at the floor rather than going quiet.
409+
const batterySeen = new Map();
410+
let openBattery = null;
411+
const battery = (name) => {
412+
openBattery = name;
413+
};
414+
const registerCase = () => {
415+
const b = openBattery ?? UNATTRIBUTED_BATTERY;
416+
batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1);
417+
};
418+
battery('check-console-intercept-disarm self-test');
419+
// The thunk PR #15198 measured: it registers the case and then runs the
420+
// existing site VERBATIM, so no assertion condition is inverted or rewritten
421+
// and the sink keeps its own semantics. Registration happens whether or not
422+
// the site fires, which is what makes the count a floor on cases RUN rather
423+
// than a count of failures.
424+
const check = (fn) => {
425+
registerCase();
426+
fn();
427+
};
371428
const cases = [
372429
{
373430
name: 'disarmed package passes',
@@ -499,24 +556,75 @@ function selfTest() {
499556
) {
500557
problems.push(`expected ${testCase.expectVitestPackages} vitest package(s), got ${vitestPackages}`);
501558
}
502-
if (problems.length > 0) {
503-
failures += 1;
504-
console.error(`self-test FAIL: ${testCase.name}\n ${problems.join('\n ')}`);
505-
for (const f of findings) console.error(` finding: ${f.split('\n')[0]}`);
506-
}
559+
check(() => {
560+
if (problems.length > 0) {
561+
failures += 1;
562+
console.error(`self-test FAIL: ${testCase.name}\n ${problems.join('\n ')}`);
563+
for (const f of findings) console.error(` finding: ${f.split('\n')[0]}`);
564+
}
565+
});
507566
} finally {
508567
rmSync(caseDir, { recursive: true, force: true });
509568
}
510569
}
511570

512571
// Anti-vacuity over the REAL tree: the scan must see the real population.
513572
const real = scan(REPO_ROOT);
514-
if (real.vitestPackages < 60) {
515-
failures += 1;
516-
console.error(
517-
`self-test FAIL: real-tree scan sees only ${real.vitestPackages} vitest-running ` +
518-
`package(s); the population this gate was written against had 72. The ` +
519-
`workspace expansion has gone blind, which would pass every future arrival.`,
573+
check(() => {
574+
if (real.vitestPackages < 60) {
575+
failures += 1;
576+
console.error(
577+
`self-test FAIL: real-tree scan sees only ${real.vitestPackages} vitest-running ` +
578+
`package(s); the population this gate was written against had 72. The ` +
579+
`workspace expansion has gone blind, which would pass every future arrival.`,
580+
);
581+
}
582+
});
583+
584+
// ── The floor: every declared battery RAN, and ran its cases (#13489) ────
585+
//
586+
// Evaluated after every battery has had its chance and BEFORE the verdict, so
587+
// the success line below can only be printed by a run in which the set of
588+
// batteries that registered assertions EQUALS the set declared. A set
589+
// difference names WHICH battery stopped; a count says only that something did.
590+
// This file's sink is the `failures` counter, so the floor speaks its idiom: a
591+
// breach prints like any other case failure and reds through the existing
592+
// verdict below.
593+
const floorFailure = (message) => { console.error(message); failures += 1; };
594+
const declaredBatteries = Object.keys(SELF_TEST_BATTERIES);
595+
let floorBreached = false;
596+
if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) {
597+
floorBreached = true;
598+
floorFailure(
599+
`SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned `
600+
+ `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`,
601+
);
602+
}
603+
for (const [name, count] of batterySeen) {
604+
if (declaredBatteries.includes(name)) continue;
605+
floorBreached = true;
606+
floorFailure(
607+
`self-test battery "${name}" registered ${count} case(s) but is not declared in `
608+
+ 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.',
609+
);
610+
}
611+
for (const name of declaredBatteries) {
612+
const count = batterySeen.get(name) ?? 0;
613+
if (count >= SELF_TEST_BATTERIES[name]) continue;
614+
floorBreached = true;
615+
floorFailure(
616+
count === 0
617+
? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. `
618+
+ 'The verdict below would have claimed those cases hold.'
619+
: `self-test battery "${name}" registered ${count} case(s), below its pinned floor of `
620+
+ `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`,
621+
);
622+
}
623+
if (floorBreached) {
624+
floorFailure(
625+
'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the '
626+
+ 'number. Find what stopped registering (an early return, a deleted block, a guard that now '
627+
+ 'skips) and restore it.',
520628
);
521629
}
522630

scripts/check-declaration-mirrors.mjs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,63 @@ async function main() {
406406
// as one that passed (#13798).
407407
const SELF_TEST_VERDICT = 'check-declaration-mirrors self-test reached its verdict';
408408

409+
// ── The self-test's own battery roster and floor (#13489) ──────────────────
410+
//
411+
// `cases.filter((c) => !c.cond)` used to be this self-test's ONLY success
412+
// condition, so "every case
413+
// held" and "the cases never ran" printed the same line. Closed the way
414+
// PR #13487 validated on check-doc-authoring: what is pinned is the registered
415+
// NAMES, not a number. The floor requires the OPENED set to equal the DECLARED
416+
// set with each battery at or above its own count.
417+
//
418+
// This file declares ONE battery, opened at the top of the self-test body. Its
419+
// blocks are headed by unmarked prose comments, so it carries fewer than the two
420+
// named section banners the sectioning criterion needs, and ⛔ a comment is NOT
421+
// promoted to a section head — that is a judgement per comment this transplant
422+
// does not make. The hoisted single battery is the shape PR #14896, PR #15003
423+
// and PR #15217 landed for exactly this case.
424+
//
425+
// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3
426+
// keeps a total "right" the moment a sibling grows.
427+
//
428+
// The count is a FLOOR, not an equality — adding cases is ordinary work and must
429+
// not red. A battery BELOW its floor means cases stopped running; the remedy is
430+
// to find what stopped registering.
431+
const SELF_TEST_BATTERIES = Object.freeze({
432+
'check-declaration-mirrors self-test': 23,
433+
});
434+
435+
// DELETING an entry silences that battery's floor exactly as effectively as
436+
// zeroing it, so the roster's own size is pinned too.
437+
const SELF_TEST_BATTERY_FLOOR = 1;
438+
439+
// The key an assertion is filed under when no battery is open. It is not a
440+
// declared battery, so it reds by the same set difference rather than silently
441+
// inflating whichever battery happened to run last.
442+
const UNATTRIBUTED_BATTERY = '(no battery open)';
443+
409444
async function selfTest() {
445+
// The battery ledger this self-test's floor is evaluated against (#13489).
446+
// `battery()` opens a battery; every assertion below is attributed to the one
447+
// most recently opened, so a section that stops running stops registering and
448+
// names ITSELF at the floor rather than going quiet.
449+
const batterySeen = new Map();
450+
let openBattery = null;
451+
const battery = (name) => {
452+
openBattery = name;
453+
};
454+
const registerCase = () => {
455+
const b = openBattery ?? UNATTRIBUTED_BATTERY;
456+
batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1);
457+
};
458+
battery('check-declaration-mirrors self-test');
410459
const cases = [];
411-
const ok = (label, cond) => cases.push({ label, cond });
460+
// The concise arrow gains a BLOCK body so the case can be registered before
461+
// it is recorded; `cases.push` is unchanged, so no assertion is rewritten.
462+
const ok = (label, cond) => {
463+
registerCase();
464+
cases.push({ label, cond });
465+
};
412466
const dir = mkdtempSync(join(tmpdir(), 'os-decl-mirror-'));
413467
let seq = 0;
414468

@@ -549,6 +603,53 @@ async function selfTest() {
549603
mirrorFiles().every((f) => f.endsWith('.d.mts')),
550604
);
551605

606+
// ── The floor: every declared battery RAN, and ran its cases (#13489) ────
607+
//
608+
// Evaluated after every battery has had its chance and BEFORE the verdict, so
609+
// the success line below can only be printed by a run in which the set of
610+
// batteries that registered assertions EQUALS the set declared. A set
611+
// difference names WHICH battery stopped; a count says only that something did.
612+
// This file's sink IS the `cases` ledger, so the floor speaks its idiom: a
613+
// breach is recorded as a failing case and reds through the existing verdict
614+
// below. It bypasses `ok()` deliberately — a floor message is not a case.
615+
const floorFailure = (message) => { cases.push({ label: message, cond: false }); };
616+
const declaredBatteries = Object.keys(SELF_TEST_BATTERIES);
617+
let floorBreached = false;
618+
if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) {
619+
floorBreached = true;
620+
floorFailure(
621+
`SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned `
622+
+ `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`,
623+
);
624+
}
625+
for (const [name, count] of batterySeen) {
626+
if (declaredBatteries.includes(name)) continue;
627+
floorBreached = true;
628+
floorFailure(
629+
`self-test battery "${name}" registered ${count} case(s) but is not declared in `
630+
+ 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.',
631+
);
632+
}
633+
for (const name of declaredBatteries) {
634+
const count = batterySeen.get(name) ?? 0;
635+
if (count >= SELF_TEST_BATTERIES[name]) continue;
636+
floorBreached = true;
637+
floorFailure(
638+
count === 0
639+
? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. `
640+
+ 'The verdict below would have claimed those cases hold.'
641+
: `self-test battery "${name}" registered ${count} case(s), below its pinned floor of `
642+
+ `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`,
643+
);
644+
}
645+
if (floorBreached) {
646+
floorFailure(
647+
'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the '
648+
+ 'number. Find what stopped registering (an early return, a deleted block, a guard that now '
649+
+ 'skips) and restore it.',
650+
);
651+
}
652+
552653
const failed = cases.filter((c) => !c.cond);
553654
for (const c of cases) console.log(`${c.cond ? 'ok ' : 'FAIL'} ${c.label}`);
554655
if (failed.length) {

scripts/check-docs-single-h1.mjs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,64 @@ function main(argv) {
378378
// handshake is a flag rather than a returned sentinel.
379379
let selfTestReachedVerdict = false;
380380

381+
// ── The self-test's own battery roster and floor (#13489) ──────────────────
382+
//
383+
// `cases.filter((c) => !c.ok)` used to be this self-test's ONLY success
384+
// condition, so "every case
385+
// held" and "the cases never ran" printed the same line. Closed the way
386+
// PR #13487 validated on check-doc-authoring: what is pinned is the registered
387+
// NAMES, not a number. The floor requires the OPENED set to equal the DECLARED
388+
// set with each battery at or above its own count.
389+
//
390+
// This file declares ONE battery, opened at the top of the self-test body. It
391+
// carries exactly ONE named section banner (`The carve-out machinery, driven by
392+
// a SYNTHETIC exclusion`), which is fewer than the two the sectioning criterion
393+
// needs, and ⛔ a single banner is NOT split on — sectioning on it would leave
394+
// every case above it in an unnamed remainder battery this transplant would
395+
// have to invent a name for. The hoisted single battery is the shape PR #14896,
396+
// PR #15003 and PR #15217 landed for exactly this case.
397+
//
398+
// ⛔ A pinned TOTAL is not the repair: a battery dropping from 9 cases to 3
399+
// keeps a total "right" the moment a sibling grows.
400+
//
401+
// The count is a FLOOR, not an equality — adding cases is ordinary work and must
402+
// not red. A battery BELOW its floor means cases stopped running; the remedy is
403+
// to find what stopped registering.
404+
const SELF_TEST_BATTERIES = Object.freeze({
405+
'check-docs-single-h1 self-test': 20,
406+
});
407+
408+
// DELETING an entry silences that battery's floor exactly as effectively as
409+
// zeroing it, so the roster's own size is pinned too.
410+
const SELF_TEST_BATTERY_FLOOR = 1;
411+
412+
// The key an assertion is filed under when no battery is open. It is not a
413+
// declared battery, so it reds by the same set difference rather than silently
414+
// inflating whichever battery happened to run last.
415+
const UNATTRIBUTED_BATTERY = '(no battery open)';
416+
381417
export function selfTest() {
418+
// The battery ledger this self-test's floor is evaluated against (#13489).
419+
// `battery()` opens a battery; every assertion below is attributed to the one
420+
// most recently opened, so a section that stops running stops registering and
421+
// names ITSELF at the floor rather than going quiet.
422+
const batterySeen = new Map();
423+
let openBattery = null;
424+
const battery = (name) => {
425+
openBattery = name;
426+
};
427+
const registerCase = () => {
428+
const b = openBattery ?? UNATTRIBUTED_BATTERY;
429+
batterySeen.set(b, (batterySeen.get(b) ?? 0) + 1);
430+
};
431+
battery('check-docs-single-h1 self-test');
382432
const cases = [];
383-
const t = (name, ok, detail) => cases.push({ name, ok: Boolean(ok), detail });
433+
// The concise arrow gains a BLOCK body so the case can be registered before
434+
// it is recorded; `cases.push` is unchanged, so no assertion is rewritten.
435+
const t = (name, ok, detail) => {
436+
registerCase();
437+
cases.push({ name, ok: Boolean(ok), detail });
438+
};
384439

385440
const dir = mkdtempSync(join(tmpdir(), 'docs-single-h1-'));
386441
try {
@@ -493,6 +548,53 @@ export function selfTest() {
493548
rmSync(dir, { recursive: true, force: true });
494549
}
495550

551+
// ── The floor: every declared battery RAN, and ran its cases (#13489) ────
552+
//
553+
// Evaluated after every battery has had its chance and BEFORE the verdict, so
554+
// the success line below can only be printed by a run in which the set of
555+
// batteries that registered assertions EQUALS the set declared. A set
556+
// difference names WHICH battery stopped; a count says only that something did.
557+
// This file's sink IS the `cases` ledger, so the floor speaks its idiom: a
558+
// breach is recorded as a failing case and reds through the existing verdict
559+
// below. It bypasses `t()` deliberately — a floor message is not a case.
560+
const floorFailure = (message) => { cases.push({ name: message, ok: false }); };
561+
const declaredBatteries = Object.keys(SELF_TEST_BATTERIES);
562+
let floorBreached = false;
563+
if (declaredBatteries.length < SELF_TEST_BATTERY_FLOOR) {
564+
floorBreached = true;
565+
floorFailure(
566+
`SELF_TEST_BATTERIES declares ${declaredBatteries.length} batteries, below the pinned `
567+
+ `${SELF_TEST_BATTERY_FLOOR} — a battery deleted from the roster takes its own floor with it.`,
568+
);
569+
}
570+
for (const [name, count] of batterySeen) {
571+
if (declaredBatteries.includes(name)) continue;
572+
floorBreached = true;
573+
floorFailure(
574+
`self-test battery "${name}" registered ${count} case(s) but is not declared in `
575+
+ 'SELF_TEST_BATTERIES — an assertion attributed to no declared battery is one nothing floors.',
576+
);
577+
}
578+
for (const name of declaredBatteries) {
579+
const count = batterySeen.get(name) ?? 0;
580+
if (count >= SELF_TEST_BATTERIES[name]) continue;
581+
floorBreached = true;
582+
floorFailure(
583+
count === 0
584+
? `self-test battery "${name}" DID NOT RUN — 0 cases registered, ${SELF_TEST_BATTERIES[name]} pinned. `
585+
+ 'The verdict below would have claimed those cases hold.'
586+
: `self-test battery "${name}" registered ${count} case(s), below its pinned floor of `
587+
+ `${SELF_TEST_BATTERIES[name]} — cases that used to run no longer do.`,
588+
);
589+
}
590+
if (floorBreached) {
591+
floorFailure(
592+
'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the '
593+
+ 'number. Find what stopped registering (an early return, a deleted block, a guard that now '
594+
+ 'skips) and restore it.',
595+
);
596+
}
597+
496598
const failed = cases.filter((c) => !c.ok);
497599
for (const c of failed) console.error(` ✗ ${c.name}${c.detail ? ` — ${c.detail}` : ''}`);
498600
if (failed.length) {

0 commit comments

Comments
 (0)