サイト内検索

hermes-talaria に .gitignore 整備スキル ht-gitignore-setup を追加した — スコープ解決・テンプレート固定・事前検証を分離する

重岡 正 · Wed, October 21, 2026

.gitignore は、github/gitignore のテンプレートをまるごと貼るだけで動いているように見えて、既存のトラック済みファイルを無視対象に巻き込む、別のテンプレートが上書き順に紛れて ! 否定行の順序が崩れる、Global/ の OS・エディタ用テンプレートが共有ファイルに入り込む、といった事故が起きやすい領域です。GitHub Docs の Ignoring files もこの点については明示していません。

このテンプレートの選定・改修・検証を AI エージェントに任せるスキルを hermes-talaria に追加しました。PR は codenote-net/hermes-talaria#61 です。

スキル名は ht-gitignore-setup、呼び出しは /ht-gitignore-setup です。対象はカレントディレクトリ、別のローカルディレクトリ、https://github.com/OWNER/REPOSITORY 形式のリポジトリ URL、https://github.com/OWNER/REPOSITORY/tree/REF/PATH 形式の tree URL です。本記事ではこのスキルを設計する際に判断した点をまとめます。

追加した機能の概要

  • .gitignore の新規作成・改善を AI エージェントに任せる Hermes Agent 用スキル
  • 対象はローカルディレクトリと、GitHub リポジトリ URL・tree URL。issue・pull-request・blob URL、非 GitHub の URL、認証情報埋め込み URL は拒否
  • モノレポ対応として、リポジトリコンテキストのルート、詳細スキャン対象、ルール適用スコープ、.gitignore の配置パスを 4 つ別々に扱う
  • 選定した github/gitignore のテンプレートは、1 回の実行で解決した main の SHA に固定し、全テンプレートを同じ raw URL スナップショットから取得する
  • Global/ の OS・エディタテンプレートは共有プロジェクトファイルに混ぜない。既定ではロックファイルも無視しない
  • 書き込み前に、ワークツリー外の一時検証リポジトリで候補ルールを評価し、トラック済みパスとの衝突を git check-ignore --no-index で検出する
  • 新規作成の .gitignore は、通常の git diff では空になるため git diff --no-index --check で空白エラーを検証する
  • 既存ルール・否定(!)行の順序・意図的な例外を保持する。削除は根拠が明確な場合に限る
  • git rm --cached・git init・コミット・プッシュ・.env、鍵、認証情報の中身読み取りは行わない

リポジトリコンテキストとスコープを 4 つに分けて解決する

スキル設計で最初に悩んだのは、「どこを見て、どこにルールを置くか」をどう決めるかでした。モノレポに /apps/web だけを指す tree URL が渡されたとき、観察するのはそのサブツリーだけでよいのか、それともリポジトリ全体を見るべきか。ルールの適用範囲はサブディレクトリまでに閉じるのか、ルートの .gitignore を触るのか。

SKILL.md では、この判断を 4 つの別々の概念として明示することにしました。

  • repository context root: リポジトリの文脈を取るためのルート
  • detailed scan target: 詳細にファイルを調べる対象
  • rule scope: ルールの適用範囲
  • .gitignore path: 書き込み先の .gitignore のパス

既定は入力ごとに次のように決めます。

入力スコープ既定.gitignore の配置詳細スキャン対象
対象なしrepositoryカレントワークツリーのルートワークツリー全体
リポジトリルートの明示repositoryワークツリーのルートワークツリー全体
リポジトリ URLrepositoryclone のルートclone 全体
ネストしたローカルディレクトリtarget指定サブディレクトリ指定サブツリー(文脈のためルートも参照)
GitHub tree URLtarget指定サブディレクトリ指定サブツリー(文脈のためルートも参照)
非 Git ディレクトリdirectory指定ディレクトリ指定ディレクトリ

モノレポ側の既存ポリシーがこの既定と衝突する場合は、黙って経路を変えずに、スコープ判断をユーザーに求めます。ユーザーが repository または target を明示したときは、既定よりそちらを優先します。

GitHub の tree URL では、REF/PATH の切れ目がブランチ名に / を含むケースで曖昧になります。この分割を推測で埋めずに、gh または GitHub API で REF を解決してから PATH を検証するよう手順を固定しました。tree URL の clone 時は、デフォルトブランチを触るのではなく解決した commit を detached HEAD で checkout し、そのスナップショット上で作業します。

URL の userinfo(https://user:[email protected]/...)は、どこにも書き出さずに拒否します。「サニタイズして続行」ではなく、クリーンな HTTPS URL を要求します。認証情報をログ・レジャー・コマンド引数に残さないためです。

github/gitignore のテンプレートを 1 回の SHA に固定する

テンプレートの出所と再現性は、.gitignore の設計で軽視されがちな論点です。github/gitignore の main を叩き続けるだけだと、同じスキル実行の中でも時刻によって違うテンプレートを引く可能性があり、生成物の来歴として記録できません。

template-sources.md では、プライマリソースを github/gitignore(CC0-1.0)に限定し、次の 2 段構えを採用しました。

  1. 実行の冒頭で main の SHA を 1 回だけ解決する
  2. 選定したテンプレートはすべて、解決した SHA に固定した raw URL から取得する
UPSTREAM_SHA="$(curl -fsSL https://api.github.com/repos/github/gitignore/commits/main \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["sha"])')"
curl -fsSL \
  "https://raw.githubusercontent.com/github/gitignore/${UPSTREAM_SHA}/Node.gitignore" \
  -o "<TEMP_DIR>/Node.gitignore"

python3 が無い環境では jq か gh api でパースします。JSON を正規表現で切り出す経路は許可しません。HTTP ステータスの確認と、空レスポンスの拒否も必須にしてあります。

ルート直下のテンプレート(Node.gitignore、Python.gitignore など)を優先し、community/ 配下は「リポジトリの証拠で特殊な技術が確認できたとき」に限定します。Global/ の OS・エディタテンプレートは、共有プロジェクトファイルに既定では入れません。macOS の .DS_Store、JetBrains の .idea/、Vim の *.swp を、現在の作業者の環境を根拠に共有 .gitignore に書き込むと、他の作業者の環境と衝突しやすく、個人用 global excludes の役割を混ぜ込むためです。

テンプレート取得に失敗したとき、記憶したテンプレート文字列を代わりに差し込むことも禁止しています。代わりに、ローカル証拠だけで狭い範囲のプロジェクト固有ルールを書くか、確実な上流ベースラインが必要な場合は停止します。

プロジェクトを調べてから書き換える

テンプレートを貼ってから「不要なブロックを消す」運用は、既存の .gitignore を上書きしたり、トラック済みファイルを巻き込んだりしがちです。SKILL.md では、書き込みの前にプロジェクトを調べ、「証拠がある技術だけをテンプレート選定に使う」順序を固定しました。

収集する情報は次のとおりです。

  • 既存のルート・ネストした .gitignore
  • .git/info/exclude と core.excludesFile。ローカルの挙動とリポジトリポリシーを区別するためだけに参照する
  • トラック済みパス(git ls-files)、未トラックパス、無視済みパス(git status --short --untracked-files=all --ignored)
  • トップレベルとワークスペースレベルの manifest・ロックファイル・ビルド設定・ツールバージョンファイル・CI 設定
  • .git を除いた浅い深さのディレクトリ一覧
  • AGENTS.md・CLAUDE.md・コントリビューションガイドなどの既存指示

このとき、.env・鍵・認証情報ファイルの中身は読みません。ファイル名と、ドキュメントに書かれた命名規約だけを見ます。「未トラックの生成物ディレクトリの存在だけで言語・フレームワークを推測しない」ことも明文化しました。manifest・設定ファイル・トラック済みのソース拡張子・ドキュメントのいずれかで裏が取れたときにだけ、対応するテンプレートを候補にします。

ワークツリー外で候補ルールを事前検証する

.gitignore の運用で最も見逃しやすい事故が、「既にトラックされているファイルを無視対象に巻き込む」ケースです。無視ルールを足してもトラック済みファイルは引き続き追跡されますが、その後 git rm --cached を打つと履歴からの除外が始まり、CI や本番で参照しているファイルが黙って外れる、という連鎖に繋がります。

このスキルは git rm --cached を自身では一切実行しませんが、「候補のルールが現在のトラック済みパスを無視対象に含んでいるかどうか」は、書き込み前に判定する必要があります。

ここで注意すべきなのは、git check-ignore は既定でトラック済みパスを「無視されない」として扱うため、.gitignore に書けば本来マッチするはずのルールでも素通りすることです。SKILL.md ではこの挙動を明示し、--no-index を付けて検査するように固定しました。

git -C "<REPO_ROOT>" ls-files -z \
  | git -C "<REPO_ROOT>" check-ignore --stdin -z -v --no-index

検査対象は「現在のワークツリー」ではなく「候補ルール」です。そのため、候補の .gitignore を本物のワークツリーに書いて事前チェックするのではなく、ワークツリー外に一時的な検証リポジトリを作ります。検証リポジトリでは、候補の .gitignore のリポジトリ相対パス、関連する祖先・子孫の .gitignore、元のトラック済みパス一覧だけを再現します。中身が不要なファイルはプレースホルダで作り、.env など機密を含む可能性があるファイルは中身を複製しません。

マッチしたトラック済みパスは、すべてレビュー対象です。「トラック済みの生成物」が意図的なケース(たとえばミグレーションスクリプトやテストフィクスチャ)もあるため、「無視対象に含まれた」だけで自動削除・自動 git rm --cached には繋げません。

重要なルール・例外ごとに、「無視されるべきパス」と「同じディレクトリ内で見えていなければならないパス」の両方を代表サンプルとして入れ、挙動を実測します。--non-matching(-n)を併用して、「マッチしなかった」結果も明示的に出力させ、コマンドの沈黙に頼らないようにしました。

新規 .gitignore は plain diff ではなく --check で検証する

既存ファイルを変更したときの検証は、git diff --check と git diff の組み合わせで足ります。しかし、新規作成した .gitignore はまだトラックされていないため、通常の git diff では何も出力されません。空白エラーの検証もそのままでは走らず、「作っただけで検証した気になる」事態になりがちです。

SKILL.md では、新規 .gitignore に対しては --no-index を併用した diff を明示的に走らせることにしました。

git diff --no-index -- /dev/null "<ABSOLUTE_GITIGNORE_PATH>"
git diff --no-index --check -- /dev/null "<ABSOLUTE_GITIGNORE_PATH>"

前者は「ファイルの中身の差分」を表示します。--no-index の diff は 2 つの入力が「異なる」場合に exit 1 を返しますが、これは異常ではないので、スキルの判定ロジックとしては空白エラー出力の有無を見ます。後者(--check)は末尾空白・インデント混在などの空白エラーを検出します。

書き込み後は、代表的な無視すべきパス・残すべきパスを git check-ignore -v -n --no-index で検査し、.gitignore 自体が Git から見えているか、manifest・ロックファイル・ソースディレクトリ・フィクスチャ・ミグレーション・サンプル設定が意図せず無視されていないかを確認します。失敗したときは、ファイル存在だけで成功を報告せず、ルールを直してすべての検査を再実行します。

既存ルールと ! 否定行の順序を保持する

既存の .gitignore を触るとき、まるごと置き換えずに差分でパッチする、という方針も明文化しました。否定行は直前の除外ルールに依存して意味が決まるため、順序を崩すと「サンプルファイルだけ残していた意図」が無効化されます。

# 具体例: /dist/ 配下は無視するが、サンプル HTML は残す
/dist/
!/dist/examples/

この並びを並び替えて /dist/ を後ろにすると、!/dist/examples/ の意味が失われます。SKILL.md では次を要求します。

  • 否定(!)が先行する除外に依存している場合は、順序を維持する
  • 用途が不明な既存の変わったルールは、推測で削除せず、残したうえでレポートで指摘する
  • 重複ブロック、矛盾するパターン、推測的な catch-all ルールの新規追加を避ける

書き換えは、「生成物・ローカル成果物は無視」「ソース・ロックファイル・ミグレーション・フィクスチャ・サンプル・意図的にコミットされた設定は残す」の原則に沿って行います。

  • ルートの単一出力ディレクトリにのみ合致するときは、dist/ ではなく /dist/ のようにディレクトリアンカーを使う
  • .env などシークレットを持ちうるローカルファイルは保守的に無視し、!.env.example のようなサンプル例外は、実際にサンプルがあるか既存ポリシーがそれを求めるときにだけ加える
  • 「生成物の一部が .ts だから」といって *.ts を無視しない
  • ロックファイルは既定で無視しない。エコシステムとリポジトリの既存ポリシーに従う

完了時の報告項目を SKILL.md 側で定める

スキルの出力は、設定された報告項目に揃えています。

  • 解決したローカルターゲット、スキャンルート、スコープ、.gitignore のパス
  • 新規作成・更新のどちらを行ったか
  • 検出した技術と、その根拠(manifest・設定ファイル・トラック済み拡張子など)
  • 使用した上流テンプレートの正確なパス、固定した SHA、commit URL。上流テンプレートを使わなかった場合はその旨
  • 重要なプロジェクト固有ルールと、意図的に省いた項目
  • 検証で見つかったトラック済みパスとの衝突、それぞれの処置
  • 実行したコマンド・チェックと、その実際の結果
  • ユーザー判断が必要な残課題

GitHub URL を対象にした実行では、「クローンでの変更」と「リモートリポジトリの変更」を混同しないようにしました。tree URL でクローン上のファイルを書き換えても、GitHub 側のリポジトリは変わっていないため、「GitHub のリポジトリを更新した」とは報告しません。コミット・プッシュ・プルリクエスト作成は、ユーザーが別途依頼したときだけ行う契約にしてあります。

このスキルが実行しない操作

SKILL.md では、作業を早く終わらせるためであっても実行しない操作を明記しました。

  • 履歴からのファイル除去、シークレットのパージ、git clean
  • git rm --cached によるトラック済みファイルの除外
  • 検証や初期化のための git init
  • コミット・プッシュ・フォーク・プルリクエスト作成
  • .env・Cookie・認証ファイル・鍵の中身の読み取り
  • 認証情報埋め込み URL のサニタイズと続行
  • 署名付き URL・資格情報値のログ・レジャーへの書き出し
  • 記憶したテンプレート文字列や、検索結果スニペット、AI 生成の広域パターンによる上流テンプレートの代替
  • 現在のマシンに存在するからという理由だけで、macOS・Windows・Linux・エディタ・個人ツールのノイズを共有ファイルに書き込む
  • 用途不明の既存ルールを根拠なく削除する
  • 「トラック済みの生成物」を自動で untracked 化する
  • モノレポの既存ポリシーと衝突するスコープへの黙示的な切り替え

以上、hermes-talaria に .gitignore 整備スキル ht-gitignore-setup を追加し、スコープ解決・テンプレートの SHA 固定・ワークツリー外での事前検証を分離した設計をまとめた、現場からお送りしました。

参考情報