update-graph.sh 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/bin/bash
  2. # update-graph.sh — 增量更新知识图谱
  3. # 用法:
  4. # ./scripts/update-graph.sh # 更新前后端
  5. # ./scripts/update-graph.sh backend # 只更新后端
  6. # ./scripts/update-graph.sh frontend # 只更新前端
  7. #
  8. # 由 AI Agent 在完成代码修改后自动调用,或手动执行。
  9. set -euo pipefail
  10. ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
  11. PYTHON=""
  12. # 检测 graphify 使用的 Python 解释器
  13. detect_python() {
  14. local graphify_out="$1/graphify-out/.graphify_python"
  15. if [ -f "$graphify_out" ]; then
  16. PYTHON="$(cat "$graphify_out")"
  17. else
  18. PYTHON="python3"
  19. fi
  20. }
  21. # 检查 graphify 是否安装
  22. check_graphify() {
  23. if ! "$PYTHON" -c "import graphify" 2>/dev/null; then
  24. echo "[graphify] 未安装,正在安装..."
  25. "$PYTHON" -m pip install graphifyy -q 2>/dev/null || "$PYTHON" -m pip install graphifyy -q --break-system-packages 2>&1 | tail -3
  26. fi
  27. }
  28. # 增量更新单个项目的图谱
  29. update_graph() {
  30. local name="$1"
  31. local src_dir="$2"
  32. local label="$3"
  33. echo ""
  34. echo "=== 更新 ${label} 知识图谱 (${src_dir}) ==="
  35. detect_python "$ROOT_DIR/$name"
  36. check_graphify
  37. cd "$ROOT_DIR/$name"
  38. local detect_json="graphify-out/.graphify_detect.json"
  39. local manifest_json="graphify-out/manifest.json"
  40. if [ ! -f "$manifest_json" ]; then
  41. echo "[graphify] 未找到 manifest.json,跳过增量更新(首次需运行完整构建)"
  42. return 0
  43. fi
  44. # 运行增量检测
  45. "$PYTHON" -c "
  46. import sys, json
  47. from graphify.detect import detect_incremental, save_manifest
  48. from pathlib import Path
  49. result = detect_incremental(Path('${src_dir}'))
  50. new_total = result.get('new_total', 0)
  51. Path('graphify-out/.graphify_incremental.json').write_text(json.dumps(result))
  52. print(f'{new_total} file(s) changed since last run.')
  53. if new_total == 0:
  54. print('SKIP: Nothing to update.')
  55. sys.exit(0)
  56. "
  57. # 如果没有变化,跳过
  58. local changed=$?
  59. if [ $changed -ne 0 ]; then
  60. return 0
  61. fi
  62. # 检查是否只是代码变更
  63. "$PYTHON" -c "
  64. import json
  65. from pathlib import Path
  66. result = json.loads(Path('graphify-out/.graphify_incremental.json').read_text())
  67. code_exts = {'.py','.ts','.js','.vue','.go','.rs','.java','.cpp','.c','.rb','.swift','.kt','.cs','.scala','.php','.cc','.cxx','.hpp','.h','.kts'}
  68. new_files = result.get('new_files', {})
  69. all_changed = [f for files in new_files.values() for f in files]
  70. code_only = all(Path(f).suffix.lower() in code_exts for f in all_changed) if all_changed else True
  71. print('code_only:', code_only)
  72. " 2>/dev/null
  73. # AST 提取
  74. "$PYTHON" -c "
  75. import sys, json
  76. from graphify.extract import collect_files, extract
  77. from graphify.build import build_from_json
  78. from graphify.cluster import cluster, score_all
  79. from graphify.analyze import god_nodes, surprising_connections, suggest_questions
  80. from graphify.report import generate
  81. from graphify.export import to_json, to_html
  82. from pathlib import Path
  83. # Re-extract AST for all code files (fast, deterministic)
  84. # 注:不依赖旧的 .graphify_extract.json;缺失时从头提取即可
  85. from graphify.detect import detect
  86. detection = detect(Path('${src_dir}'))
  87. Path('graphify-out/.graphify_detect.json').write_text(json.dumps(detection))
  88. code_files = []
  89. for f in detection.get('files', {}).get('code', []):
  90. code_files.extend(collect_files(Path(f)) if Path(f).is_dir() else [Path(f)])
  91. if code_files:
  92. result = extract(code_files)
  93. Path('graphify-out/.graphify_extract.json').write_text(json.dumps(result, indent=2))
  94. print(f'AST: {len(result[\"nodes\"])} nodes, {len(result[\"edges\"])} edges')
  95. else:
  96. print('No code files found.')
  97. sys.exit(1)
  98. # Build graph
  99. G = build_from_json(result)
  100. communities = cluster(G)
  101. cohesion = score_all(G, communities)
  102. tokens = {'input': 0, 'output': 0}
  103. gods = god_nodes(G)
  104. surprises = surprising_connections(G, communities)
  105. # Try to load existing labels
  106. labels = {}
  107. labels_path = Path('graphify-out/.graphify_labels.json')
  108. if labels_path.exists():
  109. labels_raw = json.loads(labels_path.read_text())
  110. labels = {int(k): v for k, v in labels_raw.items()}
  111. else:
  112. labels = {cid: f'Community {cid}' for cid in communities}
  113. questions = suggest_questions(G, communities, labels)
  114. # Generate outputs
  115. report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, '${src_dir}', suggested_questions=questions)
  116. Path('graphify-out/GRAPH_REPORT.md').write_text(report)
  117. to_json(G, communities, 'graphify-out/graph.json')
  118. # Save analysis for labels
  119. analysis = {
  120. 'communities': {str(k): v for k, v in communities.items()},
  121. 'cohesion': {str(k): v for k, v in cohesion.items()},
  122. 'gods': gods,
  123. 'surprises': surprises,
  124. }
  125. Path('graphify-out/.graphify_analysis.json').write_text(json.dumps(analysis, indent=2))
  126. Path('graphify-out/.graphify_labels.json').write_text(json.dumps({str(k): v for k, v in labels.items()}))
  127. # Generate HTML(节点过多时跳过可视化,仍保存 json/report)
  128. try:
  129. to_html(G, communities, 'graphify-out/graph.html', community_labels=labels or None)
  130. except ValueError as e:
  131. print(f'[graphify] skip HTML viz: {e}')
  132. # Save manifest
  133. from graphify.detect import save_manifest
  134. save_manifest(detection['files'])
  135. print(f'Graph updated: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges, {len(communities)} communities')
  136. print(f'Report: graphify-out/GRAPH_REPORT.md')
  137. " || {
  138. echo "[graphify] 增量更新失败"
  139. return 1
  140. }
  141. # Cleanup temp files(保留 .graphify_extract.json 供下次增量更新)
  142. rm -f graphify-out/.graphify_detect.json graphify-out/.graphify_ast.json graphify-out/.graphify_incremental.json
  143. cd "$ROOT_DIR"
  144. echo "[graphify] ${label} 图谱更新完成 ✓"
  145. }
  146. # ---- Main ----
  147. TARGET="${1:-all}"
  148. echo "╔══════════════════════════════════════╗"
  149. echo "║ zkxw 知识图谱增量更新工具 ║"
  150. echo "╚══════════════════════════════════════╝"
  151. case "$TARGET" in
  152. backend)
  153. update_graph "backend" "app" "后端"
  154. ;;
  155. frontend)
  156. update_graph "web-admin" "src" "前端"
  157. ;;
  158. all|"")
  159. update_graph "backend" "app" "后端"
  160. update_graph "web-admin" "src" "前端"
  161. ;;
  162. *)
  163. echo "用法: $0 [backend|frontend|all]"
  164. exit 1
  165. ;;
  166. esac
  167. echo ""
  168. echo "✅ 知识图谱更新完毕。HTML 可视化: */graphify-out/graph.html"