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.
@@ -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 = join5(currentDir, "go.mod");
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 })
@@ -2869,9 +2873,449 @@ async function parseProject(projectRoot, options) {
2869
2873
  return parsedFiles;
2870
2874
  }
2871
2875
 
2876
+ // src/cross-language/detectors/rest-api.ts
2877
+ import { readFileSync as readFileSync6 } from "fs";
2878
+ import { join as join9, resolve as resolve4 } from "path";
2879
+ function getLanguage(filePath) {
2880
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
2881
+ if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
2882
+ if (filePath.endsWith(".py")) return "python";
2883
+ if (filePath.endsWith(".go")) return "go";
2884
+ return "unknown";
2885
+ }
2886
+ function normalizePath(routePath) {
2887
+ return routePath.replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, "__PARAM__").replace(/\{[a-zA-Z_][a-zA-Z0-9_]*\}/g, "__PARAM__");
2888
+ }
2889
+ function stripTrailingSlash(p) {
2890
+ return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
2891
+ }
2892
+ function extractHttpCalls(source, filePath) {
2893
+ const calls = [];
2894
+ const lines = source.split("\n");
2895
+ for (let i = 0; i < lines.length; i++) {
2896
+ const line = lines[i];
2897
+ const fetchMatch = line.match(/fetch\s*\(\s*(['"`])([^'"`]+)\1/);
2898
+ if (fetchMatch) {
2899
+ const path6 = fetchMatch[2];
2900
+ if (isLocalApiPath(path6)) {
2901
+ const methodMatch = line.match(/method\s*:\s*['"](\w+)['"]/);
2902
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
2903
+ calls.push({ method, path: cleanPath(path6), file: filePath, line: i + 1 });
2904
+ }
2905
+ }
2906
+ if (!fetchMatch) {
2907
+ const fetchTemplateMatch = line.match(/fetch\s*\(\s*`([^`]+)`/);
2908
+ if (fetchTemplateMatch) {
2909
+ const path6 = fetchTemplateMatch[1];
2910
+ if (isLocalApiPath(path6)) {
2911
+ const methodMatch = line.match(/method\s*:\s*['"](\w+)['"]/);
2912
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
2913
+ calls.push({ method, path: cleanPath(path6), file: filePath, line: i + 1 });
2914
+ }
2915
+ }
2916
+ }
2917
+ const axiosMatch = line.match(/axios\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"`])([^'"`]+)\2/i);
2918
+ if (axiosMatch) {
2919
+ const path6 = axiosMatch[3];
2920
+ if (isLocalApiPath(path6)) {
2921
+ calls.push({ method: axiosMatch[1].toUpperCase(), path: cleanPath(path6), file: filePath, line: i + 1 });
2922
+ }
2923
+ }
2924
+ if (!axiosMatch) {
2925
+ const axiosTemplateMatch = line.match(/axios\s*\.\s*(get|post|put|delete|patch)\s*\(\s*`([^`]+)`/i);
2926
+ if (axiosTemplateMatch) {
2927
+ const path6 = axiosTemplateMatch[2];
2928
+ if (isLocalApiPath(path6)) {
2929
+ calls.push({ method: axiosTemplateMatch[1].toUpperCase(), path: cleanPath(path6), file: filePath, line: i + 1 });
2930
+ }
2931
+ }
2932
+ }
2933
+ const genericMatch = line.match(/\w+\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"`])([^'"`]+)\2/i);
2934
+ if (genericMatch && !line.match(/axios/) && !line.match(/app\s*\./) && !line.match(/router\s*\./) && !line.match(/r\s*\./)) {
2935
+ const path6 = genericMatch[3];
2936
+ if (isLocalApiPath(path6)) {
2937
+ calls.push({ method: genericMatch[1].toUpperCase(), path: cleanPath(path6), file: filePath, line: i + 1 });
2938
+ }
2939
+ }
2940
+ }
2941
+ return calls;
2942
+ }
2943
+ function isLocalApiPath(path6) {
2944
+ if (path6.startsWith("http://") || path6.startsWith("https://")) return false;
2945
+ return path6.startsWith("/") || path6.includes("/api/");
2946
+ }
2947
+ function cleanPath(path6) {
2948
+ let cleaned = path6.replace(/\$\{[^}]*\}/g, "");
2949
+ cleaned = stripTrailingSlash(cleaned);
2950
+ return cleaned;
2951
+ }
2952
+ function extractRouteDefinitions(source, filePath) {
2953
+ const routes = [];
2954
+ const lines = source.split("\n");
2955
+ const lang = getLanguage(filePath);
2956
+ for (let i = 0; i < lines.length; i++) {
2957
+ const line = lines[i];
2958
+ if (lang === "typescript" || lang === "javascript") {
2959
+ const expressMatch = line.match(/(?:app|router)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"`])([^'"`]+)\2/i);
2960
+ if (expressMatch) {
2961
+ const path6 = expressMatch[3];
2962
+ if (path6.startsWith("/")) {
2963
+ routes.push({
2964
+ method: expressMatch[1].toUpperCase(),
2965
+ path: path6,
2966
+ normalizedPath: normalizePath(path6),
2967
+ file: filePath,
2968
+ line: i + 1
2969
+ });
2970
+ }
2971
+ }
2972
+ }
2973
+ if (lang === "python") {
2974
+ const pythonMatch = line.match(/@(?:app|router)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"])([^'"]+)\2/i);
2975
+ if (pythonMatch) {
2976
+ const path6 = pythonMatch[3];
2977
+ if (path6.startsWith("/")) {
2978
+ routes.push({
2979
+ method: pythonMatch[1].toUpperCase(),
2980
+ path: path6,
2981
+ normalizedPath: normalizePath(path6),
2982
+ file: filePath,
2983
+ line: i + 1
2984
+ });
2985
+ }
2986
+ }
2987
+ const flaskMatch = line.match(/@(?:app|blueprint|router)\s*\.\s*route\s*\(\s*(['"])([^'"]+)\1/);
2988
+ if (flaskMatch) {
2989
+ const path6 = flaskMatch[2];
2990
+ if (path6.startsWith("/")) {
2991
+ const methodsMatch = line.match(/methods\s*=\s*\[([^\]]+)\]/);
2992
+ const methods = methodsMatch ? methodsMatch[1].match(/['"](\w+)['"]/g)?.map((m) => m.replace(/['"]/g, "").toUpperCase()) || ["GET"] : ["GET"];
2993
+ for (const method of methods) {
2994
+ routes.push({
2995
+ method,
2996
+ path: path6,
2997
+ normalizedPath: normalizePath(path6),
2998
+ file: filePath,
2999
+ line: i + 1
3000
+ });
3001
+ }
3002
+ }
3003
+ }
3004
+ }
3005
+ if (lang === "go") {
3006
+ const goMatch = line.match(/(?:r|router|group)\s*\.\s*(GET|POST|PUT|DELETE|PATCH)\s*\(\s*"([^"]+)"/);
3007
+ if (goMatch) {
3008
+ const path6 = goMatch[2];
3009
+ if (path6.startsWith("/")) {
3010
+ routes.push({
3011
+ method: goMatch[1].toUpperCase(),
3012
+ path: path6,
3013
+ normalizedPath: normalizePath(path6),
3014
+ file: filePath,
3015
+ line: i + 1
3016
+ });
3017
+ }
3018
+ }
3019
+ }
3020
+ }
3021
+ return routes;
3022
+ }
3023
+ function matchPaths(callPath, routeNormalized) {
3024
+ const normalizedCall = normalizePath(stripTrailingSlash(callPath));
3025
+ const normalizedRoute = stripTrailingSlash(routeNormalized);
3026
+ if (normalizedCall === normalizedRoute) return true;
3027
+ if (normalizedRoute.startsWith(normalizedCall) && normalizedRoute[normalizedCall.length] === "/") return true;
3028
+ const callParts = normalizedCall.split("/");
3029
+ const routeParts = normalizedRoute.split("/");
3030
+ if (callParts.length <= routeParts.length) {
3031
+ let match = true;
3032
+ for (let i = 0; i < callParts.length; i++) {
3033
+ if (routeParts[i] === "__PARAM__") continue;
3034
+ if (callParts[i] !== routeParts[i]) {
3035
+ match = false;
3036
+ break;
3037
+ }
3038
+ }
3039
+ if (match) return true;
3040
+ }
3041
+ return false;
3042
+ }
3043
+ function getConfidence(callPath, callMethod, routePath, routeMethod) {
3044
+ const normalizedCall = normalizePath(stripTrailingSlash(callPath));
3045
+ const normalizedRoute = normalizePath(stripTrailingSlash(routePath));
3046
+ const exactPath = normalizedCall === normalizedRoute;
3047
+ const methodMatch = callMethod === routeMethod;
3048
+ if (exactPath && methodMatch) return "high";
3049
+ if (exactPath) return "medium";
3050
+ if (methodMatch) return "medium";
3051
+ return "low";
3052
+ }
3053
+ function detectRestApiEdges(files, projectRoot) {
3054
+ const edges = [];
3055
+ const allCalls = [];
3056
+ const allRoutes = [];
3057
+ for (const file of files) {
3058
+ const fullPath = join9(projectRoot, file.filePath);
3059
+ if (!resolve4(fullPath).startsWith(resolve4(projectRoot))) continue;
3060
+ let source;
3061
+ try {
3062
+ source = readFileSync6(fullPath, "utf-8");
3063
+ } catch {
3064
+ continue;
3065
+ }
3066
+ const lang = getLanguage(file.filePath);
3067
+ if (lang === "typescript" || lang === "javascript") {
3068
+ allCalls.push(...extractHttpCalls(source, file.filePath));
3069
+ }
3070
+ allRoutes.push(...extractRouteDefinitions(source, file.filePath));
3071
+ }
3072
+ for (const call of allCalls) {
3073
+ for (const route of allRoutes) {
3074
+ if (call.file === route.file) continue;
3075
+ if (matchPaths(call.path, route.normalizedPath)) {
3076
+ const confidence = getConfidence(call.path, call.method, route.path, route.method);
3077
+ edges.push({
3078
+ sourceFile: call.file,
3079
+ targetFile: route.file,
3080
+ edgeType: "rest-api",
3081
+ confidence,
3082
+ sourceLanguage: getLanguage(call.file),
3083
+ targetLanguage: getLanguage(route.file),
3084
+ sourceLine: call.line,
3085
+ targetLine: route.line,
3086
+ metadata: {
3087
+ httpMethod: call.method,
3088
+ path: call.path
3089
+ }
3090
+ });
3091
+ }
3092
+ }
3093
+ }
3094
+ return edges;
3095
+ }
3096
+
3097
+ // src/cross-language/detectors/subprocess.ts
3098
+ import { readFileSync as readFileSync7 } from "fs";
3099
+ import { join as join10, resolve as resolve5, basename } from "path";
3100
+ var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
3101
+ function getLanguage2(filePath) {
3102
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
3103
+ if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
3104
+ if (filePath.endsWith(".py")) return "python";
3105
+ if (filePath.endsWith(".go")) return "go";
3106
+ if (filePath.endsWith(".rs")) return "rust";
3107
+ return "unknown";
3108
+ }
3109
+ function extractFilenameFromArgs(args) {
3110
+ const tokens = args.split(/[\s,'"[\]]+/).filter(Boolean);
3111
+ for (const token of tokens) {
3112
+ for (const ext of SCRIPT_EXTENSIONS) {
3113
+ if (token.endsWith(ext)) {
3114
+ return token;
3115
+ }
3116
+ }
3117
+ }
3118
+ return null;
3119
+ }
3120
+ function extractSubprocessCalls(source, filePath) {
3121
+ const calls = [];
3122
+ const lines = source.split("\n");
3123
+ const lang = getLanguage2(filePath);
3124
+ for (let i = 0; i < lines.length; i++) {
3125
+ const line = lines[i];
3126
+ if (lang === "typescript" || lang === "javascript") {
3127
+ const execMatch = line.match(/(?:execSync|exec)\s*\(\s*(['"`])([^'"`]+)\1/);
3128
+ if (execMatch) {
3129
+ const command = execMatch[2];
3130
+ const calledFile = extractFilenameFromArgs(command);
3131
+ if (calledFile) {
3132
+ calls.push({ file: filePath, line: i + 1, command, calledFile });
3133
+ }
3134
+ }
3135
+ if (!execMatch) {
3136
+ const execTemplateMatch = line.match(/(?:execSync|exec)\s*\(\s*`([^`]+)`/);
3137
+ if (execTemplateMatch) {
3138
+ const command = execTemplateMatch[1].replace(/\$\{[^}]*\}/g, "");
3139
+ const calledFile = extractFilenameFromArgs(command);
3140
+ if (calledFile) {
3141
+ calls.push({ file: filePath, line: i + 1, command: execTemplateMatch[1], calledFile });
3142
+ }
3143
+ }
3144
+ }
3145
+ const spawnMatch = line.match(/(?:spawn|spawnSync)\s*\(\s*['"](\w+)['"]\s*,\s*\[([^\]]*)\]/);
3146
+ if (spawnMatch) {
3147
+ const command = `${spawnMatch[1]} ${spawnMatch[2]}`;
3148
+ const calledFile = extractFilenameFromArgs(spawnMatch[2]);
3149
+ if (calledFile) {
3150
+ calls.push({ file: filePath, line: i + 1, command, calledFile });
3151
+ }
3152
+ }
3153
+ }
3154
+ if (lang === "python") {
3155
+ const subprocessMatch = line.match(/subprocess\s*\.\s*(?:run|call|Popen|check_output|check_call)\s*\(\s*\[([^\]]*)\]/);
3156
+ if (subprocessMatch) {
3157
+ const command = subprocessMatch[1];
3158
+ const calledFile = extractFilenameFromArgs(command);
3159
+ if (calledFile) {
3160
+ calls.push({ file: filePath, line: i + 1, command, calledFile });
3161
+ }
3162
+ }
3163
+ const osMatch = line.match(/os\s*\.\s*system\s*\(\s*['"]([^'"]+)['"]/);
3164
+ if (osMatch) {
3165
+ const command = osMatch[1];
3166
+ const calledFile = extractFilenameFromArgs(command);
3167
+ if (calledFile) {
3168
+ calls.push({ file: filePath, line: i + 1, command, calledFile });
3169
+ }
3170
+ }
3171
+ const subprocessStrMatch = line.match(/subprocess\s*\.\s*(?:run|call|Popen|check_output|check_call)\s*\(\s*['"]([^'"]+)['"]/);
3172
+ if (subprocessStrMatch) {
3173
+ const command = subprocessStrMatch[1];
3174
+ const calledFile = extractFilenameFromArgs(command);
3175
+ if (calledFile) {
3176
+ calls.push({ file: filePath, line: i + 1, command, calledFile });
3177
+ }
3178
+ }
3179
+ }
3180
+ if (lang === "go") {
3181
+ const goMatch = line.match(/exec\s*\.\s*Command\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"/);
3182
+ if (goMatch) {
3183
+ const command = `${goMatch[1]} ${goMatch[2]}`;
3184
+ const calledFile = extractFilenameFromArgs(command);
3185
+ if (calledFile) {
3186
+ calls.push({ file: filePath, line: i + 1, command, calledFile });
3187
+ }
3188
+ }
3189
+ }
3190
+ }
3191
+ return calls;
3192
+ }
3193
+ function detectSubprocessEdges(files, projectRoot) {
3194
+ const edges = [];
3195
+ const knownFiles = new Set(files.map((f) => f.filePath));
3196
+ const basenameMap = /* @__PURE__ */ new Map();
3197
+ for (const f of files) {
3198
+ const base = basename(f.filePath);
3199
+ if (!basenameMap.has(base)) basenameMap.set(base, []);
3200
+ basenameMap.get(base).push(f.filePath);
3201
+ }
3202
+ for (const file of files) {
3203
+ const fullPath = join10(projectRoot, file.filePath);
3204
+ if (!resolve5(fullPath).startsWith(resolve5(projectRoot))) continue;
3205
+ let source;
3206
+ try {
3207
+ source = readFileSync7(fullPath, "utf-8");
3208
+ } catch {
3209
+ continue;
3210
+ }
3211
+ const calls = extractSubprocessCalls(source, file.filePath);
3212
+ for (const call of calls) {
3213
+ let targetFile = null;
3214
+ let confidence = "high";
3215
+ if (knownFiles.has(call.calledFile)) {
3216
+ targetFile = call.calledFile;
3217
+ confidence = "high";
3218
+ } else {
3219
+ const base = basename(call.calledFile);
3220
+ const candidates = basenameMap.get(base);
3221
+ if (candidates && candidates.length > 0) {
3222
+ const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
3223
+ if (exactCandidate) {
3224
+ targetFile = exactCandidate;
3225
+ confidence = "high";
3226
+ } else {
3227
+ targetFile = candidates[0];
3228
+ confidence = "medium";
3229
+ }
3230
+ }
3231
+ }
3232
+ if (!targetFile) continue;
3233
+ if (targetFile === call.file) continue;
3234
+ edges.push({
3235
+ sourceFile: call.file,
3236
+ targetFile,
3237
+ edgeType: "subprocess",
3238
+ confidence,
3239
+ sourceLanguage: getLanguage2(call.file),
3240
+ targetLanguage: getLanguage2(targetFile),
3241
+ sourceLine: call.line,
3242
+ metadata: {
3243
+ command: call.command,
3244
+ calledFile: call.calledFile
3245
+ }
3246
+ });
3247
+ }
3248
+ }
3249
+ return edges;
3250
+ }
3251
+
3252
+ // src/cross-language/index.ts
3253
+ function detectCrossLanguageEdges(files, projectRoot, graph) {
3254
+ const startTime = Date.now();
3255
+ const restApiEdges = detectRestApiEdges(files, projectRoot);
3256
+ const subprocessEdges = detectSubprocessEdges(files, projectRoot);
3257
+ const allEdges = [...restApiEdges, ...subprocessEdges];
3258
+ for (const edge of allEdges) {
3259
+ const sourceNodeId = `${edge.sourceFile}::__file__`;
3260
+ const targetNodeId = `${edge.targetFile}::__file__`;
3261
+ if (!graph.hasNode(sourceNodeId)) {
3262
+ let hasSourceFile = false;
3263
+ graph.forEachNode((_nodeId, attrs) => {
3264
+ if (attrs.filePath === edge.sourceFile) hasSourceFile = true;
3265
+ });
3266
+ if (!hasSourceFile) continue;
3267
+ graph.addNode(sourceNodeId, {
3268
+ name: "__file__",
3269
+ kind: "import",
3270
+ filePath: edge.sourceFile,
3271
+ startLine: 1,
3272
+ endLine: 1,
3273
+ exported: false
3274
+ });
3275
+ }
3276
+ if (!graph.hasNode(targetNodeId)) {
3277
+ let hasTargetFile = false;
3278
+ graph.forEachNode((_nodeId, attrs) => {
3279
+ if (attrs.filePath === edge.targetFile) hasTargetFile = true;
3280
+ });
3281
+ if (!hasTargetFile) continue;
3282
+ graph.addNode(targetNodeId, {
3283
+ name: "__file__",
3284
+ kind: "import",
3285
+ filePath: edge.targetFile,
3286
+ startLine: 1,
3287
+ endLine: 1,
3288
+ exported: false
3289
+ });
3290
+ }
3291
+ graph.mergeEdge(sourceNodeId, targetNodeId, {
3292
+ kind: edge.edgeType,
3293
+ filePath: edge.sourceFile,
3294
+ line: edge.sourceLine || 1,
3295
+ crossLanguage: true,
3296
+ confidence: edge.confidence,
3297
+ edgeType: edge.edgeType,
3298
+ httpMethod: edge.metadata.httpMethod,
3299
+ path: edge.metadata.path,
3300
+ command: edge.metadata.command,
3301
+ calledFile: edge.metadata.calledFile
3302
+ });
3303
+ }
3304
+ const detectionTimeMs = Date.now() - startTime;
3305
+ return {
3306
+ edges: allEdges,
3307
+ stats: {
3308
+ restApiEdges: restApiEdges.length,
3309
+ subprocessEdges: subprocessEdges.length,
3310
+ filesAnalyzed: files.length,
3311
+ detectionTimeMs
3312
+ }
3313
+ };
3314
+ }
3315
+
2872
3316
  // src/graph/index.ts
2873
3317
  import { DirectedGraph } from "graphology";
2874
- function buildGraph(parsedFiles) {
3318
+ function buildGraph(parsedFiles, projectRoot) {
2875
3319
  const graph = new DirectedGraph();
2876
3320
  for (const file of parsedFiles) {
2877
3321
  for (const symbol of file.symbols) {
@@ -2928,6 +3372,12 @@ function buildGraph(parsedFiles) {
2928
3372
  }
2929
3373
  }
2930
3374
  }
3375
+ if (projectRoot) {
3376
+ const result = detectCrossLanguageEdges(parsedFiles, projectRoot, graph);
3377
+ if (result.stats.restApiEdges > 0 || result.stats.subprocessEdges > 0) {
3378
+ console.error(`Cross-language edges: ${result.stats.restApiEdges} rest-api, ${result.stats.subprocessEdges} subprocess detected`);
3379
+ }
3380
+ }
2931
3381
  return graph;
2932
3382
  }
2933
3383
 
@@ -3059,7 +3509,9 @@ function getCrossFileEdges(graph) {
3059
3509
  target,
3060
3510
  sourceFile: sourceAttrs.filePath,
3061
3511
  targetFile: targetAttrs.filePath,
3062
- kind: attrs.kind
3512
+ kind: attrs.kind,
3513
+ crossLanguage: attrs.crossLanguage || false,
3514
+ edgeType: attrs.edgeType
3063
3515
  });
3064
3516
  }
3065
3517
  });
@@ -3507,8 +3959,8 @@ function calculateDepthScore(graph) {
3507
3959
  }
3508
3960
 
3509
3961
  // src/health/index.ts
3510
- import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync8, mkdirSync } from "fs";
3511
- import { join as join9, dirname as dirname8 } from "path";
3962
+ import { readFileSync as readFileSync8, writeFileSync, existsSync as existsSync8, mkdirSync } from "fs";
3963
+ import { dirname as dirname8, resolve as resolve6 } from "path";
3512
3964
  function calculateHealthScore(graph, projectRoot) {
3513
3965
  const coupling = calculateCouplingScore(graph);
3514
3966
  const cohesion = calculateCohesionScore(graph);
@@ -3607,7 +4059,11 @@ function getHealthTrend(projectRoot, currentScore) {
3607
4059
  }
3608
4060
  }
3609
4061
  function saveHealthHistory(projectRoot, report) {
3610
- const historyFile = join9(projectRoot, ".depwire", "health-history.json");
4062
+ const resolvedRoot = resolve6(projectRoot);
4063
+ const historyFile = resolve6(resolvedRoot, ".depwire", "health-history.json");
4064
+ if (!historyFile.startsWith(resolvedRoot)) {
4065
+ return;
4066
+ }
3611
4067
  const entry = {
3612
4068
  timestamp: report.timestamp,
3613
4069
  score: report.overall,
@@ -3621,7 +4077,8 @@ function saveHealthHistory(projectRoot, report) {
3621
4077
  let history = [];
3622
4078
  if (existsSync8(historyFile)) {
3623
4079
  try {
3624
- const content = readFileSync6(historyFile, "utf-8");
4080
+ if (!historyFile.startsWith(resolvedRoot)) return;
4081
+ const content = readFileSync8(historyFile, "utf-8");
3625
4082
  history = JSON.parse(content);
3626
4083
  } catch {
3627
4084
  }
@@ -3631,15 +4088,18 @@ function saveHealthHistory(projectRoot, report) {
3631
4088
  history = history.slice(-50);
3632
4089
  }
3633
4090
  mkdirSync(dirname8(historyFile), { recursive: true });
4091
+ if (!historyFile.startsWith(resolvedRoot)) return;
3634
4092
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
3635
4093
  }
3636
4094
  function loadHealthHistory(projectRoot) {
3637
- const historyFile = join9(projectRoot, ".depwire", "health-history.json");
3638
- if (!existsSync8(historyFile)) {
4095
+ const resolvedRoot = resolve6(projectRoot);
4096
+ const historyFile = resolve6(resolvedRoot, ".depwire", "health-history.json");
4097
+ if (!historyFile.startsWith(resolvedRoot) || !existsSync8(historyFile)) {
3639
4098
  return [];
3640
4099
  }
3641
4100
  try {
3642
- const content = readFileSync6(historyFile, "utf-8");
4101
+ if (!historyFile.startsWith(resolvedRoot)) return [];
4102
+ const content = readFileSync8(historyFile, "utf-8");
3643
4103
  return JSON.parse(content);
3644
4104
  } catch {
3645
4105
  return [];
@@ -3648,7 +4108,7 @@ function loadHealthHistory(projectRoot) {
3648
4108
 
3649
4109
  // src/dead-code/detector.ts
3650
4110
  import path2 from "path";
3651
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
4111
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
3652
4112
  function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
3653
4113
  const deadSymbols = [];
3654
4114
  const context = { graph, projectRoot };
@@ -3777,12 +4237,13 @@ function isRelevantForDeadCodeDetection(attrs) {
3777
4237
  }
3778
4238
  function getPackageEntryPoints(projectRoot) {
3779
4239
  const entryPoints = /* @__PURE__ */ new Set();
3780
- const packageJsonPath = path2.join(projectRoot, "package.json");
3781
- if (!existsSync9(packageJsonPath)) {
4240
+ const resolvedRoot = path2.resolve(projectRoot);
4241
+ const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
4242
+ if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync9(packageJsonPath)) {
3782
4243
  return entryPoints;
3783
4244
  }
3784
4245
  try {
3785
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
4246
+ const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
3786
4247
  if (packageJson.main) {
3787
4248
  entryPoints.add(path2.resolve(projectRoot, packageJson.main));
3788
4249
  }
@@ -3923,8 +4384,8 @@ function generateReason(symbol, confidence) {
3923
4384
  return "Potentially unused";
3924
4385
  }
3925
4386
  function isBarrelFile(filePath) {
3926
- const basename4 = path3.basename(filePath);
3927
- return basename4 === "index.ts" || basename4 === "index.js";
4387
+ const basename5 = path3.basename(filePath);
4388
+ return basename5 === "index.ts" || basename5 === "index.js";
3928
4389
  }
3929
4390
  function isTestFile2(filePath) {
3930
4391
  return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
@@ -4104,7 +4565,7 @@ function filterByConfidence(symbols, minConfidence) {
4104
4565
 
4105
4566
  // src/docs/generator.ts
4106
4567
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
4107
- import { join as join12 } from "path";
4568
+ import { join as join14 } from "path";
4108
4569
 
4109
4570
  // src/docs/architecture.ts
4110
4571
  import { dirname as dirname9 } from "path";
@@ -4506,7 +4967,7 @@ function detectCycles(graph) {
4506
4967
  }
4507
4968
 
4508
4969
  // src/docs/conventions.ts
4509
- import { basename, extname as extname4 } from "path";
4970
+ import { basename as basename2, extname as extname4 } from "path";
4510
4971
  function generateConventions(graph, projectRoot, version) {
4511
4972
  let output = "";
4512
4973
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -4544,7 +5005,7 @@ function generateFileOrganization(graph) {
4544
5005
  graph.forEachNode((node, attrs) => {
4545
5006
  if (!files.has(attrs.filePath)) {
4546
5007
  files.add(attrs.filePath);
4547
- const fileName = basename(attrs.filePath);
5008
+ const fileName = basename2(attrs.filePath);
4548
5009
  if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
4549
5010
  barrelFileCount++;
4550
5011
  }
@@ -4603,7 +5064,7 @@ function generateNamingPatterns(graph) {
4603
5064
  graph.forEachNode((node, attrs) => {
4604
5065
  if (!files.has(attrs.filePath)) {
4605
5066
  files.add(attrs.filePath);
4606
- const fileName = basename(attrs.filePath, extname4(attrs.filePath));
5067
+ const fileName = basename2(attrs.filePath, extname4(attrs.filePath));
4607
5068
  if (isCamelCase(fileName)) patterns.files.camelCase++;
4608
5069
  else if (isPascalCase(fileName)) patterns.files.PascalCase++;
4609
5070
  else if (isKebabCase(fileName)) patterns.files.kebabCase++;
@@ -5668,7 +6129,7 @@ function generateDepwireUsage(projectRoot) {
5668
6129
  }
5669
6130
 
5670
6131
  // src/docs/files.ts
5671
- import { dirname as dirname11, basename as basename2 } from "path";
6132
+ import { dirname as dirname11, basename as basename3 } from "path";
5672
6133
  function generateFiles(graph, projectRoot, version) {
5673
6134
  let output = "";
5674
6135
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -5787,7 +6248,7 @@ function generateDirectoryBreakdown(graph) {
5787
6248
  dirStats.symbolCount += file.symbolCount;
5788
6249
  if (file.totalConnections > dirStats.maxConnections) {
5789
6250
  dirStats.maxConnections = file.totalConnections;
5790
- dirStats.mostConnectedFile = basename2(file.filePath);
6251
+ dirStats.mostConnectedFile = basename3(file.filePath);
5791
6252
  }
5792
6253
  }
5793
6254
  if (dirMap.size === 0) {
@@ -6415,7 +6876,7 @@ function generateRecommendations(graph) {
6415
6876
  }
6416
6877
 
6417
6878
  // src/docs/tests.ts
6418
- import { basename as basename3, dirname as dirname12 } from "path";
6879
+ import { basename as basename4, dirname as dirname12 } from "path";
6419
6880
  function generateTests(graph, projectRoot, version) {
6420
6881
  let output = "";
6421
6882
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -6443,7 +6904,7 @@ function getFileCount8(graph) {
6443
6904
  return files.size;
6444
6905
  }
6445
6906
  function isTestFile3(filePath) {
6446
- const fileName = basename3(filePath).toLowerCase();
6907
+ const fileName = basename4(filePath).toLowerCase();
6447
6908
  const dirPath = dirname12(filePath).toLowerCase();
6448
6909
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
6449
6910
  return true;
@@ -6502,7 +6963,7 @@ function generateTestFileInventory(graph) {
6502
6963
  return output;
6503
6964
  }
6504
6965
  function matchTestToSource(testFile) {
6505
- const testFileName = basename3(testFile);
6966
+ const testFileName = basename4(testFile);
6506
6967
  const testDir = dirname12(testFile);
6507
6968
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
6508
6969
  const possiblePaths = [];
@@ -6664,7 +7125,7 @@ function generateTestCoverageMap(graph) {
6664
7125
  const rows = mappings.slice(0, 30).map((m) => [
6665
7126
  `\`${m.sourceFile}\``,
6666
7127
  m.hasTest ? "\u2705" : "\u274C",
6667
- m.testFile ? `\`${basename3(m.testFile)}\`` : "-",
7128
+ m.testFile ? `\`${basename4(m.testFile)}\`` : "-",
6668
7129
  formatNumber(m.symbolCount)
6669
7130
  ]);
6670
7131
  let output = table(headers, rows);
@@ -7440,8 +7901,8 @@ function getTopLevelDir2(filePath) {
7440
7901
  }
7441
7902
 
7442
7903
  // src/docs/status.ts
7443
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
7444
- import { join as join10 } from "path";
7904
+ import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
7905
+ import { resolve as resolve7 } from "path";
7445
7906
  function generateStatus(graph, projectRoot, version) {
7446
7907
  let output = "";
7447
7908
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -7474,12 +7935,16 @@ function getFileCount11(graph) {
7474
7935
  }
7475
7936
  function extractComments(projectRoot, filePath) {
7476
7937
  const comments = [];
7477
- const fullPath = join10(projectRoot, filePath);
7938
+ const resolvedRoot = resolve7(projectRoot);
7939
+ const fullPath = resolve7(resolvedRoot, filePath);
7940
+ if (!fullPath.startsWith(resolvedRoot)) {
7941
+ return comments;
7942
+ }
7478
7943
  if (!existsSync10(fullPath)) {
7479
7944
  return comments;
7480
7945
  }
7481
7946
  try {
7482
- const content = readFileSync8(fullPath, "utf-8");
7947
+ const content = readFileSync10(fullPath, "utf-8");
7483
7948
  const lines = content.split("\n");
7484
7949
  const patterns = [
7485
7950
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -8058,15 +8523,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
8058
8523
  }
8059
8524
 
8060
8525
  // src/docs/metadata.ts
8061
- import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "fs";
8062
- import { join as join11 } from "path";
8526
+ import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync2 } from "fs";
8527
+ import { resolve as resolve8 } from "path";
8063
8528
  function loadMetadata(outputDir) {
8064
- const metadataPath = join11(outputDir, "metadata.json");
8065
- if (!existsSync11(metadataPath)) {
8529
+ const resolvedDir = resolve8(outputDir);
8530
+ const metadataPath = resolve8(resolvedDir, "metadata.json");
8531
+ if (!metadataPath.startsWith(resolvedDir) || !existsSync11(metadataPath)) {
8066
8532
  return null;
8067
8533
  }
8068
8534
  try {
8069
- const content = readFileSync9(metadataPath, "utf-8");
8535
+ const content = readFileSync11(metadataPath, "utf-8");
8070
8536
  return JSON.parse(content);
8071
8537
  } catch (err) {
8072
8538
  console.error("Failed to load metadata:", err);
@@ -8074,7 +8540,11 @@ function loadMetadata(outputDir) {
8074
8540
  }
8075
8541
  }
8076
8542
  function saveMetadata(outputDir, metadata) {
8077
- const metadataPath = join11(outputDir, "metadata.json");
8543
+ const resolvedDir = resolve8(outputDir);
8544
+ const metadataPath = resolve8(resolvedDir, "metadata.json");
8545
+ if (!metadataPath.startsWith(resolvedDir)) {
8546
+ throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
8547
+ }
8078
8548
  writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
8079
8549
  }
8080
8550
  function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
@@ -8156,7 +8626,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8156
8626
  try {
8157
8627
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
8158
8628
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
8159
- const filePath = join12(options.outputDir, "ARCHITECTURE.md");
8629
+ const filePath = join14(options.outputDir, "ARCHITECTURE.md");
8160
8630
  writeFileSync3(filePath, content, "utf-8");
8161
8631
  generated.push("ARCHITECTURE.md");
8162
8632
  } catch (err) {
@@ -8167,7 +8637,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8167
8637
  try {
8168
8638
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
8169
8639
  const content = generateConventions(graph, projectRoot, version);
8170
- const filePath = join12(options.outputDir, "CONVENTIONS.md");
8640
+ const filePath = join14(options.outputDir, "CONVENTIONS.md");
8171
8641
  writeFileSync3(filePath, content, "utf-8");
8172
8642
  generated.push("CONVENTIONS.md");
8173
8643
  } catch (err) {
@@ -8178,7 +8648,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8178
8648
  try {
8179
8649
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
8180
8650
  const content = generateDependencies(graph, projectRoot, version);
8181
- const filePath = join12(options.outputDir, "DEPENDENCIES.md");
8651
+ const filePath = join14(options.outputDir, "DEPENDENCIES.md");
8182
8652
  writeFileSync3(filePath, content, "utf-8");
8183
8653
  generated.push("DEPENDENCIES.md");
8184
8654
  } catch (err) {
@@ -8189,7 +8659,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8189
8659
  try {
8190
8660
  if (options.verbose) console.log("Generating ONBOARDING.md...");
8191
8661
  const content = generateOnboarding(graph, projectRoot, version);
8192
- const filePath = join12(options.outputDir, "ONBOARDING.md");
8662
+ const filePath = join14(options.outputDir, "ONBOARDING.md");
8193
8663
  writeFileSync3(filePath, content, "utf-8");
8194
8664
  generated.push("ONBOARDING.md");
8195
8665
  } catch (err) {
@@ -8200,7 +8670,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8200
8670
  try {
8201
8671
  if (options.verbose) console.log("Generating FILES.md...");
8202
8672
  const content = generateFiles(graph, projectRoot, version);
8203
- const filePath = join12(options.outputDir, "FILES.md");
8673
+ const filePath = join14(options.outputDir, "FILES.md");
8204
8674
  writeFileSync3(filePath, content, "utf-8");
8205
8675
  generated.push("FILES.md");
8206
8676
  } catch (err) {
@@ -8211,7 +8681,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8211
8681
  try {
8212
8682
  if (options.verbose) console.log("Generating API_SURFACE.md...");
8213
8683
  const content = generateApiSurface(graph, projectRoot, version);
8214
- const filePath = join12(options.outputDir, "API_SURFACE.md");
8684
+ const filePath = join14(options.outputDir, "API_SURFACE.md");
8215
8685
  writeFileSync3(filePath, content, "utf-8");
8216
8686
  generated.push("API_SURFACE.md");
8217
8687
  } catch (err) {
@@ -8222,7 +8692,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8222
8692
  try {
8223
8693
  if (options.verbose) console.log("Generating ERRORS.md...");
8224
8694
  const content = generateErrors(graph, projectRoot, version);
8225
- const filePath = join12(options.outputDir, "ERRORS.md");
8695
+ const filePath = join14(options.outputDir, "ERRORS.md");
8226
8696
  writeFileSync3(filePath, content, "utf-8");
8227
8697
  generated.push("ERRORS.md");
8228
8698
  } catch (err) {
@@ -8233,7 +8703,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8233
8703
  try {
8234
8704
  if (options.verbose) console.log("Generating TESTS.md...");
8235
8705
  const content = generateTests(graph, projectRoot, version);
8236
- const filePath = join12(options.outputDir, "TESTS.md");
8706
+ const filePath = join14(options.outputDir, "TESTS.md");
8237
8707
  writeFileSync3(filePath, content, "utf-8");
8238
8708
  generated.push("TESTS.md");
8239
8709
  } catch (err) {
@@ -8244,7 +8714,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8244
8714
  try {
8245
8715
  if (options.verbose) console.log("Generating HISTORY.md...");
8246
8716
  const content = generateHistory(graph, projectRoot, version);
8247
- const filePath = join12(options.outputDir, "HISTORY.md");
8717
+ const filePath = join14(options.outputDir, "HISTORY.md");
8248
8718
  writeFileSync3(filePath, content, "utf-8");
8249
8719
  generated.push("HISTORY.md");
8250
8720
  } catch (err) {
@@ -8255,7 +8725,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8255
8725
  try {
8256
8726
  if (options.verbose) console.log("Generating CURRENT.md...");
8257
8727
  const content = generateCurrent(graph, projectRoot, version);
8258
- const filePath = join12(options.outputDir, "CURRENT.md");
8728
+ const filePath = join14(options.outputDir, "CURRENT.md");
8259
8729
  writeFileSync3(filePath, content, "utf-8");
8260
8730
  generated.push("CURRENT.md");
8261
8731
  } catch (err) {
@@ -8266,7 +8736,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8266
8736
  try {
8267
8737
  if (options.verbose) console.log("Generating STATUS.md...");
8268
8738
  const content = generateStatus(graph, projectRoot, version);
8269
- const filePath = join12(options.outputDir, "STATUS.md");
8739
+ const filePath = join14(options.outputDir, "STATUS.md");
8270
8740
  writeFileSync3(filePath, content, "utf-8");
8271
8741
  generated.push("STATUS.md");
8272
8742
  } catch (err) {
@@ -8277,7 +8747,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8277
8747
  try {
8278
8748
  if (options.verbose) console.log("Generating HEALTH.md...");
8279
8749
  const content = generateHealth(graph, projectRoot, version);
8280
- const filePath = join12(options.outputDir, "HEALTH.md");
8750
+ const filePath = join14(options.outputDir, "HEALTH.md");
8281
8751
  writeFileSync3(filePath, content, "utf-8");
8282
8752
  generated.push("HEALTH.md");
8283
8753
  } catch (err) {
@@ -8288,7 +8758,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8288
8758
  try {
8289
8759
  if (options.verbose) console.log("Generating DEAD_CODE.md...");
8290
8760
  const content = generateDeadCode(graph, projectRoot, version);
8291
- const filePath = join12(options.outputDir, "DEAD_CODE.md");
8761
+ const filePath = join14(options.outputDir, "DEAD_CODE.md");
8292
8762
  writeFileSync3(filePath, content, "utf-8");
8293
8763
  generated.push("DEAD_CODE.md");
8294
8764
  } catch (err) {
@@ -8332,13 +8802,13 @@ function getFileCount13(graph) {
8332
8802
  }
8333
8803
 
8334
8804
  // src/simulation/engine.ts
8335
- import { dirname as dirname15, join as join13 } from "path";
8336
- function normalizePath(p) {
8805
+ import { dirname as dirname15, join as join15 } from "path";
8806
+ function normalizePath2(p) {
8337
8807
  return p.replace(/^\.\//, "").replace(/\/+$/, "");
8338
8808
  }
8339
8809
  function fileMatch(nodeFilePath, target) {
8340
- const a = normalizePath(nodeFilePath);
8341
- const b = normalizePath(target);
8810
+ const a = normalizePath2(nodeFilePath);
8811
+ const b = normalizePath2(target);
8342
8812
  return a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
8343
8813
  }
8344
8814
  var SimulationEngine = class {
@@ -8403,8 +8873,8 @@ var SimulationEngine = class {
8403
8873
  }
8404
8874
  // ── Action implementations ─────────────────────────────────────
8405
8875
  applyMove(clone, target, destination, brokenImports) {
8406
- const normalizedTarget = normalizePath(target);
8407
- const normalizedDest = normalizePath(destination);
8876
+ const normalizedTarget = normalizePath2(target);
8877
+ const normalizedDest = normalizePath2(destination);
8408
8878
  const nodesToMove = clone.filterNodes(
8409
8879
  (_node, attrs) => fileMatch(attrs.filePath, target)
8410
8880
  );
@@ -8463,11 +8933,11 @@ var SimulationEngine = class {
8463
8933
  }
8464
8934
  }
8465
8935
  applyRename(clone, target, newName, brokenImports) {
8466
- const destination = join13(dirname15(target), newName);
8936
+ const destination = join15(dirname15(target), newName);
8467
8937
  this.applyMove(clone, target, destination, brokenImports);
8468
8938
  }
8469
8939
  applySplit(clone, target, newFile, symbols, brokenImports) {
8470
- const normalizedNewFile = normalizePath(newFile);
8940
+ const normalizedNewFile = normalizePath2(newFile);
8471
8941
  const nodesToSplit = clone.filterNodes((_node, attrs) => {
8472
8942
  return fileMatch(attrs.filePath, target) && symbols.includes(attrs.name);
8473
8943
  });
@@ -8503,7 +8973,7 @@ var SimulationEngine = class {
8503
8973
  }
8504
8974
  }
8505
8975
  applyMerge(clone, target, source, brokenImports) {
8506
- const normalizedTarget = normalizePath(target);
8976
+ const normalizedTarget = normalizePath2(target);
8507
8977
  const sourceNodes = clone.filterNodes(
8508
8978
  (_node, attrs) => fileMatch(attrs.filePath, source)
8509
8979
  );
@@ -8664,12 +9134,12 @@ var SimulationEngine = class {
8664
9134
 
8665
9135
  // src/security/scanner.ts
8666
9136
  import { existsSync as existsSync14 } from "fs";
8667
- import { join as join23 } from "path";
9137
+ import { join as join25 } from "path";
8668
9138
 
8669
9139
  // src/security/checks/dependencies.ts
8670
9140
  import { execSync as execSync2 } from "child_process";
8671
- import { existsSync as existsSync13, readFileSync as readFileSync10, readdirSync as readdirSync5 } from "fs";
8672
- import { join as join14 } from "path";
9141
+ import { existsSync as existsSync13, readFileSync as readFileSync12, readdirSync as readdirSync5 } from "fs";
9142
+ import { join as join16 } from "path";
8673
9143
  function cvssToSeverity(score) {
8674
9144
  if (score >= 9) return "critical";
8675
9145
  if (score >= 7) return "high";
@@ -8679,18 +9149,18 @@ function cvssToSeverity(score) {
8679
9149
  async function checkDependencies(_files, projectRoot) {
8680
9150
  const findings = [];
8681
9151
  try {
8682
- if (existsSync13(join14(projectRoot, "package.json"))) {
9152
+ if (existsSync13(join16(projectRoot, "package.json"))) {
8683
9153
  findings.push(...checkNpmAudit(projectRoot));
8684
9154
  findings.push(...checkPackageJsonPatterns(projectRoot));
8685
9155
  findings.push(...checkPostinstallScripts(projectRoot));
8686
9156
  }
8687
- if (existsSync13(join14(projectRoot, "requirements.txt")) || existsSync13(join14(projectRoot, "pyproject.toml"))) {
9157
+ if (existsSync13(join16(projectRoot, "requirements.txt")) || existsSync13(join16(projectRoot, "pyproject.toml"))) {
8688
9158
  findings.push(...checkPipAudit(projectRoot));
8689
9159
  }
8690
- if (existsSync13(join14(projectRoot, "Cargo.toml"))) {
9160
+ if (existsSync13(join16(projectRoot, "Cargo.toml"))) {
8691
9161
  findings.push(...checkCargoAudit(projectRoot));
8692
9162
  }
8693
- if (existsSync13(join14(projectRoot, "go.mod"))) {
9163
+ if (existsSync13(join16(projectRoot, "go.mod"))) {
8694
9164
  findings.push(...checkGoVerify(projectRoot));
8695
9165
  }
8696
9166
  } catch (err) {
@@ -8779,8 +9249,8 @@ function checkNpmAudit(projectRoot) {
8779
9249
  function checkPackageJsonPatterns(projectRoot) {
8780
9250
  const findings = [];
8781
9251
  try {
8782
- const pkgPath = join14(projectRoot, "package.json");
8783
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
9252
+ const pkgPath = join16(projectRoot, "package.json");
9253
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
8784
9254
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
8785
9255
  for (const [name, version] of Object.entries(allDeps)) {
8786
9256
  if (version.startsWith("^") || version.startsWith("~")) {
@@ -8802,15 +9272,15 @@ function checkPackageJsonPatterns(projectRoot) {
8802
9272
  }
8803
9273
  function checkPostinstallScripts(projectRoot) {
8804
9274
  const findings = [];
8805
- const nodeModules = join14(projectRoot, "node_modules");
9275
+ const nodeModules = join16(projectRoot, "node_modules");
8806
9276
  if (!existsSync13(nodeModules)) return findings;
8807
9277
  try {
8808
9278
  const topLevelDeps = readdirSync5(nodeModules).filter((d) => !d.startsWith("."));
8809
9279
  for (const dep of topLevelDeps) {
8810
- const depPkgPath = join14(nodeModules, dep, "package.json");
9280
+ const depPkgPath = join16(nodeModules, dep, "package.json");
8811
9281
  if (!existsSync13(depPkgPath)) continue;
8812
9282
  try {
8813
- const depPkg = JSON.parse(readFileSync10(depPkgPath, "utf-8"));
9283
+ const depPkg = JSON.parse(readFileSync12(depPkgPath, "utf-8"));
8814
9284
  const scripts = depPkg.scripts || {};
8815
9285
  if (scripts.postinstall || scripts.preinstall || scripts.install) {
8816
9286
  const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
@@ -8848,7 +9318,7 @@ function checkPipAudit(projectRoot) {
8848
9318
  id: "",
8849
9319
  severity: cvssToSeverity(vuln.cvss?.score || 5),
8850
9320
  vulnerabilityClass: "dependency-cve",
8851
- file: existsSync13(join14(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
9321
+ file: existsSync13(join16(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
8852
9322
  title: `Vulnerable Python dependency: ${vuln.name}`,
8853
9323
  description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
8854
9324
  attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
@@ -8945,8 +9415,8 @@ function checkGoVerify(projectRoot) {
8945
9415
  }
8946
9416
 
8947
9417
  // src/security/checks/injection.ts
8948
- import { readFileSync as readFileSync11 } from "fs";
8949
- import { join as join15 } from "path";
9418
+ import { readFileSync as readFileSync13 } from "fs";
9419
+ import { join as join17 } from "path";
8950
9420
  var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
8951
9421
  var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
8952
9422
  var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
@@ -9047,7 +9517,7 @@ async function checkInjection(files, projectRoot) {
9047
9517
  if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
9048
9518
  let content;
9049
9519
  try {
9050
- content = readFileSync11(join15(projectRoot, file.filePath), "utf-8");
9520
+ content = readFileSync13(join17(projectRoot, file.filePath), "utf-8");
9051
9521
  } catch {
9052
9522
  continue;
9053
9523
  }
@@ -9057,6 +9527,7 @@ async function checkInjection(files, projectRoot) {
9057
9527
  if (line.trimStart().startsWith("//") || line.trimStart().startsWith("#") || line.trimStart().startsWith("*")) {
9058
9528
  continue;
9059
9529
  }
9530
+ if (line.includes("depwire-security-reviewed")) continue;
9060
9531
  for (const pattern of PATTERNS) {
9061
9532
  if (pattern.regex.test(line)) {
9062
9533
  let severity = pattern.baseSeverity;
@@ -9084,8 +9555,8 @@ async function checkInjection(files, projectRoot) {
9084
9555
  }
9085
9556
 
9086
9557
  // src/security/checks/secrets.ts
9087
- import { readFileSync as readFileSync12 } from "fs";
9088
- import { join as join16 } from "path";
9558
+ import { readFileSync as readFileSync14 } from "fs";
9559
+ import { join as join18 } from "path";
9089
9560
  var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9090
9561
  var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
9091
9562
  var SECRET_PATTERNS = [
@@ -9118,7 +9589,7 @@ async function checkSecrets(files, projectRoot) {
9118
9589
  if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
9119
9590
  let content;
9120
9591
  try {
9121
- content = readFileSync12(join16(projectRoot, file.filePath), "utf-8");
9592
+ content = readFileSync14(join18(projectRoot, file.filePath), "utf-8");
9122
9593
  } catch {
9123
9594
  continue;
9124
9595
  }
@@ -9151,8 +9622,8 @@ async function checkSecrets(files, projectRoot) {
9151
9622
  }
9152
9623
 
9153
9624
  // src/security/checks/path-traversal.ts
9154
- import { readFileSync as readFileSync13 } from "fs";
9155
- import { join as join17 } from "path";
9625
+ import { readFileSync as readFileSync15 } from "fs";
9626
+ import { join as join19 } from "path";
9156
9627
  var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9157
9628
  var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
9158
9629
  var PATTERNS2 = [
@@ -9199,7 +9670,7 @@ async function checkPathTraversal(files, projectRoot) {
9199
9670
  if (shouldSkip3(file.filePath)) continue;
9200
9671
  let content;
9201
9672
  try {
9202
- content = readFileSync13(join17(projectRoot, file.filePath), "utf-8");
9673
+ content = readFileSync15(join19(projectRoot, file.filePath), "utf-8");
9203
9674
  } catch {
9204
9675
  continue;
9205
9676
  }
@@ -9217,8 +9688,8 @@ async function checkPathTraversal(files, projectRoot) {
9217
9688
  if (SAFE_OUTPUT_PATTERNS.test(context)) continue;
9218
9689
  }
9219
9690
  if (/__dirname/.test(line) && SAFE_DIRNAME_ARGS.test(line)) continue;
9220
- const nearbyLines = lines.slice(Math.max(0, i - 3), Math.min(lines.length, i + 4)).join("\n");
9221
- if (nearbyLines.includes("startsWith") && nearbyLines.includes("resolve")) continue;
9691
+ const nearbyLines = lines.slice(Math.max(0, i - 15), Math.min(lines.length, i + 4)).join("\n");
9692
+ if (nearbyLines.includes("startsWith") && /resolve/.test(nearbyLines)) continue;
9222
9693
  const severity = inRouteOrTool ? "high" : "medium";
9223
9694
  findings.push({
9224
9695
  id: "",
@@ -9241,8 +9712,8 @@ async function checkPathTraversal(files, projectRoot) {
9241
9712
  }
9242
9713
 
9243
9714
  // src/security/checks/auth.ts
9244
- import { readFileSync as readFileSync14 } from "fs";
9245
- import { join as join18 } from "path";
9715
+ import { readFileSync as readFileSync16 } from "fs";
9716
+ import { join as join20 } from "path";
9246
9717
  var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9247
9718
  function shouldSkip4(filePath) {
9248
9719
  return SKIP_DIRS4.some((d) => filePath.includes(d));
@@ -9258,7 +9729,7 @@ async function checkAuth(files, projectRoot) {
9258
9729
  if (shouldSkip4(file.filePath)) continue;
9259
9730
  let content;
9260
9731
  try {
9261
- content = readFileSync14(join18(projectRoot, file.filePath), "utf-8");
9732
+ content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
9262
9733
  } catch {
9263
9734
  continue;
9264
9735
  }
@@ -9349,8 +9820,8 @@ async function checkAuth(files, projectRoot) {
9349
9820
  }
9350
9821
 
9351
9822
  // src/security/checks/input-validation.ts
9352
- import { readFileSync as readFileSync15 } from "fs";
9353
- import { join as join19 } from "path";
9823
+ import { readFileSync as readFileSync17 } from "fs";
9824
+ import { join as join21 } from "path";
9354
9825
  var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9355
9826
  function shouldSkip5(filePath) {
9356
9827
  return SKIP_DIRS5.some((d) => filePath.includes(d));
@@ -9362,7 +9833,7 @@ async function checkInputValidation(files, projectRoot) {
9362
9833
  if (shouldSkip5(file.filePath)) continue;
9363
9834
  let content;
9364
9835
  try {
9365
- content = readFileSync15(join19(projectRoot, file.filePath), "utf-8");
9836
+ content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
9366
9837
  } catch {
9367
9838
  continue;
9368
9839
  }
@@ -9439,8 +9910,8 @@ async function checkInputValidation(files, projectRoot) {
9439
9910
  }
9440
9911
 
9441
9912
  // src/security/checks/information-disclosure.ts
9442
- import { readFileSync as readFileSync16 } from "fs";
9443
- import { join as join20 } from "path";
9913
+ import { readFileSync as readFileSync18 } from "fs";
9914
+ import { join as join22 } from "path";
9444
9915
  var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9445
9916
  function shouldSkip6(filePath) {
9446
9917
  return SKIP_DIRS6.some((d) => filePath.includes(d));
@@ -9452,7 +9923,7 @@ async function checkInformationDisclosure(files, projectRoot) {
9452
9923
  if (shouldSkip6(file.filePath)) continue;
9453
9924
  let content;
9454
9925
  try {
9455
- content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
9926
+ content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
9456
9927
  } catch {
9457
9928
  continue;
9458
9929
  }
@@ -9522,8 +9993,8 @@ async function checkInformationDisclosure(files, projectRoot) {
9522
9993
  }
9523
9994
 
9524
9995
  // src/security/checks/cryptography.ts
9525
- import { readFileSync as readFileSync17 } from "fs";
9526
- import { join as join21 } from "path";
9996
+ import { readFileSync as readFileSync19 } from "fs";
9997
+ import { join as join23 } from "path";
9527
9998
  var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9528
9999
  function shouldSkip7(filePath) {
9529
10000
  return SKIP_DIRS7.some((d) => filePath.includes(d));
@@ -9539,7 +10010,7 @@ async function checkCryptography(files, projectRoot) {
9539
10010
  if (shouldSkip7(file.filePath)) continue;
9540
10011
  let content;
9541
10012
  try {
9542
- content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
10013
+ content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
9543
10014
  } catch {
9544
10015
  continue;
9545
10016
  }
@@ -9621,8 +10092,8 @@ async function checkCryptography(files, projectRoot) {
9621
10092
  }
9622
10093
 
9623
10094
  // src/security/checks/frontend.ts
9624
- import { readFileSync as readFileSync18 } from "fs";
9625
- import { join as join22 } from "path";
10095
+ import { readFileSync as readFileSync20 } from "fs";
10096
+ import { join as join24 } from "path";
9626
10097
  var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9627
10098
  function shouldSkip8(filePath) {
9628
10099
  return SKIP_DIRS8.some((d) => filePath.includes(d));
@@ -9638,7 +10109,7 @@ async function checkFrontend(files, projectRoot) {
9638
10109
  if (!isFrontendFile(file.filePath)) continue;
9639
10110
  let content;
9640
10111
  try {
9641
- content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
10112
+ content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
9642
10113
  } catch {
9643
10114
  continue;
9644
10115
  }
@@ -10098,11 +10569,11 @@ async function scanSecurity(projectRoot, graph, options = {}) {
10098
10569
  };
10099
10570
  }
10100
10571
  function detectPackageManager(projectRoot) {
10101
- if (existsSync14(join23(projectRoot, "package.json"))) return "npm";
10102
- if (existsSync14(join23(projectRoot, "requirements.txt"))) return "pip";
10103
- if (existsSync14(join23(projectRoot, "pyproject.toml"))) return "pip";
10104
- if (existsSync14(join23(projectRoot, "Cargo.toml"))) return "cargo";
10105
- if (existsSync14(join23(projectRoot, "go.mod"))) return "go";
10572
+ if (existsSync14(join25(projectRoot, "package.json"))) return "npm";
10573
+ if (existsSync14(join25(projectRoot, "requirements.txt"))) return "pip";
10574
+ if (existsSync14(join25(projectRoot, "pyproject.toml"))) return "pip";
10575
+ if (existsSync14(join25(projectRoot, "Cargo.toml"))) return "cargo";
10576
+ if (existsSync14(join25(projectRoot, "go.mod"))) return "go";
10106
10577
  return "unknown";
10107
10578
  }
10108
10579
 
@@ -10110,6 +10581,7 @@ export {
10110
10581
  findProjectRoot,
10111
10582
  parseTypeScriptFile,
10112
10583
  parseProject,
10584
+ detectCrossLanguageEdges,
10113
10585
  buildGraph,
10114
10586
  findSymbols,
10115
10587
  getDependencies,