Repository navigation
Description
Plan 001: Include workspace packages in changed-line coverage
Executor instructions: Follow this plan step by step. Run every
verification command and confirm the expected result before moving to the
next step. If anything in the "STOP conditions" section occurs, stop and
report — do not improvise. A reviewer maintains the plan index for this work;
do not create or editplans/README.md.Drift check (run first):
git diff --stat 13bc70f24..HEAD -- scripts/coverage-changed/model.ts scripts/coverage-changed/model.test.ts scripts/coverage-changed/run.test.ts
If any in-scope file changed since this plan was written, compare the
"Current state" excerpts against the live code before proceeding; on a
mismatch, treat it as a STOP condition.
Status
- Priority: P1
- Effort: S
- Risk: LOW
- Depends on: none
- Category: tests
- Planned at: commit
13bc70f24, 2026-08-08
Why this matters
The full Vitest coverage run instruments both root src/ code and workspace
package source, but the changed-line gate rejects every packages/*/src/**
path before consulting LCOV. A pull request can therefore add uncovered lines
to contracts, selectors, kernel, or provider packages while the 70% changed-line
gate reports no package denominator. This plan makes the package source universe
match the coverage configuration and pins the behavior at both the pure model
and executable gate entrypoint.
Current state
-
vitest.config.tsowns the full coverage include universe. It is an
authoritative reference, but it is out of scope for this fix:// vitest.config.ts:119-127 coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov', 'json-summary'], thresholds: { statements: 78, lines: 80, }, include: ['src/**/*.ts', 'packages/*/src/**/*.ts'],
-
scripts/coverage-changed/model.tsdescribes an LCOV-driven universe, but
its prefilter only recognizes root source:// scripts/coverage-changed/model.ts:6-10 // The coverable universe is the lcov report itself, not a second copy of // vitest's exclude globs: coverage runs with `all` on, so every includable // `src/**/*.ts` file appears in lcov even when untested. A changed includable // file ABSENT from lcov was dropped by an exclude glob — reported (non-gating) // so exclusions cannot silently absorb new logic. // scripts/coverage-changed/model.ts:152-160 // Vitest coverage `include` is `src/**/*.ts`. Test files are excluded there and // carry no product logic, so they never count toward the gate or the excluded // tally (which exists to surface hidden logic, not test code). export function isTestFile(path: string): boolean { return /\.test\.ts$/.test(path) || /(^|\/)__tests__\//.test(path); } export function isIncludableSource(path: string): boolean { return /^src\/.*\.ts$/.test(path) && !isTestFile(path); }
-
computeChangedCoveragedrops a diff before looking it up in LCOV:// scripts/coverage-changed/model.ts:289-299 for (const diff of input.diffs) { if (diff.deleted || diff.added.length === 0 || !isIncludableSource(diff.path)) continue; const cov = input.coverage.get(diff.path); if (!cov) { // Includable source absent from the all-files lcov report: an exclude glob // dropped it. Count its code-like added lines so the exclusion is visible.
-
The classification test claims parity with the coverage glob without a
package case:// scripts/coverage-changed/model.test.ts:86-94 test('source/test classification matches the coverage include glob', () => { assert.equal(isIncludableSource('src/core/x.ts'), true); assert.equal(isIncludableSource('src/core/x.test.ts'), false); assert.equal(isIncludableSource('src/core/__tests__/x.ts'), false); assert.equal(isIncludableSource('scripts/y.ts'), false); assert.equal(isIncludableSource('src/core/x.tsx'), false); assert.equal(isTestFile('src/a.test.ts'), true); assert.equal(isTestFile('src/__tests__/a.ts'), true); assert.equal(isTestFile('src/a.ts'), false); });
-
scripts/coverage-changed/run.test.ts:75-84is the entrypoint regression
pattern to copy: create a temporary git change, provide LCOV, callrun, and
assert a failing code plus the uncovered path/line. -
Workspace package ownership is already a first-class concept in
scripts/check-affected/model.ts:229-255; match its path shape
packages/<package>/src/**rather than introducing another package layout. -
Tests in this directory use
node:testplusnode:assert/strict. Keep that
style; do not convert them to Vitest APIs.
Commands you will need
| Purpose | Command | Expected on success |
|---|---|---|
| Focused gate tests | pnpm check:coverage-changed:test |
exit 0; all changed-coverage model and entrypoint tests pass |
| Static checks | pnpm check:quick |
exit 0; lint and TypeScript report no errors |
| Affected gates | pnpm check:affected --run |
exit 0; selected local checks pass, with CI-owned checks only reported |
| Working-tree scope | git status --short |
only the three in-scope files are changed |
Dependencies must already be installed. Do not run a different package manager
or create/update package-lock.json.
Scope
In scope (the only files you should modify):
scripts/coverage-changed/model.tsscripts/coverage-changed/model.test.tsscripts/coverage-changed/run.test.ts
Out of scope (do NOT touch, even though they look related):
vitest.config.ts— its include list is already correct..github/workflows/ci.yml— CI already invokes the model test, coverage
producer, and changed-line gate in the right order.scripts/check-affected/**— workspace packages are already classified there.- Coverage thresholds, waiver behavior, branch reporting, and output wording.
- Product source under
src/**orpackages/**. - Documentation and skills — this is an internal gate-correctness fix with no
user-facing command behavior. plans/README.mdand every other plan file.
Git workflow
- Branch:
advisor/001-workspace-package-changed-coverage - Use one logical commit after the red/green proof is complete:
fix(test): include workspace packages in changed-line coverage - Do NOT commit the intentionally red intermediate state.
- Do NOT push or open a pull request unless the operator explicitly instructs it.
Steps
Step 1: Add regressions and prove them red on the current model
In scripts/coverage-changed/model.test.ts:
- Extend
source/test classification matches the coverage include globwith:packages/contracts/src/x.ts→true;packages/contracts/src/x.test.ts→false;packages/contracts/src/__tests__/x.ts→false;- a non-source package path such as
packages/contracts/test/x.ts→false.
- Add a model-level regression whose LCOV contains
SF:packages/contracts/src/feature.ts, one coveredDAline, and one
uncoveredDAline. Pass a matching package diff to
computeChangedCoverageand assert:totalLines === 2;coveredLines === 1;passed === false;- the offender path is
packages/contracts/src/feature.tsand its uncovered
line is reported.
In scripts/coverage-changed/run.test.ts, copy the structure of
fails when a changed source line is uncovered and names that line at lines
75-84, but write and commit
packages/contracts/src/feature.ts in the temporary repo. Supply matching LCOV
with one uncovered executable line and assert run(['--base', 'main'], repo)
returns 1, prints FAIL, and names the package path and line.
Do not weaken existing root-source, docs-only, exclusion, waiver, or branch
assertions.
Verify red: pnpm check:coverage-changed:test → exit nonzero. The new
classification assertion and/or package-entrypoint regression must fail because
the current model reports the package change as non-includable or 0/0. Record
the failing test names in the implementation handoff. If the new tests pass
before production changes, STOP: the regression is not exercising the reported
gap.
Step 2: Admit workspace package source without admitting package tests
Update isIncludableSource in scripts/coverage-changed/model.ts so it accepts
exactly these TypeScript source roots:
src/**/*.tspackages/<one-package-segment>/src/**/*.ts
Continue to exclude .test.ts, every __tests__/ directory, .tsx, scripts,
package-level test/, and unrelated paths. Prefer one named source-root matcher
plus the existing isTestFile predicate; do not copy Vitest's full exclusion
list or add package names one by one.
Update the comments at lines 6-10 and 152-154 so they truthfully name both
coverage roots. Preserve all scoring, waiver, exclusion-tally, branch, and
reporting behavior.
Verify green: pnpm check:coverage-changed:test → exit 0. The new package
model and entrypoint tests pass, and all existing tests remain green.
Step 3: Run the repository gates selected for tooling changes
Run the focused suite once more, then the static and affected gates. Do not
paper over a gate by adding an allowlist or coverage waiver.
Verify:
pnpm check:coverage-changed:test && pnpm check:quick && pnpm check:affected --runExpected: exit 0. The affected selector may fail open because scripts/** is
workflow/tooling; all checks it elects to run must pass, and CI-only/native
checks may be reported rather than executed.
Step 4: Tighten comments and scope before handoff
Confirm the old root-only coverage statements are gone and no unrelated files
changed.
Verify:
rg -n 'include` is `src/\*\*/\*\.ts|every includable `src/\*\*/\*\.ts' scripts/coverage-changed/model.ts
git status --shortExpected: the rg command prints no stale root-only claim; git status --short
lists only the three in-scope files.
Test plan
- Extend the existing classification matrix in
scripts/coverage-changed/model.test.tswith package source and package-test
cases. - Add one pure-model scoring test proving a package source diff contributes to
the denominator and can fail below 70%. - Add one
run.test.tstemporary-repository test proving the executable gate
returns failure and renders the uncovered package path/line. - Keep the existing root-source regression as a control.
- Prove the new tests red before changing
model.ts; quote the failing test
names in the handoff. - Verification:
pnpm check:coverage-changed:test→ all tests pass after the
fix.
Done criteria
- The planned-at excerpts still matched before editing.
- The new package regressions failed on the pre-fix model and that failure
was recorded. -
isIncludableSource('packages/contracts/src/x.ts')is true. - Package
.test.ts,__tests__,.tsx, and package-leveltest/paths
remain excluded. - An uncovered package source line makes both the pure model and
run
entrypoint report failure. - Root
src/scoring, waivers, excluded-line reporting, and branch reporting
are unchanged. -
pnpm check:coverage-changed:testexits 0. -
pnpm check:quickexits 0. -
pnpm check:affected --runexits 0 for local gates. -
git status --shortlists only the three in-scope files. - No docs or skills were changed because the public command surface and
workflow guidance did not change.
STOP conditions
Stop and report back; do not improvise if:
- Any in-scope current-state excerpt has drifted since
13bc70f24. vitest.config.tsno longer includespackages/*/src/**/*.ts.- The coverage producer does not emit normalized
packages/<name>/src/<file>.tsLCOV paths; report the observed shape instead
of changing normalization speculatively. - The red test passes before
model.tschanges, or fails for missing fixtures
rather than the root-only classifier. - Supporting package coverage requires changing thresholds, CI workflow order,
waiver semantics, or Vitest configuration. - A verification command fails twice after one reasonable correction.
- Any fix requires touching a file outside the in-scope list.
Maintenance notes
- Reviewers should check that the source-root predicate is package-generic and
still excludes tests; an enumerated package list will drift as workspaces are
added. - Future coverage-root changes must update the classifier matrix and executable
package-style regression in the same change. - The full global thresholds remain GitHub CI's authority. This plan only closes
the changed-line prefilter gap and must not change the 70% threshold or waiver
policy.
Activity