Context
This repo is unusually prose-heavy for a codebase: a blog, project write-ups, recipes, and 55-and-counting ADRs, all published to a public site that doubles as a portfolio. Nothing deterministic checks any of that text for spelling. markdownlint and remark check structure, not words; AI reviewers catch typos non-deterministically, and only in the diff under review, never in the backlog.
The backlog made the case concretely: a first run over the repo found six real misspellings sitting in published blog posts, some for six years ("Volatily" in a table header four times, "commerical", "efficently").
The standing objection to spell-checking code repos is false-positive noise. Dictionaries hate identifiers, and a checker that cries wolf gets deleted. The tool choice hinges on precision more than recall.
Decision
Adopt typos (the Rust CLI), mise-pinned like the other linters:
mise run //:lint:typosin the rootlintaggregate, plus alint:typos:fixtask (--write-changes) matching the check/fix split of the Markdown tasks.- A blocking step in the existing Lint CI workflow alongside
markdownlint/yamllint/actionlint. The baseline is green because the six
real findings were fixed in the adopting change. Lint CI's trigger now also
covers
**/*.mdx, where most prose lives. - A committed
_typos.tomldocumenting the false-positive taxonomy the first run produced: proper nouns split from CamelCase ("Hashi" from HashiCorp, "Symetry" from SymetryML), product names ("Vertica", "Juni"), legitimate words outside the dictionary ("unparseable"), and two path exclusions, the Fuse.js ADR (whose deliberate typo examples demonstrate fuzzy search) and a generated design artifact full of encoded SVG data.
Why It Adds Something New
Spelling is a class of defect no existing tool owns: it lives mostly in content files that Biome, CodeQL, and SonarQube never parse, and the tools that do parse them (markdownlint, remark) check syntax. typos applies the stack's standard determinism argument (ADR 048) to the one artifact type this repo publishes more of than code.
typos is chosen over the generic spell-checkers for its design point: instead of flagging every word not in a dictionary (cspell's model, which needs per-project word lists to stay quiet), it only flags known misspellings via edit-distance against a curated corpus. That asymmetry is why the first full-repo run produced 29 findings rather than thousands, and why 6 of the 29 were real.
Alternatives Considered
| Tool | Role | Pros | Cons | Verdict |
|---|---|---|---|---|
| typos (accepted) | Known-misspelling checker | Low false-positive rate by design; single fast binary; understands CamelCase/snake_case; TOML config; --write-changes fix mode. | Won't catch a novel misspelling absent from its corpus. | Accepted. |
| cspell | Dictionary spell-checker | Higher recall; huge dictionary ecosystem. | Inverted noise profile: every identifier and name needs allowlisting; word-list maintenance becomes a chore that erodes the gate. | Rejected: precision over recall here. |
| codespell | Known-misspelling checker (Python) | Same philosophy as typos. | Python toolchain in a Node monorepo; smaller corpus; slower. | Rejected: typos dominates it. |
| Vale | Prose style linter | Enforces style guides, not just spelling. | A style-rule programme is a much bigger commitment than the gap (spelling) warrants. | Rejected: Less Is More; could complement later. |
| AI reviewers only (status quo) | Non-deterministic | Already running. | Six-year-old typos in published posts show the miss rate; never audits the backlog. | Rejected. |
Where It Sits On The Adoption Curve
Early-to-late majority. typos has become the default spell-check gate in large Rust/Python/JS open-source projects and pre-commit setups; stable CLI, maintained corpus. Comfortably in the Goldilocks zone, with codespell as the decade-old fallback if it ever stalls.
Relation To Building Philosophy
- Less Is More. One binary, one small TOML file, one step in an existing workflow, no service, no dictionary sprawl.
- Short Feedback Loops. A typo in a new blog post fails the PR that introduces it, instead of shipping to the public site until a reader notices.
- LLM-Optimized. Agents generate most content drafts; a deterministic spelling floor means the same slip fails the same way every time. The committed config also teaches agents which "misspellings" here are intentional.
- Respect Goodhart's Law.
Every allowlist entry is a reviewed line in
_typos.tomlwith a stated reason, not a silent suppression.
Gaps & Risks
- Recall is bounded by the corpus: a misspelling typos has never seen passes. Accepted. The low-noise design is what makes the gate sustainable.
- British/American variants are both accepted by default, so inconsistent variant usage isn't flagged. That's a style concern (Vale's domain), out of scope.
- Corpus updates can add findings: a mise version bump may flag words it previously ignored; Renovate surfaces that as an explicit PR (ADR 024).
Consequences
Positive
- Six real misspellings in published content fixed; spelling regressions now blocked at PR time across code, docs, and content.
- Near-zero-noise gate: 23 of 29 first-run findings were resolved by config taxonomy, not suppressed ad hoc.
- Free, single binary, mise/CI parity.
Negative
- One more blocking lint step (sub-second on this repo).
_typos.tomlgrows as new proper nouns enter the content.