Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Status: Accepted Date: 2026-04-22 Participants: Platform Architecture (cloud agent draft, Jeremy Bloom approval) Related: ADR-008, ADR-010, constitution §1

Context

The repo is a single Vite SPA with 12 cores, ~2,100 platform files, ~8,300 source files in src/ (per the dev-velocity baseline captured in PR #138). The recommendations doc (docs/recommendations/DEV_VELOCITY_AND_ARCHITECTURE_PLAN_2026-04-22.md, PR #137, § 5.4) calls for converting cores into real workspace packages so:
  • A PR touching one core invalidates Turborepo cache only for that core.
  • Architecture boundaries (cores depend only on PF, CL is downstream) become enforced by the workspace dependency graph instead of a custom audit script.
  • Future Phase 3 microfrontends have a clean per-zone entry point.
The original recommendation also proposed moving every src/cores/{core}/** file to packages/core-{core}/src/** and rewriting every @/cores/... import. That delivers the strongest TS-project-references win but at very high cost (~8,300 files moved, ~10,000+ imports rewritten, 12+ PRs).

Options Considered

Option A: Full physical migration

  • Move every src/cores/{core}/** to packages/core-{core}/src/**.
  • Rewrite all @/cores/{core}/... imports.
  • Configure each package with tsconfig.json composite: true for per-package incremental typecheck.
  • Land 12+ separate PRs, one per core.
Pros: Strongest possible incremental-typecheck wins (each core compiles in isolation; warm typecheck on an untouched core is ~1 s). Clearest physical separation. Cons: Massive churn (every IDE, code-search shortcut, and git blame baseline shifts). High risk of subtle regressions during file moves. Requires a long stop-the-world freeze on prod. Reversal would be even more painful than the migration itself.

Option B (chosen): Workspace overlay, no source moves

  • Add packages/{platform,shared,core-*}/package.json files declaring tier, source directory, and dependency surface.
  • Source files stay in src/ unchanged.
  • Existing path aliases (@/cores/hr/*src/cores/hr/*) keep working.
  • A new audit-package-boundaries.ts enforces the dependency graph.
  • The existing check-architecture.js continues to enforce import-statement-level boundaries.
Pros: ~14 files added (13 package.json + the audit script + docs), zero source files moved, zero imports rewritten. Reversible by deleting packages/. Captures all the workspace-graph wins (Turborepo --filter, npm ls --workspaces, structural boundary audit, foundation for future codegen). No risk to live features. Cons: Does not enable per-package tsc --build (we still have one TS project; warm typecheck remains ~8 s rather than ~1 s per untouched core). Two sources of truth for “where does HR live” (the src/cores/hr/ directory and the packages/core-hr/ package metadata) — but the linkage is one-line and explicit.

Option C: Hybrid — cores stay in src/, but each has its own tsconfig.json with TS project references

Considered briefly. Rejected because TS project references with composite: true require declaration-file emission, which conflicts with the app’s noEmit: true. Workable, but adds non-trivial config that needs careful per-build coordination. Not worth the complexity until Option B’s ceiling is hit.

Decision

Adopt Option B. Phase 2 ships the overlay packages with zero source moves. A future Phase 2.B (per-core PRs) can perform the physical migration if/when measurements show Option B’s ceiling has been reached.

Consequences

Positive

  • Workspace dependency graph enforced by npm + the new audit-package-boundaries script — closes the loophole where a developer could write import {} from '@/cores/cl/...' inside src/cores/hr/ without triggering a metadata violation (the import-scan still catches it; now there are two layers).
  • Turborepo --filter works against the workspace graph, enabling per-PR cache scoping.
  • CL’s “downstream-only” status is now machine-readable (encoreos.downstreamOnly: true in packages/core-cl/package.json) and enforced by the audit, not just documented in prose.
  • Each core’s package.json becomes the right place to declare its locally-needed npm dependencies in the future, slowing the unbounded growth of the root package.json.
  • Reversal is trivial: rm -rf packages/{platform,shared,core-*} + revert root workspaces field.

Negative

  • Two-step lookup for “what is HR’s source directory” — packages/core-hr/package.jsonencoreos.sourceDirectory: "../../src/cores/hr". Mitigated by the WORKSPACE_PACKAGES.md doc and the consistent encoreos.* field naming.
  • Per-package incremental TS typecheck is not unlocked — that requires the full physical migration (Phase 2.B).
  • The packages/docs/ directory (Docusaurus site) is intentionally excluded from the workspaces glob, which means a future contributor adding a new package under packages/ must remember to update the workspaces glob if they want it included. Mitigated by clear comment in the root package.json and the doc.

Mitigations

  • Audit script (audit:package-boundaries) is fast enough to run as a CI gate; should be added to .github/workflows/build.yml in a follow-up PR alongside the existing check-architecture:ci step.
  • The script’s negative-test path is documented and verified in the PR description (a deliberately-bad core dep correctly produces exit 1; restoration returns to exit 0).