diff --git a/docs/plans/2026-04-01-business-domain-knowledge-design.md b/docs/plans/2026-04-01-business-domain-knowledge-design.md new file mode 100644 index 0000000..52bffc2 --- /dev/null +++ b/docs/plans/2026-04-01-business-domain-knowledge-design.md @@ -0,0 +1,335 @@ +# Business Domain Knowledge Extraction — Design Spec + +**Issue:** [#61](https://github.com/Lum1104/Understand-Anything/issues/61) +**Date:** 2026-04-01 + +## Problem + +The current knowledge graph shows file-level dependency relationships, but this has limited value — you can already see imports in an IDE. When files are many, listing dependency edges doesn't reduce cognitive load; you still mentally reconstruct what the code *does*. What's needed is business domain knowledge: the logic and domain concepts embedded within the code, not the structural wiring. + +## Solution Overview + +A new `/understand-domain` skill that extracts business domain knowledge and renders it as a horizontal flow graph in the dashboard. Two viewing modes: a high-level **Domain view** (default when available) and the existing **Structural view**, with a toggle to switch between them. + +## Architecture: Separate File, Shared Schema (Approach C) + +Domain data lives in a **separate file** (`domain-graph.json`) using the **same `KnowledgeGraph` type system** — extended with new node/edge types. The dashboard detects both files and offers a view toggle. Domain nodes can reference structural nodes by ID for drill-down. + +**Why separate files:** +- `/understand-domain` works standalone (lightweight) or alongside full graph +- Shared schema means search, validation, and filtering work for both +- No risk of polluting the structural graph +- Each file is independently valid + +## Section 1: Domain Graph Schema + +### Three-Level Hierarchy + +1. **Business Domain** (top) — e.g., "Purchasing", "Logistics", "Warehouse Management" +2. **Business Flow** (mid) — e.g., "Create Order", "Process Refund" +3. **Business Step** (leaf) — e.g., "Validate input", "Check inventory", "Persist order" + +### New Node Types (3) + +| Type | Purpose | Example | +|------|---------|---------| +| `domain` | Business domain cluster | "Order Management", "Logistics" | +| `flow` | A business process within a domain | "Create Order", "Process Refund" | +| `step` | A single step in a flow | "Validate order input" | + +### New Edge Types (4) + +| Type | Purpose | +|------|---------| +| `contains_flow` | domain → flow | +| `flow_step` | flow → step (ordered via `weight` field, e.g., 0.1, 0.2, ...) | +| `cross_domain` | domain → domain (interaction between domains) | +| `implements` | step → file/function node ID (reference into structural graph) | + +### Domain Node Structure + +```typescript +// domain node +{ + id: "domain:order-management", + type: "domain", + name: "Order Management", + summary: "Handles the complete order lifecycle...", + tags: ["e-commerce", "core-business"], + complexity: "complex", + domainMeta?: { + entities: ["Order", "LineItem", "OrderStatus"], + businessRules: ["Orders require inventory check before confirmation"], + crossDomainInteractions: ["Triggers Logistics on order confirmed", "Reads from Customer Service for buyer info"] + } +} +``` + +### Flow Node Structure + +```typescript +{ + id: "flow:create-order", + type: "flow", + name: "Create Order", + summary: "Customer submits a new order through the API", + tags: ["write-path", "api"], + complexity: "moderate", + domainMeta?: { + entryPoint: "POST /api/orders", + entryType: "http" | "cli" | "event" | "cron" | "manual" + } +} +``` + +### Step Node Structure + +```typescript +{ + id: "step:create-order:validate-input", + type: "step", + name: "Validate order input", + summary: "Checks request body against order schema, rejects invalid payloads", + tags: ["validation"], + complexity: "simple", + filePath: "src/validators/order-validator.ts", + lineRange: [12, 45] +} +``` + +### File Output + +Saved to `.understand-anything/domain-graph.json` — same `KnowledgeGraph` shape, valid on its own. + +## Section 2: Analysis Pipeline + +### Two Paths, Same Output + +**Path 1: Lightweight scan (no existing graph)** + +``` +File tree scan + → Static entry point detection (tree-sitter) + → Route definitions, exported handlers, main(), event listeners, cron decorators + → Feed to LLM: file tree + detected entry points + sampled file contents + → LLM outputs: domains, flows, steps, cross-domain interactions + → Build domain-graph.json +``` + +Token cost: ~10-20% of a full `/understand` scan. + +**Path 2: Derive from existing graph** + +``` +Load knowledge-graph.json + → Extract: all nodes, edges, layers, summaries, tour + → Feed to LLM: graph data as structured context + → LLM outputs: domains, flows, steps, cross-domain interactions + → Build domain-graph.json +``` + +Very cheap — no file reading needed, LLM reasons over existing summaries and call edges. + +**Path Selection:** `/understand-domain` checks if `.understand-anything/knowledge-graph.json` exists. If yes → Path 2. If no → Path 1. + +### Agent Structure + +One new agent: **`domain-analyzer`** (opus model). Handles both paths. For large codebases, can batch by detected entry point groups. + +## Section 3: Preprocessing Script & Skill Integration + +### Script: `understand-anything-plugin/skills/understand-domain/extract-domain-context.py` + +Bundled with the skill (not in `scripts/` which is for development tooling). Runs before the LLM agent. Outputs `.understand-anything/intermediate/domain-context.json`: + +```json +{ + "fileTree": ["src/api/orders.ts", "src/services/...", "..."], + "entryPoints": [ + { + "file": "src/api/orders.ts", + "type": "http", + "method": "POST", + "path": "/api/orders", + "handler": "createOrder", + "lineRange": [15, 45], + "snippet": "async function createOrder(req, res) { ... }" + } + ], + "fileSignatures": { + "src/services/order-service.ts": { + "exports": ["createOrder", "cancelOrder", "getOrderById"], + "imports": ["inventory-service", "pricing-service", "order-repo"], + "summary": null + } + } +} +``` + +Python script (no heavy dependencies — uses `ast` for Python, regex for other languages). Uses: +- Walk the file tree (respecting `.gitignore`) +- Detect entry points by pattern: route decorators, `app.get/post`, `export default handler`, `main()`, event listeners +- Extract function signatures and import/export lists per file +- Keep code snippets short (signature + first few lines, not full bodies) + +### Skill Integration + +The `/understand-domain` skill markdown: + +1. Runs `understand-anything-plugin/skills/understand-domain/extract-domain-context.py` +2. Checks for existing `knowledge-graph.json` +3. If exists → passes both `domain-context.json` + graph data to domain-analyzer agent +4. If not → passes only `domain-context.json` +5. Agent outputs `domain-graph.json` +6. Cleans up intermediate files +7. Auto-triggers `/understand-dashboard` + +## Section 4: Dashboard — Domain View + +### View Toggle + +- Top-left corner: pill toggle — **"Domain" / "Structural"** +- Domain view is default when `domain-graph.json` exists +- If only one graph file exists, no toggle shown +- Switching views preserves sidebar state + +### Horizontal Flow Layout + +- **Layout engine:** Dagre with `rankdir: "LR"` (left-to-right) +- **Zoom levels:** + - **Zoomed out:** Domain clusters as large rounded rectangles, `cross_domain` edges between them + - **Click domain:** Expands to show flows as horizontal lanes + - **Click flow:** Shows step-by-step trace left-to-right + +### Domain Cluster Rendering + +``` +┌─────────────────────────────────────┐ +│ Order Management │ +│ "Handles the complete order..." │ +│ │ +│ Entities: Order, LineItem, Status │ +│ Flows: Create Order, Cancel Order │ +│ Rules: "Requires inventory check" │ +└─────────────────────────────────────┘ + ──cross_domain──→ [Logistics] +``` + +- Gold/amber border for domain clusters (matches existing theme) +- Shows summary, entity list, flow count on the cluster face +- Cross-domain edges: thick dashed lines with labels + +### Flow Trace Rendering + +``` +POST /api/orders + ┌──────────┐ ┌──────────────┐ ┌───────────┐ ┌──────────┐ ┌────────────┐ + │ Validate │───→│ Check │───→│ Calculate │───→│ Persist │───→│ Send │ + │ Input │ │ Inventory │ │ Pricing │ │ Order │ │ Confirm │ + └──────────┘ └──────────────┘ └───────────┘ └──────────┘ └────────────┘ +``` + +- Steps connected left-to-right by `flow_step` edges (ordered by `weight`) +- Entry point label at the left as flow trigger +- Clicking a step → sidebar shows detail + link to structural view + +### Sidebar Adaptations + +**Domain node selected:** Summary, business rules, entities, cross-domain interactions, list of flows (clickable) + +**Flow node selected:** Entry point info, step list in order, complexity + +**Step node selected:** Description, "View in code" link (switches to structural view + navigates to file/function), previous/next step links + +### Drill-Down: Domain → Structural + +When a step has an `implements` edge referencing a structural node ID: +- "View implementation" button in sidebar +- Switches to structural view and navigates to that node +- Breadcrumb: `Domain: Order Management > Flow: Create Order > Step: Validate Input → [structural view]` + +## Section 5: Skill Definition + +### `/understand-domain` Skill + +- **File:** `skills/understand-domain.md` +- **Arguments:** Optional `--full` flag to force Path 1 (rescan even if graph exists) + +### Execution Flow + +``` +1. Run scripts/extract-domain-context.mjs +2. Check for .understand-anything/knowledge-graph.json + ├── Exists → Path 2: load graph + domain-context.json + └── Missing → Path 1: domain-context.json only +3. Invoke domain-analyzer agent (opus) +4. Validate output against schema +5. Save .understand-anything/domain-graph.json +6. Clean up intermediate/domain-context.json +7. Auto-trigger /understand-dashboard +``` + +### Domain Analyzer Agent + +- **File:** `agents/domain-analyzer.md` +- **Model:** opus +- **Input:** Either (file tree + entry points) or (existing knowledge graph) +- **Output:** Complete domain graph JSON + +### Change Map + +| Area | Changes | +|------|---------| +| `packages/core/src/types.ts` | Add 3 node types, 4 edge types, `domainMeta` optional field | +| `packages/core/src/schema.ts` | Extend Zod schemas + aliases for new types | +| `packages/core/src/persistence/` | Add `loadDomainGraph()` / `saveDomainGraph()` | +| `understand-anything-plugin/skills/understand-domain/extract-domain-context.py` | New preprocessing script (bundled with skill) | +| `agents/domain-analyzer.md` | New agent definition | +| `skills/understand-domain.md` | New skill definition | +| `packages/dashboard/src/store.ts` | Add `domainGraph`, `viewMode` state | +| `packages/dashboard/src/components/` | New: `DomainGraphView.tsx`, `DomainClusterNode.tsx`, `FlowTraceNode.tsx`, `StepNode.tsx` | +| `packages/dashboard/src/components/` | Modify: `App.tsx` (view toggle), `NodeInfo.tsx` (domain sidebar), `FilterPanel.tsx` (domain filters) | +| `packages/dashboard/src/utils/` | New: `domain-layout.ts` (horizontal Dagre config) | + +## Section 6: Error Tolerance + +### Pipeline-Level Tolerance + +| Stage | Error Handling | +|-------|---------------| +| Preprocessing script | If tree-sitter fails on a file, skip and continue. Log skipped files. Entry point detection is best-effort. | +| LLM output parsing | Same strategy as existing `parseTourGenerationResponse()` — extract JSON from markdown, handle partial responses. | +| Schema validation | Existing auto-fix pipeline: sanitize → normalize (aliases) → apply defaults → validate. Drop broken nodes/edges, don't fail the whole graph. | +| Cross-graph references | `implements` edges pointing to non-existent structural node IDs → keep edge but mark as `unresolved`. Dashboard shows step without drill-down link. | + +### Domain-Specific Validation Rules + +- **Domain with no flows:** Warn, keep (summary/entities still useful) +- **Flow with no steps:** Warn, keep (entry point info still valuable) +- **Steps with broken ordering:** Re-number sequentially by array position if `weight` values missing/duplicate +- **Orphan steps:** Steps not connected to any flow → attach to synthetic "Uncategorized" flow +- **Duplicate domains:** Merge by name similarity (fuzzy match), combine flows +- **Empty domain graph:** Error banner in dashboard: "Domain extraction failed — try running `/understand` first for richer context, then `/understand-domain`" + +### Dashboard Resilience + +- If `domainMeta` missing on a domain node, sidebar shows only summary/tags +- If `domain-graph.json` fails validation entirely, fall back to structural view with warning banner +- Partial graphs render what's valid + +### Normalization Aliases for Domain Types + +```typescript +// Node type aliases +"business_domain" → "domain" +"process" → "flow" +"workflow" → "flow" +"action" → "step" +"task" → "step" + +// Edge type aliases +"has_flow" → "contains_flow" +"next_step" → "flow_step" +"interacts_with" → "cross_domain" +"implemented_by" → "implements" +``` diff --git a/docs/plans/2026-04-01-business-domain-knowledge-impl.md b/docs/plans/2026-04-01-business-domain-knowledge-impl.md new file mode 100644 index 0000000..b6071c0 --- /dev/null +++ b/docs/plans/2026-04-01-business-domain-knowledge-impl.md @@ -0,0 +1,1784 @@ +# Business Domain Knowledge Extraction — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a `/understand-domain` skill that extracts business domain knowledge from codebases and renders it as an interactive horizontal flow graph in the dashboard. + +**Architecture:** Separate `domain-graph.json` file using extended `KnowledgeGraph` schema (3 new node types, 4 new edge types, optional `domainMeta` field). Two analysis paths: lightweight scan (no existing graph) or derivation from existing graph. Dashboard shows domain view by default when available, with pill toggle to switch to structural view. + +**Tech Stack:** TypeScript, Zod, React Flow (dagre LR layout), Zustand, Vitest, web-tree-sitter + +**Design Spec:** `docs/plans/2026-04-01-business-domain-knowledge-design.md` + +--- + +## Task 1: Extend Core Types + +**Files:** +- Modify: `understand-anything-plugin/packages/core/src/types.ts` + +- [ ] **Step 1: Write failing test for new node types** + +Create test file: + +```typescript +// understand-anything-plugin/packages/core/src/__tests__/domain-types.test.ts +import { describe, it, expect } from "vitest"; +import { validateGraph } from "../schema.js"; +import type { KnowledgeGraph } from "../types.js"; + +const domainGraph: KnowledgeGraph = { + version: "1.0.0", + project: { + name: "test-project", + languages: ["typescript"], + frameworks: [], + description: "A test project", + analyzedAt: "2026-04-01T00:00:00.000Z", + gitCommitHash: "abc123", + }, + nodes: [ + { + id: "domain:order-management", + type: "domain", + name: "Order Management", + summary: "Handles order lifecycle", + tags: ["core"], + complexity: "complex", + }, + { + id: "flow:create-order", + type: "flow", + name: "Create Order", + summary: "Customer submits a new order", + tags: ["write-path"], + complexity: "moderate", + domainMeta: { + entryPoint: "POST /api/orders", + entryType: "http", + }, + }, + { + id: "step:create-order:validate", + type: "step", + name: "Validate Input", + summary: "Checks request body", + tags: ["validation"], + complexity: "simple", + filePath: "src/validators/order.ts", + lineRange: [10, 30], + }, + ], + edges: [ + { + source: "domain:order-management", + target: "flow:create-order", + type: "contains_flow", + direction: "forward", + weight: 1.0, + }, + { + source: "flow:create-order", + target: "step:create-order:validate", + type: "flow_step", + direction: "forward", + weight: 0.1, + }, + ], + layers: [], + tour: [], +}; + +describe("domain graph types", () => { + it("validates a domain graph with domain/flow/step node types", () => { + const result = validateGraph(domainGraph); + expect(result.success).toBe(true); + expect(result.data).toBeDefined(); + expect(result.data!.nodes).toHaveLength(3); + expect(result.data!.edges).toHaveLength(2); + }); + + it("validates contains_flow edge type", () => { + const result = validateGraph(domainGraph); + expect(result.success).toBe(true); + expect(result.data!.edges[0].type).toBe("contains_flow"); + }); + + it("validates flow_step edge type", () => { + const result = validateGraph(domainGraph); + expect(result.success).toBe(true); + expect(result.data!.edges[1].type).toBe("flow_step"); + }); + + it("validates cross_domain edge type", () => { + const graph = structuredClone(domainGraph); + graph.nodes.push({ + id: "domain:logistics", + type: "domain", + name: "Logistics", + summary: "Handles shipping", + tags: [], + complexity: "moderate", + }); + graph.edges.push({ + source: "domain:order-management", + target: "domain:logistics", + type: "cross_domain", + direction: "forward", + description: "Triggers on order confirmed", + weight: 0.6, + }); + const result = validateGraph(graph); + expect(result.success).toBe(true); + }); + + it("normalizes domain type aliases", () => { + const graph = structuredClone(domainGraph); + (graph.nodes[0] as any).type = "business_domain"; + (graph.nodes[1] as any).type = "workflow"; + (graph.nodes[2] as any).type = "action"; + const result = validateGraph(graph); + expect(result.success).toBe(true); + expect(result.data!.nodes[0].type).toBe("domain"); + expect(result.data!.nodes[1].type).toBe("flow"); + expect(result.data!.nodes[2].type).toBe("step"); + }); + + it("normalizes domain edge type aliases", () => { + const graph = structuredClone(domainGraph); + (graph.edges[0] as any).type = "has_flow"; + (graph.edges[1] as any).type = "next_step"; + const result = validateGraph(graph); + expect(result.success).toBe(true); + expect(result.data!.edges[0].type).toBe("contains_flow"); + expect(result.data!.edges[1].type).toBe("flow_step"); + }); + + it("preserves domainMeta on nodes through validation", () => { + const result = validateGraph(domainGraph); + expect(result.success).toBe(true); + // domainMeta is passthrough — schema uses .passthrough() + const flowNode = result.data!.nodes.find((n) => n.id === "flow:create-order"); + expect((flowNode as any).domainMeta).toEqual({ + entryPoint: "POST /api/orders", + entryType: "http", + }); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/domain-types.test.ts` + +Expected: FAIL — "domain" is not a valid NodeType enum value + +- [ ] **Step 3: Add domain/flow/step to NodeType union** + +In `understand-anything-plugin/packages/core/src/types.ts`, update the `NodeType` union (lines 1-5): + +```typescript +// 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"; +``` + +Update `EdgeType` union (lines 7-15): + +```typescript +// 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 +``` + +Add `DomainMeta` interface after `GraphNode` (after line 28): + +```typescript +// 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"; +} +``` + +- [ ] **Step 4: Update Zod schemas in schema.ts** + +In `understand-anything-plugin/packages/core/src/schema.ts`: + +Update `EdgeTypeSchema` (lines 4-12) to add the 4 new edge types: + +```typescript +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", +]); +``` + +Add domain aliases to `NODE_TYPE_ALIASES` (after line 52): + +```typescript + // Domain aliases + business_domain: "domain", + process: "flow", + workflow: "flow", + action: "step", + task: "step", +``` + +Note: This overwrites the existing `workflow: "pipeline"` and `action: "pipeline"` mappings. Since domain extraction is the newer, higher-priority feature, the domain aliases take precedence. The LLM prompt for structural analysis already uses `"pipeline"` directly. + +Add domain edge aliases to `EDGE_TYPE_ALIASES` (after line 81): + +```typescript + // Domain edge aliases + has_flow: "contains_flow", + next_step: "flow_step", + interacts_with: "cross_domain", + implemented_by: "implements", +``` + +Update `GraphNodeSchema` (lines 310-324) to add domain types and use `.passthrough()`: + +```typescript +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(); +``` + +The `.passthrough()` allows `domainMeta` and other extra fields to survive validation without needing to define them in Zod (keeps the schema simple and forward-compatible). + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/domain-types.test.ts` + +Expected: All 7 tests PASS + +- [ ] **Step 6: Run existing tests to verify no regressions** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run` + +Expected: All existing tests PASS + +- [ ] **Step 7: Commit** + +```bash +git add understand-anything-plugin/packages/core/src/types.ts \ + understand-anything-plugin/packages/core/src/schema.ts \ + understand-anything-plugin/packages/core/src/__tests__/domain-types.test.ts +git commit -m "feat(core): add domain/flow/step node types and domain edge types for business domain knowledge" +``` + +--- + +## Task 2: Add Domain Graph Persistence + +**Files:** +- Modify: `understand-anything-plugin/packages/core/src/persistence/index.ts` +- Modify: `understand-anything-plugin/packages/core/src/index.ts` + +- [ ] **Step 1: Write failing test for domain graph persistence** + +```typescript +// understand-anything-plugin/packages/core/src/__tests__/domain-persistence.test.ts +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { mkdirSync, rmSync, existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { saveDomainGraph, loadDomainGraph } from "../persistence/index.js"; +import type { KnowledgeGraph } from "../types.js"; + +const testRoot = join(tmpdir(), "ua-domain-persist-test"); + +const domainGraph: KnowledgeGraph = { + version: "1.0.0", + project: { + name: "test", + languages: ["typescript"], + frameworks: [], + description: "test", + analyzedAt: "2026-04-01T00:00:00.000Z", + gitCommitHash: "abc123", + }, + nodes: [ + { + id: "domain:orders", + type: "domain" as any, + name: "Orders", + summary: "Order management", + tags: [], + complexity: "moderate", + }, + ], + edges: [], + layers: [], + tour: [], +}; + +describe("domain graph persistence", () => { + beforeEach(() => { + if (existsSync(testRoot)) rmSync(testRoot, { recursive: true }); + mkdirSync(testRoot, { recursive: true }); + }); + + afterEach(() => { + if (existsSync(testRoot)) rmSync(testRoot, { recursive: true }); + }); + + it("saves and loads domain graph", () => { + saveDomainGraph(testRoot, domainGraph); + const loaded = loadDomainGraph(testRoot); + expect(loaded).not.toBeNull(); + expect(loaded!.nodes[0].id).toBe("domain:orders"); + }); + + it("returns null when no domain graph exists", () => { + const loaded = loadDomainGraph(testRoot); + expect(loaded).toBeNull(); + }); + + it("saves to domain-graph.json, not knowledge-graph.json", () => { + saveDomainGraph(testRoot, domainGraph); + const domainPath = join(testRoot, ".understand-anything", "domain-graph.json"); + const structuralPath = join(testRoot, ".understand-anything", "knowledge-graph.json"); + expect(existsSync(domainPath)).toBe(true); + expect(existsSync(structuralPath)).toBe(false); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/domain-persistence.test.ts` + +Expected: FAIL — `saveDomainGraph` is not exported + +- [ ] **Step 3: Implement saveDomainGraph and loadDomainGraph** + +Add to `understand-anything-plugin/packages/core/src/persistence/index.ts` (after `loadConfig` function, before end of file): + +```typescript +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; +} +``` + +- [ ] **Step 4: Export from core index** + +Add to `understand-anything-plugin/packages/core/src/index.ts` (after the existing persistence re-exports on line 2): + +The existing line 2 is `export * from "./persistence/index.js";` which will auto-export the new functions. No change needed — the wildcard export picks up `saveDomainGraph` and `loadDomainGraph` automatically. + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/domain-persistence.test.ts` + +Expected: All 3 tests PASS + +- [ ] **Step 6: Run all core tests for regressions** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run` + +Expected: All tests PASS + +- [ ] **Step 7: Commit** + +```bash +git add understand-anything-plugin/packages/core/src/persistence/index.ts \ + understand-anything-plugin/packages/core/src/__tests__/domain-persistence.test.ts +git commit -m "feat(core): add saveDomainGraph/loadDomainGraph persistence functions" +``` + +--- + +## Task 3: Update Normalize Graph for Domain ID Prefixes + +**Files:** +- Modify: `understand-anything-plugin/packages/core/src/analyzer/normalize-graph.ts` + +- [ ] **Step 1: Write failing test for domain ID normalization** + +```typescript +// understand-anything-plugin/packages/core/src/__tests__/domain-normalize.test.ts +import { describe, it, expect } from "vitest"; +import { normalizeNodeId } from "../analyzer/normalize-graph.js"; + +describe("domain node ID normalization", () => { + it("normalizes domain node IDs", () => { + const result = normalizeNodeId("domain:order-management", { + type: "domain", + name: "Order Management", + }); + expect(result).toBe("domain:order-management"); + }); + + it("normalizes flow node IDs", () => { + const result = normalizeNodeId("flow:create-order", { + type: "flow", + name: "Create Order", + }); + expect(result).toBe("flow:create-order"); + }); + + it("normalizes step node IDs with filePath", () => { + const result = normalizeNodeId("step:create-order:validate", { + type: "step", + name: "Validate", + filePath: "src/validators/order.ts", + }); + expect(result).toBe("step:src/validators/order.ts:validate"); + }); + + it("normalizes step node IDs without filePath", () => { + const result = normalizeNodeId("step:validate", { + type: "step", + name: "Validate", + }); + expect(result).toBe("step:validate"); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/domain-normalize.test.ts` + +Expected: FAIL — "domain" is not a valid prefix in `VALID_PREFIXES` + +- [ ] **Step 3: Add domain prefixes to normalize-graph.ts** + +In `understand-anything-plugin/packages/core/src/analyzer/normalize-graph.ts`: + +Add `"domain"`, `"flow"`, `"step"` to `VALID_PREFIXES` (lines 1-5): + +```typescript +const VALID_PREFIXES = new Set([ + "file", "func", "class", "module", "concept", + "config", "document", "service", "table", "endpoint", + "pipeline", "schema", "resource", + "domain", "flow", "step", +]); +``` + +Add to `TYPE_TO_PREFIX` map (lines 7-21): + +```typescript + domain: "domain", + flow: "flow", + step: "step", +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run src/__tests__/domain-normalize.test.ts` + +Expected: All 4 tests PASS + +- [ ] **Step 5: Run all core tests** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run` + +Expected: All tests PASS + +- [ ] **Step 6: Commit** + +```bash +git add understand-anything-plugin/packages/core/src/analyzer/normalize-graph.ts \ + understand-anything-plugin/packages/core/src/__tests__/domain-normalize.test.ts +git commit -m "feat(core): add domain/flow/step prefixes to node ID normalization" +``` + +--- + +## Task 4: Build Core Package and Verify + +**Files:** +- None (build verification) + +- [ ] **Step 1: Build core package** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core build` + +Expected: Build succeeds with no errors + +- [ ] **Step 2: Run full test suite** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run` + +Expected: All tests PASS + +- [ ] **Step 3: Commit (if any build config changes were needed)** + +Only commit if build required changes. Otherwise skip. + +--- + +## Task 5: Dashboard Store — Add Domain State + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/store.ts` + +- [ ] **Step 1: Add ViewMode type and domain state to store** + +In `understand-anything-plugin/packages/dashboard/src/store.ts`: + +Add `ViewMode` type after the existing type definitions (after line 14): + +```typescript +export type ViewMode = "structural" | "domain"; +``` + +Update `NodeType` (line 12) to include domain types: + +```typescript +export type NodeType = "file" | "function" | "class" | "module" | "concept" | "config" | "document" | "service" | "table" | "endpoint" | "pipeline" | "schema" | "resource" | "domain" | "flow" | "step"; +``` + +Update `ALL_NODE_TYPES` (line 23): + +```typescript +export const ALL_NODE_TYPES: NodeType[] = ["file", "function", "class", "module", "concept", "config", "document", "service", "table", "endpoint", "pipeline", "schema", "resource", "domain", "flow", "step"]; +``` + +Add domain edge category to `EDGE_CATEGORY_MAP` (after line 33): + +```typescript +export const EDGE_CATEGORY_MAP: Record = { + structural: ["imports", "exports", "contains", "inherits", "implements"], + behavioral: ["calls", "subscribes", "publishes", "middleware"], + "data-flow": ["reads_from", "writes_to", "transforms", "validates"], + dependencies: ["depends_on", "tested_by", "configures"], + semantic: ["related", "similar_to"], +}; + +export const DOMAIN_EDGE_TYPES = ["contains_flow", "flow_step", "cross_domain"]; +``` + +Add to `DashboardStore` interface (after line 93): + +```typescript + // Domain view + viewMode: ViewMode; + domainGraph: KnowledgeGraph | null; + activeDomainId: string | null; + + setDomainGraph: (graph: KnowledgeGraph) => void; + setViewMode: (mode: ViewMode) => void; + navigateToDomain: (domainId: string) => void; +``` + +- [ ] **Step 2: Implement domain state in the create() block** + +In the `create()` call (after line 183): + +```typescript + viewMode: "structural", + domainGraph: null, + activeDomainId: null, + + setDomainGraph: (graph) => { + set({ domainGraph: graph, viewMode: "domain" }); + }, + + setViewMode: (mode) => { + set({ + viewMode: mode, + selectedNodeId: null, + focusNodeId: null, + codeViewerOpen: false, + codeViewerNodeId: null, + }); + }, + + navigateToDomain: (domainId) => { + set({ + activeDomainId: domainId, + selectedNodeId: null, + focusNodeId: null, + }); + }, +``` + +- [ ] **Step 3: Verify dashboard builds** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` + +Expected: Build succeeds + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/store.ts +git commit -m "feat(dashboard): add domain view state to store (viewMode, domainGraph, activeDomainId)" +``` + +--- + +## Task 6: Dashboard — View Mode Toggle + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/App.tsx` + +- [ ] **Step 1: Add domain graph loading to Dashboard component** + +In `understand-anything-plugin/packages/dashboard/src/App.tsx`, add store selectors (after line 81): + +```typescript + const viewMode = useDashboardStore((s) => s.viewMode); + const setViewMode = useDashboardStore((s) => s.setViewMode); + const domainGraph = useDashboardStore((s) => s.domainGraph); + const setDomainGraph = useDashboardStore((s) => s.setDomainGraph); +``` + +Add a `useEffect` to load `domain-graph.json` (after the diff-overlay useEffect, ~line 265): + +```typescript + 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]); +``` + +- [ ] **Step 2: Add view mode toggle pill to header** + +In the header left section (after PersonaSelector, around line 290), add the toggle pill. Only show when both graphs exist: + +```typescript + {graph && domainGraph && ( + <> +
+
+ + +
+ + )} +``` + +- [ ] **Step 3: Verify dashboard builds** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` + +Expected: Build succeeds + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/App.tsx +git commit -m "feat(dashboard): add domain graph loading and view mode toggle pill" +``` + +--- + +## Task 7: Dashboard — Domain Cluster Node Component + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx` + +- [ ] **Step 1: Create the DomainClusterNode component** + +```typescript +// understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx +import { memo } from "react"; +import { Handle, Position } from "@xyflow/react"; +import type { Node, NodeProps } from "@xyflow/react"; +import { useDashboardStore } from "../store"; + +export interface DomainClusterData { + label: string; + summary: string; + entities?: string[]; + flowCount: number; + businessRules?: string[]; + domainId: string; +} + +export type DomainClusterFlowNode = Node; + +function DomainClusterNode({ data, id }: NodeProps) { + const navigateToDomain = useDashboardStore((s) => s.navigateToDomain); + const selectedNodeId = useDashboardStore((s) => s.selectedNodeId); + const selectNode = useDashboardStore((s) => s.selectNode); + const isSelected = selectedNodeId === data.domainId; + + return ( +
selectNode(data.domainId)} + onDoubleClick={() => navigateToDomain(data.domainId)} + > + + + +
+ {data.label} +
+
+ {data.summary} +
+ + {data.entities && data.entities.length > 0 && ( +
+
Entities
+
+ {data.entities.slice(0, 5).map((e) => ( + + {e} + + ))} + {data.entities.length > 5 && ( + +{data.entities.length - 5} + )} +
+
+ )} + +
+ {data.flowCount} flow{data.flowCount !== 1 ? "s" : ""} +
+
+ ); +} + +export default memo(DomainClusterNode); +``` + +- [ ] **Step 2: Verify dashboard builds** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` + +Expected: Build succeeds + +- [ ] **Step 3: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx +git commit -m "feat(dashboard): add DomainClusterNode component for domain view" +``` + +--- + +## Task 8: Dashboard — Flow and Step Node Components + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/components/FlowNode.tsx` +- Create: `understand-anything-plugin/packages/dashboard/src/components/StepNode.tsx` + +- [ ] **Step 1: Create the FlowNode component** + +```typescript +// understand-anything-plugin/packages/dashboard/src/components/FlowNode.tsx +import { memo } from "react"; +import { Handle, Position } from "@xyflow/react"; +import type { Node, NodeProps } from "@xyflow/react"; +import { useDashboardStore } from "../store"; + +export interface FlowNodeData { + label: string; + summary: string; + entryPoint?: string; + entryType?: string; + stepCount: number; + flowId: string; +} + +export type FlowFlowNode = Node; + +function FlowNode({ data }: NodeProps) { + const selectNode = useDashboardStore((s) => s.selectNode); + const selectedNodeId = useDashboardStore((s) => s.selectedNodeId); + const isSelected = selectedNodeId === data.flowId; + + return ( +
selectNode(data.flowId)} + > + + + + {data.entryPoint && ( +
+ {data.entryPoint} +
+ )} +
+ {data.label} +
+
+ {data.summary} +
+
+ {data.stepCount} step{data.stepCount !== 1 ? "s" : ""} +
+
+ ); +} + +export default memo(FlowNode); +``` + +- [ ] **Step 2: Create the StepNode component** + +```typescript +// understand-anything-plugin/packages/dashboard/src/components/StepNode.tsx +import { memo } from "react"; +import { Handle, Position } from "@xyflow/react"; +import type { Node, NodeProps } from "@xyflow/react"; +import { useDashboardStore } from "../store"; + +export interface StepNodeData { + label: string; + summary: string; + filePath?: string; + stepId: string; + order: number; +} + +export type StepFlowNode = Node; + +function StepNode({ data }: NodeProps) { + const selectNode = useDashboardStore((s) => s.selectNode); + const selectedNodeId = useDashboardStore((s) => s.selectedNodeId); + const isSelected = selectedNodeId === data.stepId; + + return ( +
selectNode(data.stepId)} + > + + + +
+ + {data.order} + + + {data.label} + +
+
+ {data.summary} +
+ {data.filePath && ( +
+ {data.filePath} +
+ )} +
+ ); +} + +export default memo(StepNode); +``` + +- [ ] **Step 3: Verify dashboard builds** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` + +Expected: Build succeeds + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/FlowNode.tsx \ + understand-anything-plugin/packages/dashboard/src/components/StepNode.tsx +git commit -m "feat(dashboard): add FlowNode and StepNode components for domain view" +``` + +--- + +## Task 9: Dashboard — Domain Graph View + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx` +- Modify: `understand-anything-plugin/packages/dashboard/src/App.tsx` + +- [ ] **Step 1: Create the DomainGraphView component** + +```typescript +// understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx +import { useCallback, useMemo } from "react"; +import { + ReactFlow, + ReactFlowProvider, + Background, + BackgroundVariant, + Controls, + MiniMap, +} from "@xyflow/react"; +import type { Edge, Node } from "@xyflow/react"; +import "@xyflow/react/dist/style.css"; + +import DomainClusterNode from "./DomainClusterNode"; +import type { DomainClusterFlowNode } from "./DomainClusterNode"; +import FlowNode from "./FlowNode"; +import type { FlowFlowNode } from "./FlowNode"; +import StepNode from "./StepNode"; +import type { StepFlowNode } from "./StepNode"; +import { useDashboardStore } from "../store"; +import { useTheme } from "../themes/index.ts"; +import { applyDagreLayout } from "../utils/layout"; +import type { KnowledgeGraph, GraphNode } from "@understand-anything/core/types"; + +const nodeTypes = { + "domain-cluster": DomainClusterNode, + "flow-node": FlowNode, + "step-node": StepNode, +}; + +// Dimensions for domain-specific nodes +const DOMAIN_NODE_DIMENSIONS = new Map(); + +function getDomainMeta(node: GraphNode): Record | undefined { + return (node as any).domainMeta; +} + +function buildDomainOverview(graph: KnowledgeGraph): { nodes: Node[]; edges: Edge[] } { + const domainNodes = graph.nodes.filter((n) => n.type === "domain"); + const flowNodes = graph.nodes.filter((n) => n.type === "flow"); + + // Count flows per domain + const flowCountMap = new Map(); + for (const edge of graph.edges) { + if (edge.type === "contains_flow") { + flowCountMap.set(edge.source, (flowCountMap.get(edge.source) ?? 0) + 1); + } + } + + const rfNodes: Node[] = domainNodes.map((node) => { + const meta = getDomainMeta(node); + const data = { + label: node.name, + summary: node.summary, + entities: meta?.entities as string[] | undefined, + flowCount: flowCountMap.get(node.id) ?? 0, + businessRules: meta?.businessRules as string[] | undefined, + domainId: node.id, + }; + DOMAIN_NODE_DIMENSIONS.set(node.id, { width: 320, height: 180 }); + return { + id: node.id, + type: "domain-cluster" as const, + position: { x: 0, y: 0 }, + data, + }; + }); + + 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); +} + +function buildDomainDetail( + graph: KnowledgeGraph, + domainId: string, +): { nodes: Node[]; edges: Edge[] } { + // Find flows for this domain + 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)); + + // Build step order map + const stepOrderMap = new Map(); + for (const edge of stepEdges) { + stepOrderMap.set(edge.target, edge.weight); + } + + // Count steps per flow + const stepCountMap = new Map(); + for (const edge of stepEdges) { + stepCountMap.set(edge.source, (stepCountMap.get(edge.source) ?? 0) + 1); + } + + const dims = new Map(); + + const rfNodes: Node[] = [ + ...flowNodes.map((node) => { + const meta = getDomainMeta(node); + dims.set(node.id, { width: 260, height: 120 }); + return { + id: node.id, + type: "flow-node" as const, + position: { x: 0, y: 0 }, + data: { + label: node.name, + summary: node.summary, + entryPoint: meta?.entryPoint as string | undefined, + entryType: meta?.entryType as string | undefined, + stepCount: stepCountMap.get(node.id) ?? 0, + flowId: node.id, + }, + }; + }), + ...stepNodes.map((node) => { + dims.set(node.id, { width: 200, height: 90 }); + return { + id: node.id, + type: "step-node" as const, + position: { x: 0, y: 0 }, + data: { + label: node.name, + summary: node.summary, + filePath: node.filePath, + stepId: node.id, + order: Math.round((stepOrderMap.get(node.id) ?? 0) * 10), + }, + }; + }), + ]; + + const rfEdges: Edge[] = stepEdges.map((e) => ({ + id: `${e.source}-${e.target}`, + source: e.source, + target: e.target, + style: { stroke: "var(--color-border-medium)", strokeWidth: 1.5 }, + animated: false, + })); + + return applyDagreLayout(rfNodes, rfEdges, "LR", dims); +} + +function DomainGraphViewInner() { + const domainGraph = useDashboardStore((s) => s.domainGraph); + const activeDomainId = useDashboardStore((s) => s.activeDomainId); + const navigateToDomain = useDashboardStore((s) => s.navigateToDomain); + const navigateToOverview = useDashboardStore((s) => s.navigateToOverview); + const theme = useTheme(); + + const { nodes, edges } = useMemo(() => { + if (!domainGraph) return { nodes: [], edges: [] }; + if (activeDomainId) { + return buildDomainDetail(domainGraph, activeDomainId); + } + return buildDomainOverview(domainGraph); + }, [domainGraph, activeDomainId]); + + const onNodeDoubleClick = useCallback( + (_: React.MouseEvent, node: Node) => { + if (node.type === "domain-cluster" && node.data && "domainId" in node.data) { + navigateToDomain(node.data.domainId as string); + } + }, + [navigateToDomain], + ); + + if (!domainGraph) { + return ( +
+ No domain graph available. Run /understand-domain to generate one. +
+ ); + } + + return ( +
+ {activeDomainId && ( +
+ +
+ )} + + + + theme.colors?.accent ?? "#d4a574"} + maskColor="rgba(0,0,0,0.7)" + style={{ bottom: 16, right: 16, width: 160, height: 100 }} + /> + +
+ ); +} + +export default function DomainGraphView() { + return ( + + + + ); +} +``` + +- [ ] **Step 2: Wire DomainGraphView into App.tsx** + +In `understand-anything-plugin/packages/dashboard/src/App.tsx`: + +Add import at top: + +```typescript +import DomainGraphView from "./components/DomainGraphView"; +``` + +Replace the graph area section (around lines 394-400) to conditionally render: + +```typescript + {/* Graph area */} +
+ {viewMode === "domain" && domainGraph ? ( + + ) : ( + + )} +
+ Press ? for keyboard shortcuts +
+
+``` + +- [ ] **Step 3: Verify dashboard builds** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` + +Expected: Build succeeds + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx \ + understand-anything-plugin/packages/dashboard/src/App.tsx +git commit -m "feat(dashboard): add DomainGraphView with domain overview and detail views" +``` + +--- + +## Task 10: Dashboard — Domain-Aware NodeInfo Sidebar + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/components/NodeInfo.tsx` + +- [ ] **Step 1: Add domain-aware sections to NodeInfo** + +Read the existing NodeInfo.tsx first, then add domain-specific rendering. After the existing connection sections, add handling for domain/flow/step node types. + +The key changes: +1. Read `viewMode` and `domainGraph` from store +2. When `viewMode === "domain"`, look up the selected node in `domainGraph` instead of `graph` +3. For `domain` nodes: show entities, business rules, cross-domain interactions, list of flows +4. For `flow` nodes: show entry point, step list in order +5. For `step` nodes: show description, file path, "View in code" link + +Add a helper function above the component: + +```typescript +function DomainNodeDetails({ node, graph }: { node: GraphNode; graph: KnowledgeGraph }) { + const navigateToDomain = useDashboardStore((s) => s.navigateToDomain); + const selectNode = useDashboardStore((s) => s.selectNode); + const meta = (node as any).domainMeta as Record | undefined; + + if (node.type === "domain") { + const flows = graph.edges + .filter((e) => e.type === "contains_flow" && e.source === node.id) + .map((e) => graph.nodes.find((n) => n.id === e.target)) + .filter(Boolean); + + return ( +
+ {meta?.entities && (meta.entities as string[]).length > 0 && ( +
+

Entities

+
+ {(meta.entities as string[]).map((e) => ( + {e} + ))} +
+
+ )} + {meta?.businessRules && (meta.businessRules as string[]).length > 0 && ( +
+

Business Rules

+
    + {(meta.businessRules as string[]).map((r, i) => ( +
  • -{r}
  • + ))} +
+
+ )} + {meta?.crossDomainInteractions && (meta.crossDomainInteractions as string[]).length > 0 && ( +
+

Cross-Domain

+
    + {(meta.crossDomainInteractions as string[]).map((c, i) => ( +
  • {c}
  • + ))} +
+
+ )} + {flows.length > 0 && ( +
+

Flows

+
+ {flows.map((f) => ( + + ))} +
+
+ )} +
+ ); + } + + if (node.type === "flow") { + const steps = graph.edges + .filter((e) => e.type === "flow_step" && e.source === node.id) + .sort((a, b) => a.weight - b.weight) + .map((e) => graph.nodes.find((n) => n.id === e.target)) + .filter(Boolean); + + return ( +
+ {meta?.entryPoint && ( +
+

Entry Point

+
{meta.entryPoint as string}
+
+ )} + {steps.length > 0 && ( +
+

Steps

+
    + {steps.map((s, i) => ( +
  1. + +
  2. + ))} +
+
+ )} +
+ ); + } + + if (node.type === "step") { + return ( +
+ {node.filePath && ( +
+

Implementation

+
+ {node.filePath} + {node.lineRange && :{node.lineRange[0]}-{node.lineRange[1]}} +
+
+ )} +
+ ); + } + + return null; +} +``` + +Then in the main NodeInfo component, add domain-aware rendering. After getting the `node` from the graph, add logic to also check `domainGraph`: + +```typescript + 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); +``` + +And after the summary section, add: + +```typescript + {/* Domain-specific details */} + {activeGraph && node && (node.type === "domain" || node.type === "flow" || node.type === "step") && ( + + )} +``` + +- [ ] **Step 2: Verify dashboard builds** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` + +Expected: Build succeeds + +- [ ] **Step 3: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/NodeInfo.tsx +git commit -m "feat(dashboard): add domain-aware NodeInfo sidebar for domain/flow/step nodes" +``` + +--- + +## Task 11: Dashboard — Update GraphView NODE_TYPE_TO_CATEGORY + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx` + +- [ ] **Step 1: Add domain types to NODE_TYPE_TO_CATEGORY** + +In `understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx`, update `NODE_TYPE_TO_CATEGORY` (lines 53-59): + +```typescript +const NODE_TYPE_TO_CATEGORY: Record = { + file: "code", function: "code", class: "code", module: "code", concept: "code", + config: "config", + document: "docs", + service: "infra", resource: "infra", pipeline: "infra", + table: "data", endpoint: "data", schema: "data", + // Domain types — categorized as "code" for filtering purposes + domain: "code", flow: "code", step: "code", +} as const; +``` + +- [ ] **Step 2: Verify dashboard builds** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` + +Expected: Build succeeds + +- [ ] **Step 3: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx +git commit -m "feat(dashboard): add domain node types to NODE_TYPE_TO_CATEGORY mapping" +``` + +--- + +## Task 12: Create Domain Analyzer Agent + +**Files:** +- Create: `understand-anything-plugin/agents/domain-analyzer.md` + +- [ ] **Step 1: Create the agent definition** + +```markdown +--- +name: domain-analyzer +description: | + Analyzes codebases to extract business domain knowledge — domains, business flows, and process steps. Produces a domain-graph.json that maps how business logic flows through the code. +model: opus +--- + +# Domain Analyzer Agent + +You are a business domain analysis expert. Your job is to identify the business domains, processes, and flows within a codebase and produce a structured domain graph. + +## Your Task + +Analyze the provided context (either a preprocessed domain context file OR an existing knowledge graph) and produce a complete domain graph JSON. + +## Three-Level Hierarchy + +1. **Business Domain** — High-level business areas (e.g., "Order Management", "User Authentication", "Payment Processing") +2. **Business Flow** — Specific processes within a domain (e.g., "Create Order", "Process Refund") +3. **Business Step** — Individual actions within a flow (e.g., "Validate input", "Check inventory") + +## Output Schema + +Produce a JSON object with this exact structure: + +```json +{ + "version": "1.0.0", + "project": { + "name": "", + "languages": [""], + "frameworks": [""], + "description": "", + "analyzedAt": "", + "gitCommitHash": "" + }, + "nodes": [ + { + "id": "domain:", + "type": "domain", + "name": "", + "summary": "<2-3 sentences about what this domain handles>", + "tags": [""], + "complexity": "simple|moderate|complex", + "domainMeta": { + "entities": [""], + "businessRules": [""], + "crossDomainInteractions": [""] + } + }, + { + "id": "flow:", + "type": "flow", + "name": "", + "summary": "", + "tags": [""], + "complexity": "simple|moderate|complex", + "domainMeta": { + "entryPoint": "", + "entryType": "http|cli|event|cron|manual" + } + }, + { + "id": "step::", + "type": "step", + "name": "", + "summary": "", + "tags": [""], + "complexity": "simple|moderate|complex", + "filePath": "", + "lineRange": [, ] + } + ], + "edges": [ + { "source": "domain:", "target": "flow:", "type": "contains_flow", "direction": "forward", "weight": 1.0 }, + { "source": "flow:", "target": "step::", "type": "flow_step", "direction": "forward", "weight": 0.1 }, + { "source": "domain:", "target": "domain:", "type": "cross_domain", "direction": "forward", "description": "", "weight": 0.6 } + ], + "layers": [], + "tour": [] +} +``` + +## Rules + +1. **flow_step weight encodes order**: First step = 0.1, second = 0.2, etc. +2. **Every flow must connect to a domain** via `contains_flow` edge +3. **Every step must connect to a flow** via `flow_step` edge +4. **Cross-domain edges** describe how domains interact +5. **File paths** on step nodes should be relative to project root +6. **Be specific, not generic** — use the actual business terminology from the code +7. **Don't invent flows that aren't in the code** — only document what exists + +Respond ONLY with the JSON object, no additional text or markdown fences. +``` + +- [ ] **Step 2: Commit** + +```bash +git add understand-anything-plugin/agents/domain-analyzer.md +git commit -m "feat(agents): add domain-analyzer agent for business domain extraction" +``` + +--- + +## Task 13: Create /understand-domain Skill + +**Files:** +- Create: `understand-anything-plugin/skills/understand-domain/SKILL.md` + +- [ ] **Step 1: Create the skill directory and SKILL.md** + +```bash +mkdir -p understand-anything-plugin/skills/understand-domain +``` + +```markdown +--- +name: understand-domain +description: Extract business domain knowledge from a codebase and generate an interactive domain flow graph. Works standalone (lightweight scan) or derives from an existing /understand knowledge graph. +argument-hint: [--full] +--- + +# /understand-domain + +Extracts business domain knowledge — domains, business flows, and process steps — from a codebase and produces an interactive horizontal flow graph in the dashboard. + +## How It Works + +- If a knowledge graph already exists (`.understand-anything/knowledge-graph.json`), derives domain knowledge from it (cheap, no file scanning) +- If no knowledge graph exists, performs a lightweight scan: file tree + entry point detection + sampled files +- Use `--full` flag to force a fresh scan even if a knowledge graph exists + +## Instructions + +### Phase 1: Detect Existing Graph + +1. Check if `.understand-anything/knowledge-graph.json` exists in the current project +2. If it exists AND `--full` was NOT passed → proceed to Phase 3 (derive from graph) +3. Otherwise → proceed to Phase 2 (lightweight scan) + +### Phase 2: Lightweight Scan (Path 1) + +1. Run the preprocessing script bundled with this skill: + ``` + python understand-anything-plugin/skills/understand-domain/extract-domain-context.py + ``` + This outputs `.understand-anything/intermediate/domain-context.json` containing: + - File tree (respecting `.gitignore`) + - Detected entry points (HTTP routes, CLI commands, event handlers, cron jobs, exported handlers) + - File signatures (exports, imports per file) + - Code snippets for each entry point (signature + first few lines) +2. Read the generated `domain-context.json` as context for Phase 4 +3. Proceed to Phase 4 + +### Phase 3: Derive from Existing Graph (Path 2) + +1. Read `.understand-anything/knowledge-graph.json` +2. Format the graph data as structured context: + - All nodes with their types, names, summaries, and tags + - All edges with their types (especially `calls`, `imports`, `contains`) + - All layers with their descriptions + - Tour steps if available +3. This is the context for the domain analyzer — no file reading needed +4. Proceed to Phase 4 + +### Phase 4: Domain Analysis + +1. Read the domain-analyzer agent prompt from `agents/domain-analyzer.md` +2. Dispatch a subagent with the domain-analyzer prompt + the context from Phase 2 or 3 +3. The agent writes its output to `.understand-anything/intermediate/domain-analysis.json` + +### Phase 5: Validate and Save + +1. Read the domain analysis output +2. Validate using the standard graph validation pipeline (the schema now supports domain/flow/step types) +3. If validation fails, log warnings but save what's valid (error tolerance) +4. Save to `.understand-anything/domain-graph.json` +5. Clean up `.understand-anything/intermediate/domain-analysis.json` + +### Phase 6: Launch Dashboard + +1. Auto-trigger `/understand-dashboard` to visualize the domain graph +2. The dashboard will detect `domain-graph.json` and show the domain view by default +``` + +- [ ] **Step 2: Commit** + +```bash +git add understand-anything-plugin/skills/understand-domain/SKILL.md +git commit -m "feat(skills): add /understand-domain skill for business domain knowledge extraction" +``` + +--- + +## Task 14: Dashboard — Serve domain-graph.json + +**Files:** +- Modify: `understand-anything-plugin/skills/understand-dashboard/SKILL.md` + +- [ ] **Step 1: Read the existing understand-dashboard skill** + +Read `understand-anything-plugin/skills/understand-dashboard/SKILL.md` to understand how the dashboard server is configured, then add `domain-graph.json` to the list of served files. + +The dashboard server serves files from `.understand-anything/`. The domain graph file (`domain-graph.json`) needs to be served alongside `knowledge-graph.json` and `meta.json`. + +Update the skill to mention that `domain-graph.json` should also be served if it exists. The exact change depends on how the server is configured in the skill — typically it serves the entire `.understand-anything/` directory, so `domain-graph.json` should be automatically available. Verify this is the case. + +- [ ] **Step 2: Commit (if changes needed)** + +Only commit if the skill needed updating. + +--- + +## Task 15: Full Build and Integration Verification + +**Files:** +- None (verification only) + +- [ ] **Step 1: Build core package** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core build` + +Expected: Build succeeds + +- [ ] **Step 2: Run all core tests** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/core test -- --run` + +Expected: All tests PASS + +- [ ] **Step 3: Build dashboard** + +Run: `cd understand-anything-plugin && pnpm --filter @understand-anything/dashboard build` + +Expected: Build succeeds + +- [ ] **Step 4: Run linter** + +Run: `cd understand-anything-plugin && pnpm lint` + +Expected: No errors (warnings acceptable) + +- [ ] **Step 5: Final commit (if any lint fixes needed)** + +Fix any lint issues and commit. + +--- + +## Summary of All Files + +### New Files +- `understand-anything-plugin/packages/core/src/__tests__/domain-types.test.ts` +- `understand-anything-plugin/packages/core/src/__tests__/domain-persistence.test.ts` +- `understand-anything-plugin/packages/core/src/__tests__/domain-normalize.test.ts` +- `understand-anything-plugin/packages/dashboard/src/components/DomainClusterNode.tsx` +- `understand-anything-plugin/packages/dashboard/src/components/FlowNode.tsx` +- `understand-anything-plugin/packages/dashboard/src/components/StepNode.tsx` +- `understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx` +- `understand-anything-plugin/agents/domain-analyzer.md` +- `understand-anything-plugin/skills/understand-domain/SKILL.md` +- `understand-anything-plugin/skills/understand-domain/extract-domain-context.py` + +### Modified Files +- `understand-anything-plugin/packages/core/src/types.ts` — 3 new node types, 3 new edge types, DomainMeta interface +- `understand-anything-plugin/packages/core/src/schema.ts` — Zod schemas + aliases for domain types +- `understand-anything-plugin/packages/core/src/persistence/index.ts` — saveDomainGraph/loadDomainGraph +- `understand-anything-plugin/packages/core/src/analyzer/normalize-graph.ts` — domain ID prefixes +- `understand-anything-plugin/packages/dashboard/src/store.ts` — viewMode, domainGraph, activeDomainId +- `understand-anything-plugin/packages/dashboard/src/App.tsx` — domain graph loading, view toggle, conditional rendering +- `understand-anything-plugin/packages/dashboard/src/components/NodeInfo.tsx` — domain-aware sidebar +- `understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx` — NODE_TYPE_TO_CATEGORY update