refactor: pnpm workspace + @agent-device/kernel pilot (#1490 W0) - #1494
Conversation
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep
|
Size Report
Startup median (7 runs, lower is better):
Top changed chunks: no changes in the largest emitted chunks. |
…c -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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep
|
All five review findings addressed in the latest push:
Also from the review discussion: Local gates on the new head: full unit suite 4,868 passed, layering chain 46/46 with R11 at 8 subpaths / zero back-imports, depgraph 22/22, mutation suite 45/45, typecheck ( Generated by Claude Code |
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep
|
Not ready. The package extraction direction is sound, but two blockers remain. First, R11's specifier scanner only recognizes single-quoted |
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep
|
Both blockers addressed at R11 scanner completeness — FreeRange / fresh-checkout topology — this was fixed by Fallow reachability — Local gates on Generated by Claude Code |
|
Clean re-review at |
Summary
W0 of #1490: the workspace gains
packages/*, and the kernel becomes the pilot package with an enforced public API — proving the whole toolchain on the module the layering spine already certified dependency-free.packages/kernel(@agent-device/kernel,private: true): nine consumer-earned subpath exports (errors,device,snapshot,contracts,collections,rect,redaction,daemon-error,bounds), eachtypes+defaultpointing at source — NodeNext resolves them fortsc, Node loads them under--experimental-strip-types(the pnpm symlink realpaths outsidenode_modules), and tsdown bundles them (noExternal).utils/bounds.tsmoves in as Rect vocabulary per the W0 audit;timeouts/xml/version/execstay root-side (audit verdicts in the issue thread — none of them is honestly kernel vocabulary).@agent-device/kernel/<sub>(~800 files, mechanical). Kernel tests move tosrc/__tests__/kernel/and exercise the package's public surface (they need root test-utils/replay fixtures, which a package may not import).src/at all). Newscripts/layering/package-boundaries.ts(R11) guards the four bypasses resolution alone can't see: package→root imports, relative tunnelling intopackages/*/src(the sole R8-script exception derives permitted targets from theexportsmap), undeclared workspace deps, and non-exported subpaths — plus runtimeimport.meta.resolvepins that deep specifiers are resolution errors.resolveImportEdgesand mutation ownership resolve workspace specifiers throughexportsmaps (derived, never hand-listed) — without this, kernel-errors would have silently lost all 495 of its owned tests and the P0-pinned depgraph contract (test: pin daemon modularity migration contracts #1487) stays satisfied.listSourceFilesincludespackages/*/src, kernel classifies as a deliberately unranked zone, and the mutation registry/stryker globs/mutation-affected.ymlpath filter follow the move (caught by the workflow-coverage test).check:affectedgainspackages/ownership with manifests failing open; vitest + coverage includepackages/*/src; fallow ignorespackages/**(its resolver cannot follow workspace specifiers); Testing Matrix row added.affected-selectornow installs dependencies. Its entry closure reaches workspace specifiers throughsrc/utils, and the R8 relative-import exception is unsafe for production files — Node's ESM loader does not realpath, so a module loaded both relatively and via specifier instantiates twice in one process (duplicateAppError, brokeninstanceof). That leaves zero zero-dep jobs; the R8 vacuousness guard pins the empty set with this rationale instead of passing silently.Disclosed deviation from the brief: per-package TypeScript project references are not used —
allowImportingTsExtensionsrequiresnoEmit, which conflicts with composite declaration emit. The root tsconfig includespackages/in one graph instead (exports maps still resolve per-package); revisit when a package needs an independent build.Validation
pnpm check:layering— R2–R11 green: 939 source files (packages included), R6 at 7, R9 at 102, R10 unchanged, R11: 1 package behind 9 exported subpaths, zero root back-imports; 46 layering model/policy/R11 tests pass, including the runtime resolution pins.unit-core+subprocess-stub) green on package specifiers; mutation suite 45/45 (including the workflow path-filter coverage test that caught themutation-affected.ymlupdate); depgraph 22/22 against the P0-pinned contract; selector suites 36/36.pnpm build+check:bundle-owner-files+pnpm test:smoke(24 passed, incl. packaged-CLI and every-export-subpath smokes): zero@agent-devicestrings in emitteddist/JS, runtime dependency manifest unchanged (@limrun/api,yamlonly — the workspace dep is a devDependency).check:fallow(no issues, 858 changed files) andcheck:production-exportsgreen; format/lint/typecheck (root + examples/sdk) green.pnpm checkaggregate was still completing at push time; every constituent gate above passed individually. Size report should read ≈ 0 — worth eyeballing the chunk table, since the four kernel sdk-chunk regexes moved topackages/paths.Part of #1490 (W0 checkbox). Next: W1a (
@agent-device/contracts+ProviderDeviceRuntimeinversion) once this lands.🤖 Generated with Claude Code
https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep
Generated by Claude Code