Bounded AI code edits via Codestral fill-in-the-middle: rewrite only the region between two anchors, the rest stays byte-identical.
Project description
vibe-fim
Bounded AI code edits via Codestral fill-in-the-middle. Rewrite only the region between two anchors — the rest of the file comes out byte-for-byte identical, by construction.
Why this exists
Most coding agents edit by regenerating a block and pasting it back. The model re-emits your whole function (or file) token by token, so it can silently change things you never asked it to touch — reflow a comment, drop a trailing newline, "fix" an unrelated line, normalise quotes.
Fill-in-the-middle (FIM) makes that structurally impossible. You hand the model a verbatim prefix and suffix; it only writes the middle. So the edit is provably confined to the region between two anchors. Everything outside is never sent to be rewritten — it cannot drift.
vibe-fim wraps Mistral's Codestral FIM in a one-call surgical-patch
primitive. The bytes outside the region are the original slices, concatenated
unchanged — so bounded scope is structural. On top of that it runs the checks
that can actually fail (below), so a bad completion is caught, not shipped.
No agent CLI exposes a verified bounded-edit primitive. Claude Code and Codex edit by regenerating blocks; plain
vibehas no FIM patch op. Codestral's FIM is what makes a provably-bounded edit possible, andvibe-fimturns it into one an agent can call and check.
Install
pip install vibe-fim
export MISTRAL_API_KEY=... # from https://console.mistral.ai
Zero runtime dependencies (stdlib urllib only).
CLI
# Rewrite only what's between the two anchors; show the diff without writing.
vibe-fim patch --file units.py \
--before "def to_celsius(f):" \
--after $'\n\ndef to_fahrenheit' \
--hint "round the result to 1 decimal place" \
--dry-run
The scope report goes to stderr:
[BOUNDED + PARSES] edit confined between anchors.
frozen prefix: 142 chars (unchanged)
frozen suffix: 98 chars (unchanged)
region: 31 chars -> 39 chars
Drop --dry-run to write the file in place. If the patched file no longer
parses, the CLI refuses to write (exit 3) unless you pass --allow-broken.
Agent-callable
An agent shells out and consumes a stable JSON contract — no screen-scraping:
vibe-fim patch --stdin --before "…" --after "…" --name app.py --json
# {"bounded": true, "parses": true, "scope_report": "...", "unified_diff": "...",
# "frozen_prefix_chars": 142, "frozen_suffix_chars": 98, "trimmed_overlap": 0,
# "new_middle": "...", "wrote": false}
--stdin/--text pass the source inline (no disk round-trip); on error the
record is {"bounded": false, "error": "..."} with a non-zero exit, so a loop
can branch. There is also an MCP server — see Wiring into an agent below.
Library
from vibe_fim import patch_region
result = patch_region(
source_code,
before="def to_celsius(f):", # frozen prefix ends right after this
after="\n\ndef to_fahrenheit", # frozen suffix starts right at this
hint="round the result to 1 decimal place",
)
assert result.bounded # structural invariant
assert source_code.startswith(result.prefix) # prefix is byte-identical
assert result.parses is not False # patched file still parses
new_source = result.text
The completion backend is injectable, so the bounded-edit logic is fully testable with no network:
patch_region(src, before, after, fim_fn=lambda prefix, suffix: " return 42")
Anchors must be unique in the file. A missing or ambiguous anchor raises
FimError instead of guessing — surgical edits should never be applied to the
wrong place.
Verification that can fail
Bounded scope is structural (the prefix/suffix are the original slices). The value is in the checks that a bad completion can actually trip:
- Boundary echo. FIM models sometimes re-emit the start of the suffix (or end
of the prefix) inside the middle — duplicating code, on the same line, not just
across a line break.
patch_regiondetects and strips a real re-emission (a run of ≥4 non-whitespace chars) while leaving a coincidental shared}or a boundary newline alone.result.trimmed_overlapreports what was removed. - Truncation. A completion cut off at
max_tokensis an incomplete middle.finish_reason == "length"raises instead of shipping a half-written edit. - Parse gate. For languages we can parse with the stdlib (Python via
ast), the patched file is re-parsed;result.parsesisTrue/False/None, the report shows[BOUNDED + PARSES]/[BOUNDED + DOES NOT PARSE], and the CLI refuses to write a non-parsing result by default.
The guarantee, and the drift — demonstrated
Two separate claims, kept separate:
- The guarantee is structural: surgical FIM changes 0 lines outside the target, by construction, because the frozen regions are never regenerated.
- The drift is measured.
examples/bench.pyreplays a committed Codestral transcript (examples/fixtures/) and counts collateral with one newline-aware counter for both approaches:
surgical FIM : 0 lines changed outside target
block regeneration: 2 lines changed outside target # one source line: its trailing newline dropped (counted as a -/+ diff pair)
It runs offline by default (no key, same numbers for every reviewer); pass
--live to re-capture from the API. The block-regen baseline produced the
correct body and still silently altered the file outside the edit.
python examples/bench.py # offline replay of the committed fixture
MISTRAL_API_KEY=... python examples/bench.py --live
Wiring into an agent
vibe-fim patch --json is the stable contract for shelling out. For a native
tool, install the optional MCP server and point any MCP client at it:
pip install "vibe-fim[mcp]"
vibe-fim-mcp # stdio MCP server exposing one tool: surgical_patch
The tool returns the unified diff plus the scope report, so the agent sees the
bounded proof — not just a success flag. (The base package stays
zero-dependency; the mcp SDK is pulled in only by the [mcp] extra.)
How it works
- Locate the unique
beforeandafteranchors. prefix = text[:end_of_before],suffix = text[start_of_after:].- Send
(prefix, suffix)to Codestral FIM →middle(de-duplicated, truncation-checked). new_text = prefix + middle + suffix; the bytes outside the region are the same slices, concatenated unchanged.- Run the parse gate and surface the result.
License
MIT © Guillaume Vele
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vibe_fim-0.2.1.tar.gz.
File metadata
- Download URL: vibe_fim-0.2.1.tar.gz
- Upload date:
- Size: 17.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d29daaf2812a55dd64ce40765cef21c2a7fc847a55eb8899424d44a4e1c53e41
|
|
| MD5 |
895e3a27b9a797f2216efea348184692
|
|
| BLAKE2b-256 |
e51dbf690a498c7a7e982b9f0192553d52b4f061ef82d67e10718b24bf9b2882
|
File details
Details for the file vibe_fim-0.2.1-py3-none-any.whl.
File metadata
- Download URL: vibe_fim-0.2.1-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfb79d94620ee0816b56fe321af96579f21f1f77907a75dcd72d6696088b4dd8
|
|
| MD5 |
7c437d653476e88f612072d9026e633b
|
|
| BLAKE2b-256 |
a4932a95c6a6adf327a706c1e16542f87e13186c56efa031b7ce8a7bdf0f2047
|