#!/bin/bash # claude-audit-scan.sh - Fast Claude Code setup scanner # # Scans your Claude Code configuration (global + project) and outputs # a structured report of what's configured, what's missing, and quality patterns. # # Usage: # ./audit-scan.sh # Human-readable output (default) # ./audit-scan.sh --json # JSON output for Claude processing # ./audit-scan.sh --help # Show this help set -euo pipefail # Colors for human output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Output mode (default: human) OUTPUT_MODE="human" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --json) OUTPUT_MODE="json" shift ;; --help|-h) grep '^#' "$0" | sed 's/^# \?//' exit 0 ;; *) echo "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; esac done # Helper functions check_file() { [[ -f "$1" ]] && echo "true" || echo "false" } count_files() { if [[ -d "$1" ]]; then find "$1" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' ' else echo "0" fi } check_pattern() { local file="$1" local pattern="$2" if [[ -f "$file" ]]; then grep -q "$pattern" "$file" 2>/dev/null && echo "true" || echo "false" else echo "false" fi } get_file_lines() { [[ -f "$1" ]] && wc -l < "$1" | tr -d ' ' || echo "0" } count_pattern() { [[ -f "$1" ]] && grep -c "$2" "$1" 2>/dev/null || echo "0" } # Expand home directory GLOBAL_DIR="${HOME}/.claude" # === DATA COLLECTION === # Global config GLOBAL_CLAUDE_MD=$(check_file "${GLOBAL_DIR}/CLAUDE.md") GLOBAL_SETTINGS=$(check_file "${GLOBAL_DIR}/settings.json") GLOBAL_MCP=$(check_file "${GLOBAL_DIR}/mcp.json") # Project config PROJECT_CLAUDE_MD=$(check_file "./CLAUDE.md") LOCAL_CLAUDE_MD=$(check_file "./.claude/CLAUDE.md") PROJECT_SETTINGS=$(check_file "./.claude/settings.json") LOCAL_SETTINGS=$(check_file "./.claude/settings.local.json") # Extensions AGENTS_COUNT=$(count_files "./.claude/agents") COMMANDS_COUNT=$(count_files "./.claude/commands") SKILLS_COUNT=$(count_files "./.claude/skills") HOOKS_COUNT=$(count_files "./.claude/hooks") RULES_COUNT=$(count_files "./.claude/rules") # Tech stack detection TECH_STACK="unknown" if [[ -f "package.json" ]]; then TECH_STACK="nodejs" elif [[ -f "pyproject.toml" ]] || [[ -f "requirements.txt" ]]; then TECH_STACK="python" elif [[ -f "go.mod" ]]; then TECH_STACK="go" elif [[ -f "Cargo.toml" ]]; then TECH_STACK="rust" elif [[ -f "composer.json" ]]; then TECH_STACK="php" fi # Quality patterns HAS_SECURITY_HOOKS="false" if [[ -d "./.claude/hooks" ]]; then grep -l "PreToolUse" ./.claude/hooks/* 2>/dev/null >/dev/null && HAS_SECURITY_HOOKS="true" fi # MCP servers (check global config) MCP_SERVERS="" if [[ -f "${GLOBAL_DIR}/mcp.json" ]]; then # Extract server names (works with or without jq) if command -v jq &> /dev/null; then MCP_SERVERS=$(jq -r '.mcpServers | keys[]' "${GLOBAL_DIR}/mcp.json" 2>/dev/null | tr '\n' ',' | sed 's/,$//') else # Fallback: simple grep MCP_SERVERS=$(grep -oE '"[a-zA-Z0-9_-]+"\\s*:' "${GLOBAL_DIR}/mcp.json" 2>/dev/null | sed 's/"//g;s/://g' | tr '\n' ',' | sed 's/,$//') fi fi # Memory file quality (if exists) CLAUDE_MD_LINES="0" CLAUDE_MD_REFS="0" if [[ -f "./CLAUDE.md" ]]; then CLAUDE_MD_LINES=$(get_file_lines "./CLAUDE.md") CLAUDE_MD_REFS=$(count_pattern "./CLAUDE.md" "@") fi # Single Source of Truth pattern HAS_SSOT="false" if [[ -f "./CLAUDE.md" ]]; then grep -qE "^@|/docs/|/conventions/" "./CLAUDE.md" 2>/dev/null && HAS_SSOT="true" fi # === OUTPUT === if [[ "$OUTPUT_MODE" == "json" ]]; then # JSON output cat <200 lines)" fi fi if [[ -n "$MCP_SERVERS" ]]; then echo -e " ${GREEN}✅${NC} MCP servers: $MCP_SERVERS" else echo -e " ${YELLOW}âš ī¸${NC} No MCP servers configured" fi echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "Scan complete! Use ${YELLOW}--json${NC} flag for machine-readable output." fi