depwire-cli 0.9.27 → 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.
@@ -2873,9 +2873,449 @@ async function parseProject(projectRoot, options) {
2873
2873
  return parsedFiles;
2874
2874
  }
2875
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
+
2876
3316
  // src/graph/index.ts
2877
3317
  import { DirectedGraph } from "graphology";
2878
- function buildGraph(parsedFiles) {
3318
+ function buildGraph(parsedFiles, projectRoot) {
2879
3319
  const graph = new DirectedGraph();
2880
3320
  for (const file of parsedFiles) {
2881
3321
  for (const symbol of file.symbols) {
@@ -2932,6 +3372,12 @@ function buildGraph(parsedFiles) {
2932
3372
  }
2933
3373
  }
2934
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
+ }
2935
3381
  return graph;
2936
3382
  }
2937
3383
 
@@ -3063,7 +3509,9 @@ function getCrossFileEdges(graph) {
3063
3509
  target,
3064
3510
  sourceFile: sourceAttrs.filePath,
3065
3511
  targetFile: targetAttrs.filePath,
3066
- kind: attrs.kind
3512
+ kind: attrs.kind,
3513
+ crossLanguage: attrs.crossLanguage || false,
3514
+ edgeType: attrs.edgeType
3067
3515
  });
3068
3516
  }
3069
3517
  });
@@ -3511,8 +3959,8 @@ function calculateDepthScore(graph) {
3511
3959
  }
3512
3960
 
3513
3961
  // src/health/index.ts
3514
- import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync8, mkdirSync } from "fs";
3515
- import { dirname as dirname8, resolve as resolve4 } from "path";
3962
+ import { readFileSync as readFileSync8, writeFileSync, existsSync as existsSync8, mkdirSync } from "fs";
3963
+ import { dirname as dirname8, resolve as resolve6 } from "path";
3516
3964
  function calculateHealthScore(graph, projectRoot) {
3517
3965
  const coupling = calculateCouplingScore(graph);
3518
3966
  const cohesion = calculateCohesionScore(graph);
@@ -3611,8 +4059,8 @@ function getHealthTrend(projectRoot, currentScore) {
3611
4059
  }
3612
4060
  }
3613
4061
  function saveHealthHistory(projectRoot, report) {
3614
- const resolvedRoot = resolve4(projectRoot);
3615
- const historyFile = resolve4(resolvedRoot, ".depwire", "health-history.json");
4062
+ const resolvedRoot = resolve6(projectRoot);
4063
+ const historyFile = resolve6(resolvedRoot, ".depwire", "health-history.json");
3616
4064
  if (!historyFile.startsWith(resolvedRoot)) {
3617
4065
  return;
3618
4066
  }
@@ -3630,7 +4078,7 @@ function saveHealthHistory(projectRoot, report) {
3630
4078
  if (existsSync8(historyFile)) {
3631
4079
  try {
3632
4080
  if (!historyFile.startsWith(resolvedRoot)) return;
3633
- const content = readFileSync6(historyFile, "utf-8");
4081
+ const content = readFileSync8(historyFile, "utf-8");
3634
4082
  history = JSON.parse(content);
3635
4083
  } catch {
3636
4084
  }
@@ -3644,14 +4092,14 @@ function saveHealthHistory(projectRoot, report) {
3644
4092
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
3645
4093
  }
3646
4094
  function loadHealthHistory(projectRoot) {
3647
- const resolvedRoot = resolve4(projectRoot);
3648
- const historyFile = resolve4(resolvedRoot, ".depwire", "health-history.json");
4095
+ const resolvedRoot = resolve6(projectRoot);
4096
+ const historyFile = resolve6(resolvedRoot, ".depwire", "health-history.json");
3649
4097
  if (!historyFile.startsWith(resolvedRoot) || !existsSync8(historyFile)) {
3650
4098
  return [];
3651
4099
  }
3652
4100
  try {
3653
4101
  if (!historyFile.startsWith(resolvedRoot)) return [];
3654
- const content = readFileSync6(historyFile, "utf-8");
4102
+ const content = readFileSync8(historyFile, "utf-8");
3655
4103
  return JSON.parse(content);
3656
4104
  } catch {
3657
4105
  return [];
@@ -3660,7 +4108,7 @@ function loadHealthHistory(projectRoot) {
3660
4108
 
3661
4109
  // src/dead-code/detector.ts
3662
4110
  import path2 from "path";
3663
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
4111
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
3664
4112
  function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
3665
4113
  const deadSymbols = [];
3666
4114
  const context = { graph, projectRoot };
@@ -3795,7 +4243,7 @@ function getPackageEntryPoints(projectRoot) {
3795
4243
  return entryPoints;
3796
4244
  }
3797
4245
  try {
3798
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
4246
+ const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
3799
4247
  if (packageJson.main) {
3800
4248
  entryPoints.add(path2.resolve(projectRoot, packageJson.main));
3801
4249
  }
@@ -3936,8 +4384,8 @@ function generateReason(symbol, confidence) {
3936
4384
  return "Potentially unused";
3937
4385
  }
3938
4386
  function isBarrelFile(filePath) {
3939
- const basename4 = path3.basename(filePath);
3940
- return basename4 === "index.ts" || basename4 === "index.js";
4387
+ const basename5 = path3.basename(filePath);
4388
+ return basename5 === "index.ts" || basename5 === "index.js";
3941
4389
  }
3942
4390
  function isTestFile2(filePath) {
3943
4391
  return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
@@ -4117,7 +4565,7 @@ function filterByConfidence(symbols, minConfidence) {
4117
4565
 
4118
4566
  // src/docs/generator.ts
4119
4567
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
4120
- import { join as join12 } from "path";
4568
+ import { join as join14 } from "path";
4121
4569
 
4122
4570
  // src/docs/architecture.ts
4123
4571
  import { dirname as dirname9 } from "path";
@@ -4519,7 +4967,7 @@ function detectCycles(graph) {
4519
4967
  }
4520
4968
 
4521
4969
  // src/docs/conventions.ts
4522
- import { basename, extname as extname4 } from "path";
4970
+ import { basename as basename2, extname as extname4 } from "path";
4523
4971
  function generateConventions(graph, projectRoot, version) {
4524
4972
  let output = "";
4525
4973
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -4557,7 +5005,7 @@ function generateFileOrganization(graph) {
4557
5005
  graph.forEachNode((node, attrs) => {
4558
5006
  if (!files.has(attrs.filePath)) {
4559
5007
  files.add(attrs.filePath);
4560
- const fileName = basename(attrs.filePath);
5008
+ const fileName = basename2(attrs.filePath);
4561
5009
  if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
4562
5010
  barrelFileCount++;
4563
5011
  }
@@ -4616,7 +5064,7 @@ function generateNamingPatterns(graph) {
4616
5064
  graph.forEachNode((node, attrs) => {
4617
5065
  if (!files.has(attrs.filePath)) {
4618
5066
  files.add(attrs.filePath);
4619
- const fileName = basename(attrs.filePath, extname4(attrs.filePath));
5067
+ const fileName = basename2(attrs.filePath, extname4(attrs.filePath));
4620
5068
  if (isCamelCase(fileName)) patterns.files.camelCase++;
4621
5069
  else if (isPascalCase(fileName)) patterns.files.PascalCase++;
4622
5070
  else if (isKebabCase(fileName)) patterns.files.kebabCase++;
@@ -5681,7 +6129,7 @@ function generateDepwireUsage(projectRoot) {
5681
6129
  }
5682
6130
 
5683
6131
  // src/docs/files.ts
5684
- import { dirname as dirname11, basename as basename2 } from "path";
6132
+ import { dirname as dirname11, basename as basename3 } from "path";
5685
6133
  function generateFiles(graph, projectRoot, version) {
5686
6134
  let output = "";
5687
6135
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -5800,7 +6248,7 @@ function generateDirectoryBreakdown(graph) {
5800
6248
  dirStats.symbolCount += file.symbolCount;
5801
6249
  if (file.totalConnections > dirStats.maxConnections) {
5802
6250
  dirStats.maxConnections = file.totalConnections;
5803
- dirStats.mostConnectedFile = basename2(file.filePath);
6251
+ dirStats.mostConnectedFile = basename3(file.filePath);
5804
6252
  }
5805
6253
  }
5806
6254
  if (dirMap.size === 0) {
@@ -6428,7 +6876,7 @@ function generateRecommendations(graph) {
6428
6876
  }
6429
6877
 
6430
6878
  // src/docs/tests.ts
6431
- import { basename as basename3, dirname as dirname12 } from "path";
6879
+ import { basename as basename4, dirname as dirname12 } from "path";
6432
6880
  function generateTests(graph, projectRoot, version) {
6433
6881
  let output = "";
6434
6882
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -6456,7 +6904,7 @@ function getFileCount8(graph) {
6456
6904
  return files.size;
6457
6905
  }
6458
6906
  function isTestFile3(filePath) {
6459
- const fileName = basename3(filePath).toLowerCase();
6907
+ const fileName = basename4(filePath).toLowerCase();
6460
6908
  const dirPath = dirname12(filePath).toLowerCase();
6461
6909
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
6462
6910
  return true;
@@ -6515,7 +6963,7 @@ function generateTestFileInventory(graph) {
6515
6963
  return output;
6516
6964
  }
6517
6965
  function matchTestToSource(testFile) {
6518
- const testFileName = basename3(testFile);
6966
+ const testFileName = basename4(testFile);
6519
6967
  const testDir = dirname12(testFile);
6520
6968
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
6521
6969
  const possiblePaths = [];
@@ -6677,7 +7125,7 @@ function generateTestCoverageMap(graph) {
6677
7125
  const rows = mappings.slice(0, 30).map((m) => [
6678
7126
  `\`${m.sourceFile}\``,
6679
7127
  m.hasTest ? "\u2705" : "\u274C",
6680
- m.testFile ? `\`${basename3(m.testFile)}\`` : "-",
7128
+ m.testFile ? `\`${basename4(m.testFile)}\`` : "-",
6681
7129
  formatNumber(m.symbolCount)
6682
7130
  ]);
6683
7131
  let output = table(headers, rows);
@@ -7453,8 +7901,8 @@ function getTopLevelDir2(filePath) {
7453
7901
  }
7454
7902
 
7455
7903
  // src/docs/status.ts
7456
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
7457
- import { resolve as resolve5 } from "path";
7904
+ import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
7905
+ import { resolve as resolve7 } from "path";
7458
7906
  function generateStatus(graph, projectRoot, version) {
7459
7907
  let output = "";
7460
7908
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -7487,8 +7935,8 @@ function getFileCount11(graph) {
7487
7935
  }
7488
7936
  function extractComments(projectRoot, filePath) {
7489
7937
  const comments = [];
7490
- const resolvedRoot = resolve5(projectRoot);
7491
- const fullPath = resolve5(resolvedRoot, filePath);
7938
+ const resolvedRoot = resolve7(projectRoot);
7939
+ const fullPath = resolve7(resolvedRoot, filePath);
7492
7940
  if (!fullPath.startsWith(resolvedRoot)) {
7493
7941
  return comments;
7494
7942
  }
@@ -7496,7 +7944,7 @@ function extractComments(projectRoot, filePath) {
7496
7944
  return comments;
7497
7945
  }
7498
7946
  try {
7499
- const content = readFileSync8(fullPath, "utf-8");
7947
+ const content = readFileSync10(fullPath, "utf-8");
7500
7948
  const lines = content.split("\n");
7501
7949
  const patterns = [
7502
7950
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -8075,16 +8523,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
8075
8523
  }
8076
8524
 
8077
8525
  // src/docs/metadata.ts
8078
- import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "fs";
8079
- import { resolve as resolve6 } from "path";
8526
+ import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync2 } from "fs";
8527
+ import { resolve as resolve8 } from "path";
8080
8528
  function loadMetadata(outputDir) {
8081
- const resolvedDir = resolve6(outputDir);
8082
- const metadataPath = resolve6(resolvedDir, "metadata.json");
8529
+ const resolvedDir = resolve8(outputDir);
8530
+ const metadataPath = resolve8(resolvedDir, "metadata.json");
8083
8531
  if (!metadataPath.startsWith(resolvedDir) || !existsSync11(metadataPath)) {
8084
8532
  return null;
8085
8533
  }
8086
8534
  try {
8087
- const content = readFileSync9(metadataPath, "utf-8");
8535
+ const content = readFileSync11(metadataPath, "utf-8");
8088
8536
  return JSON.parse(content);
8089
8537
  } catch (err) {
8090
8538
  console.error("Failed to load metadata:", err);
@@ -8092,8 +8540,8 @@ function loadMetadata(outputDir) {
8092
8540
  }
8093
8541
  }
8094
8542
  function saveMetadata(outputDir, metadata) {
8095
- const resolvedDir = resolve6(outputDir);
8096
- const metadataPath = resolve6(resolvedDir, "metadata.json");
8543
+ const resolvedDir = resolve8(outputDir);
8544
+ const metadataPath = resolve8(resolvedDir, "metadata.json");
8097
8545
  if (!metadataPath.startsWith(resolvedDir)) {
8098
8546
  throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
8099
8547
  }
@@ -8178,7 +8626,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8178
8626
  try {
8179
8627
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
8180
8628
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
8181
- const filePath = join12(options.outputDir, "ARCHITECTURE.md");
8629
+ const filePath = join14(options.outputDir, "ARCHITECTURE.md");
8182
8630
  writeFileSync3(filePath, content, "utf-8");
8183
8631
  generated.push("ARCHITECTURE.md");
8184
8632
  } catch (err) {
@@ -8189,7 +8637,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8189
8637
  try {
8190
8638
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
8191
8639
  const content = generateConventions(graph, projectRoot, version);
8192
- const filePath = join12(options.outputDir, "CONVENTIONS.md");
8640
+ const filePath = join14(options.outputDir, "CONVENTIONS.md");
8193
8641
  writeFileSync3(filePath, content, "utf-8");
8194
8642
  generated.push("CONVENTIONS.md");
8195
8643
  } catch (err) {
@@ -8200,7 +8648,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8200
8648
  try {
8201
8649
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
8202
8650
  const content = generateDependencies(graph, projectRoot, version);
8203
- const filePath = join12(options.outputDir, "DEPENDENCIES.md");
8651
+ const filePath = join14(options.outputDir, "DEPENDENCIES.md");
8204
8652
  writeFileSync3(filePath, content, "utf-8");
8205
8653
  generated.push("DEPENDENCIES.md");
8206
8654
  } catch (err) {
@@ -8211,7 +8659,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8211
8659
  try {
8212
8660
  if (options.verbose) console.log("Generating ONBOARDING.md...");
8213
8661
  const content = generateOnboarding(graph, projectRoot, version);
8214
- const filePath = join12(options.outputDir, "ONBOARDING.md");
8662
+ const filePath = join14(options.outputDir, "ONBOARDING.md");
8215
8663
  writeFileSync3(filePath, content, "utf-8");
8216
8664
  generated.push("ONBOARDING.md");
8217
8665
  } catch (err) {
@@ -8222,7 +8670,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8222
8670
  try {
8223
8671
  if (options.verbose) console.log("Generating FILES.md...");
8224
8672
  const content = generateFiles(graph, projectRoot, version);
8225
- const filePath = join12(options.outputDir, "FILES.md");
8673
+ const filePath = join14(options.outputDir, "FILES.md");
8226
8674
  writeFileSync3(filePath, content, "utf-8");
8227
8675
  generated.push("FILES.md");
8228
8676
  } catch (err) {
@@ -8233,7 +8681,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8233
8681
  try {
8234
8682
  if (options.verbose) console.log("Generating API_SURFACE.md...");
8235
8683
  const content = generateApiSurface(graph, projectRoot, version);
8236
- const filePath = join12(options.outputDir, "API_SURFACE.md");
8684
+ const filePath = join14(options.outputDir, "API_SURFACE.md");
8237
8685
  writeFileSync3(filePath, content, "utf-8");
8238
8686
  generated.push("API_SURFACE.md");
8239
8687
  } catch (err) {
@@ -8244,7 +8692,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8244
8692
  try {
8245
8693
  if (options.verbose) console.log("Generating ERRORS.md...");
8246
8694
  const content = generateErrors(graph, projectRoot, version);
8247
- const filePath = join12(options.outputDir, "ERRORS.md");
8695
+ const filePath = join14(options.outputDir, "ERRORS.md");
8248
8696
  writeFileSync3(filePath, content, "utf-8");
8249
8697
  generated.push("ERRORS.md");
8250
8698
  } catch (err) {
@@ -8255,7 +8703,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8255
8703
  try {
8256
8704
  if (options.verbose) console.log("Generating TESTS.md...");
8257
8705
  const content = generateTests(graph, projectRoot, version);
8258
- const filePath = join12(options.outputDir, "TESTS.md");
8706
+ const filePath = join14(options.outputDir, "TESTS.md");
8259
8707
  writeFileSync3(filePath, content, "utf-8");
8260
8708
  generated.push("TESTS.md");
8261
8709
  } catch (err) {
@@ -8266,7 +8714,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8266
8714
  try {
8267
8715
  if (options.verbose) console.log("Generating HISTORY.md...");
8268
8716
  const content = generateHistory(graph, projectRoot, version);
8269
- const filePath = join12(options.outputDir, "HISTORY.md");
8717
+ const filePath = join14(options.outputDir, "HISTORY.md");
8270
8718
  writeFileSync3(filePath, content, "utf-8");
8271
8719
  generated.push("HISTORY.md");
8272
8720
  } catch (err) {
@@ -8277,7 +8725,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8277
8725
  try {
8278
8726
  if (options.verbose) console.log("Generating CURRENT.md...");
8279
8727
  const content = generateCurrent(graph, projectRoot, version);
8280
- const filePath = join12(options.outputDir, "CURRENT.md");
8728
+ const filePath = join14(options.outputDir, "CURRENT.md");
8281
8729
  writeFileSync3(filePath, content, "utf-8");
8282
8730
  generated.push("CURRENT.md");
8283
8731
  } catch (err) {
@@ -8288,7 +8736,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8288
8736
  try {
8289
8737
  if (options.verbose) console.log("Generating STATUS.md...");
8290
8738
  const content = generateStatus(graph, projectRoot, version);
8291
- const filePath = join12(options.outputDir, "STATUS.md");
8739
+ const filePath = join14(options.outputDir, "STATUS.md");
8292
8740
  writeFileSync3(filePath, content, "utf-8");
8293
8741
  generated.push("STATUS.md");
8294
8742
  } catch (err) {
@@ -8299,7 +8747,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8299
8747
  try {
8300
8748
  if (options.verbose) console.log("Generating HEALTH.md...");
8301
8749
  const content = generateHealth(graph, projectRoot, version);
8302
- const filePath = join12(options.outputDir, "HEALTH.md");
8750
+ const filePath = join14(options.outputDir, "HEALTH.md");
8303
8751
  writeFileSync3(filePath, content, "utf-8");
8304
8752
  generated.push("HEALTH.md");
8305
8753
  } catch (err) {
@@ -8310,7 +8758,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8310
8758
  try {
8311
8759
  if (options.verbose) console.log("Generating DEAD_CODE.md...");
8312
8760
  const content = generateDeadCode(graph, projectRoot, version);
8313
- const filePath = join12(options.outputDir, "DEAD_CODE.md");
8761
+ const filePath = join14(options.outputDir, "DEAD_CODE.md");
8314
8762
  writeFileSync3(filePath, content, "utf-8");
8315
8763
  generated.push("DEAD_CODE.md");
8316
8764
  } catch (err) {
@@ -8354,13 +8802,13 @@ function getFileCount13(graph) {
8354
8802
  }
8355
8803
 
8356
8804
  // src/simulation/engine.ts
8357
- import { dirname as dirname15, join as join13 } from "path";
8358
- function normalizePath(p) {
8805
+ import { dirname as dirname15, join as join15 } from "path";
8806
+ function normalizePath2(p) {
8359
8807
  return p.replace(/^\.\//, "").replace(/\/+$/, "");
8360
8808
  }
8361
8809
  function fileMatch(nodeFilePath, target) {
8362
- const a = normalizePath(nodeFilePath);
8363
- const b = normalizePath(target);
8810
+ const a = normalizePath2(nodeFilePath);
8811
+ const b = normalizePath2(target);
8364
8812
  return a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
8365
8813
  }
8366
8814
  var SimulationEngine = class {
@@ -8425,8 +8873,8 @@ var SimulationEngine = class {
8425
8873
  }
8426
8874
  // ── Action implementations ─────────────────────────────────────
8427
8875
  applyMove(clone, target, destination, brokenImports) {
8428
- const normalizedTarget = normalizePath(target);
8429
- const normalizedDest = normalizePath(destination);
8876
+ const normalizedTarget = normalizePath2(target);
8877
+ const normalizedDest = normalizePath2(destination);
8430
8878
  const nodesToMove = clone.filterNodes(
8431
8879
  (_node, attrs) => fileMatch(attrs.filePath, target)
8432
8880
  );
@@ -8485,11 +8933,11 @@ var SimulationEngine = class {
8485
8933
  }
8486
8934
  }
8487
8935
  applyRename(clone, target, newName, brokenImports) {
8488
- const destination = join13(dirname15(target), newName);
8936
+ const destination = join15(dirname15(target), newName);
8489
8937
  this.applyMove(clone, target, destination, brokenImports);
8490
8938
  }
8491
8939
  applySplit(clone, target, newFile, symbols, brokenImports) {
8492
- const normalizedNewFile = normalizePath(newFile);
8940
+ const normalizedNewFile = normalizePath2(newFile);
8493
8941
  const nodesToSplit = clone.filterNodes((_node, attrs) => {
8494
8942
  return fileMatch(attrs.filePath, target) && symbols.includes(attrs.name);
8495
8943
  });
@@ -8525,7 +8973,7 @@ var SimulationEngine = class {
8525
8973
  }
8526
8974
  }
8527
8975
  applyMerge(clone, target, source, brokenImports) {
8528
- const normalizedTarget = normalizePath(target);
8976
+ const normalizedTarget = normalizePath2(target);
8529
8977
  const sourceNodes = clone.filterNodes(
8530
8978
  (_node, attrs) => fileMatch(attrs.filePath, source)
8531
8979
  );
@@ -8686,12 +9134,12 @@ var SimulationEngine = class {
8686
9134
 
8687
9135
  // src/security/scanner.ts
8688
9136
  import { existsSync as existsSync14 } from "fs";
8689
- import { join as join23 } from "path";
9137
+ import { join as join25 } from "path";
8690
9138
 
8691
9139
  // src/security/checks/dependencies.ts
8692
9140
  import { execSync as execSync2 } from "child_process";
8693
- import { existsSync as existsSync13, readFileSync as readFileSync10, readdirSync as readdirSync5 } from "fs";
8694
- 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";
8695
9143
  function cvssToSeverity(score) {
8696
9144
  if (score >= 9) return "critical";
8697
9145
  if (score >= 7) return "high";
@@ -8701,18 +9149,18 @@ function cvssToSeverity(score) {
8701
9149
  async function checkDependencies(_files, projectRoot) {
8702
9150
  const findings = [];
8703
9151
  try {
8704
- if (existsSync13(join14(projectRoot, "package.json"))) {
9152
+ if (existsSync13(join16(projectRoot, "package.json"))) {
8705
9153
  findings.push(...checkNpmAudit(projectRoot));
8706
9154
  findings.push(...checkPackageJsonPatterns(projectRoot));
8707
9155
  findings.push(...checkPostinstallScripts(projectRoot));
8708
9156
  }
8709
- if (existsSync13(join14(projectRoot, "requirements.txt")) || existsSync13(join14(projectRoot, "pyproject.toml"))) {
9157
+ if (existsSync13(join16(projectRoot, "requirements.txt")) || existsSync13(join16(projectRoot, "pyproject.toml"))) {
8710
9158
  findings.push(...checkPipAudit(projectRoot));
8711
9159
  }
8712
- if (existsSync13(join14(projectRoot, "Cargo.toml"))) {
9160
+ if (existsSync13(join16(projectRoot, "Cargo.toml"))) {
8713
9161
  findings.push(...checkCargoAudit(projectRoot));
8714
9162
  }
8715
- if (existsSync13(join14(projectRoot, "go.mod"))) {
9163
+ if (existsSync13(join16(projectRoot, "go.mod"))) {
8716
9164
  findings.push(...checkGoVerify(projectRoot));
8717
9165
  }
8718
9166
  } catch (err) {
@@ -8801,8 +9249,8 @@ function checkNpmAudit(projectRoot) {
8801
9249
  function checkPackageJsonPatterns(projectRoot) {
8802
9250
  const findings = [];
8803
9251
  try {
8804
- const pkgPath = join14(projectRoot, "package.json");
8805
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
9252
+ const pkgPath = join16(projectRoot, "package.json");
9253
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
8806
9254
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
8807
9255
  for (const [name, version] of Object.entries(allDeps)) {
8808
9256
  if (version.startsWith("^") || version.startsWith("~")) {
@@ -8824,15 +9272,15 @@ function checkPackageJsonPatterns(projectRoot) {
8824
9272
  }
8825
9273
  function checkPostinstallScripts(projectRoot) {
8826
9274
  const findings = [];
8827
- const nodeModules = join14(projectRoot, "node_modules");
9275
+ const nodeModules = join16(projectRoot, "node_modules");
8828
9276
  if (!existsSync13(nodeModules)) return findings;
8829
9277
  try {
8830
9278
  const topLevelDeps = readdirSync5(nodeModules).filter((d) => !d.startsWith("."));
8831
9279
  for (const dep of topLevelDeps) {
8832
- const depPkgPath = join14(nodeModules, dep, "package.json");
9280
+ const depPkgPath = join16(nodeModules, dep, "package.json");
8833
9281
  if (!existsSync13(depPkgPath)) continue;
8834
9282
  try {
8835
- const depPkg = JSON.parse(readFileSync10(depPkgPath, "utf-8"));
9283
+ const depPkg = JSON.parse(readFileSync12(depPkgPath, "utf-8"));
8836
9284
  const scripts = depPkg.scripts || {};
8837
9285
  if (scripts.postinstall || scripts.preinstall || scripts.install) {
8838
9286
  const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
@@ -8870,7 +9318,7 @@ function checkPipAudit(projectRoot) {
8870
9318
  id: "",
8871
9319
  severity: cvssToSeverity(vuln.cvss?.score || 5),
8872
9320
  vulnerabilityClass: "dependency-cve",
8873
- file: existsSync13(join14(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
9321
+ file: existsSync13(join16(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
8874
9322
  title: `Vulnerable Python dependency: ${vuln.name}`,
8875
9323
  description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
8876
9324
  attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
@@ -8967,8 +9415,8 @@ function checkGoVerify(projectRoot) {
8967
9415
  }
8968
9416
 
8969
9417
  // src/security/checks/injection.ts
8970
- import { readFileSync as readFileSync11 } from "fs";
8971
- import { join as join15 } from "path";
9418
+ import { readFileSync as readFileSync13 } from "fs";
9419
+ import { join as join17 } from "path";
8972
9420
  var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
8973
9421
  var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
8974
9422
  var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
@@ -9069,7 +9517,7 @@ async function checkInjection(files, projectRoot) {
9069
9517
  if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
9070
9518
  let content;
9071
9519
  try {
9072
- content = readFileSync11(join15(projectRoot, file.filePath), "utf-8");
9520
+ content = readFileSync13(join17(projectRoot, file.filePath), "utf-8");
9073
9521
  } catch {
9074
9522
  continue;
9075
9523
  }
@@ -9107,8 +9555,8 @@ async function checkInjection(files, projectRoot) {
9107
9555
  }
9108
9556
 
9109
9557
  // src/security/checks/secrets.ts
9110
- import { readFileSync as readFileSync12 } from "fs";
9111
- import { join as join16 } from "path";
9558
+ import { readFileSync as readFileSync14 } from "fs";
9559
+ import { join as join18 } from "path";
9112
9560
  var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9113
9561
  var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
9114
9562
  var SECRET_PATTERNS = [
@@ -9141,7 +9589,7 @@ async function checkSecrets(files, projectRoot) {
9141
9589
  if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
9142
9590
  let content;
9143
9591
  try {
9144
- content = readFileSync12(join16(projectRoot, file.filePath), "utf-8");
9592
+ content = readFileSync14(join18(projectRoot, file.filePath), "utf-8");
9145
9593
  } catch {
9146
9594
  continue;
9147
9595
  }
@@ -9174,8 +9622,8 @@ async function checkSecrets(files, projectRoot) {
9174
9622
  }
9175
9623
 
9176
9624
  // src/security/checks/path-traversal.ts
9177
- import { readFileSync as readFileSync13 } from "fs";
9178
- import { join as join17 } from "path";
9625
+ import { readFileSync as readFileSync15 } from "fs";
9626
+ import { join as join19 } from "path";
9179
9627
  var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9180
9628
  var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
9181
9629
  var PATTERNS2 = [
@@ -9222,7 +9670,7 @@ async function checkPathTraversal(files, projectRoot) {
9222
9670
  if (shouldSkip3(file.filePath)) continue;
9223
9671
  let content;
9224
9672
  try {
9225
- content = readFileSync13(join17(projectRoot, file.filePath), "utf-8");
9673
+ content = readFileSync15(join19(projectRoot, file.filePath), "utf-8");
9226
9674
  } catch {
9227
9675
  continue;
9228
9676
  }
@@ -9264,8 +9712,8 @@ async function checkPathTraversal(files, projectRoot) {
9264
9712
  }
9265
9713
 
9266
9714
  // src/security/checks/auth.ts
9267
- import { readFileSync as readFileSync14 } from "fs";
9268
- import { join as join18 } from "path";
9715
+ import { readFileSync as readFileSync16 } from "fs";
9716
+ import { join as join20 } from "path";
9269
9717
  var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9270
9718
  function shouldSkip4(filePath) {
9271
9719
  return SKIP_DIRS4.some((d) => filePath.includes(d));
@@ -9281,7 +9729,7 @@ async function checkAuth(files, projectRoot) {
9281
9729
  if (shouldSkip4(file.filePath)) continue;
9282
9730
  let content;
9283
9731
  try {
9284
- content = readFileSync14(join18(projectRoot, file.filePath), "utf-8");
9732
+ content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
9285
9733
  } catch {
9286
9734
  continue;
9287
9735
  }
@@ -9372,8 +9820,8 @@ async function checkAuth(files, projectRoot) {
9372
9820
  }
9373
9821
 
9374
9822
  // src/security/checks/input-validation.ts
9375
- import { readFileSync as readFileSync15 } from "fs";
9376
- import { join as join19 } from "path";
9823
+ import { readFileSync as readFileSync17 } from "fs";
9824
+ import { join as join21 } from "path";
9377
9825
  var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9378
9826
  function shouldSkip5(filePath) {
9379
9827
  return SKIP_DIRS5.some((d) => filePath.includes(d));
@@ -9385,7 +9833,7 @@ async function checkInputValidation(files, projectRoot) {
9385
9833
  if (shouldSkip5(file.filePath)) continue;
9386
9834
  let content;
9387
9835
  try {
9388
- content = readFileSync15(join19(projectRoot, file.filePath), "utf-8");
9836
+ content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
9389
9837
  } catch {
9390
9838
  continue;
9391
9839
  }
@@ -9462,8 +9910,8 @@ async function checkInputValidation(files, projectRoot) {
9462
9910
  }
9463
9911
 
9464
9912
  // src/security/checks/information-disclosure.ts
9465
- import { readFileSync as readFileSync16 } from "fs";
9466
- import { join as join20 } from "path";
9913
+ import { readFileSync as readFileSync18 } from "fs";
9914
+ import { join as join22 } from "path";
9467
9915
  var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9468
9916
  function shouldSkip6(filePath) {
9469
9917
  return SKIP_DIRS6.some((d) => filePath.includes(d));
@@ -9475,7 +9923,7 @@ async function checkInformationDisclosure(files, projectRoot) {
9475
9923
  if (shouldSkip6(file.filePath)) continue;
9476
9924
  let content;
9477
9925
  try {
9478
- content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
9926
+ content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
9479
9927
  } catch {
9480
9928
  continue;
9481
9929
  }
@@ -9545,8 +9993,8 @@ async function checkInformationDisclosure(files, projectRoot) {
9545
9993
  }
9546
9994
 
9547
9995
  // src/security/checks/cryptography.ts
9548
- import { readFileSync as readFileSync17 } from "fs";
9549
- import { join as join21 } from "path";
9996
+ import { readFileSync as readFileSync19 } from "fs";
9997
+ import { join as join23 } from "path";
9550
9998
  var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9551
9999
  function shouldSkip7(filePath) {
9552
10000
  return SKIP_DIRS7.some((d) => filePath.includes(d));
@@ -9562,7 +10010,7 @@ async function checkCryptography(files, projectRoot) {
9562
10010
  if (shouldSkip7(file.filePath)) continue;
9563
10011
  let content;
9564
10012
  try {
9565
- content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
10013
+ content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
9566
10014
  } catch {
9567
10015
  continue;
9568
10016
  }
@@ -9644,8 +10092,8 @@ async function checkCryptography(files, projectRoot) {
9644
10092
  }
9645
10093
 
9646
10094
  // src/security/checks/frontend.ts
9647
- import { readFileSync as readFileSync18 } from "fs";
9648
- import { join as join22 } from "path";
10095
+ import { readFileSync as readFileSync20 } from "fs";
10096
+ import { join as join24 } from "path";
9649
10097
  var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
9650
10098
  function shouldSkip8(filePath) {
9651
10099
  return SKIP_DIRS8.some((d) => filePath.includes(d));
@@ -9661,7 +10109,7 @@ async function checkFrontend(files, projectRoot) {
9661
10109
  if (!isFrontendFile(file.filePath)) continue;
9662
10110
  let content;
9663
10111
  try {
9664
- content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
10112
+ content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
9665
10113
  } catch {
9666
10114
  continue;
9667
10115
  }
@@ -10121,11 +10569,11 @@ async function scanSecurity(projectRoot, graph, options = {}) {
10121
10569
  };
10122
10570
  }
10123
10571
  function detectPackageManager(projectRoot) {
10124
- if (existsSync14(join23(projectRoot, "package.json"))) return "npm";
10125
- if (existsSync14(join23(projectRoot, "requirements.txt"))) return "pip";
10126
- if (existsSync14(join23(projectRoot, "pyproject.toml"))) return "pip";
10127
- if (existsSync14(join23(projectRoot, "Cargo.toml"))) return "cargo";
10128
- 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";
10129
10577
  return "unknown";
10130
10578
  }
10131
10579
 
@@ -10133,6 +10581,7 @@ export {
10133
10581
  findProjectRoot,
10134
10582
  parseTypeScriptFile,
10135
10583
  parseProject,
10584
+ detectCrossLanguageEdges,
10136
10585
  buildGraph,
10137
10586
  findSymbols,
10138
10587
  getDependencies,