CodeRabbit が TAKT のスポンサーになりました:せっかくなので CodeRabbit と TAKT を共存させよう
はじめに
TAKT が CodeRabbit にスポンサードされることになりました。めでたい。ありがたい。
CodeRabbit は AI がレビューを行うことで開発を支援するサービスです。TAKT は AI コーディングエージェントをオーケストレーションして目的を達成する機能を提供する OSS です。共に AI を活用して開発等を支援するもので、そういった意味では近しいものを感じますね。そんな企業から支援いただけるのは光栄です。
以上終わり――では味気がありません。せっかく支援いただいたので何か面白くできないだろうか。そうだ、現在 TAKT の開発で回しているレビューが重い(あとお金かかる)ので、CodeRabbit をうまいこと活用してみたらどうだろうか。
というわけでやっていきましょう。
TAKT の facet と CodeRabbit
さて、CodeRabbit と連携するにあたって、筋がよさそうなのは TAKT で活用しているナレッジやポリシーのファイルです。これがどういうものかというと、まず TAKT の前提をお話しなくてはなりません。
TAKT にはビルトインとして提供されているワークフローがあり、そこで利用されるプロンプトは私が日常の開発で利用しているものです。プロンプトは Faceted Prompting (プロンプトを関心で分離するという手法)にあわせて分割しています。
Faceted Prompting: https://zenn.dev/nrs/articles/5d19b4c8a39ecb
facet と呼ばれてるものがプロンプトのパーツです。TAKT には多くのワークフローが存在し、そのワークフロー上で稼働する AI エージェントへのプロンプトを SoC の観点でパーツ化して管理しています。
たとえば TAKT がビルトインで提供しているワークフローの facet 群は
builtins/ja/facets/policies/(https://github.com/nrslib/takt/tree/main/builtins/ja/facets/policies) にまとめてあります。ユーザー定義は
.takt/facets/policies配下に配置されるのがデフォルトです。
これらのファイルは基本的に判断基準のみが記載されています。AI エージェントへの指示に関しては、インストラクトという別のファイルで管理されています。つまり、コーディングへの知識や原則に関してだけまとめたファイルになっているわけです。ということは CodeRabbit にこの基準となるファイルを参照させることで、TAKT が提供している基準に近いレビューが可能ではないかというわけです。
実験に使えそうなもの
facet にはいくつか種類がありますが、ポリシーにカテゴリされる facet には REJECT されるべきことが書かれています。ここでは 3 つだけ取り上げて実験をします。どれも世間一般の lint やデフォルトのコードレビュー観点ではあまり指摘されない種類のもので、こうしたパターンを CodeRabbit に拾わせられるかどうかを、これから追っていきます。
まず取り上げるのは、冗長な条件分岐と呼んでいるパターンです。
if (x) f(a, b, {opt: x}) else f(a, b) のように、同じ関数を引数の有無だけで呼び分けている形を指します。差異がオプション引数を渡すかどうかだけなのに、それを if/else の分岐構造として表現してしまっているわけで、呼び出しを 1 本化すれば済むはずの話です。TAKT の facet ではこの形を REJECT としています。もうひとつ、コールバック + 外部変数キャプチャと呼ばれるパターンがあります。サンプルにすると
let r; await f(c => { r = c }); return r のようになります。本来は戻り値で返せる値を、コールバック内で外側の変数に書き込んで受け取る書き方で、値が返ってくる流れと副作用で外に書かれる流れがずれてしまいます。読む側は rが更新されるタイミングをコード上で追わなければなりません。戻り値で受ければ済むのに、なぜ外部キャプチャを経由するのか、という疑問が残ります。これも facet 上は REJECT 扱いです。
最後がフォールバック濫用です。
user?.id ?? "unknown"といった書き方が典型例で、本来は必須であるはずのフィールドに、欠落時のフォールバック値を当ててしまう形です。必須項目が欠けているという不整合が、値レベルで
"unknown"のような見かけ上正常なデータに差し替えられ、エラーは起きないかわりに、後段で原因不明のおかしな挙動として現れます。データの整合性をそうやって隠してしまう書き方を、facet では REJECT として並べています。
これらはいずれもファイルに定義されています。
実際に CodeRabbit に導入する
さて、では TAKT のレビュー基準を CodeRabbit と連携させましょう。
まず CodeRabbit の導入は簡単です。GitHub app から導入すればOKです。それだけで機能します。
次にレビュー観点をカスタマイズします。このカスタマイズは
.coderabbit.yamlというファイルで可能です。
.coderabbit.yamlには次のように参照させる設定を書き込むことができます。
language: ja-JP
reviews:
profile: chill
poem: false
knowledge_base:
code_guidelines:
enabled: true
filePatterns:
- "builtins/ja/facets/policies/ai-antipattern.md"
- "builtins/ja/facets/policies/coding.md"
- "builtins/ja/facets/policies/review.md"
...
この定義は初版です。紆余曲折ありまして(このあとの節で解説します)、最終的には異なる定義になります。
ハーネスエンジニアリングしていく
設定は以上ですが、これだけでは CodeRabbit が TAKT の policies をどう解釈したかが見えません。
少しの運用ののち、CodeRabbit のレビュー出力に「poem」が普通に混ざってくることに気づきました。
🐰 Two tiny lines in history's page,
One breaking change restored with age,
...
.coderabbit.yamlには
poem: falseと書いているのに、です。あわせて過去のレビューコメントの
Configuration usedの欄を確認したら、
defaultsと表示されていました。「
.coderabbit.yamlを書いたが、CodeRabbit はデフォルト設定でレビューしている」可能性が浮上します。
ここからはハーネスエンジニアリングの様相を呈してきます。つまり、結果を見てフィードフォワード(ここでは .coderabbit.yaml)を直すわけです。
検証用のプローブ PR を作る
さて、ハーネスエンジニアリングにおいてはログないしレポートがあれば、改善していくことが可能です。今回は CodeRabbit が PR にコメントするので、それを見て改善すればよいので、特別なログ機構を用意する必要はありません。つまり、CodeRabbit のレビューが走る最小の PR を立て、観察したい挙動を意図的に仕込んで、出てくるレビューを読めばよい。
実際に作った PR :https://github.com/nrslib/takt/pull/743
観察したいのは大きく次のふたつでした。
- 観察 A:
.coderabbit.yaml
自体が読み込まれているか - 観察 B:
knowledge_base.code_guidelines.filePatterns
で指定した facet ファイルが、レビューの判断に使われているか
観察 A の証拠としては、
language: ja-JP、
profile、
poem: falseあたりがそのまま反映されるかを見ます。レビューコメント末尾の
Configuration usedの表示が
defaultsから変わるかどうかが決定打になります。
観察 B のほうは少し工夫が要ります。CodeRabbit がガイドラインを読んだうえで判断しているかどうかは、レビュー文の中身から推定するしかありません。そこで先に挙げた TAKT 固有の REJECT パターン 3 種を仕込んだ小さな TypeScript ファイルを一つ追加するだけの PR を切ります。一般的な lint やコードレビューでは普通スルーされるが、TAKT のガイドラインに照らせば REJECT になるパターンを並べて、CodeRabbit がどこまで拾ってくるかを観察する設計です。
結果を確認するための REJECT パターンの仕込み
観察 B を行うために、TAKT の facet に書かれている REJECT パターンを、ここでは3つだけ取り上げます。
実際のファイル:https://github.com/nrslib/takt/blob/main/builtins/ja/facets/policies/ai-antipattern.md
どれも世間一般の lint やデフォルトのコードレビュー観点ではあまり指摘されない種類のもので、こうしたパターンを CodeRabbit に拾わせられるかどうかを、これから追っていきます。
まず取り上げるのは、冗長な条件分岐です。
if (x) f(a, b, {opt: x}) else f(a, b) のように、同じ関数を引数の有無だけで呼び分けている形を指します。差異がオプション引数を渡すかどうかだけなのに、それを if/else の分岐構造として表現してしまっているわけで、呼び出しを 1 本化すれば済むはずの話です。TAKT の facet ではこの形を REJECT としています。もうひとつ、コールバック + 外部変数キャプチャと呼ばれるパターンがあります。サンプルにすると
let r; await f(c => { r = c }); return r のようになります。本来は戻り値で返せる値を、コールバック内で外側の変数に書き込んで受け取る書き方で、値が返ってくる流れと副作用で外に書かれる流れがずれてしまいます。読む側は rが更新されるタイミングをコード上で追わなければなりません。戻り値で受ければ済むのに、なぜ外部キャプチャを経由するのか、という疑問が残ります。これも facet 上は REJECT 扱いです。
最後がフォールバック濫用です。
user?.id ?? "unknown"といった書き方が典型例で、本来は必須であるはずのフィールドに、欠落時のフォールバック値を当ててしまう形です。必須項目が欠けているという不整合が、値レベルで
"unknown"のような見かけ上正常なデータに差し替えられ、エラーは起きないかわりに、後段で原因不明のおかしな挙動として現れます。データの整合性をそうやって隠してしまう書き方を、facet では REJECT として並べています。
こういった開発を難しくさせる AI がやりがちなアンチパターンをあえて仕込み、CodeRabbit のレビュー結果を確認することでその正当性を確認します。
一発目:chill では 1/3 しか拾わない
最初のレビューで
Configuration usedの表示は、設定ファイルがマージされてからのレビューでは
defaultsではなく
.coderabbit.yamlを指すようになり、観察 A はすぐに合格します。CodeRabbit は PR ブランチ HEAD の
.coderabbit.yamlをそのまま見にいくので、プローブブランチ上で設定をいじれば即座に挙動が変わる、という点もここで確認できました。
問題は観察 B です。仕込んだ 3 種の REJECT パターンのうち、chill モードで CodeRabbit が拾ったのは次の 1 件だけでした。
- コールバック + 外部変数キャプチャ。🟡 Nitpick として 1 件
しかも actionable ではなく nitpick で重要度が高くないとみなされています。残り 2 件(冗長な条件分岐、フォールバック濫用)については、何も指摘されませんでした。
ここまでで分かったのは、
.coderabbit.yaml自体は読まれているが、facet ファイルの中身が指摘の判断に強く効いている形跡は薄い、ということです。とくに「フォールバック濫用」のような文脈によっては出現しうる記述は、policy で REJECT と書いてあってもモデル側の「これは普通の書き方」という事前確率に押し負けやすい、と見えました。
二発目:assertive にして 2/3 まで上げる
CodeRabbit の
reviews.profileには
chillと
assertiveがあり、
chillは重要度の高い指摘に絞り込む方向に、
assertiveは積極的に指摘する方向に振ったプロファイルです。プローブブランチに対してこれを切り替えて再レビューさせてみます。
reviews: profile: assertive
PR に
@coderabbitai full reviewというコメントを置くと、CodeRabbit はインクリメンタルではなく PR 全体を再レビューしてくれます。
結果は次のように変わりました。
| 違反 | chill | assertive |
|---|---|---|
| 冗長な条件分岐 | 拾われない | 拾われた |
| コールバック + 外部変数キャプチャ | 🟡 Nitpick | 拾われた |
フォールバック濫用 (user?.id ?? "unknown") |
拾われない | 拾われない |
assertive にしただけで 1/3 から 2/3 まで上がりました。新たに拾われた「冗長な条件分岐」のコメントを読むと、次のような文言になっています。
条件分岐を削除して簡潔にするには、if/else をやめて常に第 3 引数を渡すようにしてください — 例えば
await processFile(input, output, { format: options.format });として呼び出しを統一する…
これは
ai-antipattern.mdに書いてある「三項演算子で統一」「単一呼び出しに統一」という言い回しとよく合っています。表現の一致から、facet を参照しに行っていることが推測できます。
ところが「フォールバック濫用」だけは assertive にしても素通りでした。policy に明確に REJECT と書かれているのに、です。assertive 単体ではここまでが限界、と判断できます。
三発目:「特化ルールを path_instructions に書く」案は却下
「フォールバック濫用」を拾わせるために次に試す価値がありそうだなと思われたのは
reviews.path_instructionsでした。
ここでは指示を Step by Step の形で
path_instructionsに書いて再レビューを投げます。
reviews:
path_instructions:
- path: "**"
instructions: |
レビュー時は次の手順で進めること。
Step 1: knowledge_base に登録された TAKT のコーディングガイドライン
(policies/ および knowledge/ 配下のファイル群)を網羅的に参照する。
Step 2: 各ガイドラインに含まれるルール・REJECT 例・判定基準を、
今回の差分に対して 1 つずつ照合する。
Step 3: 該当する違反が見つかった場合、どのガイドラインのどのルールに
基づく指摘かを明示して指摘を作成する。
Step 4: 一般的なベストプラクティスより、TAKT のガイドラインに
書かれた基準を優先せよ。広く使われているパターンであっても、
ガイドライン上 REJECT であれば指摘する。
ステップ形式での記述は LLM に手順遵守をさせやすくなる魔法の言葉です。効果の有無は意見が分かれるところではありますが、体感では遵守性が上がる印象があります。
@coderabbitai full reviewを投げ直して結果を待ちます。
結果:3/3 すべて拾われ、ガイドラインを引用した REJECT に
返ってきたレビューは、3 つの違反すべてをガイドライン引用つきの REJECT として指摘してきました。
- 冗長な条件分岐 (
processWithFormat
) - コールバック + 外部変数キャプチャ (
selectMode
) - フォールバック濫用 (
getUserId
)
代表的なコメントを引きます。
外部変数キャプチャで結果を受ける形は REJECT です。
Line 18-22 は selectedMode への外部代入に依存しており、REJECT パターンです。戻り値で受ける形にしてください。
As per coding guidelines, 「await f(x => { external = x }) のようなコールバック + 外部変数キャプチャは REJECT」。
processWithFormatの分岐は REJECT パターンです。
Line 10-14 は同一関数呼び出しを options.format の有無だけで分岐しており、ガイドラインの REJECT 例に一致します。呼び出しを 1 本化してください。
As per coding guidelines, 「if/else で同一関数を呼び、差異がオプション引数のみの場合は REJECT」。
policy ファイルに書いたとおりの文言が、CodeRabbit のレビューコメントに直接引用されています。Step 3「どのガイドラインのどのルールに基づくかを明示」が、ちゃんと効いているのが分かります。今まで素通りしていたフォールバック濫用も含めて 3/3、すべて TAKT の facet 由来の REJECT として指摘してきました。
TAKT と CodeRabbit の共存まとめ
今回の試行錯誤の結論を、TAKT との連携という観点で整理し直します。
CodeRabbit には
knowledge_base.code_guidelines.filePatternsという、プロジェクト独自のガイドラインを登録するための機構があります。TAKT のワークフローで活用している facet (特に knowledge と policy)を適宜記述することで同一観点でレビューすることが可能です。
そのうえで、TAKT に迫る精度を出すためには次のことを理解しておくことが必要です。
-
knowledge_base
への登録はあくまで「参照可能な情報源」を増やすところまでで、それを「レビュー時に必ず引きにいく」までは強制しない - 結果として、profile が
chill
の状態では指摘が Nitpick まで丸められ、assertive
にしても確率の低い違反は素通りする - レビュー時に facet を網羅的に当てるためには、
path_instructions
に「手順」を書いて踏ませる必要がある
つまり、
knowledge_baseは「facet をどこに置いてあるか」を CodeRabbit に伝える役割、
path_instructionsは「その facet を毎回どう使うか」を CodeRabbit に伝える役割、と棲み分けるのが筋でした。前者だけだと参照されるかもしれないし、されないかもしれない。後者まで足してはじめて「facet を毎回踏むレビュー」が成立します。
別の言い方をすると、TAKT のレビュー観点を CodeRabbit に持ち込むときの統合点は
path_instructionsです。
knowledge_baseは facet の置き場所を教える窓口にすぎず、CodeRabbit に facet を実際に使わせるのは
path_instructionsの役目であると理解すべきです。
これは TAKT に限らず、プロジェクト独自の規約集を CodeRabbit と連携させたい場合に一般化できる構図だと思っています。「規約は knowledge_base、踏ませる手順は path_instructions」という分担で考えると、規約集をいじっても CodeRabbit 側の設定を都度書き換えずに済みます。手順自体は規約集の構造を前提にしないので、規約集の増減に対しても安定です。
最終的な .coderabbit.yaml
main に取り込んだ構成は次のとおりです。デフォルト値と一致する項目とコメントを抜いて、効いている設定だけを残しています。
language: ja-JP
reviews:
profile: assertive
poem: false
path_instructions:
- path: "**"
instructions: |
レビュー時は次の手順で進めること。
Step 1: knowledge_base に登録された TAKT のコーディングガイドライン
(policies/ および knowledge/ 配下のファイル群)を網羅的に参照する。
Step 2: 各ガイドラインに含まれるルール・REJECT 例・判定基準を、
今回の差分に対して 1 つずつ照合する。
Step 3: 該当する違反が見つかった場合、どのガイドラインのどのルールに
基づく指摘かを明示して指摘を作成する。
Step 4: 一般的なベストプラクティスより、TAKT のガイドラインに
書かれた基準を優先せよ。広く使われているパターンであっても、
ガイドライン上 REJECT であれば指摘する。
auto_review:
ignore_title_keywords:
- "Release v"
- "release/v"
- "WIP"
- "DO NOT MERGE"
ignore_usernames:
- "dependabot[bot]"
- "github-actions[bot]"
- "renovate[bot]"
path_filters:
- "!dist/**"
- "!node_modules/**"
- "!package-lock.json"
- "!.takt/**"
- "!coverage/**"
tools:
ruff:
enabled: false
rubocop:
enabled: false
phpstan:
enabled: false
phpmd:
enabled: false
phpcs:
enabled: false
chat:
art: false
knowledge_base:
code_guidelines:
filePatterns:
- "builtins/ja/facets/policies/ai-antipattern.md"
- "builtins/ja/facets/policies/coding.md"
- "builtins/ja/facets/policies/review.md"
- "builtins/ja/facets/policies/task-decomposition.md"
- "builtins/ja/facets/policies/testing.md"
- "builtins/ja/facets/knowledge/architecture.md"
- "builtins/ja/facets/knowledge/e2e-testing.md"
- "builtins/ja/facets/knowledge/security.md"
- "builtins/ja/facets/knowledge/takt.md"
- "builtins/ja/facets/knowledge/unit-testing.md"
実際のファイルはこちらです→ https://github.com/nrslib/takt/blob/main/.coderabbit.yaml
ポイントを並べておきます。
- 設定ファイルには「非デフォルト値」と「重要な意図のあるリスト」だけを残す
- profile は assertive にして、独自規約由来の指摘が Nitpick に丸められないようにする
-
path_instructions
には特定ルールを書かず、TAKT の facet を網羅参照させる「手順」だけを書く -
knowledge_base.code_guidelines.filePatterns
で facet の在り処を教えておく - TAKT のデフォルト基準が欲しいときは
takt eject
コマンドで出力させること
このうち重要なのは「
path_instructionsに特定ルールを書かない」ことです。これは TAKT のスタイルガイドでも instruct facet には同様のことを記述しています。なぜなら、この種のレビューは、特定のチェックリストとして使い始めるとすぐに陳腐化してしまうからです。ベストプラクティスとしては「踏ませる手順」だけを書き、規約本体は規約集側に置いておく、という分業のほうが、規約集が成長したときに自動で恩恵が伝播するので寿命が長くなります
これに関しては Faceted Prompting ( https://zenn.dev/nrs/articles/5d19b4c8a39ecb )などを参照するとよいでしょう。
おわりに
TAKT と CodeRabbit の相性はよいです。TAKT で作った資産がそのまま CodeRabbit で活用できます。みなさんも是非併用してみてください!
CodeRabbit はこちらです。
https://coderabbit.link/nrslib
TAKT のコードはこちらです。
https://github.com/nrslib/takt
Discord のコミュニティもあります。使ってみた感想や質問、「こんなワークフローを作ったよ」という共有など、お気軽にどうぞ。
Findy の OSS 応援企画にもなっています。合わせてどうぞ!