Skip to main content

stylefix

Regularize a Markdown file to US or UK English and a style guide (Chicago, Oxford, AP, GPO, APA or Guardian), fully automatically, and locally unless you point it at a hosted model.

stylefix draft.md --target us              # -> draft.us.md + draft.us.md.report.md
stylefix draft.md --target uk --oxford-ize # UK with Oxford -ize spellings
stylefix draft.md --target us --dry-run    # print a diff, write nothing
stylefix draft.md --target us --changes    # list every change as it is made

Vale decides what to change; a small Python wrapper applies the changes. The few words that need judgement (UK licence/license, programme/program, cheque/check, ...) are put to a language model as a multiple-choice question with the surrounding sentence. The model never rewrites prose: it picks one of the options you supply, or KEEP.

Install

uv tool install stylefix      # or: pipx install stylefix

stylefix also needs Vale on your PATH: brew install vale on macOS, choco install vale on Windows, or a release binary from GitHub on Linux. Python 3.11+.

stylefix --help gives the full option reference with examples. The man page is in the repository at man/stylefix.1 (man ./man/stylefix.1).

How it works

Every candidate word lands in one of three tiers:

tier mechanism what happens
deterministic suffix patterns confirmed against dictionaries, plus a curated table for the irregular families applied, case preserved
adjudicated words with a sense split (licence/license, programme/program), pattern hits whose rewrite is in no dictionary (coinages, names), any hit that is capitalised mid-sentence or inside a quotation, and the style guide's serial-comma and numeral rules a language model chooses between the candidates and KEEP
review spellings that look like the other variety but could not be converted; serial-comma and numeral hits when no model is configured listed in the report and the --changes log

Two data sources, both from the SCOWL project: VarCon, shipped in stylefix/dict/, and the Hunspell dictionaries that come with adjudicate.

  • VarCon (Variant Conversion Info) is the curated table: about 15,000 spellings mapped to their preferred American, British and Oxford forms, with sense annotations. It is the source of the Vale substitution rules, regenerated by python -m stylefix.wordlist. Where VarCon says a spelling is also a word of its own in the target ("check | verify" alongside "check / cheque | bank"; licence as noun, license as verb), the word is not converted but adjudicated, and VarCon's glosses become the rule of thumb the model reads: "British: 'check' for verify; 'cheque' for bank."
  • Hunspell dictionaries (en_US, en_GB, read with the pure-Python spylls) confirm the pattern tier. English keeps coining -ize verbs, so the -ise/-ize, -yse/-yze, -our/-or, -re/-er and -ll-/-l- endings are handled as patterns: a hit is rewritten and looked up. A rewrite that is a real word is applied ("securitise" becomes "securitize" without anyone listing it); one in no dictionary goes to the model ("platformise"); a word already valid in the target is left alone ("advertise", "glamour").

Patterns take what they can prove; the table carries everything else (closed families like -ogue and ae/oe, vocabulary like aluminium/aluminum, and inflections the patterns don't cover). On the same word the table wins.

UK variants. The default UK target writes -ise. --oxford-ize uses VarCon's Oxford column and flips the -ise/-ize pattern, keeping -yse and every other British form.

Style guides

--style chooses the mechanical conventions; spelling always follows --target. stylefix --list-styles prints this table.

guide for dash serial comma numerals from toward punctuation in quotes abbreviations
chicago (US default) Chicago Manual of Style unspaced em required 101 toward inside US, UK; PhD; 10 a.m.; vs.; e.g.
gpo US Government Publishing Office Style Manual, the federal standard unspaced em required 10 toward inside U.S., U.K.; Ph.D.; 10 a.m.; vs.; e.g.
ap Associated Press, journalism spaced em omitted in a simple series 10 toward inside U.S., U.K.; Ph.D.; 10 a.m.; vs.; e.g.
apa APA 7th, academic and policy research unspaced em required 10 no rule inside U.S.; PhD; 10 a.m.; vs.; e.g.
oxford (UK default) New Hart's Rules spaced en required 101 towards logical (unchanged) US, UK; PhD; 10 a.m.; vs; e.g.
guardian Guardian/Observer, UK journalism spaced en omitted unless needed 10 towards logical (unchanged) US, UK; PhD; 10am; v; eg, ie, etc
none no guide unchanged no rule no rule no rule unchanged unchanged

Abbreviations follow the guides' general rules, not a word list. Chicago's rule (10.4), shared by Oxford and the Guardian, is that an abbreviation in full capitals takes no periods, two letters or more: US, UK, EU, NATO, PhD, DC. It is applied to every period-group of capitals. The hazard is personal initials ("J.K. Rowling" keeps its periods), which look identical; so a group followed by a capitalised word is adjudicated, with the readings shown as they would print, and the sentence-end case ("in the U.S. The ...") is handled the same way. AP's rule is the reverse for "most two-letter abbreviations" (U.S., U.N., U.K., B.A., B.C.), with named exceptions (EU, GI, ID) and none in longer ones (FBI, NATO); since AP writes AI, IT and TV without periods, the rule is applied by category: countries, places, degrees and eras. GPO follows AP's forms; APA keeps "U.S." but writes degrees without periods (PhD, MA). Clock times, vs., and e.g., i.e., etc. take each guide's form.

The noun rule. GPO and APA use "U.S." only as an adjective ("U.S. policy") and write the noun out ("the United States"). Because that is a part-of-speech question, it is a warning by default under those guides (each likely noun use is listed for review) and an enforcement on request: --abbreviation-nouns enforce puts every "the U.S." to the model, which chooses "United States", the adjective form, or KEEP. off silences it.

Dashes, toward/towards and punctuation placement are applied outright. Serial commas and numerals need judgement (is "attacks, intentional and unintentional" a list of three? is "10" a count or a percentage?), so each hit becomes an adjudication item: the model sees the sentence and the guide's rule of thumb and chooses between one concrete edit ("ten", "a, b, and c") and KEEP. With no model configured they are listed for review instead. A guide from the other variety (--target uk --style chicago) is allowed and noted on stderr.

Guides are a few parameters each in stylefix/styleguides.py; the Vale rules are generated from them.

After the word-level edits, a punctuation pass handles what a regex rule cannot: curly quotes, primary double (US) or single (UK) quotes with correct nesting, apostrophes, parenthetical dashes (Chicago em dash / Oxford spaced en dash), en dashes in number ranges, and date order (September 4, 2026 / 4 September 2026). Chicago also moves commas and periods inside closing quotes. Front matter, fenced code, inline code, link targets and URLs are never touched.

The report next to the output lists every deterministic swap, every model decision with its confidence and reason, and every alert left for a human.

The model

The quickest way to set one up is

stylefix --setup

which asks where your model is (local Ollama, a hosted API, or your own server), tests it, and saves the answer where both stylefix and proofix find it. stylefix --check-endpoint shows what stylefix will use, where each setting came from, and whether the endpoint answers.

Any OpenAI-compatible /v1/chat/completions endpoint works: Ollama, vLLM, LiteLLM, llama.cpp's server, or a hosted API. stylefix takes its settings from, highest first: --url/--model/--timeout; STYLEFIX_LLM_URL, STYLEFIX_LLM_MODEL, STYLEFIX_LLM_KEY; ~/.config/stylefix/config.toml; ADJUDICATE_LLM_*; and ~/.config/adjudicate/config.toml, which proofix reads too. A --url or STYLEFIX_LLM_URL drops keys set in lower layers; set STYLEFIX_LLM_KEY (or api_key_env in the same config file) alongside it. With nothing configured, --llm auto (the default) uses a local Ollama if one is running with a model pulled, and otherwise runs as --llm none: ambiguous words are left alone and listed in the report, while capitalised deterministic swaps are applied anyway.

Ollama: nothing to configure; the first installed model is used. --model picks another.

A hosted API:

# ~/.config/adjudicate/config.toml
[llm]
url = "https://api.openai.com/v1"
model = "gpt-4.1-mini"
api_key_env = "OPENAI_API_KEY"   # the key stays in your environment

Self-hosted vLLM or LiteLLM with a reasoning model:

[llm]
url = "http://gpu-box:8000/v1"
model = "qwen3"
thinking_switch = true   # send enable_thinking: false, which vLLM honours

Leave model out and the server's first listed model is used. A key the file or environment names but that is unset, an unknown key, or a wrong type stops the run with the file and key named. The full table is documented in adjudicate.

Requests go out in batches of --batch-size items (20), --parallel (4) at a time, so a serving stack with continuous batching (vLLM) answers a document's worth of questions in a couple of round-trips. Decisions are cached in .stylefix/cache.json keyed on target, word and sentence; re-runs are instant and reproducible, and you can edit a cached choice to override it. Answers below --min-confidence (0.7), invalid answers, and transport errors all fall through to the report rather than being applied.

Choosing a model

The question the model answers is narrow (pick one of two or three options, given a sentence), so any current instruct model handles it; what differs is speed. scripts/eval_adjudicator.py scores an endpoint on 48 gold cases (noun/verb splits, sense splits, proper nouns, quotations) and reports latency:

uv run python scripts/eval_adjudicator.py [--url URL --model NAME] [--think] [--parallel 8] [--repeat 4]

One measurement, on a DGX Spark serving Qwen3.8-27B (NVFP4) through vLLM:

mode accuracy per item
thinking off, --parallel 1 47/48 ~1.0 s
thinking off, --parallel 8 47/48 ~0.33 s
--think, batch 8, --parallel 4 48/48 ~3.2 s

The one miss without thinking is a capitalised common noun ("Colour theory") kept as if it were a title, which is the conservative failure. Thinking buys the last case at four to ten times the latency; use --think with --batch-size 8 --timeout 600 for a final pass on an important document, and the default for everyday runs. Throughput scales with --parallel because vLLM batches concurrent requests, so a mixture-of-experts model with few active parameters (e.g. Gemma-4-26B-A4B) will be faster still on large batches without changing accuracy noticeably.

Exceptions

Words to leave alone come from four places, all optional and all combined:

  1. A built-in list (stylefix/accept.txt) of technical and policy terms and acronyms (cybersecurity, sandboxing, LLM, NIST). A test ensures nothing on it is a word the conversion tables know.
  2. ~/.config/stylefix/accept.txt, your personal list.
  3. .stylefix/accept.txt in the working directory, the project's list.
  4. --accept FILE for a one-off.

One term per line, # comments allowed, case-insensitive. They become a Vale vocabulary and are skipped by every rule. --no-default-accept uses only the --accept file.

Extending

  • Suffix families live in stylefix/patterns.py: the regexes, the never-lists, and the decision logic. The conversion table comes from stylefix/dict/varcon.txt.gz through stylefix/varcon.py; local fixes go in the override dicts in stylefix/wordlist.py. After editing any of these, run python -m stylefix.wordlist to regenerate the Vale rules (a test fails if they drift). Tests check that the patterns never contradict VarCon and that the old hand tables are still fully covered.
  • Ambiguous words live in stylefix/candidates.py and the matching Ambiguous.yml token regex.
  • Style rules are plain Vale YAML under stylefix/styles/<Guide>, generated from stylefix/styleguides.py; any substitution rule with action: replace is applied automatically, anything else goes to the report.

Limitations

  • The UK direction does not move punctuation outside quotation marks: whether a comma belongs to the quote is not knowable mechanically.
  • Numeral and serial-comma detection is heuristic, so no hit is applied mechanically: each goes to the model, which chooses the edit or KEEP, or to the report when no model is available (--llm none, or no endpoint reachable).
  • .docx files: convert with pandoc first (pandoc in.docx -t gfm -o in.md), fix, convert back.

License

MIT (see LICENSE). stylefix/dict/varcon.txt.gz is VarCon, under the notice in stylefix/dict/README-varcon.txt. The spelling dictionaries come with adjudicate.

Development

uv sync
uv run pytest

Metadata

Release files for stylefix 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stylefix 0.7.0
File Size Uploaded
stylefix-0.7.0.tar.gz 383.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for stylefix 0.7.0
File Interpreter ABI Platform
stylefix-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 759.8 kB

Release files / stylefix-0.7.0.tar.gz

Download URL stylefix-0.7.0.tar.gz
Size 383.6 kB
Tags Source
SHA-256 checksum
How to use checksums
929e8ffa769466879aa17f247d69aeff6dd53bc7bf062255fa2473b522cc02b2
BLAKE2b-256 checksum
How to use checksums
c48134b47fb819f4d63eabde64f4160e83963c7f271d43eb7bcbb879dbe17a9b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.4

Release files / stylefix-0.7.0-py3-none-any.whl

Download URL stylefix-0.7.0-py3-none-any.whl
Size 376.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e87d3db558934fc720b369ff88a98f68c3207be816e6aba37196d8474c360748
BLAKE2b-256 checksum
How to use checksums
1d63b50f5e8a09c591abc6780e33d6dc81c6c1ecff9e33bb3c7d099b08236f79
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.4

Release history Release notifications | RSS feed

0.8.1

2 release files

0.8.0

2 release files

This release

0.7.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page