Skip to content

fix(check): compile with the repository's own TypeScript, bundled only as fallback - #48

Merged
lucasbraum merged 4 commits into
mainfrom
fix/check-repo-typescript
Oct 8, 2026
Merged

lucasbraum merged 4 commits into
mainfrom
fix/check-repo-typescript

Conversation

@lucasbraum

@lucasbraum lucasbraum commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

check's compile signal judged every repository with the TypeScript ts-morph bundles (6.0.2), whatever the repository installs. A repository on TypeScript 4.9 was read by TypeScript 6's rules and its error positions: a JSX child "not assignable to ReactNode" sits on the element's first line in 4.9 and on the child in 6, which alone accounted for 7 of the react pack's 11 remaining false positives in excalidraw.

check now compiles with the repository's own TypeScript, found the way fix's verification has found it since #40 (node_modules/typescript in the workspace or the nearest ancestor, never NODE_PATH or a global folder), and falls back to the bundled compiler only when the repository installs none. The coverage line says which one judged:

compiled 11 of 11 files in 2 workspaces with the repo's TypeScript 5.9.3
compiled 355 of 356 files in 5 workspaces with the repo's TypeScript 4.9.4 and 5.4.5

How:

  • adapters/typescript/compiler.ts holds the resolver (moved from fix/verify.ts, same behavior, same tests) plus repositoryCompiler (loaded once per install directory) and describeCompiler.
  • With the repository's compiler, a baseline program is built once per workspace from the ts-morph program's root files and the options that compiler reads from the tsconfig (an option a newer compiler dropped or defaults differently is the repository's compiler's to read), with the same workspace-source paths; the overlay shares that baseline. With the bundled compiler the ts-morph program stays the baseline, as before. Signal A (usages) still runs on ts-morph.
  • The host resolver is installed both ways compilers ask: resolveModuleNameLiterals (TypeScript 5+) and resolveModuleNames (TypeScript 4).
  • The cause tracers take the compiler with the programs: node kinds and enum flags are the judging compiler's, not the bundled one's.
  • CompileCoverage.compilers records which compiler judged, merged across workspaces (one entry per distinct version); coverageLine prints it.
  • Found on the way: a workspace without a tsconfig of its own (excalidraw-app, covered by the root's include) got synthetic options. The bundled compiler's defaults papered over that (esModuleInterop, JSX); TypeScript 4.9 did not, and every default import and JSX element in that workspace errored at the target. Both programs now read the nearest ancestor tsconfig's options (never its file list) when the workspace has none. This also lifted excalidraw's coverage from 355 of 356 to 358 of 358 (the with-script-in-browser example resolves its imports now).
  • Security and privacy text: SECURITY.md, the CONTRIBUTING.md rules, the README privacy section and the CLI's --help privacy statement (kept word for word with the README) now say that check and verify load the typescript package the repository installs, resolved from its node_modules, to compile with, and never run the repository's scripts or other code outside fix's verification step.

Cost: a repository with its own TypeScript is parsed twice (ts-morph for Signal A, its compiler for Signal B). The react pack test went from about 12 to 9 minutes anyway, since the baseline is built once per workspace and shared.

How it was verified

New tests: fix/compiler.test.ts (compiler naming, bundled fallback, one load per install directory), check/verdict.test.ts (coverage line with one, several and mixed compilers), check/check.test.ts (compilers merged across workspaces), adapters/typescript/compile.test.ts (coverage names this repository's own TypeScript 5.9.3, which the fixture finds above it), adapters/typescript/repo-inherited.test.ts (inherited tsconfig options, own file list). config-cause.test.ts passes the compiler. The whole suite runs the overlay through the native path, since the fixtures sit under this repository's TypeScript.

pnpm lint && pnpm typecheck && pnpm test pass (core 900 tests, cli 346).

Storefront (fixtures/repos/storefront, built CLI): findings, counts and verdicts identical to main; only the coverage lines changed, to compiled 11 of 11 files in 2 workspaces with the repo's TypeScript 5.9.3 and compiled 7 of 7 files in 1 workspace with the repo's TypeScript 5.9.3. README sample updated accordingly.

Pack tests, before (main, same commit as #46's final numbers) and after, breaking findings:

pack before after
react (#45's pack, uncommitted here) 88% precision / 64% recall, 11 FP, 46 FN 96% / 69%, 4 FP, 40 FN
zod 100% / 71% 100% / 71%, identical
stripe 100% / 90% on 3 repos 100% / 90%, identical
ai 100% / 36% 100% / 36%, identical

react per repo: excalidraw 9 FP → 2 (predicted 61, expected 73), tldraw unchanged at 9 FP / 26 FN. The 6 "TS 6 vs 4.9 location" false positives in packages/excalidraw are gone, as is the with-script-in-browser one. The 4 remaining breaking false positives: dev-docs/.../Homepage/index.tsx:52 (TS2322 at a JSX child in a workspace on the root's 4.9, same shape as before), tldraw apps/docs/components/content/index.tsx:18 (next-mdx-remote-client/rsc after the companion move), and the two useTransform(rSvg) single-site-per-workspace ref-object-nullable findings. The other 7 of the 11 are deprecated or unverified, never counted as breaking. These go to #45.

Checklist

  • Tests for the change (a bug fix starts with a failing test)
  • pnpm lint && pnpm typecheck && pnpm test pass
  • README and docs updated if behavior changed
  • Changeset added if a user would notice (pnpm changeset)
  • No new dependency, or it was agreed in an issue
  • No private code, names or paths in fixtures, tests or docs
  • The rules in CONTRIBUTING.md ("Rules that do not bend") still hold

🤖 Generated with Claude Code

https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U

Lucas Braum and others added 4 commits October 8, 2026 15:43
…y as fallback

Signal B now judges a repository with the TypeScript it installs (node_modules/typescript in
the workspace or the nearest ancestor, the resolution fix's verification has used since #40),
and falls back to ts-morph's bundled compiler only when the repository installs none. The
errors and their positions are the ones the repository's own tsc prints: a repository on
TypeScript 4.9 was judged by TypeScript 6's rules and error locations before.

- compiler.ts holds the resolver (moved from fix/verify.ts) plus repositoryCompiler, loaded
  once per install directory, and describeCompiler for the coverage line.
- With the repository's compiler, a baseline program is built once per workspace from the
  ts-morph program's root files and the options that compiler reads from the tsconfig, with
  the same workspace-source paths; the overlay shares that baseline. With the bundled one the
  ts-morph program stays the baseline, as before.
- The host's resolver is installed both ways compilers ask: resolveModuleNameLiterals
  (TypeScript 5+) and resolveModuleNames (TypeScript 4).
- The cause tracers take the compiler with the programs, so node kinds and flags are the
  judging compiler's.
- Coverage records the compilers (CompileCoverage.compilers), merged across workspaces, and
  the coverage line says which judged: "compiled 11 of 11 files in 2 workspaces with the
  repo's TypeScript 5.9.3" or "with the bundled TypeScript 6.0.2".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U
Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
…ne above it

excalidraw-app has no tsconfig.json; the root's `include` covers it. Loaded as a workspace it
got synthetic options, which the bundled compiler's defaults papered over (esModuleInterop,
JSX) and the repository's TypeScript 4.9 did not: every default import and every JSX element
in that workspace errored at the target. Both the ts-morph program and the native baseline
now read the nearest ancestor tsconfig's options (never its file list) when the workspace
has none; only a repository with no tsconfig anywhere gets synthetic defaults, with the
bundled compiler's modern defaults made explicit for older compilers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U
Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U
Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
…script, nothing else runs

SECURITY.md, CONTRIBUTING.md's rules, the README privacy section and the CLI's privacy
statement (the same text in --help) now say that check and verify load the typescript
package the repository installs, resolved from its node_modules, to compile with, and that
neither runs the repository's scripts or other code outside fix's verification step.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U
Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
@lucasbraum
lucasbraum merged commit 94611a9 into main Oct 8, 2026
7 checks passed
@lucasbraum
lucasbraum deleted the fix/check-repo-typescript branch October 8, 2026 16:47
@uptide-release uptide-release Bot mentioned this pull request Oct 8, 2026
lucasbraum pushed a commit that referenced this pull request Oct 8, 2026
…reaking precision 96%, 4 false positives)

The four breaking false positives left are engine or ground-truth-environment
issues, not rules: dev-docs/Homepage (a workspace whose tsconfig extends a
package that is not installed), two useTransform(ref) call sites in other
workspaces than the declaration that is the one cause, and
next-mdx-remote-client/rsc after a companion move the real upgrade never made.
The pack stays a candidate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKRx4kuV6oi25MP5WguxW9
Signed-off-by: Claude <noreply@anthropic.com>
lucasbraum pushed a commit that referenced this pull request Oct 9, 2026
…reaking precision 96%, 4 false positives)

The four breaking false positives left are engine or ground-truth-environment
issues, not rules: dev-docs/Homepage (a workspace whose tsconfig extends a
package that is not installed), two useTransform(ref) call sites in other
workspaces than the declaration that is the one cause, and
next-mdx-remote-client/rsc after a companion move the real upgrade never made.
The pack stays a candidate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKRx4kuV6oi25MP5WguxW9
Signed-off-by: Claude <noreply@anthropic.com>
lucasbraum pushed a commit that referenced this pull request Oct 9, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and publish to npm
yourself or [setup this action to publish
automatically](https://github.com/changesets/action#with-publishing). If
you're not ready to do a release yet, that's fine, whenever you add more
changesets to main, this PR will be updated.


# Releases
## uptide@0.6.0

### Minor Changes

- [#46](#46)
[`458aa14`](458aa14)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - What changed in
`check`:
  
- **Breaking means confirmed.** A type-surface change is reported as
breaking only when the
compiler rejects your code against the target at that site, the runtime
probe saw the
export go, or a migration pack found it, in the verified tier as in the
generic one. What
nothing confirmed is listed as "possible impact", apart from the
breaking count.
- **Coverage, under every package.** `compiled 355 of 356 files in 5
workspaces; skipped:
<reason (count)>` says how much of the code that uses the package the
compiler judged, and
    the verdict says "types partly verified" when not every file was.
- **TypeScript-version-aware defaults.** A repository on TypeScript 5 is
read with
TypeScript 5's defaults for what its tsconfig leaves unset (`strict`
off, automatic
`@types`), not the bundled TypeScript 6's. Monorepos compile far more of
their files:
workspace packages declared `workspace:*` under Yarn and npm resolve to
their source,
scoped programs keep the tsconfig's ambient declaration files, and a
workspace that only
    peer-depends on a package finds the hoisted copy.
- **Companions.** `check` and `fix` move `@types/<pkg>` with `<pkg>`,
and a package released
in lockstep with it (react-dom with react): `check react` 18 → 19 moves
react-dom,
    @types/react and @types/react-dom together.
- **One finding for a root cause that is one edit.** A compiler option
(the global `JSX`
namespace `@types/react` 19 removes, read by `"jsx": "preserve"`) and a
repository
parameter several call sites trip are each one finding at the place to
edit, with the
    compiler errors as evidence.

- [#51](#51)
[`a18bebd`](a18bebd)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` reports
one breaking finding when call sites in different workspaces trace back
to
the same local declaration (a parameter or prop whose type names
something from the upgraded
package): the finding is anchored at the declaration, where the one edit
is, and the call
sites are listed under it as evidence with an `N call sites in M
workspaces` line, in the
terminal, the HTML report and the JSON report. Before, a site alone in
its workspace stayed a
finding of its own, so a hook typed `RefObject<HTMLElement>` and called
from three
workspaces was three findings plus the one that mattered. The JSON
report gains `root` on
call-site findings and `workspace` on evidence sites; nothing is renamed
or removed. The pack
test scorer counts the anchor as the predicted site and never the
evidence under it.

- [#49](#49)
[`2e5c510`](2e5c510)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - Two fixes to
`check`:
  
- **A project whose tsconfig cannot be resolved is not compiled.** A
tsconfig that `extends` a
package or file that is not installed describes options the repository
never builds with, so
what a compiler reports there is not a place to change. `check` skips
it, never turns its
diagnostics into findings, and says so in the coverage line (a file
governed by such a
nested project is dropped from the workspace that happens to reach it):
`compiled 5 of 7 files in 2 workspaces; not compiled: docs/tsconfig.json
(extends "@tsconfig/docusaurus/tsconfig.json" cannot be resolved)`.
- **A package that only peers on the package is left in place when its
peer range rejects the
target.** What always moves with the package stays as before: the
release group published at
the target's own version (`react-dom`), `@types/*`, the exact pins of
the target, and the
packages a pack names in `companions` (each with the official page that
says so), even when their installed range already accepts the
target. Any other package whose installed peer range rejects the target
is not moved and not
compiled at another version; `check` lists it under possible impact,
never as breaking:
`? possible impact, peer conflict: next-mdx-remote-client 1.1.2 declares
react >= 18.3.0 < 19.0.0`.

- [#53](#53)
[`d33bdd0`](d33bdd0)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A verified
migration pack for React 18 → 19, scored against excalidraw/excalidraw
and
tldraw/tldraw at the commit before their own upgrade. Rewrites by rule:
`useRef<T>()` →
`useRef<T>(undefined)`, `ref={(el) => (x = el)}` → a block body, and
`React.MutableRefObject`
→ `React.RefObject`. `RefObject<T | null>`, the removed global `JSX`
namespace, untyped
`element.props`, the removed react-dom APIs and `PropsWithRef` go to the
agent with the guide;
what the compiler cannot see (errors no longer re-thrown, removed legacy
APIs, `act` moved to
"react", Strict Mode and Suspense changes) is listed for review. 2
public repositories, 100%
precision on breaking findings (89 sites, none false), recall 69% on
breaking sites; the
  sites it misses are listed in the pack test output.

### Patch Changes

- [#48](#48)
[`94611a9`](94611a9)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` compiles
your code with the repository's own TypeScript (the
`node_modules/typescript`
its build uses, found the way `fix` has found it since verification),
and falls back to the
bundled compiler only when the repository installs none. Errors and
their positions are the
ones your `tsc` would print: a repository on TypeScript 4.9 is no longer
judged by TypeScript
6's rules. The coverage line says which compiler judged: `compiled 355
of 356 files in 5
  workspaces with the repo's TypeScript 4.9.5`.
  
A workspace without a tsconfig of its own is now configured by the
nearest one above it (a
monorepo root whose `include` covers the workspace), for its options
only; synthetic defaults
  apply only when no tsconfig exists anywhere.

Co-authored-by: uptide-release[bot] <338861287+uptide-release[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant