Skip to content

Vale

Vale is an open source prose linter, maintained by errata-ai. It is a single Go binary that reads a document, applies the checks you have configured, and prints findings with a file, a line, and a message. It makes no network call and needs no service behind it.

Taskless embeds Vale as the vale engine. Where ast-grep makes a codebase’s structure checkable, Vale makes its contents checkable, and between them they cover what a repository actually holds.

Taskless pins Vale v3.21.0. Everything on this page was measured against that build rather than transcribed from upstream documentation, so a claim here matches the binary you get.

Not a spell checker, and not a grammar checker

Section titled “Not a spell checker, and not a grammar checker”

The three tools look similar from the outside and answer different questions.

Tool Knows Decides
a spell checker a dictionary is this a word
a grammar checker a model of the language is this sentence well formed
Vale nothing until you tell it did this document follow our decisions

Out of the box Vale has no opinion about your writing. It has no house style, no readability target, and no view on the passive voice unless you give it one. That emptiness is the point: the checks are data you write, so what Vale reports is your team’s decisions rather than a vendor’s.

It does have a spelling check, so a dictionary is one of the things you can ask for. It is one of twelve options rather than the product.

A Vale check extends one of twelve built-in types, and the type is chosen by what the rule reads:

The rule is about extends
words or phrases that should not appear existence
preferring one term over another substitution
the case of a whole heading or sentence capitalization
how many times something may appear occurrence
a word repeated back to back repetition
picking one of two acceptable spellings, consistently consistency
“if X appears, Y must also appear” conditional
a document-level length or ratio threshold metric
a readability grade against a named formula readability
a misspelling against a dictionary spelling
phrases that must appear in a fixed order sequence
anything the above cannot express script

Twelve is measured, not counted off the documentation: hand Vale an extends it does not recognize and it names the whole set back at you. Upstream enumerates eleven because it folds readability into metric, but they are separate checks with separate fields.

Each check also takes a scope, which narrows where inside a document it looks. Vale understands the structure of what it is reading, so a scope can be a heading level, list items, link text, table header cells, image alt text, a front-matter key, or the comments of a source file. Since v3.21.0 a scope can also be part of a document picked by CSS selector: doc(section:has(> h2:contains("Decision"))) is one section, heading and everything under it, and ~doc(...) is everything outside it. That structural awareness is what separates Vale from running grep over your docs: a rule can apply to headings and leave the code samples alone, or put a word budget on one section and no other.

A metric check honours its scope as of v3.21.0: scope: sentence measures each sentence, scope: paragraph each paragraph. An absent scope, or scope: text, still measures the whole document.

Vale decides how to read a file from its extension, and there are three outcomes.

Reading Applies to Behaviour
markup .md, .mdx, .markdown, .mdown, .myst, .org, .qmd, .rmd, .html, .htm, .xhtml the whole document is prose, and the format’s non-prose constructs are skipped
comments only source files, including .ts, .tsx, .js, .jsx, .py, .go, .rs, .rb, .java, .c, .cpp, .php, .css, .scss the comment text is linted and the code body is invisible
notebook .ipynb Markdown cells are read as Markdown, code cells as their kernel’s comments; raw cells and outputs not at all
plaintext fallback everything else, .yml, .toml, .sh and .sql included no parser, so the file is one block of prose and code is read as prose

Supported Languages lists every extension Vale’s own format table names, at the version Taskless pins.

The fallback is the one to watch. A rule scoped to YAML flags the configuration keys as readily as the comments, which is rarely what anyone asked for.

A handful of formats are read only by shelling out to an external program: AsciiDoc, DITA, Typst, reStructuredText, and XML. This build supports none of them, and Taskless excludes those files from the Vale run whatever happens to be installed on the machine, so a repository checks the same way everywhere.

.mdx and .ipynb are the recent additions. Vale parses MDX natively as of v3.18.0, and since v3.19.0 it reads the children of an MDX component as Markdown too, so prose wrapped in a component is linted where it previously was not. Notebooks are read cell by cell as of v3.21.0; before that a rule matching [*.ipynb] was linting the notebook’s JSON, outputs and metadata included.

It sees one document at a time, and that is a feature. A Vale check needs only the file in front of it, so a run is fast, deterministic, and reproducible on any machine. The limit is real and worth knowing: a rule that needs two documents to agree is not a Vale rule.

A rule is inert data. A Vale style file is YAML. Running one executes nothing, opens no socket, and reads nothing outside the file being checked. Along with ast-grep rules, that is why the vale engine is available in the Taskless Open Source CLI with no account and no network access.

Rules stay portable. The style file and its .vale.ini are ordinary Vale artifacts. You can point the upstream Vale CLI at them, and other tooling that speaks Vale can read them, so adopting Taskless does not trap your prose rules inside it.

It already understands prose in code. Comment text is where a lot of a codebase’s writing lives, and Vale reaches it without a separate extraction step.

Authoring is covered on the Vale engine page, including the layout of a rule, the style plus scope requirement, and the silent failure that comes from getting it wrong. For which engine a given rule belongs to, see Engines; for the wider flow, Authoring rules.

Terminal window
npx @taskless/cli agent create-vale-rule