エージェントに「この定数を使っている箇所を全部挙げてください」と頼んだところ、返ってきたのは2ファイルでした。念のため手元で git grep をかけると、6ファイル出てきます。
エージェントが手を抜いたわけではありませんでした。検索の既定が、残りの4ファイルを最初から走査対象に入れていなかっただけです。
Antigravity CLI 1.1.21(8月26日)でエージェントのコード検索用に ripgrep のバイナリが同梱されました。環境に入っているかどうかに左右されなくなったのは良い変更です。ただし同梱されたことで変わらないものもあります。ripgrep が既定で何を見ないか、という部分です。
見ていないファイルは、エージェントにとって存在しないファイルです。そこにある実装を知らないまま「該当箇所はこれで全部です」と答えます。私自身、この静かな欠落のほうが検索の遅さより重いと考えています。
6ファイル中2ファイルしか返らない状況を、手元で作る
最小の再現リポジトリを作りました。同じ定数 NEEDLE_TOKEN を6つのファイルに置き、そのうち3つは .gitignore にマッチする場所へ、1つは隠しディレクトリへ配置します。
mkdir -p src/generated src/legacy .config build
echo 'const NEEDLE_TOKEN = 1;' > src/app.ts
echo 'const NEEDLE_TOKEN = 2;' > src/generated/api-client.ts
echo 'const NEEDLE_TOKEN = 3;' > src/legacy/old.ts
echo 'const NEEDLE_TOKEN = 4;' > .config/settings.ts
echo 'const NEEDLE_TOKEN = 5;' > build/bundle.js
printf 'NEEDLE_TOKEN\x00binary\n' > src/blob.bin
printf 'build/\n*.bin\nsrc/generated/\n' > .gitignore
# 生成物を「無視対象だが追跡はする」状態にする(実務でよくある形です)
git add -f src/generated/api-client.ts
git add -A -f && git commit -m initgit add -f を使っているのが肝です。生成コードやビルド済みクライアントを .gitignore に入れたまま、必要な1ファイルだけ強制的に追跡させる運用は珍しくありません。私の手元でも、生成した API クライアントを同じ形で抱えていた時期があります。
この状態で3つの検索を並べました。
git grep -l NEEDLE_TOKEN | wc -l # 6
rg -l NEEDLE_TOKEN . | wc -l # 2
rg -l -uuu NEEDLE_TOKEN . | wc -l # 6git grep は追跡済みの6ファイルすべてを返します。ripgrep の既定は src/app.ts と src/legacy/old.ts の2つだけでした。Git がバージョン管理している実コードのうち、4ファイルが検索の外にいます。
計測環境は ripgrep 13.0.0(Linux)です。同梱されたバイナリの版は手元の rg --version と一致するとは限りませんが、以下で扱う既定のフィルタはこの系統で共通して効きます。
外れる経路は3つあります
落ちた4ファイルの理由は、同じではありませんでした。
| ファイル | 外れた理由 | 戻すフラグ |
|---|---|---|
| build/bundle.js | .gitignore の build/ | --no-ignore |
| src/generated/api-client.ts | .gitignore の src/generated/(追跡済みでも除外) | --no-ignore |
| .config/settings.ts | 隠しディレクトリ | --hidden |
| src/blob.bin | NUL バイトを含むためバイナリ判定 | --binary または --text |
段階的に足していくと、拾える数が2 → 4 → 5 → 6と増えます。
rg -l NEEDLE_TOKEN . # 2
rg -l --no-ignore NEEDLE_TOKEN . # 4 (+ generated, + build)
rg -l -uu NEEDLE_TOKEN . # 5 (+ .config)
rg -l -uuu NEEDLE_TOKEN . # 6 (+ blob.bin)2番目の行が、いちばん見落としやすい部分だと思います。ripgrep は対象ファイルが Git に追跡されているかどうかを見ません。.gitignore のパターンに当たるかどうかだけで判断します。git add -f で追跡させたファイルは、Git にとっては正規の管理対象で、ripgrep にとっては無視対象です。この2つの判断がずれていることに、私は実際に検索結果を突き合わせるまで気づきませんでした。
バイナリ判定にも、報告のされ方に差があります。ディレクトリを走査したときは黙って落ちますが、ファイルを直接指定したときだけ理由を教えてくれます。
rg NEEDLE_TOKEN src/blob.bin
# binary file matches (found "\0" byte around offset 12)走査の途中で落ちたものは、この行すら出ません。「ヒットしませんでした」と「見ていません」が、出力の上では同じ顔をしています。
同じリポジトリでも、マシンによって範囲が変わります
ここから先が、共有しづらい種類の落とし穴です。ripgrep が尊重する除外規則は .gitignore だけではありません。
echo 'src/legacy/' >> .git/info/exclude
rg -l NEEDLE_TOKEN . # src/app.ts だけになる.git/info/exclude はリポジトリには入りません。手元のクローンにだけ存在するファイルです。グローバル設定の core.excludesFile も同じ挙動で、こちらはマシン単位で効きます。
つまり同じコミットを見ていても、開発者ごとにエージェントの検索範囲が違い得ます。個人開発でも、母艦とノートで設定が揃っていなければ同じことが起きます。「私の環境では見つかるのに」という報告が出たとき、疑う場所がひとつ増えます。
どのルールが外したのかは --debug が答えます
推測で当てにいく必要はありませんでした。--debug を付けると、除外の判断が1行ずつ出ます。
rg --debug --files . 2>&1 | grep "ignore::walk"出力はこうなります。
ignoring ./build: Ignore(IgnoreMatch(Gitignore(Glob { from: Some("./.gitignore"),
original: "build/", actual: "**/build", is_whitelist: false, is_only_dir: true })))
ignoring ./.config: Ignore(IgnoreMatch(Hidden))
ignoring ./src/blob.bin: Ignore(IgnoreMatch(Gitignore(Glob { from: Some("./.gitignore"),
original: "*.bin", actual: "**/*.bin", is_whitelist: false, is_only_dir: false })))from にどのファイルのパターンが効いたかが入り、隠しファイルは Hidden として区別されます。.git/info/exclude が犯人のときも from にそのパスが出ますので、共有されていない設定を突き止められます。
理由の内訳を数えたいときは、末尾を集計に回します。
rg --debug --files . 2>&1 | grep "ignore::walk" \
| sed -E 's/.*IgnoreMatch\(([A-Za-z]+).*/\1/' | sort | uniq -c | sort -rn戻し方は3通りで、常設にするなら .ignore です
一時的に範囲を広げるだけなら -uu や -uuu で足ります。ただしエージェントに毎回フラグを付けさせるのは現実的ではありませんでした。指示文に書いても、別のセッションでは落ちます。
恒久的に救いたいファイルがあるなら、.ignore ファイルに否定パターンを置く方法が確実です。ripgrep は .ignore を .gitignore より優先します。
printf '!src/generated/\n' > .ignore
rg -l NEEDLE_TOKEN . # 3 になる(app / legacy / generated).ignore はリポジトリにコミットできますので、設定が共有されます。生成コードのうちエージェントに読ませたい範囲だけを、明示的に戻せます。
ただし .ignore に !*.bin を書いても、バイナリ判定は外れませんでした。除外の層が2つあり、.ignore が効くのは無視規則の層だけだからです。バイナリを読ませたい場合は --binary を渡すしかありません。とはいえ、エージェントにバイナリを読ませたい場面はほとんどないはずですので、ここは外れたままで妥当だと考えています。
追跡済みファイルだけを確実に見たいなら、git grep を併走させるのがいちばん素直です。追跡状況そのものを基準にしますので、.gitignore の書き方に左右されません。
差分は1行で確かめられます
範囲がずれているかどうかは、毎回この1行で分かります。
comm -23 <(git ls-files -z | tr '\0' '\n' | sort) \
<(rg --files | sed 's|^\./||' | sort)Git が追跡していて ripgrep が見ていないファイルが並びます。何も出なければ範囲は一致しています。
git ls-files をそのまま使わず -z を経由しているのには理由があります。この記事を書いているリポジトリには src/app/[locale]/HomeClient.tsx があり、git ls-files はこれをクォートとエスケープ付きで出力します。素朴に比較すると存在しない差分が1件生まれました。core.quotePath=false を付けても解消しません(実測しました)。-z で区切りを NUL にすれば、そのまま比較できます。
運用中の当サイトのリポジトリで実行した結果です。
| 項目 | 実測値 |
|---|---|
| git 追跡ファイル数 | 2,264 |
| rg --files が返した数 | 2,252 |
| 差分 | 12(.gitignore・.npmrc・各カテゴリの .gitkeep 10個) |
| 除外理由の内訳 | すべて Hidden |
このリポジトリでは node_modules を置いていない状態で測ったため、.gitignore 由来の欠落は出ませんでした。落ちたのは隠しファイルだけで、検索語を含まないものばかりです。実害はありません。ただし生成コードを追跡している構成なら、先ほどの合成リポジトリと同じことが起きます。
フラグを足す負担も測りました。2,252ファイル規模で、既定が20ms、-uu が17ms、-uuu が20ms、git grep が20msです。この規模では差は誤差の範囲でした。範囲を広げるかどうかを速度で決める段階ではない、という結論になります。
検索が遅いことには気づけます。検索が見ていないことには気づけません。私はこの差分を出す1行を、ビルドやテストと同じ検証手順の中に置いています。埋め込みターミナルから同じコマンドを走らせる形については、Antigravity 2.10.0 の埋め込みターミナルで検証ループを組むにまとめています。
検索の経路そのものが切り替わってしまう問題は、/codesearch が ripgrep を使えない環境での実測で別途扱いました。除外パターンの書き方が意図どおり効いているかを確かめる手順は、.antigravityignore が効いていないときに疑う4か所にあります。
エージェントへ渡す範囲を、速度と引き換えにどこまで広げるかという話に踏み込みたい方は、読み取り専用になった .git をエージェントへどう渡すかで、履歴の受け渡しを 38 秒から 0.4 秒へ縮めた過程を書いています。
まずは手元のリポジトリで、先ほどの comm の1行を実行してみてください。何行出るかで、エージェントが今どこまで見えているのかが分かります。ここまでお付き合いいただき、ありがとうございました。