Repository navigation
feat(packs): stricter verified gate: real breaking sites, unexercised rewrites run as assisted (UPT-48) - #155
Merged
Conversation
… rewrites, behavior-only class (UPT-48) verifiedGate replaces statusOf. Besides two public repositories, no breaking false positive and a breaking site found, a verified pack needs five expected breaking sites across public ground truth (fixture repositories never count). An upgrade whose every real expected site is a behavior note's (no rule or compiler site, ioredis 6) is judged on its notes: five note sites, one found, no false positive on a note or a mechanical rule. A mechanical rule with no true positive in public ground truth is reported as unexercised; it does not change the status. pack test prints the class, the site counts and each reason a pack is a candidate, and the computed record carries them. overclaims() is unchanged: a record fails only when it claims verified where the run measures candidate. No verification.json is written here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
…e minimum (UPT-48) activePacks wraps each pack with the unexercised rules its verification.json records: transform refuses their edits (fix hands the site to the agent with the guide) and check demotes their sites to assisted. The wrapper checks the rule of the applied edit, so hand-written transforms (zod, stripe) are covered; stripe lists the rules its transform edits (meta-free `mechanical`) so the gate counts them as mechanical. meta.minBreakingSites lowers the breaking-site minimum for one pack, and pack test refuses it without a reason under '## Breaking-site minimum' in its RESEARCH.md. The README packs table gains a breaking-sites column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
… react are candidates pack test --write on every pack but joi and redis (both stay candidate by choice). Each record gains its class, real breaking and behavior-note site counts, and the unexercised mechanical rules, which check and fix now run as assisted. Only two statuses change: @sentry/browser (3 real breaking sites) and @sentry/react (2) become candidates; the Sentry RESEARCH.md says what they need and where to look. No precision, recall or count changed in any record. zod: no merged public 3 -> 4 upgrade that read formErrors/.errors off an untyped catch binding was found, so error-members runs as assisted; no ground truth was added. stripe's pin and fixture-cast edits have no real site and run as assisted too. The README packs table shows each pack's breaking sites. --write formats a record as biome does, so a written record passes lint. The fix machinery tests keep zod's required_error rewrite as written (it is assisted in the registry now). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
lucasbraum
marked this pull request as ready for review
October 15, 2026 06:58
…rites the full record The smoke ran fix without the agent and expected zod's required_error sites to migrate by rule; UPT-48 runs that rewrite as assisted, so the fixtures now use z.record(value) (record-key: mechanical, exercised by real ground truth) with the same site counts, and the smoke checks for that rule's line and the two-argument form. The fix machinery is tested the same way; the gate is unchanged. pack new writes verification.json with the gate's fields (class, site counts, the example rewrite as unexercised) in the formatter's layout, so a fresh pack's first pack test finds its record current. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
Collaborator
Author
|
CI on 02f3019 failed in two places, both from this PR, and ac063b8 fixes them:
The gate is unchanged. |
Merged
This was referenced Oct 15, 2026
lucasbraum
pushed a commit
that referenced
this pull request
Oct 15, 2026
…d-move fixes Rebased on main after #155, #156 and #157 and re-ran pack test --write on the unchanged ground truth: breaking 3 TP / 0 FP / 17 FN, and the run now measures verified. The record stays candidate (allowed since #154): seven rewrite rules are unexercised by public ground truth and recall is 15%. README table regenerated with docs:packs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FX2nBoNEvDh9v71yDhEzxL Signed-off-by: Claude <noreply@anthropic.com>
7 of 8 tasks
lucasbraum
pushed a commit
that referenced
this pull request
Oct 15, 2026
…d-move fixes Rebased on main after #155, #156 and #157 and re-ran pack test --write on the unchanged ground truth: breaking 3 TP / 0 FP / 17 FN, and the run now measures verified. The record stays candidate (allowed since #154): seven rewrite rules are unexercised by public ground truth and recall is 15%. README table regenerated with docs:packs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FX2nBoNEvDh9v71yDhEzxL Signed-off-by: Claude <noreply@anthropic.com>
lucasbraum
added a commit
that referenced
this pull request
Oct 15, 2026
## Pack `express` `>=4 <5` → `>=5 <6` (default target 5.3.0). Linear: UPT-55. **Candidate by choice.** The branch is rebased on UPT-48 (#155) and UPT-63 (#157). `pack test` passes and measures verified. The record stays `candidate` until a person has reviewed it. ## Sources All pinned and read 2026-10-14 (ledger in `packages/core/src/packs/express/RESEARCH.md`): - Migration guide, expressjs.com `4721193` - History.md at v5.3.0 (`cf6722b`) - path-to-regexp Readme at v8.4.2 - body-parser HISTORY at 2.3.0 - @types/express 4.17.25 / 5.0.6 and @types/express-serve-static-core 4.19.9 / 5.1.3 declarations - expressjs/codemod at `2f7f1e5`. These were read, never run. `@expressjs/codemod` 0.0.5 is deprecated on npm. ## Rules and behavior notes No rule rewrites. Every codemod rewrite is either unsafe whatever the surrounding code, or never met a real site: - `del` and `send` don't check the receiver. - `param` drops values that came from the body or query. - `dotfiles: 'allow'` serves dotfiles Express 4 hid. - Route paths are the owner's choice, and handlers' `req.params[0]` changes with them. Rules: - `route-syntax` (detect): path-to-regexp 8 route strings on an app or router the file binds. Covers `express()`, `Router()`, typed parameters and properties, `this.app`, a `const` list, and `for…of` over one. One site per registration. - `params-string-array` (compiler message): `req.params` values are `string | string[]`. The guide types the params and never casts. - `req-param-removed`, `sendfile-renamed`, `static-mime-removed`, `query-middleware-removed`: compiler messages. - `redirect-order`, `param-callback-removed`: detected, because their compiler messages are too generic to claim. Behavior notes: - Findings: `redirect-back` and `send-status-number`, detected on Express responses. - Decisions: `async-errors`, `req-body-undefined` (also a test follow-up), `query-parser`, `urlencoded-extended`, `static-dotfiles`, `node-minimum` (only when `engines.node` allows below 18), and `runtime-defaults`. Engine change: with the pack in use, `check` skips its curated, text-matched express 5 runtime findings for the package, because the pack owns those sites. The generic tier is unchanged. ## Ground-truth repositories The ground truth was committed in `ab94e71` before the first score and has not been changed since. 88 sites, 86 of them breaking. - [netlify/cli#7927](netlify/cli#7927): pinned `63b6f1cb`, 10 sites (8 routes, 2 `req.body` test assertions). - [bcgov/tenant-management-system#699](bcgov/tenant-management-system#699) (`backend/`): pinned `6fd01755`, 54 sites. - [amiantos/lurker#147](amiantos/lurker#147): pinned `f7a0f3da`, 18 sites. - [openhab/openhab-cloud#550](openhab/openhab-cloud#550): pinned `5a1866ef`, 6 sites. Screened out: snapshot-labs/stamp#711 (engines `node >=24.15`, while CI runs Node 22) and others. The list is in RESEARCH.md. ## `uptide pack test` output Run once after the rebase, with per-pack caches. The ground truth is unchanged since `ab94e71`. ``` uptide pack test express · verified, recorded candidate fixtures 2 cases, 17 sites · pass ground truth netlify/cli@63b6f1cbca31 express 4.22.1 → 5.2.1 predicted 5, expected 10, false positives 0, false negatives 5 with @types/express 4.17.25 → 5.0.6 · verdaccio 6.2.4 keeps its own express 4.21.2; no release accepts express 5.2.1 · http-proxy-middleware: the upgrade moved it to 3.0.5, check leaves it (peer conflict: http-proxy-middleware 2.0.9 declares @types/express ^4.17.13) bcgov/tenant-management-system@6fd01755043a express 4.22.2 → 5.2.1 predicted 53, expected 54, false positives 0, false negatives 1 with @types/express 4.17.21 → 5.0.6 amiantos/lurker@f7a0f3dac775 express 4.22.2 → 5.2.1 predicted 10, expected 18, false positives 0, false negatives 8 with @types/express 4.17.25 → 5.0.6 openhab/openhab-cloud@5a1866efe1c0 express 4.22.1 → 5.2.1 predicted 5, expected 6, false positives 0, false negatives 1 with @types/express 4.17.25 → 5.0.6 · body-parser: the upgrade moved it to 2.2.2, the repository's choice (it does not depend on the package) · serve-static: the upgrade moved it to 2.2.1, the repository's choice (it does not depend on the package) rule precision recall tp fp fn generic 100% 50% 1 0 1 params-string-array 100% 87% 62 0 9 req-body-undefined n/a 0% 0 0 2 route-syntax 100% 77% 10 0 3 overall (sites) 100% 83% 73 0 15 behavior notes (sites) n/a 0% 0 0 2 breaking (sites) 100% 85% 73 0 13 gate 86 breaking sites, 2 behavior-note sites in public ground truth · verified recorded candidate by choice; measures verified PASS express: verified ``` The moves are now explained rather than failing: - verdaccio keeps a nested copy of express 4. - http-proxy-middleware is a peer conflict, which `check` names and leaves in place. - body-parser and serve-static are the repository's own choice. ## 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 express` passes and measures verified. `verification.json` was written with `--write`, then set to candidate by choice. - [x] `pnpm lint` and `pnpm typecheck` pass. `pnpm test` passes. - [x] Changeset added - [x] No private code, names or paths anywhere: fixtures are synthetic, ground truth is public 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Lucas Braum <lucasbraum@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lucasbraum
pushed a commit
that referenced
this pull request
Oct 15, 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.13.0 ### Minor Changes - [#134](#134) [`b795705`](b795705) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Packs can name alternate leads: packages a repository declares instead of the pack's own (`react-router-dom` for `react-router`). `check` and `fix` asked for either name use the pack; where only the alternate is declared, the lead is analyzed at the version installed through it, pnpm stores included, and `fix` declares it beside the alternate at the target before upgrading both. - [#149](#149) [`3303118`](3303118) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add a candidate express 4 to 5 migration pack. Route strings that path-to-regexp 8 rejects or reads differently are found on calls of an Express app or router (including lists registered in a loop); `req.params` values widened to `string | string[]`, `req.param`, `res.sendfile`, `express.static.mime` and `express.query` are claimed from the compiler; `res.redirect(url, status)`, `app.param(fn)`, `res.redirect('back')` and `res.send(<number>)` are found in the source; runtime defaults (async errors, `req.body`, the query parser, `urlencoded`, dotfiles, Node 18) go to decisions. No rule rewrites. With the pack in use, `check` no longer adds its text-matched express 5 runtime findings. - [#148](#148) [`5143c46`](5143c46) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add a candidate joi 17 to 18 migration pack. `new ValidationError(message, {}, original)`, which stops compiling for code written against joi 17.0–17.6, is rewritten to pass `[]`; the Node.js 20 requirement and the top-level domains joi 18 no longer accepts in `email()`, `domain()` and `uri()` are reported as decisions. Scored against two public repositories, one of them a negative control. - [#131](#131) [`413d3da`](413d3da) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add a verified lucide-react 0.x to 1.x migration pack. Brand icons removed in 1.x are found at their imports and every use, with a guide whose replacement choice goes to the owner; icon names dropped during 0.x are renamed at the import or member; deep `dist/` imports, untyped icon lookups, the `absoluteStrokeWidth` and `IconNode` deprecations, and the `aria-hidden` default for repositories before 0.489.0 are reported. Lucide's lockstep packages move together and the consumer's build is required. Verified against three public repositories, one of them a negative control. - [#132](#132) [`107c107`](107c107) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add a verified React Router 6 → 7 migration pack, which also runs for applications that declare only `react-router-dom`. It moves `react-router-dom` imports to `react-router`. It reports, with a guide for the agent, the rest of the upgrade: the removed `future` prop and router option, `react-router-dom/server`, lowercase `formMethod` comparisons and `json`/`defer` calls (found by syntax where the compiler is blind), the other removed exports, and `generatePath` calls that pass `null` for a required parameter. The six v6 future flags that become defaults are reported as decisions where the repository's router setup has not enabled them. React Router 7 → 8 is a separate upgrade. - [#142](#142) [`9c57934`](9c57934) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` and `fix` plan what the target version requires: a package the target, or anything moving with it, declares as a peer or dependency at a range the installed copy rejects moves to the lowest release of its major that satisfies it (openai 7 moves zod 3.24.2 to 3.25.0), shown apart from release-group companions ("moved because openai 7.4.0 requires zod ^3.25 || ^4.0"). A move only a new major makes is never made: it is reported as a blocker with the exact range in `check`, the PR body and `pack test`. The npm install scope guard no longer lets a declared root dependency change because a moved package peers on it. - [#128](#128) [`06f11ab`](06f11ab) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add verified Sentry JavaScript SDK 10 to 11 migration packs for `@sentry/nextjs` and `@sentry/browser`, with the SDK's lockstep release group, a required build for Next.js, and inventory-scoped decisions for data collection, span streaming, sampling and platform minimums. Mechanical rules move `withSentryConfig` to `@sentry/nextjs/config` and the removed build options under `webpack`, remove `enableLogs`/`enableMetrics: true`, and rename exports, integration names and `httpIntegration` options; privacy, sampling, profiling and OpenTelemetry choices stay assisted and are reported for the owner. Fix `uptide pack new` dropping registry entries long enough to be wrapped. - [#155](#155) [`922c5ab`](922c5ab) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide pack test` applies a stricter verified gate: at least five expected breaking sites across the public ground-truth repositories, besides two repositories and no breaking false positive. Upgrades the compiler cannot see are gated on their behavior notes instead. Mechanical rules no real site exercises are recorded as unexercised, and `check` and `fix` run their sites as assisted. `@sentry/browser` and `@sentry/react` become candidates (fewer than five real breaking sites). A pack can lower the minimum only with a written reason in its RESEARCH.md. The README packs table shows each pack's breaking-site count. The output shows the gate's class, site counts and every reason a pack is a candidate. - [#147](#147) [`0b88170`](0b88170) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` and `fix` remove a deprecated `@types` stub instead of upgrading to it. When the release the upgrade would move `@types/<pkg>` to is deprecated on npm, and `<pkg>`'s target declares its own types (`types`, `typings` or a `types` export), the plan removes it: "Removed: @types/ioredis, ioredis 5 ships its own types", in `check` and in the PR body. `check` now diffs such a package against its own target types instead of failing on a stub with no declarations, and `fix` removes the entry in the same install, with the install scope guard allowing exactly that removal. - [#150](#150) [`4c3212b`](4c3212b) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check typescript` judges a TypeScript 5 to 6 upgrade with both compilers: every project of the workspace is compiled with the installed TypeScript and with the target's, and what is new at the target is a finding; the bundled compiler never stands in, and a target without a JavaScript API (TypeScript 7) is reported as not compiled. A candidate typescript 5 to 6 pack reports the tsconfig options 6.0 deprecates where they are set, the rootDir default, module-keyword namespaces and import assertions. A peer that rejects the target (typescript-eslint on TypeScript 6) is now reported as a peer conflict even when it is not grouped with the package. - [#137](#137) [`21894d7`](21894d7) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add `verify.build` to uptide.yml: the package.json script a migration pack's required build runs. Unset, Uptide runs `build:ci`, then `build-ci`, then `build`. When a build fails before the framework's build starts because it needs an environment variable or a database (`prisma db push` without `DATABASE_URL`), the pull request and the report say so ("build needs DATABASE_URL; set verify.build") instead of a bare "build failed". `uptide init` writes the setting as a comment. ### Patch Changes - [#141](#141) [`9debb6d`](9debb6d) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` no longer reports a pre-existing type error as a breaking site when the upgrade only changes its wording, such as a type the message prints in full. An error at the same place with a different reason is still reported. - [#129](#129) [`e796e4c`](e796e4c) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add a candidate ioredis 5 to 6 migration pack: client constructions that inherit the RESP3, reconnect and keep-alive defaults ioredis 6 changed, compiler-blind sites (`mode === 'subscriber'`, commands in a `connect` listener with the offline queue off, AUTH recognized by command name), dependents that bundle ioredis 5, instrumentation that stops patching ioredis 6, and Node 20. It is scored against five public upgrades and stays a candidate: none of them has a breaking site to verify it with. - [#139](#139) [`9ba005a`](9ba005a) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Allow local check and fix smoke tests of registered candidate packs with --allow-candidate, with an explicit unverified banner. Reject candidate opt-in in the Action and with publication flags, and prevent stored candidate runs from being published. Label pack-claimed assisted sites by rule id even with --no-llm. - [#157](#157) [`f3af6a1`](f3af6a1) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` no longer reports a blocker for a direct dependency whose plain dependency range rejects the target (`@azure/ai-projects` on `openai ^6`, verdaccio pinning express 4): the package manager gives it its own nested copy, so it is listed as staying, with the new major that would accept the target when there is one. `pack test` counts a `with` move that `check` leaves and explains (a blocker, a new-major stay or a peer conflict) as explained, and one of a package that does not depend on the lead as the repository's choice; neither fails the run. - [#130](#130) [`5ced450`](5ced450) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add a candidate openai 4 to 7 migration pack: named path parameters, `del` → `delete`, promoted beta chat helpers, renamed runner events, removed entry points and request-option overloads are rewritten where unambiguous; tool-call unions, `httpAgent`, `fileFromPath` and the other removals get agent guides; `APIError.headers`, Web `Response` bodies and double-encoded path ids are detected even in code typed `any`; Node 22, zod, fetch and client-subclass decisions carry repository evidence. It stays a candidate (the dependency is still checked and fixed as generic) until two engine gaps its ground truth exposed are closed. - [#136](#136) [`578a042`](578a042) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Fold implicit-any errors (TS7006, TS7031, TS7019) inside an option the target removed into the pack finding on that option, as downstream evidence: the callbacks lost the types the option gave them, and the same edit resolves them. They no longer stand as separate breaking sites. - [#143](#143) [`883e587`](883e587) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide pack test` scores each expected site at the severity of the rule or behavior note it belongs to, so a missed note that only needs a look, or a decision, no longer counts as a missed break. Behavior-note sites get their own precision and recall in the output and in `verification.json` (`behavior`, with the counts behind each rate), false negatives show their severity, and a rate with nothing to measure prints `n/a` instead of 100%, in `pack test` and in the README's packs table. - [#154](#154) [`7aa3d58`](7aa3d58) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide pack test` fails a pack's `verification.json` only when it claims more than the run measures (recorded verified, measured candidate). A pack recorded as candidate on purpose passes, with `recorded candidate by choice; measures verified`, and a record whose numbers differ gets a note instead of a failure. A `generic` ground-truth site now counts as a true positive for the rule that claims it. - [#152](#152) [`ca31aee`](ca31aee) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide pack test` takes several packs: `uptide pack test zod ai`. - [#159](#159) [`1e157fd`](1e157fd) Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide pack test --json` times each ground-truth repository: `timing` gives the fetch, install and check wall time and the process's peak memory. - [#144](#144) [`0881185`](0881185) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Ground truth can name a site for an upgrade that only adds lines inside an existing call, such as an option added to a constructor: the site is the call's start line, so a pack finding there scores as a true positive instead of a false positive. `pnpm packs:truth` drafts these anchors from insert-only hunks for review, and docs/packs.md and CONTRIBUTING describe the rule. - [#156](#156) [`9039e3c`](9039e3c) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Public ground truth now exercises three rules that ran as assisted: zod `error-params`, stripe `api-version-unpinned` and next `stable-cache-exports`. Their rewrites run mechanically again. All three packs stay verified. - [#138](#138) [`0db5640`](0db5640) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Respect repository tsconfig file selection, recursive project references, and workspace project options when checking dependency upgrades. Compiler diagnostics outside every TypeScript project no longer produce breaking findings; pack rules and build-configuration findings retain their own scope. Repositories without a tsconfig retain importer-based compilation, with that fallback stated in the report. Anchor missing members supplied by dependency augmentations at their setup import, retaining affected calls as evidence instead of counting each call as a separate breaking edit. - [#151](#151) [`9ddaaff`](9ddaaff) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Add a candidate redis (node-redis) 4 to 5 and 5 to 6 migration pack, one rule set per major: an upgrade gets the rules of the majors it crosses, so 4 to 6 gets both. 4 to 5 covers `legacyMode`, `commandOptions` and isolation, scan iterators that yield batches, string SCAN cursors, boolean replies that became numbers, enums that became constants, `keepAlive`, `@redis/graph` and internal `dist` imports; 5 to 6 covers the RESP3 client type, `unstableResp3`, and the client defaults 6 changes (RESP3, command timeout, keep-alive delay, maintenance notifications) with a decision per client. Scored against five public upgrades; it stays a candidate until the stricter verified gate lands, since the core 4 to 5 API changes have no real site yet. - [#135](#135) [`42a3b54`](42a3b54) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Move only packages released with the upgraded one. A pack's declared release group now bounds the lead's npm scope, and without one a same-scope package moves only when a pin, a peer or a release at the target relates it to the group. Packages of the scope that are versioned on their own (`@sentry/vite-plugin` 5.x, `@sentry/cli`, the old `@sentry/utils` 8.x) are reported as "stays at X" in `check`, the pull request and `pack test` instead of making `fix` refuse the whole upgrade. Registers the verified `@sentry/react` 10 to 11 pack. - [#146](#146) [`09b8682`](09b8682) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Report Stripe clients pinned to different API versions at the top of `check` and of the PR body, as a decision for the owner: every `new Stripe(...)` in TypeScript and JavaScript sources, scripts included, with the version it speaks after the upgrade (an unpinned client speaks the SDK default). The PR's decisions list, for each client behind the target, the changelog entries it would cross for the resources it calls. No pin is rewritten. - [#140](#140) [`bc428d1`](bc428d1) Thanks [@lucasbraum](https://github.com/lucasbraum)! - Keep migration PR bodies within their size budget while preserving summaries, representative diffs, decisions, review warnings and verification ahead of changelog and transitive detail. Keep every evidenced Stripe change visible and uncapped; group and cap only other changes to used resources, link the full changelog, and identify omitted text in the retained report. - [#145](#145) [`3c8902d`](3c8902d) Thanks [@lucasbraum](https://github.com/lucasbraum)! - The zod pack migrates `ZodError#errors` to `.issues` by rule: reads (`error.errors[0].message`, `error?.errors`) and destructuring (`const { errors } = error` becomes `const { issues: errors } = error`) where the type checker resolves the receiver to `ZodError`, and reads off an untyped catch binding of a zod parse. Writes are left to the agent. - [#127](#127) [`c4ead4f`](c4ead4f) Thanks [@lucasbraum](https://github.com/lucasbraum)! - zod: `check` now reports `ZodError#formErrors` and `#errors` read off an untyped catch binding of a zod `parse`/`parseAsync` (`catch (error: any) { return error.formErrors.fieldErrors }`), which zod 4 removed and the compiler cannot see. Files that only import a schema from the repository's own module (`@/lib/zod`) are covered too. `fix` rewrites `<err>.formErrors.fieldErrors` and `<err>.formErrors.formErrors` to `z.flattenError(<err>)`, importing `z` where it is missing; every other read goes to the agent. Co-authored-by: uptide-release[bot] <338861287+uptide-release[bot]@users.noreply.github.com>
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 report
Sentry demoted.
@sentry/browser(3 real breaking sites) and@sentry/react(2) are recordedcandidate.packages/core/src/packs/sentry/RESEARCH.mdsays they need at least 5 real breaking sites each, and lists repositories to search later: cal.com, midday, rallly, inbox-zero, unkey, twenty.Unexercised rules run as assisted.
pack test --writeran once on every pack except joi and redis. Only the two Sentry statuses changed; no precision, recall or count moved. Each record now carriesclass,breakingSites,behaviorSitesandunexercised.activePackswraps each pack:transformrefuses an edit whose rule is unexercised (fix hands the site to the agent with the guide), and check demotes such a site to assisted. The wrapper checks the rule of the applied edit, so the hand-written zod and stripe transforms are covered.rewriteof their own. A newPack.mechanicallist makes the gate count them.renamed-icons; nextstable-cache-exports; vitestremoved-entrypoints; zoderror-members,error-params; stripeapi-version-unpinned,fixture-cast; and the Sentry rules each record lists.error-members: no truth added. I scanned merged PRs that bump zod 3→4 and remove a.formErrors/.errorsread (chore(deps): Update dependency zod to v4 PrefectHQ/prefect#22795, [deps]: Update zod to v4 bitwarden/ai-plugins#102, chore(deps): upgrade Zod to v4.4.3 jesseiancary/plinth#41, chore: express v5 + zod v4 Seven74AI/music-library#176 and others). Every catch-binding read is narrowed byinstanceof z.ZodError, so it is typed and compiler-visible (error-issues), not read throughany. test: Add comprehensive test suite + fix Zod v4 validation crash in all controllers Naeha-S/Bloggonut#2 and fix(smart_read): guard zod-v4 error issues and require non-empty path ooples/token-optimizer-mcp#167 do read.errorsin a catch, but on zod 4 already: they are later crash fixes, not 3→4 upgrades. Soerror-membersruns as assisted.zod
error-params, 0 of 3 public sites: not fixed. All three are found as breaking true positives but filed undergeneric. They areerrorMapsites:z.literal(true, { errorMap })in AwesomeIntune, and{ errorMap }plus az.ZodErrorMapfunction in PATH. Theerror-paramsrewrite only handlesrequired_error/invalid_type_errorand skipserrorMap("needs manual merging"). zod has noruleOf, so the plan files a site undererror-paramsonly when the rewrite applies or the error text names those two keys. Re-filing them undererror-paramswould count them as evidence for a rewrite that never ran on them. The right fix is a separate assistederror-maprule with its own guide (zod 4:errorreplaceserrorMap, and the map's signature changed). That is a pack change, left for a follow-up.joi and redis stay candidate (not written). Why redis's record differed on purpose: it is still the scaffold record (0 repos, zero counts), never written. feat(packs): candidate redis (node-redis) 4 → 5 and 5 → 6 pack (UPT-58) #151 held it at candidate until this gate landed, because:
client-defaultspredictions false positives;command-options,legacy-mode,scan-iterators,number-replies,keep-alive) have no real site;Under this gate it measures verified (5 repos, 20 real breaking sites, 0 breaking FP).
keep-alive,quit-disconnect,scan-iteratorsandunstable-resp3would run as assisted.Override and README.
meta.minBreakingSiteslowers the minimum for one pack, andpack testfails a pack that sets it without a reason under## Breaking-site minimumin its RESEARCH.md. No pack uses it. The README packs table has a "Breaking sites" column, and docs/packs.md and CONTRIBUTING describe the gate.Also:
--writenow formats a record the way biome does (a short list inline), so a written record passes lint. The fix-machinery tests use zod'srequired_errorrewrite as their mechanical example; they mock the registry wrapper to keep it as written.Local runs: the
@sentry/reacttruth repo pins pnpm 12.8.2, which corepack 0.36 cannot launch (pnpm 12 ships a native binary its postinstall fetches). I scored it with a local pnpm 12.11.2 through a scratch shim. CI's corepack may hit the same thing if the repository is not in its cache. These tests also fail on main locally (environment): required-build, peer-preflight, scaffold, release, telemetry sender, third-party notices.Phase 1: the gate and its impact (measured before the records were written)
The gate
verifiedGate(contract.ts) replacesstatusOf.gateOf(pack-test.ts) feeds it from one run; "real" means public repositories, so a fixture repository is scored and never counts.genericVERIFIED_MIN_BREAKING_SITES)VERIFIED_MIN_BEHAVIOR_SITES)Unexercised rewrites: a rule with a
rewriteand no true positive in public ground truth is listed as unexercised. It never changes the status.pack testprints agateline (class, real site counts, status), each reason a pack is a candidate, and the unexercised rules.class,breakingSites,behaviorSitesandunexercised, so every record is now stale (a note, not a failure) until--write.overclaims()is unchanged from fix(packs): fail a verification record only when it claims more than the run measures #154: a record fails only when it records verified and the run measures candidate; recorded candidate by choice passes.Not in phase 1
fixrunning unexercised sites as assisted: it needs theunexercisedlist in each record, which only phase 2's--writeproduces. Wrappingtransformfor the hand-written packs (zod, stripe) is the work there.Impact: every registered pack, under the new gate
Each pack was run one at a time from source (
pnpm uptide pack test <id> --json), each with its own cache directory, deleted after the pack. Site counts are expected sites in public ground truth. "Breaking tp/fp/fn" is across every scored repository, fixtures included.client-defaultsnote sitesDrops: only @sentry/browser and @sentry/react. Both now fail
pack testas overclaims, so the Packs job is red on this PR until phase 2.react-router: on main, the only mechanical rule is
dom-imports(29 real TPs).removed-json,removed-defer,form-method-case,dom-server-entryandfuture-optionhave norewrite; they are already assisted, so the gate turns none of them assisted. Separately,dom-importshas 336 non-breaking false positives at the rule level (precision 8%), which no gate criterion reads.Mechanical rules that would turn assisted (unexercised): 24 rule entries across 8 packs, 15 distinct ids. lucide-react
renamed-icons; nextstable-cache-exports; vitestremoved-entrypoints; zoderror-members,error-params; rediskeep-alive,quit-disconnect,scan-iterators,unstable-resp3; and the Sentry rules listed per pack above.Tests
contract.test.tshas a pass and a fail case for each criterion, plus the unexercised list and the behavior-only class (verified, too few sites, false positives on notes and rules, never chosen with a rule site).pack-test.test.tscoversgateOf: fixture sites never count or exercise a rule, and an unscored repository is no evidence. (scaffold.test.tsfails locally withformatted: falseon main as well; unrelated.)🤖 Generated with Claude Code