depwire-cli 0.9.26 → 0.9.27
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/README.md +32 -2
- package/dist/{chunk-YYY5TNG7.js → chunk-DA5LWNJ4.js} +40 -17
- package/dist/{chunk-B2KGFBZL.js → chunk-RGD3YJYQ.js} +21 -13
- package/dist/index.js +36 -33
- package/dist/mcpb-entry.js +2 -2
- package/dist/sdk.js +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
**The missing context layer for AI coding assistants.**
|
|
13
13
|
|
|
14
|
-
Deterministic dependency graph.
|
|
14
|
+
Deterministic dependency graph. 17 MCP tools. Architecture health. What If simulation. Security scanner.
|
|
15
15
|
|
|
16
16
|
The context layer that turns vibe coding into software engineering.
|
|
17
17
|
|
|
@@ -69,6 +69,7 @@ depwire health
|
|
|
69
69
|
depwire dead-code
|
|
70
70
|
depwire temporal
|
|
71
71
|
depwire whatif
|
|
72
|
+
depwire security
|
|
72
73
|
|
|
73
74
|
# Or specify a directory explicitly
|
|
74
75
|
npx depwire-cli viz ./my-project
|
|
@@ -145,6 +146,7 @@ Settings → Features → Experimental → Enable MCP → Add Server:
|
|
|
145
146
|
| `find_dead_code` | Find dead code — symbols defined but never referenced |
|
|
146
147
|
| `get_temporal_graph` | Show how the graph evolved over git history |
|
|
147
148
|
| `simulate_change` | Simulate a move/delete/rename/split/merge before touching code. Returns health score delta, broken imports, and affected nodes. Zero file I/O. |
|
|
149
|
+
| `security_scan` | Scan for security vulnerabilities with graph-aware severity elevation. No API key required. |
|
|
148
150
|
|
|
149
151
|
## SDK
|
|
150
152
|
|
|
@@ -161,6 +163,7 @@ import {
|
|
|
161
163
|
calculateHealthScore,
|
|
162
164
|
analyzeDeadCode,
|
|
163
165
|
generateDocs,
|
|
166
|
+
scanSecurity,
|
|
164
167
|
SimulationEngine,
|
|
165
168
|
searchSymbols,
|
|
166
169
|
getImpact,
|
|
@@ -187,6 +190,32 @@ depwire whatif . --simulate merge --target src/utils/helpers.ts --merge-target s
|
|
|
187
190
|
Returns: health score delta, broken imports, affected nodes, circular deps introduced/resolved.
|
|
188
191
|
Also available as MCP tool `simulate_change` for AI coding assistants.
|
|
189
192
|
|
|
193
|
+
## Security Scanner
|
|
194
|
+
|
|
195
|
+
Scan your codebase for security vulnerabilities before AI-generated code ships to production:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
depwire security . # Full repo scan
|
|
199
|
+
depwire security . --target src/auth.ts # Single file
|
|
200
|
+
depwire security . --format sarif # GitHub Security tab
|
|
201
|
+
depwire security . --fail-on high # CI gate — exit 1 if HIGH+
|
|
202
|
+
depwire security . --class injection # Specific check only
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
10 vulnerability categories:
|
|
206
|
+
- Dependency CVEs (npm/pip/cargo/go audit)
|
|
207
|
+
- Shell injection + code injection
|
|
208
|
+
- Hardcoded secrets (API keys, passwords, private keys)
|
|
209
|
+
- Path traversal
|
|
210
|
+
- Auth bypass patterns
|
|
211
|
+
- Input validation gaps
|
|
212
|
+
- Information disclosure
|
|
213
|
+
- Cryptography weaknesses
|
|
214
|
+
- Frontend XSS (dangerouslySetInnerHTML, localStorage tokens)
|
|
215
|
+
- Architecture-level risks (graph-powered severity elevation)
|
|
216
|
+
|
|
217
|
+
Graph-aware severity: vulnerabilities reachable from MCP tools or HTTP routes are automatically elevated. Available as MCP tool `security_scan` and via `depwire-cli/sdk`.
|
|
218
|
+
|
|
190
219
|
## Why Depwire
|
|
191
220
|
|
|
192
221
|
| Feature | Depwire | Standard RAG (Fuzzy Search) | LLM Native Scanning |
|
|
@@ -721,7 +750,7 @@ See [SECURITY.md](SECURITY.md) for full details.
|
|
|
721
750
|
|
|
722
751
|
### ✅ Shipped
|
|
723
752
|
- [x] Arc diagram visualization
|
|
724
|
-
- [x] MCP server (
|
|
753
|
+
- [x] MCP server (17 tools)
|
|
725
754
|
- [x] Multi-language support (TypeScript, JavaScript, Python, Go, Rust, C)
|
|
726
755
|
- [x] File watching + live refresh
|
|
727
756
|
- [x] Auto-generated documentation (13 documents)
|
|
@@ -733,6 +762,7 @@ See [SECURITY.md](SECURITY.md) for full details.
|
|
|
733
762
|
- [x] WASM migration (Windows support)
|
|
734
763
|
- [x] Cloud dashboard — [app.depwire.dev](https://app.depwire.dev)
|
|
735
764
|
- [x] What If simulation — simulate refactors before touching code
|
|
765
|
+
- [x] Security scanner — deterministic vulnerability detection with graph-aware severity
|
|
736
766
|
|
|
737
767
|
### Coming Next
|
|
738
768
|
- [ ] New language support (Java, C++, Ruby — community requested)
|
|
@@ -106,7 +106,7 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
106
106
|
|
|
107
107
|
// src/parser/index.ts
|
|
108
108
|
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
109
|
-
import { join as join8 } from "path";
|
|
109
|
+
import { join as join8, resolve as resolve3 } from "path";
|
|
110
110
|
|
|
111
111
|
// src/parser/detect.ts
|
|
112
112
|
import { extname as extname3 } from "path";
|
|
@@ -1578,7 +1578,7 @@ var javascriptParser = {
|
|
|
1578
1578
|
|
|
1579
1579
|
// src/parser/go.ts
|
|
1580
1580
|
import { existsSync as existsSync5, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
1581
|
-
import { join as join5, dirname as dirname4 } from "path";
|
|
1581
|
+
import { join as join5, dirname as dirname4, resolve as resolve2 } from "path";
|
|
1582
1582
|
function parseGoFile(filePath, sourceCode, projectRoot) {
|
|
1583
1583
|
const parser = getParser("go");
|
|
1584
1584
|
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
@@ -1860,7 +1860,7 @@ function processCallExpression4(node, context) {
|
|
|
1860
1860
|
function readGoModuleName(projectRoot) {
|
|
1861
1861
|
let currentDir = projectRoot;
|
|
1862
1862
|
for (let i = 0; i < 5; i++) {
|
|
1863
|
-
const goModPath =
|
|
1863
|
+
const goModPath = resolve2(currentDir, "go.mod");
|
|
1864
1864
|
if (existsSync5(goModPath)) {
|
|
1865
1865
|
try {
|
|
1866
1866
|
const content = readFileSync2(goModPath, "utf-8");
|
|
@@ -2822,6 +2822,10 @@ async function parseProject(projectRoot, options) {
|
|
|
2822
2822
|
for (const file of files) {
|
|
2823
2823
|
try {
|
|
2824
2824
|
const fullPath = join8(projectRoot, file);
|
|
2825
|
+
if (!resolve3(fullPath).startsWith(resolve3(projectRoot))) {
|
|
2826
|
+
skippedFiles++;
|
|
2827
|
+
continue;
|
|
2828
|
+
}
|
|
2825
2829
|
if (options?.exclude) {
|
|
2826
2830
|
const shouldExclude2 = options.exclude.some(
|
|
2827
2831
|
(pattern) => minimatch(file, pattern, { matchBase: true })
|
|
@@ -3508,7 +3512,7 @@ function calculateDepthScore(graph) {
|
|
|
3508
3512
|
|
|
3509
3513
|
// src/health/index.ts
|
|
3510
3514
|
import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync8, mkdirSync } from "fs";
|
|
3511
|
-
import {
|
|
3515
|
+
import { dirname as dirname8, resolve as resolve4 } from "path";
|
|
3512
3516
|
function calculateHealthScore(graph, projectRoot) {
|
|
3513
3517
|
const coupling = calculateCouplingScore(graph);
|
|
3514
3518
|
const cohesion = calculateCohesionScore(graph);
|
|
@@ -3607,7 +3611,11 @@ function getHealthTrend(projectRoot, currentScore) {
|
|
|
3607
3611
|
}
|
|
3608
3612
|
}
|
|
3609
3613
|
function saveHealthHistory(projectRoot, report) {
|
|
3610
|
-
const
|
|
3614
|
+
const resolvedRoot = resolve4(projectRoot);
|
|
3615
|
+
const historyFile = resolve4(resolvedRoot, ".depwire", "health-history.json");
|
|
3616
|
+
if (!historyFile.startsWith(resolvedRoot)) {
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3611
3619
|
const entry = {
|
|
3612
3620
|
timestamp: report.timestamp,
|
|
3613
3621
|
score: report.overall,
|
|
@@ -3621,6 +3629,7 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
3621
3629
|
let history = [];
|
|
3622
3630
|
if (existsSync8(historyFile)) {
|
|
3623
3631
|
try {
|
|
3632
|
+
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
3624
3633
|
const content = readFileSync6(historyFile, "utf-8");
|
|
3625
3634
|
history = JSON.parse(content);
|
|
3626
3635
|
} catch {
|
|
@@ -3631,14 +3640,17 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
3631
3640
|
history = history.slice(-50);
|
|
3632
3641
|
}
|
|
3633
3642
|
mkdirSync(dirname8(historyFile), { recursive: true });
|
|
3643
|
+
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
3634
3644
|
writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
|
|
3635
3645
|
}
|
|
3636
3646
|
function loadHealthHistory(projectRoot) {
|
|
3637
|
-
const
|
|
3638
|
-
|
|
3647
|
+
const resolvedRoot = resolve4(projectRoot);
|
|
3648
|
+
const historyFile = resolve4(resolvedRoot, ".depwire", "health-history.json");
|
|
3649
|
+
if (!historyFile.startsWith(resolvedRoot) || !existsSync8(historyFile)) {
|
|
3639
3650
|
return [];
|
|
3640
3651
|
}
|
|
3641
3652
|
try {
|
|
3653
|
+
if (!historyFile.startsWith(resolvedRoot)) return [];
|
|
3642
3654
|
const content = readFileSync6(historyFile, "utf-8");
|
|
3643
3655
|
return JSON.parse(content);
|
|
3644
3656
|
} catch {
|
|
@@ -3777,8 +3789,9 @@ function isRelevantForDeadCodeDetection(attrs) {
|
|
|
3777
3789
|
}
|
|
3778
3790
|
function getPackageEntryPoints(projectRoot) {
|
|
3779
3791
|
const entryPoints = /* @__PURE__ */ new Set();
|
|
3780
|
-
const
|
|
3781
|
-
|
|
3792
|
+
const resolvedRoot = path2.resolve(projectRoot);
|
|
3793
|
+
const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
|
|
3794
|
+
if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync9(packageJsonPath)) {
|
|
3782
3795
|
return entryPoints;
|
|
3783
3796
|
}
|
|
3784
3797
|
try {
|
|
@@ -7441,7 +7454,7 @@ function getTopLevelDir2(filePath) {
|
|
|
7441
7454
|
|
|
7442
7455
|
// src/docs/status.ts
|
|
7443
7456
|
import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
|
|
7444
|
-
import {
|
|
7457
|
+
import { resolve as resolve5 } from "path";
|
|
7445
7458
|
function generateStatus(graph, projectRoot, version) {
|
|
7446
7459
|
let output = "";
|
|
7447
7460
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -7474,7 +7487,11 @@ function getFileCount11(graph) {
|
|
|
7474
7487
|
}
|
|
7475
7488
|
function extractComments(projectRoot, filePath) {
|
|
7476
7489
|
const comments = [];
|
|
7477
|
-
const
|
|
7490
|
+
const resolvedRoot = resolve5(projectRoot);
|
|
7491
|
+
const fullPath = resolve5(resolvedRoot, filePath);
|
|
7492
|
+
if (!fullPath.startsWith(resolvedRoot)) {
|
|
7493
|
+
return comments;
|
|
7494
|
+
}
|
|
7478
7495
|
if (!existsSync10(fullPath)) {
|
|
7479
7496
|
return comments;
|
|
7480
7497
|
}
|
|
@@ -8059,10 +8076,11 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
|
|
|
8059
8076
|
|
|
8060
8077
|
// src/docs/metadata.ts
|
|
8061
8078
|
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "fs";
|
|
8062
|
-
import {
|
|
8079
|
+
import { resolve as resolve6 } from "path";
|
|
8063
8080
|
function loadMetadata(outputDir) {
|
|
8064
|
-
const
|
|
8065
|
-
|
|
8081
|
+
const resolvedDir = resolve6(outputDir);
|
|
8082
|
+
const metadataPath = resolve6(resolvedDir, "metadata.json");
|
|
8083
|
+
if (!metadataPath.startsWith(resolvedDir) || !existsSync11(metadataPath)) {
|
|
8066
8084
|
return null;
|
|
8067
8085
|
}
|
|
8068
8086
|
try {
|
|
@@ -8074,7 +8092,11 @@ function loadMetadata(outputDir) {
|
|
|
8074
8092
|
}
|
|
8075
8093
|
}
|
|
8076
8094
|
function saveMetadata(outputDir, metadata) {
|
|
8077
|
-
const
|
|
8095
|
+
const resolvedDir = resolve6(outputDir);
|
|
8096
|
+
const metadataPath = resolve6(resolvedDir, "metadata.json");
|
|
8097
|
+
if (!metadataPath.startsWith(resolvedDir)) {
|
|
8098
|
+
throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
|
|
8099
|
+
}
|
|
8078
8100
|
writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
8079
8101
|
}
|
|
8080
8102
|
function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
|
|
@@ -9057,6 +9079,7 @@ async function checkInjection(files, projectRoot) {
|
|
|
9057
9079
|
if (line.trimStart().startsWith("//") || line.trimStart().startsWith("#") || line.trimStart().startsWith("*")) {
|
|
9058
9080
|
continue;
|
|
9059
9081
|
}
|
|
9082
|
+
if (line.includes("depwire-security-reviewed")) continue;
|
|
9060
9083
|
for (const pattern of PATTERNS) {
|
|
9061
9084
|
if (pattern.regex.test(line)) {
|
|
9062
9085
|
let severity = pattern.baseSeverity;
|
|
@@ -9217,8 +9240,8 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
9217
9240
|
if (SAFE_OUTPUT_PATTERNS.test(context)) continue;
|
|
9218
9241
|
}
|
|
9219
9242
|
if (/__dirname/.test(line) && SAFE_DIRNAME_ARGS.test(line)) continue;
|
|
9220
|
-
const nearbyLines = lines.slice(Math.max(0, i -
|
|
9221
|
-
if (nearbyLines.includes("startsWith") && nearbyLines
|
|
9243
|
+
const nearbyLines = lines.slice(Math.max(0, i - 15), Math.min(lines.length, i + 4)).join("\n");
|
|
9244
|
+
if (nearbyLines.includes("startsWith") && /resolve/.test(nearbyLines)) continue;
|
|
9222
9245
|
const severity = inRouteOrTool ? "high" : "medium";
|
|
9223
9246
|
findings.push({
|
|
9224
9247
|
id: "",
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
parseTypeScriptFile,
|
|
17
17
|
scanSecurity,
|
|
18
18
|
searchSymbols
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-DA5LWNJ4.js";
|
|
20
20
|
|
|
21
21
|
// src/viz/data.ts
|
|
22
22
|
import { basename } from "path";
|
|
@@ -171,14 +171,14 @@ async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
|
171
171
|
const net = await import("net");
|
|
172
172
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
173
173
|
const testPort = startPort + attempt;
|
|
174
|
-
const isAvailable = await new Promise((
|
|
174
|
+
const isAvailable = await new Promise((resolve4) => {
|
|
175
175
|
const server = net.createServer();
|
|
176
176
|
server.once("error", () => {
|
|
177
|
-
|
|
177
|
+
resolve4(false);
|
|
178
178
|
});
|
|
179
179
|
server.once("listening", () => {
|
|
180
180
|
server.close();
|
|
181
|
-
|
|
181
|
+
resolve4(true);
|
|
182
182
|
});
|
|
183
183
|
server.listen(testPort, "127.0.0.1");
|
|
184
184
|
});
|
|
@@ -377,7 +377,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
377
377
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
378
378
|
|
|
379
379
|
// src/mcp/tools.ts
|
|
380
|
-
import { dirname as dirname2, join as join5 } from "path";
|
|
380
|
+
import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
|
|
381
381
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
382
382
|
|
|
383
383
|
// src/mcp/connect.ts
|
|
@@ -626,8 +626,8 @@ async function getCurrentBranch(dir) {
|
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
628
|
async function checkoutCommit(dir, hash) {
|
|
629
|
-
if (!/^[a-
|
|
630
|
-
throw new Error(`Invalid
|
|
629
|
+
if (!/^[a-f0-9]+$/.test(hash)) {
|
|
630
|
+
throw new Error(`Invalid commit hash: ${hash}`);
|
|
631
631
|
}
|
|
632
632
|
try {
|
|
633
633
|
execSync(`git checkout -q ${hash}`, { cwd: dir, stdio: "ignore" });
|
|
@@ -636,11 +636,12 @@ async function checkoutCommit(dir, hash) {
|
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
638
|
async function restoreOriginal(dir, originalBranch) {
|
|
639
|
-
if (!/^[a-zA-Z0-
|
|
640
|
-
throw new Error(`Invalid
|
|
639
|
+
if (!/^[a-zA-Z0-9/_.\-]+$/.test(originalBranch)) {
|
|
640
|
+
throw new Error(`Invalid branch name: ${originalBranch}`);
|
|
641
641
|
}
|
|
642
642
|
try {
|
|
643
643
|
execSync(`git checkout -q ${originalBranch}`, {
|
|
644
|
+
// depwire-security-reviewed: branch validated above
|
|
644
645
|
cwd: dir,
|
|
645
646
|
stdio: "ignore"
|
|
646
647
|
});
|
|
@@ -788,19 +789,22 @@ function getWeekNumber(date) {
|
|
|
788
789
|
|
|
789
790
|
// src/temporal/snapshots.ts
|
|
790
791
|
import { writeFileSync, readFileSync, mkdirSync, existsSync as existsSync2, readdirSync } from "fs";
|
|
791
|
-
import {
|
|
792
|
+
import { resolve as resolve2 } from "path";
|
|
792
793
|
function saveSnapshot(snapshot, outputDir) {
|
|
793
794
|
if (!existsSync2(outputDir)) {
|
|
794
795
|
mkdirSync(outputDir, { recursive: true });
|
|
795
796
|
}
|
|
796
797
|
const filename = `${snapshot.commitHash.substring(0, 8)}.json`;
|
|
797
|
-
const filepath =
|
|
798
|
+
const filepath = resolve2(outputDir, filename);
|
|
799
|
+
if (!filepath.startsWith(resolve2(outputDir))) {
|
|
800
|
+
throw new Error(`Path traversal attempt blocked: ${filepath}`);
|
|
801
|
+
}
|
|
798
802
|
writeFileSync(filepath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
799
803
|
}
|
|
800
804
|
function loadSnapshot(commitHash, outputDir) {
|
|
801
805
|
const shortHash = commitHash.substring(0, 8);
|
|
802
|
-
const filepath =
|
|
803
|
-
if (!existsSync2(filepath)) {
|
|
806
|
+
const filepath = resolve2(outputDir, `${shortHash}.json`);
|
|
807
|
+
if (!filepath.startsWith(resolve2(outputDir)) || !existsSync2(filepath)) {
|
|
804
808
|
return null;
|
|
805
809
|
}
|
|
806
810
|
try {
|
|
@@ -1737,6 +1741,10 @@ Available document types:
|
|
|
1737
1741
|
continue;
|
|
1738
1742
|
}
|
|
1739
1743
|
const filePath = join5(docsDir, metadata.documents[doc].file);
|
|
1744
|
+
if (!resolve3(filePath).startsWith(resolve3(docsDir))) {
|
|
1745
|
+
missing.push(doc);
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1740
1748
|
if (!existsSync3(filePath)) {
|
|
1741
1749
|
missing.push(doc);
|
|
1742
1750
|
continue;
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
stashChanges,
|
|
18
18
|
updateFileInGraph,
|
|
19
19
|
watchProject
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-RGD3YJYQ.js";
|
|
21
21
|
import {
|
|
22
22
|
SimulationEngine,
|
|
23
23
|
analyzeDeadCode,
|
|
@@ -31,11 +31,11 @@ import {
|
|
|
31
31
|
parseProject,
|
|
32
32
|
scanSecurity,
|
|
33
33
|
searchSymbols
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-DA5LWNJ4.js";
|
|
35
35
|
|
|
36
36
|
// src/index.ts
|
|
37
37
|
import { Command } from "commander";
|
|
38
|
-
import { resolve as
|
|
38
|
+
import { resolve as resolve4, dirname as dirname4, join as join5 } from "path";
|
|
39
39
|
import { writeFileSync, readFileSync as readFileSync3, existsSync } from "fs";
|
|
40
40
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
41
41
|
|
|
@@ -231,7 +231,7 @@ import { join as join2 } from "path";
|
|
|
231
231
|
import express from "express";
|
|
232
232
|
import { readFileSync } from "fs";
|
|
233
233
|
import { fileURLToPath } from "url";
|
|
234
|
-
import { dirname,
|
|
234
|
+
import { dirname, resolve } from "path";
|
|
235
235
|
import open from "open";
|
|
236
236
|
|
|
237
237
|
// src/viz/temporal-data.ts
|
|
@@ -306,10 +306,10 @@ async function findAvailablePort(startPort) {
|
|
|
306
306
|
const net = await import("net");
|
|
307
307
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
308
308
|
const testPort = startPort + attempt;
|
|
309
|
-
const isAvailable = await new Promise((
|
|
310
|
-
const server = net.createServer().once("error", () =>
|
|
309
|
+
const isAvailable = await new Promise((resolve5) => {
|
|
310
|
+
const server = net.createServer().once("error", () => resolve5(false)).once("listening", () => {
|
|
311
311
|
server.close();
|
|
312
|
-
|
|
312
|
+
resolve5(true);
|
|
313
313
|
}).listen(testPort, "127.0.0.1");
|
|
314
314
|
});
|
|
315
315
|
if (isAvailable) {
|
|
@@ -325,19 +325,22 @@ async function startTemporalServer(snapshots, projectRoot, preferredPort = 3334)
|
|
|
325
325
|
app.get("/api/data", (_req, res) => {
|
|
326
326
|
res.json(vizData);
|
|
327
327
|
});
|
|
328
|
-
const publicDir =
|
|
328
|
+
const publicDir = resolve(__dirname, "viz", "public");
|
|
329
329
|
app.get("/", (_req, res) => {
|
|
330
|
-
const htmlPath =
|
|
330
|
+
const htmlPath = resolve(publicDir, "temporal.html");
|
|
331
|
+
if (!htmlPath.startsWith(publicDir)) return res.status(403).send("Forbidden");
|
|
331
332
|
const html = readFileSync(htmlPath, "utf-8");
|
|
332
333
|
res.send(html);
|
|
333
334
|
});
|
|
334
335
|
app.get("/temporal.js", (_req, res) => {
|
|
335
|
-
const jsPath =
|
|
336
|
+
const jsPath = resolve(publicDir, "temporal.js");
|
|
337
|
+
if (!jsPath.startsWith(publicDir)) return res.status(403).send("Forbidden");
|
|
336
338
|
const js = readFileSync(jsPath, "utf-8");
|
|
337
339
|
res.type("application/javascript").send(js);
|
|
338
340
|
});
|
|
339
341
|
app.get("/temporal.css", (_req, res) => {
|
|
340
|
-
const cssPath =
|
|
342
|
+
const cssPath = resolve(publicDir, "temporal.css");
|
|
343
|
+
if (!cssPath.startsWith(publicDir)) return res.status(403).send("Forbidden");
|
|
341
344
|
const css = readFileSync(cssPath, "utf-8");
|
|
342
345
|
res.type("text/css").send(css);
|
|
343
346
|
});
|
|
@@ -350,13 +353,13 @@ async function startTemporalServer(snapshots, projectRoot, preferredPort = 3334)
|
|
|
350
353
|
console.log(" (Could not open browser automatically)");
|
|
351
354
|
});
|
|
352
355
|
});
|
|
353
|
-
await new Promise((
|
|
356
|
+
await new Promise((resolve5, reject) => {
|
|
354
357
|
server.on("error", reject);
|
|
355
358
|
process.on("SIGINT", () => {
|
|
356
359
|
console.log("\n\nShutting down temporal server...");
|
|
357
360
|
server.close(() => {
|
|
358
361
|
console.log("Server stopped");
|
|
359
|
-
|
|
362
|
+
resolve5();
|
|
360
363
|
process.exit(0);
|
|
361
364
|
});
|
|
362
365
|
});
|
|
@@ -501,7 +504,7 @@ async function trackCommand(command, version = "unknown") {
|
|
|
501
504
|
}
|
|
502
505
|
|
|
503
506
|
// src/commands/whatif.ts
|
|
504
|
-
import { resolve } from "path";
|
|
507
|
+
import { resolve as resolve2 } from "path";
|
|
505
508
|
import chalk from "chalk";
|
|
506
509
|
|
|
507
510
|
// src/viz/whatif-server.ts
|
|
@@ -690,14 +693,14 @@ async function findAvailablePort2(startPort, maxAttempts = 10) {
|
|
|
690
693
|
const net = await import("net");
|
|
691
694
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
692
695
|
const testPort = startPort + attempt;
|
|
693
|
-
const isAvailable = await new Promise((
|
|
696
|
+
const isAvailable = await new Promise((resolve5) => {
|
|
694
697
|
const server = net.createServer();
|
|
695
698
|
server.once("error", () => {
|
|
696
|
-
|
|
699
|
+
resolve5(false);
|
|
697
700
|
});
|
|
698
701
|
server.once("listening", () => {
|
|
699
702
|
server.close();
|
|
700
|
-
|
|
703
|
+
resolve5(true);
|
|
701
704
|
});
|
|
702
705
|
server.listen(testPort, "127.0.0.1");
|
|
703
706
|
});
|
|
@@ -752,7 +755,7 @@ Opening What If UI at ${url}`);
|
|
|
752
755
|
// src/commands/whatif.ts
|
|
753
756
|
async function whatif(dir, options) {
|
|
754
757
|
if (!options.simulate) {
|
|
755
|
-
const projectRoot2 = dir === "." ? findProjectRoot() :
|
|
758
|
+
const projectRoot2 = dir === "." ? findProjectRoot() : resolve2(dir);
|
|
756
759
|
console.error(`Parsing project: ${projectRoot2}`);
|
|
757
760
|
const parsedFiles2 = await parseProject(projectRoot2);
|
|
758
761
|
const graph2 = buildGraph(parsedFiles2);
|
|
@@ -778,7 +781,7 @@ async function whatif(dir, options) {
|
|
|
778
781
|
process.exit(1);
|
|
779
782
|
}
|
|
780
783
|
const action = buildAction(options);
|
|
781
|
-
const projectRoot = dir === "." ? findProjectRoot() :
|
|
784
|
+
const projectRoot = dir === "." ? findProjectRoot() : resolve2(dir);
|
|
782
785
|
console.error(`Parsing project: ${projectRoot}`);
|
|
783
786
|
const parsedFiles = await parseProject(projectRoot);
|
|
784
787
|
const graph = buildGraph(parsedFiles);
|
|
@@ -889,7 +892,7 @@ function formatAction(action) {
|
|
|
889
892
|
}
|
|
890
893
|
|
|
891
894
|
// src/commands/security.ts
|
|
892
|
-
import { resolve as
|
|
895
|
+
import { resolve as resolve3, dirname as dirname3, join as join4 } from "path";
|
|
893
896
|
import { readFileSync as readFileSync2 } from "fs";
|
|
894
897
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
895
898
|
|
|
@@ -1031,7 +1034,7 @@ function getVersion() {
|
|
|
1031
1034
|
}
|
|
1032
1035
|
var SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
|
|
1033
1036
|
async function securityCommand(dir, options) {
|
|
1034
|
-
const projectRoot = dir === "." ? findProjectRoot() :
|
|
1037
|
+
const projectRoot = dir === "." ? findProjectRoot() : resolve3(dir);
|
|
1035
1038
|
console.error(`Scanning: ${projectRoot}`);
|
|
1036
1039
|
const startTime = Date.now();
|
|
1037
1040
|
const parsedFiles = await parseProject(projectRoot);
|
|
@@ -1079,7 +1082,7 @@ program.command("parse").description("Parse a TypeScript project and build depen
|
|
|
1079
1082
|
trackCommand("parse", packageJson.version);
|
|
1080
1083
|
const startTime = Date.now();
|
|
1081
1084
|
try {
|
|
1082
|
-
const projectRoot = directory ?
|
|
1085
|
+
const projectRoot = directory ? resolve4(directory) : findProjectRoot();
|
|
1083
1086
|
console.log(`Parsing project: ${projectRoot}`);
|
|
1084
1087
|
const parsedFiles = await parseProject(projectRoot, {
|
|
1085
1088
|
exclude: options.exclude,
|
|
@@ -1118,8 +1121,8 @@ Orphan Files (no cross-references): ${summary.orphanFiles.length}`);
|
|
|
1118
1121
|
program.command("query").description("Query impact analysis for a symbol").argument("<directory>", "Project directory").argument("<symbol-name>", "Symbol name to query").action(async (directory, symbolName) => {
|
|
1119
1122
|
trackCommand("query", packageJson.version);
|
|
1120
1123
|
try {
|
|
1121
|
-
const projectRoot =
|
|
1122
|
-
const cacheFile = "depwire-output.json";
|
|
1124
|
+
const projectRoot = resolve4(directory);
|
|
1125
|
+
const cacheFile = resolve4("depwire-output.json");
|
|
1123
1126
|
let graph;
|
|
1124
1127
|
if (existsSync(cacheFile)) {
|
|
1125
1128
|
console.log("Loading from cache...");
|
|
@@ -1167,7 +1170,7 @@ Total Transitive Dependents: ${impact.transitiveDependents.length}`);
|
|
|
1167
1170
|
program.command("viz").description("Launch interactive arc diagram visualization").argument("[directory]", "Project directory to visualize (defaults to current directory or auto-detected project root)").option("-p, --port <number>", "Server port", "3333").option("--no-open", "Don't auto-open browser").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').option("--verbose", "Show detailed parsing progress").action(async (directory, options) => {
|
|
1168
1171
|
trackCommand("viz", packageJson.version);
|
|
1169
1172
|
try {
|
|
1170
|
-
const projectRoot = directory ?
|
|
1173
|
+
const projectRoot = directory ? resolve4(directory) : findProjectRoot();
|
|
1171
1174
|
console.log(`Parsing project: ${projectRoot}`);
|
|
1172
1175
|
const parsedFiles = await parseProject(projectRoot, {
|
|
1173
1176
|
exclude: options.exclude,
|
|
@@ -1190,7 +1193,7 @@ program.command("viz").description("Launch interactive arc diagram visualization
|
|
|
1190
1193
|
program.command("temporal").description("Visualize how the dependency graph evolved over git history").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--commits <number>", "Number of commits to sample", "20").option("--strategy <type>", "Sampling strategy: even, weekly, monthly", "even").option("-p, --port <number>", "Server port", "3334").option("--output <path>", "Save snapshots to custom path (default: .depwire/temporal/)").option("--verbose", "Show progress for each commit being parsed").option("--stats", "Show summary statistics at end").action(async (directory, options) => {
|
|
1191
1194
|
trackCommand("temporal", packageJson.version);
|
|
1192
1195
|
try {
|
|
1193
|
-
const projectRoot = directory ?
|
|
1196
|
+
const projectRoot = directory ? resolve4(directory) : findProjectRoot();
|
|
1194
1197
|
await runTemporalAnalysis(projectRoot, {
|
|
1195
1198
|
commits: parseInt(options.commits, 10),
|
|
1196
1199
|
strategy: options.strategy,
|
|
@@ -1210,7 +1213,7 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
|
|
|
1210
1213
|
const state = createEmptyState();
|
|
1211
1214
|
let projectRootToConnect = null;
|
|
1212
1215
|
if (directory) {
|
|
1213
|
-
projectRootToConnect =
|
|
1216
|
+
projectRootToConnect = resolve4(directory);
|
|
1214
1217
|
} else {
|
|
1215
1218
|
const detectedRoot = findProjectRoot();
|
|
1216
1219
|
const cwd = process.cwd();
|
|
@@ -1271,8 +1274,8 @@ program.command("docs").description("Generate comprehensive codebase documentati
|
|
|
1271
1274
|
trackCommand("docs", packageJson.version);
|
|
1272
1275
|
const startTime = Date.now();
|
|
1273
1276
|
try {
|
|
1274
|
-
const projectRoot = directory ?
|
|
1275
|
-
const outputDir = options.output ?
|
|
1277
|
+
const projectRoot = directory ? resolve4(directory) : findProjectRoot();
|
|
1278
|
+
const outputDir = options.output ? resolve4(options.output) : join5(projectRoot, ".depwire");
|
|
1276
1279
|
const includeList = options.include.split(",").map((s) => s.trim());
|
|
1277
1280
|
const onlyList = options.only ? options.only.split(",").map((s) => s.trim()) : void 0;
|
|
1278
1281
|
if (options.gitignore === void 0 && !existsSyncNode(outputDir)) {
|
|
@@ -1334,11 +1337,11 @@ async function promptGitignore() {
|
|
|
1334
1337
|
input: process.stdin,
|
|
1335
1338
|
output: process.stdout
|
|
1336
1339
|
});
|
|
1337
|
-
return new Promise((
|
|
1340
|
+
return new Promise((resolve5) => {
|
|
1338
1341
|
rl.question("Add .depwire/ to .gitignore? [Y/n] ", (answer) => {
|
|
1339
1342
|
rl.close();
|
|
1340
1343
|
const normalized = answer.trim().toLowerCase();
|
|
1341
|
-
|
|
1344
|
+
resolve5(normalized === "" || normalized === "y" || normalized === "yes");
|
|
1342
1345
|
});
|
|
1343
1346
|
});
|
|
1344
1347
|
}
|
|
@@ -1368,7 +1371,7 @@ ${pattern}
|
|
|
1368
1371
|
program.command("health").description("Analyze dependency architecture health (0-100 score)").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--json", "Output as JSON").option("--verbose", "Show detailed breakdown").action(async (directory, options) => {
|
|
1369
1372
|
trackCommand("health", packageJson.version);
|
|
1370
1373
|
try {
|
|
1371
|
-
const projectRoot = directory ?
|
|
1374
|
+
const projectRoot = directory ? resolve4(directory) : findProjectRoot();
|
|
1372
1375
|
const startTime = Date.now();
|
|
1373
1376
|
const parsedFiles = await parseProject(projectRoot);
|
|
1374
1377
|
const graph = buildGraph(parsedFiles);
|
|
@@ -1392,7 +1395,7 @@ program.command("health").description("Analyze dependency architecture health (0
|
|
|
1392
1395
|
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").option("--debug", "Show debug information (exclusion stats)").action(async (directory, options) => {
|
|
1393
1396
|
trackCommand("dead-code", packageJson.version);
|
|
1394
1397
|
try {
|
|
1395
|
-
const projectRoot = directory ?
|
|
1398
|
+
const projectRoot = directory ? resolve4(directory) : findProjectRoot();
|
|
1396
1399
|
const startTime = Date.now();
|
|
1397
1400
|
const parsedFiles = await parseProject(projectRoot);
|
|
1398
1401
|
const graph = buildGraph(parsedFiles);
|
package/dist/mcpb-entry.js
CHANGED
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
startMcpServer,
|
|
5
5
|
updateFileInGraph,
|
|
6
6
|
watchProject
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-RGD3YJYQ.js";
|
|
8
8
|
import {
|
|
9
9
|
buildGraph,
|
|
10
10
|
parseProject
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DA5LWNJ4.js";
|
|
12
12
|
|
|
13
13
|
// src/mcpb-entry.ts
|
|
14
14
|
import { resolve } from "path";
|
package/dist/sdk.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "depwire-cli",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "Dependency graph +
|
|
3
|
+
"version": "0.9.27",
|
|
4
|
+
"description": "Dependency graph + 17 MCP tools for AI coding assistants. Impact analysis, health scoring, security scanner.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"depwire": "dist/index.js"
|
|
@@ -63,16 +63,16 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@modelcontextprotocol/sdk": "1.26.0",
|
|
66
|
-
"chalk": "
|
|
66
|
+
"chalk": "5.6.2",
|
|
67
67
|
"chokidar": "5.0.0",
|
|
68
68
|
"commander": "14.0.3",
|
|
69
69
|
"express": "5.2.1",
|
|
70
70
|
"graphology": "0.26.0",
|
|
71
71
|
"graphology-types": "0.24.8",
|
|
72
|
-
"minimatch": "
|
|
72
|
+
"minimatch": "10.2.4",
|
|
73
73
|
"open": "11.0.0",
|
|
74
|
-
"simple-git": "
|
|
75
|
-
"web-tree-sitter": "
|
|
74
|
+
"simple-git": "3.35.2",
|
|
75
|
+
"web-tree-sitter": "0.26.6",
|
|
76
76
|
"ws": "8.19.0",
|
|
77
77
|
"zod": "4.3.6"
|
|
78
78
|
},
|