Run the test suite against TypeScript sources behind an uncheatable coverage gate
Why
Coverage was unmeasured here. The suite imported the compiled output from dist/, so the only obtainable numbers pointed at generated JavaScript offsets an author cannot act on, and no threshold was wired into any gate.
What changed
- Tests import the TypeScript source directly (
../index.ts) instead of../dist/index.js, so the coverage report lands on editable source lines. Node strips types natively from 22.18.0, the floor this package already declares inengines— no new tooling. tsconfig.jsonenablesrewriteRelativeImportExtensions+allowImportingTsExtensions. This package has no relative source imports today, so the publisheddist/artifact is byte-identical; the flag keeps the door open for future multi-file sources.scripts/coverage-gate.ts(copied verbatim frompm-changelog) enforces per-dimension thresholds and reconciles the reported file list against the configured source list. That second check is why it is a script, not a bare flag: Node omits files that never load from its coverage report entirely rather than reporting them at zero, so a threshold alone is satisfiable by narrowing what the suite imports.release:checkruns the gate in place of a bare test run; CI runs it on a Node 22 + 26 matrix.
Measured baseline (single source file index.ts)
| dimension | measured | threshold (floor) |
|---|---|---|
| lines | 70.53 | 70 |
| branches | 80.42 | 80 |
| functions | 80.43 | 80 |
Thresholds are pinned at the measured floor so the number can only ratchet up.
Negative control
A throwaway __probe.ts source was added to coverageGate.sources. No test imported it, so the gate exited 1 and named __probe.ts as never loaded. After removing the probe the gate exited 0 again. The presence check is not vacuous.
pm item
Resolves pm-linear-v618 after merge.
Summary by Sourcery
Enforce a coverage gate over the TypeScript sources by running Node’s built-in test coverage against .ts test files and wiring the gate into local release checks and CI.
New Features:
- Introduce a reusable coverage gate script that runs the Node test runner with coverage against TypeScript sources and enforces per-dimension thresholds plus source presence checks.
Enhancements:
- Switch tests to import the TypeScript entrypoint instead of compiled JavaScript so coverage maps directly to authored source.
- Add coverageGate configuration to package.json to declare covered sources, test entry patterns, thresholds, and ignores.
- Enable rewriteRelativeImportExtensions and allowImportingTsExtensions in tsconfig to support direct .ts imports while keeping the emitted dist output stable.
Build:
- Add a coverage npm script that builds the project and runs the coverage gate instead of a plain test run.
- Update the release:check script to run the coverage gate in place of the bare test suite.
CI:
- Update the CI workflow to run the coverage-gated test command on a Node 22 and 26 matrix instead of a simple Node 22 test run.
Chores:
- Register the associated pm-linear item files under .agents/pm to track this change.
Summary by cubic
Run tests against the TypeScript source and enforce a coverage gate that also fails when untested files don’t appear in the report. Uses a dynamic source walk, pins TZ=UTC, and runs in CI on Node 22 and 26; satisfies Linear pm-linear-v618.
-
New Features
- Tests import
../index.tsso coverage maps to authored lines; Node 22+ executes.ts. - Gate enforces thresholds (lines 70, branches 80, functions 80) and reconciles the report with a directory walk from
coverageGate.sources; only required files are included. npm run coveragebuilds, typechecks tests and the gate, then runs the gate; CI runs this job;coverage/is gitignored;tsconfigenablesrewriteRelativeImportExtensionsandallowImportingTsExtensions; tests arenoEmitwith repo-rootrootDir.- Baseline: lines 70.53, branches 80.42, functions 80.43.
- Tests import
-
Bug Fixes
- Propagate the runner’s exit status before reading the coverage report, so real test failures show first.
- Normalize
lcovSF:paths to repo-relative POSIX to avoid false “never loaded” on Windows. - Clear error when a
coverageGate.sourcesentry does not exist. - Delete previous
coverage/lcov.infobefore the run to prevent passing on stale data. - Validate
coverageGate.ignoreentries are type-only against emitted JS (stripping block comments); resolveoutDir/rootDirviatsc --showConfigand fail closed if it cannot be resolved; reject non-TS or.d.tsentries incoverageGate.sources.