npx skills add https://github.com/hiclaude/health --skill health使用六层框架审计当前项目的 Claude Code 设置:CLAUDE.md → rules → skills → hooks → subagents → verifiers
目标是发现违规行为并识别错位的层级,根据项目复杂性进行校准。
输出语言: 按顺序检查:(1) CLAUDE.md 中的 ## Communication 规则(全局优先于本地);(2) 用户最近对话消息的语言;(3) 默认英语。将检测到的语言应用于所有输出,包括进度行、报告和停止条件问题。
重要提示: 在第一次工具调用之前,用输出语言输出一个进度块:
Step 1/3: 收集配置数据
· CLAUDE.md(全局 + 本地)· rules/ · settings.local.json · hooks
· MCP 服务器 · 技能清单 + 安全扫描
· 对话历史记录(最多 3 个最近会话)
选择层级:
| 层级 | 信号 | 预期内容 |
|---|---|---|
| 简单 | <500 个项目文件,1 个贡献者,无 CI | 仅 CLAUDE.md;0–1 个技能;无 rules/;hooks 可选 |
| 标准 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 500–5K 个项目文件,小团队或存在 CI |
| CLAUDE.md + 1–2 个规则文件;2–4 个技能;基本 hooks |
| 复杂 | >5K 个项目文件,多贡献者,多语言,活跃的 CI | 需要完整的六层设置 |
仅应用检测到的层级的要求。
运行一个块来收集数据。
P=$(pwd)
SETTINGS="$P/.claude/settings.local.json"
echo "=== 层级指标 ==="
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(全局) ===" ; cat ~/.claude/CLAUDE.md 2>/dev/null || echo "(none)"
echo "=== CLAUDE.md(本地) ===" ; 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 "=== 技能描述 ===" ; { [ -d "$P/.claude/skills" ] && grep -r "^description:" "$P/.claude/skills" 2>/dev/null; grep -r "^description:" ~/.claude/skills 2>/dev/null; } | sort -u
echo "=== 启动上下文估计 ==="
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 "=== 嵌套 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 "=== 对话文件 ==="
PROJECT_PATH=$(pwd | sed 's|[/_]|-|g; s|^-||')
CONVO_DIR=~/.claude/projects/-${PROJECT_PATH}
ls -lhS "$CONVO_DIR"/*.jsonl 2>/dev/null | head -10
echo "=== 对话提取(最多 3 个最近文件,文件越多置信度越高) ==="
# 跳过活动会话,它可能尚未完成。
_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 访问拒绝 ==="
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
# --- 技能扫描 ---
# 通过 frontmatter 名称排除自身,确保跨安装路径稳定。
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 "=== 技能清单 ==="
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 "=== 技能 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 "=== 技能符号链接来源 ==="
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 "=== 技能完整内容(示例:最多 5 个技能,每个 80 行) ==="
{ 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
在解释步骤 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 路径构建
sed 's|[/_]|-|g' 在 pwd 上构建路径。异常字符会产生错误的项目键。如果 MEMORY.md 显示 (none) 但用户提到了之前的会话,请在标记为 [!] 之前手动验证路径。对话提取范围
.jsonl 文件,跳过活动会话。来自少于 3 个文件的发现信号较弱 —— 始终标记 [LOW CONFIDENCE]。MCP 令牌估计
层级错误分类边缘情况
node_modules/、dist/ 和 build/,但并非所有生成器。包含 .next/、__pycache__/ 或 .turbo/ 输出的单体仓库可能会夸大文件数量并错误地触发 COMPLEX 层级。如果感觉层级不对,请手动重新检查。步骤 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:在上面输出 "Analyzing locally"。不要启动子代理。从步骤 1 进行分析,优先进行核心配置检查,除非证据明显,否则跳过对话密集的交叉验证。
STANDARD/COMPLEX:在上面输出 "Launching parallel analysis agents",然后列出覆盖范围:
· Agent 1: CLAUDE.md, rules, skills, MCP context + security scan
· Agent 2: hooks, allowedTools, behavior patterns, three-layer defense
并行启动两个子代理。将所有数据内联粘贴 —— 不要传递文件路径。
从本技能的目录中读取 agents/agent1-context.md。它指定了要粘贴的步骤 1 部分以及完整的审计清单。
从本技能的目录中读取 agents/agent2-control.md。它指定了要粘贴的步骤 1 部分以及完整的审计清单。
在撰写报告之前,用输出语言输出一个进度行:
Step 3/3: 综合报告
将本地分析和所有代理输出汇总成一份报告:
健康报告:{project}({tier} 层级,{file_count} 个文件)
呈现一个紧凑的通过检查表。仅包含与检测到的层级相关的检查。限制在 5 行。省略有发现的检查行。
| 检查项 | 详情 |
|---|---|
| settings.local.json gitignored | ok |
| No nested CLAUDE.md | ok |
| Skill security scan | no flags |
违反规则、缺少验证器定义、危险的 allowedTools、MCP 开销 >12.5%、必需路径的 Access denied、活跃的缓存破坏者以及安全发现。
CLAUDE.md 中应放在其他地方的内容、缺少 hooks、过大的技能描述、单层关键规则、模型切换、验证器缺口、子代理权限缺口以及技能结构问题。
要添加的新模式、要删除的过时项、全局与本地放置、上下文卫生、HANDOFF.md 采用、技能调用调整以及来源问题。
如果所有三个问题部分都为空,则用输出语言输出一行简短内容,例如:✓ 所有相关检查均已通过。无需修复。
停止条件: 报告之后,用输出语言提问:
"我是否应该草拟更改?我可以分别处理每个层级:全局 CLAUDE.md / 本地 CLAUDE.md / hooks / skills。"
未经明确确认,请勿进行任何编辑。
每周安装次数
83
仓库
GitHub 星标数
5
首次出现
13 天前
安全审计
安装于
gemini-cli74
codex74
opencode74
github-copilot73
amp73
kimi-cli73
Audit the current project's Claude Code setup with the six-layer framework: CLAUDE.md → rules → skills → hooks → subagents → verifiers
The goal is to find violations and identify the misaligned layer, calibrated to project complexity.
Output language: Check in order: (1) CLAUDE.md ## Communication rule (global takes precedence over local); (2) language of the user's recent conversation messages; (3) default English. Apply the detected language to all output including progress lines, the report, and the stop-condition question.
IMPORTANT: Before the first tool call, output a progress block in the output language:
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)
Pick tier:
| Tier | Signal | What's expected |
|---|---|---|
| Simple | <500 project files, 1 contributor, no CI | CLAUDE.md only; 0–1 skills; no rules/; hooks optional |
| Standard | 500–5K project files, small team or CI present | CLAUDE.md + 1–2 rules files; 2–4 skills; basic hooks |
| Complex | >5K project files, multi-contributor, multi-language, active CI | Full six-layer setup required |
Apply only the detected tier's requirements.
Run one block to collect data.
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
Before interpreting Step 1 output, check these known failure modes.
Data collection silent failures
jq not installed: conversation extraction prints (unavailable: jq not installed or parse error). BEHAVIOR section will be empty -- treat as [INSUFFICIENT DATA], not a finding.python3 not on PATH: all MCP/hooks/allowedTools sections print (unavailable). Do not flag those areas when the data source itself failed.settings.local.json absent: hooks, MCP, and allowedTools all show (unavailable). Normal for projects using global settings only -- not a misconfiguration.MEMORY.md path construction
sed 's|[/_]|-|g' on pwd. Unusual characters produce the wrong project key. If MEMORY.md shows (none) but the user mentions prior sessions, verify the path manually before flagging as [!].Conversation extract scope
.jsonl files are sampled, skipping the active session. Findings from fewer than 3 files carry low signal -- always tag [LOW CONFIDENCE].MCP token estimate
Tier misclassification edge cases
node_modules/, dist/, and build/, but not all generators. Monorepos with .next/, __pycache__/, or .turbo/ output can inflate the file count and trigger COMPLEX tier falsely. Recheck manually if the tier feels wrong.After Step 1 completes, output a summary line, then the step indicator:
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: output "Analyzing locally" above. Do not launch subagents. Analyze from Step 1, prioritize core config checks, skip conversation-heavy cross-validation unless evidence is obvious.
STANDARD/COMPLEX: output "Launching parallel analysis agents" above, then list coverage:
· Agent 1: CLAUDE.md, rules, skills, MCP context + security scan
· Agent 2: hooks, allowedTools, behavior patterns, three-layer defense
Launch two subagents in parallel. Paste all data inline -- do not pass file paths.
Read agents/agent1-context.md from this skill's directory. It specifies which Step 1 sections to paste and the full audit checklist.
Read agents/agent2-control.md from this skill's directory. It specifies which Step 1 sections to paste and the full audit checklist.
Before writing the report, output a progress line in the output language:
Step 3/3: Synthesizing report
Aggregate the local analysis and any agent outputs into one report:
Health Report: {project} ({tier} tier, {file_count} files)
Render a compact table of checks that passed. Include only checks relevant to the detected tier. Limit to 5 rows. Omit rows for checks that have findings.
| Check | Detail |
|---|---|
| settings.local.json gitignored | ok |
| No nested CLAUDE.md | ok |
| Skill security scan | no flags |
Rules violated, missing verification definitions, dangerous allowedTools, MCP overhead >12.5%, required-path Access denied, active cache-breakers, and security findings.
CLAUDE.md content that belongs elsewhere, missing hooks, oversized skill descriptions, single-layer critical rules, model switching, verifier gaps, subagent permission gaps, and skill structural issues.
New patterns to add, outdated items to remove, global vs local placement, context hygiene, HANDOFF.md adoption, skill invoke tuning, and provenance issues.
If all three issue sections are empty, output one short line in the output language like: ✓ All relevant checks passed. Nothing to fix.
Stop condition: After the report, ask in the output language:
"Should I draft the changes? I can handle each layer separately: global CLAUDE.md / local CLAUDE.md / hooks / skills."
Do not make any edits without explicit confirmation.
Weekly Installs
83
Repository
GitHub Stars
5
First Seen
13 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykFail
Installed on
gemini-cli74
codex74
opencode74
github-copilot73
amp73
kimi-cli73