Skip to content

Overview

Taskless rules live in your repository and flag patterns your team has decided to catch. When a rule matches, it reports the issue and can suggest a fix. Your coding agent also reads your rules while it writes code, so problems get caught during generation instead of in review.

Rules differ by what enforces them, and that follows from what the rule is decided by. There are four engines, covered in full on the Engines page:

The rule is decided by… Engine Enforced by
a tool your repo already runs linter that tool, in your own lint step
one file’s syntax tree sg taskless check
a document’s words vale taskless check
more than one file, or evidence outside the files runtime taskless check

Two properties are worth carrying around.

sg and vale rules are inert data. Running one executes nothing and makes no network call, so they always run, offline and with no account.

Runtime rules execute a check.ts, so they run only when that code has been verified. See Runtime rules for the layout and Security for the verification model.

Linter rules are enforced by your toolchain, not by Taskless. taskless check does not invoke ESLint, Ruff, RuboCop, or Stylelint. It writes the rule in that tool’s dialect; the tool runs it the next time you lint.

An sg rule matches against your code’s syntax tree, so it catches a pattern regardless of formatting, variable names, or whitespace:

id: no-console-log
language: TypeScript
rule:
pattern: console.log($$$ARGS)
severity: warning
message: Avoid console.log in production code. Use a structured logger instead.
fix: logger.info($$$ARGS)
Field Purpose
id Unique identifier for the rule
language Target language (TypeScript, JavaScript, Python, etc.)
rule The syntax-tree pattern to match against
severity error, warning, info, or hint
message Human-readable explanation of the issue
fix Optional auto-fix replacement pattern

A vale rule looks quite different, because it matches words rather than structure. See Vale.

You don’t hand-write rule YAML. You describe the pattern in natural language and let your coding agent author it.

Taskless is local-first: it authors the rule on your machine whenever it reasonably can, and only reaches for the Taskless service when a rule genuinely needs it. Start with routing, which reads your repository and decides which engine fits:

Terminal window
npx @taskless/cli agent route

See Authoring rules for how that decision gets made.

agent returns a recipe, the steps for an agent to follow. The commands that recipe runs are still there when you want them directly:

Terminal window
npx @taskless/cli rule create --from <file>

--from points at a JSON file describing the rule: a prompt in natural language, plus optional successCases and failureCases. Add --anonymous to use the local-only path with no API call.

Every rule occupies its own directory, under its engine:

your-repo/
└── .taskless/
├── taskless.json # Install manifest (targets, CLI version)
└── rules/
├── sg/
│ └── no-console-log/
│ ├── no-console-log.yml
│ └── .tests/
│ └── no-console-log-test.yml
├── vale/
│ └── no-simply/
│ ├── no-simply.yml # the style
│ ├── .vale.ini # the scope
│ └── .tests/
│ ├── fail/bad.md
│ └── pass/ok.md
└── runtime/
└── env-read/
├── captures/
├── check.ts
└── .tests/

Writing a rule means creating one of those directories; deleting a rule means deleting it. Nothing outside it is touched either way.

The dot on .tests/ is required. See Engines for what breaks without it.

Two commands close the authoring loop, whichever engine wrote the rule:

Terminal window
npx @taskless/cli verify .taskless/rules/sg/no-console-log/
npx @taskless/cli test .taskless/rules/sg/no-console-log/

verify checks that the rule has the components its engine requires. test runs its cases, after verifying it first. For Vale rules, verify is not optional. An incomplete Vale rule reports nothing rather than failing, so a passing check proves nothing until it has been verified.

Terminal window
npx @taskless/cli rule delete <id>

That takes a bare rule id, not a path. The CLI works out which engine holds it and removes the rule directory, then also tries to remove a legacy metadata sidecar at .taskless/rule-metadata/<id>.yml. That sidecar is one this CLI never writes, so expect it to be absent; the removal is there for a directory someone created by hand. What deleting by hand really skips is the ambiguity check below.

Ids are not unique across engines. If two engines hold the same id, the CLI refuses rather than guessing which you meant.