Farol 开源发布:为 PR 审批人生成合并风险地图
Show HN: Farol – A risk map of a pull request for the person who approves it
Farol 发布开源 CLI 和 GitHub Action,在 AI 审查之后为 PR 审批人给出合并风险评分(0–100)、fast track/quick review/careful review 三档结论,以及风险最高的文件清单。
了解合并拉取请求的风险,以及风险所在。
一个免费、开源的 CLI 和 GitHub Action。在你的 AI 审查之后,farol 会告诉批准合并的人这次合并有多大的风险,以及哪些文件承载了风险,从而让风险最高的改动获得最多的关注。
智能体编写代码,智能体审查代码。最终决定仍然由人做出:批准合并或不批准。这个批准环节是稀缺的,而如果它被均匀地分摊到每一个拉取请求上,就会变成走马观花,于是一行对共享钩子的改动就能让四十个页面崩溃。
farol 出现在 AI 审查之后,而不是取代它。你的审查智能体会对 diff 中的行发表评论。farol 为批准者提供智能体无法可靠给出的信息:接受这次改动的风险,以及风险所在。目标不仅是更快的审查:低风险的拉取请求可以快速通过,而高风险的则要逐行阅读,从承载风险的文件开始。它基于静态分析(编译器对你代码的视角,而非模型的猜测)工作,因此同一个拉取请求总是得到相同的答案。
Claude Code 团队在 How the Claude Code team does code review 中描述了同样的原则:审查的投入程度跟随改动的风险。正如他们所说,“是改动决定层级,而不是作者。”
farol(葡萄牙语意为灯塔)在审查者打开 diff 之前完成分诊:
- 这次改动会影响什么?运行被改动代码的每一个现有页面、API 操作、RPC 方法、任务和脚本,以及到每一个的调用链。
- 这次合并有多大的风险?一个结论(快速通道、快速审查、仔细审查)和一个 0–100 的风险评分,评分依据包括影响范围、diff 覆盖率、安全问题、契约破坏和依赖变更。
- 我从哪里开始?风险最高的文件优先,附带内联 diff、标记未测试的行,以及一份手动尝试的检查清单。
免费且开源(MIT)。用 npx @winninjs/farol 作为 CLI 运行,或通过 GitHub
Action winnin/farol/action 在每个拉取请求上运行。无需账户,无需托管服务:它运行在你的机器上或 CI runner 中,对每个 PR 发表评论,且从不阻止合并。它从不执行你的代码,也从不将代码发送到任何地方。
在线演示
演示中保存了 farol 在 CI 中为一个拉取请求生成的报告,该拉取请求位于 FastAPI 全栈模板上:一个 FastAPI 后端和一个使用 TanStack Router 的 React 前端。该拉取请求为条目添加了状态,并新增了一个 Insights 页面,其中包含审查者应当发现的那类问题:破坏性的 API 变更、用 f-string 构建的 SQL、没有测试的共享组件。
这些报告是交互式的:将文件标记为已审查、将页面标记为位于功能开关之后、打开内联 diff、按照测试计划操作,并从 Result 标签页复制审查记录。它们与拉取请求评论所链接的是同一批文件(该拉取请求);副本位于 docs/demo/。
| AI 审查智能体 | farol | |
|---|---|---|
| 读取 | diff,以及提示中能容纳的任何上下文 | 基线和头部的整个程序,连同编译器(TypeScript 类型检查器、go/types、Python ast) |
| 告诉你 | 这些行里可能有什么问题 | 这些行触及了什么、这次合并有多危险,以及应该先看哪里 |
| 答案是 | 概率性的:每次运行措辞都不同 | 确定性的:同一个 PR 给出同一份报告,而且每条结论都附带其调用链 |
| 不确定时 | 可能听起来仍然很自信 | 会说明 unknown 并请求仔细审查 |
| 运行方式 | 一个模型,通常是托管服务 | 在本地或你的 CI 中:它从不执行你的代码,也从不把代码发送到任何地方 |
按这个顺序两者都用。agent 先审查这些行;然后 farol 告诉批准者这次合并需要多少关注、哪些文件值得关注、哪些页面和端点需要手动测试,以及哪些契约变更需要检查其客户端。当一个小改动影响范围很大时,agent 的审查恰恰不够,而这正是 farol 所展示的。
为什么选择 farol
- 审查注意力流向风险所在之处。 一个 40 个文件的 PR 通常只有两三个文件真正可能造成伤害。farol 对它们进行排序,因此审查者会仔细阅读这些文件,并心安理得地略读其余部分。
- 它跟随代码,而不是文件名。 farol 使用真正的编译器(TypeScript 类型检查器、
go/types、Python 的ast)构建声明图。对formatPrice的更改会列出渲染它的十二个页面,而不是“1 个文件已更改”。 - 契约破坏在客户端受影响之前就被捕获。 被移除的响应字段、新增的必需参数、GraphQL、FastAPI、Go 处理器和 gRPC 中更改的类型,以及调用每个端点的服务。
- 审查整个 PR,或只审查自上次查看以来更改的内容。 每次推送都会得到两份报告:完整的拉取请求,以及仅自上次推送以来的提交。经过一轮修复后,审查者检查新的提交,而不是从头开始。
- 它利用你的 CI 已有的内容。 覆盖率、lint、安全和审计报告变成更改行上的 diff 覆盖率和发现项。没有任何东西运行两次。
- 失败关闭。 当 farol 无法读取更改的某部分时,它会说明这一点(
unknown)并请求仔细审查。它绝不会因为不理解某事物就称其安全。 - 为循环中的人而构建。 将文件标记为已审查,将页面标记为位于功能标志之后,勾选发现项,并将结果粘贴回 PR 中,作为审查内容的记录。
目录
- 在线演示
- AI 审查之后的 farol
- 开始使用
- 配方:React Router · Next.js · TanStack Router · TypeScript API · Go · Python · Datadog
- 支持哪些内容
- 阅读审查报告
- 工作原理 · 安全 · 限制 · 常见问题
- 参考(每个命令、选项和配置键)
开始使用
你需要 Node.js 22+ 和 git(Go 项目还需要 go,Python 项目需要 python3 3.11+)。无需安装:用 npx @winninjs/farol 运行 farol。
1. 描述你的项目,在根目录的 farol.config.json 中。一行就足以开始;选择你的技术栈:
| 你的项目 | farol.config.json |
完整配方 |
|---|---|---|
| 使用 React Router 的 React 应用 | { "routes": { "file": "src/routes.tsx" } }(构建路由表的文件) |
React Router |
| Next.js 应用(Pages Router 或 App Router) | { "routes": { "adapter": "next" } } |
Next.js |
| 使用 TanStack Router 的 React 应用 | { "routes": { "adapter": "tanstack-router", "dir": "src/routes" } } |
TanStack Router |
| TypeScript API(GraphQL、REST) | { "surface": "backend" } |
TypeScript API |
| Go 服务 | { "language": "go", "surface": "backend" } |
Go |
| Python 服务(FastAPI、Flask、Django、Celery……) | { "language": "python", "surface": "backend" } |
Python |
2. 查看 farol 发现了什么:它找到的页面、端点、任务和脚本。
npx @winninjs/farol routes
3. 分析一个分支与其目标分支的差异,并打开报告。
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
示例:使用 React Router 的 React 应用
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
报告已经显示了结论、评分、应从哪些文件入手、变更影响到的页面,以及需要手动测试的内容。后续步骤:添加你的覆盖率、lint 和审计报告,并在每个 PR 上运行 farol,使用适合你技术栈的 recipe。所有选项都在参考文档中,各技术栈的选项则在examples/README.md中。
经常运行?
npm install --global @winninjs/farol为你提供一个简单的farol命令。
Recipes
每个 recipe 包含三部分:要提交的 farol.config.json、要生成的报告,以及 GitHub Actions 工作流。examples/ 中的工作流是完整的,并且共享同一种结构:
tests运行你的测试、linter 和审计,并保留报告。它运行 PR 的代码,因此没有密钥,且使用只读令牌。如果你已经有测试任务,保留它并添加其上传步骤。farol在其之后运行(无论通过还是失败),读取 git 和报告,并在每次推送时发布一条新评论,其中包含一个在浏览器中打开 HTML 报告的链接,以及一份仅包含自上次推送以来提交的第二份报告。它从不运行 PR 的代码。- 草稿会被跳过,fork PR 不会收到评论(其令牌为只读),并且每个 farol 步骤都是
continue-on-error。
在你确认可用后,将 action 固定到某个提交:winnin/farol/action@<sha> # main as of <date>。
examples/README.md 针对每个技术栈深入介绍:每个配置键、CLI 参数和 action 输入,以及如何检查配置。
React 前端(React Router)
React Router 路由表(对象和 JSX 形式、懒加载路由、拆分路由模块),按声明计算可达性。
{
"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:将 examples/workflow.yml 复制到 .github/workflows/farol.yml。
在 Vitest 配置中设置 coverage.include: ['src/**/*.{ts,tsx}'],以便没有测试加载的文件被报告为未测试。拆分的路由树放在 routes.extraFiles 中;更完整的配置在 examples/farol.config.json 中。
Next.js
Pages Router 和 App Router:每个页面、API 路由、布局、_app/_document 和中间件,均从文件名读取。动态段([id])、全捕获、路由组和并行槽都能被理解。
{
"routes": { "adapter": "next" },
"roles": { "global": ["next.config.*", "package.json"] }
}pages/ 和 app/ 会在项目中或其 src/ 中找到;当它们位于别处时设置 "dir"。报告和 CI 与 React Router 相同(Jest 或 Vitest 覆盖率、ESLint、审计)。在 tsconfig 继承工作区包("extends": "tsconfig/nextjs.json")的 monorepo 中,请在分析前安装依赖,以便该包位于 node_modules 中。
TanStack Router
基于文件的路由:routes 目录下每个调用 createFileRoute('<id>') 的模块,根路由、无路径布局(_app)和父路由作为其子路由的布局。
{ "routes": { "adapter": "tanstack-router", "dir": "src/routes" } }报告和 CI 与 React Router 相同。
TypeScript API(GraphQL 或 REST)
GraphQL 根操作(schema 文件和 gql 模板,按 resolver)以及 Fastify/Express 风格的 REST 端点。
{
"surface": "backend",
"backend": { "graphql": true, "rest": true },
"roles": { "global": ["package.json", "src/app.ts"] }
}报告和 CI 与 React Router 相同:examples/workflow.yml。如果 resolver 通过注入的上下文(ctx.loaders.Brand.byId())访问数据,请告诉 farol 这些 provider 所在的位置:"backend": { "context": { "loaders": ["src/modules/*/loaders/index.ts"] } }。
Go 服务
chi、gin、echo 和 net/http 路由,gRPC 服务方法和 main 二进制文件,按声明计算可达性,并将接口调用解析到其实现。
{ "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:复制 examples/workflow-go.yml。farol 离线读取模块,因此工作流会先为 base 和 head 运行
go mod download。
Python 服务
FastAPI 和 Flask 路由(routers、blueprints、prefixes)、Django URL、MCP tools/resources/prompts、Celery 任务、
Temporal workflows 和 activities、Prefect flows 以及 __main__ 脚本。farol 解析你的代码;它从不导入代码。
{ "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:复制 examples/workflow-python.yml(使用 uv;替换为你的安装器)。
添加 Datadog 流量和错误
可选,且仅供参考:它从不改变评分。启用后,报告会显示每个受影响的 页面或操作获得多少流量、其不同的生产错误(链接到 Datadog)、已经抛出 错误的已更改文件,以及调用每个已更改端点的服务。
{
"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.jsonCI:示例工作流有一个 farol-usage job:设置 DD_API_KEY 和 DD_APP_KEY secrets 以及
仓库变量 FAROL_DATADOG=true。它每天读取一次 Datadog,且从不查看 PR 的代码。密钥、作用域和
匹配规则见参考。
支持的内容
| TypeScript / React 前端 | TypeScript / JavaScript 后端 | Go | Python | |
|---|---|---|---|---|
| 入口点 | React Router 路由表(对象和 JSX 形式、懒加载路由、拆分路由模块);Next.js Pages 和 App Router(pages、API routes、layouts、middleware);TanStack Router 基于文件的路由 | GraphQL 根操作(schema 文件和 gql 模板)以及 REST 端点(Fastify/Express 风格) |
chi、gin、echo 和 net/http 路由;gRPC 服务方法;main 二进制文件 |
FastAPI 和 Flask 路由(routers、blueprints、prefixes);Django URL;MCP tools、resources 和 prompts;Celery 任务;Temporal workflows 和 activities;Prefect flows;__main__ 脚本 |
| 可达精度 | 按声明(TypeScript 类型检查器) | 按模块,GraphQL 按 resolver | 按声明(go/types,接口调用解析到实现) |
按声明(Python ast 带本地类型推断) |
| 契约变更 | — | GraphQL schema(破坏性 / 危险 / 安全) | Handler 请求和响应类型(net/http、chi、gin、echo);gRPC 消息 | FastAPI 参数、body、response 和 Pydantic 模型 |
| 依赖 | package.json + npm/pnpm/yarn/bun lockfiles |
同上 | go.mod + go.sum |
pyproject.toml(PEP 621、extras、groups、uv、Poetry)、requirements.txt + uv.lock/poetry.lock/Pipfile.lock |
| Diff 覆盖率 | Istanbul coverage-final.json(Vitest、Jest) |
同上 | go test -coverprofile |
coverage.py JSON(pytest --cov) |
| Lint | eslint -f json |
同上 | golangci-lint JSON | ruff JSON |
| 安全 | 内置规则(XSS sinks、eval、硬编码密钥、javascript: URL……) |
同上 | gosec(通过 golangci-lint) | flake8-bandit(ruff S 规则) |
| 漏洞 | npm audit --json、pnpm audit --json |
同上 | govulncheck -json |
pip-audit --format json |
| 抑制 | eslint-disable、@ts-ignore、@ts-expect-error、as any…… |
同上 | //nolint、//lint:ignore、#nosec |
# noqa、# type: ignore、# nosec、# pragma: no cover…… |
| 流量和错误(可选) | Datadog RUM 页面浏览量和错误 | Datadog APM 请求、错误 span 和调用方 | 同上 | 同上 |
在所有语言中:环境、基础设施和 CI 文件(.env*、Dockerfiles、compose、CI pipelines、helm、k8s、
terraform……)单独放在一个部分;自任意提交起的增量分析(--since);根据
git blame 和 CODEOWNERS 建议审查者;报告以 GitHub 评论、Markdown 或单文件 HTML 页面形式呈现。
阅读审查结果
farol 生成两样东西:一份包含结论、主要文件和测试清单的PR 评论,以及 一份单文件 HTML 报告(从评论链接过去),审查就在那里进行。下面说明每个部分的用途以及 如何使用它。
结论和合并风险评分
它告诉你的是这个 PR 值得你花多少时间。
| 结论 | 含义 | 该做什么 |
|---|---|---|
| ✓ 快速通道 | 仅限新代码、测试或低风险改动。 | 浏览 diff 并批准。 |
| ◐ 快速审查 | 现有代码以受控方式改动,或信号较弱(覆盖率低、lint 错误、配置文件)。 | 检查 Start here 中的文件,然后批准。 |
| ⚠ 仔细审查 | 可能出现大范围或静默破坏:评分 60+、破坏性契约变更、高严重度发现、未测试的核心文件,或代码 farol 无法读取。 | 逐行阅读 Start here 中的文件,并测试它们触及的内容。 |
评分(0–100)是至少一个风险项造成破坏的概率。展开 What adds to it 可查看 每一项及其分值:文件按影响范围 × 未测试比例、发现、契约变更、依赖。结论 总是附带原因;请先阅读原因再看 diff。
从这里开始
改动过的代码文件,风险最高的在前。每一行显示有多少现有页面或操作会运行该文件、其 diff 覆盖率、发现及其自身风险分值;接入 Datadog 时,还会显示其流量和生产错误。打开内联 diff:没有测试运行的添加行标记为 ○,有发现的行标记为 ⚠。
使用方法:自上而下审查,并在每个文件上勾选 Mark as reviewed。评分会随之下降,因此你 始终知道还剩多少风险。其下是需快速浏览的低风险文件、环境/基础设施/CI 文件 (按环境检查其取值),以及无需审查的文件(文档、锁文件、测试)。
位于功能开关之后
在每个页面或操作行上,以及顶部面板中:标记完全位于开关之后的入口点。仅被 仅位于开关后的入口点使用的文件会退出审查,标记为 behind a flag 而非已审查。用于 以暗发布方式上线的进行中功能,使剩余评分反映用户实际会触及的内容。
页面 / 操作
每个新增和受影响的入口点,以列表或按模块的树形展示。打开一个即可查看改动如何逐文件 到达它。接入 Datadog 时,每个入口点会显示其流量占比和不同错误,最繁忙的在前。用它来 判断影响范围:一个改动到达 3 个很少使用的管理页面,与到达结账页面并不相同。
契约(后端)
每个请求或响应结构发生变化的端点,标记为 破坏性(为基线构建的客户端可能失败)、 危险(它们可能行为异常)或 安全(兼容性新增),并附上确切字段和来自 Datadog 的调用服务。 用它确认每个破坏性变更都是有意为之且其客户端已更新,然后勾选 checked with clients。
安全、依赖、覆盖率
评分背后的证据:改动行上的安全发现(每项带一个 checked 复选框)、新增或升级的 存在已知漏洞的依赖,以及每个文件的 diff 覆盖率。未提供的报告显示为 not verified,绝不会显示为通过。
测试计划
需要手动尝试的新增或受影响的页面或操作,新增的排在前面,然后按到达它们的文件风险排序。PR 评论将其列为任务清单。当你在本地或预览环境运行该分支时,使用它。它会跟随你的审查进度:一旦某个条目背后的所有变更文件都被标记为已审查,该条目也会被标记(✓ 文件已审查),或者标记为 🚩 处于标志之后。它自己的复选框,用于手动测试,仍由你勾选。
审查者
谁应该审查:编写此 PR 所更改代码的人(git blame,按每个文件的风险加权),加上 CODEOWNERS。PR 自身的作者和机器人被排除在外,不活跃的人会被标注。使用它来请求了解最高风险文件的人,而不仅仅是任何有空的人。
结果
审查的 Markdown 记录:初始判定和分数、当前分数、标记的入口点、已审查的文件,以及已检查的发现和契约变更。复制为 Markdown 并粘贴到 PR 中作为你的审查评论,以便下一个人知道查看了什么。
自上次推送以来
在推送到一个打开的 PR 后,评论会携带第二份报告,仅包含自上次推送以来的提交(analyze --since <sha>;强制推送后,从其合并基点开始)。其判定、分数和从这里开始仅涵盖这些更改。使用它进行第二轮审查:检查修复是否完成了要求且没有其他改动,然后仅当新提交触及新地方时才回到完整报告。
类别和原因
保留在详情和评论的折叠块中,以便审查者先形成自己的意见:isolated(仅新增)、touches-existing(现有代码更改)、global(全局文件、清单或关键依赖)和 unknown(部分更改无法读取:绝不安全)。每个原因代码都在 docs/reasons.md 中。
工作原理
- 对比合并基点和头部,检测重命名并忽略仅空白和尾随逗号的行。
- 读取两棵树而不运行任何东西:TypeScript 编译器 API、
go list+go/types(离线)、Pythonast。从中获取入口点以及哪个声明使用哪个声明的图。 - 比较声明通过其代码的哈希,因此注释和格式从不计入。新声明和入口点是附加的。
- 可达性:从每个更改的声明到运行它的每个入口点,保留最短链。
- 分类、加权和计划:类别、分数、判定、起始文件、测试清单。
在四个仓库的 662 个历史提交上,快速通道在 56 个提交中有 0 个问题,分数的 AUC 为 0.72–0.83;在你自己的历史上运行 farol calibrate。每种语言的详细信息在 docs/how-it-works.md 中,每个选择背后的原因在 docs/DECISIONS.md 中。
安全和隐私
analyze从不执行被分析仓库的代码,且从不使用网络。Go 和 Python 助手仅使用其标准库并且仅进行解析。- 只有
comment(GitHub API)和usage(Datadog API)与网络通信。错误消息不从 Datadog 复制,仅错误类型。 - 在 CI 中,运行你的代码的作业没有机密和写令牌;发表评论的作业从不运行你的代码。
- 影子模式:farol 提供信息,它从不批准、请求更改或使检查失败。
限制
- 静态读取:运行时构建的值不可见(计算出的路由路径、
getattr、importlib、DI 容器、循环中填充的注册表)。当无法读取入口点时,该 PR 为unknown;当无法解析某个调用时,farol 假定它触达的范围更多,而非更少。 - TypeScript 前端:仅支持 React Router、Next.js 和 TanStack Router(基于文件):不支持基于代码的 TanStack 路由树、Remix/React Router 文件约定、Vue 或 Svelte。JavaScript 文件、重命名后的文件和 TypeScript 后端使用模块级可达性。
- Go:来自 chi/gin/echo 分组之外中间件栈的路由前缀不会被应用,且用自身注册 API 包装路由器的项目类型(Gitea 的
web.Router)不会被读取。Python:Django REST Framework 路由器不会被读取。 - 评分和判定阈值是在少数仓库上检验过的启发式规则。请在你的仓库上校准它们。
常见问题
我们已经有 AI 审查器了。为什么还要加 farol? 它们回答的是不同的问题。该 agent 评论 diff 中的代码行;在它之后,farol 通过对整个程序的静态分析,告诉批准合并的人这次合并有多大风险、哪些文件承载了风险。两者都运行:farol 的评论和报告与 agent 的并列显示,且都不会阻止合并。
它是免费的吗? 是的。farol 是 MIT 许可证下的开源项目,没有账户、托管服务或付费层级:用 npx @winninjs/farol 或 GitHub Action winnin/farol/action 运行它。
它会拖慢 CI 吗? 不会。farol 在你的测试之后运行,并复用它们的报告。对于一个 300 文件的 Python 服务,分析约需 3 秒;对于大型 TypeScript monorepo 或冷缓存的 Go 服务,需 8–10 秒;当辅助输出被缓存时,base 不到一秒。
我可以在 GitHub 之外使用它吗? 可以。analyze、html 和 report 在任何地方都能用;只有 comment 是 GitHub 专用的。用你平台的 API 发布 npx @winninjs/farol report 的输出。
报告显示“未验证”。 该报告未被提供,或路径错误。传给 analyze 的路径相对于当前目录;farol.config.json 中的路径相对于项目。
我更改的页面没有被列出。 运行 npx @winninjs/farol routes 查看 farol 读取的入口点,运行 npx @winninjs/farol impact <file> 查看什么能触达某个文件。无法读取的路由构造会列在诊断信息下。
Go:“farol 离线读取 Go 模块:运行 go mod download”。 模块缓存缺少 base 或 head 的某个依赖。分析前对两者都运行 go mod download。
Monorepo? 将 --cwd 指向该应用。路径保持相对于仓库根目录,项目未使用的应用中的更改会被列为项目之外。
贡献
欢迎贡献:路由适配器、框架、语言、报告格式和更好的启发式规则。有关设置、架构和约定,请参阅 CONTRIBUTING.md;要报告漏洞,请参阅 SECURITY.md。
许可证
MIT © 2026 Winnin
来源:Hacker News · github.com
![A breaking contract change: GET /brands no longer returns response[].name](/api/img-proxy?u=https%3A%2F%2Fgithub.com%2Fwinnin%2Ffarol%2Fraw%2Fmain%2Fdocs%2Fimages%2Freport-contracts.png&mode=full&exp=1793145600&sig=afa5ea6335bf6f2b)