Skip to content

refactor: cut the daemon's commands-closure leak and its last root hubs (successor to #2278 and #2335) #2545

Description

@thymikee

Purpose

Remove the last structural coupling that makes the daemon expensive to change and impossible to package, and stop it paying for the whole command pipeline it never calls. Successor to #2278 (residual semantic coupling, closed) and #2335 (decoupling wave 3, closed). Those waves moved code into packages and classified what remained; this one cuts the two things measurement shows are still load-bearing.

Measured at 9aa6465768 with the gate's own model (pnpm depgraph plus a throwaway probe against scripts/layering/model.ts): 1,684 production files, 8,689 edges, 38 zones, R4 = 0, R5 = 0, R6 = 4.

Two findings drive the sequence:

  1. The daemon's eager value closure contains 121 src/commands/** files (717 files total in the closure). One value edge is responsible for 48 of them: src/runtime.ts → ./commands/index.ts. Five daemon files import src/runtime.ts.
  2. Daemon → src/ outside the daemon is 51 file pairs (42 root flat files, 7 src/core/**, 2 src/cli-schema/**), down from 154 edges into 67 files at the wave-3 baseline. Twelve of those pairs are one file, src/provider-device-runtime.ts, and ten of its twelve importers need exactly one symbol, isActiveProviderDevice.

Fan-in is not cost. Churn over the last 400 commits: src/backend.ts 53, src/provider-device-runtime.ts 19, src/runtime.ts 6, src/runtime-contract.ts 5, src/io.ts 4. runtime-contract.ts has 27 importers and is nearly frozen; it is not a target.

Children

Wave A (independent):

Wave B (after Wave A):

Wave B is not scheduled while a Wave A branch is open on the same files; src/commands/index.ts (#2540) and cli-schema/command-schema.ts (#2543) overlap, and #2542 owns the R76 inventory both #2541 and #2544 want to edit.

Completion conditions

This umbrella closes when every child is closed and:

  • The daemon's value closure reaches src/commands only through src/runtime-command-surface.ts, contains only the capture, selectors, and interactions runtime families (src/commands/*/runtime/**) plus their shared runtime helpers, and includes no command facet, CLI reader, output formatter, or the all-family barrel. Measured at 775163bb35: 34 files, 6,368 LOC, down from 121 files, 19,659 LOC. Zero was the original wording; it is unreachable while the daemon executes those three families in-process, and relocating the runtime directories would only change the count, not what loads (decided 2026-09-14 on refactor(daemon): stop importing the commands barrel through src/runtime.ts #2540).
  • Daemon → src/ outside src/daemon/ carries only (a) edges classified composition-essential or daemon-policy-essential by the R76 inventory, (b) the shared runtime contracts and command surface (backend.ts, runtime-contract.ts, runtime-factory.ts, runtime-command-surface.ts, io.ts, src/core/**), and (c) process-root contracts that refactor(daemon): stop the client value-importing daemon internals (#2559) #2594 moved out of the daemon so the client could share them without importing daemon internals (daemon-resolution.ts, daemon-process.ts, request-progress-protocol.ts, session-repair-tombstone.ts). No daemon file imports a root hub that reaches platform mechanics outside the inventory. The raw pair count is not the target: it was 51 at 9aa6465768, 42 after Wave A, and 52 after refactor(daemon): stop the client value-importing daemon internals (#2559) #2594 by design, because the client-side repair added daemon → root leaves rather than removing them (reworded 2026-09-16).
  • pnpm depgraph reports the same R4/R5 = 0 and R6 does not grow past its ratchet.

Explicitly not scheduled

  • Packaging the daemon. Inbound is already only ~20 non-daemon files and no packages/** file imports src/**, but packaging it before the closure cut and the hub ports just relocates the eager-closure problem. gates: let the eager-closure budget tolerate a module split without admitting new eager work #2469 must also land first: the eager-closure budget cannot currently tolerate a module split.
  • Moving src/backend.ts or src/runtime-contract.ts. Both are type-only surfaces (value closure of backend.ts is one file) with churn of 53 and 5 commits per 400. Relocating a near-frozen type surface is a rename, not decoupling.
  • Growing @agent-device/contracts as a destination for src/core/** without a separate measurement of its own churn. At 18k LOC and 185 files it is the second hub in the repo.
  • Collocation-only file moves, generic hook bags, and new gate detectors that do not name a measured edge. Per fix/refactor: 2026-09-11 audit execution, successor to #2409 #2531's rules: fix-shaped, not detector-shaped.

Reproduction

pnpm depgraph --out /tmp/depgraph.json, or a throwaway scripts/layering/.probe.ts over listSourceFiles() + resolveImportEdges() restricted to isProductionSourceFile, computing per-bucket outbound pairs, edge kinds (typeOnly/dynamic), and value-only transitive closure from src/daemon/**. Delete the probe after.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions