Git diff/PR/branch visualizer that analyzes changed files with ts-morph, extracts import relationships, and renders an interactive force-directed graph as a self-contained HTML file. Built with TypeScript (ESM), ts-morph for static analysis, and force-graph (2D canvas) for visualization. Installable as an npm package via tsmorph-graph CLI. Integrated into the ADW dashboard (agent_playground) alongside CodeAtlas for C# analysis.
- Runtime: Node.js (ESM,
"type": "module") - Static analysis: ts-morph ^24.0.0 (TypeScript AST — extracts imports between changed files)
- Visualization: force-graph 1.47.3 (loaded from unpkg CDN, d3-force physics)
- Syntax highlighting: highlight.js 11.9.0 (loaded from CDN, used in diff viewer)
- Runner: tsx ^4.19.0 (runs .ts files directly, no build step needed for dev)
- TypeScript: ^5.7.0 (compiles to
dist/for packaging) - Workspace: Nx (minimal config — build caching, target orchestration)
- Testing: Playwright (browser-based viewer tests with screenshots)
npm install— Install dependenciesnpm run analyze -- --repo <path> --base <branch> --branch <branch>— Analyze a real git repo diffnpm run analyze -- --repo <path> --base <branch> --branch <branch> --diff-content— Include file diffs in outputnpm run analyze -- --repo <path> --base <branch> --branch <branch> --diff-content --open— Start live server with branch switchingnpm run analyze -- --repo <path> --list-branches— List available branches as JSONnpm run generate— Generate synthetic test datasets (small/medium/huge/impossible)npm run build— Compile TypeScript todist/+ copy template.htmlnpx playwright test— Run viewer tests with screenshots
--repo <path>— Path to git repository (default: cwd)--base <branch>— Base branch for diff (default: main)--branch <branch>— Compare branch (default: HEAD)--diff-content— Include unified diff content per file in JSON (opt-in, caps at 500 lines/file)--list-branches— Print available branches as JSON and exit--output <dir>— Output directory (default: ./output)--open— Start live server (port 3742) and open browser. Enables branch switching via dropdowns--serve— Start live server without opening browser--port <number>— Server port (default: 3742)--no-all-branches— Skip pre-analyzing all branches (faster, used by dashboard integration)
src/types.ts— Shared type definitions (GraphNode, GraphLink, GraphData, ArchRules)src/analyze.ts— Library: git diff parsing, ts-morph import analysis, violation detection, HTML generationsrc/cli.ts— CLI entry point (thin wrapper around analyze, becomesbintarget)src/index.ts— Public API exports for programmatic usesrc/template.html— Force-graph viewer template (split layout, search, filters, diff viewer, violations)src/generate-test-data.ts— Synthetic dataset generator for stress testing (30–8000 nodes)tests/viewer.spec.ts— Playwright tests for the HTML viewertests/clustering.spec.ts— Playwright tests for clustering featuredocs/plans/— Implementation plansdocs/engine-benchmark.md— ts-morph vs ast-grep benchmark resultsoutput/— Generated files:graph.json,graph.html, test datasets, screenshotsdist/— Compiled JS output (for npm packaging, includes template.html)nx.json/project.json— Nx workspace configuration
- The HTML viewer is a single self-contained file — force-graph + highlight.js loaded from CDN, graph data injected into
<script id="graph-data">+<script id="all-branch-data">tags - All node rendering uses
nodeCanvasObject— custom Canvas2D drawing, not DOM elements - Force simulation config:
d3AlphaDecay(0.05),d3VelocityDecay(0.65),cooldownTime(8000),enableNodeDrag(false) Graph.refresh()does not exist in force-graph — useGraph.d3ReheatSimulation()for physics reheat- Force canvas repaint: After changing
highlightNodes/highlightLinkssets, callGraph.linkColor(Graph.linkColor())to trigger a repaint. Without this, highlight changes won't be visible until the next zoom/pan - Template uses
__GRAPH_DATA__and__ALL_BRANCH_DATA__as placeholder strings replaced at build time - JSON escaping: When injecting JSON into HTML, escape
</to<\/and use() => jsonas replacer function (avoids$special patterns in String.replace) - Filter visibility works by rebuilding graphData with filtered nodes/links via
Graph.graphData() - Build step:
npm run buildrunstsc && cp src/template.html dist/template.html— template must be copied to dist for the packaged CLI to work
- Split layout: 63% left (graph canvas) / 37% right (detail panel), with resizable drag handle
- Top toolbar: Title, branch selectors, file count, stats, Cluster toggle, Violations toggle + count, Spacing slider, Switch to CodeAtlas button, FPS counter
- Floating bar on graph: Search input + filter dropdowns (Type, Group, Status) + Connected toggle + Violations Only toggle
- Right panel: Empty state when no node selected → File info (collapsible), Connections (collapsible), Violations list, Diff viewer with search + syntax highlighting + line numbers + word-level diffs + collapsible hunks
The JSON fed to the viewer follows this structure:
meta:{ branch, base, totalFiles, totalAdditions, totalDeletions, generatedAt, availableBranches?, repoPath? }nodes[]:{ id (filePath), name, filePath, additions, deletions, totalChanges, status, fileType, group, diffContent? }links[]:{ source (filePath), target (filePath), violation?, violationType? }violations[]:{ source, target, violation (description), violationType (layer|circular|forbidden) }archRules?:{ rules?, forbidden?, detectCircular? }— loaded from.tsmorph-rules.json- Node
status:added|modified|deleted|renamed - Node
fileType:typescript|javascript|json|style|html|csharp|yaml|sql|markdown|other - Node
group: 2-level directory path (e.g.,adws/dashboard,adws/workflows) for granular architecture rules - Node
diffContent: unified diff string (optional, present when--diff-contentflag is used)
- Search:
Cmd+K/Ctrl+Kto focus, floats on graph canvas, substring match on file path/name, arrow keys to navigate, Enter/click flies to node - Filters: Dropdown multiselects for file types, groups/layers, statuses. Connected toggle (hide isolated nodes). Violations Only toggle
- Clustering: Toggle to collapse groups into cluster nodes with dashed ring + count. Click cluster to expand. Only expands clicked cluster, not nearby ones
- Detail panel: Click a node — right panel shows file info (collapsible), connections (collapsible, clickable to navigate), violations for this node, and diff viewer
- Diff viewer: Syntax highlighting (highlight.js), line numbers, word-level diff highlighting (LCS-based), collapsible hunks (folds 8+ context lines), search with prev/next navigation
- Violations: Red edges (transparent when unselected, solid when selected node is involved). Toggle on/off. Rules panel shows configured rules + detected violations with checkboxes per type
- Branch switching: Dropdown selectors for Base/Compare branches. All branches pre-analyzed and embedded for instant switching. Live server mode (
--open) for real-time re-analysis - Right-click menu: Focus neighborhood, Copy file path
- Resizable split: Drag the border between graph and panel to adjust the 63/37 ratio
- Spacing slider: Adjusts repulsion between unconnected groups (charge strength + distanceMax)
- Escape key: Deselects node, closes detail panel, exits focus neighborhood, zooms to fit
Place in repo root to enable violation detection:
{
"rules": {
"adws/dashboard": { "canImportFrom": ["adws/core", "adws/workflows"] },
"adws/workflows": { "canImportFrom": ["adws/core"] }
},
"forbidden": [
{ "from": ".spec.", "to": "fixtures", "description": "Spec files should use shared fixtures" }
],
"detectCircular": true
}rules: Layer rules — which group can import from which (matched by nodegroup)forbidden: Pattern-based rules —from/tomatch against file paths (substring match)detectCircular: DFS cycle detection on the import graph
- Branch:
feat/viz-integrationonQusayNaserFHS/agent_playground - Backend:
adws/dashboard/backend/workflows/visualizers.py— invokes TSMorphGraph and CodeAtlas CLIs - Backend:
adws/dashboard/backend/workflows/routes.py—/api/workflows/{id}/tsmorphand/codeatlasendpoints - Frontend:
card-actions.tsx— TSMorph and CodeAtlas buttons on workflow cards - Switch buttons: Each visualization has a "Switch to CodeAtlas/TSMorph" button in the toolbar (only visible when served from dashboard)
- CodeAtlas multi-language: Branch
feat/multi-languageonFoothillSolutions/GitNexus— MrAnalyzerLite for non-C# projects
/plan <description>— Create an implementation plan for a task/implement <plan file>— Execute a plan step-by-step with validation/test— Run full validation suite (types, analyze, generate, output check)/review <plan file>— Review work against a plan with Playwright screenshots/commit [type]— Create a formatted git commit from current changes/prime— Quick codebase orientation from CLAUDE.md/visualize <repo> [base] [branch]— Run analyzer and open the force-graph/stress-test— Generate all datasets and measure FPS per size
- The pipeline is:
git diff→ parse name-status + numstat → ts-morph import analysis → load.tsmorph-rules.json→ detect violations → build nodes + links + violations → serialize JSON → inject into HTML template → write self-contained output - Reference project:
../GitNexus-CodeAtlas/tools/CodeAtlas/— C#/Roslyn backend with dagre layout, now supports multi-language via MrAnalyzerLite - The viewer is framework-light: vanilla JS in the template, no React/Preact build step
- Package is installable via
npm install tsmorph-graph— exposestsmorph-graphCLI binary and programmatic API viasrc/index.ts - ast-grep was benchmarked as alternative engine but ts-morph won on both speed (2.7x faster) and accuracy (see
docs/engine-benchmark.md)