Skip to content

Commit 76453ad

Browse files
thymikeeclaude
andauthored
refactor: pnpm workspace + @agent-device/kernel pilot (#1490 W0) (#1494)
* refactor: pnpm workspace + @agent-device/kernel pilot (#1490 W0) Extend the workspace with packages/* and move the kernel behind an enforced public API: packages/kernel with nine consumer-earned subpath exports (errors, device, snapshot, contracts, collections, rect, redaction, daemon-error, bounds — the last absorbed from utils as Rect vocabulary). Every kernel import repo-wide becomes the @agent-device/kernel/<sub> specifier; kernel tests move to src/__tests__/kernel/ and exercise the package surface. The root declares the package in devDependencies (workspace:*), tsdown bundles it (noExternal) so the published artifact and its runtime dependency manifest are unchanged. Gate rewiring in the same change, per the W0 brief: - R1 kernel-sink retires (physically subsumed); new R11 package-boundaries guards no-root-back-imports, relative tunnelling past exports maps, undeclared workspace deps, and non-exported subpaths, with runtime resolution pins via import.meta.resolve. - resolveImportEdges and mutation ownership follow workspace specifiers through exports maps, keeping R4 cycle checks, depgraph, and derived test ownership connected across the seam (kernel-errors still owns 495 tests). listSourceFiles includes packages/*/src. - kernel becomes an unranked zone; mutation registry, stryker mutate globs, and the mutation-affected workflow path filter move to packages/kernel/src/errors.ts. - check:affected gains packages/ ownership (manifests fail open); vitest and coverage include packages/*/src; fallow ignores packages/** (its resolver cannot follow workspace specifiers). - The affected-selector CI job installs dependencies: its closure now crosses workspace specifiers, and the R8 relative exception is unsafe for production src files (Node ESM does not realpath, so dual specifier/relative loads would instantiate modules twice). The R8 zero-dep set is pinned empty with that rationale. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: address W0 review — mutation sandbox, exports-map resolution, tsc -b Review findings on #1494, all five: 1. contracts-schema-public.test.ts reads the kernel source at its packages/ path (fs access invisible to the codemod and typecheck). 2. Mutation lane: Stryker sandboxes the tree but pnpm's node_modules symlink resolves @agent-device/* back to the real repo, so mutants in the sandbox never load and vitest.related finds no tests. vitest.mutation.config.ts now aliases each EXPORTED specifier to its source (derived from exports maps, never a wildcard), keeping resolution inside the mutated tree. Validated: kernel-errors module runs end to end (dry run 3,984 tests, mutants killed, exit 0). 3. Layering/depgraph resolve workspace specifiers through the exports-derived map (workspaceSpecifierTargets) instead of reconstructing paths, so '.'-facade packages resolve; the positional fallback remains only for map-less fixtures (P0 pin). 4. Per-package project references implemented: packages/kernel is composite (emitDeclarationOnly -> dist-types, gitignored), the root references it, and typecheck becomes tsc -b — probed to catch type errors on both sides under TypeScript 7 native. 5. R11's relative-route exception now requires membership in an actual R8 zero-dep job closure (zeroDepClosureFiles walks entries), not mere scripts/ placement — closing the dual-instantiation bypass. Also from review discussion: daemon-error moves out of the kernel package to src/client/ — its consumers (cli, client facade) rehydrate wire DaemonErrors client-side; the daemon only produces them. Kernel drops to 8 exported subpaths before any of them ship. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * refactor: one exports-map reader for mutation alias and ownership Fallow flagged workspaceExportAliases (cognitive 15, CRAP 90). The manifest-reading logic already exists as workspaceSpecifierTargets in scripts/layering/package-boundaries.ts, so both the Stryker sandbox alias table and the mutation ownership walker now consume it instead of carrying near-clones. Behavior unchanged; mutation suite 45/45 and changed-code fallow green. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: composite kernel without a root references edge FreeRange runs plain `tsc -p tsconfig.json`, and a root `references` entry makes non-build-mode TypeScript demand the referenced project's built declarations (TS6305) — a standing "build first" tax on every plain -p consumer (fr, editors). Keep the per-package composite project and build it in typecheck (`tsc -b packages/kernel` before the root and examples/sdk passes), but drop the root references edge: root consumption resolves through exports to source, identical to runtime and to the bundler. Probed: plain -p green with no prebuilt output; kernel-side type errors still caught by its own build. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: R11 uses the layering parser; mutation config is a fallow entry Review blockers on #1494: - R11's private single-quote regex could miss a double-quoted or re-export route into packages/*/src. specifierSites now delegates to the layering model's parseImports (both quote styles, side-effect imports, re-exports, dynamic imports), with direct regressions for each formerly-invisible form. - vitest.mutation.config.ts becomes a declared fallow entry instead of a tolerated unused-file finding: the full-repo audit now reports it reachable (unused files 2 -> 1; the remainder predates this PR). FreeRange clean-checkout evidence: with packages/kernel/dist-types and every *.tsbuildinfo deleted, `pnpm check:freerange` reports 0 findings on this head — the TS6305 topology died with the root references edge in the previous commit; check:freerange has no build precondition. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep --------- Co-authored-by: Claude <[email protected]>
1 parent 7f991e4 commit 76453ad

865 files changed

Lines changed: 2021 additions & 1223 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.fallowrc.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
33
"entry": [
44
"tsdown.config.ts",
5+
"vitest.mutation.config.ts",
56
"src/sdk/index.ts",
67
"src/sdk/io.ts",
78
"src/sdk/artifacts.ts",
@@ -33,6 +34,7 @@
3334
"website/rspress.config.ts"
3435
],
3536
"ignorePatterns": [
37+
"packages/**",
3638
"examples/test-app/**",
3739
"scripts/perf/**",
3840
"scripts/layering/**",

‎.github/workflows/ci.yml‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,16 @@ jobs:
124124
- name: Checkout
125125
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
126126

127+
# The selector's entry closure reaches `@agent-device/kernel` workspace
128+
# specifiers through src/utils/exec.ts and diagnostics.ts (#1490 W0), and
129+
# workspace package resolution needs the pnpm link in node_modules. The
130+
# R8 relative-import exception is reserved for scripts, not production
131+
# src files, so this job installs dependencies instead.
127132
- name: Setup toolchain
128133
uses: ./.github/actions/setup-node-pnpm
129-
with:
130-
install-deps: false
131134

132135
# The selector is fail-open and advisory (GitHub CI stays authoritative),
133-
# so the gate only guards the derivation model. Invoked with no deps,
134-
# mirroring the layering guard. R8 (scripts/layering/zero-dep-jobs.ts)
135-
# resolves this bare pnpm script name back to package.json's own command
136-
# to find its entry scripts, so this is not a `pnpm install` for the job.
136+
# so the gate only guards the derivation model.
137137
- name: Check affected-selector model
138138
run: pnpm check:affected:test
139139

‎.github/workflows/mutation-affected.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ on:
2525
# Kernel sources, every src test (ownership is derived, so any test may own
2626
# a kernel — `select` decides, not this filter), and the lane's own tooling.
2727
# scripts/mutation/workflow.test.ts asserts this covers the registry.
28-
- 'src/kernel/errors.ts'
28+
- 'packages/kernel/src/errors.ts'
2929
- 'src/daemon/ref-frame.ts'
3030
- 'src/commands/interaction/runtime/settle.ts'
3131
- 'src/utils/scroll-edge-state.ts'

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,7 @@ android/snapshot-helper/build/
3939
android/snapshot-helper/dist/
4040
android/ime-helper/build/
4141
android/ime-helper/dist/
42+
43+
# Workspace package declaration output (tsc -b project references)
44+
packages/*/dist-types/
45+
*.tsbuildinfo

‎AGENTS.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ the new thing — never to suppress or allowlist it.
9393
- `keyboard dismiss` is the iOS keyboard dismissal path. It may tap safe native controls such as
9494
`Done`, but must not fall back to system back navigation.
9595
- Do not remove shared snapshot/session model behavior without full migration.
96-
- Apple-family target changes keep `src/kernel/device.ts`, `src/core/capabilities.ts`,
96+
- Apple-family target changes keep `packages/kernel/src/device.ts`, `src/core/capabilities.ts`,
9797
`src/core/dispatch-resolve.ts`, `src/platforms/apple/core/devices.ts`, and
9898
`src/platforms/apple/core/runner/runner-xctestrun.ts` in sync.
9999
- iOS simulator-set scoping is iOS-specific: `iosSimulatorDeviceSet` must not hide the host macOS
100100
desktop target when `--platform macos` or `--target desktop` is requested.
101-
- Use `inferFillText` (`src/daemon/action-utils.ts`), `uniqueStrings` (`src/kernel/collections.ts`),
101+
- Use `inferFillText` (`src/daemon/action-utils.ts`), `uniqueStrings` (`@agent-device/kernel/collections`),
102102
and `evaluateIsPredicate` (`src/selectors/predicates.ts`) rather than reimplementing them.
103103
- Do not update `skills/**/SKILL.md` for command behavior or workflow guidance unless the user asks.
104104
Skills are thin routers to versioned CLI help; they must not carry behavior details.
@@ -188,7 +188,7 @@ connect errors, retry policy, or command typing, start in
188188
and must not duplicate backend logic. App/device logs stay in `app.log`; Apple runner and
189189
`xcodebuild` subprocess output belongs in the session-scoped `runner.log`. Preserve the external
190190
grep/tail workflow documented in help/skills.
191-
- Normalize user-facing failures via `normalizeError` (`src/kernel/errors.ts`). Payload contract:
191+
- Normalize user-facing failures via `normalizeError` (`@agent-device/kernel/errors`). Payload contract:
192192
`code`, `message`, `hint`, `diagnosticId`, `logPath`, `details`. Preserve `hint`, `diagnosticId`,
193193
and `logPath` when wrapping or rethrowing. Errors say what failed, why when known, and how to
194194
recover — recovery steps go in `hint` when the action is not obvious.

‎CONTEXT.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ The perfect-shape refactor is complete and merged. Its end-state:
252252
- Apple platform model. Internally `Platform` is `apple` (plus `android`/`vega`/`linux`/`web`) with an
253253
`appleOs` discriminant (`ios | ipados | tvos | watchos | visionos | macos`); the shared Apple engine
254254
lives under `src/platforms/apple/core/` with per-OS leaves under `src/platforms/apple/os/<os>/`.
255-
The public wire stays non-breaking: `PUBLIC_PLATFORMS` (`src/kernel/device.ts`) still emits
255+
The public wire stays non-breaking: `PUBLIC_PLATFORMS` (`packages/kernel/src/device.ts`) still emits
256256
`ios`/`macos` leaf output. See [ADR 0009](docs/adr/0009-apple-platform-consolidation.md).
257257
- Vega platform model. Initial Vega OS support is deliberately **VVD-only**: discovery returns
258258
`VirtualDevice`, and platform capability admission rejects physical Fire TV devices until durable
@@ -270,7 +270,7 @@ The perfect-shape refactor is complete and merged. Its end-state:
270270
it ranks an explicit target spine — as rank groups, lowest (kernel sink) to highest, where `A ◄ B`
271271
means B may not be outranked by A (the back-edge order the gate rejects), NOT that every displayed
272272
import exists:
273-
`kernel ◄ { contracts, request, selectors, platforms, utils, replay, recording, snapshot, screenshot-diff, cloud-webdriver } ◄ { core, providers } ◄ { commands, cli-schema, mcp } ◄ { client, daemon-server, compat, remote, metro, sdk } ◄ daemon-client ◄ cli` —
273+
`{ contracts, request, selectors, platforms, utils, replay, recording, snapshot, screenshot-diff, cloud-webdriver } ◄ { core, providers } ◄ { commands, cli-schema, mcp } ◄ { client, daemon-server, compat, remote, metro, sdk } ◄ daemon-client ◄ cli` (the former rank-0 kernel zone lives in `packages/kernel` since #1490 W0; R11 package-boundaries owns that seam, and R1 kernel-sink is retired as physically subsumed) —
274274
and rejects every back-edge within it. Only `(root)` is unranked (`UNRANKED_ZONES` in
275275
`scripts/layering/model.ts`): it holds the entrypoints and the composition roots that wire the
276276
command surface into the daemon, and R2 forbids `daemon/` from importing `commands/`, so those

‎docs/agents/testing.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ The mapping it encodes, for when you need to run a gate directly or reason about
2828
| Help benchmark cases (`scripts/help-conformance-*.mjs`) | `pnpm exec vitest run scripts/__tests__` (deterministic gates); model-backed: `pnpm bench:help-conformance` (paid LLM calls, local only) |
2929
| `.ad` grammar (`src/replay/script.ts`, gesture arity, replay vars) | `pnpm exec vitest run --project unit-core test/replay-compat` — the frozen replay-compat corpus asserts which released script surfaces still parse; a flipped verdict is edited in `test/replay-compat/manifest.ts`, never in the script. Adding or re-pinning a corpus entry also runs `pnpm check:replay-compat`, which re-derives each entry from its release tag in git history |
3030
| Anything in `src/`, `test/` | `pnpm format` (`skills/` is Markdown-only guidance: oxfmt ignores `**/*.md`, and the affected-check selector classifies it docs-only) |
31-
| A decision kernel or its tests (`src/kernel/errors.ts`, `src/daemon/ref-frame.ts`, `src/commands/interaction/runtime/settle.ts`, `src/utils/scroll-edge-state.ts`, `src/selectors/`) | `pnpm mutation:affected --base origin/main` (minutes; GitHub runs it per PR — see the mutation ratchet section) |
31+
| Workspace package source (`packages/*/src/**`) | Root format/lint/typecheck plus layering (R11 package-boundaries); Vitest resolves affected tests through the module graph; package manifests/tsconfigs fail open to the full set |
32+
| A decision kernel or its tests (`packages/kernel/src/errors.ts`, `src/daemon/ref-frame.ts`, `src/commands/interaction/runtime/settle.ts`, `src/utils/scroll-edge-state.ts`, `src/selectors/`) | `pnpm mutation:affected --base origin/main` (minutes; GitHub runs it per PR — see the mutation ratchet section) |
3233

3334
Two traps worth naming:
3435

‎package.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
"check:affected:test": "node --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/check-affected/run.test.ts",
130130
"check:coverage-changed": "node --experimental-strip-types scripts/coverage-changed/run.ts",
131131
"check:coverage-changed:test": "node --experimental-strip-types --test scripts/coverage-changed/model.test.ts scripts/coverage-changed/run.test.ts",
132-
"check:layering": "node --experimental-strip-types --test scripts/layering/model.test.ts scripts/layering/zone-policy.test.ts scripts/layering/daemon-modularity.test.ts && node --experimental-strip-types scripts/layering/check.ts",
132+
"check:layering": "node --experimental-strip-types --test scripts/layering/model.test.ts scripts/layering/zone-policy.test.ts scripts/layering/daemon-modularity.test.ts scripts/layering/package-boundaries.test.ts && node --experimental-strip-types scripts/layering/check.ts",
133133
"depgraph": "node --experimental-strip-types scripts/depgraph/build.ts",
134134
"depgraph:test": "node --experimental-strip-types --test scripts/depgraph/model.test.ts scripts/depgraph/affected.test.ts",
135135
"check:production-exports": "fallow dead-code --config fallow-production-exports.json --production --unused-exports --fail-on-issues",
@@ -145,7 +145,7 @@
145145
"check:unit": "pnpm check:contention-retry && pnpm test:unit && pnpm test:smoke",
146146
"check": "pnpm check:tooling && pnpm check:fallow && pnpm check:unit",
147147
"prepack": "pnpm check:mcp-metadata && pnpm package:npm",
148-
"typecheck": "tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
148+
"typecheck": "tsc -b packages/kernel && tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
149149
"test-app:install": "pnpm install --dir examples/test-app",
150150
"test-app:start": "pnpm --dir examples/test-app start",
151151
"test-app:ios": "pnpm --dir examples/test-app ios",
@@ -244,6 +244,7 @@
244244
"yaml": "^2.9.0"
245245
},
246246
"devDependencies": {
247+
"@agent-device/kernel": "workspace:*",
247248
"@chenglou/freerange": "^0.0.1",
248249
"@stryker-mutator/core": "9.6.1",
249250
"@stryker-mutator/vitest-runner": "9.6.1",

‎packages/kernel/package.json‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "@agent-device/kernel",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"description": "Foundational vocabulary for agent-device: errors, device identity, snapshot model, shared contracts. Internal workspace package bundled into the published agent-device artifact.",
7+
"exports": {
8+
"./errors": {
9+
"types": "./src/errors.ts",
10+
"default": "./src/errors.ts"
11+
},
12+
"./device": {
13+
"types": "./src/device.ts",
14+
"default": "./src/device.ts"
15+
},
16+
"./snapshot": {
17+
"types": "./src/snapshot.ts",
18+
"default": "./src/snapshot.ts"
19+
},
20+
"./contracts": {
21+
"types": "./src/contracts.ts",
22+
"default": "./src/contracts.ts"
23+
},
24+
"./collections": {
25+
"types": "./src/collections.ts",
26+
"default": "./src/collections.ts"
27+
},
28+
"./rect": {
29+
"types": "./src/rect.ts",
30+
"default": "./src/rect.ts"
31+
},
32+
"./redaction": {
33+
"types": "./src/redaction.ts",
34+
"default": "./src/redaction.ts"
35+
},
36+
"./bounds": {
37+
"types": "./src/bounds.ts",
38+
"default": "./src/bounds.ts"
39+
}
40+
}
41+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Rect } from '../kernel/snapshot.ts';
1+
import type { Rect } from './snapshot.ts';
22

33
export function parseBounds(bounds: string | null): Rect | undefined {
44
if (!bounds) return undefined;

0 commit comments

Comments
 (0)