はじめに
対象読者
CLAUDE.md と AGENTS.md と .clinerules と .github/copilot-instructions.md に、ほぼ同じルールを別々のフォーマットで書き写している方に向けた記事です。
Ruler(MIT ライセンス・stars 2,824)は、この重複を「.ruler/ に 1 回書けば全エージェントへ配布する」形で解消する CLI です。実際に Linux 環境へ導入して 32 エージェント分の設定を生成し、後片付けの ruler revert まで通したところ、導入前から存在した手書きの AGENTS.md が復元されずに削除される 挙動に行き当たりました。
この記事で分かること
-
.ruler/の 1 ソースが、どのエージェントのどのファイルへ展開されるか(実測 19 出力先) -
ruler applyが.gitignoreに何を書き込むか(/CLAUDE.mdを含む 48 パス) -
ruler revertで手書きファイルを失う条件と、--keep-backupsによる回避
前提環境
- OS: Linux 6.18.5(x86_64)
- Node.js: v22.22.2
-
@intellectronica/ruler: 0.3.44(npm view時点の latest) - 検証日: 2026-07-27(JST)
TL;DR
-
ruler applyは 1 ソースを 32 エージェント・19 出力先へ展開し、.gitignoreの Ruler ブロックに 48 パスを追加します。/CLAUDE.mdと/AGENTS.mdもそのブロックに入ります。 -
ruler revert(既定)はCLAUDE.mdを.bakから復元する一方、リポジトリ直下のAGENTS.mdは「生成物」として削除 しました。2 リポジトリで再現しています。 - 回避策は
ruler revert --keep-backupsです。復元件数が 2 件から 18 件に変わり、AGENTS.mdも原本のまま残りました。 -
ruler --versionは ruler 自身ではなく、実行元プロジェクトのpackage.jsonの version を表示します。バージョン確認はnode -pで package.json を読む方が確実です。
Ruler がやっていること
Ruler の中心は「1 ソース多出力」です。.ruler/AGENTS.md にルールを書き、ruler apply を実行すると、各エージェントが読む場所へ同じ内容が転記されます。
図の左上に AGENTS.md が 2 つ出てくる点が、後半のハマりポイントに直結します。リポジトリ直下の AGENTS.md は 入力(最優先ソース)でありながら出力先でもある ためです。README も優先順位を次のように定義しています。
A repository root
AGENTS.md(outside.ruler/) if present (highest precedence, prepended)
出典: Ruler README
セットアップと apply
インストールから初期化までは 2 コマンドです。
npm install @intellectronica/ruler
npx ruler init
[ruler] Created /path/to/repo/.ruler/AGENTS.md
[ruler] Created /path/to/repo/.ruler/ruler.toml
.ruler/ruler.toml にはエージェントの有効・無効、出力先の上書き、[gitignore] と [backup] の設定、MCP サーバー定義がコメントアウトされた状態で並びます。既定は「全エージェント有効・gitignore 更新あり・バックアップあり」です。
検証では、ruler 導入前の状態として次の 2 ファイルを手書きで置いた git リポジトリを用意しました。
printf '# CLAUDE.md\n\nHANDWRITTEN-CLAUDE\n' > CLAUDE.md
printf '# AGENTS.md\n\nHANDWRITTEN-AGENTS\n' > AGENTS.md
git add -A && git commit -m init
そのうえで .ruler/AGENTS.md にチームルールを書き、ruler apply を実行します。
npx ruler apply
[ruler] Applying rules for GitHub Copilot...
[ruler] Applying rules for Claude Code...
[ruler] Applying rules for OpenAI Codex CLI...
(中略・合計 32 エージェント)
[ruler] Applying rules for JetBrains AI Assistant...
[ruler] Updated .gitignore with 48 unique path(s) in the Ruler block.
Ruler apply completed successfully.
Applying rules for の行数を数えると 32 でした。生成された CLAUDE.md は次のようになります。
<!-- Source: AGENTS.md -->
# AGENTS.md
HANDWRITTEN-AGENTS
<!-- Source: .ruler/AGENTS.md -->
# チームルール
- 日本語で応答する
- テストを先に書く
手書きだった # CLAUDE.md ... HANDWRITTEN-CLAUDE は本体から消え、CLAUDE.md.bak にのみ残ります。上書き前にバックアップを作る仕様なので原本は失われませんが、.bak の存在に気づかないまま git add -A するとレビューで驚くことになります。
確認1: ruler --version は ruler のバージョンではない
最初につまずいたのはバージョン確認でした。npm view は 0.3.44 を返すのに、CLI は別の値を出します。
$ npm view @intellectronica/ruler version
0.3.44
$ npx ruler --version
1.0.0
1.0.0 は npm init -y が作った 検証用プロジェクト自身 のバージョンです。裏取りとして、プロジェクトの version を書き換えてから再実行しました。
$ node -e "const p=require('./package.json');p.version='9.9.9-test';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2))"
$ npx ruler --version
9.9.9-test
--version が実行元プロジェクトの値をそのまま返しています。導入バージョンを記録したいときは、インストール先の package.json を直接読むのが確実です。
node -p "require('./node_modules/@intellectronica/ruler/package.json').version"
# => 0.3.44
不具合報告や社内の導入記録に「ruler 1.0.0 で発生」と書くと再現できなくなります。バージョンは package.json 側の値で残してください。
確認2: .gitignore に /CLAUDE.md が入る
ruler apply は .gitignore に専用ブロックを追記します。48 パスの内訳は、生成ファイルとその .bak がセットで並ぶ形です。
# START Ruler Generated Files
/.agent/rules/ruler.md
/.agent/rules/ruler.md.bak
/.aider.conf.yml
/.aider.conf.yml.bak
(中略)
/AGENTS.md
/AGENTS.md.bak
/CLAUDE.md
/CLAUDE.md.bak
/CRUSH.md
/CRUSH.md.bak
/WARP.md
/WARP.md.bak
/firebender.json
/firebender.json.bak
# END Ruler Generated Files
CLAUDE.md を「チームで共有する規約」としてコミットしている場合、この既定は方針とぶつかります。git は追跡済みファイルを .gitignore では除外しないため既存の追跡は続きますが、新規クローンや新規追加の場面で挙動が変わります。ブロックごと生成物として扱うか、ruler.toml の [gitignore] を無効化するかを、導入時に決めておく必要があります。
[gitignore]
enabled = false
生成物のうち .gemini/settings.json と .qwen/settings.json はこのブロックに含まれておらず、apply 後も未追跡ファイルとして残りました。「生成物は全部 ignore される」前提でコミットすると、この 2 つが混ざります。
確認3: 初回 apply だけ結果が違う
同じソースで ruler apply を 2 回実行すると、出力が変わりました。
| 実行 |
AGENTS.md の行数 |
<!-- Source: ... --> の数 |
|---|---|---|
| 1 回目 | 17 | 2(AGENTS.md と .ruler/AGENTS.md) |
| 2 回目 | 9 | 1(.ruler/AGENTS.md のみ) |
1 回目は手書きの AGENTS.md が最優先ソースとして前置されるため、その内容が出力にも混ざります。1 回目の実行で AGENTS.md の先頭に <!-- Generated by Ruler --> が付くと、2 回目以降はソース扱いされなくなり、前置分が落ちます。差分は 2 回目に現れます。
<!-- Generated by Ruler -->
-<!-- Source: AGENTS.md -->
-
-# AGENTS.md
-
-HANDWRITTEN-AGENTS
-
<!-- Source: .ruler/AGENTS.md -->
3 回目以降は安定するため、実運用では「導入直後の 1 回だけ内容が違う」形になります。CI で ruler apply の差分チェックを入れる場合は、初回コミットの前に 2 回実行して落ち着かせておくと、無用な差分に悩まされません。
確認4: ruler revert で手書き AGENTS.md が消えた
README は revert を次のように説明しています。
The
revertcommand safely undoes all changes made byruler apply, restoring your project to its pre-ruler state.
出典: Ruler README
この説明のとおりなら、手書きの CLAUDE.md と AGENTS.md はどちらも原本へ戻るはずです。実行結果は次のようになりました。
npx ruler revert
[ruler] Revert completed successfully.
Files processed: 19
Files restored from backup: 2
Generated files removed: 17
Backup files removed: 2
Empty directories removed: 5
.gitignore cleaned: yes
$ cat CLAUDE.md
# CLAUDE.md
HANDWRITTEN-CLAUDE
$ cat AGENTS.md
cat: AGENTS.md: No such file or directory
$ git status --short
M .gitignore
D AGENTS.md
CLAUDE.md は原本に戻り、AGENTS.md は削除されました。AGENTS.md.bak も同時に消えているため、git 管理下でなければ手書き内容はこの時点で失われます。空のリポジトリを作り直して apply と revert だけを通す最小手順でも同じ結果になり、2 リポジトリで再現しました。
原因は出力先の共有にあります。32 エージェントに対して出力先は 19 個で、AGENTS.md は複数エージェントが同じパスへ書き込みます。共有パスを 1 件目の処理で .bak から復元し、その後に別エージェントの処理が同じパスを「生成物」として削除すると、最終状態は削除になります。upstream にも .mcp.json の同種不具合(#638 Restore shared MCP configs only once during revert)や Qwen 設定の復元漏れ(#647)が記録されており、共有出力先の revert は既知の弱点です。ただし issue 検索の限りでは、リポジトリ直下 AGENTS.md の削除として報告されたものは見つかりませんでした。
回避策: --keep-backups を付ける
revert には .bak を残すオプションがあります。付けて実行すると結果が変わりました。
npx ruler revert --keep-backups
Files restored from backup: 18
Generated files removed: 16
Backup files removed: 0
Empty directories removed: 5
$ cat AGENTS.md
A-ORIG
$ cat AGENTS.md.bak
A-ORIG
復元件数が 2 から 18 に増え、AGENTS.md は原本のまま残りました。.bak を消さないことで「復元してから削除する」順序が崩れなくなったと読めます。既定値は --keep-backups が無効(default: false)なので、明示的に付ける運用にしておくのが安全です。
一方で、対象エージェントを切って回避しようとした試みは失敗しました。
[agents.agentsmd]
enabled = false
この設定で apply しても AGENTS.md は <!-- Generated by Ruler --> 付きで生成され、revert では同じく削除されました。AGENTS.md を出力先とするエージェントは AgentsMd だけではないため、1 つ無効化しても共有パスへの書き込みは止まりません。
まとめると、実測した回避策の可否は次のとおりです。
| 手段 | 結果 |
|---|---|
ruler revert --keep-backups |
AGENTS.md が原本のまま復元される(有効) |
[agents.agentsmd] enabled = false |
生成・削除とも変わらず(無効) |
導入前に git commit しておく |
git checkout -- AGENTS.md で復旧可(回避ではなく保険) |
運用への落とし込み
検証結果を踏まえると、導入時に決めておくべき点は 4 つに絞られます。
-
導入コミットを先に作る。apply 前の状態をコミットしておけば、共有パスの取りこぼしは
git checkoutで戻せます。 -
revertは必ず--keep-backupsを付ける。手順書やスクリプトに直接埋め込み、素のruler revertを打たせないようにします。 -
.gitignoreの方針を明示する。CLAUDE.mdを規約としてコミットする運用なら[gitignore] enabled = falseを選び、生成物として扱うならブロックごと受け入れます。 - 初回 apply は 2 回流す。前置ソースの取り込み差分を落ち着かせてからコミットすると、CI の差分チェックが安定します。
いずれも 1 行の設定か 1 行の手順で済みます。ルール重複の解消という本来の効果は大きいので、後片付けの経路だけ先に固めておくのが現実的な使い方です。
まとめ
- Ruler は 1 ソースを 32 エージェント・19 出力先へ配布し、
.gitignoreに 48 パスを追記します。効果は明快ですが、CLAUDE.mdとAGENTS.mdも生成物として扱われます。 - 既定の
ruler revertは、導入前から存在した手書きAGENTS.mdを削除しました。README の "restoring your project to its pre-ruler state" と実挙動は一致しません。 -
--keep-backupsを付けると復元されます。エージェント単位の無効化では回避できません。 -
ruler --versionは実行元プロジェクトのバージョンを返すため、記録には package.json の値を使ってください。
関連記事
- Agent Reach をサーバーで実測、doctorの✅4件中2件は疎通未確認
- agent-cli入門 — 音声・テキスト補正・git worktree並列開発を1つのCLIで動かす
- Agent設定をnpmのように配布するAPM入門 — 実機で見えた2つの罠
参考リンク
- intellectronica/ruler(GitHub)(セクション「はじめに」「Ruler がやっていること」で引用)
- Ruler README(セクション「Ruler がやっていること」「確認4」で引用)
- Issue #638 Restore shared MCP configs only once during revert(セクション「確認4」で引用)
- Issue #647 Back up Qwen settings before apply so revert restores contextFileName(セクション「確認4」で引用)
- AGENTS.md(セクション「はじめに」で引用)