@shipispec/tsfix 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,111 @@ All notable changes to `@shipispec/tsfix` are documented here. Format follows [K
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.1] - 2026-05-19
8
+
9
+ **Integration release.** Combines v0.6.0's library-aware error recovery with the multi-provider + telemetry work that landed on `main` between v0.5.0 and v0.6.0. The npm-published v0.6.0 was built from a stale local checkout and shipped without the Tier 2 (multi-provider) and Tier 3 (onLayerEvent + runFullStack) features that were already on `main`. v0.6.1 is the canonical "everything-since-0.5.0" release; users upgrading from v0.5.0 should jump straight to v0.6.1.
10
+
11
+ ### Added (Tier 3 — telemetry + unified entrypoint)
12
+ - **`onLayerEvent?: (event: LayerEvent) => void`** callback option on `ValidationLoopOptions`, `RunMendLoopOptions`, and (new) `RunFullStackOptions`. Wires the `LayerEvent` type that's been published since v0.3.0 but never had a callback. Optional — undefined callback costs nothing.
13
+ - **Layer 1** emits one event per fixable-error attempt: `{layer: 1, errorCode, fixed, latencyMs, ts}`. `fixed: true` when a safe LSP fix landed; `fixed: false` when the fixer abstained (no candidate, ambiguous candidates, or zero-fix response).
14
+ - **Layer 2** emits one event per `runMendLoop` iteration: `{layer: 2, errorCode: <dominant code in iteration input>, fixed: <iteration cleared all errors>, latencyMs, ts}`. `costUsd` intentionally omitted from the per-event payload — callers can compute it from `result.layer2.totalInputTokens` + `totalOutputTokens` plus their own pricing.
15
+ - **Layer 4** emits one event per stub applied: `{layer: 4, errorCode: <parsed from "TSNNNN">, fixed: true, latencyMs: 0, ts}`. Multi-error coalesced stubs emit one event per `(stub × errorCode)` pair.
16
+ - **`runFullStack(opts)`** — new top-level entrypoint that composes Layer 0/1 → Layer 2 (opt-in via `llm`) → Layer 4 (opt-in via `stubOnFailure`) and returns a unified `RunFullStackResult`. Callers who want "run the whole stack" no longer need to compose `runValidationLoop` + `runMendLoop` + (post-`runInProcessTsc` re-check) by hand. Library equivalent of the CLI's existing all-layers flow.
17
+ - **`RunFullStackResult`** flat shape: `passed`, `errorsBefore`, `errorsAfterLayer1`, `errorsAfterAllLayers`, `layer1` (LSPFixer sub-result), `layer2` (RunMendLoopResult | null), `layer4` (`{stubsApplied: AppliedStub[]} | null`), `totalCostUsd`, `totalLatencyMs`, `remainingByCode`, `remainingByFile`. Matches the v0.3.0 roadmap sketch for the "unified result" type with cost + telemetry rolled in.
18
+ - **10 new unit tests** in `src/runFullStack.test.ts` covering: clean workspace, Layer-1-only fix, unfixable-no-LLM, mocked-Layer-2 + cost math, unknown-model fallback, Layer-4 stubOnFailure path, per-error Layer-1 events, per-iteration Layer-2 events, per-stub Layer-4 events, undefined-callback smoke.
19
+
20
+ ### Added (multi-provider — Tier 2)
21
+ - **OpenAI and Google providers** for Layer 2. `runMendLoop` and `mendSingleFile` now accept `llm.provider: "anthropic" | "openai" | "google"` (was: `"anthropic"` only). Each provider uses its corresponding `@ai-sdk/X` package via a small `buildLanguageModel` factory in `mendAgent.ts`. The factory's `switch` is exhaustive — TypeScript flags missing cases if a new provider is added to the `LLMProvider` union.
22
+ - **`LLMProvider` type** exported from `src/index.ts`. Re-exportable for callers building their own CLI / pipeline integrations.
23
+ - **CLI `--llm-provider <name>`** flag — `anthropic` (default, back-compat), `openai`, or `google`. Invalid values exit 2 with a clear message.
24
+ - **Per-provider default models** when `--llm-model` is omitted: `claude-haiku-4-5` for anthropic, `gpt-5-mini` for openai, `gemini-2.5-flash` for google.
25
+ - **Per-provider env var routing** in the CLI: `--llm-provider anthropic` → `ANTHROPIC_API_KEY`, `openai` → `OPENAI_API_KEY`, `google` → `GOOGLE_GENERATIVE_AI_API_KEY`. The error message names the exact missing var.
26
+ - **Pricing table refreshed against current provider pricing pages (snapshot 2026-05-16):**
27
+ - **OpenAI:** `gpt-5-nano`, `gpt-5-mini`, `gpt-5`, `gpt-5.1`, `gpt-5.2`, `o3-mini`, `o4-mini`, `o3`. Default `--llm-model` for openai is `gpt-5-mini`.
28
+ - **Google:** `gemini-2.5-flash-lite`, `gemini-2.5-flash`, `gemini-2.5-pro`. Default for google is `gemini-2.5-flash`.
29
+ - **Anthropic (corrects v0.5.0 bugs):** `claude-haiku-4-5` was listed at `$0.80 / $4.00` — actual is **`$1.00 / $5.00`** (v0.5.0 carried the older Haiku 3.5 numbers). `claude-opus-4-7` was listed at `$15.00 / $75.00` — actual is **`$5.00 / $25.00`** (the 4.5 release dropped Opus pricing 3×; v0.5.0 carried the Opus 4.1 numbers). Now also lists `-sonnet-4-6`, `-opus-4-5`, `-opus-4-6`, `-opus-4-1` so callers pinning any 4.x model get accurate cost estimates.
30
+ - **Cost impact for v0.5.0 users:** `--llm-budget-usd` enforcement on `claude-haiku-4-5` was ~20% under-estimating actual spend; on `claude-opus-4-7` was ~3× over-estimating (your budget triggered earlier than it should have). Both fixed.
31
+ - Newer / unlisted models still fall back to cost=0 with a logger warning — `--llm-budget-usd` won't trigger for unpriced models.
32
+ - **CLI JSON report** `layer2.provider` field added.
33
+ - **CLI human report** Layer-2 line now shows `<provider>/<model>` instead of just `<model>`.
34
+ - **2 new cache tests** in `benchmark/cache.test.ts` covering provider-discrimination in the cache key + back-compat default to `anthropic` when provider is omitted (preserves v0.5.0 cache entries on upgrade).
35
+ - **5 new CLI tests** in `cli/run-stack.test.ts` covering `--help` listing all three providers + env-var names, invalid `--llm-provider` rejection, per-provider env-var routing, and the default-provider-is-anthropic back-compat case.
36
+
37
+ ### Changed
38
+ - **`tsLanguageServiceFixer.ts`** internal loop now emits a `LayerEvent` per fixable error attempt when `onLayerEvent` is provided to `LSPFixerOptions`. Loop control unchanged; if the callback is undefined the only cost is one optional-chaining check per fix.
39
+ - **`runMendLoop.ts`** emits per-iteration Layer-2 events and per-stub Layer-4 events when `onLayerEvent` is provided. New internal helpers `parseTsCode("TS2304") → 2304` and `dominantErrorCode(diags) → 2304` for event payload assembly.
40
+ - **`LLMCall` type** input gains optional `provider?: LLMProvider`. Optional so v0.5.0 callers' `LLMCall` injections still type-check (their callbacks just ignore the new field).
41
+ - **Cache key** at `benchmark/cache.ts` now includes provider: `sha256(systemBlock + " " + userBlock + " " + provider + " " + model)`. Provider defaults to `"anthropic"` when not passed → v0.5.0 cache entries remain valid for unchanged anthropic prompts.
42
+ - **`scripts/build.mjs`** externalizes `@ai-sdk/openai` and `@ai-sdk/google` (in addition to `@ai-sdk/anthropic` and `ai`). Consumers who never invoke Layer 2 still don't load any AI SDK; consumers who do get whichever provider package they hit.
43
+ - **Runtime dependencies added:** `@ai-sdk/openai@^3.0.64`, `@ai-sdk/google@^3.0.75`. Both are loaded lazily — the AI SDK package only loads when its corresponding provider is actually called.
44
+
45
+ ### Note on v0.6.0 npm tarball
46
+ The `0.6.0` tarball on the npm registry was published from a stale local checkout that was based on `8921356 (chore: release v0.5.0)` and never fetched the `feat/multi-provider` and `feat/tier-3-onlayerevent` PRs that had already merged to `main`. As a result, npm `0.6.0` contains library-aware error recovery (see [0.6.0] below) but **not** multi-provider or onLayerEvent. v0.6.1 is the first release that combines all three feature sets. We did not unpublish `0.6.0` to avoid leaving an unpublish tombstone in the registry; please upgrade directly to `0.6.1` or later.
47
+
48
+ ## [0.6.0] - 2026-05-19
49
+
50
+ **Library-aware error recovery.** Layer 2 now auto-detects breaking-change hints for known libraries from your `package.json` and steers the LLM away from tsc's misleading quick-fixes when a library migration is the real cause. Plus a hardened type-context walk (no more crashes on rename cascades or branded types) and a meaningful set of security anti-patterns in the system prompt.
51
+
52
+ ### Added (library-migration hints)
53
+ - **`detectLibraryMigrations(workspaceRoot, registry?)`** — reads `package.json`, matches installed deps against a built-in registry of known breaking changes, returns matching hints. Auto-invoked by `runMendLoop` when `context.libraryMigrations` is left `undefined`. Pass `[]` explicitly to opt out.
54
+ - **`BUILT_IN_LIBRARY_MIGRATIONS` registry** — initial entries cover `vite-plugin-svgr` (v4 — `?react` query suffix), `next` (15 — `params`/`searchParams` are Promises), `ai` (v3 / v6), `drizzle-orm` (parameterized template literals).
55
+ - **`formatLibraryMigrationsBlock(hints)`** + **`formatLibraryMigrationsTaskDescription(hints)`** — public formatters. The latter produces the `taskDescription` headline (`Library migration: <names>`) that overrides any caller-supplied description when migrations apply — empirically, models follow tsc's quick-fix when the migration is mentioned only in a buried section.
56
+ - **CLI `--no-library-hints`** — opt-out flag. Default behavior auto-detects and injects hints. When a migration matches AND `--llm` is set, the CLI also skips Layer 0/1 (tsc's quick-fix is the misleading path for these cases).
57
+ - **`MendContext.libraryMigrations?: Array<{ name: string; hint: string }>`** — new optional field. `undefined` = auto-detect; `[]` = opt out; populated array = override (skip detection).
58
+
59
+ ### Added (system-prompt security anti-patterns)
60
+ - **Type-assertion escape-hatches** — explicitly forbids `as keyof T` for runtime-string TS7053 silencing, `x as any` / `x as unknown as T` to dodge a real mismatch, `!` non-null assertions to dodge TS18047/TS2532. The prompt directs the model to narrow at the function signature, widen with an index signature, or guard with `if (key in obj)` instead.
61
+ - **Dependency removal/substitution** — restoring a missing import is preferred to substituting a different library (e.g. `bcrypt` → `crypto.subtle.digest` is flagged as a security regression even when tsc accepts it).
62
+ - **SQL / NoSQL / shell injection** — forbids string concatenation of user-controlled values into raw query strings; directs the model to Drizzle's tagged template, Prisma / mysql2 placeholders, etc.
63
+ - **React XSS** — forbids `dangerouslySetInnerHTML` as a way to dodge a children-type error; recommends auto-escaping JSX or DOMPurify.
64
+
65
+ ### Added (union-cleanup positive guidance)
66
+ - When a type variant or interface property has been removed/renamed, the prompt now directs the model to do a FULL sweep in the same patch instead of partial cleanup. Specific TS2322 / TS2353 / TS2367 guidance: drop the excess property, drop now-orphaned function parameters with their use sites, replace the no-longer-valid comparison or delete it with its branch. Aimed at the "I changed one reference and left three more" failure mode that produces fresh errors on iteration 2.
67
+
68
+ ### Added (tests)
69
+ - 14 unit tests in `libraryMigrations.test.ts` covering empty / matching / minMajor / maxMajor / multi-dep / malformed-package.json / custom-registry / formatter shapes / headline generation.
70
+ - 4 tests in `mendAgent.test.ts` for `buildSystemBlock`'s library-migration integration (block present, taskDescription override, empty array, custom description preserved without migrations).
71
+ - 2 tests in `runMendLoop.test.ts` for auto-detect (populates from package.json when omitted; opts out on explicit `[]`).
72
+ - 1 regression test in `typeContext.test.ts` — "does not throw on multi-file rename-cascade (TS2305: unresolvable named import)" with 4 importers.
73
+ - Total: **130/130 tests pass.**
74
+
75
+ ### Fixed
76
+ - **`getTypeContext` no longer crashes on multi-file rename cascades or branded types.** `typeContext.ts:tryResolve` now wraps `checker.getTypeAtLocation(n)` and the subsequent `getSymbol()` / `aliasSymbol` / `getDeclarations()` chain in try/catch — TypeScript's internals throw `Cannot read properties of undefined (reading 'kind' / 'flags')` from `isDeclarationNameOrImportPropertyName` on these shapes; tsfix treats those as "no resolvable type" and continues. Belt-and-suspenders try/catch added in `mendAgent.ts` around the per-diagnostic context build — if one diagnostic's context fails for any reason, that diagnostic is skipped instead of killing the whole mend (one bad diag should not lose the LLM's chance to fix the other errors in the file).
77
+
78
+ ### Bench results
79
+ Re-measured against the 34-fixture corpus (24 single-file + 10 multi-file) at n=3 per cell:
80
+
81
+ | Surface | v0.5.0 | v0.6.0 | Δ |
82
+ |---|---|---|---|
83
+ | Single-file pass rate | 95.8% | **98.6%** | +2.8pp |
84
+ | Multi-file pass rate | 23.3% | **40.0%** | +16.7pp |
85
+ | Aggregate (102 cells) | 74.5% | **81.4%** | +6.9pp |
86
+ | Hard crashes | 6 cells | **0** | -6 |
87
+ | Cost per full bench | — | **$0.21** | — |
88
+ | Cost per case (haiku-4-5) | — | **<$0.005** | — |
89
+
90
+ Per-fixture flips notable enough to call out:
91
+
92
+ - **`case-ts2614-vite-svgr` (0/3 → 3/3)** — vite-plugin-svgr v4's `?react` query suffix migration. Before: model followed tsc's quick-fix and emitted `import Logo from "./logo.svg"` (type-checks under the `*.svg` ambient, breaks at runtime under vite). After: with the registry hint, the model emits `import Logo from "./logo.svg?react"` and the resulting code works in both tsc and the dev server.
93
+ - **`case-m7-index-signature-removed` (0/3 → 3/3)** — anti-pattern prompts ended the `as keyof T` escape-hatch loop.
94
+ - **`case-m3-union-variant-removed` (2/3 → 3/3)** + **`case-m6-hook-tuple-arity` (2/3 → 3/3)** — union-cleanup guidance fixed the partial-sweep failure mode.
95
+ - **`case-m1` + `case-m10`** — previously errored with the `typeContext` crash; now produce measurable results.
96
+
97
+ Caveats: n=3 per cell is noisy at the per-case level (a single-cell flip from 2→3 may revert); aggregate column totals at 24+ cases are the trustworthy signal. Multi-file scenarios remain the gap — Layer 3 (multi-file mend) is the deferred answer.
98
+
99
+ ### Changed
100
+ - **`runMendLoop`** auto-populates `context.libraryMigrations` from `rawContext.workspaceRoot`'s `package.json` when the caller leaves it `undefined`. Existing callers that omit the field get the new behavior automatically; existing callers that pass a non-empty array see no change.
101
+ - **`buildSystemBlock`** leads the prompt body with the library-migrations section when any apply, and uses the migration headline as the `taskDescription` (overrides any caller-supplied description). The library section lives between `SYSTEM_INSTRUCTIONS` and the errored-file content — earliest position where the model still sees it before reaching the file.
102
+ - **CLI** — when a library migration matches AND `--llm` is set, Layer 0/1 is skipped (tsc's quick-fix is the misleading path for these cases). Existing zero-LLM CLI behavior unchanged.
103
+
104
+ ### Engines
105
+ - Node `>=20.9.0` (unchanged)
106
+ - TypeScript `>=5.0.0` peer (unchanged)
107
+
108
+ ## [0.5.0] - 2026-05-16
109
+
110
+ **Layer 4 (stub-and-continue), Day 2/3 fixture mutators, parallel + cached Layer-2 benchmark, and CLI exposure of Layer 2.** This closes the "tsfix never leaves the workspace worse than it found it" property: when Layer 2 can't resolve the last few errors, the workspace can opt-in to `@ts-expect-error` directives that self-destruct once the underlying issue is fixed elsewhere. The CLI now exposes `--llm` end-to-end (was library-API only).
111
+
7
112
  ### Added (Layer 4 — stub-and-continue escape hatch)
8
113
  - **`stubAndContinue(opts)`** — new public API. Inserts `// @ts-expect-error - tsfix: <codes> — <message>` immediately above each unresolved error site so `tsc --noEmit` exits 0. Closes the "tsfix never leaves the workspace worse than it found it" property. Uses `@ts-expect-error` (not `@ts-ignore`) so directives self-destruct once the underlying issue is fixed by other means.
9
114
  - **`runMendLoop` opt-in flag** — new `stubOnFailure?: boolean` option (default `false`). When the LLM loop terminates with leftover errors and the flag is set, Layer 4 runs automatically. New `"stubbed"` stop reason; new `stubs?: AppliedStub[]` result field with what was applied.
@@ -201,7 +306,10 @@ Initial public release. **Layers 0–1 only** (deterministic detection + auto-fi
201
306
  - Node `>=20.9.0` (matches VS Code Extension Host runtime)
202
307
  - TypeScript `>=5.0.0` (peer dep, must be installed in the consuming workspace)
203
308
 
204
- [Unreleased]: https://github.com/owgreen-dev/tsfix/compare/v0.4.0...HEAD
309
+ [Unreleased]: https://github.com/owgreen-dev/tsfix/compare/v0.6.1...HEAD
310
+ [0.6.1]: https://github.com/owgreen-dev/tsfix/compare/v0.6.0...v0.6.1
311
+ [0.6.0]: https://github.com/owgreen-dev/tsfix/compare/v0.5.0...v0.6.0
312
+ [0.5.0]: https://github.com/owgreen-dev/tsfix/compare/v0.4.0...v0.5.0
205
313
  [0.4.0]: https://github.com/owgreen-dev/tsfix/compare/v0.3.0...v0.4.0
206
314
  [0.3.0]: https://github.com/owgreen-dev/tsfix/compare/v0.2.0...v0.3.0
207
315
  [0.2.0]: https://github.com/owgreen-dev/tsfix/compare/v0.1.1...v0.2.0
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # tsfix
2
2
 
3
- > Two-layer TypeScript error recovery for LLM-generated code fix `TS2304`, `TS2305`, `TS2551`, `TS2552`, `TS2724` deterministically with the same engine that powers VS Code's Quick Fix, and escalate the rest to a single-file LLM mend.
3
+ > Library-aware TypeScript error recovery for LLM-generated code. Fix `TS2304`, `TS2305`, `TS2551`, `TS2552`, `TS2724` deterministically with the same engine that powers VS Code's Quick Fix. Escalate the rest to a single-file LLM mend that knows what tsc's quick-fix gets wrong about your installed libraries.
4
4
 
5
- `@shipispec/tsfix` is what you reach for when you've just generated a few hundred files of TypeScript with an LLM and `tsc --noEmit` is screaming at you. It runs in two layers:
5
+ `@shipispec/tsfix` is what you reach for when you've just generated a few hundred files of TypeScript with an LLM and `tsc --noEmit` is screaming at you. It runs in layers:
6
6
 
7
7
  - **Layer 0/1** — Deterministic. Borrows the same TypeScript Language Service that powers VS Code's "Quick Fix" lightbulb and runs it as a CLI. Fixes typos, missing imports, and did-you-mean errors with no LLM, no network, no config.
8
- - **Layer 2** — Opt-in. A single-file LLM mend agent (Vercel AI SDK + Anthropic) that picks up what Layer 0 abstains on: TS2339 (property doesn't exist), TS7006 (implicit `any`), TS2741 (missing required prop), and other cases where the LSP can't statically derive the fix. Driven by **type-context injection** — when tsc says "Property 'foo' doesn't exist on type 'Bar'", tsfix resolves the `Bar` declaration via the TypeChecker and feeds its source to the model.
8
+ - **Layer 2** — Opt-in. A single-file LLM mend agent (Vercel AI SDK) that picks up what Layer 0 abstains on: TS2339 (property doesn't exist), TS7006 (implicit `any`), TS2741 (missing required prop), and other cases where the LSP can't statically derive the fix. Driven by **type-context injection** — when tsc says "Property 'foo' doesn't exist on type 'Bar'", tsfix resolves the `Bar` declaration via the TypeChecker and feeds its source to the model. **Multi-provider** (Anthropic / OpenAI / Google) via `--llm-provider`. As of v0.6.0, also **library-aware**: tsfix reads your `package.json` and injects breaking-change hints for known libraries (`vite-plugin-svgr`, `next`, `ai`, `drizzle-orm`) so the model picks the runtime-correct fix instead of tsc's misleading quick-fix.
9
+ - **Layer 4** — Escape hatch. When Layer 2 can't resolve the last few errors, opt in to `// @ts-expect-error - tsfix: ...` directives that self-destruct once the underlying issue is fixed elsewhere. tsfix never leaves the workspace worse than it found it.
9
10
 
10
- Layer 2 only runs if you explicitly call its API or set `ANTHROPIC_API_KEY` and use the `runMendLoop` entry point. The default `tsfix --workspace ...` CLI is still **Layer 0/1 only**.
11
+ Layer 2 only runs if you explicitly call its API or set `ANTHROPIC_API_KEY` and pass `--llm` to the CLI. The default `tsfix --workspace ...` CLI is still **Layer 0/1 only**.
11
12
 
12
13
  ## Before / after (Layer 0)
13
14
 
@@ -97,20 +98,73 @@ Layer 2 is built for the cases the LSP can't statically resolve:
97
98
  - `TS7006` — Implicit `any`. The LLM picks the right annotation from surrounding context.
98
99
  - `TS2741` — Missing required property. The LLM sees the contextual type and supplies a real value, not a placeholder.
99
100
 
100
- Against a 35-fixture Layer-2 benchmark (3 hand-authored minimal + 2 realistic + 30 ts-morph-generated mutations across TS2339/TS7006/TS2741), **35/35 pass at $0.001/fixture avg, P95 latency ~1.5s on `claude-haiku-4-5`.** Caveat: the 30 generated fixtures are mutations of 3 seeds — real-world diversity will move these numbers.
101
+ Against a 35-fixture Layer-2 benchmark (3 hand-authored minimal + 2 realistic + 30 ts-morph-generated mutations across TS2339/TS7006/TS2741), **35/35 pass at $0.001/fixture avg, P95 latency ~1.5s on `claude-haiku-4-5`.** Caveat: the 30 generated fixtures are mutations of 3 seeds — real-world diversity will move these numbers; see the realistic 34-fixture bench below.
102
+
103
+ ## Library-aware error recovery (v0.6.0)
104
+
105
+ A typical TypeScript LLM-repair failure mode: tsc reports `TS2614: Module '"./logo.svg"' has no exported member 'ReactComponent'. Did you mean to use 'import Logo from "./logo.svg"' instead?` The model dutifully follows tsc's quick-fix and emits `import Logo from "./logo.svg"`. **tsc is now green. The dev server is now broken.** Under `vite-plugin-svgr@4`, importing an SVG as a React component requires the `?react` query suffix — `import Logo from "./logo.svg?react"`. The default export is the asset URL, not a component. Quick-fix accuracy ≠ runtime correctness.
106
+
107
+ tsfix v0.6.0 reads your `package.json` on every Layer 2 invocation, matches installed deps against a built-in registry of known breaking changes, and injects library-migration hints into the system prompt's headline (not buried — headline framing matters more than buried context). With `vite-plugin-svgr@^4` installed:
108
+
109
+ ```
110
+ ### library-migrations
111
+ - vite-plugin-svgr: v4 requires the `?react` query suffix to import an SVG
112
+ as a React component. `import Logo from "./logo.svg"` returns the asset URL.
113
+ `import Logo from "./logo.svg?react"` returns the component.
114
+
115
+ ### task
116
+ Library migration: vite-plugin-svgr
117
+ ```
118
+
119
+ Bench result on this exact case before/after: **0/3 → 3/3**.
120
+
121
+ The built-in registry currently covers four libraries chosen for high LLM-repair confusion ratio:
122
+
123
+ | Library | Hint |
124
+ |---|---|
125
+ | `vite-plugin-svgr` v4+ | `?react` query suffix to import as React component |
126
+ | `next` v15+ | `params` / `searchParams` are now Promises (must `await`) |
127
+ | `ai` v3 / v6 | `generateText` API shape changes |
128
+ | `drizzle-orm` | parameterized `sql` template literals, not string concat |
129
+
130
+ `detectLibraryMigrations(workspaceRoot, registry?)` is also exported as a public API; pass your own registry to extend it. `runMendLoop` auto-invokes detection when you leave `context.libraryMigrations` `undefined`; pass `[]` to opt out, or `--no-library-hints` on the CLI.
131
+
132
+ ### Security-aware system prompt
133
+
134
+ The same release hardened the system prompt against the LLM-repair failure modes that silence tsc at the cost of runtime semantics:
135
+
136
+ - **`as keyof T` to silence TS7053** — fix the function signature or guard with `if (key in obj)` instead. Casting away an index-signature error keeps the call type-passing while losing all the runtime safety.
137
+ - **Substituting one library for another to dodge a missing import** — e.g. `bcrypt` → `crypto.subtle.digest`. The fix is to restore the missing import, not swap to a different cryptographic primitive that tsc accepts.
138
+ - **String concatenation of user input into raw SQL** — use Drizzle's tagged template / Prisma placeholders.
139
+ - **`dangerouslySetInnerHTML` to dodge a children-type error** — JSX `{value}` auto-escapes; if you need HTML, sanitize via DOMPurify.
140
+
141
+ ### Realistic bench (34 fixtures, single + multi-file)
142
+
143
+ Measured against a 34-fixture corpus drawn from real LLM-repair failures in adjacent projects (24 single-file + 10 multi-file), n=3 per cell:
144
+
145
+ | Surface | v0.5.0 | v0.6.0 | Δ |
146
+ |---|---|---|---|
147
+ | Single-file pass rate | 95.8% | **98.6%** | +2.8pp |
148
+ | Multi-file pass rate | 23.3% | **40.0%** | +16.7pp |
149
+ | Aggregate (102 cells) | 74.5% | **81.4%** | +6.9pp |
150
+ | Hard crashes | 6 cells | **0** | -6 |
151
+ | Cost per full bench | — | **$0.21** | — |
152
+ | Cost per case (`claude-haiku-4-5`) | — | **<$0.005** | — |
153
+
154
+ Multi-file scenarios remain the gap — Layer 3 (multi-file mend with `findReferences`-driven blast-radius search) is the deferred answer.
101
155
 
102
156
  ## The four-layer model
103
157
 
104
158
  ```
105
- Layer 0 — Prevention (prompt rules, exported-API injection — your problem)
106
- Layer 1 — Deterministic (this package: LSP auto-fix, CLI default)
107
- Layer 2 — Single-file LLM (this package: opt-in via library API)
159
+ Layer 0 — Prevention (prompt rules, exported-API injection — your problem)
160
+ Layer 1 — Deterministic (this package: LSP auto-fix, CLI default)
161
+ Layer 2 — Single-file LLM (this package: opt-in via --llm or runMendLoop)
162
+ Layer 4 — Stub-and-continue (this package: opt-in escape hatch, @ts-expect-error)
108
163
  ─────────────────────────────────────────────────────────────────
109
- Layer 3 — Multi-file LLM (planned: blast-radius search/replace via findReferences)
110
- Layer 4 — Stub-and-continue (planned: escape hatch)
164
+ Layer 3 — Multi-file LLM (planned: blast-radius search/replace via findReferences)
111
165
  ```
112
166
 
113
- The bet: roughly half of TypeScript errors in LLM output are deterministically fixable. By catching them in Layer 1 you dodge the LLM tax (latency, cost, nondeterminism) on the easy half. Layer 2 takes the other half — but only when you explicitly invoke it.
167
+ The bet: roughly half of TypeScript errors in LLM output are deterministically fixable. By catching them in Layer 1 you dodge the LLM tax (latency, cost, nondeterminism) on the easy half. Layer 2 takes the other half — but only when you explicitly invoke it. Layer 4 makes sure the workspace is never left worse than it started.
114
168
 
115
169
  ## Library API
116
170