Vale
Vale checks the words of a document. ast-grep makes your codebase’s structure checkable; Vale makes its contents checkable. Between them they cover what a repository actually holds.
That includes more than the docs directory. Vale reads prose wherever it lives: Markdown, README files, commit bodies, and the prose parts of source code, such as comments.
What it is good for
Section titled “What it is good for”Rules about language, phrased as rules rather than as style-guide advice nobody rereads:
- Ban a word or phrase the team has decided against.
- Prefer one term over another, consistently.
- Enforce a product’s spelling, including its capitalization.
- Require headings to be sentence case.
- Catch a doubled word, or a phrase repeated more often than it should be.
- Require a term to be defined before it is used.
What it is not for
Section titled “What it is not for”Vale sees one document at a time. A rule that needs two documents to agree is not a Vale rule, however much it feels like a prose question. “These twelve pages should use the same term for this concept” depends on evidence outside the file being checked, which makes it a runtime rule.
Keep the distinction in mind when phrasing a rule. “No page may use the word simply” is Vale. “Every page must use whichever of rule or check the glossary uses” is not.
A Vale rule has two files
Section titled “A Vale rule has two files”This is the part that catches people. A Vale rule is the style plus the scope, and it is not a rule until both exist.
.taskless/rules/vale/no-simply/ no-simply.yml the style: what the rule looks for .vale.ini the scope: which files it applies to .tests/fail/bad.md prose it must flag .tests/pass/ok.md prose it must leave aloneThe style file says what to match. The .vale.ini says where it applies. A style with no scope is a rule that is never asked about anything.
The id appears four times, and all four must agree
Section titled “The id appears four times, and all four must agree”.taskless/rules/vale/no-simply/ <- the directory.taskless/rules/vale/no-simply/no-simply.yml <- the style fileno-simply.no-simply = YES <- the assignment in .vale.initskl) rule = no-simply <- the breadcrumb in .vale.iniThe doubled no-simply.no-simply is not a typo. Vale addresses a check by the style it belongs to and then by its own name, and here both are the rule’s id.
The silent failure
Section titled “The silent failure”npx @taskless/cli verify .taskless/rules/vale/no-simply/verify checks that the rule has the components its engine requires. For Vale, that means the style and the scope both exist and the id agrees in all four places. It runs before Vale is invoked, so it catches the condition that would otherwise produce a passing check and no output. It also refuses an action name outside replace, remove, suggest, convert, edit: Vale v3.21.0 rejects an unknown action when the rule loads, and one config serves the whole run, so a typo there would fail every Vale rule in the project rather than only the one that carries it.
Inside a .vale.ini, a repeated [glob] section or a repeated key keeps its last assignment. Put a disable after the enable it narrows. BlockIgnores and TokenIgnores now take effect under an [*.html] matcher too; through Vale 3.20.0 they were ignored there, so a rule scoped to HTML that carries either key reports fewer findings after the upgrade, with nothing warning that the ignored region is now honoured.
Run it before you believe a new Vale rule works. Then run its tests:
npx @taskless/cli test .taskless/rules/vale/no-simply/Test cases are documents
Section titled “Test cases are documents”Vale fixtures are prose files in two buckets:
.tests/fail/holds prose the rule must flag..tests/pass/holds prose the rule must leave alone.
The pass/fail naming is Vale’s own. It differs from ast-grep’s test files, which use valid/invalid as key names inside a test YAML. Those are handed to sg test verbatim, so the word has to match what that tool expects. Nothing hands a Vale fixture to ast-grep, so the buckets keep Vale’s vocabulary.
Write the failing cases first. A prose rule that matches nothing is easy to write by accident and hard to notice, which is the same hazard as the missing scope in a different disguise.
Authoring one
Section titled “Authoring one”npx @taskless/cli agent create-vale-ruleThat returns the full recipe for your coding agent to follow: the check types available, how to scope a rule to particular files, and worked examples for each kind of check. If you are not sure Vale is the right engine, start a step earlier:
npx @taskless/cli agent routeThe routing recipe teaches the agent how to understand your request, and how to write a rule for the systems you already have, bringing in Taskless’ engines where needed. See Engines for what it is choosing between, and Authoring rules for the wider flow.
Cost and account
Section titled “Cost and account”The Vale engine is available in the Taskless Open Source CLI and does not require a Taskless Cloud account.
Vale rules are inert data, exactly like ast-grep rules. Running one executes nothing and makes no network call, so it works offline.