depwire-cli 0.9.26 → 0.9.28
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-ITEGMPF7.js} +576 -104
- package/dist/{chunk-B2KGFBZL.js → chunk-VJLBOFCD.js} +36 -22
- package/dist/index.js +47 -44
- package/dist/mcpb-entry.js +3 -3
- package/dist/sdk.d.ts +31 -2
- package/dist/sdk.js +3 -1
- package/package.json +6 -6
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
parseTypeScriptFile,
|
|
17
17
|
scanSecurity,
|
|
18
18
|
searchSymbols
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ITEGMPF7.js";
|
|
20
20
|
|
|
21
21
|
// src/viz/data.ts
|
|
22
22
|
import { basename } from "path";
|
|
@@ -45,12 +45,18 @@ function prepareVizData(graph, projectRoot) {
|
|
|
45
45
|
if (!arc.edgeKinds.includes(edge.kind)) {
|
|
46
46
|
arc.edgeKinds.push(edge.kind);
|
|
47
47
|
}
|
|
48
|
+
if (edge.crossLanguage) {
|
|
49
|
+
arc.crossLanguage = true;
|
|
50
|
+
arc.edgeType = edge.edgeType || arc.edgeType;
|
|
51
|
+
}
|
|
48
52
|
} else {
|
|
49
53
|
arcMap.set(key, {
|
|
50
54
|
sourceFile: edge.sourceFile,
|
|
51
55
|
targetFile: edge.targetFile,
|
|
52
56
|
edgeCount: 1,
|
|
53
|
-
edgeKinds: [edge.kind]
|
|
57
|
+
edgeKinds: [edge.kind],
|
|
58
|
+
crossLanguage: edge.crossLanguage || false,
|
|
59
|
+
edgeType: edge.edgeType
|
|
54
60
|
});
|
|
55
61
|
}
|
|
56
62
|
}
|
|
@@ -171,14 +177,14 @@ async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
|
171
177
|
const net = await import("net");
|
|
172
178
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
173
179
|
const testPort = startPort + attempt;
|
|
174
|
-
const isAvailable = await new Promise((
|
|
180
|
+
const isAvailable = await new Promise((resolve4) => {
|
|
175
181
|
const server = net.createServer();
|
|
176
182
|
server.once("error", () => {
|
|
177
|
-
|
|
183
|
+
resolve4(false);
|
|
178
184
|
});
|
|
179
185
|
server.once("listening", () => {
|
|
180
186
|
server.close();
|
|
181
|
-
|
|
187
|
+
resolve4(true);
|
|
182
188
|
});
|
|
183
189
|
server.listen(testPort, "127.0.0.1");
|
|
184
190
|
});
|
|
@@ -238,7 +244,7 @@ Depwire visualization running at ${url2}`);
|
|
|
238
244
|
console.error(`File changed: ${filePath} \u2014 re-parsing project...`);
|
|
239
245
|
try {
|
|
240
246
|
const parsedFiles = await parseProject(projectRoot, options);
|
|
241
|
-
const newGraph = buildGraph(parsedFiles);
|
|
247
|
+
const newGraph = buildGraph(parsedFiles, projectRoot);
|
|
242
248
|
graph.clear();
|
|
243
249
|
newGraph.forEachNode((node, attrs) => {
|
|
244
250
|
graph.addNode(node, attrs);
|
|
@@ -257,7 +263,7 @@ Depwire visualization running at ${url2}`);
|
|
|
257
263
|
console.error(`File added: ${filePath} \u2014 re-parsing project...`);
|
|
258
264
|
try {
|
|
259
265
|
const parsedFiles = await parseProject(projectRoot, options);
|
|
260
|
-
const newGraph = buildGraph(parsedFiles);
|
|
266
|
+
const newGraph = buildGraph(parsedFiles, projectRoot);
|
|
261
267
|
graph.clear();
|
|
262
268
|
newGraph.forEachNode((node, attrs) => {
|
|
263
269
|
graph.addNode(node, attrs);
|
|
@@ -276,7 +282,7 @@ Depwire visualization running at ${url2}`);
|
|
|
276
282
|
console.error(`File deleted: ${filePath} \u2014 re-parsing project...`);
|
|
277
283
|
try {
|
|
278
284
|
const parsedFiles = await parseProject(projectRoot, options);
|
|
279
|
-
const newGraph = buildGraph(parsedFiles);
|
|
285
|
+
const newGraph = buildGraph(parsedFiles, projectRoot);
|
|
280
286
|
graph.clear();
|
|
281
287
|
newGraph.forEachNode((node, attrs) => {
|
|
282
288
|
graph.addNode(node, attrs);
|
|
@@ -377,7 +383,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
377
383
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
378
384
|
|
|
379
385
|
// src/mcp/tools.ts
|
|
380
|
-
import { dirname as dirname2, join as join5 } from "path";
|
|
386
|
+
import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
|
|
381
387
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
382
388
|
|
|
383
389
|
// src/mcp/connect.ts
|
|
@@ -509,7 +515,7 @@ async function connectToRepo(source, subdirectory, state) {
|
|
|
509
515
|
message: `No supported source files (.ts, .tsx, .js, .jsx, .py, .go) found in ${projectRoot}`
|
|
510
516
|
};
|
|
511
517
|
}
|
|
512
|
-
const graph = buildGraph(parsedFiles);
|
|
518
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
513
519
|
state.graph = graph;
|
|
514
520
|
state.projectRoot = projectRoot;
|
|
515
521
|
state.projectName = projectName;
|
|
@@ -626,8 +632,8 @@ async function getCurrentBranch(dir) {
|
|
|
626
632
|
}
|
|
627
633
|
}
|
|
628
634
|
async function checkoutCommit(dir, hash) {
|
|
629
|
-
if (!/^[a-
|
|
630
|
-
throw new Error(`Invalid
|
|
635
|
+
if (!/^[a-f0-9]+$/.test(hash)) {
|
|
636
|
+
throw new Error(`Invalid commit hash: ${hash}`);
|
|
631
637
|
}
|
|
632
638
|
try {
|
|
633
639
|
execSync(`git checkout -q ${hash}`, { cwd: dir, stdio: "ignore" });
|
|
@@ -636,11 +642,12 @@ async function checkoutCommit(dir, hash) {
|
|
|
636
642
|
}
|
|
637
643
|
}
|
|
638
644
|
async function restoreOriginal(dir, originalBranch) {
|
|
639
|
-
if (!/^[a-zA-Z0-
|
|
640
|
-
throw new Error(`Invalid
|
|
645
|
+
if (!/^[a-zA-Z0-9/_.\-]+$/.test(originalBranch)) {
|
|
646
|
+
throw new Error(`Invalid branch name: ${originalBranch}`);
|
|
641
647
|
}
|
|
642
648
|
try {
|
|
643
649
|
execSync(`git checkout -q ${originalBranch}`, {
|
|
650
|
+
// depwire-security-reviewed: branch validated above
|
|
644
651
|
cwd: dir,
|
|
645
652
|
stdio: "ignore"
|
|
646
653
|
});
|
|
@@ -788,19 +795,22 @@ function getWeekNumber(date) {
|
|
|
788
795
|
|
|
789
796
|
// src/temporal/snapshots.ts
|
|
790
797
|
import { writeFileSync, readFileSync, mkdirSync, existsSync as existsSync2, readdirSync } from "fs";
|
|
791
|
-
import {
|
|
798
|
+
import { resolve as resolve2 } from "path";
|
|
792
799
|
function saveSnapshot(snapshot, outputDir) {
|
|
793
800
|
if (!existsSync2(outputDir)) {
|
|
794
801
|
mkdirSync(outputDir, { recursive: true });
|
|
795
802
|
}
|
|
796
803
|
const filename = `${snapshot.commitHash.substring(0, 8)}.json`;
|
|
797
|
-
const filepath =
|
|
804
|
+
const filepath = resolve2(outputDir, filename);
|
|
805
|
+
if (!filepath.startsWith(resolve2(outputDir))) {
|
|
806
|
+
throw new Error(`Path traversal attempt blocked: ${filepath}`);
|
|
807
|
+
}
|
|
798
808
|
writeFileSync(filepath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
799
809
|
}
|
|
800
810
|
function loadSnapshot(commitHash, outputDir) {
|
|
801
811
|
const shortHash = commitHash.substring(0, 8);
|
|
802
|
-
const filepath =
|
|
803
|
-
if (!existsSync2(filepath)) {
|
|
812
|
+
const filepath = resolve2(outputDir, `${shortHash}.json`);
|
|
813
|
+
if (!filepath.startsWith(resolve2(outputDir)) || !existsSync2(filepath)) {
|
|
804
814
|
return null;
|
|
805
815
|
}
|
|
806
816
|
try {
|
|
@@ -935,7 +945,7 @@ function getToolsList() {
|
|
|
935
945
|
},
|
|
936
946
|
{
|
|
937
947
|
name: "impact_analysis",
|
|
938
|
-
description: "Analyze what would break if a symbol is changed, renamed, or removed. Shows direct dependents, transitive dependents (chain reaction), and all affected files. Pass a symbol name (e.g., 'Router') or a fully qualified ID (e.g., 'src/router.ts::Router') for exact matching. If multiple symbols share the same name, returns all matches for disambiguation. Use this before making changes to understand the blast radius.",
|
|
948
|
+
description: "Analyze what would break if a symbol is changed, renamed, or removed. Shows direct dependents, transitive dependents (chain reaction), and all affected files. Cross-language edges included \u2014 a TypeScript fetch call to a Python route will show the Python file as affected. Pass a symbol name (e.g., 'Router') or a fully qualified ID (e.g., 'src/router.ts::Router') for exact matching. If multiple symbols share the same name, returns all matches for disambiguation. Use this before making changes to understand the blast radius.",
|
|
939
949
|
inputSchema: {
|
|
940
950
|
type: "object",
|
|
941
951
|
properties: {
|
|
@@ -953,7 +963,7 @@ function getToolsList() {
|
|
|
953
963
|
},
|
|
954
964
|
{
|
|
955
965
|
name: "get_file_context",
|
|
956
|
-
description: "Get complete context about a file \u2014 all symbols defined in it, all imports, all exports, and all files that import from it.",
|
|
966
|
+
description: "Get complete context about a file \u2014 all symbols defined in it, all imports, all exports, and all files that import from it. Includes cross-language connections (REST API calls, subprocess invocations).",
|
|
957
967
|
inputSchema: {
|
|
958
968
|
type: "object",
|
|
959
969
|
properties: {
|
|
@@ -1090,7 +1100,7 @@ function getToolsList() {
|
|
|
1090
1100
|
},
|
|
1091
1101
|
{
|
|
1092
1102
|
name: "simulate_change",
|
|
1093
|
-
description: `Simulate an architectural change before touching any code. Returns health score delta, broken imports, and affected nodes. Zero file I/O \u2014 pure in-memory simulation.
|
|
1103
|
+
description: `Simulate an architectural change before touching any code. Returns health score delta, broken imports, and affected nodes. Zero file I/O \u2014 pure in-memory simulation. Cross-language edges included \u2014 deleting a Python route file will show TypeScript callers as affected.
|
|
1094
1104
|
|
|
1095
1105
|
Operations:
|
|
1096
1106
|
- delete: Simulate deleting a file. Shows every file that would break and the full blast radius.
|
|
@@ -1737,6 +1747,10 @@ Available document types:
|
|
|
1737
1747
|
continue;
|
|
1738
1748
|
}
|
|
1739
1749
|
const filePath = join5(docsDir, metadata.documents[doc].file);
|
|
1750
|
+
if (!resolve3(filePath).startsWith(resolve3(docsDir))) {
|
|
1751
|
+
missing.push(doc);
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1740
1754
|
if (!existsSync3(filePath)) {
|
|
1741
1755
|
missing.push(doc);
|
|
1742
1756
|
continue;
|
|
@@ -1769,7 +1783,7 @@ async function handleUpdateProjectDocs(docType, state) {
|
|
|
1769
1783
|
const docsDir = join5(state.projectRoot, ".depwire");
|
|
1770
1784
|
console.error("Regenerating project documentation...");
|
|
1771
1785
|
const parsedFiles = await parseProject(state.projectRoot);
|
|
1772
|
-
const graph = buildGraph(parsedFiles);
|
|
1786
|
+
const graph = buildGraph(parsedFiles, state.projectRoot);
|
|
1773
1787
|
const parseTime = (Date.now() - startTime) / 1e3;
|
|
1774
1788
|
state.graph = graph;
|
|
1775
1789
|
const packageJsonPath = join5(__dirname, "../../package.json");
|
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-VJLBOFCD.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-ITEGMPF7.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
|
});
|
|
@@ -407,7 +410,7 @@ async function runTemporalAnalysis(projectDir, options) {
|
|
|
407
410
|
}
|
|
408
411
|
await checkoutCommit(projectDir, commit.hash);
|
|
409
412
|
const parsedFiles = await parseProject(projectDir);
|
|
410
|
-
const graph = buildGraph(parsedFiles);
|
|
413
|
+
const graph = buildGraph(parsedFiles, projectDir);
|
|
411
414
|
const projectGraph = exportToJSON(graph, projectDir);
|
|
412
415
|
const snapshot = createSnapshot(
|
|
413
416
|
projectGraph,
|
|
@@ -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,10 +755,10 @@ 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
|
-
const graph2 = buildGraph(parsedFiles2);
|
|
761
|
+
const graph2 = buildGraph(parsedFiles2, projectRoot2);
|
|
759
762
|
console.error(`Built graph: ${graph2.order} symbols, ${graph2.size} edges`);
|
|
760
763
|
const vizData = prepareVizData(graph2, projectRoot2);
|
|
761
764
|
const emptyResult = {
|
|
@@ -778,10 +781,10 @@ 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
|
-
const graph = buildGraph(parsedFiles);
|
|
787
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
785
788
|
console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
786
789
|
console.error("");
|
|
787
790
|
const engine = new SimulationEngine(graph);
|
|
@@ -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,12 +1034,12 @@ 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);
|
|
1038
1041
|
console.error(`Parsed ${parsedFiles.length} files`);
|
|
1039
|
-
const graph = buildGraph(parsedFiles);
|
|
1042
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
1040
1043
|
console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
1041
1044
|
const result = await scanSecurity(projectRoot, graph, {
|
|
1042
1045
|
target: options.target,
|
|
@@ -1079,14 +1082,14 @@ 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,
|
|
1086
1089
|
verbose: options.verbose
|
|
1087
1090
|
});
|
|
1088
1091
|
console.log(`Parsed ${parsedFiles.length} files`);
|
|
1089
|
-
const graph = buildGraph(parsedFiles);
|
|
1092
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
1090
1093
|
const projectGraph = exportToJSON(graph, projectRoot);
|
|
1091
1094
|
const json = options.pretty ? JSON.stringify(projectGraph, null, 2) : JSON.stringify(projectGraph);
|
|
1092
1095
|
writeFileSync(options.output, json, "utf-8");
|
|
@@ -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...");
|
|
@@ -1128,7 +1131,7 @@ program.command("query").description("Query impact analysis for a symbol").argum
|
|
|
1128
1131
|
} else {
|
|
1129
1132
|
console.log("Parsing project...");
|
|
1130
1133
|
const parsedFiles = await parseProject(projectRoot);
|
|
1131
|
-
graph = buildGraph(parsedFiles);
|
|
1134
|
+
graph = buildGraph(parsedFiles, projectRoot);
|
|
1132
1135
|
}
|
|
1133
1136
|
const matches = searchSymbols(graph, symbolName);
|
|
1134
1137
|
if (matches.length === 0) {
|
|
@@ -1167,14 +1170,14 @@ 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,
|
|
1174
1177
|
verbose: options.verbose
|
|
1175
1178
|
});
|
|
1176
1179
|
console.log(`Parsed ${parsedFiles.length} files`);
|
|
1177
|
-
const graph = buildGraph(parsedFiles);
|
|
1180
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
1178
1181
|
const vizData = prepareVizData(graph, projectRoot);
|
|
1179
1182
|
console.log(`Found ${vizData.stats.totalSymbols} symbols, ${vizData.stats.totalCrossFileEdges} cross-file edges`);
|
|
1180
1183
|
const port = parseInt(options.port, 10);
|
|
@@ -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();
|
|
@@ -1222,7 +1225,7 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
|
|
|
1222
1225
|
console.error(`Parsing project: ${projectRootToConnect}`);
|
|
1223
1226
|
const parsedFiles = await parseProject(projectRootToConnect);
|
|
1224
1227
|
console.error(`Parsed ${parsedFiles.length} files`);
|
|
1225
|
-
const graph = buildGraph(parsedFiles);
|
|
1228
|
+
const graph = buildGraph(parsedFiles, projectRootToConnect);
|
|
1226
1229
|
console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
1227
1230
|
state.graph = graph;
|
|
1228
1231
|
state.projectRoot = projectRootToConnect;
|
|
@@ -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)) {
|
|
@@ -1289,7 +1292,7 @@ program.command("docs").description("Generate comprehensive codebase documentati
|
|
|
1289
1292
|
verbose: options.verbose
|
|
1290
1293
|
});
|
|
1291
1294
|
console.log(`Parsed ${parsedFiles.length} files`);
|
|
1292
|
-
const graph = buildGraph(parsedFiles);
|
|
1295
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
1293
1296
|
const parseTime = (Date.now() - startTime) / 1e3;
|
|
1294
1297
|
console.log(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
1295
1298
|
if (options.verbose) {
|
|
@@ -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,10 +1371,10 @@ ${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
|
-
const graph = buildGraph(parsedFiles);
|
|
1377
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
1375
1378
|
const parseTime = Date.now() - startTime;
|
|
1376
1379
|
const report = calculateHealthScore(graph, projectRoot);
|
|
1377
1380
|
const trend = getHealthTrend(projectRoot, report.overall);
|
|
@@ -1392,10 +1395,10 @@ 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
|
-
const graph = buildGraph(parsedFiles);
|
|
1401
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
1399
1402
|
const confidence = options.includeLow ? "low" : options.confidence || "medium";
|
|
1400
1403
|
const report = analyzeDeadCode(graph, projectRoot, {
|
|
1401
1404
|
confidence,
|
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-VJLBOFCD.js";
|
|
8
8
|
import {
|
|
9
9
|
buildGraph,
|
|
10
10
|
parseProject
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ITEGMPF7.js";
|
|
12
12
|
|
|
13
13
|
// src/mcpb-entry.ts
|
|
14
14
|
import { resolve } from "path";
|
|
@@ -21,7 +21,7 @@ async function main() {
|
|
|
21
21
|
console.error(`[MCPB] Parsing project: ${projectRoot}`);
|
|
22
22
|
const parsedFiles = await parseProject(projectRoot);
|
|
23
23
|
console.error(`[MCPB] Parsed ${parsedFiles.length} files`);
|
|
24
|
-
const graph = buildGraph(parsedFiles);
|
|
24
|
+
const graph = buildGraph(parsedFiles, projectRoot);
|
|
25
25
|
console.error(`[MCPB] Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
26
26
|
state.graph = graph;
|
|
27
27
|
state.projectRoot = projectRoot;
|
package/dist/sdk.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ declare function parseProject(projectRoot: string, options?: {
|
|
|
36
36
|
verbose?: boolean;
|
|
37
37
|
}): Promise<ParsedFile[]>;
|
|
38
38
|
|
|
39
|
-
declare function buildGraph(parsedFiles: ParsedFile[]): DirectedGraph;
|
|
39
|
+
declare function buildGraph(parsedFiles: ParsedFile[], projectRoot?: string): DirectedGraph;
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Health Score Type Definitions
|
|
@@ -270,6 +270,35 @@ interface SecurityScanOptions {
|
|
|
270
270
|
|
|
271
271
|
declare function scanSecurity(projectRoot: string, graph: DirectedGraph, options?: SecurityScanOptions): Promise<SecurityScanResult>;
|
|
272
272
|
|
|
273
|
+
type CrossLanguageEdgeType = 'rest-api' | 'subprocess';
|
|
274
|
+
interface CrossLanguageEdge {
|
|
275
|
+
sourceFile: string;
|
|
276
|
+
targetFile: string;
|
|
277
|
+
edgeType: CrossLanguageEdgeType;
|
|
278
|
+
confidence: 'high' | 'medium' | 'low';
|
|
279
|
+
sourceLanguage: string;
|
|
280
|
+
targetLanguage: string;
|
|
281
|
+
sourceLine?: number;
|
|
282
|
+
targetLine?: number;
|
|
283
|
+
metadata: {
|
|
284
|
+
httpMethod?: string;
|
|
285
|
+
path?: string;
|
|
286
|
+
command?: string;
|
|
287
|
+
calledFile?: string;
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
interface CrossLanguageDetectionResult {
|
|
291
|
+
edges: CrossLanguageEdge[];
|
|
292
|
+
stats: {
|
|
293
|
+
restApiEdges: number;
|
|
294
|
+
subprocessEdges: number;
|
|
295
|
+
filesAnalyzed: number;
|
|
296
|
+
detectionTimeMs: number;
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
declare function detectCrossLanguageEdges(files: ParsedFile[], projectRoot: string, graph: DirectedGraph): CrossLanguageDetectionResult;
|
|
301
|
+
|
|
273
302
|
/**
|
|
274
303
|
* depwire-cli SDK — Public API Surface
|
|
275
304
|
*
|
|
@@ -282,4 +311,4 @@ declare function scanSecurity(projectRoot: string, graph: DirectedGraph, options
|
|
|
282
311
|
/** Current SDK version — matches depwire-cli npm version */
|
|
283
312
|
declare const DepwireSDKVersion: string;
|
|
284
313
|
|
|
285
|
-
export { type BrokenImport, DepwireSDKVersion, type GraphDiff, type HealthDelta, type SecurityFinding, type SecurityScanOptions, type SecurityScanResult, type Severity, type SimulationAction, SimulationEngine, type SimulationResult, type VulnerabilityClass, analyzeDeadCode, buildGraph, calculateHealthScore, generateDocs, getArchitectureSummary, getImpact, parseProject, scanSecurity, searchSymbols };
|
|
314
|
+
export { type BrokenImport, type CrossLanguageDetectionResult, type CrossLanguageEdge, DepwireSDKVersion, type GraphDiff, type HealthDelta, type SecurityFinding, type SecurityScanOptions, type SecurityScanResult, type Severity, type SimulationAction, SimulationEngine, type SimulationResult, type VulnerabilityClass, analyzeDeadCode, buildGraph, calculateHealthScore, detectCrossLanguageEdges, generateDocs, getArchitectureSummary, getImpact, parseProject, scanSecurity, searchSymbols };
|
package/dist/sdk.js
CHANGED
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
analyzeDeadCode,
|
|
4
4
|
buildGraph,
|
|
5
5
|
calculateHealthScore,
|
|
6
|
+
detectCrossLanguageEdges,
|
|
6
7
|
generateDocs,
|
|
7
8
|
getArchitectureSummary,
|
|
8
9
|
getImpact,
|
|
9
10
|
parseProject,
|
|
10
11
|
scanSecurity,
|
|
11
12
|
searchSymbols
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ITEGMPF7.js";
|
|
13
14
|
|
|
14
15
|
// src/sdk.ts
|
|
15
16
|
import { readFileSync } from "fs";
|
|
@@ -25,6 +26,7 @@ export {
|
|
|
25
26
|
analyzeDeadCode,
|
|
26
27
|
buildGraph,
|
|
27
28
|
calculateHealthScore,
|
|
29
|
+
detectCrossLanguageEdges,
|
|
28
30
|
generateDocs,
|
|
29
31
|
getArchitectureSummary,
|
|
30
32
|
getImpact,
|
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.28",
|
|
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
|
},
|