#!/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 </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 "$@"