TL;DR
This is the executable companion to ADR-025. It breaks the migration to the Go-basedtsgo 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).
tsgocold typecheck on this repo: 53 s vstsc(TS 6.0.3) cold: 478 s (~9× faster).tsgowarm typecheck (no cache configured): 3.6 s vstscwarm-cache target: ~8 s (per.github/workflows/build.ymlcomment).- Currently 0 uses of
ignoreDeprecationsin any tsconfig (the most common 6→7 break) — seetsconfig.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.tsscripts/audit/audit-appicon-tones.tsscripts/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.x→typescript >=4.8.4 <6.1.0eslint-plugin-tsdoc@0.5.2(via@typescript-eslint/utils@8.56) → same rangets-node@10.9.2→typescript: >=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.2→typescript: >=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→ spawnstsc/vue-tscfrom the project’stypescript
- Current
package.jsondeclarestypescript@^6.0.3; lockfile resolved6.0.3; innode_modulesactually deduped to5.9.3(because peer ranges in installed minor versions oftypescript-eslintcap<6.1.0and the deduper picked the lower-but-satisfying version after a freshnpm ci --legacy-peer-deps). - The
packages/docs/workspace pins its owntypescript@~5.2.2; isolated from this plan. .github/workflows/build.ymlstep “Cache TypeScript incremental build info” cachesnode_modules/.cache/tsckeyed ontsconfig*.json+package-lock.json+src/integrations/supabase/types.ts.- Husky
pre-commitrunsnpm 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).
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.
tsgodoes not require--incrementalto 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=8192becausetsgois 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 typecheckexit code, output, and timing unchanged (within 5%) — existing CI gate is unaffected. -
npm run typecheck:fastexits 0 with byte-identical diagnostic count tonpm run typecheckon 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 validateandnpm run validate:governancestill pass. - Husky pre-commit hook still runs the existing
tscpath. - 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, oreslint-plugin-tsdocconfigurations.
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)
- 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--devtag, or atypescript@7.xpublication on thelatestchannel). typescript-eslintships a release whose installedpeerDependencies.typescriptrange opens beyond<6.1.0(verified vianpm view typescript-eslint peerDependenciesandnpm view @typescript-eslint/parser peerDependencies). The cleanest signal is an explicit “TypeScript 7 support” line in their changelog; otherwise verify the peer range alone.
typescript-eslint openly supports TS 7. Prefer waiting for both unless CI cold-cache pain becomes acute.
Diff 2.1 — package.json
typescriptresolves to@typescript/typescript6so any tool that doesimport 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 stabletypescript@^7) provides the binary we want to invoke directly. By the time Phase 2 ships, the publishedtypescript@7.xpackage will own thetscbinary; we keep@typescript/native-previewinstalled only as long as we still need a beta channel.
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.
tsc6is the binary the@typescript/typescript6package exposes (per Microsoft’s compatibility docs). Keeptypecheck:legacyavailable for ~1 release cycle as an escape hatch if atsgoregression is discovered post-cutover.typecheck:fastis now redundant withtypecheck; keep the alias for one release for muscle memory, then remove in Phase 3.
Diff 2.3 — .github/workflows/build.yml
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:Buildworkflow (with the newtsgo-based typecheck step)governance-weeklyworkflow (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-checkworkflows must continue to pass — none of these invoketscdirectly, so no diff is expected.
Acceptance criteria (Phase 2)
- CI
Buildworkflow Typecheck step runs in < 90 seconds cold and ideally < 10 seconds warm (from cache restore alone if any). - CI
Buildworkflow has identical pass/fail outcome asprodbaseline on the same commit. -
npm run lintexit 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.tsconsumers) execute without import resolution errors. -
npm run test,npm run test:integration,npm run test:rls:smoke,npm run test:e2e:smokeall pass. - Husky pre-commit hook runs
npm run typecheck(nowtsgo) 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:git revertthe Phase 2 commit onprod.npm ci --legacy-peer-depsrestores TS 6.0.3 as the resolvedtypescript.- CI
Buildreverts to the old typecheck step automatically (cache step is restored by the same revert). - Pre-commit hook reverts to the old
tscinvocation. - Open an issue capturing the regression with reproduction steps. File upstream against microsoft/typescript-go if it’s a
tsgobug.
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) advertisetypescriptpeer ranges that include^7. -
eslint-plugin-tsdocditto (it consumes via@typescript-eslint/utils). -
msw,ts-node,vite-plugin-checker,tsx,@vitest/*ditto (verify bynpm 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-tonesseetests/unit/scripts/audit-appicon-tones.test.ts), OR - (b) been explicitly pinned to
@typescript/typescript6via direct import path if a TS 7 incompatibility is found.
- (a) been verified to work against the TS 7 Compiler API (run their tests; for
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:@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
tsgoin dev docs → simplify to “TypeScript 7 (tsc)”.
Verification (Phase 3)
Acceptance criteria (Phase 3)
-
typescriptpackage resolves to a TS 7 version innode_modules. - No
@typescript/typescript6or@typescript/native-previewin 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-previewwithin the7.0.0-dev.*train at most weekly; require manual approval for jumps across train boundaries (e.g.,dev→rc→7.0.0). - During Phase 2, pin
typescriptto the alias literal; renovate cannot upgrade an alias automatically but can flag a PR when@typescript/typescript6itself 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
tsxis esbuild-based and ignores the installedtypescriptversion for its transform — out of scope.ts-nodeconsumes thetypescriptpackage 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.