知识图谱执行流程
面向新人的 Graphify 知识图谱系统完整链路说明
精确到每个文件和关键函数,方便定位和调试
一、系统总览
#mermaid-svg-KOe6LZKly0jy66zR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KOe6LZKly0jy66zR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KOe6LZKly0jy66zR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KOe6LZKly0jy66zR .error-icon{fill:#552222;}#mermaid-svg-KOe6LZKly0jy66zR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KOe6LZKly0jy66zR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KOe6LZKly0jy66zR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KOe6LZKly0jy66zR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KOe6LZKly0jy66zR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KOe6LZKly0jy66zR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KOe6LZKly0jy66zR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KOe6LZKly0jy66zR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KOe6LZKly0jy66zR .marker.cross{stroke:#333333;}#mermaid-svg-KOe6LZKly0jy66zR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KOe6LZKly0jy66zR p{margin:0;}#mermaid-svg-KOe6LZKly0jy66zR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KOe6LZKly0jy66zR .cluster-label text{fill:#333;}#mermaid-svg-KOe6LZKly0jy66zR .cluster-label span{color:#333;}#mermaid-svg-KOe6LZKly0jy66zR .cluster-label span p{background-color:transparent;}#mermaid-svg-KOe6LZKly0jy66zR .label text,#mermaid-svg-KOe6LZKly0jy66zR span{fill:#333;color:#333;}#mermaid-svg-KOe6LZKly0jy66zR .node rect,#mermaid-svg-KOe6LZKly0jy66zR .node circle,#mermaid-svg-KOe6LZKly0jy66zR .node ellipse,#mermaid-svg-KOe6LZKly0jy66zR .node polygon,#mermaid-svg-KOe6LZKly0jy66zR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KOe6LZKly0jy66zR .rough-node .label text,#mermaid-svg-KOe6LZKly0jy66zR .node .label text,#mermaid-svg-KOe6LZKly0jy66zR .image-shape .label,#mermaid-svg-KOe6LZKly0jy66zR .icon-shape .label{text-anchor:middle;}#mermaid-svg-KOe6LZKly0jy66zR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KOe6LZKly0jy66zR .rough-node .label,#mermaid-svg-KOe6LZKly0jy66zR .node .label,#mermaid-svg-KOe6LZKly0jy66zR .image-shape .label,#mermaid-svg-KOe6LZKly0jy66zR .icon-shape .label{text-align:center;}#mermaid-svg-KOe6LZKly0jy66zR .node.clickable{cursor:pointer;}#mermaid-svg-KOe6LZKly0jy66zR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KOe6LZKly0jy66zR .arrowheadPath{fill:#333333;}#mermaid-svg-KOe6LZKly0jy66zR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KOe6LZKly0jy66zR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KOe6LZKly0jy66zR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KOe6LZKly0jy66zR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KOe6LZKly0jy66zR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KOe6LZKly0jy66zR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KOe6LZKly0jy66zR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KOe6LZKly0jy66zR .cluster text{fill:#333;}#mermaid-svg-KOe6LZKly0jy66zR .cluster span{color:#333;}#mermaid-svg-KOe6LZKly0jy66zR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KOe6LZKly0jy66zR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KOe6LZKly0jy66zR rect.text{fill:none;stroke-width:0;}#mermaid-svg-KOe6LZKly0jy66zR .icon-shape,#mermaid-svg-KOe6LZKly0jy66zR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KOe6LZKly0jy66zR .icon-shape p,#mermaid-svg-KOe6LZKly0jy66zR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KOe6LZKly0jy66zR .icon-shape .label rect,#mermaid-svg-KOe6LZKly0jy66zR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KOe6LZKly0jy66zR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KOe6LZKly0jy66zR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KOe6LZKly0jy66zR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 运行时(AI 查询)
开发时(构建图谱)
源代码变更
graphify update .
AST 解析
构建图
社群聚类
导出 graph.json
Cursor 启动
MCP Server
加载 graph.json
等待查询
AI 发起查询
返回相关节点
核心文件位置 :.venv\Lib\site-packages\graphify\
| 文件 | 行数 | 职责 |
|---|---|---|
__main__.py |
4583 | CLI 主入口,命令分发 |
serve.py |
1312 | MCP stdio 服务器 + 查询引擎 |
extract.py |
11658 | AST 提取(tree-sitter,30+ 语言) |
build.py |
490 | NetworkX 图构建 |
cluster.py |
273 | Leiden/Louvain 社群聚类 |
report.py |
--- | 生成 GRAPH_REPORT.md |
export.py |
--- | 导出 JSON/HTML |
watch.py |
--- | 增量更新 + 文件监听 |
cache.py |
--- | AST 缓存管理 |
manifest.py |
--- | 文件变更清单追踪 |
二、链路一:Cursor 启动 → MCP 服务就绪 → AI 查询
这是运行时的核心链路:AI 如何查询知识图谱。
Claude/AI graph.json serve.py Python 进程 Cursor IDE Claude/AI graph.json serve.py Python 进程 Cursor IDE #mermaid-svg-mM0Q8uY3M9JdI5qn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mM0Q8uY3M9JdI5qn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mM0Q8uY3M9JdI5qn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mM0Q8uY3M9JdI5qn .error-icon{fill:#552222;}#mermaid-svg-mM0Q8uY3M9JdI5qn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mM0Q8uY3M9JdI5qn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mM0Q8uY3M9JdI5qn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mM0Q8uY3M9JdI5qn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mM0Q8uY3M9JdI5qn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mM0Q8uY3M9JdI5qn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mM0Q8uY3M9JdI5qn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mM0Q8uY3M9JdI5qn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mM0Q8uY3M9JdI5qn .marker.cross{stroke:#333333;}#mermaid-svg-mM0Q8uY3M9JdI5qn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mM0Q8uY3M9JdI5qn p{margin:0;}#mermaid-svg-mM0Q8uY3M9JdI5qn .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mM0Q8uY3M9JdI5qn text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-mM0Q8uY3M9JdI5qn .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-mM0Q8uY3M9JdI5qn .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-mM0Q8uY3M9JdI5qn .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-mM0Q8uY3M9JdI5qn .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-mM0Q8uY3M9JdI5qn #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-mM0Q8uY3M9JdI5qn .sequenceNumber{fill:white;}#mermaid-svg-mM0Q8uY3M9JdI5qn #sequencenumber{fill:#333;}#mermaid-svg-mM0Q8uY3M9JdI5qn #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-mM0Q8uY3M9JdI5qn .messageText{fill:#333;stroke:none;}#mermaid-svg-mM0Q8uY3M9JdI5qn .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mM0Q8uY3M9JdI5qn .labelText,#mermaid-svg-mM0Q8uY3M9JdI5qn .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-mM0Q8uY3M9JdI5qn .loopText,#mermaid-svg-mM0Q8uY3M9JdI5qn .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-mM0Q8uY3M9JdI5qn .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-mM0Q8uY3M9JdI5qn .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-mM0Q8uY3M9JdI5qn .noteText,#mermaid-svg-mM0Q8uY3M9JdI5qn .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-mM0Q8uY3M9JdI5qn .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mM0Q8uY3M9JdI5qn .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mM0Q8uY3M9JdI5qn .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mM0Q8uY3M9JdI5qn .actorPopupMenu{position:absolute;}#mermaid-svg-mM0Q8uY3M9JdI5qn .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-mM0Q8uY3M9JdI5qn .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mM0Q8uY3M9JdI5qn .actor-man circle,#mermaid-svg-mM0Q8uY3M9JdI5qn line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-mM0Q8uY3M9JdI5qn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 启动时读取 .cursor/mcp.json 🟢 服务就绪,等待请求 启动 python.exe -m graphify.serve graphify-out/graph.json__main__.py → serve.py:serve()_build_server(graph_path)_load_graph() → 读取 JSONNetworkX Graph 对象(2494节点)asyncio.run() → stdio_server 就绪call_tool("query_graph", {question: "保存接口"})_maybe_reload() → 检查文件是否变化_query_terms("保存接口") → 分词_score_nodes(G, terms) → IDF 评分_pick_seeds(scored) → 选种子节点_bfs_context(seeds, depth=3) → BFS 遍历_format_result() → 按社群分组 + Token 裁剪返回相关节点和关系文本
关键文件调用栈
.cursor/mcp.json ← 配置入口
└─ command: ".venv/Scripts/python.exe"
└─ args: ["-m", "graphify.serve", "graphify-out/graph.json"]
graphify/__main__.py :: main() ← Python 包入口
└─ 检测 sys.argv → cmd = "serve"
└─ serve.py :: serve(graph_path) ← MCP 服务入口
├─ _load_graph(path) ← 加载图谱
│ └─ json.load() → nx.node_link_graph()
├─ _build_server(graph_path) ← 注册 MCP 工具
│ ├─ list_tools() ← 声明可用工具
│ └─ call_tool(name, args) ← 工具调度路由
└─ asyncio.run(main()) ← 启动 stdio 事件循环
查询引擎内部逻辑
serve.py :: _query_graph_text(G, question, mode, depth, token_budget)
Step 1: 分词
├─ _query_terms(question)
│ ├─ 中文 → jieba.cut() 分词
│ └─ 英文 → re.findall(r"\w+")
Step 2: 节点评分
├─ _score_nodes(G, terms)
│ ├─ 精确匹配 × 1000
│ ├─ 前缀匹配 × 100
│ ├─ 子串匹配 × 1
│ └─ 源文件路径匹配 × 0.5
Step 3: 选择种子节点
├─ _pick_seeds(scored, max_k=3, gap_ratio=0.2)
│ └─ 按分数排序,取 Top-K(置信度间隙过滤)
Step 4: 图遍历
├─ _bfs_context(seeds, depth=3) 或 _dfs_context()
│ ├─ 层 0: 种子节点
│ ├─ 层 1: 直接邻居
│ ├─ 层 2: 二度关系
│ └─ 层 3: 三度关系
Step 5: 格式化返回
└─ _format_result(nodes, edges, token_budget)
├─ 按 community 分组
├─ 去重
└─ 截断至 token 限制
MCP 暴露的 5 个工具
| 工具名 | 功能 | 关键参数 |
|---|---|---|
query_graph |
自然语言查询图谱 | question, mode(bfs/dfs), depth, token_budget |
get_node |
获取节点详情 | label(节点标签或 ID) |
get_neighbors |
获取邻居节点 | label, relation_filter |
list_communities |
列出所有社群 | --- |
get_community |
获取社群内节点 | community_id |
三、链路二:代码变更 → 图谱更新
这是构建时的核心链路:代码变更后如何更新知识图谱。
graphify-out/ report.py cluster.py build.py extract.py manifest.json watch.py graphify CLI 开发者 graphify-out/ report.py cluster.py build.py extract.py manifest.json watch.py graphify CLI 开发者 #mermaid-svg-7pNHIag0p7h6lUY5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7pNHIag0p7h6lUY5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7pNHIag0p7h6lUY5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7pNHIag0p7h6lUY5 .error-icon{fill:#552222;}#mermaid-svg-7pNHIag0p7h6lUY5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7pNHIag0p7h6lUY5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7pNHIag0p7h6lUY5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7pNHIag0p7h6lUY5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7pNHIag0p7h6lUY5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7pNHIag0p7h6lUY5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7pNHIag0p7h6lUY5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7pNHIag0p7h6lUY5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7pNHIag0p7h6lUY5 .marker.cross{stroke:#333333;}#mermaid-svg-7pNHIag0p7h6lUY5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7pNHIag0p7h6lUY5 p{margin:0;}#mermaid-svg-7pNHIag0p7h6lUY5 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7pNHIag0p7h6lUY5 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-7pNHIag0p7h6lUY5 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-7pNHIag0p7h6lUY5 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-7pNHIag0p7h6lUY5 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-7pNHIag0p7h6lUY5 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-7pNHIag0p7h6lUY5 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-7pNHIag0p7h6lUY5 .sequenceNumber{fill:white;}#mermaid-svg-7pNHIag0p7h6lUY5 #sequencenumber{fill:#333;}#mermaid-svg-7pNHIag0p7h6lUY5 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-7pNHIag0p7h6lUY5 .messageText{fill:#333;stroke:none;}#mermaid-svg-7pNHIag0p7h6lUY5 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7pNHIag0p7h6lUY5 .labelText,#mermaid-svg-7pNHIag0p7h6lUY5 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-7pNHIag0p7h6lUY5 .loopText,#mermaid-svg-7pNHIag0p7h6lUY5 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-7pNHIag0p7h6lUY5 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-7pNHIag0p7h6lUY5 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-7pNHIag0p7h6lUY5 .noteText,#mermaid-svg-7pNHIag0p7h6lUY5 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-7pNHIag0p7h6lUY5 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7pNHIag0p7h6lUY5 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7pNHIag0p7h6lUY5 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7pNHIag0p7h6lUY5 .actorPopupMenu{position:absolute;}#mermaid-svg-7pNHIag0p7h6lUY5 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-7pNHIag0p7h6lUY5 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7pNHIag0p7h6lUY5 .actor-man circle,#mermaid-svg-7pNHIag0p7h6lUY5 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-7pNHIag0p7h6lUY5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} tree-sitter AST 解析 图谱更新完成 ✅ graphify update . --forcemain.py → _rebuild_code()读取 manifest.json(历史 hash)扫描文件系统 → 对比 mtime + hash标记变动文件列表extract(changed_files)collect_files() + .graphifyignore 过滤并行: extract_csharp() / extract_js() / ...{nodes: ..., edges: ...}build_from_json(extraction)三层去重 + NetworkX 构建nx.Graph 对象cluster(G, resolution=1.0)Leiden 算法 → 社群分割 → ID 稳定化{community_id: nodes}generate(G, communities)GRAPH_REPORT.mdgraph.json + manifest.json + .graphify_labels.json
关键文件调用栈
命令行: .\.venv\Scripts\graphify update . [--force]
graphify/__main__.py :: main() ← CLI 入口
└─ cmd = "update"
└─ watch.py :: _rebuild_code(path, force, no_cluster)
│
├─ 1. 增量检测
│ ├─ manifest.py :: load_manifest() ← 读取历史清单
│ ├─ 扫描文件系统 mtime
│ └─ 对比 ast_hash → 识别变动文件
│
├─ 2. AST 提取
│ ├─ extract.py :: extract(target) ← 主提取函数
│ │ ├─ collect_files() ← 收集文件(.graphifyignore 过滤)
│ │ ├─ extract_csharp(path) ← C# 提取器
│ │ ├─ extract_js(path) ← JS/Vue 提取器
│ │ ├─ extract_python(path) ← Python 提取器
│ │ └─ ... (30+ 语言)
│ └─ cache.py :: load_cached() / save_cached()
│ └─ 缓存位置: graphify-out/cache/ast/<hash>.json
│
├─ 3. 图构建
│ └─ build.py :: build_from_json(extraction)
│ ├─ _normalize_id() ← 节点 ID 标准化
│ ├─ _deduplicate_by_label() ← 三层去重
│ └─ nx.Graph.add_node() / .add_edge()
│
├─ 4. 社群聚类
│ └─ cluster.py :: cluster(G, resolution=1.0)
│ ├─ _partition(G) ← Leiden 优先,Louvain 备选
│ ├─ 社群分割(>25% 图 → 递归)
│ └─ remap_communities_to_previous() ← ID 稳定化
│
├─ 5. 报告生成
│ └─ report.py :: generate(G, communities)
│ ├─ analyze.py :: god_nodes()
│ └─ analyze.py :: surprising_connections()
│
└─ 6. 导出保存
├─ export.py :: to_json(G) → graphify-out/graph.json
├─ export.py :: to_html(G) → graphify-out/graph.html
└─ manifest.py :: save_manifest() → graphify-out/manifest.json
增量 vs 全量
| 模式 | 命令 | 行为 |
|---|---|---|
| 增量(默认) | graphify update . |
只提取变动文件,保留旧节点 |
| 全量(强制) | graphify update . --force |
重新扫描全部文件 |
增量更新的判定逻辑:
manifest.json 中的 ast_hash ≠ 当前文件内容 hash → 需要重新提取
manifest.json 中的 ast_hash = 当前文件内容 hash → 跳过,复用缓存
四、链路三:入图规则(何时触发更新)
#mermaid-svg-CX0HWL2F7OPfkMPp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CX0HWL2F7OPfkMPp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CX0HWL2F7OPfkMPp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CX0HWL2F7OPfkMPp .error-icon{fill:#552222;}#mermaid-svg-CX0HWL2F7OPfkMPp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CX0HWL2F7OPfkMPp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CX0HWL2F7OPfkMPp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CX0HWL2F7OPfkMPp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CX0HWL2F7OPfkMPp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CX0HWL2F7OPfkMPp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CX0HWL2F7OPfkMPp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CX0HWL2F7OPfkMPp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CX0HWL2F7OPfkMPp .marker.cross{stroke:#333333;}#mermaid-svg-CX0HWL2F7OPfkMPp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CX0HWL2F7OPfkMPp p{margin:0;}#mermaid-svg-CX0HWL2F7OPfkMPp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CX0HWL2F7OPfkMPp .cluster-label text{fill:#333;}#mermaid-svg-CX0HWL2F7OPfkMPp .cluster-label span{color:#333;}#mermaid-svg-CX0HWL2F7OPfkMPp .cluster-label span p{background-color:transparent;}#mermaid-svg-CX0HWL2F7OPfkMPp .label text,#mermaid-svg-CX0HWL2F7OPfkMPp span{fill:#333;color:#333;}#mermaid-svg-CX0HWL2F7OPfkMPp .node rect,#mermaid-svg-CX0HWL2F7OPfkMPp .node circle,#mermaid-svg-CX0HWL2F7OPfkMPp .node ellipse,#mermaid-svg-CX0HWL2F7OPfkMPp .node polygon,#mermaid-svg-CX0HWL2F7OPfkMPp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CX0HWL2F7OPfkMPp .rough-node .label text,#mermaid-svg-CX0HWL2F7OPfkMPp .node .label text,#mermaid-svg-CX0HWL2F7OPfkMPp .image-shape .label,#mermaid-svg-CX0HWL2F7OPfkMPp .icon-shape .label{text-anchor:middle;}#mermaid-svg-CX0HWL2F7OPfkMPp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CX0HWL2F7OPfkMPp .rough-node .label,#mermaid-svg-CX0HWL2F7OPfkMPp .node .label,#mermaid-svg-CX0HWL2F7OPfkMPp .image-shape .label,#mermaid-svg-CX0HWL2F7OPfkMPp .icon-shape .label{text-align:center;}#mermaid-svg-CX0HWL2F7OPfkMPp .node.clickable{cursor:pointer;}#mermaid-svg-CX0HWL2F7OPfkMPp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CX0HWL2F7OPfkMPp .arrowheadPath{fill:#333333;}#mermaid-svg-CX0HWL2F7OPfkMPp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CX0HWL2F7OPfkMPp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CX0HWL2F7OPfkMPp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CX0HWL2F7OPfkMPp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CX0HWL2F7OPfkMPp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CX0HWL2F7OPfkMPp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CX0HWL2F7OPfkMPp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CX0HWL2F7OPfkMPp .cluster text{fill:#333;}#mermaid-svg-CX0HWL2F7OPfkMPp .cluster span{color:#333;}#mermaid-svg-CX0HWL2F7OPfkMPp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CX0HWL2F7OPfkMPp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CX0HWL2F7OPfkMPp rect.text{fill:none;stroke-width:0;}#mermaid-svg-CX0HWL2F7OPfkMPp .icon-shape,#mermaid-svg-CX0HWL2F7OPfkMPp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CX0HWL2F7OPfkMPp .icon-shape p,#mermaid-svg-CX0HWL2F7OPfkMPp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CX0HWL2F7OPfkMPp .icon-shape .label rect,#mermaid-svg-CX0HWL2F7OPfkMPp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CX0HWL2F7OPfkMPp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CX0HWL2F7OPfkMPp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CX0HWL2F7OPfkMPp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 命中
未命中
L0
L1
L2
L3
AI 会话开始
路由器查询图谱
命中?
返回相关节点,继续工作
触发入图流程
分级处理
补充 reference 文件
→ graphify update .
已有文件未索引
→ graphify update .
需新增 Spec/CHG
→ graphify update . --force
路径被 .graphifyignore 过滤
→ 编辑过滤 + --force
图谱更新完成
下次查询命中 ✅
配置文件对应
| 规则/配置 | 文件路径 | 作用 |
|---|---|---|
| 入图规则 | .cursor/rules/05-graphify-entry.mdc |
定义何时必须查图、未命中怎么处理 |
| 运维操作 | .cursor/skills/20-capabilities/10-graphify-ops/SKILL.md |
L0~L3 分级操作手册 |
| 过滤清单 | .graphifyignore |
哪些路径不纳入图谱 |
| MCP 配置 | .cursor/mcp.json |
服务启动命令 |
五、数据流全景图
#mermaid-svg-aSaN2i5FF35tRSuf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aSaN2i5FF35tRSuf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aSaN2i5FF35tRSuf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aSaN2i5FF35tRSuf .error-icon{fill:#552222;}#mermaid-svg-aSaN2i5FF35tRSuf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aSaN2i5FF35tRSuf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aSaN2i5FF35tRSuf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aSaN2i5FF35tRSuf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aSaN2i5FF35tRSuf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aSaN2i5FF35tRSuf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aSaN2i5FF35tRSuf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aSaN2i5FF35tRSuf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aSaN2i5FF35tRSuf .marker.cross{stroke:#333333;}#mermaid-svg-aSaN2i5FF35tRSuf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aSaN2i5FF35tRSuf p{margin:0;}#mermaid-svg-aSaN2i5FF35tRSuf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aSaN2i5FF35tRSuf .cluster-label text{fill:#333;}#mermaid-svg-aSaN2i5FF35tRSuf .cluster-label span{color:#333;}#mermaid-svg-aSaN2i5FF35tRSuf .cluster-label span p{background-color:transparent;}#mermaid-svg-aSaN2i5FF35tRSuf .label text,#mermaid-svg-aSaN2i5FF35tRSuf span{fill:#333;color:#333;}#mermaid-svg-aSaN2i5FF35tRSuf .node rect,#mermaid-svg-aSaN2i5FF35tRSuf .node circle,#mermaid-svg-aSaN2i5FF35tRSuf .node ellipse,#mermaid-svg-aSaN2i5FF35tRSuf .node polygon,#mermaid-svg-aSaN2i5FF35tRSuf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aSaN2i5FF35tRSuf .rough-node .label text,#mermaid-svg-aSaN2i5FF35tRSuf .node .label text,#mermaid-svg-aSaN2i5FF35tRSuf .image-shape .label,#mermaid-svg-aSaN2i5FF35tRSuf .icon-shape .label{text-anchor:middle;}#mermaid-svg-aSaN2i5FF35tRSuf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aSaN2i5FF35tRSuf .rough-node .label,#mermaid-svg-aSaN2i5FF35tRSuf .node .label,#mermaid-svg-aSaN2i5FF35tRSuf .image-shape .label,#mermaid-svg-aSaN2i5FF35tRSuf .icon-shape .label{text-align:center;}#mermaid-svg-aSaN2i5FF35tRSuf .node.clickable{cursor:pointer;}#mermaid-svg-aSaN2i5FF35tRSuf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aSaN2i5FF35tRSuf .arrowheadPath{fill:#333333;}#mermaid-svg-aSaN2i5FF35tRSuf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aSaN2i5FF35tRSuf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aSaN2i5FF35tRSuf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aSaN2i5FF35tRSuf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aSaN2i5FF35tRSuf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aSaN2i5FF35tRSuf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aSaN2i5FF35tRSuf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aSaN2i5FF35tRSuf .cluster text{fill:#333;}#mermaid-svg-aSaN2i5FF35tRSuf .cluster span{color:#333;}#mermaid-svg-aSaN2i5FF35tRSuf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aSaN2i5FF35tRSuf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aSaN2i5FF35tRSuf rect.text{fill:none;stroke-width:0;}#mermaid-svg-aSaN2i5FF35tRSuf .icon-shape,#mermaid-svg-aSaN2i5FF35tRSuf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aSaN2i5FF35tRSuf .icon-shape p,#mermaid-svg-aSaN2i5FF35tRSuf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aSaN2i5FF35tRSuf .icon-shape .label rect,#mermaid-svg-aSaN2i5FF35tRSuf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aSaN2i5FF35tRSuf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aSaN2i5FF35tRSuf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aSaN2i5FF35tRSuf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 消费层
服务层(serve.py)
产物层(graphify-out/)
构建层
提取层(extract.py)
源码层(输入)
.cs 文件
NRD.WebAPI.New/
.vue/.js 文件
nrd-eln/src/
.md 文件
docs/ + .cursor/skills/
.ps1 脚本
.cursor/hooks/
tree-sitter AST 解析
缓存
cache/ast/<hash>.json
NetworkX Graph
build.py
Leiden 聚类
cluster.py
报告生成
report.py
graph.json
2494 节点 · 3038 边
GRAPH_REPORT.md
统计 + 社群导航
graph.html
可视化界面
manifest.json
文件变更追踪
.graphify_labels.json
社群命名
MCP stdio Server
查询引擎
分词→评分→BFS→格式化
Claude / AI 助手
路由器
00-router-skill
六、热重载机制
AI 发起查询
↓
serve.py :: _maybe_reload()
├─ 检查 graph.json 的 mtime
├─ 如果文件变化 → 重新加载
│ └─ _load_graph() → 新的 NetworkX Graph
└─ 如果未变化 → 使用内存缓存
↓
继续执行查询
这意味着:你执行 graphify update 后,无需重启 Cursor,下次 AI 查询会自动使用最新图谱。
七、常用命令速查
powershell
# 增量更新图谱(日常使用)
.\.venv\Scripts\graphify update .
# 全量重建图谱(大改后使用)
.\.venv\Scripts\graphify update . --force
# 仅重新聚类(不重新提取)
.\.venv\Scripts\graphify cluster-only graphify-out/graph.json
# 给社群命名(需要 LLM API Key)
.\.venv\Scripts\graphify label graphify-out/
# 手动启动 MCP 服务(调试用)
.\.venv\Scripts\python.exe -m graphify.serve graphify-out/graph.json
# 查看当前图谱统计
打开 graphify-out/GRAPH_REPORT.md
# 可视化浏览图谱
打开 graphify-out/graph.html
八、新人快速理解要点
1. 两条独立链路
| 链路 | 触发时机 | 关键文件 | 产物 |
|---|---|---|---|
| 构建链路 | 手动执行 graphify update |
extract.py → build.py → cluster.py | graph.json |
| 查询链路 | Cursor 启动后 AI 自动调用 | serve.py(MCP 协议) | 文本上下文 |
2. 三个核心概念
| 概念 | 解释 | 类比 |
|---|---|---|
| 节点(Node) | 代码里的类、方法、配置项、业务术语 | 百科词条 |
| 边(Edge) | 节点之间的关系(调用、继承、引用) | 词条之间的超链接 |
| 社群(Community) | 紧密关联的一组节点 | 百科的分类目录 |
3. 为什么需要这个系统?
没有图谱:AI 要全局搜索 337 个文件 → 慢、不准、带入无关代码
有了图谱:AI 查一次图 → 精准定位 3-5 个相关节点 → 快、准、上下文紧凑