refactor(contracts): move single-owner modules out of @agent-device/contracts - #2357
Conversation
… zone Six @agent-device/contracts modules had no consumer outside the root zones, so the shared vocabulary package carried types only the daemon and root composition ever read. Each one moves to the zone that owns it and every consumer switches to the owning module; no re-export stays behind at the old contracts path. - perf-runtime-plan, snapshot-timeout-evidence, platform-resource-cleanup -> src/daemon - daemon-owner-cleanup -> src/ - interaction-error -> src/core wait-runtime-plan stays in contracts: @agent-device/command-registry consumes it, so it is not root-only after the registry package landed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdXHhx9NkfH1PT4XjYe7tE
… platform package Four modules in @agent-device/contracts had exactly one consuming package, so the shared vocabulary carried Android- and Apple-specific shapes no other zone could use. Each moves into the package that owns it, with every consumer switched to the owning module and no re-export left at the old contracts path. - android-helper-artifacts -> platform-android/src/helper-artifacts.ts - android-touch-plan -> platform-android/src/touch-plan-lowering.ts, which also retires the package-local touch-plan.ts re-export barrel that existed only to give the contracts module a local name - snapshot-presentation -> platform-android/src/snapshot-presentation-node.ts (renamed to keep the package's existing Android-specific snapshot-presentation.ts distinct) - apple-multitouch-support -> platform-apple/src/multitouch-support.ts APPLE_OS_DISPLAY_NAMES folds into gesture-admission.ts, its one remaining contracts caller, so both gesture refusals still share one copy of the wording without a new contracts subpath for a table its own doc calls non-public. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdXHhx9NkfH1PT4XjYe7tE
replay-divergence.ts mixed the wire vocabulary every zone reads with the sanitizing, bounding and reporting implementation only root zones call. The ten value consumers are all root (daemon replay, the session replay coordinator, the daemon client lifecycle, the replay-test reporter, the command error projection, and the MCP tool error), so the implementation moves to src/core/replay-divergence.ts and carries its test unchanged. The types stay in contracts and keep the @agent-device/contracts/divergence subpath, which packages/ad-replay and packages/selectors type-import. ReplayVarScrubEntry follows the implementation: it is the sanitizer's own parameter shape, not part of the divergence wire report. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdXHhx9NkfH1PT4XjYe7tE
The nine relocated modules no longer live in @agent-device/contracts, so its exports map drops their subpaths (118 -> 109) and scripts/layering/contracts-exports.snapshot.json is regenerated from the manifest, which is what R11 package-boundaries diffs the live surface against. The two resolution assertions naming the retired snapshot-presentation and snapshot-timeout-evidence subpaths go with them; interaction, snapshot and react-native-overlay still cover both the direct-module and facade shapes the assertions were there to prove. The property tests that needed fast-check left with snapshot-presentation and replay-divergence, so the dependency moves too: contracts drops it and platform-android declares it, as fallow's unused-devDependency check reports. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdXHhx9NkfH1PT4XjYe7tE
|
Size Report
Startup median (7 runs, lower is better):
|
thymikee
left a comment
There was a problem hiding this comment.
Adversarial review from the #2335 coordinator, at 6bf0b3d. I re-derived the load-bearing claims from the tree. Everything checks out, and the wait-runtime-plan catch is the most valuable thing in this PR.
Verified independently against base fc40090:
- Contracts production source 18,217 → 16,992 lines (−1,225), recomputed with my own count, not yours — matches your figure exactly and clears the ≥1,000 requirement.
- All nine moved modules are absent from
packages/contracts/src/: no re-export shim survives at any old path. - Zero suppressions anywhere in the diff — no
fallow-ignore, noAPPROVED_OVER_CEILING, nots-expect-error, no disable comments, no skipped tests. - Commit structure is exactly right: three implementation commits, then one final
chore(gates).
The wait-runtime-plan.ts correction is right, and it is subtler than the PR body says.
I confirmed it: packages/command-registry/src/__tests__/wait-runtime-execution.test.ts:3 imports @agent-device/contracts/wait-runtime-plan. Moving that module to src/daemon as #2339's table instructs would make a workspace package import root src/ — an R11 violation, and a gate failure you would then have been tempted to explain away.
What makes it worth calling out: #2339's table was correct when it was written and became wrong underneath you. It was measured on main at 27a97ee, where that test lived at src/core/command-descriptor/__tests__/ — genuinely root-only. A's packaging moved the test into a package and changed the fact. You verified against your actual base instead of the issue, which is the only reason this PR does not ship an R11 break.
That generalises, and I am carrying it forward: every table in this umbrella was measured at 27a97ee, and main has since absorbed #2349 and #2347, with #2348 and #2346 close behind. #2341 and #2342 inherit tables with the same staleness risk, and #2342's seam list is hand-written in precisely this style. Re-verification against the real base is now a standing requirement for the rest of the wave, not a nicety.
Your snapshot-presentation.ts correction (2 platform-android consumers, not 6) is the third issue-table defect this wave after #2340's two wrong filenames. Worth noting the pattern rather than fixing each silently — which you did, correctly, in the body.
Judgement calls I agree with: keeping interaction-guarantees.ts (AGENTS.md names it a declaration site; its non-daemon consumers are repo tooling, not a root zone, and the line target is met without it); folding APPLE_OS_DISPLAY_NAMES into gesture-admission.ts rather than minting a subpath for one caller; and saying plainly that the replay-divergence.ts implementation/type split "is not expressible as a rename" instead of dressing it up as one. ReplayVarScrubEntry following the implementation is right — it is the sanitizer's parameter shape, not wire vocabulary.
The pre-existing 19-line error.ts ↔ tool-error.ts clone that fallow surfaces only because both files changed an import line is correctly left alone; adopting it here would be scope creep into a move PR.
Nothing blocking. CI is still settling — the chmod 0500 trio is the known uid-0 artifact of this container and fails identically on the base, and the alert-related device lanes have been intermittently red across this whole wave independently of any of these branches.
Generated by Claude Code
|
Smoke Tests failure on Failing check: Smoke Tests — Why it is not this PR's — I checked the mechanism rather than assuming, because this PR does move modules into
That leaves a slow native compile on a loaded macOS runner. Re-running once; if it fails again I will say so rather than re-running a second time. For the record, this is the third distinct device/native lane failure across this wave (#2346 Android emulator wait-timeout, #2346 iOS alert XCUITest, this one a native-build hook timeout), on three branches that between them are a type split, a contracts move and a registry move. None share a mechanism with their failures. The macOS/device lanes look load-sensitive right now, which is worth a look independently of #2335. Generated by Claude Code |
Summary
Closes #2339. Stacked on #2348 (child A) — base is
claude/2336-command-registry-package, notmain.Nine modules leave
@agent-device/contractsas git renames, no re-export shim at any old path. Contracts production source drops 18,217 → 16,992 lines (−1,225) across 190 → 180 files (find packages/contracts/src -name '*.ts' ! -name '*.test.ts' ! -path '*__tests__*' | xargs wc -l, base vs. HEAD). Exports map 118 → 109 subpaths, snapshot regenerated. 76 files changed; every moved module isR091–R100undergit diff -M90% --name-status origin/claude/2336-command-registry-package...HEAD.Corrections to the issue's tables, verified against the tree:
wait-runtime-plan.tskept. A'spackages/command-registry/src/__tests__/wait-runtime-execution.test.tsimports it, so it is not root-only. Moving it would make a package import rootsrc/.snapshot-presentation.tshas 2 platform-android consumers, not 6 (snapshot-presentation.ts,ui-hierarchy-builder.ts). Still single-package; moved.android-touch-planlands astouch-plan-lowering.tsand retires the package-localtouch-plan.tsre-export barrel it fed;APPLE_OS_DISPLAY_NAMESfolds intogesture-admission.ts, its one remaining contracts caller, so both gesture refusals share one copy of the wording without a new subpath.fast-checkfollows the property tests from contracts to platform-android.replay-divergence.ts: implementation + its test tosrc/core, types stay on the/divergencesubpath.ReplayVarScrubEntryfollows the implementation — it is the sanitizer's parameter shape, not wire vocabulary. A split is not expressible as a rename.interaction-guarantees.ts: kept. AGENTS.md names it a declaration site and points readers at that path; its non-daemon consumers are the interaction-contract test project andscripts/depgraph/affected-run.ts, i.e. repo tooling rather than a root zone. The ≥1,000-line target is met without it.Validation
Tested SHA
6bf0b3d37c5c8541f6faffbfdb8fb5e659cab58e(the pushed head), afterpnpm install --frozen-lockfile && pnpm build.pnpm check:affected --run: format, lint, typecheck, layering, di-seams, fallow, mcp-metadata, build, package (R11 package-boundaries), integration-node, macos-coverage all pass.vitest-related: 9696 passed, 3 failed —app-log-session-resource,durable-capture-resource,capture-kit/durable-capture/adoption, all asserting achmod 0500directory is unwritable, which this container ignores because it runs as uid 0. All three are untouched by this branch and fail identically on the base.The gates after that point were run individually and pass: replay-compat, daemon-wire-compat (after
git fetch --unshallow --tags), affected-selector, gate-manifest, depgraph, tmpdir-leaks, coverage-model, wire-compat-model, production-exports, bundle-owner-files, command-docs, agent-guidance, xctest-selection, provider-integration, integration-progress, maestro-conformance, mutation-model, fixture-cache.Fallow reports one pre-existing 19-line clone (
src/commands/output/error.ts↔src/mcp/tool-error.ts), byte-identical on the base and only in scope because both files changed one import line. No gate suppressed.🤖 Generated with Claude Code
https://claude.ai/code/session_01SdXHhx9NkfH1PT4XjYe7tE
Generated by Claude Code