首页
/ Understand Anything /understand-diff 实战指南:把 Git Diff 变成知识图谱上的结构化影响分析

Understand Anything /understand-diff 实战指南:把 Git Diff 变成知识图谱上的结构化影响分析

2026-09-06 13:45:33作者:董宙帆

本篇指南围绕 Understand Anything 插件中的 understand-diff 技能展开,讲解如何以项目数据目录中的知识图谱(.ua/knowledge-graph.json 或旧版 .understand-anything/knowledge-graph.json)为基准,对当前未提交改动、功能分支或指定 PR 的 diff 做"变更组件 / 受影响组件 / 受影响架构层 / 风险等级"四维分析,并产出可供 Dashboard 可视化的高亮覆盖层 diff-overlay.json。读完并掌握文中的八步流程、新鲜度检查的 Git 命令细节与 diff 分析器的实现原理后,你可以直接在自己的项目里复现整套"改了什么、会波及什么、风险有多大"的工程化审查流程。

Understand Anything 知识图谱 Dashboard 界面总览,/understand-diff 产出的 diff overlay 即在此可视化

技能定位与前置条件

/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 回答不了的问题:

  1. 影响面:哪些组件通过 imports / calls 等边与本次变更相连(1-hop 波及范围);
  2. 架构风险:变更触碰了哪些架构层,是否跨层、是否命中高复杂度节点。

数据目录解析(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:pathfunction:path:nameconfig:patharticle:path。源码中 GraphNodeSchema 进一步约束了字段细节:

  • complexity 只允许 simple / moderate / complex 三档(lowhigh 等别名会在 normalizeGraph 阶段被自动映射为规范值);
  • summarytags 是必填的(缺失时 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)九类组织。directionforward / backward / bidirectionalweight[0, 1] 区间的数字(越界会被 clamp)。

layers[] 与 tour[]

layers[] 每项为 {id, name, description, nodeIds[]},表示架构分层;tour[] 每项为 {order, title, description, nodeIds[]},是项目的导览步骤。diff 分析主要用到 layers——判断变更落在哪些架构层。

高效读取策略

技能文档对"如何读图谱"给出四条明确纪律,本质是控制 LLM 上下文成本:

  1. 先 Grep 后 Read:在完整读取 JSON 之前,先用 Grep 在其中检索相关条目;
  2. 只读需要的片段:不要将整个图谱一次性倾倒进上下文;
  3. namesummary 是最有价值的字段:理解组件语义优先看这两个字段;
  4. 顺着边找依赖链:edges 描述组件如何相连,沿 importscalls 边即可追踪依赖链。

这与后文 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."——unknownfresh 刻意区分,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:输出结构化分析

分析结果按四个板块组织:

  1. Changed Components(变更组件):直接修改的内容,附命中节点的 summary;
  2. Affected Components(受影响组件):来自 1-hop 边扩展;
  3. Affected Layers(受影响层):触碰了哪些架构层、是否涉及跨层关注点;
  4. Risk Assessment(风险评估):基于节点 complexity 值、跨层边数量与 blast radius(受影响组件数量)。

最后给出"应重点审查什么"以及潜在问题的建议。

风险判定不是拍脑袋,diff-analyzer.tsformatDiffAnalysis 给出了可复核的阈值:

  • 变更节点中存在 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)),并对响应做严格形状校验——必须同时存在 changedNodeIdsaffectedNodeIds 两个数组字段,且 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 不相交);
  • 层判定layersnodeIds 与"变更 ∪ 受影响"集合有交集的层即为受影响层。

测试文件 diff-analyzer.test.ts 用一个五节点三层的迷你图谱把每条规则都钉住了:改 src/service.ts 后,断言变更集合包含文件节点与其 contains 子节点 function:src/service.ts:process(L43-L46);断言 affected 通过 calls/reads_from 边扩展到 file:src/routes.tsfile: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 技能启动的可视化界面,一次变更审查从命令行一路贯通到图形化复盘。

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