Files
Marcos 30d2a3d7da feat(factory): five review skills + a per-review cost model
Skills — the primary now loads conditionally (each one is input tokens), per a
load table in pragent.md:

- attention-tiering: classify every PR trivial/lite/full/oversized BEFORE
  reading anything, and cap file reads, linter runs and subagent fan-out per
  tier. This is the cost governor; the other skills defer to its budget.
- linter-playbook: per-ecosystem detect-and-run commands scoped to changed
  files, the never-install rule, and how to turn a diagnostic into a finding
  instead of pasting tool output.
- security-lens: the inline security checklist for when @security isn't worth
  delegating, built around a source -> sink test each finding must pass.
- malicious-change: hostile-PR detection — injection aimed at the reviewer,
  install/CI-time hooks, obfuscated payloads, dependency confusion, logic
  backdoors. Complements the runtime containment added in the previous commit:
  that stops the agent being hijacked, this makes it report the attempt.
- comment-craft: how to write problem/fix/suggestion so a maintainer can act in
  one read, and what to cut.

pilot/cost_model.py — prices a review against published Claude and OpenAI rates
(fetched 2026-08-18). Prompt sizes are measured from the factory files rather
than guessed; per-tier workloads come from the tiering budgets. The model is
explicit about the thing that actually dominates an agent loop: the whole
conversation is resent every step, so caching moves ~2.3x of the bill.

Blended over a 5/35/55/5 mix with caching on: ~$0.61/PR on Opus 5 or GPT-5.6
Sol, ~$0.24 on Sonnet 5 or Terra, ~$0.12 on Haiku 4.5, ~$0.02 on Luna. At 350
PRs/month that's ~$212 / ~$85 / ~$43 / ~$8.50.

Tests: 101 -> 122.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B11e8TZZxJyzHW7jj7KWUN
2026-08-18 04:53:49 +00:00

3.9 KiB

name, description
name description
linter-playbook 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 <files>
Python pyproject.toml/ruff.toml/.ruff.toml ruff check <files>
Python types mypy.ini, [tool.mypy], pyrightconfig.json python -m mypy <files> or npx --no-install pyright <files>
Go go.mod go vet ./<changed-pkg>/..., gofmt -l <files>
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 <files> if the bundle is installed, else rubocop <files>
PHP phpstan.neon, psalm.xml vendor/bin/phpstan analyse <files> if vendor/ exists
Shell any *.sh in the diff shellcheck <files>
YAML/K8s *.yaml in the diff yamllint <files>; 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.