Files
fusion/packages
gsxdsm 387e836432 U4 substrate PR1: extract the git-evidence readers (19/19 bodies byte-identical, self-healing.ts -449) (#2489)
Based on `main`. **PR1 of the substrate decomposition** — deliberately
small and boring.

## Mechanical proof (the point of this PR)

Every moved body diffed against its pre-move text:

```
BLOCK                            KIND     DIFF-vs-ORIGINAL
  LandedTaskCommit               iface    EMPTY
  commitOwnedByTask              helper   EMPTY
  escapeRegex                    helper   EMPTY
  shellQuote                     helper   EMPTY
  parseShortstat                 helper   EMPTY
  findLandedTaskCommit           method   EMPTY
  findAlreadyMergedTaskCommit    method   EMPTY
  refreshRemoteBaseRef           method   EMPTY
  readCommitTaskOwnership        method   EMPTY
  branchHasNoUniqueDiff          method   EMPTY
  baseHasExplicitTaskOwnership   method   EMPTY
  foreignTipRejection            method   EMPTY
  branchTipForeignOwnership      method   EMPTY
  isCommitReachableFromBranch    method   EMPTY
  findWorktreePathForBranch      method   EMPTY
  repoBranchExists               method   EMPTY
  readShortstatForSha            method   EMPTY
  readLandedFilesForSha          method   EMPTY
  isBranchTipMisboundToTask      method   EMPTY

RESULT: 19/19 BYTE-IDENTICAL modulo the enumerated deviations; 0 differ.
```

No condition reordered, no signature changed, no rename, no inlining, no
"while I am here" cleanup.

## The premise needed correcting — the cheap lesson this cut was meant
to buy

The brief described **13 pure functions**. They are 13 `private async`
**methods** closing over `this.options`, so nothing here is
byte-identical in the strict sense. Three deviations were structurally
unavoidable:

1. **`private` → `protected`** on the 14 methods and the `options` field
— a subclass cannot call a `private` base member. One token per
declaration.
2. **Two type annotations** rewritten:
`SelfHealingManager["readCommitTaskOwnership"]` → the base class, since
the original would be a circular import.
3. **Four module-level helpers moved along** and re-exported. Forced by
direction — the new module must not import `self-healing.ts`, so
everything the bodies call has to live beside them. `execAsync` and
`shellQuote` are imported back because call sites remain.

## The set is 14, not 13

`foreignTipRejection` had to come too, and it's what **closes** the
cluster — it depends only on `baseHasExplicitTaskOwnership` and
`branchHasNoUniqueDiff`, both already in the set. Without it the
extraction isn't self-contained and `this.options` isn't the only
external dependency.

## Base class, not free functions — deliberately

Converting to free functions would change all 14 signatures: a
behavior-adjacent edit riding inside a file move, which is exactly the
combination that hid the last four safeguard regressions. An abstract
base keeps `this` semantics, so every call site stays
`this.<method>(...)` and every body is unchanged text.

## Two pre-existing collisions, preserved exactly

- The detector module already exports a **free**
`findAlreadyMergedTaskCommit` sharing a name with the protected method;
inside the class body the bare identifier resolves to the import.
- `SelfHealingOptions` is declared in `self-healing.ts`, imported here
**type-only** so it's erased at runtime and creates no module cycle.

## Measured line delta — not an estimate

| | lines |
|---|---:|
| `self-healing.ts` | 13,394 → 12,945 = **−449** |
| new module | **+527** (454 moved verbatim, 73 scaffold) |
| **NET** | **+78** |

Same shape as every consolidation in this program: the target file
shrinks, the total grows slightly. At −449 for the first and safest cut,
the remaining substrate cuts plausibly take `self-healing.ts` under 11k
— but that is **file-size reduction, not code reduction**.

## Verification

464 passed across three engine suites with the single known
**pre-existing** `archiveStaleDoneTasks` failure; tsc clean; lint clean;
merge gate green (299 + 10 + 71).

No changeset: `@fusion/engine` is private.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 20:15:46 -07:00
..
2026-07-26 18:11:47 -07:00