|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# 修正并简化的 get_basename 函数 |
| 4 | +get_basename() { |
| 5 | + # 检查输入是否为 "." 或 ".." |
| 6 | + if [[ "$1" == "." || "$1" == ".." ]]; then |
| 7 | + # 对于当前或上级目录,使用 basename 结合 pwd |
| 8 | + basename "$(cd "$1" && pwd)" |
| 9 | + else |
| 10 | + # 对于其他路径,直接使用 basename |
| 11 | + basename "$1" |
| 12 | + fi |
| 13 | +} |
| 14 | + |
| 15 | +# 新增:检测项目语言并获取仓库标识 |
| 16 | +detect_project_info() { |
| 17 | + local target_dir="$1" |
| 18 | + local project_info="" |
| 19 | + |
| 20 | + # 1. 优先检测 Go 项目(判断 go.mod 或 main.go) |
| 21 | + if [[ -f "${target_dir}/go.mod" ]]; then |
| 22 | + # 从 go.mod 中提取 module name |
| 23 | + local module_name=$(grep "^module " "${target_dir}/go.mod" | head -1 | awk '{print $2}') |
| 24 | + if [[ -n "$module_name" ]]; then |
| 25 | + echo "go|${module_name}" |
| 26 | + return 0 |
| 27 | + fi |
| 28 | + # 如果无法获取 module name,使用 main.go 所在目录 |
| 29 | + if [[ -f "${target_dir}/main.go" ]]; then |
| 30 | + echo "go|$(get_basename "$target_dir")" |
| 31 | + return 0 |
| 32 | + fi |
| 33 | + fi |
| 34 | + |
| 35 | + # 2. 检测 TypeScript 项目(判断 package.json 或 tsconfig.json) |
| 36 | + if [[ -f "${target_dir}/package.json" ]]; then |
| 37 | + # 从 package.json 中提取 name |
| 38 | + local package_name=$(jq -r '.name // empty' "${target_dir}/package.json" 2>/dev/null) |
| 39 | + if [[ -n "$package_name" && "$package_name" != "null" ]]; then |
| 40 | + echo "ts|${package_name}" |
| 41 | + return 0 |
| 42 | + fi |
| 43 | + fi |
| 44 | + |
| 45 | + # 3. 检测 TypeScript 项目(判断 tsconfig.json 或 .ts/.tsx 文件) |
| 46 | + if [[ -f "${target_dir}/tsconfig.json" ]]; then |
| 47 | + echo "ts|$(get_basename "$target_dir")" |
| 48 | + return 0 |
| 49 | + fi |
| 50 | + |
| 51 | + # 统计 .ts 和 .tsx 文件数量(排除 node_modules 目录) |
| 52 | + local ts_file_count=$(find "${target_dir}" -type f -not -path "*/node_modules/*" \( -name "*.ts" -o -name "*.tsx" \) | wc -l) |
| 53 | + if [[ $ts_file_count -gt 0 ]]; then |
| 54 | + echo "ts|$(get_basename "$target_dir")" |
| 55 | + return 0 |
| 56 | + fi |
| 57 | + |
| 58 | + # 4. 未检测到目标语言 |
| 59 | + echo "unknown|$(get_basename "$target_dir")" |
| 60 | + return 1 |
| 61 | +} |
| 62 | + |
| 63 | +# 直接映射 abc 为别名,处理 parse <语言> <仓库路径> 形式的参数 |
| 64 | +abc() { |
| 65 | + # 检查命令格式是否为 "parse <语言> <仓库路径>" |
| 66 | + if [ $# -eq 3 ] && [ "$1" = "parse" ]; then |
| 67 | + local lang="$2" |
| 68 | + local repo_path="$3" |
| 69 | + local repo_name=$(get_basename "$repo_path") |
| 70 | + |
| 71 | + # 确保输出目录存在 |
| 72 | + mkdir -p ~/.asts/ |
| 73 | + |
| 74 | + # 执行实际命令 |
| 75 | + abcoder parse "${lang}" "${repo_path}" -o "~/.asts/${repo_name}.json" |
| 76 | + else |
| 77 | + # 如果不是预期的 parse 命令格式,直接将参数传递给原始 abcoder 命令 |
| 78 | + abcoder "$@" |
| 79 | + fi |
| 80 | +} |
| 81 | + |
| 82 | +# LOG_FILE="/tmp/claude-hook-debug.log" |
| 83 | + |
| 84 | +# 获取脚本所在的绝对路径 |
| 85 | +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 86 | + |
| 87 | +# 定义 _need_update 文件路径 |
| 88 | +need_update_file="${script_dir}/_need_update" |
| 89 | + |
| 90 | +input=$(cat) |
| 91 | +repo_name=$(echo "$input" | jq -r '.tool_input.repo_name // ""') |
| 92 | +cwd=$(echo "$input" | jq -r '.cwd // ""') |
| 93 | + |
| 94 | +# echo "=== $(date) ===" >> "$LOG_FILE" |
| 95 | +# echo "repo_name: $repo_name" >> "$LOG_FILE" |
| 96 | +# echo "cwd: $cwd" >> "$LOG_FILE" |
| 97 | + |
| 98 | +# 读取 _need_update 文件判断是否需要强制更新 |
| 99 | +force_update=0 |
| 100 | +if [[ -f "$need_update_file" ]]; then |
| 101 | + need_update_value=$(cat "$need_update_file" 2>/dev/null || echo "0") |
| 102 | + if [[ "$need_update_value" == "1" ]]; then |
| 103 | + force_update=1 |
| 104 | + # 强制更新后,将 _need_update 重置为 0 |
| 105 | + echo "0" > "$need_update_file" |
| 106 | + fi |
| 107 | +fi |
| 108 | + |
| 109 | +# 复用现有的 get_basename 函数 |
| 110 | +current_base_name=$(get_basename "$cwd") |
| 111 | + |
| 112 | +# 检测项目信息(语言和仓库标识符) |
| 113 | +project_info=$(detect_project_info "$cwd") |
| 114 | +project_lang=$(echo "$project_info" | cut -d'|' -f1) |
| 115 | +project_identifier=$(echo "$project_info" | cut -d'|' -f2) |
| 116 | + |
| 117 | +# 优化判断逻辑:只要检测到有效项目,就执行 parse |
| 118 | +if [[ "$project_lang" != "unknown" ]]; then |
| 119 | + # 捕获标准输出和错误输出 |
| 120 | + output_file=$(mktemp) |
| 121 | + error_file=$(mktemp) |
| 122 | + |
| 123 | + # 使用检测到的语言执行 parse 命令 |
| 124 | + # 确保输出目录存在 |
| 125 | + mkdir -p ~/.asts/ |
| 126 | + ast_output_file=~/.asts/$(echo "$project_identifier" | sed 's|/|_|g').json |
| 127 | + |
| 128 | + # 检查 AST 文件是否存在且更新时间小于 3 分钟(缓存优化) |
| 129 | + # 如果 force_update=1,则跳过缓存检查,强制重新 parse |
| 130 | + if [[ $force_update -eq 0 && -f "$ast_output_file" ]]; then |
| 131 | + file_age_seconds=$(($(date +%s) - $(stat -f %m "$ast_output_file" 2>/dev/null || stat -c %Y "$ast_output_file" 2>/dev/null))) |
| 132 | + if [[ $file_age_seconds -lt 180 ]]; then |
| 133 | + jq -n --arg lang "$project_lang" --arg repo "$project_identifier" --arg age "$file_age_seconds" '{ |
| 134 | + "continue": true, |
| 135 | + "systemMessage": ("abcoder AST 缓存命中(语言:" + $lang + ",仓库:" + $repo + ")。文件更新于 " + $age + " 秒前(小于3分钟更新阈值),跳过 parse 操作。") |
| 136 | + }' |
| 137 | + exit 0 |
| 138 | + fi |
| 139 | + fi |
| 140 | + |
| 141 | + # 使用检测到的语言执行 parse 命令,并输出到 AST 目录 |
| 142 | + if abcoder parse "$project_lang" . -o "$ast_output_file" >"$output_file" 2>"$error_file"; then |
| 143 | + msg_prefix="[定时更新]" |
| 144 | + if [[ $force_update -eq 1 ]]; then |
| 145 | + msg_prefix="[检测到变更] " |
| 146 | + fi |
| 147 | + jq -n --arg lang "$project_lang" --arg repo "$project_identifier" --arg prefix "$msg_prefix" '{ |
| 148 | + "continue": true, |
| 149 | + "systemMessage": ($prefix + "abcoder parse 已成功完成(语言:" + $lang + ",仓库:" + $repo + ")。AST文件已生成,可以继续分析代码。") |
| 150 | + }' |
| 151 | + else |
| 152 | + exit_code=$? |
| 153 | + # 读取错误信息 |
| 154 | + error_msg=$(cat "$error_file" | tail -20) |
| 155 | + |
| 156 | + jq -n --arg code "$exit_code" --arg err "$error_msg" --arg lang "$project_lang" --arg repo "$project_identifier" '{ |
| 157 | + "decision": "block", |
| 158 | + "reason": ("abcoder parse 失败(语言:" + $lang + ",仓库:" + $repo + ",退出码: " + $code + ")。错误信息:\n" + $err + "\n\n可能的原因:\n1. 项目配置文件有问题(Go: go.mod;TS: tsconfig.json)\n2. 缺少依赖包\n3. 代码语法错误\n\n建议:\n- Go 项目:运行 'go mod tidy' 和 'go build' 检查\n- TS 项目:运行 'npm install' 和 'tsc --noEmit' 检查"), |
| 159 | + "hookSpecificOutput": { |
| 160 | + "hookEventName": "PreToolUse", |
| 161 | + "additionalContext": "解析失败,需要修复后重试" |
| 162 | + } |
| 163 | + }' |
| 164 | + fi |
| 165 | + |
| 166 | + # 清理临时文件 |
| 167 | + trash "$output_file" "$error_file" 2>/dev/null || rm -f "$output_file" "$error_file" |
| 168 | +else |
| 169 | + # 当前目录不是支持的项目,返回空对象 |
| 170 | + jq -n '{ |
| 171 | + "decision": "block", |
| 172 | + "reason": "当前目录未检测到支持的语言(仅支持 Go 和 TypeScript),请确保项目是 Go 或 TypeScript 类型" |
| 173 | + }' |
| 174 | +fi |
| 175 | + |
| 176 | +exit 0 |
0 commit comments