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.

Version: 1.0.0 Last Updated: 2026-04-23 Owner: Platform Architecture ADR: ADR-018 Status: Phase 1 ready to implement; Phase 2 gated; Phase 3 follow-up

TL;DR

This is the executable companion to ADR-018. It breaks the migration to the Go-based tsgo compiler into three phases with concrete diffs, verification commands, gating criteria, and rollback procedures.
PhaseGoalRiskGatingStatus
1Opt-in tsgo for local dev (typecheck:fast); CI gate unchangedNone — purely additiveNone — implement nowReady
2Cut CI typecheck over to tsgo; introduce npm:@typescript/typescript6 alias for tools that import the TS Compiler APILow–medium (touches CI gate, lockfile)(a) TS 7.0 RC or GA shipped, (b) typescript-eslint peer range opens to >=6.1.0 (ideally explicit TS 7 support)Blocked on upstream
3Drop the @typescript/typescript6 alias; let typescript resolve to 7.x nativelyLowAll TS-API-consuming deps (linter, msw, vitest, ts-node, vite-plugin-checker, eslint-plugin-tsdoc, internal audit scripts) advertise TS 7-compatible peer rangesLong-tail follow-up

Inputs to this plan (already established by ADR-018)

  • TS 7.0 Beta = same diagnostics as TS 6.0 on this commit (validated: 0 errors → 0 errors).
  • tsgo cold typecheck on this repo: 53 s vs tsc (TS 6.0.3) cold: 478 s (~9× faster).
  • tsgo warm typecheck (no cache configured): 3.6 s vs tsc warm-cache target: ~8 s (per .github/workflows/build.yml comment).
  • Currently 0 uses of ignoreDeprecations in any tsconfig (the most common 6→7 break) — see tsconfig.json, tsconfig.app.json, tsconfig.node.json, packages/docs/tsconfig.json, supabase/functions/tsconfig.json.
  • Internal TS Compiler API consumers: 3 audit scripts.
    • scripts/audit/doc-comment-coverage/parser.ts
    • scripts/audit/audit-appicon-tones.ts
    • scripts/audit/audit-page-spacing.ts
  • Third-party TS-API consumers in node_modules (peer ranges as of writing):
    • typescript-eslint@8.58.x / @typescript-eslint/parser@8.58.xtypescript >=4.8.4 <6.1.0
    • eslint-plugin-tsdoc@0.5.2 (via @typescript-eslint/utils@8.56) → same range
    • ts-node@10.9.2typescript: >=2.7 (compatible)
    • tsx@4.21.x (esbuild-based; no TS Compiler API peer)
    • fallow@2.66.x → standalone Rust binary (no TS Compiler API peer dependency)
    • msw@2.13.2typescript: >=4.8
    • @vitest/coverage-v8@4.1.5 / @vitest/ui@4.1.5 (no direct TS API peer; bundles its own)
    • vite-plugin-checker@0.13.0 → spawns tsc/vue-tsc from the project’s typescript
  • Current package.json declares typescript@^6.0.3; lockfile resolved 6.0.3; in node_modules actually deduped to 5.9.3 (because peer ranges in installed minor versions of typescript-eslint cap <6.1.0 and the deduper picked the lower-but-satisfying version after a fresh npm ci --legacy-peer-deps).
  • The packages/docs/ workspace pins its own typescript@~5.2.2; isolated from this plan.
  • .github/workflows/build.yml step “Cache TypeScript incremental build info” caches node_modules/.cache/tsc keyed on tsconfig*.json + package-lock.json + src/integrations/supabase/types.ts.
  • Husky pre-commit runs npm run typecheck (full project); changing this script changes pre-commit behavior.

Phase 1 — Opt-in tsgo for local development

Goal: Give every developer a sub-minute cold typecheck, with zero impact on the existing npm run typecheck / CI gate / linter / pre-commit hook. Risk: None (purely additive — existing TS 6.0.3 path is unchanged). No gating criteria — implement immediately.

Diff 1.1 — package.json

Add @typescript/native-preview as a devDependency (pin to a specific beta build to keep CI deterministic; let renovate roll it forward).
   "devDependencies": {
     "@biomejs/biome": "^2.4.11",
+    "@typescript/native-preview": "7.0.0-dev.20260421.2",
     ...
     "typescript": "^6.0.3",
     "typescript-eslint": "^8.59.0",
     ...
   }
Rationale for pinning the exact beta build (no caret): the package’s dist-tags show fast-moving latest/beta channels (e.g. 7.0.0-dev.20260421.2 vs 7.0.0-dev.20260422.1); we don’t want CI to roll silently within a beta train.

Diff 1.2 — package.json scripts

Add a fast-path typecheck. Do not modify the existing typecheck script.
   "scripts": {
     ...
     "typecheck": "cross-env NODE_OPTIONS=--max-old-space-size=8192 tsc --noEmit -p tsconfig.app.json --incremental --tsBuildInfoFile node_modules/.cache/tsc/app.tsbuildinfo",
+    "typecheck:fast": "tsgo --noEmit -p tsconfig.app.json",
     ...
   }
Notes:
  • tsgo does not require --incremental to be fast (warm 3.6s without one in our test). Omitting it keeps the script simple and avoids creating a second cache directory.
  • No NODE_OPTIONS=--max-old-space-size=8192 because tsgo is a Go binary (not Node) and uses far less memory; the env flag is harmless if a wrapper Node entry point is involved, but adding it would imply Node memory tuning that doesn’t apply.

Diff 1.3 — AGENTS.md “Pre-Flight Checklist → Before commit”

Add a one-liner pointer for fast local iteration. Keep npm run validate as the authoritative gate.
 **Before commit (required):** `npm run format:check && npm run typecheck && npm run lint && npm run build` (or `npm run validate`). ...
+
+**Faster local typecheck (developer ergonomics):** `npm run typecheck:fast` (uses the TypeScript 7.0 native compiler `tsgo`; ~9× faster cold, ~2× faster warm; same diagnostics as `tsc`; opt-in). The authoritative pre-commit / CI gate remains `npm run typecheck`. See `docs/development/TYPESCRIPT_7_MIGRATION_PLAN.md`.

Diff 1.4 — docs/development/CI_PIPELINE.md

In the “What to run locally before pushing” section, add:
 Or the same steps individually: `npm run format:check`, `npm run typecheck`, `npm run lint`, `npm run build`, `npm run test`, `npm run test:rls:smoke` (or full RLS as needed). See [TESTING_SETUP_AND_RUN.md](../testing/TESTING_SETUP_AND_RUN.md) for E2E and env requirements.
+
+**Tip:** For inner-loop iteration, `npm run typecheck:fast` runs the TypeScript 7.0 (Go) compiler and is roughly 9× faster on cold runs. It produces the same diagnostics as `npm run typecheck`. CI continues to use `tsc` until [Phase 2 of the TS 7 migration plan](TYPESCRIPT_7_MIGRATION_PLAN.md#phase-2--cut-ci-typecheck-over-to-tsgo) lands.

Diff 1.5 — docs/development/DEVELOPMENT_QUICK_REFERENCE.md

Add a row to the developer ergonomics table referencing typecheck:fast and the TypeScript Native Preview VS Code extension.

Verification (Phase 1)

Run, in this order, on a clean checkout:
npm ci --legacy-peer-deps
node -e "console.log(require('@typescript/native-preview/package.json').version)"
./node_modules/.bin/tsgo --version          # → Version 7.0.0-dev.20260421.2

# Existing path still works and still gates the same as before
npm run typecheck
npm run validate

# Fast path returns identical diagnostics
npm run typecheck:fast

# Ergonomics check: cold time
rm -rf node_modules/.cache/tsc
time npm run typecheck         # baseline
time npm run typecheck:fast    # should be ~9× faster cold

Acceptance criteria (Phase 1)

  • npm run typecheck exit code, output, and timing unchanged (within 5%) — existing CI gate is unaffected.
  • npm run typecheck:fast exits 0 with byte-identical diagnostic count to npm run typecheck on the same commit. (Diagnostic messages are expected to be identical too — TS 7 is a port, not a rewrite — but byte-identity of the ordered diagnostics list is the meaningful gate.)
  • npm run validate and npm run validate:governance still pass.
  • Husky pre-commit hook still runs the existing tsc path.
  • No changes to .github/workflows/build.yml.
  • No changes to tsconfig*.json.
  • No changes to the linter, audit scripts, MSW, Vitest, Fallow, vite-plugin-checker, or eslint-plugin-tsdoc configurations.

Rollback (Phase 1)

git revert the Phase 1 commit. npm ci --legacy-peer-deps removes @typescript/native-preview and its platform-specific binary. There are no schema, format, or behavioral changes to reverse.

Phase 2 — Cut CI typecheck over to tsgo

Goal: Make tsgo the authoritative typecheck for npm run typecheck, the CI build gate, and the husky pre-commit hook. Save ~7 minutes on cold-cache CI runs (Supabase types regen, dep bumps, tsconfig changes). Risk: Low–medium. Touches the CI gate, the lockfile, and the package the linter resolves at runtime. Mitigated by the npm:@typescript/typescript6 alias and a one-PR-at-a-time staged rollout.

Gating criteria (BOTH must hold before opening the Phase 2 PR)

  1. Microsoft has shipped TypeScript 7.0 RC or GA. Track via the TypeScript blog and npm view @typescript/native-preview dist-tags (looking for a non--dev tag, or a typescript@7.x publication on the latest channel).
  2. typescript-eslint ships a release whose installed peerDependencies.typescript range opens beyond <6.1.0 (verified via npm view typescript-eslint peerDependencies and npm view @typescript-eslint/parser peerDependencies). The cleanest signal is an explicit “TypeScript 7 support” line in their changelog; otherwise verify the peer range alone.
If only criterion (1) is met but not (2), Phase 2 is still doable using the alias — but the optics of the alias are simpler to explain after typescript-eslint openly supports TS 7. Prefer waiting for both unless CI cold-cache pain becomes acute.

Diff 2.1 — package.json

   "devDependencies": {
     ...
     "@typescript/native-preview": "7.0.0-dev.20260421.2",
     ...
-    "typescript": "^6.0.3",
+    "typescript": "npm:@typescript/typescript6@^6.0.0",
+    "@typescript/typescript-7": "npm:typescript@^7.0.0",
     "typescript-eslint": "^8.59.0",
     ...
   }
Why two entries:
  • typescript resolves to @typescript/typescript6 so any tool that does import ts from 'typescript' (linter, msw, vitest, ts-node, vite-plugin-checker, eslint-plugin-tsdoc, and our 3 audit scripts) keeps the TS 6 Compiler API. This matches Microsoft’s documented compatibility recipe.
  • @typescript/typescript-7 (alias of stable typescript@^7) provides the binary we want to invoke directly. By the time Phase 2 ships, the published typescript@7.x package will own the tsc binary; we keep @typescript/native-preview installed only as long as we still need a beta channel.
Once stable typescript@7 is published, the @typescript/native-preview dep can be dropped in the same PR. If only @typescript/native-preview is available (RC era), keep using tsgo and skip the second alias.

Diff 2.2 — package.json scripts

Switch the canonical typecheck to tsgo (or to tsc from the aliased TS 7 package, depending on what’s published at cutover time). Drop the now-unnecessary --incremental and --tsBuildInfoFile.
-    "typecheck": "cross-env NODE_OPTIONS=--max-old-space-size=8192 tsc --noEmit -p tsconfig.app.json --incremental --tsBuildInfoFile node_modules/.cache/tsc/app.tsbuildinfo",
+    "typecheck": "tsgo --noEmit -p tsconfig.app.json",
+    "typecheck:legacy": "cross-env NODE_OPTIONS=--max-old-space-size=8192 tsc6 --noEmit -p tsconfig.app.json --incremental --tsBuildInfoFile node_modules/.cache/tsc/app.tsbuildinfo",
     "typecheck:fast": "tsgo --noEmit -p tsconfig.app.json",
  • tsc6 is the binary the @typescript/typescript6 package exposes (per Microsoft’s compatibility docs). Keep typecheck:legacy available for ~1 release cycle as an escape hatch if a tsgo regression is discovered post-cutover.
  • typecheck:fast is now redundant with typecheck; keep the alias for one release for muscle memory, then remove in Phase 3.

Diff 2.3 — .github/workflows/build.yml

-      - name: Cache TypeScript incremental build info
-        uses: actions/cache@v4
-        with:
-          # Survives across runs so warm `npm run typecheck` finishes in
-          # ~8s instead of the 7m+ cold compile against the 100k-line
-          # generated Supabase types file.
-          path: node_modules/.cache/tsc
-          key: tsc-${{ runner.os }}-${{ hashFiles('tsconfig.json', 'tsconfig.app.json', 'tsconfig.node.json', 'package-lock.json', 'src/integrations/supabase/types.ts') }}
-          restore-keys: |
-            tsc-${{ runner.os }}-
-
       - name: Typecheck
         run: npm run typecheck
Drop the tsc incremental cache step entirely — tsgo cold time is already < 1 minute. Keeping the cache step would be harmless but wasteful (cache restore alone takes a few seconds on every job).

Diff 2.4 — .husky/pre-commit

No diff required — npm run typecheck is unchanged in name. Pre-commit hook now invokes tsgo and finishes in seconds instead of minutes (a major DX win independent of CI).

Diff 2.5 — Documentation refresh

FileChange
AGENTS.md “Pre-Flight Checklist → Before commit”Remove the “faster local typecheck” addendum from Phase 1; update the canonical command list to mention TS 7 / tsgo is now the gate.
docs/development/CI_PIPELINE.md Job sequence step 5Types: npm run typecheck (TypeScript 7 / tsgo)“
docs/development/CI_PIPELINE.md “Tip” added in Phase 1Remove (no longer needed).
.github/workflows/build.yml header commentReplace “7m+ cold compile” mention with TS 7 cold-time.
docs/architecture/decisions/ADR-018-typescript-7-go-compiler-evaluation.mdFlip status to Accepted. Add a “Phase 2 implemented in PR #NNN” line.
docs/architecture/decisions/index.mdUpdate ADR-018 row status.
CLAUDE.md “Common Commands” tableIf it pins a TS version anywhere, bump. (Currently version-free.)
docs/VERSIONS.mdIf AGENTS.md minor-version is bumped per the maintenance guide, mirror here.

Verification (Phase 2)

In a feature branch, after Diffs 2.1–2.5 are applied:
# Lockfile health
npm ci --legacy-peer-deps
node -e "const v=require('typescript/package.json').version; console.log('typescript=', v); if(!/^6\\./.test(v)) process.exit(1)"
./node_modules/.bin/tsc --version    # via the TS 7 alias entry → Version 7.x.y (or `tsgo --version` if not yet published)
./node_modules/.bin/tsc6 --version   # via the alias → Version 6.0.x

# Authoritative gate
npm run typecheck                    # → expect < 90 s cold, < 10 s warm, 0 errors
npm run typecheck:legacy             # → still works; 0 errors; same as old gate
npm run validate                     # full pipeline

# Linter unchanged
npm run lint
npm run lint:ci

# Audit scripts that import 'typescript' still work
npx tsx scripts/audit/audit-appicon-tones.ts --help 2>&1 | head -5
npx tsx scripts/audit/audit-page-spacing.ts --help 2>&1 | head -5
npx tsx scripts/audit/doc-comment-coverage/audit.ts --help 2>&1 | head -5  # entry that uses parser.ts

# Tests
npm run test
npm run test:integration
npm run test:rls:smoke
npm run test:e2e:smoke
CI run on the PR must pass:
  • Build workflow (with the new tsgo-based typecheck step)
  • governance-weekly workflow (if it runs on PR; otherwise verify it succeeds on first scheduled run after merge)
  • db-advisor-quarterly, db-migration-guard, e2e-full, fhir-validate, supabase-deploy-*, supabase-drift-check workflows must continue to pass — none of these invoke tsc directly, so no diff is expected.

Acceptance criteria (Phase 2)

  • CI Build workflow Typecheck step runs in < 90 seconds cold and ideally < 10 seconds warm (from cache restore alone if any).
  • CI Build workflow has identical pass/fail outcome as prod baseline on the same commit.
  • npm run lint exit code unchanged (linter still resolves the TS 6 Compiler API).
  • All 3 internal audit scripts (audit-appicon-tones.ts, audit-page-spacing.ts, doc-comment-coverage/parser.ts consumers) execute without import resolution errors.
  • npm run test, npm run test:integration, npm run test:rls:smoke, npm run test:e2e:smoke all pass.
  • Husky pre-commit hook runs npm run typecheck (now tsgo) successfully on a sample commit.
  • Lockfile diff in the PR contains only the expected typescript/@typescript/typescript6/@typescript/typescript-7 (and possibly removed @typescript/native-preview) changes — no unexpected transitive bumps.

Rollback (Phase 2)

If a regression surfaces post-merge:
  1. git revert the Phase 2 commit on prod.
  2. npm ci --legacy-peer-deps restores TS 6.0.3 as the resolved typescript.
  3. CI Build reverts to the old typecheck step automatically (cache step is restored by the same revert).
  4. Pre-commit hook reverts to the old tsc invocation.
  5. Open an issue capturing the regression with reproduction steps. File upstream against microsoft/typescript-go if it’s a tsgo bug.
The escape hatch npm run typecheck:legacy (added in Diff 2.2) provides per-developer rollback without touching the repo: anyone can run the TS 6 compiler against the same source.

Phase 3 — Drop the alias; let typescript resolve to 7.x natively

Goal: Simplify dependency graph by removing the npm:@typescript/typescript6 alias. Risk: Low. Mostly a cleanup; the heavy lifting was Phase 2.

Gating criteria (ALL must hold)

  • typescript-eslint (and @typescript-eslint/parser, @typescript-eslint/utils) advertise typescript peer ranges that include ^7.
  • eslint-plugin-tsdoc ditto (it consumes via @typescript-eslint/utils).
  • msw, ts-node, vite-plugin-checker, tsx, @vitest/* ditto (verify by npm view <pkg> peerDependencies).
  • Our 3 internal audit scripts have either:
    • (a) been verified to work against the TS 7 Compiler API (run their tests; for audit-appicon-tones see tests/unit/scripts/audit-appicon-tones.test.ts), OR
    • (b) been explicitly pinned to @typescript/typescript6 via direct import path if a TS 7 incompatibility is found.

Diff 3.1 — package.json

   "devDependencies": {
     ...
-    "@typescript/native-preview": "7.0.0-dev.20260421.2",     // already removed in Phase 2 if stable shipped
-    ...
-    "typescript": "npm:@typescript/typescript6@^6.0.0",
-    "@typescript/typescript-7": "npm:typescript@^7.0.0",
+    "typescript": "^7.0.0",
     "typescript-eslint": "^8.59.0",   // bump to whatever explicit-TS-7 version is current
     ...
   }

Diff 3.2 — package.json scripts

-    "typecheck": "tsgo --noEmit -p tsconfig.app.json",
-    "typecheck:legacy": "cross-env NODE_OPTIONS=--max-old-space-size=8192 tsc6 --noEmit -p tsconfig.app.json --incremental --tsBuildInfoFile node_modules/.cache/tsc/app.tsbuildinfo",
-    "typecheck:fast": "tsgo --noEmit -p tsconfig.app.json",
+    "typecheck": "tsc --noEmit -p tsconfig.app.json",
(tsc here is now the TS 7 binary owned by the typescript package.)

Diff 3.3 — Internal audit scripts

If any of the 3 internal audit scripts emits a deprecation warning or breaks against the TS 7 Compiler API, change their import to:
-import ts from 'typescript';
+import ts from '@typescript/typescript6';   // pin TS 6 API for legacy AST shape
…and add a single line of @typescript/typescript6 as a devDependency. This is per-script and only if needed; first preference is to migrate the script to TS 7.

Diff 3.4 — Documentation refresh

  • docs/architecture/decisions/ADR-018-typescript-7-go-compiler-evaluation.md → add a “Phase 3 completed in PR #NNN” line.
  • Any remaining mention of tsgo in dev docs → simplify to “TypeScript 7 (tsc)”.

Verification (Phase 3)

npm ci --legacy-peer-deps
./node_modules/.bin/tsc --version    # → Version 7.x.y
node -e "console.log(require('typescript/package.json').version)"  # → 7.x.y
npm run typecheck
npm run lint
npm run validate
npm run test

Acceptance criteria (Phase 3)

  • typescript package resolves to a TS 7 version in node_modules.
  • No @typescript/typescript6 or @typescript/native-preview in the lockfile (unless retained in Diff 3.3 as an explicit per-script pin).
  • All Phase 2 acceptance criteria still hold.

Rollback (Phase 3)

git revert to Phase 2 state. The Phase 2 alias-based configuration is the long-lived stable target; Phase 3 is purely a cleanup that can be deferred indefinitely if upstream peer ranges don’t open up.

Cross-cutting concerns

Renovate / Dependabot

  • During Phase 1, allow renovate to bump @typescript/native-preview within the 7.0.0-dev.* train at most weekly; require manual approval for jumps across train boundaries (e.g., devrc7.0.0).
  • During Phase 2, pin typescript to the alias literal; renovate cannot upgrade an alias automatically but can flag a PR when @typescript/typescript6 itself publishes a new minor.

Editor / IDE guidance

  • Phase 1: VS Code “TypeScript Native Preview” extension is opt-in per developer. Workspace settings already use the workspace TypeScript via node_modules/typescript, so Cursor / VS Code default behavior is unchanged.
  • Phase 2: Recommend developers uninstall the Native Preview extension and rely on the workspace TS 7 install instead (avoids the bundled-vs-workspace mismatch).

packages/docs/ workspace

This Docusaurus workspace pins typescript@~5.2.2 for its own build. Out of scope for this plan — leave it as is. If/when Docusaurus’s @docusaurus/tsconfig supports TS 7 cleanly, it can be migrated as an independent change.

supabase/functions/

Deno’s tsconfig.json is consumed by Deno, not the npm typescript package. Out of scope.

Test-completeness audit interactions

Steps 12 (RLS coverage) and 16 (test:baseline:smoke) of the CI build do not invoke tsc; they run via tsx/vitest/playwright. No diffs needed.

tsx, ts-node script runtime

  • tsx is esbuild-based and ignores the installed typescript version for its transform — out of scope.
  • ts-node consumes the typescript package at runtime. With Phase 2’s alias, it sees TS 6, which matches today’s runtime behavior — out of scope.

Lint warning budget

npm run lint:ci currently enforces --max-warnings 1100 (see package.json for the authoritative value). The linter’s behavior is type-information-driven (via @typescript-eslint/typescript-estree resolving the project tsconfig with the TS 6 API in Phase 2). No warning-count change is expected.

Risk register

IDRiskLikelihoodImpactMitigation
R1tsgo emits a diagnostic that tsc does not (or vice versa)Low (validated 0/0 on this commit)Medium (could surface a real type error or false positive)Phase 1 ships behind opt-in script; CI gate is unchanged until Phase 2. Phase 2 verification step diffs the diagnostic outputs explicitly.
R2A tsgo beta release introduces a regression mid-Phase-1MediumLow (developers can fall back to npm run typecheck)Pin exact beta version in package.json; require manual approval to bump within the dev train.
R3typescript-eslint peer range remains stuck <6.1.0 indefinitelyLowMedium (delays Phase 2 and 3)The @typescript/typescript6 alias is the documented Microsoft escape hatch — Phase 2 can ship without typescript-eslint changes.
R4An internal audit script breaks against the TS 7 Compiler API in Phase 3MediumLow (per-script alias workaround in Diff 3.3)Run the audit-script tests in Phase 3 verification before flipping the dep.
R5CI cache step removal in Phase 2 leaves stale node_modules/.cache/tsc directories on self-hosted runnersLowNegligibleOne-time actions/cache purge or accept the few-MB carry-over until cache TTL evicts.
R6vite-plugin-checker invokes tsc directly during dev (npm run dev) and breaks if tsc resolves to TS 7 in Phase 3 while plugin is pinned to TS 6 expectationsLowLow (dev-only impact)Bump vite-plugin-checker to a release that supports TS 7 before Phase 3, or omit Phase 3 until it does.

Tracking artifacts

This plan does not create GitHub issues or specs. When implementation begins:
  • Phase 1 PR title: feat(toolchain): add opt-in TypeScript 7 (tsgo) typecheck:fast script — link this plan and ADR-018 in the body.
  • Phase 2 PR title: feat(toolchain): cut CI typecheck over to TypeScript 7 (tsgo) with @typescript/typescript6 alias — link this plan and ADR-018; mention the gating-criteria evidence (RC/GA URL, typescript-eslint peer range output).
  • Phase 3 PR title: chore(toolchain): drop @typescript/typescript6 alias; let typescript resolve to 7.x natively.
Each PR should update the Status column of the table at the top of this doc.