Farol: オープンソースでLLMを使わないプルリクエストのリスクマップ(TypeScript、Go、Python)

Farolは、ブラジルの企業Winninが提供する無料のMITライセンスのCLIおよびGitHub Actionで、プルリクエストを承認する人向けにリスクマップを生成します。AIレビューエージェントの後に実行され、モデルではなく静的解析を使用し、0〜100のマージリスクスコア、判定(fast track、quick review、careful review)、および各ファイルが到達するページ、エンドポイント、ジョブ、スクリプトを含む最もリスクの高い変更ファイルのランキングリストを返します。

farolが行うこと(1段落で)

farolはCLI(npx @winninjs/farol)およびGitHub Action(winnin/farol/action)で、プロジェクトの言語の実際のコンパイラを使用してプルリクエストのベースとヘッドを解析し、マージのリスクとリスクがどこにあるかを報告します。解析対象のリポジトリのコードを実行することはなく、コードをどこにも送信せず、マージをブロックすることもありません。AIレビューの後に配置されるように設計されており、置き換えるものではありません。レビューエージェントが差分の行にコメントする一方、farolは人間の承認者にそれらの行が何に到達するかを伝えます。

中心的な主張:小さな変更が大きな影響を持つ場合、エージェントレビューだけでは不十分

このプロジェクトの主張するテーゼは、承認が希少なリソースであり、すべてのプルリクエストに均等に注意を分散させるとレビューが流し読みになるというものです。著者がHacker Newsで述べた言葉では、失敗モードは具体的です:「38のファイルが孤立している40ファイルのPRは、23のエンドポイントで使用されるモデルを変更する3ファイルのPRとは大きく異なります」。

したがって、farolはレビューアが差分を開く前にトリアージを行い、3つの質問に答えます:

  • この変更は何に到達するか?変更されたコードを実行するすべての既存ページ、API操作、RPCメソッド、ジョブ、スクリプトと、それぞれへの呼び出しチェーン。
  • マージのリスクはどの程度か?リーチ、差分カバレッジ、セキュリティ検出、契約変更、依存関係の変更から計算された判定と0〜100のスコア。
  • どこから始めるか?最もリスクの高いファイルから順に、インラインディフ、未テスト行のマーク、手動で試すべきチェックリスト付き。

READMEは、Claude Codeチームが同じ原則を説明した言葉を引用しています:「変更がティアを決定し、著者が決定するのではない」。

判定と0〜100のマージリスクスコア

farolはすべてのプルリクエストに3つの判定のいずれかをラベル付けし、各判定は特定のレビューアのアクションに対応します:

  • Fast track:新しいコード、テスト、または低リスクの変更のみ。差分をざっと見て承認。
  • Quick review:既存コードが限定的に変更された場合、または低カバレッジ、lintエラー、設定ファイルなどの弱いシグナルがある場合。「Start here」ファイルを確認してから承認。
  • Careful review:広範囲または静かな破壊の可能性がある場合。スコア60以上、破壊的な契約変更、高重大度の検出、未テストの中心ファイル、またはfarolが読み取れないコードによってトリガーされます。

スコア(0〜100)は、少なくとも1つのリスクアイテムが何かを壊す確率として説明されています。寄与するアイテムは「What adds to it」の下に分類されます:リーチ×未テスト割合のファイル、検出、契約変更、依存関係。各判定には理由が付属します。

モデルではなく静的解析:答えが決定的である理由

farolのAIレビューアとの主要なアーキテクチャ上の違いは、その到達可能性分析が言語モデルではなくコンパイラに基づいていることです。READMEの比較表は、AIレビューエージェントが差分とプロンプトに収まるコンテキストを読み、実行ごとに異なる表現で確率的な回答を与えるのに対し、farolはTypeScript型チェッカー、go/types、Pythonのastを使用してベースとヘッドのプログラム全体を読み、各主張に呼び出しチェーンが付属する決定的な回答を与えると述べています。

著者のHacker Newsコメントによると、「分析中はLLMもネットワークもなく、解析対象のリポジトリのコードを実行することもありません。決定的であり、情報提供のみで、マージをブロックすることはありません」。GoおよびPythonのパスでは、ヘルパープログラムは標準ライブラリのみを使用し、解析のみを行います。

分析パイプラインは次のとおりです:

  1. マージベースとヘッドを差分し、リネームを検出し、空白のみおよび末尾カンマの行を無視します。
  2. 何も実行せずに両方のツリーを読み取ります。TypeScriptコンパイラAPI、go listとgo/types(オフライン)、Pythonのastを使用します。
  3. コードのハッシュで宣言を比較するため、コメントやフォーマットはカウントされません。
  4. リーチを計算:各変更された宣言からそれを実行するすべてのエントリポイントまで、最短チェーンを保持します。
  5. 分類、重み付け、計画:クラス、スコア、判定、開始するファイル、テストチェックリスト。

サポートされているスタック

farolはTypeScript/Reactフロントエンド、TypeScriptおよびJavaScriptバックエンド、Go、Pythonをサポートしています。エントリポイントは言語ごとに読み取られます:

  • ReactおよびTypeScriptフロントエンド:React Routerルートテーブル(オブジェクトおよびJSX形式、遅延ルート、分割ルートモジュール)、Next.js PagesおよびApp Router(ページ、APIルート、レイアウト、ミドルウェア)、TanStack Routerファイルベースルート。リーチはTypeScript型チェッカーを介して宣言ごとに計算されます。
  • TypeScript/JavaScriptバックエンド:スキーマファイルおよびgqlテンプレートからのGraphQLルート操作、Fastify/ExpressスタイルのRESTエンドポイント。リーチはモジュールごと、GraphQLの場合はリゾルバごとです。契約変更はGraphQLスキーマから取得され、破壊的、危険、安全に分類されます。
  • Go:chi、gin、echo、net/httpルート、gRPCサービスメソッド、mainバイナリ。インターフェース呼び出しは実装に解決されます。契約変更はハンドラのリクエストおよびレスポンス型、gRPCメッセージから取得されます。
  • Python:FastAPIおよびFlaskルート(ルーター、ブループリント、プレフィックス)、Django URL、MCPツール、リソース、プロンプト、Celeryタスク、Temporalワークフローおよびアクティビティ、Prefectフロー、__main__スクリプト。契約変更はFastAPIパラメータ、ボディ、レスポンス、Pydanticモデルから取得されます。

farolがCIから再利用する入力には、差分カバレッジ用のIstanbul coverage-final.json(Vitest、Jest)、go test -coverprofile、coverage.py JSON、ESLintおよびgolangci-lint JSON、ruff JSON、golangci-lintを介したgosec、ruff Sルールを介したflake8-bandit、既知の脆弱性用のnpm audit、pnpm audit、govulncheck、pip-auditが含まれます。また、eslint-disable、@ts-ignore、//nolint、#noqa、#nosecなどの抑制も読み取ります。オプションのDatadog RUMおよびAPM統合により、影響を受けるページまたは操作ごとのトラフィックシェアと個別の本番エラーが追加されますが、明示的に情報提供のみであり、スコアを変更することはありません。

出力に含まれるもの

farolは2つのアーティファクトを生成します:判定、上位ファイル、テストチェックリストを含むPRコメントと、そのコメントからリンクされる単一ファイルのHTMLレポート(レビューが行われる場所)。レポートのセクションは次のとおりです:

  • 判定とスコア:差分の前に理由がリストされます。
  • Start here:変更されたコードファイルをリスクの高い順にランク付けし、各行にそのファイルを実行する既存のページまたは操作の数、差分カバレッジ、検出、独自のリスクポイントを表示します。テストが実行されない追加行は白丸でマークされ、検出のある行は警告マーカーでマークされます。
  • Behind a feature flag:エントリポイント全体をフラグの背後にマークします。フラグ付きエントリポイントのみに到達するファイルはレビューから除外されます。
  • Pages / Operations:すべての新規および影響を受けるエントリポイントを、モジュールごとのリストまたはツリーとして、変更が到達するパスとともに表示します。
  • Contracts (back ends):リクエストまたはレスポンスの形状が変更された各エンドポイントを、破壊的、危険、安全に分類し、正確なフィールドと呼び出しサービスを表示します。READMEの例は、GET /brandsがresponse[].nameを返さなくなった破壊的な変更です。
  • Security, Dependencies, Coverage:スコアの根拠となる証拠。提供されなかったレポートは「not verified」と表示され、合格として表示されることはありません。
  • Test plan:手動で試すべき新規および影響を受けるページまたは操作を、PRコメント内のタスクリストとして表示します。
  • Reviewers:git blameから各ファイルのリスクで重み付けされた推奨レビューアを、CODEOWNERSに加えて表示します。PRの著者自身とボットは除外されます。
  • Result:レビューのMarkdown記録で、PRにコピーして戻すことができます。
  • Since the last push:オープンPRへのプッシュの場合、前回のプッシュ以降のコミットのみをカバーする2番目のレポート(analyze --since <sha>)。

PRごとのクラスは折りたたみブロックに保持され、レビューアが最初に自分の意見を形成できるようにします:isolated(追加のみ)、touches-existing、global(グローバルファイル、マニフェスト、または重要な依存関係)、unknown。

フェイルクローズ動作と測定された精度

farolが変更の一部を読み取れない場合、安全性を仮定せずにunknownを報告し、慎重なレビューを求めます。呼び出しを解決できない場合、変更がより多くに到達すると仮定します(少なくとも、より少なくはありません)。

READMEは、4つのリポジトリからの662の過去のコミットに対するキャリブレーションを報告しています:fast trackは56コミットで0の問題、スコアのAUCは0.72〜0.83でした。プロジェクトにはfarol calibrateコマンドが含まれており、チームは自分の履歴でスコアを確認できます。READMEは、スコアと判定のしきい値はいくつかのリポジトリでチェックされたヒューリスティックであると明確に述べています。

ランタイムとCIの安全性モデル

farolはテストジョブの後に実行され、そのレポートを再利用するため、何も2回実行されません。READMEはおおよその分析時間を示しています:300ファイルのPythonサービスで約3秒、大規模なTypeScriptモノレポまたはコールドキャッシュのGoサービスで8〜10秒、ヘルパー出力がキャッシュされている場合のベースでは1秒未満です。

推奨されるGitHub Actionsセットアップは2つのジョブを使用します。testsジョブはプルリクエストのコードを実行し、レポートを保持するため、シークレットはなく読み取り専用トークンを持ちます。farolジョブはその後に実行され、成功または失敗に関係なく、gitとレポートを読み取り、プッシュごとに新しいコメントを投稿します。ドラフトはスキップされ、フォークのプルリクエストにはトークンが読み取り専用のためコメントは投稿されず、すべてのfarolステップはcontinue-on-errorです。ネットワークに触れるのはcommentとusageコマンドのみで、エラーメッセージはDatadogからコピーされず、エラータイプのみがコピーされます。

farolにはNode.js 22+とgitが必要で、Goプロジェクトにはgo、Pythonプロジェクトにはpython3 3.11+が必要です。analyze、html、reportはGitHub外で動作します。commentのみがGitHub固有です。

既知の制限

  • 静的読み取りでは、実行時に構築される値(計算されたルートパス、getattr、importlib、依存性注入コンテナ、ループで埋められるレジストリ)は見えません。読み取れないエントリポイントがあると、PRはunknownになります。
  • TypeScriptフロントエンドはReact Router、Next.js、ファイルベースのTanStack Routerに限定されます。コードベースのTanStackルートツリー、Remix、React Routerファイル規約、Vue、Svelteはサポートされていません。JavaScriptファイル、リネームされたファイル、TypeScriptバックエンドはモジュールレベルのリーチを使用します。
  • Goでは、chi/gin/echoグループ以外のミドルウェアスタックからのルートプレフィックスは適用されず、独自の登録APIでルーターをラップするプロジェクトは読み取られません。Pythonでは、Django REST Frameworkルーターは読み取られません。
  • スコアと判定のしきい値はヒューリスティックであり、プロジェクトは自分の履歴でキャリブレーションすることを推奨しています。

Hacker Newsのコメント投稿者が反論した点

Show HNスレッド(39ポイント、25コメント)では、4つの繰り返しの異議が提起されました:レポートのシグナル対ノイズ、UIのセンス、機械割り当てのリスクスコアのアイデア、そしてツールがそもそも必要かどうかというより基本的な質問です。

レポートの密度が最も実質的な批判でした。あるコメント投稿者は次のように書きました:

レポートにノイズが多い。シグナル対ノイズ比をもっと高くしたい。すでにAI出力をたくさん読んでいる。

別の投稿者は同じ点を製品方向の議論として組み立てました:

バックエンドのデモレポートは、コードを読むよりも長くて複雑に見える。私も似たようなものを構築しようとしているが、誰もこれをうまくやっていない。リスクマップ/支援レビューは簡潔で情報密度が高く、注意を導く必要がある。私の現在の哲学は、コードに注釈を付けることであり、コードレビューインターフェース全体をテキストの塊に置き換えることではない。

3人目はライブデモのランキングを直接批判しました:

バックエンドのデモを見たが、最初に提案された行はインポートだった。まさに私がレビューする必要のないものだ。

スコアを信頼することについて、あるコメント投稿者は次のように述べました:

最近ここで「LLMを信頼スコアリングに使うのは悪い考えだ」という投稿があったが、リスクのスコアリングにLLMを使うことをすぐに思い浮かべた。悪い考えのように思える。リスクを評価するためにまだ人間が必要なものがあるなら、それはこれだ。

その異議は特にLLMベースのスコアリングを対象としています。farolの分析パスにはLLMは含まれておらず、著者は出力が決定的であり、コードを読み取れない場合は推測ではなくunknownが返されると述べています。

2つのコメントは支援トリアージの前提に異議を唱えました。1つは率直でした:「または、あなたが承認している変更を読むこともできるでしょう」。もう1つはプレゼンテーションを却下しました:「コンセプトは好きだ。リスクマップは状況によっては役立つかもしれないが、UIデザインは絶対に嫌いだ。AIが人間の思考や配慮なしに生成したように見える」。

デモのプルリクエスト自体も、含まれていたコードのために注目を集めました。あるコメント投稿者は、Claudeで生成されたと説明された例の変更に、SQLAlchemyコードベース内でf-stringで組み立てられたSQLが含まれていると指摘し、SQLAlchemyの式言語を使用すると、テーブルマッピングORMレイヤーなしでプログラム的にSQLを構築できると主張しました。

スレッドにはフラグが付けられた、または削除されたコメントもいくつかあり、少なくとも1人のコメント投稿者は、投稿自体は残っているのに、なぜ投稿者自身のコメントがフラグされたのかと尋ねました。

これは誰のためのものか

farolは、AIエージェントがコードの大部分を作成およびレビューするが、人間がまだマージを押すチームを対象としています。その主張は狭く反証可能です:レビュー労力は変更のリスクに従うべきであり、承認する人は変更が何に到達するかの決定的で再現可能なステートメントを必要とします。スレッドからの反論も同様に狭いです:差分よりも長いレポート、またはインポート行を最初にランク付けするレポートは、注意を導くという唯一の仕事に失敗します。

farolはMITライセンスで、アカウント、ホスト型サービス、有料ティアはなく、npx @winninjs/farolでインストールするか、GitHub Action winnin/farol/actionとして使用します。

Sources