--- name: linter-playbook description: Which typecheck/lint command to run per ecosystem, how to scope it to changed files, and how to turn its output into review findings. Load before running any bash checks on a repo. --- # Linter playbook The repo's own tooling is the cheapest high-signal source you have: it finds real defects without you reading the code. Run it on the **changed files only**, then translate diagnostics into findings — never paste raw tool output into a review. ## Rules that apply to every ecosystem - **Detect, don't assume.** Check the config file exists before running the tool. - **Never install anything.** No `npm install`, `go mod download`, `pip install`, `bundle install`, `cargo fetch`. If the tool needs missing deps, skip it and say so in the summary. Installs are slow, noisy, and run untrusted code. - **Scope to changed files.** Whole-repo runs bury the PR's diagnostics in pre-existing ones and cost tokens. - **Ignore diagnostics in files the PR didn't touch.** A pre-existing error is not this PR's problem. - **Cap the output.** Pipe through `head -50`. A wall of errors means the tool is misconfigured, not that the PR has 400 bugs. - **Timebox.** If a command hasn't returned quickly, drop it and move on. ## Per ecosystem | Ecosystem | Detect | Run (changed files) | |---|---|---| | TypeScript | `tsconfig.json` | `npx --no-install tsc --noEmit` (project-wide by design; filter output to changed paths) | | JS/TS lint | `eslint.config.*`, `.eslintrc*` | `npx --no-install eslint ` | | Python | `pyproject.toml`/`ruff.toml`/`.ruff.toml` | `ruff check ` | | Python types | `mypy.ini`, `[tool.mypy]`, `pyrightconfig.json` | `python -m mypy ` or `npx --no-install pyright ` | | Go | `go.mod` | `go vet .//...`, `gofmt -l ` | | Rust | `Cargo.toml` | `cargo clippy --no-deps` if the target dir already exists, else skip (a cold build is too slow) | | Java/Kotlin | `pom.xml`, `build.gradle*` | usually skip — a Gradle/Maven run is a build. Read the code instead. | | Ruby | `.rubocop.yml` | `bundle exec rubocop ` if the bundle is installed, else `rubocop ` | | PHP | `phpstan.neon`, `psalm.xml` | `vendor/bin/phpstan analyse ` if `vendor/` exists | | Shell | any `*.sh` in the diff | `shellcheck ` | | YAML/K8s | `*.yaml` in the diff | `yamllint `; for manifests prefer reading — schema tools are rarely installed | | Terraform | `*.tf` | `terraform fmt -check`, `terraform validate` only if `.terraform/` exists | | SQL migrations | `migrations/` | no tool — read them; look for missing rollback, non-concurrent index, table lock on a big table | If `rtk` is on `PATH`, prefer `rtk grep` for searching — same results, far less output for the same information. ## Turning diagnostics into findings A diagnostic is evidence, not a review comment. - **Translate.** `TS2532: Object is possibly 'undefined'` becomes "`opts.retry` can be undefined when called from `scheduleJob` (line 88) — this throws on the retry path". Name the caller you checked. - **Confirm reachability** before reporting. A type error on a branch that cannot execute is `low`, not `high`. - **One finding per root cause**, not one per diagnostic. Twelve `no-unused-vars` in one file is one finding at most — and usually it's a nitpick worth skipping. - **Formatter-only output is not a finding.** `gofmt -l` listing a file is a style issue; skip it unless the repo's CI enforces it and the PR would break the build — then it's `low` and worth one line. - **A clean run is not a finding either.** Don't report "linters passed". Mention it in the summary, one clause. ## When tooling is unavailable Say which check you wanted and why you skipped it — one clause in the summary ("`tsc` skipped: `node_modules` absent"). That tells a maintainer the review had a blind spot, which is more useful than silence and far more useful than a fabricated pass.