Taskless v0.11.3: Rules, Renames, and Rendered Messages
0.11.3 is a patch release with 17 changesets. Most of it is about rules you already have: two rules can't share an id anymore, a Vale config that switched off its neighbors gets caught, and test shows you the message a rule actually renders. There's also a way to run a single rule.
Run one rule with check --rule
taskless check --rule <id> limits a run to the rules you name. Repeat the flag for more than one. Before this, measuring one rule across a project meant running every rule and filtering the JSON afterward.
taskless check --rule no-eval --rule no-em-dashesThe filter covers ast-grep, Vale, and runtime rules, and keeps every exclusion a whole-project run applies. An id that doesn't match a rule directory fails with RULE_NOT_FOUND, so a typo can't pass as zero findings.
Rule ids are unique across engines
.taskless/rules/sg/no-eval/ could sit beside .taskless/rules/vale/no-eval/ and nothing complained. check prints error[no-eval] without an engine, so a collision looked like the same finding twice, and any command that takes a rule id had two rules to choose from.
verify now fails a rule whose id is also a directory under another engine. Existing collisions get renamed the first time you run taskless init after upgrading: sg/no-eval becomes sg/no-eval-sg and vale/no-eval becomes vale/no-eval-vale. When both move, neither keeps the bare id, so you don't have to work out which one did. A runtime rule is never renamed.
The rename stays inside each rule's directory (the rule file, its id: field, the .tests/ fixtures, a Vale rule's .vale.ini) and prints every path it touches. It's safe to interrupt, too. If a run dies halfway, the next init picks up where it stopped.
If an id you use changes, update whatever outside .taskless/ names it: CI config, baselines, suppression comments, and any check --rule you've scripted.
Vale 3.22.0, and configs that switched off other rules
The bundled Vale goes to 3.22.0. What a rule can do now:
scope: text & ~link(also~strong,~emphasis,~code) lints the paragraph with that element's text blanked out, so a wording rule can skip link text and bold terms and still check the sentence around them.split: trueon aspellingrule checks each part of an identifier.getHTTPResponsze_v2reportsResponsze.- A
frontmatterrule reports every occurrence in a field. A token appearing twice used to be reported once. - A one-line MDX element like
<Note>...</Note>gets linted.
One change needs you to do something. Every earlier version of our create-vale-rule recipe wrote BasedOnStyles = into each rule's .vale.ini. Through 3.21.0 the line did nothing. On 3.22.0 an empty value clears every setting inherited from an earlier matcher, and Taskless assembles one run config from all your rules in id order. A rule with that line under [docs/**] switched off every rule that sorted before it for files under docs/, and nothing reported it.
Migration 0008 removes the line on your next init. check and verify won't run until it has, and they tell you why. Commit the rewritten files. A migrated config enables exactly what it did on 3.21.0.
A rule whose scope negates link, strong, emphasis, or code also changes: it no longer sees that element's text inside a paragraph, so findings inside those elements go away. Drop the negation if you wanted them.
verify checks .vale.ini against a schema
BasedOnStyles was one case of a wider problem, a .vale.ini that Vale misreads without saying so. verify now parses each rule's config and rejects the shapes Vale gets wrong, naming the constraint and the line:
- an assignment above the first matcher
- a matcher missing its breadcrumb comment
- a key that names another rule
- a value other than
YESorNO - any
BasedOnStylesline - a config with no matcher, or no
YES - a
NOmatcher ahead of everyYES
Each one appears under a vale-config-* constraint in verify --json. check runs the same schema before it assembles the Vale config. A rejected config stops Vale for that run with the rule and line named, and ast-grep still runs. taskless verify shows every rejected line at once.
Two more Vale rules that loaded fine and never fired are caught now:
- A
substitutionrule with a backreference like\1in aswapkey. Vale wraps each key in its own capture group, so\1points at the wrapper and matches nothing.$1in the swap value still works. - A
rawlist with more than one entry gets a warning. Vale joins the entries end to end into one pattern, so write(a|b)if you meant alternatives.
test shows the message a rule renders
A rule whose message fills in its captures can put them in the wrong order, fire on every fail/ fixture, stay quiet on every pass/ one, and pass test. The rendered message was the only evidence, and seeing it meant a second check run against a fixture path you built yourself.
test --json now gives each rule a findings array in the same shape check --json uses, plus a bucket of "pass" or "fail" naming the fixture that produced it. That covers ast-grep, Vale, and runtime rules. In the human output, a passing rule still prints one line. A failing rule prints the findings behind the failure, so pass fixture wrongly fired now shows you what matched.
The 128KB guard is gone
0.11.2 skipped Vale target files over 128KB, because the Vale build we'd shipped slowed down badly on large files, and we said we'd remove the guard in the next patch. Vale 3.21.0 made that cost linear, so it's out. Re-measured on the reproduction from taskless/cli#325:
- 3.2 MB in one block: ~81,000 ms on Vale 3.20.0, ~230 ms on 3.21.0.
- 128 KB in one block, the old cap: ~770 ms on 3.20.0, ~26 ms on 3.21.0.
- 25 MB in one block: ~2,200 ms on 3.21.0.
A file 0.11.2 listed in its "did not check" notice gets linted again. The per-file retry for unparseable frontmatter and the 60 second run timeout both stay.
A shorter feedback survey
0.11.2 added taskless feedback. In 0.11.3 the only required answer is what kind of rule you were trying to create, and everything else is optional. Saying skip at the invite no longer dismisses it: the agent sends its own account of the session and leaves your words out. feedback dismiss is for when you want nothing sent. Every install gets invited once more, since the new survey keeps its own cadence. DO_NOT_TRACK=1, TASKLESS_TELEMETRY_DISABLED=1, and CI=true still turn it off.
Also in this release
taskless detectrecognizes oxlint from.oxlintrc.json,.oxlintrc.jsonc,oxlint.config.ts,oxlint.config.mts, or anoxlintentry inpackage.json.taskless --versionand-vprint the version. They used to print the usage banner.checkmarks every notice it prints. A second notice used to print as a bare line, so a Vale advisory could read like stray output.- A failing migration is reported once, with its number.
- The
onboardrecipe says which sources it found and why one isn't offered. It offers a bug-tracker scan when your agent has an MCP that reaches one, Jira or Linear for example. - The
create-vale-rulerecipe fixes its regex guidance. Vale compiles withregexp2, so lookahead, lookbehind, and backreferences all work, and the recipe documents where a lookaround runs into the implicit word boundary ontokensandswap.
Getting it
npx @taskless/cliThen run taskless init to apply the migrations and commit what it rewrites, and taskless update to see what this release changes for the rules you already have.