+
+---
+
+> [!TIP]
+> **Topluluğa çok teşekkürler!** Understand-Anything'e gösterilen destek inanılmaz oldu. Bu araç sana karmaşıklığı anlamak için birkaç dakika kazandırıyorsa, istediğim tek şey buydu. 🚀
+
+**Yeni bir ekibe katıldın. Kod tabanı 200.000 satır kod. Nereden başlayacaksın bile bilemiyorsun?**
+
+Understand Anything, projenizi çok-ajan hattıyla analiz eden, her dosya, fonksiyon, sınıf ve bağımlılığın bilgi grafiğini oluşturan ve hepsini görsel olarak keşfetmen için interaktif bir kontrol paneli sunan bir [Claude Code](https://docs.anthropic.com/en/docs/claude-code) eklentisidir. Kodu körü körüne okumayı bırak. Büyük resmi görmeye başla.
+
+---
+
+## 🤔 Neden?
+
+Kod okumak zor. Bütün bir kod tabanını anlamak daha da zor. Dokümantasyon her zaman güncel değil, işe alıştırma haftalar alıyor ve her yeni özellik arkeoloji gibi hissettiriyor.
+
+Understand Anything bunu **LLM zekası** ile **statik analizi** birleştirerek çözüyor ve projenin canlı, keşfedilebilir bir haritasını üretiyor — her şey için sade Türkçe açıklamalarla.
+
+---
+
+## 🎯 Kimler için?
+
+
+
+
+
👩💻 Junior Geliştiriciler
+
Tanımadığın kodda boğulmayı bırak. Her fonksiyon ve sınıfın sade Türkçe açıklandığı, mimariyi adım adım anlatan rehberli turlar al.
+
+
+
📋 Ürün Yöneticileri ve Tasarımcılar
+
Kod okumadan sistemin gerçekte nasıl çalıştığını nihayet anla. "Kimlik doğrulama nasıl çalışır?" gibi sorular sor ve gerçek kod tabanına dayalı net cevaplar al.
+
+
+
🤖 AI Destekli Geliştiriciler
+
AI araçlarına projen hakkında derin bağlam ver. Kod incelemeden önce /understand-diff, herhangi bir modüle dalmak için /understand-explain veya mimari hakkında akıl yürütmek için /understand-chat kullan.
+
+
+
+
+---
+
+## 🚀 Hızlı Başlangıç
+
+### 1. Eklentiyi yükle
+
+```bash
+/plugin marketplace add Lum1104/Understand-Anything
+/plugin install understand-anything
+```
+
+### 2. Kod tabanını analiz et
+
+```bash
+/understand
+```
+
+Çok-ajan hattı projenizi tarar, her dosya, fonksiyon, sınıf ve bağımlılığı çıkarır, ardından `.understand-anything/knowledge-graph.json` dosyasına kaydedilen bir bilgi grafiği oluşturur.
+
+### 3. Kontrol panelini keşfet
+
+```bash
+/understand-dashboard
+```
+
+Kod tabanın bir grafik olarak görselleştirilmiş, mimari katmana göre renklendirilmiş, aranabilir ve tıklanabilir interaktif bir web kontrol paneli açılır. Kodunu, ilişkilerini ve sade Türkçe açıklamasını görmek için herhangi bir düğüm seç.
+
+### 4. Öğrenmeye devam et
+
+```bash
+# Kod tabanı hakkında her şeyi sor
+/understand-chat Ödeme akışı nasıl çalışır?
+
+# Mevcut değişikliklerinin etkisini analiz et
+/understand-diff
+
+# Belirli bir dosya veya fonksiyona derinlemesine dal
+/understand-explain src/auth/login.ts
+
+# Yeni ekip üyeleri için bir işe alıştırma rehberi oluştur
+/understand-onboard
+```
+
+---
+
+## 🌐 Çoklu Platform Kurulumu
+
+Understand-Anything birden fazla AI kodlama platformunda çalışır.
+
+### Claude Code (Yerli)
+
+```bash
+/plugin marketplace add Lum1104/Understand-Anything
+/plugin install understand-anything
+```
+
+### Codex
+
+Codex'e söyle:
+```
+Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.codex/INSTALL.md
+```
+
+### OpenCode
+
+OpenCode'a söyle:
+```
+Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.opencode/INSTALL.md
+```
+
+### OpenClaw
+
+OpenClaw'a söyle:
+```
+Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.openclaw/INSTALL.md
+```
+
+### Cursor
+
+Bu depo klonlandığında Cursor, eklentiyi `.cursor-plugin/plugin.json` aracılığıyla otomatik olarak keşfeder. Manuel kurulum gerekmez — sadece klonla ve Cursor'da aç.
+
+### Antigravity
+
+Antigravity'e söyle:
+```text
+Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.antigravity/INSTALL.md
+```
+
+### Gemini CLI
+
+Gemini CLI'a söyle:
+```text
+Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.gemini/INSTALL.md
+```
+
+### Pi Agent
+
+Pi Agent'a söyle:
+```text
+Fetch and follow instructions from https://raw.githubusercontent.com/Lum1104/Understand-Anything/refs/heads/main/.pi/INSTALL.md
+```
+
+### Platform Uyumluluğu
+
+| Platform | Durum | Kurulum Yöntemi |
+|----------|--------|----------------|
+| Claude Code | ✅ Yerli | Eklenti pazarı |
+| Codex | ✅ Destekleniyor | AI güdümlü kurulum |
+| OpenCode | ✅ Destekleniyor | AI güdümlü kurulum |
+| OpenClaw | ✅ Destekleniyor | AI güdümlü kurulum |
+| Cursor | ✅ Destekleniyor | Otomatik keşif |
+| Antigravity | ✅ Destekleniyor | AI güdümlü kurulum |
+| Gemini CLI | ✅ Destekleniyor | AI güdümlü kurulum |
+| Pi Agent | ✅ Destekleniyor | AI güdümlü kurulum |
+
+---
+
+## ✨ Özellikler
+
+
+
+
+
+
+
+
+
🗺️ İnteraktif Bilgi Grafiği
+
Dosyalar, fonksiyonlar, sınıflar ve ilişkileri React Flow ile görselleştirildi. Kodunu ve bağlantılarını görmek için herhangi bir düğüme tıkla.
+
+
+
💬 Sade Türkçe Özetler
+
Her düğüm bir LLM tarafından açıklanır, böylece herkes — teknik olsun ya da olmasın — ne yaptığını ve neden var olduğunu anlayabilir.
+
+
+
+
+
🧭 Rehberli Turlar
+
Bağımlılığa göre sıralanmış, mimarinin otomatik oluşturulmuş gözden geçirmeleri. Kod tabanını doğru sırayla öğren.
+
+
+
🔍 Bulanık ve Anlamsal Arama
+
İsme veya anlamına göre her şeyi bul. "Kimlik doğrulamayı hangi parçalar yönetiyor?" ara ve grafik boyunca ilgili sonuçları al.
+
+
+
+
+
📊 Diff Etki Analizi
+
Değişikliklerinin sistemin hangi bölümlerini etkilediğini commit etmeden önce gör. Kod tabanı boyunca dalgalanma etkilerini anla.
+
+
+
🎭 Kişiye Uyarlanabilir UI
+
Kontrol paneli, kim olduğuna göre ayrıntı seviyesini ayarlar — junior geliştirici, ürün yöneticisi veya güçlü kullanıcı.
+
+
+
+
+
🏗️ Katman Görselleştirmesi
+
Mimari katmana göre otomatik gruplama — API, Servis, Veri, UI, Yardımcı — renk kodlu efsaneyle.
+
+
+
📚 Dil Kavramları
+
12 programlama deseni (generikler, kapanışlar, dekoratörler, vb.) göründükleri her yerde bağlam içinde açıklanır.
+
+
+
+
+---
+
+## 🔧 Kaputun Altında
+
+### Çok-Ajan Hattı
+
+`/understand` komutu 5 özel ajan düzenler:
+
+| Ajan | Rol |
+|-------|------|
+| `project-scanner` | Dosyaları keşfet, dilleri ve çerçeveleri tespit et |
+| `file-analyzer` | Fonksiyonları, sınıfları, içe aktarmaları çıkar; grafik düğümleri ve kenarları üret |
+| `architecture-analyzer` | Mimari katmanları tanımla |
+| `tour-builder` | Rehberli öğrenme turları oluştur |
+| `graph-reviewer` | Grafik bütünlüğünü ve referans bütünlüğünü doğrula |
+
+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.
+
+### Proje Yapısı
+
+```
+understand-anything-plugin/
+ .claude-plugin/ — Eklenti manifestosu
+ agents/ — Özel AI ajanları
+ skills/ — Yetenek tanımları (/understand, /understand-chat, vb.)
+ src/ — TypeScript kaynağı (context-builder, diff-analyzer, vb.)
+ packages/
+ core/ — Analiz motoru (tipler, kalıcılık, tree-sitter, arama, şema, turlar)
+ dashboard/ — React + TypeScript web kontrol paneli
+```
+
+### Teknoloji Yığını
+
+TypeScript, pnpm workspaces, React 18, Vite, TailwindCSS v4, React Flow, Zustand, web-tree-sitter, Fuse.js, Zod, Dagre
+
+### Geliştirme Komutları
+
+| Komut | Açıklama |
+|---------|-------------|
+| `pnpm install` | Tüm bağımlılıkları yükle |
+| `pnpm --filter @understand-anything/core build` | Core paketini derle |
+| `pnpm --filter @understand-anything/core test` | Core testlerini çalıştır |
+| `pnpm --filter @understand-anything/skill build` | Eklenti paketini derle |
+| `pnpm --filter @understand-anything/skill test` | Eklenti testlerini çalıştır |
+| `pnpm --filter @understand-anything/dashboard build` | Kontrol panelini derle |
+| `pnpm dev:dashboard` | Kontrol paneli geliştirme sunucusunu başlat |
+
+---
+
+## 🤝 Katkıda Bulunma
+
+Katkılar memnuniyetle karşılanır! Başlamak için:
+
+1. Depoyu fork'la
+2. Bir özellik dalı oluştur (`git checkout -b feature/benim-ozellligim`)
+3. Testleri çalıştır (`pnpm --filter @understand-anything/core test`)
+4. Değişikliklerini commit et ve bir pull request aç
+
+Büyük değişiklikler için lütfen önce bir issue aç ki yaklaşımı tartışalım.
+
+---
+
+
+ Kodu körü körüne okumayı bırak. Her şeyi anlamaya başla.
+
+ );
+}
diff --git a/understand-anything-plugin/packages/dashboard/src/hooks/useKeyboardShortcuts.ts b/understand-anything-plugin/packages/dashboard/src/hooks/useKeyboardShortcuts.ts
new file mode 100644
index 0000000..fcf91e3
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/hooks/useKeyboardShortcuts.ts
@@ -0,0 +1,71 @@
+import { useEffect } from "react";
+
+export interface KeyboardShortcut {
+ key: string;
+ ctrlKey?: boolean;
+ shiftKey?: boolean;
+ altKey?: boolean;
+ metaKey?: boolean;
+ description: string;
+ action: () => void;
+ category: string;
+}
+
+export function useKeyboardShortcuts(
+ shortcuts: KeyboardShortcut[],
+ enabled = true
+) {
+ useEffect(() => {
+ if (!enabled) return;
+
+ const handleKeyDown = (event: KeyboardEvent) => {
+ // Prevent shortcuts from firing when typing in input fields
+ const target = event.target as HTMLElement;
+ const tagName = target.tagName.toLowerCase();
+ if (tagName === 'input' || tagName === 'textarea' || target.isContentEditable) {
+ if (event.key !== 'Escape') return;
+ }
+
+ for (const shortcut of shortcuts) {
+ const keyMatches = event.key.toLowerCase() === shortcut.key.toLowerCase();
+ const ctrlMatches = shortcut.ctrlKey ? event.ctrlKey : !event.ctrlKey;
+ const shiftMatches = shortcut.shiftKey ? event.shiftKey : !event.shiftKey;
+ const altMatches = shortcut.altKey ? event.altKey : !event.altKey;
+ const metaMatches = shortcut.metaKey ? event.metaKey : !event.metaKey;
+
+ if (keyMatches && ctrlMatches && shiftMatches && altMatches && metaMatches) {
+ // Prevent default for shortcuts that might conflict with browser
+ if (event.ctrlKey || event.metaKey || event.altKey) {
+ event.preventDefault();
+ }
+ shortcut.action();
+ break;
+ }
+ }
+ };
+
+ document.addEventListener("keydown", handleKeyDown);
+ return () => document.removeEventListener("keydown", handleKeyDown);
+ }, [shortcuts, enabled]);
+}
+
+export function formatShortcutKey(shortcut: KeyboardShortcut): string {
+ const keys: string[] = [];
+
+ // Use userAgentData with fallback to navigator.platform
+ const isMac = (navigator as Navigator & { userAgentData?: { platform: string } }).userAgentData?.platform
+ ? (navigator as Navigator & { userAgentData: { platform: string } }).userAgentData.platform === 'macOS'
+ : navigator.platform.includes("Mac");
+
+ if (shortcut.ctrlKey || shortcut.metaKey) {
+ keys.push(isMac ? "⌘" : "Ctrl");
+ }
+ // Don't show ⇧ for keys that inherently require Shift (e.g. ?, !, @)
+ const isShiftedPunctuation = shortcut.key.length === 1 && /[^a-zA-Z0-9]/.test(shortcut.key);
+ if (shortcut.shiftKey && !isShiftedPunctuation) keys.push("⇧");
+ if (shortcut.altKey) keys.push(isMac ? "⌥" : "Alt");
+
+ keys.push(isShiftedPunctuation ? shortcut.key : shortcut.key.toUpperCase());
+
+ return keys.join(" + ");
+}
diff --git a/understand-anything-plugin/packages/dashboard/src/index.css b/understand-anything-plugin/packages/dashboard/src/index.css
index a767a27..a0f8ffa 100644
--- a/understand-anything-plugin/packages/dashboard/src/index.css
+++ b/understand-anything-plugin/packages/dashboard/src/index.css
@@ -71,6 +71,31 @@ body {
-webkit-backdrop-filter: blur(12px);
}
+.glass-heavy {
+ background: rgba(20, 20, 20, 0.95);
+ border: 1px solid rgba(212, 165, 116, 0.15);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+}
+
+/* Keyboard shortcut key styling */
+.kbd {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 1.75rem;
+ height: 1.75rem;
+ padding: 0 0.5rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: var(--color-gold);
+ background: rgba(212, 165, 116, 0.1);
+ border: 1px solid rgba(212, 165, 116, 0.3);
+ border-radius: 0.25rem;
+ box-shadow: 0 1px 0 rgba(212, 165, 116, 0.2);
+}
+
/* Animation keyframes */
@keyframes fadeSlideIn {
from {
diff --git a/understand-anything-plugin/packages/dashboard/src/utils/layout.ts b/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
index e7ff519..548ebfc 100644
--- a/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
+++ b/understand-anything-plugin/packages/dashboard/src/utils/layout.ts
@@ -1,6 +1,7 @@
import dagre from "@dagrejs/dagre";
import type { Node, Edge } from "@xyflow/react";
import type { KnowledgeGraph } from "@understand-anything/core/types";
+import type { LayoutMessage, LayoutResult } from "./layout.worker";
export const NODE_WIDTH = 280;
export const NODE_HEIGHT = 120;
@@ -15,6 +16,9 @@ export const LANE_GAP = 40;
export const LANE_PADDING = 30;
export const LANE_HEADER_HEIGHT = 40;
+/**
+ * Synchronous dagre layout — used for small graphs.
+ */
export function applyDagreLayout(
nodes: Node[],
edges: Edge[],
@@ -65,10 +69,86 @@ export function applyDagreLayout(
return { nodes: layoutedNodes, edges };
}
+// ── Async layout via Web Worker ────────────────────────────────────────
+
+let _worker: Worker | null = null;
+let _nextRequestId = 0;
+let _latestRequestId = -1;
+const _pending = new Map<
+ number,
+ {
+ nodes: Node[];
+ edges: Edge[];
+ resolve: (v: { nodes: Node[]; edges: Edge[] }) => void;
+ reject: (reason?: unknown) => void;
+ }
+>();
+
+function getWorker(): Worker {
+ if (!_worker) {
+ _worker = new Worker(
+ new URL("./layout.worker.ts", import.meta.url),
+ { type: "module" },
+ );
+
+ _worker.onmessage = (e: MessageEvent) => {
+ const { requestId, positions } = e.data;
+ const entry = _pending.get(requestId);
+ _pending.delete(requestId);
+
+ // Discard stale results — only honour the latest request.
+ if (!entry || requestId !== _latestRequestId) return;
+
+ const layoutedNodes = entry.nodes.map((node) => ({
+ ...node,
+ position: positions[node.id] ?? { x: 0, y: 0 },
+ }));
+
+ entry.resolve({ nodes: layoutedNodes, edges: entry.edges });
+ };
+
+ _worker.onerror = (err: ErrorEvent) => {
+ for (const [, entry] of _pending) {
+ entry.reject(err);
+ }
+ _pending.clear();
+ };
+ }
+ return _worker;
+}
+
+/**
+ * Async dagre layout via Web Worker — used for large graphs.
+ * Keeps the main thread responsive while dagre computes positions.
+ */
+export function applyDagreLayoutAsync(
+ nodes: Node[],
+ edges: Edge[],
+ direction: "TB" | "LR" = "TB",
+): Promise<{ nodes: Node[]; edges: Edge[] }> {
+ return new Promise((resolve, reject) => {
+ const worker = getWorker();
+ const requestId = _nextRequestId++;
+ _latestRequestId = requestId;
+
+ _pending.set(requestId, { nodes, edges, resolve, reject });
+
+ const msg: LayoutMessage = {
+ requestId,
+ nodes: nodes.map((n) => ({ id: n.id, width: NODE_WIDTH, height: NODE_HEIGHT })),
+ edges: edges.map((e) => ({ source: e.source, target: e.target })),
+ direction,
+ };
+
+ worker.postMessage(msg);
+ });
+}
+
+// ── Swim-lane layout ───────────────────────────────────────────────────
+
/**
* Preferred order of layers for the swim-lane flow view.
* Reflects a typical request lifecycle: entry → middleware → logic → data → external.
- * Layers not in this list are appended at the end.
*/
const LAYER_FLOW_ORDER = [
"API Layer",
@@ -89,11 +169,8 @@ function getLayerSortIndex(layerName: string): number {
}
export interface SwimLaneResult {
- /** All nodes: lane background groups + file nodes positioned inside them */
nodes: Node[];
- /** Cross-lane and intra-lane edges */
edges: Edge[];
- /** Ordered layer info for reference */
lanes: Array<{ layerId: string; layerName: string; columnIndex: number }>;
}
@@ -107,12 +184,10 @@ export function applySwimLaneLayout(
fileNodes: Node[],
allEdges: Edge[],
): SwimLaneResult {
- // Sort layers by flow order
const sortedLayers = [...graph.layers].sort(
(a, b) => getLayerSortIndex(a.name) - getLayerSortIndex(b.name),
);
- // Build nodeId → layer mapping
const nodeToLayerId = new Map();
for (const layer of sortedLayers) {
for (const nid of layer.nodeIds) {
@@ -120,7 +195,6 @@ export function applySwimLaneLayout(
}
}
- // Group file nodes by layer
const nodesByLayer = new Map();
for (const layer of sortedLayers) {
nodesByLayer.set(layer.id, []);
@@ -132,8 +206,6 @@ export function applySwimLaneLayout(
}
}
- // For each lane, use dagre to compute y-positions of its nodes
- // (treating the lane as a vertical sub-graph)
const laneHeights = new Map();
for (const [layerId, nodes] of nodesByLayer) {
@@ -144,20 +216,13 @@ export function applySwimLaneLayout(
const g = new dagre.graphlib.Graph();
g.setDefaultEdgeLabel(() => ({}));
- g.setGraph({
- rankdir: "TB",
- nodesep: 20,
- ranksep: 40,
- marginx: 0,
- marginy: 0,
- });
+ g.setGraph({ rankdir: "TB", nodesep: 20, ranksep: 40, marginx: 0, marginy: 0 });
const laneNodeIds = new Set(nodes.map((n) => n.id));
for (const node of nodes) {
g.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
}
- // Only include edges within this lane for vertical ordering
for (const edge of allEdges) {
if (laneNodeIds.has(edge.source) && laneNodeIds.has(edge.target)) {
g.setEdge(edge.source, edge.target);
@@ -166,12 +231,10 @@ export function applySwimLaneLayout(
dagre.layout(g);
- // Read dagre's y-positions and assign relative positions within the lane
let maxY = 0;
for (const node of nodes) {
const pos = g.node(node.id);
if (pos) {
- // Store the y position in the node's data for later use
(node as Node & { _laneY: number })._laneY = pos.y - NODE_HEIGHT / 2;
maxY = Math.max(maxY, pos.y + NODE_HEIGHT / 2);
}
@@ -180,13 +243,8 @@ export function applySwimLaneLayout(
laneHeights.set(layerId, LANE_HEADER_HEIGHT + maxY + LANE_PADDING * 2);
}
- // Find the tallest lane so all lanes are the same height
- const maxLaneHeight = Math.max(
- 200,
- ...Array.from(laneHeights.values()),
- );
+ const maxLaneHeight = Math.max(200, ...Array.from(laneHeights.values()));
- // Build lane group nodes and position file nodes as children
const resultNodes: Node[] = [];
const lanes: SwimLaneResult["lanes"] = [];
@@ -194,13 +252,8 @@ export function applySwimLaneLayout(
const laneX = colIdx * (LANE_WIDTH + LANE_GAP);
const laneId = `lane:${layer.id}`;
- lanes.push({
- layerId: layer.id,
- layerName: layer.name,
- columnIndex: colIdx,
- });
+ lanes.push({ layerId: layer.id, layerName: layer.name, columnIndex: colIdx });
- // Lane background group node
resultNodes.push({
id: laneId,
type: "group",
@@ -216,7 +269,6 @@ export function applySwimLaneLayout(
},
});
- // Position file nodes within this lane
const nodes = nodesByLayer.get(layer.id) ?? [];
for (const node of nodes) {
const laneY = (node as Node & { _laneY?: number })._laneY ?? 0;
@@ -229,7 +281,6 @@ export function applySwimLaneLayout(
y: LANE_HEADER_HEIGHT + LANE_PADDING + laneY,
},
});
- // Clean up temp property
delete (node as Node & { _laneY?: number })._laneY;
}
});
diff --git a/understand-anything-plugin/packages/dashboard/src/utils/layout.worker.ts b/understand-anything-plugin/packages/dashboard/src/utils/layout.worker.ts
new file mode 100644
index 0000000..4f466f3
--- /dev/null
+++ b/understand-anything-plugin/packages/dashboard/src/utils/layout.worker.ts
@@ -0,0 +1,47 @@
+import dagre from "@dagrejs/dagre";
+
+export interface LayoutMessage {
+ requestId: number;
+ nodes: Array<{ id: string; width: number; height: number }>;
+ edges: Array<{ source: string; target: string }>;
+ direction: "TB" | "LR";
+}
+
+export interface LayoutResult {
+ requestId: number;
+ positions: Record;
+}
+
+self.onmessage = (e: MessageEvent) => {
+ const { requestId, nodes, edges, direction } = e.data;
+
+ const g = new dagre.graphlib.Graph();
+ g.setDefaultEdgeLabel(() => ({}));
+ g.setGraph({
+ rankdir: direction,
+ nodesep: 60,
+ ranksep: 80,
+ marginx: 20,
+ marginy: 20,
+ });
+
+ for (const node of nodes) {
+ g.setNode(node.id, { width: node.width, height: node.height });
+ }
+
+ for (const edge of edges) {
+ g.setEdge(edge.source, edge.target);
+ }
+
+ dagre.layout(g);
+
+ const positions: Record = {};
+ for (const node of nodes) {
+ const pos = g.node(node.id);
+ positions[node.id] = pos
+ ? { x: pos.x - node.width / 2, y: pos.y - node.height / 2 }
+ : { x: 0, y: 0 };
+ }
+
+ self.postMessage({ requestId, positions } satisfies LayoutResult);
+};
diff --git a/understand-anything-plugin/skills/understand-dashboard/SKILL.md b/understand-anything-plugin/skills/understand-dashboard/SKILL.md
index ec0710d..e0614d7 100644
--- a/understand-anything-plugin/skills/understand-dashboard/SKILL.md
+++ b/understand-anything-plugin/skills/understand-dashboard/SKILL.md
@@ -19,18 +19,40 @@ Start the Understand Anything dashboard to visualize the knowledge graph for the
No knowledge graph found. Run /understand first to analyze this project.
```
-3. Find the dashboard code. The dashboard is at `packages/dashboard/` relative to this plugin's root directory. Use the Bash tool to resolve the path:
- ```bash
- PLUGIN_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
- ```
- Or locate it by checking these paths in order:
- - `${CLAUDE_PLUGIN_ROOT}/packages/dashboard/`
- - The parent directory of this skill file, then `../../packages/dashboard/`
+3. Find the dashboard code. The dashboard is at `packages/dashboard/` relative to this plugin's root directory. Check these paths in order and use the first that exists:
+ - `~/.understand-anything-plugin/packages/dashboard/` (universal symlink, all installs)
+ - `${CLAUDE_PLUGIN_ROOT}/packages/dashboard/` (Claude Code plugin)
+ - Two levels up from this skill file's real path: `../../packages/dashboard/` (self-relative fallback)
-4. Install dependencies if needed:
+ Use the Bash tool to resolve:
+ ```bash
+ SKILL_REAL=$(realpath ~/.agents/skills/understand-dashboard 2>/dev/null || readlink -f ~/.agents/skills/understand-dashboard 2>/dev/null || echo "")
+ SELF_RELATIVE=$([ -n "$SKILL_REAL" ] && cd "$SKILL_REAL/../.." 2>/dev/null && pwd || echo "")
+
+ PLUGIN_ROOT=""
+ for candidate in \
+ "$HOME/.understand-anything-plugin" \
+ "${CLAUDE_PLUGIN_ROOT}" \
+ "$SELF_RELATIVE"; do
+ if [ -n "$candidate" ] && [ -d "$candidate/packages/dashboard" ]; then
+ PLUGIN_ROOT="$candidate"; break
+ fi
+ done
+
+ if [ -z "$PLUGIN_ROOT" ]; then
+ echo "Error: Cannot find the understand-anything plugin root. Make sure you followed the installation instructions and that ~/.understand-anything-plugin exists."
+ exit 1
+ fi
+ ```
+
+4. Install dependencies and build if needed:
```bash
cd && pnpm install --frozen-lockfile 2>/dev/null || pnpm install
```
+ Then ensure the core package is built (the dashboard depends on it):
+ ```bash
+ cd && pnpm --filter @understand-anything/core build
+ ```
5. Start the Vite dev server pointing at the project's knowledge graph:
```bash
diff --git a/understand-anything-plugin/skills/understand/SKILL.md b/understand-anything-plugin/skills/understand/SKILL.md
index 3954054..46d9b7a 100644
--- a/understand-anything-plugin/skills/understand/SKILL.md
+++ b/understand-anything-plugin/skills/understand/SKILL.md
@@ -20,29 +20,19 @@ Analyze the current codebase and produce a `knowledge-graph.json` file in `.unde
Determine whether to run a full analysis or incremental update.
-1. **STOP and ask the user where temporary scripts and intermediate files should be written.** You MUST wait for the user's response before proceeding to step 2 or any other step. Do NOT read prompt templates, launch subagents, or begin any analysis until this question is answered.
-
- Ask the user:
- > Where should I write temporary scripts and intermediate files during analysis?
- > 1. **Project directory** (recommended): `.understand-anything/tmp/` — stays within the project
- > 2. **System temp**: `/tmp/` — may require permissions outside the project
-
- After the user responds, store the chosen path as `$TMP_DIR`. Create it: `mkdir -p $TMP_DIR`.
-
- All subagent prompts reference `$TMP_DIR` for script files and intermediate JSON. When dispatching subagents, replace any `/tmp/ua-` paths in the prompt templates with `$TMP_DIR/ua-` so scripts and results are written to the user's chosen location.
-
-2. Set `PROJECT_ROOT` to the current working directory.
-3. Get the current git commit hash:
+1. Set `PROJECT_ROOT` to the current working directory.
+2. Get the current git commit hash:
```bash
git rev-parse HEAD
```
-4. Create the intermediate output directory:
+3. Create the intermediate and temp output directories:
```bash
mkdir -p $PROJECT_ROOT/.understand-anything/intermediate
+ mkdir -p $PROJECT_ROOT/.understand-anything/tmp
```
-5. Check if `$PROJECT_ROOT/.understand-anything/knowledge-graph.json` exists. If it does, read it.
-6. Check if `$PROJECT_ROOT/.understand-anything/meta.json` exists. If it does, read it to get `gitCommitHash`.
-7. **Decision logic:**
+4. Check if `$PROJECT_ROOT/.understand-anything/knowledge-graph.json` exists. If it does, read it.
+5. Check if `$PROJECT_ROOT/.understand-anything/meta.json` exists. If it does, read it to get `gitCommitHash`.
+6. **Decision logic:**
| Condition | Action |
|---|---|
@@ -57,7 +47,7 @@ Determine whether to run a full analysis or incremental update.
```
If this returns no files, report "Graph is up to date" and STOP.
-8. **Collect project context for subagent injection:**
+7. **Collect project context for subagent injection:**
- Read `README.md` (or `README.rst`, `readme.md`) from `$PROJECT_ROOT` if it exists. Store as `$README_CONTENT` (first 3000 characters).
- Read the primary package manifest (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `pom.xml`) if it exists. Store as `$MANIFEST_CONTENT`.
- Capture the top-level directory tree:
@@ -113,8 +103,8 @@ For each batch, dispatch a subagent using the prompt template at `./file-analyze
**Build the combined prompt template:**
1. Read the base template at `./file-analyzer-prompt.md`.
-2. **Language context injection:** For each language detected in Phase 1 (e.g., `python`), read the file at `./languages/.md` (e.g., `./languages/python.md`) and append its content after the base template under a `## Language Context` header. These files are in the `languages/` subdirectory next to this SKILL.md file. Use `ls ./languages/` to discover available language files if needed.
-3. **Framework addendum injection:** For each framework detected in Phase 1 (e.g., `Django`), read the file at `./frameworks/.md` (e.g., `./frameworks/django.md`) and append its full content after the language context. These files are in the `frameworks/` subdirectory next to this SKILL.md file. Use `ls ./frameworks/` to discover available framework files if needed.
+2. **Language context injection:** For each language detected in Phase 1 (e.g., `python`), read the file at `./languages/.md` (e.g., `./languages/python.md`) and append its content after the base template under a `## Language Context` header. If the file does not exist for a detected language, skip it silently and continue. These files are in the `languages/` subdirectory next to this SKILL.md file. Use `ls ./languages/` to discover available language files if needed.
+3. **Framework addendum injection:** For each framework detected in Phase 1 (e.g., `Django`), read the file at `./frameworks/.md` (e.g., `./frameworks/django.md`) and append its full content after the language context. If the file does not exist for a detected framework, skip it silently and continue. These files are in the `frameworks/` subdirectory next to this SKILL.md file. Use `ls ./frameworks/` to discover available framework files if needed.
Then for each batch pass the combined template content as the subagent's prompt, appending the following additional context:
@@ -172,8 +162,8 @@ Merge all file-analyzer results into a single set of nodes and edges. Then perfo
**Build the combined prompt template:**
1. Read the base template at `./architecture-analyzer-prompt.md`.
-2. **Language context injection:** For each language detected in Phase 1 (e.g., `python`), read the file at `./languages/.md` (e.g., `./languages/python.md`) and append its content after the base template under a `## Language Context` header. These files are in the `languages/` subdirectory next to this SKILL.md file.
-3. **Framework addendum injection:** For each framework detected in Phase 1 (e.g., `Django`), read the file at `./frameworks/.md` (e.g., `./frameworks/django.md`) and append its full content after the language context. These files are in the `frameworks/` subdirectory next to this SKILL.md file.
+2. **Language context injection:** For each language detected in Phase 1 (e.g., `python`), read the file at `./languages/.md` (e.g., `./languages/python.md`) and append its content after the base template under a `## Language Context` header. If the file does not exist for a detected language, skip it silently and continue. These files are in the `languages/` subdirectory next to this SKILL.md file.
+3. **Framework addendum injection:** For each framework detected in Phase 1 (e.g., `Django`), read the file at `./frameworks/.md` (e.g., `./frameworks/django.md`) and append its full content after the language context. If the file does not exist for a detected framework, skip it silently and continue. These files are in the `frameworks/` subdirectory next to this SKILL.md file.
Pass the combined content as the subagent's prompt, appending the following additional context:
@@ -205,13 +195,15 @@ Pass these parameters in the dispatch prompt:
> [list of edges with type "imports"]
> ```
-After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/layers.json` to get the layer assignments.
+After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/layers.json` and normalize it into a final `layers` array. Apply these steps **in order**:
-`layers.json` may be either:
-- a top-level JSON array of layer objects, or
-- an envelope object such as `{ "layers": [...] }` from the current prompt/template output
+1. **Unwrap envelope:** If the file contains `{ "layers": [...] }` instead of a plain array, extract the inner array. (The prompt requests a plain array, but LLMs may still produce an envelope.)
+2. **Rename legacy fields:** If any layer object has a `nodes` field instead of `nodeIds`, rename `nodes` → `nodeIds`. If `nodes` entries are objects with an `id` field rather than plain strings, extract just the `id` values into `nodeIds`.
+3. **Synthesize missing IDs:** If any layer is missing an `id`, generate one as `layer:`.
+4. **Convert file paths:** If `nodeIds` entries are raw file paths (not prefixed with `file:`), convert them to `file:`.
+5. **Drop dangling refs:** Remove any `nodeIds` entries that do not exist in the merged node set.
-Normalize either form into a final top-level `layers` array before assembling the graph. Each final saved layer object MUST match this exact shape:
+Each element of the final `layers` array MUST have this shape:
```json
[
@@ -224,13 +216,7 @@ Normalize either form into a final top-level `layers` array before assembling th
]
```
-Rules:
-- `id` is required and must be unique
-- `nodeIds` is required and must contain graph node IDs, not raw file paths
-- If the intermediate output is an envelope object, unwrap its `layers` array before any other normalization
-- If the subagent returns file paths, convert them to file node IDs before assembling the final graph
-- Drop any `nodeIds` that do not exist in the merged node set
-- Do not use a `nodes` field in the final saved layer objects
+All four fields (`id`, `name`, `description`, `nodeIds`) are required.
**For incremental updates:** Always re-run architecture analysis on the full merged node set, since layer assignments may shift when files change.
@@ -283,13 +269,15 @@ Pass these parameters in the dispatch prompt:
> [imports and calls edges]
> ```
-After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/tour.json` to get the tour steps.
+After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/tour.json` and normalize it into a final `tour` array. Apply these steps **in order**:
-`tour.json` may be either:
-- a top-level JSON array of tour step objects, or
-- an envelope object such as `{ "steps": [...] }` from the current prompt/template output
+1. **Unwrap envelope:** If the file contains `{ "steps": [...] }` instead of a plain array, extract the inner array. (The prompt requests a plain array, but LLMs may still produce an envelope.)
+2. **Rename legacy fields:** If any step has `nodesToInspect` instead of `nodeIds`, rename it → `nodeIds`. If any step has `whyItMatters` instead of `description`, rename it → `description`.
+3. **Convert file paths:** If `nodeIds` entries are raw file paths, convert them to `file:`.
+4. **Drop dangling refs:** Remove any `nodeIds` entries that do not exist in the merged node set.
+5. **Sort** by `order` before saving.
-Normalize either form into a final top-level `tour` array before assembling the graph. Each final saved tour step object MUST match this exact shape:
+Each element of the final `tour` array MUST have this shape:
```json
[
@@ -302,31 +290,7 @@ Normalize either form into a final top-level `tour` array before assembling the
]
```
-Rules:
-- If the intermediate output is an envelope object, unwrap its `steps` array before any other normalization
-- `description` is required; do not use `whyItMatters` in the final saved tour steps
-- `nodeIds` is required; do not use `nodesToInspect` in the final saved tour steps
-- `nodeIds` must reference existing graph node IDs
-- Preserve optional `languageLesson` when present
-- Sort by `order` before saving
-
----
-
-## Phase 5.5 — NORMALIZE
-
-Before assembling the final graph:
-
-- Unwrap legacy or prompt-shaped envelopes before field renaming:
- - `{ "layers": [...] }` -> use the contained array as the working `layers` value
- - `{ "steps": [...] }` -> use the contained array as the working `tour` value
-- Convert any layer `nodes` field to `nodeIds`
-- Convert any tour `nodesToInspect` field to `nodeIds`
-- Convert any tour `whyItMatters` field to `description`
-- If layers or tour reference file paths, map them to file node IDs using the `file:` convention
-- Synthesize missing layer IDs as `layer:`
-- Drop unresolved layer and tour node references
-- Ensure the final `layers` value is an array of `{ id, name, description, nodeIds }`
-- Ensure the final `tour` value is an array of `{ order, title, description, nodeIds }`, preserving optional `languageLesson`
+Required fields: `order`, `title`, `description`, `nodeIds`. Preserve optional `languageLesson` when present.
---
@@ -416,7 +380,7 @@ Pass these parameters in the dispatch prompt:
3. Clean up intermediate files:
```bash
rm -rf $PROJECT_ROOT/.understand-anything/intermediate
- rm -rf $TMP_DIR
+ rm -rf $PROJECT_ROOT/.understand-anything/tmp
```
4. Report a summary to the user containing:
diff --git a/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md b/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md
index e7b1158..fb4dca4 100644
--- a/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md
+++ b/understand-anything-plugin/skills/understand/architecture-analyzer-prompt.md
@@ -164,7 +164,7 @@ For each pair of groups with imports between them, determine the dominant direct
Before writing the script, create its input JSON file:
```bash
-cat > /tmp/ua-arch-input.json << 'ENDJSON'
+cat > $PROJECT_ROOT/.understand-anything/tmp/ua-arch-input.json << 'ENDJSON'
{
"fileNodes": [],
"importEdges": []
@@ -177,7 +177,7 @@ ENDJSON
After writing the script, execute it:
```bash
-node /tmp/ua-arch-analyze.js /tmp/ua-arch-input.json /tmp/ua-arch-results.json
+node $PROJECT_ROOT/.understand-anything/tmp/ua-arch-analyze.js $PROJECT_ROOT/.understand-anything/tmp/ua-arch-input.json $PROJECT_ROOT/.understand-anything/tmp/ua-arch-results.json
```
If the script exits with a non-zero code, read stderr, diagnose the issue, fix the script, and re-run. You have up to 2 retry attempts.
@@ -186,7 +186,7 @@ If the script exits with a non-zero code, read stderr, diagnose the issue, fix t
## Phase 2 -- Semantic Layer Assignment
-After the script completes, read `/tmp/ua-arch-results.json`. Use the structural analysis as the primary input for your layer decisions. Do NOT re-read source files or re-analyze imports -- trust the script's results entirely.
+After the script completes, read `$PROJECT_ROOT/.understand-anything/tmp/ua-arch-results.json`. Use the structural analysis as the primary input for your layer decisions. Do NOT re-read source files or re-analyze imports -- trust the script's results entirely.
### Step 1 -- Evaluate Directory Groups as Layer Candidates
diff --git a/understand-anything-plugin/skills/understand/file-analyzer-prompt.md b/understand-anything-plugin/skills/understand/file-analyzer-prompt.md
index c948b1f..15f90be 100644
--- a/understand-anything-plugin/skills/understand/file-analyzer-prompt.md
+++ b/understand-anything-plugin/skills/understand/file-analyzer-prompt.md
@@ -111,7 +111,7 @@ The script must write this exact JSON structure to the output file:
Before writing the script, create its input JSON file. **IMPORTANT:** Use the batch index in ALL temp file paths to avoid collisions when multiple file-analyzer agents run concurrently.
```bash
-cat > /tmp/ua-file-analyzer-input-.json << 'ENDJSON'
+cat > $PROJECT_ROOT/.understand-anything/tmp/ua-file-analyzer-input-.json << 'ENDJSON'
{
"projectRoot": "",
"allProjectFiles": [],
@@ -126,9 +126,9 @@ After writing the script, execute it. **Use the batch index in every temp file p
```bash
# For Node.js scripts:
-node /tmp/ua-file-extract-.js /tmp/ua-file-analyzer-input-.json /tmp/ua-file-extract-results-.json
+node $PROJECT_ROOT/.understand-anything/tmp/ua-file-extract-.js $PROJECT_ROOT/.understand-anything/tmp/ua-file-analyzer-input-.json $PROJECT_ROOT/.understand-anything/tmp/ua-file-extract-results-.json
# For Python scripts:
-python3 /tmp/ua-file-extract-.py /tmp/ua-file-analyzer-input-.json /tmp/ua-file-extract-results-.json
+python3 $PROJECT_ROOT/.understand-anything/tmp/ua-file-extract-.py $PROJECT_ROOT/.understand-anything/tmp/ua-file-analyzer-input-.json $PROJECT_ROOT/.understand-anything/tmp/ua-file-extract-results-.json
```
If the script exits with a non-zero code, read stderr, diagnose the issue, fix the script, and re-run. You have up to 2 retry attempts.
@@ -137,7 +137,7 @@ If the script exits with a non-zero code, read stderr, diagnose the issue, fix t
## Phase 2 -- Semantic Analysis
-After the script completes, read `/tmp/ua-file-extract-results-.json`. Use these structured results as the foundation for your analysis. Do NOT re-read the source files unless the script skipped a file or you need to understand a specific code pattern that the script could not capture.
+After the script completes, read `$PROJECT_ROOT/.understand-anything/tmp/ua-file-extract-results-.json`. Use these structured results as the foundation for your analysis. Do NOT re-read the source files unless the script skipped a file or you need to understand a specific code pattern that the script could not capture.
For each file in the script's `results` array, produce `GraphNode` and `GraphEdge` objects by combining the script's structural data with your expert judgment.
diff --git a/understand-anything-plugin/skills/understand/graph-reviewer-prompt.md b/understand-anything-plugin/skills/understand/graph-reviewer-prompt.md
index f141521..ed6d3f6 100644
--- a/understand-anything-plugin/skills/understand/graph-reviewer-prompt.md
+++ b/understand-anything-plugin/skills/understand/graph-reviewer-prompt.md
@@ -134,7 +134,7 @@ The script must write this exact JSON structure to the output file:
After writing the script, execute it:
```bash
-node /tmp/ua-graph-validate.js "" "/tmp/ua-review-results.json"
+node $PROJECT_ROOT/.understand-anything/tmp/ua-graph-validate.js "" "$PROJECT_ROOT/.understand-anything/tmp/ua-review-results.json"
```
If the script exits with a non-zero code, read stderr, diagnose the issue, fix the script, and re-run. You have up to 2 retry attempts.
@@ -143,7 +143,7 @@ If the script exits with a non-zero code, read stderr, diagnose the issue, fix t
## Phase 2 -- Review and Decision
-After the script completes, read `/tmp/ua-review-results.json`. Do NOT re-read the original graph file -- trust the script's results entirely.
+After the script completes, read `$PROJECT_ROOT/.understand-anything/tmp/ua-review-results.json`. Do NOT re-read the original graph file -- trust the script's results entirely.
Review the `issues` and `warnings` arrays and render your decision:
diff --git a/understand-anything-plugin/skills/understand/project-scanner-prompt.md b/understand-anything-plugin/skills/understand/project-scanner-prompt.md
index 25ae5c9..b80de31 100644
--- a/understand-anything-plugin/skills/understand/project-scanner-prompt.md
+++ b/understand-anything-plugin/skills/understand/project-scanner-prompt.md
@@ -141,7 +141,7 @@ The script must write this exact JSON structure to the output file:
After writing the script, execute it:
```bash
-node /tmp/ua-project-scan.js "" "/tmp/ua-scan-results.json"
+node $PROJECT_ROOT/.understand-anything/tmp/ua-project-scan.js "" "$PROJECT_ROOT/.understand-anything/tmp/ua-scan-results.json"
```
(Or the equivalent for bash/Python, depending on which language you chose.)
@@ -152,7 +152,7 @@ If the script exits with a non-zero code, read stderr, diagnose the issue, fix t
## Phase 2 -- Description and Final Assembly
-After the script completes, read `/tmp/ua-scan-results.json`. Do NOT re-run file discovery commands or re-count lines -- trust the script's results entirely.
+After the script completes, read `$PROJECT_ROOT/.understand-anything/tmp/ua-scan-results.json`. Do NOT re-run file discovery commands or re-count lines -- trust the script's results entirely.
**IMPORTANT:** The final output must NOT contain the `scriptCompleted`, `rawDescription`, or `readmeHead` fields. These are intermediate script fields only. Strip them when assembling the final JSON.
diff --git a/understand-anything-plugin/skills/understand/tour-builder-prompt.md b/understand-anything-plugin/skills/understand/tour-builder-prompt.md
index 5840928..8b1d318 100644
--- a/understand-anything-plugin/skills/understand/tour-builder-prompt.md
+++ b/understand-anything-plugin/skills/understand/tour-builder-prompt.md
@@ -132,7 +132,7 @@ Create a lookup of each node ID to its `summary`, `type`, `tags` (default to emp
Before writing the script, create its input JSON file:
```bash
-cat > /tmp/ua-tour-input.json << 'ENDJSON'
+cat > $PROJECT_ROOT/.understand-anything/tmp/ua-tour-input.json << 'ENDJSON'
{
"nodes": [],
"edges": [],
@@ -146,7 +146,7 @@ ENDJSON
After writing the script, execute it:
```bash
-node /tmp/ua-tour-analyze.js /tmp/ua-tour-input.json /tmp/ua-tour-results.json
+node $PROJECT_ROOT/.understand-anything/tmp/ua-tour-analyze.js $PROJECT_ROOT/.understand-anything/tmp/ua-tour-input.json $PROJECT_ROOT/.understand-anything/tmp/ua-tour-results.json
```
If the script exits with a non-zero code, read stderr, diagnose the issue, fix the script, and re-run. You have up to 2 retry attempts.
@@ -155,7 +155,7 @@ If the script exits with a non-zero code, read stderr, diagnose the issue, fix t
## Phase 2 -- Pedagogical Tour Design
-After the script completes, read `/tmp/ua-tour-results.json`. Use the structural analysis as your primary guide for designing the tour. Do NOT re-read source files or re-analyze the graph -- trust the script's results entirely.
+After the script completes, read `$PROJECT_ROOT/.understand-anything/tmp/ua-tour-results.json`. Use the structural analysis as your primary guide for designing the tour. Do NOT re-read source files or re-analyze the graph -- trust the script's results entirely.
### Step 1 -- Choose the Starting Point