From 78b72724ed338ade0ec8662826a217745150ec82 Mon Sep 17 00:00:00 2001 From: Yuxiang Lin Date: Fri, 10 Apr 2026 19:45:02 +0800 Subject: [PATCH 01/11] Add funding information for Patreon --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..cb75fdf --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +patreon: Lum1104 From 098ad791c09ab9c03528229a932d4d9ac177fb85 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Thu, 9 Apr 2026 22:56:00 +0800 Subject: [PATCH 02/11] docs: add /understand-knowledge design spec for personal knowledge base plugin New skill that takes markdown knowledge bases (Obsidian, Logseq, Dendron, Foam, Karpathy-style, Zettelkasten, plain) and produces interactive knowledge graphs with typed nodes/edges, auto-format detection, and dashboard visualization. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-09-understand-knowledge-design.md | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-09-understand-knowledge-design.md diff --git a/docs/superpowers/specs/2026-04-09-understand-knowledge-design.md b/docs/superpowers/specs/2026-04-09-understand-knowledge-design.md new file mode 100644 index 0000000..2c63252 --- /dev/null +++ b/docs/superpowers/specs/2026-04-09-understand-knowledge-design.md @@ -0,0 +1,335 @@ +# /understand-knowledge — Personal Knowledge Base Plugin Design + +## Overview + +A new `/understand-knowledge` skill within the existing Understand Anything plugin that takes any folder of markdown notes and produces an interactive knowledge graph visualized in the existing dashboard. + +Inspired by Andrej Karpathy's LLM Wiki pattern — where an LLM compiles and maintains a structured wiki from raw sources — this plugin goes further by adding typed relationship discovery and interactive graph visualization that tools like Obsidian and Logseq cannot provide. + +### Goals + +- Accept any markdown-based knowledge base (Obsidian vault, Logseq graph, Dendron workspace, Foam, Karpathy-style LLM wiki, Zettelkasten, or plain markdown) +- Auto-detect the format and adapt parsing accordingly +- Use LLM analysis to discover implicit relationships beyond explicit links +- Produce a knowledge graph with typed nodes and edges +- Visualize in the existing dashboard with knowledge-specific layout, sidebar, and reading mode + +### Non-Goals + +- Real-time sync with the knowledge base tool (Obsidian, Logseq, etc.) +- Replacing the user's existing PKM tool — this is a visualization/analysis layer on top +- Supporting non-markdown formats (PDFs, bookmarks) in v1 + +--- + +## Schema Extensions + +### New Node Types (5) + +Added to the existing `NodeType` union (currently 16 types): + +```typescript +export type NodeType = + // existing (16) + | "file" | "function" | "class" | "module" | "concept" + | "config" | "document" | "service" | "table" | "endpoint" + | "pipeline" | "schema" | "resource" + | "domain" | "flow" | "step" + // knowledge (5 new → 21 total) + | "article" | "entity" | "topic" | "claim" | "source"; +``` + +| Type | What it represents | Example | +|------|-------------------|---------| +| `article` | A wiki/note page — the primary content unit | "LLM Knowledge Bases.md" | +| `entity` | A named thing: person, tool, paper, org, project | "Andrej Karpathy", "Obsidian" | +| `topic` | A thematic cluster grouping related articles | "Personal Knowledge Management" | +| `claim` | A specific assertion, insight, or takeaway | "RAG loses context at chunk boundaries" | +| `source` | Raw/reference material that articles are compiled from | A paper URL, a raw PDF reference | + +### New Edge Types (6) + +Added to the existing `EdgeType` union (currently 29 types): + +```typescript +export type EdgeType = + // existing (29) + | ... + // knowledge (6 new → 35 total) + | "cites" | "contradicts" | "builds_on" + | "exemplifies" | "categorized_under" | "authored_by"; +``` + +| Type | Direction | Meaning | +|------|-----------|---------| +| `cites` | article → source | References or draws from | +| `contradicts` | claim → claim | Conflicts or disagrees with | +| `builds_on` | article → article | Extends, refines, or deepens | +| `exemplifies` | entity → concept/topic | Is a concrete example of | +| `categorized_under` | article/entity → topic | Belongs to this theme | +| `authored_by` | article → entity | Written or created by | + +### New Metadata Interface + +```typescript +export interface KnowledgeMeta { + format?: "obsidian" | "logseq" | "dendron" | "foam" | "karpathy" | "zettelkasten" | "plain"; + wikilinks?: string[]; + backlinks?: string[]; + frontmatter?: Record; + sourceUrl?: string; + confidence?: number; // 0-1, for LLM-inferred relationships +} +``` + +Added as an optional field on `GraphNode`: + +```typescript +export interface GraphNode { + // ...existing fields + knowledgeMeta?: KnowledgeMeta; +} +``` + +### Graph-Level Kind Flag + +```typescript +export interface KnowledgeGraph { + version: string; + kind: "codebase" | "knowledge"; // NEW + project: ProjectMeta; + nodes: GraphNode[]; + edges: GraphEdge[]; + layers: Layer[]; + tour: TourStep[]; +} +``` + +The `kind` field tells the dashboard which layout, sidebar, and visual styling to use. For backward compatibility, graphs without a `kind` field default to `"codebase"`. + +--- + +## Format Detection & Format Guides + +### Auto-Detection Logic + +Scans the target directory for signature files/patterns. Priority order (first match wins): + +| Priority | Signal | Detected Format | +|----------|--------|----------------| +| 1 | `.obsidian/` directory | Obsidian | +| 2 | `logseq/` + `pages/` directories | Logseq | +| 3 | `.dendron.yml` or `*.schema.yml` | Dendron | +| 4 | `.foam/` or `.vscode/foam.json` | Foam | +| 5 | `raw/` + `wiki/` + `index.md` | Karpathy | +| 6 | `[[wikilinks]]` + unique ID prefixes in filenames | Zettelkasten | +| 7 | Fallback | Plain markdown | + +### Format Guides + +Located at `skills/understand-knowledge/formats/`. Each guide tells the LLM agents how to parse that format: + +``` +skills/understand-knowledge/ + SKILL.md + formats/ + obsidian.md — [[wikilinks]], [[note|alias]], [[note#heading]], + #tags, YAML frontmatter, .obsidian/ config, + dataview annotations, canvas files + logseq.md — block-based outliner, ((block-refs)), + journals/YYYY_MM_DD.md, pages/, + property:: value syntax, TODO/DONE states + dendron.md — dot-delimited hierarchy (a.b.c.md), + .schema.yml for structure validation, + cross-vault links, refactoring rules + foam.md — [[wikilinks]] + link reference definitions + at file bottom, .foam/config, placeholder links + karpathy.md — raw/ → wiki/ pipeline, index.md master map, + log.md append-only record, _meta/ state, + LLM-maintained cross-references + zettelkasten.md — atomic notes, unique ID prefixes (timestamps), + typed semantic links, one idea per note + plain.md — standard [markdown](links), folder hierarchy, + heading structure, no special conventions +``` + +Each format guide covers: +- How to parse links (wikilinks vs standard vs block refs) +- Where metadata lives (frontmatter vs inline properties vs block properties) +- What the folder structure means (journals/ = daily notes, pages/ = permanent notes) +- What conventions to respect vs what to infer + +### Format Guide Authoring Process + +Format guides must be research-backed. During implementation, the agent building each format guide must: +1. Read the official documentation for that format (Obsidian Help, Logseq docs, Dendron wiki, Foam docs, etc.) +2. Study real-world examples of that format's structure +3. Write the guide based on verified behavior, not assumptions + +--- + +## Agent Pipeline + +``` +knowledge-scanner → format-detector → article-analyzer → relationship-builder → graph-reviewer +``` + +### Agent Definitions + +| Agent | Input | Output | Model | +|-------|-------|--------|-------| +| `knowledge-scanner` | Target directory path | File manifest: all `.md` files with paths, sizes, first 20 lines preview | `inherit` | +| `format-detector` | File manifest + directory structure | Detected format + format-specific parsing hints | `inherit` | +| `article-analyzer` | Individual `.md` file + format guide | Per-file nodes (article, entities, claims) + explicit edges (wikilinks, tags) | `inherit` | +| `relationship-builder` | All per-file results | Cross-file implicit edges (builds_on, contradicts, categorized_under) + topic clustering + layers | `inherit` | +| `graph-reviewer` | Assembled graph | Validated graph — deduped entities, consistent edge weights, orphan detection | `inherit` | + +### Key Differences from Codebase Pipeline + +- **No tree-sitter** — markdown parsing is simpler, mostly regex + LLM interpretation +- **format-detector** replaces framework detection — picks the right format guide +- **article-analyzer** replaces file-analyzer — extracts knowledge concepts instead of code structure +- **relationship-builder** is the heavy LLM step — discovers implicit connections across files that explicit links miss +- **graph-reviewer** stays similar — validates the assembled graph for consistency + +### Intermediate Files + +Same pattern as codebase analysis: + +``` +.understand-anything/intermediate/ + knowledge-manifest.json — scanner output + format-detection.json — detected format + hints + article-*.json — per-file analysis + relationships.json — cross-file edges + knowledge-graph.json — final assembled graph +``` + +Intermediate files are cleaned up after graph assembly (same as codebase flow). + +### Incremental Mode (`--ingest`) + +When the user runs `/understand-knowledge --ingest path/to/new-source.md`: + +1. **knowledge-scanner** — runs on just the new file(s) +2. **format-detector** — skipped (format already known from initial scan) +3. **article-analyzer** — processes only new/changed files +4. **relationship-builder** — runs on new nodes against the existing graph, finds connections to what's already there +5. **graph-reviewer** — validates the merged result + +Existing nodes are preserved; only new nodes/edges are added or updated. + +--- + +## Dashboard Changes + +All changes are scoped to graphs with `"kind": "knowledge"`. + +### Vertical Flow Layout + +- Default to top-down vertical layout (like existing domain/business flow view) +- Topics at top → articles in middle → entities/claims/sources at bottom +- Reads like a knowledge hierarchy: broad themes flow down into specifics +- User can still switch to horizontal or force-directed layout via controls + +### Knowledge Sidebar + +Replaces NodeInfo when a knowledge graph is loaded: + +| Selection | Sidebar Shows | +|-----------|---------------| +| Nothing selected | ProjectOverview: format detected, total articles/entities/topics/claims/sources | +| Article node | Title, summary, tags, frontmatter metadata, backlinks list (clickable), outgoing links, related topics | +| Entity node | Name, type (person/tool/paper/org), articles that mention it, relationships to other entities | +| Topic node | Description, child articles, child entities, cross-topic connections | +| Claim node | Assertion text, supporting articles, contradicting claims (if any), confidence score | +| Source node | Original URL/path, articles that cite it, ingestion date | + +### Reading Mode + +- Clicking an article node triggers a reading panel that slides up from the bottom (same pattern as current code viewer overlay) +- Shows the full compiled markdown rendered as HTML +- Includes a mini backlinks sidebar within the panel +- Clicking a `[[wikilink]]` or entity reference in the reading panel navigates the graph to that node + +### Node Visual Styling + +| Node Type | Shape | Color Accent | +|-----------|-------|-------------| +| `article` | Rounded rectangle | Warm amber | +| `entity` | Circle | Soft blue | +| `topic` | Large rounded rectangle | Muted gold | +| `claim` | Diamond | Green/red depending on contradictions | +| `source` | Small square | Gray | + +### Edge Visual Styling + +| Edge Type | Style | +|-----------|-------| +| `cites` | Dashed line | +| `contradicts` | Red line | +| `builds_on` | Solid with arrow | +| `categorized_under` | Thin gray | +| `authored_by` | Dotted blue | +| `exemplifies` | Dotted green | + +--- + +## Skill Interface + +### Usage + +```bash +# Full scan — first time or rescan +/understand-knowledge + +# Point at a specific directory +/understand-knowledge path/to/my-notes + +# Incremental ingest — add new sources to existing graph +/understand-knowledge --ingest path/to/new-note.md +/understand-knowledge --ingest path/to/new-folder/ +``` + +### Behavior + +1. Auto-detects format (Obsidian, Logseq, Karpathy, etc.) +2. Announces: "Detected Obsidian vault with 342 notes. Scanning..." +3. Runs the agent pipeline (scanner → detector → analyzer → relationship-builder → reviewer) +4. Writes `knowledge-graph.json` to `.understand-anything/` with `"kind": "knowledge"` +5. Auto-triggers `/understand-dashboard` after completion + +### File Structure + +``` +skills/understand-knowledge/ + SKILL.md — skill entry point, orchestration logic + formats/ + obsidian.md + logseq.md + dendron.md + foam.md + karpathy.md + zettelkasten.md + plain.md +``` + +### Coexistence with `/understand` + +- `/understand` produces `"kind": "codebase"` graphs +- `/understand-knowledge` produces `"kind": "knowledge"` graphs +- Both write to `.understand-anything/knowledge-graph.json` +- Running one replaces the other +- To scope knowledge analysis to a subdirectory (e.g., `docs/` within a code repo), use `/understand-knowledge path/to/docs` + +--- + +## What This Enables That Nothing Else Does + +| Existing Tools | Limitation | Our Advantage | +|---------------|-----------|---------------| +| Obsidian graph view | Untyped edges — all links look the same | Typed edges: cites, contradicts, builds_on | +| Logseq graph | Only shows explicit links | LLM discovers implicit relationships | +| All PKM tools | Single-format only | Cross-format support with auto-detection | +| Karpathy LLM Wiki | Flat text wiki, no visualization | Interactive graph dashboard with guided tours | +| None | No knowledge graph tours | Tour mode walks through a knowledge base step by step | From e58ceb856faef5d3d5aea88c855754e2b3a833a5 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Thu, 9 Apr 2026 23:06:32 +0800 Subject: [PATCH 03/11] docs: add /understand-knowledge implementation plan (14 tasks) Detailed step-by-step plan covering core type extensions, schema validation, dashboard changes (CSS, store, sidebar, reading panel, edge styling, layout), agent definitions, format guides, and skill definition. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plans/2026-04-09-understand-knowledge.md | 1740 +++++++++++++++++ 1 file changed, 1740 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-09-understand-knowledge.md diff --git a/docs/superpowers/plans/2026-04-09-understand-knowledge.md b/docs/superpowers/plans/2026-04-09-understand-knowledge.md new file mode 100644 index 0000000..4c0635f --- /dev/null +++ b/docs/superpowers/plans/2026-04-09-understand-knowledge.md @@ -0,0 +1,1740 @@ +# /understand-knowledge 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-knowledge` skill that takes any folder of markdown notes (Obsidian, Logseq, Dendron, Foam, Karpathy-style, Zettelkasten, or plain) and produces an interactive knowledge graph with typed nodes, edges, and dashboard visualization. + +**Architecture:** Extends the existing schema with 5 knowledge node types and 6 knowledge edge types. A new 5-agent pipeline (knowledge-scanner → format-detector → article-analyzer → relationship-builder → graph-reviewer) processes markdown files. The dashboard renders knowledge graphs with vertical layout, a knowledge-specific sidebar, and a reading mode panel — all driven by a new `kind` field on the root graph object. + +**Tech Stack:** TypeScript, Zod (schema validation), React + ReactFlow (dashboard), dagre (layout), TailwindCSS v4, Vitest (testing) + +**Spec:** `docs/superpowers/specs/2026-04-09-understand-knowledge-design.md` + +--- + +## File Structure + +### Core package changes +- Modify: `understand-anything-plugin/packages/core/src/types.ts` — add 5 node types, 6 edge types, `KnowledgeMeta` interface, `kind` field +- Modify: `understand-anything-plugin/packages/core/src/schema.ts` — add new types to Zod schemas, add aliases +- Modify: `understand-anything-plugin/packages/core/src/types.test.ts` — add tests for new types +- Test: `understand-anything-plugin/packages/core/src/__tests__/knowledge-schema.test.ts` — validation tests for knowledge-specific schema + +### Dashboard changes +- Modify: `understand-anything-plugin/packages/dashboard/src/store.ts` — add knowledge node types, edge categories, `ViewMode`, node categories +- Modify: `understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx` — add colors for 5 new node types +- Modify: `understand-anything-plugin/packages/dashboard/src/components/NodeInfo.tsx` — add badge colors and edge labels for new types, add knowledge sidebar sections +- Modify: `understand-anything-plugin/packages/dashboard/src/components/ProjectOverview.tsx` — add knowledge-specific stats +- Modify: `understand-anything-plugin/packages/dashboard/src/index.css` — add CSS variables for 5 new node colors +- Modify: `understand-anything-plugin/packages/dashboard/src/App.tsx` — detect `kind` field, set view mode +- Create: `understand-anything-plugin/packages/dashboard/src/components/KnowledgeInfo.tsx` — knowledge-specific sidebar +- Create: `understand-anything-plugin/packages/dashboard/src/components/ReadingPanel.tsx` — full article reading overlay + +### Skill & agent definitions +- Create: `understand-anything-plugin/skills/understand-knowledge/SKILL.md` — skill entry point +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/obsidian.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/logseq.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/dendron.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/foam.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/karpathy.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/zettelkasten.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/plain.md` +- Create: `understand-anything-plugin/agents/knowledge-scanner.md` +- Create: `understand-anything-plugin/agents/format-detector.md` +- Create: `understand-anything-plugin/agents/article-analyzer.md` +- Create: `understand-anything-plugin/agents/relationship-builder.md` + +Existing `graph-reviewer.md` agent is reused for the final validation step. + +--- + +## Task 1: Extend Core Types + +**Files:** +- Modify: `understand-anything-plugin/packages/core/src/types.ts` + +- [ ] **Step 1: Add knowledge node types to NodeType union** + +In `understand-anything-plugin/packages/core/src/types.ts`, add the 5 knowledge types after the domain types: + +```typescript +// Node types (21 total: 5 code + 8 non-code + 3 domain + 5 knowledge) +export type NodeType = + | "file" | "function" | "class" | "module" | "concept" + | "config" | "document" | "service" | "table" | "endpoint" + | "pipeline" | "schema" | "resource" + | "domain" | "flow" | "step" + | "article" | "entity" | "topic" | "claim" | "source"; +``` + +- [ ] **Step 2: Add knowledge edge types to EdgeType union** + +```typescript +// Edge types (35 total in 8 categories) +export type EdgeType = + | "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" + | "cites" | "contradicts" | "builds_on" | "exemplifies" | "categorized_under" | "authored_by"; +``` + +- [ ] **Step 3: Add KnowledgeMeta interface** + +Add after the `DomainMeta` interface: + +```typescript +// Optional knowledge metadata for article/entity/topic/claim/source nodes +export interface KnowledgeMeta { + format?: "obsidian" | "logseq" | "dendron" | "foam" | "karpathy" | "zettelkasten" | "plain"; + wikilinks?: string[]; + backlinks?: string[]; + frontmatter?: Record; + sourceUrl?: string; + confidence?: number; // 0-1, for LLM-inferred relationships +} +``` + +- [ ] **Step 4: Add knowledgeMeta to GraphNode** + +```typescript +export interface GraphNode { + id: string; + type: NodeType; + name: string; + filePath?: string; + lineRange?: [number, number]; + summary: string; + tags: string[]; + complexity: "simple" | "moderate" | "complex"; + languageNotes?: string; + domainMeta?: DomainMeta; + knowledgeMeta?: KnowledgeMeta; +} +``` + +- [ ] **Step 5: Add kind field to KnowledgeGraph** + +```typescript +export interface KnowledgeGraph { + version: string; + kind?: "codebase" | "knowledge"; // undefined defaults to "codebase" for backward compat + project: ProjectMeta; + nodes: GraphNode[]; + edges: GraphEdge[]; + layers: Layer[]; + tour: TourStep[]; +} +``` + +- [ ] **Step 6: Build core and verify no type errors** + +Run: `pnpm --filter @understand-anything/core build` +Expected: Clean build, no errors + +- [ ] **Step 7: Commit** + +```bash +git add understand-anything-plugin/packages/core/src/types.ts +git commit -m "feat(core): add knowledge node types, edge types, KnowledgeMeta, and graph kind field" +``` + +--- + +## Task 2: Extend Schema Validation + +**Files:** +- Modify: `understand-anything-plugin/packages/core/src/schema.ts` +- Create: `understand-anything-plugin/packages/core/src/__tests__/knowledge-schema.test.ts` + +- [ ] **Step 1: Add knowledge edge types to EdgeTypeSchema** + +In `understand-anything-plugin/packages/core/src/schema.ts`, update the `EdgeTypeSchema` z.enum to include the 6 new 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", + // Knowledge + "cites", "contradicts", "builds_on", "exemplifies", "categorized_under", "authored_by", +]); +``` + +- [ ] **Step 2: Add knowledge node type aliases** + +Add to `NODE_TYPE_ALIASES`: + +```typescript + // Knowledge aliases + note: "article", + page: "article", + wiki_page: "article", + person: "entity", + tool: "entity", + paper: "entity", + organization: "entity", + org: "entity", + category: "topic", + theme: "topic", + tag_topic: "topic", + assertion: "claim", + insight: "claim", + takeaway: "claim", + reference: "source", + raw: "source", + citation: "source", +``` + +- [ ] **Step 3: Add knowledge edge type aliases** + +Add to `EDGE_TYPE_ALIASES`: + +```typescript + // Knowledge aliases + references: "cites", + cited_by: "cites", + sourced_from: "cites", + conflicts_with: "contradicts", + disagrees_with: "contradicts", + extends: "builds_on", // Note: "extends" was already mapped to "inherits" — knowledge context will use builds_on via the relationship-builder agent prompt, so keep "extends" → "inherits" for code + refines: "builds_on", + deepens: "builds_on", + example_of: "exemplifies", + instance_of: "exemplifies", + belongs_to: "categorized_under", + tagged_with: "categorized_under", + part_of: "categorized_under", + written_by: "authored_by", + created_by: "authored_by", +``` + +- [ ] **Step 4: Write the failing test for knowledge graph validation** + +Create `understand-anything-plugin/packages/core/src/__tests__/knowledge-schema.test.ts`: + +```typescript +import { describe, it, expect } from "vitest"; +import { validateGraph } from "../schema"; +import type { KnowledgeGraph } from "../types"; + +describe("knowledge graph schema validation", () => { + const minimalKnowledgeGraph: KnowledgeGraph = { + version: "1.0", + kind: "knowledge", + project: { + name: "Test KB", + languages: [], + frameworks: [], + description: "A test knowledge base", + analyzedAt: new Date().toISOString(), + gitCommitHash: "abc123", + }, + nodes: [ + { + id: "article:test-note", + type: "article", + name: "Test Note", + summary: "A test article node", + tags: ["test"], + complexity: "simple", + }, + { + id: "entity:karpathy", + type: "entity", + name: "Andrej Karpathy", + summary: "AI researcher", + tags: ["person", "ai"], + complexity: "simple", + }, + { + id: "topic:pkm", + type: "topic", + name: "Personal Knowledge Management", + summary: "Tools and methods for managing personal knowledge", + tags: ["knowledge", "productivity"], + complexity: "moderate", + }, + ], + edges: [ + { + source: "article:test-note", + target: "entity:karpathy", + type: "authored_by", + direction: "forward", + weight: 0.8, + }, + { + source: "article:test-note", + target: "topic:pkm", + type: "categorized_under", + direction: "forward", + weight: 0.7, + }, + ], + layers: [ + { + id: "layer:pkm", + name: "PKM", + description: "Personal Knowledge Management topic cluster", + nodeIds: ["article:test-note", "topic:pkm"], + }, + ], + tour: [], + }; + + it("validates a minimal knowledge graph", () => { + const result = validateGraph(minimalKnowledgeGraph); + const fatals = result.issues.filter((i) => i.level === "fatal"); + expect(fatals).toHaveLength(0); + }); + + it("accepts all knowledge node types", () => { + const graph = { + ...minimalKnowledgeGraph, + nodes: [ + ...minimalKnowledgeGraph.nodes, + { id: "claim:rag-bad", type: "claim", name: "RAG loses context", summary: "An assertion", tags: ["claim"], complexity: "simple" }, + { id: "source:paper1", type: "source", name: "Attention paper", summary: "A source", tags: ["paper"], complexity: "simple" }, + ], + }; + const result = validateGraph(graph); + const fatals = result.issues.filter((i) => i.level === "fatal"); + expect(fatals).toHaveLength(0); + }); + + it("accepts all knowledge edge types", () => { + const graph = { + ...minimalKnowledgeGraph, + nodes: [ + ...minimalKnowledgeGraph.nodes, + { id: "claim:c1", type: "claim", name: "Claim 1", summary: "c1", tags: [], complexity: "simple" }, + { id: "claim:c2", type: "claim", name: "Claim 2", summary: "c2", tags: [], complexity: "simple" }, + { id: "source:s1", type: "source", name: "Source 1", summary: "s1", tags: [], complexity: "simple" }, + { id: "article:a2", type: "article", name: "Article 2", summary: "a2", tags: [], complexity: "simple" }, + ], + edges: [ + ...minimalKnowledgeGraph.edges, + { source: "article:test-note", target: "source:s1", type: "cites", direction: "forward", weight: 0.7 }, + { source: "claim:c1", target: "claim:c2", type: "contradicts", direction: "forward", weight: 0.6 }, + { source: "article:a2", target: "article:test-note", type: "builds_on", direction: "forward", weight: 0.7 }, + { source: "entity:karpathy", target: "topic:pkm", type: "exemplifies", direction: "forward", weight: 0.5 }, + ], + }; + const result = validateGraph(graph); + const fatals = result.issues.filter((i) => i.level === "fatal"); + expect(fatals).toHaveLength(0); + }); + + it("resolves knowledge node type aliases", () => { + const graph = { + ...minimalKnowledgeGraph, + nodes: [ + { id: "note:n1", type: "note", name: "A Note", summary: "note alias", tags: [], complexity: "simple" }, + { id: "person:p1", type: "person", name: "A Person", summary: "person alias", tags: [], complexity: "simple" }, + ], + edges: [], + layers: [], + }; + const result = validateGraph(graph); + const noteNode = result.graph.nodes.find((n) => n.id === "note:n1"); + const personNode = result.graph.nodes.find((n) => n.id === "person:p1"); + expect(noteNode?.type).toBe("article"); + expect(personNode?.type).toBe("entity"); + }); + + it("resolves knowledge edge type aliases", () => { + const graph = { + ...minimalKnowledgeGraph, + edges: [ + { source: "article:test-note", target: "entity:karpathy", type: "written_by", direction: "forward", weight: 0.8 }, + ], + }; + const result = validateGraph(graph); + const edge = result.graph.edges.find((e) => e.source === "article:test-note" && e.target === "entity:karpathy"); + expect(edge?.type).toBe("authored_by"); + }); +}); +``` + +- [ ] **Step 5: Run tests to verify they fail** + +Run: `pnpm --filter @understand-anything/core test -- --run src/__tests__/knowledge-schema.test.ts` +Expected: Tests fail because EdgeTypeSchema doesn't include knowledge types yet (if schema.ts wasn't updated), or pass if Steps 1-3 were done correctly. + +- [ ] **Step 6: Run all core tests to verify nothing is broken** + +Run: `pnpm --filter @understand-anything/core test -- --run` +Expected: All existing tests pass, new knowledge tests pass + +- [ ] **Step 7: Commit** + +```bash +git add understand-anything-plugin/packages/core/src/schema.ts understand-anything-plugin/packages/core/src/__tests__/knowledge-schema.test.ts +git commit -m "feat(core): add knowledge types to schema validation with aliases and tests" +``` + +--- + +## Task 3: Dashboard — CSS Variables & Node Colors + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/index.css` + +- [ ] **Step 1: Add CSS variables for 5 knowledge node types** + +In `understand-anything-plugin/packages/dashboard/src/index.css`, add after the existing `--color-node-resource` line: + +```css + /* Knowledge node colors */ + --color-node-article: #d4a574; /* warm amber */ + --color-node-entity: #7ba4c9; /* soft blue */ + --color-node-topic: #c9b06c; /* muted gold */ + --color-node-claim: #6fb07a; /* soft green */ + --color-node-source: #8a8a8a; /* gray */ +``` + +- [ ] **Step 2: Add Tailwind text-color utilities for knowledge nodes** + +Verify TailwindCSS v4 picks up the CSS variables automatically. If the existing pattern uses `text-node-*` classes defined elsewhere, add matching entries. Check if there's a Tailwind config or if the CSS variables are consumed directly. + +Look at how existing `text-node-file` etc. are defined — if they're in the CSS file as utility classes, add: + +```css + .text-node-article { color: var(--color-node-article); } + .text-node-entity { color: var(--color-node-entity); } + .text-node-topic { color: var(--color-node-topic); } + .text-node-claim { color: var(--color-node-claim); } + .text-node-source { color: var(--color-node-source); } +``` + +And corresponding `border-node-*` and `bg-node-*` variants if the pattern requires them. + +- [ ] **Step 3: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/index.css +git commit -m "feat(dashboard): add CSS variables and utility classes for knowledge node types" +``` + +--- + +## Task 4: Dashboard — Store & Type Maps + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/store.ts` + +- [ ] **Step 1: Add knowledge types to NodeType union** + +Update the local `NodeType` in store.ts: + +```typescript +export type NodeType = "file" | "function" | "class" | "module" | "concept" | "config" | "document" | "service" | "table" | "endpoint" | "pipeline" | "schema" | "resource" | "domain" | "flow" | "step" | "article" | "entity" | "topic" | "claim" | "source"; +``` + +- [ ] **Step 2: Add knowledge edge category** + +Update `EdgeCategory` and `EDGE_CATEGORY_MAP`: + +```typescript +export type EdgeCategory = "structural" | "behavioral" | "data-flow" | "dependencies" | "semantic" | "infrastructure" | "domain" | "knowledge"; + +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"], + infrastructure: ["deploys", "serves", "provisions", "triggers"], + domain: ["contains_flow", "flow_step", "cross_domain"], + knowledge: ["cites", "contradicts", "builds_on", "exemplifies", "categorized_under", "authored_by"], +}; +``` + +- [ ] **Step 3: Add knowledge to ALL_NODE_TYPES and ALL_EDGE_CATEGORIES** + +```typescript +export const ALL_NODE_TYPES: NodeType[] = ["file", "function", "class", "module", "concept", "config", "document", "service", "table", "endpoint", "pipeline", "schema", "resource", "domain", "flow", "step", "article", "entity", "topic", "claim", "source"]; + +export const ALL_EDGE_CATEGORIES: EdgeCategory[] = ["structural", "behavioral", "data-flow", "dependencies", "semantic", "infrastructure", "domain", "knowledge"]; +``` + +- [ ] **Step 4: Add "knowledge" to ViewMode and NodeCategory** + +```typescript +export type ViewMode = "structural" | "domain" | "knowledge"; + +export type NodeCategory = "code" | "config" | "docs" | "infra" | "data" | "domain" | "knowledge"; +``` + +Update the `NODE_CATEGORY_MAP` (find where it maps node types to categories) to include: + +```typescript + article: "knowledge", + entity: "knowledge", + topic: "knowledge", + claim: "knowledge", + source: "knowledge", +``` + +- [ ] **Step 5: Add knowledge node type filter default** + +In the store's initial state `nodeTypeFilters`, add: + +```typescript +nodeTypeFilters: { code: true, config: true, docs: true, infra: true, data: true, domain: true, knowledge: true }, +``` + +- [ ] **Step 6: Build dashboard and verify no errors** + +Run: `pnpm --filter @understand-anything/dashboard build` +Expected: Clean build + +- [ ] **Step 7: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/store.ts +git commit -m "feat(dashboard): add knowledge types to store, edge categories, and view mode" +``` + +--- + +## Task 5: Dashboard — CustomNode & NodeInfo Type Maps + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx` +- Modify: `understand-anything-plugin/packages/dashboard/src/components/NodeInfo.tsx` + +- [ ] **Step 1: Add knowledge node colors to CustomNode.tsx** + +In `typeColors` map, add after the `step` entry: + +```typescript + // Knowledge + article: "var(--color-node-article)", + entity: "var(--color-node-entity)", + topic: "var(--color-node-topic)", + claim: "var(--color-node-claim)", + source: "var(--color-node-source)", +``` + +In `typeTextColors` map, add: + +```typescript + // Knowledge + article: "text-node-article", + entity: "text-node-entity", + topic: "text-node-topic", + claim: "text-node-claim", + source: "text-node-source", +``` + +- [ ] **Step 2: Add knowledge node badge colors to NodeInfo.tsx** + +In `typeBadgeColors` map, add: + +```typescript + // Knowledge + article: "text-node-article border border-node-article/30 bg-node-article/10", + entity: "text-node-entity border border-node-entity/30 bg-node-entity/10", + topic: "text-node-topic border border-node-topic/30 bg-node-topic/10", + claim: "text-node-claim border border-node-claim/30 bg-node-claim/10", + source: "text-node-source border border-node-source/30 bg-node-source/10", +``` + +- [ ] **Step 3: Add knowledge edge labels to NodeInfo.tsx** + +In `EDGE_LABELS` map, add: + +```typescript + // Knowledge + cites: { forward: "cites", backward: "cited by" }, + contradicts: { forward: "contradicts", backward: "contradicted by" }, + builds_on: { forward: "builds on", backward: "built upon by" }, + exemplifies: { forward: "exemplifies", backward: "exemplified by" }, + categorized_under: { forward: "categorized under", backward: "categorizes" }, + authored_by: { forward: "authored by", backward: "authored" }, +``` + +- [ ] **Step 4: Build dashboard and verify** + +Run: `pnpm --filter @understand-anything/dashboard build` +Expected: Clean build, no type errors + +- [ ] **Step 5: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx understand-anything-plugin/packages/dashboard/src/components/NodeInfo.tsx +git commit -m "feat(dashboard): add knowledge node colors, badge colors, and edge labels" +``` + +--- + +## Task 6: Dashboard — Knowledge Sidebar Component + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/components/KnowledgeInfo.tsx` +- Modify: `understand-anything-plugin/packages/dashboard/src/App.tsx` + +- [ ] **Step 1: Create KnowledgeInfo.tsx** + +Create `understand-anything-plugin/packages/dashboard/src/components/KnowledgeInfo.tsx`: + +```tsx +import { useDashboardStore } from "../store"; +import type { GraphNode, GraphEdge, KnowledgeGraph } from "@understand-anything/core/types"; + +const KNOWLEDGE_NODE_TYPES = new Set(["article", "entity", "topic", "claim", "source"]); + +function getBacklinks(nodeId: string, edges: GraphEdge[]): string[] { + return edges + .filter((e) => e.target === nodeId) + .map((e) => e.source); +} + +function getOutgoingLinks(nodeId: string, edges: GraphEdge[]): string[] { + return edges + .filter((e) => e.source === nodeId) + .map((e) => e.target); +} + +function NodeLink({ nodeId, nodes, onNavigate }: { nodeId: string; nodes: GraphNode[]; onNavigate: (id: string) => void }) { + const node = nodes.find((n) => n.id === nodeId); + if (!node) return {nodeId}; + return ( + + ); +} + +export default function KnowledgeInfo() { + const graph = useDashboardStore((s) => s.graph); + const selectedNode = useDashboardStore((s) => s.selectedNode); + const setSelectedNode = useDashboardStore((s) => s.setSelectedNode); + + if (!graph || !selectedNode) return null; + + const node = graph.nodes.find((n) => n.id === selectedNode); + if (!node) return null; + + const backlinks = getBacklinks(node.id, graph.edges); + const outgoing = getOutgoingLinks(node.id, graph.edges); + const meta = node.knowledgeMeta; + + return ( +
+ {/* Header */} +
+
{node.type}
+

{node.name}

+
+ + {/* Summary */} +

{node.summary}

+ + {/* Tags */} + {node.tags.length > 0 && ( +
+ {node.tags.map((tag) => ( + + {tag} + + ))} +
+ )} + + {/* Knowledge-specific metadata */} + {meta?.sourceUrl && ( +
+
Source
+ {meta.sourceUrl} +
+ )} + + {meta?.confidence !== undefined && ( +
+
Confidence
+
+
+
+
+ {Math.round(meta.confidence * 100)}% +
+
+ )} + + {/* Frontmatter */} + {meta?.frontmatter && Object.keys(meta.frontmatter).length > 0 && ( +
+
Frontmatter
+
+ {Object.entries(meta.frontmatter).map(([key, value]) => ( +
+ {key}:{" "} + {String(value)} +
+ ))} +
+
+ )} + + {/* Backlinks */} + {backlinks.length > 0 && ( +
+
+ Backlinks ({backlinks.length}) +
+
+ {backlinks.map((id) => ( + + ))} +
+
+ )} + + {/* Outgoing */} + {outgoing.length > 0 && ( +
+
+ Outgoing Links ({outgoing.length}) +
+
+ {outgoing.map((id) => ( + + ))} +
+
+ )} +
+ ); +} +``` + +- [ ] **Step 2: Integrate KnowledgeInfo into App.tsx sidebar rendering** + +In `understand-anything-plugin/packages/dashboard/src/App.tsx`, find where the sidebar renders `NodeInfo` and add a condition: if `graph.kind === "knowledge"` and a node is selected, render `KnowledgeInfo` instead of `NodeInfo`. + +Import at top: +```typescript +import KnowledgeInfo from "./components/KnowledgeInfo"; +``` + +In the sidebar section, wrap the existing NodeInfo render: +```tsx +{graph?.kind === "knowledge" ? : } +``` + +- [ ] **Step 3: Build dashboard and verify** + +Run: `pnpm --filter @understand-anything/dashboard build` +Expected: Clean build + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/KnowledgeInfo.tsx understand-anything-plugin/packages/dashboard/src/App.tsx +git commit -m "feat(dashboard): add KnowledgeInfo sidebar component for knowledge graphs" +``` + +--- + +## Task 7: Dashboard — Reading Panel + +**Files:** +- Create: `understand-anything-plugin/packages/dashboard/src/components/ReadingPanel.tsx` +- Modify: `understand-anything-plugin/packages/dashboard/src/App.tsx` + +- [ ] **Step 1: Create ReadingPanel.tsx** + +Create `understand-anything-plugin/packages/dashboard/src/components/ReadingPanel.tsx`: + +```tsx +import { useState } from "react"; +import { useDashboardStore } from "../store"; + +export default function ReadingPanel() { + const graph = useDashboardStore((s) => s.graph); + const selectedNode = useDashboardStore((s) => s.selectedNode); + const setSelectedNode = useDashboardStore((s) => s.setSelectedNode); + const [isExpanded, setIsExpanded] = useState(false); + + if (!graph || graph.kind !== "knowledge" || !selectedNode) return null; + + const node = graph.nodes.find((n) => n.id === selectedNode); + if (!node || node.type !== "article") return null; + + // Get backlinks for this article + const backlinks = graph.edges + .filter((e) => e.target === node.id) + .map((e) => { + const sourceNode = graph.nodes.find((n) => n.id === e.source); + return sourceNode ? { id: sourceNode.id, name: sourceNode.name, type: sourceNode.type } : null; + }) + .filter(Boolean) as { id: string; name: string; type: string }[]; + + return ( +
+ {/* Header bar */} +
+
+ Reading + {node.name} +
+
+ + +
+
+ +
+ {/* Main content */} +
+
+

{node.name}

+ + {/* Tags */} + {node.tags.length > 0 && ( +
+ {node.tags.map((tag) => ( + + {tag} + + ))} +
+ )} + + {/* Article content (summary for now — full markdown rendering is a future enhancement) */} +
+

{node.summary}

+
+ + {/* Frontmatter metadata */} + {node.knowledgeMeta?.frontmatter && Object.keys(node.knowledgeMeta.frontmatter).length > 0 && ( +
+
Metadata
+ {Object.entries(node.knowledgeMeta.frontmatter).map(([key, value]) => ( +
+ {key}:{" "} + {String(value)} +
+ ))} +
+ )} +
+
+ + {/* Backlinks sidebar */} + {backlinks.length > 0 && ( +
+
+ Backlinks ({backlinks.length}) +
+
+ {backlinks.map((link) => ( + + ))} +
+
+ )} +
+
+ ); +} +``` + +- [ ] **Step 2: Add ReadingPanel to App.tsx** + +Import and render `ReadingPanel` in the main dashboard layout, positioned at the bottom: + +```typescript +import ReadingPanel from "./components/ReadingPanel"; +``` + +Add `` inside the dashboard container, after the graph view area. + +- [ ] **Step 3: Build and verify** + +Run: `pnpm --filter @understand-anything/dashboard build` +Expected: Clean build + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/ReadingPanel.tsx understand-anything-plugin/packages/dashboard/src/App.tsx +git commit -m "feat(dashboard): add ReadingPanel for article reading mode in knowledge graphs" +``` + +--- + +## Task 8: Dashboard — Vertical Layout for Knowledge Graphs + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx` +- Modify: `understand-anything-plugin/packages/dashboard/src/utils/layout.ts` (if direction isn't already configurable) + +- [ ] **Step 1: Check how layout direction is passed to dagre** + +Read `GraphView.tsx` to find where `applyDagreLayout` is called. The layout.ts `applyDagreLayout` already accepts a `direction: "TB" | "LR"` parameter (default `"TB"`). + +Find where GraphView calls this function and check what direction it passes. + +- [ ] **Step 2: Pass graph kind to layout decision** + +In `GraphView.tsx`, where the layout is applied, check the graph's `kind` field. If `kind === "knowledge"`, use `"TB"` (top-to-bottom). If `kind === "codebase"` or undefined, keep the existing default. + +The graph object is available via the store. Add: + +```typescript +const graphKind = useDashboardStore((s) => s.graph?.kind); +const layoutDirection = graphKind === "knowledge" ? "TB" : "LR"; +``` + +Pass `layoutDirection` to the layout call. + +- [ ] **Step 3: Build and verify** + +Run: `pnpm --filter @understand-anything/dashboard build` +Expected: Clean build + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx +git commit -m "feat(dashboard): use vertical top-down layout for knowledge graphs" +``` + +--- + +## Task 9: Dashboard — Knowledge Edge Styling + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx` + +- [ ] **Step 1: Add knowledge edge style map** + +In `GraphView.tsx`, add a style map for knowledge edge types. Follow the existing pattern from `DomainGraphView.tsx` which uses ReactFlow's `style` prop: + +```typescript +const KNOWLEDGE_EDGE_STYLES: Record = { + cites: { strokeDasharray: "6 3", strokeWidth: 1.5 }, + contradicts: { stroke: "#c97070", strokeWidth: 2 }, + builds_on: { stroke: "var(--color-accent)", strokeWidth: 2 }, + categorized_under: { stroke: "rgba(150,150,150,0.5)", strokeWidth: 1 }, + authored_by: { strokeDasharray: "3 3", stroke: "var(--color-node-entity)", strokeWidth: 1.5 }, + exemplifies: { strokeDasharray: "3 3", stroke: "var(--color-node-claim)", strokeWidth: 1.5 }, +}; +``` + +- [ ] **Step 2: Apply styles when building ReactFlow edges** + +Where edges are converted to ReactFlow format, check if the graph is `kind === "knowledge"` and the edge type has a knowledge style. Merge the style: + +```typescript +const knowledgeStyle = graph?.kind === "knowledge" ? KNOWLEDGE_EDGE_STYLES[edge.type] : undefined; +// Merge with existing edge style +const style = { ...baseEdgeStyle, ...knowledgeStyle }; +``` + +- [ ] **Step 3: Build and verify** + +Run: `pnpm --filter @understand-anything/dashboard build` +Expected: Clean build + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx +git commit -m "feat(dashboard): add distinct edge styles for knowledge relationship types" +``` + +--- + +## Task 10: Dashboard — Knowledge-Aware ProjectOverview + +**Files:** +- Modify: `understand-anything-plugin/packages/dashboard/src/components/ProjectOverview.tsx` + +- [ ] **Step 1: Add knowledge-specific stats** + +In `ProjectOverview.tsx`, detect `graph.kind === "knowledge"` and show knowledge-specific stats: + +- Total articles, entities, topics, claims, sources (instead of "code, config, docs, infra, data") +- Detected format (from the first node's `knowledgeMeta.format`) +- Remove "Languages" and "Frameworks" sections for knowledge graphs (they'll be empty) + +Add after the existing stats grid: + +```tsx +{graph.kind === "knowledge" && ( +
+
Knowledge Stats
+
+ n.type === "article").length} /> + n.type === "entity").length} /> + n.type === "topic").length} /> + n.type === "claim").length} /> + n.type === "source").length} /> +
+
+)} +``` + +Reuse or create a `StatBox` component matching the existing style. + +- [ ] **Step 2: Conditionally hide code-specific sections** + +Wrap the "Languages", "Frameworks", and code-specific file type breakdown sections in a condition: + +```tsx +{graph.kind !== "knowledge" && ( + <> + {/* existing languages/frameworks/file-types sections */} + +)} +``` + +- [ ] **Step 3: Build and verify** + +Run: `pnpm --filter @understand-anything/dashboard build` +Expected: Clean build + +- [ ] **Step 4: Commit** + +```bash +git add understand-anything-plugin/packages/dashboard/src/components/ProjectOverview.tsx +git commit -m "feat(dashboard): add knowledge-specific stats to ProjectOverview" +``` + +--- + +## Task 11: Create Agent Definitions + +**Files:** +- Create: `understand-anything-plugin/agents/knowledge-scanner.md` +- Create: `understand-anything-plugin/agents/format-detector.md` +- Create: `understand-anything-plugin/agents/article-analyzer.md` +- Create: `understand-anything-plugin/agents/relationship-builder.md` + +- [ ] **Step 1: Create knowledge-scanner agent** + +Create `understand-anything-plugin/agents/knowledge-scanner.md`: + +```markdown +--- +name: knowledge-scanner +description: Scans a directory for markdown files and produces a file manifest for knowledge base analysis +model: inherit +--- + +# Knowledge Scanner Agent + +You scan a target directory to discover all markdown files for knowledge base analysis. + +## Input + +You receive a JSON block with: +- `targetDir` — absolute path to the knowledge base directory + +## Task + +1. Use Glob/Bash to find all `.md` files in the target directory (recursive) +2. Exclude common non-content directories: `.obsidian/`, `logseq/`, `.foam/`, `_meta/`, `node_modules/`, `.git/` +3. For each file, capture: + - `path` — relative path from targetDir + - `sizeLines` — number of lines + - `preview` — first 20 lines of content +4. Detect directory structure signatures: + - Check for `.obsidian/` directory + - Check for `logseq/` + `pages/` directories + - Check for `.dendron.yml` or `*.schema.yml` + - Check for `.foam/` or `.vscode/foam.json` + - Check for `raw/` + `wiki/` + `index.md` + - Scan a sample of files for `[[wikilinks]]` and unique ID prefixes +5. Write results to `$PROJECT_ROOT/.understand-anything/intermediate/knowledge-manifest.json` + +## Output Schema + +```json +{ + "targetDir": "/absolute/path", + "totalFiles": 342, + "directorySignatures": { + "hasObsidianDir": true, + "hasLogseqDir": false, + "hasDendronConfig": false, + "hasFoamConfig": false, + "hasKarpathyStructure": false, + "hasWikilinks": true, + "hasUniqueIdPrefixes": false + }, + "files": [ + { + "path": "notes/topic.md", + "sizeLines": 45, + "preview": "---\ntags: [ai, ml]\n---\n# Topic Name\n..." + } + ] +} +``` + +## Rules + +- Do NOT read file contents beyond the 20-line preview +- Sort files by path alphabetically +- Report total count prominently +- Write output to `.understand-anything/intermediate/knowledge-manifest.json` +``` + +- [ ] **Step 2: Create format-detector agent** + +Create `understand-anything-plugin/agents/format-detector.md`: + +```markdown +--- +name: format-detector +description: Detects the knowledge base format from directory signatures and file samples +model: inherit +--- + +# Format Detector Agent + +You analyze the knowledge-manifest.json to determine which knowledge base format is being used. + +## Input + +Read `.understand-anything/intermediate/knowledge-manifest.json` produced by the knowledge-scanner. + +## Detection Priority + +Apply these rules in order (first match wins): + +| Priority | Signal | Format | +|----------|--------|--------| +| 1 | `hasObsidianDir === true` | `obsidian` | +| 2 | `hasLogseqDir === true` | `logseq` | +| 3 | `hasDendronConfig === true` | `dendron` | +| 4 | `hasFoamConfig === true` | `foam` | +| 5 | `hasKarpathyStructure === true` | `karpathy` | +| 6 | `hasWikilinks === true` AND `hasUniqueIdPrefixes === true` | `zettelkasten` | +| 7 | fallback | `plain` | + +## Output + +Write to `.understand-anything/intermediate/format-detection.json`: + +```json +{ + "format": "obsidian", + "confidence": 0.95, + "parsingHints": { + "linkStyle": "wikilink", + "metadataLocation": "yaml-frontmatter", + "folderSemantics": "none", + "specialFiles": [".obsidian/app.json"], + "tagSyntax": "hashtag-inline" + } +} +``` + +## Rules + +- Always produce exactly one format +- Set confidence based on how many signals matched +- Include parsing hints that will help the article-analyzer +``` + +- [ ] **Step 3: Create article-analyzer agent** + +Create `understand-anything-plugin/agents/article-analyzer.md`: + +```markdown +--- +name: article-analyzer +description: Analyzes individual markdown files to extract knowledge nodes and explicit edges +model: inherit +--- + +# Article Analyzer Agent + +You analyze batches of markdown files from a knowledge base to extract structured knowledge graph data. + +## Input + +You receive a JSON block with: +- `projectRoot` — absolute path to the knowledge base +- `batchFiles` — array of file objects from the manifest (path, sizeLines, preview) +- `format` — detected format from format-detection.json +- `parsingHints` — format-specific parsing guidance + +You also receive a **format guide** (injected by the skill) that describes how to parse this specific format. + +## Task + +For each file in the batch: + +### 1. Read the full file content + +### 2. Extract the article node + +- **id**: `article:` (e.g., `article:notes/topic`) +- **type**: `article` +- **name**: First heading, or frontmatter title, or filename +- **filePath**: relative path +- **summary**: 2-3 sentence summary of the article content +- **tags**: from frontmatter tags, inline #tags, or inferred from content (3-5 tags) +- **complexity**: `simple` (<50 lines), `moderate` (50-200 lines), `complex` (>200 lines) +- **knowledgeMeta**: `{ format, wikilinks, frontmatter }` + +### 3. Extract entity nodes + +Identify named entities mentioned in the article: +- People, organizations, tools, papers, projects, datasets +- **id**: `entity:` (e.g., `entity:andrej-karpathy`) +- **type**: `entity` +- **summary**: one-sentence description based on context in the article +- **tags**: entity category tags like `person`, `tool`, `paper`, `organization` + +### 4. Extract claim nodes (for articles with strong assertions) + +- Only extract claims that are significant takeaways or insights +- **id**: `claim::` (e.g., `claim:notes/topic:rag-loses-context`) +- **type**: `claim` +- **summary**: the assertion itself + +### 5. Extract source nodes (for cited references) + +- External URLs, paper references, book citations +- **id**: `source:` +- **type**: `source` +- **knowledgeMeta**: `{ sourceUrl }` + +### 6. Extract explicit edges + +- `[[wikilinks]]` → find target article, create `related` edge +- Frontmatter references → `categorized_under` or `related` edges +- Inline citations/URLs → `cites` edges to source nodes +- Author mentions → `authored_by` edges + +## Node ID Conventions + +``` +article: +entity: +topic: +claim:: +source: +``` + +Normalize: lowercase, replace spaces with hyphens, remove special characters. + +**Deduplicate entities**: If the same entity appears across multiple files in the batch, emit it only once. Use the most informative summary. + +## Edge Weight Conventions + +``` +contains: 1.0 +authored_by: 0.9 +cites: 0.8 +categorized_under: 0.7 +builds_on: 0.7 +related: 0.5 +exemplifies: 0.5 +contradicts: 0.6 +``` + +## Output + +Write per-batch results to `.understand-anything/intermediate/article-batch-.json`: + +```json +{ + "nodes": [...], + "edges": [...] +} +``` + +## Rules + +- One article node per file (always) +- Entity nodes only for clearly named entities (not generic concepts) +- Claim nodes only for significant assertions (not every sentence) +- Source nodes only for explicit external references +- Deduplicate entities within the batch +- Respect the format guide for parsing links and metadata +``` + +- [ ] **Step 4: Create relationship-builder agent** + +Create `understand-anything-plugin/agents/relationship-builder.md`: + +```markdown +--- +name: relationship-builder +description: Discovers implicit cross-file relationships and builds topic clusters from analyzed knowledge nodes +model: inherit +--- + +# Relationship Builder Agent + +You analyze all extracted nodes and edges to discover implicit relationships that explicit links missed. + +## Input + +Read all `article-batch-*.json` files from `.understand-anything/intermediate/`. Merge all nodes and edges. + +## Task + +### 1. Deduplicate entities globally + +Multiple batches may have emitted the same entity. Merge them: +- Keep the most detailed summary +- Union all tags +- Collapse duplicate IDs + +### 2. Discover implicit relationships + +For each pair of articles/entities, determine if there's an implicit relationship: + +- **builds_on**: Article A extends or deepens ideas from Article B (similar topics, references same entities, but goes further) +- **contradicts**: Article A makes claims that conflict with Article B +- **categorized_under**: Group articles into topic clusters +- **exemplifies**: An entity is a concrete example of a concept/topic +- **related**: Articles share significant thematic overlap but aren't explicitly linked + +Set `confidence` in knowledgeMeta for LLM-inferred edges (0.0-1.0). + +### 3. Build topic nodes + +Identify thematic clusters across all articles: +- **id**: `topic:` +- **type**: `topic` +- **summary**: description of what this topic covers +- Create `categorized_under` edges from articles/entities to their topics + +### 4. Build layers + +Group nodes into layers by topic: +- Each topic becomes a layer +- Articles, entities, claims, and sources are assigned to their primary topic's layer +- Nodes not clearly belonging to any topic go into an "Uncategorized" layer + +### 5. Build tour + +Create a guided tour through the knowledge base: +- Start with the broadest topic overview +- Walk through key articles in a logical learning order +- Each step covers 1-3 related nodes +- 5-10 tour steps total + +## Output + +Write to `.understand-anything/intermediate/relationships.json`: + +```json +{ + "nodes": [...], + "edges": [...], + "layers": [...], + "tour": [...] +} +``` + +## Rules + +- Only add edges with confidence > 0.4 +- Don't duplicate edges that already exist from article-analyzer +- Topics should be meaningful clusters (3+ articles), not one-off categories +- Tour should be navigable by someone new to the knowledge base +- Keep layers balanced — no layer with 50%+ of all nodes +``` + +- [ ] **Step 5: Commit** + +```bash +git add understand-anything-plugin/agents/knowledge-scanner.md understand-anything-plugin/agents/format-detector.md understand-anything-plugin/agents/article-analyzer.md understand-anything-plugin/agents/relationship-builder.md +git commit -m "feat(agents): add knowledge-scanner, format-detector, article-analyzer, and relationship-builder agents" +``` + +--- + +## Task 12: Create Format Guides + +**Files:** +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/obsidian.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/logseq.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/dendron.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/foam.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/karpathy.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/zettelkasten.md` +- Create: `understand-anything-plugin/skills/understand-knowledge/formats/plain.md` + +**IMPORTANT**: Each format guide must be **research-backed**. The implementing agent MUST: +1. Use WebSearch and WebFetch to read the **official documentation** for each format +2. Study the actual parsing rules, not assumptions +3. Include specific syntax examples from real documentation + +- [ ] **Step 1: Create obsidian.md format guide** + +Research Obsidian's official docs (https://help.obsidian.md/) and create `understand-anything-plugin/skills/understand-knowledge/formats/obsidian.md`: + +The guide must cover: +- Detection: `.obsidian/` directory exists +- Link syntax: `[[wikilink]]`, `[[note|alias]]`, `[[note#heading]]`, `![[embed]]` +- Metadata: YAML frontmatter between `---` delimiters +- Tags: `#tag` inline, `tags:` in frontmatter (both array and space-separated) +- Properties: Obsidian Properties (frontmatter fields rendered in UI) +- Folder semantics: Obsidian doesn't assign folder meaning by default +- Special files: `.obsidian/app.json`, `.obsidian/workspace.json` (ignore these) +- Canvas: `.canvas` files (JSON format, describe spatial layouts — extract card references) +- Dataview: inline fields `key:: value`, `[key:: value]` + +- [ ] **Step 2: Create logseq.md format guide** + +Research Logseq docs (https://docs.logseq.com/) and create `understand-anything-plugin/skills/understand-knowledge/formats/logseq.md`: + +Cover: +- Detection: `logseq/` + `pages/` directories +- Structure: `journals/YYYY_MM_DD.md` (daily notes), `pages/*.md` (named pages) +- Link syntax: `[[wikilinks]]`, `((block-references))` by UUID +- Block-based: Content is organized as bullet-point outlines +- Properties: `key:: value` syntax on blocks +- Tags: `#tag` inline, page tags via properties +- Special: `logseq/config.edn` for configuration + +- [ ] **Step 3: Create dendron.md format guide** + +Research Dendron wiki (https://wiki.dendron.so/) and create `understand-anything-plugin/skills/understand-knowledge/formats/dendron.md`: + +Cover: +- Detection: `.dendron.yml` or `*.schema.yml` files +- Hierarchy: dot-delimited filenames (`a.b.c.md`) +- Link syntax: `[[wikilinks]]` with hierarchy awareness +- Schemas: `.schema.yml` files define expected hierarchy structure +- Frontmatter: YAML with required `id` and `title` fields +- Stubs: auto-created intermediate hierarchy files + +- [ ] **Step 4: Create foam.md format guide** + +Research Foam docs (https://foambubble.github.io/foam/) and create `understand-anything-plugin/skills/understand-knowledge/formats/foam.md`: + +Cover: +- Detection: `.foam/` directory or `.vscode/foam.json` +- Link syntax: `[[wikilinks]]` plus link reference definitions at file bottom +- Placeholder links: links to non-existent files +- Frontmatter: standard YAML +- Auto-linking: Foam auto-updates links on file rename/move + +- [ ] **Step 5: Create karpathy.md format guide** + +Research Karpathy's gist (https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) and create `understand-anything-plugin/skills/understand-knowledge/formats/karpathy.md`: + +Cover: +- Detection: `raw/` + `wiki/` directories + `index.md` +- Structure: `raw/` (immutable sources), `wiki/` (compiled articles), `_meta/` (state) +- Special files: `index.md` (master page list), `log.md` (append-only operations log) +- Link style: standard markdown links (not wikilinks) +- Log parsing: `## [YYYY-MM-DD] operation | Title` entries +- Wiki articles: LLM-compiled, may have cross-references and backlinks + +- [ ] **Step 6: Create zettelkasten.md format guide** + +Research zettelkasten.de and create `understand-anything-plugin/skills/understand-knowledge/formats/zettelkasten.md`: + +Cover: +- Detection: `[[wikilinks]]` + unique ID prefixes in filenames (timestamps like `202604091234`) +- Atomic notes: one idea per note +- Unique IDs: timestamp or alphanumeric prefix in filename +- Links: `[[wikilinks]]` with optional typed links +- Frontmatter: YAML with tags, creation date +- No folder hierarchy: flat structure, connections via links only + +- [ ] **Step 7: Create plain.md format guide** + +Create `understand-anything-plugin/skills/understand-knowledge/formats/plain.md`: + +Cover: +- Detection: fallback when no other format detected +- Links: standard markdown `[text](relative/path.md)` links +- Structure: folder hierarchy provides categorization +- Headings: `#` hierarchy provides structure within files +- No special metadata expectations +- Tags: none expected (LLM infers topics) + +- [ ] **Step 8: Commit** + +```bash +git add understand-anything-plugin/skills/understand-knowledge/formats/ +git commit -m "feat(skill): add 7 research-backed format guides for knowledge base parsing" +``` + +--- + +## Task 13: Create SKILL.md + +**Files:** +- Create: `understand-anything-plugin/skills/understand-knowledge/SKILL.md` + +- [ ] **Step 1: Create the skill definition** + +Create `understand-anything-plugin/skills/understand-knowledge/SKILL.md`: + +```markdown +--- +name: understand-knowledge +description: Analyze a markdown knowledge base (Obsidian, Logseq, Dendron, Foam, Karpathy-style, Zettelkasten, or plain) to produce an interactive knowledge graph with typed relationships +argument-hint: [path/to/notes] [--ingest ] +--- + +# /understand-knowledge + +Analyze a personal knowledge base of markdown files and produce an interactive knowledge graph. + +## Arguments + +- `path/to/notes` — (optional) directory containing markdown files. Defaults to current working directory. +- `--ingest ` — (optional) incrementally add new file(s) to an existing knowledge graph. + +## Phase 0: Pre-flight + +1. Determine the target directory: + - If a path argument is provided, use it + - Otherwise use the current working directory +2. Create `.understand-anything/` and `.understand-anything/intermediate/` directories if they don't exist +3. If `--ingest` flag is present: + - Verify `.understand-anything/knowledge-graph.json` exists (error if not — must run full scan first) + - Read the existing graph + - Skip to Phase 2 with only the new/changed files +4. Get the current git commit hash (if in a git repo, otherwise use "no-git") + +## Phase 1: SCAN + +Dispatch the **knowledge-scanner** agent: + +```json +{ + "targetDir": "" +} +``` + +Wait for the agent to write `.understand-anything/intermediate/knowledge-manifest.json`. + +Report: "Scanned {totalFiles} markdown files." + +## Phase 2: FORMAT DETECTION + +Dispatch the **format-detector** agent. + +Wait for `.understand-anything/intermediate/format-detection.json`. + +Report: "Detected format: {format} (confidence: {confidence})" + +## Phase 3: ANALYZE + +Read the format detection result. Load the corresponding format guide: + +- `obsidian` → inject `skills/understand-knowledge/formats/obsidian.md` +- `logseq` → inject `skills/understand-knowledge/formats/logseq.md` +- `dendron` → inject `skills/understand-knowledge/formats/dendron.md` +- `foam` → inject `skills/understand-knowledge/formats/foam.md` +- `karpathy` → inject `skills/understand-knowledge/formats/karpathy.md` +- `zettelkasten` → inject `skills/understand-knowledge/formats/zettelkasten.md` +- `plain` → inject `skills/understand-knowledge/formats/plain.md` + +Batch the files from the manifest into groups of 15-25 files each. + +For each batch, dispatch an **article-analyzer** agent with: + +```json +{ + "projectRoot": "", + "batchFiles": [...], + "format": "", + "parsingHints": {...} +} +``` + +Inject the format guide content into each agent's context. + +Run up to 5 batches concurrently. + +Wait for all `article-batch-*.json` files. + +Report: "Analyzed {totalFiles} files across {batchCount} batches." + +## Phase 4: RELATIONSHIPS + +Dispatch the **relationship-builder** agent. + +Wait for `.understand-anything/intermediate/relationships.json`. + +Report: "Discovered {topicCount} topics, {implicitEdgeCount} implicit relationships." + +## Phase 5: ASSEMBLE + +Merge all intermediate results into a single knowledge graph: + +1. Read all `article-batch-*.json` files — collect all nodes and edges +2. Read `relationships.json` — merge in topic nodes, implicit edges, layers, and tour +3. Deduplicate nodes by ID (keep the most complete version) +4. Deduplicate edges by source+target+type +5. Assemble into `KnowledgeGraph` format: + +```json +{ + "version": "1.0", + "kind": "knowledge", + "project": { + "name": "", + "languages": [], + "frameworks": [], + "description": "Knowledge base analyzed from format", + "analyzedAt": "", + "gitCommitHash": "" + }, + "nodes": [...], + "edges": [...], + "layers": [...], + "tour": [...] +} +``` + +## Phase 6: REVIEW + +Dispatch the existing **graph-reviewer** agent to validate: +- All edge source/target IDs reference existing nodes +- No orphan nodes (nodes with zero edges) +- No duplicate node IDs +- All layers reference existing nodes +- Tour steps reference existing nodes + +Apply fixes from the reviewer. + +## Phase 7: SAVE + +1. Write `.understand-anything/knowledge-graph.json` +2. Write `.understand-anything/meta.json`: + ```json + { + "lastAnalyzedAt": "", + "gitCommitHash": "", + "version": "1.0", + "analyzedFiles": , + "knowledgeFormat": "" + } + ``` +3. Clean up `.understand-anything/intermediate/` directory +4. Report: "Knowledge graph saved with {nodeCount} nodes and {edgeCount} edges." + +## Phase 8: DASHBOARD + +Auto-trigger `/understand-dashboard` to launch the visualization. + +## Incremental Mode (--ingest) + +When `--ingest ` is specified: + +1. Read the existing `knowledge-graph.json` +2. Scan only the specified file(s) or folder +3. Skip format detection (reuse format from existing graph's metadata) +4. Run article-analyzer on only the new/changed files +5. Run relationship-builder on new nodes against the full existing graph +6. Merge new nodes/edges into the existing graph +7. Re-run graph-reviewer +8. Save updated graph +``` + +- [ ] **Step 2: Commit** + +```bash +git add understand-anything-plugin/skills/understand-knowledge/SKILL.md +git commit -m "feat(skill): add /understand-knowledge skill definition with 8-phase pipeline" +``` + +--- + +## Task 14: Build, Test & Verify End-to-End + +**Files:** +- All modified files + +- [ ] **Step 1: Build core package** + +Run: `pnpm --filter @understand-anything/core build` +Expected: Clean build, no errors + +- [ ] **Step 2: Run core tests** + +Run: `pnpm --filter @understand-anything/core test -- --run` +Expected: All tests pass, including new knowledge-schema tests + +- [ ] **Step 3: Build dashboard** + +Run: `pnpm --filter @understand-anything/dashboard build` +Expected: Clean build, no errors + +- [ ] **Step 4: Run lint** + +Run: `pnpm lint` +Expected: No lint errors + +- [ ] **Step 5: Verify skill is discoverable** + +Check that the skill file exists and has valid frontmatter: + +Run: `head -5 understand-anything-plugin/skills/understand-knowledge/SKILL.md` +Expected: Valid `---` delimited YAML with name, description, argument-hint + +- [ ] **Step 6: Verify all agents are present** + +Run: `ls understand-anything-plugin/agents/ | grep knowledge\|format\|article\|relationship` +Expected: `knowledge-scanner.md`, `format-detector.md`, `article-analyzer.md`, `relationship-builder.md` + +- [ ] **Step 7: Verify all format guides are present** + +Run: `ls understand-anything-plugin/skills/understand-knowledge/formats/` +Expected: `obsidian.md`, `logseq.md`, `dendron.md`, `foam.md`, `karpathy.md`, `zettelkasten.md`, `plain.md` + +- [ ] **Step 8: Final commit** + +```bash +git add -A +git commit -m "feat: complete /understand-knowledge implementation — knowledge base analysis skill" +``` From b2c2934291ed223d3efdc43e6d63bd71e869535c Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Thu, 9 Apr 2026 23:11:44 +0800 Subject: [PATCH 04/11] docs: reorganize docs/ into superpowers/specs/ and superpowers/plans/ Move all design docs to docs/superpowers/specs/ and all implementation plans to docs/superpowers/plans/ for consistent organization. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/{ => superpowers}/plans/2026-03-14-phase1-implementation.md | 0 docs/{ => superpowers}/plans/2026-03-14-phase2-implementation.md | 0 docs/{ => superpowers}/plans/2026-03-14-phase3-implementation.md | 0 docs/{ => superpowers}/plans/2026-03-14-phase4-implementation.md | 0 .../{ => superpowers}/plans/2026-03-15-homepage-implementation.md | 0 .../plans/2026-03-18-multi-platform-simple-implementation.md | 0 docs/{ => superpowers}/plans/2026-03-21-language-agnostic-plan.md | 0 .../plans/2026-03-25-dashboard-robustness-impl.md | 0 .../plans/2026-03-25-dashboard-robustness-plan.md | 0 .../plans/2026-03-26-theme-system-implementation.md | 0 docs/{ => superpowers}/plans/2026-03-27-token-reduction-impl.md | 0 .../plans/2026-03-28-understand-anything-extension-impl.md | 0 docs/{ => superpowers}/plans/2026-03-29-homepage-update-impl.md | 0 .../plans/2026-04-01-business-domain-knowledge-impl.md | 0 .../specs}/2026-03-14-understand-anything-design.md | 0 docs/{plans => superpowers/specs}/2026-03-15-homepage-design.md | 0 .../specs}/2026-03-18-multi-platform-simple-design.md | 0 .../specs}/2026-03-21-language-agnostic-design.md | 0 .../specs}/2026-03-26-theme-system-design.md | 0 .../specs}/2026-03-27-token-reduction-design.md | 0 .../specs}/2026-03-28-understand-anything-extension-design.md | 0 .../specs}/2026-03-29-homepage-update-design.md | 0 .../specs}/2026-04-01-business-domain-knowledge-design.md | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename docs/{ => superpowers}/plans/2026-03-14-phase1-implementation.md (100%) rename docs/{ => superpowers}/plans/2026-03-14-phase2-implementation.md (100%) rename docs/{ => superpowers}/plans/2026-03-14-phase3-implementation.md (100%) rename docs/{ => superpowers}/plans/2026-03-14-phase4-implementation.md (100%) rename docs/{ => superpowers}/plans/2026-03-15-homepage-implementation.md (100%) rename docs/{ => superpowers}/plans/2026-03-18-multi-platform-simple-implementation.md (100%) rename docs/{ => superpowers}/plans/2026-03-21-language-agnostic-plan.md (100%) rename docs/{ => superpowers}/plans/2026-03-25-dashboard-robustness-impl.md (100%) rename docs/{ => superpowers}/plans/2026-03-25-dashboard-robustness-plan.md (100%) rename docs/{ => superpowers}/plans/2026-03-26-theme-system-implementation.md (100%) rename docs/{ => superpowers}/plans/2026-03-27-token-reduction-impl.md (100%) rename docs/{ => superpowers}/plans/2026-03-28-understand-anything-extension-impl.md (100%) rename docs/{ => superpowers}/plans/2026-03-29-homepage-update-impl.md (100%) rename docs/{ => superpowers}/plans/2026-04-01-business-domain-knowledge-impl.md (100%) rename docs/{plans => superpowers/specs}/2026-03-14-understand-anything-design.md (100%) rename docs/{plans => superpowers/specs}/2026-03-15-homepage-design.md (100%) rename docs/{plans => superpowers/specs}/2026-03-18-multi-platform-simple-design.md (100%) rename docs/{plans => superpowers/specs}/2026-03-21-language-agnostic-design.md (100%) rename docs/{plans => superpowers/specs}/2026-03-26-theme-system-design.md (100%) rename docs/{plans => superpowers/specs}/2026-03-27-token-reduction-design.md (100%) rename docs/{plans => superpowers/specs}/2026-03-28-understand-anything-extension-design.md (100%) rename docs/{plans => superpowers/specs}/2026-03-29-homepage-update-design.md (100%) rename docs/{plans => superpowers/specs}/2026-04-01-business-domain-knowledge-design.md (100%) diff --git a/docs/plans/2026-03-14-phase1-implementation.md b/docs/superpowers/plans/2026-03-14-phase1-implementation.md similarity index 100% rename from docs/plans/2026-03-14-phase1-implementation.md rename to docs/superpowers/plans/2026-03-14-phase1-implementation.md diff --git a/docs/plans/2026-03-14-phase2-implementation.md b/docs/superpowers/plans/2026-03-14-phase2-implementation.md similarity index 100% rename from docs/plans/2026-03-14-phase2-implementation.md rename to docs/superpowers/plans/2026-03-14-phase2-implementation.md diff --git a/docs/plans/2026-03-14-phase3-implementation.md b/docs/superpowers/plans/2026-03-14-phase3-implementation.md similarity index 100% rename from docs/plans/2026-03-14-phase3-implementation.md rename to docs/superpowers/plans/2026-03-14-phase3-implementation.md diff --git a/docs/plans/2026-03-14-phase4-implementation.md b/docs/superpowers/plans/2026-03-14-phase4-implementation.md similarity index 100% rename from docs/plans/2026-03-14-phase4-implementation.md rename to docs/superpowers/plans/2026-03-14-phase4-implementation.md diff --git a/docs/plans/2026-03-15-homepage-implementation.md b/docs/superpowers/plans/2026-03-15-homepage-implementation.md similarity index 100% rename from docs/plans/2026-03-15-homepage-implementation.md rename to docs/superpowers/plans/2026-03-15-homepage-implementation.md diff --git a/docs/plans/2026-03-18-multi-platform-simple-implementation.md b/docs/superpowers/plans/2026-03-18-multi-platform-simple-implementation.md similarity index 100% rename from docs/plans/2026-03-18-multi-platform-simple-implementation.md rename to docs/superpowers/plans/2026-03-18-multi-platform-simple-implementation.md diff --git a/docs/plans/2026-03-21-language-agnostic-plan.md b/docs/superpowers/plans/2026-03-21-language-agnostic-plan.md similarity index 100% rename from docs/plans/2026-03-21-language-agnostic-plan.md rename to docs/superpowers/plans/2026-03-21-language-agnostic-plan.md diff --git a/docs/plans/2026-03-25-dashboard-robustness-impl.md b/docs/superpowers/plans/2026-03-25-dashboard-robustness-impl.md similarity index 100% rename from docs/plans/2026-03-25-dashboard-robustness-impl.md rename to docs/superpowers/plans/2026-03-25-dashboard-robustness-impl.md diff --git a/docs/plans/2026-03-25-dashboard-robustness-plan.md b/docs/superpowers/plans/2026-03-25-dashboard-robustness-plan.md similarity index 100% rename from docs/plans/2026-03-25-dashboard-robustness-plan.md rename to docs/superpowers/plans/2026-03-25-dashboard-robustness-plan.md diff --git a/docs/plans/2026-03-26-theme-system-implementation.md b/docs/superpowers/plans/2026-03-26-theme-system-implementation.md similarity index 100% rename from docs/plans/2026-03-26-theme-system-implementation.md rename to docs/superpowers/plans/2026-03-26-theme-system-implementation.md diff --git a/docs/plans/2026-03-27-token-reduction-impl.md b/docs/superpowers/plans/2026-03-27-token-reduction-impl.md similarity index 100% rename from docs/plans/2026-03-27-token-reduction-impl.md rename to docs/superpowers/plans/2026-03-27-token-reduction-impl.md diff --git a/docs/plans/2026-03-28-understand-anything-extension-impl.md b/docs/superpowers/plans/2026-03-28-understand-anything-extension-impl.md similarity index 100% rename from docs/plans/2026-03-28-understand-anything-extension-impl.md rename to docs/superpowers/plans/2026-03-28-understand-anything-extension-impl.md diff --git a/docs/plans/2026-03-29-homepage-update-impl.md b/docs/superpowers/plans/2026-03-29-homepage-update-impl.md similarity index 100% rename from docs/plans/2026-03-29-homepage-update-impl.md rename to docs/superpowers/plans/2026-03-29-homepage-update-impl.md diff --git a/docs/plans/2026-04-01-business-domain-knowledge-impl.md b/docs/superpowers/plans/2026-04-01-business-domain-knowledge-impl.md similarity index 100% rename from docs/plans/2026-04-01-business-domain-knowledge-impl.md rename to docs/superpowers/plans/2026-04-01-business-domain-knowledge-impl.md diff --git a/docs/plans/2026-03-14-understand-anything-design.md b/docs/superpowers/specs/2026-03-14-understand-anything-design.md similarity index 100% rename from docs/plans/2026-03-14-understand-anything-design.md rename to docs/superpowers/specs/2026-03-14-understand-anything-design.md diff --git a/docs/plans/2026-03-15-homepage-design.md b/docs/superpowers/specs/2026-03-15-homepage-design.md similarity index 100% rename from docs/plans/2026-03-15-homepage-design.md rename to docs/superpowers/specs/2026-03-15-homepage-design.md diff --git a/docs/plans/2026-03-18-multi-platform-simple-design.md b/docs/superpowers/specs/2026-03-18-multi-platform-simple-design.md similarity index 100% rename from docs/plans/2026-03-18-multi-platform-simple-design.md rename to docs/superpowers/specs/2026-03-18-multi-platform-simple-design.md diff --git a/docs/plans/2026-03-21-language-agnostic-design.md b/docs/superpowers/specs/2026-03-21-language-agnostic-design.md similarity index 100% rename from docs/plans/2026-03-21-language-agnostic-design.md rename to docs/superpowers/specs/2026-03-21-language-agnostic-design.md diff --git a/docs/plans/2026-03-26-theme-system-design.md b/docs/superpowers/specs/2026-03-26-theme-system-design.md similarity index 100% rename from docs/plans/2026-03-26-theme-system-design.md rename to docs/superpowers/specs/2026-03-26-theme-system-design.md diff --git a/docs/plans/2026-03-27-token-reduction-design.md b/docs/superpowers/specs/2026-03-27-token-reduction-design.md similarity index 100% rename from docs/plans/2026-03-27-token-reduction-design.md rename to docs/superpowers/specs/2026-03-27-token-reduction-design.md diff --git a/docs/plans/2026-03-28-understand-anything-extension-design.md b/docs/superpowers/specs/2026-03-28-understand-anything-extension-design.md similarity index 100% rename from docs/plans/2026-03-28-understand-anything-extension-design.md rename to docs/superpowers/specs/2026-03-28-understand-anything-extension-design.md diff --git a/docs/plans/2026-03-29-homepage-update-design.md b/docs/superpowers/specs/2026-03-29-homepage-update-design.md similarity index 100% rename from docs/plans/2026-03-29-homepage-update-design.md rename to docs/superpowers/specs/2026-03-29-homepage-update-design.md diff --git a/docs/plans/2026-04-01-business-domain-knowledge-design.md b/docs/superpowers/specs/2026-04-01-business-domain-knowledge-design.md similarity index 100% rename from docs/plans/2026-04-01-business-domain-knowledge-design.md rename to docs/superpowers/specs/2026-04-01-business-domain-knowledge-design.md From 55a03f66472ff327fa122c5fa532cc85cc24aadd Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sat, 11 Apr 2026 09:19:13 +0800 Subject: [PATCH 05/11] docs: add Star History rank badge to all READMEs Co-Authored-By: Claude Opus 4.6 (1M context) --- README.ja-JP.md | 10 ++++++++++ README.md | 10 ++++++++++ README.tr-TR.md | 10 ++++++++++ README.zh-CN.md | 10 ++++++++++ README.zh-TW.md | 10 ++++++++++ 5 files changed, 50 insertions(+) diff --git a/README.ja-JP.md b/README.ja-JP.md index 7a68455..dd5fd59 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -10,6 +10,16 @@ English | 简体中文 | 繁體中文 | 日本語 | Türkçe

+

+ + + + + Star History Rank + + +

+

クイックスタート License: MIT diff --git a/README.md b/README.md index ef87951..4081f6c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,16 @@ English | 简体中文 | 繁體中文 | 日本語 | Türkçe

+

+ + + + + Star History Rank + + +

+

Quick Start License: MIT diff --git a/README.tr-TR.md b/README.tr-TR.md index 39cab1b..96b278a 100644 --- a/README.tr-TR.md +++ b/README.tr-TR.md @@ -10,6 +10,16 @@ English | 简体中文 | 繁體中文 | 日本語 | Türkçe

+

+ + + + + Star History Rank + + +

+

Hızlı Başlangıç Lisans: MIT diff --git a/README.zh-CN.md b/README.zh-CN.md index 62f722a..da80ab2 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,6 +9,16 @@ English | 简体中文 | 繁體中文 | 日本語 | Türkçe

+

+ + + + + Star History Rank + + +

+

Quick Start License: MIT diff --git a/README.zh-TW.md b/README.zh-TW.md index 08aa44e..46d10a7 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -9,6 +9,16 @@ English | 简体中文 | 繁體中文 | 日本語 | Türkçe

+

+ + + + + Star History Rank + + +

+

Quick Start License: MIT From 2fc85e68c3975fc116e63c5a2e717e04816fd103 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sun, 12 Apr 2026 11:09:41 +0800 Subject: [PATCH 06/11] feat: add /understand-knowledge for Karpathy LLM wiki knowledge bases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support the Karpathy LLM wiki pattern — a three-layer architecture (raw sources + wiki markdown + schema) with wikilinks, index.md categories, and append-only log.md. Pipeline: - parse-knowledge-base.py: deterministic extraction of articles, wikilinks, categories from index.md, source nodes from raw/ - article-analyzer agent: LLM-based entity/claim extraction and implicit relationship discovery (builds_on, contradicts, etc.) - merge-knowledge-graph.py: combines scan + analysis with entity dedup, layer assignment from categories, tour generation Dashboard: - KnowledgeGraphView with d3-force layout (community clustering by index.md categories, degree-proportional sizing) - 5 knowledge node types (article, entity, topic, claim, source) - 6 knowledge edge types with visual styling - KnowledgeNodeDetails sidebar (wikilinks, backlinks, preview) - Auto-detect kind:"knowledge" → knowledge-only view mode Core: - 5 node types + 6 edge types added to NodeType/EdgeType unions - KnowledgeMeta interface (wikilinks, backlinks, category, content) - kind field on KnowledgeGraph for view mode detection - Zod schemas + node/edge type aliases Co-Authored-By: Claude Opus 4.6 (1M context) --- pnpm-lock.yaml | 37 +- .../agents/article-analyzer.md | 93 ++++ .../packages/core/src/schema.ts | 43 +- .../packages/core/src/types.ts | 22 +- .../packages/dashboard/package.json | 6 +- .../packages/dashboard/src/App.tsx | 32 +- .../dashboard/src/components/CustomNode.tsx | 10 + .../dashboard/src/components/GraphView.tsx | 1 + .../src/components/KnowledgeGraphView.tsx | 260 +++++++++ .../dashboard/src/components/NodeInfo.tsx | 107 ++++ .../packages/dashboard/src/index.css | 7 + .../packages/dashboard/src/store.ts | 24 +- .../packages/dashboard/src/utils/layout.ts | 120 +++++ understand-anything-plugin/pnpm-lock.yaml | 27 + .../skills/understand-knowledge/SKILL.md | 132 +++++ .../merge-knowledge-graph.py | 397 ++++++++++++++ .../parse-knowledge-base.py | 492 ++++++++++++++++++ 17 files changed, 1775 insertions(+), 35 deletions(-) create mode 100644 understand-anything-plugin/agents/article-analyzer.md create mode 100644 understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx create mode 100644 understand-anything-plugin/skills/understand-knowledge/SKILL.md create mode 100644 understand-anything-plugin/skills/understand-knowledge/merge-knowledge-graph.py create mode 100644 understand-anything-plugin/skills/understand-knowledge/parse-knowledge-base.py diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28627d0..f6efbda 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -82,6 +82,9 @@ importers: '@xyflow/react': specifier: ^12.0.0 version: 12.10.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + d3-force: + specifier: ^3.0.0 + version: 3.0.0 devlop: specifier: ^1.1.0 version: 1.1.0 @@ -104,6 +107,9 @@ importers: '@tailwindcss/vite': specifier: ^4.0.0 version: 4.2.1(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) + '@types/d3-force': + specifier: ^3.0.10 + version: 3.0.10 '@types/react': specifier: ^19.0.0 version: 19.2.14 @@ -1011,6 +1017,9 @@ packages: '@types/d3-drag@3.0.7': resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} @@ -1317,10 +1326,18 @@ packages: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + d3-selection@3.0.0: resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} engines: {node: '>=12'} @@ -3345,6 +3362,8 @@ snapshots: dependencies: '@types/d3-selection': 3.0.11 + '@types/d3-force@3.0.10': {} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 @@ -3447,14 +3466,6 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3) - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3))': dependencies: '@vitest/spy': 3.2.4 @@ -3762,10 +3773,18 @@ snapshots: d3-ease@3.0.1: {} + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + d3-quadtree@3.0.1: {} + d3-selection@3.0.0: {} d3-timer@3.0.1: {} @@ -5297,7 +5316,7 @@ snapshots: dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(yaml@2.8.3)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 diff --git a/understand-anything-plugin/agents/article-analyzer.md b/understand-anything-plugin/agents/article-analyzer.md new file mode 100644 index 0000000..013a8dd --- /dev/null +++ b/understand-anything-plugin/agents/article-analyzer.md @@ -0,0 +1,93 @@ +--- +name: article-analyzer +description: | + Analyzes markdown files using pre-parsed structural data and LLM inference to extract knowledge graph nodes and edges (entities, claims, implicit relationships, topic clustering). +model: inherit +--- + +# Article Analyzer Agent + +You are a knowledge graph extraction expert. Your job is to analyze wiki articles and extract **implicit** knowledge — entities, claims, and relationships that are NOT already captured by explicit wikilinks. + +## Input + +You will receive a batch of articles as a JSON array. Each article has: +- `id`: the article node ID (e.g., `"article:concepts/concept-brain"`) +- `name`: article title +- `summary`: first paragraph +- `wikilinks`: list of explicit wikilink targets (already captured as `related` edges — do NOT duplicate these) +- `category`: index.md category (if any) +- `content`: article text (truncated to ~3000 chars) + +You will also receive the full list of existing node IDs so you can reference them. + +## Task + +For each article in the batch, extract: + +### 1. Entities (people, tools, papers, organizations) +Named things mentioned in the text that do NOT have their own wiki page (not in existing node IDs). Create `entity` nodes. + +- `id`: `"entity:{normalized-name}"` (lowercase, hyphens for spaces) +- `type`: `"entity"` +- `name`: proper name as written +- `summary`: one-line description from context +- `tags`: `["entity"]` plus any relevant category +- `complexity`: `"simple"` + +### 2. Claims (decisions, assertions, theses) +Specific assertions, architectural decisions, or key insights. Create `claim` nodes. + +- `id`: `"claim:{article-stem}:{short-slug}"` (e.g., `"claim:decision-typescript-python:ts-core-py-clones"`) +- `type`: `"claim"` +- `name`: short claim title +- `summary`: the assertion itself (1-2 sentences) +- `tags`: `["claim"]` plus category +- `complexity`: `"simple"` + +### 3. Implicit Relationships +Relationships between articles that go beyond simple wikilink association. Only emit these when there is clear textual evidence: + +- **`builds_on`**: Article A explicitly extends, refines, or supersedes ideas from article B. Weight: 0.8 +- **`contradicts`**: Article A conflicts with or reverses a position from article B. Weight: 0.9 +- **`exemplifies`**: An entity or article is a concrete example of a concept. Weight: 0.7 +- **`authored_by`**: Article attributed to a specific entity (person/agent). Weight: 0.6 +- **`cites`**: Article references a raw source document. Weight: 0.7 + +Edge format: +```json +{ + "source": "article:...", + "target": "article:... or entity:... or claim:... or source:...", + "type": "builds_on", + "direction": "forward", + "weight": 0.8, + "description": "Brief reason for this relationship" +} +``` + +## Rules + +1. **Do NOT duplicate wikilink edges.** The parse script already created `related` edges for every `[[wikilink]]`. Your job is to find what the wikilinks missed. +2. **Be conservative.** Only create edges with clear textual evidence. A vague thematic similarity is not enough. +3. **Deduplicate entities.** If the same person/tool appears in multiple articles, create the entity node once. +4. **Use existing IDs.** When creating edges to existing articles, use their exact `id` from the provided node list. +5. **Keep it small.** For a batch of 10-15 articles, expect ~5-15 entities, ~5-10 claims, and ~10-20 implicit edges. Don't over-extract. + +## Output Format + +Write a JSON file to `$INTERMEDIATE_DIR/analysis-batch-$BATCH_NUM.json`: + +```json +{ + "nodes": [ + { "id": "entity:...", "type": "entity", "name": "...", "summary": "...", "tags": [...], "complexity": "simple" }, + { "id": "claim:...", "type": "claim", "name": "...", "summary": "...", "tags": [...], "complexity": "simple" } + ], + "edges": [ + { "source": "...", "target": "...", "type": "builds_on", "direction": "forward", "weight": 0.8, "description": "..." } + ] +} +``` + +Do NOT include any article or topic nodes in your output — those already exist from the parse script. Only output NEW entity nodes, claim nodes, and implicit edges. diff --git a/understand-anything-plugin/packages/core/src/schema.ts b/understand-anything-plugin/packages/core/src/schema.ts index 88790a8..cc89e25 100644 --- a/understand-anything-plugin/packages/core/src/schema.ts +++ b/understand-anything-plugin/packages/core/src/schema.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -// Edge types (29 values across 7 categories) +// Edge types (35 values across 8 categories) export const EdgeTypeSchema = z.enum([ "imports", "exports", "contains", "inherits", "implements", // Structural "calls", "subscribes", "publishes", "middleware", // Behavioral @@ -10,6 +10,7 @@ export const EdgeTypeSchema = z.enum([ "deploys", "serves", "provisions", "triggers", // Infrastructure "migrates", "documents", "routes", "defines_schema", // Schema/Data "contains_flow", "flow_step", "cross_domain", // Domain + "cites", "contradicts", "builds_on", "exemplifies", "categorized_under", "authored_by", // Knowledge ]); // Aliases that LLMs commonly generate instead of canonical node types @@ -55,6 +56,22 @@ export const NODE_TYPE_ALIASES: Record = { business_process: "flow", task: "step", business_step: "step", + // Knowledge aliases + note: "article", + page: "article", + wiki_page: "article", + person: "entity", + actor: "entity", + organization: "entity", + tag: "topic", + category: "topic", + theme: "topic", + assertion: "claim", + decision: "claim", + thesis: "claim", + reference: "source", + raw: "source", + paper: "source", }; // Aliases that LLMs commonly generate instead of canonical edge types @@ -88,6 +105,20 @@ export const EDGE_TYPE_ALIASES: Record = { has_flow: "contains_flow", next_step: "flow_step", interacts_with: "cross_domain", + // Knowledge aliases + references: "cites", + cites_source: "cites", + conflicts_with: "contradicts", + disagrees_with: "contradicts", + refines: "builds_on", + elaborates: "builds_on", + illustrates: "exemplifies", + instance_of: "exemplifies", + example_of: "exemplifies", + belongs_to: "categorized_under", + tagged_with: "categorized_under", + written_by: "authored_by", + created_by: "authored_by", // Note: "implemented_by" is intentionally NOT aliased to "implements" — // it inverts edge direction (see commit fd0df15). The LLM should use // "implements" with correct source/target instead. @@ -327,6 +358,13 @@ const DomainMetaSchema = z.object({ entryType: z.enum(["http", "cli", "event", "cron", "manual"]).optional(), }).passthrough(); +const KnowledgeMetaSchema = z.object({ + wikilinks: z.array(z.string()).optional(), + backlinks: z.array(z.string()).optional(), + category: z.string().optional(), + content: z.string().optional(), +}).passthrough(); + export const GraphNodeSchema = z.object({ id: z.string(), type: z.enum([ @@ -334,6 +372,7 @@ export const GraphNodeSchema = z.object({ "config", "document", "service", "table", "endpoint", "pipeline", "schema", "resource", "domain", "flow", "step", + "article", "entity", "topic", "claim", "source", ]), name: z.string(), filePath: z.string().optional(), @@ -343,6 +382,7 @@ export const GraphNodeSchema = z.object({ complexity: z.enum(["simple", "moderate", "complex"]), languageNotes: z.string().optional(), domainMeta: DomainMetaSchema.optional(), + knowledgeMeta: KnowledgeMetaSchema.optional(), }).passthrough(); export const GraphEdgeSchema = z.object({ @@ -380,6 +420,7 @@ export const ProjectMetaSchema = z.object({ export const KnowledgeGraphSchema = z.object({ version: z.string(), + kind: z.enum(["codebase", "knowledge"]).optional(), project: ProjectMetaSchema, nodes: z.array(GraphNodeSchema), edges: z.array(GraphEdgeSchema), diff --git a/understand-anything-plugin/packages/core/src/types.ts b/understand-anything-plugin/packages/core/src/types.ts index 106f25e..1617926 100644 --- a/understand-anything-plugin/packages/core/src/types.ts +++ b/understand-anything-plugin/packages/core/src/types.ts @@ -1,11 +1,12 @@ -// Node types (16 total: 5 code + 8 non-code + 3 domain) +// Node types (21 total: 5 code + 8 non-code + 3 domain + 5 knowledge) export type NodeType = | "file" | "function" | "class" | "module" | "concept" | "config" | "document" | "service" | "table" | "endpoint" | "pipeline" | "schema" | "resource" - | "domain" | "flow" | "step"; + | "domain" | "flow" | "step" + | "article" | "entity" | "topic" | "claim" | "source"; -// Edge types (29 total in 7 categories: Structural, Behavioral, Data flow, Dependencies, Semantic, Infrastructure/Schema, Domain) +// Edge types (35 total in 8 categories: Structural, Behavioral, Data flow, Dependencies, Semantic, Infrastructure/Schema, Domain, Knowledge) export type EdgeType = | "imports" | "exports" | "contains" | "inherits" | "implements" // Structural | "calls" | "subscribes" | "publishes" | "middleware" // Behavioral @@ -14,7 +15,16 @@ export type EdgeType = | "related" | "similar_to" // Semantic | "deploys" | "serves" | "provisions" | "triggers" // Infrastructure | "migrates" | "documents" | "routes" | "defines_schema" // Schema/Data - | "contains_flow" | "flow_step" | "cross_domain"; // Domain + | "contains_flow" | "flow_step" | "cross_domain" // Domain + | "cites" | "contradicts" | "builds_on" | "exemplifies" | "categorized_under" | "authored_by"; // Knowledge + +// Optional knowledge metadata for article/entity/topic/claim/source nodes +export interface KnowledgeMeta { + wikilinks?: string[]; + backlinks?: string[]; + category?: string; + content?: string; +} // Optional domain metadata for domain/flow/step nodes export interface DomainMeta { @@ -25,7 +35,7 @@ export interface DomainMeta { entryType?: "http" | "cli" | "event" | "cron" | "manual"; } -// GraphNode with 16 types: 5 code + 8 non-code + 3 domain +// GraphNode with 21 types: 5 code + 8 non-code + 3 domain + 5 knowledge export interface GraphNode { id: string; type: NodeType; @@ -37,6 +47,7 @@ export interface GraphNode { complexity: "simple" | "moderate" | "complex"; languageNotes?: string; domainMeta?: DomainMeta; + knowledgeMeta?: KnowledgeMeta; } // GraphEdge with rich relationship modeling @@ -79,6 +90,7 @@ export interface ProjectMeta { // Root KnowledgeGraph export interface KnowledgeGraph { version: string; + kind?: "codebase" | "knowledge"; project: ProjectMeta; nodes: GraphNode[]; edges: GraphEdge[]; diff --git a/understand-anything-plugin/packages/dashboard/package.json b/understand-anything-plugin/packages/dashboard/package.json index b9bdbe4..2702091 100644 --- a/understand-anything-plugin/packages/dashboard/package.json +++ b/understand-anything-plugin/packages/dashboard/package.json @@ -13,15 +13,17 @@ "@dagrejs/dagre": "^2.0.4", "@understand-anything/core": "workspace:*", "@xyflow/react": "^12.0.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "d3-force": "^3.0.0", "devlop": "^1.1.0", "hast-util-to-jsx-runtime": "^2.3.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-markdown": "^10.1.0", "zustand": "^5.0.0" }, "devDependencies": { "@tailwindcss/vite": "^4.0.0", + "@types/d3-force": "^3.0.10", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.0", diff --git a/understand-anything-plugin/packages/dashboard/src/App.tsx b/understand-anything-plugin/packages/dashboard/src/App.tsx index c6e262e..71a2555 100644 --- a/understand-anything-plugin/packages/dashboard/src/App.tsx +++ b/understand-anything-plugin/packages/dashboard/src/App.tsx @@ -4,6 +4,7 @@ import type { GraphIssue } from "@understand-anything/core/schema"; import { useDashboardStore } from "./store"; import GraphView from "./components/GraphView"; import DomainGraphView from "./components/DomainGraphView"; +import KnowledgeGraphView from "./components/KnowledgeGraphView"; import CodeViewer from "./components/CodeViewer"; import SearchBar from "./components/SearchBar"; import NodeInfo from "./components/NodeInfo"; @@ -104,6 +105,7 @@ function Dashboard({ accessToken }: { accessToken: string }) { const [metaTheme, setMetaTheme] = useState(null); const viewMode = useDashboardStore((s) => s.viewMode); const setViewMode = useDashboardStore((s) => s.setViewMode); + const isKnowledgeGraph = useDashboardStore((s) => s.isKnowledgeGraph); const domainGraph = useDashboardStore((s) => s.domainGraph); const setDomainGraph = useDashboardStore((s) => s.setDomainGraph); @@ -240,6 +242,11 @@ function Dashboard({ accessToken }: { accessToken: string }) { if (result.success && result.data) { setGraph(result.data); setGraphIssues(result.issues); + // Auto-detect knowledge graph kind + if ((data as Record).kind === "knowledge") { + setViewMode("knowledge"); + useDashboardStore.getState().setIsKnowledgeGraph(true); + } for (const issue of result.issues) { if (issue.level === "auto-corrected") { console.warn(`[graph] auto-corrected: ${issue.message}`); @@ -331,7 +338,7 @@ function Dashboard({ accessToken }: { accessToken: string }) {

- {graph && domainGraph && ( + {graph && !isKnowledgeGraph && domainGraph && ( <>
@@ -369,14 +376,17 @@ function Dashboard({ accessToken }: { accessToken: string }) {
- {([ - { key: "code", label: "Code", color: "var(--color-node-file)" }, - { key: "config", label: "Config", color: "var(--color-node-config)" }, - { key: "docs", label: "Docs", color: "var(--color-node-document)" }, - { key: "infra", label: "Infra", color: "var(--color-node-service)" }, - { key: "data", label: "Data", color: "var(--color-node-table)" }, - { key: "domain", label: "Domain", color: "var(--color-node-concept)" }, - ] as const).map((cat) => ( + {(isKnowledgeGraph ? [ + { key: "knowledge" as const, label: "All", color: "var(--color-node-article)" }, + ] : [ + { key: "code" as const, label: "Code", color: "var(--color-node-file)" }, + { key: "config" as const, label: "Config", color: "var(--color-node-config)" }, + { key: "docs" as const, label: "Docs", color: "var(--color-node-document)" }, + { key: "infra" as const, label: "Infra", color: "var(--color-node-service)" }, + { key: "data" as const, label: "Data", color: "var(--color-node-table)" }, + { key: "domain" as const, label: "Domain", color: "var(--color-node-concept)" }, + { key: "knowledge" as const, label: "Knowledge", color: "var(--color-node-article)" }, + ]).map((cat) => ( +
+ )} + {meta?.wikilinks && meta.wikilinks.length > 0 && ( +
+

+ Wikilinks ({wikilinks.length}) +

+
+ {wikilinks.map((n) => ( + + ))} +
+
+ )} + {backlinks.length > 0 && ( +
+

+ Backlinks ({backlinks.length}) +

+
+ {backlinks.map((n) => ( + + ))} +
+
+ )} + {meta?.content && ( +
+

Preview

+
+ {meta.content.slice(0, 1500)} + {meta.content.length > 1500 && ( + ... (truncated) + )} +
+
+ )} +
+ ); +} + function DomainNodeDetails({ node, graph }: { node: GraphNode; graph: KnowledgeGraph }) { const navigateToDomain = useDashboardStore((s) => s.navigateToDomain); const selectNode = useDashboardStore((s) => s.selectNode); @@ -380,6 +482,11 @@ export default function NodeInfo() {
)} + {/* Knowledge-specific details */} + {activeGraph && node && (node.type === "article" || node.type === "entity" || node.type === "topic" || node.type === "claim" || node.type === "source") && ( + + )} + {/* Domain-specific details */} {activeGraph && node && (node.type === "domain" || node.type === "flow" || node.type === "step") && ( diff --git a/understand-anything-plugin/packages/dashboard/src/index.css b/understand-anything-plugin/packages/dashboard/src/index.css index 03690b6..b8b6841 100644 --- a/understand-anything-plugin/packages/dashboard/src/index.css +++ b/understand-anything-plugin/packages/dashboard/src/index.css @@ -36,6 +36,13 @@ --color-node-schema: #fcd34d; --color-node-resource: #a5b4fc; + /* Knowledge node types */ + --color-node-article: #d4a574; + --color-node-entity: #7ba4c9; + --color-node-topic: #c9b06c; + --color-node-claim: #6fb07a; + --color-node-source: #8a8a8a; + /* Diff */ --color-diff-changed: #e05252; --color-diff-affected: #d4a030; diff --git a/understand-anything-plugin/packages/dashboard/src/store.ts b/understand-anything-plugin/packages/dashboard/src/store.ts index 17bd31e..73ae557 100644 --- a/understand-anything-plugin/packages/dashboard/src/store.ts +++ b/understand-anything-plugin/packages/dashboard/src/store.ts @@ -9,10 +9,10 @@ import type { ReactFlowInstance } from "@xyflow/react"; export type Persona = "non-technical" | "junior" | "experienced"; export type NavigationLevel = "overview" | "layer-detail"; -export type NodeType = "file" | "function" | "class" | "module" | "concept" | "config" | "document" | "service" | "table" | "endpoint" | "pipeline" | "schema" | "resource" | "domain" | "flow" | "step"; +export type NodeType = "file" | "function" | "class" | "module" | "concept" | "config" | "document" | "service" | "table" | "endpoint" | "pipeline" | "schema" | "resource" | "domain" | "flow" | "step" | "article" | "entity" | "topic" | "claim" | "source"; export type Complexity = "simple" | "moderate" | "complex"; -export type EdgeCategory = "structural" | "behavioral" | "data-flow" | "dependencies" | "semantic" | "infrastructure" | "domain"; -export type ViewMode = "structural" | "domain"; +export type EdgeCategory = "structural" | "behavioral" | "data-flow" | "dependencies" | "semantic" | "infrastructure" | "domain" | "knowledge"; +export type ViewMode = "structural" | "domain" | "knowledge"; export interface FilterState { nodeTypes: Set; @@ -21,9 +21,9 @@ export interface FilterState { edgeCategories: Set; } -export const ALL_NODE_TYPES: NodeType[] = ["file", "function", "class", "module", "concept", "config", "document", "service", "table", "endpoint", "pipeline", "schema", "resource", "domain", "flow", "step"]; +export const ALL_NODE_TYPES: NodeType[] = ["file", "function", "class", "module", "concept", "config", "document", "service", "table", "endpoint", "pipeline", "schema", "resource", "domain", "flow", "step", "article", "entity", "topic", "claim", "source"]; export const ALL_COMPLEXITIES: Complexity[] = ["simple", "moderate", "complex"]; -export const ALL_EDGE_CATEGORIES: EdgeCategory[] = ["structural", "behavioral", "data-flow", "dependencies", "semantic", "infrastructure", "domain"]; +export const ALL_EDGE_CATEGORIES: EdgeCategory[] = ["structural", "behavioral", "data-flow", "dependencies", "semantic", "infrastructure", "domain", "knowledge"]; export const EDGE_CATEGORY_MAP: Record = { structural: ["imports", "exports", "contains", "inherits", "implements"], @@ -33,6 +33,7 @@ export const EDGE_CATEGORY_MAP: Record = { semantic: ["related", "similar_to"], infrastructure: ["deploys", "serves", "provisions", "triggers", "migrates", "documents", "routes", "defines_schema"], domain: ["contains_flow", "flow_step", "cross_domain"], + knowledge: ["cites", "contradicts", "builds_on", "exemplifies", "categorized_under", "authored_by"], }; export const DOMAIN_EDGE_TYPES = EDGE_CATEGORY_MAP.domain; @@ -45,7 +46,7 @@ const DEFAULT_FILTERS: FilterState = { }; /** Categories used for node type filter toggles. Single source of truth for NodeCategory. */ -export type NodeCategory = "code" | "config" | "docs" | "infra" | "data" | "domain"; +export type NodeCategory = "code" | "config" | "docs" | "infra" | "data" | "domain" | "knowledge"; /** Find which layer a node belongs to. Returns layerId or null. */ function findNodeLayer(graph: KnowledgeGraph, nodeId: string): string | null { @@ -133,13 +134,15 @@ interface DashboardStore { nextTourStep: () => void; prevTourStep: () => void; - // Domain view + // View mode viewMode: ViewMode; + isKnowledgeGraph: boolean; domainGraph: KnowledgeGraph | null; activeDomainId: string | null; setDomainGraph: (graph: KnowledgeGraph) => void; setViewMode: (mode: ViewMode) => void; + setIsKnowledgeGraph: (value: boolean) => void; navigateToDomain: (domainId: string) => void; clearActiveDomain: () => void; } @@ -197,7 +200,7 @@ export const useDashboardStore = create()((set, get) => ({ pathFinderOpen: false, reactFlowInstance: null, - nodeTypeFilters: { code: true, config: true, docs: true, infra: true, data: true, domain: true }, + nodeTypeFilters: { code: true, config: true, docs: true, infra: true, data: true, domain: true, knowledge: true }, toggleNodeTypeFilter: (category) => set((state) => ({ @@ -464,6 +467,7 @@ export const useDashboardStore = create()((set, get) => ({ }, viewMode: "structural", + isKnowledgeGraph: false, domainGraph: null, activeDomainId: null, @@ -471,6 +475,10 @@ export const useDashboardStore = create()((set, get) => ({ set({ domainGraph: graph }); }, + setIsKnowledgeGraph: (value) => { + set({ isKnowledgeGraph: value }); + }, + setViewMode: (mode) => { set({ viewMode: mode, diff --git a/understand-anything-plugin/packages/dashboard/src/utils/layout.ts b/understand-anything-plugin/packages/dashboard/src/utils/layout.ts index 2d7c50d..f418b06 100644 --- a/understand-anything-plugin/packages/dashboard/src/utils/layout.ts +++ b/understand-anything-plugin/packages/dashboard/src/utils/layout.ts @@ -1,4 +1,14 @@ import dagre from "@dagrejs/dagre"; +import { + forceSimulation, + forceLink, + forceManyBody, + forceCenter, + forceCollide, + forceX, + forceY, +} from "d3-force"; +import type { SimulationNodeDatum, SimulationLinkDatum } from "d3-force"; import type { Node, Edge } from "@xyflow/react"; export const NODE_WIDTH = 280; @@ -62,4 +72,114 @@ export function applyDagreLayout( return { nodes: layoutedNodes, edges }; } +// --------------------------------------------------------------------------- +// Force-directed layout (for knowledge graphs) +// --------------------------------------------------------------------------- + +interface ForceNode extends SimulationNodeDatum { + id: string; + community?: number; +} + +/** + * Force-directed layout using d3-force — used for knowledge graphs. + * Optionally groups nodes by community (layer/category). + */ +export function applyForceLayout( + nodes: Node[], + edges: Edge[], + nodeDimensions?: Map, + communityMap?: Map, +): { nodes: Node[]; edges: Edge[] } { + if (nodes.length === 0) return { nodes, edges }; + + // Build simulation nodes with optional community assignment + const simNodes: ForceNode[] = nodes.map((n) => ({ + id: n.id, + x: Math.random() * 800 - 400, + y: Math.random() * 800 - 400, + community: communityMap?.get(n.id), + })); + + const nodeIdSet = new Set(simNodes.map((n) => n.id)); + const simLinks: SimulationLinkDatum[] = edges + .filter((e) => nodeIdSet.has(e.source as string) && nodeIdSet.has(e.target as string)) + .map((e) => ({ + source: e.source as string, + target: e.target as string, + })); + + // Compute community centers for cluster attraction + const communityCount = communityMap + ? Math.max(1, new Set(communityMap.values()).size) + : 1; + const communityAngle = (i: number) => (2 * Math.PI * i) / communityCount; + // Scale cluster radius with node count for better spread + const clusterRadius = Math.max(600, nodes.length * 5); + + // Scale forces based on graph size + const isLarge = nodes.length > 100; + const chargeStrength = isLarge ? -600 : -350; + const linkDistance = isLarge ? 250 : 150; + + const sim = forceSimulation(simNodes) + .force( + "link", + forceLink>(simLinks) + .id((d) => d.id) + .distance(linkDistance) + .strength(0.2), + ) + .force("charge", forceManyBody().strength(chargeStrength).distanceMax(1500)) + .force("center", forceCenter(0, 0).strength(0.03)) + .force( + "collide", + forceCollide().radius((d) => { + const dims = nodeDimensions?.get(d.id); + return Math.max(20, ((dims?.width ?? NODE_WIDTH) + 40) / 2); + }).strength(0.8), + ); + + // Add community clustering force if communities are provided + if (communityMap && communityCount > 1) { + sim.force( + "clusterX", + forceX((d) => { + const c = d.community ?? 0; + return Math.cos(communityAngle(c)) * clusterRadius; + }).strength(0.3), + ); + sim.force( + "clusterY", + forceY((d) => { + const c = d.community ?? 0; + return Math.sin(communityAngle(c)) * clusterRadius; + }).strength(0.3), + ); + } + + // Run to convergence synchronously + const ticks = Math.min(300, Math.max(100, nodes.length)); + sim.tick(ticks); + sim.stop(); + + // Map positions back to xyflow nodes + const posMap = new Map(simNodes.map((n) => [n.id, { x: n.x ?? 0, y: n.y ?? 0 }])); + const layoutedNodes = nodes.map((node) => { + const pos = posMap.get(node.id) ?? { x: 0, y: 0 }; + const dims = nodeDimensions?.get(node.id); + const w = dims?.width ?? NODE_WIDTH; + const h = dims?.height ?? NODE_HEIGHT; + return { + ...node, + position: { + x: pos.x - w / 2, + y: pos.y - h / 2, + }, + }; + }); + + return { nodes: layoutedNodes, edges }; +} + diff --git a/understand-anything-plugin/pnpm-lock.yaml b/understand-anything-plugin/pnpm-lock.yaml index 0134903..9f2efe4 100644 --- a/understand-anything-plugin/pnpm-lock.yaml +++ b/understand-anything-plugin/pnpm-lock.yaml @@ -67,6 +67,9 @@ importers: '@xyflow/react': specifier: ^12.0.0 version: 12.10.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + d3-force: + specifier: ^3.0.0 + version: 3.0.0 devlop: specifier: ^1.1.0 version: 1.1.0 @@ -89,6 +92,9 @@ importers: '@tailwindcss/vite': specifier: ^4.0.0 version: 4.2.2(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) + '@types/d3-force': + specifier: ^3.0.10 + version: 3.0.10 '@types/react': specifier: ^19.0.0 version: 19.2.14 @@ -630,6 +636,9 @@ packages: '@types/d3-drag@3.0.7': resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} @@ -858,10 +867,18 @@ packages: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + d3-selection@3.0.0: resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} engines: {node: '>=12'} @@ -2078,6 +2095,8 @@ snapshots: dependencies: '@types/d3-selection': 3.0.11 + '@types/d3-force@3.0.10': {} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 @@ -2330,10 +2349,18 @@ snapshots: d3-ease@3.0.1: {} + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + d3-quadtree@3.0.1: {} + d3-selection@3.0.0: {} d3-timer@3.0.1: {} diff --git a/understand-anything-plugin/skills/understand-knowledge/SKILL.md b/understand-anything-plugin/skills/understand-knowledge/SKILL.md new file mode 100644 index 0000000..74f6136 --- /dev/null +++ b/understand-anything-plugin/skills/understand-knowledge/SKILL.md @@ -0,0 +1,132 @@ +--- +name: understand-knowledge +description: Analyze a Karpathy-pattern LLM wiki knowledge base and generate an interactive knowledge graph with entity extraction, implicit relationships, and topic clustering. +argument-hint: [wiki-directory] +--- + +# /understand-knowledge + +Analyzes a Karpathy-pattern LLM wiki — a three-layer knowledge base with raw sources, wiki markdown, and a schema file — and produces an interactive knowledge graph dashboard. + +## What It Detects + +The **Karpathy LLM wiki pattern** (see https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f): +- **Raw sources** — immutable source documents (articles, papers, data files) +- **Wiki** — LLM-generated markdown files with wikilinks (`[[target]]` syntax) +- **Schema** — CLAUDE.md, AGENTS.md, or similar configuration file +- **index.md** — content catalog organized by categories +- **log.md** — chronological operation log + +Detection signals: has `index.md` + multiple `.md` files with wikilinks. May have `raw/` directory and schema file. + +## Instructions + +### Phase 1: DETECT + +1. Determine the target directory: + - If the user provided a path argument, use that + - Otherwise, use the current working directory + +2. Run the format detection script bundled with this skill: + ``` + python3 /parse-knowledge-base.py + ``` + - If the script exits with an error, tell the user this doesn't appear to be a Karpathy-pattern wiki and explain what was expected + - If successful, proceed. The script writes `scan-manifest.json` to `/.understand-anything/intermediate/` + +3. Read the scan-manifest.json and announce the results: + - "Detected Karpathy wiki: N articles, N sources, N topics, N wikilinks (N unresolved)" + - List the categories found from index.md + +### Phase 2: SCAN (already done) + +The parse script in Phase 1 already performed the deterministic scan. The scan-manifest.json contains: +- Article nodes (one per wiki .md file) with extracted wikilinks, headings, frontmatter +- Source nodes (one per raw/ file) +- Topic nodes (from index.md section headings) +- `related` edges (from wikilinks) +- `categorized_under` edges (from index.md sections) + +No additional scanning is needed. Proceed to Phase 3. + +### Phase 3: ANALYZE + +Dispatch `article-analyzer` subagents to extract implicit knowledge: + +1. Read the scan-manifest.json to get the article list + +2. Prepare batches of 10-15 articles each, grouped by category when possible (articles in the same category are more likely to have implicit cross-references) + +3. For each batch, dispatch an `article-analyzer` subagent with: + - The batch of articles (id, name, summary, wikilinks, category, content from knowledgeMeta) + - The full list of existing node IDs (so the agent can reference them) + - The batch number for output file naming + - The intermediate directory path: `$INTERMEDIATE_DIR = /.understand-anything/intermediate` + + The agent will write `analysis-batch-{N}.json` to the intermediate directory. + +4. Run up to 3 batches concurrently. Wait for all batches to complete. + +5. If any batch fails, log a warning but continue — the scan-manifest provides a solid base graph even without LLM analysis. + +### Phase 4: MERGE + +1. Run the merge script bundled with this skill: + ``` + python3 /merge-knowledge-graph.py + ``` + +2. The script: + - Combines scan-manifest.json + all analysis-batch-*.json files + - Deduplicates entities (case-insensitive name matching) + - Normalizes node/edge types via alias maps + - Builds layers from index.md categories + - Builds a tour from index.md section ordering + - Writes `assembled-graph.json` to the intermediate directory + +3. Read the merge report from stderr and announce: + - Total nodes, edges, layers, tour steps + - How many entities/claims the LLM analysis added + +### Phase 5: SAVE + +1. Read the assembled-graph.json + +2. Run basic validation: + - Every edge source/target must reference an existing node + - Every node must have: id, type, name, summary, tags, complexity + - Remove any edges with dangling references + +3. Copy the validated graph to `/.understand-anything/knowledge-graph.json` + +4. Write metadata to `/.understand-anything/meta.json`: + ```json + { + "lastAnalyzedAt": "", + "gitCommitHash": "", + "version": "1.0.0", + "analyzedFiles": + } + ``` + +5. Clean up intermediate files: + ``` + rm -rf /.understand-anything/intermediate + ``` + +6. Report summary to the user: + - "Knowledge graph saved: N articles, N entities, N topics, N claims, N sources" + - "N edges (N wikilink, N categorized, N implicit)" + - "N layers, N tour steps" + +7. Auto-trigger the dashboard: + ``` + /understand-dashboard + ``` + +## Notes + +- The parse script handles ALL deterministic extraction (wikilinks, headings, frontmatter, categories from index.md). The LLM agents only add implicit knowledge that requires inference. +- Categories and taxonomy come from index.md section headings, NOT from filename prefixes. The Karpathy spec is intentionally abstract about naming conventions. +- The graph uses `kind: "knowledge"` to signal the dashboard to use force-directed layout instead of hierarchical dagre. +- Source nodes from raw/ are lightweight (filename + size only) — we don't parse PDFs or binary files. diff --git a/understand-anything-plugin/skills/understand-knowledge/merge-knowledge-graph.py b/understand-anything-plugin/skills/understand-knowledge/merge-knowledge-graph.py new file mode 100644 index 0000000..1ef224e --- /dev/null +++ b/understand-anything-plugin/skills/understand-knowledge/merge-knowledge-graph.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +""" +Merge script for Karpathy-pattern knowledge graphs. + +Combines the deterministic scan-manifest.json with LLM analysis batches +(analysis-batch-*.json) into a final assembled knowledge graph. + +Handles: entity deduplication, edge normalization, layer building from +index.md categories, tour generation from index.md section ordering. + +Usage: + python merge-knowledge-graph.py + +Output: + Writes assembled-graph.json to /.understand-anything/intermediate/ +""" + +import json +import os +import re +import sys +from datetime import datetime, timezone +from pathlib import Path + +# --------------------------------------------------------------------------- +# Canonical type sets (must match core/src/types.ts) +# --------------------------------------------------------------------------- + +VALID_NODE_TYPES = { + "article", "entity", "topic", "claim", "source", + # Codebase types (for cross-compatibility) + "file", "function", "class", "module", "concept", + "config", "document", "service", "table", "endpoint", + "pipeline", "schema", "resource", "domain", "flow", "step", +} + +VALID_EDGE_TYPES = { + "cites", "contradicts", "builds_on", "exemplifies", + "categorized_under", "authored_by", "related", "similar_to", + # Codebase types + "imports", "exports", "contains", "inherits", "implements", + "calls", "subscribes", "publishes", "middleware", + "reads_from", "writes_to", "transforms", "validates", + "depends_on", "tested_by", "configures", + "deploys", "serves", "provisions", "triggers", + "migrates", "documents", "routes", "defines_schema", + "contains_flow", "flow_step", "cross_domain", +} + +NODE_TYPE_ALIASES = { + "note": "article", "page": "article", "wiki_page": "article", + "person": "entity", "actor": "entity", "organization": "entity", + "tag": "topic", "category": "topic", "theme": "topic", + "assertion": "claim", "decision": "claim", "thesis": "claim", + "reference": "source", "raw": "source", "paper": "source", +} + +EDGE_TYPE_ALIASES = { + "references": "cites", "cites_source": "cites", + "conflicts_with": "contradicts", "disagrees_with": "contradicts", + "refines": "builds_on", "elaborates": "builds_on", + "illustrates": "exemplifies", "instance_of": "exemplifies", "example_of": "exemplifies", + "belongs_to": "categorized_under", "tagged_with": "categorized_under", + "written_by": "authored_by", "created_by": "authored_by", + "relates_to": "related", "related_to": "related", +} + + +# --------------------------------------------------------------------------- +# Normalization +# --------------------------------------------------------------------------- + +def normalize_node_type(t: str) -> str: + t = t.lower().strip() + return NODE_TYPE_ALIASES.get(t, t) + + +def normalize_edge_type(t: str) -> str: + t = t.lower().strip() + return EDGE_TYPE_ALIASES.get(t, t) + + +def normalize_entity_name(name: str) -> str: + """Normalize entity names for deduplication.""" + return re.sub(r'\s+', ' ', name.strip().lower()) + + +# --------------------------------------------------------------------------- +# Merge pipeline +# --------------------------------------------------------------------------- + +def merge(root: Path) -> dict: + intermediate = root / ".understand-anything" / "intermediate" + manifest_path = intermediate / "scan-manifest.json" + + if not manifest_path.is_file(): + print(f"Error: {manifest_path} not found. Run parse-knowledge-base.py first.", + file=sys.stderr) + sys.exit(1) + + # Load scan manifest (deterministic base) + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + nodes = {n["id"]: n for n in manifest["nodes"]} + edges = list(manifest["edges"]) + + report = {"base_nodes": len(nodes), "base_edges": len(edges), + "batches": 0, "new_entities": 0, "new_claims": 0, + "new_edges": 0, "deduped_entities": 0, "dropped_edges": 0} + + # Load analysis batches + batch_files = sorted(intermediate.glob("analysis-batch-*.json")) + entity_name_map: dict[str, str] = {} # normalized_name → entity_id + dedup_remap: dict[str, str] = {} # duplicate_id → canonical_id + + for bf in batch_files: + report["batches"] += 1 + try: + batch = json.loads(bf.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError) as e: + print(f"[merge] Warning: Failed to load {bf.name}: {e}", file=sys.stderr) + continue + + # Process new nodes from LLM analysis + for node in batch.get("nodes", []): + node_type = normalize_node_type(node.get("type", "")) + if node_type not in VALID_NODE_TYPES: + print(f"[merge] Warning: Unknown node type '{node.get('type')}' — skipping", + file=sys.stderr) + continue + + node["type"] = node_type + node_id = node.get("id", "") + + # Entity deduplication — track remapping for edge fixup + if node_type == "entity": + norm_name = normalize_entity_name(node.get("name", "")) + if norm_name in entity_name_map: + # Map duplicate ID → canonical ID for edge remapping + dedup_remap[node_id] = entity_name_map[norm_name] + report["deduped_entities"] += 1 + continue + entity_name_map[norm_name] = node_id + report["new_entities"] += 1 + elif node_type == "claim": + report["new_claims"] += 1 + + # Ensure required fields + node.setdefault("summary", node.get("name", "")) + node.setdefault("tags", []) + node.setdefault("complexity", "simple") + + nodes[node_id] = node + + # Process new edges from LLM analysis + for edge in batch.get("edges", []): + edge_type = normalize_edge_type(edge.get("type", "")) + if edge_type not in VALID_EDGE_TYPES: + print(f"[merge] Warning: Unknown edge type '{edge.get('type')}' — " + f"mapped to 'related'", file=sys.stderr) + edge_type = "related" + + edge["type"] = edge_type + edge.setdefault("direction", "forward") + edge.setdefault("weight", 0.5) + + # Remap deduped entity IDs, then validate source/target exist + src = dedup_remap.get(edge.get("source", ""), edge.get("source", "")) + tgt = dedup_remap.get(edge.get("target", ""), edge.get("target", "")) + edge["source"] = src + edge["target"] = tgt + if src in nodes and tgt in nodes: + edges.append(edge) + report["new_edges"] += 1 + else: + report["dropped_edges"] += 1 + + # --- Deduplicate edges --- + seen: set[tuple[str, str, str]] = set() + final_edges = [] + for edge in edges: + key = (edge["source"], edge["target"], edge["type"]) + if key not in seen: + seen.add(key) + final_edges.append(edge) + + # --- Build article→layer map from categories --- + categories = manifest.get("categories", []) + article_layer_map: dict[str, str] = {} # article_id → layer_id + layer_members: dict[str, list[str]] = {} # layer_id → [node_ids] + + for cat in categories: + cat_name = cat["name"] + cat_slug = cat_name.lower().replace(" ", "-") + layer_id = f"layer:{cat_slug}" + topic_id = f"topic:{cat_slug}" + members = [e["source"] for e in final_edges + if e["type"] == "categorized_under" and e["target"] == topic_id] + if topic_id in nodes: + members.append(topic_id) + layer_members[layer_id] = members + for mid in members: + article_layer_map[mid] = layer_id + + # --- Assign entity/claim nodes to their parent article's layer --- + # Step 1: Build entity/claim → article mapping from edges + child_to_article: dict[str, str] = {} + for edge in final_edges: + src_type = nodes.get(edge["source"], {}).get("type", "") + tgt_type = nodes.get(edge["target"], {}).get("type", "") + # If an article connects to an entity/claim, map the child to the article + if src_type == "article" and tgt_type in ("entity", "claim"): + child_to_article.setdefault(edge["target"], edge["source"]) + elif tgt_type == "article" and src_type in ("entity", "claim"): + child_to_article.setdefault(edge["source"], edge["target"]) + + # Step 2: For orphan entities/claims, try to match by ID prefix + # Build a reverse lookup: bare article name → full article ID + # e.g., "concept-aaak-compression" → "article:concepts/concept-aaak-compression" + bare_to_article: dict[str, str] = {} + for nid in nodes: + if nid.startswith("article:"): + # Extract the bare filename from paths like "article:concepts/concept-foo" + bare = nid.split("/")[-1] if "/" in nid else nid.replace("article:", "") + bare_to_article[bare] = nid + + for nid, node in nodes.items(): + if node["type"] in ("entity", "claim") and nid not in child_to_article: + # e.g., "claim:concept-aaak-compression:not-zero-loss" → stem "concept-aaak-compression" + # e.g., "entity:brain" → stem "brain" + raw = nid.split(":", 1)[1] if ":" in nid else nid # "concept-aaak-compression:not-zero-loss" + stem = raw.split(":")[0] # "concept-aaak-compression" + + # Try exact bare name match first + if stem in bare_to_article: + child_to_article[nid] = bare_to_article[stem] + else: + # Try suffix/substring match against bare names + # e.g., entity:brain → segment-brain, entity:mempalace → tool-mempalace + matched = False + for bare, aid in bare_to_article.items(): + if stem in bare or bare in stem: + child_to_article[nid] = aid + matched = True + break + # Also try: bare ends with -stem (e.g., "segment-brain" ends with "-brain") + if bare.endswith(f"-{stem}") or bare.endswith(f"/{stem}"): + child_to_article[nid] = aid + matched = True + break + # Last resort: check if the node's name appears in any article's + # name OR content (knowledgeMeta.content) + if not matched and node.get("name"): + node_name_lower = node["name"].lower() + for aid, anode in nodes.items(): + if not aid.startswith("article:"): + continue + # Match against article name + if node_name_lower in anode.get("name", "").lower(): + child_to_article[nid] = aid + matched = True + break + # Match against article content (wikilinks or text) + meta = anode.get("knowledgeMeta", {}) + content = (meta.get("content") or "").lower() + if len(node_name_lower) >= 3 and node_name_lower in content: + child_to_article[nid] = aid + matched = True + break + + # Step 3: Place children into their parent article's layer + for child_id, article_id in child_to_article.items(): + layer_id = article_layer_map.get(article_id) + if layer_id and layer_id in layer_members: + layer_members[layer_id].append(child_id) + article_layer_map[child_id] = layer_id + + # --- Build layers --- + layers = [] + for cat in categories: + cat_name = cat["name"] + cat_slug = cat_name.lower().replace(" ", "-") + layer_id = f"layer:{cat_slug}" + members = list(dict.fromkeys(layer_members.get(layer_id, []))) # Deduplicate preserving order + layers.append({ + "id": layer_id, + "name": cat_name, + "description": f"{cat_name} ({len(members)} nodes)", + "nodeIds": members, + }) + + # Assign uncategorized nodes to an "Other" layer + categorized_ids = set() + for layer in layers: + categorized_ids.update(layer["nodeIds"]) + uncategorized = [nid for nid in nodes if nid not in categorized_ids] + if uncategorized: + layers.append({ + "id": "layer:other", + "name": "Other", + "description": f"Uncategorized nodes ({len(uncategorized)})", + "nodeIds": uncategorized, + }) + + # --- Build tour from index.md category ordering --- + tour = [] + for i, cat in enumerate(categories): + cat_slug = cat["name"].lower().replace(" ", "-") + topic_id = f"topic:{cat_slug}" + # Pick representative articles (up to 3 per category) + members = [e["source"] for e in final_edges + if e["type"] == "categorized_under" and e["target"] == topic_id][:3] + if not members and topic_id in nodes: + members = [topic_id] + if members: + tour.append({ + "order": i + 1, + "title": cat["name"], + "description": f"Explore the {cat['name']} section ({cat['count']} articles)", + "nodeIds": members, + }) + + # --- Detect project name --- + project_name = root.name + # Try to find a better name from index.md H1 + index_path = root / "wiki" / "index.md" + if not index_path.is_file(): + index_path = root / "index.md" + if index_path.is_file(): + text = index_path.read_text(encoding="utf-8", errors="replace") + h1_match = re.search(r"^#\s+(.+)$", text, re.MULTILINE) + if h1_match: + project_name = h1_match.group(1).strip() + + # --- Assemble final graph --- + graph = { + "version": "1.0.0", + "kind": "knowledge", + "project": { + "name": project_name, + "languages": ["markdown"], + "frameworks": ["karpathy-wiki"], + "description": f"Knowledge graph for {project_name}", + "analyzedAt": datetime.now(timezone.utc).isoformat(), + "gitCommitHash": "", + }, + "nodes": list(nodes.values()), + "edges": final_edges, + "layers": layers, + "tour": tour, + } + + # Try to get git commit hash + try: + import subprocess + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + capture_output=True, text=True, cwd=str(root), timeout=5 + ) + if result.returncode == 0: + graph["project"]["gitCommitHash"] = result.stdout.strip() + except (OSError, subprocess.TimeoutExpired): + pass + + # Write output + out_path = intermediate / "assembled-graph.json" + out_path.write_text(json.dumps(graph, indent=2), encoding="utf-8") + + # Report + print(f"[merge] Input: {report['base_nodes']} scan nodes, " + f"{report['base_edges']} scan edges, {report['batches']} analysis batches", + file=sys.stderr) + print(f"[merge] Added: {report['new_entities']} entities, " + f"{report['new_claims']} claims, {report['new_edges']} edges " + f"({report['deduped_entities']} deduped entities, " + f"{report['dropped_edges']} dropped dangling edges)", file=sys.stderr) + print(f"[merge] Output: {len(graph['nodes'])} nodes, {len(final_edges)} edges, " + f"{len(layers)} layers, {len(tour)} tour steps", file=sys.stderr) + print(f"[merge] Written: {out_path}", file=sys.stderr) + + return graph + + +def main(): + if len(sys.argv) < 2: + print("Usage: merge-knowledge-graph.py ", file=sys.stderr) + sys.exit(1) + + root = Path(sys.argv[1]).resolve() + if not root.is_dir(): + print(f"Error: {root} is not a directory", file=sys.stderr) + sys.exit(1) + + merge(root) + + +if __name__ == "__main__": + main() diff --git a/understand-anything-plugin/skills/understand-knowledge/parse-knowledge-base.py b/understand-anything-plugin/skills/understand-knowledge/parse-knowledge-base.py new file mode 100644 index 0000000..10c6b2e --- /dev/null +++ b/understand-anything-plugin/skills/understand-knowledge/parse-knowledge-base.py @@ -0,0 +1,492 @@ +#!/usr/bin/env python3 +""" +Deterministic parser for Karpathy-pattern LLM wikis. + +Detects the three-layer pattern (raw sources + wiki markdown + schema), +extracts structure from markdown files, resolves wikilinks, and derives +categories from index.md section headings. + +Usage: + python parse-knowledge-base.py + +Output: + Writes scan-manifest.json to /.understand-anything/intermediate/ +""" + +import json +import os +import re +import sys +from pathlib import Path + +# --------------------------------------------------------------------------- +# Regex patterns +# --------------------------------------------------------------------------- +WIKILINK_RE = re.compile(r"\[\[([^\]|]+)(?:\|([^\]]+))?\]\]") +FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL) +CODE_BLOCK_RE = re.compile(r"```(\w*)") +HEADING_RE = re.compile(r"^(#{1,6})\s+(.+)$", re.MULTILINE) +INDEX_SECTION_RE = re.compile(r"^##\s+(.+)$", re.MULTILINE) + +# Files that are part of wiki infrastructure, not content articles +INFRA_FILES = {"index.md", "log.md", "claude.md", "agents.md", "soul.md"} + +# --------------------------------------------------------------------------- +# Detection: is this a Karpathy-pattern wiki? +# --------------------------------------------------------------------------- + +def detect_format(root: Path) -> dict: + """Detect if directory follows the Karpathy LLM wiki three-layer pattern.""" + signals = { + "has_index": (root / "index.md").is_file() or (root / "wiki" / "index.md").is_file(), + "has_log": (root / "log.md").is_file() or (root / "wiki" / "log.md").is_file(), + "has_raw": (root / "raw").is_dir(), + "has_schema": any( + (root / f).is_file() or (root / "wiki" / f).is_file() + for f in ["CLAUDE.md", "AGENTS.md"] + ), + } + + # Find the wiki root — could be the directory itself or a wiki/ subdirectory + if (root / "wiki").is_dir(): + wiki_root = root / "wiki" + else: + wiki_root = root + + # Count markdown files in the wiki root + md_files = list(wiki_root.rglob("*.md")) + signals["md_count"] = len(md_files) + signals["wiki_root"] = str(wiki_root) + + # Primary signal: has index.md + meaningful number of markdown files + if signals["has_index"] and signals["md_count"] >= 3: + signals["detected"] = True + signals["format"] = "karpathy" + else: + signals["detected"] = False + signals["format"] = "unknown" + + return signals + + +# --------------------------------------------------------------------------- +# Markdown extraction helpers +# --------------------------------------------------------------------------- + +def extract_frontmatter(text: str) -> dict: + """Extract YAML frontmatter as a simple key-value dict.""" + m = FRONTMATTER_RE.match(text) + if not m: + return {} + fm = {} + for line in m.group(1).split("\n"): + if ":" in line: + key, _, val = line.partition(":") + fm[key.strip()] = val.strip().strip('"').strip("'") + return fm + + +def extract_wikilinks(text: str) -> list[dict]: + """Extract all [[target]] and [[target|display]] wikilinks.""" + links = [] + for m in WIKILINK_RE.finditer(text): + links.append({ + "target": m.group(1).strip(), + "display": m.group(2).strip() if m.group(2) else None, + }) + return links + + +def extract_headings(text: str) -> list[dict]: + """Extract all markdown headings with level and text.""" + return [ + {"level": len(m.group(1)), "text": m.group(2).strip()} + for m in HEADING_RE.finditer(text) + ] + + +def extract_code_blocks(text: str) -> list[str]: + """Extract languages from fenced code blocks.""" + return [m.group(1) for m in CODE_BLOCK_RE.finditer(text) if m.group(1)] + + +def extract_first_paragraph(text: str) -> str: + """Extract the first non-empty paragraph after frontmatter and H1.""" + # Strip frontmatter + stripped = FRONTMATTER_RE.sub("", text).strip() + if not stripped: + return "" + lines = stripped.split("\n") + + def _collect_paragraph(start_lines: list[str]) -> str: + """Collect the first paragraph from the given lines.""" + para: list[str] = [] + for s_raw in start_lines: + s = s_raw.strip() + if not s and not para: + continue # Skip leading blank lines + if not s and para: + break # End of paragraph + if s.startswith(">"): + continue # Skip blockquotes + if re.match(r"^[-*_]{3,}\s*$", s): + continue # Skip horizontal rules + if s.startswith("#"): + if para: + break # End paragraph at next heading + continue # Skip headings before paragraph + para.append(s) + return " ".join(para) + + # Try: find first paragraph after H1 + for i, line in enumerate(lines): + if line.strip().startswith("# "): + result = _collect_paragraph(lines[i + 1:]) + if result: + if len(result) > 200: + return result[:197] + "..." + return result + + # Fallback: no H1 found, take first paragraph from start + result = _collect_paragraph(lines) + if len(result) > 200: + result = result[:197] + "..." + return result or "" + + +def extract_h1(text: str) -> str: + """Extract the first H1 heading.""" + for m in HEADING_RE.finditer(text): + if len(m.group(1)) == 1: + # Strip trailing wiki-style decorations like " — subtitle" + return m.group(2).strip() + return "" + + +# --------------------------------------------------------------------------- +# Index.md parsing — categories come from section headings +# --------------------------------------------------------------------------- + +def parse_index(index_path: Path) -> list[dict]: + """Parse index.md to extract categories from ## headings and their wikilinks.""" + if not index_path.is_file(): + return [] + text = index_path.read_text(encoding="utf-8", errors="replace") + categories = [] + current_category = None + + for line in text.split("\n"): + # Detect ## section heading + sec_match = re.match(r"^##\s+(.+)$", line) + if sec_match: + current_category = { + "name": sec_match.group(1).strip(), + "articles": [], + } + categories.append(current_category) + continue + + # Collect wikilinks under current section + if current_category: + for wl in WIKILINK_RE.finditer(line): + current_category["articles"].append(wl.group(1).strip()) + + return categories + + +# --------------------------------------------------------------------------- +# Log.md parsing — extract operation timeline +# --------------------------------------------------------------------------- + +def parse_log(log_path: Path) -> list[dict]: + """Parse log.md to extract chronological entries.""" + if not log_path.is_file(): + return [] + text = log_path.read_text(encoding="utf-8", errors="replace") + entries = [] + log_entry_re = re.compile( + r"^##\s+\[(\d{4}-\d{2}-\d{2})\]\s+(\w+)\s*\|\s*(.+)$", re.MULTILINE + ) + for m in log_entry_re.finditer(text): + entries.append({ + "date": m.group(1), + "operation": m.group(2), + "title": m.group(3).strip(), + }) + return entries + + +# --------------------------------------------------------------------------- +# Main pipeline +# --------------------------------------------------------------------------- + +def build_name_to_stem_map(wiki_root: Path) -> dict[str, str]: + """Build a case-insensitive map from filename stem to relative stem path.""" + name_map: dict[str, str] = {} + for md_file in wiki_root.rglob("*.md"): + rel = md_file.relative_to(wiki_root) + stem = str(rel.with_suffix("")) # e.g., "decisions/decision-foo" + basename = md_file.stem # e.g., "decision-foo" + # Map both full relative path and bare filename (for flat wikilink resolution) + name_map[stem.lower()] = stem + name_map[basename.lower()] = stem + return name_map + + +def resolve_wikilink(target: str, name_map: dict[str, str], node_ids: set[str] | None = None) -> str | None: + """Resolve a wikilink target to an article node ID. + + If node_ids is provided, only resolve to IDs that exist in the set. + """ + key = target.lower().strip() + # Skip targets that are clearly not page names (shell flags, etc.) + if key.startswith("-"): + return None + stem = name_map.get(key) + if stem: + candidate = f"article:{stem}" + # If we have a node set, verify the target exists + if node_ids is not None and candidate not in node_ids: + return None + return candidate + # Try without directory prefix + for stored_key, stored_stem in name_map.items(): + if stored_key.endswith("/" + key) or stored_key == key: + candidate = f"article:{stored_stem}" + if node_ids is not None and candidate not in node_ids: + return None + return candidate + return None + + +def parse_wiki(root: Path) -> dict: + """Parse a Karpathy-pattern wiki and produce the scan manifest.""" + detection = detect_format(root) + if not detection["detected"]: + print(json.dumps({"error": "Not a Karpathy-pattern wiki", "detection": detection}), + file=sys.stderr) + sys.exit(1) + + wiki_root = Path(detection["wiki_root"]) + raw_root = root / "raw" + + # Build name resolution map + name_map = build_name_to_stem_map(wiki_root) + + # Find index.md and log.md + index_path = wiki_root / "index.md" + if not index_path.is_file(): + index_path = root / "index.md" + log_path = wiki_root / "log.md" + if not log_path.is_file(): + log_path = root / "log.md" + + # Parse index for categories + categories = parse_index(index_path) + log_entries = parse_log(log_path) + + # Build category lookup: wikilink target → category name + category_lookup: dict[str, str] = {} + for cat in categories: + for article_target in cat["articles"]: + category_lookup[article_target.lower()] = cat["name"] + + # --- Pre-compute article IDs (for edge resolution validation) --- + # Must use the same filter logic as the main loop (skip if EITHER matches INFRA_FILES) + article_ids: set[str] = set() + for md_file in sorted(wiki_root.rglob("*.md")): + rel = md_file.relative_to(wiki_root) + stem = str(rel.with_suffix("")) + basename = md_file.stem + if basename.lower() in INFRA_FILES or rel.name.lower() in INFRA_FILES: + continue + article_ids.add(f"article:{stem}") + + # --- Build article nodes --- + nodes = [] + edges = [] + warnings = [] + stats = {"articles": 0, "sources": 0, "topics": 0, "wikilinks": 0, "unresolved": 0} + + for md_file in sorted(wiki_root.rglob("*.md")): + rel = md_file.relative_to(wiki_root) + stem = str(rel.with_suffix("")) + basename = md_file.stem + + # Skip infrastructure files + if basename.lower() in INFRA_FILES or rel.name.lower() in INFRA_FILES: + continue + + text = md_file.read_text(encoding="utf-8", errors="replace") + h1 = extract_h1(text) + frontmatter = extract_frontmatter(text) + wikilinks = extract_wikilinks(text) + headings = extract_headings(text) + code_langs = extract_code_blocks(text) + summary = extract_first_paragraph(text) + line_count = text.count("\n") + 1 + word_count = len(text.split()) + + # Derive category from index.md lookup + category = category_lookup.get(basename.lower(), "") + if not category: + # Try stem match + category = category_lookup.get(stem.lower(), "") + + # Derive tags (deduplicated) + tag_set: set[str] = set() + if category: + tag_set.add(category.lower()) + if rel.parent != Path("."): + tag_set.add(str(rel.parent)) + fm_tags = frontmatter.get("tags", "") + if fm_tags: + tag_set.update(t.strip() for t in fm_tags.split(",") if t.strip()) + tags = sorted(tag_set) + + # Complexity from wikilink density + wl_count = len(wikilinks) + if wl_count > 15: + complexity = "complex" + elif wl_count > 5: + complexity = "moderate" + else: + complexity = "simple" + + node_id = f"article:{stem}" + nodes.append({ + "id": node_id, + "type": "article", + "name": h1 or basename, + "filePath": str(rel), + "summary": summary or f"Wiki article: {h1 or basename}", + "tags": tags, + "complexity": complexity, + "knowledgeMeta": { + "wikilinks": [wl["target"] for wl in wikilinks], + "category": category or None, + "content": text[:3000], # First 3000 chars for LLM analysis + }, + }) + stats["articles"] += 1 + stats["wikilinks"] += wl_count + + # Build edges from wikilinks (resolve against known article IDs) + for wl in wikilinks: + target_id = resolve_wikilink(wl["target"], name_map, article_ids) + if target_id and target_id != node_id: + edges.append({ + "source": node_id, + "target": target_id, + "type": "related", + "direction": "forward", + "weight": 0.7, + }) + elif not target_id: + warnings.append(f"Unresolved wikilink: [[{wl['target']}]] in {rel}") + stats["unresolved"] += 1 + + # --- Build topic nodes from index.md categories --- + for cat in categories: + topic_id = f"topic:{cat['name'].lower().replace(' ', '-')}" + nodes.append({ + "id": topic_id, + "type": "topic", + "name": cat["name"], + "summary": f"Category from index: {cat['name']} ({len(cat['articles'])} articles)", + "tags": ["category"], + "complexity": "simple", + }) + stats["topics"] += 1 + + # categorized_under edges (only resolve to known article nodes) + for article_target in cat["articles"]: + article_id = resolve_wikilink(article_target, name_map, article_ids) + if article_id: + edges.append({ + "source": article_id, + "target": topic_id, + "type": "categorized_under", + "direction": "forward", + "weight": 0.6, + }) + + # --- Build source nodes from raw/ --- + if raw_root.is_dir(): + for raw_file in sorted(raw_root.rglob("*")): + if raw_file.is_file() and not raw_file.name.startswith("."): + rel_raw = raw_file.relative_to(root) + ext = raw_file.suffix.lower() + size_kb = raw_file.stat().st_size / 1024 + source_id = f"source:{raw_file.relative_to(raw_root).with_suffix('')}" + nodes.append({ + "id": source_id, + "type": "source", + "name": raw_file.name, + "filePath": str(rel_raw), + "summary": f"Raw source ({ext or 'unknown'}, {size_kb:.0f} KB)", + "tags": ["raw", ext.lstrip(".") or "unknown"], + "complexity": "simple", + }) + stats["sources"] += 1 + + # --- Compute backlinks --- + backlink_map: dict[str, list[str]] = {} + for edge in edges: + if edge["type"] == "related": + target = edge["target"] + source = edge["source"] + backlink_map.setdefault(target, []).append(source) + for node in nodes: + if node["type"] == "article" and "knowledgeMeta" in node: + bl = backlink_map.get(node["id"], []) + node["knowledgeMeta"]["backlinks"] = bl + + # --- Deduplicate edges --- + seen_edges: set[tuple[str, str, str]] = set() + deduped_edges = [] + for edge in edges: + key = (edge["source"], edge["target"], edge["type"]) + if key not in seen_edges: + seen_edges.add(key) + deduped_edges.append(edge) + + return { + "format": "karpathy", + "stats": stats, + "categories": [{"name": c["name"], "count": len(c["articles"])} for c in categories], + "logEntries": len(log_entries), + "nodes": nodes, + "edges": deduped_edges, + "warnings": warnings[:50], # Cap warnings + } + + +def main(): + if len(sys.argv) < 2: + print("Usage: parse-knowledge-base.py ", file=sys.stderr) + sys.exit(1) + + root = Path(sys.argv[1]).resolve() + if not root.is_dir(): + print(f"Error: {root} is not a directory", file=sys.stderr) + sys.exit(1) + + manifest = parse_wiki(root) + + # Write output + out_dir = root / ".understand-anything" / "intermediate" + out_dir.mkdir(parents=True, exist_ok=True) + out_path = out_dir / "scan-manifest.json" + out_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") + + # Report to stderr + s = manifest["stats"] + print(f"[parse] Karpathy wiki: {s['articles']} articles, {s['sources']} sources, " + f"{s['topics']} topics, {s['wikilinks']} wikilinks " + f"({s['unresolved']} unresolved)", file=sys.stderr) + print(f"[parse] Output: {out_path}", file=sys.stderr) + + +if __name__ == "__main__": + main() From 66a87066462e2e0901f6c1919200359f612fe0c6 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sun, 12 Apr 2026 11:12:38 +0800 Subject: [PATCH 07/11] docs: add /understand-knowledge to README Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4081f6c..6ce8da7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Understand Anything

- Turn any codebase, Dockerfile, or docs into an interactive knowledge graph you can explore, search, and ask questions about. + Turn any codebase, knowledge base, or docs into an interactive knowledge graph you can explore, search, and ask questions about.
Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, and more.

@@ -65,6 +65,10 @@ Switch to the domain view and see how your code maps to real business processes Domain graph — business domains, flows, and process steps

+### Analyze knowledge bases + +Point `/understand-knowledge` at a [Karpathy-pattern LLM wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) and get a force-directed knowledge graph with community clustering. The deterministic parser extracts wikilinks and categories from `index.md`, then LLM agents discover implicit relationships, extract entities, and surface claims — turning your wiki into a navigable graph of interconnected ideas. +
@@ -142,6 +146,9 @@ An interactive web dashboard opens with your codebase visualized as a graph — # Extract business domain knowledge (domains, flows, steps) /understand-domain + +# Analyze a Karpathy-pattern LLM wiki knowledge base +/understand-knowledge ~/path/to/wiki ``` --- @@ -242,6 +249,7 @@ The `/understand` command orchestrates 5 specialized agents, and `/understand-do | `tour-builder` | Generate guided learning tours | | `graph-reviewer` | Validate graph completeness and referential integrity (runs inline by default; use `--review` for full LLM review) | | `domain-analyzer` | Extract business domains, flows, and process steps (used by `/understand-domain`) | +| `article-analyzer` | Extract entities, claims, and implicit relationships from wiki articles (used by `/understand-knowledge`) | File analyzers run in parallel (up to 5 concurrent, 20-30 files per batch). Supports incremental updates — only re-analyzes files that changed since the last run. From d3dfbd873b56b9cb0da2cf32fe392c3ce17160ac Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sun, 12 Apr 2026 11:15:51 +0800 Subject: [PATCH 08/11] docs: add /understand-knowledge to translated READMEs Updated zh-CN, zh-TW, ja-JP, tr-TR with the same 4 changes: tagline, knowledge base section, command example, agent table row. Co-Authored-By: Claude Opus 4.6 (1M context) --- README.ja-JP.md | 10 +++++++++- README.tr-TR.md | 10 +++++++++- README.zh-CN.md | 10 +++++++++- README.zh-TW.md | 10 +++++++++- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/README.ja-JP.md b/README.ja-JP.md index dd5fd59..1eb0c48 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -1,7 +1,7 @@

Understand Anything

- あらゆるコードベース、Dockerfile、ドキュメントを、探索・検索・質問ができるインタラクティブなナレッジグラフに変換します。 + あらゆるコードベース、ナレッジベース、ドキュメントを、探索・検索・質問ができるインタラクティブなナレッジグラフに変換します。
Claude Code、Codex、Cursor、Copilot、Gemini CLI など、マルチプラットフォーム対応。

@@ -65,6 +65,10 @@ Understand Anything は [Claude Code](https://docs.anthropic.com/en/docs/claude- ドメイングラフ——ビジネスドメイン、フロー、プロセスステップ

+### ナレッジベースを分析 + +`/understand-knowledge` を [Karpathy パターンの LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) に向けると、コミュニティクラスタリング付きのフォースディレクテッドナレッジグラフが生成されます。決定論的パーサーが `index.md` から wikilinks とカテゴリを抽出し、LLM エージェントが暗黙の関係を発見、エンティティを抽出、主張を浮き彫りにして、wiki をナビゲート可能な相互接続されたアイデアのグラフに変換します。 +
@@ -142,6 +146,9 @@ Understand Anything は [Claude Code](https://docs.anthropic.com/en/docs/claude- # ビジネスドメイン知識を抽出(ドメイン、フロー、ステップ) /understand-domain + +# Karpathy パターンの LLM Wiki ナレッジベースを分析 +/understand-knowledge ~/path/to/wiki ``` --- @@ -242,6 +249,7 @@ Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Und | `tour-builder` | ガイド学習ツアーの生成 | | `graph-reviewer` | グラフの完全性と参照整合性の検証 | | `domain-analyzer` | ビジネスドメイン、フロー、処理ステップの抽出(`/understand-domain` で使用) | +| `article-analyzer` | wiki 記事からエンティティ、主張、暗黙の関係を抽出(`/understand-knowledge` で使用) | ファイルアナライザーは並列実行されます(最大3つ同時)。インクリメンタル更新に対応しており、前回の実行から変更されたファイルのみを再分析します。 diff --git a/README.tr-TR.md b/README.tr-TR.md index 96b278a..73da194 100644 --- a/README.tr-TR.md +++ b/README.tr-TR.md @@ -1,7 +1,7 @@

Understand Anything

- Herhangi bir kod tabanını, Dockerfile'ı veya dokümantasyonu keşfedebileceğin, arayabileceğin ve hakkında sorular sorabileceğin interaktif bir bilgi grafiğine dönüştür. + Herhangi bir kod tabanını, bilgi tabanını veya dokümantasyonu keşfedebileceğin, arayabileceğin ve hakkında sorular sorabileceğin interaktif bir bilgi grafiğine dönüştür.
Claude Code, Codex, Cursor, Copilot, Gemini CLI ve daha fazlasıyla çalışır.

@@ -65,6 +65,10 @@ Alan görünümüne geçin ve kodunuzun gerçek iş süreçleriyle nasıl eşle Alan grafiği — iş alanları, akışlar ve süreç adımları

+### Bilgi tabanlarını analiz et + +`/understand-knowledge` komutunu bir [Karpathy deseni LLM Wiki'sine](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) yönlendirin ve topluluk kümeleme ile kuvvet yönelimli bir bilgi grafiği elde edin. Deterministik ayrıştırıcı `index.md`'den wikilinkleri ve kategorileri çıkarır, ardından LLM ajanları örtük ilişkileri keşfeder, varlıkları çıkarır ve iddiaları ortaya çıkarır — wiki'nizi gezinilebilir, birbirine bağlı fikirler grafiğine dönüştürür. +
@@ -142,6 +146,9 @@ Kod tabanın bir grafik olarak görselleştirilmiş, mimari katmana göre renkle # İş alanı bilgisini çıkar (alanlar, akışlar, adımlar) /understand-domain + +# Karpathy deseni LLM Wiki bilgi tabanını analiz et +/understand-knowledge ~/path/to/wiki ``` --- @@ -242,6 +249,7 @@ Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Und | `tour-builder` | Rehberli öğrenme turları oluştur | | `graph-reviewer` | Grafik bütünlüğünü ve referans bütünlüğünü doğrula | | `domain-analyzer` | İş alanları, akışlar ve işlem adımlarını çıkar (`/understand-domain` tarafından kullanılır) | +| `article-analyzer` | Wiki makalelerinden varlıkları, iddiaları ve örtük ilişkileri çıkar (`/understand-knowledge` tarafından kullanılır) | Dosya analizörleri paralel çalışır (en fazla 3 eşzamanlı). Artımlı güncellemeleri destekler — yalnızca son çalıştırmadan bu yana değişen dosyaları yeniden analiz eder. diff --git a/README.zh-CN.md b/README.zh-CN.md index da80ab2..8bc4f57 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,6 +1,6 @@

Understand Anything

- 将任意代码库、Dockerfile 或文档转化为可探索、可搜索、可对话的交互式知识图谱 + 将任意代码库、知识库或文档转化为可探索、可搜索、可对话的交互式知识图谱
支持 Claude Code、Codex、Cursor、Copilot、Gemini CLI 等多平台。

@@ -64,6 +64,10 @@ Understand Anything 是一个基于 [Claude Code](https://docs.anthropic.com/en/ 领域图——业务领域、流程和处理步骤

+### 分析知识库 + +将 `/understand-knowledge` 指向一个 [Karpathy 模式的 LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f),即可获得带有社区聚类的力导向知识图谱。确定性解析器从 `index.md` 中提取 wikilinks 和分类,然后 LLM 代理发现隐式关系、提取实体并挖掘论断——将你的 wiki 转化为可导航的互联思想图谱。 +
@@ -141,6 +145,9 @@ Understand Anything 是一个基于 [Claude Code](https://docs.anthropic.com/en/ # 提取业务领域知识(领域、流程、步骤) /understand-domain + +# 分析 Karpathy 模式的 LLM Wiki 知识库 +/understand-knowledge ~/path/to/wiki ``` --- @@ -241,6 +248,7 @@ Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Und | `tour-builder` | 生成引导式学习路径 | | `graph-reviewer` | 验证图的完整性和引用完整性 | | `domain-analyzer` | 提取业务领域、流程和处理步骤(由 `/understand-domain` 使用) | +| `article-analyzer` | 从 wiki 文章中提取实体、论断和隐式关系(由 `/understand-knowledge` 使用) | 文件分析器并行运行(最多 3 个并发)。支持增量更新 — 仅重新分析自上次运行以来发生更改的文件。 diff --git a/README.zh-TW.md b/README.zh-TW.md index 46d10a7..e2c17cc 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -1,6 +1,6 @@

Understand Anything

- 將任意程式碼庫、Dockerfile 或文件轉化為可探索、可搜尋、可對話的互動式知識圖譜 + 將任意程式碼庫、知識庫或文件轉化為可探索、可搜尋、可對話的互動式知識圖譜
支援 Claude Code、Codex、Cursor、Copilot、Gemini CLI 等多平台。

@@ -64,6 +64,10 @@ Understand Anything 是一個基於 [Claude Code](https://docs.anthropic.com/en/ 領域圖——業務領域、流程和處理步驟

+### 分析知識庫 + +將 `/understand-knowledge` 指向一個 [Karpathy 模式的 LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f),即可獲得帶有社群聚類的力導向知識圖譜。確定性解析器從 `index.md` 中提取 wikilinks 和分類,然後 LLM 代理發現隱式關係、提取實體並挖掘論斷——將你的 wiki 轉化為可導航的互聯思想圖譜。 +
@@ -141,6 +145,9 @@ Understand Anything 是一個基於 [Claude Code](https://docs.anthropic.com/en/ # 提取業務領域知識(領域、流程、步驟) /understand-domain + +# 分析 Karpathy 模式的 LLM Wiki 知識庫 +/understand-knowledge ~/path/to/wiki ``` --- @@ -241,6 +248,7 @@ Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Und | `tour-builder` | 產生引導式學習路徑 | | `graph-reviewer` | 驗證圖的完整性和參考完整性 | | `domain-analyzer` | 提取業務領域、流程和處理步驟(由 `/understand-domain` 使用) | +| `article-analyzer` | 從 wiki 文章中提取實體、論斷和隱式關係(由 `/understand-knowledge` 使用) | 檔案分析器並行執行(最多 3 個並發)。支援增量更新 — 僅重新分析自上次執行以來發生變更的檔案。 From 7e41ba7a68e3be0567b7d21c7519f0bd302d00a3 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sun, 12 Apr 2026 11:21:24 +0800 Subject: [PATCH 09/11] =?UTF-8?q?fix:=20address=20Codex=20review=20?= =?UTF-8?q?=E2=80=94=20stable=20layout,=20scoped=20infra=20filter,=20basen?= =?UTF-8?q?ame=20dedup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P1: Separate force layout computation from visual state updates so clicking/searching/touring doesn't re-randomize node positions. Layout only recomputes when the graph data or filters change. P1: Restrict infrastructure-file skipping (index.md, log.md, etc.) to the wiki root level only. Nested files like concepts/index.md are now correctly treated as content articles. P2: Track ambiguous bare basenames in the wikilink resolution map. Duplicate basenames (e.g., a/foo.md and b/foo.md) are removed from the flat lookup so [[foo]] doesn't silently resolve to the wrong page. Also fixed: edge IDs now use stable source-target-type keys instead of array indices for proper React reconciliation. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/KnowledgeGraphView.tsx | 203 +++++++++--------- .../parse-knowledge-base.py | 33 ++- 2 files changed, 132 insertions(+), 104 deletions(-) diff --git a/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx b/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx index 848d9a6..1d05f48 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx @@ -37,7 +37,6 @@ const EDGE_STYLES: Record = { function getNodeDimensions( edgeCount: number, ): { width: number; height: number } { - // Scale width/height by degree (connections) const scale = Math.min(1.5, Math.max(0.85, 0.85 + edgeCount * 0.03)); return { width: Math.round(NODE_WIDTH * scale), @@ -45,22 +44,19 @@ function getNodeDimensions( }; } -function buildKnowledgeGraph( +/** + * Compute the stable layout (positions) from graph topology. + * This only re-runs when the graph data or filters change, NOT on selection/search. + */ +function computeLayout( graph: KnowledgeGraph, - selectedNodeId: string | null, - focusNodeId: string | null, - searchResults: Map, - tourHighlightedNodeIds: Set, - onNodeClick: (nodeId: string) => void, -): { nodes: Node[]; edges: Edge[] } { - // Count edges per node for degree-proportional sizing +): { positionMap: Map; edgeCounts: Map; communityMap: Map } { const edgeCounts = new Map(); for (const edge of graph.edges) { edgeCounts.set(edge.source, (edgeCounts.get(edge.source) ?? 0) + 1); edgeCounts.set(edge.target, (edgeCounts.get(edge.target) ?? 0) + 1); } - // Build community map from layers const communityMap = new Map(); graph.layers.forEach((layer, i) => { for (const nodeId of layer.nodeIds) { @@ -68,83 +64,33 @@ function buildKnowledgeGraph( } }); - // Determine neighbor IDs for focus/selection fading - const neighborIds = new Set(); - if (focusNodeId || selectedNodeId) { - const focusId = focusNodeId ?? selectedNodeId; - for (const edge of graph.edges) { - if (edge.source === focusId) neighborIds.add(edge.target); - if (edge.target === focusId) neighborIds.add(edge.source); - } - } - - // Build node dimensions map const dims = new Map(); for (const node of graph.nodes) { - const d = getNodeDimensions(edgeCounts.get(node.id) ?? 0); - dims.set(node.id, d); + dims.set(node.id, getNodeDimensions(edgeCounts.get(node.id) ?? 0)); } - // Build xyflow nodes - const rfNodes: Node[] = graph.nodes.map((node) => { - const isSelected = node.id === selectedNodeId; - const isFocused = node.id === focusNodeId; - const isNeighbor = neighborIds.has(node.id); - const isSelectionFaded = - (focusNodeId || selectedNodeId) && - !isSelected && - !isFocused && - !isNeighbor; - const searchScore = searchResults.get(node.id); - const isHighlighted = searchScore !== undefined; - const isTourHighlighted = tourHighlightedNodeIds.has(node.id); + // Build temporary nodes/edges for layout computation only + const tmpNodes: Node[] = graph.nodes.map((node) => ({ + id: node.id, + type: "custom" as const, + position: { x: 0, y: 0 }, + data: {}, + })); - const data: CustomNodeData = { - label: node.name, - nodeType: node.type, - summary: node.summary, - complexity: node.complexity, - isHighlighted, - searchScore, - isSelected, - isTourHighlighted, - isDiffChanged: false, - isDiffAffected: false, - isDiffFaded: false, - isNeighbor, - isSelectionFaded: !!isSelectionFaded, - onNodeClick, - incomingCount: edgeCounts.get(node.id) ?? 0, - tags: node.tags, - }; + const tmpEdges: Edge[] = graph.edges.map((e, i) => ({ + id: `ke-${i}`, + source: e.source, + target: e.target, + })); - return { - id: node.id, - type: "custom" as const, - position: { x: 0, y: 0 }, - data, - }; - }); + const { nodes: layoutedNodes } = applyForceLayout(tmpNodes, tmpEdges, dims, communityMap); - // Build xyflow edges - const rfEdges: Edge[] = graph.edges.map((e, i) => { - const style = EDGE_STYLES[e.type] ?? EDGE_STYLES.related; - return { - id: `ke-${i}-${e.source}-${e.target}`, - source: e.source, - target: e.target, - style, - animated: e.type === "contradicts", - label: e.type !== "related" && e.type !== "categorized_under" ? e.type.replace(/_/g, " ") : undefined, - labelStyle: { fill: "var(--color-text-muted)", fontSize: 9, opacity: 0.7 }, - labelBgStyle: { fill: "var(--color-surface)", fillOpacity: 0.9 }, - labelBgPadding: [4, 2] as [number, number], - labelBgBorderRadius: 3, - }; - }); + const positionMap = new Map(); + for (const n of layoutedNodes) { + positionMap.set(n.id, n.position); + } - // Apply force layout with community clustering - return applyForceLayout(rfNodes, rfEdges, dims, communityMap); + return { positionMap, edgeCounts, communityMap }; } function KnowledgeGraphViewInner() { @@ -171,10 +117,10 @@ function KnowledgeGraphViewInner() { [tourHighlightedNodeIds], ); - const { nodes, edges } = useMemo(() => { - if (!graph) return { nodes: [], edges: [] }; + // Filter graph — only recompute when graph data or filters change + const filteredGraph = useMemo((): KnowledgeGraph | null => { + if (!graph) return null; - // Filter graph by active node type filters const filteredNodes = graph.nodes.filter((n) => { if (["article", "entity", "topic", "claim", "source"].includes(n.type)) { return nodeTypeFilters.knowledge !== false; @@ -187,21 +133,86 @@ function KnowledgeGraphViewInner() { (e) => filteredNodeIds.has(e.source) && filteredNodeIds.has(e.target), ); - const filteredGraph: KnowledgeGraph = { - ...graph, - nodes: filteredNodes, - edges: filteredEdges, - }; + return { ...graph, nodes: filteredNodes, edges: filteredEdges }; + }, [graph, nodeTypeFilters]); - return buildKnowledgeGraph( - filteredGraph, - selectedNodeId, - focusNodeId, - searchResults, - tourSet, - onNodeClick, - ); - }, [graph, selectedNodeId, focusNodeId, searchResults, tourSet, onNodeClick, nodeTypeFilters]); + // Compute layout ONCE per graph/filter change — stable positions + const { positionMap, edgeCounts } = useMemo(() => { + if (!filteredGraph) return { positionMap: new Map(), edgeCounts: new Map() }; + return computeLayout(filteredGraph); + }, [filteredGraph]); + + // Build visual nodes/edges — recomputes on selection/search/tour WITHOUT re-layout + const { nodes, edges } = useMemo(() => { + if (!filteredGraph) return { nodes: [], edges: [] }; + + const neighborIds = new Set(); + if (focusNodeId || selectedNodeId) { + const focusId = focusNodeId ?? selectedNodeId; + for (const edge of filteredGraph.edges) { + if (edge.source === focusId) neighborIds.add(edge.target); + if (edge.target === focusId) neighborIds.add(edge.source); + } + } + + const rfNodes: Node[] = filteredGraph.nodes.map((node) => { + const isSelected = node.id === selectedNodeId; + const isFocused = node.id === focusNodeId; + const isNeighbor = neighborIds.has(node.id); + const isSelectionFaded = + (focusNodeId || selectedNodeId) && + !isSelected && + !isFocused && + !isNeighbor; + const searchScore = searchResults.get(node.id); + const isHighlighted = searchScore !== undefined; + const isTourHighlighted = tourSet.has(node.id); + + const data: CustomNodeData = { + label: node.name, + nodeType: node.type, + summary: node.summary, + complexity: node.complexity, + isHighlighted, + searchScore, + isSelected, + isTourHighlighted, + isDiffChanged: false, + isDiffAffected: false, + isDiffFaded: false, + isNeighbor, + isSelectionFaded: !!isSelectionFaded, + onNodeClick, + incomingCount: edgeCounts.get(node.id) ?? 0, + tags: node.tags, + }; + + return { + id: node.id, + type: "custom" as const, + position: positionMap.get(node.id) ?? { x: 0, y: 0 }, + data, + }; + }); + + const rfEdges: Edge[] = filteredGraph.edges.map((e) => { + const style = EDGE_STYLES[e.type] ?? EDGE_STYLES.related; + return { + id: `ke-${e.source}-${e.target}-${e.type}`, + source: e.source, + target: e.target, + style, + animated: e.type === "contradicts", + label: e.type !== "related" && e.type !== "categorized_under" ? e.type.replace(/_/g, " ") : undefined, + labelStyle: { fill: "var(--color-text-muted)", fontSize: 9, opacity: 0.7 }, + labelBgStyle: { fill: "var(--color-surface)", fillOpacity: 0.9 }, + labelBgPadding: [4, 2] as [number, number], + labelBgBorderRadius: 3, + }; + }); + + return { nodes: rfNodes, edges: rfEdges }; + }, [filteredGraph, selectedNodeId, focusNodeId, searchResults, tourSet, onNodeClick, positionMap, edgeCounts]); if (!graph) { return ( diff --git a/understand-anything-plugin/skills/understand-knowledge/parse-knowledge-base.py b/understand-anything-plugin/skills/understand-knowledge/parse-knowledge-base.py index 10c6b2e..45d95e4 100644 --- a/understand-anything-plugin/skills/understand-knowledge/parse-knowledge-base.py +++ b/understand-anything-plugin/skills/understand-knowledge/parse-knowledge-base.py @@ -221,15 +221,31 @@ def parse_log(log_path: Path) -> list[dict]: # --------------------------------------------------------------------------- def build_name_to_stem_map(wiki_root: Path) -> dict[str, str]: - """Build a case-insensitive map from filename stem to relative stem path.""" + """Build a case-insensitive map from filename stem to relative stem path. + + Full relative paths always map uniquely. Bare basenames map only when + unambiguous — duplicate basenames are removed so they don't silently + resolve to the wrong page. + """ name_map: dict[str, str] = {} + # Track which bare basenames appear more than once + basename_counts: dict[str, int] = {} for md_file in wiki_root.rglob("*.md"): rel = md_file.relative_to(wiki_root) stem = str(rel.with_suffix("")) # e.g., "decisions/decision-foo" basename = md_file.stem # e.g., "decision-foo" - # Map both full relative path and bare filename (for flat wikilink resolution) + # Full relative path always maps uniquely name_map[stem.lower()] = stem - name_map[basename.lower()] = stem + # Track basename for ambiguity detection + key = basename.lower() + basename_counts[key] = basename_counts.get(key, 0) + 1 + name_map[key] = stem + + # Remove ambiguous basename entries (appear more than once) + for key, count in basename_counts.items(): + if count > 1 and key in name_map: + del name_map[key] + return name_map @@ -292,13 +308,14 @@ def parse_wiki(root: Path) -> dict: category_lookup[article_target.lower()] = cat["name"] # --- Pre-compute article IDs (for edge resolution validation) --- - # Must use the same filter logic as the main loop (skip if EITHER matches INFRA_FILES) + # Only skip infra files at the wiki root level, not in subdirectories + # (e.g., wiki/index.md is infra, but wiki/concepts/index.md is content) article_ids: set[str] = set() for md_file in sorted(wiki_root.rglob("*.md")): rel = md_file.relative_to(wiki_root) stem = str(rel.with_suffix("")) - basename = md_file.stem - if basename.lower() in INFRA_FILES or rel.name.lower() in INFRA_FILES: + # Only filter infra files at root level (no parent directory) + if rel.parent == Path(".") and rel.name.lower() in INFRA_FILES: continue article_ids.add(f"article:{stem}") @@ -313,8 +330,8 @@ def parse_wiki(root: Path) -> dict: stem = str(rel.with_suffix("")) basename = md_file.stem - # Skip infrastructure files - if basename.lower() in INFRA_FILES or rel.name.lower() in INFRA_FILES: + # Skip infrastructure files only at wiki root level + if rel.parent == Path(".") and rel.name.lower() in INFRA_FILES: continue text = md_file.read_text(encoding="utf-8", errors="replace") From 1d1585190588f974c77dc39c5fd4ec8ed8bc2db0 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sun, 12 Apr 2026 11:24:17 +0800 Subject: [PATCH 10/11] fix: highlight connected edges on node selection in knowledge view Connected edges now highlight (thicker, full opacity) when a node is selected/focused. Unconnected edges dim to near-invisible. Edge labels only show on connected edges to reduce visual noise. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/KnowledgeGraphView.tsx | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx b/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx index 1d05f48..f53e577 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx @@ -195,15 +195,36 @@ function KnowledgeGraphViewInner() { }; }); + const activeId = focusNodeId ?? selectedNodeId; const rfEdges: Edge[] = filteredGraph.edges.map((e) => { - const style = EDGE_STYLES[e.type] ?? EDGE_STYLES.related; + const baseStyle = EDGE_STYLES[e.type] ?? EDGE_STYLES.related; + const isConnected = activeId && (e.source === activeId || e.target === activeId); + + // When a node is selected: highlight connected edges, dim the rest + let style: React.CSSProperties; + if (activeId) { + if (isConnected) { + style = { + ...baseStyle, + strokeWidth: Math.max(2, (baseStyle.strokeWidth as number ?? 1) * 1.5), + opacity: 1, + }; + } else { + style = { ...baseStyle, opacity: 0.04 }; + } + } else { + style = baseStyle; + } + return { id: `ke-${e.source}-${e.target}-${e.type}`, source: e.source, target: e.target, style, - animated: e.type === "contradicts", - label: e.type !== "related" && e.type !== "categorized_under" ? e.type.replace(/_/g, " ") : undefined, + animated: e.type === "contradicts" && (!activeId || !!isConnected), + label: isConnected && e.type !== "related" && e.type !== "categorized_under" + ? e.type.replace(/_/g, " ") + : undefined, labelStyle: { fill: "var(--color-text-muted)", fontSize: 9, opacity: 0.7 }, labelBgStyle: { fill: "var(--color-surface)", fillOpacity: 0.9 }, labelBgPadding: [4, 2] as [number, number], From c2f5b02b2d72e345b55d82d7d36c4c2cc1fd2ff1 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Sun, 12 Apr 2026 11:31:46 +0800 Subject: [PATCH 11/11] docs: add Korean and Spanish README translations Co-Authored-By: Claude Opus 4.6 (1M context) --- README.es-ES.md | 286 ++++++++++++++++++++++++++++++++++++++++++++++++ README.ja-JP.md | 2 +- README.ko-KR.md | 286 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- README.tr-TR.md | 2 +- README.zh-CN.md | 2 +- README.zh-TW.md | 2 +- 7 files changed, 577 insertions(+), 5 deletions(-) create mode 100644 README.es-ES.md create mode 100644 README.ko-KR.md diff --git a/README.es-ES.md b/README.es-ES.md new file mode 100644 index 0000000..e320c4d --- /dev/null +++ b/README.es-ES.md @@ -0,0 +1,286 @@ +

Understand Anything

+

+ Convierte cualquier código fuente, base de conocimiento o documentación en un grafo de conocimiento interactivo que puedes explorar, buscar y consultar. +
+ Compatible con Claude Code, Codex, Cursor, Copilot, Gemini CLI y más. +

+ +

+ English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Español | Türkçe +

+ +

+ + + + + Star History Rank + + +

+ +

+ Quick Start + License: MIT + Claude Code + Codex + Copilot + Gemini CLI + OpenCode + Homepage + Live Demo +

+ +

+ Understand Anything — Convierte cualquier código fuente en un grafo de conocimiento interactivo +

+ +--- + +> [!TIP] +> **¡Un enorme agradecimiento a la comunidad!** El apoyo a Understand-Anything ha sido increíble. Si esta herramienta te ahorra unos minutos de buscar entre la complejidad, eso es todo lo que quería. 🚀 + +**Acabas de unirte a un nuevo equipo. El código tiene 200,000 líneas. ¿Por dónde empiezas?** + +Understand Anything es un plugin de [Claude Code](https://docs.anthropic.com/en/docs/claude-code) que analiza tu proyecto con un pipeline multi-agente, construye un grafo de conocimiento de cada archivo, función, clase y dependencia, y luego te ofrece un panel interactivo para explorarlo visualmente. Deja de leer código a ciegas. Empieza a ver el panorama completo. + +--- + +## ✨ Características + +### Explora el grafo estructural + +Navega tu código como un grafo de conocimiento interactivo: cada archivo, función y clase es un nodo que puedes hacer clic, buscar y explorar. Selecciona cualquier nodo para ver resúmenes en lenguaje natural, relaciones y recorridos guiados. + +

+ Grafo estructural — explora archivos, funciones, clases y sus relaciones +

+ +### Comprende la lógica de negocio + +Cambia a la vista de dominio y observa cómo tu código se mapea a procesos de negocio reales: dominios, flujos y pasos representados como un grafo horizontal. + +

+ Grafo de dominio — dominios de negocio, flujos y pasos de proceso +

+ +### Analiza bases de conocimiento + +Apunta `/understand-knowledge` a un [wiki LLM con patrón Karpathy](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) y obtén un grafo de conocimiento dirigido por fuerzas con agrupación por comunidad. El parser determinístico extrae wikilinks y categorías de `index.md`, luego los agentes LLM descubren relaciones implícitas, extraen entidades y revelan afirmaciones, convirtiendo tu wiki en un grafo navegable de ideas interconectadas. + + + + + + + + + + + + + + +
+

🧭 Recorridos Guiados

+

Recorridos generados automáticamente de la arquitectura, ordenados por dependencia. Aprende el código en el orden correcto.

+
+

🔍 Búsqueda Difusa y Semántica

+

Encuentra cualquier cosa por nombre o por significado. Busca "¿qué partes manejan la autenticación?" y obtén resultados relevantes en todo el grafo.

+
+

📊 Análisis de Impacto de Cambios

+

Visualiza qué partes del sistema afectan tus cambios antes de hacer commit. Comprende los efectos en cascada a través del código.

+
+

🎭 Interfaz Adaptativa por Persona

+

El panel ajusta su nivel de detalle según quién eres: desarrollador junior, PM o usuario avanzado.

+
+

🏗️ Visualización por Capas

+

Agrupación automática por capa arquitectónica — API, Servicio, Datos, UI, Utilidades — con leyenda codificada por colores.

+
+

📚 Conceptos del Lenguaje

+

12 patrones de programación (genéricos, closures, decoradores, etc.) explicados en contexto donde aparecen.

+
+ +--- + +## 🚀 Inicio Rápido + +### 1. Instala el plugin + +```bash +/plugin marketplace add Lum1104/Understand-Anything +/plugin install understand-anything +``` + +### 2. Analiza tu código + +```bash +/understand +``` + +Un pipeline multi-agente escanea tu proyecto, extrae cada archivo, función, clase y dependencia, y construye un grafo de conocimiento guardado en `.understand-anything/knowledge-graph.json`. + +### 3. Explora el panel + +```bash +/understand-dashboard +``` + +Se abre un panel web interactivo con tu código visualizado como un grafo, codificado por colores según la capa arquitectónica, con funciones de búsqueda y clic. Selecciona cualquier nodo para ver su código, relaciones y una explicación en lenguaje natural. + +### 4. Sigue aprendiendo + +```bash +# Pregunta cualquier cosa sobre el código +/understand-chat How does the payment flow work? + +# Analiza el impacto de tus cambios actuales +/understand-diff + +# Profundiza en un archivo o función específica +/understand-explain src/auth/login.ts + +# Genera una guía de incorporación para nuevos miembros del equipo +/understand-onboard + +# Extrae conocimiento de dominio de negocio (dominios, flujos, pasos) +/understand-domain + +# Analiza un wiki LLM con patrón Karpathy +/understand-knowledge ~/path/to/wiki +``` + +--- + +## 🌐 Instalación Multiplataforma + +Understand-Anything funciona en múltiples plataformas de codificación con IA. + +### Claude Code (Nativo) + +```bash +/plugin marketplace add Lum1104/Understand-Anything +/plugin install understand-anything +``` + +### Codex + +Dile a Codex: +``` +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.codex/INSTALL.md +``` + +### OpenCode + +Dile a OpenCode: +``` +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.opencode/INSTALL.md +``` + +### OpenClaw + +Dile a OpenClaw: +``` +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.openclaw/INSTALL.md +``` + +### Cursor + +Cursor detecta automáticamente el plugin a través de `.cursor-plugin/plugin.json` cuando se clona este repositorio. No requiere instalación manual: simplemente clona y abre en Cursor. + +### VS Code + GitHub Copilot + +VS Code con GitHub Copilot (v1.108+) detecta automáticamente el plugin a través de `.copilot-plugin/plugin.json` cuando se clona este repositorio. No requiere instalación manual: simplemente clona y abre en VS Code. + +Para habilidades personales (disponibles en todos los proyectos), dile a GitHub Copilot: +```text +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.vscode/INSTALL.md +``` + +### Antigravity + +Dile a Antigravity: +```text +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.antigravity/INSTALL.md +``` + +### Gemini CLI + +Dile a Gemini CLI: +```text +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.gemini/INSTALL.md +``` + +### Pi Agent + +Dile a Pi Agent: +```text +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.pi/INSTALL.md +``` + +### Compatibilidad de Plataformas + +| Plataforma | Estado | Método de Instalación | +|----------|--------|----------------| +| Claude Code | ✅ Nativo | Marketplace de plugins | +| Codex | ✅ Soportado | Instalación guiada por IA | +| OpenCode | ✅ Soportado | Instalación guiada por IA | +| OpenClaw | ✅ Soportado | Instalación guiada por IA | +| Cursor | ✅ Soportado | Detección automática | +| VS Code + GitHub Copilot | ✅ Soportado | Detección automática | +| Antigravity | ✅ Soportado | Instalación guiada por IA | +| Gemini CLI | ✅ Soportado | Instalación guiada por IA | +| Pi Agent | ✅ Soportado | Instalación guiada por IA | + +--- + +## 🔧 Bajo el Capó + +### Pipeline Multi-Agente + +El comando `/understand` orquesta 5 agentes especializados, y `/understand-domain` añade un sexto: + +| Agente | Rol | +|-------|------| +| `project-scanner` | Descubre archivos, detecta lenguajes y frameworks | +| `file-analyzer` | Extrae funciones, clases e importaciones; produce nodos y aristas del grafo | +| `architecture-analyzer` | Identifica capas arquitectónicas | +| `tour-builder` | Genera recorridos de aprendizaje guiados | +| `graph-reviewer` | Valida la completitud y la integridad referencial del grafo (se ejecuta inline por defecto; usa `--review` para una revisión completa con LLM) | +| `domain-analyzer` | Extrae dominios de negocio, flujos y pasos de proceso (usado por `/understand-domain`) | +| `article-analyzer` | Extrae entidades, afirmaciones y relaciones implícitas de artículos wiki (usado por `/understand-knowledge`) | + +Los analizadores de archivos se ejecutan en paralelo (hasta 5 concurrentes, 20-30 archivos por lote). Soporta actualizaciones incrementales: solo reanaliza los archivos que cambiaron desde la última ejecución. + +--- + +## 🤝 Contribuir + +¡Las contribuciones son bienvenidas! Así puedes empezar: + +1. Haz fork del repositorio +2. Crea una rama de funcionalidad (`git checkout -b feature/my-feature`) +3. Ejecuta las pruebas (`pnpm --filter @understand-anything/core test`) +4. Haz commit de tus cambios y abre un pull request + +Para cambios importantes, abre primero un issue para que podamos discutir el enfoque. + +--- + +

+ Deja de leer código a ciegas. Empieza a entenderlo todo. +

+ +## Historial de Stars + + + + + + Star History Chart + + + +

+ Licencia MIT © Lum1104 +

diff --git a/README.ja-JP.md b/README.ja-JP.md index 1eb0c48..4b1f5bd 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -7,7 +7,7 @@

- English | 简体中文 | 繁體中文 | 日本語 | Türkçe + English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Español | Türkçe

diff --git a/README.ko-KR.md b/README.ko-KR.md new file mode 100644 index 0000000..7bac616 --- /dev/null +++ b/README.ko-KR.md @@ -0,0 +1,286 @@ +

Understand Anything

+

+ 모든 코드베이스, 지식 베이스 또는 문서를 탐색, 검색, 질문할 수 있는 인터랙티브 지식 그래프로 변환합니다. +
+ Claude Code, Codex, Cursor, Copilot, Gemini CLI 등 다양한 플랫폼을 지원합니다. +

+ +

+ English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Español | Türkçe +

+ +

+ + + + + Star History Rank + + +

+ +

+ Quick Start + License: MIT + Claude Code + Codex + Copilot + Gemini CLI + OpenCode + Homepage + Live Demo +

+ +

+ Understand Anything — 모든 코드베이스를 인터랙티브 지식 그래프로 변환 +

+ +--- + +> [!TIP] +> **커뮤니티의 엄청난 응원에 감사드립니다!** Understand-Anything에 대한 관심이 정말 놀라웠습니다. 이 도구가 복잡한 코드를 파악하는 시간을 단 몇 분이라도 줄여드린다면, 그것만으로도 충분합니다. 🚀 + +**새 팀에 합류했습니다. 코드베이스가 20만 줄입니다. 어디서부터 시작하시겠습니까?** + +Understand Anything은 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 플러그인으로, 멀티 에이전트 파이프라인을 통해 프로젝트를 분석하고, 모든 파일, 함수, 클래스, 의존성에 대한 지식 그래프를 구축한 뒤, 이를 시각적으로 탐색할 수 있는 인터랙티브 대시보드를 제공합니다. 더 이상 코드를 맹목적으로 읽지 마세요. 전체 그림을 파악하세요. + +--- + +## ✨ 주요 기능 + +### 구조 그래프 탐색 + +코드베이스를 인터랙티브 지식 그래프로 탐색하세요. 모든 파일, 함수, 클래스가 클릭, 검색, 탐색 가능한 노드입니다. 노드를 선택하면 이해하기 쉬운 요약, 관계, 가이드 투어를 확인할 수 있습니다. + +

+ 구조 그래프 — 파일, 함수, 클래스 및 관계 탐색 +

+ +### 비즈니스 로직 이해 + +도메인 뷰로 전환하면 코드가 실제 비즈니스 프로세스에 어떻게 매핑되는지 확인할 수 있습니다. 도메인, 흐름, 단계가 수평 그래프로 표시됩니다. + +

+ 도메인 그래프 — 비즈니스 도메인, 흐름 및 프로세스 단계 +

+ +### 지식 베이스 분석 + +`/understand-knowledge`를 [Karpathy 패턴 LLM 위키](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f)에 연결하면 커뮤니티 클러스터링이 적용된 힘 기반 지식 그래프를 생성합니다. 결정론적 파서가 `index.md`에서 위키링크와 카테고리를 추출한 후, LLM 에이전트가 암묵적 관계를 발견하고, 엔티티를 추출하며, 주장을 도출하여 위키를 탐색 가능한 상호 연결된 아이디어 그래프로 변환합니다. + + + + + + + + + + + + + + +
+

🧭 가이드 투어

+

의존성 순서에 따라 자동 생성되는 아키텍처 워크스루입니다. 올바른 순서로 코드베이스를 학습하세요.

+
+

🔍 퍼지 및 시맨틱 검색

+

이름 또는 의미로 무엇이든 검색하세요. "인증을 처리하는 부분은?" 같은 질문으로 그래프 전체에서 관련 결과를 얻을 수 있습니다.

+
+

📊 변경 영향 분석

+

커밋 전에 변경 사항이 시스템의 어떤 부분에 영향을 미치는지 확인하세요. 코드베이스 전반의 파급 효과를 이해하세요.

+
+

🎭 페르소나 적응형 UI

+

사용자 유형(주니어 개발자, PM, 파워 유저)에 따라 대시보드의 상세 수준이 자동으로 조정됩니다.

+
+

🏗️ 레이어 시각화

+

아키텍처 레이어별 자동 그룹화 — API, 서비스, 데이터, UI, 유틸리티 — 색상 코드 범례가 함께 제공됩니다.

+
+

📚 프로그래밍 개념

+

12가지 프로그래밍 패턴(제네릭, 클로저, 데코레이터 등)이 코드에 등장하는 맥락에서 설명됩니다.

+
+ +--- + +## 🚀 빠른 시작 + +### 1. 플러그인 설치 + +```bash +/plugin marketplace add Lum1104/Understand-Anything +/plugin install understand-anything +``` + +### 2. 코드베이스 분석 + +```bash +/understand +``` + +멀티 에이전트 파이프라인이 프로젝트를 스캔하고, 모든 파일, 함수, 클래스, 의존성을 추출한 뒤, `.understand-anything/knowledge-graph.json`에 지식 그래프를 저장합니다. + +### 3. 대시보드 탐색 + +```bash +/understand-dashboard +``` + +코드베이스가 그래프로 시각화된 인터랙티브 웹 대시보드가 열립니다. 아키텍처 레이어별로 색상이 구분되어 있으며, 검색과 클릭이 가능합니다. 노드를 선택하면 코드, 관계, 이해하기 쉬운 설명을 확인할 수 있습니다. + +### 4. 더 깊이 탐구하기 + +```bash +# 코드베이스에 대해 무엇이든 질문하기 +/understand-chat How does the payment flow work? + +# 현재 변경 사항의 영향 분석 +/understand-diff + +# 특정 파일이나 함수를 심층 분석 +/understand-explain src/auth/login.ts + +# 새 팀원을 위한 온보딩 가이드 생성 +/understand-onboard + +# 비즈니스 도메인 지식 추출 (도메인, 흐름, 단계) +/understand-domain + +# Karpathy 패턴 LLM 위키 지식 베이스 분석 +/understand-knowledge ~/path/to/wiki +``` + +--- + +## 🌐 멀티 플랫폼 설치 + +Understand-Anything은 다양한 AI 코딩 플랫폼에서 사용할 수 있습니다. + +### Claude Code (네이티브) + +```bash +/plugin marketplace add Lum1104/Understand-Anything +/plugin install understand-anything +``` + +### Codex + +Codex에 입력하세요: +``` +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.codex/INSTALL.md +``` + +### OpenCode + +OpenCode에 입력하세요: +``` +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.opencode/INSTALL.md +``` + +### OpenClaw + +OpenClaw에 입력하세요: +``` +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.openclaw/INSTALL.md +``` + +### Cursor + +이 저장소를 클론하면 Cursor가 `.cursor-plugin/plugin.json`을 통해 플러그인을 자동으로 인식합니다. 수동 설치가 필요 없습니다. 클론 후 Cursor에서 열기만 하면 됩니다. + +### VS Code + GitHub Copilot + +GitHub Copilot(v1.108+)이 설치된 VS Code는 `.copilot-plugin/plugin.json`을 통해 플러그인을 자동으로 인식합니다. 수동 설치가 필요 없습니다. 클론 후 VS Code에서 열기만 하면 됩니다. + +모든 프로젝트에서 사용하려면(개인 스킬) GitHub Copilot에 입력하세요: +```text +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.vscode/INSTALL.md +``` + +### Antigravity + +Antigravity에 입력하세요: +```text +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.antigravity/INSTALL.md +``` + +### Gemini CLI + +Gemini CLI에 입력하세요: +```text +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.gemini/INSTALL.md +``` + +### Pi Agent + +Pi Agent에 입력하세요: +```text +Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.pi/INSTALL.md +``` + +### 플랫폼 호환성 + +| 플랫폼 | 상태 | 설치 방법 | +|----------|--------|----------------| +| Claude Code | ✅ 네이티브 | 플러그인 마켓플레이스 | +| Codex | ✅ 지원 | AI 기반 설치 | +| OpenCode | ✅ 지원 | AI 기반 설치 | +| OpenClaw | ✅ 지원 | AI 기반 설치 | +| Cursor | ✅ 지원 | 자동 인식 | +| VS Code + GitHub Copilot | ✅ 지원 | 자동 인식 | +| Antigravity | ✅ 지원 | AI 기반 설치 | +| Gemini CLI | ✅ 지원 | AI 기반 설치 | +| Pi Agent | ✅ 지원 | AI 기반 설치 | + +--- + +## 🔧 작동 원리 + +### 멀티 에이전트 파이프라인 + +`/understand` 명령은 5개의 전문 에이전트를 조율하며, `/understand-domain`은 6번째 에이전트를 추가합니다: + +| 에이전트 | 역할 | +|-------|------| +| `project-scanner` | 파일 탐색, 언어 및 프레임워크 감지 | +| `file-analyzer` | 함수, 클래스, 임포트 추출; 그래프 노드 및 엣지 생성 | +| `architecture-analyzer` | 아키텍처 레이어 식별 | +| `tour-builder` | 가이드 학습 투어 생성 | +| `graph-reviewer` | 그래프 완전성 및 참조 무결성 검증 (기본적으로 인라인 실행; 전체 LLM 검토는 `--review` 사용) | +| `domain-analyzer` | 비즈니스 도메인, 흐름 및 프로세스 단계 추출 (`/understand-domain`에서 사용) | +| `article-analyzer` | 위키 문서에서 엔티티, 주장 및 암묵적 관계 추출 (`/understand-knowledge`에서 사용) | + +파일 분석기는 병렬로 실행됩니다(최대 5개 동시, 배치당 20~30개 파일). 증분 업데이트를 지원하여 마지막 실행 이후 변경된 파일만 재분석합니다. + +--- + +## 🤝 기여하기 + +기여를 환영합니다! 시작하는 방법은 다음과 같습니다: + +1. 저장소를 Fork합니다 +2. 기능 브랜치를 생성합니다 (`git checkout -b feature/my-feature`) +3. 테스트를 실행합니다 (`pnpm --filter @understand-anything/core test`) +4. 변경 사항을 커밋하고 Pull Request를 생성합니다 + +주요 변경 사항의 경우, 먼저 Issue를 열어 접근 방식을 논의해 주세요. + +--- + +

+ 더 이상 코드를 맹목적으로 읽지 마세요. 모든 것을 이해하세요. +

+ +## Star 히스토리 + + + + + + Star History Chart + + + +

+ MIT 라이선스 © Lum1104 +

diff --git a/README.md b/README.md index 6ce8da7..72e5820 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

- English | 简体中文 | 繁體中文 | 日本語 | Türkçe + English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Español | Türkçe

diff --git a/README.tr-TR.md b/README.tr-TR.md index 73da194..17d6e07 100644 --- a/README.tr-TR.md +++ b/README.tr-TR.md @@ -7,7 +7,7 @@

- English | 简体中文 | 繁體中文 | 日本語 | Türkçe + English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Español | Türkçe

diff --git a/README.zh-CN.md b/README.zh-CN.md index 8bc4f57..7b6946a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,7 +6,7 @@

- English | 简体中文 | 繁體中文 | 日本語 | Türkçe + English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Español | Türkçe

diff --git a/README.zh-TW.md b/README.zh-TW.md index e2c17cc..f4a0aba 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -6,7 +6,7 @@

- English | 简体中文 | 繁體中文 | 日本語 | Türkçe + English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Español | Türkçe