Repository navigation
feat(packs): react 18 → 19 candidate pack (pack test fails, draft) - #45
lucasbraum wants to merge 3 commits into
Conversation
…ding) Scaffold only: the example rule is still in place and ground-truth.json is empty. The tldraw ground truth could not be drafted because Yarn 4 cannot be downloaded by corepack behind the sandbox proxy. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiuU3en7qKGdWHBGbAZirY Signed-off-by: Claude <noreply@anthropic.com>
…ldraw Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PiuU3en7qKGdWHBGbAZirY Signed-off-by: Claude <noreply@anthropic.com>
… are one site, at the tsconfig that causes them
apps/dotcom/client reads JSX element types through the global JSX namespace
("jsx": "preserve", no "jsxImportSource"); after the upgrade its lockfile
keeps a second copy of @types/react (18.3.23, for a dependency pinned to
16 || 17 || 18) beside @types/react 19, and every element in every file
errors with one fix, the "jsxImportSource": "react" the upgrade added.
Those 1745 expected sites are one, at that line of the tsconfig, with the
cause in its note. verification.json records the run of uptide-dev/uptide
with the check engine fixes (breaking precision 88%, recall 64%).
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>
…ckstep companions, root causes at the edit (#46) ## What and why The react pack's ground truth (#45) scored `check` at 4% precision / 1% recall on breaking findings: `check` did not move `@types/react` or `react-dom` with `react`, compiled almost none of the monorepos' files, and called every type-surface change at a site the compiler never saw "breaking". This fixes the engine, in five steps, with `pack test react` on #45 as the acceptance test. 1. **Companions.** `@types/<pkg>` moves with `<pkg>` by name, to the release that types the target (newest at the target's major.minor, else its major, `src/check/types-release.ts`; `prepare` diffs the types at that version). A package whose release at the target's own version asks for the target (react-dom 19.0.0 peer-requires react ^19.0.0) is released in lockstep and moves too, even when its installed copy would accept the target. The closure is transitive (`@types/react-dom` follows `react-dom`, which follows `react`), every workspace's dependencies are candidates (the root's hoisted `@types/react` serves the app that declares `react`), and the plan is carried by every report of the package and kept by the merged entry. `check react` 18 → 19 moves react, react-dom, @types/react and @types/react-dom together, and `fix` installs them together. A workspace that only peer-depends on a package (excalidraw's `packages/excalidraw`) resolved it through its types and then looked for the runtime copy in its own `node_modules` alone; it now finds the hoisted copy, as Node does. 2. **Compile coverage.** Why most files were not compiled, found and fixed: - workspace packages declared `workspace:*` under Yarn Berry (lockfile `0.0.0-use.local`) and npm were never mapped to their source, and the bundled compiler does not resolve a `main` pointing at a `.ts` file from `node_modules`: the declared specifier is now what the engine reads, hoisted links are found from ancestor `node_modules`, and the mapping follows the linked packages' own links (`@tldraw/editor` → `@tldraw/utils`); - a scoped program dropped the tsconfig's ambient `.d.ts` files (`vite-env.d.ts`, `css.d.ts`), so every `*.module.css` import failed; they are added; - the bundled compiler is TypeScript 6, whose defaults for unset options differ from TypeScript 5's (`strict` on, no automatic `@types`, modern target/module/resolution, interop on): a repository on TypeScript 5 is now read with TypeScript 5's defaults made explicit (`src/adapters/typescript/legacy-options.ts`). excalidraw's `packages/excalidraw` went from 4,212 baseline errors (missing jest globals under strict) to 15. Every analyzed package prints `compiled N of M files in K workspaces; skipped: <reason (count)>`, and the verdict says `types partly verified: ...` instead of `compiled against <version>` when not every file was (`CompileSignal.coverage`, `PackageReport.compile.coverage`, `src/check/verdict.ts`). 3. **Breaking means confirmed, in every tier.** `confirmBreaking` (was `confirmGeneric`) runs for the verified tier too: a finding is breaking only with evidence (compiler, runtime probe, module format, removed export, or the pack's own `detect`). Unconfirmed surface changes are "possible impact": counted apart in the row (`✗ 3 breaking, 17 possible`), listed under their own line in the package section, never counted as breaking. Packs keep their rules. 4. **One finding for a root cause that is one edit.** Two new anchors, marked `anchorOnly` (one site, the diagnostics as evidence; `fix`, the plan, `sitesOf` and `pack test` count one): - a compiler option: when the target drops the global `JSX` namespace and the workspace's `"jsx": "preserve"`/`"react"` reads it (no `jsxImportSource`), the TS7026/TS2602 diagnostics are anchored at the `jsx` line of the tsconfig that sets it, through `extends` (`src/adapters/typescript/config-cause.ts`); - a repository parameter: an argument (TS2345) or JSX attribute (TS2322) rejected at a parameter or prop declared in the repository with a type from outside it, when two or more sites trip it (`parameterCause` in `cause.ts`): `usePassThroughWheelEvents(ref: RefObject<HTMLElement>)` in `packages/editor`, tripped at eleven call sites across tldraw's packages once `useRef` returns `RefObject<HTMLElement | null>`, is one finding at the parameter, under its repository path. A pack rule claims an anchor when it claims most of the errors under it (`ruleFor`). 5. **Ground truth of #45.** The only expectation change: tldraw's 1,745 JSX element diagnostics in `apps/dotcom/client` are one site, at the `"jsx"` line of its tsconfig (pushed to #45 as 3180034, with `verification.json` recorded from this engine). What the compile showed while doing it: those errors come from **two copies** of the React types. tldraw's lockfile after its own upgrade still keeps `@types/react` 18.3.23 for a dependency pinned to `16 || 17 || 18`, beside `@types/react` 19; with one copy (what the overlay links) `"jsx": "preserve"` resolves element types through the UMD `React` namespace and nothing errors. `check` does not model the package manager keeping a nested copy, so it does not predict that site (the one `global-jsx-namespace` false negative left in tldraw at the tsconfig). The config-cause anchor fires when the namespace really is gone from the program. ## How it was verified `pnpm uptide pack test <pack>` before (main + #45) and after (this branch + #45), same machine, same ground-truth cache. | pack | before: breaking precision / recall | after | | --- | --- | --- | | react (#45) | 4% / 1% (477 FP, 20 TP, 1854 FN); react-dom, @types/react, @types/react-dom left behind | **88% / 64%** (11 FP, 83 TP, 46 FN); all three moved, plus next-mdx-remote-client in tldraw | | zod | 100% / 71% (PASS, verified) | 100% / 71% (PASS, verified), identical | | stripe | 100% / 90% (PASS, verified; 18 TP, 2 FN over 3 repositories) | 100% / 90% (PASS, verified), identical | | ai | 100% / 36% (PASS, verified) | 100% / 36% (PASS, verified), identical | react, per rule, after: removed-react-dom 100%/100% (18), use-ref-argument 100%/80%, ref-callback-return, mutable-ref-object, props-with-ref 100%/100%, element-props-unknown 100%/60%, ref-object-nullable 91%/37%, global-jsx-namespace 100%/36%. The 11 breaking false positives left are listed one by one below. **Storefront, before and after** (`check zod stripe` on `fixtures/repos/storefront`, built CLI, main vs this branch): the rows, rules and counts are identical; the only difference is the coverage line under each package. The README sample is updated with those two lines, and the tier table now says that breaking means confirmed in both tiers and what "possible impact" is. There is no `site/` on main. ```console zod 28 breaking · compiled against 4.6.5: 28 new type errors compiled 11 of 11 files in 2 workspaces ✗ New error API (required_error → error) 24 sites auto-fixable ... stripe 4 breaking · compiled against 23.0.0: 4 new type errors compiled 7 of 7 files in 1 workspace ✗ Test fixture casts widened renewal.test.ts:7 auto-fixable ``` A package with possible impact prints, under its rules: ```console ? possible impact: 17 sites in 9 files the types changed where this code uses them; nothing confirmed that it breaks, so these are not counted as breaking ? RefObject#current changed type 12 sites needs the agent (LLM) ``` **The 11 remaining react false positives** (breaking, `pack test react` after; for #45, not fixed here): 1. `packages/excalidraw/components/ButtonIconSelect.tsx:52`, `IconPicker.tsx:140`, `JSONExportDialog.tsx:90`, `LayerUI.tsx:346`, `MobileMenu.tsx:92`, `Stats/index.tsx:231` (6): TS2322 `Type 'Element' is not assignable to type 'ReactNode'` at a JSX child. The same error the ground truth expects a few lines away in each file (`ButtonIconSelect.tsx:40`, `IconPicker.tsx:117/221`, `JSONExportDialog.tsx:45`, `LayerUI.tsx:332/352`, `MobileMenu.tsx:76-146`, `Stats/index.tsx:188`): the truth was drafted with the repository's TypeScript 4.9, which reports it at the element's first line, and the bundled TypeScript 6 reports it at the offending child. A line-tolerant match, or re-drafting the truth with the same compiler, resolves these. 2. `dev-docs/src/components/Homepage/index.tsx:52`: the same TS2322 in dev-docs, where the truth lists only line 1 (the `import React` the upgrade removed). 3. `examples/excalidraw/with-script-in-browser/index.tsx:16`: `createRoot(rootElement)`, react-dom's `createRoot` container parameter changed; the example's one file cannot resolve `@excalidraw/excalidraw` (unbuilt `dist`), so the compiler skipped it and the finding stands on the companion's diff plus the runtime probe. The truth has no site in that example. 4. `apps/examples/src/examples/custom-components/CustomComponentsExample.tsx:11` and `packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx:59`: `useTransform(rSvg, ...)`, TS2345 `RefObject<SVGSVGElement | null>` not assignable to the hook's `RefObject<HTMLElement | SVGElement>`. The truth expects the parameter (`packages/editor/src/lib/hooks/useTransform.ts:6`), which `check` also reports where two sites in one workspace trip it; these two workspaces each trip it once, and the ≥2 rule is per workspace compile. Clustering across workspaces resolves them. 5. `apps/docs/components/content/index.tsx:18`: TS2307 `Cannot find module 'next-mdx-remote-client/rsc'` after the companion move of next-mdx-remote-client 1.1.2 → 2.1.12 (the version the upgrade also chose). A real break of the companion, reported under react; the truth lists react's sites only. **CI and stripe's third repository.** `nextjs/saas-starter` pins `pnpm@12.10.1`. It failed to install here only because this machine runs pnpm through a corepack shim (corepack 0.34 looks for `bin/pnpm.cjs`, which pnpm 12 no longer ships). The Packs job installs pnpm with `pnpm/action-setup`, not corepack, and `pack test` sets `npm_config_manage_package_manager_versions`; run that way here, pnpm 10 switches itself to 12.10.1 and the repository installs ("Done in 58s using pnpm v10.28.0"), after which stripe scores 100% / 90% over all three repositories, before and after. CI should not hit the problem; if it does, the Packs job log will show the same `MODULE_NOT_FOUND` for `bin/pnpm.cjs`. Coverage, from `check react` with the built CLI: - excalidraw: `compiled 355 of 356 files in 5 workspaces; skipped: most files cannot resolve their imports at the installed version (1)` (before: `4 of 5 files cannot resolve their imports ... compile signal skipped`, `packages/excalidraw` not analyzed at all); - tldraw, `apps/dotcom/client`: `compiled 164 of 164 files in 1 workspace` (before: `130 of 163 files cannot resolve their imports`); - supabase/supabase (fixtures/corpus.json, react 19.2.6 → 19.3.0): `list` output byte-identical to main; `check react` prints `compiled 1109 of 1110 files in 12 workspaces; skipped: most files cannot resolve their imports at the installed version (1)` and `↑ upgrades with @types/react 19.2.14 → 19.3.0, @types/react-dom 19.2.3 → 19.3.0, react-dom 19.2.6 → 19.3.0`. `apps/studio` is over the memory budget on this machine with main and with this branch alike; `apps/www` (449 files) is analyzed now, where main left it out, and reports 64 compiler-confirmed TS2322 sites against @types/react 19.3.0's SVG props. Main reported `0 breaking · compiled against 19.3.0: 0 new type errors` for the same upgrade with that workspace unanalyzed. - tldraw, whole repository, built CLI: `compiled 616 of 616 files in 18 workspaces`, 2m42s (before: 7m, most workspaces skipped). `pnpm lint`, `pnpm typecheck`, `pnpm test` pass (core 895, cli 346). New tests: companions (react 18 → 19, lockstep, workspace visibility), types-release, legacy-options, config-cause, parameter cause (cause.test.ts), anchor-only clusters (anchor.test.ts, unattributed.test.ts), verdict coverage, workspace-source (hoisted, transitive), readInstalled (`workspace:*` under Yarn Berry), CLI possible-impact and coverage lines. Known limits, said plainly: - the engine thread briefly ran out of heap on tldraw and supabase while this was being built: gathering every workspace's manifests for the companion plan loaded a program per workspace; manifests are now read from disk (d82a922) and both repositories run through the built CLI again. - a parameter cause clusters per workspace compile; the same parameter tripped once in each of two workspaces stays two sites. - `check` links one copy of a target's types; an install that keeps a nested older copy (tldraw's `@types/react` 18 beside 19) is not modeled. ## Checklist - [x] Tests for the change (a bug fix starts with a failing test) - [x] `pnpm lint && pnpm typecheck && pnpm test` pass - [x] README and docs updated if behavior changed (`docs/architecture.md`, `docs/cli.md`, README sample and tier table) - [x] Changeset added if a user would notice (`pnpm changeset`): minor, "What changed in check" - [x] No new dependency, or it was agreed in an issue - [x] No private code, names or paths in fixtures, tests or docs - [x] The rules in CONTRIBUTING.md ("Rules that do not bend") still hold 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U --------- Signed-off-by: Lucas Braum <lucas.braum@deskfy.io> Co-authored-by: Lucas Braum <lucas.braum@deskfy.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…y as fallback (#48) ## 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 - [x] Tests for the change (a bug fix starts with a failing test) - [x] `pnpm lint && pnpm typecheck && pnpm test` pass - [x] README and docs updated if behavior changed - [x] Changeset added if a user would notice (`pnpm changeset`) - [x] No new dependency, or it was agreed in an issue - [x] No private code, names or paths in fixtures, tests or docs - [x] The rules in CONTRIBUTING.md ("Rules that do not bend") still hold 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U --------- Signed-off-by: Lucas Braum <lucas.braum@deskfy.io> Co-authored-by: Lucas Braum <lucas.braum@deskfy.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
react pack on main after #46 and #48 (rebased, rerun)PR #45's head (
None of the 4 remaining false positives is fixable in the pack or its ground truth without editing an expectation to fit Uptide's output, so I stopped:
No other breaking false positive is left. The pack stays a candidate with Decision for you: push Generated by Claude Code |
…cting peer in place and report it (#49) ## 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 - [x] Tests for the change (a bug fix starts with a failing test) - [x] `pnpm lint && pnpm typecheck && pnpm test` pass - [x] README and docs updated if behavior changed (docs/packs.md: `companions` and their source rule, peer conflicts) - [x] Changeset added if a user would notice (`pnpm changeset`) - [x] No new dependency, or it was agreed in an issue - [x] No private code, names or paths in fixtures, tests or docs (the repositories named are public) - [x] The rules in CONTRIBUTING.md ("Rules that do not bend") still hold 🤖 Generated with [Claude Code](https://claude.ai/code/session_01AKRx4kuV6oi25MP5WguxW9) https://claude.ai/code/session_01AKRx4kuV6oi25MP5WguxW9 --------- Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
react pack on main after #49 (rerun, nothing pushed)Ran
Remaining breaking false positives, both engine (cause not traced across workspaces):
Both are Existing packs unchanged: Sandbox note: tldraw (Yarn 4) only installed after
Generated by Claude Code |
…to one declaration (#51) ## What and why Engine item from the #45 thread: cause not traced across workspaces. A mismatch at an argument traces to the repository parameter it lands on (`parameterCause`), but the compile signal anchored the finding there only when two or more sites in the *same* workspace tripped it. tldraw's `useTransform(ref)` is called from three workspaces; `apps/examples` and `packages/tldraw` have one site each, so `check` reported those two as findings of their own beside the anchor in `packages/editor`, the one place upstream edited (`useTransform.ts:6`). Those two were the last breaking false positives of the react pack. Now, when diagnostics in different workspaces trace back to the same local declaration (same file and symbol), `check` emits one breaking finding anchored at that declaration and lists the call sites under it as evidence. - **Tracer** (`adapters/typescript/compile.ts`): a diagnostic keeps the declaration it traces to as `root`, whether or not enough sites in its workspace share it for `cause` to be set. `parameterCause` itself is unchanged. - **Findings** (`check/unattributed.ts`): a call-site finding carries `Finding.root` (workspace-relative, like `usage.file`); an in-workspace parameter anchor carries it too. - **Merge** (`check/check.ts` → `check/root-cause.ts` `foldSharedRoots`): after the workspaces are merged and roots are prefixed to repository paths, two or more sites at one root, or a site plus the anchor another workspace already produced, become (or join) one anchored finding: `anchorOnly`, `downstream` = the sites (each with its `workspace`), `details` opening with `N call sites in M workspaces`, `fixability: assisted`, the pack rule the sites agreed on. The anchor counts as one site in the verdict. - **Report model**: JSON gains `root` on call-site and anchored findings and `workspace` on `downstream` entries; nothing is renamed or removed. The terminal `--details` prints `N call sites in M workspaces, for example` with up to 8 sites under the anchor (instead of `evidence: N errors at the target` for a declaration root; config anchors keep that wording). The HTML report lists the call sites with their workspaces under the anchor site. - **Scorer** (`packs/tooling/pack-test.ts` `predictedSites`): the anchor is the predicted site, so a ground-truth finding at the declaration matches it; a call site listed as evidence under an anchored finding is neither a true nor a false positive, even when another finding lands on that line. On tldraw the merged finding reads: `packages/editor/src/lib/hooks/useTransform.ts:6` · `7 call sites in 3 workspaces` (5 in `packages/editor`, 1 in `apps/examples`, 1 in `packages/tldraw`). No pack rule, expectation, ground truth or companion was edited. ## How it was verified New tests: `adapters/typescript/cause.test.ts` (a single site keeps its `root`), `check/root-cause.test.ts` (fold across workspaces, join an existing anchor, a lone site stays, the call-sites line), `check/check.test.ts` (`mergeAcrossWorkspaces` prefixes `../editor/...` roots from two workspaces to one repository path and folds them), `packs/tooling/pack-test.test.ts` (anchor scored, evidence excluded even under another finding), `format-check-details.test.ts` and `html/render.test.ts` (the `N call sites` line and the listed sites). `pnpm lint && pnpm typecheck && pnpm test` pass (core 917 tests, cli 349). Pack tests, before (main at 2e5c510) and after, run in a scratch worktree with the react pack from #45's branch (`claude/pensive-galileo-mw5yx8`) copied in uncommitted; breaking findings: | pack | before: precision / recall, breaking FP | after: precision / recall, breaking FP | |---|---|---| | react (candidate) | 98% / 69%, 2 (the two `useTransform` sites) | **100% / 69%, 0** | | zod | 100% / 71%, 0 | 100% / 71%, 0, identical | | stripe | 100% / 90%, 0 | 100% / 90%, 0, identical | | ai | 100% / 36%, 0 | 100% / 36%, 0, identical | react overall (every severity): 91% / 70% → 93% / 70%; the 7 remaining non-breaking false positives (6 deprecated, 1 unverified) are unchanged. `pack test react` still exits non-zero only because #45's `verification.json` records 96% / 4 false positives and no longer matches; it is not edited here. ## Checklist - [x] Tests for the change (a bug fix starts with a failing test) - [x] `pnpm lint && pnpm typecheck && pnpm test` pass - [x] README and docs updated if behavior changed (`docs/architecture.md`, `docs/cli.md`) - [x] Changeset added if a user would notice (`pnpm changeset`): minor - [x] No new dependency, or it was agreed in an issue - [x] No private code, names or paths in fixtures, tests or docs - [x] The rules in CONTRIBUTING.md ("Rules that do not bend") still hold 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U --- _Generated by [Claude Code](https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U)_ --------- Signed-off-by: Lucas Braum <lucas.braum@deskfy.io> Co-authored-by: Lucas Braum <lucas.braum@deskfy.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
|
Superseded by #53 Generated by Claude Code |
## Pack `react`, `>=18 <19` → `>=19 <20`. Companions move with it: `react-dom`, `@types/react` and `@types/react-dom`, as the engine's companion rules decide (not listed by the pack). Supersedes #45: same pack and ground truth, brought onto current main (merge of `main` at 0de525c, no conflict), with the changeset, the README table and a test fixed up. ## Sources - React 19 Upgrade Guide: https://react.dev/blog/2024/04/25/react-19-upgrade-guide - React 19.0.0 changelog: https://github.com/facebook/react/blob/main/CHANGELOG.md#1900-december-5-2024 - types-react-codemod (preset-19): https://github.com/eps1lon/types-react-codemod ## Rules and behavior notes Rewrites by rule: - `use-ref-argument`: `useRef<T>()` → `useRef<T>(undefined)`. - `ref-callback-return`: `ref={(el) => (x = el)}` → a block body (sites from `detect`). - `mutable-ref-object`: `React.MutableRefObject` → `React.RefObject`. To the agent, with a guide: - `ref-object-nullable`: a parameter or prop typed `RefObject<T>` once `useRef` returns `RefObject<T | null>`. - `global-jsx-namespace`: the removed global `JSX` namespace (one finding at the tsconfig's `jsx` line, the elements as evidence). - `element-props-unknown`: untyped `element.props`. - `removed-react-dom`: `ReactDOM.render`, `hydrate`, `unmountComponentAtNode`, `findDOMNode`. - `props-with-ref`. Behavior notes, listed for review (the compiler cannot see them): `errors-not-rethrown`, `removed-legacy-apis`, `act-moved-to-react`, `element-ref-deprecated`, `strict-mode-memoization`, `suspense-fallback-first`, `umd-removed`, `javascript-urls-blocked`, `use-reducer-types`. ## Ground-truth repositories | Repository | Pinned commit (before the upgrade) | Upgrade | With | Expected findings | |---|---|---|---|---| | excalidraw/excalidraw | 9ee0b8ffcbd3664a47748a93262860321a203821 | react 18.2.0 → 19.0.0 | react-dom 19.0.0, @types/react 19.0.10, @types/react-dom 19.0.4 | 73 | | tldraw/tldraw | 5957867a9c5c205a0b0c3665be443138b8014a79 | react 18.3.1 → 19.2.1 | react-dom 19.2.1, @types/react 19.2.7, @types/react-dom 19.2.3 | 60 | Expected findings were drafted with `pnpm packs:truth` from each repository's own TypeScript and its upgrade commit, and read by hand; tldraw's 1,745 JSX element diagnostics are one site at the tsconfig that causes them (#45). Rules, expectations, ground truth and companions are unchanged from #45's last state. ## `uptide pack test` output `verification.json` on this branch records the run on main after #51 (commit 8573cc0); `pack test react` was not rerun here, since the merge touched neither `packages/core/src/check` nor the pack. That run: ``` uptide pack test react · verified fixtures 3 cases, 6 sites · pass ground truth excalidraw/excalidraw@9ee0b8ffcbd3 react 18.2.0 → 19.0.0 predicted 60, expected 73, false positives 1, false negatives 14 tldraw/tldraw@5957867a9c5c react 18.3.1 → 19.2.1 predicted 39, expected 60, false positives 6, false negatives 26 rule precision recall tp fp fn element-props-unknown 100% 60% 6 0 4 generic 4% 33% 1 26 2 global-jsx-namespace 100% 51% 20 0 19 mutable-ref-object 100% 100% 2 0 0 props-with-ref 100% 100% 1 0 0 ref-callback-return 100% 100% 1 0 0 ref-object-nullable 100% 37% 20 0 34 removed-react-dom 100% 100% 18 0 0 use-ref-argument 100% 80% 4 0 1 overall (sites) 93% 70% 92 7 40 breaking (sites) 100% 69% 89 0 40 ``` Breaking findings: precision 100% (89 sites, none false), recall 69%, on 2 public repositories. The 7 non-breaking false positives are deprecated or unverified findings and do not count toward the verdict. ## What is not covered - Recall on breaking sites is 69%: 40 expected sites are missed, most of them `ref-object-nullable` (34) and `global-jsx-namespace` (19 in the per-rule table, counted at the tsconfig anchor) sites the compiler does not reach, plus 4 `element-props-unknown` and 1 `use-ref-argument`. They are listed by `pack test react` as false negatives. - Behavior changes the compiler cannot see are reported as notes for review, never rewritten. - The engine links one copy of the target types; a nested second `@types/react` copy, as a real install can keep, is not modeled. ## This PR on top of #45 - `.changeset/react-pack.md` is `minor` (a new verified pack) and opens with the two repositories; numbers unchanged. - README "Verified packs" regenerated with `pnpm docs:packs`; `pnpm docs:packs --check` and `pnpm docs:links` pass. - `packages/core/src/fix/isolate.test.ts` used react as its example of a package without a pack; it is vitest now, like the two `cli.test.ts` cases. No other test did. - `pnpm lint`, `pnpm typecheck`, core (919 tests) and CLI (350 tests) pass. ## Checklist - [x] Every rule and note comes from a source listed in `meta.sources` - [x] Every rule that rewrites or detects has a fixture case, with a site it must leave alone (`keep`) - [x] Ground truth: public repositories at the commit before their upgrade; expected findings read from their own upgrade commit and the compiler, never copied from Uptide's output - [x] At least two public repositories for `verified`; otherwise the pack ships as a candidate - [x] `pnpm uptide pack test <package>` passes and its output is pasted above; `verification.json` written with `--write` - [x] `pnpm lint && pnpm typecheck && pnpm test` pass - [x] Changeset added (`pnpm changeset`): a new pack is something users notice - [x] No private code, names or paths anywhere: fixtures are synthetic, ground truth is public Supersedes #45. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U --- _Generated by [Claude Code](https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U)_ --------- Signed-off-by: Claude <noreply@anthropic.com> Signed-off-by: Lucas Braum <lucas.braum@deskfy.io> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Lucas Braum <lucas.braum@deskfy.io>
Pack
react>=18 <19→>=19 <20(companions:react-dom,@types/react,@types/react-dom). Firsttodoentry inpacks/queue.json; no open pack-request issue or PR existed. Scaffolded withuptide pack new react --from ">=18 <19" --to ">=19 <20"and scored only withuptide pack test react.Status: candidate, and
pack test reactFAILS (477 false positives among breaking findings). The failure comes from whatcheckreports, not from the rules or from an edited expectation; see "Tooling friction". This draft should not merge as is. A human decision is needed (below).Sources
Rules and behavior notes
Rewrites:
use-ref-argument(breaking, TS2554):useRef<T>()→useRef<T>(undefined). Same edit astypes-react-codemod useRef-required-initial; declines two calls on a line.ref-callback-return(breaking,detectthrough the syntax tree of arefattribute):ref={(el) => (x = el)}→ref={(el) => { x = el; }}. Object keys namedrefand non-assigning callbacks are left alone.mutable-ref-object(deprecated, TS6385):React.MutableRefObject→React.RefObject; an imported bareMutableRefObjectis declined (needs the import edited).To the agent, with a guide:
ref-object-nullable(RefObject<T>→RefObject<T | null>, fixed in the declaration, not at the error),global-jsx-namespace,element-props-unknown,removed-react-dom(render,hydrate,unmountComponentAtNode,findDOMNode; says what thereact/19/migration-recipecodemod leaves),props-with-ref.Behavior notes (all
decision):errors-not-rethrown,removed-legacy-apis(propTypes/defaultProps, legacy context, string refs, createFactory, shallow renderer),act-moved-to-react,element-ref-deprecated,strict-mode-memoization,suspense-fallback-first,umd-removed,javascript-urls-blocked,use-reducer-types. None hasdetect, so none is scored.Ground-truth repositories
Expected findings: the repository's own TypeScript with the target and its companions linked in (new diagnostics only), plus the lines the upgrade commit changed for react; read by hand (
ground-truth.json$commentlists what was left out on purpose).9ee0b8ffcbd3664a47748a93262860321a20382131e8476c78aca31df1342dc26903e42301d2c3fd5957867a9c5c205a0b0c3665be443138b8014a79f7c148187c8113aa102592e07d87b7877a9376231751 of tldraw's expected sites are one cause (the JSX namespace; the upgrade added
jsxImportSource: "react"to its tsconfigs), kept as the compiler reports them.uptide pack testoutputpnpm uptide pack test react --write(fixtures pass: 3 cases, 6 sites):Breaking precision 4% (20 of 497), recall 1% (20 of 1874).
verification.jsonrecordscandidate.Existing packs, unchanged (
verification.jsonuntouched, all PASS):zodverified, breaking precision 100%, recall 71%;stripeverified, 100% / 90%;aiverified, 100% / 36%.pnpm lint,pnpm typecheckandpnpm testpass (core 878 passed, cli 345 passed). Changeset added.What isn't covered
checkreports as breaking at sites the compiler accepts:RefObject#current,ReactNodewidened,useContext,ChangeEvent#target,ReactElement,JSX.Element(most of them in tldraw). No rule can lower a finding's severity, and I did not edit the ground truth.checkskipped the compile signal in most files (see below), souse-ref-argument,global-jsx-namespaceandremoved-react-domrarely fire there. Fixtures cover their rewrites.forwardRef→ ref-prop rewrite (React 19 does not require it); tldraw's casts on spread menu-item props (no diagnostic, not attributable to a react API); tests of other packages (vitest, testing-library).useReducertype arguments (a note),propTypes/defaultProps, legacy context, string refs (notes; the compiler barely sees them).Tooling friction
checkdoes not move@types/react/@types/react-domwithreact(nothing in their manifests links them), and reported noreact-domcompanion in either repository, althoughcompanionsOffindsreact-dom19.0.0 for react 19.0.0 when given the registry manifests.pack testfails the pack on that (leftBehind), whatever the findings. Without the types moved, the compile runs against the latest@types/react(19.3.0), not the one the upgrade chose.packages/excalidraw("imports react without declaring it, and it does not resolve"). Most of excalidraw's expected sites are there.pack testexits non-zero for a candidate with breaking false positives, so a candidate pack cannot land with CI green unless its findings are clean.packs:truthcould not install tldraw (Yarn 4 via corepack) untilCOREPACK_NPM_REGISTRY=https://registry.npmjs.orgwas set; the compiler draft of 1.8k sites needs hand grouping by cause.Decisions for you
Fix the engine (companions for
@types/*; severity of unconfirmed type-diff changes; peer-only workspaces) before this pack, or merge as a failing candidate: CI's Packs job will be red as long as there are breaking false positives.Whether tldraw's JSX-namespace diagnostics (1751 sites, one tsconfig cause) belong in the ground truth one by one or as a single site.
Every rule and note comes from a source listed in
meta.sourcesEvery rule that rewrites or detects has a fixture case, with a
keepsiteGround truth from public repositories, never copied from Uptide's output
At least two public repositories for
verified: met, but precision is not 100%, so it ships as a candidatepnpm uptide pack test reactpasses: it fails (477 false positives)pnpm lint && pnpm typecheck && pnpm testpassChangeset added
No private code, names or paths
🤖 Generated with Claude Code
https://claude.ai/code/session_01PiuU3en7qKGdWHBGbAZirY
Generated by Claude Code