depwire-cli 1.0.5 → 1.0.8

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.
@@ -35,8 +35,10 @@ function scanDirectory(rootDir, baseDir = rootDir) {
35
35
  const isCpp = entry.endsWith(".cpp") || entry.endsWith(".cc") || entry.endsWith(".cxx") || entry.endsWith(".c++") || entry.endsWith(".hpp") || entry.endsWith(".hh") || entry.endsWith(".hxx") || entry.endsWith(".h++") || entry.endsWith(".h") || entry.endsWith(".inl") || entry.endsWith(".ipp");
36
36
  const isCSharp = entry.endsWith(".cs") || entry.endsWith(".csx") || entry.endsWith(".csproj");
37
37
  const isJava = entry.endsWith(".java") || entry === "pom.xml" || entry === "build.gradle" || entry === "build.gradle.kts";
38
+ const isKotlin = entry.endsWith(".kt") || entry.endsWith(".kts") || entry === "settings.gradle.kts" || entry === "settings.gradle";
39
+ const isPhp = entry.endsWith(".php");
38
40
  const isCppBuild = entry === "CMakeLists.txt" || entry === "conanfile.txt" || entry === "vcpkg.json";
39
- if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isCppBuild) {
41
+ if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isPhp || isCppBuild) {
40
42
  files.push(relative(rootDir, fullPath));
41
43
  }
42
44
  }
@@ -78,6 +80,10 @@ function findProjectRoot(startDir = process.cwd()) {
78
80
  // Java (Maven)
79
81
  "build.gradle",
80
82
  // Java (Gradle)
83
+ "build.gradle.kts",
84
+ // Kotlin (Gradle KTS)
85
+ "composer.json",
86
+ // PHP
81
87
  ".git"
82
88
  // Any git repo
83
89
  ];
@@ -111,11 +117,11 @@ function findProjectRoot(startDir = process.cwd()) {
111
117
  }
112
118
 
113
119
  // src/parser/index.ts
114
- import { readFileSync as readFileSync8, statSync as statSync5 } from "fs";
115
- import { join as join11, resolve as resolve5 } from "path";
120
+ import { readFileSync as readFileSync10, statSync as statSync7 } from "fs";
121
+ import { join as join13, resolve as resolve7 } from "path";
116
122
 
117
123
  // src/parser/detect.ts
118
- import { extname as extname6, basename as basename4 } from "path";
124
+ import { extname as extname8, basename as basename6 } from "path";
119
125
 
120
126
  // src/parser/wasm-init.ts
121
127
  import { Parser, Language } from "web-tree-sitter";
@@ -145,7 +151,9 @@ async function initParser() {
145
151
  "c": "tree-sitter-c.wasm",
146
152
  "c_sharp": "tree-sitter-c_sharp.wasm",
147
153
  "java": "tree-sitter-java.wasm",
148
- "cpp": "tree-sitter-cpp.wasm"
154
+ "cpp": "tree-sitter-cpp.wasm",
155
+ "kotlin": "tree-sitter-kotlin.wasm",
156
+ "php": "tree-sitter-php.wasm"
149
157
  };
150
158
  for (const [name, file] of Object.entries(grammarFiles)) {
151
159
  const wasmPath = path.join(grammarsDir, file);
@@ -4927,6 +4935,1302 @@ var cppParser = {
4927
4935
  parseFile: parseCppFile
4928
4936
  };
4929
4937
 
4938
+ // src/parser/kotlin.ts
4939
+ import { dirname as dirname10, join as join11, basename as basename4 } from "path";
4940
+ import { existsSync as existsSync11, readdirSync as readdirSync8, statSync as statSync5 } from "fs";
4941
+ function parseKotlinFile(filePath, sourceCode, projectRoot) {
4942
+ if (filePath.endsWith("build.gradle.kts")) {
4943
+ return parseGradleBuild2(filePath, sourceCode, projectRoot);
4944
+ }
4945
+ if (filePath.endsWith("build.gradle")) {
4946
+ return parseGradleBuild2(filePath, sourceCode, projectRoot);
4947
+ }
4948
+ if (filePath.endsWith("settings.gradle.kts") || filePath.endsWith("settings.gradle")) {
4949
+ return parseSettingsGradle(filePath, sourceCode, projectRoot);
4950
+ }
4951
+ const parser = getParser("kotlin");
4952
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
4953
+ const context = {
4954
+ filePath,
4955
+ projectRoot,
4956
+ sourceCode,
4957
+ symbols: [],
4958
+ edges: [],
4959
+ currentScope: [],
4960
+ currentClass: null,
4961
+ currentPackage: null,
4962
+ imports: /* @__PURE__ */ new Map(),
4963
+ isBuildFile: false,
4964
+ isScriptFile: filePath.endsWith(".kts")
4965
+ };
4966
+ walkNode10(tree.rootNode, context);
4967
+ return {
4968
+ filePath,
4969
+ symbols: context.symbols,
4970
+ edges: context.edges
4971
+ };
4972
+ }
4973
+ function walkNode10(node, context) {
4974
+ processNode10(node, context);
4975
+ for (let i = 0; i < node.childCount; i++) {
4976
+ const child = node.child(i);
4977
+ if (child) {
4978
+ walkNode10(child, context);
4979
+ }
4980
+ }
4981
+ }
4982
+ function processNode10(node, context) {
4983
+ switch (node.type) {
4984
+ case "package_header":
4985
+ processPackageHeader(node, context);
4986
+ break;
4987
+ case "import_header":
4988
+ processImportHeader(node, context);
4989
+ break;
4990
+ case "class_declaration":
4991
+ processClassDeclaration5(node, context);
4992
+ break;
4993
+ case "object_declaration":
4994
+ processObjectDeclaration(node, context);
4995
+ break;
4996
+ case "companion_object":
4997
+ processCompanionObject(node, context);
4998
+ break;
4999
+ case "function_declaration":
5000
+ processFunctionDeclaration4(node, context);
5001
+ break;
5002
+ case "property_declaration":
5003
+ processPropertyDeclaration2(node, context);
5004
+ break;
5005
+ case "secondary_constructor":
5006
+ processSecondaryConstructor(node, context);
5007
+ break;
5008
+ case "type_alias":
5009
+ processTypeAlias(node, context);
5010
+ break;
5011
+ case "call_expression":
5012
+ processCallExpression10(node, context);
5013
+ break;
5014
+ case "navigation_expression":
5015
+ processNavigationExpression(node, context);
5016
+ break;
5017
+ }
5018
+ }
5019
+ function processPackageHeader(node, context) {
5020
+ const ident = findDescendantByTypes2(node, ["identifier"]);
5021
+ if (!ident) return;
5022
+ const name = nodeText9(ident, context);
5023
+ const symbolId = `${context.filePath}::${name}`;
5024
+ context.symbols.push({
5025
+ id: symbolId,
5026
+ name,
5027
+ kind: "module",
5028
+ filePath: context.filePath,
5029
+ startLine: node.startPosition.row + 1,
5030
+ endLine: node.endPosition.row + 1,
5031
+ exported: true
5032
+ });
5033
+ context.currentPackage = name;
5034
+ }
5035
+ function processImportHeader(node, context) {
5036
+ const ident = findDescendantByTypes2(node, ["identifier"]);
5037
+ if (!ident) return;
5038
+ let importPath = nodeText9(ident, context);
5039
+ const text = nodeText9(node, context).trim();
5040
+ const isWildcard = text.endsWith(".*");
5041
+ if (isWildcard && !importPath.endsWith(".*")) {
5042
+ importPath = importPath + ".*";
5043
+ }
5044
+ const aliasMatch = text.match(/\bas\s+(\w+)/);
5045
+ const alias = aliasMatch ? aliasMatch[1] : null;
5046
+ const resolvedPath = resolveKotlinImport(importPath, context.filePath, context.projectRoot);
5047
+ if (resolvedPath) {
5048
+ const sourceId = `${context.filePath}::__file__`;
5049
+ const targetId = `${resolvedPath}::__file__`;
5050
+ context.edges.push({
5051
+ source: sourceId,
5052
+ target: targetId,
5053
+ kind: "imports",
5054
+ filePath: context.filePath,
5055
+ line: node.startPosition.row + 1
5056
+ });
5057
+ const parts = importPath.replace(/\.\*$/, "").split(".");
5058
+ if (!isWildcard) {
5059
+ const simpleName = alias || parts[parts.length - 1];
5060
+ context.imports.set(simpleName, `${resolvedPath}::${parts[parts.length - 1]}`);
5061
+ }
5062
+ }
5063
+ const symbolId = `${context.filePath}::import:${importPath}`;
5064
+ context.symbols.push({
5065
+ id: symbolId,
5066
+ name: importPath,
5067
+ kind: "import",
5068
+ filePath: context.filePath,
5069
+ startLine: node.startPosition.row + 1,
5070
+ endLine: node.endPosition.row + 1,
5071
+ exported: false
5072
+ });
5073
+ }
5074
+ function processClassDeclaration5(node, context) {
5075
+ const nameNode = findChildByType10(node, "type_identifier");
5076
+ if (!nameNode) return;
5077
+ let name = nodeText9(nameNode, context);
5078
+ const angleBracketIdx = name.indexOf("<");
5079
+ if (angleBracketIdx > 0) {
5080
+ name = name.substring(0, angleBracketIdx);
5081
+ }
5082
+ const text = nodeText9(node, context);
5083
+ const modifiers = getModifiers(node, context);
5084
+ let kind = "class";
5085
+ if (text.match(/\binterface\b/) && !text.match(/\bfun\s+interface\b/)) {
5086
+ kind = "interface";
5087
+ } else if (text.match(/\benum\s+class\b/)) {
5088
+ kind = "enum";
5089
+ } else if (text.match(/\bannotation\s+class\b/)) {
5090
+ kind = "interface";
5091
+ }
5092
+ const exported = modifiers.includes("public") || modifiers.includes("internal") || !modifiers.includes("private") && !modifiers.includes("protected");
5093
+ const scope = context.currentClass || void 0;
5094
+ const symbolId = `${context.filePath}::${name}`;
5095
+ context.symbols.push({
5096
+ id: symbolId,
5097
+ name,
5098
+ kind,
5099
+ filePath: context.filePath,
5100
+ startLine: node.startPosition.row + 1,
5101
+ endLine: node.endPosition.row + 1,
5102
+ exported,
5103
+ scope
5104
+ });
5105
+ const delegationSpecifiers = findChildByType10(node, "delegation_specifiers");
5106
+ if (delegationSpecifiers) {
5107
+ processDelegationSpecifiers(delegationSpecifiers, symbolId, context);
5108
+ }
5109
+ if (kind === "enum") {
5110
+ processEnumEntries(node, name, context);
5111
+ }
5112
+ const oldClass = context.currentClass;
5113
+ context.currentClass = name;
5114
+ context.currentScope.push(name);
5115
+ const body = findChildByType10(node, "class_body") || findChildByType10(node, "enum_class_body");
5116
+ if (body) {
5117
+ walkNode10(body, context);
5118
+ }
5119
+ context.currentScope.pop();
5120
+ context.currentClass = oldClass;
5121
+ }
5122
+ function processObjectDeclaration(node, context) {
5123
+ const nameNode = findChildByType10(node, "type_identifier");
5124
+ if (!nameNode) return;
5125
+ const name = nodeText9(nameNode, context);
5126
+ const modifiers = getModifiers(node, context);
5127
+ const exported = !modifiers.includes("private");
5128
+ const scope = context.currentClass || void 0;
5129
+ const symbolId = `${context.filePath}::${name}`;
5130
+ context.symbols.push({
5131
+ id: symbolId,
5132
+ name,
5133
+ kind: "class",
5134
+ // Objects are singletons, map to class
5135
+ filePath: context.filePath,
5136
+ startLine: node.startPosition.row + 1,
5137
+ endLine: node.endPosition.row + 1,
5138
+ exported,
5139
+ scope
5140
+ });
5141
+ const delegationSpecifiers = findChildByType10(node, "delegation_specifiers");
5142
+ if (delegationSpecifiers) {
5143
+ processDelegationSpecifiers(delegationSpecifiers, symbolId, context);
5144
+ }
5145
+ const oldClass = context.currentClass;
5146
+ context.currentClass = name;
5147
+ context.currentScope.push(name);
5148
+ const body = findChildByType10(node, "class_body");
5149
+ if (body) {
5150
+ walkNode10(body, context);
5151
+ }
5152
+ context.currentScope.pop();
5153
+ context.currentClass = oldClass;
5154
+ }
5155
+ function processCompanionObject(node, context) {
5156
+ const nameNode = findChildByType10(node, "type_identifier");
5157
+ const name = nameNode ? nodeText9(nameNode, context) : "Companion";
5158
+ const scope = context.currentClass || void 0;
5159
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5160
+ context.symbols.push({
5161
+ id: symbolId,
5162
+ name,
5163
+ kind: "class",
5164
+ filePath: context.filePath,
5165
+ startLine: node.startPosition.row + 1,
5166
+ endLine: node.endPosition.row + 1,
5167
+ exported: true,
5168
+ scope
5169
+ });
5170
+ const oldClass = context.currentClass;
5171
+ context.currentClass = scope ? `${scope}.${name}` : name;
5172
+ context.currentScope.push(context.currentClass);
5173
+ const body = findChildByType10(node, "class_body");
5174
+ if (body) {
5175
+ walkNode10(body, context);
5176
+ }
5177
+ context.currentScope.pop();
5178
+ context.currentClass = oldClass;
5179
+ }
5180
+ function processDelegationSpecifiers(node, sourceId, context) {
5181
+ for (let i = 0; i < node.childCount; i++) {
5182
+ const child = node.child(i);
5183
+ if (!child) continue;
5184
+ const typeName = extractTypeName5(child, context);
5185
+ if (typeName) {
5186
+ const baseId = resolveSymbol9(typeName, context);
5187
+ if (baseId) {
5188
+ const edgeKind = typeName.startsWith("I") && typeName.length > 1 && typeName[1] === typeName[1].toUpperCase() ? "implements" : "inherits";
5189
+ context.edges.push({
5190
+ source: sourceId,
5191
+ target: baseId,
5192
+ kind: edgeKind,
5193
+ filePath: context.filePath,
5194
+ line: child.startPosition.row + 1
5195
+ });
5196
+ }
5197
+ }
5198
+ }
5199
+ }
5200
+ function processEnumEntries(node, enumName, context) {
5201
+ const body = findChildByType10(node, "enum_class_body");
5202
+ if (!body) return;
5203
+ const entries = findChildrenByType4(body, "enum_entry");
5204
+ for (const entry of entries) {
5205
+ const nameNode = findChildByType10(entry, "simple_identifier");
5206
+ if (!nameNode) continue;
5207
+ const constName = nodeText9(nameNode, context);
5208
+ const constId = `${context.filePath}::${enumName}.${constName}`;
5209
+ context.symbols.push({
5210
+ id: constId,
5211
+ name: constName,
5212
+ kind: "constant",
5213
+ filePath: context.filePath,
5214
+ startLine: entry.startPosition.row + 1,
5215
+ endLine: entry.endPosition.row + 1,
5216
+ exported: true,
5217
+ scope: enumName
5218
+ });
5219
+ }
5220
+ }
5221
+ function processFunctionDeclaration4(node, context) {
5222
+ const nameNode = findChildByType10(node, "simple_identifier");
5223
+ if (!nameNode) return;
5224
+ const name = nodeText9(nameNode, context);
5225
+ const modifiers = getModifiers(node, context);
5226
+ const exported = !modifiers.includes("private");
5227
+ const scope = context.currentClass || void 0;
5228
+ const text = nodeText9(node, context);
5229
+ const isExtension = text.match(/fun\s+[\w.<>, ]+\./) !== null;
5230
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5231
+ context.symbols.push({
5232
+ id: symbolId,
5233
+ name,
5234
+ kind: context.currentClass ? "method" : "function",
5235
+ filePath: context.filePath,
5236
+ startLine: node.startPosition.row + 1,
5237
+ endLine: node.endPosition.row + 1,
5238
+ exported,
5239
+ scope
5240
+ });
5241
+ const scopeName = scope ? `${scope}.${name}` : name;
5242
+ context.currentScope.push(scopeName);
5243
+ const body = findChildByType10(node, "function_body");
5244
+ if (body) {
5245
+ walkNode10(body, context);
5246
+ }
5247
+ context.currentScope.pop();
5248
+ }
5249
+ function processPropertyDeclaration2(node, context) {
5250
+ const varDecl = findChildByType10(node, "variable_declaration");
5251
+ if (!varDecl) return;
5252
+ const nameNode = findChildByType10(varDecl, "simple_identifier");
5253
+ if (!nameNode) return;
5254
+ const name = nodeText9(nameNode, context);
5255
+ const modifiers = getModifiers(node, context);
5256
+ const exported = !modifiers.includes("private");
5257
+ const scope = context.currentClass || void 0;
5258
+ const text = nodeText9(node, context);
5259
+ const isConst = modifiers.includes("const") || text.match(/\bval\b/) !== null && text.match(/\bconst\b/) !== null;
5260
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5261
+ context.symbols.push({
5262
+ id: symbolId,
5263
+ name,
5264
+ kind: isConst ? "constant" : "property",
5265
+ filePath: context.filePath,
5266
+ startLine: node.startPosition.row + 1,
5267
+ endLine: node.endPosition.row + 1,
5268
+ exported,
5269
+ scope
5270
+ });
5271
+ }
5272
+ function processSecondaryConstructor(node, context) {
5273
+ const scope = context.currentClass || void 0;
5274
+ if (!scope) return;
5275
+ const name = "constructor";
5276
+ const symbolId = `${context.filePath}::${scope}.${name}:${node.startPosition.row + 1}`;
5277
+ context.symbols.push({
5278
+ id: symbolId,
5279
+ name,
5280
+ kind: "method",
5281
+ filePath: context.filePath,
5282
+ startLine: node.startPosition.row + 1,
5283
+ endLine: node.endPosition.row + 1,
5284
+ exported: true,
5285
+ scope
5286
+ });
5287
+ const scopeName = `${scope}.${name}`;
5288
+ context.currentScope.push(scopeName);
5289
+ const body = findChildByType10(node, "function_body");
5290
+ if (body) {
5291
+ walkNode10(body, context);
5292
+ }
5293
+ context.currentScope.pop();
5294
+ }
5295
+ function processTypeAlias(node, context) {
5296
+ const nameNode = findChildByType10(node, "type_identifier");
5297
+ if (!nameNode) return;
5298
+ const name = nodeText9(nameNode, context);
5299
+ const symbolId = `${context.filePath}::${name}`;
5300
+ context.symbols.push({
5301
+ id: symbolId,
5302
+ name,
5303
+ kind: "type_alias",
5304
+ filePath: context.filePath,
5305
+ startLine: node.startPosition.row + 1,
5306
+ endLine: node.endPosition.row + 1,
5307
+ exported: true
5308
+ });
5309
+ }
5310
+ function processCallExpression10(node, context) {
5311
+ if (context.currentScope.length === 0) return;
5312
+ const firstChild = node.child(0);
5313
+ if (!firstChild) return;
5314
+ let calleeName = null;
5315
+ if (firstChild.type === "simple_identifier") {
5316
+ calleeName = nodeText9(firstChild, context);
5317
+ } else if (firstChild.type === "navigation_expression") {
5318
+ const suffix = findChildByType10(firstChild, "navigation_suffix");
5319
+ if (suffix) {
5320
+ const ident = findChildByType10(suffix, "simple_identifier");
5321
+ if (ident) calleeName = nodeText9(ident, context);
5322
+ }
5323
+ if (!calleeName) {
5324
+ for (let i = firstChild.childCount - 1; i >= 0; i--) {
5325
+ const child = firstChild.child(i);
5326
+ if (child && child.type === "simple_identifier") {
5327
+ calleeName = nodeText9(child, context);
5328
+ break;
5329
+ }
5330
+ }
5331
+ }
5332
+ }
5333
+ if (!calleeName) return;
5334
+ const builtins = /* @__PURE__ */ new Set([
5335
+ "println",
5336
+ "print",
5337
+ "toString",
5338
+ "equals",
5339
+ "hashCode",
5340
+ "let",
5341
+ "apply",
5342
+ "also",
5343
+ "run",
5344
+ "with",
5345
+ "takeIf",
5346
+ "takeUnless",
5347
+ "repeat",
5348
+ "require",
5349
+ "check",
5350
+ "error",
5351
+ "TODO",
5352
+ "listOf",
5353
+ "mapOf",
5354
+ "setOf",
5355
+ "arrayOf",
5356
+ "mutableListOf",
5357
+ "mutableMapOf",
5358
+ "mutableSetOf",
5359
+ "emptyList",
5360
+ "emptyMap",
5361
+ "emptySet",
5362
+ "to",
5363
+ "Pair",
5364
+ "Triple",
5365
+ "lazy",
5366
+ "synchronized",
5367
+ "map",
5368
+ "filter",
5369
+ "forEach",
5370
+ "flatMap",
5371
+ "fold",
5372
+ "reduce",
5373
+ "any",
5374
+ "all",
5375
+ "none",
5376
+ "find",
5377
+ "first",
5378
+ "last",
5379
+ "count",
5380
+ "sum",
5381
+ "average",
5382
+ "sortedBy",
5383
+ "groupBy",
5384
+ "associate",
5385
+ "zip",
5386
+ "joinToString",
5387
+ "getOrDefault",
5388
+ "getOrElse",
5389
+ "getOrPut",
5390
+ "contains",
5391
+ "containsKey",
5392
+ "add",
5393
+ "remove",
5394
+ "clear",
5395
+ "size",
5396
+ "isEmpty",
5397
+ "isNotEmpty"
5398
+ ]);
5399
+ if (builtins.has(calleeName)) return;
5400
+ const callerId = getCurrentSymbolId10(context);
5401
+ if (!callerId) return;
5402
+ const calleeId = resolveSymbol9(calleeName, context);
5403
+ if (calleeId) {
5404
+ context.edges.push({
5405
+ source: callerId,
5406
+ target: calleeId,
5407
+ kind: "calls",
5408
+ filePath: context.filePath,
5409
+ line: node.startPosition.row + 1
5410
+ });
5411
+ }
5412
+ }
5413
+ function processNavigationExpression(node, context) {
5414
+ }
5415
+ function parseGradleBuild2(filePath, sourceCode, projectRoot) {
5416
+ const symbols = [];
5417
+ const edges = [];
5418
+ const lines = sourceCode.split("\n");
5419
+ const projectName = basename4(dirname10(join11(projectRoot, filePath)));
5420
+ symbols.push({
5421
+ id: `${filePath}::${projectName}`,
5422
+ name: projectName,
5423
+ kind: "module",
5424
+ filePath,
5425
+ startLine: 1,
5426
+ endLine: lines.length,
5427
+ exported: true
5428
+ });
5429
+ for (let i = 0; i < lines.length; i++) {
5430
+ const line = lines[i].trim();
5431
+ const lineNum = i + 1;
5432
+ const depMatch = line.match(
5433
+ /(?:implementation|api|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|kapt|ksp|annotationProcessor)\s*[\(]?\s*['"]([^'"]+)['"]\s*[\)]?/
5434
+ );
5435
+ if (depMatch) {
5436
+ const depCoord = depMatch[1];
5437
+ if (!depCoord.startsWith(":")) {
5438
+ symbols.push({
5439
+ id: `${filePath}::dep:${depCoord}`,
5440
+ name: depCoord,
5441
+ kind: "import",
5442
+ filePath,
5443
+ startLine: lineNum,
5444
+ endLine: lineNum,
5445
+ exported: false
5446
+ });
5447
+ }
5448
+ }
5449
+ const projectMatch = line.match(/project\s*\(\s*['":]+([^'")\s]+)['"]*\s*\)/);
5450
+ if (projectMatch) {
5451
+ const moduleName = projectMatch[1].replace(/^:/, "");
5452
+ const candidates = [
5453
+ join11(moduleName, "build.gradle.kts"),
5454
+ join11(moduleName, "build.gradle")
5455
+ ];
5456
+ for (const candidate of candidates) {
5457
+ if (existsSync11(join11(projectRoot, candidate))) {
5458
+ edges.push({
5459
+ source: `${filePath}::__file__`,
5460
+ target: `${candidate}::__file__`,
5461
+ kind: "imports",
5462
+ filePath,
5463
+ line: lineNum
5464
+ });
5465
+ break;
5466
+ }
5467
+ }
5468
+ }
5469
+ }
5470
+ return { filePath, symbols, edges };
5471
+ }
5472
+ function parseSettingsGradle(filePath, sourceCode, projectRoot) {
5473
+ const symbols = [];
5474
+ const edges = [];
5475
+ const lines = sourceCode.split("\n");
5476
+ symbols.push({
5477
+ id: `${filePath}::settings`,
5478
+ name: "settings",
5479
+ kind: "module",
5480
+ filePath,
5481
+ startLine: 1,
5482
+ endLine: lines.length,
5483
+ exported: true
5484
+ });
5485
+ for (let i = 0; i < lines.length; i++) {
5486
+ const line = lines[i].trim();
5487
+ const lineNum = i + 1;
5488
+ const includeMatches = line.matchAll(/['"]:([^'"]+)['"]/g);
5489
+ for (const match of includeMatches) {
5490
+ const moduleName = match[1];
5491
+ const candidates = [
5492
+ join11(moduleName, "build.gradle.kts"),
5493
+ join11(moduleName, "build.gradle")
5494
+ ];
5495
+ for (const candidate of candidates) {
5496
+ if (existsSync11(join11(projectRoot, candidate))) {
5497
+ edges.push({
5498
+ source: `${filePath}::__file__`,
5499
+ target: `${candidate}::__file__`,
5500
+ kind: "imports",
5501
+ filePath,
5502
+ line: lineNum
5503
+ });
5504
+ break;
5505
+ }
5506
+ }
5507
+ }
5508
+ }
5509
+ return { filePath, symbols, edges };
5510
+ }
5511
+ function resolveKotlinImport(importPath, currentFile, projectRoot) {
5512
+ const cleanPath2 = importPath.replace(/\.\*$/, "");
5513
+ const parts = cleanPath2.split(".");
5514
+ const className = parts[parts.length - 1];
5515
+ const packagePath = parts.slice(0, -1).join("/");
5516
+ const sourceRoots = [
5517
+ "",
5518
+ "src/main/kotlin",
5519
+ "src/main/java",
5520
+ // Kotlin can live in java source dirs
5521
+ "src",
5522
+ "app/src/main/kotlin",
5523
+ "app/src/main/java"
5524
+ ];
5525
+ for (const root of sourceRoots) {
5526
+ for (const ext of [".kt", ".java"]) {
5527
+ const filePath = packagePath ? join11(packagePath, className + ext) : className + ext;
5528
+ const candidate = root ? join11(root, filePath) : filePath;
5529
+ const fullPath = join11(projectRoot, candidate);
5530
+ if (existsSync11(fullPath)) {
5531
+ return candidate;
5532
+ }
5533
+ }
5534
+ }
5535
+ if (importPath.endsWith(".*")) {
5536
+ const dirPath = cleanPath2.replace(/\./g, "/");
5537
+ for (const root of sourceRoots) {
5538
+ const candidate = root ? join11(root, dirPath) : dirPath;
5539
+ const fullPath = join11(projectRoot, candidate);
5540
+ if (existsSync11(fullPath)) {
5541
+ try {
5542
+ const stats = statSync5(fullPath);
5543
+ if (stats.isDirectory()) {
5544
+ const ktFiles = readdirSync8(fullPath).filter((f) => f.endsWith(".kt"));
5545
+ if (ktFiles.length > 0) {
5546
+ return join11(candidate, ktFiles[0]);
5547
+ }
5548
+ }
5549
+ } catch {
5550
+ }
5551
+ }
5552
+ }
5553
+ }
5554
+ return null;
5555
+ }
5556
+ function resolveSymbol9(name, context) {
5557
+ if (context.imports.has(name)) {
5558
+ return context.imports.get(name) || null;
5559
+ }
5560
+ const currentFileId = `${context.filePath}::${name}`;
5561
+ if (context.symbols.find((s) => s.id === currentFileId)) {
5562
+ return currentFileId;
5563
+ }
5564
+ if (context.currentClass) {
5565
+ const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
5566
+ if (context.symbols.find((s) => s.id === classMethodId)) {
5567
+ return classMethodId;
5568
+ }
5569
+ }
5570
+ return null;
5571
+ }
5572
+ function getModifiers(node, context) {
5573
+ const modifiers = [];
5574
+ const modList = findChildByType10(node, "modifiers");
5575
+ if (modList) {
5576
+ for (let i = 0; i < modList.childCount; i++) {
5577
+ const child = modList.child(i);
5578
+ if (child) {
5579
+ const text = nodeText9(child, context).trim();
5580
+ if (text) modifiers.push(text);
5581
+ }
5582
+ }
5583
+ }
5584
+ return modifiers;
5585
+ }
5586
+ function extractTypeName5(node, context) {
5587
+ const text = nodeText9(node, context).trim();
5588
+ if (!text || text === "," || text === ":") return null;
5589
+ let name = text;
5590
+ const angleBracketIdx = name.indexOf("<");
5591
+ if (angleBracketIdx > 0) name = name.substring(0, angleBracketIdx);
5592
+ const parenIdx = name.indexOf("(");
5593
+ if (parenIdx > 0) name = name.substring(0, parenIdx);
5594
+ const dotIdx = name.lastIndexOf(".");
5595
+ name = dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
5596
+ return name.trim() || null;
5597
+ }
5598
+ function findChildByType10(node, type) {
5599
+ for (let i = 0; i < node.childCount; i++) {
5600
+ const child = node.child(i);
5601
+ if (child && child.type === type) return child;
5602
+ }
5603
+ return null;
5604
+ }
5605
+ function findChildrenByType4(node, type) {
5606
+ const results = [];
5607
+ for (let i = 0; i < node.childCount; i++) {
5608
+ const child = node.child(i);
5609
+ if (child && child.type === type) results.push(child);
5610
+ }
5611
+ return results;
5612
+ }
5613
+ function findDescendantByTypes2(node, types) {
5614
+ for (let i = 0; i < node.childCount; i++) {
5615
+ const child = node.child(i);
5616
+ if (!child) continue;
5617
+ if (types.includes(child.type)) return child;
5618
+ const found = findDescendantByTypes2(child, types);
5619
+ if (found) return found;
5620
+ }
5621
+ return null;
5622
+ }
5623
+ function nodeText9(node, context) {
5624
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
5625
+ }
5626
+ function getCurrentSymbolId10(context) {
5627
+ if (context.currentScope.length === 0) return null;
5628
+ return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
5629
+ }
5630
+ var kotlinParser = {
5631
+ name: "kotlin",
5632
+ extensions: [".kt", ".kts", "build.gradle.kts", "settings.gradle.kts", "settings.gradle"],
5633
+ parseFile: parseKotlinFile
5634
+ };
5635
+
5636
+ // src/parser/php.ts
5637
+ import { dirname as dirname11, join as join12 } from "path";
5638
+ import { existsSync as existsSync12 } from "fs";
5639
+ function parsePhpFile(filePath, sourceCode, projectRoot) {
5640
+ const parser = getParser("php");
5641
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
5642
+ const context = {
5643
+ filePath,
5644
+ projectRoot,
5645
+ sourceCode,
5646
+ symbols: [],
5647
+ edges: [],
5648
+ currentScope: [],
5649
+ currentClass: null,
5650
+ currentNamespace: null,
5651
+ imports: /* @__PURE__ */ new Map()
5652
+ };
5653
+ walkNode11(tree.rootNode, context);
5654
+ return {
5655
+ filePath,
5656
+ symbols: context.symbols,
5657
+ edges: context.edges
5658
+ };
5659
+ }
5660
+ var SCOPE_TYPES = /* @__PURE__ */ new Set([
5661
+ "class_declaration",
5662
+ "interface_declaration",
5663
+ "trait_declaration",
5664
+ "enum_declaration",
5665
+ "function_definition",
5666
+ "method_declaration"
5667
+ ]);
5668
+ function walkNode11(node, context) {
5669
+ processNode11(node, context);
5670
+ if (SCOPE_TYPES.has(node.type)) return;
5671
+ for (let i = 0; i < node.childCount; i++) {
5672
+ const child = node.child(i);
5673
+ if (child) {
5674
+ walkNode11(child, context);
5675
+ }
5676
+ }
5677
+ }
5678
+ function processNode11(node, context) {
5679
+ switch (node.type) {
5680
+ case "namespace_definition":
5681
+ processNamespaceDefinition2(node, context);
5682
+ break;
5683
+ case "namespace_use_declaration":
5684
+ processUseDeclaration2(node, context);
5685
+ break;
5686
+ case "class_declaration":
5687
+ processClassDeclaration6(node, context);
5688
+ break;
5689
+ case "interface_declaration":
5690
+ processInterfaceDeclaration4(node, context);
5691
+ break;
5692
+ case "trait_declaration":
5693
+ processTraitDeclaration(node, context);
5694
+ break;
5695
+ case "enum_declaration":
5696
+ processEnumDeclaration4(node, context);
5697
+ break;
5698
+ case "function_definition":
5699
+ processFunctionDefinition4(node, context);
5700
+ break;
5701
+ case "method_declaration":
5702
+ processMethodDeclaration4(node, context);
5703
+ break;
5704
+ case "property_declaration":
5705
+ processPropertyDeclaration3(node, context);
5706
+ break;
5707
+ case "const_declaration":
5708
+ processConstDeclaration2(node, context);
5709
+ break;
5710
+ case "function_call_expression":
5711
+ processCallExpression11(node, context);
5712
+ break;
5713
+ case "member_call_expression":
5714
+ processMemberCallExpression(node, context);
5715
+ break;
5716
+ case "scoped_call_expression":
5717
+ processScopedCallExpression(node, context);
5718
+ break;
5719
+ case "include_expression":
5720
+ case "include_once_expression":
5721
+ case "require_expression":
5722
+ case "require_once_expression":
5723
+ processIncludeRequire(node, context);
5724
+ break;
5725
+ }
5726
+ }
5727
+ function processNamespaceDefinition2(node, context) {
5728
+ const nameNode = findChildByType11(node, "namespace_name");
5729
+ if (!nameNode) return;
5730
+ const name = nodeText10(nameNode, context);
5731
+ const symbolId = `${context.filePath}::${name}`;
5732
+ context.symbols.push({
5733
+ id: symbolId,
5734
+ name,
5735
+ kind: "module",
5736
+ filePath: context.filePath,
5737
+ startLine: node.startPosition.row + 1,
5738
+ endLine: node.endPosition.row + 1,
5739
+ exported: true
5740
+ });
5741
+ context.currentNamespace = name;
5742
+ }
5743
+ function processUseDeclaration2(node, context) {
5744
+ const clauses = findChildrenByType5(node, "namespace_use_clause");
5745
+ for (const clause of clauses) {
5746
+ const nameNode = findChildByType11(clause, "namespace_name") || findChildByType11(clause, "qualified_name");
5747
+ if (!nameNode) continue;
5748
+ const importPath = nodeText10(nameNode, context);
5749
+ const aliasNode = findChildByType11(clause, "namespace_aliasing_clause");
5750
+ const alias = aliasNode ? nodeText10(findChildByType11(aliasNode, "name") || aliasNode, context).trim() : null;
5751
+ const parts = importPath.split("\\");
5752
+ const simpleName = alias || parts[parts.length - 1];
5753
+ const resolvedPath = resolvePhpImport(importPath, context.filePath, context.projectRoot);
5754
+ if (resolvedPath) {
5755
+ const sourceId = `${context.filePath}::__file__`;
5756
+ const targetId = `${resolvedPath}::__file__`;
5757
+ context.edges.push({
5758
+ source: sourceId,
5759
+ target: targetId,
5760
+ kind: "imports",
5761
+ filePath: context.filePath,
5762
+ line: node.startPosition.row + 1
5763
+ });
5764
+ context.imports.set(simpleName, `${resolvedPath}::${parts[parts.length - 1]}`);
5765
+ }
5766
+ const symbolId = `${context.filePath}::import:${importPath}`;
5767
+ context.symbols.push({
5768
+ id: symbolId,
5769
+ name: importPath,
5770
+ kind: "import",
5771
+ filePath: context.filePath,
5772
+ startLine: node.startPosition.row + 1,
5773
+ endLine: node.endPosition.row + 1,
5774
+ exported: false
5775
+ });
5776
+ }
5777
+ }
5778
+ function processClassDeclaration6(node, context) {
5779
+ const nameNode = findChildByType11(node, "name");
5780
+ if (!nameNode) return;
5781
+ const name = nodeText10(nameNode, context);
5782
+ const symbolId = `${context.filePath}::${name}`;
5783
+ const text = nodeText10(node, context);
5784
+ const isAbstract = text.trimStart().startsWith("abstract");
5785
+ context.symbols.push({
5786
+ id: symbolId,
5787
+ name,
5788
+ kind: "class",
5789
+ filePath: context.filePath,
5790
+ startLine: node.startPosition.row + 1,
5791
+ endLine: node.endPosition.row + 1,
5792
+ exported: true,
5793
+ scope: context.currentClass || void 0
5794
+ });
5795
+ const baseClause = findChildByType11(node, "base_clause");
5796
+ if (baseClause) {
5797
+ const baseName = extractQualifiedName(baseClause, context);
5798
+ if (baseName) {
5799
+ const baseId = resolveSymbol10(baseName, context);
5800
+ if (baseId) {
5801
+ context.edges.push({
5802
+ source: symbolId,
5803
+ target: baseId,
5804
+ kind: "inherits",
5805
+ filePath: context.filePath,
5806
+ line: node.startPosition.row + 1
5807
+ });
5808
+ }
5809
+ }
5810
+ }
5811
+ const interfaceClause = findChildByType11(node, "class_interface_clause");
5812
+ if (interfaceClause) {
5813
+ const names = findChildrenByType5(interfaceClause, "name");
5814
+ const qualifiedNames = findChildrenByType5(interfaceClause, "qualified_name");
5815
+ for (const n of [...names, ...qualifiedNames]) {
5816
+ const ifaceName = nodeText10(n, context).trim();
5817
+ if (ifaceName && ifaceName !== ",") {
5818
+ const ifaceId = resolveSymbol10(ifaceName, context);
5819
+ if (ifaceId) {
5820
+ context.edges.push({
5821
+ source: symbolId,
5822
+ target: ifaceId,
5823
+ kind: "implements",
5824
+ filePath: context.filePath,
5825
+ line: node.startPosition.row + 1
5826
+ });
5827
+ }
5828
+ }
5829
+ }
5830
+ }
5831
+ const oldClass = context.currentClass;
5832
+ context.currentClass = name;
5833
+ context.currentScope.push(name);
5834
+ const body = findChildByType11(node, "declaration_list");
5835
+ if (body) {
5836
+ walkNode11(body, context);
5837
+ }
5838
+ context.currentScope.pop();
5839
+ context.currentClass = oldClass;
5840
+ }
5841
+ function processInterfaceDeclaration4(node, context) {
5842
+ const nameNode = findChildByType11(node, "name");
5843
+ if (!nameNode) return;
5844
+ const name = nodeText10(nameNode, context);
5845
+ const symbolId = `${context.filePath}::${name}`;
5846
+ context.symbols.push({
5847
+ id: symbolId,
5848
+ name,
5849
+ kind: "interface",
5850
+ filePath: context.filePath,
5851
+ startLine: node.startPosition.row + 1,
5852
+ endLine: node.endPosition.row + 1,
5853
+ exported: true,
5854
+ scope: context.currentClass || void 0
5855
+ });
5856
+ const oldClass = context.currentClass;
5857
+ context.currentClass = name;
5858
+ context.currentScope.push(name);
5859
+ const body = findChildByType11(node, "declaration_list");
5860
+ if (body) {
5861
+ walkNode11(body, context);
5862
+ }
5863
+ context.currentScope.pop();
5864
+ context.currentClass = oldClass;
5865
+ }
5866
+ function processTraitDeclaration(node, context) {
5867
+ const nameNode = findChildByType11(node, "name");
5868
+ if (!nameNode) return;
5869
+ const name = nodeText10(nameNode, context);
5870
+ const symbolId = `${context.filePath}::${name}`;
5871
+ context.symbols.push({
5872
+ id: symbolId,
5873
+ name,
5874
+ kind: "class",
5875
+ // Traits map to class kind
5876
+ filePath: context.filePath,
5877
+ startLine: node.startPosition.row + 1,
5878
+ endLine: node.endPosition.row + 1,
5879
+ exported: true,
5880
+ scope: context.currentClass || void 0
5881
+ });
5882
+ const oldClass = context.currentClass;
5883
+ context.currentClass = name;
5884
+ context.currentScope.push(name);
5885
+ const body = findChildByType11(node, "declaration_list");
5886
+ if (body) {
5887
+ walkNode11(body, context);
5888
+ }
5889
+ context.currentScope.pop();
5890
+ context.currentClass = oldClass;
5891
+ }
5892
+ function processEnumDeclaration4(node, context) {
5893
+ const nameNode = findChildByType11(node, "name");
5894
+ if (!nameNode) return;
5895
+ const name = nodeText10(nameNode, context);
5896
+ const symbolId = `${context.filePath}::${name}`;
5897
+ context.symbols.push({
5898
+ id: symbolId,
5899
+ name,
5900
+ kind: "enum",
5901
+ filePath: context.filePath,
5902
+ startLine: node.startPosition.row + 1,
5903
+ endLine: node.endPosition.row + 1,
5904
+ exported: true
5905
+ });
5906
+ const oldClass = context.currentClass;
5907
+ context.currentClass = name;
5908
+ context.currentScope.push(name);
5909
+ const body = findChildByType11(node, "declaration_list");
5910
+ if (body) {
5911
+ walkNode11(body, context);
5912
+ }
5913
+ context.currentScope.pop();
5914
+ context.currentClass = oldClass;
5915
+ }
5916
+ function processFunctionDefinition4(node, context) {
5917
+ const nameNode = findChildByType11(node, "name");
5918
+ if (!nameNode) return;
5919
+ const name = nodeText10(nameNode, context);
5920
+ const scope = context.currentClass || void 0;
5921
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5922
+ context.symbols.push({
5923
+ id: symbolId,
5924
+ name,
5925
+ kind: "function",
5926
+ filePath: context.filePath,
5927
+ startLine: node.startPosition.row + 1,
5928
+ endLine: node.endPosition.row + 1,
5929
+ exported: true,
5930
+ scope
5931
+ });
5932
+ const scopeName = scope ? `${scope}.${name}` : name;
5933
+ context.currentScope.push(scopeName);
5934
+ const body = findChildByType11(node, "compound_statement");
5935
+ if (body) {
5936
+ walkNode11(body, context);
5937
+ }
5938
+ context.currentScope.pop();
5939
+ }
5940
+ function processMethodDeclaration4(node, context) {
5941
+ const nameNode = findChildByType11(node, "name");
5942
+ if (!nameNode) return;
5943
+ const name = nodeText10(nameNode, context);
5944
+ const modifiers = getModifiers2(node, context);
5945
+ const exported = !modifiers.includes("private");
5946
+ const scope = context.currentClass || void 0;
5947
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5948
+ context.symbols.push({
5949
+ id: symbolId,
5950
+ name,
5951
+ kind: "method",
5952
+ filePath: context.filePath,
5953
+ startLine: node.startPosition.row + 1,
5954
+ endLine: node.endPosition.row + 1,
5955
+ exported,
5956
+ scope
5957
+ });
5958
+ const scopeName = scope ? `${scope}.${name}` : name;
5959
+ context.currentScope.push(scopeName);
5960
+ const body = findChildByType11(node, "compound_statement");
5961
+ if (body) {
5962
+ walkNode11(body, context);
5963
+ }
5964
+ context.currentScope.pop();
5965
+ }
5966
+ function processPropertyDeclaration3(node, context) {
5967
+ const varNode = findDescendantByTypes3(node, ["variable_name"]);
5968
+ if (!varNode) return;
5969
+ const name = nodeText10(varNode, context).replace(/^\$/, "");
5970
+ const modifiers = getModifiers2(node, context);
5971
+ const exported = !modifiers.includes("private");
5972
+ const scope = context.currentClass || void 0;
5973
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5974
+ context.symbols.push({
5975
+ id: symbolId,
5976
+ name,
5977
+ kind: "property",
5978
+ filePath: context.filePath,
5979
+ startLine: node.startPosition.row + 1,
5980
+ endLine: node.endPosition.row + 1,
5981
+ exported,
5982
+ scope
5983
+ });
5984
+ }
5985
+ function processConstDeclaration2(node, context) {
5986
+ const elements = findChildrenByType5(node, "const_element");
5987
+ for (const elem of elements) {
5988
+ const nameNode = findChildByType11(elem, "name");
5989
+ if (!nameNode) continue;
5990
+ const name = nodeText10(nameNode, context);
5991
+ const scope = context.currentClass || void 0;
5992
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5993
+ context.symbols.push({
5994
+ id: symbolId,
5995
+ name,
5996
+ kind: "constant",
5997
+ filePath: context.filePath,
5998
+ startLine: elem.startPosition.row + 1,
5999
+ endLine: elem.endPosition.row + 1,
6000
+ exported: true,
6001
+ scope
6002
+ });
6003
+ }
6004
+ }
6005
+ function processCallExpression11(node, context) {
6006
+ if (context.currentScope.length === 0) return;
6007
+ const firstChild = node.child(0);
6008
+ if (!firstChild) return;
6009
+ let calleeName = null;
6010
+ if (firstChild.type === "name") {
6011
+ calleeName = nodeText10(firstChild, context);
6012
+ } else if (firstChild.type === "qualified_name") {
6013
+ const parts = nodeText10(firstChild, context).split("\\");
6014
+ calleeName = parts[parts.length - 1];
6015
+ }
6016
+ if (!calleeName) return;
6017
+ const builtins = /* @__PURE__ */ new Set([
6018
+ "echo",
6019
+ "print",
6020
+ "var_dump",
6021
+ "print_r",
6022
+ "isset",
6023
+ "unset",
6024
+ "empty",
6025
+ "array",
6026
+ "list",
6027
+ "count",
6028
+ "strlen",
6029
+ "strpos",
6030
+ "substr",
6031
+ "explode",
6032
+ "implode",
6033
+ "array_map",
6034
+ "array_filter",
6035
+ "array_merge",
6036
+ "array_push",
6037
+ "array_pop",
6038
+ "array_shift",
6039
+ "array_unshift",
6040
+ "array_keys",
6041
+ "array_values",
6042
+ "in_array",
6043
+ "json_encode",
6044
+ "json_decode",
6045
+ "sprintf",
6046
+ "printf",
6047
+ "is_array",
6048
+ "is_string",
6049
+ "is_int",
6050
+ "is_null",
6051
+ "is_bool",
6052
+ "intval",
6053
+ "floatval",
6054
+ "strval",
6055
+ "boolval",
6056
+ "trim",
6057
+ "ltrim",
6058
+ "rtrim",
6059
+ "strtolower",
6060
+ "strtoupper",
6061
+ "str_replace",
6062
+ "preg_match",
6063
+ "preg_replace",
6064
+ "file_exists",
6065
+ "is_file",
6066
+ "is_dir",
6067
+ "dirname",
6068
+ "basename",
6069
+ "date",
6070
+ "time",
6071
+ "strtotime",
6072
+ "compact",
6073
+ "extract",
6074
+ "defined",
6075
+ "define",
6076
+ "class_exists",
6077
+ "function_exists",
6078
+ "throw",
6079
+ "die",
6080
+ "exit"
6081
+ ]);
6082
+ if (builtins.has(calleeName)) return;
6083
+ const callerId = getCurrentSymbolId11(context);
6084
+ if (!callerId) return;
6085
+ const calleeId = resolveSymbol10(calleeName, context);
6086
+ if (calleeId) {
6087
+ context.edges.push({
6088
+ source: callerId,
6089
+ target: calleeId,
6090
+ kind: "calls",
6091
+ filePath: context.filePath,
6092
+ line: node.startPosition.row + 1
6093
+ });
6094
+ }
6095
+ }
6096
+ function processMemberCallExpression(node, context) {
6097
+ }
6098
+ function processScopedCallExpression(node, context) {
6099
+ }
6100
+ function processIncludeRequire(node, context) {
6101
+ const text = nodeText10(node, context);
6102
+ const pathMatch = text.match(/['"]([^'"]+)['"]/);
6103
+ if (!pathMatch) return;
6104
+ const includePath = pathMatch[1];
6105
+ const resolvedPath = resolvePhpInclude(includePath, context.filePath, context.projectRoot);
6106
+ if (resolvedPath) {
6107
+ const sourceId = `${context.filePath}::__file__`;
6108
+ const targetId = `${resolvedPath}::__file__`;
6109
+ context.edges.push({
6110
+ source: sourceId,
6111
+ target: targetId,
6112
+ kind: "imports",
6113
+ filePath: context.filePath,
6114
+ line: node.startPosition.row + 1
6115
+ });
6116
+ }
6117
+ }
6118
+ function resolvePhpImport(importPath, currentFile, projectRoot) {
6119
+ const parts = importPath.split("\\");
6120
+ const filePath = parts.join("/") + ".php";
6121
+ const sourceRoots = [
6122
+ "",
6123
+ "src",
6124
+ "app",
6125
+ "lib",
6126
+ "includes",
6127
+ "wp-content/plugins",
6128
+ "wp-content/themes"
6129
+ ];
6130
+ for (const root of sourceRoots) {
6131
+ const candidate = root ? join12(root, filePath) : filePath;
6132
+ const fullPath = join12(projectRoot, candidate);
6133
+ if (existsSync12(fullPath)) {
6134
+ return candidate;
6135
+ }
6136
+ const loweredParts = [...parts];
6137
+ loweredParts[0] = loweredParts[0].toLowerCase();
6138
+ const loweredFilePath = loweredParts.join("/") + ".php";
6139
+ const loweredCandidate = root ? join12(root, loweredFilePath) : loweredFilePath;
6140
+ const loweredFullPath = join12(projectRoot, loweredCandidate);
6141
+ if (existsSync12(loweredFullPath)) {
6142
+ return loweredCandidate;
6143
+ }
6144
+ }
6145
+ return null;
6146
+ }
6147
+ function resolvePhpInclude(includePath, currentFile, projectRoot) {
6148
+ const currentDir = dirname11(join12(projectRoot, currentFile));
6149
+ const relativePath = join12(currentDir, includePath);
6150
+ const relativeToRoot = relativePath.replace(projectRoot + "/", "");
6151
+ if (existsSync12(relativePath)) {
6152
+ return relativeToRoot;
6153
+ }
6154
+ const fromRoot = join12(projectRoot, includePath);
6155
+ if (existsSync12(fromRoot)) {
6156
+ return includePath;
6157
+ }
6158
+ return null;
6159
+ }
6160
+ function resolveSymbol10(name, context) {
6161
+ if (context.imports.has(name)) {
6162
+ return context.imports.get(name) || null;
6163
+ }
6164
+ const currentFileId = `${context.filePath}::${name}`;
6165
+ if (context.symbols.find((s) => s.id === currentFileId)) {
6166
+ return currentFileId;
6167
+ }
6168
+ if (context.currentClass) {
6169
+ const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
6170
+ if (context.symbols.find((s) => s.id === classMethodId)) {
6171
+ return classMethodId;
6172
+ }
6173
+ }
6174
+ return null;
6175
+ }
6176
+ function getModifiers2(node, context) {
6177
+ const modifiers = [];
6178
+ for (let i = 0; i < node.childCount; i++) {
6179
+ const child = node.child(i);
6180
+ if (!child) continue;
6181
+ const type = child.type;
6182
+ if (type === "visibility_modifier" || type === "static_modifier" || type === "abstract_modifier" || type === "final_modifier" || type === "readonly_modifier") {
6183
+ modifiers.push(nodeText10(child, context).trim());
6184
+ }
6185
+ }
6186
+ return modifiers;
6187
+ }
6188
+ function extractQualifiedName(node, context) {
6189
+ const nameNode = findDescendantByTypes3(node, ["name", "qualified_name", "namespace_name"]);
6190
+ if (!nameNode) return null;
6191
+ const text = nodeText10(nameNode, context).trim();
6192
+ if (!text) return null;
6193
+ const parts = text.split("\\");
6194
+ return parts[parts.length - 1];
6195
+ }
6196
+ function findChildByType11(node, type) {
6197
+ for (let i = 0; i < node.childCount; i++) {
6198
+ const child = node.child(i);
6199
+ if (child && child.type === type) return child;
6200
+ }
6201
+ return null;
6202
+ }
6203
+ function findChildrenByType5(node, type) {
6204
+ const results = [];
6205
+ for (let i = 0; i < node.childCount; i++) {
6206
+ const child = node.child(i);
6207
+ if (child && child.type === type) results.push(child);
6208
+ }
6209
+ return results;
6210
+ }
6211
+ function findDescendantByTypes3(node, types) {
6212
+ for (let i = 0; i < node.childCount; i++) {
6213
+ const child = node.child(i);
6214
+ if (!child) continue;
6215
+ if (types.includes(child.type)) return child;
6216
+ const found = findDescendantByTypes3(child, types);
6217
+ if (found) return found;
6218
+ }
6219
+ return null;
6220
+ }
6221
+ function nodeText10(node, context) {
6222
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
6223
+ }
6224
+ function getCurrentSymbolId11(context) {
6225
+ if (context.currentScope.length === 0) return null;
6226
+ return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
6227
+ }
6228
+ var phpParser = {
6229
+ name: "php",
6230
+ extensions: [".php"],
6231
+ parseFile: parsePhpFile
6232
+ };
6233
+
4930
6234
  // src/parser/detect.ts
4931
6235
  var parsers = [
4932
6236
  typescriptParser,
@@ -4937,12 +6241,14 @@ var parsers = [
4937
6241
  cParser,
4938
6242
  csharpParser,
4939
6243
  javaParser,
4940
- cppParser
6244
+ cppParser,
6245
+ kotlinParser,
6246
+ phpParser
4941
6247
  ];
4942
6248
  var CPP_KEYWORDS = /\b(?:class|namespace|template|public:|private:|protected:|virtual|nullptr|constexpr|auto\s+\w+\s*=|using\s+\w+\s*=|static_cast|dynamic_cast|reinterpret_cast|const_cast|noexcept|override|final|decltype|concept|requires|co_await|co_yield|co_return|std::)\b/;
4943
6249
  function getParserForFile(filePath, content) {
4944
- const ext = extname6(filePath).toLowerCase();
4945
- const fileName = basename4(filePath);
6250
+ const ext = extname8(filePath).toLowerCase();
6251
+ const fileName = basename6(filePath);
4946
6252
  if (ext === ".h" && content) {
4947
6253
  if (CPP_KEYWORDS.test(content)) {
4948
6254
  return cppParser;
@@ -4960,7 +6266,7 @@ import { minimatch } from "minimatch";
4960
6266
  var MAX_FILE_SIZE = 1e6;
4961
6267
  function shouldParseFile(fullPath) {
4962
6268
  try {
4963
- const stats = statSync5(fullPath);
6269
+ const stats = statSync7(fullPath);
4964
6270
  if (stats.size > MAX_FILE_SIZE) {
4965
6271
  console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
4966
6272
  return false;
@@ -4978,8 +6284,8 @@ async function parseProject(projectRoot, options) {
4978
6284
  let errorFiles = 0;
4979
6285
  for (const file of files) {
4980
6286
  try {
4981
- const fullPath = join11(projectRoot, file);
4982
- if (!resolve5(fullPath).startsWith(resolve5(projectRoot))) {
6287
+ const fullPath = join13(projectRoot, file);
6288
+ if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) {
4983
6289
  skippedFiles++;
4984
6290
  continue;
4985
6291
  }
@@ -5002,7 +6308,7 @@ async function parseProject(projectRoot, options) {
5002
6308
  if (options?.verbose) {
5003
6309
  console.error(`[Parser] Parsing: ${file}`);
5004
6310
  }
5005
- const sourceCode = readFileSync8(fullPath, "utf-8");
6311
+ const sourceCode = readFileSync10(fullPath, "utf-8");
5006
6312
  const parser = getParserForFile(file, sourceCode);
5007
6313
  if (!parser) {
5008
6314
  console.error(`No parser found for file: ${file}`);
@@ -5031,8 +6337,8 @@ async function parseProject(projectRoot, options) {
5031
6337
  }
5032
6338
 
5033
6339
  // src/cross-language/detectors/rest-api.ts
5034
- import { readFileSync as readFileSync9 } from "fs";
5035
- import { join as join12, resolve as resolve6 } from "path";
6340
+ import { readFileSync as readFileSync11 } from "fs";
6341
+ import { join as join14, resolve as resolve8 } from "path";
5036
6342
  function getLanguage(filePath) {
5037
6343
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
5038
6344
  if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
@@ -5040,6 +6346,8 @@ function getLanguage(filePath) {
5040
6346
  if (filePath.endsWith(".go")) return "go";
5041
6347
  if (filePath.endsWith(".cs") || filePath.endsWith(".csx")) return "csharp";
5042
6348
  if (filePath.endsWith(".java")) return "java";
6349
+ if (filePath.endsWith(".kt") || filePath.endsWith(".kts")) return "kotlin";
6350
+ if (filePath.endsWith(".php")) return "php";
5043
6351
  if (filePath.endsWith(".cpp") || filePath.endsWith(".cc") || filePath.endsWith(".cxx") || filePath.endsWith(".c++") || filePath.endsWith(".hpp") || filePath.endsWith(".hh") || filePath.endsWith(".hxx") || filePath.endsWith(".h++") || filePath.endsWith(".h") || filePath.endsWith(".inl") || filePath.endsWith(".ipp")) return "cpp";
5044
6352
  return "unknown";
5045
6353
  }
@@ -5252,43 +6560,208 @@ function extractRouteDefinitions(source, filePath) {
5252
6560
  }
5253
6561
  }
5254
6562
  }
5255
- const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
5256
- if (requestMappingMatch) {
5257
- let path6 = requestMappingMatch[1];
6563
+ const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
6564
+ if (requestMappingMatch) {
6565
+ let path6 = requestMappingMatch[1];
6566
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6567
+ const methodMatch = line.match(/method\s*=\s*RequestMethod\.(\w+)/);
6568
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
6569
+ routes.push({
6570
+ method,
6571
+ path: path6,
6572
+ normalizedPath: normalizePath(path6),
6573
+ file: filePath,
6574
+ line: i + 1
6575
+ });
6576
+ }
6577
+ const jaxPathMatch = line.match(/@Path\s*\(\s*["']([^"']+)["']\s*\)/);
6578
+ if (jaxPathMatch) {
6579
+ let path6 = jaxPathMatch[1];
6580
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6581
+ const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
6582
+ const prevLine = i > 0 ? lines[i - 1] : "";
6583
+ const jaxMethodMatch = (nextLine + prevLine).match(/@(GET|POST|PUT|DELETE|PATCH)/);
6584
+ const method = jaxMethodMatch ? jaxMethodMatch[1] : "ANY";
6585
+ routes.push({
6586
+ method,
6587
+ path: path6,
6588
+ normalizedPath: normalizePath(path6),
6589
+ file: filePath,
6590
+ line: i + 1
6591
+ });
6592
+ }
6593
+ const webFluxMatch = line.match(/(?:route|andRoute)\s*\(\s*(GET|POST|PUT|DELETE|PATCH)\s*\(\s*["']([^"']+)["']\s*\)/);
6594
+ if (webFluxMatch) {
6595
+ const path6 = webFluxMatch[2].startsWith("/") ? webFluxMatch[2] : "/" + webFluxMatch[2];
6596
+ routes.push({
6597
+ method: webFluxMatch[1].toUpperCase(),
6598
+ path: path6,
6599
+ normalizedPath: normalizePath(path6),
6600
+ file: filePath,
6601
+ line: i + 1
6602
+ });
6603
+ }
6604
+ }
6605
+ if (lang === "kotlin") {
6606
+ const springMethodMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
6607
+ if (springMethodMatch) {
6608
+ const method = springMethodMatch[1].toUpperCase();
6609
+ let path6 = springMethodMatch[2];
6610
+ const classPrefix = findClassLevelPrefix(source);
6611
+ if (classPrefix) path6 = classPrefix + path6;
6612
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6613
+ routes.push({
6614
+ method,
6615
+ path: path6,
6616
+ normalizedPath: normalizePath(path6),
6617
+ file: filePath,
6618
+ line: i + 1
6619
+ });
6620
+ }
6621
+ if (!springMethodMatch) {
6622
+ const springNoPathMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*$/);
6623
+ if (springNoPathMatch) {
6624
+ const method = springNoPathMatch[1].toUpperCase();
6625
+ const classPrefix = findClassLevelPrefix(source);
6626
+ if (classPrefix) {
6627
+ routes.push({
6628
+ method,
6629
+ path: classPrefix,
6630
+ normalizedPath: normalizePath(classPrefix),
6631
+ file: filePath,
6632
+ line: i + 1
6633
+ });
6634
+ }
6635
+ }
6636
+ }
6637
+ const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*(?:\[)?\s*)?["']([^"']+)["']/);
6638
+ if (requestMappingMatch) {
6639
+ let path6 = requestMappingMatch[1];
6640
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6641
+ const methodMatch = line.match(/method\s*=\s*\[?\s*RequestMethod\.(\w+)/);
6642
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
6643
+ routes.push({
6644
+ method,
6645
+ path: path6,
6646
+ normalizedPath: normalizePath(path6),
6647
+ file: filePath,
6648
+ line: i + 1
6649
+ });
6650
+ }
6651
+ const ktorMatch = line.match(/\b(get|post|put|delete|patch|head|options)\s*\(\s*["']([^"']+)["']\s*\)/);
6652
+ if (ktorMatch) {
6653
+ const path6 = ktorMatch[2];
6654
+ if (path6.startsWith("/")) {
6655
+ routes.push({
6656
+ method: ktorMatch[1].toUpperCase(),
6657
+ path: path6,
6658
+ normalizedPath: normalizePath(path6),
6659
+ file: filePath,
6660
+ line: i + 1
6661
+ });
6662
+ }
6663
+ }
6664
+ const ktorRouteMatch = line.match(/\broute\s*\(\s*["']([^"']+)["']\s*\)/);
6665
+ if (ktorRouteMatch) {
6666
+ const path6 = ktorRouteMatch[1];
6667
+ if (path6.startsWith("/")) {
6668
+ routes.push({
6669
+ method: "ANY",
6670
+ path: path6,
6671
+ normalizedPath: normalizePath(path6),
6672
+ file: filePath,
6673
+ line: i + 1
6674
+ });
6675
+ }
6676
+ }
6677
+ const resourceMatch = line.match(/@Resource\s*\(\s*["']([^"']+)["']\s*\)/);
6678
+ if (resourceMatch) {
6679
+ const path6 = resourceMatch[1];
6680
+ if (path6.startsWith("/")) {
6681
+ routes.push({
6682
+ method: "ANY",
6683
+ path: path6,
6684
+ normalizedPath: normalizePath(path6),
6685
+ file: filePath,
6686
+ line: i + 1
6687
+ });
6688
+ }
6689
+ }
6690
+ const http4kMatch = line.match(/["']([^"']+)["']\s*bind\s*(GET|POST|PUT|DELETE|PATCH)/);
6691
+ if (http4kMatch) {
6692
+ const path6 = http4kMatch[1];
6693
+ if (path6.startsWith("/")) {
6694
+ routes.push({
6695
+ method: http4kMatch[2].toUpperCase(),
6696
+ path: path6,
6697
+ normalizedPath: normalizePath(path6),
6698
+ file: filePath,
6699
+ line: i + 1
6700
+ });
6701
+ }
6702
+ }
6703
+ const retrofitMatch = line.match(/@(GET|POST|PUT|DELETE|PATCH|HEAD)\s*\(\s*["']([^"']+)["']\s*\)/);
6704
+ if (retrofitMatch) {
6705
+ let path6 = retrofitMatch[2];
6706
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6707
+ }
6708
+ }
6709
+ if (lang === "php") {
6710
+ const laravelRouteMatch = line.match(/Route\s*::\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/i);
6711
+ if (laravelRouteMatch) {
6712
+ const path6 = laravelRouteMatch[2];
6713
+ if (path6.startsWith("/")) {
6714
+ routes.push({
6715
+ method: laravelRouteMatch[1].toUpperCase(),
6716
+ path: path6,
6717
+ normalizedPath: normalizePath(path6),
6718
+ file: filePath,
6719
+ line: i + 1
6720
+ });
6721
+ }
6722
+ }
6723
+ const symfonyRouteMatch = line.match(/#\[Route\s*\(\s*['"]([^'"]+)['"]/);
6724
+ if (symfonyRouteMatch) {
6725
+ const path6 = symfonyRouteMatch[1];
6726
+ if (path6.startsWith("/")) {
6727
+ const methodsMatch = line.match(/methods\s*:\s*\[([^\]]+)\]/);
6728
+ const methods = methodsMatch ? methodsMatch[1].match(/['"](\w+)['"]/g)?.map((m) => m.replace(/['"]/g, "").toUpperCase()) || ["ANY"] : ["ANY"];
6729
+ for (const method of methods) {
6730
+ routes.push({
6731
+ method,
6732
+ path: path6,
6733
+ normalizedPath: normalizePath(path6),
6734
+ file: filePath,
6735
+ line: i + 1
6736
+ });
6737
+ }
6738
+ }
6739
+ }
6740
+ const slimMatch = line.match(/\$(?:app|group)\s*->\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/i);
6741
+ if (slimMatch) {
6742
+ const path6 = slimMatch[2];
6743
+ if (path6.startsWith("/")) {
6744
+ routes.push({
6745
+ method: slimMatch[1].toUpperCase(),
6746
+ path: path6,
6747
+ normalizedPath: normalizePath(path6),
6748
+ file: filePath,
6749
+ line: i + 1
6750
+ });
6751
+ }
6752
+ }
6753
+ const wpRestMatch = line.match(/register_rest_route\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/);
6754
+ if (wpRestMatch) {
6755
+ const namespace = wpRestMatch[1];
6756
+ let path6 = wpRestMatch[2];
5258
6757
  if (!path6.startsWith("/")) path6 = "/" + path6;
5259
- const methodMatch = line.match(/method\s*=\s*RequestMethod\.(\w+)/);
6758
+ const fullPath = `/wp-json/${namespace}${path6}`;
6759
+ const methodMatch = line.match(/methods\s*['"=>\s]+['"](\w+)['"]/i);
5260
6760
  const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
5261
6761
  routes.push({
5262
6762
  method,
5263
- path: path6,
5264
- normalizedPath: normalizePath(path6),
5265
- file: filePath,
5266
- line: i + 1
5267
- });
5268
- }
5269
- const jaxPathMatch = line.match(/@Path\s*\(\s*["']([^"']+)["']\s*\)/);
5270
- if (jaxPathMatch) {
5271
- let path6 = jaxPathMatch[1];
5272
- if (!path6.startsWith("/")) path6 = "/" + path6;
5273
- const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
5274
- const prevLine = i > 0 ? lines[i - 1] : "";
5275
- const jaxMethodMatch = (nextLine + prevLine).match(/@(GET|POST|PUT|DELETE|PATCH)/);
5276
- const method = jaxMethodMatch ? jaxMethodMatch[1] : "ANY";
5277
- routes.push({
5278
- method,
5279
- path: path6,
5280
- normalizedPath: normalizePath(path6),
5281
- file: filePath,
5282
- line: i + 1
5283
- });
5284
- }
5285
- const webFluxMatch = line.match(/(?:route|andRoute)\s*\(\s*(GET|POST|PUT|DELETE|PATCH)\s*\(\s*["']([^"']+)["']\s*\)/);
5286
- if (webFluxMatch) {
5287
- const path6 = webFluxMatch[2].startsWith("/") ? webFluxMatch[2] : "/" + webFluxMatch[2];
5288
- routes.push({
5289
- method: webFluxMatch[1].toUpperCase(),
5290
- path: path6,
5291
- normalizedPath: normalizePath(path6),
6763
+ path: fullPath,
6764
+ normalizedPath: normalizePath(fullPath),
5292
6765
  file: filePath,
5293
6766
  line: i + 1
5294
6767
  });
@@ -5408,11 +6881,11 @@ function detectRestApiEdges(files, projectRoot) {
5408
6881
  const allCalls = [];
5409
6882
  const allRoutes = [];
5410
6883
  for (const file of files) {
5411
- const fullPath = join12(projectRoot, file.filePath);
5412
- if (!resolve6(fullPath).startsWith(resolve6(projectRoot))) continue;
6884
+ const fullPath = join14(projectRoot, file.filePath);
6885
+ if (!resolve8(fullPath).startsWith(resolve8(projectRoot))) continue;
5413
6886
  let source;
5414
6887
  try {
5415
- source = readFileSync9(fullPath, "utf-8");
6888
+ source = readFileSync11(fullPath, "utf-8");
5416
6889
  } catch {
5417
6890
  continue;
5418
6891
  }
@@ -5420,6 +6893,46 @@ function detectRestApiEdges(files, projectRoot) {
5420
6893
  if (lang === "typescript" || lang === "javascript") {
5421
6894
  allCalls.push(...extractHttpCalls(source, file.filePath));
5422
6895
  }
6896
+ if (lang === "kotlin") {
6897
+ const kotlinLines = source.split("\n");
6898
+ for (let i = 0; i < kotlinLines.length; i++) {
6899
+ const line = kotlinLines[i];
6900
+ const retrofitMatch = line.match(/@(GET|POST|PUT|DELETE|PATCH|HEAD)\s*\(\s*["']([^"']+)["']\s*\)/);
6901
+ if (retrofitMatch) {
6902
+ let path6 = retrofitMatch[2];
6903
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6904
+ allCalls.push({ method: retrofitMatch[1].toUpperCase(), path: path6, file: file.filePath, line: i + 1 });
6905
+ }
6906
+ }
6907
+ }
6908
+ if (lang === "php") {
6909
+ const phpLines = source.split("\n");
6910
+ for (let i = 0; i < phpLines.length; i++) {
6911
+ const line = phpLines[i];
6912
+ const guzzleMatch = line.match(/\$\w+\s*->\s*(get|post|put|delete|patch|request)\s*\(\s*['"]([^'"]+)['"]/i);
6913
+ if (guzzleMatch) {
6914
+ let method = guzzleMatch[1].toUpperCase();
6915
+ let path6 = guzzleMatch[2];
6916
+ if (method === "REQUEST") {
6917
+ const reqMethodMatch = line.match(/request\s*\(\s*['"](\w+)['"]\s*,\s*['"]([^'"]+)['"]/i);
6918
+ if (reqMethodMatch) {
6919
+ method = reqMethodMatch[1].toUpperCase();
6920
+ path6 = reqMethodMatch[2];
6921
+ }
6922
+ }
6923
+ if (isLocalApiPath(path6)) {
6924
+ allCalls.push({ method, path: cleanPath(path6), file: file.filePath, line: i + 1 });
6925
+ }
6926
+ }
6927
+ const fgcMatch = line.match(/file_get_contents\s*\(\s*['"]([^'"]+)['"]/);
6928
+ if (fgcMatch) {
6929
+ const path6 = fgcMatch[1];
6930
+ if (isLocalApiPath(path6)) {
6931
+ allCalls.push({ method: "GET", path: cleanPath(path6), file: file.filePath, line: i + 1 });
6932
+ }
6933
+ }
6934
+ }
6935
+ }
5423
6936
  allRoutes.push(...extractRouteDefinitions(source, file.filePath));
5424
6937
  }
5425
6938
  for (const call of allCalls) {
@@ -5448,8 +6961,8 @@ function detectRestApiEdges(files, projectRoot) {
5448
6961
  }
5449
6962
 
5450
6963
  // src/cross-language/detectors/subprocess.ts
5451
- import { readFileSync as readFileSync10 } from "fs";
5452
- import { join as join13, resolve as resolve7, basename as basename5 } from "path";
6964
+ import { readFileSync as readFileSync12 } from "fs";
6965
+ import { join as join15, resolve as resolve9, basename as basename7 } from "path";
5453
6966
  var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
5454
6967
  function getLanguage2(filePath) {
5455
6968
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
@@ -5548,16 +7061,16 @@ function detectSubprocessEdges(files, projectRoot) {
5548
7061
  const knownFiles = new Set(files.map((f) => f.filePath));
5549
7062
  const basenameMap = /* @__PURE__ */ new Map();
5550
7063
  for (const f of files) {
5551
- const base = basename5(f.filePath);
7064
+ const base = basename7(f.filePath);
5552
7065
  if (!basenameMap.has(base)) basenameMap.set(base, []);
5553
7066
  basenameMap.get(base).push(f.filePath);
5554
7067
  }
5555
7068
  for (const file of files) {
5556
- const fullPath = join13(projectRoot, file.filePath);
5557
- if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) continue;
7069
+ const fullPath = join15(projectRoot, file.filePath);
7070
+ if (!resolve9(fullPath).startsWith(resolve9(projectRoot))) continue;
5558
7071
  let source;
5559
7072
  try {
5560
- source = readFileSync10(fullPath, "utf-8");
7073
+ source = readFileSync12(fullPath, "utf-8");
5561
7074
  } catch {
5562
7075
  continue;
5563
7076
  }
@@ -5569,7 +7082,7 @@ function detectSubprocessEdges(files, projectRoot) {
5569
7082
  targetFile = call.calledFile;
5570
7083
  confidence = "high";
5571
7084
  } else {
5572
- const base = basename5(call.calledFile);
7085
+ const base = basename7(call.calledFile);
5573
7086
  const candidates = basenameMap.get(base);
5574
7087
  if (candidates && candidates.length > 0) {
5575
7088
  const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
@@ -5948,7 +7461,7 @@ function getArchitectureSummary(graph) {
5948
7461
  }
5949
7462
 
5950
7463
  // src/health/metrics.ts
5951
- import { dirname as dirname10 } from "path";
7464
+ import { dirname as dirname12 } from "path";
5952
7465
  function scoreToGrade(score) {
5953
7466
  if (score >= 90) return "A";
5954
7467
  if (score >= 80) return "B";
@@ -5981,8 +7494,8 @@ function calculateCouplingScore(graph) {
5981
7494
  totalEdges++;
5982
7495
  fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
5983
7496
  fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
5984
- const sourceDir = dirname10(sourceAttrs.filePath).split("/")[0];
5985
- const targetDir = dirname10(targetAttrs.filePath).split("/")[0];
7497
+ const sourceDir = dirname12(sourceAttrs.filePath).split("/")[0];
7498
+ const targetDir = dirname12(targetAttrs.filePath).split("/")[0];
5986
7499
  if (sourceDir !== targetDir) {
5987
7500
  crossDirEdges++;
5988
7501
  }
@@ -6029,8 +7542,8 @@ function calculateCohesionScore(graph) {
6029
7542
  const sourceAttrs = graph.getNodeAttributes(source);
6030
7543
  const targetAttrs = graph.getNodeAttributes(target);
6031
7544
  if (sourceAttrs.filePath !== targetAttrs.filePath) {
6032
- const sourceDir = dirname10(sourceAttrs.filePath);
6033
- const targetDir = dirname10(targetAttrs.filePath);
7545
+ const sourceDir = dirname12(sourceAttrs.filePath);
7546
+ const targetDir = dirname12(targetAttrs.filePath);
6034
7547
  if (!dirEdges.has(sourceDir)) {
6035
7548
  dirEdges.set(sourceDir, { internal: 0, total: 0 });
6036
7549
  }
@@ -6312,8 +7825,8 @@ function calculateDepthScore(graph) {
6312
7825
  }
6313
7826
 
6314
7827
  // src/health/index.ts
6315
- import { readFileSync as readFileSync11, writeFileSync, existsSync as existsSync11, mkdirSync } from "fs";
6316
- import { dirname as dirname11, resolve as resolve8 } from "path";
7828
+ import { readFileSync as readFileSync13, writeFileSync, existsSync as existsSync13, mkdirSync } from "fs";
7829
+ import { dirname as dirname13, resolve as resolve10 } from "path";
6317
7830
  function calculateHealthScore(graph, projectRoot) {
6318
7831
  const coupling = calculateCouplingScore(graph);
6319
7832
  const cohesion = calculateCohesionScore(graph);
@@ -6412,8 +7925,8 @@ function getHealthTrend(projectRoot, currentScore) {
6412
7925
  }
6413
7926
  }
6414
7927
  function saveHealthHistory(projectRoot, report) {
6415
- const resolvedRoot = resolve8(projectRoot);
6416
- const historyFile = resolve8(resolvedRoot, ".depwire", "health-history.json");
7928
+ const resolvedRoot = resolve10(projectRoot);
7929
+ const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
6417
7930
  if (!historyFile.startsWith(resolvedRoot)) {
6418
7931
  return;
6419
7932
  }
@@ -6428,10 +7941,10 @@ function saveHealthHistory(projectRoot, report) {
6428
7941
  }))
6429
7942
  };
6430
7943
  let history = [];
6431
- if (existsSync11(historyFile)) {
7944
+ if (existsSync13(historyFile)) {
6432
7945
  try {
6433
7946
  if (!historyFile.startsWith(resolvedRoot)) return;
6434
- const content = readFileSync11(historyFile, "utf-8");
7947
+ const content = readFileSync13(historyFile, "utf-8");
6435
7948
  history = JSON.parse(content);
6436
7949
  } catch {
6437
7950
  }
@@ -6440,19 +7953,19 @@ function saveHealthHistory(projectRoot, report) {
6440
7953
  if (history.length > 50) {
6441
7954
  history = history.slice(-50);
6442
7955
  }
6443
- mkdirSync(dirname11(historyFile), { recursive: true });
7956
+ mkdirSync(dirname13(historyFile), { recursive: true });
6444
7957
  if (!historyFile.startsWith(resolvedRoot)) return;
6445
7958
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
6446
7959
  }
6447
7960
  function loadHealthHistory(projectRoot) {
6448
- const resolvedRoot = resolve8(projectRoot);
6449
- const historyFile = resolve8(resolvedRoot, ".depwire", "health-history.json");
6450
- if (!historyFile.startsWith(resolvedRoot) || !existsSync11(historyFile)) {
7961
+ const resolvedRoot = resolve10(projectRoot);
7962
+ const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
7963
+ if (!historyFile.startsWith(resolvedRoot) || !existsSync13(historyFile)) {
6451
7964
  return [];
6452
7965
  }
6453
7966
  try {
6454
7967
  if (!historyFile.startsWith(resolvedRoot)) return [];
6455
- const content = readFileSync11(historyFile, "utf-8");
7968
+ const content = readFileSync13(historyFile, "utf-8");
6456
7969
  return JSON.parse(content);
6457
7970
  } catch {
6458
7971
  return [];
@@ -6461,7 +7974,7 @@ function loadHealthHistory(projectRoot) {
6461
7974
 
6462
7975
  // src/dead-code/detector.ts
6463
7976
  import path2 from "path";
6464
- import { readFileSync as readFileSync12, existsSync as existsSync12 } from "fs";
7977
+ import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
6465
7978
  function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
6466
7979
  const deadSymbols = [];
6467
7980
  const context = { graph, projectRoot };
@@ -6594,11 +8107,11 @@ function getPackageEntryPoints(projectRoot) {
6594
8107
  const entryPoints = /* @__PURE__ */ new Set();
6595
8108
  const resolvedRoot = path2.resolve(projectRoot);
6596
8109
  const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
6597
- if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync12(packageJsonPath)) {
8110
+ if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync14(packageJsonPath)) {
6598
8111
  return entryPoints;
6599
8112
  }
6600
8113
  try {
6601
- const packageJson = JSON.parse(readFileSync12(packageJsonPath, "utf-8"));
8114
+ const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
6602
8115
  if (packageJson.main) {
6603
8116
  entryPoints.add(path2.resolve(projectRoot, packageJson.main));
6604
8117
  }
@@ -6652,6 +8165,12 @@ function shouldExclude(attrs, context, includeTests, packageEntryPoints) {
6652
8165
  if (isCppExcluded(attrs)) {
6653
8166
  return "framework";
6654
8167
  }
8168
+ if (isKotlinExcluded(attrs)) {
8169
+ return "framework";
8170
+ }
8171
+ if (isPhpExcluded(attrs)) {
8172
+ return "framework";
8173
+ }
6655
8174
  return null;
6656
8175
  }
6657
8176
  function isRealPackageEntryPoint(filePath, packageEntryPoints) {
@@ -6690,6 +8209,98 @@ function isCppExcluded(attrs) {
6690
8209
  if (kind === "constant" && /\.(?:h|hpp)$/.test(filePath)) return true;
6691
8210
  return false;
6692
8211
  }
8212
+ function isKotlinExcluded(attrs) {
8213
+ const filePath = attrs.file || attrs.filePath || "";
8214
+ const name = attrs.name || "";
8215
+ if (!filePath.endsWith(".kt") && !filePath.endsWith(".kts")) return false;
8216
+ if (name === "main") return true;
8217
+ const androidLifecycle = [
8218
+ "onCreate",
8219
+ "onStart",
8220
+ "onResume",
8221
+ "onPause",
8222
+ "onStop",
8223
+ "onDestroy",
8224
+ "onCreateView",
8225
+ "onViewCreated",
8226
+ "onDestroyView",
8227
+ "onSaveInstanceState",
8228
+ "onRestoreInstanceState",
8229
+ "onActivityResult",
8230
+ "onRequestPermissionsResult",
8231
+ "onConfigurationChanged",
8232
+ "onNewIntent"
8233
+ ];
8234
+ if (androidLifecycle.includes(name)) return true;
8235
+ if (["readObject", "writeObject", "readResolve", "writeReplace"].includes(name)) return true;
8236
+ if (name.startsWith("operator")) return true;
8237
+ return false;
8238
+ }
8239
+ function isPhpExcluded(attrs) {
8240
+ const filePath = attrs.file || attrs.filePath || "";
8241
+ const name = attrs.name || "";
8242
+ if (!filePath.endsWith(".php")) return false;
8243
+ const magicMethods = [
8244
+ "__construct",
8245
+ "__destruct",
8246
+ "__call",
8247
+ "__callStatic",
8248
+ "__get",
8249
+ "__set",
8250
+ "__isset",
8251
+ "__unset",
8252
+ "__sleep",
8253
+ "__wakeup",
8254
+ "__serialize",
8255
+ "__unserialize",
8256
+ "__toString",
8257
+ "__invoke",
8258
+ "__set_state",
8259
+ "__clone",
8260
+ "__debugInfo"
8261
+ ];
8262
+ if (magicMethods.includes(name)) return true;
8263
+ const wpHooks = [
8264
+ "init",
8265
+ "admin_init",
8266
+ "wp_enqueue_scripts",
8267
+ "admin_enqueue_scripts",
8268
+ "widgets_init",
8269
+ "register_activation_hook",
8270
+ "register_deactivation_hook",
8271
+ "add_action",
8272
+ "add_filter",
8273
+ "activate",
8274
+ "deactivate"
8275
+ ];
8276
+ if (wpHooks.includes(name)) return true;
8277
+ const laravelMethods = [
8278
+ "register",
8279
+ "boot",
8280
+ "handle",
8281
+ "authorize",
8282
+ "rules",
8283
+ "messages",
8284
+ "prepareForValidation",
8285
+ "failed",
8286
+ "broadcastOn",
8287
+ "broadcastAs",
8288
+ "broadcastWith"
8289
+ ];
8290
+ if (laravelMethods.includes(name)) return true;
8291
+ const symfonyMethods = [
8292
+ "__invoke",
8293
+ "getSubscribedEvents",
8294
+ "getSubscribedServices",
8295
+ "configureOptions",
8296
+ "buildForm",
8297
+ "load",
8298
+ "getConfigTreeBuilder"
8299
+ ];
8300
+ if (symfonyMethods.includes(name)) return true;
8301
+ if (name.startsWith("test") || name === "setUp" || name === "tearDown" || name === "setUpBeforeClass" || name === "tearDownAfterClass") return true;
8302
+ return false;
8303
+ }
6693
8304
 
6694
8305
  // src/dead-code/classifier.ts
6695
8306
  import path3 from "path";
@@ -6756,8 +8367,8 @@ function generateReason(symbol, confidence) {
6756
8367
  return "Potentially unused";
6757
8368
  }
6758
8369
  function isBarrelFile(filePath) {
6759
- const basename9 = path3.basename(filePath);
6760
- return basename9 === "index.ts" || basename9 === "index.js";
8370
+ const basename11 = path3.basename(filePath);
8371
+ return basename11 === "index.ts" || basename11 === "index.js";
6761
8372
  }
6762
8373
  function isTestFile2(filePath) {
6763
8374
  return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
@@ -6936,11 +8547,11 @@ function filterByConfidence(symbols, minConfidence) {
6936
8547
  }
6937
8548
 
6938
8549
  // src/docs/generator.ts
6939
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync15 } from "fs";
6940
- import { join as join17 } from "path";
8550
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync17 } from "fs";
8551
+ import { join as join19 } from "path";
6941
8552
 
6942
8553
  // src/docs/architecture.ts
6943
- import { dirname as dirname12 } from "path";
8554
+ import { dirname as dirname14 } from "path";
6944
8555
 
6945
8556
  // src/docs/templates.ts
6946
8557
  function header(text, level = 1) {
@@ -7091,7 +8702,7 @@ function generateModuleStructure(graph) {
7091
8702
  function getDirectoryStats(graph) {
7092
8703
  const dirMap = /* @__PURE__ */ new Map();
7093
8704
  graph.forEachNode((node, attrs) => {
7094
- const dir = dirname12(attrs.filePath);
8705
+ const dir = dirname14(attrs.filePath);
7095
8706
  if (dir === ".") return;
7096
8707
  if (!dirMap.has(dir)) {
7097
8708
  dirMap.set(dir, {
@@ -7116,7 +8727,7 @@ function getDirectoryStats(graph) {
7116
8727
  });
7117
8728
  const filesPerDir = /* @__PURE__ */ new Map();
7118
8729
  graph.forEachNode((node, attrs) => {
7119
- const dir = dirname12(attrs.filePath);
8730
+ const dir = dirname14(attrs.filePath);
7120
8731
  if (!filesPerDir.has(dir)) {
7121
8732
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
7122
8733
  }
@@ -7131,8 +8742,8 @@ function getDirectoryStats(graph) {
7131
8742
  graph.forEachEdge((edge, attrs, source, target) => {
7132
8743
  const sourceAttrs = graph.getNodeAttributes(source);
7133
8744
  const targetAttrs = graph.getNodeAttributes(target);
7134
- const sourceDir = dirname12(sourceAttrs.filePath);
7135
- const targetDir = dirname12(targetAttrs.filePath);
8745
+ const sourceDir = dirname14(sourceAttrs.filePath);
8746
+ const targetDir = dirname14(targetAttrs.filePath);
7136
8747
  if (sourceDir !== targetDir) {
7137
8748
  if (!dirEdges.has(sourceDir)) {
7138
8749
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -7339,7 +8950,7 @@ function detectCycles(graph) {
7339
8950
  }
7340
8951
 
7341
8952
  // src/docs/conventions.ts
7342
- import { basename as basename6, extname as extname7 } from "path";
8953
+ import { basename as basename8, extname as extname9 } from "path";
7343
8954
  function generateConventions(graph, projectRoot, version) {
7344
8955
  let output = "";
7345
8956
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -7377,7 +8988,7 @@ function generateFileOrganization(graph) {
7377
8988
  graph.forEachNode((node, attrs) => {
7378
8989
  if (!files.has(attrs.filePath)) {
7379
8990
  files.add(attrs.filePath);
7380
- const fileName = basename6(attrs.filePath);
8991
+ const fileName = basename8(attrs.filePath);
7381
8992
  if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
7382
8993
  barrelFileCount++;
7383
8994
  }
@@ -7436,7 +9047,7 @@ function generateNamingPatterns(graph) {
7436
9047
  graph.forEachNode((node, attrs) => {
7437
9048
  if (!files.has(attrs.filePath)) {
7438
9049
  files.add(attrs.filePath);
7439
- const fileName = basename6(attrs.filePath, extname7(attrs.filePath));
9050
+ const fileName = basename8(attrs.filePath, extname9(attrs.filePath));
7440
9051
  if (isCamelCase(fileName)) patterns.files.camelCase++;
7441
9052
  else if (isPascalCase(fileName)) patterns.files.PascalCase++;
7442
9053
  else if (isKebabCase(fileName)) patterns.files.kebabCase++;
@@ -8113,7 +9724,7 @@ function detectCyclesDetailed(graph) {
8113
9724
  }
8114
9725
 
8115
9726
  // src/docs/onboarding.ts
8116
- import { dirname as dirname13 } from "path";
9727
+ import { dirname as dirname15 } from "path";
8117
9728
  function generateOnboarding(graph, projectRoot, version) {
8118
9729
  let output = "";
8119
9730
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -8172,7 +9783,7 @@ function generateQuickOrientation(graph) {
8172
9783
  const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
8173
9784
  const dirs = /* @__PURE__ */ new Set();
8174
9785
  graph.forEachNode((node, attrs) => {
8175
- const dir = dirname13(attrs.filePath);
9786
+ const dir = dirname15(attrs.filePath);
8176
9787
  if (dir !== ".") {
8177
9788
  const topLevel = dir.split("/")[0];
8178
9789
  dirs.add(topLevel);
@@ -8276,7 +9887,7 @@ function generateModuleMap(graph) {
8276
9887
  function getDirectoryStats2(graph) {
8277
9888
  const dirMap = /* @__PURE__ */ new Map();
8278
9889
  graph.forEachNode((node, attrs) => {
8279
- const dir = dirname13(attrs.filePath);
9890
+ const dir = dirname15(attrs.filePath);
8280
9891
  if (dir === ".") return;
8281
9892
  if (!dirMap.has(dir)) {
8282
9893
  dirMap.set(dir, {
@@ -8291,7 +9902,7 @@ function getDirectoryStats2(graph) {
8291
9902
  });
8292
9903
  const filesPerDir = /* @__PURE__ */ new Map();
8293
9904
  graph.forEachNode((node, attrs) => {
8294
- const dir = dirname13(attrs.filePath);
9905
+ const dir = dirname15(attrs.filePath);
8295
9906
  if (!filesPerDir.has(dir)) {
8296
9907
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
8297
9908
  }
@@ -8306,8 +9917,8 @@ function getDirectoryStats2(graph) {
8306
9917
  graph.forEachEdge((edge, attrs, source, target) => {
8307
9918
  const sourceAttrs = graph.getNodeAttributes(source);
8308
9919
  const targetAttrs = graph.getNodeAttributes(target);
8309
- const sourceDir = dirname13(sourceAttrs.filePath);
8310
- const targetDir = dirname13(targetAttrs.filePath);
9920
+ const sourceDir = dirname15(sourceAttrs.filePath);
9921
+ const targetDir = dirname15(targetAttrs.filePath);
8311
9922
  if (sourceDir !== targetDir) {
8312
9923
  if (!dirEdges.has(sourceDir)) {
8313
9924
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -8388,7 +9999,7 @@ function detectClusters(graph) {
8388
9999
  const dirFiles = /* @__PURE__ */ new Map();
8389
10000
  const fileEdges = /* @__PURE__ */ new Map();
8390
10001
  graph.forEachNode((node, attrs) => {
8391
- const dir = dirname13(attrs.filePath);
10002
+ const dir = dirname15(attrs.filePath);
8392
10003
  if (!dirFiles.has(dir)) {
8393
10004
  dirFiles.set(dir, /* @__PURE__ */ new Set());
8394
10005
  }
@@ -8442,8 +10053,8 @@ function inferClusterName(files) {
8442
10053
  if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
8443
10054
  return capitalizeFirst2(sortedWords[0][0]);
8444
10055
  }
8445
- const commonDir = dirname13(files[0]);
8446
- if (files.every((f) => dirname13(f) === commonDir)) {
10056
+ const commonDir = dirname15(files[0]);
10057
+ if (files.every((f) => dirname15(f) === commonDir)) {
8447
10058
  return capitalizeFirst2(commonDir.split("/").pop() || "Core");
8448
10059
  }
8449
10060
  return "Core";
@@ -8501,7 +10112,7 @@ function generateDepwireUsage(projectRoot) {
8501
10112
  }
8502
10113
 
8503
10114
  // src/docs/files.ts
8504
- import { dirname as dirname14, basename as basename7 } from "path";
10115
+ import { dirname as dirname16, basename as basename9 } from "path";
8505
10116
  function generateFiles(graph, projectRoot, version) {
8506
10117
  let output = "";
8507
10118
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -8605,7 +10216,7 @@ function generateDirectoryBreakdown(graph) {
8605
10216
  const fileStats = getFileStats2(graph);
8606
10217
  const dirMap = /* @__PURE__ */ new Map();
8607
10218
  for (const file of fileStats) {
8608
- const dir = dirname14(file.filePath);
10219
+ const dir = dirname16(file.filePath);
8609
10220
  const topDir = dir === "." ? "." : dir.split("/")[0];
8610
10221
  if (!dirMap.has(topDir)) {
8611
10222
  dirMap.set(topDir, {
@@ -8620,7 +10231,7 @@ function generateDirectoryBreakdown(graph) {
8620
10231
  dirStats.symbolCount += file.symbolCount;
8621
10232
  if (file.totalConnections > dirStats.maxConnections) {
8622
10233
  dirStats.maxConnections = file.totalConnections;
8623
- dirStats.mostConnectedFile = basename7(file.filePath);
10234
+ dirStats.mostConnectedFile = basename9(file.filePath);
8624
10235
  }
8625
10236
  }
8626
10237
  if (dirMap.size === 0) {
@@ -9248,7 +10859,7 @@ function generateRecommendations(graph) {
9248
10859
  }
9249
10860
 
9250
10861
  // src/docs/tests.ts
9251
- import { basename as basename8, dirname as dirname15 } from "path";
10862
+ import { basename as basename10, dirname as dirname17 } from "path";
9252
10863
  function generateTests(graph, projectRoot, version) {
9253
10864
  let output = "";
9254
10865
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -9276,8 +10887,8 @@ function getFileCount8(graph) {
9276
10887
  return files.size;
9277
10888
  }
9278
10889
  function isTestFile3(filePath) {
9279
- const fileName = basename8(filePath).toLowerCase();
9280
- const dirPath = dirname15(filePath).toLowerCase();
10890
+ const fileName = basename10(filePath).toLowerCase();
10891
+ const dirPath = dirname17(filePath).toLowerCase();
9281
10892
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
9282
10893
  return true;
9283
10894
  }
@@ -9335,13 +10946,13 @@ function generateTestFileInventory(graph) {
9335
10946
  return output;
9336
10947
  }
9337
10948
  function matchTestToSource(testFile) {
9338
- const testFileName = basename8(testFile);
9339
- const testDir = dirname15(testFile);
10949
+ const testFileName = basename10(testFile);
10950
+ const testDir = dirname17(testFile);
9340
10951
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
9341
10952
  const possiblePaths = [];
9342
10953
  possiblePaths.push(testDir + "/" + sourceFileName);
9343
10954
  if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
9344
- const parentDir = dirname15(testDir);
10955
+ const parentDir = dirname17(testDir);
9345
10956
  possiblePaths.push(parentDir + "/" + sourceFileName);
9346
10957
  }
9347
10958
  if (testDir.includes("test")) {
@@ -9497,7 +11108,7 @@ function generateTestCoverageMap(graph) {
9497
11108
  const rows = mappings.slice(0, 30).map((m) => [
9498
11109
  `\`${m.sourceFile}\``,
9499
11110
  m.hasTest ? "\u2705" : "\u274C",
9500
- m.testFile ? `\`${basename8(m.testFile)}\`` : "-",
11111
+ m.testFile ? `\`${basename10(m.testFile)}\`` : "-",
9501
11112
  formatNumber(m.symbolCount)
9502
11113
  ]);
9503
11114
  let output = table(headers, rows);
@@ -9538,7 +11149,7 @@ function generateTestStatistics(graph) {
9538
11149
  `;
9539
11150
  const dirTestCoverage = /* @__PURE__ */ new Map();
9540
11151
  for (const sourceFile of sourceFiles) {
9541
- const dir = dirname15(sourceFile).split("/")[0];
11152
+ const dir = dirname17(sourceFile).split("/")[0];
9542
11153
  if (!dirTestCoverage.has(dir)) {
9543
11154
  dirTestCoverage.set(dir, { total: 0, tested: 0 });
9544
11155
  }
@@ -9561,7 +11172,7 @@ function generateTestStatistics(graph) {
9561
11172
  }
9562
11173
 
9563
11174
  // src/docs/history.ts
9564
- import { dirname as dirname16 } from "path";
11175
+ import { dirname as dirname18 } from "path";
9565
11176
  import { execSync } from "child_process";
9566
11177
  function generateHistory(graph, projectRoot, version) {
9567
11178
  let output = "";
@@ -9842,7 +11453,7 @@ function generateFeatureClusters(graph) {
9842
11453
  const dirFiles = /* @__PURE__ */ new Map();
9843
11454
  const fileEdges = /* @__PURE__ */ new Map();
9844
11455
  graph.forEachNode((node, attrs) => {
9845
- const dir = dirname16(attrs.filePath);
11456
+ const dir = dirname18(attrs.filePath);
9846
11457
  if (!dirFiles.has(dir)) {
9847
11458
  dirFiles.set(dir, /* @__PURE__ */ new Set());
9848
11459
  }
@@ -9924,7 +11535,7 @@ function capitalizeFirst3(str) {
9924
11535
  }
9925
11536
 
9926
11537
  // src/docs/current.ts
9927
- import { dirname as dirname17 } from "path";
11538
+ import { dirname as dirname19 } from "path";
9928
11539
  function generateCurrent(graph, projectRoot, version) {
9929
11540
  let output = "";
9930
11541
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -10062,7 +11673,7 @@ function generateCompleteFileIndex(graph) {
10062
11673
  fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
10063
11674
  const dirGroups = /* @__PURE__ */ new Map();
10064
11675
  for (const info of fileInfos) {
10065
- const dir = dirname17(info.filePath);
11676
+ const dir = dirname19(info.filePath);
10066
11677
  const topDir = dir === "." ? "root" : dir.split("/")[0];
10067
11678
  if (!dirGroups.has(topDir)) {
10068
11679
  dirGroups.set(topDir, []);
@@ -10273,8 +11884,8 @@ function getTopLevelDir2(filePath) {
10273
11884
  }
10274
11885
 
10275
11886
  // src/docs/status.ts
10276
- import { readFileSync as readFileSync13, existsSync as existsSync13 } from "fs";
10277
- import { resolve as resolve9 } from "path";
11887
+ import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
11888
+ import { resolve as resolve11 } from "path";
10278
11889
  function generateStatus(graph, projectRoot, version) {
10279
11890
  let output = "";
10280
11891
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -10307,16 +11918,16 @@ function getFileCount11(graph) {
10307
11918
  }
10308
11919
  function extractComments(projectRoot, filePath) {
10309
11920
  const comments = [];
10310
- const resolvedRoot = resolve9(projectRoot);
10311
- const fullPath = resolve9(resolvedRoot, filePath);
11921
+ const resolvedRoot = resolve11(projectRoot);
11922
+ const fullPath = resolve11(resolvedRoot, filePath);
10312
11923
  if (!fullPath.startsWith(resolvedRoot)) {
10313
11924
  return comments;
10314
11925
  }
10315
- if (!existsSync13(fullPath)) {
11926
+ if (!existsSync15(fullPath)) {
10316
11927
  return comments;
10317
11928
  }
10318
11929
  try {
10319
- const content = readFileSync13(fullPath, "utf-8");
11930
+ const content = readFileSync15(fullPath, "utf-8");
10320
11931
  const lines = content.split("\n");
10321
11932
  const patterns = [
10322
11933
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -10895,16 +12506,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
10895
12506
  }
10896
12507
 
10897
12508
  // src/docs/metadata.ts
10898
- import { existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync2 } from "fs";
10899
- import { resolve as resolve10 } from "path";
12509
+ import { existsSync as existsSync16, readFileSync as readFileSync16, writeFileSync as writeFileSync2 } from "fs";
12510
+ import { resolve as resolve12 } from "path";
10900
12511
  function loadMetadata(outputDir) {
10901
- const resolvedDir = resolve10(outputDir);
10902
- const metadataPath = resolve10(resolvedDir, "metadata.json");
10903
- if (!metadataPath.startsWith(resolvedDir) || !existsSync14(metadataPath)) {
12512
+ const resolvedDir = resolve12(outputDir);
12513
+ const metadataPath = resolve12(resolvedDir, "metadata.json");
12514
+ if (!metadataPath.startsWith(resolvedDir) || !existsSync16(metadataPath)) {
10904
12515
  return null;
10905
12516
  }
10906
12517
  try {
10907
- const content = readFileSync14(metadataPath, "utf-8");
12518
+ const content = readFileSync16(metadataPath, "utf-8");
10908
12519
  return JSON.parse(content);
10909
12520
  } catch (err) {
10910
12521
  console.error("Failed to load metadata:", err);
@@ -10912,8 +12523,8 @@ function loadMetadata(outputDir) {
10912
12523
  }
10913
12524
  }
10914
12525
  function saveMetadata(outputDir, metadata) {
10915
- const resolvedDir = resolve10(outputDir);
10916
- const metadataPath = resolve10(resolvedDir, "metadata.json");
12526
+ const resolvedDir = resolve12(outputDir);
12527
+ const metadataPath = resolve12(resolvedDir, "metadata.json");
10917
12528
  if (!metadataPath.startsWith(resolvedDir)) {
10918
12529
  throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
10919
12530
  }
@@ -10959,7 +12570,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
10959
12570
  const generated = [];
10960
12571
  const errors = [];
10961
12572
  try {
10962
- if (!existsSync15(options.outputDir)) {
12573
+ if (!existsSync17(options.outputDir)) {
10963
12574
  mkdirSync2(options.outputDir, { recursive: true });
10964
12575
  if (options.verbose) {
10965
12576
  console.log(`Created output directory: ${options.outputDir}`);
@@ -10998,7 +12609,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
10998
12609
  try {
10999
12610
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
11000
12611
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
11001
- const filePath = join17(options.outputDir, "ARCHITECTURE.md");
12612
+ const filePath = join19(options.outputDir, "ARCHITECTURE.md");
11002
12613
  writeFileSync3(filePath, content, "utf-8");
11003
12614
  generated.push("ARCHITECTURE.md");
11004
12615
  } catch (err) {
@@ -11009,7 +12620,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11009
12620
  try {
11010
12621
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
11011
12622
  const content = generateConventions(graph, projectRoot, version);
11012
- const filePath = join17(options.outputDir, "CONVENTIONS.md");
12623
+ const filePath = join19(options.outputDir, "CONVENTIONS.md");
11013
12624
  writeFileSync3(filePath, content, "utf-8");
11014
12625
  generated.push("CONVENTIONS.md");
11015
12626
  } catch (err) {
@@ -11020,7 +12631,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11020
12631
  try {
11021
12632
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
11022
12633
  const content = generateDependencies(graph, projectRoot, version);
11023
- const filePath = join17(options.outputDir, "DEPENDENCIES.md");
12634
+ const filePath = join19(options.outputDir, "DEPENDENCIES.md");
11024
12635
  writeFileSync3(filePath, content, "utf-8");
11025
12636
  generated.push("DEPENDENCIES.md");
11026
12637
  } catch (err) {
@@ -11031,7 +12642,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11031
12642
  try {
11032
12643
  if (options.verbose) console.log("Generating ONBOARDING.md...");
11033
12644
  const content = generateOnboarding(graph, projectRoot, version);
11034
- const filePath = join17(options.outputDir, "ONBOARDING.md");
12645
+ const filePath = join19(options.outputDir, "ONBOARDING.md");
11035
12646
  writeFileSync3(filePath, content, "utf-8");
11036
12647
  generated.push("ONBOARDING.md");
11037
12648
  } catch (err) {
@@ -11042,7 +12653,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11042
12653
  try {
11043
12654
  if (options.verbose) console.log("Generating FILES.md...");
11044
12655
  const content = generateFiles(graph, projectRoot, version);
11045
- const filePath = join17(options.outputDir, "FILES.md");
12656
+ const filePath = join19(options.outputDir, "FILES.md");
11046
12657
  writeFileSync3(filePath, content, "utf-8");
11047
12658
  generated.push("FILES.md");
11048
12659
  } catch (err) {
@@ -11053,7 +12664,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11053
12664
  try {
11054
12665
  if (options.verbose) console.log("Generating API_SURFACE.md...");
11055
12666
  const content = generateApiSurface(graph, projectRoot, version);
11056
- const filePath = join17(options.outputDir, "API_SURFACE.md");
12667
+ const filePath = join19(options.outputDir, "API_SURFACE.md");
11057
12668
  writeFileSync3(filePath, content, "utf-8");
11058
12669
  generated.push("API_SURFACE.md");
11059
12670
  } catch (err) {
@@ -11064,7 +12675,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11064
12675
  try {
11065
12676
  if (options.verbose) console.log("Generating ERRORS.md...");
11066
12677
  const content = generateErrors(graph, projectRoot, version);
11067
- const filePath = join17(options.outputDir, "ERRORS.md");
12678
+ const filePath = join19(options.outputDir, "ERRORS.md");
11068
12679
  writeFileSync3(filePath, content, "utf-8");
11069
12680
  generated.push("ERRORS.md");
11070
12681
  } catch (err) {
@@ -11075,7 +12686,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11075
12686
  try {
11076
12687
  if (options.verbose) console.log("Generating TESTS.md...");
11077
12688
  const content = generateTests(graph, projectRoot, version);
11078
- const filePath = join17(options.outputDir, "TESTS.md");
12689
+ const filePath = join19(options.outputDir, "TESTS.md");
11079
12690
  writeFileSync3(filePath, content, "utf-8");
11080
12691
  generated.push("TESTS.md");
11081
12692
  } catch (err) {
@@ -11086,7 +12697,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11086
12697
  try {
11087
12698
  if (options.verbose) console.log("Generating HISTORY.md...");
11088
12699
  const content = generateHistory(graph, projectRoot, version);
11089
- const filePath = join17(options.outputDir, "HISTORY.md");
12700
+ const filePath = join19(options.outputDir, "HISTORY.md");
11090
12701
  writeFileSync3(filePath, content, "utf-8");
11091
12702
  generated.push("HISTORY.md");
11092
12703
  } catch (err) {
@@ -11097,7 +12708,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11097
12708
  try {
11098
12709
  if (options.verbose) console.log("Generating CURRENT.md...");
11099
12710
  const content = generateCurrent(graph, projectRoot, version);
11100
- const filePath = join17(options.outputDir, "CURRENT.md");
12711
+ const filePath = join19(options.outputDir, "CURRENT.md");
11101
12712
  writeFileSync3(filePath, content, "utf-8");
11102
12713
  generated.push("CURRENT.md");
11103
12714
  } catch (err) {
@@ -11108,7 +12719,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11108
12719
  try {
11109
12720
  if (options.verbose) console.log("Generating STATUS.md...");
11110
12721
  const content = generateStatus(graph, projectRoot, version);
11111
- const filePath = join17(options.outputDir, "STATUS.md");
12722
+ const filePath = join19(options.outputDir, "STATUS.md");
11112
12723
  writeFileSync3(filePath, content, "utf-8");
11113
12724
  generated.push("STATUS.md");
11114
12725
  } catch (err) {
@@ -11119,7 +12730,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11119
12730
  try {
11120
12731
  if (options.verbose) console.log("Generating HEALTH.md...");
11121
12732
  const content = generateHealth(graph, projectRoot, version);
11122
- const filePath = join17(options.outputDir, "HEALTH.md");
12733
+ const filePath = join19(options.outputDir, "HEALTH.md");
11123
12734
  writeFileSync3(filePath, content, "utf-8");
11124
12735
  generated.push("HEALTH.md");
11125
12736
  } catch (err) {
@@ -11130,7 +12741,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11130
12741
  try {
11131
12742
  if (options.verbose) console.log("Generating DEAD_CODE.md...");
11132
12743
  const content = generateDeadCode(graph, projectRoot, version);
11133
- const filePath = join17(options.outputDir, "DEAD_CODE.md");
12744
+ const filePath = join19(options.outputDir, "DEAD_CODE.md");
11134
12745
  writeFileSync3(filePath, content, "utf-8");
11135
12746
  generated.push("DEAD_CODE.md");
11136
12747
  } catch (err) {
@@ -11174,7 +12785,7 @@ function getFileCount13(graph) {
11174
12785
  }
11175
12786
 
11176
12787
  // src/simulation/engine.ts
11177
- import { dirname as dirname18, join as join18 } from "path";
12788
+ import { dirname as dirname20, join as join20 } from "path";
11178
12789
  function normalizePath2(p) {
11179
12790
  return p.replace(/^\.\//, "").replace(/\/+$/, "");
11180
12791
  }
@@ -11306,7 +12917,7 @@ var SimulationEngine = class {
11306
12917
  }
11307
12918
  }
11308
12919
  applyRename(clone, target, newName, brokenImports) {
11309
- const destination = join18(dirname18(target), newName);
12920
+ const destination = join20(dirname20(target), newName);
11310
12921
  this.applyMove(clone, target, destination, brokenImports);
11311
12922
  }
11312
12923
  applySplit(clone, target, newFile, symbols, brokenImports) {
@@ -11506,13 +13117,13 @@ var SimulationEngine = class {
11506
13117
  };
11507
13118
 
11508
13119
  // src/security/scanner.ts
11509
- import { existsSync as existsSync17 } from "fs";
11510
- import { join as join28 } from "path";
13120
+ import { existsSync as existsSync19 } from "fs";
13121
+ import { join as join30 } from "path";
11511
13122
 
11512
13123
  // src/security/checks/dependencies.ts
11513
13124
  import { execSync as execSync2 } from "child_process";
11514
- import { existsSync as existsSync16, readFileSync as readFileSync15, readdirSync as readdirSync8 } from "fs";
11515
- import { join as join19 } from "path";
13125
+ import { existsSync as existsSync18, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
13126
+ import { join as join21 } from "path";
11516
13127
  function cvssToSeverity(score) {
11517
13128
  if (score >= 9) return "critical";
11518
13129
  if (score >= 7) return "high";
@@ -11522,18 +13133,18 @@ function cvssToSeverity(score) {
11522
13133
  async function checkDependencies(_files, projectRoot) {
11523
13134
  const findings = [];
11524
13135
  try {
11525
- if (existsSync16(join19(projectRoot, "package.json"))) {
13136
+ if (existsSync18(join21(projectRoot, "package.json"))) {
11526
13137
  findings.push(...checkNpmAudit(projectRoot));
11527
13138
  findings.push(...checkPackageJsonPatterns(projectRoot));
11528
13139
  findings.push(...checkPostinstallScripts(projectRoot));
11529
13140
  }
11530
- if (existsSync16(join19(projectRoot, "requirements.txt")) || existsSync16(join19(projectRoot, "pyproject.toml"))) {
13141
+ if (existsSync18(join21(projectRoot, "requirements.txt")) || existsSync18(join21(projectRoot, "pyproject.toml"))) {
11531
13142
  findings.push(...checkPipAudit(projectRoot));
11532
13143
  }
11533
- if (existsSync16(join19(projectRoot, "Cargo.toml"))) {
13144
+ if (existsSync18(join21(projectRoot, "Cargo.toml"))) {
11534
13145
  findings.push(...checkCargoAudit(projectRoot));
11535
13146
  }
11536
- if (existsSync16(join19(projectRoot, "go.mod"))) {
13147
+ if (existsSync18(join21(projectRoot, "go.mod"))) {
11537
13148
  findings.push(...checkGoVerify(projectRoot));
11538
13149
  }
11539
13150
  } catch (err) {
@@ -11622,8 +13233,8 @@ function checkNpmAudit(projectRoot) {
11622
13233
  function checkPackageJsonPatterns(projectRoot) {
11623
13234
  const findings = [];
11624
13235
  try {
11625
- const pkgPath = join19(projectRoot, "package.json");
11626
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
13236
+ const pkgPath = join21(projectRoot, "package.json");
13237
+ const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
11627
13238
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
11628
13239
  for (const [name, version] of Object.entries(allDeps)) {
11629
13240
  if (version.startsWith("^") || version.startsWith("~")) {
@@ -11645,15 +13256,15 @@ function checkPackageJsonPatterns(projectRoot) {
11645
13256
  }
11646
13257
  function checkPostinstallScripts(projectRoot) {
11647
13258
  const findings = [];
11648
- const nodeModules = join19(projectRoot, "node_modules");
11649
- if (!existsSync16(nodeModules)) return findings;
13259
+ const nodeModules = join21(projectRoot, "node_modules");
13260
+ if (!existsSync18(nodeModules)) return findings;
11650
13261
  try {
11651
- const topLevelDeps = readdirSync8(nodeModules).filter((d) => !d.startsWith("."));
13262
+ const topLevelDeps = readdirSync10(nodeModules).filter((d) => !d.startsWith("."));
11652
13263
  for (const dep of topLevelDeps) {
11653
- const depPkgPath = join19(nodeModules, dep, "package.json");
11654
- if (!existsSync16(depPkgPath)) continue;
13264
+ const depPkgPath = join21(nodeModules, dep, "package.json");
13265
+ if (!existsSync18(depPkgPath)) continue;
11655
13266
  try {
11656
- const depPkg = JSON.parse(readFileSync15(depPkgPath, "utf-8"));
13267
+ const depPkg = JSON.parse(readFileSync17(depPkgPath, "utf-8"));
11657
13268
  const scripts = depPkg.scripts || {};
11658
13269
  if (scripts.postinstall || scripts.preinstall || scripts.install) {
11659
13270
  const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
@@ -11691,7 +13302,7 @@ function checkPipAudit(projectRoot) {
11691
13302
  id: "",
11692
13303
  severity: cvssToSeverity(vuln.cvss?.score || 5),
11693
13304
  vulnerabilityClass: "dependency-cve",
11694
- file: existsSync16(join19(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
13305
+ file: existsSync18(join21(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
11695
13306
  title: `Vulnerable Python dependency: ${vuln.name}`,
11696
13307
  description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
11697
13308
  attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
@@ -11788,8 +13399,8 @@ function checkGoVerify(projectRoot) {
11788
13399
  }
11789
13400
 
11790
13401
  // src/security/checks/injection.ts
11791
- import { readFileSync as readFileSync16 } from "fs";
11792
- import { join as join20 } from "path";
13402
+ import { readFileSync as readFileSync18 } from "fs";
13403
+ import { join as join22 } from "path";
11793
13404
  var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
11794
13405
  var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
11795
13406
  var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
@@ -11975,6 +13586,107 @@ var PATTERNS = [
11975
13586
  description: "popen() called with a variable argument \u2014 potential command injection.",
11976
13587
  attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands.",
11977
13588
  suggestedFix: "Avoid popen(). Use pipe/fork/exec with argument arrays instead."
13589
+ },
13590
+ // Kotlin injection patterns
13591
+ {
13592
+ regex: /["']SELECT\b[^"']*\$(?:\{|\w)/,
13593
+ title: "Kotlin SQL injection via string template",
13594
+ vulnClass: "code-injection",
13595
+ baseSeverity: "high",
13596
+ description: "SQL query built using Kotlin string templates \u2014 vulnerable to SQL injection.",
13597
+ attackScenario: "An attacker could inject SQL through interpolated variables to read, modify, or delete database data.",
13598
+ suggestedFix: "Use parameterized queries with PreparedStatement or your ORM's query builder."
13599
+ },
13600
+ {
13601
+ regex: /["'](?:INSERT|UPDATE|DELETE)\b[^"']*\$(?:\{|\w)/,
13602
+ title: "Kotlin SQL injection via string template",
13603
+ vulnClass: "code-injection",
13604
+ baseSeverity: "high",
13605
+ description: "SQL mutation query built using Kotlin string templates \u2014 vulnerable to SQL injection.",
13606
+ attackScenario: "An attacker could inject SQL through interpolated variables.",
13607
+ suggestedFix: "Use parameterized queries with PreparedStatement or your ORM's query builder."
13608
+ },
13609
+ {
13610
+ regex: /Runtime\.getRuntime\(\)\.exec\s*\(/,
13611
+ title: "Kotlin/Java command injection via Runtime.exec",
13612
+ vulnClass: "shell-injection",
13613
+ baseSeverity: "high",
13614
+ description: "Runtime.exec() executes a system command \u2014 vulnerable if user input reaches the argument.",
13615
+ attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
13616
+ suggestedFix: "Use ProcessBuilder with an argument array. Validate all input against a strict allowlist."
13617
+ },
13618
+ {
13619
+ regex: /\.csrf\(\)\s*\.?\s*disable\(\)/,
13620
+ title: "Spring Security CSRF protection disabled",
13621
+ vulnClass: "code-injection",
13622
+ baseSeverity: "medium",
13623
+ description: "CSRF protection has been explicitly disabled in Spring Security configuration.",
13624
+ attackScenario: "An attacker could forge cross-site requests to perform actions on behalf of authenticated users.",
13625
+ suggestedFix: "Only disable CSRF for stateless APIs using JWT. Keep CSRF enabled for session-based authentication."
13626
+ },
13627
+ {
13628
+ regex: /\.permitAll\(\).*(?:admin|manage|delete|config|setting)/i,
13629
+ title: "Spring Security permitAll on sensitive path",
13630
+ vulnClass: "code-injection",
13631
+ baseSeverity: "high",
13632
+ description: "permitAll() applied to a path that appears security-sensitive.",
13633
+ attackScenario: "An attacker could access administrative or destructive endpoints without authentication.",
13634
+ suggestedFix: 'Use .hasRole("ADMIN") or .authenticated() for sensitive endpoints.'
13635
+ },
13636
+ // PHP injection patterns
13637
+ {
13638
+ regex: /\$wpdb\s*->\s*query\s*\(\s*["'][^"']*\$|.*\$wpdb\s*->\s*query\s*\(\s*[^"']*\.\s*\$/,
13639
+ title: "PHP SQL injection via $wpdb->query with string concatenation",
13640
+ vulnClass: "code-injection",
13641
+ baseSeverity: "high",
13642
+ description: "WordPress $wpdb->query() called with direct variable interpolation \u2014 vulnerable to SQL injection.",
13643
+ attackScenario: "An attacker could inject SQL through unescaped user input to read, modify, or delete database data.",
13644
+ suggestedFix: 'Use $wpdb->prepare() with placeholders: $wpdb->query($wpdb->prepare("SELECT * FROM table WHERE id = %d", $id))'
13645
+ },
13646
+ {
13647
+ regex: /\beval\s*\(\s*\$/,
13648
+ title: "PHP eval() with variable input",
13649
+ vulnClass: "code-injection",
13650
+ baseSeverity: "high",
13651
+ description: "eval() executes arbitrary PHP code from a variable \u2014 potential RCE.",
13652
+ attackScenario: "An attacker could inject malicious PHP code if user input reaches eval().",
13653
+ suggestedFix: "Remove eval() entirely. Use safe alternatives like json_decode() for data or specific parsers."
13654
+ },
13655
+ {
13656
+ regex: /\b(?:system|exec|shell_exec|passthru)\s*\(\s*\$/,
13657
+ title: "PHP command injection via system/exec/shell_exec/passthru",
13658
+ vulnClass: "shell-injection",
13659
+ baseSeverity: "high",
13660
+ description: "Shell command execution with a variable argument \u2014 potential command injection.",
13661
+ attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
13662
+ suggestedFix: "Use escapeshellarg() and escapeshellcmd() to sanitize input, or avoid shell commands entirely."
13663
+ },
13664
+ {
13665
+ regex: /preg_replace\s*\(\s*['"]\/[^'"]*\/e['"]/,
13666
+ title: "PHP preg_replace with /e modifier (code execution)",
13667
+ vulnClass: "code-injection",
13668
+ baseSeverity: "high",
13669
+ description: "preg_replace() with the /e modifier evaluates the replacement as PHP code \u2014 deprecated and dangerous.",
13670
+ attackScenario: "An attacker could inject PHP code through the matched string to achieve remote code execution.",
13671
+ suggestedFix: "Use preg_replace_callback() instead of the /e modifier."
13672
+ },
13673
+ {
13674
+ regex: /\bunserialize\s*\(\s*\$(?:_GET|_POST|_REQUEST|_COOKIE)/,
13675
+ title: "PHP insecure deserialization of user input",
13676
+ vulnClass: "code-injection",
13677
+ baseSeverity: "high",
13678
+ description: "unserialize() called on user-controlled superglobal input \u2014 potential RCE via PHP object injection.",
13679
+ attackScenario: "An attacker could craft a malicious serialized PHP object to achieve remote code execution.",
13680
+ suggestedFix: "Use json_decode() instead of unserialize() for user input. If unserialize is necessary, use the allowed_classes option."
13681
+ },
13682
+ {
13683
+ regex: /\bextract\s*\(\s*\$(?:_GET|_POST|_REQUEST)/,
13684
+ title: "PHP extract() on superglobal input",
13685
+ vulnClass: "code-injection",
13686
+ baseSeverity: "high",
13687
+ description: "extract() on $_GET/$_POST/$_REQUEST overwrites local variables \u2014 can bypass security checks.",
13688
+ attackScenario: "An attacker could set arbitrary variables by crafting request parameters, potentially overwriting auth flags or config values.",
13689
+ suggestedFix: "Avoid extract() on user input. Access superglobals directly or use a whitelist of expected keys."
11978
13690
  }
11979
13691
  ];
11980
13692
  function shouldSkip(filePath) {
@@ -11991,7 +13703,7 @@ async function checkInjection(files, projectRoot) {
11991
13703
  if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
11992
13704
  let content;
11993
13705
  try {
11994
- content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
13706
+ content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
11995
13707
  } catch {
11996
13708
  continue;
11997
13709
  }
@@ -12029,8 +13741,8 @@ async function checkInjection(files, projectRoot) {
12029
13741
  }
12030
13742
 
12031
13743
  // src/security/checks/secrets.ts
12032
- import { readFileSync as readFileSync17 } from "fs";
12033
- import { join as join21 } from "path";
13744
+ import { readFileSync as readFileSync19 } from "fs";
13745
+ import { join as join23 } from "path";
12034
13746
  var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12035
13747
  var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
12036
13748
  var SECRET_PATTERNS = [
@@ -12063,7 +13775,7 @@ async function checkSecrets(files, projectRoot) {
12063
13775
  if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
12064
13776
  let content;
12065
13777
  try {
12066
- content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
13778
+ content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
12067
13779
  } catch {
12068
13780
  continue;
12069
13781
  }
@@ -12096,8 +13808,8 @@ async function checkSecrets(files, projectRoot) {
12096
13808
  }
12097
13809
 
12098
13810
  // src/security/checks/path-traversal.ts
12099
- import { readFileSync as readFileSync18 } from "fs";
12100
- import { join as join22 } from "path";
13811
+ import { readFileSync as readFileSync20 } from "fs";
13812
+ import { join as join24 } from "path";
12101
13813
  var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12102
13814
  var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
12103
13815
  var PATTERNS2 = [
@@ -12144,7 +13856,7 @@ async function checkPathTraversal(files, projectRoot) {
12144
13856
  if (shouldSkip3(file.filePath)) continue;
12145
13857
  let content;
12146
13858
  try {
12147
- content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
13859
+ content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
12148
13860
  } catch {
12149
13861
  continue;
12150
13862
  }
@@ -12186,8 +13898,8 @@ async function checkPathTraversal(files, projectRoot) {
12186
13898
  }
12187
13899
 
12188
13900
  // src/security/checks/auth.ts
12189
- import { readFileSync as readFileSync19 } from "fs";
12190
- import { join as join23 } from "path";
13901
+ import { readFileSync as readFileSync21 } from "fs";
13902
+ import { join as join25 } from "path";
12191
13903
  var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12192
13904
  function shouldSkip4(filePath) {
12193
13905
  return SKIP_DIRS4.some((d) => filePath.includes(d));
@@ -12203,7 +13915,7 @@ async function checkAuth(files, projectRoot) {
12203
13915
  if (shouldSkip4(file.filePath)) continue;
12204
13916
  let content;
12205
13917
  try {
12206
- content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
13918
+ content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
12207
13919
  } catch {
12208
13920
  continue;
12209
13921
  }
@@ -12294,8 +14006,8 @@ async function checkAuth(files, projectRoot) {
12294
14006
  }
12295
14007
 
12296
14008
  // src/security/checks/input-validation.ts
12297
- import { readFileSync as readFileSync20 } from "fs";
12298
- import { join as join24 } from "path";
14009
+ import { readFileSync as readFileSync22 } from "fs";
14010
+ import { join as join26 } from "path";
12299
14011
  var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12300
14012
  function shouldSkip5(filePath) {
12301
14013
  return SKIP_DIRS5.some((d) => filePath.includes(d));
@@ -12307,7 +14019,7 @@ async function checkInputValidation(files, projectRoot) {
12307
14019
  if (shouldSkip5(file.filePath)) continue;
12308
14020
  let content;
12309
14021
  try {
12310
- content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
14022
+ content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
12311
14023
  } catch {
12312
14024
  continue;
12313
14025
  }
@@ -12384,8 +14096,8 @@ async function checkInputValidation(files, projectRoot) {
12384
14096
  }
12385
14097
 
12386
14098
  // src/security/checks/information-disclosure.ts
12387
- import { readFileSync as readFileSync21 } from "fs";
12388
- import { join as join25 } from "path";
14099
+ import { readFileSync as readFileSync23 } from "fs";
14100
+ import { join as join27 } from "path";
12389
14101
  var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12390
14102
  function shouldSkip6(filePath) {
12391
14103
  return SKIP_DIRS6.some((d) => filePath.includes(d));
@@ -12397,7 +14109,7 @@ async function checkInformationDisclosure(files, projectRoot) {
12397
14109
  if (shouldSkip6(file.filePath)) continue;
12398
14110
  let content;
12399
14111
  try {
12400
- content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
14112
+ content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
12401
14113
  } catch {
12402
14114
  continue;
12403
14115
  }
@@ -12467,8 +14179,8 @@ async function checkInformationDisclosure(files, projectRoot) {
12467
14179
  }
12468
14180
 
12469
14181
  // src/security/checks/cryptography.ts
12470
- import { readFileSync as readFileSync22 } from "fs";
12471
- import { join as join26 } from "path";
14182
+ import { readFileSync as readFileSync24 } from "fs";
14183
+ import { join as join28 } from "path";
12472
14184
  var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12473
14185
  var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
12474
14186
  function shouldSkip7(filePath) {
@@ -12485,7 +14197,7 @@ async function checkCryptography(files, projectRoot) {
12485
14197
  if (shouldSkip7(file.filePath)) continue;
12486
14198
  let content;
12487
14199
  try {
12488
- content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
14200
+ content = readFileSync24(join28(projectRoot, file.filePath), "utf-8");
12489
14201
  } catch {
12490
14202
  continue;
12491
14203
  }
@@ -12613,6 +14325,136 @@ async function checkCryptography(files, projectRoot) {
12613
14325
  suggestedFix: "Load credentials from environment variables or a secure vault at runtime."
12614
14326
  });
12615
14327
  }
14328
+ if (/(?:val|var)\s+(?:password|secret|apiKey|api_key|token)\s*=\s*["']/i.test(line)) {
14329
+ findings.push({
14330
+ id: "",
14331
+ severity: "high",
14332
+ vulnerabilityClass: "cryptography",
14333
+ file: file.filePath,
14334
+ line: i + 1,
14335
+ title: "Hardcoded credentials in Kotlin source",
14336
+ description: "A password, secret, or API key is hardcoded as a string literal.",
14337
+ attackScenario: "An attacker with access to the binary or source could extract the credential.",
14338
+ suggestedFix: "Load credentials from environment variables or a secure vault at runtime."
14339
+ });
14340
+ }
14341
+ if (/\bRandom\s*\(\s*\)/.test(line) && isCryptoFile) {
14342
+ findings.push({
14343
+ id: "",
14344
+ severity: "medium",
14345
+ vulnerabilityClass: "cryptography",
14346
+ file: file.filePath,
14347
+ line: i + 1,
14348
+ title: "Insecure random in Kotlin security context",
14349
+ description: "kotlin.random.Random() is not cryptographically secure \u2014 its output can be predicted.",
14350
+ attackScenario: "An attacker could predict random values to forge tokens or bypass security checks.",
14351
+ suggestedFix: "Use java.security.SecureRandom for cryptographic purposes."
14352
+ });
14353
+ }
14354
+ if (/!!\s*\./.test(line) && isCryptoFile) {
14355
+ findings.push({
14356
+ id: "",
14357
+ severity: "medium",
14358
+ vulnerabilityClass: "cryptography",
14359
+ file: file.filePath,
14360
+ line: i + 1,
14361
+ title: "Not-null assertion (!!) in security-sensitive Kotlin code",
14362
+ description: "The !! operator can throw NullPointerException, potentially bypassing security checks.",
14363
+ attackScenario: "An attacker could trigger a null value to cause an exception that bypasses validation logic.",
14364
+ suggestedFix: "Use safe calls (?.) with proper null handling instead of !! assertions."
14365
+ });
14366
+ }
14367
+ if (/(?:val|var)\s+\w*[Uu]rl\w*\s*=\s*["']http:\/\/(?!(?:localhost|127\.))/.test(line)) {
14368
+ findings.push({
14369
+ id: "",
14370
+ severity: "medium",
14371
+ vulnerabilityClass: "cryptography",
14372
+ file: file.filePath,
14373
+ line: i + 1,
14374
+ title: "Hardcoded HTTP URL in Kotlin source",
14375
+ description: "An HTTP (not HTTPS) URL is hardcoded \u2014 data is transmitted unencrypted.",
14376
+ attackScenario: "An attacker on the network path could intercept, read, or modify data in transit.",
14377
+ suggestedFix: "Use HTTPS for all external URLs to ensure data confidentiality and integrity."
14378
+ });
14379
+ }
14380
+ if (/\bmd5\s*\(/.test(line) && /password|passwd|pass|pwd/i.test(line)) {
14381
+ findings.push({
14382
+ id: "",
14383
+ severity: "high",
14384
+ vulnerabilityClass: "cryptography",
14385
+ file: file.filePath,
14386
+ line: i + 1,
14387
+ title: "PHP md5() used for password hashing",
14388
+ description: "md5() is cryptographically broken and should never be used for password hashing.",
14389
+ attackScenario: "An attacker could crack MD5 password hashes in seconds using rainbow tables or GPU brute force.",
14390
+ suggestedFix: "Use password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID."
14391
+ });
14392
+ }
14393
+ if (/\bsha1\s*\(/.test(line) && /password|passwd|pass|pwd/i.test(line)) {
14394
+ findings.push({
14395
+ id: "",
14396
+ severity: "high",
14397
+ vulnerabilityClass: "cryptography",
14398
+ file: file.filePath,
14399
+ line: i + 1,
14400
+ title: "PHP sha1() used for password hashing",
14401
+ description: "SHA-1 has known collision attacks and should not be used for password hashing.",
14402
+ attackScenario: "An attacker could crack SHA-1 password hashes using precomputed tables.",
14403
+ suggestedFix: "Use password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID."
14404
+ });
14405
+ }
14406
+ if (/\bcrypt\s*\(\s*[^,]+,\s*['"][\$]?[12a-zA-Z]{0,3}['"]/.test(line)) {
14407
+ findings.push({
14408
+ id: "",
14409
+ severity: "medium",
14410
+ vulnerabilityClass: "cryptography",
14411
+ file: file.filePath,
14412
+ line: i + 1,
14413
+ title: "PHP crypt() with potentially weak salt",
14414
+ description: "crypt() with a short or weak salt may use DES or MD5 algorithm.",
14415
+ attackScenario: "An attacker could crack weakly-salted crypt() hashes using brute force.",
14416
+ suggestedFix: "Use password_hash() instead of crypt(). It automatically uses a strong algorithm and salt."
14417
+ });
14418
+ }
14419
+ if (/\bmcrypt_/.test(line)) {
14420
+ findings.push({
14421
+ id: "",
14422
+ severity: "high",
14423
+ vulnerabilityClass: "cryptography",
14424
+ file: file.filePath,
14425
+ line: i + 1,
14426
+ title: "PHP deprecated mcrypt_* function",
14427
+ description: "mcrypt extension was deprecated in PHP 7.1 and removed in PHP 7.2. It has known vulnerabilities.",
14428
+ attackScenario: "An attacker could exploit known weaknesses in mcrypt implementations.",
14429
+ suggestedFix: "Use openssl_encrypt()/openssl_decrypt() or the sodium extension (sodium_crypto_*)."
14430
+ });
14431
+ }
14432
+ if (/\b(?:rand|mt_rand)\s*\(/.test(line) && isCryptoFile) {
14433
+ findings.push({
14434
+ id: "",
14435
+ severity: "medium",
14436
+ vulnerabilityClass: "cryptography",
14437
+ file: file.filePath,
14438
+ line: i + 1,
14439
+ title: "PHP rand()/mt_rand() in security context",
14440
+ description: "rand() and mt_rand() are not cryptographically secure \u2014 their output can be predicted.",
14441
+ attackScenario: "An attacker could predict random values to forge tokens or bypass security checks.",
14442
+ suggestedFix: "Use random_bytes() or random_int() for cryptographic purposes."
14443
+ });
14444
+ }
14445
+ if (/\$(?:password|secret|api_?key|token)\s*=\s*['"][^'"]{4,}['"]/i.test(line)) {
14446
+ findings.push({
14447
+ id: "",
14448
+ severity: "high",
14449
+ vulnerabilityClass: "cryptography",
14450
+ file: file.filePath,
14451
+ line: i + 1,
14452
+ title: "Hardcoded credentials in PHP source",
14453
+ description: "A password, secret, or API key is hardcoded as a string literal.",
14454
+ attackScenario: "An attacker with access to the source could extract the credential.",
14455
+ suggestedFix: "Load credentials from environment variables using getenv() or $_ENV."
14456
+ });
14457
+ }
12616
14458
  }
12617
14459
  }
12618
14460
  } catch {
@@ -12621,8 +14463,8 @@ async function checkCryptography(files, projectRoot) {
12621
14463
  }
12622
14464
 
12623
14465
  // src/security/checks/frontend.ts
12624
- import { readFileSync as readFileSync23 } from "fs";
12625
- import { join as join27 } from "path";
14466
+ import { readFileSync as readFileSync25 } from "fs";
14467
+ import { join as join29 } from "path";
12626
14468
  var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12627
14469
  function shouldSkip8(filePath) {
12628
14470
  return SKIP_DIRS8.some((d) => filePath.includes(d));
@@ -12638,7 +14480,7 @@ async function checkFrontend(files, projectRoot) {
12638
14480
  if (!isFrontendFile(file.filePath)) continue;
12639
14481
  let content;
12640
14482
  try {
12641
- content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
14483
+ content = readFileSync25(join29(projectRoot, file.filePath), "utf-8");
12642
14484
  } catch {
12643
14485
  continue;
12644
14486
  }
@@ -13098,13 +14940,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
13098
14940
  };
13099
14941
  }
13100
14942
  function detectPackageManager(projectRoot) {
13101
- if (existsSync17(join28(projectRoot, "package.json"))) return "npm";
13102
- if (existsSync17(join28(projectRoot, "requirements.txt"))) return "pip";
13103
- if (existsSync17(join28(projectRoot, "pyproject.toml"))) return "pip";
13104
- if (existsSync17(join28(projectRoot, "Cargo.toml"))) return "cargo";
13105
- if (existsSync17(join28(projectRoot, "go.mod"))) return "go";
13106
- if (existsSync17(join28(projectRoot, "pom.xml"))) return "maven";
13107
- if (existsSync17(join28(projectRoot, "build.gradle")) || existsSync17(join28(projectRoot, "build.gradle.kts"))) return "gradle";
14943
+ if (existsSync19(join30(projectRoot, "package.json"))) return "npm";
14944
+ if (existsSync19(join30(projectRoot, "requirements.txt"))) return "pip";
14945
+ if (existsSync19(join30(projectRoot, "pyproject.toml"))) return "pip";
14946
+ if (existsSync19(join30(projectRoot, "Cargo.toml"))) return "cargo";
14947
+ if (existsSync19(join30(projectRoot, "go.mod"))) return "go";
14948
+ if (existsSync19(join30(projectRoot, "pom.xml"))) return "maven";
14949
+ if (existsSync19(join30(projectRoot, "build.gradle")) || existsSync19(join30(projectRoot, "build.gradle.kts"))) return "gradle";
13108
14950
  return "unknown";
13109
14951
  }
13110
14952