optimize-claude-config について
リポジトリの `.claude/` 設定を監査・最適化し、関連する場面でのみルールを読み込み、`CLAUDE.md` を簡潔に保つスキルです。変更案を報告して確認後に適用でき、監査のみの実行にも対応します。
主な機能
- 01ルールのフロントマターとパス glob の監査
- 02追跡ファイルに照らした glob の検証
- 03`CLAUDE.md` の確認と簡素化
- 04ルール注入フックの確認
- 05確認後の変更適用
活用シーン
- 01Cursor 形式の `globs:` を `paths:` に移行
- 02ファイルに一致しないパスパターンの検出
- 03重複またはインポートのみの `CLAUDE.md` の特定
- 04新規ファイル作成時のフック設定の監査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ somnio-software/ somnio-ai-tools --skill optimize-claude-configインストール後、Claude Code で/optimize-claude-configを入力して読み込まれたか確認します。
詳細
optimize-claude-config
リポジトリの .claude/ 設定を最適化し、ルールが 関連する場合にのみ 読み込まれ、CLAUDE.md が小さく保たれるようにします。デフォルトモードでは監査、報告、確認の依頼を行ってから適用します。--audit-only を指定すると、報告後に終了します。
重要な理由(確定した事実)
Claude Code 2.1.145 で実証的に確認されています。
- YAML 配列としての
paths:は、ルールを遅延読み込みします(一致するファイルが読み込まれたときにのみコンテキストに追加されます)。また、IDE 拡張機能のスキーマ検証にも合格します。これが目標とする形式です。 - CSV 形式の単一行
paths: a, bは実行時に遅延読み込みされますが、IDE では赤く警告されます("paths must be an array of glob patterns")。配列形式に移行してください。YAML 配列のサポートは CC 2.1.84 で追加されました。 globs: "..."(Cursor のフィールド)は、スコープを無視してセッション開始時に一括読み込みされます。これが主に修正すべき点です。paths:に変換してください。alwaysApply:は Cursor のフィールドであり、Claude Code では無視されます。無害なノイズなので、そのまま残してください。削除するだけのためにファイルを変更しないでください。paths:もglobs:もないルールは、実質的に常時読み込まれます。これを指摘してください。実際に CLAUDE.md に含めるべきか、paths:スコープが必要かのどちらかです。- 読み込み時であり作成時ではないという注意点(バグ #23478): パススコープ付きルールは、読み込み時に適用され、作成時には適用されません。隣接するファイルを先に読み込まずに、ルールに一致する新しいファイルを作成しても、そのルールは読み込まれません。エリア 3 のフックでこれを補完します。
- CLAUDE.md は軽量で常時読み込まれるインデックスにしてください。プロジェクトの概要、ルールマップ(
.claude/rules/*への参照)、短い「重要なルール」の要約を含めます。ルール本文全体をインラインで記載するのは避けてください。ルールがpaths:経由で自動読み込みされるようになれば、@importのみのディレクトリ別 CLAUDE.md ファイルは不要です。
ステップ 0 — 引数を解析する
--audit-onlyが指定されている場合 → 報告のみを行い、編集や書き込みは行いません。- 末尾にパス引数がある場合 → スキャンするリポジトリのルートとして扱います。それ以外の場合は、現在の作業ディレクトリを使用します。ルート配下の
.claude/を対象にします。 <root>/.claudeが存在しない場合は、ここには最適化対象がないことをユーザーに伝えて終了します。
ステップ 1 — 検出する
<root> 配下から、次の項目を収集します。
find .claude/rules -type f -name '*.md' 2>/dev/null
find . -name 'CLAUDE.md' -not -path '*/node_modules/*' 2>/dev/null
ls .claude/settings.json .claude/settings.local.json 2>/dev/null
ls .claude/hooks/inject-rules.py 2>/dev/null
各ルールファイルのフロントマター、各 CLAUDE.md、設定ファイルを読み込みます。
ステップ 1b — プロジェクト構造を分析する(実態からパスを導出する)
パスの glob を推測したり、既存のものを無条件に信頼したりしないでください。まずリポジトリの実態に基づくマップを作成し、それからすべての paths: を導出・検証します。
実際のツリーを列挙します(ビルド出力と依存関係を除外するため、追跡対象ファイルのみ)。
git ls-files 2>/dev/null || find . -type f -not -path '*/.git/*' -not -path '*/node_modules/*' -not -path '*/build/*'
このファイル一覧から、次の項目を算出します。
- トップレベルの領域 — 各ファイルの最初のパスセグメント(例:
apps/、functions/、packages/)。これらはルールのスコープ対象となる大まかな領域です。 - モノレポのパッケージルート — マニフェスト(
pubspec.yaml、package.json、Cargo.toml、go.mod、pyproject.toml)を含むディレクトリ。ネストされたパッケージ(例:apps/mobile/packages/*/)がある場合は、適切な glob の深さが変わります。フラットな**/*.dartよりもapps/mobile/packages/*/lib/**/*.dartを優先してください。 - 領域ごとの拡張子 — それぞれの場所にあるファイルの種類(例:
apps/配下の.dart、functions/配下の.ts)。これにより、各 glob のファイル種別の末尾(**/*.dartまたは**/*.ts)を決めます。 - 生成物/ベンダー提供物/凍結された領域 — ルールを一致させるべきではないディレクトリ(
*.g.dart、*.freezed.dart、build/、node_modules/、lib/のビルド出力、およびリポジトリの CLAUDE.md で凍結または非推奨とされているもの)。提案する glob から除外してください。
次のように、簡単な集計で構成を確認します。
# extensions by top-level domain
git ls-files | awk -F/ '{d=$1; n=split($NF,a,"."); print d" *."a[n]}' | sort | uniq -c | sort -rn | head -40
# package roots
git ls-files | grep -E '/(pubspec\.yaml|package\.json|Cargo\.toml|go\.mod|pyproject\.toml)$'
このマップを保持してください。ステップ 2 で glob の検証と導出に使用します。
Step 2 — ルールのフロントマターを監査する
すべての .claude/rules/**/*.md について、フロントマターを分類します。
| 検出内容 | 条件 | 提案する対応 |
|---|---|---|
| ✅ OK | paths: が YAML 配列(1 行につき - pattern が 1 つ) | なし |
🔴 eager globs: | globs: がある(形式は問わない) | キーを paths: に変更し、値を YAML 配列(1 行につき項目 1 つ)に変換する |
🟡 CSV paths: | paths: の値がインライン形式/1 行のカンマ区切り | YAML 配列に整形する |
| 🟡 スコープ未指定 | paths: も globs: もない | Step 1b とルール自体の対象領域からスコープ候補を導き、ユーザーに確認する案として提示する。スコープを自動適用してはいけません。 |
| 🟠 古い glob | paths:/globs: のパターンに一致する追跡対象ファイルがゼロ件 | 問題として提示する — ディレクトリが名前変更または削除されたか、階層/拡張子が誤っています。実際のツリーに基づく修正パターンを提案する。 |
| 🟠 対象が広すぎる | パターンが生成ファイル/ベンダー提供ファイル/凍結されたファイル(Step 1b の一覧)に一致する | 適切なパッケージ階層を加えるか、拡張子を絞り、手書きのソースに対してのみルールが読み込まれるようにする。 |
すべてのパターンを実際のツリーに照らして検証します。 paths:/globs:
の各項目について、実際にファイルに一致することを確認します(フックの glob→regex ロジックを再利用するか、glob を変換して git ls-files | grep を使います)。ファイルに一件も一致しないパターンは、最もよくある気づきにくい不具合です — スコープが設定されているように見えても、ルールは読み込まれません。
glob の導出(スコープ未指定または古いルールの場合)。 Step 1b のマップを使用します。
- ルールの
description:/本文から対象領域(例:「Flutter クライアント」、「Cloud Functions」、「TypeScript」)を特定し、見つかった実際のトップレベル領域と拡張子に対応付けます。 - モノレポのパッケージ階層を考慮します — パッケージがある場合は、フラットな
**/*.dartではなくapps/mobile/packages/*/lib/**/*.dartを出力します。 - 生成された領域/凍結された領域を除外します。ルールのスコープに
build/、node_modules/、*.g.dart、または非推奨/凍結されたディレクトリを含めてはいけません。 - 導出した各 glob について、一致するファイル数を併記し、ユーザーが妥当性を確認できるようにします(例:
apps/mobile/lib/**/*.dart → 142 files)。
変換ルール:
- パターンがすでに正しい場合は、そのまま保持し、キー名と YAML の形式のみを変更します。CSV 形式で値を囲んでいる引用符は削除します。
- Step 1b によって古い、または対象が広すぎると確認できた場合に限り、パターンの値を書き換え、レポートに変更前/変更後を示します。
description:とalwaysApply:の行は変更しません。- 冪等にします。パターンが実際のファイルに一致する ✅ 状態のファイルには変更を加えません。
目標とする形式:
---
description: <unchanged>
alwaysApply: false # only if it was already there
paths:
- apps/mobile/lib/**/*.dart
- functions/**/*.ts
---
Step 3 — CLAUDE.md のスリム化を監査する
見つかった各 CLAUDE.md について:
@importのみのシェル — ファイル本文(空行/コメントを取り除いた後)が@import/@pathの行だけで構成されている → ルールがpaths:を介して自動読み込みされるため、冗長です。削除を提案します。例外:親のCLAUDE.mdがそのファイルをインポートしていて、かつ固有の文章が含まれている場合は残します。- 肥大化したインデックス — トップレベルの CLAUDE.md に、
.claude/rules/以下にすでに存在するルール本文が丸ごと含まれている。重複しているセクションを 1 行の参照((→ .claude/rules/<area>/<file>.md))に置き換え、短い要約だけを残すことを提案します。固有の内容は削除してはいけません — ルールファイルに逐語的に含まれている内容だけを重複排除します。 - 適切なインデックス — 概要 + ルールマップ + 短い重要ルールの要約 → 変更せずに残します。
慎重に対応してください。内容が固有かどうか判断できない場合は、削除せずユーザーに確認事項として提示します。ルールで網羅されていない手書きのガイダンスを含む CLAUDE.md を削除してはいけません。
Step 4 — 読み取り専用フックを監査する
次の2点を確認します。
.claude/settings.jsonで、.claude/hooks/inject-rules.pyを指定する matcherWriteのPreToolUseフックが登録されていること。.claude/hooks/inject-rules.pyが存在すること。
どちらかが欠けている場合は、インストールを提案してください。正規ファイルは以下に埋め込まれています。このスキルは単体で完結します。必ずこのドキュメント内のブロックから、内容を一字一句そのまま再現してください。別のリポジトリ、兄弟プロジェクト、またはホストマシン上のパスから inject-rules.py(または設定ファイル)をコピーしては絶対にいけません。「参照リポジトリ」はありません。このスキルファイルしか持っていない同僚を含め、誰が実行しても同じ動作をする必要があります。
.claude/settings.json(既存の hooks ブロックにマージし、他のフックを上書きしないでください):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/inject-rules.py\""
}
]
}
]
}
}
.claude/hooks/inject-rules.py — 存在しない場合は、以下の内容をそのまま Write してください(下のブロックを一字一句そのままコピーし、他の場所から取得しないでください)。
#!/usr/bin/env python3
"""PreToolUse(Write) hook: inject path-scoped rules when creating a new file.
Works around the read-not-create caveat (#23478): `.claude/rules/*.md` files
with `paths:` frontmatter load on read, not on create. When Claude creates a
NEW file matching a rule's paths, this injects that rule's body as
additionalContext so it is in context before the file is written.
Fires only for: Write to a file that does NOT yet exist AND matches at least
one rule's `paths:` patterns. Otherwise it exits silently.
"""
import json
import sys
import os
import re
import glob
def glob_to_regex(pat):
out, i = ['^'], 0
while i < len(pat):
if pat[i:i + 3] == '**/':
out.append('(?:.*/)?')
i += 3
elif pat[i:i + 2] == '**':
out.append('.*')
i += 2
elif pat[i] == '*':
out.append('[^/]*')
i += 1
elif pat[i] == '?':
out.append('[^/]')
i += 1
else:
out.append(re.escape(pat[i]))
i += 1
out.append('$')
return re.compile(''.join(out))
def parse_paths(frontmatter):
paths, in_paths = [], False
for line in frontmatter.splitlines():
if re.match(r'^paths:\s*$', line):
in_paths = True
continue
if in_paths:
m = re.match(r'^\s*-\s*(.+?)\s*$', line)
if m:
paths.append(m.group(1))
elif line.strip() and not line[0].isspace():
in_paths = False
return paths
def main():
try:
data = json.load(sys.stdin)
except Exception:
return
proj = os.environ.get('CLAUDE_PROJECT_DIR') or data.get('cwd') or os.getcwd()
file_path = (data.get('tool_input') or {}).get('file_path', '')
if not file_path:
return
abs_fp = file_path if os.path.isabs(file_path) else os.path.join(proj, file_path)
# Only the caveat case: a brand-new file.
if os.path.exists(abs_fp):
return
rel = os.path.relpath(abs_fp, proj)
chunks = []
pattern = os.path.join(proj, '.claude', 'rules', '**', '*.md')
for rule_file in glob.glob(pattern, recursive=True):
try:
txt = open(rule_file, encoding='utf-8').read()
except Exception:
continue
if not txt.startswith('---'):
continue
end = txt.find('\n---', 3)
if end == -1:
continue
frontmatter, body = txt[3:end], txt[end + 4:]
if any(glob_to_regex(p).match(rel) for p in parse_paths(frontmatter)):
chunks.append(body.strip())
if not chunks:
return
context = (
'The file you are about to create (`' + rel + '`) matches path-scoped '
'rules that were NOT yet loaded into context (they load on read, not on '
'create). Apply the following rule(s) to the content you write now:\n\n'
+ '\n\n---\n\n'.join(chunks)
)
print(json.dumps({
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'additionalContext': context,
}
}))
main()
inject-rules.py が存在するものの、埋め込みの正規版と異なる場合は、差分を報告し、上書きする前に確認を求めてください。ユーザーがカスタマイズしている可能性があります。ユーザーが同期を確認したら、上記の埋め込みブロックを Write してください。他の場所からファイルを取得してはいけません。
Step 5 — 報告する
領域(Rules / CLAUDE.md / Hook)ごとにまとめた、統合された表を1つ出力してください。各行には path · finding · proposed action を記載します。変更または派生する glob については、範囲をひと目で確認できるよう、ファイル一致数とともに変更前 → 変更後を示してください(例:globs:"lib/**" → paths: apps/mobile/lib/**/*.dart (142 files))。最後に件数の概要を記載します。例:3 rules to migrate · 1 stale glob fixed · 1 CLAUDE.md to delete · hook already installed。
変更が不要な場合は、その旨を明確に伝えて終了してください。設定はすでに最適です。
Step 6 — 確認して適用する
--audit-onlyの場合は、報告後に終了します。- それ以外の場合は、ユーザーに確認を求めてください(すべて適用する場合は
y、または適用対象の行を個別に除外できます)。その後、次の処理を行います。Editでルールの frontmatter を編集します(キー名の変更と YAML 配列への整形)。- 冗長なシェルを
rmで削除し、肥大化した CLAUDE.md の重複をEditで解消します。 - その行が確認された場合に限り、フックをインストールします(スクリプトを
Writeし、settings.json をEdit/Writeします)。
unscopedルールや、内容が曖昧な CLAUDE.md は自動で変更しないでください。これらは、ユーザーが手動で解決するための注意事項です。
Step 7 — 検証する
適用後、遅延読み込みが機能していることを確認する方法をユーザーに伝えてください。
/memoryを実行する(または移行したルールのpaths:に一致するファイルを読む)と、⎿ Loaded …InstructionsLoaded メッセージが表示されます。ルールはセッション開始時ではなく、一致するファイルがコンテキストに含まれた場合にのみ表示されるはずです。- フックが起動するのは、新しい一致ファイルの作成時だけです。
additionalContextは最初の書き込み後に届くため、モデルは2回目の書き込みで自己修正します(作成してから修正)。結果として、新しいファイルはルールに準拠した状態になります。
ユーザーから依頼されない限り、git commit を実行しないでください。