Autofix
Repair a broken Markdown document so it satisfies a contract, with a model you provide — validator-in-the-loop, provider-agnostic, safe by construction.
R2-D2 drops onto the wing, plugs in, and starts patching the hyperdrive. He tries something, the panel stays red, he tries again — beeping the whole time — until the light finally goes green. He can fix the wiring. He cannot conjure fuel that isn't in the tank.
That's autofix: a little astromech for your Markdown. It repairs a document until it passes the contract's shape checks (validateSyntax), using a model you supply, validator-in-the-loop — validate first, ask the model only when needed, re-validate, retry once with the error. The oracle is validateSyntax — the FORM-only surface — so the droid is never asked to fake state it cannot see (no inventing fuel).
import { autofix } from "mediva";
const result = await autofix({
document: brokenMarkdown,
schema: contract, // the .mdv.md contract
generate, // (prompt: string) => Promise<string>
});
if (result.ok) await save(result.document);The call
autofix({
document, // string — the broken Markdown
schema, // string — the mediva contract
generate, // (prompt: string) => Promise<string>
strategy?, // Strategy — default: answerTagStrategy
maxAttempts?, // number — default 2 (first generation + one validator-in-the-loop retry)
context?, // ValidationContext — passed through to validateSyntax
}): Promise<{
ok, // boolean — does the final document pass validateSyntax?
document, // string — the repaired Markdown (the original, unchanged, if it already validated)
attempts, // number — model calls made (0 if the input already validated)
error?, // string — the remaining validator error when ok is false
}>generate is the only required moving part. mediva has no model dependency — you pass a callback that turns a prompt into text, so any provider works (any droid with the right socket). If the document already passes validateSyntax, autofix returns it unchanged with attempts: 0 and never calls generate — no point spinning up R2 for a ship that already flies.
Not every shape failure is a document edit, though. A broken contract or a context check like the PR title (title-format, title-too-long) can't be fixed by rewriting the Markdown, so autofix preflights those and returns { ok: false, attempts: 0 } with an explanatory error instead of asking the model to repair something outside the document — fix the contract or title first.
Wiring a model
Any function (prompt) => Promise<string> works. With the Vercel AI SDK:
import { autofix } from "mediva";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
const generate = (prompt: string) =>
generateText({ model: openai("gpt-4o-mini"), temperature: 0, prompt }).then((r) => r.text);
const result = await autofix({ document, schema, generate });The callback is deliberately minimal, so the same code runs against OpenAI, OpenRouter, a local model, or a stub in tests:
const generate = async (prompt: string) => callMyModel(prompt);The loop
For each attempt, autofix:
- builds a repair prompt for the chosen strategy — the shared repair rules plus a
<repair_action>card derived automatically from the failing diagnostics, - calls
generate, - extracts the corrected Markdown with the strategy's parser,
- re-runs
validateSyntax; on failure it retries with the new error, up tomaxAttempts.
It's the red-light/green-light loop on the wing. State concerns (content, attestation, external) are never in the prompt, so a repair can't tick an unearned box or invent filler — the same guarantee as the manual loop, packaged.
Strategies
A strategy is how the model is asked to return the fix, and how it is recovered — which socket the droid plugs into:
interface Strategy {
id: string; // short id for diagnostics / telemetry
promptSignature: string; // stable one-line restatement that identifies this envelope in a prompt
buildPrompt(input: { document: string; error: string; repairAction?: string }): string;
extract(output: string): string | null; // the corrected Markdown, or null if unparseable
isStrictResponse(output: string): boolean; // true iff the output is exactly the container, nothing extra
}buildPrompt receives a RepairInput (document, the validateSyntax error, and an optional
repairAction card); extract recovers the fixed Markdown; isStrictResponse lets the loop tell a
clean, well-formed envelope from a sloppy one that merely happened to parse.
Two are built in:
answerTagStrategy(the default) — the model returns the fix in a single<answer>block.jsonStrategy— the model returns{ "corrected_markdown": "..." }.
import { autofix, jsonStrategy } from "mediva";
await autofix({ document, schema, generate, strategy: jsonStrategy });Pass your own Strategy to use a different response container; extract returning null counts as a format failure and triggers a retry (a beep of protest, then another go).
When to use it
Reach for autofix when you generate or accept Markdown that must satisfy a contract — fixing LLM output before you persist or render it, or cleaning up authored docs in a build step. See Validate LLM output before persisting. Use validate when you only need to check, and the CLI to gate files in CI.
When NOT to use it
autofix repairs shape and format only; it must not invent factual content or claim human-owned state.
Never use it to satisfy minWords with filler, tick attestations, claim tests ran, invent missing facts, or resolve external issue state. The syntax/state split enforces that boundary, but say it plainly to the droid: patch the wiring, do not fake the fuel gauge.