首页
/ Understand-Anything 业务领域知识提取全解析:domain/flow/step 图谱扩展与 Dashboard 领域视图实现

Understand-Anything 业务领域知识提取全解析:domain/flow/step 图谱扩展与 Dashboard 领域视图实现

2026-09-06 13:30:41作者:凌朦慧Richard

本文以 Understand-Anything 仓库中「Business Domain Knowledge Extraction」实施计划为骨架,完整还原该功能从核心类型扩展、Zod 校验、独立持久化、节点 ID 规范化,到 Dashboard 领域视图组件、/understand-domain 技能管线的全部实现细节。读完本文,你将掌握:如何在既有 KnowledgeGraph 体系上扩展三种领域节点类型与三种领域边类型、domain-graph.json 的读写与隐私脱敏机制,以及 dashboard 如何在结构视图与业务领域流图之间通过 pill 切换。

Understand-Anything Dashboard 的 Domain 视图:左侧横向流图中可见 Domain 节点经 cross_domain 虚线连接到具体服务节点,顶部有 Domain / Structural 视图切换 pill

一、功能定位与整体架构

该实施计划(见 2026-04-01-business-domain-knowledge-impl.md,设计规格见 2026-04-01-business-domain-knowledge-design.md)要解决的核心问题是:让 Understand-Anything 不仅能画出"代码长什么样"(结构图谱),还能画出"业务是怎么跑的"(领域流图)。其目标、架构与技术栈在计划开头即明确定义:

  • Goal:新增 /understand-domain 技能,从代码库中提取业务领域知识(domains、business flows、process steps),并在 dashboard 中渲染为交互式横向流图
  • Architecture:独立的 domain-graph.json 文件,使用扩展后的 KnowledgeGraph schema(3 种新节点类型、3 种新边类型、可选 domainMeta 字段);存在两条分析路径——轻量扫描(无既有图谱时)或从既有图谱派生(有图谱时,更廉价);dashboard 在有领域图时默认展示领域视图,并可用 pill 切回结构视图;
  • Tech Stack:TypeScript、Zod、React Flow(横向 LR 布局)、Zustand、Vitest、web-tree-sitter。

整个实施分为 15 个任务,按依赖顺序推进:先做 core 包的类型与 schema(Task 1),再做持久化(Task 2)与 ID 规范化(Task 3),然后构建验证(Task 4),接着是 dashboard 侧的状态(Task 5)、视图切换(Task 6)、三类节点组件(Task 7–8)、领域视图(Task 9)、侧栏(Task 10)、分类映射(Task 11),最后是 agent 与技能定义(Task 12–13)、dashboard 文件服务(Task 14)和集成验证(Task 15)。下文按这一脉络逐层展开,并对照当前仓库源码说明每个环节的落地形态。

二、核心类型扩展:3 节点类型、3 边类型与 DomainMeta

2.1 NodeType 与 EdgeType 联合类型的扩展

计划 Task 1 要求在 types.ts 中把 domainflowstep 加入 NodeType 联合类型,并新增三种领域边类型。计划中的目标形态为:

// Node types (16 total: 5 code + 8 non-code + 3 domain)
export type NodeType =
  | "file" | "function" | "class" | "module" | "concept"
  | "config" | "document" | "service" | "table" | "endpoint"
  | "pipeline" | "schema" | "resource"
  | "domain" | "flow" | "step";

// Edge types (30 total in 7 categories)
export type EdgeType =
  | "imports" | "exports" | "contains" | "inherits" | "implements"  // Structural
  | "calls" | "subscribes" | "publishes" | "middleware"              // Behavioral
  | "reads_from" | "writes_to" | "transforms" | "validates"         // Data flow
  | "depends_on" | "tested_by" | "configures"                       // Dependencies
  | "related" | "similar_to"                                         // Semantic
  | "deploys" | "serves" | "provisions" | "triggers"                // Infrastructure
  | "migrates" | "documents" | "routes" | "defines_schema"          // Schema/Data
  | "contains_flow" | "flow_step" | "cross_domain";                 // Domain

对照当前源码,这三项扩展已完整落地。从源码结构看,此后项目又先后引入了 knowledge 图谱(article/entity/topic/claim/source)与 Figma 设计图谱(page/screen/component 等)节点类型,NodeType 现已扩展为 27 种(5 code + 8 non-code + 3 domain + 5 knowledge + 6 design),EdgeType 扩展到 38 种 9 大类——领域三节点与领域三边作为其中独立的一类被稳定保留,可见见 types.ts

三种领域边类型各自的语义在计划中通过 dashboard 渲染逻辑得到强化:

边类型 方向语义 在流图中的表现
contains_flow domain → flow 领域包含流程,weight 固定 1.0,同时被用于统计每个 domain 的 flow 数量
flow_step flow → step 流程包含步骤,weight 编码步骤顺序(第 1 步 0.1、第 2 步 0.2……),dashboard 用它计算步骤编号
cross_domain domain → domain 领域间交互,带 description 字段说明交互内容,在概览图中渲染为动画虚线

2.2 DomainMeta 可选元数据

计划要求在 GraphNode 之后新增 DomainMeta 接口,为三类领域节点提供可选的结构化元数据:

// Optional domain metadata for domain/flow/step nodes
export interface DomainMeta {
  // For domain nodes
  entities?: string[];
  businessRules?: string[];
  crossDomainInteractions?: string[];
  // For flow nodes
  entryPoint?: string;
  entryType?: "http" | "cli" | "event" | "cron" | "manual";
}

字段与节点类型的对应关系:domain 节点携带 entities(核心实体)、businessRules(业务规则/不变量)、crossDomainInteractions(与外部领域的交互方式);flow 节点携带 entryPoint(触发器,如 POST /api/orders)与 entryTypehttp/cli/event/cron/manual 五选一)。当前 types.ts 中该接口与计划完全一致,GraphNodedomainMeta?: DomainMeta 可选字段挂载(见 types.ts)。

2.3 Zod 校验层:枚举、别名与 .passthrough()

计划 Task 1 Step 4 对 schema.ts 的改造分四步,全部可以在当前源码中逐条对应:

(1)扩展 EdgeTypeSchema 枚举,追加三种领域边:

export const EdgeTypeSchema = z.enum([
  "imports", "exports", "contains", "inherits", "implements",
  "calls", "subscribes", "publishes", "middleware",
  "reads_from", "writes_to", "transforms", "validates",
  "depends_on", "tested_by", "configures",
  "related", "similar_to",
  "deploys", "serves", "provisions", "triggers",
  "migrates", "documents", "routes", "defines_schema",
  "contains_flow", "flow_step", "cross_domain",
]);

当前 schema.ts 中该枚举已包含这三个值(另含 Knowledge/Design 类边)。

(2)节点类型别名表。计划提出 business_domain: "domain"workflow: "flow"action: "step" 等别名。对照当前 schema.ts 的落地版本:

// Domain aliases — "process" intentionally excluded (ambiguous with OS/Node.js process)
business_domain: "domain",
business_flow: "flow",
business_process: "flow",
task: "step",
business_step: "step",

从源码注释可以看出两处有意的调整:process 因与操作系统/Node.js 进程语义混淆被刻意排除workflow/action 这类歧义更大的通用词也未纳入。边别名则保留了计划中的三项(见 schema.ts):

// Domain aliases
has_flow: "contains_flow",
next_step: "flow_step",
interacts_with: "cross_domain",

值得注意的是,计划中 implemented_by: "implements" 这一条没有被采纳——当前源码注释明确说明 implemented_by 会反转边的 source/target 方向,LLM 应直接使用方向正确的 implements(见 schema.ts)。

(3)GraphNodeSchema 扩展节点枚举并启用 .passthrough()

export const GraphNodeSchema = z.object({
  id: z.string(),
  type: z.enum([
    "file", "function", "class", "module", "concept",
    "config", "document", "service", "table", "endpoint",
    "pipeline", "schema", "resource",
    "domain", "flow", "step",
  ]),
  name: z.string(),
  filePath: z.string().optional(),
  lineRange: z.tuple([z.number(), z.number()]).optional(),
  summary: z.string(),
  tags: z.array(z.string()),
  complexity: z.enum(["simple", "moderate", "complex"]),
  languageNotes: z.string().optional(),
}).passthrough();

计划中的关键设计意图是:.passthrough()domainMeta 等额外字段无需在 Zod 中定义即可通过校验,保持 schema 简洁且前向兼容。当前 schema.ts 在保留 .passthrough() 的基础上更进一步,为 domainMeta 显式定义了 DomainMetaSchema(字段与 DomainMeta 接口一一对应,同样 .passthrough()),使其获得完整的类型约束。

(4)失败驱动的测试验证。计划为 Task 1 编写的失败测试现已落地为 domain-types.test.ts,覆盖七类断言:domain/flow/step 节点图整体可校验、contains_flow/flow_step/cross_domain 边类型可校验、节点类型别名归一化(business_domaindomain 等)、边类型别名归一化(has_flowcontains_flownext_stepflow_step)、domainMeta 经校验后完整保留。测试使用的领域图样例本身就是理解 schema 的最佳示例——3 个节点(domain:order-managementflow:create-orderstep:create-order:validate)与 2 条边,其中 flow 节点带 domainMeta: { entryPoint: "POST /api/orders", entryType: "http" },step 节点带 filePath: "src/validators/order.ts"lineRange: [10, 30]。运行方式(计划 Step 2/5):

cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/domain-types.test.ts

三、独立持久化:domain-graph.json 的读写与路径脱敏

计划 Task 2 在 persistence/index.ts 中新增两个函数,与结构图谱的 saveGraph/loadGraph 平行但独立成文件

const DOMAIN_GRAPH_FILE = "domain-graph.json";

export function saveDomainGraph(projectRoot: string, graph: KnowledgeGraph): void {
  const dir = ensureDir(projectRoot);
  const sanitised = sanitiseFilePaths(graph, projectRoot);
  writeFileSync(
    join(dir, DOMAIN_GRAPH_FILE),
    JSON.stringify(sanitised, null, 2),
    "utf-8",
  );
}

export function loadDomainGraph(
  projectRoot: string,
  options?: { validate?: boolean },
): KnowledgeGraph | null {
  const filePath = join(projectRoot, UA_DIR, DOMAIN_GRAPH_FILE);
  if (!existsSync(filePath)) return null;

  const data = JSON.parse(readFileSync(filePath, "utf-8"));

  if (options?.validate !== false) {
    const result = validateGraph(data);
    if (!result.success) {
      throw new Error(
        `Invalid domain graph: ${result.fatal ?? "unknown error"}`,
      );
    }
    return result.data as KnowledgeGraph;
  }

  return data as KnowledgeGraph;
}

当前实现(persistence/index.ts)与计划一致,并有两点值得展开的细节:

  1. 写盘前强制脱敏saveDomainGraphsaveGraph 一样先经过 sanitiseFilePaths:LLM 产出的绝对路径(如 /Users/alice/company/src/auth.ts)会被转换为相对 projectRoot 的相对路径;项目外部的绝对路径只保留文件名(见 persistence/index.ts 的三分支处理)。这意味着 domain-graph.json 不会泄露开发者主目录与公司目录结构——而该文件恰恰会被 dashboard 服务器直接对外服务,脱敏是安全闭环的一环。
  2. 数据目录的兼容策略。当前源码中数据目录由 resolveUaDirName 决定:已存在 .understand-anything/ 的旧项目继续沿用该目录,新项目使用 .ua/(见 persistence/index.ts)。计划文本中写的是 .understand-anything/domain-graph.json,实际运行时两者都可能出现,loadDomainGraph 通过 resolveUaDir 自动适配。

导出层面,计划指出 index.ts 中的 export * from "./persistence/index.js" 通配导出会自动带出新函数,无需改动。持久化测试 domain-persistence.test.ts 验证了三个要点:存取往返一致、文件不存在时返回 null、以及写入的是 domain-graph.json 而非 knowledge-graph.json——最后一条正是"独立成文件、互不干扰"架构决策的回归保障。

四、节点 ID 规范化:domain/flow/step 前缀与 step 的去碰撞重建

计划 Task 3 要求在 normalize-graph.tsVALID_PREFIXESTYPE_TO_PREFIX 中登记三个新前缀:

const VALID_PREFIXES = new Set([
  "file", "func", "class", "module", "concept",
  "config", "document", "service", "table", "endpoint",
  "pipeline", "schema", "resource",
  "domain", "flow", "step",
]);

const TYPE_TO_PREFIX: Record<string, string> = {
  // ...
  domain: "domain",
  flow: "flow",
  step: "step",
};

当前源码(normalize-graph.ts)与计划完全一致。计划对应的测试 domain-normalize.test.ts 断言了四个场景:domain:order-managementflow:create-order 原样通过(幂等);带 filePath 的 step 归一化为 step:src/validators/order.ts:validate;无 filePathstep:validate 保持不变。

从源码结构看,step 前缀的规范化是三类领域中唯一有"特殊逻辑"的。normalizeNodeIdstep 类型做了重建处理:当 step 携带 filePath 时,ID 会重构为 step:<flowSlug>:<filePath>:<stepSlug> 形式(见 normalize-graph.ts),其中 flowSlugnormalizeBatchOutput 扫描 flow_step 边反查得出(见 normalize-graph.ts)。这样设计的目的是避免两个不同 flow 在同一文件中存在同名 step 时发生 ID 碰撞——flow 判别段让 ID 全局唯一。这一点也解释了 agent 输出 schema 中 step ID 约定为 step:<flow-name>:<step-name> 的三层结构。

五、Dashboard 状态层:ViewMode、领域图状态与视图切换

5.1 Store 扩展

计划 Task 5 在 store.ts 中引入视图模式与领域状态。计划的目标形态:

export type ViewMode = "structural" | "domain";

// DashboardStore 新增字段
  // Domain view
  viewMode: ViewMode;
  domainGraph: KnowledgeGraph | null;
  activeDomainId: string | null;

  setDomainGraph: (graph: KnowledgeGraph) => void;
  setViewMode: (mode: ViewMode) => void;
  navigateToDomain: (domainId: string) => void;

当前实现中 ViewMode 已扩展为 "structural" | "domain" | "knowledge" 三值(见 store.ts),核心的 viewMode/domainGraph/activeDomainId 状态与 setViewMode/navigateToDomain 动作与计划一致。setViewMode 在切换时会重置 selectedNodeIdfocusNodeId、代码查看器等瞬态状态,避免跨视图选中态残留。

边分类映射也按计划落位:EDGE_CATEGORY_MAP 新增 domain 类别,DOMAIN_EDGE_TYPES 导出常量与之绑定(见 store.ts):

export const EDGE_CATEGORY_MAP: Record<EdgeCategory, string[]> = {
  // ...
  domain: ["contains_flow", "flow_step", "cross_domain"],
};

export const DOMAIN_EDGE_TYPES = EDGE_CATEGORY_MAP.domain;

5.2 领域图加载与 pill 切换

计划 Task 6 在 App.tsx 中做两件事。第一,加载 domain-graph.json(计划 Step 1 的目标代码):

useEffect(() => {
  fetch(tokenUrl("/domain-graph.json", accessToken))
    .then((res) => {
      if (!res.ok) return null;
      return res.json();
    })
    .then((data: unknown) => {
      if (!data) return;
      const result = validateGraph(data);
      if (result.success && result.data) {
        setDomainGraph(result.data);
      }
    })
    .catch(() => {
      // Silently ignore — domain graph is optional
    });
}, [setDomainGraph]);

当前实现(App.tsx)保持同样的"静默容错"策略:领域图是可选产物,404 或解析失败都不影响结构视图。

第二,在 header 中渲染 pill 切换器,仅当两张图都存在时才显示(计划 Step 2 的目标代码):

{graph && domainGraph && (
  <>
    <div className="w-px h-5 bg-border-subtle" />
    <div className="flex items-center bg-elevated rounded-lg p-0.5">
      <button
        onClick={() => setViewMode("domain")}
        className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
          viewMode === "domain"
            ? "bg-accent/20 text-accent"
            : "text-text-muted hover:text-text-secondary"
        }`}
      >
        Domain
      </button>
      <button
        onClick={() => setViewMode("structural")}
        className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
          viewMode === "structural"
            ? "bg-accent/20 text-accent"
            : "text-text-muted hover:text-text-secondary"
        }`}
      >
        Structural
      </button>
    </div>
  </>
)}

当前源码中该 pill 的显示条件为 graph && !isKnowledgeGraph && domainGraphApp.tsx),在计划的两图共存条件上叠加了"知识图谱模式下隐藏"的分支。文章开头引用的截图正是这一 UI:顶部 Domain/Structural 两态 pill,选中态以 bg-accent/20 text-accent 高亮。

同一 Dashboard 切换到 Structural 视图后展示分层结构:Layer 卡片纵向组织项目分层,顶部 pill 处于 Structural 选中态

5.3 主区域的条件渲染

计划 Task 9 Step 2 将 App.tsx 的图区替换为条件渲染:

{/* Graph area */}
<div className="flex-1 min-w-0 min-h-0 relative">
  {viewMode === "domain" && domainGraph ? (
    <DomainGraphView />
  ) : (
    <GraphView />
  )}
  <div className="absolute top-3 right-3 text-sm text-text-muted/60 pointer-events-none select-none">
    Press <kbd className="kbd">?</kbd> for keyboard shortcuts
  </div>
</div>

当前 App.tsx 中该分支逻辑同样成立:viewMode === "domain" && domainGraph 时渲染 DomainGraphView,否则回落到结构视图 GraphView

六、三类节点组件:DomainClusterNode、FlowNode、StepNode

计划 Task 7 与 Task 8 定义了领域视图的三档节点组件,均已落地为 DomainClusterNode.tsxFlowNode.tsxStepNode.tsx

**DomainClusterNode(领域簇节点)**是概览层的核心组件,数据接口与渲染逻辑:

export interface DomainClusterData {
  label: string;
  summary: string;
  entities?: string[];
  flowCount: number;
  businessRules?: string[];
  domainId: string;
}

其交互设计有两层:单击 selectNode(data.domainId) 选中并在侧栏查看详情,双击 navigateToDomain(data.domainId) 下钻进入该领域的流程明细。视觉上以 serif 字体标题、实体 chips(最多 5 个,超出显示 +N)和 flow 计数呈现,min-w-[280px] max-w-[360px] 的宽度约束保证横向流图中卡片尺寸稳定。

**FlowNode(流程节点)**展示 entryPoint(等宽字体、accent 色调)、流程名、摘要与步骤计数,选中态切换 border/background。

**StepNode(步骤节点)**额外携带 order: number 字段——步骤顺序号在图上以等宽小数字展示,其来源是 flow_step 边的 weight(见下文 buildDomainDetail)。

三个组件都用 memo 包裹并暴露对应的 React Flow 节点类型别名(DomainClusterFlowNodeFlowFlowNodeStepFlowNode),供 DomainGraphViewnodeTypes 注册表引用:

const nodeTypes = {
  "domain-cluster": DomainClusterNode,
  "flow-node": FlowNode,
  "step-node": StepNode,
};

七、DomainGraphView:横向领域流图的构建与布局

计划 Task 9 的 DomainGraphView.tsx 是该功能在 dashboard 侧的枢纽组件,其核心是两个纯函数构建器加一个带状态的记忆化外壳。

7.1 概览层 buildDomainOverview

function buildDomainOverview(graph: KnowledgeGraph): { nodes: Node[]; edges: Edge[] } {
  const domainNodes = graph.nodes.filter((n) => n.type === "domain");
  // ...
  // Count flows per domain
  const flowCountMap = new Map<string, number>();
  for (const edge of graph.edges) {
    if (edge.type === "contains_flow") {
      flowCountMap.set(edge.source, (flowCountMap.get(edge.source) ?? 0) + 1);
    }
  }
  // ... 映射为 "domain-cluster" 节点,尺寸 320x180 ...
  const rfEdges: Edge[] = graph.edges
    .filter((e) => e.type === "cross_domain")
    .map((e) => ({
      id: `${e.source}-${e.target}`,
      source: e.source,
      target: e.target,
      label: e.description ?? "",
      style: { stroke: "var(--color-accent)", strokeDasharray: "6 3", strokeWidth: 2 },
      labelStyle: { fill: "var(--color-text-muted)", fontSize: 10 },
      animated: true,
    }));

  return applyDagreLayout(rfNodes, rfEdges, "LR", DOMAIN_NODE_DIMENSIONS);
}

三个关键设计点:

  1. flow 计数复用 contains_flow,而不是给 domain 节点另加字段——数据源保持单一;
  2. cross_domain 边渲染为动画虚线strokeDasharray: "6 3" + animated: true),并在标签上显示 description,这是截图中"领域 → 服务"横向虚线的来源;
  3. 布局方向 "LR"(Left-to-Right) 实现横向流图语义:领域在左,跨领域交互向右展开。

7.2 明细层 buildDomainDetail

function buildDomainDetail(graph: KnowledgeGraph, domainId: string) {
  // 该领域下的 flow 集合
  const flowIds = new Set(
    graph.edges
      .filter((e) => e.type === "contains_flow" && e.source === domainId)
      .map((e) => e.target),
  );
  const flowNodes = graph.nodes.filter((n) => flowIds.has(n.id));
  const stepEdges = graph.edges.filter(
    (e) => e.type === "flow_step" && flowIds.has(e.source),
  );
  const stepIds = new Set(stepEdges.map((e) => e.target));
  const stepNodes = graph.nodes.filter((n) => stepIds.has(n.id));

  // 步骤顺序:weight 即顺序
  const stepOrderMap = new Map<string, number>();
  for (const edge of stepEdges) {
    stepOrderMap.set(edge.target, edge.weight);
  }
  // 每个 flow 的步骤数
  const stepCountMap = new Map<string, number>();
  for (const edge of stepEdges) {
    stepCountMap.set(edge.source, (stepCountMap.get(edge.source) ?? 0) + 1);
  }
  // ... flow 节点 260x120,step 节点 200x90 ...
  // step 的顺序号 = round(weight * 10)
  return applyDagreLayout(rfNodes, rfEdges, "LR", dims);
}

这里 Math.round((stepOrderMap.get(node.id) ?? 0) * 10)flow_step 边的 weight(0.1/0.2/0.3……)还原为整数步骤序号 1/2/3……,与 agent 输出规则"flow_step weight encodes order"首尾呼应。

7.3 从 dagre 到 ELK:布局引擎的演进

对照当前源码(DomainGraphView.tsx),可以推断项目在后续版本中将布局引擎从 dagre 切换为 ELK,但刻意保留了横向方向语义

// DomainGraphView used dagre LR; preserve that direction with ELK.
const elkInput = nodesToElkInput(nodesArray, edgesArray, dims, {
  "elk.direction": "RIGHT",
});
applyElkLayout(elkInput, { strict: import.meta.env.DEV })
  .then(({ positioned, issues }) => { /* ... */ })

布局调用改为异步 useEffect(带 cancelled 竞态保护),布局问题通过 appendLayoutIssues 汇入全局告警横幅。节点尺寸常量(domain 320×180、flow 260×120、step 200×90)与计划一致。当没有领域图时,视图渲染引导文案:No domain graph available. Run /understand-domain to generate one.DomainGraphView.tsx),形成"dashboard 提示 → 用户执行技能"的闭环。

八、NodeInfo 侧栏的领域感知

计划 Task 10 在 NodeInfo.tsx 中新增 DomainNodeDetails 组件(当前实现见 NodeInfo.tsx),为三类领域节点提供差异化详情面板:

  • domain 节点:渲染 domainMeta.entities(chips)、businessRules(带 accent 符号的列表)、crossDomainInteractions(列表),以及由 contains_flow 边解析出的可点击 flow 列表(点击同时 navigateToDomain + selectNode);
  • flow 节点:渲染 entryPoint(等宽字体)与按 weight 排序(.sort((a, b) => a.weight - b.weight))的步骤有序列表,每步可点击跳转;
  • step 节点:渲染 filePathlineRange文件路径:起始行-结束行 格式),提供定位到实现的锚点。

主组件侧的接线逻辑是"活动图"概念:

const viewMode = useDashboardStore((s) => s.viewMode);
const domainGraph = useDashboardStore((s) => s.domainGraph);

const activeGraph = viewMode === "domain" && domainGraph ? domainGraph : graph;
const node = activeGraph?.nodes.find((n) => n.id === selectedNodeId);

当前源码(NodeInfo.tsx)与计划一致,并在详情区条件挂载:

{activeGraph && node && (node.type === "domain" || node.type === "flow" || node.type === "step") && (
  <DomainNodeDetails node={node} graph={activeGraph} />
)}

计划 Task 11 还要求把领域类型纳入 GraphView.tsxNODE_TYPE_TO_CATEGORY 映射(domain/flow/step 均归入 "code" 类别以便复用现有过滤器面板),该改动确保领域节点在结构视图侧的过滤体系中不丢失。

九、domain-analyzer Agent:三层业务语义与输出 Schema

计划 Task 12 定义了 domain-analyzer.md agent 提示词。它把代码库的分析对象从"符号"提升为三层业务语义:

  1. Business Domain——高层业务区域(如 "Order Management"、"User Authentication"、"Payment Processing");
  2. Business Flow——领域内的具体流程(如 "Create Order"、"Process Refund");
  3. Business Step——流程内的单个动作(如 "Validate input"、"Check inventory")。

agent 要求产出的 JSON 结构如下(计划原文 schema,与 core 包校验逻辑一一对应):

{
  "version": "1.0.0",
  "project": {
    "name": "<project name>",
    "languages": ["<detected languages>"],
    "frameworks": ["<detected frameworks>"],
    "description": "<project description focused on business purpose>",
    "analyzedAt": "<ISO timestamp>",
    "gitCommitHash": "<commit hash>"
  },
  "nodes": [
    {
      "id": "domain:<kebab-case-name>",
      "type": "domain",
      "name": "<Human Readable Domain Name>",
      "summary": "<2-3 sentences about what this domain handles>",
      "tags": ["<relevant-tags>"],
      "complexity": "simple|moderate|complex",
      "domainMeta": {
        "entities": ["<key domain objects>"],
        "businessRules": ["<important constraints/invariants>"],
        "crossDomainInteractions": ["<how this domain interacts with others>"]
      }
    },
    {
      "id": "flow:<kebab-case-name>",
      "type": "flow",
      "name": "<Flow Name>",
      "summary": "<what this flow accomplishes>",
      "tags": ["<relevant-tags>"],
      "complexity": "simple|moderate|complex",
      "domainMeta": {
        "entryPoint": "<trigger, e.g. POST /api/orders>",
        "entryType": "http|cli|event|cron|manual"
      }
    },
    {
      "id": "step:<flow-name>:<step-name>",
      "type": "step",
      "name": "<Step Name>",
      "summary": "<what this step does>",
      "tags": ["<relevant-tags>"],
      "complexity": "simple|moderate|complex",
      "filePath": "<relative path to implementing file>",
      "lineRange": [<start>, <end>]
    }
  ],
  "edges": [
    { "source": "domain:<name>", "target": "flow:<name>", "type": "contains_flow", "direction": "forward", "weight": 1.0 },
    { "source": "flow:<name>", "target": "step:<flow>:<step>", "type": "flow_step", "direction": "forward", "weight": 0.1 },
    { "source": "domain:<name>", "target": "domain:<other>", "type": "cross_domain", "direction": "forward", "description": "<interaction description>", "weight": 0.6 }
  ],
  "layers": [],
  "tour": []
}

计划附带的 7 条产出规则(agent 提示词原文)值得逐条理解:

  1. flow_step 的 weight 编码顺序:第一步 0.1、第二步 0.2……(dashboard 正是靠它排序与编号);
  2. 每个 flow 必须经 contains_flow 连接到某个 domain;
  3. 每个 step 必须经 flow_step 连接到某个 flow;
  4. 跨领域边用 cross_domain 并描述交互方式;
  5. step 节点的 filePath 相对项目根;
  6. 用代码中真实的业务术语,不泛化("Be specific, not generic");
  7. 不虚构代码中不存在的流程——只记录实际存在的内容。

最后一条约束是整个功能可信度的基石:领域图必须能从代码中找到落点,而不是 LLM 的"合理想象"。

十、/understand-domain 技能管线:两条路径、六个阶段

计划 Task 13 创建了 skills/understand-domain/SKILL.md,定义了完整的执行管线。技能 frontmatter 声明了 argument-hint: [--full]--full 用于在既有图谱存在时强制走全量扫描路径。

10.1 两条分析路径的选择

  • Path 1(轻量扫描):无既有知识图谱时执行。运行随技能捆绑的预处理器 extract-domain-context.py
python ./extract-domain-context.py "$PROJECT_ROOT"

产出 $UA_DIR/intermediate/domain-context.json,包含:文件树(遵守 .gitignore)、检测到的入口点(HTTP 路由、CLI 命令、事件处理器、cron 任务、导出 handler)、每个文件的签名(exports/imports)、每个入口点的代码片段(签名 + 前几行)。从设计意图看,这一步是廉价 Python 预处理喂给昂贵 LLM 的"小抄":把几十个文件探索工具调用压缩为一次脚本执行,让 domain-analyzer 专注于真正的领域推理。

  • Path 2(从图谱派生).ua/knowledge-graph.json(或旧目录 .understand-anything/knowledge-graph.json)已存在且未传 --full 时,直接把图谱格式化结构化为上下文——所有节点(类型/名称/摘要/标签)、所有边(尤其 calls/imports/contains)、所有 layer 描述、tour 步骤——零文件扫描,代价最低。

10.2 六阶段流程

阶段 动作 产物
Phase 1 检测既有图谱并做新鲜度预检 决定走 Path 1 或 Path 2
Phase 2 轻量扫描(仅 Path 1) $UA_DIR/intermediate/domain-context.json
Phase 3 图谱派生(仅 Path 2) 结构化图谱上下文(不落盘)
Phase 4 派发 domain-analyzer 子代理 $UA_DIR/intermediate/domain-analysis.json
Phase 5 标准校验管线验证、容错保存 $UA_DIR/domain-graph.json(清理 intermediate 文件)
Phase 6 自动触发 /understand-dashboard dashboard 检测到 domain-graph.json 并默认展示领域视图

Phase 5 的"容错"策略值得强调:校验失败时记录警告但保存有效部分(error tolerance),与 core 包 validateGraph 的分级设计(auto-corrected / dropped / fatal,见 schema.ts)一脉相承——LLM 产出的图允许局部瑕疵,不允许整体报废。

10.3 当前实现相对计划的增强

对照当前 SKILL.md,实现阶段追加了两块计划中未展开的工程细节:

  1. Phase 0 的 worktree 重定向:若 PROJECT_ROOT 位于 git worktree 内,通过比较 git rev-parse --git-dirgit rev-parse --git-common-dir 检测,并把输出重定向到主仓库根,防止 Claude Code 临时 worktree 会话结束连带销毁领域图(issue #133);可用环境变量 UNDERSTAND_NO_WORKTREE_REDIRECT=1 关闭;
  2. 新鲜度预检:Path 2 派生前会比较图谱 project.gitCommitHash 与当前 HEAD 之间的项目级 diff(git diff --name-only "$GRAPH_COMMIT" HEAD -- . 等四条命令),若项目文件有变更则提示先运行 /understand 刷新;-- . 路径限定确保 monorepo 中兄弟项目的提交不会误判本项目图谱过期。

十一、构建、测试与文件清单

计划 Task 4 与 Task 15 定义了统一的验证命令矩阵,全部基于 pnpm workspace 的 --filter 机制:

# core 包构建与测试
cd understand-anything-plugin && pnpm --filter @understand-anything/core build
cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run

# dashboard 构建
cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build

# 全仓 lint
cd understand-anything-plugin && pnpm lint

计划 Task 14 则确认了 dashboard 服务器侧无需特殊改动:服务器按目录整体服务数据文件,domain-graph.jsonknowledge-graph.jsonmeta.json 并列自动可用;App.tsx 中的 fetch 已验证该假设(App.tsx 中甚至为 demo 模式准备了 VITE_DOMAIN_GRAPH_URL 注入点)。

最后按计划的文件总清单收尾,全部文件在当前仓库中均可查证:

新增文件

文件 职责
domain-types.test.ts 领域节点/边类型与别名归一化测试
domain-persistence.test.ts domain-graph.json 存取与隔离测试
domain-normalize.test.ts domain/flow/step ID 前缀规范化测试
DomainClusterNode.tsx 领域簇节点(概览层)
FlowNode.tsx 流程节点(明细层)
StepNode.tsx 步骤节点(明细层)
DomainGraphView.tsx 领域视图总组件(overview/detail 双构建器)
domain-analyzer.md 领域分析 agent 提示词
understand-domain/SKILL.md /understand-domain 技能定义
extract-domain-context.py Path 1 轻量扫描预处理器

修改文件

文件 改动
types.ts 3 节点类型、3 边类型、DomainMeta 接口
schema.ts Zod 枚举扩展 + 领域节点/边别名表 + .passthrough()
persistence/index.ts saveDomainGraph/loadDomainGraph
normalize-graph.ts domain/flow/step 前缀与 step 去碰撞重建
store.ts viewMode/domainGraph/activeDomainId 状态与 DOMAIN_EDGE_TYPES
App.tsx 领域图加载、pill 切换、条件渲染
NodeInfo.tsx DomainNodeDetails 领域感知侧栏
GraphView.tsx NODE_TYPE_TO_CATEGORY 纳入领域类型

十二、小结

这套业务领域知识提取功能的工程价值在于三点:schema 级的前向兼容.passthrough() + 别名归一化让 LLM 的非规范输出可收敛)、数据文件的职责隔离domain-graph.json 独立于结构图谱,可单独缺失、独立刷新)以及渲染层对数据语义的忠实映射(weight 即顺序、contains_flow 计数、cross_domain 动画虚线、双击下钻)。对使用方而言,只需在安装了 Understand-Anything 插件的代码库中执行 /understand-domain(可选 --full),即可获得一张可交互的横向业务流图;对阅读源码者而言,上文按"类型 → 持久化 → 规范化 → dashboard → agent/技能"的调用链,提供了从 types.tsSKILL.md 的完整定位索引。

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