#!/bin/bash # update-graph.sh — 增量更新知识图谱 # 用法: # ./scripts/update-graph.sh # 更新前后端 # ./scripts/update-graph.sh backend # 只更新后端 # ./scripts/update-graph.sh frontend # 只更新前端 # # 由 AI Agent 在完成代码修改后自动调用,或手动执行。 set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" PYTHON="" # 检测 graphify 使用的 Python 解释器 detect_python() { local graphify_out="$1/graphify-out/.graphify_python" if [ -f "$graphify_out" ]; then PYTHON="$(cat "$graphify_out")" else PYTHON="python3" fi } # 检查 graphify 是否安装 check_graphify() { if ! "$PYTHON" -c "import graphify" 2>/dev/null; then echo "[graphify] 未安装,正在安装..." "$PYTHON" -m pip install graphifyy -q 2>/dev/null || "$PYTHON" -m pip install graphifyy -q --break-system-packages 2>&1 | tail -3 fi } # 增量更新单个项目的图谱 update_graph() { local name="$1" local src_dir="$2" local label="$3" echo "" echo "=== 更新 ${label} 知识图谱 (${src_dir}) ===" detect_python "$ROOT_DIR/$name" check_graphify cd "$ROOT_DIR/$name" local detect_json="graphify-out/.graphify_detect.json" local manifest_json="graphify-out/manifest.json" if [ ! -f "$manifest_json" ]; then echo "[graphify] 未找到 manifest.json,跳过增量更新(首次需运行完整构建)" return 0 fi # 运行增量检测 "$PYTHON" -c " import sys, json from graphify.detect import detect_incremental, save_manifest from pathlib import Path result = detect_incremental(Path('${src_dir}')) new_total = result.get('new_total', 0) Path('graphify-out/.graphify_incremental.json').write_text(json.dumps(result)) print(f'{new_total} file(s) changed since last run.') if new_total == 0: print('SKIP: Nothing to update.') sys.exit(0) " # 如果没有变化,跳过 local changed=$? if [ $changed -ne 0 ]; then return 0 fi # 检查是否只是代码变更 "$PYTHON" -c " import json from pathlib import Path result = json.loads(Path('graphify-out/.graphify_incremental.json').read_text()) code_exts = {'.py','.ts','.js','.vue','.go','.rs','.java','.cpp','.c','.rb','.swift','.kt','.cs','.scala','.php','.cc','.cxx','.hpp','.h','.kts'} new_files = result.get('new_files', {}) all_changed = [f for files in new_files.values() for f in files] code_only = all(Path(f).suffix.lower() in code_exts for f in all_changed) if all_changed else True print('code_only:', code_only) " 2>/dev/null # AST 提取 "$PYTHON" -c " import sys, json from graphify.extract import collect_files, extract from graphify.build import build_from_json from graphify.cluster import cluster, score_all from graphify.analyze import god_nodes, surprising_connections, suggest_questions from graphify.report import generate from graphify.export import to_json, to_html from pathlib import Path # Re-extract AST for all code files (fast, deterministic) # 注:不依赖旧的 .graphify_extract.json;缺失时从头提取即可 from graphify.detect import detect detection = detect(Path('${src_dir}')) Path('graphify-out/.graphify_detect.json').write_text(json.dumps(detection)) code_files = [] for f in detection.get('files', {}).get('code', []): code_files.extend(collect_files(Path(f)) if Path(f).is_dir() else [Path(f)]) if code_files: result = extract(code_files) Path('graphify-out/.graphify_extract.json').write_text(json.dumps(result, indent=2)) print(f'AST: {len(result[\"nodes\"])} nodes, {len(result[\"edges\"])} edges') else: print('No code files found.') sys.exit(1) # Build graph G = build_from_json(result) communities = cluster(G) cohesion = score_all(G, communities) tokens = {'input': 0, 'output': 0} gods = god_nodes(G) surprises = surprising_connections(G, communities) # Try to load existing labels labels = {} labels_path = Path('graphify-out/.graphify_labels.json') if labels_path.exists(): labels_raw = json.loads(labels_path.read_text()) labels = {int(k): v for k, v in labels_raw.items()} else: labels = {cid: f'Community {cid}' for cid in communities} questions = suggest_questions(G, communities, labels) # Generate outputs report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, '${src_dir}', suggested_questions=questions) Path('graphify-out/GRAPH_REPORT.md').write_text(report) to_json(G, communities, 'graphify-out/graph.json') # Save analysis for labels analysis = { 'communities': {str(k): v for k, v in communities.items()}, 'cohesion': {str(k): v for k, v in cohesion.items()}, 'gods': gods, 'surprises': surprises, } Path('graphify-out/.graphify_analysis.json').write_text(json.dumps(analysis, indent=2)) Path('graphify-out/.graphify_labels.json').write_text(json.dumps({str(k): v for k, v in labels.items()})) # Generate HTML(节点过多时跳过可视化,仍保存 json/report) try: to_html(G, communities, 'graphify-out/graph.html', community_labels=labels or None) except ValueError as e: print(f'[graphify] skip HTML viz: {e}') # Save manifest from graphify.detect import save_manifest save_manifest(detection['files']) print(f'Graph updated: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges, {len(communities)} communities') print(f'Report: graphify-out/GRAPH_REPORT.md') " || { echo "[graphify] 增量更新失败" return 1 } # Cleanup temp files(保留 .graphify_extract.json 供下次增量更新) rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_ast.json graphify-out/.graphify_incremental.json cd "$ROOT_DIR" echo "[graphify] ${label} 图谱更新完成 ✓" } # ---- Main ---- TARGET="${1:-all}" echo "╔══════════════════════════════════════╗" echo "║ zkxw 知识图谱增量更新工具 ║" echo "╚══════════════════════════════════════╝" case "$TARGET" in backend) update_graph "backend" "app" "后端" ;; frontend) update_graph "web-admin" "src" "前端" ;; all|"") update_graph "backend" "app" "后端" update_graph "web-admin" "src" "前端" ;; *) echo "用法: $0 [backend|frontend|all]" exit 1 ;; esac echo "" echo "✅ 知识图谱更新完毕。HTML 可视化: */graphify-out/graph.html"