graphify 语料扩展实战:/graphify add 拉取 URL 入图与 --watch 文件夹自动重建
本篇基于 graphify 的 Kiro 技能参考文档 add-watch.md,完整讲解 graphify 两个非默认构建流程——/graphify add <url>(把 URL 拉取进语料库并更新图谱)与 --watch(后台监听文件夹、文件变化时自动更新图谱)——的标准命令、参数替换规则、六类 URL 的自动识别与落地行为,以及监听重建背后的事件过滤、防抖机制与并发锁实现,帮助你在 Agent 协作的长周期开发中保持知识图谱与代码库实时同步。
两个非默认流程的定位
graphify 的核心技能 skill-kiro.md 定义了默认构建流水线(检测、AST/语义抽取、建图、聚类、报告、导出)。add 与 --watch 都不属于这条默认链路:技能文档明确写道,当用户执行 /graphify add <url> 或传入 --watch 时才加载参考文档 references/add-watch.md。二者解决的是同一类问题——让图谱持续吸收新内容:
/graphify add:把外部 URL(视频、推文、论文、PDF、图片、网页)抓下来放进语料目录,再走增量更新合并进现有图谱;--watch:起一个后台监听进程,代码文件变化时免 LLM 自动重建,文档类变化则打上需要语义重抽的标记。
两个流程都依赖一个前置约定:graphify-out/.graphify_python 文件记录着当前项目可用的 Python 解释器路径,后续命令统一用 $(cat graphify-out/.graphify_python) 前缀调用,确保与首次构建用的是同一解释器。该文件由技能 Step 1(检测/安装阶段)写入,若被误删需先按技能文档中的 Interpreter guard 重新解析。
/graphify add:抓取 URL 并并入语料库
标准命令
参考文档给出的完整命令如下(执行于项目根目录,与 graphify-out/ 同级):
$(cat graphify-out/.graphify_python) -c "
import sys
from graphify.ingest import ingest
from pathlib import Path
try:
out = ingest('URL', Path('./raw'), author='AUTHOR', contributor='CONTRIBUTOR')
print(f'Saved to {out}')
except ValueError as e:
print(f'error: {e}', file=sys.stderr)
sys.exit(1)
except RuntimeError as e:
print(f'error: {e}', file=sys.stderr)
sys.exit(1)
"
使用时做三处替换:
URL:实际要抓取的地址;AUTHOR:内容作者(用户提供了才填),写入产物 frontmatter 的author字段;CONTRIBUTOR:把内容加入语料库的人,写入contributor字段,团队共用语料库时用于追溯是谁加的料。
错误处理是硬性要求:若命令以非零码退出(ValueError 通常是 URL 校验失败,RuntimeError 是抓取/网络失败),必须把错误告诉用户,不能静默继续。保存成功后,自动对 ./raw 触发 --update 增量流水线,把新文件合并进既有图谱——增量流程(只重抽变更文件、代码变更跳过语义抽取省 token)详见姊妹参考文档 update.md。
六类 URL 的自动识别与落地行为
文档声明 URL 类型是自动检测的,不需要用户指定。对照实现 graphify/ingest.py 中的 _detect_url_type(),检测顺序与文档一一对应:
| URL 类型 | 识别条件 | 落地行为 |
|---|---|---|
| YouTube / 任意视频 | 含 youtube.com 或 youtu.be |
通过 yt-dlp 下载音频到 ./raw,下次运行时转写为 .txt;需要 pip install 'graphifyy[video]' |
| Twitter/X | 含 twitter.com 或 x.com |
走 oEmbed 接口抓取,存为 .md,含推文正文与作者 |
| arXiv | 含 arxiv.org |
抓摘要页,存摘要 + 元数据为 .md |
路径以 .pdf 结尾 |
直接下载为 .pdf |
|
| 图片 | 路径以 .png/.jpg/.webp 结尾 |
直接下载,下次构建时由宿主 Agent 的视觉能力(Claude vision)提取 |
| 任意网页 | 兜底 | 经 HTML 转 Markdown 后存 .md |
几个值得注意的实现细节(均出自 ingest.py):
- 推文抓取(
_fetch_tweet,L103-L133):先把x.com规范化回twitter.com,请求publish.twitter.com/oembed端点;若 oEmbed 失败则降级为 URL 占位存根,而不是整体报错。产物带 YAML frontmatter(source_url、type: tweet、author、captured_at、contributor)。 - arXiv 抓取(
_fetch_arxiv,L165-L207):用正则\d{4}\.\d{4,5}从 URL 中提取 arXiv 编号,改走export.arxiv.org/abs/页面抽取标题、作者、摘要;URL 中识别不出编号时退回普通网页路径。文件名固定为arxiv_<id>.md。 - 网页转换(
_fetch_webpage,L136-L162):先正则剥离<script>/<style>防止脚本内容泄漏,再优先用markdownify转 ATX 风格 Markdown,缺失该依赖时退化为裸标签剥离并截断到 8000 字符;正文统一截断到 12000 字符再落盘。 - 防覆盖(L259-L265):同名文件已存在时自动追加
_1、_2… 后缀(上限 1000),避免同一 URL 抓两次互相覆盖。 - frontmatter 注入防护(
_yaml_str,L13-L52):所有写进 YAML 双引号标量的抓取内容(页面标题、作者名)都经过逐字符转义,覆盖\、"、换行、\t、\0以及 Unicode 行分隔符 U+2028/U+2029——恶意页面的标题不可能借此逃逸引号注入 YAML 键。 - URL 安全校验:
ingest()入口先调用 security.py 的validate_url再做任何网络请求,抓取统一走safe_fetch/safe_fetch_text。 - 源码支持范围略宽于文档:图片后缀实际还接受
.jpeg、.gif(L79),另外含github.com的 URL 会被分类为github类型——但仓库内 GitHub 仓库的克隆流程由技能的 Step 0(github-and-merge.md)负责,ingest()本身对 github 类型会走通用网页路径。
CLI 等价入口
不经过 Agent 技能时,也可以直接用命令行子命令,实现在 cli.py:
graphify add <url> [--author Name] [--contributor Name] [--dir ./raw]
CLI 版本抓取成功后会打印 Saved to <path> 并提示 Run /graphify --update in your AI assistant to update the graph.——与技能版“自动触发 --update”的区别在于 CLI 把更新动作交还给人/AI 助手执行,语义一致。
--watch:后台监听与自动重建
标准命令
$(cat graphify-out/.graphify_python) -m graphify.watch INPUT_PATH --debounce 3
INPUT_PATH 替换为要监听的文件夹。该命令直接执行 graphify/watch.py 模块的 __main__ 入口(L2240-L2247),其 argparse 定义了两个参数:位置参数 path(默认 .)与浮点参数 --debounce(默认 3.0 秒)。启动后会打印监听目标、行为说明与防抖时长,按 Ctrl+C 停止。
两类变化,两种处理
行为取决于变化文件的类型(判定逻辑在 _batch_triggers_rebuild / _batch_needs_llm_flag,watch.py):
- 仅代码文件变化(.py、.ts、.go 等): 立即重跑 AST 抽取 + 重建 + 聚类,不需要 LLM,
graphify-out/graph.json与graphify-out/GRAPH_REPORT.md自动更新。删除任意受监听文件同样触发重建——因为驱逐节点只需要“文件已从磁盘消失”这一事实,同样不需要 LLM(源码注释引用了 #2580 的修复:纯文档删除批次此前会卡在标记文件后面,直到下一次代码事件)。 - 文档、论文或图片变化: 写入
graphify-out/needs_update标记文件并打印通知,提示运行/graphify --update完成 LLM 语义重抽取。check_update()(L2077-L2089)是配套的只读检查入口:标记存在时打印提示,且永远返回成功,方便挂进 cron 而不会误报警;它只负责“提醒”,不清除标记。
防抖(debounce)机制
默认 3 秒。实现上(watch(),L2149-L2237):watchdog 事件到达时只记录最后事件时间戳并累积变更文件集合,主循环每 0.5 秒醒来检查一次,当“距上次事件 ≥ debounce”才把整批变更取出处理。目的正如文档所说:一波并行的 Agent 写入不会每个文件都触发一次重建,而是合并成一次批处理。这与“在 Agent 工作流中跑 watch”的场景直接对应——下一节展开。
监听重建的实现纵深
参考文档只描述了表面行为,watch.py 源码(约 2200 行)揭示了它为何能在多进程、多提交的高压场景下保持图谱一致:
事件过滤:只认真实写事件
- 只读事件剔除(
_is_read_only_event,L2121-L2136):Linux inotify 会把“打开/关闭未写”(opened、closed_no_write)也报成事件——包括监听器自己的重建读树、编辑器与 Agent 的读文件。不过滤的话监听器会自我触发死循环、空耗 CPU。只有创建、修改、移动、删除、写完关闭才算变化;macOS 的轮询观察者天然不发这些事件,过滤为无操作。 .graphifyignore启动时一次性加载(L2171-L2181,注释引用 gh-928):处理器在观察者线程上运行、对 OS 投递的每个事件都会被调用(Time Machine 写入、Spotlight 索引等),若逐事件解析忽略规则,繁忙卷会打满一个 CPU 核;宽泛模式(node_modules/、.venv/)的用户因此获益最大。- 其余过滤:目录事件忽略;扩展名白名单为代码/文档/论文/图片四类后缀的并集(
_WATCHED_EXTENSIONS,L278);任一路径分量以.开头(隐藏目录)或位于graphify-out/内的事件直接丢弃。 - macOS 用轮询观察者(L2212):
PollingObserver替代 FSEvents,规避某些编辑器快速保存被 FSEvents 漏掉的问题。
重建核心 _rebuild_code:锁、排队与防缩水
_rebuild_code()(L1274 起)是“无 LLM 重建”的完整实现,几个关键设计:
- 每仓库 flock 锁(
_rebuild_lock,L161-L218):多个仓库的 post-commit 钩子可能并发重建,fcntl.flock保证同一时刻只有一个进程写图;锁文件.rebuild.lock内写入持有者 PID 供外部轮询,释放时删除。拿不到锁的增量调用不会丢工作——先把变更路径追加进.pending_changes队列(_queue_pending,L22-L41),拿到锁的进程在重建前后各 drain 一次并合并(_drain_pending+_merge_changed_paths),重建结束还会补最多 20 轮“晚到批次”排空,避免提交风暴下的活锁。Windows 无 fcntl 时降级为直接放行。 - 增量 vs 全量:传入变更列表时只重抽“变更且仍存在”的文件,未变更文件的节点从既有
graph.json保留;被删除的路径单独追踪用于驱逐陈旧节点。不传列表则全量重抽(watch 主循环与 post-checkout 钩子走此路径)。 - 解析上下文注入(L1558-L1639,注释引用 #2406/#2437/#2438):增量重建只解析变更文件,跨文件解析器看不到未变更文件里的被调用方,会导致“变更→未变更”的
calls边全部消失。实现上把既有图中未重抽文件的 AST 节点(含_callable标记)与contains/method边作为只读上下文交给extract(),专门用于扩展解析索引,不解析、不修改、不输出。 - 防缩水守卫(
_check_shrink,L1080-L1150):新图节点数少于旧图时拒绝覆写并打印警告,--force可绕过。但“合理缩水”会计入豁免:每个丢失节点都归属于本次重抽/删除的源文件时放行,失败源(failed_sources)的节点缺失永远豁免不了——守卫防的正是“抽取失败导致的大面积静默丢节点”。 - 写前备份 + 原子替换:覆写
graph.json前先backup_if_protected,候选图写入.graph.tmp.json再replace,中途崩溃不会留下半截 JSON。既有图不可读(超大小上限或解析失败)时 fail-closed 拒绝覆写——“读失败”不等于“没变化”。 - 无变化快路径:拓扑对比(节点/边/超边规范化后逐字节比 JSON)与报告对比都相同时,完全不动盘,只打印
No code-graph changes detected; outputs left untouched.;graph.html缺失或被标记 stale 时仍会从graph.json里已有的社区信息独立重建(_reconcile_graph_html,L1192-L1271)。 - 社区标签保鲜:重建后用成员签名(
community_member_sigs)校验既有标签,社区构成变了就丢弃旧名、按枢纽节点(最高度数成员)确定性重命名,并在 stderr 提示可运行graphify label用 LLM 刷新命名(L1904-L1947)。 - 清单同步:每次重建用
save_manifest刷新manifest.json(AST 侧),让后续--update能正确判断哪些文件已抽取;本批抽取失败的文件不盖时间戳、旧哈希清空(L1653-L1658,注释引用 #2543),下次运行重新排队。 needs_update标记清除:成功重建后删除该标记(L2058-L2061),所以纯代码变化不会留下过期标记。- HTML 可视化联动:若
graphify-out/下已有*-callflow.html,重建成功后自动重新生成(按存在即 opt-in,L2041-L2056)。
语义文档不重复 AST 快扫
一个容易踩坑的点(L1407-L1482,注释引用 #1915/#2333):已有语义(LLM)节点的文档不会再被 AST 快扫——否则每次重建都会在保留的语义节点之上又造一层标题节点,图谱体积膨胀数倍。这类文档保留在语料集合中参与“删除证据”判断与缩水会计,但其语义层是唯一定位,增量重建也不得把它列入重抽目标,否则会清掉语义节点。
Agent 工作流中的推荐用法
参考文档给出两条实战建议:
- 后台终端跑 watch:
--watch放在一个独立终端常驻,Agent 一波波(wave)改代码时,波与波之间监听器自动拾取变化并重建;由于防抖合并,一波内的批量写入只产生一次重建。 - 文档/笔记变化需要手动收尾:如果 Agent 同时写文档或笔记,这些变化只会留下
needs_update标记,那些波结束后需要手动跑一次/graphify --update完成语义重抽。
配合 post-commit 钩子(见 hooks.md)时,钩子进程拿不到重建锁也不会丢提交——变更集进 .pending_changes 队列,由持锁者合并重建(watch.py 顶部注释引用的 #1059 即此场景)。
依赖安装与验证
对照 pyproject.toml 的可选依赖,两条流程的额外依赖分别是:
- 视频类 URL 抓取:
pip install 'graphifyy[video]'(faster-whisper+yt-dlp,需 Python ≥ 3.11)——PDF 与网页 Markdown 转换则依赖pdf额外包里的pypdf+markdownify; - 监听:
pip install 'graphifyy[watch]'(watchdog);watch()在缺少 watchdog 时会抛出明确的ImportError: watchdog not installed. Run: pip install watchdog。
行为可用仓库内测试核对:tests/test_watch.py 覆盖了变更批次判定(纯文档删除触发重建但跳过 LLM 标记、纯代码变更触发重建)、needs_update 标记的创建/幂等/check_update 只提醒不清除、flock 锁的 PID 写入与释放、删除文件的节点驱逐、重命名源文件的驱逐、既有图合并保留超边与语义边等;URL 抓取路径的测试见 tests/test_ingest.py。
小结
/graphify add 与 --watch 是 graphify 把知识图谱从“一次性快照”变成“活体语料”的两个杠杆:前者用六类 URL 自动识别 + 安全抓取 + frontmatter 注入防护,把外部内容规范化地并入 ./raw 再走 --update 增量流水线;后者用 watchdog 事件过滤(只读事件剔除、忽略规则预加载)、防抖批处理、flock + 待处理队列、增量重建 + 解析上下文注入与防缩水守卫,在零 LLM 成本下让代码图谱随每次文件写盘自动收敛到最新。两者共同的边界也很清晰:语义类内容(文档、论文、图片、视频转写)永远需要一次显式的 /graphify --update,watch 只负责及时地替你提醒。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00