* refactor: move runtime resource mechanics out of daemon
* refactor: move Apple resource access out of daemon
* chore: enforce the terminal daemon platform boundary
* 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep
---------
Co-authored-by: Claude <noreply@anthropic.com>
R1-R3 were three hand-written predicate functions. Each was short, but each
buried its boundary in control flow: you had to read the early-returns to learn
that R3 tolerates dynamic imports, or that R1 opens exactly one door. They are
now data in scripts/layering/zone-policy.ts -- which zones a boundary governs,
which import kinds it tolerates, which path prefixes are its declared seam --
walked by one small evaluator. A fourth zone boundary becomes a table entry
rather than a fourth predicate to keep consistent with the other three.
R1 is deliberately two entries rather than one with a special case, because
"kernel may import contracts type-only" and "kernel may import nothing else at
all" are two statements, and writing them separately is what makes the single
open door visible.
The refactor exposed a real gap: checkLayeringRules had NO unit test. The only
thing exercising R1-R3 was the real tree, which is clean, so a rule that had
silently stopped matching would have looked exactly like a rule being obeyed.
zone-policy.test.ts now asserts each boundary fires and each documented
exemption holds, including that src/daemon/client/ is excluded from the daemon
seam. Verified end-to-end by injecting one violation per rule plus an exempt
file: the gate reports 4 zone-policy violations (R3 twice, catching the
daemon/client case), ignores the type-only and dynamic edges, and exits 1.
Also records in docs/dependency-graph-findings.md the result of spiking
eslint-plugin-boundaries under oxlint's jsPlugins, so nobody repeats it. It
does work -- jsPlugins loads npm ESLint plugins with no ESLint install, and
R1-R3 are all expressible once you know importKind: "value" and
settings["boundaries/dependency-nodes"]. Not adopted: no ratchet mechanism (the
thing that took R6 from 61 to 7 incrementally), it cannot express R4-R9 so the
architecture would be defined in two places, it misreads inline `{ type Foo }`
specifiers as value imports (one false positive on providers/limrun/android.ts),
message interpolation renders empty under the current selector syntax, and it
costs 230 transitive packages on an API documented as alpha.
No behaviour change: 932 source files, R6 = 7, R7 = 41 fields, R8 clean,
R9 = 102, all identical to before.
Claude-Session: https://claude.ai/code/session_01Bfu8HofkhybiAm5LECfqur
Co-authored-by: Claude <noreply@anthropic.com>