Show HN: Farol – a risk map of a pull request for the person who approves it

Hacker News by 21 min read 60x views
Show HN: Farol – a risk map of a pull request for the person who approves it

Share Post

farol

Know the hazard of merging a drag request, and anywhere it lives.

A free, open-source CLI and GitHub Action. After your AI review, farol tells the individual who approves how risky the merge is and which records transport the risk, so the riskiest changes get the most attention.

Website · Live demo · Get started · Reference

 MIT Node.js 22+ TypeScript · Go · Python Shadow mode


Agents compose the code and agents assessment it. The final call is motionless a person's: endorse the merge or not. That approval is the scarce part, and dispersed evenly complete all drag petition it turns into a skim, which is how a one-line change to a shared hook takes downward forty pages.

farol comes following the AI review, not alternatively of it. Your assessment delegate comments on the lines in the diff. farol gives the individual who approves what the delegate cannot reliably give: the hazard of accepting the change, and anywhere it lives. The goal is not lone a faster review: low-risk drag requests can go fast, and the risky ones get peruse row by line, starting alongside the records that transport the risk. It plant from fixed inspection (the compilers' own perspective of your code, not a model's guess), so the identical drag petition continually gets the identical answer.

The Claude Code squad describes the identical regulation in How the Claude Code squad does code review: assessment effort follows the hazard of the change. As they put it, "The alter sets the tier, not the author."

farol (Portuguese for lighthouse) does the triage before the reviewer opens the diff:

  • What does this alter reach? Every existing page, API operation, RPC method, job and manuscript that runs the changed code, alongside the call sequence to each.
  • How risky is the merge? A verdict (fast track, quick review, careful review) and a 0–100 hazard mark from reach, diff coverage, safety findings, agreement breaks and dependency changes.
  • Where do I start? The riskiest records first, alongside inline diffs, untested lines marked, and a checklist of what to try by hand.

Free and open origin (MIT). Run it as a CLI alongside npx @winninjs/farol, or on all drag petition alongside the GitHub Action winnin/farol/action. No account, no hosted service: it runs on your device or in your CI runner, comments on every PR and never blocks a merge. It never executes your code and never sends it anywhere.

 a careful assessment at 74/100, the two records to commencement with, and three breaking agreement changes

See farol on a genuine drag petition →

The demo holds the reports farol produced in CI for a drag petition on the FastAPI full-stack template: a FastAPI backend and a React frontend alongside TanStack Router. The drag petition adds a position to items and an Insights page, alongside the benevolent of problems a reviewer should catch: a breaking API change, SQL built alongside an f-string, a shared component without tests.

  • Backend report: ⚠ careful review, a breaking agreement alter and a high-severity safety finding.
  • Frontend report: ⚠ careful review, a new leaf and the existing pages a shared component reaches.

The reports are interactive: grade records as reviewed, grade a leaf as rearward a characteristic flag, open the inline diffs, follow the test scheme and copy the assessment document from the Result tab. They are the identical records the drag request's comments nexus to (the drag request); the copies live in docs/demo/.

farol following your AI review

AI assessment agents farol
Reads the diff, and any environment fits in the prompt the entire program at the basis and the head, alongside the compilers (TypeScript category checker, go/types, Python ast)
Tells you what may be incorrect in these lines what these lines reach, how risky the merge is, and anywhere to appearance first
Answers are probabilistic: worded alternatively on all run deterministic: the identical PR gives the identical report, and all assertion comes alongside its call chain
When unsure may audio assured anyway says unidentified and asks for a careful review
Runs a model, normally a hosted service locally or in your CI: it never executes your code and never sends it anywhere

Use both, in that order. The delegate reviews the lines first; afterward farol tells the individual who approves how much attention the merge needs, which records deserve it, which pages and endpoints to test by hand and which agreement changes need their clients checked. The agent's assessment is not adequate exactly whenever a small alter has a ample reach, and that is what farol shows.

  • Review notice goes anywhere the hazard is. A 40-file PR normally has two or three records that can really hurt. farol ranks them, so the reviewer says those closely and skims the remainder alongside a apparent conscience.
  • It follows the code, not the document names. farol builds a declaration chart alongside the genuine compilers (the TypeScript category checker, go/types, Python's ast). A alter to formatPrice lists the dozen pages that render it, not "1 document changed".
  • Contract breaks are caught before clients are. Removed reply fields, new required parameters, changed types in GraphQL, FastAPI, Go handlers and gRPC, alongside the services that call all endpoint.
  • Review the entire PR, or fair what changed since your final look. Every shove gets two reports: the complete drag request, and lone the commits since the former push. After a circular of fixes, the reviewer checks the new commits alternatively of starting over.
  • It uses what your CI already has. Coverage, lint, safety and audit reports rotate into diff safety and findings on the changed lines. Nothing runs twice.
  • Fail closed. When farol cannot peruse part of a change, it says so (unknown) and asks for a careful review. It never calls item harmless since it did not comprehend it.
  • Built for the individual in the loop. Mark records as reviewed, grade pages as rearward a characteristic flag, inspect off findings, and paste the outcome rear in the PR as a document of what was reviewed.

You need Node.js 22+ and git (plus go for Go projects, python3 3.11+ for Python ones). Nothing to install: run farol alongside npx @winninjs/farol.

1. Describe your project in a farol.config.json at its root. One row is adequate to start; choice your stack:

Your project farol.config.json Full recipe
React app alongside React Router { "routes": { "file": "src/routes.tsx" } } (the document that builds the path table) React Router
Next.js app (Pages or App Router) { "routes": { "adapter": "next" } } Next.js
React app alongside TanStack Router { "routes": { "adapter": "tanstack-router", "dir": "src/routes" } } TanStack Router
TypeScript API (GraphQL, REST) { "surface": "backend" } TypeScript API
Go service { "language": "go", "surface": "backend" } Go
Python assistance (FastAPI, Flask, Django, Celery…) { "language": "python", "surface": "backend" } Python

2. Check what farol sees: the pages, endpoints, jobs and scripts it found.

npx @winninjs/farol routes

3. Analyze a branch against its mark 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 alongside 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 study already shows the verdict, the score, the records to commencement with, the pages the alter reaches and what to test by hand. Next steps: add your coverage, lint and audit reports and run farol on all PR alongside the recipe for your stack. Every choice is in the reference and, per stack, in examples/README.md.

Running it often? npm instal --global @winninjs/farol gives you a plain farol command.

Each formula 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 portion one shape:

  1. tests runs your tests, linter and audit and keeps the reports. It runs the PR's code, so it has no secrets and a read-only token. If you already have a test job, keep it and add its upload step.
  2. farol runs following it (pass or fail), says git and the reports, and posts a new comment on all push alongside a nexus that opens the HTML study in the browser, affirmative a second study alongside lone the commits since the former push. It never runs the PR's code.
  3. Drafts are skipped, fork PRs get no comment (their token is read-only), and all farol stage is continue-on-error.

Pin the act to a commit formerly it plant for you: winnin/farol/action@<sha> # chief as of <date>.

examples/README.md goes deeper for all stack: all configuration key, CLI indicator and action input, and how to inspect a setup.

React frontend (React Router)

React Router path tables (object and JSX forms, lazy routes, divided path modules), attain 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 records no test loads are reported as untested. Split path trees go in routes.extraFiles; a fuller config is in examples/farol.config.json.

Pages Router and App Router: all page, API route, layout, _app/_document and middleware, peruse from the file names. Dynamic segments ([id]), catch-alls, path groups and parallel slots are understood.

{ "routes": { "adapter": "next" }, "roles": { "global": ["next.config.*", "package.json"] } }

pages/ and app/ are established in the project or in its src/; set "dir" whenever they live elsewhere. Reports and CI are the identical as for React Router (Jest or Vitest coverage, ESLint, audit). In a monorepo whose tsconfig extends a workspace bundle ("extends": "tsconfig/nextjs.json"), instal dependencies before analyzing, so the bundle is in node_modules.

File-based routing: all component calling createFileRoute('<id>') under the routes directory, alongside the base route, pathless layouts (_app) and genitor routes as layouts of their children.

{ "routes": { "adapter": "tanstack-router", "dir": "src/routes" } }

Reports and CI are the identical as for React Router.

TypeScript API (GraphQL or REST)

GraphQL base operations (schema records 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 identical as for React Router: examples/workflow.yml. If resolvers attain data through an injected environment (ctx.loaders.Brand.byId()), inform farol anywhere the providers live: "backend": { "context": { "loaders": ["src/modules/*/loaders/index.ts"] } }.

chi, gin, echo and net/http routes, gRPC assistance methods and chief binaries, attain per declaration alongside 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 ./... # allow 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 says modules offline, so the workflow runs go mod download for the basis and the caput 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 inspect --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; toggle in your installer).

Add Datadog traffic and errors

Optional, and data only: it never changes the score. With it the study shows how much traffic all affected page or procedure gets, its distinct manufacturing errors (linked to Datadog), the changed records that already throw errors, and the services that call all changed endpoint.

{ "datadog": { "env": "prod", "rum": { "applicationId": "<RUM use id>" }, "apm": { "service": "orders-api", "operations": ["fastapi.request"] } } }
DD_API_KEY=… DD_APP_KEY=… npx @winninjs/farol use --out usage.json # final 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 changeable FAROL_DATADOG=true. It says Datadog formerly 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 path tables (object and JSX forms, lazy routes, divided path modules); Next.js Pages and App Router (pages, API routes, layouts, middleware); TanStack Router file-based routes GraphQL base operations (schema records and gql templates) and REST endpoints (Fastify/Express style) chi, gin, echo and net/http routes; gRPC assistance methods; chief binaries FastAPI and Flask routes (routers, blueprints, prefixes); Django URLs; MCP tools, resources and prompts; Celery tasks; Temporal workflows and activities; Prefect flows; __main__ scripts
Reach precision per declaration (TypeScript category checker) per module, per resolver for GraphQL per declaration (go/types, interface calls resolved to implementations) per declaration (Python ast alongside local category inference)
Contract changes — GraphQL schema (breaking / hazardous / safe) Handler petition and reply types (net/http, chi, gin, echo); gRPC messages FastAPI parameters, body, reply and Pydantic models
Dependencies package.json + npm/pnpm/yarn/bun lockfiles same go.mod + go.sum pyproject.toml (PEP 621, extras, groups, uv, Poetry), requirements.txt + uv.lock/poetry.lock/Pipfile.lock
Diff coverage Istanbul coverage-final.json (Vitest, Jest) same go test -coverprofile coverage.py JSON (pytest --cov)
Lint eslint -f json same golangci-lint JSON ruff JSON
Security built-in rules (XSS sinks, eval, hard-coded secrets, javascript: URLs…) same gosec (through golangci-lint) flake8-bandit (ruff S rules)
Vulnerabilities npm audit --json, pnpm audit --json same govulncheck -json pip-audit --format json
Suppressions eslint-disable, @ts-ignore, @ts-expect-error, as any… same //nolint, //lint:ignore, #nosec # noqa, # type: ignore, # nosec, # pragma: no cover…
Traffic and errors (optional) Datadog RUM leaf views and errors Datadog APM requests, error spans and callers same same

In all language: environment, infrastructure and CI records (.env*, Dockerfiles, compose, CI pipelines, helm, k8s, terraform…) in a division of their own; incremental inspection since any commit (--since); suggested reviewers from git accuse and CODEOWNERS; reports as a GitHub comment, Markdown or a single-file HTML page.

farol produces two things: a PR comment alongside the verdict, the top records and a test checklist, and a single-file HTML report (linked from the comment) anywhere the assessment happens. Here is what all part is for and how to use it.

Verdict and merge hazard score

What it tells you is how much of your period the PR deserves.

Verdict Means What to do
✓ Fast track Only new code, tests or low-risk changes. Skim the diff and approve.
◐ Quick review Existing code changed in a contained way, or a weaker indication (low coverage, lint errors, a config file). Check the Start here files, afterward approve.
⚠ Careful review Wide or silent breakage is possible: a mark of 60+, a breaking agreement change, a high-severity finding, an untested chief file, or code farol could not read. Read the Start here records row by row and test what they reach.

The score (0–100) is the chance that at smallest one risky item breaks something. Expand What adds to it to see each item and its points: the records by attain × untested share, findings, agreement changes, dependencies. The verdict always comes alongside its reasons; peruse them before the diff.

The changed code files, riskiest first. Each row shows how many existing pages or operations run the file, its diff coverage, its findings and its own hazard points; alongside Datadog, additionally its traffic and manufacturing errors. Open the inline diff: added lines no test runs are marked ○, lines alongside findings ⚠.

How to use it: assessment top-down and tick Mark as reviewed on all file. The mark drops as you go, so you always cognize how much hazard is left. Below it arrive the low-risk records to skim, environment/infrastructure/CI files (check their values per environment), and records that need no assessment (docs, lockfiles, tests).

On all leaf or procedure row, and in a panel at the top: grade an admission item that is entirely rearward a flag. Files that only flagged admission points use depart the review, tagged behind a flag fairly than reviewed. Use it for work-in-progress features shipped dark, so the remaining mark reflects what users volition really hit.

Every new and affected admission point, as a catalog or a tree by module. Opening one draws how the alter reaches it, file by file. With Datadog all one shows its traffic portion and its distinct errors, busiest first. Use it to judge blast radius: a alter reaching 3 rarely used admin pages is not the identical as one reaching the checkout.

Each endpoint whose petition or reply form changed, as breaking (clients built for the basis can fail), dangerous (they may misbehave) or safe (compatible additions), alongside the exact site and the calling services from Datadog. Use it to verify all breaking alter is intended and its clients are updated, afterward tick checked alongside clients.

 GET /brands no longer returns response[].name

Security, Dependencies, Coverage

The evidence rearward the score: safety findings on the changed lines (with a checked box each), added or bumped dependencies alongside known vulnerabilities, and diff safety per file. A study that was not stated shows as not verified, never as passing.

The new and affected pages or operations to try by hand, new ones first, afterward by the hazard of the document that reaches them. The PR comment has it as a project list. Use it whenever you run the branch locally or on a preview environment. It follows your review: formerly all changed document rearward an item is marked as reviewed, the item is marked too (✓ records reviewed), or 🚩 rearward a flag. Its own checkbox, for the manual test, stays yours to tick.

Who should review: the group who wrote the code this PR changes (git blame, valued by all file's risk), plus CODEOWNERS. The PR's own authors and bots are remaining out, and idle group are labeled. Use it to petition the person who knows the riskiest file, not fair whoever is free.

A Markdown document of the review: the first verdict and score, the mark remaining now, the flagged admission points, the reviewed files, and the checked findings and agreement changes. Copy as Markdown and paste it in the PR as your review comment, so the next individual knows what was looked at.

After a shove to an open PR, the comment carries a second study alongside lone the commits since the former push (analyze --since <sha>; following a force-push, from its merge-base). Its verdict, mark and Start here shield just those changes. Use it for the second circular of a review: inspect that the fixes do what was asked and nothing else, afterward go rear to the complete study lone if the new commits attain location new.

Kept in Details and a crumbled obstacle of the comment, so reviewers form their own opinion first: isolated (only adds), touches-existing (existing code changed), earth (a earth file, manifest or critical dependency) and unidentified (part of the alter could not be read: never safe). Every logic code is in docs/reasons.md.

  1. Diff the merge-base and the head, detecting renames and ignoring whitespace-only and trailing-comma lines.
  2. Read the two trees without operating anything: the TypeScript compiler API, go catalog + go/types (offline), Python ast. From them, the admission points and a chart of which declaration uses which.
  3. Compare declarations by a hash of their code, so comments and formatting never count. New declarations and admission points are additive.
  4. Reach: from all changed declaration to all admission item that runs it, keeping the shortest chain.
  5. Classify, measure and plan: class, score, verdict, records to commencement with, test checklist.

On 662 former commits of four repositories, fast track had 0 problems in 56 commits and the score's AUC was 0.72–0.83; run farol calibrate on your own history. The particulars per tongue are in docs/how-it-works.md, and the reasons rearward all choice in docs/DECISIONS.md.

  • analyze never executes code of the analyzed repository and never uses the network. The Go and Python helpers use lone their norm libraries and lone parse.
  • Only comment (GitHub API) and use (Datadog API) conversation to the network. Error messages are not copied from Datadog, lone error types.
  • In CI the job that runs your code has no secrets and no compose token; the job that comments never runs your code.
  • Shadow mode: farol informs, it never approves, requests changes or fails a check.
  • Static reading: values built at runtime are invisible (computed path paths, getattr, importlib, DI containers, registries filled in loops). When an admission item cannot be read, the PR is unknown; whenever a call cannot be resolved, farol assumes it reaches more, never less.
  • TypeScript frontends: React Router, Next.js and TanStack Router (file-based) only: no code-based TanStack path trees, Remix/React Router document conventions, Vue or Svelte. JavaScript files, renamed records and TypeScript backends use module-level reach.
  • Go: path prefixes from middleware stacks another than chi/gin/echo groups are not applied, and project types that cover a router alongside their own registration API (Gitea's web.Router) are not read. Python: Django REST Framework routers are not read.
  • The mark and verdict thresholds are heuristics checked on a few repositories. Calibrate them on yours.

We already have an AI reviewer. Why add farol? They answer distinct questions. The delegate comments on the lines in the diff; following it, farol tells the individual who approves how risky the merge is and which records transport the risk, from static inspection of the entire program. Run both: farol's comment and study sit next to the agent's, and neither blocks the merge.

Is it free? Yes. farol is open origin under the MIT license, alongside no account, hosted assistance or paid tier: run it with npx @winninjs/farol or the GitHub Action winnin/farol/action.

Will it dilatory downward CI? No. farol runs following your tests and reuses their reports. The inspection takes concerning 3 s for a 300-file Python service, 8–10 s for a ample TypeScript monorepo or a Go assistance alongside a chilly cache, and under a second for the basis whenever its helper output is cached.

Can I use it exterior GitHub? Yes. analyze, html and study activity anywhere; lone comment is GitHub-specific. Post the output of npx @winninjs/farol study alongside your platform's API.

The study says "not verified". That study was not given, or the way is wrong. Paths stated to analyze are relative to the current directory; paths in farol.config.json are related to the project.

A leaf I changed is not listed. Run npx @winninjs/farol routes to see the admission points farol reads, and npx @winninjs/farol effect <file> to see what reaches a file. Unreadable path constructs are listed under diagnostics.

Go: "farol says Go modules offline: run go mod download". The component cache lacks a dependency of the basis or the head. Run go mod download for the two before analyzing.

Monorepo? Point --cwd at the app. Paths remain related to the repository root, and changes in apps the project does not use are listed as exterior it.

Contributions are welcome: path adapters, frameworks, languages, study formats and improved heuristics. See CONTRIBUTING.md for the setup, the architecture and the conventions, and SECURITY.md to study a vulnerability.

MIT © 2026 Winnin

Other Article Hacker News
↑
Close Right Ads
Close Left Ads