Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

## [0.3.1] — Unreleased

Stricter UTF-8 tail handling, a guard against Python-style option names in `filterRelevant`, a typed `SmartApplyError` for smartapply refusals, and a new default model to replace a retired one. No change to `generateDiff` or to what `smartapply` writes.
Stricter UTF-8 tail handling, a guard against Python-style option names in `filterRelevant`, a typed `SmartApplyError` for smartapply refusals, and a new default model to replace a retired one. `generateDiff` now has a working fallback model: with no `model` and no `GPTDIFF_MODEL` it uses `DEFAULT_MODEL` (`deepseek/deepseek-v4.1-flash-1017`) instead of the retired id, which failed with HTTP 410. What `smartapply` writes is unchanged.

### Behavior changes

- **`filterRelevant` now throws on snake_case options.** Passing any patchling-py keyword spelling (`on_binary`, `on_oversize`, `max_bytes`, `max_tokens`, `max_output_tokens`, `api_key`, `base_url`, `call_llm`, `on_decision`, `reasoning_effort`, `retry_base_ms`, `skip_patterns`, `max_attempts`, `retry_backoff`, `sleep`) throws a `TypeError` before any model call, naming the camelCase option to use (`skip_patterns` → `ignore`, `max_attempts: n` → `retries: n - 1`, `retry_backoff: s` → `retryBaseMs: s * 1000`; `sleep` is not supported). In 0.3.0 these keys were silently ignored, so `{ on_binary: 'include' }` quietly fell back to the default `exclude`. Code that passed such keys and relied on the defaults will now fail loudly. The guard is not configurable (the `SNAKE_CASE_OPTIONS` map lives in `src/filter.js` and is not re-exported from the package root).
- **A truncated UTF-8 tail keeps its valid prefix at any file length.** In 0.3.0 an incomplete trailing sequence was tolerated only when the buffer was exactly 8192 bytes; any other length took the binary path. Now a file that ends in a legal partial character is text with the partial character dropped (`0x61 0xC3` and `0x61 0xE0 0xA0` are `"a"`). Tails that can never become a legal character stay binary (`0x61 0xE0 0x80`, `0x61 0xED 0xA0`, `0x61 0xF0 0x80`, `0x61 0xF4 0x90`; leads C0, C1, F5-FF). Some short files that 0.3.0 skipped as binary are now sent to the decision model.
- **`smartapply` refusals throw `SmartApplyError` instead of a plain `Error`.** When smartapply refuses a model rewrite (it changed lines the diff did not add or remove, and the edit could not be replayed from the `+`/`-` lines), the thrown object is now a `SmartApplyError`, exported from the package root and matching patchling-py. What changed: the error's class and constructor (`err.constructor === SmartApplyError`, `instanceof SmartApplyError` is true; it is still `instanceof Error`), `err.name` (`'SmartApplyError'`, was `'Error'`), and therefore how it prints (`String(err)` and the first line of `err.stack` read `SmartApplyError: smartapply: refusing to write …` instead of `Error: …`). Code that checks `err.name === 'Error'`, `err.constructor === Error`, or snapshots the printed error will see the difference. What did not change: the message text (`smartapply: refusing to write "<path>": the model changed lines the diff did not add or remove`, so matching on `err.message` keeps working), when a refusal happens, and provider/LLM errors, which still propagate as the original error object and are never wrapped.

- **Default model is now `deepseek/deepseek-v4.1-flash-1017`.** The 0.3.0 default, `xiaomi/mimo-v2.5-pro-ultraspeed`, has been retired by NanoGPT and returns HTTP 410, so `generateDiff` and `smartapply` failed whenever `GPTDIFF_MODEL` was unset and no `model` was passed. `DEFAULT_MODEL` (and its `.d.ts` literal type) is now `deepseek/deepseek-v4.1-flash-1017`, the October 17, 2026 snapshot of DeepSeek V4.1 Flash as listed on NanoGPT ($0.10 / $0.60 per 1M input / output tokens, 1M context). NanoGPT lists no non-Flash V4.1 model. patchling-py uses the same default. Calls that pass `model` or set `GPTDIFF_MODEL` are unchanged. The `filterRelevant` default (`xiaomi/mimo-v2.6-flash`) is unchanged. The verified catalog snapshot (`test/fixtures/demo-models.json`) adds the new id and was re-checked on 2026-10-21.
- **Default model is now `deepseek/deepseek-v4.1-flash-1017`.** The 0.3.0 default, `xiaomi/mimo-v2.5-pro-ultraspeed`, has been retired by NanoGPT and returns HTTP 410. So whenever `GPTDIFF_MODEL` was unset and no `model` was passed, `generateDiff` failed, and so did `smartapply` on its model-backed path: files whose patch could not be applied deterministically from the `+`/`-` lines. Patches that applied deterministically, and file deletions, never called the model and were unaffected. `DEFAULT_MODEL` (and its `.d.ts` literal type) is now `deepseek/deepseek-v4.1-flash-1017`, the October 17, 2026 snapshot of DeepSeek V4.1 Flash as listed on NanoGPT ($0.10 / $0.60 per 1M input / output tokens, 1M context). NanoGPT lists no non-Flash V4.1 model. patchling-py uses the same default. Calls that pass `model` or set `GPTDIFF_MODEL` are unchanged. The `filterRelevant` default (`xiaomi/mimo-v2.6-flash`) is unchanged. The verified catalog snapshot (`test/fixtures/demo-models.json`) adds the new id and was re-checked on 2026-10-21.

### Docs and tests

Expand Down
Loading