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.
How a check is built
Section titled “How a check is built”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.
What Vale can read
Section titled “What Vale can read”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.
Why Taskless uses it
Section titled “Why Taskless uses it”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.
Writing one
Section titled “Writing one”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.
npx @taskless/cli agent create-vale-rule