fix(scripts): ask check:type-check-coverage's four per-package clauses of the ROOT workspace package - #15516
Conversation
…package
`check-type-check-coverage.mjs` built its subject list two ways and only one
of them was walked: `workspacePackages()` ran `testCoverage(dir, scripts)` over
the 79 enumerated dirs, while `observed()` built the root from its manifest
alone -- `{ name, scripts }`, with none of the four observation fields. The
per-package invariants are asked inside `for (const pkg of packages)`, so
TESTS_COVERED, SOURCES_COVERED, PINS_CHECKED and GENERATED_COVERED were asked
of 79 packages and never of the root, while the file's own header said the root
is counted "like any other package's".
Measured on main @ 5023630 before deciding: the root walk reaches 106
directories in 11ms, 0 test files, 0 `@ts-expect-error` pins and 14 non-test
source files, and 0 of them sit outside the root program -- so the root can
join the population with zero new findings, and the repair is a guard rather
than a burn-down.
- `perPackageClauses(pkg, state)` extracts the four clauses out of the loop, so
the loop is no longer what defines their population; `evaluate` asks it of
every package and once of the root.
- The root is walked by `testCoverage('.')`, whose `walk` already skips any
subdirectory carrying a `package.json` at depth > 0, so the 79 are not
re-walked and nothing is billed twice.
- The clauses are decided against the root's COVERAGE script (`typecheck:root`)
rather than its `typecheck` aggregator, carried as `coverageScript` /
`coverageScriptName`. Anchoring PINS_CHECKED and GENERATED_COVERED on
`turbo run typecheck` would manufacture findings instead of finding them, and
a finding now quotes the slot its reader has to edit.
- The RECONCILED sweeps and the three layer figures read the same subject list,
so a root ledger row cannot read as stale the moment it is written.
- `SCOPED_CLAUSES.asked` reads `coverageScript`: the root's manifest `typecheck`
is always defined, so the old predicate would have reported the root as ASKED
on a run where the clause was not asked of it.
- The `SCOPE_LINE_BOUNDARY` sentence naming this card is replaced by the
population sentence -- it stops being true -- and the line's count is now 80,
which is why the sentence stays printed: the headline counts 79.
Self-test: five new cases (one red per clause planted on the root, plus the
RECONCILED half) and one scope case pinning that a root with no `typecheck:root`
is named in the skipped list. 55 semantic + 75 observation cases; battery roster
and floor moved with them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…pe-check-coverage-root-package
|
ACCEPT — PM seat Reviewed against the three-dot diff Generated by Claude Code |
Fixes #15483
check:type-check-coveragebuilt its subject list two ways and only one of them was walked:workspacePackages()walks the 79 dirsworkspacePackageDirs(ROOT)returns and callstestCoverage(dir, scripts)on each — the walk that produceshiddenTests,pinFiles,uncheckedSourcesandgeneratedIncludes.observed()built the workspace root as{ name: rootManifest.name, scripts: rootManifest.scripts ?? {} }— a manifest read, with none of those four fields.evaluate()asks the per-package invariants insidefor (const pkg of packages), and the root is not in that array. So TESTS_COVERED, SOURCES_COVERED, PINS_CHECKED and GENERATED_COVERED were asked of 79 packages and never of the root, while the file's own header said the root is counted "like any other package's". Fourth axis of the same gate after #14630, #14386 and #14918 — each of the four was a clause skipped for a population with the run saying so nowhere.Route (a): the root joins the population — measured first
The card is explicit that this is LATENT, so the walk was measured on
origin/mainbefore choosing a route.testCoverage('.')over the real tree:package.jsonat depth > 0, so the 79 are not re-walked)@ts-expect-errorpin filesRoot
tsconfig.jsondeclares noinclude, soreadTsconfiggives itroots: ['']— every file it does notexclude(node_modules,packages,apps,examples) is in the program. The root can therefore be walked like any other package with zero new findings, which is route (a): no ledger row added, no exclusion widened,UNCHECKED_SOURCE_DEBTstill empty and still closed. What was missing is the guard — for the day that config grows aninclude, or a roottsconfig.build.jsonexcludes a test glob, or the repo gains its first root-level test file carrying a pin.The repair
perPackageClauses(pkg, state)lifts the four clauses out ofevaluate's loop, so the loop is no longer what defines their population.evaluateasks it of every enumerated package and once of the root.observed()walks the root:...testCoverage('.', rootCoverageScripts), plusdir: '.'.PINS_CHECKEDanchors onconfigsNamedByTypecheck, and the root's coverage runs throughtypecheck:rootwhile itstypecheckslot isturbo run typecheck. That script is handed totestCoverageunder thetypecheckkey and carried back ascoverageScript;coverageScriptName(typecheck:root) is interpolated into the findings, so a remedy quotes the slot its reader has to go and edit instead of sending them to the aggregator. For the 79 both aretypecheckand every message is byte-identical — the 50 pre-existing semantic cases pass unchanged.clauseSubjects = [...packages, root]). A sweep still running over the enumerated packages alone would report a rootPHANTOM_PIN_DEBTrow as stale the moment it was written.SCOPED_CLAUSES.askedreadscoverageScript, notpkg.scripts?.typecheck: the root's manifesttypecheckis always defined, so the old predicate would report the root as ASKED on a run where the clause was not asked of it — a scope line lying about scope.SCOPE_LINE_BOUNDARYis gone, as the card requires: it named [finding] check:type-check-coverage never asks TESTS_COVERED / SOURCES_COVERED / PINS_CHECKED / GENERATED_COVERED of the ROOT workspace package — its header says the root is included "like any other package's" #15483 and stops being true. It is replaced by a population sentence, still printed, because the headline counts the enumerated packages (79) and this line now counts the clause population (80) — a reader comparing the two numbers needs the sentence that says where the extra subject comes from.The line, before and after
The root appears in the
GENERATED_COVEREDskipped list because it declares notypecheck:roottoday — the same empty, not hidden population the other four sit in, now said out loud instead of being unsayable.Self-test
Five new semantic cases and one new observation case, each pinned by an ablation on this branch's committed tree (mutate, confirm the mutation on disk by counting the removed and injected text, run, restore, verify the file's blob hash equals its
HEADblob):problems.push(...perPackageClauses(root, state))got []phantomSeenback overpackagesalonea PHANTOM_PIN_DEBT row for a ROOT pin is covered, not reported stale— the row reads as staleSCOPED_CLAUSES.askedback onpkg.scripts?.typecheckthe ROOT is in this population, and one with no typecheck:root is named like anyone else—expected [{skipped:["root"]}], got []Each of the four clause cases plants its finding on the root and nothing else (
packages: []), because the defect was not a wrong answer — it was four questions never asked, which no amount of green over the 79 could distinguish from four answers of "nothing here". Each also pins the script name in the remedy, so a root reader is not sent to edit the workspace aggregator.SELF_TEST_BATTERIESgained the six rows at floor 1 each (registerCase(c.label)stays the first statement of the driving loop; the table row is the battery),observation caseswas re-floored 63 → 64,SELF_TEST_BATTERY_FLOOR56 → 61, and the two prose figures quoting them moved with them (63 → 64, 74 → 75).Verification
Every command below ran on the final commit
6be83bdc5(git rev-parse --short HEAD, tree clean), with each exit code captured before any pipe.On
origin/main@615fac3a0, for comparison (a detached worktree at that commit): the same two commands print50 semantic case(s) + 74 observation case(s)and the pre-repair scope line —asked of all 79with the⚠️ ... the ROOT package is not one of them ... (#15483)boundary. That is the premise, still true on current main at dispatch time.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 26 commands from the real change set (one path:scripts/check-type-check-coverage.mjs, three-dot against merge base615fac3a0). All 26 were run; 25 EXIT=0.pnpm check:type-check-debt, is NOT MEASURED — neither a pass nor a red:EXIT=3,check-type-check-coverage: PREREQUISITE NOT MET — --re-measure cannot run: 33 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk. A property of a fresh worktree, not of this diff:origin/main's own copy refuses identically,EXIT=3, same message and same count of 33. CI builds the closure before that step.Repo-wide
pnpm lintwas run in full rather than narrowed, so no scoping argument is owed. All figures were measured on a shared box; the lock excludes other locked runs and nothing else.skip-changeset: the diff is one file underscripts/, publishes nothing from any package.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code