health について
health は、CLAUDE.md、ルール、スキル、フック、サブエージェント、検証ツールの6層にわたり、プロジェクトの Claude Code 設定を監査するスキルです。プロジェクトの規模に応じて確認項目を調整し、変更を加えずに問題を報告します。
主な機能
- 016層の設定監査
- 02プロジェクト規模に応じた確認
- 03セキュリティと動作の分析
- 04構造化された問題レポート
- 05設定の自動変更なし
活用シーン
- 01プロジェクトの Claude Code 設定確認
- 02規模に応じた設定不足の検出
- 03スキル、フック、MCP 設定の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ hiclaude/ health --skill healthインストール後、Claude Code で/healthを入力して読み込まれたか確認します。
同梱ファイルagents/agent1-context.mdagents/agent2-control.md
詳細
Claude Code 設定の健全性監査
6 層のフレームワークに沿って、現在のプロジェクトの Claude Code 設定を監査します。
CLAUDE.md → rules → skills → hooks → subagents → verifiers
目的は、プロジェクトの複雑さに応じて、違反を見つけ、整合性の取れていない層を特定することです。
出力言語: 次の順に確認します:(1)CLAUDE.md の ## Communication ルール(グローバル設定がローカル設定より優先);(2)ユーザーの直近の会話メッセージの言語;(3)既定の英語。検出した言語を、進捗行、レポート、停止条件の質問を含むすべての出力に適用します。
重要: 最初のツール呼び出しの前に、出力言語で進捗ブロックを出力してください:
Step 1/3: Collecting configuration data
· CLAUDE.md (global + local) · rules/ · settings.local.json · hooks
· MCP servers · skills inventory + security scan
· conversation history (up to 3 recent sessions)
Step 0:プロジェクトのティアを評価する
ティアを選択します:
| ティア | 目安 | 期待される設定 |
|---|---|---|
| Simple | プロジェクトファイルが 500 個未満、コントリビューターが 1 人、CI なし | CLAUDE.md のみ;skills は 0~1 個;rules/ なし;hooks は任意 |
| Standard | プロジェクトファイルが 500~5K 個、小規模チーム、または CI あり | CLAUDE.md + rules ファイル 1~2 個;skills は 2~4 個;基本的な hooks |
| Complex | プロジェクトファイルが 5K 個超、複数のコントリビューター、多言語、CI が活発 | 6 層すべての設定が必須 |
検出されたティアの要件のみを適用してください。
Step 1:すべてのデータを収集する(単一の bash ブロック)
データを収集するため、1 つのブロックを実行します。
P=$(pwd)
SETTINGS="$P/.claude/settings.local.json"
echo "=== TIER METRICS ==="
echo "project_files: $(git -C "$P" ls-files 2>/dev/null | wc -l || find "$P" -type f -not -path "*/.git/*" -not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/build/*" | wc -l)"
echo "contributors: $(git -C "$P" log -n 500 --format='%ae' 2>/dev/null | sort -u | wc -l)"
echo "ci_workflows: $(ls "$P/.github/workflows/"*.yml "$P/.github/workflows/"*.yaml 2>/dev/null | wc -l)"
echo "skills: $(find "$P/.claude/skills" -name "SKILL.md" 2>/dev/null | grep -v '/health/SKILL.md' | wc -l)"
echo "claude_md_lines: $(wc -l < "$P/CLAUDE.md" 2>/dev/null)"
echo "=== CLAUDE.md (global) ===" ; cat ~/.claude/CLAUDE.md 2>/dev/null || echo "(none)"
echo "=== CLAUDE.md (local) ===" ; cat "$P/CLAUDE.md" 2>/dev/null || echo "(none)"
echo "=== settings.local.json ===" ; cat "$SETTINGS" 2>/dev/null || echo "(none)"
echo "=== rules/ ===" ; find "$P/.claude/rules" -name "*.md" 2>/dev/null | while IFS= read -r f; do echo "--- $f ---"; cat "$f"; done
echo "=== skill descriptions ===" ; { [ -d "$P/.claude/skills" ] && grep -r "^description:" "$P/.claude/skills" 2>/dev/null; grep -r "^description:" ~/.claude/skills 2>/dev/null; } | sort -u
echo "=== STARTUP CONTEXT ESTIMATE ==="
echo "global_claude_words: $(wc -w < ~/.claude/CLAUDE.md 2>/dev/null | tr -d ' ' || echo 0)"
echo "local_claude_words: $(wc -w < "$P/CLAUDE.md" 2>/dev/null | tr -d ' ' || echo 0)"
echo "rules_words: $(find "$P/.claude/rules" -name "*.md" 2>/dev/null | while IFS= read -r f; do cat "$f"; done | wc -w | tr -d ' ')"
echo "skill_desc_words: $({ [ -d "$P/.claude/skills" ] && grep -r "^description:" "$P/.claude/skills" 2>/dev/null; grep -r "^description:" ~/.claude/skills 2>/dev/null; } | wc -w | tr -d ' ')"
python3 -c "
import json, sys
try:
d = json.load(open('$SETTINGS'))
except Exception as e:
msg = '(unavailable: settings.local.json missing or malformed)'
print('=== hooks ==='); print(msg)
print('=== MCP ==='); print(msg)
print('=== MCP FILESYSTEM ==='); print(msg)
print('=== allowedTools count ==='); print(msg)
sys.exit(0)
print('=== hooks ===')
print(json.dumps(d.get('hooks', {}), indent=2))
print('=== MCP ===')
s = d.get('mcpServers', d.get('enabledMcpjsonServers', {}))
names = list(s.keys()) if isinstance(s, dict) else list(s)
n = len(names)
print(f'servers({n}):', ', '.join(names))
est = n * 25 * 200
print(f'est_tokens: ~{est} ({round(est/2000)}% of 200K)')
print('=== MCP FILESYSTEM ===')
if isinstance(s, list):
print('filesystem_present: (array format -- check .mcp.json)')
print('allowedDirectories: (not detectable)')
else:
fs = s.get('filesystem') if isinstance(s, dict) else None; a = []
if isinstance(fs, dict):
a = fs.get('allowedDirectories') or (fs.get('config', {}).get('allowedDirectories') if isinstance(fs.get('config'), dict) else [])
if not a and isinstance(fs.get('args'), list):
args = fs['args']
for i, v in enumerate(args):
if v in ('--allowed-directories', '--allowedDirectories') and i+1 < len(args): a = [args[i+1]]; break
if not a: a = [v for v in args if v.startswith('/') or (v.startswith('~') and len(v) > 1)]
print('filesystem_present:', 'yes' if fs else 'no')
print('allowedDirectories:', a or '(missing or not detected)')
print('=== allowedTools count ===')
print(len(d.get('permissions', {}).get('allow', [])))
" 2>/dev/null || echo "(unavailable)"
echo "=== NESTED CLAUDE.md ===" ; find "$P" -maxdepth 4 -name "CLAUDE.md" -not -path "$P/CLAUDE.md" -not -path "*/.git/*" -not -path "*/node_modules/*" 2>/dev/null || echo "(none)"
echo "=== GITIGNORE ==="
_GITIGNORE_HIT=$(git -C "$P" check-ignore -v .claude/settings.local.json 2>/dev/null || true)
if [ -n "$_GITIGNORE_HIT" ]; then
_GITIGNORE_SOURCE=${_GITIGNORE_HIT%%:*}
case "$_GITIGNORE_SOURCE" in
.gitignore|.claude/.gitignore)
echo "settings.local.json: gitignored"
;;
*)
echo "settings.local.json: ignored only by non-project rule ($_GITIGNORE_SOURCE) -- add a repo-local ignore rule"
;;
esac
else
echo "settings.local.json: NOT gitignored -- risk of committing tokens/credentials"
fi
echo "=== HANDOFF.md ===" ; cat "$P/HANDOFF.md" 2>/dev/null || echo "(none)"
echo "=== MEMORY.md ===" ; cat "$HOME/.claude/projects/-$(pwd | sed 's|[/_]|-|g; s|^-||')/memory/MEMORY.md" 2>/dev/null | head -50 || echo "(none)"
echo "=== CONVERSATION FILES ==="
PROJECT_PATH=$(pwd | sed 's|[/_]|-|g; s|^-||')
CONVO_DIR=~/.claude/projects/-${PROJECT_PATH}
ls -lhS "$CONVO_DIR"/*.jsonl 2>/dev/null | head -10
echo "=== CONVERSATION EXTRACT (up to 3 most recent, confidence improves with more files) ==="
# Skip the active session, it may still be incomplete.
_PREV_FILES=$(ls -t "$CONVO_DIR"/*.jsonl 2>/dev/null | tail -n +2 | head -3)
if [ -n "$_PREV_FILES" ]; then
echo "$_PREV_FILES" | while IFS= read -r F; do
[ -f "$F" ] || continue
echo "--- file: $F ---"
head -c 2M "$F" | jq -r '
if .type == "user" then "USER: " + ((.message.content // "") | if type == "array" then map(select(.type == "text") | .text) | join(" ") else . end)
elif .type == "assistant" then
"ASSISTANT: " + ((.message.content // []) | map(select(.type == "text") | .text) | join("\n"))
else empty
end
' 2>/dev/null | grep -v "^ASSISTANT: $" | head -300 || echo "(unavailable: jq not installed or parse error)"
done
else
echo "(no conversation files)"
fi
echo "=== MCP ACCESS DENIALS ==="
ls -t "$CONVO_DIR"/*.jsonl 2>/dev/null | head -5 | while IFS= read -r F; do
head -c 1M "$F" | grep -Em 2 'Access denied - path outside allowed directories|tool-results/.+ not in ' 2>/dev/null
done | head -20
# --- Skill scan ---
# Exclude self by frontmatter name, stable across install paths.
SELF_SKILL=$( (grep -rl '^name: health$' "$P/.claude/skills" "$HOME/.claude/skills" 2>/dev/null || true) | grep 'SKILL.md' | head -1)
[ -z "$SELF_SKILL" ] && SELF_SKILL="health/SKILL.md"
echo "=== SKILL INVENTORY ==="
for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
[ -d "$DIR" ] || continue
find -L "$DIR" -name "SKILL.md" 2>/dev/null | grep -v "$SELF_SKILL" | while IFS= read -r f; do
WORDS=$(wc -w < "$f" | tr -d ' ')
IS_LINK="no"; LINK_TARGET=""
SKILL_DIR=$(dirname "$f")
if [ -L "$SKILL_DIR" ]; then
IS_LINK="yes"; LINK_TARGET=$(readlink -f "$SKILL_DIR")
fi
echo "path=$f words=$WORDS symlink=$IS_LINK target=$LINK_TARGET"
done
done
echo "=== SKILL FRONTMATTER ==="
for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
[ -d "$DIR" ] || continue
find -L "$DIR" -name "SKILL.md" 2>/dev/null | grep -v "$SELF_SKILL" | while IFS= read -r f; do
if head -1 "$f" | grep -q '^---'; then
echo "frontmatter=yes path=$f"
sed -n '2,/^---$/p' "$f" | head -10
else
echo "frontmatter=MISSING path=$f"
fi
done
done
echo "=== SKILL SYMLINK PROVENANCE ==="
for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
[ -d "$DIR" ] || continue
find "$DIR" -maxdepth 1 -type l 2>/dev/null | while IFS= read -r link; do
TARGET=$(readlink -f "$link")
echo "link=$(basename "$link") target=$TARGET"
if [ -d "$TARGET/.git" ]; then
REMOTE=$(git -C "$TARGET" remote get-url origin 2>/dev/null || echo "unknown")
COMMIT=$(git -C "$TARGET" rev-parse --short HEAD 2>/dev/null || echo "unknown")
echo " git_remote=$REMOTE commit=$COMMIT"
fi
done
done
echo "=== SKILL FULL CONTENT (sample: up to 5 skills, 80 lines each) ==="
{ for DIR in "$P/.claude/skills" "$HOME/.claude/skills"; do
[ -d "$DIR" ] || continue
find -L "$DIR" -name "SKILL.md" 2>/dev/null | grep -v "$SELF_SKILL"
done
} | head -5 | while IFS= read -r f; do
echo "--- FULL: $f ---"
head -80 "$f"
done
注意点
Step 1 の出力を解釈する前に、次の既知の失敗モードを確認してください。
データ収集のサイレントな失敗
jqがインストールされていない場合:会話の抽出結果に(unavailable: jq not installed or parse error)と表示されます。BEHAVIOR セクションは空になります。これは指摘事項ではなく、[INSUFFICIENT DATA] として扱ってください。python3が PATH 上にない場合:MCP/hooks/allowedTools のすべてのセクションに(unavailable)と表示されます。データソース自体が失敗している領域は指摘しないでください。settings.local.jsonがない場合:hooks、MCP、allowedTools はすべて(unavailable)と表示されます。グローバル設定のみを使うプロジェクトでは通常の状態であり、設定ミスではありません。
MEMORY.md のパス構築
- パスは、
pwdに対してsed 's|[/_]|-|g'を実行して構築されます。通常とは異なる文字が含まれると、プロジェクトキーが誤って生成されます。MEMORY.md に(none)と表示されていても、ユーザーが以前のセッションに言及している場合は、指摘する前にパスを手動で確認してください。
会話の抽出範囲
- アクティブなセッションを除き、直近の
.jsonlファイル 3 件のみをサンプリングします。3 件未満のファイルに基づく指摘の確度は低いため、必ず [LOW CONFIDENCE] を付けてください。
MCP トークンの推定値
- サーバーあたり約 25 個のツール、ツールあたり約 200 トークンを前提としています。ツール数が大幅に異なるサーバーでは、推定値が大きく過大または過小になります。正確な値ではなく、目安として扱ってください。
ティアの誤分類が起きやすいケース
- bash ブロックでは
node_modules/、dist/、build/は除外されますが、すべての生成物が除外されるわけではありません。.next/、__pycache__/、.turbo/の出力を含むモノレポでは、ファイル数が水増しされ、誤って COMPLEX ティアになる可能性があります。ティアが妥当でないと感じた場合は、手動で再確認してください。
Step 2:ティアに応じた深さで分析する
Step 1 が完了したら、概要行を出力し、その後にステップ表示を出力します:
Tier: {SIMPLE/STANDARD/COMPLEX} -- {file_count} files · {contributor_count} contributors · CI: {present/absent}
Step 2/3: {SIMPLE: "Analyzing locally" | STANDARD/COMPLEX: "Launching parallel analysis agents"}
SIMPLE:上に「ローカルで分析中」と出力します。サブエージェントは起動しません。Step 1 の結果に基づいて分析し、主要な設定チェックを優先します。明らかな証拠がない限り、会話を多用する相互検証は省略します。
STANDARD/COMPLEX:上に「並列分析エージェントを起動中」と出力し、その後、対象範囲を列挙します:
· Agent 1: CLAUDE.md, rules, skills, MCP context + security scan
· Agent 2: hooks, allowedTools, behavior patterns, three-layer defense
2 つのサブエージェントを並列で起動します。すべてのデータをインラインで貼り付け、ファイルパスは渡さないでください。
Agent 1 -- コンテキスト + セキュリティ監査(会話は不要)
このスキルのディレクトリにある agents/agent1-context.md を読みます。貼り付ける Step 1 のセクションと、監査チェックリスト全体が記載されています。
Agent 2 -- 制御 + 振る舞いの監査(会話の証拠を使用)
このスキルのディレクトリにある agents/agent2-control.md を読みます。貼り付ける Step 1 のセクションと、監査チェックリスト全体が記載されています。
Step 3:統合して提示する
レポートを書く前に、出力言語で進捗行を出力します:
Step 3/3: Synthesizing report
ローカル分析とサブエージェントの出力を統合し、1 つのレポートにまとめます:
健全性レポート:{project}({tier} ティア、{file_count} ファイル)
✓ 合格
合格したチェックを簡潔な表にまとめます。検出されたティアに関連するチェックのみを含めます。行数は最大5行です。指摘事項のあるチェックは行を省略します。
| チェック | 詳細 |
|---|---|
| settings.local.json が gitignore 対象 | OK |
| ネストされた CLAUDE.md がない | OK |
| スキルのセキュリティスキャン | フラグなし |
☻ 重大 -- すぐに修正
違反しているルール、検証定義の不足、危険な allowedTools、12.5% を超える MCP オーバーヘッド、必須パスでの Access denied、有効なキャッシュブレーカー、セキュリティ上の指摘事項。
◎ 構造上の問題 -- 早めに修正
別の場所に記載すべき CLAUDE.md の内容、フックの不足、過大なスキルの説明、単一レイヤーの重大なルール、モデルの切り替え、検証の不足、サブエージェントの権限不足、スキルの構造上の問題。
○ 段階的な改善 -- できれば対応
追加すべき新しいパターン、削除すべき古い項目、グローバルとローカルの配置、コンテキストの衛生管理、HANDOFF.md の導入、スキル呼び出しの調整、出所に関する問題。
3つの問題セクションがすべて空の場合は、出力言語で次のような短い1行を出力します:✓ 関連するチェックはすべて合格しました。修正事項はありません。
対象外
- 確認なしに修正を自動適用しないでください。
- シンプルなプロジェクトに複雑なティアのチェックを適用しないでください。
- 問題を指摘するにとどめ、アーキテクチャ上の判断に踏み込まないでください。
停止条件: レポートの後、出力言語で次のように尋ねます:
「変更案を作成しましょうか?各レイヤーを個別に対応できます:グローバル CLAUDE.md / ローカル CLAUDE.md / フック / スキル。」
明示的な確認なしに編集を行わないでください。