feat(skill): add /understand-explain command for deep-dive file analysis

Adds the explain-builder module that constructs rich context for explaining
a specific file or function. Supports file paths and path:function notation,
gathers child nodes, connected components, layer membership, and formats
a structured prompt for LLM-powered deep-dive explanations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lum1104
2026-03-14 22:31:09 +08:00
Unverified
parent e69acffd06
commit eb0d2eaac0
4 changed files with 307 additions and 0 deletions
@@ -0,0 +1,24 @@
---
name: understand-explain
description: Deep-dive explanation of a specific file or function using the knowledge graph
arguments: path
---
# /understand-explain
Provide a thorough, in-depth explanation of a specific code component.
## Instructions
1. Read the knowledge graph file at `.understand-anything/knowledge-graph.json`
2. If it doesn't exist, tell the user to run `/understand` first
3. Find the component matching the path: "${ARGUMENTS}"
- Supports file paths: `src/auth/login.ts`
- Supports function notation: `src/auth/login.ts:verifyToken`
4. Analyze the component in context:
- Its role in the architecture (which layer, why it exists)
- Internal structure (functions, classes it contains)
- External connections (what it imports, what calls it, what it depends on)
- Data flow (inputs -> processing -> outputs)
5. Explain clearly, assuming the reader may not know the programming language
6. Highlight any patterns, idioms, or complexity worth understanding
@@ -0,0 +1,82 @@
import { describe, it, expect } from "vitest";
import { buildExplainContext, formatExplainPrompt } from "../explain-builder.js";
import type { KnowledgeGraph } from "@understand-anything/core";
const sampleGraph: KnowledgeGraph = {
version: "1.0.0",
project: {
name: "test-project",
languages: ["typescript"],
frameworks: ["express"],
description: "A test project",
analyzedAt: "2026-03-14T00:00:00Z",
gitCommitHash: "abc123",
},
nodes: [
{ id: "file:src/auth.ts", type: "file", name: "auth.ts", filePath: "src/auth.ts", summary: "Auth module", tags: ["auth"], complexity: "complex" },
{ id: "func:src/auth.ts:login", type: "function", name: "login", filePath: "src/auth.ts", lineRange: [10, 30], summary: "Login handler", tags: ["auth", "login"], complexity: "moderate" },
{ id: "func:src/auth.ts:verify", type: "function", name: "verify", filePath: "src/auth.ts", lineRange: [32, 50], summary: "Token verification", tags: ["auth", "jwt"], complexity: "moderate" },
{ id: "file:src/db.ts", type: "file", name: "db.ts", filePath: "src/db.ts", summary: "Database", tags: ["db"], complexity: "simple" },
],
edges: [
{ source: "file:src/auth.ts", target: "func:src/auth.ts:login", type: "contains", direction: "forward", weight: 1.0 },
{ source: "file:src/auth.ts", target: "func:src/auth.ts:verify", type: "contains", direction: "forward", weight: 1.0 },
{ source: "func:src/auth.ts:login", target: "file:src/db.ts", type: "reads_from", direction: "forward", weight: 0.8 },
],
layers: [
{ id: "layer:auth", name: "Auth Layer", description: "Authentication", nodeIds: ["file:src/auth.ts", "func:src/auth.ts:login", "func:src/auth.ts:verify"] },
],
tour: [],
};
describe("explain-builder", () => {
describe("buildExplainContext", () => {
it("finds the file node by path", () => {
const ctx = buildExplainContext(sampleGraph, "src/auth.ts");
expect(ctx.targetNode?.id).toBe("file:src/auth.ts");
});
it("includes child nodes (functions/classes in the file)", () => {
const ctx = buildExplainContext(sampleGraph, "src/auth.ts");
expect(ctx.childNodes.map((n) => n.name)).toContain("login");
expect(ctx.childNodes.map((n) => n.name)).toContain("verify");
});
it("includes connected nodes", () => {
const ctx = buildExplainContext(sampleGraph, "src/auth.ts");
const allIds = ctx.connectedNodes.map((n) => n.id);
expect(allIds).toContain("file:src/db.ts");
});
it("includes the layer", () => {
const ctx = buildExplainContext(sampleGraph, "src/auth.ts");
expect(ctx.layer?.name).toBe("Auth Layer");
});
it("returns null targetNode for unknown paths", () => {
const ctx = buildExplainContext(sampleGraph, "src/unknown.ts");
expect(ctx.targetNode).toBeNull();
});
it("finds function nodes by partial path match", () => {
const ctx = buildExplainContext(sampleGraph, "src/auth.ts:login");
expect(ctx.targetNode?.name).toBe("login");
});
});
describe("formatExplainPrompt", () => {
it("produces structured markdown for valid context", () => {
const ctx = buildExplainContext(sampleGraph, "src/auth.ts");
const prompt = formatExplainPrompt(ctx);
expect(prompt).toContain("auth.ts");
expect(prompt).toContain("login");
expect(prompt).toContain("Auth Layer");
});
it("produces helpful message for unknown path", () => {
const ctx = buildExplainContext(sampleGraph, "src/unknown.ts");
const prompt = formatExplainPrompt(ctx);
expect(prompt).toContain("not found");
});
});
});
+196
View File
@@ -0,0 +1,196 @@
import type {
KnowledgeGraph,
GraphNode,
GraphEdge,
Layer,
} from "@understand-anything/core";
export interface ExplainContext {
projectName: string;
path: string;
targetNode: GraphNode | null;
childNodes: GraphNode[];
connectedNodes: GraphNode[];
relevantEdges: GraphEdge[];
layer: Layer | null;
}
/**
* Build a context for explaining a specific file or function.
* Supports file paths ("src/auth.ts") and path:function ("src/auth.ts:login").
*/
export function buildExplainContext(
graph: KnowledgeGraph,
path: string,
): ExplainContext {
const { nodes, edges, layers } = graph;
let targetNode: GraphNode | null = null;
// Check for path:function format (e.g. "src/auth.ts:login")
const colonIdx = path.lastIndexOf(":");
if (colonIdx > 0 && !path.includes("://")) {
const filePath = path.slice(0, colonIdx);
const funcName = path.slice(colonIdx + 1);
targetNode =
nodes.find(
(n) => n.filePath === filePath && n.name === funcName,
) ?? null;
}
// Fall back to file path match
if (!targetNode) {
targetNode = nodes.find((n) => n.filePath === path) ?? null;
}
if (!targetNode) {
return {
projectName: graph.project.name,
path,
targetNode: null,
childNodes: [],
connectedNodes: [],
relevantEdges: [],
layer: null,
};
}
// Find child nodes (contained by this node via "contains" edges)
const childNodes = nodes.filter((n) =>
edges.some(
(e) =>
e.source === targetNode!.id &&
e.target === n.id &&
e.type === "contains",
),
);
const allRelatedIds = new Set([
targetNode.id,
...childNodes.map((n) => n.id),
]);
// Find connected nodes (1-hop neighbors, excluding children and self)
const connectedIds = new Set<string>();
const relevantEdges: GraphEdge[] = [];
for (const edge of edges) {
if (allRelatedIds.has(edge.source) || allRelatedIds.has(edge.target)) {
relevantEdges.push(edge);
if (allRelatedIds.has(edge.source) && !allRelatedIds.has(edge.target)) {
connectedIds.add(edge.target);
}
if (allRelatedIds.has(edge.target) && !allRelatedIds.has(edge.source)) {
connectedIds.add(edge.source);
}
}
}
const connectedNodes = nodes.filter((n) => connectedIds.has(n.id));
const layer =
layers.find((l) => l.nodeIds.includes(targetNode!.id)) ?? null;
return {
projectName: graph.project.name,
path,
targetNode,
childNodes,
connectedNodes,
relevantEdges,
layer,
};
}
/**
* Format the explain context as a structured prompt for LLM consumption.
*/
export function formatExplainPrompt(ctx: ExplainContext): string {
if (!ctx.targetNode) {
return [
`# Component Not Found`,
``,
`The path "${ctx.path}" was not found in the knowledge graph for ${ctx.projectName}.`,
``,
`Possible reasons:`,
`- The file hasn't been analyzed yet — try running /understand first`,
`- The path may be different in the graph — check the exact file path`,
`- The file may have been deleted or renamed since the last analysis`,
].join("\n");
}
const { targetNode, childNodes, connectedNodes, relevantEdges, layer } = ctx;
const lines: string[] = [];
lines.push(`# Deep Dive: ${targetNode.name}`);
lines.push("");
lines.push(
`**Type:** ${targetNode.type} | **Complexity:** ${targetNode.complexity}`,
);
if (targetNode.filePath)
lines.push(`**File:** \`${targetNode.filePath}\``);
if (targetNode.lineRange)
lines.push(
`**Lines:** ${targetNode.lineRange[0]}-${targetNode.lineRange[1]}`,
);
lines.push("");
lines.push(`**Summary:** ${targetNode.summary}`);
lines.push("");
if (layer) {
lines.push(`## Architectural Layer: ${layer.name}`);
lines.push(layer.description);
lines.push("");
}
if (childNodes.length > 0) {
lines.push("## Internal Components");
for (const child of childNodes) {
lines.push(`- **${child.name}** (${child.type}): ${child.summary}`);
}
lines.push("");
}
if (connectedNodes.length > 0) {
lines.push("## Connected Components");
for (const node of connectedNodes) {
lines.push(`- **${node.name}** (${node.type}): ${node.summary}`);
}
lines.push("");
}
if (relevantEdges.length > 0) {
const nodeMap = new Map(
[...[targetNode], ...childNodes, ...connectedNodes].map((n) => [
n.id,
n,
]),
);
lines.push("## Relationships");
for (const edge of relevantEdges) {
if (edge.type === "contains") continue;
const src = nodeMap.get(edge.source)?.name ?? edge.source;
const tgt = nodeMap.get(edge.target)?.name ?? edge.target;
const desc = edge.description ? `${edge.description}` : "";
lines.push(`- ${src} --[${edge.type}]--> ${tgt}${desc}`);
}
lines.push("");
}
if (targetNode.languageNotes) {
lines.push("## Language Notes");
lines.push(targetNode.languageNotes);
lines.push("");
}
lines.push("## Instructions");
lines.push("Provide a thorough explanation of this component:");
lines.push("1. What it does and why it exists in the project");
lines.push("2. How data flows through it (inputs, processing, outputs)");
lines.push("3. How it interacts with connected components");
lines.push("4. Any patterns, idioms, or design decisions worth noting");
lines.push("5. Potential gotchas or areas of complexity");
lines.push("");
return lines.join("\n");
}
+5
View File
@@ -9,3 +9,8 @@ export {
formatDiffAnalysis,
type DiffContext,
} from "./diff-analyzer.js";
export {
buildExplainContext,
formatExplainPrompt,
type ExplainContext,
} from "./explain-builder.js";