Files
gh-zephyrdeng-cc-plugins-pl…/hooks/serena-auto-index.sh
2025-11-30 09:08:25 +08:00

214 lines
5.7 KiB
Bash
Executable File

#!/bin/bash
# Serena 自动索引 Hook 脚本
# 支持智能索引决策,避免不必要的重复索引
set -euo pipefail
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${BLUE}[Serena Hook]${NC} $1"
}
log_success() {
echo -e "${GREEN}[Serena Hook]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[Serena Hook]${NC} $1"
}
log_error() {
echo -e "${RED}[Serena Hook]${NC} $1" >&2
}
# 获取脚本参数
HOOK_TYPE="${1:-session-start}"
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
SERENA_DIR="$PROJECT_ROOT/.serena"
INDEX_METADATA="$SERENA_DIR/metadata.json"
log_info "Hook 触发: $HOOK_TYPE"
log_info "项目路径: $PROJECT_ROOT"
# 检查是否为 Git 仓库
if [[ ! -d "$PROJECT_ROOT/.git" ]]; then
log_info "非 Git 仓库,跳过索引检查"
exit 0
fi
# 检查 Serena MCP 是否配置
if ! command -v uvx &> /dev/null; then
log_info "uvx 工具未安装,跳过索引检查"
exit 0
fi
# 检查 Serena MCP 是否已启用
check_serena_mcp_enabled() {
# 方案 1: 检查用户级配置 (~/.claude.json)
if [[ -f ~/.claude.json ]]; then
if jq -e '.mcpServers | has("serena")' ~/.claude.json &>/dev/null 2>&1; then
return 0
fi
fi
# 方案 2: 检查项目级配置 (.mcp.json)
if [[ -f "$PROJECT_ROOT/.mcp.json" ]]; then
if jq -e 'has("serena")' "$PROJECT_ROOT/.mcp.json" &>/dev/null 2>&1; then
return 0
fi
fi
# 方案 3: 检查旧版配置位置(兼容性)
if [[ -f ~/.claude/mcp.json ]]; then
if jq -e 'has("serena")' ~/.claude/mcp.json &>/dev/null 2>&1; then
return 0
fi
fi
return 1
}
# 检查 metadata.json 文件完整性
validate_metadata() {
if [[ ! -f "$INDEX_METADATA" ]]; then
return 1
fi
# 检查文件是否为有效的 JSON
if ! jq empty "$INDEX_METADATA" 2>/dev/null; then
log_warning "metadata.json 文件损坏,将重新创建"
rm -f "$INDEX_METADATA"
return 1
fi
return 0
}
# 检查是否需要智能索引决策
should_skip_index() {
local index_age_threshold=3600 # 1小时
local change_threshold=10 # 10个文件变更
# 如果索引不存在,需要索引
if ! validate_metadata; then
return 1
fi
# 检查索引年龄
local current_time=$(date +%s)
local index_time=$(jq -r '.timestamp // 0' "$INDEX_METADATA" 2>/dev/null)
if [[ -z "$index_time" || "$index_time" == "null" ]]; then
index_time=0
fi
local index_age=$((current_time - index_time))
if [[ $index_age -lt $index_age_threshold ]]; then
log_info "索引仍然新鲜($(($index_age / 60))分钟前),跳过索引"
return 0
fi
# 检查文件变更数量
local changed_files
changed_files=$(cd "$PROJECT_ROOT" && git diff --name-only HEAD~1 2>/dev/null | wc -l || echo 0)
if [[ $changed_files -lt $change_threshold ]]; then
log_info "文件变更较少($changed_files 个),跳过索引"
return 0
fi
return 1
}
# 执行索引
run_index() {
log_info "开始 Serena 索引..."
# 创建 .serena 目录
mkdir -p "$SERENA_DIR"
# 执行索引命令
cd "$PROJECT_ROOT"
if uvx --from git+https://github.com/oraios/serena serena project index 2>&1 | while IFS= read -r line; do
log_info "索引: $line"
done; then
# 创建索引元数据
local metadata_content=$(cat <<EOF
{
"timestamp": $(date +%s),
"hook_type": "$HOOK_TYPE",
"project_root": "$PROJECT_ROOT",
"git_commit": "$(git rev-parse HEAD 2>/dev/null || echo 'unknown')",
"files_indexed": $(find . -name "*.go" -o -name "*.py" -o -name "*.js" -o -name "*.ts" | wc -l),
"index_size": $(du -sh "$SERENA_DIR" 2>/dev/null | cut -f1 || echo "unknown"),
"status": "completed"
}
EOF
)
echo "$metadata_content" > "$INDEX_METADATA"
log_success "Serena 索引完成"
return 0
else
log_error "Serena 索引失败"
return 1
fi
}
# 主逻辑
main() {
case "$HOOK_TYPE" in
"session-start")
log_info "会话开始 - 检查索引状态"
# 优化:如果 Serena MCP 未启用,跳过索引检查
if ! check_serena_mcp_enabled; then
log_info "Serena MCP 未启用,跳过索引检查"
exit 0
fi
if should_skip_index; then
log_success "索引检查完成,无需更新"
else
log_info "需要更新索引"
run_index
fi
;;
"pre-tool")
log_info "Serena 工具使用前 - 快速检查"
# PreToolUse hook 执行更快的检查
if validate_metadata; then
local timestamp=$(jq -r '.timestamp // 0' "$INDEX_METADATA" 2>/dev/null)
if [[ -z "$timestamp" || "$timestamp" == "null" ]]; then
timestamp=0
fi
local index_age=$(($(date +%s) - timestamp))
if [[ $index_age -lt 7200 ]]; then # 2小时内不重新索引
log_success "索引仍然新鲜,继续使用工具"
else
log_warning "索引可能过期,建议手动运行 /serena-index"
fi
else
log_warning "未找到索引,建议手动运行 /serena-index"
fi
;;
*)
log_error "未知的 hook 类型: $HOOK_TYPE"
exit 1
;;
esac
}
# 错误处理
trap 'log_error "Hook 执行过程中发生错误"' ERR
# 执行主逻辑
main "$@"