Scan a TypeScript codebase, build a dependency graph, and serve an interactive visualization — all in one command.
npx ts-modularityNo install required. Opens a browser at http://localhost:3008.
- Five views — Chord diagram, Force graph, DAG layout, Coupling matrix, Move Suggestions
- Modularity score per cluster (0 = cohesive, 1 = coupled)
- Move suggestions — per-file refactor candidates with confidence rating and ready-to-run
git mvcommands - Bidirectional cycle detection highlighted in the matrix view
- tsconfig path aliases resolved automatically
- Optional config file to define explicit module boundaries
- Zero runtime dependencies
# Scan ./src, serve on port 3008
npx ts-modularity
# Custom source dir
npx ts-modularity --src ./packages/app/src
# Write files only, no server
npx ts-modularity --no-serve
# All options
npx ts-modularity --src ./src --out ./dep-graph-out --depth 1 --port 3008| Flag | Default | Description |
|---|---|---|
--src <dir> |
. |
Directory to scan for .ts / .tsx files |
--out <dir> |
./dep-graph-out |
Output directory for graph.json and index.html |
--depth <n> |
1 |
Cluster grouping depth when no config file is present |
--port <n> |
3008 |
Preferred HTTP port (falls back to a random port if unavailable) |
--no-serve |
— | Write output files and exit without starting the server |
--help |
— | Print help |
| File | Description |
|---|---|
dep-graph-out/graph.json |
Raw graph data — clusters, edges, modularity metrics |
dep-graph-out/index.html |
Self-contained interactive visualization |
Create dep-graph.config.json in your project root to define explicit module boundaries.
{
"excludeOther": true,
"modularityThreshold": 0.4,
"modules": [
"src/api/*",
"src/app/*",
"src/services/*",
"src/store/*",
"src/utils/*",
"src/components"
]
}Rules are relative to the project root (where dep-graph.config.json lives), not --src.
| Pattern | Behavior |
|---|---|
"src/api/*" |
Each direct child of src/api/ becomes its own cluster (api/checkout, api/user, …) |
"src/components" |
Entire src/components/ is one cluster |
"src/store/auth" |
Exactly src/store/auth/ is one cluster |
"packages/*" |
Each package in a monorepo becomes its own cluster |
Rules are matched in order — first match wins. Files that match no rule:
excludeOther: false(default) — grouped into an<other>clusterexcludeOther: true— excluded from the graph entirely
| Field | Type | Default | Description |
|---|---|---|---|
modules |
string[] |
[] |
Module boundary rules (see syntax above) |
excludeOther |
boolean |
false |
Drop files that match no rule |
modularityThreshold |
number |
0.4 |
Informational — clusters above this score are flagged |
Modularity score measures how self-contained a cluster is:
modularity = internal_edges / (internal_edges + external_edges)
| Score | Meaning |
|---|---|
1.0 |
Fully modular — all dependencies are internal |
0.5 |
Equal internal and external coupling |
0.0 |
Fully coupled — no internal dependencies |
Color coding in the UI: green ≥ 0.67, amber ≥ 0.34, red < 0.34.
Default view. Arcs represent clusters; ribbons show inter-cluster imports weighted by import count.
Physics simulation. Node size scales with file count; the instability arc shows the modularity score. Drag nodes to explore.
Layered top-down layout grouped by modularity bucket. Orthogonal edge routing. Use the Sort dropdown to reorder by modularity, file count, in-deps, or name.
Heatmap where row A, column B = number of imports from cluster A into cluster B. Bidirectional cells (cycle risk) are highlighted in red.
Lists files whose external coupling points strongly to a different cluster. Each suggestion includes:
- Source and destination cluster
- Confidence rating (
high/medium/low) based on affinity score - A
git mvcommand block you can copy and run directly
Adjust the threshold input and click Run to re-compute at any value. Files with score < 2 (too diffuse to have a dominant destination) are omitted.
import { buildGraph, serve, computeSuggestions, loadModuleConfig, loadAliases } from 'ts-modularity';
const root = process.cwd();
const moduleConfig = loadModuleConfig(root); // reads dep-graph.config.json
const aliases = loadAliases(root); // reads tsconfig.json paths
const graph = buildGraph(root, './src', './out', 1, moduleConfig, aliases);
// graph.clusters — ClusterNode[]
// graph.files — FileNode[]
// graph.edges — Edge[]
// graph.clusterEdges — ClusterEdge[]
await serve(graph, 'src', 3008);
// Compute move suggestions programmatically
const result = computeSuggestions(graph, 0.4);
// result.badClusters — ClusterSuggestions[]
// result.gitMvCommands — string[] (ready to run)
console.log(result.gitMvCommands.join('\n'));interface ClusterNode {
id: string;
label: string;
files: number;
outEdges: number;
inEdges: number;
internalEdges: number;
modularity: number; // 0–1
coupling: number;
cohesion: number;
}
interface FileNode {
id: string;
label: string;
path: string;
cluster: string;
imports: number;
importedBy: number;
}
interface MoveSuggestion {
file: string;
fromCluster: string;
fromLabel: string;
toCluster: string;
toLabel: string;
score: number;
affinity: number;
confidence: 'high' | 'medium' | 'low';
}
interface SuggestionsResult {
threshold: number;
badClusters: { cluster: ClusterNode; suggestions: MoveSuggestion[] }[];
gitMvCommands: string[];
}Full type definitions are exported from the package (dist/index.d.ts).
- Node.js 18+
- TypeScript project with
.ts/.tsxsource files
MIT


