@shipispec/tsfix 0.6.1 → 0.6.2

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,14 @@ All notable changes to `@shipispec/tsfix` are documented here. Format follows [K
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.2] - 2026-05-23
8
+
9
+ **Cosmetic patch.** No behavior change; no API surface change. Drops the stale prototype-era "TSC Defense Stack" string from CLI output (was the human-report banner header), replaced with "tsfix" to match the published package name. Surfaced while debugging a vhs demo recording — the recording would otherwise have shown stale branding above the fold. 147/147 tests still pass.
10
+
11
+ ### Changed
12
+ - **CLI human-report header** now prints `tsfix — <workspace>` instead of `TSC Defense Stack — <workspace>` (`cli/run-stack.ts:287`).
13
+ - **Benchmark runner header** now prints `tsfix benchmark — N fixture(s)` instead of `TSC Defense Stack Benchmark — N fixture(s)` (`benchmark/run-benchmark.ts:187`; contributor-facing, not shipped in the npm tarball).
14
+
7
15
  ## [0.6.1] - 2026-05-19
8
16
 
9
17
  **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.
@@ -306,7 +314,8 @@ Initial public release. **Layers 0–1 only** (deterministic detection + auto-fi
306
314
  - Node `>=20.9.0` (matches VS Code Extension Host runtime)
307
315
  - TypeScript `>=5.0.0` (peer dep, must be installed in the consuming workspace)
308
316
 
309
- [Unreleased]: https://github.com/owgreen-dev/tsfix/compare/v0.6.1...HEAD
317
+ [Unreleased]: https://github.com/owgreen-dev/tsfix/compare/v0.6.2...HEAD
318
+ [0.6.2]: https://github.com/owgreen-dev/tsfix/compare/v0.6.1...v0.6.2
310
319
  [0.6.1]: https://github.com/owgreen-dev/tsfix/compare/v0.6.0...v0.6.1
311
320
  [0.6.0]: https://github.com/owgreen-dev/tsfix/compare/v0.5.0...v0.6.0
312
321
  [0.5.0]: https://github.com/owgreen-dev/tsfix/compare/v0.4.0...v0.5.0
package/README.md CHANGED
@@ -1,14 +1,26 @@
1
1
  # tsfix
2
2
 
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.
3
+ > **TypeScript error recovery for LLM-generated code.** When Cursor / Claude Code / Copilot / your spec-to-code agent leaves you with a wall of `tsc --noEmit` errors, tsfix repairs them — **library-aware** mend on the hard ones, deterministic VS Code Quick Fix on the trivial ones. **98.6% pass on a real-world single-file bench at <$0.005 per fix.** MIT, BYOK.
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 layers:
5
+ <!-- demo.gif goes here broken project `npx @shipispec/tsfix --workspace . --llm` green tsc. See docs/demo/demo.tape -->
6
6
 
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) 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.
7
+ ## Why tsfix exists
10
8
 
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**.
9
+ tsfix is built for the *output of code generators*, not human-written TypeScript. The thing it does that nothing else does: **it knows what tsc's own quick-fix gets wrong about your installed libraries.**
10
+
11
+ When `vite-plugin-svgr@4` is in your `package.json` and tsc says *"Module '"./logo.svg"' has no exported member 'ReactComponent'. Did you mean `import Logo from "./logo.svg"`?"*, tsc is right about types and wrong about runtime. The default import resolves to the asset URL string under vite, not a component. **tsc is now green. The dev server is now broken.** An LLM dutifully following tsc's quick-fix produces code that type-checks and crashes the page.
12
+
13
+ tsfix reads your `package.json` on every Layer 2 invocation, matches installed deps against a built-in registry of known breaking changes (vite-plugin-svgr, Next.js 15 async params, Vercel AI SDK v3, Drizzle ORM), and injects the correct migration hint into the LLM prompt's headline. The model then emits `import Logo from "./logo.svg?react"` — tsc green AND the dev server works.
14
+
15
+ That's the one-sentence pitch. The rest of the package is a careful, layered, cost-aware way to deliver it across every TS error class.
16
+
17
+ ## The layers, opt-in by layer
18
+
19
+ - **Layer 1 (default, deterministic)** — Auto-fix typos, missing imports, and did-you-mean errors via the same TypeScript Language Service that powers VS Code's "Quick Fix" lightbulb. Zero network, zero LLM cost, zero config. Catches roughly half of LLM-generated TS errors before you ever pay for an LLM call.
20
+ - **Layer 2 (opt-in via `--llm`)** — Single-file LLM mend via the Vercel AI SDK. Multi-provider: **Anthropic / OpenAI / Google** (`--llm-provider`). Library-aware (above). Driven by **type-context injection** — when tsc says *"Property 'foo' doesn't exist on type 'Bar'"*, tsfix resolves `Bar`'s declaration via the TypeChecker and feeds its source to the model. That's the architectural moat: every other LLM-driven repair tool uses generic grep or repo-maps.
21
+ - **Layer 4 (library-only, opt-in via `runMendLoop({stubOnFailure: true})`)** — Escape hatch. When Layer 2 can't resolve the last few errors, inserts `// @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.
22
+
23
+ The default CLI is **Layer 0/1 only** — no network calls, no surprises. Layer 2 only runs when you opt in with `--llm` and have a provider key in your environment.
12
24
 
13
25
  ## Before / after (Layer 0)
14
26
 
@@ -56,6 +68,8 @@ npx @shipispec/tsfix --workspace . --json
56
68
 
57
69
  ### All flags
58
70
 
71
+ **Core (Layer 0/1):**
72
+
59
73
  | Flag | Meaning |
60
74
  |---|---|
61
75
  | `--workspace <path>` | Required. Directory containing your `tsconfig.json`. |
@@ -66,7 +80,40 @@ npx @shipispec/tsfix --workspace . --json
66
80
  | `--verbose` | Per-fix logging. |
67
81
  | `--help` | Print usage. |
68
82
 
69
- The CLI does not run Layer 2 call the library API for that (below).
83
+ **Layer 2 (LLM mendopt-in, sends source to your chosen provider):**
84
+
85
+ | Flag | Meaning |
86
+ |---|---|
87
+ | `--llm` | Escalate errors that survive Layer 0/1 to Layer 2. Requires the provider's API key in the environment. |
88
+ | `--llm-provider <name>` | `anthropic` (default) \| `openai` \| `google`. Each provider reads its own env var: `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_GENERATIVE_AI_API_KEY`. |
89
+ | `--llm-model <name>` | Model name. Defaults per provider: `claude-haiku-4-5` / `gpt-5-mini` / `gemini-2.5-flash`. |
90
+ | `--llm-max-iterations <N>` | Cap on LLM retries (default: `3`). Each iteration sends the still-erroring files plus updated diagnostics. |
91
+ | `--llm-budget-usd <amount>` | Soft cost cap; exits with code `3` if exceeded. Cost estimates use a per-provider pricing table (snapshot 2026-05-16) — unknown models log a warning and don't trigger the cap. |
92
+ | `--no-library-hints` | Disable auto-detection of library breaking-change hints from `package.json`. |
93
+
94
+ ### Exit codes
95
+
96
+ | Code | Meaning |
97
+ |---|---|
98
+ | `0` | Workspace is clean (or Layer 2 cleared all errors). |
99
+ | `1` | Errors remain. |
100
+ | `2` | Bad arguments / missing API key / `--llm` + `--dry-run` rejected. |
101
+ | `3` | Layer 2 budget cap (`--llm-budget-usd`) exceeded. Partial work is persisted to disk. |
102
+
103
+ ### Using in CI
104
+
105
+ `npx @shipispec/tsfix` prints `npm warn exec The following package was not found and will be installed: @shipispec/tsfix@<version>` on every cold runner. To avoid the warning and skip the install prompt, either install once at workflow start:
106
+
107
+ ```bash
108
+ npm install -g @shipispec/tsfix@latest
109
+ tsfix --workspace .
110
+ ```
111
+
112
+ …or pass `--yes` to npx:
113
+
114
+ ```bash
115
+ npx --yes @shipispec/tsfix --workspace .
116
+ ```
70
117
 
71
118
  ## What Layer 0 fixes
72
119
 
@@ -98,9 +145,14 @@ Layer 2 is built for the cases the LSP can't statically resolve:
98
145
  - `TS7006` — Implicit `any`. The LLM picks the right annotation from surrounding context.
99
146
  - `TS2741` — Missing required property. The LLM sees the contextual type and supplies a real value, not a placeholder.
100
147
 
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.
148
+ tsfix ships two benchmark suites, and it's worth being precise about what each measures:
102
149
 
103
- ## Library-aware error recovery (v0.6.0)
150
+ - **Synthetic suite** (in-package, `npm run benchmark:llm`) — hand-authored minimal cases plus ts-morph-generated single-error mutations of a few seed files. These are *easy* by construction (one isolated error, no cross-file ripple) and Layer 2 passes effectively all of them. Useful as a regression gate, **not** as a real-world accuracy claim.
151
+ - **Realistic suite** (34 fixtures drawn from real LLM-repair failures — see the table below) — this is the number to trust. It's where the headline 98.6% / 81.4% figures come from.
152
+
153
+ If you only read one number, read the realistic suite.
154
+
155
+ ## Library-aware error recovery (v0.6.0+)
104
156
 
105
157
  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
158
 
@@ -142,7 +194,7 @@ The same release hardened the system prompt against the LLM-repair failure modes
142
194
 
143
195
  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
196
 
145
- | Surface | v0.5.0 | v0.6.0 | Δ |
197
+ | Surface | v0.5.0 | v0.6.1 | Δ |
146
198
  |---|---|---|---|
147
199
  | Single-file pass rate | 95.8% | **98.6%** | +2.8pp |
148
200
  | Multi-file pass rate | 23.3% | **40.0%** | +16.7pp |
@@ -151,7 +203,7 @@ Measured against a 34-fixture corpus drawn from real LLM-repair failures in adja
151
203
  | Cost per full bench | — | **$0.21** | — |
152
204
  | Cost per case (`claude-haiku-4-5`) | — | **<$0.005** | — |
153
205
 
154
- Multi-file scenarios remain the gap — Layer 3 (multi-file mend with `findReferences`-driven blast-radius search) is the deferred answer.
206
+ The mend-quality gains landed in v0.6.0 (library-migrations, crash hardening, anti-patterns); v0.6.1 adds multi-provider + telemetry without changing these numbers. Multi-file scenarios remain the gap — Layer 3 (multi-file mend with `findReferences`-driven blast-radius search) is the deferred answer.
155
207
 
156
208
  ## The four-layer model
157
209
 
@@ -257,29 +309,39 @@ error: this workspace has no TypeScript installed.
257
309
  run: npm install --save-dev typescript
258
310
  ```
259
311
 
260
- ## Contributing
312
+ ## Build from source
261
313
 
262
- ### Adding a Layer-0 fix
314
+ tsfix is plain TypeScript bundled with esbuild — no special toolchain.
263
315
 
264
- 1. **Probe** — write a tiny test workspace with the exact error you want fixable. Drop it under `fixtures/<descriptive-name>/` with an `expected.json` declaring `errorsBefore`, `errorsAfterMax`, `lspFixesAppliedMin/Max`, and `mustPass`.
265
- 2. **Verify** — run `npm run benchmark -- --fixture <name>` and inspect what the language service offers (the `fix.fixName` field).
266
- 3. **Allowlist change** — if `fixName` is unsafe (`fixMissingFunctionDeclaration`, `addMissingPropertyAndOptional`, etc.), document why we don't trust it. Otherwise, add the error code to `SAFE_FIXABLE_CODES` and the fix name to `SAFE_FIX_NAMES` in `src/tsLanguageServiceFixer.ts`.
267
- 4. **Lock it in** — confirm all existing fixtures still pass (`npm run benchmark`). Open a PR.
316
+ ```bash
317
+ git clone https://github.com/owgreen-dev/tsfix
318
+ cd tsfix
319
+ npm install
268
320
 
269
- Each new code/fix-name pair gets its own fixture. We don't trust the language service blindly we trust it under specific, pinned conditions.
321
+ npm run check-types # tsc --noEmitmust pass
322
+ npm test # vitest unit suite
323
+ npm run build # bundle to dist/ (index.js, cli.js, *.d.ts)
324
+ ```
325
+
326
+ Run a single test file or pattern:
327
+
328
+ ```bash
329
+ npx vitest run src/libraryMigrations.test.ts
330
+ npx vitest run -t "auto-populates libraryMigrations"
331
+ ```
270
332
 
271
- ### Adding a Layer-2 fixture
333
+ Try your local build against a real project without publishing:
272
334
 
273
- Layer-2 fixtures live under `fixtures/` alongside Layer-0 ones, identified by `expectedErrorCode` (singular) or `costUsdMax` in their `expected.json`. The Layer-0 benchmark skips them; `npm run benchmark:llm` runs them against Anthropic.
335
+ ```bash
336
+ npm run build
337
+ node dist/cli.js --workspace /path/to/some/broken-project
338
+ ```
274
339
 
275
- - Hand-author one under `fixtures/mend-<descriptive-name>/` for new error classes.
276
- - Or generate one via `npm run generate-fixtures -- --code=TS2339 --seed=apiRouter.ts --count=10 --rng-seed=42`. The generator validates every mutation through Layer 0 first to confirm Layer 0 abstains (otherwise it's not Layer 2 territory).
340
+ Requires Node `>=20.9.0`. The package has no `dev` watch script — the loop is edit → `npm run check-types` `npm test`.
277
341
 
278
- ### Pre-publish gates
342
+ ## Contributing
279
343
 
280
- - `npm run benchmark` Layer 0, 14 fixtures, no network.
281
- - `npm run benchmark:llm` — Layer 2, 35 fixtures, requires `ANTHROPIC_API_KEY`. Total cost ~$0.04 per run.
282
- - `npm run matrix` — runs the local tarball against 6 distinct project shapes (Next.js, Vite + React, plain `nodenext`, plain `bundler`, plain CommonJS, monorepo with project references). Adds ~3 min; run manually before tagging.
344
+ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for the full guide: dev setup, how to add a Layer-0 fix or a Layer-2 fixture, how to extend the library-migration registry, and the pre-publish gates. PRs that add a library to the migration registry are especially welcome — that's the highest-leverage contribution.
283
345
 
284
346
  ## License
285
347
 
@@ -287,8 +349,8 @@ MIT.
287
349
 
288
350
  ## See also
289
351
 
290
- - `CHANGELOG.md` — release notes per version.
291
- - `ARCHITECTURE.md` — internal design rationale (the four-layer model, the workspace lib-path workaround).
292
- - `STATUS.md` — current snapshot, gaps, and roadmap state.
293
- - `tsc-defense-roadmap.md` — phased plan.
294
- - `docs/internal-orientation.md` — the original SpecToShip-context README, kept for contributors who want the design history.
352
+ - `CHANGELOG.md` — release notes per version (authoritative for current state).
353
+ - `CONTRIBUTING.md` — dev setup, how to add a fix/fixture, how to extend the migration registry.
354
+ - `ARCHITECTURE.md` — design rationale (the four-layer model, the workspace lib-path workaround).
355
+ - `ROADMAP.md` — phased plan and resolved/deferred decisions.
356
+ - `docs/blog-tsc-correctness-is-not-runtime-correctness.md` — the "tsc-correctness runtime-correctness" writeup (the svgr `?react` case).
package/dist/cli.js CHANGED
@@ -1703,7 +1703,7 @@ function makeLogger(captureLines, verbose) {
1703
1703
  function printHumanReport(r) {
1704
1704
  const w = process.stderr;
1705
1705
  w.write(`
1706
- TSC Defense Stack \u2014 ${r.workspace}${r.dryRun ? " (dry-run)" : ""}
1706
+ tsfix \u2014 ${r.workspace}${r.dryRun ? " (dry-run)" : ""}
1707
1707
  `);
1708
1708
  w.write(` errors before: ${r.errorsBefore}
1709
1709
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipispec/tsfix",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "TypeScript error-recovery for LLM-generated code. Layer 0/1 deterministic auto-fix via the TS Language Service + Layer 2 LLM mend (Vercel AI SDK + Anthropic) in one package.",
5
5
  "keywords": [
6
6
  "typescript",