Files
Fulfilled-Knowledge/Understand-Anything-main/docs/superpowers/plans/2026-04-09-understand-knowledge.md
2026-05-27 15:40:32 +08:00

60 KiB

/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:

// 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
// 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:

// 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<string, unknown>;
  sourceUrl?: string;
  confidence?: number; // 0-1, for LLM-inferred relationships
}
  • Step 4: Add knowledgeMeta to GraphNode
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
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
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:

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:

  // 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:

  // 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:

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
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:

  /* 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:

  .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
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:

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:

export type EdgeCategory = "structural" | "behavioral" | "data-flow" | "dependencies" | "semantic" | "infrastructure" | "domain" | "knowledge";

export const EDGE_CATEGORY_MAP: Record<EdgeCategory, string[]> = {
  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
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
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:

  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:

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
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:

  // 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:

  // 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:

  // 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:

  // 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
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:

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 <span className="text-text-muted text-xs">{nodeId}</span>;
  return (
    <button
      className="text-accent-dim hover:text-accent text-xs text-left truncate block w-full"
      onClick={() => onNavigate(nodeId)}
    >
      <span className="text-text-muted mr-1">[{node.type}]</span>
      {node.name}
    </button>
  );
}

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 (
    <div className="space-y-4 p-4">
      {/* Header */}
      <div>
        <div className="text-xs text-text-muted uppercase tracking-wider mb-1">{node.type}</div>
        <h2 className="text-lg font-serif text-text-primary">{node.name}</h2>
      </div>

      {/* Summary */}
      <p className="text-sm text-text-secondary leading-relaxed">{node.summary}</p>

      {/* Tags */}
      {node.tags.length > 0 && (
        <div className="flex flex-wrap gap-1">
          {node.tags.map((tag) => (
            <span key={tag} className="text-[10px] px-2 py-0.5 rounded-full bg-elevated border border-border-subtle text-text-muted">
              {tag}
            </span>
          ))}
        </div>
      )}

      {/* Knowledge-specific metadata */}
      {meta?.sourceUrl && (
        <div>
          <div className="text-xs text-text-muted uppercase tracking-wider mb-1">Source</div>
          <span className="text-xs text-accent-dim break-all">{meta.sourceUrl}</span>
        </div>
      )}

      {meta?.confidence !== undefined && (
        <div>
          <div className="text-xs text-text-muted uppercase tracking-wider mb-1">Confidence</div>
          <div className="flex items-center gap-2">
            <div className="flex-1 h-1.5 bg-elevated rounded-full overflow-hidden">
              <div
                className="h-full bg-accent-dim rounded-full"
                style={{ width: `${meta.confidence * 100}%` }}
              />
            </div>
            <span className="text-xs text-text-muted">{Math.round(meta.confidence * 100)}%</span>
          </div>
        </div>
      )}

      {/* Frontmatter */}
      {meta?.frontmatter && Object.keys(meta.frontmatter).length > 0 && (
        <div>
          <div className="text-xs text-text-muted uppercase tracking-wider mb-1">Frontmatter</div>
          <div className="space-y-1">
            {Object.entries(meta.frontmatter).map(([key, value]) => (
              <div key={key} className="text-xs">
                <span className="text-text-muted">{key}:</span>{" "}
                <span className="text-text-secondary">{String(value)}</span>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Backlinks */}
      {backlinks.length > 0 && (
        <div>
          <div className="text-xs text-text-muted uppercase tracking-wider mb-1">
            Backlinks ({backlinks.length})
          </div>
          <div className="space-y-0.5">
            {backlinks.map((id) => (
              <NodeLink key={id} nodeId={id} nodes={graph.nodes} onNavigate={setSelectedNode} />
            ))}
          </div>
        </div>
      )}

      {/* Outgoing */}
      {outgoing.length > 0 && (
        <div>
          <div className="text-xs text-text-muted uppercase tracking-wider mb-1">
            Outgoing Links ({outgoing.length})
          </div>
          <div className="space-y-0.5">
            {outgoing.map((id) => (
              <NodeLink key={id} nodeId={id} nodes={graph.nodes} onNavigate={setSelectedNode} />
            ))}
          </div>
        </div>
      )}
    </div>
  );
}
  • 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:

import KnowledgeInfo from "./components/KnowledgeInfo";

In the sidebar section, wrap the existing NodeInfo render:

{graph?.kind === "knowledge" ? <KnowledgeInfo /> : <NodeInfo />}
  • Step 3: Build dashboard and verify

Run: pnpm --filter @understand-anything/dashboard build Expected: Clean build

  • Step 4: Commit
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:

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 (
    <div
      className={`absolute bottom-0 left-0 right-0 bg-surface border-t border-border-subtle transition-all duration-300 z-50 ${
        isExpanded ? "h-[70vh]" : "h-[45vh]"
      }`}
    >
      {/* Header bar */}
      <div className="flex items-center justify-between px-4 py-2 border-b border-border-subtle bg-elevated">
        <div className="flex items-center gap-2">
          <span className="text-xs text-text-muted uppercase tracking-wider">Reading</span>
          <span className="text-sm font-serif text-text-primary">{node.name}</span>
        </div>
        <div className="flex items-center gap-2">
          <button
            onClick={() => setIsExpanded(!isExpanded)}
            className="text-xs text-text-muted hover:text-text-primary px-2 py-1"
          >
            {isExpanded ? "Collapse" : "Expand"}
          </button>
          <button
            onClick={() => setSelectedNode(null)}
            className="text-xs text-text-muted hover:text-text-primary px-2 py-1"
          >
            Close
          </button>
        </div>
      </div>

      <div className="flex h-[calc(100%-40px)] overflow-hidden">
        {/* Main content */}
        <div className="flex-1 overflow-y-auto p-6">
          <div className="max-w-3xl mx-auto">
            <h1 className="text-2xl font-serif text-text-primary mb-4">{node.name}</h1>

            {/* Tags */}
            {node.tags.length > 0 && (
              <div className="flex flex-wrap gap-1 mb-4">
                {node.tags.map((tag) => (
                  <span key={tag} className="text-[10px] px-2 py-0.5 rounded-full bg-elevated border border-border-subtle text-text-muted">
                    {tag}
                  </span>
                ))}
              </div>
            )}

            {/* Article content (summary for now — full markdown rendering is a future enhancement) */}
            <div className="prose prose-invert prose-sm max-w-none">
              <p className="text-text-secondary leading-relaxed">{node.summary}</p>
            </div>

            {/* Frontmatter metadata */}
            {node.knowledgeMeta?.frontmatter && Object.keys(node.knowledgeMeta.frontmatter).length > 0 && (
              <div className="mt-6 p-3 rounded-lg bg-elevated border border-border-subtle">
                <div className="text-xs text-text-muted uppercase tracking-wider mb-2">Metadata</div>
                {Object.entries(node.knowledgeMeta.frontmatter).map(([key, value]) => (
                  <div key={key} className="text-xs mb-1">
                    <span className="text-text-muted">{key}:</span>{" "}
                    <span className="text-text-secondary">{String(value)}</span>
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>

        {/* Backlinks sidebar */}
        {backlinks.length > 0 && (
          <div className="w-56 border-l border-border-subtle overflow-y-auto p-3 bg-elevated">
            <div className="text-xs text-text-muted uppercase tracking-wider mb-2">
              Backlinks ({backlinks.length})
            </div>
            <div className="space-y-1">
              {backlinks.map((link) => (
                <button
                  key={link.id}
                  className="text-xs text-accent-dim hover:text-accent text-left truncate block w-full"
                  onClick={() => setSelectedNode(link.id)}
                >
                  <span className="text-text-muted mr-1">[{link.type}]</span>
                  {link.name}
                </button>
              ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}
  • Step 2: Add ReadingPanel to App.tsx

Import and render ReadingPanel in the main dashboard layout, positioned at the bottom:

import ReadingPanel from "./components/ReadingPanel";

Add <ReadingPanel /> 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
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:

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
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:

const KNOWLEDGE_EDGE_STYLES: Record<string, React.CSSProperties> = {
  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:

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
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:

{graph.kind === "knowledge" && (
  <div className="space-y-2">
    <div className="text-xs text-text-muted uppercase tracking-wider">Knowledge Stats</div>
    <div className="grid grid-cols-2 gap-2">
      <StatBox label="Articles" value={graph.nodes.filter(n => n.type === "article").length} />
      <StatBox label="Entities" value={graph.nodes.filter(n => n.type === "entity").length} />
      <StatBox label="Topics" value={graph.nodes.filter(n => n.type === "topic").length} />
      <StatBox label="Claims" value={graph.nodes.filter(n => n.type === "claim").length} />
      <StatBox label="Sources" value={graph.nodes.filter(n => n.type === "source").length} />
    </div>
  </div>
)}

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:

{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
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:

---
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:<relative-path-without-extension>` (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:<normalized-lowercase-name>` (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:<article-path>:<short-slug>` (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:<normalized-url-or-title>`
- **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-<N>.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:<normalized-name>`
- **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

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:

---
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 <file-or-folder>]
---

# /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 <path>` — (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": "<absolute-path-to-target>"
}

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:

{
  "projectRoot": "<absolute-path>",
  "batchFiles": [...],
  "format": "<detected-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:
{
  "version": "1.0",
  "kind": "knowledge",
  "project": {
    "name": "<directory-name>",
    "languages": [],
    "frameworks": [],
    "description": "Knowledge base analyzed from <format> format",
    "analyzedAt": "<ISO-timestamp>",
    "gitCommitHash": "<hash>"
  },
  "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:
    {
      "lastAnalyzedAt": "<ISO-timestamp>",
      "gitCommitHash": "<hash>",
      "version": "1.0",
      "analyzedFiles": <count>,
      "knowledgeFormat": "<detected-format>"
    }
    
  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 <path> 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
git add -A
git commit -m "feat: complete /understand-knowledge implementation — knowledge base analysis skill"