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.
@@ -16,7 +16,7 @@ import {
16
16
  parseTypeScriptFile,
17
17
  scanSecurity,
18
18
  searchSymbols
19
- } from "./chunk-YYY5TNG7.js";
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((resolve2) => {
180
+ const isAvailable = await new Promise((resolve4) => {
175
181
  const server = net.createServer();
176
182
  server.once("error", () => {
177
- resolve2(false);
183
+ resolve4(false);
178
184
  });
179
185
  server.once("listening", () => {
180
186
  server.close();
181
- resolve2(true);
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-zA-Z0-9_.\-\/]+$/.test(hash)) {
630
- throw new Error(`Invalid git ref: ${hash}`);
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-9_.\-\/]+$/.test(originalBranch)) {
640
- throw new Error(`Invalid git ref: ${originalBranch}`);
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 { join as join4 } from "path";
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 = join4(outputDir, filename);
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 = join4(outputDir, `${shortHash}.json`);
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-B2KGFBZL.js";
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-YYY5TNG7.js";
34
+ } from "./chunk-ITEGMPF7.js";
35
35
 
36
36
  // src/index.ts
37
37
  import { Command } from "commander";
38
- import { resolve as resolve3, dirname as dirname4, join as join5 } from "path";
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, join } from "path";
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((resolve4) => {
310
- const server = net.createServer().once("error", () => resolve4(false)).once("listening", () => {
309
+ const isAvailable = await new Promise((resolve5) => {
310
+ const server = net.createServer().once("error", () => resolve5(false)).once("listening", () => {
311
311
  server.close();
312
- resolve4(true);
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 = join(__dirname, "viz", "public");
328
+ const publicDir = resolve(__dirname, "viz", "public");
329
329
  app.get("/", (_req, res) => {
330
- const htmlPath = join(publicDir, "temporal.html");
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 = join(publicDir, "temporal.js");
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 = join(publicDir, "temporal.css");
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((resolve4, reject) => {
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
- resolve4();
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((resolve4) => {
696
+ const isAvailable = await new Promise((resolve5) => {
694
697
  const server = net.createServer();
695
698
  server.once("error", () => {
696
- resolve4(false);
699
+ resolve5(false);
697
700
  });
698
701
  server.once("listening", () => {
699
702
  server.close();
700
- resolve4(true);
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() : resolve(dir);
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() : resolve(dir);
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 resolve2, dirname as dirname3, join as join4 } from "path";
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() : resolve2(dir);
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 ? resolve3(directory) : findProjectRoot();
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 = resolve3(directory);
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 ? resolve3(directory) : findProjectRoot();
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 ? resolve3(directory) : findProjectRoot();
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 = resolve3(directory);
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 ? resolve3(directory) : findProjectRoot();
1275
- const outputDir = options.output ? resolve3(options.output) : join5(projectRoot, ".depwire");
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((resolve4) => {
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
- resolve4(normalized === "" || normalized === "y" || normalized === "yes");
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 ? resolve3(directory) : findProjectRoot();
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 ? resolve3(directory) : findProjectRoot();
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,
@@ -4,11 +4,11 @@ import {
4
4
  startMcpServer,
5
5
  updateFileInGraph,
6
6
  watchProject
7
- } from "./chunk-B2KGFBZL.js";
7
+ } from "./chunk-VJLBOFCD.js";
8
8
  import {
9
9
  buildGraph,
10
10
  parseProject
11
- } from "./chunk-YYY5TNG7.js";
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-YYY5TNG7.js";
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.26",
4
- "description": "Dependency graph + 16 MCP tools for AI coding assistants. Impact analysis, health scoring, visualization.",
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": "^5.6.2",
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": "^10.2.4",
72
+ "minimatch": "10.2.4",
73
73
  "open": "11.0.0",
74
- "simple-git": "^3.35.2",
75
- "web-tree-sitter": "^0.26.6",
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
  },