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

TL;DR

This is the executable companion to ADR-025. It breaks the migration to the Go-based tsgo compiler into three phases with concrete diffs, verification commands, gating criteria, and rollback procedures.

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

  • 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).
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.
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.

Diff 1.4 — docs/development/CI_PIPELINE.md

In the “What to run locally before pushing” section, add:

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:

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

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.
  • 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

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

Verification (Phase 2)

In a feature branch, after Diffs 2.1–2.5 are applied:
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

Diff 3.2 — package.json scripts

(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:
…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-025-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)

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


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-025 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-025; 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.