個人開発で続けている壁紙アプリの配信用画像を抱えたリポジトリで、エージェントに読ませる範囲を絞ろうとしたときのことです。.antigravityignore に assets/ と書き、そのすぐ下に !assets/keep.png と足しました。1枚だけは読ませたかったからです。
結果は、意図の半分だけ通りました。assets/ 配下は静かになった一方で、keep.png も一緒に消えていたのです。書き方の問題だとは思わず、しばらくキャッシュを疑って時間を溶かしました。
.antigravityignore は .gitignore と同じ書式の除外ファイルです。そして gitignore 形式には、読んだだけでは気づきにくい規則がいくつもあります。手元で1件ずつ判定を取ってみたところ、「書いたのに効かない」も「思ったより効きすぎる」も、原因はだいたい4か所に集まっていました。
目視をやめて、判定器に聞く
最初に確かめ方から共有します。パターンが当たっているかを目で追うのは、行数が増えた時点で破綻します。
書式が gitignore 系ですから、手元の git をパターンの判定器として借りるのが手っ取り早い方法です。検証用の空ディレクトリに実際のパス構造だけを再現し、ルールを書いた状態で当たり外れを見ます。
mkdir -p /tmp/ignore-lab && cd /tmp/ignore-lab && git init -q .
mkdir -p assets/thumbs/ja
touch assets/a.png assets/keep.png assets/thumbs/b.png assets/thumbs/ja/c.png
printf 'assets/\n!assets/keep.png\n' > .gitignore
# 除外されずに「読まれる側」に残るファイルを列挙する
git ls-files -o --exclude-standardgit ls-files -o --exclude-standard は、除外されなかったファイルだけを並べます。「何が消えたか」ではなく「何が残っているか」を見るほうが、除外しすぎに気づきやすいという理由でこちらを使っています。
ひとつ注意があります。git check-ignore -v は否定行(! で始まる行)に一致したときもその行を表示し、終了コードが 0 になります。私はこれを「除外された」と読み違えて、最初の切り分けを一度やり直しました。可否を機械的に判定したいときは -v ではなく -q を使い、終了コード 1 を「除外されていない」と読みます。
git check-ignore -v assets/keep.png # → .gitignore:2:!assets/keep.png (終了コード 0)
git check-ignore -q assets/keep.png; echo $? # → 1(=除外されていない)なお以下の実測は git 2.34.1 の判定器で取ったものです。書式は共通ですが実装が完全に同一である保証まではありませんので、最後は Antigravity 側の実挙動でも一度確認しておくと安心です。
1. 親ディレクトリごと除外すると、! は届かない
冒頭でつまずいたのがこれでした。ディレクトリ自体を除外対象にすると、その中身は走査されなくなります。走査されないものは、あとから ! で呼び戻せません。
同じ「1枚だけ残す」を3通りの書き方で試した結果です。
| 書き方 | 読まれる側に残ったファイル |
|---|---|
assets/ + !assets/keep.png | なし(keep.png も除外された) |
assets/** + !assets/keep.png | assets/keep.png |
assets/** + !assets/thumbs/** | なし |
assets/** + !assets/thumbs/ + !assets/thumbs/** | assets/thumbs/b.png、assets/thumbs/ja/c.png |
3行目が興味深いところです。!assets/thumbs/** だけでは1件も戻ってきません。assets/** が assets/thumbs というディレクトリそのものにも当たっているため、その下へ降りる前に打ち切られるからです。ディレクトリを先に呼び戻し(!assets/thumbs/)、そのうえで中身を呼び戻す(!assets/thumbs/**)という2行が要ります。
「ディレクトリ単位で消して、一部だけ例外にする」という直感的な書き方が、ちょうど成立しない形になっている、と覚えておくのが早いと思います。
行の順序も効きます。!assets/keep.png を assets/** より前に書いた場合、keep.png は戻ってきませんでした。最後に一致した行が勝つ仕組みですので、例外は必ず除外の後ろに置きます。
2. スラッシュの有無で、探し始める場所が変わる
これが「効きすぎ」の主因でした。パターンの途中にスラッシュが含まれるかどうかで、意味がまるく変わります。
| パターン | src/tmp/t.txt | docs/src/tmp/z.txt |
|---|---|---|
tmp/(スラッシュを含まない) | 除外 | 除外 |
src/tmp/(内部にスラッシュ) | 除外 | 除外されず |
スラッシュを含まないパターンは、どの階層にある同名ディレクトリにも当たります。内部にスラッシュを含むパターンは、.antigravityignore を置いた位置から見た相対パスに固定されます。
規模のある場所でどう効くかも測ってみました。2,250 ファイルの Next.js プロジェクトで、content という1行だけを書いた場合です。
| 書いた1行 | 除外対象になったファイル数 |
|---|---|
content | 2,133 / 2,250 件(約 95%) |
content/articles/en/、public/content/、*.lock の3行 | 1,032 / 2,250 件 |
content と1語書いただけで、リポジトリのほぼ全体が対象になりました。src/content、docs/content、そして深い階層の同名ディレクトリまで、すべて拾ってしまうためです。エージェントが急に的外れな回答を返し始めたとき、モデルではなく除外ファイルの1行が原因という場合があります。
ちなみに public/content/ の1行は 0 件でした。このリポジトリではビルド時に生成されるパスで、クローン直後には存在しないからです。「書いたのに効いていない」ように見えて、そもそも対象が無いだけということもあります。
3. * はディレクトリの区切りを越えない
assets/*.png のつもりが下の階層に届いていない、というパターンです。
| パターン | assets/a.png | assets/thumbs/b.png | assets/thumbs/ja/c.png |
|---|---|---|---|
assets/*.png | 除外 | 除外されず | 除外されず |
assets/**/*.png | 除外 | 除外 | 除外 |
* は1階層分しか担当しません。任意の深さを含めたいときは ** を挟みます。画像やビルド成果物のように階層が深くなりがちなものほど、この差が効いてきます。
4. 目に見えない文字と、行頭の記号
最後は、ファイルを見ても分からない種類の原因です。実際に測った4件を挙げます。
| 状況 | 結果 |
|---|---|
行末に半角スペースがある(assets/a.png␣) | スペースは切り捨てられ、除外は成立した |
行末スペースをバックスラッシュで保護(assets/a.png\␣) | 除外されず(末尾スペース込みの名前を要求する) |
| 改行コードが CRLF | 除外は成立した |
ファイル名が # で始まる(#temp.txt) | コメント扱いになり除外されず。\#temp.txt で成立 |
末尾スペースと CRLF は、疑わしく見えて実際には無害でした。切り分けの順序としては後回しで構いません。逆に、# や ! で始まる実在のファイル名を書くときは、バックスラッシュでの保護が要ります。
公開前に、残っている側を一度数える
書いたルールの棚卸しは、次の1コマンドで足ります。除外できたものではなく、まだ読まれる側に残っているものを数えます。
# .antigravityignore の内容をそのまま判定器にかけ、残るパス数を数える
find . -path ./.git -prune -o -type f -print | sed 's|^\./||' > /tmp/all-paths.txt
git -c core.excludesFile=.antigravityignore \
check-ignore --no-index --stdin < /tmp/all-paths.txt | wc -l # 除外される件数
wc -l < /tmp/all-paths.txt # 全体件数私はこの2つの数を見て、想定と桁が違えばルールを書き直すようにしています。先ほどの 2,133 件のようなケースは、数えれば一目で分かります。逆に0件のままなら、パスの綴りか、そのパスの存在自体を疑う番です。
除外の設計そのもの、たとえば何を機密として扱い何をノイズとして落とすかについては、Antigravity に渡してはいけないファイルを確実に除外すると.antigravityignore ファイルを使いこなすに整理してあります。除外が効いているのに動作が重い、という場合はWorkspace Indexing が止まる・完了しない時の診断と修復手順のほうが近いはずです。
今日いちばんお勧めしたい一歩は、いま開いているプロジェクトで上の2行を走らせ、残っている件数を一度見ておくことです。数字を持っていると、次に何かがおかしくなったとき、除外ファイルを容疑者リストの上に置けるようになります。
なお、エージェントに書き込みや削除まで任せる段階になると、「読ませない」だけでは足りません。承認を通したあとに何がどこまで実行されるかを止められる形にする話は、消えたのは中身ではなく作業ディレクトリでしたで実際の事故と復旧をたどりながら書いています。
最後までお読みいただきありがとうございました。私自身、除外ファイルは書いた時点で満足しがちでしたので、数えて確かめる習慣が身についたのはこの一件のおかげです。