Skip to content

fix(stryker-js): read package manifest exports per entry so one shape cannot refuse the whole file - #330

Merged
ryanleecode merged 17 commits into
mainfrom
stream-d/fix-manifest-exports-decode
Oct 13, 2026
Merged

ryanleecode merged 17 commits into
mainfrom
stream-d/fix-manifest-exports-decode

Conversation

@systemfsoftware-maker

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

Copy link
Copy Markdown
Collaborator

Follow-up to #308 (Stream D; intents r41, r46, r49). Plan: docs/plans/2026-10-13-0656-fix-manifest-exports-decode-plan.md. It supersedes 0620, 0606, 0546, 0519, 0441 and 0239, none of which reached main.

Outcome

On main, a project that imports a workspace-linked package re-tests every mutant after any edit when that package's package.json declares exports with nested condition objects, array fallbacks, a top-level array or a null target. The whole manifest fails to decode, the closure stays open, and each run refuses reuse with closureChanged. This repo hits it: vitest-config, tsdown-config, stryker-config and oxlint-ignorer-config all declare { ".": { "import": { "types": "...", "default": "..." } } }.

After this PR, each exports, imports, main and module entry is read on its own, the way Node's resolver reads it. An unrelated edit keeps every verdict. A closure closes only on the file Node loads for both ESM and CommonJS importers. When the two load different files, or Node loads none, the importers' closures stay open.

What changes

  • PackageManifestSchema (import-closure.schema.ts, r49). The foreign package.json stays the Encoded side. exports, imports, main and module decode into PackageEntryField, a tagged tree with one variant per JSON shape: EntryPath, EntryFallbacks, EntryMap (key order kept), EntryNull and EntryScalar, plus EntryUndeclared for an absent key. The decode is total over JSON. The generated round-trip, encode-stability and recursion laws grade it. ExportEntrySchema is deleted.
  • selectManifestTarget (select-manifest-target.workflow.ts) reads every entry twice, under Node's ESM conditions (import, node, module-sync, node-addons, default) and its CommonJS conditions (require in place of import). Its rules follow Node v24's resolvePackageTarget (lib/internal/modules/esm/resolve.js:377-548):
    • A string is a target when Node accepts it: in exports it starts with ./ and has no ., .. or node_modules segment; in imports, likewise or a bare package specifier. A rejected string yields no file.
    • A condition object yields @systemfsoftware/source if present. Otherwise its first key in the condition set decides; every other key (types, browser, module, user conditions) is skipped.
    • A condition object with an array-index key (0, 1, …) refuses the read, as Node's ERR_INVALID_PACKAGE_CONFIG does (:522-530). Node rethrows that error inside an array (:501-506), so the reader carries the refusal in a failure channel: an array stops at it instead of skipping it.
    • An array yields its first element that yields a file, skipping null, numbers, booleans, rejected strings and unmatched objects. An empty array, or one with no file and a null or rejected element, yields none.
    • Both reads yielding one file, or one a file and the other none, gives that file. Two different files give no target. A present entry that gives no target is final: no pattern or main rescues it.
    • Root conditions (an exports object with no . key) serve the root. Mixed keys yield no target. For the root fallback, a string main is the target, a present non-string main yields no target, and module is read only when main is absent (Node never reads module).
  • Laws (src/__tests__/select-manifest-target.workflow.property.test.ts): 23, all drawn from the production ManifestTargetCommand and written as package.json JSON through the codec. New or rewritten this PR: the condition chain at every depth; unmatched conditions falling through; ESM and CommonJS loading different files; root conditions; the source condition at its own depth; array fallbacks; rejected string targets; array-index condition keys at every depth; null exports; main/module (every non-string main shape).
  • Differential spec against Node (tests/manifest-target-node-resolution.differential.test.ts, @systemfsoftware/differential-spec, already a devDependency). It generates exports and imports entry trees (conditions include node-addons and the array index 0) and writes each to a real temp project. The reference is a real node process resolving #x and @fx/lib/x with import.meta.resolve and require.resolve. The candidate is ImportClosure.analyzeImportClosure, reached through the public barrel. Oracle: the closure must equal the file both of Node's answers agree on, or the one answer when the other is none; otherwise the closure must be open. Host-bound lane, 100 runs. The probe and its output decode live in the reference harness tests/__fixtures__/node-resolution.fixture.ts.
  • Built-CLI feature (tests/workspace-package-exports-reuse.integration.test.ts, 12 rows). It spawns dist/main.mjs run twice around an edit and reads only exit codes and the machine-mode reuse line. It covers every exports shape around an unrelated edit, and a runtime-only edit (types-first packages, root conditions, a dual import/require package required by its test). Rows run on the vm runner and on an on-disk runner fixture, tests/__fixtures__/closure-only-runner/, which reports no loaded modules.
  • Lesson (docs/solutions/performance-issues/strict-optional-key-…-refuses-the-whole-manifest.md), CONCEPTS.md (import closure, closure digest), plan 0656, and a patch changeset for @systemfsoftware/stryker-js.

Evidence (local, parent-run)

Check Before Head e75022860
Built-CLI feature on main (round 1) nested, array, top-level array and null-subpath rows fail; flat control passes all 12 rows pass
Built-CLI feature on 14a06fdab (first-value rule) root-conditions row and the closure-only nested and flat types-first runtime edits fail (stale verdicts) pass
Built-CLI dual import/require row on 07f55c9f9 (import followed whatever its siblings) fails: run 2 reuses 2 stale verdicts pass
Laws on 07f55c9f9 fall-through, ESM/CommonJS and fallbacks laws fail 23/23 pass
Laws on 72410ddba / 3eee2a48c fallbacks law fails / string-targets law fails 23/23 pass
Laws on e8393042d array-index and main/module laws fail 23/23 pass
Differential vs Node on 07f55c9f9 fails, shrunk to imports {"require": "./a.js", "types": …} (Node's CommonJS side loads a.js; closure open) passes
Differential vs Node on 72410ddba fails, shrunk to {"module-sync": [], "node": "./a.js"} (Node blocks; closure closed on a.js) passes
Differential vs Node on 3eee2a48c (with rejected strings drawn) fails, shrunk to exports "/a.js" (Node rejects; closure closed on pkgs/lib/a.js) passes
Differential vs Node on e8393042d (with 0 drawn) fails, shrunk to an imports object holding "0" (Node refuses it; closure closed on a.js) passes on 3 fresh seeds and in the suite
stryker-js full suite on 958d299cf's tree 282 files, 1762 tests pass
e75022860 (widens one law only) laws 23/23, typecheck, lint, lint:tsgo, dprint pass
build, typecheck, lint, lint:tsgo, dprint, changeset and lesson validators pass

The old sides ran in a scratch git worktree under /tmp. Nothing was reverted in this checkout.

Review receipts

Round 4, full path (ce-code-review, report-only): run 20261013-063937-1f11f4b8, base 2083ba43a, reviewed head e8393042d. The validator confirmed two P2s and rejected the P1; the learnings ledger found one pack violation. All three are fixed in 958d299cf, so the verdict on e8393042d was Not ready. As in round 3, I fixed the findings before a report leaf ran, so the run has no review.json.

Lens Agent Served (turns)
correctness correctness-reviewer DeepSeek v4.1 Flash 98
project-standards generalist-reviewer DeepSeek v4.1 Flash 36
testing generalist-reviewer DeepSeek v4.1 Flash 25
adversarial (in-process; no external peer) adversarial-reviewer DeepSeek v4.1 Flash 51
learnings + pack ledger generalist-reviewer DeepSeek v4.1 Flash 24
validator correctness-reviewer DeepSeek v4.1 Flash 16

Pack rules on e8393042d: 10 comply, 1 violate. The violation is schema-laws / tests-own-no-schemas: the differential spec's probe schemas sat in tests/__fixtures__/node-resolution.schema.ts. Fixed in 958d299cf: they moved, unexported, into the reference harness tests/__fixtures__/node-resolution.fixture.ts.

# Finding (round 4) Sev Source Validator Disposition
1 Pattern exports pick the wider wildcard P1 correctness rejected Node orders pattern keys by indexOf('*') + 1 (resolve.js:673-681), which is monotone in the reader's prefix length. My node probe of overlapping patterns in both key orders matched the reader.
2 Array-index condition keys are read as conditions instead of refusing P2 correctness confirmed Fixed in 958d299cf: refusal channel, ∀c_ConditionObject_≡AnArrayIndexKeyRefusesTheEntryEvenInsideAnArray, and the differential spec draws 0. Both fail on e8393042d.
3 A non-string main closes the root on module, which Node never loads P2 adversarial confirmed Fixed in 958d299cf: ∀c_MainOrModule_≡ANonStringMainYieldsNoneAndModuleCountsOnlyWithoutMain (fails on e8393042d); e75022860 widens it to every non-string shape.

Delta lenses after round 4 (correctness only, report-only; each over exactly the commits named):

Run Commits reviewed Served (turns) Findings Disposition
20261013-070109-64000e1b e8393042d..958d299cf DeepSeek v4.1 Flash 19 0 Its testing gap (the law drew no object, null or boolean main) is closed in e75022860.
20261013-072719-49846993 958d299cf..e75022860 (current head) DeepSeek v4.1 Flash 35 none No finding to dispose of. The lens ran the real codec and confirmed that each of the five new non-string main rows fails against the pre-958d299cf mainTargetOption and holds at head.

Residual risks the 20261013-072719-49846993 lens recorded, all recorded with no code change:

  • The main/module root fallback is pinned only by the law. The differential spec keeps it out of its program space (plan 0656 U5). Node v24 loads <pkg>/index.js for a non-string main, while the reader keeps the closure open: safe direction.
  • A package with only module closes over module, which Node never loads (it loads index.js). This behaviour already existed on the main branch, and this PR leaves it unchanged. It is the same class as the held runner-conditions question (Decisions, item 1), which stays with root.
  • The law's values are fixed by construction (the root request makes every draw the same), so it acts as a deterministic table rather than a property over the draw.

Round 4 residual risks and gaps, and what happened to each:

  • No test drew node-addons; no case had an array-index key: fixed (both now drawn by the differential spec).
  • "Node falls through to the next pattern when the winner's target is rejected": refuted. Node resolves only the single bestMatch and throws exportsNotFound when it yields nothing (resolve.js:644-663, :739-746), as the reader does.
  • exports present with no entry for the request falls back to main, where Node throws ERR_PACKAGE_PATH_NOT_EXPORTED: pre-existing, byte-identical to main (round 3's P3, rejected). The import fails at runtime either way.
  • Trailing-slash subpaths and pattern captures are not segment-checked after substitution, where Node rejects them: recorded. Node refuses the import, so no test outcome depends on the file the closure names.
  • The reference is whichever node runs the suite (24.x in CI), and the spec spawns node 100 times under a 240 s host bound: accepted. Pinning to the runtime CI actually ships with is the intent.
  • @systemfsoftware/source wins unconditionally: accepted. The workspace enables it in every resolver it runs (tsconfig customConditions, Vitest resolve.conditions).
  • The built-CLI suite varies only exports; imports/main/module are proved at the analyzeImportClosure layer and by the laws: recorded.
  • A non-string main makes Node load index.js, while the reader keeps the closure open: accepted. That direction costs reuse and is never stale.

Round 3, full path (ce-code-review mode:agent base:origin/main, report-only): run 20261013-052410-5e2140d5, base 117bff023, reviewed head 07f55c9f9. The validator confirmed one P2 and rejected one P3, so the verdict on 07f55c9f9 was Not ready. I fixed the P2 before the report leaf ran, so the run has no review.json.

Lens Agent Served (turns)
correctness correctness-reviewer DeepSeek v4.1 Flash 39
project-standards generalist-reviewer DeepSeek v4.1 Flash 33
testing generalist-reviewer DeepSeek v4.1 Flash 42
adversarial (in-process; no external peer) adversarial-reviewer DeepSeek v4.1 Flash 31
learnings + pack ledger generalist-reviewer DeepSeek v4.1 Flash 21
validator correctness-reviewer DeepSeek v4.1 Flash 13

Pack rules on 07f55c9f9 (enrolled by .compound-engineering/config.yaml:1-4): 10 comply, 1 violate. The violation is boundary-testing / pin-dependency-semantics at select-manifest-target.workflow.ts:64: the reader encoded Node's resolver, and nothing pinned it against Node. Fixed in 3eee2a48c by the differential spec. The complying rows: cell-architecture decode-never-cast and pure-decision-workflows; boundary-testing no-mocks-on-internal-glue and real-system-oracles; schema-laws law-failure-is-a-codec-defect, recursive-schema-suspend, refusals-beside-generated-laws, rich-type-over-foreign-encoded (resolved by r49; no exception held), tagged-unions-over-state-by-presence and tests-own-no-schemas.

# Finding (round 3) Sev Source Validator Disposition
1 A CommonJS importer closes over the import branch it never loads P2 correctness + adversarial confirmed Fixed in 590361469 (merged as 72410ddba): ESM and CommonJS reads must agree. Pinned against Node by the differential spec (3eee2a48c).
2 exports: {} falls back to main, an untested boundary P3 testing rejected Pre-existing: main behaves byte-identically. ∀c_RootEntry_≡MainDecidesTheRootOnlyWhenExportsDeclaresNone asserts it on purpose.

Delta lenses after round 3 (correctness only, report-only; each over exactly the commits named):

Run Commits reviewed Served (turns) Findings
20261013-061107-f1739e97 07f55c9f9..590361469 and 72410ddba..3eee2a48c (the merge of main excluded) DeepSeek v4.1 Flash 40 0
20261013-063104-4f686a1b 3eee2a48c..e8393042d DeepSeek v4.1 Flash 29 0

Residual risks those lenses recorded, and what happened to each:

  • The reader took string targets Node rejects: fixed in e8393042d (a rejected string yields no file and is skipped inside an array), and the differential spec now draws rejected strings.
  • The reader skips conditions a runner enables beyond Node's defaults: held for you (Decisions, item 1).
  • The oracle shares the agreement rule with the reader, so it pins each runtime's read against Node but argues the agreement rule rather than pinning it: accepted. The rule never omits a file either module system loads.
  • Percent-encoded segments (./%2e%2e/a.js) are accepted where Node rejects them. The named file cannot exist, so the closure stays open: slow, never stale. Out of scope, recorded in plan 0656.
  • Bare imports targets get no segment check (unconfirmed consequence; unchanged by this PR): recorded.

No xai or Grok model appears in any lens transcript. Each round's candidate findings were merged by the orchestrator from the lens artifacts and judged by the validator leaf.

Earlier rounds (all fixed or rejected, each with its fixing commit in the PR history): round 2 (run 20261013-043819-d9c96519, head f6d463bec) found that the plan contradicted the code, fixed in f10aa825c. Round 1 (run 20261013-032850-f4ff0347, head 14a06fdab) found that nested types-first targets keep stale verdicts (P1), fixed in f6d463bec. Its deferred array finding is now fixed, since arrays follow Node.

Decisions for you

  1. Conditions a runner adds beyond Node's defaults (browser, module, development, --conditions, Vite's SSR conditions). The reader follows Node's default sets and skips such keys. Take a package with { "module": "./m.js", "default": "./d.js" } on a runner that both enables module and reports no loaded modules: when only m.js changes, that runner can reuse a stale verdict. Vitest reports the modules each test loaded (testFileModules), and those join the closure, so the built-in runners are covered. The alternative, keeping a closure open whenever a non-Node condition precedes the chosen key, costs reuse for every { module, default } package. My recommendation: keep Node's rule. Say if you want the conservative one.

Mutation

No local mutation runs. Latest completed main Mutation run: 38349712161 (add57742a). For select-manifest-target.workflow.ts it reports 45 killed and 4 survived: bf8fc33bfe874ae6, c1c49ef7599681d9, f00516bf7f032d1a and ecd4fec1e600291a, all in patternMatchOf's guards, which this PR does not change and these tests do not target. The new reader's mutants get their first grade on the next main run.

Notes

  • Off main, unstacked, per r41. No changed file is shared with any of my other open PRs.
  • Turbo rule: does not apply. No CI task scope or cache input changes.
  • Spawning the CLI and node in tests: the write-gherkin-integration-tests skill refuses process spawns (WGI-CLS1). Ryan's rule for this unit requires the built CLI, and Node is the reference the differential spec pins against.
  • Memory: Engram registration failed this session, so nothing was saved there.

…e manifest

Solution doc for the import-closure decode regression found in review of
PR #308: an `imports` key typed narrower than Node's grammar made a null,
array or nested-condition entry fail the whole package.json decode, so
exports/main/module were lost and every importing closure opened. States
the refuse-at-the-granularity-of-use invariant, the grep-able smell, and
the instance still left in `exports` for workspace-linked packages.

Seeds CONCEPTS.md with the import closure and closure digest entries.
… cannot refuse the whole file

PackageManifestSchema decodes exports, imports, main and module as JSON. selectManifestTarget reads entries at any depth (source condition, else first value; arrays by their first element), treats a present entry without a target as final, and reads a top-level null exports as absent, as Node does. Plan: docs/plans/2026-10-13-0239-fix-manifest-exports-decode-plan.md (supersedes the 0230 draft).
…e its exports take

Built-CLI suite: a project whose test imports a workspace-linked package runs twice around an edit to a file no test imports; the second run must reuse every verdict for flat, nested-condition, array-fallback and null-subpath exports. On main the last three refuse with closureChanged. Simplifications from ce-simplify-code: one exact-entry reader, stringOf covers absent main/module, root entries gated by Boolean.match, events decoded through RunEventWireLine.
…linked package exports

Review fixes for the manifest exports unit. The root law now draws a string and a one-element array root (fails against main's decision). The built-CLI feature becomes an outline over five shapes, adding a top-level array, plus a scenario that edits only the linked package's runtime file and expects every mutant tested again: the guard that a types-first condition closing on the .d.ts never keeps a stale verdict while the runner reports testFileModules. The lesson records the exports fix and that hazard instead of the pre-fix 'one instance remains' claim.
…untime loads

A condition object took @systemfsoftware/source or else its first value, so a types-first package closed its importers' closures over a .d.ts no runtime loads, and a runner without testFileModules reused stale verdicts after an edit to the package's .js (review P1 on 14a06fd). Past types/types@..., an import, node or default condition is followed; any other first condition leaves the closure open. An exports object with no '.'-prefixed key is read as the root's conditions, and a mixed one yields no target, as in Node. Gate: a closure-only runner fixture in the exports-reuse feature, whose nested and flat types-first runtime-edit rows and root-conditions row fail on the first-value rule.
…dition rule

The 0239 plan still chose the first-value rule and rejected Node's runtime condition set, which f6d463b ships (review of f6d463b: correctness P3, adversarial P2). The 0441 plan restates R3 per conductor r46 (resolve to the file the runtime loads, or stay open), records the rule in KTD2 with its rejected alternatives, states Node's array continuation rule, and adds U4 (the closure-only runner gate). The missing-target law now also pins a condition object holding only types conditions (testing gap).
…he resolver chooses from

PackageManifestSchema keeps the foreign package.json as its Encoded side and decodes exports, imports, main and module into PackageEntryField: EntryPath, EntryFallbacks, EntryMap (key order kept), EntryNull and EntryScalar, one variant per JSON shape, with EntryUndeclared for an absent key. The decode is total over JSON, and the generated round-trip, encode-stability and recursion laws grade it from the Type side (schema-laws rich-type-over-foreign-encoded, root ruling r49). selectManifestTarget chooses the target by dispatching exhaustively on the entry's tag; which condition, fallback and key form counts is unchanged. The laws keep writing package.json JSON and reach the decision through the production codec. Plan 0519 supersedes 0441 (KTD1, U1, scope).
…onJS condition sets

Full-path review of 07f55c9 confirmed a P2: an import-first condition object closed a CommonJS importer over the ESM file it never loads. selectManifestTarget now reads each entry twice, as Node reads it, under the ESM set (import, node, module-sync, node-addons, default) and the CommonJS set (require in place of import), skipping keys outside the set. One file from both reads, or a file from one and none from the other, is the target; two different files keep the importers' closures open. Arrays skip numbers, booleans and objects no condition matches; a number or boolean inside a condition object yields no target.

Laws: the fall-through, ESM/CommonJS and fallbacks laws fail on 07f55c9 and pass on this head. Plan 0546 supersedes 0519.
…ver against Node

A differential spec now resolves generated exports and imports entries with a real node process (import.meta.resolve and require.resolve) and compares the import closure Stryker builds for the same project (boundary-testing pin-dependency-semantics, reported violated by #330's review ledger). Its first runs found two array rules that disagreed with Node's resolvePackageTarget (lib/internal/modules/esm/resolve.js:488-520, v24.x): an empty array yields null, so a condition holding [] blocks instead of falling through; and a null element is skipped, not final. selectManifestTarget now reads arrays that way, and the fallbacks law states it.

Two-sided: the spec fails on 07f55c9 and on 72410dd, and passes on this head. Plan 0606 supersedes 0546.
… over them

A delta correctness lens over 3eee2a4 (run 20261013-061107-f1739e97, zero findings) recorded that the reader took string targets Node's resolvePackageTarget rejects (resolve.js:389-423, v24.x): an exports string without ./, or one with a ., .. or node_modules segment, or an imports string starting with / or ../. selectManifestTarget judged a string only after choosing it, so an array stopped at a string Node skips, and exports {"./x": "/a.js"} closed over pkgs/lib/a.js. The reader now judges each string as it reads it: a rejected target yields no file, and inside an array it is skipped.

The differential spec now draws rejected targets (a.js for exports only; in imports it names a package) and shrank the stale case on 3eee2a4; the new string-targets law fails there too. Both pass on this head. Plan 0620 supersedes 0606.
…g module past a non-string main

Full-path review of e839304 (run 20261013-063937-1f11f4b8) confirmed two P2s and one pack violation.

- A condition object with an array-index key was read as conditions; Node refuses it with ERR_INVALID_PACKAGE_CONFIG (resolve.js:522-530) and rethrows that inside an array (:501-506). The reader now carries the refusal in a failure channel: an object returns it, an array stops at it, and the entry yields no target.
- A non-string main fell through to module, which Node never reads; on main such a manifest failed to decode and left the closure open. A present non-string main now yields no target, and module counts only when main is absent.
- schema-laws tests-own-no-schemas: the differential spec's probe schemas move from tests/__fixtures__/node-resolution.schema.ts into the reference harness tests/__fixtures__/node-resolution.fixture.ts, unexported.

The differential spec now also draws node-addons and the array index 0. The two new laws and the spec fail on e839304 and pass here. Plan 0656 supersedes 0620.
… law

Delta lens run 20261013-070109-64000e1b over 958d299 found 0 issues and noted that the law drew only a number and an array for main, leaving the EntryMap, EntryNull and boolean arms of mainTargetOption unasserted. The law now draws a number, a boolean, null, an array and an object.

@ryanleecode ryanleecode 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.

Root verdict: e750228 green apart from inherited bench; 0 threads; up to date with main 2083ba4; full-path round 4 (20261013-063937-1f11f4b8) plus delta lenses 20261013-070109-64000e1b and 20261013-072719-49846993 cover the head on DeepSeek v4.1; both pack violations (pin-dependency-semantics, tests-own-no-schemas) fixed with commits named; built-CLI scenarios pin the manifest reading.

@ryanleecode
ryanleecode merged commit 381b3f5 into main Oct 13, 2026
22 checks passed
@ryanleecode
ryanleecode deleted the stream-d/fix-manifest-exports-decode branch October 13, 2026 07:35
systemfsoftware-maker added a commit that referenced this pull request Oct 13, 2026
…t of the next run's inputs

Takes main through b4db6d8 (#313 root-bound dry-run coverage, #314, #317,
#321, #322, #326, #330, #331, #332, #334, #335, #337).

Merge resolutions:
- dry-run-choice: main's current-root candidate filter over the one
  incremental file this branch reads; prior statuses come from the
  verdict store, flaky ids from the kept coverage.
- dry-run.cell, deferrable-dry-run.cell, plan-request.cell: pass that one
  report text into main's choice; the plan prices from the kept coverage.
- typescript-checker: main moved the runtime into
  typescript-checker-runtime.ts; it carries this branch's config/program
  digest scope.
- e2e StrykerCliRunner: main split service from layer; runScript and
  runInFork move into microsandbox-stryker-cli-runner.ts.
- Tests: main's flaky-root CompileError scenario writes the incremental
  file instead of the removed incrementalSources; the relocated-project
  multi-report scenario and dry-run-coverage-across-roots go with
  incrementalSources; the version-gate test uses layerStage.
- The solution doc keeps both the store key and the root binding.

Fix found while validating the merge: "A run killed after it scored its
first mutant spares the next run its initial test run" failed 2 of 4
runs. The fixture kills the CLI at the first mutant event
(run/mutant-run.ts:264-265), which can land inside the checkpoint's
atomic write of the incremental file (mutation-reporting.service.ts:754).
SIGKILL skips the temp file's cleanup (replace-file-atomically.ts:44), so
reports/.main.json.<hex>.tmp stays. The crawl excluded only main.json
(stryker-outputs.ts, read-project.cell.ts:116-124), so the temp file
became a project file and the next run refused its dry run with
closureChanged.

replace-file-atomically.ts now owns the temp name and the rule matching
it, and strykerOutputFilesOf excludes the incremental file's temp
siblings. A new scenario plants that temp file between two runs of the
built CLI; on the unfixed build the second run executed 2 tests, and it
now executes none. The changeset says what holds for a killed run.
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.

2 participants