Skip to content

fix(route): name every detected linter, and add an Oxlint guide - #617

Merged
theCodeDrift merged 3 commits into
mainfrom
fix/route-names-every-linter
Nov 7, 2026
Merged

theCodeDrift merged 3 commits into
mainfrom
fix/route-names-every-linter

Conversation

@theCodeDrift

@theCodeDrift theCodeDrift commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

route prints "The linters detect found here", then lists only linters that have a guide paragraph. On NoMercyLabs/nomnomzbot, detect correctly reports ESLint and Oxlint, but route showed ESLint alone. With Oxlint as the only linter, it said steps 1 and 2 of the legacy ladder did not apply, which contradicts the spec.

What changed

  • route names every detected tool that can host a rule. A tool with no paragraph is named and sent to the generic recipe, so this can't recur for the next linter.
  • Formatters (prettier, black, pint, php-cs-fixer) and Taskless's own engines (ast-grep, vale) stay out of that list.
  • A scanned route now says detect recognizes a fixed list of tools, so a repository may run others.
  • New create-legacy-rule --tool=oxlint recipe, and an Oxlint paragraph in route. route is now topic v9.

Worth a close look

  • The recipe tells agents to run oxlint --rules --format default. Under an agent or GitHub Actions, Oxlint picks an output format with no rule list, so a bare oxlint --rules prints nothing and exits 0 (linter: oxlint --rules prints nothing when an AI agent is detected (agent format) oxc-project/oxc#27569). The contract check caught this.
  • Part of the Oxlint recipe is still from the oxc.rs docs only. The contract check, pinned to oxlint@1.82.0, confirms both catch-all rules exist and that no-restricted-syntax does not. It does not cover nested configs replacing their parent.
  • The eslint-plugin-oxlint note is not from those docs. The recipe says buildFromOxlintConfigFile turns off in ESLint the rules the Oxlint config enables. nomnomzbot does call it, but the behavior itself is from prior knowledge.
  • NOT_A_LEGACY_HOST lives in recipes.ts, because LINTER_SIGNALS is in scan.ts and the prompts bundle must not import host capabilities.

Verification

Typecheck and lint pass, and the CLI suite passes with 2,939 tests. route was run against a nomnomzbot clone and an Oxlint-only fixture. The archive dropped no scenarios: 38 → 41 in cli-rule-routing, 12 → 12 in cli-legacy-rule-recipes.

Not in this PR

C# language detection, the ESLint custom-rule count reading unknown from a repository root, and .githooks/ missing from hooks.

route printed "The linters detect found here" and listed only linters
with a guide paragraph, so a repository running Oxlint read as though it
did not. With Oxlint alone it said steps 1 and 2 of the legacy ladder did
not apply, which contradicts the requirement that step 1 is taken whenever
a detected linter could host the rule.

route now names every detected tool that can host a rule, and sends one
with no paragraph to the generic recipe. Formatters and Taskless's own
engines stay out. Oxlint gets its own create-legacy-rule recipe.
@theCodeDrift

Copy link
Copy Markdown
Member Author

@claude /review

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @theCodeDrift's task in 2m 43s —— View job


Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json contains only the trigger comment (@claude /review), with no prior reviews or review threads, so this is the first review of this PR.

  • Read .prior-review.json
  • Gather PR diff and changed files
  • Review spec/proposal changes
  • Review recipes.ts and Oxlint recipe
  • Review tests
  • Post final summary

Summary

This is the first review of this PR; everything below is [New].

The core fix in packages/cli/src/prompts/recipes.ts (legacyLintersBlock) does what the proposal describes: it separates "has a written paragraph" (LEGACY_LINTER_GUIDE) from "can host a legacy rule at all" (everything detect finds minus NOT_A_LEGACY_HOST), and names the latter group instead of silently omitting it. I cross-checked NOT_A_LEGACY_HOST against the full LINTER_SIGNALS table in packages/cli/src/detect/scan.ts — every formatter (prettier, black, pint, php-cs-fixer) and both Taskless engines (ast-grep, vale) are excluded, and the rest of the signals not in LEGACY_LINTER_GUIDE (stylelint, rubocop, standard, golangci-lint, clippy, phpstan, php_codesniffer, psalm, clang-tidy, swiftlint, checkstyle) correctly fall through to the new "named with no guide of their own" branch rather than being dropped. The unrecognized/"fixed list of tools" paragraph is correctly gated on linters !== undefined (only appended for an actual scan) in both the empty-match and non-empty branches.

Tests (prompts.test.ts, legacy-ladder.test.ts, agent-extensions.test.ts) exercise the new branches well: oxlint-only detection, a detected-but-unguided tool (stylelint) still being named, a non-host tool (vale) staying out of that list, and the "fixed list" sentence appearing only when scanned. One minor gap: there's no test for the case where only unguided tools are detected (e.g. linters: ["stylelint"] with no known linter at all) — I traced the logic by hand and it behaves correctly (produces the "Also detected here…" paragraph without the "step 1 and 2 do not apply" sentence), but it's the one branch combination without direct coverage.

The new create-legacy-rule.oxlint.md recipe mirrors the structure of the existing Biome/ESLint recipes closely and is internally consistent with scan.ts's oxlint config-file list. Per the PR body, the oxc.rs-sourced claims (no no-restricted-syntax, nested-config replace-not-merge semantics) are pinned in legacy-linter-vendor-contract.test.ts but that suite is opt-in (TASKLESS_LEGACY_LINTER_CONTRACT=1) and, as disclosed, hasn't actually run yet — so I can't independently verify those facts against the real oxlint@1.82.0 binary; that's already flagged by the author as the thing to watch in the legacy-linter-contract CI result rather than something I'm newly raising. Likewise the eslint-plugin-oxlint/buildFromOxlintConfigFile note is explicitly called out by the author as coming from prior knowledge rather than the docs, so I'm not re-raising that as a new finding either.

I didn't find any correctness bugs in the diff itself. No inline comments to post.

Under an agent or GitHub Actions, Oxlint picks an output format with no
rule list, so `oxlint --rules` prints nothing and exits 0. The guide's
confirm step would have read as "this rule does not exist". Pass
`--format default`, which the contract check now pins.

Drop the claim that `--print-config` shows which config governs a file:
it prints the root config. Also cover `route` with only unguided linters.
@theCodeDrift

Copy link
Copy Markdown
Member Author

Re: @github-actions — "Claude finished @theCodeDrift's task in 2m 43s…"
#617 (comment)

Added the missing test for a repository where only unguided tools are detected, in a6f9a3e. No other findings to act on.

— AI Coding Agent

@theCodeDrift
theCodeDrift merged commit e4a2ed5 into main Nov 7, 2026
9 checks passed
@theCodeDrift
theCodeDrift deleted the fix/route-names-every-linter branch November 7, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant