Skip to content

fix(check): skip projects with an unresolvable tsconfig; leave a rejecting peer in place and report it - #49

Merged
lucasbraum merged 6 commits into
mainfrom
claude/engine-fixes-react
Oct 9, 2026
Merged

lucasbraum merged 6 commits into
mainfrom
claude/engine-fixes-react

Conversation

@lucasbraum

@lucasbraum lucasbraum commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

Two of the three engine issues behind the react pack's remaining breaking false positives (#45). The third (several diagnostics, one local declaration, across workspaces) is not in this PR; see the end.

  1. 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. check skips it (and any file governed by it as the nearest tsconfig, even when a parent workspace reaches the file), never turns its diagnostics into findings, and says so in the coverage line: compiled 5 of 7 files in 2 workspaces; skipped: tsconfig cannot be resolved (2); not compiled: docs/tsconfig.json (extends "@tsconfig/docusaurus/tsconfig.json" cannot be resolved). The ground-truth draft script (pnpm packs:truth) skips such projects the same way and prints them under notCompiled. Case: excalidraw dev-docs, which the root workspace reached and judged under its own options.
  2. Companions: lockstep moves, a rejecting peer stays and is reported. (Replaces my first version of this item, which moved any package whose peer range rejected the target to the lowest release that accepted it. Real upgrades often leave such a package in place: moving next-mdx-remote-client to 2.0.0 broke three more lines than leaving it.)
    • Always moves with the package, even when the installed range already accepts the target (the old react-dom test is back): the exact pins of the target; a release-group member published at the target's own version (react-dom); @types/* by name; and the packages a pack names in its optional companions list.
    • Every other package whose installed peer range rejects the target is not moved and not compiled at another version. check lists it under possible impact as a peer conflict, next-mdx-remote-client 1.1.2 declares react >= 18.3.0 < 19.0.0, in PackageReport.peerConflicts and the report's package block. It is not a finding, so it never counts as breaking, and it does not block fix the way an inconsistent group does.
  3. A pack's companions carry an official source. Entries are { name, source }, where source is the https URL of the official migration guide or changelog that says the package moves with the leader. docs/packs.md states the rule: companions come from official docs, never from ground truth alone. uptide pack test fails a pack that has an entry with no source, a non-https source, or the pack's own package, and scores nothing (companionProblems, run for every pack).

How it was verified

New tests: unresolved-config.test.ts; compile.test.ts (a workspace whose own tsconfig is unresolvable; a nested project that is, with the workspace's own file still judged); verdict.test.ts (coverage line); companions.test.ts (accepting react-dom still moves; a peer-rejecting package is left in place and reported, with the real next-mdx-remote-client ranges; a package the pack names moves even though its range accepts, and does not when the pack is silent); contract.test.ts (companions carried by definePack; companionProblems for no source, empty, plain text, http, file, no name, own package); pack-test.test.ts (testPack fails the ai pack with a source-less companion and scores nothing; every registered pack passes); format-check.test.ts (the peer-conflict line, not added to the breaking count).

pnpm lint, pnpm typecheck, pnpm test pass (core 912, cli 347).

Pack tests, breaking sites, before (main + the react pack of #45, rebased) and after (this PR + the same pack, kept out of this PR):

pack precision before → after recall before → after breaking FP before → after
react 96% → 98% 69% → 69% 4 → 2
zod 100% → 100% 71% → 71% 0 → 0
stripe 100% → 100% 89% → 89% 0 → 0
ai 100% → 100% 36% → 36% 0 → 0

These numbers are from the run before the source requirement; that commit changes the shape of companions and the pack-test problems only, not what check finds, and the ai pack still passes pack test problems checks (pack-test.test.ts). I did not rerun the four pack tests after it.

zod and ai pass, unchanged. stripe is unchanged on the two repositories that install here (storefront, unkey); nextjs/saas-starter does not install in this sandbox (pnpm MODULE_NOT_FOUND), before and after, so pack test stripe fails here for that reason alone.

react: dev-docs/.../Homepage/index.tsx:52 and apps/docs/components/content/index.tsx:18 are gone, and next-mdx-remote-client stays at 1.1.2 as in the real upgrade. The two left are the useTransform(rSvg, …) call sites (below). No breaking finding comes from the peer conflict.

The ai companion and its source. With the new rule, ai-sdk-ollama (a community provider that only peers on ai; its 3.x range rejects ai 7) was left behind and pack test ai failed, because the real upgrade moved it to 4.1.0 and nothing in the registry distinguishes it from next-mdx-remote-client. The ai pack now declares it, in the two commits after the main one so they can be dropped or argued separately. The AI SDK's own 7.0 migration guide does not name ai-sdk-ollama or community providers (I searched it for both), so there is no AI SDK guide link that says it must move. The source is the package's own documentation, https://github.com/jagreehal/ai-sdk-ollama/blob/main/README.md, which states that each ai-sdk-ollama major targets one ai major (4.x is ai@^7, 3.x stays on ai@^6). If you want a different source, it is one line in packs/ai/index.ts. I did not feed the ground-truth with lists to check: that would hand the scorer's answer to the code it scores.

Not in this PR: shared-root findings across workspaces

In tldraw, useTransform(rSvg, …) is rejected at one call site in apps/examples and one in packages/tldraw, both because of the declaration useTransform.ts:6, already found once through another workspace. compile.ts anchors a repository parameter at the declaration only when two or more sites in the same compile trip over it; a single site stays its own diagnostic. Folding them needs the candidate cause carried on every single-site diagnostic, a merge step in check.ts that joins the workspaces' findings by declaration, and a scorer change. That touches the report model, so it goes in its own PR.

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 (docs/packs.md: companions and their source rule, peer conflicts)
  • 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 repositories named are public)
  • The rules in CONTRIBUTING.md ("Rules that do not bend") still hold

🤖 Generated with Claude Code

https://claude.ai/code/session_01AKRx4kuV6oi25MP5WguxW9

claude added 4 commits October 8, 2026 17:41
…companion only when its peers reject the target, to the lowest release that accepts it

A tsconfig that extends a package that is not installed is reported as
"not compiled: <path> (<reason>)" in the coverage line and contributes no
findings, in check and in the ground-truth draft script. A companion whose
installed copy accepts the new target is no longer moved; one that must move goes
to the lowest release above the installed one whose range accepts the target.

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>
…ble tsconfig

The workspace that reaches such a file (excalidraw's root reaches dev-docs) would judge it
under its own options. The file is counted as not compiled and its project named in the
coverage line.

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>
…e or the pack names it; report a rejecting peer as possible impact

Replaces the rule that moved any companion whose peer range rejects the target. Packages in a
pack's companions, release-group members published at the target's version and @types/* always
move, even when the installed range accepts the target. Any other package whose installed peer
range rejects the target stays where it is, is not compiled at another version, and is listed
as a peer conflict under possible impact, never as breaking.

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>
…i names ai-sdk-ollama

ai-sdk-ollama only peers on ai, and its installed range rejects ai 7, so it is a peer conflict
unless the pack says it moves with ai. The repository's upgrade moved it (4.1.0).

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 lucasbraum changed the title fix(check): skip projects with an unresolvable tsconfig; move companions only when their peers reject the target fix(check): skip projects with an unresolvable tsconfig; leave a rejecting peer in place and report it Oct 8, 2026
claude added 2 commits October 8, 2026 23:12
…es; pack test fails without one

companions entries are now { name, source }. source is the https URL of the official migration
guide or changelog that says the package moves with the leader, never ground truth alone.
pack test reports an entry with no source, a non-https source, or the pack's own package as a
problem and scores nothing. ai-sdk-ollama cites the package's own documentation (its README
states each major targets one ai major), because the AI SDK 7.0 guide does not name
community providers. docs/packs.md states the rule.

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>
…angeset is minor

New fields in the report (peerConflicts) and in the pack format (companions).

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
lucasbraum marked this pull request as ready for review October 9, 2026 07:14
@lucasbraum
lucasbraum merged commit 2e5c510 into main Oct 9, 2026
7 checks passed
@lucasbraum
lucasbraum deleted the claude/engine-fixes-react branch October 9, 2026 07:21
@uptide-release uptide-release Bot mentioned this pull request Oct 9, 2026
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.

2 participants