Problem Statement
Deciding how the CLI renders (machine stream, colour support, output file) is one outside interaction — read flags and terminal, decide the mode, write through the opened stream — but its spine is scattered: six Effect.gen blocks in Output.ts and mid-tree service binding in StreamFile.ts and Cli.ts, next to a Cell that already exists but is run inline. The composition root belongs once at the process edge; today anyone changing output behavior edits procedures across three files, and nothing stops the next provideService from landing mid-tree again.
Goal
The output-mode interaction (probe → decide mode → open the stream) is exported as one typed pipe run once at the CLI's process edge, the services it needs are provided at src/main.ts rather than mid-tree, the gen-based functions it replaces are deleted, and pasting the old shape back fails pnpm --filter @systemfsoftware/stryker-js-cli typecheck because its home no longer exists.
Strangler slice: outside interaction = output-mode resolution and stream opening; intercept = packages/stryker-js/stryker-js-cli/src/main.ts (the process entry); forbidden shape = mid-tree provideService and multi-run gen sequencing on this path. No two-run lint exists in this repo and this slice creates none — the re-firing mechanism is the deleted surface (typecheck) plus the existing gold-path tests, per SJ-R4 in packages/stryker-js/AGENTS.md. Old paths for other interactions stay put; this slice moves only this one (SF5 coexistence).
Evidence: Current Behavior
packages/stryker-js/stryker-js-cli/src/StreamFile.ts:40-49 and the existing-but-inline Cell at packages/stryker-js/stryker-js-cli/src/Output.ts:396-480, at 7f11497e8b35da696e9f975df80f9714b91f79fa:
return openClose.pipe(
Effect.provideService(FileSystem.FileSystem, fs),
Effect.provideService(Path.Path, path),
)
export const outputModeProbeCell = Cell.layer({ ... })
// ...
Cell.run(outputModeProbeCell, flags).pipe(
Orientation
- The typed building blocks already exist in-repo:
Workflow.make decisions (resolve-output-mode.workflow.ts) and Cell.layer/Cell.run (Output.ts). If the two-run shape later recurs as a defect, a rule earns admission then — named, dated defect plus a known-bad fixture, in its own change — not as part of this slice.
src/main.ts is the package's process entry and already the place Logger.LogToStderr is bound (main.ts:58); it is the one legal binding site after the slice.
- Gold paths: the mode/colour assertions in
tests/cli-contract.integration.test.ts (TERM/NO_COLOR matrix) and the package's unit tests under src/__tests__/.
- Other interactions in
Cli.ts keep their current shape in this slice; migrating them is follow-on slices behind the same intercept.
Non-Counting Outcomes
- The pipe exists but the old gen-based functions remain exported and callable "just in case" — both paths live, the forbidden shape stays legal.
- A bespoke two-run or host-binding lint minted to prove this slice — no such defect has been dated in this repo, so the earn test fails (ENFORCEMENT.md gate economy).
- Services moved into a shared "context module" that is still imported mid-tree — the binding moved, the mid-tree import did not.
- Consumer-visible output (NDJSON stream shape, colour behavior, exit codes) changed as a side effect and tests were re-blessed to match.
- Success claimed because
Output.ts now mentions Cell more, with no caller forced onto the new pipe.
Acceptance Criteria
Problem Statement
Deciding how the CLI renders (machine stream, colour support, output file) is one outside interaction — read flags and terminal, decide the mode, write through the opened stream — but its spine is scattered: six
Effect.genblocks inOutput.tsand mid-tree service binding inStreamFile.tsandCli.ts, next to aCellthat already exists but is run inline. The composition root belongs once at the process edge; today anyone changing output behavior edits procedures across three files, and nothing stops the nextprovideServicefrom landing mid-tree again.Goal
The output-mode interaction (probe → decide mode → open the stream) is exported as one typed pipe run once at the CLI's process edge, the services it needs are provided at
src/main.tsrather than mid-tree, the gen-based functions it replaces are deleted, and pasting the old shape back failspnpm --filter @systemfsoftware/stryker-js-cli typecheckbecause its home no longer exists.Strangler slice: outside interaction = output-mode resolution and stream opening; intercept =
packages/stryker-js/stryker-js-cli/src/main.ts(the process entry); forbidden shape = mid-treeprovideServiceand multi-run gen sequencing on this path. No two-run lint exists in this repo and this slice creates none — the re-firing mechanism is the deleted surface (typecheck) plus the existing gold-path tests, per SJ-R4 inpackages/stryker-js/AGENTS.md. Old paths for other interactions stay put; this slice moves only this one (SF5 coexistence).Evidence: Current Behavior
packages/stryker-js/stryker-js-cli/src/StreamFile.ts:40-49and the existing-but-inline Cell atpackages/stryker-js/stryker-js-cli/src/Output.ts:396-480, at7f11497e8b35da696e9f975df80f9714b91f79fa:Orientation
Workflow.makedecisions (resolve-output-mode.workflow.ts) andCell.layer/Cell.run(Output.ts). If the two-run shape later recurs as a defect, a rule earns admission then — named, dated defect plus a known-bad fixture, in its own change — not as part of this slice.src/main.tsis the package's process entry and already the placeLogger.LogToStderris bound (main.ts:58); it is the one legal binding site after the slice.tests/cli-contract.integration.test.ts(TERM/NO_COLOR matrix) and the package's unit tests undersrc/__tests__/.Cli.tskeep their current shape in this slice; migrating them is follow-on slices behind the same intercept.Non-Counting Outcomes
Output.tsnow mentionsCellmore, with no caller forced onto the new pipe.Acceptance Criteria
pnpm --filter @systemfsoftware/stryker-js-cli typecheckexits non-zero or the duplicate shape is unreachable because its call sites are gone; the packet records the command, its non-zero exit, and the pasted snippet.grepshows the old gen-based equivalents deleted.pnpm --filter @systemfsoftware/stryker-js-cli testandtest:contractexit 0 with the TERM/NO_COLOR stream-shape assertions unchanged.provide/runfor this interaction appear once, insrc/main.ts.