Skip to content

feat(route): legacy ladder and per-linter custom-rule counts - #577

Merged
theCodeDrift merged 3 commits into
mainfrom
legacy-linter-ladder
Oct 23, 2026
Merged

theCodeDrift merged 3 commits into
mainfrom
legacy-linter-ladder

Conversation

@theCodeDrift

@theCodeDrift theCodeDrift commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Stack (root → tip):

First of two PRs for the legacy-linter-recipes OpenSpec change. For a linter the repository already runs, the agent now uses a rule that linter already ships instead of working out each tool's dialect from scratch. It writes a new custom rule there only when the repository's own rules give it something to copy.

The legacy ladder. route (topic v8) takes these in order:

  1. A rule the detected linter already ships solves the request. Always checked first, including the tool's catch-all rules.
  2. The repository already has 5 or more of its own custom rules for that linter, so a new one can be modeled on them.
  3. A Taskless sg or vale rule.
  4. A Taskless runtime rule.

route no longer asks the user to choose between a linter rule and a Taskless rule: the order decides. It also renders a paragraph for each linter detect found, saying what that tool always checks first.

detect --json reports customRules for ESLint, Biome, pylint, flake8, and Ruff: a count of the rules the repository wrote itself for that linter, and where they are.

  • ESLint counts rule files in house rule directories.
  • Biome counts plugins entries.
  • pylint counts load-plugins modules that exist in the repository.
  • flake8 counts [flake8:local-plugins] entries and published entry points.
  • Ruff is always 0.

ESLint's count is "unknown" when the config defines rules inline or imports a plugin from a local file, because counting those would mean running JavaScript. The recipes treat "unknown" as below the threshold.

Recipes. create-legacy-rule v3 looks for a shipped rule first and asks before adding a plugin the repository does not have. During onboarding (v11), the agent only enables rules from packages already installed: it writes no custom rules and adds no dependencies.

Safe to merge on its own. The route paragraphs name only create-legacy-rule here; #578 adds --tool.

Typecheck, lint, and the full test suite pass. The changeset is in this PR, and #578 extends it.

Adds the legacy-linter-recipes OpenSpec change and the customRules
count detect reports for eslint, biome, pylint, flake8, and ruff.
route v7 checks for a rule the linter ships before anything else, gates a
new custom rule on five of the repository's own, and renders a paragraph
per linter detect found. create-legacy-rule v2 and onboard v7 follow it.
@theCodeDrift
theCodeDrift added this pull request to stack #579 October 23, 2026 01:22
@github-actions github-actions Bot added the Open OpenSpec Contains unresolved OpenSpec changes. All openspec changes must eventually reach an archive state. label Oct 23, 2026
@theCodeDrift theCodeDrift changed the title legacy linter ladder feat(route): legacy ladder and per-linter custom-rule counts Oct 23, 2026
@theCodeDrift

Copy link
Copy Markdown
Member Author

@claude /review

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @theCodeDrift's task in 5m 47s —— View job


Review complete

Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json contained no reviews, review threads, or top-level comments beyond the trigger itself, so this is the first review of this PR; everything below is assessed fresh and nothing is being skipped as "already addressed."

  • Read .prior-review.json
  • Fetch PR diff and changed files
  • Review OpenSpec docs for intent
  • Review detect/custom-rules.ts
  • Review detect/scan.ts and schemas/detect.ts
  • Review agent markdown changes (route.md, create-legacy-rule.md, onboard.md)
  • Review prompts/recipes.ts and commands/agent.ts
  • Review tests
  • Post inline comments for findings
  • Post final summary

Summary

The implementation lines up well with the OpenSpec proposal/design/spec deltas: the customRules count added to detect --json for ESLint/Biome/pylint/flake8/Ruff, the route legacy ladder, the %(LEGACY_LINTERS)s paragraph rendering, and the rewritten create-legacy-rule.md/onboard.md all match what the specs describe, and the new/updated tests (legacy-ladder.test.ts, the detect.test.ts custom-rule-count suite, prompts.test.ts) exercise the scenarios the spec calls out, including the "unknown treated as below threshold" and monorepo/nested-config cases. I traced the ESLint rule-directory counting, the JSONC-lite parser for Biome, the hand-rolled INI parser for pylint/flake8, and the pyproject entry-point counting against the test fixtures and they check out — the --tool flag and per-tool recipes are correctly left out of scope for this PR (confirmed in agent.ts and tasks.md, section 3+ unchecked).

Two inline findings, both [New]:

  1. packages/cli/src/detect/custom-rules.ts:117 — readdirSync(path) in countRuleDirectory isn't wrapped in try/catch, unlike the statSync right below it and every other read in this module. An unreadable rule directory (EACCES, etc.) throws through countEslint → detectRepository, which neither detect.ts nor agent.ts catches; the top-level handler in index.ts would print to stderr and set exitCode = 1 without ever writing JSON to stdout under detect --json. This conflicts with the module's own "derived from files on disk without executing anything" framing and the established "unreadable files are skipped" invariant elsewhere in scan.ts.
  2. packages/cli/src/detect/custom-rules.ts:75 — minor: the house rule-directory list (eslint-rules, eslint-local-rules, tools/eslint-rules) is duplicated verbatim in scan.ts's detectRuleStyles. Not a bug, just a drift risk worth a shared constant.

Nothing else stood out as incorrect — the ESLint "unknown" vs. counted-threshold logic, the Biome nested-config summation, the pylint pylint.* exclusion, and the flake8 local-plugin + entry-point counting all matched their test expectations and the design doc's stated behavior.

Comment thread packages/cli/src/detect/custom-rules.ts Outdated
Comment thread packages/cli/src/detect/custom-rules.ts Outdated
…ing the scan

Share the house rule-directory list between the custom-rule count and the
scan's rule-style hints so the two cannot drift.
@theCodeDrift

Copy link
Copy Markdown
Member Author

Re: @github-actions — "Claude finished @theCodeDrift's task in 5m 47s…"
#577 (comment)

Both inline findings are fixed in ed49196: an unreadable ESLint rule directory is now skipped rather than failing detect (with a test), and the house rule-directory list is shared between custom-rules.ts and scan.ts.

— AI Coding Agent

@theCodeDrift
theCodeDrift marked this pull request as ready for review October 23, 2026 15:05
@theCodeDrift
theCodeDrift merged commit c77692b into main Oct 23, 2026
14 checks passed
@theCodeDrift
theCodeDrift deleted the legacy-linter-ladder branch October 23, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Open OpenSpec Contains unresolved OpenSpec changes. All openspec changes must eventually reach an archive state.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant