Skip to content

refactor: cut the layers and files one feature change must touch (successor to #2278 and #2335) #2409

Description

@thymikee

Purpose

Reduce the vertical cost of a feature or fix: the number of ownership layers and files one change must touch. Successor to #2278 (residual daemon coupling; open children #2333, #2334) and #2335 (decoupling wave 3). Those waves moved code into packages and cut daemon→root edges; this umbrella targets what a contributor still pays per change.

The median change is already at its floor. Half of feature commits touch 4 to 5 production files in one module plus their tests. The cost is in the upper quartile: end-to-end features that cross the command pipeline (contracts → cli-schema → commands → core → daemon → platform → native → tests).

Baseline

Measured on main at e7d97f7dfe over the last 1000 non-merge commits (2026-06-29..2026-09-09; 912 edit code). Files = A/M/D source files from git log -M -C --name-status; pure renames, lockfile, changelog, dist and baselines excluded. Layers = fixed 10-way partition: contracts, cli-schema (+cli), commands (+command-registry), core, daemon, platform-ts (src/platforms, packages/platform-, src/platform-runtime), native (apple, android, linux), client/mcp/sdk, kernel/kits, test/ dir. The partition is stable under file moves inside a layer.

Recent 456 code commits:

measure feat (n=49) fix (n=137) feat+fix refactor (n=150)
files p50 / p75 / p90 8 / 17 / 33 25 / 62 / 121
layers p50 / p75 / p90 2 / 5 / 7 1 / 3 / 4 2 / 3 / 6
commits spanning ≥4 layers 39% 17% 23%
commits touching ≥2 packages 17% 41%

Across the four quarters: median files per code commit 8 → 10, production files 4 → 5, test files 3 → 4, feat+fix median 7 → 8; median modules 3 → 3; TS files 1248 → 3218 (3586 at HEAD); packages 0 → 23 holding 44% of TS files. Relative edit density (median files per 1000 TS files) 6.6 → 3.1 is context only: it falls because the denominator grew.

Layer presence in feature commits: daemon 65%, contracts 37%, cli-schema 37%, commands 35%, platform-ts 33%, core 29%.

Counterfactual replay (recent 186 feat+fix): removing every hand-maintained table, manifest and config edit moves the median from 8 to 8 and the mean from 13.2 to 13.0. Files-per-PR at the median is not a lever.

Targets

Measured over the first 100 feat+fix commits after the last child merges, then again after 100 more:

  • feat layers p75 ≤ 3 (from 5); share of feat commits spanning ≥4 layers ≤ 25% (from 39%).
  • feat+fix files p75 ≤ 14 (from 17). Median stays 8; a lower median is not forecast.
  • feat+fix commits touching ≥2 packages ≤ 10% (from 17%).
  • No loss of test cases, platform coverage classifications, or typed capability-denial behaviour.

Children

F/L = files/layers the same eligible change costs before the child.

# Child Scope and acceptance (measurable from git) Effort Expected effect
1 #2410 Command option declared once Replay #1665 (custom-action flags: 42 files, 8 layers) and #1693 (16 files, 4 layers) as hop traces per #2278's method (policy / orchestration / translation / adapter / pass-through). Collapse pass-through hops so an option is declared in the command descriptor (@agent-device/command-registry) and derived for cli-schema flags, MCP tool schema and daemon request flags. Acceptance: a replayed option addition touches contracts + descriptor + handler + tests; no edit in cli-schema or mcp parity tests. L L −2..−3 on option additions (≈39% of features span ≥4 layers). Hoped-for; commit only after the trace shows removable pass-through hops.
2 #2411 Command-owned coverage declaration One declaration per public command carrying all six platform classifications; the six test/integration/*-e2e/coverage-manifest.ts become projections. Replay one historical command addition: coverage inputs 6 → 1 with the same evidence and gap rows. M F −5 per new public command. L unchanged. Mechanical after the pilot; event is rare.
3 #2412 Additive capability facts, one family Operations are already Partial; facts are required per operation (packages/contracts/src/platform-runtime.ts:148). Pilot one family with a typed unavailable default (as touch-runtime.ts already does for tapRef/hoverRef/fillRef). Replay a single-platform addition; stop if fewer than two unsupported-owner edits are avoided. M/L F −2..−4, L −1..−3 on single-platform additions. Conditional. Main lever for the ≥2-package share.
4 #2333 lifecycle participation, #2334 open-target planning As specified there. M each F −0..1, L −0..1. No aggregate credit until observed.
5 #2402 canonical fixtures One definition per duplicated helper. S/M 2 edits → 1 on fixture changes. Mechanical.
6 Targeted test collocation Classify the 96 direct src/__tests__ tests; a 20-file sample shows 6 clean single-subject production-unit candidates. Move only those with one owner; diff is moves and import paths only. M F unchanged. Modules −1 when the owner is already touched. Navigation benefit; report separately.
7 #2353 oversized daemon tests As specified there. M Zero credit (may add a file). Maintainability.
8 #2413 Residual scripts bookkeeping audit Ten recent feat/fix commits touching scripts/; each proposed removal must replay as F −1 without replacing independent evidence. Close with no work if none qualify. S Forecast 0.

Order: 5 → 2 → 3 → 1 (needs the trace first; can start in parallel with 3) → 4 → 6 → 7 → 8.

Before / after estimate

metric before after (conservative) after (if child 1 lands)
feat+fix files median 8 8 8
feat+fix files p75 / p90 17 / 33 15 / 30 14 / 27
feat layers p75 / ≥4-layer share 5 / 39% 4 / 33% 3 / 25%
feat+fix ≥2 packages 17% 12% 10%
refactor files p50 (migration tail) 25 ends with the extraction program

Illustrative 100-PR workload: 2 public-command additions (−10 files), 2 qualifying capability additions (−4..−8), 2 shared-fixture changes (−2), 10 option additions at −2..−3 layers each (−20..−30 files including tests). Total −36..−50 file edits, i.e. 0.4–0.5 files per PR on average, all of it in the upper quartile. Without child 1: 0.16–0.2 files per PR.

Not in scope

  • Generic self-registration, lifecycle hook bag or service locator (rejected in refactor(daemon): audit and retire residual semantic platform coupling #2278).
  • Defaults that hide an unclassified capability or imply success.
  • Regenerating Maestro conformance fixtures during ordinary tests (they are pinned upstream JVM evidence).
  • Wholesale test relocation; bulk package creation; optimizing graph density by renaming.
  • vitest.config.ts globbing: already done (packages/*/src/**/*.test.ts).
  • Lowering the median files per PR. Smaller files raise it by design; the size ratchets and this umbrella pull in opposite directions at the median and that is accepted.

How to re-measure

Freeze the script (scripts/-free: a one-off under the issue) and the exclusions before the first child merges. Report per 100-commit window from git: files p50/p75/p90 with production/test split, layers p50/p75 and ≥4-layer share for feat, ≥2-package share for feat+fix, refactor commits separately. Publish as a CI artifact or issue comment, delta-only; no committed baseline table and no per-PR gate. Follow renamed hotspots (registry.ts still carries RAW_COMMAND_DESCRIPTORS in its package: 3 edits in 48 commits since the move, not zero). Attribute savings to concrete diffs: an option added without a cli-schema edit, a command added with one coverage declaration, a platform addition that left the other owners untouched. Close only on measured child outcomes.

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