首页
/ graphify /graphify 技能详解:跨框架 Agent 的知识图谱构建、查询与增量更新完整流水线

graphify /graphify 技能详解:跨框架 Agent 的知识图谱构建、查询与增量更新完整流水线

2026-09-04 19:39:46作者:卓艾滢Kingsley

本文围绕 graphify/skill-agents.md 展开——它是 graphify 为「通用 Agent-Skills 框架」(~/.agents/skills 或项目内 ./.agents/skills)生成的技能文件,与 Claude Code 版 graphify/skill.md、Codex 版 graphify/skill-codex.md 等共享同一套核心模板渲染而来。读完后你会掌握:/graphify 命令的全部用法与九步构建流水线(检测、AST + LLM 双路抽取、建图聚类、健康检查、社区标注、导出)、query/path/explain 图查询协议、--update 增量重建机制,以及该技能文件本身由 tools/skillgen 生成体系驱动的「防漂移」工程化细节。

1. 技能定位与 frontmatter

技能文件以 YAML frontmatter 开头,符合 Agent Skills 规范(仅 namedescription 两个字段,由渲染器 tools/skillgen/gen.py_render_frontmatter 严格保证):

---
name: graphify
description: "Use for any question about a codebase, its architecture, file relationships, or project content — especially when graphify-out/ exists, where the question should be treated as a graphify query first. Turns any input (code, docs, papers, images, videos) into a persistent knowledge graph with god nodes, community detection, and query/path/explain tools."
---

其中一句关键契约是:当 graphify-out/ 已存在时,任何关于代码库的问题都应先当作图查询处理,而不是重建。这一约定贯穿全文档(见第 5 节快速路径)。

graphify 的目标一句话概括:把任意文件夹(代码、文档、论文、图片、音视频)变成一个可查询的知识图谱,输出三样东西——交互式 HTML、可直接喂给 GraphRAG 的 JSON、以及人类可读的 GRAPH_REPORT.md。每条边都带 EXTRACTED / INFERRED / AMBIGUOUS 的诚实审计标记,社区发现(community detection)会浮现出跨文档的隐藏关联。

2. 完整命令参考(Usage)

这是技能文档 ## Usage 一节的全部内容,也是 /graphify --help 必须原样打印的区块。默认行为是「对当前目录跑完整流水线并生成 HTML 可视化」;加 --obsidian 额外产出 Obsidian 仓库。

/graphify                                             # full pipeline on current directory (HTML viz; add --obsidian for a vault)
/graphify <path>                                      # full pipeline on specific path
/graphify https://github.com/<owner>/<repo>           # clone repo then run full pipeline on it
/graphify https://github.com/<owner>/<repo> --branch <branch>  # clone a specific branch
/graphify <url1> <url2> ...                           # clone multiple repos, build each, merge into one cross-repo graph
/graphify <path> --mode deep                          # thorough extraction, richer INFERRED edges
/graphify <path> --update                             # incremental - re-extract only new/changed files
/graphify <path> --directed                            # build directed graph (preserves edge direction: source→target)
/graphify <path> --whisper-model medium                # use a larger Whisper model for better transcription accuracy
/graphify <path> --cluster-only                       # rerun clustering on existing graph
/graphify <path> --no-viz                             # skip visualization, just report + JSON
/graphify <path> --html                               # (HTML is generated by default - this flag is a no-op)
/graphify <path> --svg                                # also export graph.svg (embeds in Notion, GitHub)
/graphify <path> --graphml                            # export graph.graphml (Gephi, yEd)
/graphify <path> --neo4j                              # generate graphify-out/cypher.txt for Neo4j
/graphify <path> --neo4j-push bolt://localhost:7687   # push directly to Neo4j
/graphify <path> --falkordb                           # generate graphify-out/cypher.txt for FalkorDB
/graphify <path> --falkordb-push falkordb://localhost:6379   # push directly to FalkorDB
/graphify <path> --mcp                                # start MCP stdio server for agent access
/graphify <path> --watch                              # watch folder, auto-rebuild on code changes (no LLM needed)
/graphify <path> --wiki                               # build agent-crawlable wiki (index.md + one article per community)
/graphify <path> --obsidian --obsidian-dir ~/vaults/my-project  # write vault to custom path (e.g. existing vault)
/graphify add <url>                                   # fetch URL, save to ./raw, update graph
/graphify add <url> --author "Name"                   # tag who wrote it
/graphify add <url> --contributor "Name"              # tag who added it to the corpus
/graphify query "<question>"                          # BFS traversal - broad context
/graphify query "<question>" --dfs                    # DFS - trace a specific path
/graphify query "<question>" --budget 1500            # cap answer at N tokens
/graphify path "AuthModule" "Database"                # shortest path between two concepts
/graphify explain "SwinTransformer"                   # plain-language explanation of a node

参数要点(结合技能正文与配套参考文件):

  • --mode deep:更激进的 INFERRED 边(间接依赖、共享假设、潜在耦合),不确定的一律标 AMBIGUOUS 而不是丢弃。该标志必须透传到 Step 3 Part B 的每个子代理(DEEP_MODE=true)。
  • --directed:构建有向图(DiGraph,保留 source→target 边方向)。在流水线中体现为所有 build_from_json(...) 调用都传入 directed=IS_DIRECTED——技能文档特别强调要像替换 INPUT_PATH 一样把 IS_DIRECTED 替换成 True/False,否则字面量会残留进代码。
  • --update / --cluster-only:非默认子命令,详见第 8 节。
  • 各导出开关(--svg--graphml--neo4j*--falkordb*--mcp--wiki)只在显式传入时才执行,具体流程见 graphify/skills/agents/references/exports.md

3. 调用协议:--help、快速路径与路径规则

技能对 Agent 的「被调用行为」有硬性规定,这部分是理解整个技能如何被执行的核心:

  1. /graphify --help(无其他参数):原样打印 ## Usage 区块后立刻停止——不执行任何命令、不做文件检测、不把路径默认为 .
  2. 快速路径(existing graph):做任何事之前先检查 graphify-out/graph.json 是否存在(相对当前工作目录,即运行命令的项目根)。若它存在,且用户请求的是关于代码库的自然语言问题("X 是怎么工作的?""谁调用了 Y?"),而不是显式重建命令(--update--cluster-only、或暗示全新抽取的裸路径/URL),则跳过 Step 1–5,直接跳到 ## For /graphify query,立即执行 graphify query "<question>"。不跑 detect、不检查语料规模、不要求用户缩小范围——"图已经建好了,用图"。
  3. 未给路径时用 .,不得反问用户路径。
  4. 路径以 https://github.com/http://github.com/ 开头时视为 GitHub URL,先执行 Step 0(克隆 + 跨仓库合并流程见 graphify/skills/agents/references/github-and-merge.md),再用解析后的本地路径继续。

4. 九步构建流水线详解

技能正文按 "Follow these steps in order. Do not skip steps." 组织。所有 bash 块中的 INPUT_PATH 是占位符,Agent 必须替换为用户实际传入的路径;所有 Python 调用都用 $(cat graphify-out/.graphify_python) 取 Step 1 落盘的真实解释器路径,而不是写死 python3

Step 0 - GitHub 仓库与多路径合并

仅当路径是一个或多个 GitHub URL、或要合并多个本地子文件夹时才执行。纯本地路径跳过此步。克隆分支、多仓库合并成单一跨仓图、monorepo 流程都在 graphify/skills/agents/references/github-and-merge.md

Step 1 - 确保 graphify 已安装

技能内置一段解释器探测脚本,依次尝试:uv tool(现代 Mac/Linux 上最可靠)→ graphify 二进制的 shebang(覆盖 pipx 与直接 pip 安装)→ 回退 python3。若 import graphify 失败,优先用 uv tool install --upgrade graphifyy -q 安装,否则 pip install graphifyy(必要时加 --break-system-packages)。成功后写两个持久化状态文件:

# 写解释器路径(跨调用持久)
mkdir -p graphify-out
"$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)"
# 保存扫描根,让无参的 graphify update 知道去哪找
echo "$(cd INPUT_PATH && pwd)" > graphify-out/.graphify_root

若 import 成功则什么都不打印,直接进入 Step 2。文档同时明确:包名是 graphifyy(PyPI),导入名是 graphify

Step 2 - 文件检测(detect)

$(cat graphify-out/.graphify_python) -c "
import json
from graphify.detect import detect
from pathlib import Path
result = detect(Path('INPUT_PATH'))
# 由 Python 写 sidecar 而非 shell 重定向,
# 避免 PowerShell 宿主上的控制台编码漂移(#2528)
Path('graphify-out/.graphify_detect.json').write_text(json.dumps(result, ensure_ascii=False), encoding="utf-8")
print(f'Detected {result[\"total_files\"]} files')
"

从源码看,detect() 定义在 graphify/detect.py,签名接受 follow_symlinksgoogle_workspaceextra_excludescache_rootgitignore 等参数。检测结果写入 .graphify_detect.json 后,Agent 不 cat 原始 JSON,而是静默读取并给出干净的汇总(0 文件的类别直接省略):

Corpus: X files · ~Y words
  code:     N files (.py .ts .go ...)
  docs:     N files (.md .txt ...)
  papers:   N files (.pdf ...)
  images:   N files
  video:    N files (.mp4 .mp3 ...)

然后基于结果行动,这是 Honesty Rules 的落地:

  • total_files == 0:停止并报告 "No supported files found in [path]."。
  • skipped_sensitive 非空:报告数量并列出被跳过的文件名,让被误伤的源码/文档可见、可改名修复(#2106)。
  • total_words > 2,000,000total_files > 500:必须展示警告,然后按文件数统计前 5 个一级子目录,等待用户选择再继续。统计算法:从 detect JSON 读 scan_root(解析后 INPUT_PATH 的绝对路径),合并 code/document/paper/image/video 全部文件列表,剔除以 scan_root + "/graphify-out/" 开头的转换 sidecar,剥掉 scan_root 前缀取第一个路径分量;文件直接位于 scan_root 且无子目录的记为 (root)。若全部落在 (root) 则不要要求用户缩小范围(没有子目录可缩),改为建议 --no-cluster 跳过昂贵聚类后继续;否则展示 top 5 及文件数,询问在哪个子目录上运行。

Step 2.5 - 视频与音频(仅当检测到视频文件)

detect 返回零个 video 文件时完全跳过。语料含音视频时,按 graphify/skills/agents/references/transcribe.md 先用 Whisper 转写成文本(可用 --whisper-model medium 提升准确度),再把转录稿当作文档文件进入 Step 3。

Step 3 - 实体与关系抽取:AST 与语义双路并行

技能文档在此处有一条重要的密钥契约

graphify 不需要任何 API key,绝不向用户索要 key、绝不因缺 key 而阻塞。 代码走确定性 AST 抽取,完全不需要 LLM 和 key——纯代码语料(对仓库执行最常见的 /graphify .)直接跳过语义抽取。语义抽取(仅文档/论文/图片)只有在 GEMINI_API_KEY/GOOGLE_API_KEY 已经设置时才走 Gemini;否则由宿主 Agent 自己充当 LLM。graphify 不读取 ANTHROPIC_API_KEYOPENAI_API_KEY 或其他任何 provider 的 key。

若两个 Gemini key 均未设置,只打印一次提示(Tip: set GEMINI_API_KEY or GOOGLE_API_KEY to use Gemini for semantic extraction (pip install 'graphifyy[gemini]').)然后继续,不等待;若已设置,则改用 graphify.llm.extract_corpus_parallel(files, backend="gemini") 做语义抽取,默认模型 gemini-3-flash-preview,可用 GRAPHIFY_GEMINI_MODEL 或 headless CLI 的 --model 覆盖。

执行顺序上是并行:同一条消息里既派发全部语义子代理,又启动 AST 抽取——两者操作不同文件类型,互不冲突;文档注明并行在大语料上可省 5–15 秒。

Part A - 代码文件的结构化抽取(确定性、零成本)

$(cat graphify-out/.graphify_python) -c "
import sys, json
from graphify.extract import collect_files, extract
from pathlib import Path

code_files = []
detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding="utf-8"))
for f in detect.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, cache_root=Path('INPUT_PATH'))
    Path('graphify-out/.graphify_ast.json').write_text(json.dumps(result, indent=2, ensure_ascii=False), encoding="utf-8")
    print(f'AST: {len(result[\"nodes\"])} nodes, {len(result[\"edges\"])} edges')
else:
    Path('graphify-out/.graphify_ast.json').write_text(json.dumps({'nodes':[],'edges':[],'input_tokens':0,'output_tokens':0}, ensure_ascii=False), encoding="utf-8")
    print('No code files - skipping AST extraction')
"

从源码结构看,AST 侧由 graphify/extract.py 提供 collect_files/extract 入口,底层是 graphify/extractors/ 下一组语言专用解析器(go、rust、csharp、swift/objc、pascal、sql、terraform、zig、bash、markdown……见 graphify/extractors/engine.py),这解释了技能为何声称"代码抽取零 token 成本"。

Part B - 语义抽取(并行子代理)

快速路径(纯代码语料):detect 未发现任何 docs/papers/images 时跳过 Part B 直达 Part C,但必须先写一个空语义文件——Part C 的合并无条件读取 .graphify_semantic.json,缺了会 FileNotFoundError

Path('graphify-out/.graphify_semantic.json').write_text(json.dumps({'nodes':[],'edges':[],'hyperedges':[],'input_tokens':0,'output_tokens':0}), encoding='utf-8')

对含文档的语料,文档用粗体强调:必须使用 Agent/Task 工具派发子代理,自己逐文件读取是禁止的(慢 5–10 倍)。派发前先打印耗时估计:代理数 ≈ ceil(uncached_non_code_files / 22)(块大小 20–25 文件),每批约 45 秒(并行执行,总时长 ≈ 45s × ceil(agents/parallel_limit))。

Step B0 - 先查抽取缓存SPEC_PATH 是与技能文件同目录、随包分发的 graphify/skills/agents/references/extraction-spec.md绝对路径——它是抽取提示词本身,因此缓存条目按它归属:graphify 升级改了提示词,旧提示词产生的条目会被重新抽取而不是重放;提示词不变则命中缓存(#1939)。B0 与 B3 必须传同一个 SPEC_PATH:

$(cat graphify-out/.graphify_python) -c "
import json
from graphify.cache import check_semantic_cache
from pathlib import Path

detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding="utf-8"))
# 只有内容文件才进语义抽取。代码已被 AST(Part A)结构性覆盖;
# 这里摊平所有类别会让子代理重读每个源文件(#1392)。
# 视频在 Step 2.5 已先转写为文档。
all_files = [f for cat in ('document', 'paper', 'image') for f in detect['files'].get(cat, [])]

cached_nodes, cached_edges, cached_hyperedges, uncached = check_semantic_cache(all_files, root='INPUT_PATH', prompt_file='SPEC_PATH')

# 总是(重)写缓存文件:命中则写;未命中则删除上一次残留,
# 防止 Part C 合并到过期的 .graphify_cached.json(#1392)
if cached_nodes or cached_edges or cached_hyperedges:
    Path('graphify-out/.graphify_cached.json').write_text(json.dumps({'nodes': cached_nodes, 'edges': cached_edges, 'hyperedges': cached_hyperedges}, ensure_ascii=False), encoding="utf-8")
else:
    Path('graphify-out/.graphify_cached.json').unlink(missing_ok=True)
Path('graphify-out/.graphify_uncached.txt').write_text('\n'.join(uncached), encoding="utf-8")
print(f'Cache: {len(all_files)-len(uncached)} files hit, {len(uncached)} files need extraction')
"

只为 .graphify_uncached.txt 里列出的文件派发子代理;全部命中缓存则直接进 Part C。

Step B1 - 分块:从 .graphify_uncached.txt 读文件,按每块 20–25 个文件切分;每张图单独成块(视觉需要独立上下文);同一目录的文件尽量同块,让块内跨文件关系更容易被抽到。

Step B2 - 单条消息派发全部子代理(agents 平台使用 Task 工具,对应生成模板 tools/skillgen/fragments/dispatch/task-tool-disk.md):

Task(description="Your task is to perform the following. Follow the instructions below exactly.\n\n<agent-instructions>\n[extraction prompt, with FILE_LIST, CHUNK_NUM, TOTAL_CHUNKS, DEEP_MODE substituted]\n</agent-instructions>\n\nExecute this now. Output ONLY the structured JSON response.")

每个子代理把结果写到自己独立的 graphify-out/.graphify_chunk_NN.jsonCHUNK_PATH 必须是绝对路径(相对路径在 Write 工具里会对着未定义的 cwd 解析,文件被静默弄丢):PROJECT_ROOT=$(pwd)CHUNK_PATH="${PROJECT_ROOT}/graphify-out/.graphify_chunk_0N.json"

子代理提示词模板即 graphify/skills/agents/references/extraction-spec.md,只在至少一个块含 doc/paper/image 时加载;纯代码语料永远不读它。该规格的核心约束值得摘录(它们直接决定图的质量与可合并性):

  • 三级置信度:EXTRACTED(源码中显式存在的关系:import、call、citation、"see §3.2")、INFERRED(合理推断)、AMBIGUOUS(不确定——标记待审,绝不省略)。
  • 代码文件只补 AST 找不到的语义边(调用关系、共享数据、架构模式),不重抽 import(AST 已有)。
  • calls 边方向必须是 caller→callee,且只能同语言内——跨语言调用边是幻影伪迹,永不产出。
  • file_type 只允许六个值之一:codedocumentpaperimagerationaleconcept。设计理由(WHY)作为 rationale 属性挂在相关概念节点上,不单独建节点。
  • 节点 ID 规则(与 AST 抽取器必须逐字节一致):小写 [a-z0-9_],格式 {stem}_{entity},stem 是去掉扩展名的完整仓库相对路径、每段小写并用 _ 连接——src/auth/session.py + ValidateTokensrc_auth_session_validatetoken。只用文件名或只用父目录会制造孤儿幻影重复节点;永远不许附加块序号后缀(_c1_chunk2),同一实体必须永远产出同一个 ID。
  • confidence_score 离散评分标尺:EXTRACTED 恒为 1.0;INFERRED 只允许从 {0.95, 0.85, 0.75, 0.65, 0.55} 中选一个(禁止 0.5——生产数据呈双峰分布,连续区间指导被模型坍缩成二值);AMBIGUOUS 为 0.1–0.3。
  • 语义相似边:两个概念无结构链接却解决同一问题时加 semantically_similar_to(INFERRED,0.6–0.95),仅限真正非显然的跨切面相似。
  • 超边(hyperedges):≥3 个节点共同参与一个成对边无法表达的共享概念/流程/模式时添加,每块最多 3 条(如"实现同一协议的所有类""认证流程中的所有函数")。
  • source_file 必须逐字符照抄 FILE_LIST 中的路径(引擎下游会归一化分隔符、按构建根相对化)——这是全量构建与增量 --update 保持同基线、让 build_merge 的 replace-on-re-extract 能命中同一节点而不累积重复的前提。
  • JSON Schema:nodes[](id/label/file_type/source_file/source_location/source_url/captured_at/author/contributor)、edges[](source/target/relation/confidence/confidence_score/source_file/source_location/weight,relation 取值含 calls|implements|references|cites|conceptually_related_to|shares_data_with|semantically_similar_to|rationale_for)、hyperedges[]input_tokens/output_tokens

Step B3 - 收集、缓存、合并

  • 子代理成功的信号是 graphify-out/.graphify_chunk_NN.json 落盘。文件缺失多半是子代理被以只读(Explore 型)派发——打印警告 "chunk N missing from disk — subagent may have been read-only. Re-run with general-purpose agent.",绝不静默跳过。单个块失败/非法 JSON 则警告并跳过该块,不中止整体;超过一半的块失败时停止并提示用户确认 subagent_type="general-purpose"
  • chunk JSON 自带占位零 token 计数;每个 Agent 调用完成后,从工具结果的 usage 字段读真实 token 数回写进 chunk JSON 再合并。合并脚本把全部 chunk 的 nodes/edges/hyperedges 与 token 计数汇总为 .graphify_semantic_new.json 并打印 Merged N chunks: X in / Y out tokens
  • 新结果写缓存(传与 B0 相同的 SPEC_PATH——"以不同提示词写入的条目,下次运行时根本查不到",#1939):save_semantic_cache(nodes, edges, hyperedges, root='INPUT_PATH', allowed_source_files=uncached, prompt_file='SPEC_PATH')
  • 缓存 + 新结果按节点 ID 去重合并为 graphify-out/.graphify_semantic.json,打印 Extraction complete - N nodes, M edges (K from cache, J new);随后清理临时文件:rm -f graphify-out/.graphify_cached.json graphify-out/.graphify_uncached.txt graphify-out/.graphify_semantic_new.json

Part C - AST 与语义合并为最终抽取

ast = json.loads(Path('graphify-out/.graphify_ast.json').read_text(encoding="utf-8"))
sem = json.loads(Path('graphify-out/.graphify_semantic.json').read_text(encoding="utf-8"))

# 合并:AST 节点优先,语义节点按 id 去重
seen = {n['id'] for n in ast['nodes']}
merged_nodes = list(ast['nodes'])
for n in sem['nodes']:
    if n['id'] not in seen:
        merged_nodes.append(n); seen.add(n['id'])

merged_edges = ast['edges'] + sem['edges']
merged_hyperedges = sem.get('hyperedges', [])
# ... 写入 graphify-out/.graphify_extract.json
print(f'Merged: {total} nodes, {edges} edges ({len(ast["nodes"])} AST + {len(sem["nodes"])} semantic)')

节点 ID 规则(见上)正是这一步能干净去重的保证:LLM 语义节点若与 AST 节点同 ID 即被折叠,否则保留。

Step 4 - 建图、聚类、分析、生成产物

$(cat graphify-out/.graphify_python) -c "
import sys, json
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
from pathlib import Path

extraction = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding="utf-8"))
detection  = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding="utf-8"))

# root= 与 --update runbook 一致(#1361):把 source_file 相对化到同一基准,
# 全量构建与增量 --update 在重抽取时不会漂移
G = build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED)
# 守卫在任何写操作之前:空抽取不得砸掉好的 graph.json / GRAPH_REPORT.md / analysis sidecar
if G.number_of_nodes() == 0:
    print('ERROR: Graph is empty - extraction produced no nodes.')
    print('Possible causes: all files were skipped, binary-only corpus, or extraction failed.')
    raise SystemExit(1)
communities = cluster(G)
cohesion = score_all(G, communities)
tokens = {'input': extraction.get('input_tokens', 0), 'output': extraction.get('output_tokens', 0)}
gods = god_nodes(G)
surprises = surprising_connections(G, communities)
labels = {cid: 'Community ' + str(cid) for cid in communities}
questions = suggest_questions(G, communities, labels)

# 先导出并遵守 #479 shrink-guard:当新图比现有 graph.json 小时 to_json
# 返回 False(不写任何东西)。只有图真正被写入后,才写 GRAPH_REPORT.md
# 与 analysis sidecar,保证它们从不描述 graph.json 里没有的图(#1392)
wrote = to_json(G, communities, 'graphify-out/graph.json')
if not wrote:
    print('ERROR: refused to shrink graphify-out/graph.json (existing graph has more nodes; #479).')
    print('If this shrink is intentional (you deleted files), re-run a full build with --force.')
    raise SystemExit(1)
report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, 'INPUT_PATH', suggested_questions=questions)
Path('graphify-out/GRAPH_REPORT.md').write_text(report, encoding="utf-8")
analysis = {'communities': ..., 'cohesion': ..., 'gods': gods, 'surprises': surprises, 'questions': questions}
Path('graphify-out/.graphify_analysis.json').write_text(json.dumps(analysis, indent=2, ensure_ascii=False), encoding="utf-8")
print(f'Graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges, {len(communities)} communities')
"

这一步体现了技能最讲究的写入顺序纪律,背后是一串被修复过的事故编号:

  • 零节点守卫前置(#1392):旧版先写报告/JSON/analysis 再检查空图,空抽取会砸掉好图;现在空图立即 SystemExit(1)。若打印 ERROR: Graph is empty,停止并告知用户,不进入标注或可视化。
  • shrink-guard(#479):to_json 在新图比现有 graph.json 小(节点数更少)时拒绝写入并返回 False,防止一次失败的抽取把大图缩成小图;有意删文件导致的收缩需显式 --force 全量重建。
  • 社区标签先用 Community N 占位,Step 5 拿到真实名称后再重新生成问题与报告。

Step 4.5 - 图健康检查(只读完整性门禁)

在标注之前对抽取结果做一次非破坏性诊断,暴露增量更新与 AST/LLM ID 不匹配的两类"静默腐化":边折叠、悬空/缺失端点、自环。只读,永不中止:

from graphify.diagnostics import diagnose_extraction, format_diagnostic_report
summary = diagnose_extraction(extraction, directed=IS_DIRECTED, root='INPUT_PATH')
print(format_diagnostic_report(summary))
# 检查 dangling_endpoint_edges / missing_endpoint_edges / self_loop_edges /
# directed_same_endpoint_collapsed_edges / undirected_same_endpoint_collapsed_edges
print('GRAPH HEALTH WARNING: ... - graph may be incomplete/corrupt.' if flags else 'Graph health: OK (no dangling/missing/collapsed edges).')

从源码结构看,诊断逻辑位于 graphify/diagnostics.py,测试 tests/test_multigraph_diagnostics.py 覆盖多图层。若打印了 GRAPH HEALTH WARNING,按 Honesty Rules 必须在最终汇总中展示——图仍可用,但完整性问题必须可见。

Step 5 - 社区标注

.graphify_analysis.json,为每个社区的节点集写一个 2–5 词的白话名(如 "Attention Mechanism"、"Training Pipeline"、"Data Loading")。然后把 LABELS_DICT(如 {0: "Attention Mechanism", 1: "Training Pipeline"})填入脚本:重建图(同 Step 4 的 root=/IS_DIRECTED)、用真实标签重新 suggest_questions(标签影响提问措辞)、重新生成 GRAPH_REPORT.md、把标签存为 .graphify_labels.json 供可视化器使用,并再次 to_json(..., community_labels=labels) 重新导出——让 graph.json 的节点携带人工审核过的 community_name(#2490)。由于抽取输入与 Step 4 相同,节点数不变,shrink-guard 会放行;若仍被拒,原样展示 guard 信息,不强行越过。

Step 6 - Obsidian 仓库(opt-in)+ HTML

  • HTML 恒生成(除非 --no-viz):graphify export html——节点数超过 5000 时自动聚合到社区视图(graphify export html --no-viz 可关闭)。
  • Obsidian 仅在显式 --obsidian 时生成(每个节点一个文件,很贵):graphify export obsidian,或 graphify export obsidian --dir ~/vaults/my-project 写入自定义路径(如已有 vault)。

Steps 6b–8 - 条件触发的导出(wiki / Neo4j / FalkorDB / SVG / GraphML / MCP / benchmark)

仅当对应标志存在时执行(--wiki--neo4j/--neo4j-push--falkordb/--falkordb-push--svg--graphml--mcp),token 缩减基准测试在 total_words > 5,000 时触发;无导出标志的默认运行全部跳过。--wiki 的导出必须在 Step 9 清理之前执行,因为 .graphify_labels.json 之后会被删除。逐项说明见 graphify/skills/agents/references/exports.md

Step 9 - 保存 manifest、成本记账、清理与汇报

这一步的 manifest 逻辑是增量更新(--update)正确性的根基,技能文档用大段注释解释每个设计:

from graphify.detect import save_manifest
from graphify.cli import _stamped_manifest_files
# 'all_files' 携带全量语料(--update 模式),'files' 是变更子集;
# 全量重建模式只填 'files',fallback 处理之
_corpus = detect.get('all_files') or detect['files']
# root= 把 manifest 键相对化到扫描根(与构建同一基准):
# 磁盘上的 manifest 跨 clone/机器可移植,之后的 --update 命中缓存而非全部 miss(#1417)
_manifest_files = _stamped_manifest_files(_corpus, extract, Path('INPUT_PATH'))

# 只给"实际产出了结果"的语义文件(docs/papers/images)盖章:
# 检测到但 chunk 失败/被省略的文件必须保持未盖章,下次 --update 重新入队,
# 否则会被标记完成、内容永久丢失(#2015)。代码文件恒盖章(AST 确定性)
# 本轮派发但未盖章的语义文件:清除其陈旧 semantic_hash,
# 让 detect_incremental 重新入队而非当作未变更(#1948)
# scan_corpus = 原始全量语料(而非盖章过滤后的子集),使自上次运行起
# 新被排除的根内文件被丢弃,而非伪装成删除;未触碰文件的旧行保留(#1908)
save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None)

# 累计成本记账 → graphify-out/cost.json(runs[] + total_input/output_tokens)

save_manifest_stamped_manifest_files 分别实现在 graphify/detect.pygraphify/cli.py,库路径(graphify extract CLI)与技能 runbook 走同一套盖章/清 hash 逻辑,保证两条路径行为一致。

清理:删除 .graphify_detect.json.graphify_extract.json.graphify_ast.json.graphify_semantic.json.graphify_analysis.json 与全部 .graphify_chunk_*.json.needs_update

最后向用户汇报(未给 --obsidian 则省略该行):

Graph complete. Outputs in PATH_TO_DIR/graphify-out/

  graph.html            - interactive graph, open in browser
  GRAPH_REPORT.md       - audit report
  graph.json            - raw graph data
  obsidian/             - Obsidian vault (only if --obsidian was given)

并把 GRAPH_REPORT.md 中的 God NodesSurprising ConnectionsSuggested Questions 三节直接贴进对话(不贴全报告)。然后主动发起探索:从报告里挑出跨越最多社区边界、桥接节点最惊人的那条建议问题,问 "The most interesting question this graph can answer: [question]. Want me to trace it?";用户同意后跑 graphify query,带着用户沿图结构走——哪些节点相连、跨了哪些社区边界、路径揭示了什么——每轮回答以自然的追问收尾。"图是地图,流水线结束后你的工作是当向导。"

5. 子命令守卫、增量更新与查询协议

子命令解释器守卫

任何子命令(--update--cluster-onlyquerypathexplainadd)执行前,检查 graphify-out/.graphify_python 是否存在;缺失(例如用户删了 graphify-out/)时按 shebang→python3 的顺序重新解析解释器并落盘,流程同 Step 1。

--update 增量重建(references/update.md)

完整流程见 graphify/skills/agents/references/update.md,要点:

  1. detect_incremental(Path('INPUT_PATH'))graphify/detect.py)对比 manifest 找出变更/删除文件;new_total == 0 且无删除则直接退出。
  2. 重写 .graphify_detect.jsonfiles = 变更子集(驱动 Step 3A AST 与 Step 3B0 缓存检查只作用于变更部分),all_files = 全量语料。
  3. 纯代码变更快速通道:变更文件扩展名全部命中内置代码扩展名表(.py .ts .js .go .rs .java .cpp .c .rb .swift .kt .cs .scala .php .cc .cxx .hpp .h .kts .lua .toc 及 Fortran 系列)时,打印 "Code-only changes detected - skipping semantic extraction (no LLM needed)",只跑 Step 3A AST,完全跳过子代理。
  4. 变更含视频文件时,先按 transcribe 流程转写并把转录稿路径挪进 files['document']、移除 files['video'](否则原始 .mp4/.mp3 会被喂给语义子代理当不可读媒体,#1392)。
  5. 仅删除时创建空抽取,让合并步骤执行剪枝。
  6. 合并核心是 build_merge([new_extraction], graph_path='graphify-out/graph.json', prune_sources=prune, root='INPUT_PATH', directed=IS_DIRECTED)
    • 直接读 graph.json 合并,不经 NetworkX 往返,边方向(calls/implements/imports)恒保留(#801);
    • prune_sources 只用于真正被删除的文件;变更/重抽文件由 build_merge 的 replace-on-re-extract 处理(#1344)——new_chunks 里的每个 source_file 在合并前先从基图中剔除,旧节点不残留;
    • root= 把绝对路径的 prune_sources 相对化到与图内 source_file 同一基准,否则剪枝静默失效、陈旧节点每次更新都累积(#1361);
    • directed=IS_DIRECTED:不加它,--directed --update 会静默重建为无向图、折叠互反 A↔B 边(#1392)。
  7. 合并后跑 Step 4–8 常规流程;Step 4 之后展示图 diff(graph_diff(G_old, G_new) 输出 summary、新节点、新边数;合并前 cp graphify-out/graph.json graphify-out/.graphify_old.json 备份,事后删除)。

--cluster-only 则跳过 Step 1–3,直接 graphify cluster-only .——它是自包含的:对现有图重新聚类、命名社区、重新生成 GRAPH_REPORT.mdgraph.jsongraph.html。切勿再跑 Step 5–9:它们依赖的中间文件(.graphify_extract.json 等)早已被上一次构建的 Step 9 清理删除,会 FileNotFoundError(#1392)。

/graphify query 协议(references/query.md)

完整流程见 graphify/skills/agents/references/query.md,核心是「先扩展、再遍历、后回写」三段式:

模式 标志 适用
BFS(默认) "X 与什么相连?"——宽上下文,近邻优先(内联实现遍历 3 层)
DFS --dfs "X 如何到达 Y?"——追踪特定依赖链(内联实现深度上限 6)
  • Step 0 受约束查询扩展(遍历前强制)graphify query CLI 用 case-fold 子串 + IDF 匹配节点,没有词干还原、没有同义词、没有跨语言匹配。若用户措辞与图标签词表不一致(用户说 "обработчик"/图里是 "handler";说 "authentication"/图里是 "Guardian"),字面匹配返回 0 命中、答案坍缩成噪声。解法是从 graph.json 节点标签提取真实词表(写入 .vocab.txt),从中只选最多 12 个语义匹配的词(不得自造 token;无匹配就输出空表并明说"语料没有相关词汇",不得虚构搜索),并把选择显式打印给用户以保证可审计。
  • Step 1 遍历:优先 graphify query "QUESTION"QUESTION 是扩展后的 token 串而非原问题);CLI 不可用时内联 NetworkX 回退:对扩展 token 按标签子串给节点打分取 top-3 起点,BFS 扩 3 层 / DFS 限深 6,输出按相关度排序,并按 token 预算截断(默认 2000 token,约 4 字符/token,--budget N 可调)。回答只用图里有的内容,引用具体事实时引用 source_location;图信息不足就明说,不得幻觉出边。
  • 回写闭环:答案写回图供未来查询使用——graphify save-result --question "原始问题" --answer "答案(含扩展 token 轨迹)" --type query --nodes ...,下一次 --update 会把这个 Q&A 抽成图节点。追加 --outcome useful|dead_end|corrected(纠正时带 --correction "正确答案")形成工作记忆:useful 的被引用节点成为首选来源,dead_end 的路径下次跳过,corrected 记录纠正。会话开始图工作时先跑 graphify reflect --if-stale(廉价、确定性、无 LLM;已最新时 no-op)再读 graphify-out/reflections/LESSONS.md——首选来源、已知死路、历史纠正一目了然。

/graphify path/graphify explain

  • path:求两个命名概念间最短路径。CLI 优先:graphify path "NODE_A" "NODE_B";内联回退用 nx.shortest_path,逐跳打印 节点 --{relation}--> [{confidence}],然后用人话解释每一跳的含义与意义。事后 save-result --type path_query 回写。
  • explain:对单节点给白话解释(3–5 句:它是什么、连着什么、为何重要,引用 source 位置作引证)。CLI:graphify explain "NODE_NAME";内联回退输出节点元数据(source、file_type、degree)与全部相邻边(relation + confidence + 邻居源文件)。事后 save-result --type explain 回写。

6. add / --watch 与 hook、AGENTS.md 集成

  • /graphify add <url> 抓取 URL 存入 ./raw 并更新图(可 --author/--contributor 标记作者/贡献者);--watch 监视文件夹、代码变更时自动重建(不需要 LLM)。两者都不是默认构建的一部分,流程见 graphify/skills/agents/references/add-watch.md
  • commit hookgraphify hook install / uninstall / status 安装 post-commit 钩子,每次 git commit 后通过 git diff HEAD~1 找出变更的代码文件、重跑 AST 抽取并重建 graph.jsonGRAPH_REPORT.md;文档/图片变更被钩子忽略(需手动 /graphify --update)。已有 post-commit 钩子时追加而非替换。
  • AGENTS.md 原生集成(agents 平台的特色,见 graphify/skills/agents/references/hooks.md):项目内运行一次 graphify agents install,会在本地 AGENTS.md 写入 ## graphify 段落,指示 agent 在回答代码库问题前先查图、代码变更后重建图——后续会话无需手动 /graphify 即始终在线(always-on)。graphify agents uninstall 移除该段。对应的 always-on 指令块模板为 graphify/always_on/agents-md.md

7. Honesty Rules(诚实规则)

技能末尾的五条硬约束,是整份文档"诚实审计"主题的收口:

  • 绝不发明边。不确定就用 AMBIGUOUS。
  • 绝不跳过语料检查警告(>2M 词 / >500 文件时必须警告)。
  • 报告中必须展示 token 成本。
  • 绝不把内聚度(cohesion)分数藏在符号后面——展示原始数字。
  • 节点数超过 5,000 的图,未经警告用户不得跑 HTML 可视化。

8. 工程化视角:skill-agents.md 是如何生成与被守护的

理解 graphify/skill-agents.md 从何而来,能解释它为何与 graphify/skill-amp.md 正文几乎逐字节相同、只有 hooks 参考文件不同。该文件是 tools/skillgen 生成体系的一个产物:

  • 单一事实源是片段tools/skillgen/fragments/ 下的人类可编辑片段才是源头;graphify/skill*.mdgraphify/skills/<platform>/references/ 是生成并提交到仓库的产物。平台装配声明在 tools/skillgen/platforms.toml
  • agents 平台的装配(platforms.toml 中 [platform.agents]):bucket = "split"(瘦核心 + references 伴生目录)、共享核心模板 core/core.mddispatch = "task-tool-disk"Task 工具派发、子代理落盘收集——即 tools/skillgen/fragments/dispatch/task-tool-disk.md)、extraction = "verbose"(完整抽取规格,区别于 codex/claw/kiro/pi 的 compact 版)、hooks_variant = "agents-md"(读 AGENTS.md、经 graphify agents install 接线 always-on)。注释明确写道:agents 是 post-v8 全新平台,"渲染的技能正文与 amp 相同;只有 hooks 参考文件不同",因此其覆盖审计基线取 amp 的旧版正文。
  • 渲染幂等:核心模板的平台槽位按固定顺序填充(@@INSTALL@@@@INTERP_GUARD@@@@DISPATCH@@@@QUERY_STUB@@@@HOOKS_TARGET@@@@EXTRA@@),参考索引按名称排序,输出统一 LF 换行,且从不写入时间戳或版本号——因此同输入必得同字节。
  • 防漂移守护python -m tools.skillgen 的子命令):
    • --check:渲染结果与提交产物、与 tools/skillgen/expected/ 快照双向字节 diff,任何手改生成文件或过期快照都会失败(接 CI/pre-commit);
    • --audit-coverage:对每个宿主,断言其旧版(v8)技能正文的每个标题在新渲染中"唯一落家"(core 或恰好一个 reference),按各自宿主的旧正文审计——只在某一个宿主上丢内容的回归(如某宿主丢了 AGENTS.md 集成段)在"所有宿主对照 claude 单体"的审计下不可见,必须逐宿主检查;
    • --schema-singleton:断言六值 file_type 枚举在所有平台渲染中逐字节一致,任何 4/5 值旧枚举残留都判漂移;
    • --monolith-roundtrip / --always-on-roundtrip:单体技能与 always-on 块(graphify/always_on/ 六个文件)的往返校验。

这也回答了第 2 节"Usage 原样打印"约定的来源:瘦核心模板(tools/skillgen/fragments/core/)中 Usage 区块是共享内容,跨平台保持一致由 --check 字节级保证。

9. 小结:从 skill-agents.md 能学到什么

  1. 一个技能文件 = 一份可执行的 Agent 运维手册:它不是 API 文档,而是把"如何安装解释器、如何并行派发子代理、如何按顺序写中间文件、何时允许中止"写死成步骤 + 可复制 bash/Python 块,Agent 照做即可复现完整流水线。
  2. 每个中间文件都是契约.graphify_python(解释器)、.graphify_root(扫描根)、.graphify_detect.json.graphify_ast.json.graphify_semantic.json.graphify_extract.json.graphify_analysis.jsoncost.jsonmanifest——Step 3–9 严格按读写顺序依赖它们,Step 9 统一清理。
  3. 可靠性全部来自被编号的事故修复:#479 shrink-guard、#1392 零节点守卫/陈旧缓存/directed 传播、#1417 manifest 可移植、#2015 未盖章文件重入队、#2528 PowerShell 宿主编码——技能正文里每条注释都对应一类曾真实发生的静默数据损坏。
  4. 查询是一等公民:图建好后,query(词表受约束扩展 + BFS/DFS + 预算截断 + save-result/reflect 回写闭环)比重建更常用;这正是 frontmatter 里"graphify-out/ 存在时先当查询"契约的含义。
  5. 想在自己的 Agent 框架中使用:把技能安装到 ~/.agents/skills/graphify/(或项目内 ./.agents/skills/),项目内执行 graphify agents install 写入 AGENTS.md always-on 段落;随后 /graphify . 建图、/graphify --update 增量维护、/graphify query "..." 查询即可。

相关延伸阅读:完整技能索引 graphify/skill.md、查询参考 graphify/skills/agents/references/query.md、增量参考 graphify/skills/agents/references/update.md、抽取子代理规格 graphify/skills/agents/references/extraction-spec.md、生成器实现 tools/skillgen/gen.py

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384