Repository navigation
feat(repo): publish a dry-run-only run's coverage into the incremental file - #227
Merged
Merged
Conversation
…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
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
systemfsoftware-maker
added this pull request to stack #229
October 7, 2026 17:17
Contributor
There was a problem hiding this comment.
Dry-run-only run publishes its coverage (carrier for stranded #155). 8/8 green.
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
--dryRunOnlyrun now writes its dry-run coverage into the incremental file and keeps the verdicts already there.dryRunOnlyno longer feeds the run-inputs digest. As a result, a later run that reads that file throughincrementalSourcesreuses the coverage and skips its own dry run. On 17.0.2,--dryRunOnlywrites 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 deletedscripts/, and.github/workflows/is read-only for agents. Thebase.jshunk is also dropped; it only added thesarifreporter for CI.The engine hunks were re-fitted to main: main renamed
writeAtomictowriteFileAtomic, and the imports merged with main's mutant-cost imports.Validation
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),preflightedDryRunSpawnswas expected to be 0 and was 2.check:ciexit 0. The full stryker-js suite passes: 233 files, 1257 tests.dryRunOnlyout of the run-inputs digest only lets a dry-run-only run and later runs share cached coverage.etc/stryker-js.api.mdkeeps main'sNode_2 as Noderendering, which CI'sapi:checkaccepts.Nothing was run with stryker.
Cherry-picked from eafe8f3. Ships with the CompileError verdict reuse PR in the next release.