Understand Anything /understand-diff 实战指南:把 Git Diff 变成知识图谱上的结构化影响分析
本篇指南围绕 Understand Anything 插件中的 understand-diff 技能展开,讲解如何以项目数据目录中的知识图谱(.ua/knowledge-graph.json 或旧版 .understand-anything/knowledge-graph.json)为基准,对当前未提交改动、功能分支或指定 PR 的 diff 做"变更组件 / 受影响组件 / 受影响架构层 / 风险等级"四维分析,并产出可供 Dashboard 可视化的高亮覆盖层 diff-overlay.json。读完并掌握文中的八步流程、新鲜度检查的 Git 命令细节与 diff 分析器的实现原理后,你可以直接在自己的项目里复现整套"改了什么、会波及什么、风险有多大"的工程化审查流程。
技能定位与前置条件
/understand-diff 的技能定义位于 understand-anything-plugin/skills/understand-diff/SKILL.md,其 frontmatter 描述为:"Use when you need to analyze git diffs or pull requests to understand what changed, affected components, and risks"。它不是替代 git diff 的阅读工具,而是把文件级变更映射到知识图谱的节点与边上,回答两个传统 diff 回答不了的问题:
- 影响面:哪些组件通过 imports / calls 等边与本次变更相连(1-hop 波及范围);
- 架构风险:变更触碰了哪些架构层,是否跨层、是否命中高复杂度节点。
数据目录解析(Step 1 前置)
技能第一步要求先解析数据目录 $UA_DIR,且旧目录优先:
UA_DIR=$([ -d .understand-anything ] && echo .understand-anything || echo .ua)
即:若项目中已存在旧版 .understand-anything/ 目录,则沿用旧目录;否则使用新版 .ua/。随后必须确认 $UA_DIR/knowledge-graph.json 存在,否则应提示用户先运行 /understand 生成图谱——没有图谱,后续的节点映射与边追踪全部无从谈起。Dashboard 技能(understand-anything-plugin/skills/understand-dashboard/SKILL.md)中采用完全相同的目录判定逻辑,两处保持一致。
知识图谱结构参考(Graph Structure Reference)
技能文档给出了完整的图谱 JSON 结构约定,做任何 diff 分析前必须熟悉。图谱顶层由五个部分构成:
project:项目元数据
{name, description, languages, frameworks, analyzedAt, gitCommitHash}。其中 gitCommitHash 是新鲜度检查的关键——它记录图谱是在哪个提交上生成的。从源码看,这一字段由 ProjectMetaSchema 定义为必填字段,缺失会导致整个图谱校验失败。
nodes[]:节点
每个节点包含 {id, type, name, filePath?, summary, tags[], complexity, languageNotes?},节点类型分三大类:
| 类别 | 类型 |
|---|---|
| 代码节点 | file, function, class, module, concept |
| 非代码节点 | config, document, service, table, endpoint, pipeline, schema, resource |
| 领域/知识节点 | domain, flow, step, article, entity, topic, claim, source |
节点 ID 以类型前缀开头,例如 file:path、function:path:name、config:path、article:path。源码中 GraphNodeSchema 进一步约束了字段细节:
complexity只允许simple/moderate/complex三档(low、high等别名会在 normalizeGraph 阶段被自动映射为规范值);summary与tags是必填的(缺失时 autoFixGraph 会用name兜底并记录 auto-corrected 问题);lineRange为可选的[start, end]二元组,用于函数/类级节点的定位。
edges[]:边
每条边为 {source, target, type, direction, weight}。技能文档列出与 diff 分析最相关的边类型:imports, contains, calls, depends_on, configures, documents, deploys, triggers, contains_flow, flow_step, related, cites。完整的边类型枚举定义在 EdgeTypeSchema:共 38 种边类型,按结构(structural)、行为(behavioral)、数据流(data flow)、依赖(dependencies)、语义(semantic)、基础设施(infrastructure)、Schema/数据、领域(domain)、知识(knowledge)、设计(design)九类组织。direction 取 forward / backward / bidirectional,weight 是 [0, 1] 区间的数字(越界会被 clamp)。
layers[] 与 tour[]
layers[] 每项为 {id, name, description, nodeIds[]},表示架构分层;tour[] 每项为 {order, title, description, nodeIds[]},是项目的导览步骤。diff 分析主要用到 layers——判断变更落在哪些架构层。
高效读取策略
技能文档对"如何读图谱"给出四条明确纪律,本质是控制 LLM 上下文成本:
- 先 Grep 后 Read:在完整读取 JSON 之前,先用 Grep 在其中检索相关条目;
- 只读需要的片段:不要将整个图谱一次性倾倒进上下文;
name与summary是最有价值的字段:理解组件语义优先看这两个字段;- 顺着边找依赖链:edges 描述组件如何相连,沿
imports与calls边即可追踪依赖链。
这与后文 Step 4–6 的"Grep 节点 → Grep 边 → Grep 层"三步检索法完全对应。
八步工作流详解
Step 2:获取变更文件列表(先不要读图谱)
技能强调"do NOT read the graph yet",三种场景对应三种取法:
# 当前分支有未提交改动
git diff --name-only
# 功能分支相对基线分支(如 main)
git diff main...HEAD --name-only
# 用户指定 PR 号时,取该 PR 的 diff
先拿到纯净的文件路径列表,再进入图谱检索,可以避免在"确定变更范围"之前被大文件内容干扰。
Step 3:读取 project 元数据并检查图谱新鲜度(最关键的守卫步骤)
用 Grep 或限制行数的 Read 提取图谱中的 "project" 段,把 gitCommitHash 记为 GRAPH_COMMIT_RAW,然后执行以下命令组:
GRAPH_COMMIT=$(git rev-parse --verify --end-of-options "${GRAPH_COMMIT_RAW}^{commit}" 2>/dev/null)
git rev-parse HEAD
git diff --name-only "$GRAPH_COMMIT" HEAD -- .
git diff --cached --name-only -- .
git diff --name-only -- .
git ls-files --others --exclude-standard -- .
这条命令组里有几个容易做错、但技能文档明确强调的细节:
- 先 resolve 再使用:
GRAPH_COMMIT_RAW必须先通过git rev-parse --verify --end-of-options "${GRAPH_COMMIT_RAW}^{commit}"解析为规范 commit 才能进入 diff。--end-of-options防止以-开头的异常输入被当作选项,^{commit}强制要求它是一个 commit 对象。 -- .pathspec 是必需的:git diff "$GRAPH_COMMIT" HEAD -- .中结尾的-- .把 diff 限定在当前项目目录内。其动机是 monorepo 场景:只改动了兄弟子项目的提交,不应让本项目图谱被判为过期。文档原话:"A hash mismatch alone is not stale when the project diff is empty."——仅哈希不一致而项目内无 diff 时,不算 stale。- 数据目录要排除:所有命令输出中必须忽略
.ua/或.understand-anything/,因为它们存放的是生成的图谱产物,不是项目源码漂移。源码中的新鲜度模块 staleness.ts 用 Git 的 pathspec 排除语法实现了同一语义:
const PROJECT_PATHSPEC = [
"--", ".",
":(exclude).understand-anything",
":(exclude).understand-anything/**",
":(exclude).ua",
":(exclude).ua/**",
] as const;
- stale 时先警告再分析:若 committed diff 或任何 working-tree 命令报告了项目文件,必须在影响分析之前发出警告(图谱可能缺失这些变更),并建议运行
/understand刷新图谱。 - 元数据缺失不阻塞:只在
GRAPH_COMMIT_RAW解析成功时才执行 commit diff;若图谱 commit 缺失、非法或不可用,给出一条简短的 best-effort 警告后继续,不中断整个分析流程。
从源码结构看,这套"宽松但不含糊"的判定逻辑与 getGraphFreshness 的实现一致:新鲜度结果是一个四态判别联合——fresh(无漂移)、dirty(commit 相同但有工作区改动)、stale(带 behind / ahead / diverged 关系,并有 commitsBehind / commitsAhead 计数)、unknown(Git 元数据不可读)。源码注释特别指出:"Unknown is intentionally distinct from fresh: if Git metadata cannot be read, callers should warn softly rather than imply the graph is current."——unknown 与 fresh 刻意区分,Git 读不到时只能软警告,绝不能暗示图谱是最新的。
Step 4:为每个变更文件定位图谱节点
对每个变更文件路径,用 Grep 在知识图谱中搜索匹配的 "filePath" 值:
grep "changed/file/path" "$UA_DIR/knowledge-graph.json"
这一步同时命中两类节点:
- 文件级节点(包括非代码类型,如
config:、document:前缀节点); - 定义在该文件内的函数/类节点(如
function:path:name)。
把全部命中节点的 id 记录下来,它们就是"变更组件"的集合。
Step 5:沿边做 1-hop 扩展,找出受影响组件
对每个命中的节点 ID,Grep 边数组中 source 或 target 等于该 ID 的边,区分两个方向:
- 上游调用者:谁 import / depends on 了变更节点——这些组件可能需要同步更新;
- 下游依赖:变更节点 import / calls 了什么——这些依赖的签名或行为变化会传导下去。
两个方向合并即"受影响组件",也就是"可能损坏或需要更新"的部分。注意只扩展 1-hop,不无限递归——这是技能刻意选择的精度与成本平衡点。
Step 6:识别受影响的架构层
把 Step 4 命中的节点 ID 在 "layers" 段中检索,确定哪些架构层被触碰。跨多个层出现的变更通常意味着需要额外关注层间契约(如 API 变更波及 Service 层与数据层)。
Step 7:输出结构化分析
分析结果按四个板块组织:
- Changed Components(变更组件):直接修改的内容,附命中节点的 summary;
- Affected Components(受影响组件):来自 1-hop 边扩展;
- Affected Layers(受影响层):触碰了哪些架构层、是否涉及跨层关注点;
- Risk Assessment(风险评估):基于节点
complexity值、跨层边数量与 blast radius(受影响组件数量)。
最后给出"应重点审查什么"以及潜在问题的建议。
风险判定不是拍脑袋,diff-analyzer.ts 的 formatDiffAnalysis 给出了可复核的阈值:
- 变更节点中存在
complexity === "complex"→ 报 High complexity; - 受影响架构层数 > 1 → 报 Cross-layer impact;
- 受影响组件数 > 5 → 报 Wide blast radius;
- 存在未映射进图谱的文件 → 单独提示"New/unmapped files(可能需要重新分析)";
- 以上条件均不满足 → 输出 Low risk: Changes are localized with limited downstream impact.
Step 8:写出 diff overlay,交给 Dashboard 可视化
分析产出之后,把 diff 数据写入 $UA_DIR/diff-overlay.json,供 Dashboard 高亮变更与受影响组件。文件结构为:
{
"version": "1.0.0",
"baseBranch": "<the base branch used>",
"generatedAt": "<ISO timestamp>",
"changedFiles": ["<list of changed file paths>"],
"changedNodeIds": ["<node IDs from step 4>"],
"affectedNodeIds": ["<node IDs from step 5, excluding changedNodeIds>"]
}
写完后应提示用户可以运行 /understand-anything:understand-dashboard 可视化查看。Dashboard 端的消费逻辑可以在 App.tsx 中逐行印证:启动时 fetch(dataUrl("diff-overlay.json", accessToken)),并对响应做严格形状校验——必须同时存在 changedNodeIds 与 affectedNodeIds 两个数组字段,且 changedNodeIds 非空时才会调用 store.ts 中的 setDiffOverlay(changed, affected) 写入状态。UI 上由 DiffToggle.tsx 提供 "Diff ON/OFF" 开关,开启后图例会按 changed / affected 两种颜色区分节点,并在图例旁展示两类节点的计数;没有 overlay 数据时按钮置灰不可点。这条链路解释了为什么 affectedNodeIds 要"excluding changedNodeIds"——两类集合在前端是分开着色、分开计数的,重复 ID 会污染 affected 侧的统计。
源码级实现对照:buildDiffContext 与测试用例
技能文档描述的是"让 LLM 用 Grep 手动完成"的流程,而插件在 understand-anything-plugin/src/diff-analyzer.ts 中提供了同一套算法的程序化实现 buildDiffContext,可作为流程正确性的参照实现:
- 文件到节点的映射:遍历
nodes,凡node.filePath === file即计入changedNodeIds;未命中的文件进入unmappedFiles; - contains 子节点扩展:对
type === "contains"且 source 是变更节点的边,把 target(文件内的函数/类节点)也并入变更集合——对应 Step 4 中"文件节点及其内部函数/类节点一并命中"的语义; - 1-hop 扩展:遍历所有边,source 或 target 在变更集合中的边记为
impactedEdges;另一端不在变更集合的节点记为 affected(天然去重,affected 与 changed 不相交); - 层判定:
layers中nodeIds与"变更 ∪ 受影响"集合有交集的层即为受影响层。
测试文件 diff-analyzer.test.ts 用一个五节点三层的迷你图谱把每条规则都钉住了:改 src/service.ts 后,断言变更集合包含文件节点与其 contains 子节点 function:src/service.ts:process(L43-L46);断言 affected 通过 calls/reads_from 边扩展到 file:src/routes.ts 与 file:src/db.ts(L48-L52);断言 affected 与 changed 无交集的去重不变量(L76-L82);断言未入图文件落入 unmappedFiles 且优雅降级(L64-L68);断言空 diff 不产生任何结果(L70-L74)。这些用例与 SKILL.md 的 Step 4–7 一一对应,是验证自己手工 Grep 流程是否漏判的直接参照。
适用前提与限制
- 图谱必须先存在且可读:
$UA_DIR/knowledge-graph.json缺失时整个技能退化为提示"先运行 /understand"; - 依赖 Git:新鲜度检查、变更文件列表都来自 Git 命令;在 Git 元数据不可用的仓库(浅克隆、被剥离历史的检出等),按 Step 3 的 best-effort 约定只应给出简短警告并继续,不能把"unknown"当作"fresh";
- 1-hop 是上限:本技能刻意只扩展一层边,二跳及以上的间接影响不会出现在 affected 集合中,审查长依赖链时仍需人工沿边继续追踪;
- 未映射文件不在分析范围内:新增文件若尚未被
/understand收进图谱,只会出现在"unmapped"提示中,其真实影响需要刷新图谱后重跑; - monorepo 场景务必保留
-- .pathspec 与数据目录排除,否则会因兄弟项目提交或图谱产物文件的变动误判过期。
综合来看,/understand-diff 把"读 diff"升级为"在知识图谱上读 diff":Step 3 的新鲜度守卫保证分析基准可信,Step 4–6 的三次 Grep 检索把文件变更翻译成节点、边与层,Step 7 按可复核的阈值给出风险分级,Step 8 再把结果沉淀为 Dashboard 可渲染的 overlay。配合 understand-dashboard 技能启动的可视化界面,一次变更审查从命令行一路贯通到图形化复盘。
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 StartedRust0623
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
