Skip to content

feat(repo): publish a dry-run-only run's coverage into the incremental file - #227

Merged
kiro-systemf[bot] merged 4 commits into
mainfrom
stryker/dry-run-only-coverage
Oct 7, 2026
Merged

kiro-systemf[bot] merged 4 commits into
mainfrom
stryker/dry-run-only-coverage

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This brings #155's engine change to main. #155 (eafe8f3a9) merged into its stack's base branch, so the change never reached main or a release.

With incremental mode on, a --dryRunOnly run now writes its dry-run coverage into the incremental file and keeps the verdicts already there. dryRunOnly no longer feeds the run-inputs digest. As a result, a later run that reads that file through incrementalSources reuses the coverage and skips its own dry run. On 17.0.2, --dryRunOnly writes nothing, so a run done once to share its dry run has nothing to share.

Scope

Only the engine part is ported. The workflow and scripts/ parts of #155 are dropped: #191 deleted scripts/, and .github/workflows/ is read-only for agents. The base.js hunk is also dropped; it only added the sarif reporter for CI.

The engine hunks were re-fitted to main: main renamed writeAtomic to writeFileAtomic, and the imports merged with main's mutant-cost imports.

Validation

  • Failing before: incremental-reuse.integration.test.ts › "A dry-run-only preflight publishes its coverage, and a run reading it through incrementalSources skips its own dry run". Run against origin/main (db180fc3e), preflightedDryRunSpawns was expected to be 0 and was 2.
  • Regression guard: "A dry-run-only run into an existing incremental file keeps the verdicts it holds" also passes on main, because there a dry-run-only run writes nothing. It covers the new write path.
  • Verified in the parent session: the integration file passes 16/16, and the changeset check exits 0.
  • Worker gates: format, lint, typecheck and check:ci exit 0. The full stryker-js suite passes: 233 files, 1257 tests.
  • Verdict-Semantics: unchanged. A dry run produces no verdicts, so taking dryRunOnly out of the run-inputs digest only lets a dry-run-only run and later runs share cached coverage.
  • API report: etc/stryker-js.api.md keeps main's Node_2 as Node rendering, which CI's api:check accepts.

Nothing was run with stryker.

Cherry-picked from eafe8f3. Ships with the CompileError verdict reuse PR in the next release.

…l file (#155)

A dry-run-only run with incremental mode on now writes its dry-run coverage into the incremental file, keeping any verdicts already there, so a later run that reads the file through incrementalSources skips its own dry run. The run-inputs digest no longer covers dryRunOnly, so a dry-run-only preflight and the runs that reuse its coverage share one digest; existing caches are refused once with runInputsChanged.

(cherry picked from eafe8f3)

Verdict-Semantics: unchanged
@systemfsoftware-maker systemfsoftware-maker changed the title stryker/dry run only coverage feat(repo): publish a dry-run-only run's coverage into the incremental file Oct 7, 2026
Local builds render the Plugin namespace export as Node and CI renders Node_2 as Node (docs/solutions/api-extractor-node-alias-nondeterminism.md). #227 committed the local rendering and api:check failed in run 37626449206; main's rendering is the one CI accepts

Verdict-Semantics: unchanged
… CI and local builds

Run 37626449206 failed api:check on a report regenerated locally; run 37649609332 passed with main's rendering

Verdict-Semantics: unchanged
…ools now decode

pnpm-release-management main renamed versioning.strategy pnpm to changesets (#9, #33), and both the Changeset Check and Release callers run its main, so every pull request here failed with 'cannot parse config release.jsonc' (run 37657899053)

Verdict-Semantics: unchanged

@kiro-systemf kiro-systemf Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dry-run-only run publishes its coverage (carrier for stranded #155). 8/8 green.

@kiro-systemf
kiro-systemf Bot merged commit 65155c7 into main Oct 7, 2026
8 checks passed
@kiro-systemf
kiro-systemf Bot deleted the stryker/dry-run-only-coverage branch October 7, 2026 18:07
kiro-systemf Bot pushed a commit that referenced this pull request Oct 7, 2026
…program (#228)

* feat(repo): publish a dry-run-only run's coverage into the incremental file (#155)

A dry-run-only run with incremental mode on now writes its dry-run coverage into the incremental file, keeping any verdicts already there, so a later run that reads the file through incrementalSources skips its own dry run. The run-inputs digest no longer covers dryRunOnly, so a dry-run-only preflight and the runs that reuse its coverage share one digest; existing caches are refused once with runInputsChanged.

(cherry picked from eafe8f3)

Verdict-Semantics: unchanged

* feat(repo): reuse CompileError verdicts across incremental runs

The TypeScript checker reports the digest of the program it loaded: every source
file the program holds, hashed, the tsconfig files it was built from, the
TypeScript version, the checker plugin version and the checker's options. The
engine stamps that digest beside every CompileError verdict it records, and a
later incremental run reuses such a verdict only when the digest it recomputes is
byte-equal. A missing digest, a changed file anywhere in the program, a changed
tsconfig, toolchain or option all send the mutant back to the checker. Hashing
the program needs the checker running, so the saving is the per-mutant check

Holding the digest means a plugin interface that can answer `digest`, and a
`programChanged` refusal reason on the reuse stream line. Verdicts the engine did
not remember keep their previous keys and refusals

Verdict-Semantics: unchanged

* fix(repo): keep the reuse stream line decodable without the new count

The reuse stream's refused counts gained a `programChanged` member, and the
generated contract document listed it as required, so a consumer built against the
committed stream would have refused every line an earlier engine wrote. The count
is optional, the way the plan line's `projects` array is: a stream line without it
still decodes

Verdict-Semantics: unchanged

* chore(repo): keep the api report CI renders for the Plugin namespace

Local builds render the Plugin namespace export as Node and CI renders Node_2 as Node (docs/solutions/api-extractor-node-alias-nondeterminism.md). #227 committed the local rendering and api:check failed in run 37626449206; main's rendering is the one CI accepts

Verdict-Semantics: unchanged

* docs(repo): mark the checker digest RPC as a breaking change

Every checker worker must answer the new digest RPC, so the plugin interface and the engine take a major bump per BREAK-1

Verdict-Semantics: unchanged

* docs(solutions): record the Plugin namespace Node alias split between CI and local builds

Run 37626449206 failed api:check on a report regenerated locally; run 37649609332 passed with main's rendering

Verdict-Semantics: unchanged

* fix(repo): hash the tsconfig extends chain into the program digest

The digest covered the root tsconfig and its project references but not the
configs reached through "extends", so editing an extended base left the
program key unchanged and CompileError verdicts were reused across a real
program change. The chain is now followed the way TypeScript resolves it -
relative, absolute and node_modules package specifiers, string or array form -
and every file in it is hashed. An unresolvable or unparseable config fails the
digest, which forces a re-check. The engine-level scenario needs a fixture
checker that digests the same chain, which the program-digesting fixture now
does

Verdict-Semantics: unchanged

* fix(repo): key the program digest on project-relative paths

Every digest entry was an absolute path, so the checker's sandbox directory
(.stryker-tmp/sandbox-<random>) put a different key on the same program on
every run and CompileError verdicts were never reused on CI. Paths are now
named relative to the root tsconfig's directory; files outside it, such as
node_modules realpaths, become deterministic "../" paths

Verdict-Semantics: unchanged

* fix(repo): canonicalize checker options for the digest and fail closed

The options JSON was re-encoded in insertion order, so semantically equal
option sets produced different keys and never matched; and a value that is not
JSON was silently replaced with {}, keying a configuration the checker never
ran. Object keys are now sorted before hashing and an unencodable option set
fails the digest, which forces a re-check

Verdict-Semantics: unchanged

* fix(repo): name a failed closure analysis in the reuse refusal reason

A refusal caused only by a failed closure analysis was reported as
closureChanged, and a keyed CompileError whose program still matched was
reported the same way - neither names the input that was actually missing. The
stream now carries a closureAnalysisFailed count, and that reason is chosen
whenever the closure analysis failed and no earlier gate outranks it

Verdict-Semantics: unchanged

* refactor(repo): consolidate the reuse key and version helpers

cacheKeyOf and programKeyOf differed only in the digest they read, so one
keyOf now serves both. readTypescriptVersion and readCheckerVersion share one
reader whose failure returns no version and so no digest. The program digest
borrows the existing sha256 and optional-field helpers instead of local
copies, and reads program files with bounded concurrency

Verdict-Semantics: unchanged

* test(repo): pin the new closure-analysis refusal count

The reuse-refusal objects these scenarios compare gained a
closureAnalysisFailed count, so each zero-refusal fixture now names it

Verdict-Semantics: unchanged

* chore(repo): regenerate the stream contract with the refusal count

The published stream schema gains the optional closureAnalysisFailed count
that the reuse report now emits alongside the other refusal counts

Verdict-Semantics: unchanged

* refactor(repo): satisfy the cell and effect lint rules

The digest helpers now branch through Option and Boolean match, so no function
exceeds the complexity budget, the key-order canonicaliser sorts schema keys
without assertions, and the closure walker resolves extends candidates without
a loop. The optional-field builder moves to the reuse module as a dual export,
which keeps it out of a cell file and off the pipeable-signature rule

Verdict-Semantics: unchanged

* build(release): name the changesets versioning strategy the release tools now decode

pnpm-release-management main renamed versioning.strategy pnpm to changesets (#9, #33), and both the Changeset Check and Release callers run its main, so every pull request here failed with 'cannot parse config release.jsonc' (run 37657899053)

Verdict-Semantics: unchanged

* ci(repo): rerun CI after two e2e lanes hit the lane limit on slow runners

Run 37664668214 timed out e2e sabotage and rest-1 at 19m59s with no failing test, on a tree identical to d82f758, which passed in run 37658299516

Verdict-Semantics: unchanged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant