Skip to content

Fix a finding

emendant fix turns scan findings into patch files. It does not edit your source files or Git history. You review and apply each patch yourself.

Can AI generate safe patches for SDK migrations?

Section titled “Can AI generate safe patches for SDK migrations?”

Not on its own authority. A generated patch needs your review before you apply it. Structural checks, your project’s typecheck, and your tests give bounded evidence that a patch did what it claimed. They are not a guarantee that the migration is complete or that the code behaves as before.

How Emendant treats that evidence:

  • Deterministic fixes come first. Most patches are a mechanical transform on your machine, such as one name for one name. They use no model. See fixes written in two ways.
  • A model writes only when you name a provider. Migrations with no safe mechanical transform stay unfixed unless you pass --model-provider. The patch header and report say that a model wrote it, and it goes through the same checks.
  • A patch can be declined. Emendant withholds a patch as failed when the checks show it is wrong, and as inconclusive when the machine could not decide. The report says which and why. See read the evidence.
  • A check that did not run is not a pass. With no typecheck or test script, a patch is graded structurally checked at best. Emendant does not invent a command.

A green result also says nothing about changes the feed does not report. One recorded migration shows both sides.

Start with a dry run:

Terminal window
npx emendant fix --dry-run

This prints each candidate diff without writing .emendant/. It also does not run your repository’s checks, install a target SDK release, or call a model. Emendant applies the edits in memory and matches the workspace again, so a successful preview is structurally checked.

A preview shows what Emendant plans to change. It does not show that the result compiles, passes tests, or works with the target SDK release.

Run fix mode without --dry-run when the preview is correct:

Terminal window
npx emendant fix

Emendant writes the result below .emendant/:

.emendant/
report.json
report.md
patches/
<finding>.patch
all.patch

all.patch is present only when two or more offered patches also pass together.

The command first checks every patch in memory. It then finds a typecheck and test command that the repository already owns. When it finds checks, it runs them against a patched copy outside your working tree. For a pending upgrade, it also uses your package manager to install the target SDK release into a source-free preparation directory before it checks the copy.

Emendant does not invent a command when the repository has no typecheck or test script. It still writes a patch that passes the structural check, and grades it structurally checked.

Open .emendant/report.md, then read the header and diff inside the patch. Each patch has one of four grades:

Grade What Emendant proved
test verified The repository’s tests passed with the patch. A selected typecheck also passed.
typechecked The repository’s typecheck passed. No test command ran.
structurally checked The edits applied and matching found no new affected use. No code compiled or ran.
unchecked Emendant could not obtain structural, type, or test evidence.

A patch can be withheld after Emendant creates its edits:

  • failed means the baseline was green and the patched copy was red, or the structural check found that the patch did not do what it claimed. This result is about the patch.
  • inconclusive means the machine could not decide. For example, the baseline was already red, an install did not complete, or a command timed out. This result is not evidence that the patch is wrong.

The report names the checks that ran, where they came from, and why a patch was written or withheld. See the CLI reference for flags that override check discovery or keep the verification copies.

Emendant writes one patch per finding. When two or more offered patches also pass together, it writes all.patch.

  • Use a per-finding patch when you want to apply one change.
  • Use all.patch when you want every offered change. Do not apply all of the per-finding patches one after another. Nearby patches can share context and fail when applied in sequence.

Review the diff before you apply it:

Terminal window
git apply --check .emendant/patches/all.patch
git apply .emendant/patches/all.patch

For one finding, replace all.patch with that patch’s filename. git apply --check confirms that the patch still fits your current files. git apply makes the source changes. Review the resulting Git diff and run any additional checks your project needs.

Most patches use a deterministic transform on your machine. They need no model, account, or model request.

Some migrations have no safe mechanical transform. These findings stay unfixed unless you name a model provider:

Terminal window
npx emendant fix --model-provider codex # the login your Codex CLI already has
npx emendant fix --model-provider claude # the login your Claude Code CLI already has

Without --model-provider, and without a provider in emendant.json, nothing is sent anywhere. When you name a provider, Emendant sends a bounded excerpt of the matched file directly from your machine to that provider. The result goes through the same bounds and verification as a deterministic patch. The report and patch header state that a model wrote it.

See Security and privacy for the data boundary and the configuration reference for persistent provider and verification settings.

Everything on this page above is a general description. This section is a recorded result, taken from one measured AI SDK 6 to 7 migration of steel-dev/atlas. Nothing below is illustrative.

Pinned details. Repository steel-dev/atlas at commit 7aea679b740afa97b26617e53e5c1f2e1429eec5, emendant@0.5.0 from npm, feed snapshot 2026-09-23.8, Node.js 22.23.3, run on 29 September 2026. The example page gives every command to repeat it.

The proposed edit. npx emendant@0.5.0 fix --feed-snapshot 2026-09-23.8 --dry-run printed one deterministic patch, +7 −7 in 2 files, graded structurally checked. It renames stepCountIs to isStepCount:

import { generateText, type ModelMessage, stepCountIs } from "ai";
import { generateText, type ModelMessage, isStepCount } from "ai";

The checks and results.

Check Result
Structural check Passed. The edits applied and matching found no new affected use.
Verification run 1 Withheld as inconclusive. The baseline npm run test was already red, because one test file could not listen on 127.0.0.1 in the sandbox.
Verification run 2 Withheld as inconclusive. With that file excluded, the baseline passed, but the typecheck failed on a copy that had ai@7.0.0 and the old provider packages.

Both withheld results were about the verification environment, not the patch. The patch applied cleanly, and the repository’s lint, typecheck, 262 tests and build all passed after the full migration.

The work that remained. Emendant does not change dependency versions or run lint, so those were manual. The second finding, a provider type change, needs a model provider, and none was named, so it got no patch. Three changes were not reported at all:

  • The wrapLanguageModel return type changed to LanguageModelV4. The typecheck found it, and the edit touched 13 files.
  • result.response.messages became the last step only. The types are unchanged, so the typecheck and all 262 tests passed. A mock-model run on both versions showed it.
  • A cachedInputTokens read arrived as a function parameter, which the matcher does not follow.

What this shows. A patch that was graded only structurally checked was correct, and a clean toolchain still missed a behaviour change. It is one repository and one release. It does not measure how often Emendant finds or misses a change, and the assisted patch was not tested. Read the full example for the commands, outputs and limits.