Engines
A rule has to be enforced by something. Taskless routes each rule to the engine that can decide it, and which engine that is follows from the evidence the rule depends on rather than from a preference.
Which engine, and why
Section titled “Which engine, and why”Start from what decides the rule. Everything else follows.
| The rule is decided by… | Engine | Rules live in | Needs an account |
|---|---|---|---|
| a tool your repo already runs, in that tool’s dialect | linter | that tool’s own config | no |
| one file’s syntax tree | sg |
.taskless/rules/sg/<id>/ |
no |
| a document’s words | vale |
.taskless/rules/vale/<id>/ |
no |
| more than one file, or evidence outside the files | runtime |
.taskless/rules/runtime/<name>/ |
yes |
You do not have to pick. npx @taskless/cli agent route reads the repository and decides, and the authoring page covers how that decision gets made.
The order matters more than it looks. Naming a destination first and justifying it afterwards is how a rule about prose ends up in sg, and how a question that spans files ends up in vale. State what the rule is decided by, then let the engine follow.
The engines
Section titled “The engines”linter: a tool you already run
Section titled “linter: a tool you already run”If your repository already runs ESLint, Ruff, RuboCop, or Stylelint, a rule that tool can express belongs there. Taskless writes it in that tool’s own dialect, matching the conventions and severity choices your existing config already uses.
Your toolchain enforces it, not Taskless. taskless check does not invoke ESLint, Ruff, RuboCop, or Stylelint. The rule runs the next time your lint step runs, exactly like every other rule in that config. Taskless authors; your tools enforce.
This keeps a rule in the place your team already looks, and it means no new runner in CI for a rule your linter could always have caught.
sg: one file’s syntax tree
Section titled “sg: one file’s syntax tree”The ast-grep engine. It matches against the structure of a single source file, so it catches a pattern regardless of formatting, variable names, or whitespace.
Reach for it when the rule is decided by a call, an import, a JSX attribute, or a type annotation: anything you could point at in one file. Correlating two things within one file is still sg: a useEffect whose dependency array omits a value used in its body is a single-file question, even though it compares two places.
An sg rule carries its own files and ignores, so the rule file is the whole rule. It has its own page, and ast-grep covers the pattern syntax.
vale: a document’s words
Section titled “vale: a document’s words”The prose engine. Where sg makes your codebase’s structure checkable, Vale makes its contents checkable: documentation, README files, commit bodies, and the prose parts of source such as comments.
Vale sees one document at a time. It is the right engine for “no comment should promise future work” and the wrong one for “these twelve pages should agree on a term”. Consistency across documents is evidence outside the file, which makes it a runtime question.
Vale is the engine most likely to be new to you, and the only one with a failure mode that is silent by default. It has its own page, and Vale covers the tool itself.
runtime: evidence beyond one file
Section titled “runtime: evidence beyond one file”When a rule needs to hold two files at once, read git metadata, resolve an alias, follow a config chain, or look outside the repository, no single-file engine can express it. A runtime rule pairs ast-grep capture rules with a check.ts that receives the matches and returns findings.
The capture rules do the cheap work: if nothing matches, check.ts is never invoked. It has its own page, with Anatomy of a Rule covering the layout and the check.ts contract, runtime rules the gate, and Security the execution model.
One rule, one directory
Section titled “One rule, one directory”An sg, vale, or runtime rule occupies its own directory, named for the rule, under its engine:
.taskless/rules/sg/no-eval/ no-eval.yml the rule .tests/no-eval-YYYYMMDD-test.yml its test cases
.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 alone
.taskless/rules/runtime/env-read/ captures/env-read.yml ast-grep rule narrowing which files the check sees check.ts receives those matches and returns findings .tests/fail/undeclared/ a directory per case, as many files as it needs .tests/pass/declared/What each of those files holds, and the contract a runtime check.ts has to meet, is on Anatomy of a Rule.
Writing a rule means creating that directory. Deleting a rule means deleting it. Nothing outside it is touched either way, so two rules never collide, including two authored at the same time.
A linter rule is the exception. It has no directory here at all: it is written into ESLint’s, Ruff’s, RuboCop’s, or Stylelint’s own configuration, wherever your repository keeps that. This is also why taskless rule delete <id> resolves an id across sg, vale, and runtime only. A linter rule is removed the way any other rule in that tool’s config is removed.
The dot on .tests/ is required
Section titled “The dot on .tests/ is required”Not a convention. A requirement, and the failure is disproportionate.
ast-grep walks the rules tree and parses every .yml it reaches as a rule. A plain tests/ directory means your test files get read as rules, and the scan fails for the whole project, not just for the rule you were editing. A dot-directory is skipped by that walk, and the test runner still finds it.
If you rename .tests/ to tests/ to tidy up, you will get an error that points nowhere near what you changed.
What needs an account
Section titled “What needs an account”Three of these engines need nothing from us. One does.
- In the Taskless Open Source CLI: linter,
sg, andvale. They run locally and offline, with no account and no network call. - Requires a Taskless Cloud account:
runtime.
The reason for that split is worth stating plainly, because the obvious guess is wrong.
Runtime rules are gated because they execute code, not because they are a paid feature. An sg or vale rule is inert data. Whatever is in it, the worst a bad one does is report something wrong. A check.ts is a program that runs on your machine with your permissions, and a rule file arriving in a pull request is code arriving in a pull request. So Taskless signs each check.ts, reconciles the signature with the service, and runs only what comes back verified.
That is a property of executing code. It says nothing about how capable an engine is. sg and vale rules are not less trusted, they are unexecuted.
In particular, checking prose costs nothing and needs no account. Vale is available in the Taskless Open Source CLI, like ast-grep.
Verifying a rule
Section titled “Verifying a rule”Whichever engine authored it, two commands close the loop:
npx @taskless/cli verify .taskless/rules/vale/no-simply/npx @taskless/cli test .taskless/rules/vale/no-simply/verify checks that a rule has the components its engine requires. test runs its cases, after verifying it first.
verify matters most for Vale, where an incomplete rule does not error. It simply never fires. See Vale.