Skip to content

fix(fix): verify with the repository's own TypeScript; CLI tests stop writing to /repo - #40

Merged
lucasbraum merged 2 commits into
mainfrom
claude/green-main-env-tests
Oct 7, 2026
Merged

lucasbraum merged 2 commits into
mainfrom
claude/green-main-env-tests

Conversation

@lucasbraum

Copy link
Copy Markdown
Collaborator

What and why

Two tests failed in a cloud container, which runs as root with NODE_PATH set, but not in CI. CI on main is green. The latest run on 3e7f744 (#37) passed, and the last red run on main was 089f7b9 (#34), already followed by green runs. So no merge let a red main through. Both failures were environment-dependent: CI passes them by accident rather than by design.

  1. fix.test.ts › checks workspace source under its own resolution mode — a code bug. resolveCompiler loaded typescript with createRequire(...), and Node's resolution also searches NODE_PATH and the global folders. In the container NODE_PATH holds a global TypeScript 6.0.2, so a repository without its own compiler was verified with TS 6, which reports TS5107 (moduleResolution: node10 deprecated). In CI NODE_PATH is unset and the bundled 5.9 compiler is used, so the test passes there. The same thing would happen to any user with a global TypeScript on NODE_PATH. Now consumerCompilerDir takes node_modules/typescript from the repository or the nearest ancestor that has one, else the bundled compiler.
  2. cli.test.ts › uptide verify › ends with the summary and the next commands — a test bug. fixReport() set prBody: '/repo/.uptide/pr-body.md', and fix writes report.html next to the stored run. On a CI runner (non-root) creating /repo fails silently, so the engine's html path survives. As root the write succeeds, replaces html, the assertion fails, and /repo/.uptide/ is left behind. Now each report gets a fresh temporary directory. The test asserts the absolute path of its own stored run outside the working directory. One formatter assertion now matches its line regardless of column padding, since the padding depends on path lengths.

No test was skipped.

How it was verified

  • New compiler.test.ts cases: a workspace finds the root's node_modules/typescript; a repository without TypeScript gets the bundled compiler. The second case fails on the old resolveCompiler in the container (where it got the global 6.0.2) and passes now.
  • In the container, which has NODE_PATH and runs as root: core 852 passed (3 files skipped, as before), CLI 343/343, pnpm lint and pnpm typecheck clean. /repo/.uptide/report.html kept its old timestamp across the CLI run, so nothing writes there anymore.

Checklist

  • Tests for the change (the compiler fix starts with a test that fails)
  • pnpm lint && pnpm typecheck && pnpm test pass
  • README and docs updated if behavior changed (no doc describes compiler resolution beyond "the consumer's TypeScript", which is now what it does)
  • Changeset added (.changeset/consumer-compiler-only.md, patch)
  • No new dependency
  • 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_01B2T8GmEVnZwfNrugajEMqi


Generated by Claude Code

claude added 2 commits October 7, 2026 16:22
… NODE_PATH

resolveCompiler required typescript from the repository with Node's
resolution, which also searches NODE_PATH and the global folders: with a
global TypeScript 6 on NODE_PATH, a repository without its own compiler
was verified by it (TS5107 for moduleResolution node10, which CI, with no
NODE_PATH, never saw). It now takes node_modules/typescript of the
repository or an ancestor, else the bundled compiler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B2T8GmEVnZwfNrugajEMqi
Signed-off-by: Claude <noreply@anthropic.com>
fixReport's prBody was /repo/.uptide/pr-body.md, and fix writes the
report page next to it: where tests run as root the write succeeds, so
the verify summary test saw its own report path replaced, and /repo was
left behind. A fresh temporary directory per report; the summary test
asserts the absolute path of its own stored run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B2T8GmEVnZwfNrugajEMqi
Signed-off-by: Claude <noreply@anthropic.com>
@lucasbraum
lucasbraum merged commit 3e3a5fa into main Oct 7, 2026
6 checks passed
@lucasbraum
lucasbraum deleted the claude/green-main-env-tests branch October 7, 2026 18:41
lucasbraum pushed a commit that referenced this pull request Oct 7, 2026
main is this branch's base (#38, squash-merged) plus #40: the result
keeps this branch's tree and applies #40's changes on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B2T8GmEVnZwfNrugajEMqi
Signed-off-by: Claude <noreply@anthropic.com>
@uptide-release uptide-release Bot mentioned this pull request Oct 7, 2026
lucasbraum pushed a commit that referenced this pull request Oct 8, 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.5.0

### Minor Changes

- [#39](#39)
[`b9f9974`](b9f9974)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A verified
migration pack for the AI SDK (`ai`) 6 → 7: the renamed options
(`system` →
`instructions`, `onFinish` → `onEnd`, `onStepFinish` → `onStepEnd`,
`experimental_telemetry`
→ `telemetry`), `stepCountIs` → `isStepCount`, `fullStream` → `stream`,
`totalUsage` → `usage`
and the removed `experimental_*` options are rewritten by rule;
telemetry `metadata`, tool
`context` and the stream result helpers go to the agent with the guide;
what the compiler
cannot see (telemetry registration, results that now cover every step,
rejected system
messages) is listed for review. Scored against vercel/chatbot and
miurla/morphic at the
commit before their own upgrade: no false positive. `uptide pack test`
gains
`--update-fixtures`, fixture markers take the compiler's `message`, and
ground-truth
  repositories can use bun.

- [#39](#39)
[`b9f9974`](b9f9974)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` and `fix`
upgrade a package together with the packages that must move with it,
using the groups `list` already draws (family, peer link, shared pin),
each at the version
that agrees with the target. `fix ai` also bumps `@ai-sdk/react`,
`@ai-sdk/provider` and the
installed `@ai-sdk/*` providers in one install and one commit; check's
plan, the fix summary
and the PR description say which and why, and `fix` stops before
changing anything when a
member has no release that agrees. npm and pnpm installs resolve the
exact version first,
so a `^` range keeps the version the target pins. Ground-truth entries
take `with`, the
packages the real upgrade moved, and `pack test` fails when check would
leave one behind.

- [#38](#38)
[`adaf8f9`](adaf8f9)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - Migration packs
are a public, testable contract (docs/packs.md). `uptide pack new`
scaffolds
a pack (an example rule, a fixture pair, a test, empty ground truth) and
registers it;
`uptide pack test` scores packs against their fixtures and against
public repositories at the
commit before their upgrade, with precision and recall per rule, every
false positive and
false negative, and `--json` for CI. A pack is labeled verified only
with ground truth from at
least two public repositories and no false positive among its breaking
findings; otherwise it
ships as a candidate and `check`, `list` and `fix` treat the dependency
as generic.

### Patch Changes

- [#44](#44)
[`b4cc21f`](b4cc21f)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` no longer
dies with "Manipulation error: A syntax error was inserted." on packages
whose type aliases give their type parameters defaults (`type-fest` 4 →
5, `i18next` 23 → 26):
the alias body is read after the parameter list, not at the first
default's ` = `, so these
aliases are compared by the checker instead of breaking the comparison
program.

- [#40](#40)
[`3e3a5fa`](3e3a5fa)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` verifies
with the repository's own TypeScript (its `node_modules/typescript`, or
an
ancestor's) or the bundled one, and never with a TypeScript that
`NODE_PATH` or Node's global
folders happen to provide. A machine with a global TypeScript 6 made
verification report
  TS5107 deprecations the repository never sees.

- [#37](#37)
[`3e7f744`](3e7f744)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - Gemini is
labelled experimental: in `fix --help`, in the `LLM:` line `fix` prints,
and in the
docs. Its evaluation runs on the storefront fixture failed on service
errors (HTTP 503/429),
  so its migration quality is not established yet.

Co-authored-by: uptide-release[bot] <338861287+uptide-release[bot]@users.noreply.github.com>
lucasbraum added a commit that referenced this pull request Oct 8, 2026
…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>
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