Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -304,6 +304,30 @@ const SOURCE_FILE = /\.([cm]?ts|tsx)$/;
const PIN_DIRECTIVE = /^[ \t]*(?:\/\/|\/\*|\*)[ \t]*@ts-expect-error\b/m;
const PIN_ISSUE = 'https://github.com/objectstack-ai/objectstack/issues/5286';

// A path in the root program whose edits move the `@objectstack/spec-monorepo`
// count below, declared as a bare, whole-literal path so the dispatch
// derivation can read it. It accounts for 29 of that entry's 80 -- the largest
// single contributor, and the one an ordinary card actually edits. The note on
// that entry carries the accounting and references this constant instead of
// spelling the path inside its sentence.
//
// Why the SHAPE and not just the mention: scripts/pm/dispatch-gates.mjs scans
// each gate's own module body for path literals and accepts one only when the
// WHOLE string is path-shaped. Named mid-sentence, this path was discarded, and
// the family then scored neither matched nor undetermined but SILENT -- printed
// nowhere at all. A card editing that file was told no check family names its
// paths, which reads as "no gates apply" when it means "no gate names this
// path", and a dev who trusted it would skip the gate most likely to redden the
// diff (measured on such a card: this gate stayed green only because the change
// was designed around the coupling).
//
// This is per-coupling manual upkeep, deliberately. The root program is
// everything outside packages/apps/examples, so no list here can ever be
// complete -- add a constant when a coupling has actually been measured, the way
// this one was. Deleting this one does not go quiet: dispatch-gates' own
// self-test pins that a card editing this path derives check:type-check-coverage.
const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';

// Package name -> { errors, note? }. `errors` is the raw `tsc --noEmit` count
// measured per package. Seeded on main @ b07d829 (2026-07-31), re-measured
// after the NodeNext repair below, and re-measured WHOLESALE on main @ 5ab08428
Expand DownExpand Up@@ -424,7 +448,7 @@ const DEBT = {
+ 'and TS2550); noise 8 (TS7006 x7, TS6133). Re-measured 80 at 5ab08428, up from 50. This entry '
+ 'drifts differently from a package: the root program is `scripts/` and the top-level configs '
+ '(everything outside packages/apps/examples), so it grows whenever the repo gains a script -- '
+ 'scripts/check-test-typecheck.mts alone accounts for 29 of the 80, and the analytics-reconcile '
+ ROOT_PROGRAM_COUPLED_SCRIPT + ' alone accounts for 29 of the 80, and the analytics-reconcile '
+ 'tree for 32. Almost all of it is one missing `types:["node"]`, not 80 defects. One wrinkle to '
+ 'know before reading this number as "the root scripts": `exclude` only drops files from the '
+ 'initial walk, so example sources IMPORTED by a script are still pulled into the program -- 4 of '
Expand Down
37 changes: 37 additions & 0 deletions scripts/pm/dispatch-gates.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1196,6 +1196,43 @@ function selfTest() {
t('this tool still hints the workflow directory it reads', covers(ownHints, '.github/workflows/lint.yml'));
t('this tool no longer hints the spec paths its own fixtures name', !covers(ownHints, 'packages/spec/src/data/filter.zod.ts'));

// ── The one DECLARED coupling (#8551) ─────────────────────────────────────
//
// The narrowing above is about gates that MENTION a path without reading it.
// This is its mirror image: a gate that really does move with a path it never
// opens. The type-check ledgers ratchet a count for the workspace root, whose
// program is the scripts tree, and one script accounts for 29 of that entry's
// 80 errors — so editing it moves a number this farm holds. The coupling was
// written down all along, inside the ledger note's prose, where whole-literal
// extraction discards it: the family then scored `silent` — neither matched
// nor undetermined, printed nowhere — and a card editing that script was told
// no family names its paths.
//
// The remedy is per-coupling and manual (a bare, whole-literal constant in
// the gate's own module body), which is exactly the kind of declaration that
// rots quietly. So it is pinned LIVE, against both real files: delete the
// constant and this gate reddens instead of the silence coming back. If the
// ledger's coupling genuinely ends, delete the constant AND these cases in
// the same change — the evidence goes with the claim, never ahead of it.
//
// This does NOT retire the test-file entry in CHANGE_KIND_GATES, whose
// deletion criterion is a discoverable literal for that KIND: the constant
// names one script carrying no `.test.` infix, while that entry answers for
// every test file in the tree.
const coverageHints = readHints('scripts/check-type-check-coverage.mjs');
t(
'the type-check ledger gate declares the root-program script whose errors it ratchets',
covers(coverageHints, 'scripts/check-test-typecheck.mts'),
);
const coupledVerdict = classifyEntry(
{ files: ['scripts/check-type-check-coverage.mjs'], hints: coverageHints },
['scripts/check-test-typecheck.mts'],
);
t(
'so a card editing that script is MATCHED through that constant, not dropped as silent',
coupledVerdict.verdict === 'matched' && coupledVerdict.hits[0]?.hint === 'scripts/check-test-typecheck.mts',
);

// ── A family's OWN script files as match keys (#8509) ─────────────────────
//
// Both directions are the product, and both are pinned: a card editing a
Expand Down
Loading