Show HN: Farol – A risk map of a pull request for the person who approves it
farol is a free, MIT-licensed CLI and GitHub Action that runs after an AI code review and gives the human approver a risk verdict (fast track, quick review, careful review) and a 0-100 score for a pull request. It uses the TypeScript type checker, go/types and Python ast to trace which pages, APIs and jobs a change reaches, and it adds diff coverage, security findings and contract breaks. It never executes or sends code.
What it claims
farol scores how risky a pull request is to merge and ranks the files that carry the risk. It runs after an AI review, not in place of one. The output is deterministic: the same PR gives the same report, and every reach claim comes with its call chain. It is shadow-only. It comments, but it never approves, requests changes or fails a check. When it cannot read part of a change, it labels the PR "unknown" and asks for careful review instead of calling it safe.
How it works
- Diff the merge-base against the head. Renames are detected, and whitespace-only and trailing-comma lines are ignored.
- Parse both trees without running them, using the TypeScript compiler API,
go listplus go/types offline, and Python ast. This builds entry points and a declaration-use graph. - Compare declarations by a hash of their code, so comments and formatting do not count.
- Find the shortest chain from each changed declaration to every entry point that runs it.
- Combine reach × untested share, findings, contract changes and dependencies into a score.
The score is described as the chance that at least one risky item breaks something. Any of the following forces "careful review": a score of 60 or more, a breaking contract change, a high-severity finding, an untested central file, or unreadable code. Reach is per declaration for TypeScript frontends, Go and Python. It is only per module for TypeScript/JavaScript backends (per resolver for GraphQL), JavaScript files and renamed files.
Each push produces two reports: the full PR, and only the commits since the previous push (analyze --since <sha>). Optional Datadog traffic and error data is shown for information and never changes the score.
Evidence
The README reports results on 662 past commits from four repositories. Fast track had 0 problems in 56 commits, and the score's AUC was 0.72 to 0.83. The repositories and the definition of "problem" are not given. Analysis takes about 3 s for a 300-file Python service, and 8 to 10 s for a large TypeScript monorepo or a cold-cache Go service.
How to use it
It needs Node.js 22+ and git, plus go for Go projects or python3 3.11+ for Python ones. Start with a one-line farol.config.json, for example { "language": "python", "surface": "backend" } or { "routes": { "adapter": "next" } }. Then run:
npx @winninjs/farol routes
npx @winninjs/farol analyze --base origin/main --head HEAD --out farol.json
npx @winninjs/farol html --in farol.json --out farol.htmlCoverage, lint and audit reports from existing CI become diff coverage and findings. Python uses coverage.py JSON, ruff with S rules, and pip-audit. Go uses -coverprofile, golangci-lint with gosec, and govulncheck. TypeScript uses Istanbul, ESLint and npm/pnpm audit. A report that is not supplied shows as "not verified", never as passing. Go analysis needs go mod download for both base and head first.
The Action is winnin/farol/action, and the README advises pinning it to a commit SHA. Only the comment command is GitHub-specific. analyze, html and report work on any platform. Draft PRs are skipped, and fork PRs get no comment.
flowchart LR PR[PR code] --> T[tests job: no secrets, read-only token] T -->|coverage, lint, audit reports| F[farol job: never runs PR code] D[farol-usage: Datadog daily] -.->|optional, score unchanged| F F --> C[PR comment + HTML report]
Limits
- Static reading only. Runtime-built values are invisible: computed route paths,
getattr,importlib, DI containers and registries filled in loops. Unresolved calls are assumed to reach more, never less. - TypeScript frontends. Only React Router, Next.js and file-based TanStack Router are read. Code-based TanStack trees, Remix conventions, Vue and Svelte are not.
- Python. Django REST Framework routers are not read.
- Go. Prefixes from middleware other than chi/gin/echo groups are not applied, and wrapped routers such as Gitea's
web.Routerare not read. - Contract checks cover GraphQL, FastAPI/Pydantic, Go handlers and gRPC. Plain TypeScript REST endpoints and frontends are not covered.
- Thresholds. The authors call the score and verdict thresholds heuristics checked on a few repositories, and recommend running
farol calibrateon your own history.
Technologies named here
- Go
- Python
- farol
- @winninjs/farol
- winnin/farol/action
- TypeScript type checker
- go/types
- Python ast
- React Router
- Next.js
- TanStack Router
- FastAPI
- Flask
- Django
- Celery
- Temporal
- Prefect
- Model Context Protocol
- Vitest
- Jest
- ESLint
- golangci-lint
- govulncheck
- ruff
- pip-audit
- pytest
- Datadog
- GraphQL
- gRPC
- GitHub Actions
- TypeScript
- React
- npm
- Node.js
- Git
- GitHub
- Claude Code
Techniques named here
- Static-analysis risk triage of pull requests
- Risk scoring and verdicts
- Fail-closed analysis
- Incremental review since last push
- Contract change detection
Also named here
- static analysis
Related in coding-agents
