
Show HN: Farol – a risk map of a pull request for the person who approves it
A free, open-source CLI and GitHub Action. After your AI review, farol tells the person who approves how risky the
以下正文同步自 Hacker News,版权归原站所有,已转换为易读排版。
A free, open-source CLI and GitHub Action. After your AI review, farol tells the person who approves how risky the merge is and which files carry the risk, so the riskiest changes get the most attention.
Website · Live demo · Get started · Reference
Agents write the code and agents review it. The last call is still a person's: approve the merge or not. That approval is the scarce part, and spread evenly over every pull request it turns into a skim, which is how a one-line change to a shared hook takes down forty pages.
farol comes after the AI review, not instead of it. Your review agent comments on the lines in the diff. farol gives the person who approves what the agent cannot reliably give: the risk of accepting the change, and where it lives. The goal is not only a faster review: low-risk pull requests can go fast, and the risky ones get read line by line, starting with the files that carry the risk. It works from static analysis (the compilers' own view of your code, not a model's guess), so the same pull request always gets the same answer.
The Claude Code team describes the same principle in How the Claude Code team does code review: review effort follows the risk of the change. As they put it, "The change sets the tier, not the author."
farol (Portuguese for lighthouse) does the triage before the reviewer opens the diff:
Free and open source (MIT). Run it as a CLI with npx @winninjs/farol, or on every pull request with the GitHub Action winnin/farol/action. No account, no hosted service: it runs on your machine or in your CI runner, comments on every PR and never blocks a merge. It never executes your code and never sends it anywhere.
The demo holds the reports farol produced in CI for a pull request on the FastAPI full-stack template: a FastAPI backend and a React frontend with TanStack Router. The pull request adds a status to items and an Insights page, with the kind of problems a reviewer should catch: a breaking API change, SQL built with an f-string, a shared component without tests.
The reports are interactive: mark files as reviewed, mark a page as behind a feature flag, open the inline diffs, follow the test plan and copy the review record from the Result tab. They are the same files the pull request's comments link to (the pull request); the copies live in docs/demo/.
AI review agents farol
Reads the diff, and whatever context fits in the prompt the whole program at the base and the head, with the compilers (TypeScript type checker, go/types, Python ast)
Tells you what may be wrong in these lines what these lines reach, how risky the merge is, and where to look first
Answers are probabilistic: worded differently on every run deterministic: the same PR gives the same report, and every claim comes with its call chain
When unsure may sound confident anyway says unknown and asks for a careful review
Runs a model, usually a hosted service locally or in your CI: it never executes your code and never sends it anywhere
Use both, in that order. The agent reviews the lines first; then farol tells the person who approves how much attention the merge needs, which files deserve it, which pages and endpoints to test by hand and which contract changes need their clients checked. The agent's review is not enough exactly when a small change has a large reach, and that is what farol shows.
Review attention goes where the risk is. A 40-file PR usually has two or three files that can actually hurt. farol ranks them, so the reviewer reads those closely and skims the rest with a clear conscience.
It follows the code, not the file names. farol builds a declaration graph with the real compilers (the TypeScript type checker, go/types, Python's ast). A change to formatPrice lists the twelve pages that render it, not "1 file changed".
Contract breaks are caught before clients are. Removed response fields, new required parameters, changed types in GraphQL, FastAPI, Go handlers and gRPC, with the services that call each endpoint.
Review the whole PR, or just what changed since your last look. Every push gets two reports: the full pull request, and only the commits since the previous push. After a round of fixes, the reviewer checks the new commits instead of starting over.
It uses what your CI already has. Coverage, lint, security and audit reports become diff coverage and findings on the changed lines. Nothing runs twice.
Fail closed. When farol cannot read part of a change, it says so (unknown) and asks for a careful review. It never calls something safe because it did not understand it.
Built for the human in the loop. Mark files as reviewed, mark pages as behind a feature flag, check off findings, and paste the result back in the PR as a record of what was reviewed.
Contents
Live demo
farol after your AI review
Get started
Recipes: React Router · Next.js · TanStack Router · TypeScript API · Go · Python · Datadog
What is supported
Reading the review
How it works · Security · Limitations · FAQ
Reference (every command, option and configuration key)
Get started
You need Node.js 22+ and git (plus go for Go projects, python3 3.11+ for Python ones). Nothing to install: run farol with npx @winninjs/farol.
-
Describe your project in a farol.config.json at its root. One line is enough to start; pick your stack:
-
Check what farol sees: the pages, endpoints, jobs and scripts it found.
npx @winninjs/farol routes
- Analyze a branch against its target and open the report.
npx @winninjs/farol analyze --base origin/main --head HEAD --out farol.json npx @winninjs/farol html --in farol.json --out farol.html && open farol.html # xdg-open on Linux
Example: a React app with React Router
cd path/to/your/react-app echo '{ "routes": { "file": "src/routes.tsx" } }' > farol.config.json
npx @winninjs/farol routes # lists your pages: "/", "/products", "/settings"… git checkout my-feature-branch npx @winninjs/farol analyze --base origin/main --head HEAD --out farol.json npx @winninjs/farol html --in farol.json --out farol.html && open farol.html
The report already shows the verdict, the score, the files to start with, the pages the change reaches and what to test by hand. Next steps: add your coverage, lint and audit reports and run farol on every PR with the recipe for your stack. Every option is in the reference and, per stack, in examples/README.md.
Running it often? npm install --global @winninjs/farol gives you a plain farol command.
Each recipe has three parts: the farol.config.json to commit, the reports to produce, and the GitHub Actions workflow. The workflows in examples/ are complete and share one shape:
Pin the action to a commit once it works for you: winnin/farol/action@<sha> # main as of <date>.
examples/README.md goes deeper for each stack: every configuration key, CLI parameter and action input, and how to check a setup.
React Router route tables (object and JSX forms, lazy routes, split route modules), reach per declaration.
{ "routes": { "file": "src/routes.tsx" }, "roles": { "global": ["src/main.tsx", "src/App.tsx", "src/styles/global.css", "package.json", "vite.config.ts"] } }
npx vitest run --coverage.enabled --coverage.reporter=json --coverage.reportsDirectory=reports/coverage npx eslint -f json -o reports/eslint.json <changed files> npm audit --json > reports/audit.json # or pnpm audit --json
npx @winninjs/farol analyze --base origin/main --head HEAD
--coverage reports/coverage/coverage-final.json --eslint reports/eslint.json --audit reports/audit.json --out farol.json
CI: copy examples/workflow.yml to .github/workflows/farol.yml.
Set coverage.include: ['src/**/*.{ts,tsx}'] in the Vitest config so files no test loads are reported as untested. Split route trees go in routes.extraFiles; a fuller config is in examples/farol.config.json.
Pages Router and App Router: every page, API route, layout, _app/_document and middleware, read from the file names. Dynamic segments ([id]), catch-alls, route groups and parallel slots are understood.
{ "routes": { "adapter": "next" }, "roles": { "global": ["next.config.*", "package.json"] } }
pages/ and app/ are found in the project or in its src/; set "dir" when they live elsewhere. Reports and CI are the same as for React Router (Jest or Vitest coverage, ESLint, audit). In a monorepo whose tsconfig extends a workspace package ("extends": "tsconfig/nextjs.json"), install dependencies before analyzing, so the package is in node_modules.
File-based routing: every module calling createFileRoute('<id>') under the routes directory, with the root route, pathless layouts (_app) and parent routes as layouts of their children.
{ "routes": { "adapter": "tanstack-router", "dir": "src/routes" } }
Reports and CI are the same as for React Router.
GraphQL root operations (schema files and gql templates, per resolver) and Fastify/Express-style REST endpoints.
{ "surface": "backend", "backend": { "graphql": true, "rest": true }, "roles": { "global": ["package.json", "src/app.ts"] } }
Reports and CI are the same as for React Router: examples/workflow.yml. If resolvers reach data through an injected context (ctx.loaders.Brand.byId()), tell farol where the providers live: "backend": { "context": { "loaders": ["src/modules/*/loaders/index.ts"] } }.
chi, gin, echo and net/http routes, gRPC service methods and main binaries, reach per declaration with interface calls resolved to their implementations.
{ "language": "go", "surface": "backend" }
go test -coverpkg=./... -coverprofile=reports/cover.out ./... golangci-lint run --output.json.path=reports/golangci.json --issues-exit-code=0 ./... # enable gosec govulncheck -json ./... > reports/govulncheck.json
npx @winninjs/farol analyze --base origin/main --head HEAD
--coverage reports/cover.out --eslint reports/golangci.json --audit reports/govulncheck.json --out farol.json
CI: copy examples/workflow-go.yml. farol reads modules offline, so the workflow runs go mod download for the base and the head first.
FastAPI and Flask routes (routers, blueprints, prefixes), Django URLs, MCP tools/resources/prompts, Celery tasks, Temporal workflows and activities, Prefect flows and main scripts. farol parses your code; it never imports it.
{ "language": "python", "surface": "backend", "python": { "roots": ["src", "."] } }
pytest --cov=src --cov-report=json:reports/coverage.json ruff check --output-format json --extend-select S --exit-zero . > reports/ruff.json # S = flake8-bandit pip-audit --format json -o reports/pip-audit.json
npx @winninjs/farol analyze --base origin/main --head HEAD
--coverage reports/coverage.json --eslint reports/ruff.json --audit reports/pip-audit.json --out farol.json
CI: copy examples/workflow-python.yml (uses uv; swap in your installer).
Optional, and information only: it never changes the score. With it the report shows how much traffic each affected page or operation gets, its distinct production errors (linked to Datadog), the changed files that already throw errors, and the services that call each changed endpoint.
{ "datadog": { "env": "prod", "rum": { "applicationId": "<RUM application id>" }, "apm": { "service": "orders-api", "operations": ["fastapi.request"] } } }
DD_API_KEY=… DD_APP_KEY=… npx @winninjs/farol usage --out usage.json # last 14 days npx @winninjs/farol analyze … --usage usage.json
CI: the example workflows have a farol-usage job: set the DD_API_KEY and DD_APP_KEY secrets and the repository variable FAROL_DATADOG=true. It reads Datadog once a day and never sees the PR's code. Keys, scopes and matching rules are in the reference.
TypeScript / React frontends TypeScript / JavaScript backends Go Python
Entry points React Router route tables (object and JSX forms, lazy routes, split route modules); Next.js Pages and App Router (pages, API routes, layouts, middleware); TanStack Router file-based routes GraphQL root operations (schema files and gql templates) and REST endpoints (Fastify/Exp
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。