mcpField
H
health

health

github.com/hiclaudeAgent 工作流147 次安装
代码仓库

关于 health

health 是一项技能,用于审计项目 Claude Code 配置的六个层面,涵盖 CLAUDE.md、规则、技能、钩子、子 Agent 和验证器。它会按项目复杂度调整检查,并报告问题而不直接修改配置。

主要功能

  1. 01六层配置审计
  2. 02按项目级别调整检查
  3. 03安全与行为分析
  4. 04结构化问题报告
  5. 05不自动修改配置

适用场景

  1. 01检查项目的 Claude Code 配置
  2. 02按项目复杂度发现配置缺口
  3. 03评估技能、钩子和 MCP 设置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hiclaude/health --skill health

安装后在 Claude Code 中输入/health确认已加载。

附带文件agents/agent1-context.mdagents/agent2-control.md

详细介绍

Claude Code 配置健康审计

使用六层框架审计当前项目的 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)

步骤 0:评估项目级别

选择级别:

级别信号预期配置
简单<500 个项目文件,1 位贡献者,无 CI仅需 CLAUDE.md;0–1 个 skills;无需 rules/;hooks 可选
标准500–5K 个项目文件,小型团队或已启用 CICLAUDE.md + 1–2 个 rules 文件;2–4 个 skills;基础 hooks
复杂>5K 个项目文件,多位贡献者、多种语言、CI 活跃必须具备完整的六层配置

仅应用检测到的级别所要求的配置。

步骤 1:收集所有数据(单个 bash 代码块)

运行一个代码块来收集数据。

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

注意事项

解读步骤 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),但用户提到了之前的会话,请先手动验证路径,再标记为 [!]。

对话提取范围

  • 仅抽取最近的 3 个 .jsonl 文件,跳过当前会话。少于 3 个文件的发现可信度较低——务必标记为 [LOW CONFIDENCE]。

MCP 令牌估算

  • 假设每个服务约有 25 个工具,每个工具约需 200 个令牌。工具数量远多或远少的服务会导致估算大幅偏高或偏低。此估算仅供参考,并非精确值。

级别误判边缘情况

  • bash 代码块会排除 node_modules/、dist/ 和 build/,但不会排除所有生成目录。包含 .next/、__pycache__/ 或 .turbo/ 输出的单体仓库可能会导致文件数虚高,进而被误判为 COMPLEX 级别。如果级别看起来不合理,请手动复核。

步骤 2:按级别调整分析深度

步骤 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:在上方输出“正在本地分析”。不要启动子 Agent。根据步骤 1 的结果进行分析,优先检查核心配置;除非证据明显,否则跳过需要大量交叉验证的对话分析。

STANDARD/COMPLEX:在上方输出“正在启动并行分析 Agent”,然后列出覆盖范围:

  · Agent 1: CLAUDE.md, rules, skills, MCP context + security scan
  · Agent 2: hooks, allowedTools, behavior patterns, three-layer defense

并行启动两个子 Agent。将所有数据直接粘贴到内容中——不要传递文件路径。

Agent 1——上下文与安全审计(无需对话内容)

阅读此 skill 所在目录中的 agents/agent1-context.md。该文件说明应粘贴哪些步骤 1 的部分内容,并提供完整的审计清单。

Agent 2——控制与行为审计(使用对话证据)

阅读此 skill 所在目录中的 agents/agent2-control.md。该文件说明应粘贴哪些步骤 1 的部分内容,并提供完整的审计清单。

步骤 3:综合并呈现

撰写报告前,先使用输出语言输出一行进度信息:

Step 3/3: Synthesizing report

汇总本地分析结果和所有 Agent 的输出,生成一份报告:


健康报告:{project}({tier} 级别,{file_count} 个文件)

✓ 通过

以精简表格呈现通过的检查。仅包含与检测到的层级相关的检查。最多 5 行。有问题的检查不显示。

检查详情
settings.local.json 已加入 gitignore正常
没有嵌套的 CLAUDE.md正常
Skill 安全扫描未发现问题
☻ 严重——立即修复

违反规则、缺少验证定义、危险的 allowedTools、MCP 开销 >12.5%、必需路径 Access denied、活跃的缓存破坏因素,以及安全问题。

◎ 结构性——尽快修复

应放在其他位置的 CLAUDE.md 内容、缺少 hooks、Skill 描述过长、关键规则仅有单层防护、模型切换、验证器缺口、子 Agent 权限缺口,以及 Skill 结构问题。

○ 增量改进——有则更好

可新增的模式、应移除的过时内容、全局与本地的放置方式、上下文整洁度、采用 HANDOFF.md、Skill 调用调优,以及来源信息问题。


如果三个问题部分都为空,请用输出语言输出一条简短提示,例如:✓ 所有相关检查均已通过。无需修复。

非目标

  • 未经确认,绝不自动应用修复。
  • 绝不对简单项目执行复杂层级的检查。
  • 只标记问题,不替代架构判断。

**停止条件:**报告结束后,用输出语言询问:

“要我起草这些更改吗?我可以分别处理每一层:全局 CLAUDE.md / 本地 CLAUDE.md / hooks / skills。”

未经明确确认,不得进行任何编辑。

评论

支持 Markdown