depwire-cli 0.7.1 โ 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -0
- package/README.md +117 -5
- package/dist/{chunk-65H7HCM4.js โ chunk-IGRFC3MQ.js} +512 -30
- package/dist/index.js +30 -1
- package/dist/mcpb-entry.js +1 -1
- package/package.json +3 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Depwire
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://glama.ai/mcp/servers/depwire/depwire">
|
|
5
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/depwire/depwire/badge" alt="Depwire MCP server" />
|
|
6
|
+
</a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/depwire-cli">
|
|
11
|
+
<img src="https://img.shields.io/npm/v/depwire-cli.svg?style=flat-square" alt="npm version" />
|
|
12
|
+
</a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/depwire-cli">
|
|
14
|
+
<img src="https://img.shields.io/npm/dm/depwire-cli.svg?style=flat-square" alt="npm downloads" />
|
|
15
|
+
</a>
|
|
16
|
+
<a href="LICENSE">
|
|
17
|
+
<img src="https://img.shields.io/badge/license-BUSL--1.1-blue.svg?style=flat-square" alt="License" />
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://github.com/depwire/depwire/stargazers">
|
|
20
|
+
<img src="https://img.shields.io/github/stars/depwire/depwire.svg?style=flat-square" alt="GitHub stars" />
|
|
21
|
+
</a>
|
|
22
|
+
<a href="https://github.com/depwire/depwire/network/members">
|
|
23
|
+
<img src="https://img.shields.io/github/forks/depwire/depwire.svg?style=flat-square" alt="GitHub forks" />
|
|
24
|
+
</a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
3
27
|

|
|
4
28
|
|
|
5
29
|
**See how your code connects. Give AI tools full codebase context.**
|
|
@@ -10,9 +34,10 @@ Depwire analyzes codebases to build a cross-reference graph showing how every fi
|
|
|
10
34
|
|
|
11
35
|
- ๐จ **Beautiful arc diagram visualization** โ Interactive Harrison Bible-style graphic
|
|
12
36
|
- ๐ค **MCP server for AI tools** โ Cursor, Claude Desktop get full dependency context
|
|
13
|
-
- ๐ **Dependency health score** โ 0-100 score across 6 dimensions (coupling, cohesion, circular deps, god files, orphans, depth)
|
|
14
|
-
- ๐ **Auto-generated documentation** โ
|
|
37
|
+
- ๐ **Dependency health score** โ 0-100 score across 6 dimensions (coupling, cohesion, circular deps, god files, orphans & dead code, depth)
|
|
38
|
+
- ๐ **Auto-generated documentation** โ 13 comprehensive documents: architecture, conventions, dependencies, onboarding, file catalog, API surface, error patterns, test coverage, git history, full snapshot, TODO/FIXME inventory, health report, and dead code analysis
|
|
15
39
|
- ๐ **Impact analysis** โ "What breaks if I rename this function?" answered precisely
|
|
40
|
+
- ๐งน **Dead code detection** โ Find symbols that are defined but never referenced, categorized by confidence level
|
|
16
41
|
- ๐ **Live updates** โ Graph stays current as you edit code
|
|
17
42
|
- ๐ **Multi-language** โ TypeScript, JavaScript, Python, and Go
|
|
18
43
|
|
|
@@ -34,14 +59,14 @@ Depwire fixes this by giving AI tools a complete dependency graph of your codeba
|
|
|
34
59
|
### Ship Better Code
|
|
35
60
|
- **Impact analysis for any change** โ renaming a function, moving a file, upgrading a dependency, deleting a module โ know the full blast radius before you touch anything
|
|
36
61
|
- **Refactor with confidence** โ see every downstream consumer, every transitive dependency, 2-3 levels deep
|
|
37
|
-
- **Catch dead code** โ find symbols nobody references anymore
|
|
62
|
+
- **Catch dead code** โ find symbols nobody references anymore with confidence-based classification (high/medium/low)
|
|
38
63
|
|
|
39
64
|
### Stay in Flow
|
|
40
65
|
- **Live graph, always current** โ edit a file and the dependency map updates in real-time. No re-indexing, no waiting.
|
|
41
66
|
- **Works locally, stays private** โ zero cloud accounts, zero data leaving your machine. Just `npm install` and go.
|
|
42
67
|
|
|
43
|
-
###
|
|
44
|
-
Depwire isn't just a pretty graph. It's a full context engine with
|
|
68
|
+
### 15 MCP Tools, Not Just Visualization
|
|
69
|
+
Depwire isn't just a pretty graph. It's a full context engine with 15 tools that AI assistants call autonomously โ architecture summaries, dependency tracing, symbol search, file context, health scores, dead code detection, temporal evolution, and more. The AI decides which tool to use based on your question.
|
|
45
70
|
|
|
46
71
|
## Installation
|
|
47
72
|
|
|
@@ -66,11 +91,13 @@ depwire viz
|
|
|
66
91
|
depwire parse
|
|
67
92
|
depwire docs
|
|
68
93
|
depwire health
|
|
94
|
+
depwire dead-code
|
|
69
95
|
depwire temporal
|
|
70
96
|
|
|
71
97
|
# Or specify a directory explicitly
|
|
72
98
|
npx depwire-cli viz ./my-project
|
|
73
99
|
npx depwire-cli parse ./my-project
|
|
100
|
+
npx depwire-cli dead-code ./my-project
|
|
74
101
|
npx depwire-cli temporal ./my-project
|
|
75
102
|
|
|
76
103
|
# Temporal visualization options
|
|
@@ -139,6 +166,7 @@ Settings โ Features โ Experimental โ Enable MCP โ Add Server:
|
|
|
139
166
|
| `get_project_docs` | Retrieve auto-generated codebase documentation |
|
|
140
167
|
| `update_project_docs` | Regenerate documentation on demand |
|
|
141
168
|
| `get_health_score` | Get 0-100 dependency health score with recommendations |
|
|
169
|
+
| `find_dead_code` | Find dead code โ symbols defined but never referenced |
|
|
142
170
|
| `get_temporal_graph` | Show how the graph evolved over git history |
|
|
143
171
|
|
|
144
172
|
## Supported Languages
|
|
@@ -189,6 +217,8 @@ Opens an interactive arc diagram in your browser:
|
|
|
189
217
|
|
|
190
218
|
Visualize how your codebase architecture evolved over git history. Scrub through time with an interactive timeline slider.
|
|
191
219
|
|
|
220
|
+

|
|
221
|
+
|
|
192
222
|
```bash
|
|
193
223
|
# Auto-detects project root
|
|
194
224
|
depwire temporal
|
|
@@ -220,6 +250,37 @@ Opens an interactive temporal visualization in your browser:
|
|
|
220
250
|
- Auto-zoom to fit all arcs on snapshot change
|
|
221
251
|
- Search to highlight specific files across time
|
|
222
252
|
|
|
253
|
+
## ๐ชฆ Dead Code Detection
|
|
254
|
+
|
|
255
|
+
Find unused symbols across your codebase before they become technical debt.
|
|
256
|
+
|
|
257
|
+
- Detects symbols with zero incoming references (never called, never imported)
|
|
258
|
+
- Confidence scoring: **high** (definitely dead), **medium** (probably dead), **low** (might be dead)
|
|
259
|
+
- Smart exclusion rules โ ignores entry points, test files, barrel files, and config files to reduce false positives
|
|
260
|
+
- Filter by confidence level, export as JSON for CI pipelines
|
|
261
|
+
- Integrated into the health score (orphans dimension)
|
|
262
|
+
- New MCP tool: `find_dead_code` โ AI assistants can query dead code directly
|
|
263
|
+
- New document generator: `DEAD_CODE.md` โ auto-generated dead code report
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
depwire dead-code
|
|
267
|
+
depwire dead-code --confidence high
|
|
268
|
+
depwire dead-code --stats
|
|
269
|
+
depwire dead-code --json
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Confidence Levels:**
|
|
273
|
+
- ๐ด **High confidence (definitely dead)**: Not exported with zero references, or exported but never used
|
|
274
|
+
- ๐ก **Medium confidence (probably dead)**: Exported from barrel files with zero dependents, or only used in test files
|
|
275
|
+
- โช **Low confidence (might be dead)**: Exported from package entry points, types with zero dependents, or in dynamic-use directories (routes, middleware, etc.)
|
|
276
|
+
|
|
277
|
+
The dead code detector automatically excludes:
|
|
278
|
+
- Entry point files (index.ts, main.ts, server.ts, etc.)
|
|
279
|
+
- Test files (*.test.*, *.spec.*, __tests__/)
|
|
280
|
+
- Config files (*.config.*)
|
|
281
|
+
- Type declarations (*.d.ts)
|
|
282
|
+
- Framework auto-loaded directories (pages/, routes/, middleware/, commands/)
|
|
283
|
+
|
|
223
284
|
## How It Works
|
|
224
285
|
|
|
225
286
|
1. **Parser** โ tree-sitter extracts every symbol and reference
|
|
@@ -357,6 +418,7 @@ depwire docs --update --only conventions
|
|
|
357
418
|
| `CURRENT.md` | Complete codebase snapshot (every file, symbol, connection) |
|
|
358
419
|
| `STATUS.md` | TODO/FIXME/HACK inventory with priority matrix |
|
|
359
420
|
| `HEALTH.md` | Dependency health score (0-100) across 6 dimensions with recommendations |
|
|
421
|
+
| `DEAD_CODE.md` | Dead code analysis โ unused symbols by confidence level (high/medium/low) |
|
|
360
422
|
|
|
361
423
|
Documents are stored in `.depwire/` with `metadata.json` tracking generation timestamps for staleness detection.
|
|
362
424
|
|
|
@@ -401,6 +463,56 @@ depwire health --json
|
|
|
401
463
|
|
|
402
464
|
Health history is stored in `.depwire/health-history.json` (last 50 checks).
|
|
403
465
|
|
|
466
|
+
### `depwire dead-code [directory]`
|
|
467
|
+
|
|
468
|
+
Detect unused symbols across your codebase with confidence-based classification.
|
|
469
|
+
|
|
470
|
+
**Directory argument is optional** โ Auto-detects project root.
|
|
471
|
+
|
|
472
|
+
**Options:**
|
|
473
|
+
- `--confidence <level>` โ Minimum confidence level to show: `high`, `medium`, `low` (default: `medium`)
|
|
474
|
+
- `--include-low` โ Shortcut for `--confidence low`
|
|
475
|
+
- `--verbose` โ Show detailed info for each dead symbol (file, line, kind, reason)
|
|
476
|
+
- `--stats` โ Show summary statistics
|
|
477
|
+
- `--include-tests` โ Include test files in analysis (excluded by default)
|
|
478
|
+
- `--json` โ Output as JSON for CI/automation
|
|
479
|
+
|
|
480
|
+
**Confidence Levels:**
|
|
481
|
+
- ๐ด **High confidence (definitely dead)**: Not exported with zero references, or exported but never used
|
|
482
|
+
- ๐ก **Medium confidence (probably dead)**: Exported from barrel files with zero dependents, or only used in test files
|
|
483
|
+
- โช **Low confidence (might be dead)**: Exported from package entry points, types with zero dependents, or in dynamic-use directories (routes, middleware, etc.)
|
|
484
|
+
|
|
485
|
+
**Examples:**
|
|
486
|
+
```bash
|
|
487
|
+
# Analyze dead code (default: medium confidence and above)
|
|
488
|
+
depwire dead-code
|
|
489
|
+
|
|
490
|
+
# Show only high-confidence dead code
|
|
491
|
+
depwire dead-code --confidence high
|
|
492
|
+
|
|
493
|
+
# Show all potential dead code (including low confidence)
|
|
494
|
+
depwire dead-code --confidence low
|
|
495
|
+
# Or use shortcut
|
|
496
|
+
depwire dead-code --include-low
|
|
497
|
+
|
|
498
|
+
# Detailed analysis with reasons and statistics
|
|
499
|
+
depwire dead-code --verbose --stats
|
|
500
|
+
|
|
501
|
+
# Include test files in analysis (excluded by default)
|
|
502
|
+
depwire dead-code --include-tests
|
|
503
|
+
|
|
504
|
+
# JSON output for CI/automation
|
|
505
|
+
depwire dead-code --json
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**Automatic Exclusions:**
|
|
509
|
+
The dead code detector automatically excludes:
|
|
510
|
+
- Entry point files (index.ts, main.ts, server.ts, etc.)
|
|
511
|
+
- Test files (*.test.*, *.spec.*, __tests__/)
|
|
512
|
+
- Config files (*.config.*)
|
|
513
|
+
- Type declarations (*.d.ts)
|
|
514
|
+
- Framework auto-loaded directories (pages/, routes/, middleware/, commands/)
|
|
515
|
+
|
|
404
516
|
### `depwire temporal [directory]`
|
|
405
517
|
|
|
406
518
|
Visualize how the dependency graph evolved over git history.
|
|
@@ -2009,10 +2009,10 @@ async function parseProject(projectRoot, options) {
|
|
|
2009
2009
|
try {
|
|
2010
2010
|
const fullPath = join6(projectRoot, file);
|
|
2011
2011
|
if (options?.exclude) {
|
|
2012
|
-
const
|
|
2012
|
+
const shouldExclude2 = options.exclude.some(
|
|
2013
2013
|
(pattern) => minimatch(file, pattern, { matchBase: true })
|
|
2014
2014
|
);
|
|
2015
|
-
if (
|
|
2015
|
+
if (shouldExclude2) {
|
|
2016
2016
|
if (options.verbose) {
|
|
2017
2017
|
console.error(`[Parser] Excluded: ${file}`);
|
|
2018
2018
|
}
|
|
@@ -2820,11 +2820,11 @@ function calculateCircularDepsScore(graph) {
|
|
|
2820
2820
|
const visited = /* @__PURE__ */ new Set();
|
|
2821
2821
|
const recStack = /* @__PURE__ */ new Set();
|
|
2822
2822
|
const cycles = [];
|
|
2823
|
-
function dfs(node,
|
|
2823
|
+
function dfs(node, path6) {
|
|
2824
2824
|
if (recStack.has(node)) {
|
|
2825
|
-
const cycleStart =
|
|
2825
|
+
const cycleStart = path6.indexOf(node);
|
|
2826
2826
|
if (cycleStart >= 0) {
|
|
2827
|
-
cycles.push(
|
|
2827
|
+
cycles.push(path6.slice(cycleStart));
|
|
2828
2828
|
}
|
|
2829
2829
|
return;
|
|
2830
2830
|
}
|
|
@@ -2833,11 +2833,11 @@ function calculateCircularDepsScore(graph) {
|
|
|
2833
2833
|
}
|
|
2834
2834
|
visited.add(node);
|
|
2835
2835
|
recStack.add(node);
|
|
2836
|
-
|
|
2836
|
+
path6.push(node);
|
|
2837
2837
|
const neighbors = fileGraph.get(node);
|
|
2838
2838
|
if (neighbors) {
|
|
2839
2839
|
for (const neighbor of neighbors) {
|
|
2840
|
-
dfs(neighbor, [...
|
|
2840
|
+
dfs(neighbor, [...path6]);
|
|
2841
2841
|
}
|
|
2842
2842
|
}
|
|
2843
2843
|
recStack.delete(node);
|
|
@@ -2939,25 +2939,40 @@ function calculateOrphansScore(graph) {
|
|
|
2939
2939
|
});
|
|
2940
2940
|
const orphanCount = files.size - connectedFiles.size;
|
|
2941
2941
|
const orphanPercent = files.size > 0 ? orphanCount / files.size * 100 : 0;
|
|
2942
|
+
const totalSymbols = graph.order;
|
|
2943
|
+
let deadSymbolCount = 0;
|
|
2944
|
+
graph.forEachNode((node) => {
|
|
2945
|
+
const inDegree = graph.inDegree(node);
|
|
2946
|
+
if (inDegree === 0) {
|
|
2947
|
+
deadSymbolCount++;
|
|
2948
|
+
}
|
|
2949
|
+
});
|
|
2950
|
+
const deadCodePercent = totalSymbols > 0 ? deadSymbolCount / totalSymbols * 100 : 0;
|
|
2951
|
+
const combinedScore = (orphanPercent + deadCodePercent) / 2;
|
|
2942
2952
|
let score = 100;
|
|
2943
|
-
if (
|
|
2953
|
+
if (combinedScore === 0) {
|
|
2944
2954
|
score = 100;
|
|
2945
|
-
} else if (
|
|
2955
|
+
} else if (combinedScore <= 5) {
|
|
2946
2956
|
score = 80;
|
|
2947
|
-
} else if (
|
|
2957
|
+
} else if (combinedScore <= 10) {
|
|
2948
2958
|
score = 60;
|
|
2949
|
-
} else if (
|
|
2959
|
+
} else if (combinedScore <= 20) {
|
|
2950
2960
|
score = 40;
|
|
2951
2961
|
} else {
|
|
2952
2962
|
score = 20;
|
|
2953
2963
|
}
|
|
2954
2964
|
return {
|
|
2955
|
-
name: "
|
|
2965
|
+
name: "Orphans & Dead Code",
|
|
2956
2966
|
score,
|
|
2957
2967
|
weight: 0.1,
|
|
2958
2968
|
grade: scoreToGrade(score),
|
|
2959
|
-
details:
|
|
2960
|
-
metrics: {
|
|
2969
|
+
details: `${orphanCount} orphan file${orphanCount === 1 ? "" : "s"} (${orphanPercent.toFixed(0)}%), ${deadSymbolCount} dead symbols (${deadCodePercent.toFixed(1)}%)`,
|
|
2970
|
+
metrics: {
|
|
2971
|
+
orphans: orphanCount,
|
|
2972
|
+
orphanPercentage: parseFloat(orphanPercent.toFixed(1)),
|
|
2973
|
+
deadSymbols: deadSymbolCount,
|
|
2974
|
+
deadCodePercentage: parseFloat(deadCodePercent.toFixed(1))
|
|
2975
|
+
}
|
|
2961
2976
|
};
|
|
2962
2977
|
}
|
|
2963
2978
|
function calculateDepthScore(graph) {
|
|
@@ -3159,6 +3174,304 @@ function loadHealthHistory(projectRoot) {
|
|
|
3159
3174
|
}
|
|
3160
3175
|
}
|
|
3161
3176
|
|
|
3177
|
+
// src/dead-code/detector.ts
|
|
3178
|
+
import path2 from "path";
|
|
3179
|
+
function findDeadSymbols(graph, projectRoot, includeTests = false) {
|
|
3180
|
+
const deadSymbols = [];
|
|
3181
|
+
const context = { graph, projectRoot };
|
|
3182
|
+
for (const node of graph.nodes()) {
|
|
3183
|
+
const attrs = graph.getNodeAttributes(node);
|
|
3184
|
+
if (!attrs.file || !attrs.name) continue;
|
|
3185
|
+
const inDegree = graph.inDegree(node);
|
|
3186
|
+
if (inDegree === 0) {
|
|
3187
|
+
if (shouldExclude(attrs, context, includeTests)) {
|
|
3188
|
+
continue;
|
|
3189
|
+
}
|
|
3190
|
+
deadSymbols.push({
|
|
3191
|
+
name: attrs.name,
|
|
3192
|
+
kind: attrs.kind || "unknown",
|
|
3193
|
+
file: attrs.file,
|
|
3194
|
+
line: attrs.startLine || 0,
|
|
3195
|
+
exported: attrs.exported || false,
|
|
3196
|
+
dependents: 0,
|
|
3197
|
+
confidence: "high",
|
|
3198
|
+
reason: "Zero dependents"
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
return deadSymbols;
|
|
3203
|
+
}
|
|
3204
|
+
function shouldExclude(attrs, context, includeTests) {
|
|
3205
|
+
const filePath = attrs.file;
|
|
3206
|
+
const relativePath = path2.relative(context.projectRoot, filePath);
|
|
3207
|
+
if (!includeTests && isTestFile(relativePath)) {
|
|
3208
|
+
return true;
|
|
3209
|
+
}
|
|
3210
|
+
if (isEntryPoint(relativePath)) {
|
|
3211
|
+
return true;
|
|
3212
|
+
}
|
|
3213
|
+
if (isConfigFile(relativePath)) {
|
|
3214
|
+
return true;
|
|
3215
|
+
}
|
|
3216
|
+
if (isTypeDeclarationFile(relativePath)) {
|
|
3217
|
+
return true;
|
|
3218
|
+
}
|
|
3219
|
+
if (attrs.kind === "default") {
|
|
3220
|
+
return true;
|
|
3221
|
+
}
|
|
3222
|
+
if (isFrameworkAutoLoadedFile(relativePath)) {
|
|
3223
|
+
return true;
|
|
3224
|
+
}
|
|
3225
|
+
return false;
|
|
3226
|
+
}
|
|
3227
|
+
function isTestFile(filePath) {
|
|
3228
|
+
return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
|
|
3229
|
+
}
|
|
3230
|
+
function isEntryPoint(filePath) {
|
|
3231
|
+
const basename6 = path2.basename(filePath);
|
|
3232
|
+
return basename6 === "index.ts" || basename6 === "index.js" || basename6 === "main.ts" || basename6 === "main.js" || basename6 === "app.ts" || basename6 === "app.js" || basename6 === "server.ts" || basename6 === "server.js";
|
|
3233
|
+
}
|
|
3234
|
+
function isConfigFile(filePath) {
|
|
3235
|
+
return filePath.includes(".config.") || filePath.includes("config/") || filePath.includes("vite.config") || filePath.includes("rollup.config") || filePath.includes("webpack.config");
|
|
3236
|
+
}
|
|
3237
|
+
function isTypeDeclarationFile(filePath) {
|
|
3238
|
+
return filePath.endsWith(".d.ts");
|
|
3239
|
+
}
|
|
3240
|
+
function isFrameworkAutoLoadedFile(filePath) {
|
|
3241
|
+
return filePath.includes("/pages/") || filePath.includes("/routes/") || filePath.includes("/middleware/") || filePath.includes("/commands/") || filePath.includes("/api/") || filePath.includes("/app/");
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
// src/dead-code/classifier.ts
|
|
3245
|
+
import path3 from "path";
|
|
3246
|
+
function classifyDeadSymbols(symbols, graph) {
|
|
3247
|
+
return symbols.map((symbol) => {
|
|
3248
|
+
const confidence = calculateConfidence(symbol, graph);
|
|
3249
|
+
const reason = generateReason(symbol, confidence);
|
|
3250
|
+
return {
|
|
3251
|
+
...symbol,
|
|
3252
|
+
confidence,
|
|
3253
|
+
reason
|
|
3254
|
+
};
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
function calculateConfidence(symbol, graph) {
|
|
3258
|
+
if (!symbol.exported && symbol.dependents === 0) {
|
|
3259
|
+
return "high";
|
|
3260
|
+
}
|
|
3261
|
+
if (symbol.exported && symbol.dependents === 0 && !isBarrelFile(symbol.file)) {
|
|
3262
|
+
return "high";
|
|
3263
|
+
}
|
|
3264
|
+
if (symbol.exported && symbol.dependents === 0 && isBarrelFile(symbol.file)) {
|
|
3265
|
+
return "medium";
|
|
3266
|
+
}
|
|
3267
|
+
const dependents = getSymbolDependents(symbol, graph);
|
|
3268
|
+
if (dependents.length === 1 && isTestFile2(dependents[0])) {
|
|
3269
|
+
return "medium";
|
|
3270
|
+
}
|
|
3271
|
+
if (symbol.exported && isPackageEntryPoint(symbol.file)) {
|
|
3272
|
+
return "low";
|
|
3273
|
+
}
|
|
3274
|
+
if ((symbol.kind === "interface" || symbol.kind === "type") && symbol.dependents === 0) {
|
|
3275
|
+
return "low";
|
|
3276
|
+
}
|
|
3277
|
+
if (isLikelyDynamicUsage(symbol)) {
|
|
3278
|
+
return "low";
|
|
3279
|
+
}
|
|
3280
|
+
return "medium";
|
|
3281
|
+
}
|
|
3282
|
+
function generateReason(symbol, confidence) {
|
|
3283
|
+
if (!symbol.exported && symbol.dependents === 0) {
|
|
3284
|
+
return "Not exported, zero references";
|
|
3285
|
+
}
|
|
3286
|
+
if (symbol.exported && symbol.dependents === 0 && !isBarrelFile(symbol.file)) {
|
|
3287
|
+
return "Exported, zero dependents";
|
|
3288
|
+
}
|
|
3289
|
+
if (symbol.exported && symbol.dependents === 0 && isBarrelFile(symbol.file)) {
|
|
3290
|
+
return "Exported from barrel file, zero dependents (might be used externally)";
|
|
3291
|
+
}
|
|
3292
|
+
if (confidence === "medium") {
|
|
3293
|
+
return "Low usage, might be dead";
|
|
3294
|
+
}
|
|
3295
|
+
if (confidence === "low") {
|
|
3296
|
+
if (symbol.kind === "interface" || symbol.kind === "type") {
|
|
3297
|
+
return "Type with zero dependents (might be used via import type)";
|
|
3298
|
+
}
|
|
3299
|
+
if (isPackageEntryPoint(symbol.file)) {
|
|
3300
|
+
return "Exported from package entry point (might be public API)";
|
|
3301
|
+
}
|
|
3302
|
+
if (isLikelyDynamicUsage(symbol)) {
|
|
3303
|
+
return "In dynamic-use pattern directory (might be auto-loaded)";
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
return "Potentially unused";
|
|
3307
|
+
}
|
|
3308
|
+
function isBarrelFile(filePath) {
|
|
3309
|
+
const basename6 = path3.basename(filePath);
|
|
3310
|
+
return basename6 === "index.ts" || basename6 === "index.js";
|
|
3311
|
+
}
|
|
3312
|
+
function isTestFile2(filePath) {
|
|
3313
|
+
return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
|
|
3314
|
+
}
|
|
3315
|
+
function isPackageEntryPoint(filePath) {
|
|
3316
|
+
return filePath.includes("/src/index.") || filePath.includes("/lib/index.") || filePath.endsWith("/index.ts") || filePath.endsWith("/index.js");
|
|
3317
|
+
}
|
|
3318
|
+
function isLikelyDynamicUsage(symbol) {
|
|
3319
|
+
const filePath = symbol.file;
|
|
3320
|
+
return filePath.includes("/routes/") || filePath.includes("/pages/") || filePath.includes("/middleware/") || filePath.includes("/commands/") || filePath.includes("/handlers/") || filePath.includes("/api/");
|
|
3321
|
+
}
|
|
3322
|
+
function getSymbolDependents(symbol, graph) {
|
|
3323
|
+
const dependents = [];
|
|
3324
|
+
for (const node of graph.nodes()) {
|
|
3325
|
+
const attrs = graph.getNodeAttributes(node);
|
|
3326
|
+
if (attrs.file === symbol.file && attrs.name === symbol.name) {
|
|
3327
|
+
const inNeighbors = graph.inNeighbors(node);
|
|
3328
|
+
for (const neighbor of inNeighbors) {
|
|
3329
|
+
const neighborAttrs = graph.getNodeAttributes(neighbor);
|
|
3330
|
+
if (neighborAttrs.file) {
|
|
3331
|
+
dependents.push(neighborAttrs.file);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
break;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
return dependents;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
// src/dead-code/display.ts
|
|
3341
|
+
import chalk from "chalk";
|
|
3342
|
+
import path4 from "path";
|
|
3343
|
+
function displayDeadCodeReport(report, options, projectRoot) {
|
|
3344
|
+
if (options.json) {
|
|
3345
|
+
console.log(JSON.stringify(report, null, 2));
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
console.log(chalk.cyan.bold("\n\u{1F50D} Dead Code Analysis\n"));
|
|
3349
|
+
const { high, medium, low } = report.byConfidence;
|
|
3350
|
+
console.log(
|
|
3351
|
+
`Found ${chalk.yellow.bold(report.deadSymbols)} potentially dead symbols (${chalk.red(high)} high, ${chalk.yellow(medium)} medium, ${chalk.gray(low)} low confidence)
|
|
3352
|
+
`
|
|
3353
|
+
);
|
|
3354
|
+
const symbolsByConfidence = groupByConfidence(report.symbols);
|
|
3355
|
+
if (symbolsByConfidence.high.length > 0) {
|
|
3356
|
+
displayConfidenceGroup("HIGH", symbolsByConfidence.high, options.verbose, projectRoot);
|
|
3357
|
+
}
|
|
3358
|
+
if (symbolsByConfidence.medium.length > 0) {
|
|
3359
|
+
displayConfidenceGroup("MEDIUM", symbolsByConfidence.medium, options.verbose, projectRoot);
|
|
3360
|
+
}
|
|
3361
|
+
if (symbolsByConfidence.low.length > 0) {
|
|
3362
|
+
displayConfidenceGroup("LOW", symbolsByConfidence.low, options.verbose, projectRoot);
|
|
3363
|
+
}
|
|
3364
|
+
if (options.stats) {
|
|
3365
|
+
displayStats(report);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
function groupByConfidence(symbols) {
|
|
3369
|
+
return symbols.reduce(
|
|
3370
|
+
(acc, symbol) => {
|
|
3371
|
+
acc[symbol.confidence].push(symbol);
|
|
3372
|
+
return acc;
|
|
3373
|
+
},
|
|
3374
|
+
{ high: [], medium: [], low: [] }
|
|
3375
|
+
);
|
|
3376
|
+
}
|
|
3377
|
+
function displayConfidenceGroup(level, symbols, verbose, projectRoot) {
|
|
3378
|
+
const emoji = level === "HIGH" ? "\u{1F534}" : level === "MEDIUM" ? "\u{1F7E1}" : "\u26AA";
|
|
3379
|
+
const color = level === "HIGH" ? chalk.red : level === "MEDIUM" ? chalk.yellow : chalk.gray;
|
|
3380
|
+
console.log(
|
|
3381
|
+
color.bold(`
|
|
3382
|
+
${emoji} ${level} CONFIDENCE `) + chalk.gray(`(${level === "HIGH" ? "definitely" : level === "MEDIUM" ? "probably" : "might be"} dead)`)
|
|
3383
|
+
);
|
|
3384
|
+
const headers = ["Symbol", "Kind", "File", verbose ? "Reason" : ""];
|
|
3385
|
+
const rows = symbols.map((symbol) => {
|
|
3386
|
+
const relativePath = path4.relative(projectRoot, symbol.file);
|
|
3387
|
+
return [
|
|
3388
|
+
chalk.bold(symbol.name),
|
|
3389
|
+
symbol.kind,
|
|
3390
|
+
`${relativePath}:${symbol.line}`,
|
|
3391
|
+
verbose ? symbol.reason : ""
|
|
3392
|
+
];
|
|
3393
|
+
});
|
|
3394
|
+
displayTable(headers, rows.filter((row) => row[3] !== ""));
|
|
3395
|
+
}
|
|
3396
|
+
function displayTable(headers, rows) {
|
|
3397
|
+
if (rows.length === 0) return;
|
|
3398
|
+
const columnWidths = headers.map((header2, i) => {
|
|
3399
|
+
const maxRowWidth = Math.max(...rows.map((row) => stripAnsi(row[i]).length));
|
|
3400
|
+
return Math.max(header2.length, maxRowWidth);
|
|
3401
|
+
});
|
|
3402
|
+
const separator = "\u250C" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u2510";
|
|
3403
|
+
const headerRow = "\u2502 " + headers.map((h, i) => h.padEnd(columnWidths[i])).join(" \u2502 ") + " \u2502";
|
|
3404
|
+
const divider = "\u251C" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C") + "\u2524";
|
|
3405
|
+
const footer = "\u2514" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u2518";
|
|
3406
|
+
console.log(separator);
|
|
3407
|
+
console.log(headerRow);
|
|
3408
|
+
console.log(divider);
|
|
3409
|
+
for (const row of rows) {
|
|
3410
|
+
const formattedRow = "\u2502 " + row.map((cell, i) => {
|
|
3411
|
+
const stripped = stripAnsi(cell);
|
|
3412
|
+
const padding = columnWidths[i] - stripped.length;
|
|
3413
|
+
return cell + " ".repeat(padding);
|
|
3414
|
+
}).join(" \u2502 ") + " \u2502";
|
|
3415
|
+
console.log(formattedRow);
|
|
3416
|
+
}
|
|
3417
|
+
console.log(footer);
|
|
3418
|
+
}
|
|
3419
|
+
function displayStats(report) {
|
|
3420
|
+
console.log(chalk.cyan.bold("\n\u{1F4CA} Summary\n"));
|
|
3421
|
+
console.log(` Total symbols analyzed: ${chalk.bold(report.totalSymbols.toLocaleString())}`);
|
|
3422
|
+
console.log(` Potentially dead: ${chalk.yellow.bold(report.deadSymbols)} (${report.deadPercentage.toFixed(1)}%)`);
|
|
3423
|
+
console.log(
|
|
3424
|
+
` By confidence: ${chalk.red(report.byConfidence.high)} high, ${chalk.yellow(report.byConfidence.medium)} medium, ${chalk.gray(report.byConfidence.low)} low`
|
|
3425
|
+
);
|
|
3426
|
+
const estimatedLines = report.deadSymbols * 18;
|
|
3427
|
+
console.log(` Estimated dead code: ${chalk.gray(`~${estimatedLines.toLocaleString()} lines`)}
|
|
3428
|
+
`);
|
|
3429
|
+
}
|
|
3430
|
+
function stripAnsi(str) {
|
|
3431
|
+
return str.replace(
|
|
3432
|
+
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
3433
|
+
""
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
// src/dead-code/index.ts
|
|
3438
|
+
function analyzeDeadCode(graph, projectRoot, options = {}) {
|
|
3439
|
+
const opts = {
|
|
3440
|
+
confidence: options.confidence || "medium",
|
|
3441
|
+
includeTests: options.includeTests || false,
|
|
3442
|
+
verbose: options.verbose || false,
|
|
3443
|
+
stats: options.stats || false,
|
|
3444
|
+
json: options.json || false
|
|
3445
|
+
};
|
|
3446
|
+
const rawDeadSymbols = findDeadSymbols(graph, projectRoot, opts.includeTests);
|
|
3447
|
+
const classifiedSymbols = classifyDeadSymbols(rawDeadSymbols, graph);
|
|
3448
|
+
const filteredSymbols = filterByConfidence(classifiedSymbols, opts.confidence);
|
|
3449
|
+
const totalSymbols = graph.order;
|
|
3450
|
+
const byConfidence = {
|
|
3451
|
+
high: classifiedSymbols.filter((s) => s.confidence === "high").length,
|
|
3452
|
+
medium: classifiedSymbols.filter((s) => s.confidence === "medium").length,
|
|
3453
|
+
low: classifiedSymbols.filter((s) => s.confidence === "low").length
|
|
3454
|
+
};
|
|
3455
|
+
const report = {
|
|
3456
|
+
totalSymbols,
|
|
3457
|
+
deadSymbols: filteredSymbols.length,
|
|
3458
|
+
deadPercentage: filteredSymbols.length / totalSymbols * 100,
|
|
3459
|
+
byConfidence,
|
|
3460
|
+
symbols: filteredSymbols
|
|
3461
|
+
};
|
|
3462
|
+
if (!opts.json) {
|
|
3463
|
+
displayDeadCodeReport(report, opts, projectRoot);
|
|
3464
|
+
}
|
|
3465
|
+
return report;
|
|
3466
|
+
}
|
|
3467
|
+
function filterByConfidence(symbols, minConfidence) {
|
|
3468
|
+
const confidenceLevels = { high: 3, medium: 2, low: 1 };
|
|
3469
|
+
const minLevel = confidenceLevels[minConfidence];
|
|
3470
|
+
return symbols.filter(
|
|
3471
|
+
(s) => confidenceLevels[s.confidence] >= minLevel
|
|
3472
|
+
);
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3162
3475
|
// src/docs/generator.ts
|
|
3163
3476
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync9 } from "fs";
|
|
3164
3477
|
import { join as join12 } from "path";
|
|
@@ -4208,20 +4521,20 @@ function findLongestPaths(graph, limit) {
|
|
|
4208
4521
|
}
|
|
4209
4522
|
const allPaths = [];
|
|
4210
4523
|
const visited = /* @__PURE__ */ new Set();
|
|
4211
|
-
function dfs(file,
|
|
4524
|
+
function dfs(file, path6) {
|
|
4212
4525
|
visited.add(file);
|
|
4213
|
-
|
|
4526
|
+
path6.push(file);
|
|
4214
4527
|
const neighbors = fileGraph.get(file);
|
|
4215
4528
|
if (!neighbors || neighbors.size === 0) {
|
|
4216
|
-
allPaths.push([...
|
|
4529
|
+
allPaths.push([...path6]);
|
|
4217
4530
|
} else {
|
|
4218
4531
|
for (const neighbor of neighbors) {
|
|
4219
4532
|
if (!visited.has(neighbor)) {
|
|
4220
|
-
dfs(neighbor,
|
|
4533
|
+
dfs(neighbor, path6);
|
|
4221
4534
|
}
|
|
4222
4535
|
}
|
|
4223
4536
|
}
|
|
4224
|
-
|
|
4537
|
+
path6.pop();
|
|
4225
4538
|
visited.delete(file);
|
|
4226
4539
|
}
|
|
4227
4540
|
for (const root of roots.slice(0, 10)) {
|
|
@@ -5499,7 +5812,7 @@ function getFileCount8(graph) {
|
|
|
5499
5812
|
});
|
|
5500
5813
|
return files.size;
|
|
5501
5814
|
}
|
|
5502
|
-
function
|
|
5815
|
+
function isTestFile3(filePath) {
|
|
5503
5816
|
const fileName = basename4(filePath).toLowerCase();
|
|
5504
5817
|
const dirPath = dirname11(filePath).toLowerCase();
|
|
5505
5818
|
if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
|
|
@@ -5513,7 +5826,7 @@ function isTestFile(filePath) {
|
|
|
5513
5826
|
function getTestFiles(graph) {
|
|
5514
5827
|
const testFiles = /* @__PURE__ */ new Map();
|
|
5515
5828
|
graph.forEachNode((node, attrs) => {
|
|
5516
|
-
if (
|
|
5829
|
+
if (isTestFile3(attrs.filePath)) {
|
|
5517
5830
|
if (!testFiles.has(attrs.filePath)) {
|
|
5518
5831
|
testFiles.set(attrs.filePath, {
|
|
5519
5832
|
filePath: attrs.filePath,
|
|
@@ -5572,9 +5885,9 @@ function matchTestToSource(testFile) {
|
|
|
5572
5885
|
const srcDir = testDir.replace(/test[s]?/g, "src");
|
|
5573
5886
|
possiblePaths.push(srcDir + "/" + sourceFileName);
|
|
5574
5887
|
}
|
|
5575
|
-
for (const
|
|
5576
|
-
if (!
|
|
5577
|
-
return
|
|
5888
|
+
for (const path6 of possiblePaths) {
|
|
5889
|
+
if (!isTestFile3(path6)) {
|
|
5890
|
+
return path6;
|
|
5578
5891
|
}
|
|
5579
5892
|
}
|
|
5580
5893
|
return null;
|
|
@@ -5629,7 +5942,7 @@ function generateUntestedFiles(graph) {
|
|
|
5629
5942
|
allFiles.add(attrs.filePath);
|
|
5630
5943
|
});
|
|
5631
5944
|
for (const file of allFiles) {
|
|
5632
|
-
if (!
|
|
5945
|
+
if (!isTestFile3(file)) {
|
|
5633
5946
|
sourceFiles.push(file);
|
|
5634
5947
|
}
|
|
5635
5948
|
}
|
|
@@ -5688,7 +6001,7 @@ function generateTestCoverageMap(graph) {
|
|
|
5688
6001
|
allFiles.add(attrs.filePath);
|
|
5689
6002
|
});
|
|
5690
6003
|
for (const file of allFiles) {
|
|
5691
|
-
if (!
|
|
6004
|
+
if (!isTestFile3(file)) {
|
|
5692
6005
|
sourceFiles.push(file);
|
|
5693
6006
|
}
|
|
5694
6007
|
}
|
|
@@ -5740,7 +6053,7 @@ function generateTestStatistics(graph) {
|
|
|
5740
6053
|
allFiles.add(attrs.filePath);
|
|
5741
6054
|
});
|
|
5742
6055
|
for (const file of allFiles) {
|
|
5743
|
-
if (!
|
|
6056
|
+
if (!isTestFile3(file)) {
|
|
5744
6057
|
sourceFiles.push(file);
|
|
5745
6058
|
}
|
|
5746
6059
|
}
|
|
@@ -7005,6 +7318,98 @@ function generateDetailedMetrics(dimensions) {
|
|
|
7005
7318
|
return output;
|
|
7006
7319
|
}
|
|
7007
7320
|
|
|
7321
|
+
// src/docs/dead-code.ts
|
|
7322
|
+
import path5 from "path";
|
|
7323
|
+
function generateDeadCode(graph, projectRoot, projectName) {
|
|
7324
|
+
const report = analyzeDeadCode(graph, projectRoot, {
|
|
7325
|
+
confidence: "low",
|
|
7326
|
+
includeTests: false,
|
|
7327
|
+
verbose: true,
|
|
7328
|
+
stats: true,
|
|
7329
|
+
json: true
|
|
7330
|
+
});
|
|
7331
|
+
let output = "";
|
|
7332
|
+
output += header(`${projectName} - Dead Code Analysis`, 1);
|
|
7333
|
+
output += timestamp();
|
|
7334
|
+
output += "\n";
|
|
7335
|
+
output += header("Summary", 2);
|
|
7336
|
+
output += `Total symbols analyzed: **${formatNumber(report.totalSymbols)}**
|
|
7337
|
+
|
|
7338
|
+
`;
|
|
7339
|
+
output += `Potentially dead symbols: **${formatNumber(report.deadSymbols)}** (${report.deadPercentage.toFixed(1)}%)
|
|
7340
|
+
|
|
7341
|
+
`;
|
|
7342
|
+
output += `- \u{1F534} High confidence (definitely dead): **${report.byConfidence.high}**
|
|
7343
|
+
`;
|
|
7344
|
+
output += `- \u{1F7E1} Medium confidence (probably dead): **${report.byConfidence.medium}**
|
|
7345
|
+
`;
|
|
7346
|
+
output += `- \u26AA Low confidence (might be dead): **${report.byConfidence.low}**
|
|
7347
|
+
|
|
7348
|
+
`;
|
|
7349
|
+
const estimatedLines = report.deadSymbols * 18;
|
|
7350
|
+
output += `Estimated dead code: **~${formatNumber(estimatedLines)} lines**
|
|
7351
|
+
|
|
7352
|
+
`;
|
|
7353
|
+
const symbolsByConfidence = groupByConfidence2(report.symbols);
|
|
7354
|
+
if (symbolsByConfidence.high.length > 0) {
|
|
7355
|
+
output += generateConfidenceSection(
|
|
7356
|
+
"High Confidence",
|
|
7357
|
+
"definitely dead",
|
|
7358
|
+
symbolsByConfidence.high,
|
|
7359
|
+
projectRoot
|
|
7360
|
+
);
|
|
7361
|
+
}
|
|
7362
|
+
if (symbolsByConfidence.medium.length > 0) {
|
|
7363
|
+
output += generateConfidenceSection(
|
|
7364
|
+
"Medium Confidence",
|
|
7365
|
+
"probably dead",
|
|
7366
|
+
symbolsByConfidence.medium,
|
|
7367
|
+
projectRoot
|
|
7368
|
+
);
|
|
7369
|
+
}
|
|
7370
|
+
if (symbolsByConfidence.low.length > 0) {
|
|
7371
|
+
output += generateConfidenceSection(
|
|
7372
|
+
"Low Confidence",
|
|
7373
|
+
"might be dead",
|
|
7374
|
+
symbolsByConfidence.low,
|
|
7375
|
+
projectRoot
|
|
7376
|
+
);
|
|
7377
|
+
}
|
|
7378
|
+
output += "\n---\n\n";
|
|
7379
|
+
output += "_This document was auto-generated by Depwire. It reflects the current state of the codebase and should be reviewed before taking action._\n";
|
|
7380
|
+
return output;
|
|
7381
|
+
}
|
|
7382
|
+
function groupByConfidence2(symbols) {
|
|
7383
|
+
return symbols.reduce(
|
|
7384
|
+
(acc, symbol) => {
|
|
7385
|
+
acc[symbol.confidence].push(symbol);
|
|
7386
|
+
return acc;
|
|
7387
|
+
},
|
|
7388
|
+
{ high: [], medium: [], low: [] }
|
|
7389
|
+
);
|
|
7390
|
+
}
|
|
7391
|
+
function generateConfidenceSection(title, description, symbols, projectRoot) {
|
|
7392
|
+
let output = "";
|
|
7393
|
+
output += header(`${title} (${description})`, 2);
|
|
7394
|
+
output += `Found **${symbols.length}** symbol${symbols.length === 1 ? "" : "s"}.
|
|
7395
|
+
|
|
7396
|
+
`;
|
|
7397
|
+
const headers = ["Symbol", "Kind", "File", "Exported", "Reason"];
|
|
7398
|
+
const rows = symbols.map((s) => {
|
|
7399
|
+
const relativePath = path5.relative(projectRoot, s.file);
|
|
7400
|
+
return [
|
|
7401
|
+
code(s.name),
|
|
7402
|
+
s.kind,
|
|
7403
|
+
`${relativePath}:${s.line}`,
|
|
7404
|
+
s.exported ? "Yes" : "No",
|
|
7405
|
+
s.reason
|
|
7406
|
+
];
|
|
7407
|
+
});
|
|
7408
|
+
output += table(headers, rows);
|
|
7409
|
+
output += "\n";
|
|
7410
|
+
return output;
|
|
7411
|
+
}
|
|
7412
|
+
|
|
7008
7413
|
// src/docs/metadata.ts
|
|
7009
7414
|
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
7010
7415
|
import { join as join11 } from "path";
|
|
@@ -7088,7 +7493,8 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7088
7493
|
"history",
|
|
7089
7494
|
"current",
|
|
7090
7495
|
"status",
|
|
7091
|
-
"health"
|
|
7496
|
+
"health",
|
|
7497
|
+
"dead_code"
|
|
7092
7498
|
];
|
|
7093
7499
|
}
|
|
7094
7500
|
let metadata = null;
|
|
@@ -7231,6 +7637,17 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
7231
7637
|
errors.push(`Failed to generate HEALTH.md: ${err}`);
|
|
7232
7638
|
}
|
|
7233
7639
|
}
|
|
7640
|
+
if (docsToGenerate.includes("dead_code")) {
|
|
7641
|
+
try {
|
|
7642
|
+
if (options.verbose) console.log("Generating DEAD_CODE.md...");
|
|
7643
|
+
const content = generateDeadCode(graph, projectRoot, version);
|
|
7644
|
+
const filePath = join12(options.outputDir, "DEAD_CODE.md");
|
|
7645
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
7646
|
+
generated.push("DEAD_CODE.md");
|
|
7647
|
+
} catch (err) {
|
|
7648
|
+
errors.push(`Failed to generate DEAD_CODE.md: ${err}`);
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7234
7651
|
} else if (options.format === "json") {
|
|
7235
7652
|
errors.push("JSON format not yet supported");
|
|
7236
7653
|
}
|
|
@@ -7689,8 +8106,8 @@ function createSnapshot(graph, commitHash, commitDate, commitMessage, commitAuth
|
|
|
7689
8106
|
}
|
|
7690
8107
|
}
|
|
7691
8108
|
}
|
|
7692
|
-
const files = Array.from(fileMap.entries()).map(([
|
|
7693
|
-
path:
|
|
8109
|
+
const files = Array.from(fileMap.entries()).map(([path6, data]) => ({
|
|
8110
|
+
path: path6,
|
|
7694
8111
|
symbols: data.symbols,
|
|
7695
8112
|
connections: data.inbound + data.outbound
|
|
7696
8113
|
}));
|
|
@@ -7927,6 +8344,21 @@ function getToolsList() {
|
|
|
7927
8344
|
}
|
|
7928
8345
|
}
|
|
7929
8346
|
}
|
|
8347
|
+
},
|
|
8348
|
+
{
|
|
8349
|
+
name: "find_dead_code",
|
|
8350
|
+
description: "Find potentially dead code \u2014 symbols that are defined but never referenced anywhere in the codebase. Returns symbols categorized by confidence level (high, medium, low). High confidence means definitely unused. Use this to identify cleanup opportunities.",
|
|
8351
|
+
inputSchema: {
|
|
8352
|
+
type: "object",
|
|
8353
|
+
properties: {
|
|
8354
|
+
confidence: {
|
|
8355
|
+
type: "string",
|
|
8356
|
+
enum: ["high", "medium", "low"],
|
|
8357
|
+
description: "Minimum confidence level to return (default: medium)",
|
|
8358
|
+
default: "medium"
|
|
8359
|
+
}
|
|
8360
|
+
}
|
|
8361
|
+
}
|
|
7930
8362
|
}
|
|
7931
8363
|
];
|
|
7932
8364
|
}
|
|
@@ -7989,6 +8421,15 @@ async function handleToolCall(name, args, state) {
|
|
|
7989
8421
|
} else {
|
|
7990
8422
|
result = await handleGetTemporalGraph(state, args.commits || 10, args.strategy || "even");
|
|
7991
8423
|
}
|
|
8424
|
+
} else if (name === "find_dead_code") {
|
|
8425
|
+
if (!isProjectLoaded(state)) {
|
|
8426
|
+
result = {
|
|
8427
|
+
error: "No project loaded",
|
|
8428
|
+
message: "Use connect_repo to connect to a codebase first"
|
|
8429
|
+
};
|
|
8430
|
+
} else {
|
|
8431
|
+
result = handleFindDeadCode(state, args.confidence || "medium");
|
|
8432
|
+
}
|
|
7992
8433
|
} else {
|
|
7993
8434
|
if (!isProjectLoaded(state)) {
|
|
7994
8435
|
result = {
|
|
@@ -8604,6 +9045,46 @@ async function handleGetTemporalGraph(state, commits, strategy) {
|
|
|
8604
9045
|
};
|
|
8605
9046
|
}
|
|
8606
9047
|
}
|
|
9048
|
+
function handleFindDeadCode(state, confidence) {
|
|
9049
|
+
if (!state.graph || !state.projectRoot) {
|
|
9050
|
+
return {
|
|
9051
|
+
error: "No project loaded",
|
|
9052
|
+
message: "Use connect_repo to connect to a codebase first"
|
|
9053
|
+
};
|
|
9054
|
+
}
|
|
9055
|
+
try {
|
|
9056
|
+
const report = analyzeDeadCode(state.graph, state.projectRoot, {
|
|
9057
|
+
confidence,
|
|
9058
|
+
includeTests: false,
|
|
9059
|
+
verbose: false,
|
|
9060
|
+
stats: false,
|
|
9061
|
+
json: true
|
|
9062
|
+
});
|
|
9063
|
+
return {
|
|
9064
|
+
status: "success",
|
|
9065
|
+
totalSymbols: report.totalSymbols,
|
|
9066
|
+
deadSymbols: report.deadSymbols,
|
|
9067
|
+
deadPercentage: report.deadPercentage,
|
|
9068
|
+
byConfidence: report.byConfidence,
|
|
9069
|
+
symbols: report.symbols.map((s) => ({
|
|
9070
|
+
name: s.name,
|
|
9071
|
+
kind: s.kind,
|
|
9072
|
+
file: s.file,
|
|
9073
|
+
line: s.line,
|
|
9074
|
+
exported: s.exported,
|
|
9075
|
+
dependents: s.dependents,
|
|
9076
|
+
confidence: s.confidence,
|
|
9077
|
+
reason: s.reason
|
|
9078
|
+
})),
|
|
9079
|
+
summary: `Found ${report.deadSymbols} potentially dead symbols (${report.byConfidence.high} high, ${report.byConfidence.medium} medium, ${report.byConfidence.low} low confidence) out of ${report.totalSymbols} total symbols (${report.deadPercentage.toFixed(1)}% dead code).`
|
|
9080
|
+
};
|
|
9081
|
+
} catch (error) {
|
|
9082
|
+
return {
|
|
9083
|
+
error: "Failed to analyze dead code",
|
|
9084
|
+
message: String(error)
|
|
9085
|
+
};
|
|
9086
|
+
}
|
|
9087
|
+
}
|
|
8607
9088
|
|
|
8608
9089
|
// src/mcp/server.ts
|
|
8609
9090
|
async function startMcpServer(state) {
|
|
@@ -8652,6 +9133,7 @@ export {
|
|
|
8652
9133
|
updateFileInGraph,
|
|
8653
9134
|
calculateHealthScore,
|
|
8654
9135
|
getHealthTrend,
|
|
9136
|
+
analyzeDeadCode,
|
|
8655
9137
|
generateDocs,
|
|
8656
9138
|
getCommitLog,
|
|
8657
9139
|
getCurrentBranch,
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
analyzeDeadCode,
|
|
3
4
|
buildGraph,
|
|
4
5
|
calculateHealthScore,
|
|
5
6
|
checkoutCommit,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
stashChanges,
|
|
27
28
|
updateFileInGraph,
|
|
28
29
|
watchProject
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-IGRFC3MQ.js";
|
|
30
31
|
|
|
31
32
|
// src/index.ts
|
|
32
33
|
import { Command } from "commander";
|
|
@@ -787,4 +788,32 @@ program.command("health").description("Analyze dependency architecture health (0
|
|
|
787
788
|
process.exit(1);
|
|
788
789
|
}
|
|
789
790
|
});
|
|
791
|
+
program.command("dead-code").description("Identify dead code - symbols defined but never referenced").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--confidence <level>", "Minimum confidence level to show: high, medium, low (default: medium)", "medium").option("--json", "Output as JSON (for CI/automation)").option("--verbose", "Show detailed info for each dead symbol").option("--stats", "Show summary statistics").option("--include-tests", "Include test files in analysis").option("--include-low", "Shortcut for --confidence low").action(async (directory, options) => {
|
|
792
|
+
try {
|
|
793
|
+
const projectRoot = directory ? resolve(directory) : findProjectRoot();
|
|
794
|
+
const startTime = Date.now();
|
|
795
|
+
const parsedFiles = await parseProject(projectRoot);
|
|
796
|
+
const graph = buildGraph(parsedFiles);
|
|
797
|
+
const confidence = options.includeLow ? "low" : options.confidence || "medium";
|
|
798
|
+
const report = analyzeDeadCode(graph, projectRoot, {
|
|
799
|
+
confidence,
|
|
800
|
+
includeTests: options.includeTests || false,
|
|
801
|
+
verbose: options.verbose || false,
|
|
802
|
+
stats: options.stats || false,
|
|
803
|
+
json: options.json || false
|
|
804
|
+
});
|
|
805
|
+
if (options.json) {
|
|
806
|
+
console.log(JSON.stringify(report, null, 2));
|
|
807
|
+
}
|
|
808
|
+
const totalTime = Date.now() - startTime;
|
|
809
|
+
if (!options.json) {
|
|
810
|
+
console.log(`
|
|
811
|
+
Analysis completed in ${(totalTime / 1e3).toFixed(2)}s
|
|
812
|
+
`);
|
|
813
|
+
}
|
|
814
|
+
} catch (err) {
|
|
815
|
+
console.error("Error analyzing dead code:", err);
|
|
816
|
+
process.exit(1);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
790
819
|
program.parse();
|
package/dist/mcpb-entry.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "depwire-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Code cross-reference visualization and AI context engine for TypeScript, JavaScript, Python, and Go. Zero native dependencies โ works on Windows, macOS, and Linux.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"cross-reference"
|
|
32
32
|
],
|
|
33
33
|
"author": "Atef Ataya (https://www.youtube.com/@atefataya)",
|
|
34
|
-
"license": "
|
|
34
|
+
"license": "BUSL-1.1",
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
37
|
"url": "git+https://github.com/depwire/depwire.git"
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@modelcontextprotocol/sdk": "1.26.0",
|
|
54
|
+
"chalk": "^5.6.2",
|
|
54
55
|
"chokidar": "5.0.0",
|
|
55
56
|
"commander": "14.0.3",
|
|
56
57
|
"express": "5.2.1",
|