Skip to content

Extract src/platforms into the platform packages (umbrella: substrate wave → per-family moves → seam retirement) #2082

Description

@thymikee

Goal

Finish the extraction that #1490 started: src/platforms (181 production files, ~28.4k lines) moves behind the six packages/platform-* exports maps, and the platforms src zone retires from the spine. After this, every platform implementation lives in a workspace package held by R11/R13 instead of the R3 zone seam.

Numbers below come from the repo's own gate model (scripts/layering + scripts/depgraph, extended with a workspace/exports-usage scan) at c794c11.

Why it is close

src/platforms already gets almost everything from packages — kernel (184 import sites), contracts (108), and it imports @agent-device/platform-apple/-android directly. The daemon no longer imports it at all (#2072). What still pins it to root src/ is small and concrete.

Blockers, measured

1. Shared substrate: 47 root-src files (~5.6k lines) that platforms imports at 136 sites. A package cannot import root src/ (R11, no edge kind tolerated), so these must sink below the boundary first. The set is transitively closed over root src — it depends only on itself plus existing packages — so it can move wholesale:

  • 40 files in src/utils — led by exec.ts (37 sites), diagnostics.ts (21), timeouts.ts (10), parsing.ts, retry.ts, the owned-process/archive families
  • src/request/cancel.ts, src/request/progress.ts
  • src/snapshot-quality/verdict.ts, src/snapshot-quality/backend-capabilities.ts
  • src/snapshot/snapshot-occlusion.ts, mobile-snapshot-semantics.ts (snapshot-desktop-surface.ts turned out to be an upward edge itself — it dynamically imports platform implementations, so it belongs to W2, not the substrate)

Destinations follow the repo's own signals: host/process mechanics into capture-kit (R18's violation hint names it as the mechanics home), pure kernel-typed geometry into kernel, snapshot semantics/vocabulary into contracts.

2. Four upward edges. Left in place they drag the whole host layer (the naive closure balloons from 47 files to 286):

  • src/platforms/android/app-helpers.ts → dynamic import of src/platform-runtime.ts (composition back-reference; invert to injection)
  • src/platforms/web/agent-browser-network.ts and src/platforms/web/provider.ts → type-only imports of src/backend.ts (types move to contracts; R11 grants no type-only exemption)
  • src/snapshot/snapshot-desktop-surface.ts → dynamic imports of platforms/linux/snapshot.ts and platforms/apple/os/macos/helper.ts (desktop dispatch; invert or relocate with its family)

3. Shared cross-family files need an owner — and family dirs import them (20 import pairs), so this must land before the family moves. ~20 files at src/platforms/ root (install-source*, toolchain-probe, boot-diagnostics, perf-utils, appearance, command-attempts, setting-state, …) serve several families; a family package cannot import them while they stay in root src. They need a substrate home (capture-kit or a dedicated package) first.

4. Policy follows the files. R3 (platforms-seam) is keyed to the src zone, which disappears; the CLI cold-start guarantee must be re-expressed at package level (extend the R13/composition policy: platform packages statically importable only from the composition files and interactor seam, dynamic/type-only elsewhere). TARGET_DAG_RANK drops platforms; the UNRANKED_ZONES comment already documents this retirement pattern from the kernel/contracts extractions.

Waves (one PR each, deletion of the old location included)

  • W1 substrate: move the platforms-blocking closure into capture-kit (38 utils mechanics modules + png-worker + request-cancel/request-progress), kernel (rect-center, scroll-indicator), and contracts (snapshot-occlusion, mobile-snapshot-semantics, snapshot-quality-verdict, snapshot-quality-backend-capabilities); full consumer re-point, no shims; the keyed-lock shim retires — branch refactor/platform-substrate-wave
  • W2 upward edges: the composition inversion in android/app-helpers.ts; web backend types → contracts; desktop-surface dispatch inversion
  • W3 shared platform files → their substrate home (prerequisite for every family move)
  • W4 apple (largest: ~81 files incl. core/) → packages/platform-apple
  • W5 android (~60 files) → packages/platform-android
  • W6 harmonyos / linux / vega / web (small; parallelizable per family)
  • W7 seam retirement: retire R3, extend the package-level seam policy, drop platforms from the spine, update ADR/docs

After W1–W3, the family waves (W4–W6) are mutually independent — different source dirs, different target packages — and can run in parallel, with two serialization rules: a handful of shared platform-runtime-* host files get one-line specifier swaps from several family PRs (merge order is arbitrary, conflicts are trivial), and any wave touching the same package as the operation-catalog work (see coordination below) finishes before the other starts on that package.

Each family wave: files move behind the package's exports map (named-export façades per the R11 facade gate), colocated tests move along, and the ~110 external import sites (≈35 platform-runtime-* host files with dynamic imports, core/interactors/*, sdk/*) re-point to @agent-device/platform-* specifiers.

Coordination with the post-#1739 cleanup lane (#2081 and successors)

The two efforts are file-disjoint almost everywhere; the contract to keep it that way:

  • Sequencing: the platform-runtime-* host-surface deepening (item 6 of that plan) starts after W4–W6 land — the family moves shrink its inbound/local import counts and sink the root utilities it still leans on, so deepening gets easier, and running both concurrently on the same ~35 host files is the main conflict generator.
  • Per-package serialization: the operation-catalog work rewrites packages/platform-*/src/runtime.ts and contracts' operations vocabulary; the waves here add mechanics files and exports entries to the same packages. Whoever starts on a given package finishes before the other touches it.
  • Shared-file watchlist (small, trivial conflicts, but worth knowing): packages/contracts/package.json exports map, packages/platform-*/package.json, scripts/layering/check.ts wiring + rule ids, src/platform-runtime.ts / platform-runtime/request-providers.ts.

Verification per wave

pnpm check:layering (R11 package boundaries, facade exports, R13), pnpm typecheck, pnpm lint, pnpm test:unit, pnpm build + scripts/check-package.ts (published-tarball soundness), cold-start budget (src/__tests__/eager-closure-budgets.ts) since the R3 seam exists to protect CLI startup, and the depgraph report to confirm the platforms zone shrinks to zero by W6.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions