chore: 立项 graphify-analysis

Graphify v0.4.8 源码全面解析 + GitNexus 替换评估报告,单页深色主题。

- public/index.html: ~600 行报告(架构/流程/逐模块/GraphRAG 验证/GitNexus 对比/建议)
- Dockerfile + nginx.conf: Coolify 静态部署
- .memory/analysis.md: 完整调研笔记
- graphify/ 源码快照不入库(作为只读参考)

结论: 不要替换 GitNexus, 可选抄 Graphify 7 个细节。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kang
2026-04-13 18:27:54 +08:00
commit 857cd7d01b
8 changed files with 771 additions and 0 deletions

544
public/index.html Normal file
View File

@@ -0,0 +1,544 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Graphify 源码解析 & GitNexus 对比 · 2026-04-13</title>
<style>
:root {
--bg: #0b0d12;
--bg-2: #11141b;
--bg-3: #171b24;
--border: #232836;
--fg: #e6e8ee;
--muted: #8b93a7;
--accent: #7aa2f7;
--accent-2: #9ece6a;
--warn: #e0af68;
--bad: #f7768e;
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Noto Sans CJK SC", sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; background: var(--bg); color: var(--fg); font-family: var(--sans); -webkit-font-smoothing: antialiased; }
body { line-height: 1.65; font-size: 15px; }
.wrap { max-width: 980px; margin: 0 auto; padding: 48px 28px 96px; }
header { border-bottom: 1px solid var(--border); padding-bottom: 20px; margin-bottom: 40px; }
header .eyebrow { color: var(--muted); font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; }
header h1 { font-size: 28px; font-weight: 600; margin: 8px 0 6px; }
header .sub { color: var(--muted); font-size: 14px; }
header .meta { margin-top: 14px; display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }
header .meta span { background: var(--bg-3); padding: 4px 10px; border-radius: 12px; border: 1px solid var(--border); }
h2 { font-size: 20px; font-weight: 600; margin: 52px 0 16px; padding-top: 18px; border-top: 1px solid var(--border); }
h2:first-of-type { border-top: none; padding-top: 0; }
h3 { font-size: 16px; font-weight: 600; margin: 28px 0 10px; color: var(--fg); }
h4 { font-size: 14px; font-weight: 600; margin: 18px 0 8px; color: var(--accent); }
p { margin: 0 0 14px; }
code { font-family: var(--mono); font-size: 0.88em; background: var(--bg-3); padding: 1px 6px; border-radius: 4px; color: var(--accent-2); }
a { color: var(--accent); text-decoration: none; border-bottom: 1px dashed var(--border); }
a:hover { border-bottom-color: var(--accent); }
ul, ol { margin: 0 0 14px; padding-left: 22px; }
li { margin-bottom: 6px; }
.card { background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; padding: 18px 22px; margin: 14px 0; }
.card-accent { border-left: 3px solid var(--accent); }
.card-good { border-left: 3px solid var(--accent-2); }
.card-warn { border-left: 3px solid var(--warn); }
.card-bad { border-left: 3px solid var(--bad); }
.tldr { background: linear-gradient(180deg, rgba(122,162,247,0.06), transparent); border: 1px solid var(--border); border-radius: 12px; padding: 20px 24px; margin-bottom: 28px; }
.tldr .label { font-size: 12px; letter-spacing: 0.1em; color: var(--accent); text-transform: uppercase; margin-bottom: 8px; }
.tldr .verdict { font-size: 18px; font-weight: 600; line-height: 1.5; }
table { width: 100%; border-collapse: collapse; margin: 14px 0 24px; font-size: 13.5px; }
th, td { border: 1px solid var(--border); padding: 10px 12px; text-align: left; vertical-align: top; }
th { background: var(--bg-3); font-weight: 600; color: var(--fg); }
td.dim { color: var(--muted); }
td.yes { color: var(--accent-2); font-weight: 600; }
td.no { color: var(--bad); }
td.partial { color: var(--warn); }
.num { text-align: right; font-family: var(--mono); }
.ref { color: var(--muted); font-family: var(--mono); font-size: 12px; }
.pill { display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-3); color: var(--muted); margin-right: 4px; font-family: var(--mono); }
.pill-good { color: var(--accent-2); border-color: rgba(158,206,106,0.3); }
.pill-warn { color: var(--warn); border-color: rgba(224,175,104,0.3); }
.pill-bad { color: var(--bad); border-color: rgba(247,118,142,0.3); }
pre { background: var(--bg-3); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; overflow-x: auto; font-family: var(--mono); font-size: 12.5px; line-height: 1.55; color: #cdd6f4; }
pre .c { color: var(--muted); }
pre .k { color: var(--accent); }
pre .s { color: var(--accent-2); }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) { .grid-2 { grid-template-columns: 1fr; } }
.toc { background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; padding: 16px 22px; margin-bottom: 32px; }
.toc .label { font-size: 11px; letter-spacing: 0.12em; color: var(--muted); text-transform: uppercase; margin-bottom: 10px; }
.toc ol { margin: 0; padding-left: 20px; font-size: 13.5px; }
.toc li { margin-bottom: 3px; }
footer { margin-top: 72px; padding-top: 20px; border-top: 1px solid var(--border); color: var(--muted); font-size: 12px; }
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="eyebrow">源码评估报告</div>
<h1>Graphify 全面解析 &amp; GitNexus 替换评估</h1>
<div class="sub">"看下这个项目是不是噱头 · 需不需要把工作看板的代码图谱换成这个"</div>
<div class="meta">
<span>日期 2026-04-13</span>
<span>Graphify v0.4.8 · 8,237 LOC · Python</span>
<span>GitNexus · 31,367 LOC · TypeScript</span>
<span>仓库快照 <code>~/Projects/research/20260413-graphify-analysis/</code></span>
</div>
</header>
<div class="tldr">
<div class="label">结论</div>
<div class="verdict">
Graphify 不是噱头,是一个<b>被过度营销的实用单项目 AST 索引工具</b>,"GraphRAG / 多模态 AI 架构助理" 是包装话术。
<b>不要替换工作看板的 GitNexus</b> —— GitNexus 在代码静态分析这一维度比 Graphify 领先一个量级。
</div>
</div>
<div class="toc">
<div class="label">目录</div>
<ol>
<li><a href="#overview">一、Graphify 架构速览</a></li>
<li><a href="#pipeline">二、执行流程 &amp; 关键发现</a></li>
<li><a href="#modules">三、核心模块逐个解剖(带行号)</a></li>
<li><a href="#graphrag">四、"GraphRAG" 噱头验证</a></li>
<li><a href="#bench">五、"71.5× token 节省" 真相</a></li>
<li><a href="#highlights">六、设计亮点(可抄)</a></li>
<li><a href="#weaknesses">七、技术短板</a></li>
<li><a href="#vs">八、GitNexus vs Graphify 对比</a></li>
<li><a href="#recommendation">九、最终建议</a></li>
</ol>
</div>
<h2 id="overview">一、Graphify 架构速览</h2>
<p>一句话:<b>tree-sitter AST 提取器 + networkx 图 + Leiden 社区检测 + 10 种导出格式 + 9 种 Agent 平台集成脚本</b>
零 LLM 依赖,"智能"部分全部外包给宿主 Agent(Claude Code / Codex / OpenCode / ...)。</p>
<table>
<thead><tr><th>文件</th><th class="num">行数</th><th>作用</th></tr></thead>
<tbody>
<tr><td><code>extract.py</code></td><td class="num">2804</td><td>tree-sitter AST 提取(22 语言)—— <b>真正的发动机</b></td></tr>
<tr><td><code>export.py</code></td><td class="num">1001</td><td>HTML / SVG / GraphML / Cypher / Obsidian 导出</td></tr>
<tr><td><code>__main__.py</code></td><td class="num">972</td><td>CLI + 9 种 AI 平台 install/uninstall 脚本</td></tr>
<tr><td><code>analyze.py</code></td><td class="num">537</td><td>god nodes(度数排序)+ surprising connections(跨社区边)</td></tr>
<tr><td><code>detect.py</code></td><td class="num">502</td><td>文件扫描 + 类型识别(含论文启发式)+ .graphifyignore</td></tr>
<tr><td><code>serve.py</code></td><td class="num">364</td><td>MCP stdio server(7 个工具)</td></tr>
<tr><td><code>ingest.py</code></td><td class="num">297</td><td>URL 抓取:twitter/arxiv/github/youtube/pdf/image/web</td></tr>
<tr><td><code>hooks.py</code></td><td class="num">198</td><td>git post-commit/checkout 自动重建(纯 AST)</td></tr>
<tr><td><code>security.py</code></td><td class="num">197</td><td>SSRF 防护 + URL 校验 + redirect 再验证</td></tr>
<tr><td><code>watch.py</code></td><td class="num">183</td><td>文件监听,代码变更增量重建</td></tr>
<tr><td><code>transcribe.py</code></td><td class="num">182</td><td>whisper 视频/音频转写(可选)</td></tr>
<tr><td><code>report.py</code></td><td class="num">175</td><td>GRAPH_REPORT.md 生成</td></tr>
<tr><td><code>cache.py</code></td><td class="num">154</td><td>SHA256 文件级缓存(markdown 剥 YAML frontmatter)</td></tr>
<tr><td><code>cluster.py</code></td><td class="num">137</td><td>Leiden / Louvain 社区检测 + 大社区拆分</td></tr>
<tr><td><code>benchmark.py</code></td><td class="num">129</td><td>token reduction 计算 —— 71.5× 来源</td></tr>
<tr><td><code>build.py</code></td><td class="num">87</td><td>dict → networkx.Graph 组装</td></tr>
<tr><td colspan="3" class="dim">(其余小文件:validate / wiki / __init__ / manifest · 共 ~130 行)</td></tr>
</tbody>
<tfoot><tr><th>合计</th><th class="num">8,237</th><th class="dim">20 个 .py 文件 · MIT License · PyPI: <code>graphifyy</code></th></tr></tfoot>
</table>
<h3>依赖(全部本地库,无 LLM SDK)</h3>
<pre><span class="c"># 核心</span>
networkx
tree-sitter &gt;= 0.23.0
tree-sitter-{python,javascript,typescript,go,rust,java,c,cpp,ruby,
c-sharp,kotlin,scala,php,swift,lua,zig,powershell,
elixir,objc,julia} <span class="c"># 22 种语言</span>
<span class="c"># 可选 extras</span>
mcp <span class="c"># MCP server</span>
neo4j <span class="c"># 直推图数据库</span>
pypdf <span class="c"># PDF 文本提取</span>
graspologic <span class="c"># Leiden(否则回落 Louvain)</span>
python-docx <span class="c"># Office</span>
faster-whisper <span class="c"># 视频转写</span>
yt-dlp <span class="c"># YouTube 下载</span></pre>
<h2 id="pipeline">二、执行流程 &amp; 关键发现</h2>
<div class="card card-accent">
<h4>关键发现:完整 pipeline 不在 CLI 里</h4>
<p><code>__main__.py</code><code>main()</code> 只有 <code>install / query / benchmark / save-result / hook</code> 等子命令
(<span class="ref">__main__.py:780-968</span>)—— <b>根本没有 "build graph" 子命令</b></p>
<p>完整 pipeline 是被 <code>skill.md</code> 驱动的:宿主 Agent 按 skill.md 的 step 1..N,用内联
<code>python3 -c "from graphify.X import Y"</code> 挨个调各模块。这种"skill 脚本驱动"设计
把 LLM 部分合法地外包给宿主 Agent,Python 包就完全 offline。</p>
</div>
<pre><span class="c"># 实际执行顺序(由 skill.md 协调)</span>
detect.detect(root) <span class="c"># detect.py:329 扫描+分类</span>
<span class="c">并行</span>
├── extract.extract(code_paths) <span class="c"># extract.py:2639 tree-sitter AST</span>
└── [<span class="k">宿主 Agent subagent 读 PDF / 图 / markdown</span>] → JSON
cache.save_semantic_cache(...) <span class="c"># cache.py:119</span>
build.build([ast_result, semantic_result]) <span class="c"># build.py:84</span>
cluster.cluster(G) <span class="c"># cluster.py:59 Leiden/Louvain</span>
analyze.god_nodes + surprising_connections <span class="c"># analyze.py:42/61</span>
report.generate(...) <span class="c"># report.py:14</span>
export.to_html / to_json / to_obsidian <span class="c"># export.py:329/285/447</span></pre>
<h2 id="modules">三、核心模块逐个解剖</h2>
<h3>3.1 <code>extract.py</code>(2804 行) —— 真正的发动机</h3>
<p><b>架构模式</b>:<code>LanguageConfig</code> dataclass(<span class="ref">extract.py:24-60</span>)+ 通用
<code>_extract_generic()</code>(<span class="ref">extract.py:645-1010</span>)+ 每种语言一个配置实例 + 少数语言特有的 walker。</p>
<pre><span class="k">class</span> LanguageConfig:
class_types <span class="c"># 哪些 tree-sitter 节点类型算"类"</span>
function_types <span class="c"># 哪些算"函数"</span>
import_types
call_types
name_field <span class="c"># 默认从哪个 field 拿名字</span>
body_field
function_boundary_types <span class="c"># 递归到这里停</span>
import_handler <span class="c"># 每种语言 import 语法不一样,单独回调</span>
resolve_function_name_fn <span class="c"># C/C++ 用 declarator 解包</span>
extra_walk_fn <span class="c"># JS 箭头、C# namespace 等特殊分支</span></pre>
<p>13 种语言走通用路径(<span class="ref">extract.py:419-627</span>),<b>7 种独立手写</b>(因为 tree-sitter 节点命名差太大):
<code>extract_go</code>(190 行)<code>extract_rust</code>(168 行)<code>extract_zig</code>
<code>extract_powershell</code><code>extract_objc</code><code>extract_elixir</code><code>extract_julia</code></p>
<p><b>两遍 walk</b>:</p>
<ol>
<li><b>结构 walk</b>(<span class="ref">extract.py:703-866</span>):进 class 建节点 + <code>contains/method/inherits</code> 边;进 function 记下 body 挂到 <code>function_bodies</code> 列表</li>
<li><b>调用图 walk</b>(<span class="ref">extract.py:877+</span>):对每个 function body 再 walk,找 callee 名字,查 <code>label_to_nid</code><code>calls</code></li>
</ol>
<div class="card card-good">
<h4>亮点: Python 独有的 <code>_resolve_cross_file_imports</code></h4>
<p><span class="ref">extract.py:2110-2240</span>,130 行 —— 整个项目 <b>唯一真正跨文件推理</b>的地方。</p>
<p>Pass 1:扫所有 Python 文件的 AST 节点,建 <code>stem_to_entities[stem][ClassName] = nid</code> 全局映射表。<br>
Pass 2:对每个文件找 <code>from .models import Response</code>,在映射表里查到 <code>Response</code> 的 nid,然后<b>对当前文件里的每个 class</b>,添加 <code>DigestAuth --uses--&gt; Response</code> 的 INFERRED 边。</p>
<p>这是"god nodes / surprising connections"最后能出东西的关键 —— 没这步,Python 项目全是 <code>contains</code> 边的垃圾。<b>其他 21 种语言都没做这个</b>,只有结构 + 同文件 calls。</p>
</div>
<h3>3.2 <code>detect.py</code>(502 行) —— 文件扫描</h3>
<ul>
<li><code>_looks_like_paper()</code>(<span class="ref">detect.py:68</span>)用 13 条正则(arxiv / doi / <code>\cite{}</code> / <code>\d{4}\.\d{4,5}</code>)判断一个 <code>.md</code> 是不是学术论文转出来的 —— 命中 ≥3 条升级为 PAPER</li>
<li><code>_SENSITIVE_PATTERNS</code>(<span class="ref">detect.py:33-40</span>)过滤 <code>.env / .pem / id_rsa / aws_credentials</code> —— <b>默认不读密钥文件</b></li>
<li><code>_SKIP_DIRS</code>(<span class="ref">detect.py:238</span>)自动跳 <code>venv / node_modules / .git / dist / build</code></li>
<li><code>.graphifyignore</code> 支持向上查找到 <code>.git</code> 目录为止(<span class="ref">detect.py:266</span>)</li>
<li><code>convert_office_file</code>(<span class="ref">detect.py:194</span>)把 .docx / .xlsx 转 markdown sidecar,哈希命名避冲突</li>
</ul>
<h3>3.3 <code>cache.py</code>(154 行) —— SHA256 内容缓存</h3>
<ul>
<li><code>file_hash = SHA256(content) ⊕ SHA256(resolved_path)</code>(<span class="ref">cache.py:20</span>)—— 路径加进哈希防同内容碰撞</li>
<li><b>贴心细节</b>:Markdown 的 YAML frontmatter 会先剥离再哈希(<span class="ref">cache.py:10-17</span>),metadata 变更(reviewed/status/tags)不触发重抽取</li>
<li>存成 <code>graphify-out/cache/{hash}.json</code>,<code>os.replace</code> 原子写</li>
</ul>
<h3>3.4 <code>cluster.py</code>(137 行) —— Leiden / Louvain</h3>
<ul>
<li>先试 <code>graspologic.partition.leiden</code>,失败 fallback 到 <code>networkx.community.louvain_communities</code>(<span class="ref">cluster.py:22-52</span>)</li>
<li>孤立点(degree 0)Leiden 会 warning,单独抠出来每个自成一个社区(<span class="ref">cluster.py:77-91</span>)</li>
<li>大社区限制 <code>_MAX_COMMUNITY_FRACTION = 0.25</code>:超过总节点 25% 的社区二次 Leiden 拆分(<span class="ref">cluster.py:93</span>)</li>
<li>最后按 size 降序重编号 —— 社区 0 永远是最大的(可复现)</li>
<li>PowerShell 5.1 缓冲污染 workaround:graspologic 会吐 ANSI 转义,专门包了 <code>redirect_stdout</code>(<span class="ref">cluster.py:11-20</span>,issue #19)</li>
</ul>
<h3>3.5 <code>analyze.py</code>(537 行) —— "智能"层</h3>
<p><b>其实都是 networkx 一行题</b></p>
<p><code>god_nodes</code>(<span class="ref">analyze.py:42</span>)= <code>dict(G.degree())</code> 排序,过滤掉文件节点 + concept 节点,取 top_n。
所谓"god nodes" = 度数最高的真实实体。<code>_is_file_node</code>(<span class="ref">analyze.py:11-38</span>)过滤三类伪节点:文件级 hub、<code>.method()</code> 方法桩、孤立 <code>func()</code></p>
<p><code>_surprise_score()</code>(<span class="ref">analyze.py:131-184</span>)是一个朴素加权:</p>
<ul>
<li>置信度加成:AMBIGUOUS +3 / INFERRED +2 / EXTRACTED +1</li>
<li>跨文件类型(code↔paper/image)+2</li>
<li>跨 repo(顶级目录不同)+2</li>
<li>跨社区(Leiden 说结构上远)+1</li>
<li><code>semantically_similar_to</code> 关系 ×1.5</li>
<li>低度节点(≤2)连到高度节点(≥5)+1,生成 "peripheral → hub" 叙述</li>
</ul>
<h3>3.6 <code>serve.py</code>(364 行) —— MCP 服务器</h3>
<p>暴露 7 个 MCP 工具:<code>query_graph</code><code>get_node</code><code>get_neighbors</code><code>get_community</code><code>god_nodes</code><code>graph_stats</code><code>shortest_path</code>(<span class="ref">serve.py:156-226</span>)。</p>
<div class="card card-warn">
<h4>注意:"查询"不是语义搜索</h4>
<p><code>_score_nodes</code>(<span class="ref">serve.py:42</span>)就是把问题 split 成 &gt;2 字符的词,
在 label 里子串匹配 +1 / source_file +0.5,排序取 top3 当 BFS 起点。
<b>没有 embedding、没有 BM25、甚至没有 stemming</b>。对中英混合语料很不友好。</p>
</div>
<h3>3.7 <code>export.py</code>(1001 行) —— 十种输出</h3>
<ul>
<li><code>to_json</code> / <code>to_cypher</code> / <code>to_html</code>(交互 vis.js 单文件)/ <code>to_obsidian</code>(每节点一 md,每社区一 hub,带 wikilinks)</li>
<li><code>to_canvas</code>(Obsidian Canvas)/ <code>push_to_neo4j</code>(bolt 协议直推)/ <code>to_graphml</code>(Gephi/yEd)/ <code>to_svg</code></li>
<li><code>MAX_NODES_FOR_VIZ = 5_000</code>(<span class="ref">export.py:19</span>)—— 超过就不出 HTML</li>
</ul>
<h3>3.8 <code>security.py</code>(197 行) —— SSRF 防护</h3>
<p><code>validate_url</code> 解析 hostname → <code>getaddrinfo</code> → 拒绝
<code>is_private / is_reserved / is_loopback / is_link_local</code>,加黑名单 <code>metadata.google.internal</code>
(<span class="ref">security.py:14-64</span>)。<code>_NoFileRedirectHandler</code> 重写 <code>HTTPRedirectHandler</code>,
每次 redirect 都重新校验一遍 —— 防 open-redirect SSRF。<b>写一个抓取模块就自带这种层次的防护,不常见。</b></p>
<h2 id="graphrag">四、"GraphRAG" 噱头验证</h2>
<p>"GraphRAG" 按微软原论文定义:<b>LLM 抽实体 + LLM 抽关系 + LLM 写社区摘要 + 分层答案合成</b>。Graphify 对齐情况:</p>
<table>
<thead><tr><th>GraphRAG 环节</th><th>Graphify 做法</th><th>状态</th></tr></thead>
<tbody>
<tr><td>LLM 抽实体</td><td>tree-sitter AST(代码)/ 宿主 Agent subagent(非代码)</td><td class="no"></td></tr>
<tr><td>LLM 抽关系</td><td>AST + 字符串 heuristics</td><td class="no"></td></tr>
<tr><td>社区检测</td><td>Leiden / Louvain</td><td class="yes"></td></tr>
<tr><td>LLM 写社区摘要</td><td>只有 label 模板,宿主 Agent 可能帮忙写</td><td class="no"></td></tr>
<tr><td>分层答案合成</td><td>BFS + term-frequency 子串匹配</td><td class="no"></td></tr>
</tbody>
</table>
<p><b>结论</b>:这不是 GraphRAG,是"一个有置信度标签的 tree-sitter 代码图谱工具 + Obsidian 导出器 + 9 个 Agent 平台的 SKILL.md 胶水"。
技术本身合格(8k LOC 大部分在 extract.py 真的干活),但把它叫 "GraphRAG AI 架构助理" 是营销话术。</p>
<h2 id="bench">五、"71.5× token 节省" 真相</h2>
<p>拆开 <code>benchmark.py</code> 看(<span class="ref">benchmark.py:1-130</span>):</p>
<pre>_CHARS_PER_TOKEN = 4
corpus_tokens = corpus_words * 100 // 75 <span class="c"># 1 词 ≈ 1.33 token</span>
query_tokens = BFS 子图渲染文本长度 // 4
reduction_ratio = corpus_tokens / avg(query_tokens of 5 个固定英文问题)
<span class="c"># 5 个固定问题(benchmark.py:55-61)</span>
"how does authentication work"
"what is the main entry point"
"how are errors handled"
"what connects the data layer to the api"
"what are the core abstractions"</pre>
<div class="card card-warn">
<h4>71.5× 是怎么来的</h4>
<p>= "整个语料的 token 数" 除以 "单次 BFS 子图的 token 数"。<b>不包含建图时烧掉的 subagent token</b></p>
<p><b>代码库</b>很好看(AST 零 token + 后续查询便宜),对<b>论文/截图</b>混合语料有水分 —— 首次建图那一次 PDF subagent 读取的 token 会把 ratio 稀释。</p>
<p>而且固定 5 个英文问题 + 子串匹配选起点 —— 换一个中文项目可能选不到起点直接返回 0。</p>
</div>
<h2 id="highlights">六、设计亮点(这些可以抄)</h2>
<div class="grid-2">
<div class="card card-good">
<h4>1. LanguageConfig 模式</h4>
<p><span class="ref">extract.py:24</span>。通用 walker + 每语言差异填配置。13 种语言共用 200 行通用代码。</p>
</div>
<div class="card card-good">
<h4>2. SHA256 内容缓存 + Markdown 剥 frontmatter</h4>
<p><span class="ref">cache.py:20</span>。"改 metadata 不触发重抽取"这种细节。</p>
</div>
<div class="card card-good">
<h4>3. 三档置信度 + score</h4>
<p>每条边打 EXTRACTED/INFERRED/AMBIGUOUS,INFERRED 再带 0.6-0.95 的 <code>confidence_score</code>,报告里直接出现 "avg confidence: 0.78",<b>诚实度拉满</b></p>
</div>
<div class="card card-good">
<h4>4. Python 跨文件 class 级 uses 边</h4>
<p><span class="ref">extract.py:2110</span>。让 god nodes 变得有意义的关键。</p>
</div>
<div class="card card-good">
<h4>5. 单 HTML 交互图</h4>
<p><span class="ref">export.py:329</span>。内嵌 vis.js,无需服务器,发一个文件给客户直接看。</p>
</div>
<div class="card card-good">
<h4>6. 9 平台集成脚本</h4>
<p><span class="ref">__main__.py:49-105</span>。Claude/Codex/Cursor/OpenCode/Aider/Copilot/OpenClaw/Factory/Trae/Antigravity/Gemini。</p>
</div>
<div class="card card-good">
<h4>7. SSRF 防护默认打开</h4>
<p><span class="ref">security.py:14-64</span>。默认写一个 URL 抓取模块就有这种层次的防护,业界少见。</p>
</div>
<div class="card card-good">
<h4>8. 大社区自动拆分</h4>
<p><span class="ref">cluster.py:93</span>&gt;25% 超大社区二次 Leiden 拆,防退化。</p>
</div>
</div>
<h2 id="weaknesses">七、技术短板</h2>
<ol>
<li><b>22 语言"支持"很不均匀</b>:只有 Python 做了跨文件 uses 推断,其他 21 种只有"同文件级 calls + imports"。多语言 polyglot 项目图质量会肉眼可见不如 Python。</li>
<li><b><code>_extract_python_rationale</code></b>(<span class="ref">extract.py:1011</span>)基于 docstring 的 "X because Y" heuristics 脆得很。</li>
<li><b>查询不是语义搜索</b>,是子串 +1/+0.5 打分,没有 embedding/BM25/stemming。中英混合语料不友好。</li>
<li><b><code>_make_id</code><code>re.sub(r"[^a-zA-Z0-9]+", "_", combined)</code></b>(<span class="ref">extract.py:14</span>)—— 对中文/非 ASCII 标识符直接 collapse 成空串,<b>中文代码库识别不了</b></li>
<li><b>C++ 模板/宏、Python metaclass/装饰器动态类、JS Proxy/Reflect</b> 这类 AST 看不穿的东西通通识别不出来 —— 本质上是静态结构提取。</li>
<li><b>benchmark 的 71.5×</b>:固定 5 个英文问题 + 子串匹配选起点。</li>
<li><b>无类型系统</b>:没有 symbol table、没有 type env、没有方法解析顺序(MRO)。</li>
<li><b><code>_is_file_node</code> 启发式</b>(<span class="ref">analyze.py:11-38</span>)在非 Python 语言下容易误伤 —— Go/Rust 函数都是 <code>()</code> 结尾,又按 degree 判断……补丁摞补丁。</li>
<li><b>PyPI 包名 <code>graphifyy</code></b>(两个 y,因为 <code>graphify</code> 被占),看得出作者赶时间。</li>
</ol>
<h2 id="vs">八、GitNexus vs Graphify 对比</h2>
<p>你的工作看板现在用的是 <code>~/Projects/code/20260319-gitnexus/</code>(来源 <code>abhigyanpatwari/GitNexus</code>,17.8k stars,浏览器端 WASM 方案)。</p>
<table>
<thead>
<tr><th>维度</th><th>GitNexus(你已有)</th><th>Graphify</th></tr>
</thead>
<tbody>
<tr>
<td>代码规模</td>
<td class="yes">31,367 行 TypeScript</td>
<td class="partial">8,237 行 Python</td>
</tr>
<tr>
<td>架构</td>
<td class="yes">浏览器 WASM 零服务器<br><span class="pill">Tree-sitter</span><span class="pill">LadybugDB</span><span class="pill">Sigma.js</span></td>
<td class="partial">Python CLI + networkx 内存图</td>
</tr>
<tr>
<td>图数据库</td>
<td class="yes">LadybugDB(Cypher 查询)</td>
<td class="no">JSON 文件</td>
</tr>
<tr>
<td>类型系统</td>
<td class="yes"><code>symbol-table.ts</code> + <code>type-env.ts</code> + <code>type-extractors/</code> —— 真的做类型推导</td>
<td class="no"></td>
</tr>
<tr>
<td>Python MRO</td>
<td class="yes"><code>mro-processor.ts</code> —— 方法解析顺序</td>
<td class="no"></td>
</tr>
<tr>
<td>框架识别</td>
<td class="yes"><code>framework-detection.ts</code></td>
<td class="no"></td>
</tr>
<tr>
<td>入口打分</td>
<td class="yes"><code>entry-point-scoring.ts</code></td>
<td class="no"></td>
</tr>
<tr>
<td>跨文件 resolver</td>
<td class="yes"><code>resolvers/</code> + <code>resolution-context.ts</code> + <code>named-binding-extraction.ts</code></td>
<td class="partial">只有 Python 做了 130 行</td>
</tr>
<tr>
<td>社区检测</td>
<td class="yes">Leiden + <code>cluster-enricher.ts</code></td>
<td class="yes">Leiden/Louvain</td>
</tr>
<tr>
<td>可视化</td>
<td class="yes">Sigma.js Web UI @ 4090</td>
<td class="yes">单 HTML + vis.js</td>
</tr>
<tr>
<td>多项目面板</td>
<td class="yes">Web UI 常驻</td>
<td class="no">每项目一目录,一次性</td>
</tr>
<tr>
<td>MCP Server</td>
<td class="yes"><code>gitnexus/src/mcp/</code></td>
<td class="yes"><code>serve.py</code>(7 工具)</td>
</tr>
<tr>
<td>非代码输入(PDF/图/视频)</td>
<td class="no"></td>
<td class="yes">支持(外包给 subagent)</td>
</tr>
<tr>
<td>Obsidian 导出</td>
<td class="no"></td>
<td class="yes">有(每节点一 md + 社区 hub)</td>
</tr>
<tr>
<td>Agent 平台集成</td>
<td class="partial">Claude plugin + Cursor integration</td>
<td class="yes">9 种平台</td>
</tr>
<tr>
<td>诚信度标签</td>
<td class="partial">未知</td>
<td class="yes">EXTRACTED/INFERRED/AMBIGUOUS 三档</td>
</tr>
<tr>
<td>LLM 依赖</td>
<td class="partial">LangChain + 多家 LLM(可选增强)</td>
<td class="yes">无硬依赖(委托宿主 Agent)</td>
</tr>
</tbody>
</table>
<div class="card card-good">
<h4>GitNexus 的 <code>gitnexus/src/core/ingestion/</code> 目录(一瞥)</h4>
<pre>ast-cache.ts call-processor.ts call-routing.ts
cluster-enricher.ts community-processor.ts entry-point-scoring.ts
export-detection.ts filesystem-walker.ts framework-detection.ts
heritage-processor.ts import-processor.ts language-config.ts
mro-processor.ts named-binding-extraction.ts
parsing-processor.ts pipeline.ts process-processor.ts
resolution-context.ts structure-processor.ts symbol-table.ts
tree-sitter-queries.ts type-env.ts type-extractors/
resolvers/ workers/</pre>
<p class="dim">光是 ingestion 里就有 20+ 个处理器。在代码静态分析这件事上 <b>GitNexus 已经把 Graphify 吊打了</b></p>
</div>
<h2 id="recommendation">九、最终建议</h2>
<div class="card card-bad">
<h4>不要替换</h4>
<p>GitNexus 在代码静态分析维度领先 Graphify 一个量级:真类型系统、MRO、框架识别、入口打分、完整 resolver —— 这些 Graphify 全都没有。
替换过去你会失去跨项目 Web UI、看板集成、浏览器 WASM 零服务器架构,换来的只是一套更弱的单项目 AST 抽取和几个你不需要的 Obsidian 模板。</p>
</div>
<div class="card card-good">
<h4>保持现状,按需并用</h4>
<p>GitNexus 继续作为<b>工作看板代码图谱主干</b>,Graphify 作为<b>偶尔跑一次的单项目深度报告工具</b>(需要 Karpathy 式 <code>/raw</code> 混合语料场景时)。两者并不冲突。</p>
</div>
<div class="card card-accent">
<h4>可以从 Graphify 借鉴的东西(选抄)</h4>
<ol>
<li><b>三档置信度 EXTRACTED/INFERRED/AMBIGUOUS + <code>confidence_score</code></b> —— 给 GitNexus 的边也加上,报告诚实度立即拉满</li>
<li><b>Markdown 剥 YAML frontmatter 再哈希</b>(<code>cache.py:10-17</code>)—— 细节体验</li>
<li><b>论文启发式识别 <code>_looks_like_paper</code></b>(<code>detect.py:68</code>)—— 13 条正则,简单有效</li>
<li><b>敏感文件过滤 <code>_SENSITIVE_PATTERNS</code></b>(<code>detect.py:33-40</code>)—— 安全默认值</li>
<li><b>SSRF 防护整套</b>(<code>security.py:14-90</code>)—— 如果 GitNexus 要加 URL 抓取功能直接移植</li>
<li><b>大社区 25% 拆分规则</b>(<code>cluster.py:93</code>)—— 防 Leiden 出一个超大社区</li>
<li><b><code>ingest.py</code> 的 URL 分类抓取</b>(297 行:twitter/arxiv/github/youtube/pdf/image/web)—— 给 GitNexus 补非代码输入时有现成逻辑</li>
</ol>
</div>
<div class="card">
<h4>下一步(如果你感兴趣)</h4>
<p>我可以继续深入看 GitNexus 的 <code>gitnexus/src/core/ingestion/pipeline.ts</code>
<code>type-extractors/</code>,给你出一份 "GitNexus 现状 + 可增强点" 的专门评估 —— 这份评估完成后,
决定要不要把上述 Graphify 的借鉴项真正落到 GitNexus 里。</p>
</div>
<footer>
<p>
解析日期 2026-04-13 ·
源码快照 <code>~/Projects/research/20260413-graphify-analysis/graphify/</code>(Graphify v0.4.8, commit <code>04e2960</code>) ·
对照项目 <code>~/Projects/code/20260319-gitnexus/</code> ·
本页由 Claude Code(Opus 4.6 1M)生成
</p>
</footer>
</div>
</body>
</html>