depwire-cli 1.0.4 → 1.0.7

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,9 @@ 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";
38
39
  const isCppBuild = entry === "CMakeLists.txt" || entry === "conanfile.txt" || entry === "vcpkg.json";
39
- if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isCppBuild) {
40
+ if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isCppBuild) {
40
41
  files.push(relative(rootDir, fullPath));
41
42
  }
42
43
  }
@@ -78,6 +79,8 @@ function findProjectRoot(startDir = process.cwd()) {
78
79
  // Java (Maven)
79
80
  "build.gradle",
80
81
  // Java (Gradle)
82
+ "build.gradle.kts",
83
+ // Kotlin (Gradle KTS)
81
84
  ".git"
82
85
  // Any git repo
83
86
  ];
@@ -111,11 +114,11 @@ function findProjectRoot(startDir = process.cwd()) {
111
114
  }
112
115
 
113
116
  // src/parser/index.ts
114
- import { readFileSync as readFileSync8, statSync as statSync5 } from "fs";
115
- import { join as join11, resolve as resolve5 } from "path";
117
+ import { readFileSync as readFileSync9, statSync as statSync6 } from "fs";
118
+ import { join as join12, resolve as resolve6 } from "path";
116
119
 
117
120
  // src/parser/detect.ts
118
- import { extname as extname6, basename as basename4 } from "path";
121
+ import { extname as extname7, basename as basename5 } from "path";
119
122
 
120
123
  // src/parser/wasm-init.ts
121
124
  import { Parser, Language } from "web-tree-sitter";
@@ -145,7 +148,8 @@ async function initParser() {
145
148
  "c": "tree-sitter-c.wasm",
146
149
  "c_sharp": "tree-sitter-c_sharp.wasm",
147
150
  "java": "tree-sitter-java.wasm",
148
- "cpp": "tree-sitter-cpp.wasm"
151
+ "cpp": "tree-sitter-cpp.wasm",
152
+ "kotlin": "tree-sitter-kotlin.wasm"
149
153
  };
150
154
  for (const [name, file] of Object.entries(grammarFiles)) {
151
155
  const wasmPath = path.join(grammarsDir, file);
@@ -4907,24 +4911,722 @@ function getCurrentSymbolId9(context) {
4907
4911
  if (context.currentScope.length === 0) return null;
4908
4912
  return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
4909
4913
  }
4910
- var cppParser = {
4911
- name: "cpp",
4912
- extensions: [
4913
- ".cpp",
4914
- ".cc",
4915
- ".cxx",
4916
- ".c++",
4917
- ".hpp",
4918
- ".hh",
4919
- ".hxx",
4920
- ".h++",
4921
- ".inl",
4922
- ".ipp",
4923
- "CMakeLists.txt",
4924
- "conanfile.txt",
4925
- "vcpkg.json"
4926
- ],
4927
- parseFile: parseCppFile
4914
+ var cppParser = {
4915
+ name: "cpp",
4916
+ extensions: [
4917
+ ".cpp",
4918
+ ".cc",
4919
+ ".cxx",
4920
+ ".c++",
4921
+ ".hpp",
4922
+ ".hh",
4923
+ ".hxx",
4924
+ ".h++",
4925
+ ".inl",
4926
+ ".ipp",
4927
+ "CMakeLists.txt",
4928
+ "conanfile.txt",
4929
+ "vcpkg.json"
4930
+ ],
4931
+ parseFile: parseCppFile
4932
+ };
4933
+
4934
+ // src/parser/kotlin.ts
4935
+ import { dirname as dirname10, join as join11, basename as basename4 } from "path";
4936
+ import { existsSync as existsSync11, readdirSync as readdirSync8, statSync as statSync5 } from "fs";
4937
+ function parseKotlinFile(filePath, sourceCode, projectRoot) {
4938
+ if (filePath.endsWith("build.gradle.kts")) {
4939
+ return parseGradleBuild2(filePath, sourceCode, projectRoot);
4940
+ }
4941
+ if (filePath.endsWith("build.gradle")) {
4942
+ return parseGradleBuild2(filePath, sourceCode, projectRoot);
4943
+ }
4944
+ if (filePath.endsWith("settings.gradle.kts") || filePath.endsWith("settings.gradle")) {
4945
+ return parseSettingsGradle(filePath, sourceCode, projectRoot);
4946
+ }
4947
+ const parser = getParser("kotlin");
4948
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
4949
+ const context = {
4950
+ filePath,
4951
+ projectRoot,
4952
+ sourceCode,
4953
+ symbols: [],
4954
+ edges: [],
4955
+ currentScope: [],
4956
+ currentClass: null,
4957
+ currentPackage: null,
4958
+ imports: /* @__PURE__ */ new Map(),
4959
+ isBuildFile: false,
4960
+ isScriptFile: filePath.endsWith(".kts")
4961
+ };
4962
+ walkNode10(tree.rootNode, context);
4963
+ return {
4964
+ filePath,
4965
+ symbols: context.symbols,
4966
+ edges: context.edges
4967
+ };
4968
+ }
4969
+ function walkNode10(node, context) {
4970
+ processNode10(node, context);
4971
+ for (let i = 0; i < node.childCount; i++) {
4972
+ const child = node.child(i);
4973
+ if (child) {
4974
+ walkNode10(child, context);
4975
+ }
4976
+ }
4977
+ }
4978
+ function processNode10(node, context) {
4979
+ switch (node.type) {
4980
+ case "package_header":
4981
+ processPackageHeader(node, context);
4982
+ break;
4983
+ case "import_header":
4984
+ processImportHeader(node, context);
4985
+ break;
4986
+ case "class_declaration":
4987
+ processClassDeclaration5(node, context);
4988
+ break;
4989
+ case "object_declaration":
4990
+ processObjectDeclaration(node, context);
4991
+ break;
4992
+ case "companion_object":
4993
+ processCompanionObject(node, context);
4994
+ break;
4995
+ case "function_declaration":
4996
+ processFunctionDeclaration4(node, context);
4997
+ break;
4998
+ case "property_declaration":
4999
+ processPropertyDeclaration2(node, context);
5000
+ break;
5001
+ case "secondary_constructor":
5002
+ processSecondaryConstructor(node, context);
5003
+ break;
5004
+ case "type_alias":
5005
+ processTypeAlias(node, context);
5006
+ break;
5007
+ case "call_expression":
5008
+ processCallExpression10(node, context);
5009
+ break;
5010
+ case "navigation_expression":
5011
+ processNavigationExpression(node, context);
5012
+ break;
5013
+ }
5014
+ }
5015
+ function processPackageHeader(node, context) {
5016
+ const ident = findDescendantByTypes2(node, ["identifier"]);
5017
+ if (!ident) return;
5018
+ const name = nodeText9(ident, context);
5019
+ const symbolId = `${context.filePath}::${name}`;
5020
+ context.symbols.push({
5021
+ id: symbolId,
5022
+ name,
5023
+ kind: "module",
5024
+ filePath: context.filePath,
5025
+ startLine: node.startPosition.row + 1,
5026
+ endLine: node.endPosition.row + 1,
5027
+ exported: true
5028
+ });
5029
+ context.currentPackage = name;
5030
+ }
5031
+ function processImportHeader(node, context) {
5032
+ const ident = findDescendantByTypes2(node, ["identifier"]);
5033
+ if (!ident) return;
5034
+ let importPath = nodeText9(ident, context);
5035
+ const text = nodeText9(node, context).trim();
5036
+ const isWildcard = text.endsWith(".*");
5037
+ if (isWildcard && !importPath.endsWith(".*")) {
5038
+ importPath = importPath + ".*";
5039
+ }
5040
+ const aliasMatch = text.match(/\bas\s+(\w+)/);
5041
+ const alias = aliasMatch ? aliasMatch[1] : null;
5042
+ const resolvedPath = resolveKotlinImport(importPath, context.filePath, context.projectRoot);
5043
+ if (resolvedPath) {
5044
+ const sourceId = `${context.filePath}::__file__`;
5045
+ const targetId = `${resolvedPath}::__file__`;
5046
+ context.edges.push({
5047
+ source: sourceId,
5048
+ target: targetId,
5049
+ kind: "imports",
5050
+ filePath: context.filePath,
5051
+ line: node.startPosition.row + 1
5052
+ });
5053
+ const parts = importPath.replace(/\.\*$/, "").split(".");
5054
+ if (!isWildcard) {
5055
+ const simpleName = alias || parts[parts.length - 1];
5056
+ context.imports.set(simpleName, `${resolvedPath}::${parts[parts.length - 1]}`);
5057
+ }
5058
+ }
5059
+ const symbolId = `${context.filePath}::import:${importPath}`;
5060
+ context.symbols.push({
5061
+ id: symbolId,
5062
+ name: importPath,
5063
+ kind: "import",
5064
+ filePath: context.filePath,
5065
+ startLine: node.startPosition.row + 1,
5066
+ endLine: node.endPosition.row + 1,
5067
+ exported: false
5068
+ });
5069
+ }
5070
+ function processClassDeclaration5(node, context) {
5071
+ const nameNode = findChildByType10(node, "type_identifier");
5072
+ if (!nameNode) return;
5073
+ let name = nodeText9(nameNode, context);
5074
+ const angleBracketIdx = name.indexOf("<");
5075
+ if (angleBracketIdx > 0) {
5076
+ name = name.substring(0, angleBracketIdx);
5077
+ }
5078
+ const text = nodeText9(node, context);
5079
+ const modifiers = getModifiers(node, context);
5080
+ let kind = "class";
5081
+ if (text.match(/\binterface\b/) && !text.match(/\bfun\s+interface\b/)) {
5082
+ kind = "interface";
5083
+ } else if (text.match(/\benum\s+class\b/)) {
5084
+ kind = "enum";
5085
+ } else if (text.match(/\bannotation\s+class\b/)) {
5086
+ kind = "interface";
5087
+ }
5088
+ const exported = modifiers.includes("public") || modifiers.includes("internal") || !modifiers.includes("private") && !modifiers.includes("protected");
5089
+ const scope = context.currentClass || void 0;
5090
+ const symbolId = `${context.filePath}::${name}`;
5091
+ context.symbols.push({
5092
+ id: symbolId,
5093
+ name,
5094
+ kind,
5095
+ filePath: context.filePath,
5096
+ startLine: node.startPosition.row + 1,
5097
+ endLine: node.endPosition.row + 1,
5098
+ exported,
5099
+ scope
5100
+ });
5101
+ const delegationSpecifiers = findChildByType10(node, "delegation_specifiers");
5102
+ if (delegationSpecifiers) {
5103
+ processDelegationSpecifiers(delegationSpecifiers, symbolId, context);
5104
+ }
5105
+ if (kind === "enum") {
5106
+ processEnumEntries(node, name, context);
5107
+ }
5108
+ const oldClass = context.currentClass;
5109
+ context.currentClass = name;
5110
+ context.currentScope.push(name);
5111
+ const body = findChildByType10(node, "class_body") || findChildByType10(node, "enum_class_body");
5112
+ if (body) {
5113
+ walkNode10(body, context);
5114
+ }
5115
+ context.currentScope.pop();
5116
+ context.currentClass = oldClass;
5117
+ }
5118
+ function processObjectDeclaration(node, context) {
5119
+ const nameNode = findChildByType10(node, "type_identifier");
5120
+ if (!nameNode) return;
5121
+ const name = nodeText9(nameNode, context);
5122
+ const modifiers = getModifiers(node, context);
5123
+ const exported = !modifiers.includes("private");
5124
+ const scope = context.currentClass || void 0;
5125
+ const symbolId = `${context.filePath}::${name}`;
5126
+ context.symbols.push({
5127
+ id: symbolId,
5128
+ name,
5129
+ kind: "class",
5130
+ // Objects are singletons, map to class
5131
+ filePath: context.filePath,
5132
+ startLine: node.startPosition.row + 1,
5133
+ endLine: node.endPosition.row + 1,
5134
+ exported,
5135
+ scope
5136
+ });
5137
+ const delegationSpecifiers = findChildByType10(node, "delegation_specifiers");
5138
+ if (delegationSpecifiers) {
5139
+ processDelegationSpecifiers(delegationSpecifiers, symbolId, context);
5140
+ }
5141
+ const oldClass = context.currentClass;
5142
+ context.currentClass = name;
5143
+ context.currentScope.push(name);
5144
+ const body = findChildByType10(node, "class_body");
5145
+ if (body) {
5146
+ walkNode10(body, context);
5147
+ }
5148
+ context.currentScope.pop();
5149
+ context.currentClass = oldClass;
5150
+ }
5151
+ function processCompanionObject(node, context) {
5152
+ const nameNode = findChildByType10(node, "type_identifier");
5153
+ const name = nameNode ? nodeText9(nameNode, context) : "Companion";
5154
+ const scope = context.currentClass || void 0;
5155
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5156
+ context.symbols.push({
5157
+ id: symbolId,
5158
+ name,
5159
+ kind: "class",
5160
+ filePath: context.filePath,
5161
+ startLine: node.startPosition.row + 1,
5162
+ endLine: node.endPosition.row + 1,
5163
+ exported: true,
5164
+ scope
5165
+ });
5166
+ const oldClass = context.currentClass;
5167
+ context.currentClass = scope ? `${scope}.${name}` : name;
5168
+ context.currentScope.push(context.currentClass);
5169
+ const body = findChildByType10(node, "class_body");
5170
+ if (body) {
5171
+ walkNode10(body, context);
5172
+ }
5173
+ context.currentScope.pop();
5174
+ context.currentClass = oldClass;
5175
+ }
5176
+ function processDelegationSpecifiers(node, sourceId, context) {
5177
+ for (let i = 0; i < node.childCount; i++) {
5178
+ const child = node.child(i);
5179
+ if (!child) continue;
5180
+ const typeName = extractTypeName5(child, context);
5181
+ if (typeName) {
5182
+ const baseId = resolveSymbol9(typeName, context);
5183
+ if (baseId) {
5184
+ const edgeKind = typeName.startsWith("I") && typeName.length > 1 && typeName[1] === typeName[1].toUpperCase() ? "implements" : "inherits";
5185
+ context.edges.push({
5186
+ source: sourceId,
5187
+ target: baseId,
5188
+ kind: edgeKind,
5189
+ filePath: context.filePath,
5190
+ line: child.startPosition.row + 1
5191
+ });
5192
+ }
5193
+ }
5194
+ }
5195
+ }
5196
+ function processEnumEntries(node, enumName, context) {
5197
+ const body = findChildByType10(node, "enum_class_body");
5198
+ if (!body) return;
5199
+ const entries = findChildrenByType4(body, "enum_entry");
5200
+ for (const entry of entries) {
5201
+ const nameNode = findChildByType10(entry, "simple_identifier");
5202
+ if (!nameNode) continue;
5203
+ const constName = nodeText9(nameNode, context);
5204
+ const constId = `${context.filePath}::${enumName}.${constName}`;
5205
+ context.symbols.push({
5206
+ id: constId,
5207
+ name: constName,
5208
+ kind: "constant",
5209
+ filePath: context.filePath,
5210
+ startLine: entry.startPosition.row + 1,
5211
+ endLine: entry.endPosition.row + 1,
5212
+ exported: true,
5213
+ scope: enumName
5214
+ });
5215
+ }
5216
+ }
5217
+ function processFunctionDeclaration4(node, context) {
5218
+ const nameNode = findChildByType10(node, "simple_identifier");
5219
+ if (!nameNode) return;
5220
+ const name = nodeText9(nameNode, context);
5221
+ const modifiers = getModifiers(node, context);
5222
+ const exported = !modifiers.includes("private");
5223
+ const scope = context.currentClass || void 0;
5224
+ const text = nodeText9(node, context);
5225
+ const isExtension = text.match(/fun\s+[\w.<>, ]+\./) !== null;
5226
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5227
+ context.symbols.push({
5228
+ id: symbolId,
5229
+ name,
5230
+ kind: context.currentClass ? "method" : "function",
5231
+ filePath: context.filePath,
5232
+ startLine: node.startPosition.row + 1,
5233
+ endLine: node.endPosition.row + 1,
5234
+ exported,
5235
+ scope
5236
+ });
5237
+ const scopeName = scope ? `${scope}.${name}` : name;
5238
+ context.currentScope.push(scopeName);
5239
+ const body = findChildByType10(node, "function_body");
5240
+ if (body) {
5241
+ walkNode10(body, context);
5242
+ }
5243
+ context.currentScope.pop();
5244
+ }
5245
+ function processPropertyDeclaration2(node, context) {
5246
+ const varDecl = findChildByType10(node, "variable_declaration");
5247
+ if (!varDecl) return;
5248
+ const nameNode = findChildByType10(varDecl, "simple_identifier");
5249
+ if (!nameNode) return;
5250
+ const name = nodeText9(nameNode, context);
5251
+ const modifiers = getModifiers(node, context);
5252
+ const exported = !modifiers.includes("private");
5253
+ const scope = context.currentClass || void 0;
5254
+ const text = nodeText9(node, context);
5255
+ const isConst = modifiers.includes("const") || text.match(/\bval\b/) !== null && text.match(/\bconst\b/) !== null;
5256
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
5257
+ context.symbols.push({
5258
+ id: symbolId,
5259
+ name,
5260
+ kind: isConst ? "constant" : "property",
5261
+ filePath: context.filePath,
5262
+ startLine: node.startPosition.row + 1,
5263
+ endLine: node.endPosition.row + 1,
5264
+ exported,
5265
+ scope
5266
+ });
5267
+ }
5268
+ function processSecondaryConstructor(node, context) {
5269
+ const scope = context.currentClass || void 0;
5270
+ if (!scope) return;
5271
+ const name = "constructor";
5272
+ const symbolId = `${context.filePath}::${scope}.${name}:${node.startPosition.row + 1}`;
5273
+ context.symbols.push({
5274
+ id: symbolId,
5275
+ name,
5276
+ kind: "method",
5277
+ filePath: context.filePath,
5278
+ startLine: node.startPosition.row + 1,
5279
+ endLine: node.endPosition.row + 1,
5280
+ exported: true,
5281
+ scope
5282
+ });
5283
+ const scopeName = `${scope}.${name}`;
5284
+ context.currentScope.push(scopeName);
5285
+ const body = findChildByType10(node, "function_body");
5286
+ if (body) {
5287
+ walkNode10(body, context);
5288
+ }
5289
+ context.currentScope.pop();
5290
+ }
5291
+ function processTypeAlias(node, context) {
5292
+ const nameNode = findChildByType10(node, "type_identifier");
5293
+ if (!nameNode) return;
5294
+ const name = nodeText9(nameNode, context);
5295
+ const symbolId = `${context.filePath}::${name}`;
5296
+ context.symbols.push({
5297
+ id: symbolId,
5298
+ name,
5299
+ kind: "type_alias",
5300
+ filePath: context.filePath,
5301
+ startLine: node.startPosition.row + 1,
5302
+ endLine: node.endPosition.row + 1,
5303
+ exported: true
5304
+ });
5305
+ }
5306
+ function processCallExpression10(node, context) {
5307
+ if (context.currentScope.length === 0) return;
5308
+ const firstChild = node.child(0);
5309
+ if (!firstChild) return;
5310
+ let calleeName = null;
5311
+ if (firstChild.type === "simple_identifier") {
5312
+ calleeName = nodeText9(firstChild, context);
5313
+ } else if (firstChild.type === "navigation_expression") {
5314
+ const suffix = findChildByType10(firstChild, "navigation_suffix");
5315
+ if (suffix) {
5316
+ const ident = findChildByType10(suffix, "simple_identifier");
5317
+ if (ident) calleeName = nodeText9(ident, context);
5318
+ }
5319
+ if (!calleeName) {
5320
+ for (let i = firstChild.childCount - 1; i >= 0; i--) {
5321
+ const child = firstChild.child(i);
5322
+ if (child && child.type === "simple_identifier") {
5323
+ calleeName = nodeText9(child, context);
5324
+ break;
5325
+ }
5326
+ }
5327
+ }
5328
+ }
5329
+ if (!calleeName) return;
5330
+ const builtins = /* @__PURE__ */ new Set([
5331
+ "println",
5332
+ "print",
5333
+ "toString",
5334
+ "equals",
5335
+ "hashCode",
5336
+ "let",
5337
+ "apply",
5338
+ "also",
5339
+ "run",
5340
+ "with",
5341
+ "takeIf",
5342
+ "takeUnless",
5343
+ "repeat",
5344
+ "require",
5345
+ "check",
5346
+ "error",
5347
+ "TODO",
5348
+ "listOf",
5349
+ "mapOf",
5350
+ "setOf",
5351
+ "arrayOf",
5352
+ "mutableListOf",
5353
+ "mutableMapOf",
5354
+ "mutableSetOf",
5355
+ "emptyList",
5356
+ "emptyMap",
5357
+ "emptySet",
5358
+ "to",
5359
+ "Pair",
5360
+ "Triple",
5361
+ "lazy",
5362
+ "synchronized",
5363
+ "map",
5364
+ "filter",
5365
+ "forEach",
5366
+ "flatMap",
5367
+ "fold",
5368
+ "reduce",
5369
+ "any",
5370
+ "all",
5371
+ "none",
5372
+ "find",
5373
+ "first",
5374
+ "last",
5375
+ "count",
5376
+ "sum",
5377
+ "average",
5378
+ "sortedBy",
5379
+ "groupBy",
5380
+ "associate",
5381
+ "zip",
5382
+ "joinToString",
5383
+ "getOrDefault",
5384
+ "getOrElse",
5385
+ "getOrPut",
5386
+ "contains",
5387
+ "containsKey",
5388
+ "add",
5389
+ "remove",
5390
+ "clear",
5391
+ "size",
5392
+ "isEmpty",
5393
+ "isNotEmpty"
5394
+ ]);
5395
+ if (builtins.has(calleeName)) return;
5396
+ const callerId = getCurrentSymbolId10(context);
5397
+ if (!callerId) return;
5398
+ const calleeId = resolveSymbol9(calleeName, context);
5399
+ if (calleeId) {
5400
+ context.edges.push({
5401
+ source: callerId,
5402
+ target: calleeId,
5403
+ kind: "calls",
5404
+ filePath: context.filePath,
5405
+ line: node.startPosition.row + 1
5406
+ });
5407
+ }
5408
+ }
5409
+ function processNavigationExpression(node, context) {
5410
+ }
5411
+ function parseGradleBuild2(filePath, sourceCode, projectRoot) {
5412
+ const symbols = [];
5413
+ const edges = [];
5414
+ const lines = sourceCode.split("\n");
5415
+ const projectName = basename4(dirname10(join11(projectRoot, filePath)));
5416
+ symbols.push({
5417
+ id: `${filePath}::${projectName}`,
5418
+ name: projectName,
5419
+ kind: "module",
5420
+ filePath,
5421
+ startLine: 1,
5422
+ endLine: lines.length,
5423
+ exported: true
5424
+ });
5425
+ for (let i = 0; i < lines.length; i++) {
5426
+ const line = lines[i].trim();
5427
+ const lineNum = i + 1;
5428
+ const depMatch = line.match(
5429
+ /(?:implementation|api|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|kapt|ksp|annotationProcessor)\s*[\(]?\s*['"]([^'"]+)['"]\s*[\)]?/
5430
+ );
5431
+ if (depMatch) {
5432
+ const depCoord = depMatch[1];
5433
+ if (!depCoord.startsWith(":")) {
5434
+ symbols.push({
5435
+ id: `${filePath}::dep:${depCoord}`,
5436
+ name: depCoord,
5437
+ kind: "import",
5438
+ filePath,
5439
+ startLine: lineNum,
5440
+ endLine: lineNum,
5441
+ exported: false
5442
+ });
5443
+ }
5444
+ }
5445
+ const projectMatch = line.match(/project\s*\(\s*['":]+([^'")\s]+)['"]*\s*\)/);
5446
+ if (projectMatch) {
5447
+ const moduleName = projectMatch[1].replace(/^:/, "");
5448
+ const candidates = [
5449
+ join11(moduleName, "build.gradle.kts"),
5450
+ join11(moduleName, "build.gradle")
5451
+ ];
5452
+ for (const candidate of candidates) {
5453
+ if (existsSync11(join11(projectRoot, candidate))) {
5454
+ edges.push({
5455
+ source: `${filePath}::__file__`,
5456
+ target: `${candidate}::__file__`,
5457
+ kind: "imports",
5458
+ filePath,
5459
+ line: lineNum
5460
+ });
5461
+ break;
5462
+ }
5463
+ }
5464
+ }
5465
+ }
5466
+ return { filePath, symbols, edges };
5467
+ }
5468
+ function parseSettingsGradle(filePath, sourceCode, projectRoot) {
5469
+ const symbols = [];
5470
+ const edges = [];
5471
+ const lines = sourceCode.split("\n");
5472
+ symbols.push({
5473
+ id: `${filePath}::settings`,
5474
+ name: "settings",
5475
+ kind: "module",
5476
+ filePath,
5477
+ startLine: 1,
5478
+ endLine: lines.length,
5479
+ exported: true
5480
+ });
5481
+ for (let i = 0; i < lines.length; i++) {
5482
+ const line = lines[i].trim();
5483
+ const lineNum = i + 1;
5484
+ const includeMatches = line.matchAll(/['"]:([^'"]+)['"]/g);
5485
+ for (const match of includeMatches) {
5486
+ const moduleName = match[1];
5487
+ const candidates = [
5488
+ join11(moduleName, "build.gradle.kts"),
5489
+ join11(moduleName, "build.gradle")
5490
+ ];
5491
+ for (const candidate of candidates) {
5492
+ if (existsSync11(join11(projectRoot, candidate))) {
5493
+ edges.push({
5494
+ source: `${filePath}::__file__`,
5495
+ target: `${candidate}::__file__`,
5496
+ kind: "imports",
5497
+ filePath,
5498
+ line: lineNum
5499
+ });
5500
+ break;
5501
+ }
5502
+ }
5503
+ }
5504
+ }
5505
+ return { filePath, symbols, edges };
5506
+ }
5507
+ function resolveKotlinImport(importPath, currentFile, projectRoot) {
5508
+ const cleanPath2 = importPath.replace(/\.\*$/, "");
5509
+ const parts = cleanPath2.split(".");
5510
+ const className = parts[parts.length - 1];
5511
+ const packagePath = parts.slice(0, -1).join("/");
5512
+ const sourceRoots = [
5513
+ "",
5514
+ "src/main/kotlin",
5515
+ "src/main/java",
5516
+ // Kotlin can live in java source dirs
5517
+ "src",
5518
+ "app/src/main/kotlin",
5519
+ "app/src/main/java"
5520
+ ];
5521
+ for (const root of sourceRoots) {
5522
+ for (const ext of [".kt", ".java"]) {
5523
+ const filePath = packagePath ? join11(packagePath, className + ext) : className + ext;
5524
+ const candidate = root ? join11(root, filePath) : filePath;
5525
+ const fullPath = join11(projectRoot, candidate);
5526
+ if (existsSync11(fullPath)) {
5527
+ return candidate;
5528
+ }
5529
+ }
5530
+ }
5531
+ if (importPath.endsWith(".*")) {
5532
+ const dirPath = cleanPath2.replace(/\./g, "/");
5533
+ for (const root of sourceRoots) {
5534
+ const candidate = root ? join11(root, dirPath) : dirPath;
5535
+ const fullPath = join11(projectRoot, candidate);
5536
+ if (existsSync11(fullPath)) {
5537
+ try {
5538
+ const stats = statSync5(fullPath);
5539
+ if (stats.isDirectory()) {
5540
+ const ktFiles = readdirSync8(fullPath).filter((f) => f.endsWith(".kt"));
5541
+ if (ktFiles.length > 0) {
5542
+ return join11(candidate, ktFiles[0]);
5543
+ }
5544
+ }
5545
+ } catch {
5546
+ }
5547
+ }
5548
+ }
5549
+ }
5550
+ return null;
5551
+ }
5552
+ function resolveSymbol9(name, context) {
5553
+ if (context.imports.has(name)) {
5554
+ return context.imports.get(name) || null;
5555
+ }
5556
+ const currentFileId = `${context.filePath}::${name}`;
5557
+ if (context.symbols.find((s) => s.id === currentFileId)) {
5558
+ return currentFileId;
5559
+ }
5560
+ if (context.currentClass) {
5561
+ const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
5562
+ if (context.symbols.find((s) => s.id === classMethodId)) {
5563
+ return classMethodId;
5564
+ }
5565
+ }
5566
+ return null;
5567
+ }
5568
+ function getModifiers(node, context) {
5569
+ const modifiers = [];
5570
+ const modList = findChildByType10(node, "modifiers");
5571
+ if (modList) {
5572
+ for (let i = 0; i < modList.childCount; i++) {
5573
+ const child = modList.child(i);
5574
+ if (child) {
5575
+ const text = nodeText9(child, context).trim();
5576
+ if (text) modifiers.push(text);
5577
+ }
5578
+ }
5579
+ }
5580
+ return modifiers;
5581
+ }
5582
+ function extractTypeName5(node, context) {
5583
+ const text = nodeText9(node, context).trim();
5584
+ if (!text || text === "," || text === ":") return null;
5585
+ let name = text;
5586
+ const angleBracketIdx = name.indexOf("<");
5587
+ if (angleBracketIdx > 0) name = name.substring(0, angleBracketIdx);
5588
+ const parenIdx = name.indexOf("(");
5589
+ if (parenIdx > 0) name = name.substring(0, parenIdx);
5590
+ const dotIdx = name.lastIndexOf(".");
5591
+ name = dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
5592
+ return name.trim() || null;
5593
+ }
5594
+ function findChildByType10(node, type) {
5595
+ for (let i = 0; i < node.childCount; i++) {
5596
+ const child = node.child(i);
5597
+ if (child && child.type === type) return child;
5598
+ }
5599
+ return null;
5600
+ }
5601
+ function findChildrenByType4(node, type) {
5602
+ const results = [];
5603
+ for (let i = 0; i < node.childCount; i++) {
5604
+ const child = node.child(i);
5605
+ if (child && child.type === type) results.push(child);
5606
+ }
5607
+ return results;
5608
+ }
5609
+ function findDescendantByTypes2(node, types) {
5610
+ for (let i = 0; i < node.childCount; i++) {
5611
+ const child = node.child(i);
5612
+ if (!child) continue;
5613
+ if (types.includes(child.type)) return child;
5614
+ const found = findDescendantByTypes2(child, types);
5615
+ if (found) return found;
5616
+ }
5617
+ return null;
5618
+ }
5619
+ function nodeText9(node, context) {
5620
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
5621
+ }
5622
+ function getCurrentSymbolId10(context) {
5623
+ if (context.currentScope.length === 0) return null;
5624
+ return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
5625
+ }
5626
+ var kotlinParser = {
5627
+ name: "kotlin",
5628
+ extensions: [".kt", ".kts", "build.gradle.kts", "settings.gradle.kts", "settings.gradle"],
5629
+ parseFile: parseKotlinFile
4928
5630
  };
4929
5631
 
4930
5632
  // src/parser/detect.ts
@@ -4937,12 +5639,13 @@ var parsers = [
4937
5639
  cParser,
4938
5640
  csharpParser,
4939
5641
  javaParser,
4940
- cppParser
5642
+ cppParser,
5643
+ kotlinParser
4941
5644
  ];
4942
5645
  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
5646
  function getParserForFile(filePath, content) {
4944
- const ext = extname6(filePath).toLowerCase();
4945
- const fileName = basename4(filePath);
5647
+ const ext = extname7(filePath).toLowerCase();
5648
+ const fileName = basename5(filePath);
4946
5649
  if (ext === ".h" && content) {
4947
5650
  if (CPP_KEYWORDS.test(content)) {
4948
5651
  return cppParser;
@@ -4960,7 +5663,7 @@ import { minimatch } from "minimatch";
4960
5663
  var MAX_FILE_SIZE = 1e6;
4961
5664
  function shouldParseFile(fullPath) {
4962
5665
  try {
4963
- const stats = statSync5(fullPath);
5666
+ const stats = statSync6(fullPath);
4964
5667
  if (stats.size > MAX_FILE_SIZE) {
4965
5668
  console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
4966
5669
  return false;
@@ -4978,8 +5681,8 @@ async function parseProject(projectRoot, options) {
4978
5681
  let errorFiles = 0;
4979
5682
  for (const file of files) {
4980
5683
  try {
4981
- const fullPath = join11(projectRoot, file);
4982
- if (!resolve5(fullPath).startsWith(resolve5(projectRoot))) {
5684
+ const fullPath = join12(projectRoot, file);
5685
+ if (!resolve6(fullPath).startsWith(resolve6(projectRoot))) {
4983
5686
  skippedFiles++;
4984
5687
  continue;
4985
5688
  }
@@ -5002,7 +5705,7 @@ async function parseProject(projectRoot, options) {
5002
5705
  if (options?.verbose) {
5003
5706
  console.error(`[Parser] Parsing: ${file}`);
5004
5707
  }
5005
- const sourceCode = readFileSync8(fullPath, "utf-8");
5708
+ const sourceCode = readFileSync9(fullPath, "utf-8");
5006
5709
  const parser = getParserForFile(file, sourceCode);
5007
5710
  if (!parser) {
5008
5711
  console.error(`No parser found for file: ${file}`);
@@ -5031,8 +5734,8 @@ async function parseProject(projectRoot, options) {
5031
5734
  }
5032
5735
 
5033
5736
  // src/cross-language/detectors/rest-api.ts
5034
- import { readFileSync as readFileSync9 } from "fs";
5035
- import { join as join12, resolve as resolve6 } from "path";
5737
+ import { readFileSync as readFileSync10 } from "fs";
5738
+ import { join as join13, resolve as resolve7 } from "path";
5036
5739
  function getLanguage(filePath) {
5037
5740
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
5038
5741
  if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
@@ -5040,6 +5743,7 @@ function getLanguage(filePath) {
5040
5743
  if (filePath.endsWith(".go")) return "go";
5041
5744
  if (filePath.endsWith(".cs") || filePath.endsWith(".csx")) return "csharp";
5042
5745
  if (filePath.endsWith(".java")) return "java";
5746
+ if (filePath.endsWith(".kt") || filePath.endsWith(".kts")) return "kotlin";
5043
5747
  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
5748
  return "unknown";
5045
5749
  }
@@ -5294,6 +5998,110 @@ function extractRouteDefinitions(source, filePath) {
5294
5998
  });
5295
5999
  }
5296
6000
  }
6001
+ if (lang === "kotlin") {
6002
+ const springMethodMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
6003
+ if (springMethodMatch) {
6004
+ const method = springMethodMatch[1].toUpperCase();
6005
+ let path6 = springMethodMatch[2];
6006
+ const classPrefix = findClassLevelPrefix(source);
6007
+ if (classPrefix) path6 = classPrefix + path6;
6008
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6009
+ routes.push({
6010
+ method,
6011
+ path: path6,
6012
+ normalizedPath: normalizePath(path6),
6013
+ file: filePath,
6014
+ line: i + 1
6015
+ });
6016
+ }
6017
+ if (!springMethodMatch) {
6018
+ const springNoPathMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*$/);
6019
+ if (springNoPathMatch) {
6020
+ const method = springNoPathMatch[1].toUpperCase();
6021
+ const classPrefix = findClassLevelPrefix(source);
6022
+ if (classPrefix) {
6023
+ routes.push({
6024
+ method,
6025
+ path: classPrefix,
6026
+ normalizedPath: normalizePath(classPrefix),
6027
+ file: filePath,
6028
+ line: i + 1
6029
+ });
6030
+ }
6031
+ }
6032
+ }
6033
+ const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*(?:\[)?\s*)?["']([^"']+)["']/);
6034
+ if (requestMappingMatch) {
6035
+ let path6 = requestMappingMatch[1];
6036
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6037
+ const methodMatch = line.match(/method\s*=\s*\[?\s*RequestMethod\.(\w+)/);
6038
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
6039
+ routes.push({
6040
+ method,
6041
+ path: path6,
6042
+ normalizedPath: normalizePath(path6),
6043
+ file: filePath,
6044
+ line: i + 1
6045
+ });
6046
+ }
6047
+ const ktorMatch = line.match(/\b(get|post|put|delete|patch|head|options)\s*\(\s*["']([^"']+)["']\s*\)/);
6048
+ if (ktorMatch) {
6049
+ const path6 = ktorMatch[2];
6050
+ if (path6.startsWith("/")) {
6051
+ routes.push({
6052
+ method: ktorMatch[1].toUpperCase(),
6053
+ path: path6,
6054
+ normalizedPath: normalizePath(path6),
6055
+ file: filePath,
6056
+ line: i + 1
6057
+ });
6058
+ }
6059
+ }
6060
+ const ktorRouteMatch = line.match(/\broute\s*\(\s*["']([^"']+)["']\s*\)/);
6061
+ if (ktorRouteMatch) {
6062
+ const path6 = ktorRouteMatch[1];
6063
+ if (path6.startsWith("/")) {
6064
+ routes.push({
6065
+ method: "ANY",
6066
+ path: path6,
6067
+ normalizedPath: normalizePath(path6),
6068
+ file: filePath,
6069
+ line: i + 1
6070
+ });
6071
+ }
6072
+ }
6073
+ const resourceMatch = line.match(/@Resource\s*\(\s*["']([^"']+)["']\s*\)/);
6074
+ if (resourceMatch) {
6075
+ const path6 = resourceMatch[1];
6076
+ if (path6.startsWith("/")) {
6077
+ routes.push({
6078
+ method: "ANY",
6079
+ path: path6,
6080
+ normalizedPath: normalizePath(path6),
6081
+ file: filePath,
6082
+ line: i + 1
6083
+ });
6084
+ }
6085
+ }
6086
+ const http4kMatch = line.match(/["']([^"']+)["']\s*bind\s*(GET|POST|PUT|DELETE|PATCH)/);
6087
+ if (http4kMatch) {
6088
+ const path6 = http4kMatch[1];
6089
+ if (path6.startsWith("/")) {
6090
+ routes.push({
6091
+ method: http4kMatch[2].toUpperCase(),
6092
+ path: path6,
6093
+ normalizedPath: normalizePath(path6),
6094
+ file: filePath,
6095
+ line: i + 1
6096
+ });
6097
+ }
6098
+ }
6099
+ const retrofitMatch = line.match(/@(GET|POST|PUT|DELETE|PATCH|HEAD)\s*\(\s*["']([^"']+)["']\s*\)/);
6100
+ if (retrofitMatch) {
6101
+ let path6 = retrofitMatch[2];
6102
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6103
+ }
6104
+ }
5297
6105
  if (lang === "cpp") {
5298
6106
  const crowMatch = line.match(/CROW_ROUTE\s*\(\s*\w+\s*,\s*"([^"]+)"/);
5299
6107
  if (crowMatch) {
@@ -5408,11 +6216,11 @@ function detectRestApiEdges(files, projectRoot) {
5408
6216
  const allCalls = [];
5409
6217
  const allRoutes = [];
5410
6218
  for (const file of files) {
5411
- const fullPath = join12(projectRoot, file.filePath);
5412
- if (!resolve6(fullPath).startsWith(resolve6(projectRoot))) continue;
6219
+ const fullPath = join13(projectRoot, file.filePath);
6220
+ if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) continue;
5413
6221
  let source;
5414
6222
  try {
5415
- source = readFileSync9(fullPath, "utf-8");
6223
+ source = readFileSync10(fullPath, "utf-8");
5416
6224
  } catch {
5417
6225
  continue;
5418
6226
  }
@@ -5420,6 +6228,18 @@ function detectRestApiEdges(files, projectRoot) {
5420
6228
  if (lang === "typescript" || lang === "javascript") {
5421
6229
  allCalls.push(...extractHttpCalls(source, file.filePath));
5422
6230
  }
6231
+ if (lang === "kotlin") {
6232
+ const kotlinLines = source.split("\n");
6233
+ for (let i = 0; i < kotlinLines.length; i++) {
6234
+ const line = kotlinLines[i];
6235
+ const retrofitMatch = line.match(/@(GET|POST|PUT|DELETE|PATCH|HEAD)\s*\(\s*["']([^"']+)["']\s*\)/);
6236
+ if (retrofitMatch) {
6237
+ let path6 = retrofitMatch[2];
6238
+ if (!path6.startsWith("/")) path6 = "/" + path6;
6239
+ allCalls.push({ method: retrofitMatch[1].toUpperCase(), path: path6, file: file.filePath, line: i + 1 });
6240
+ }
6241
+ }
6242
+ }
5423
6243
  allRoutes.push(...extractRouteDefinitions(source, file.filePath));
5424
6244
  }
5425
6245
  for (const call of allCalls) {
@@ -5448,8 +6268,8 @@ function detectRestApiEdges(files, projectRoot) {
5448
6268
  }
5449
6269
 
5450
6270
  // 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";
6271
+ import { readFileSync as readFileSync11 } from "fs";
6272
+ import { join as join14, resolve as resolve8, basename as basename6 } from "path";
5453
6273
  var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
5454
6274
  function getLanguage2(filePath) {
5455
6275
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
@@ -5548,16 +6368,16 @@ function detectSubprocessEdges(files, projectRoot) {
5548
6368
  const knownFiles = new Set(files.map((f) => f.filePath));
5549
6369
  const basenameMap = /* @__PURE__ */ new Map();
5550
6370
  for (const f of files) {
5551
- const base = basename5(f.filePath);
6371
+ const base = basename6(f.filePath);
5552
6372
  if (!basenameMap.has(base)) basenameMap.set(base, []);
5553
6373
  basenameMap.get(base).push(f.filePath);
5554
6374
  }
5555
6375
  for (const file of files) {
5556
- const fullPath = join13(projectRoot, file.filePath);
5557
- if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) continue;
6376
+ const fullPath = join14(projectRoot, file.filePath);
6377
+ if (!resolve8(fullPath).startsWith(resolve8(projectRoot))) continue;
5558
6378
  let source;
5559
6379
  try {
5560
- source = readFileSync10(fullPath, "utf-8");
6380
+ source = readFileSync11(fullPath, "utf-8");
5561
6381
  } catch {
5562
6382
  continue;
5563
6383
  }
@@ -5569,7 +6389,7 @@ function detectSubprocessEdges(files, projectRoot) {
5569
6389
  targetFile = call.calledFile;
5570
6390
  confidence = "high";
5571
6391
  } else {
5572
- const base = basename5(call.calledFile);
6392
+ const base = basename6(call.calledFile);
5573
6393
  const candidates = basenameMap.get(base);
5574
6394
  if (candidates && candidates.length > 0) {
5575
6395
  const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
@@ -5948,7 +6768,7 @@ function getArchitectureSummary(graph) {
5948
6768
  }
5949
6769
 
5950
6770
  // src/health/metrics.ts
5951
- import { dirname as dirname10 } from "path";
6771
+ import { dirname as dirname11 } from "path";
5952
6772
  function scoreToGrade(score) {
5953
6773
  if (score >= 90) return "A";
5954
6774
  if (score >= 80) return "B";
@@ -5981,8 +6801,8 @@ function calculateCouplingScore(graph) {
5981
6801
  totalEdges++;
5982
6802
  fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
5983
6803
  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];
6804
+ const sourceDir = dirname11(sourceAttrs.filePath).split("/")[0];
6805
+ const targetDir = dirname11(targetAttrs.filePath).split("/")[0];
5986
6806
  if (sourceDir !== targetDir) {
5987
6807
  crossDirEdges++;
5988
6808
  }
@@ -6029,8 +6849,8 @@ function calculateCohesionScore(graph) {
6029
6849
  const sourceAttrs = graph.getNodeAttributes(source);
6030
6850
  const targetAttrs = graph.getNodeAttributes(target);
6031
6851
  if (sourceAttrs.filePath !== targetAttrs.filePath) {
6032
- const sourceDir = dirname10(sourceAttrs.filePath);
6033
- const targetDir = dirname10(targetAttrs.filePath);
6852
+ const sourceDir = dirname11(sourceAttrs.filePath);
6853
+ const targetDir = dirname11(targetAttrs.filePath);
6034
6854
  if (!dirEdges.has(sourceDir)) {
6035
6855
  dirEdges.set(sourceDir, { internal: 0, total: 0 });
6036
6856
  }
@@ -6312,8 +7132,8 @@ function calculateDepthScore(graph) {
6312
7132
  }
6313
7133
 
6314
7134
  // 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";
7135
+ import { readFileSync as readFileSync12, writeFileSync, existsSync as existsSync12, mkdirSync } from "fs";
7136
+ import { dirname as dirname12, resolve as resolve9 } from "path";
6317
7137
  function calculateHealthScore(graph, projectRoot) {
6318
7138
  const coupling = calculateCouplingScore(graph);
6319
7139
  const cohesion = calculateCohesionScore(graph);
@@ -6412,8 +7232,8 @@ function getHealthTrend(projectRoot, currentScore) {
6412
7232
  }
6413
7233
  }
6414
7234
  function saveHealthHistory(projectRoot, report) {
6415
- const resolvedRoot = resolve8(projectRoot);
6416
- const historyFile = resolve8(resolvedRoot, ".depwire", "health-history.json");
7235
+ const resolvedRoot = resolve9(projectRoot);
7236
+ const historyFile = resolve9(resolvedRoot, ".depwire", "health-history.json");
6417
7237
  if (!historyFile.startsWith(resolvedRoot)) {
6418
7238
  return;
6419
7239
  }
@@ -6428,10 +7248,10 @@ function saveHealthHistory(projectRoot, report) {
6428
7248
  }))
6429
7249
  };
6430
7250
  let history = [];
6431
- if (existsSync11(historyFile)) {
7251
+ if (existsSync12(historyFile)) {
6432
7252
  try {
6433
7253
  if (!historyFile.startsWith(resolvedRoot)) return;
6434
- const content = readFileSync11(historyFile, "utf-8");
7254
+ const content = readFileSync12(historyFile, "utf-8");
6435
7255
  history = JSON.parse(content);
6436
7256
  } catch {
6437
7257
  }
@@ -6440,19 +7260,19 @@ function saveHealthHistory(projectRoot, report) {
6440
7260
  if (history.length > 50) {
6441
7261
  history = history.slice(-50);
6442
7262
  }
6443
- mkdirSync(dirname11(historyFile), { recursive: true });
7263
+ mkdirSync(dirname12(historyFile), { recursive: true });
6444
7264
  if (!historyFile.startsWith(resolvedRoot)) return;
6445
7265
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
6446
7266
  }
6447
7267
  function loadHealthHistory(projectRoot) {
6448
- const resolvedRoot = resolve8(projectRoot);
6449
- const historyFile = resolve8(resolvedRoot, ".depwire", "health-history.json");
6450
- if (!historyFile.startsWith(resolvedRoot) || !existsSync11(historyFile)) {
7268
+ const resolvedRoot = resolve9(projectRoot);
7269
+ const historyFile = resolve9(resolvedRoot, ".depwire", "health-history.json");
7270
+ if (!historyFile.startsWith(resolvedRoot) || !existsSync12(historyFile)) {
6451
7271
  return [];
6452
7272
  }
6453
7273
  try {
6454
7274
  if (!historyFile.startsWith(resolvedRoot)) return [];
6455
- const content = readFileSync11(historyFile, "utf-8");
7275
+ const content = readFileSync12(historyFile, "utf-8");
6456
7276
  return JSON.parse(content);
6457
7277
  } catch {
6458
7278
  return [];
@@ -6461,7 +7281,7 @@ function loadHealthHistory(projectRoot) {
6461
7281
 
6462
7282
  // src/dead-code/detector.ts
6463
7283
  import path2 from "path";
6464
- import { readFileSync as readFileSync12, existsSync as existsSync12 } from "fs";
7284
+ import { readFileSync as readFileSync13, existsSync as existsSync13 } from "fs";
6465
7285
  function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
6466
7286
  const deadSymbols = [];
6467
7287
  const context = { graph, projectRoot };
@@ -6594,11 +7414,11 @@ function getPackageEntryPoints(projectRoot) {
6594
7414
  const entryPoints = /* @__PURE__ */ new Set();
6595
7415
  const resolvedRoot = path2.resolve(projectRoot);
6596
7416
  const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
6597
- if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync12(packageJsonPath)) {
7417
+ if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync13(packageJsonPath)) {
6598
7418
  return entryPoints;
6599
7419
  }
6600
7420
  try {
6601
- const packageJson = JSON.parse(readFileSync12(packageJsonPath, "utf-8"));
7421
+ const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf-8"));
6602
7422
  if (packageJson.main) {
6603
7423
  entryPoints.add(path2.resolve(projectRoot, packageJson.main));
6604
7424
  }
@@ -6652,6 +7472,9 @@ function shouldExclude(attrs, context, includeTests, packageEntryPoints) {
6652
7472
  if (isCppExcluded(attrs)) {
6653
7473
  return "framework";
6654
7474
  }
7475
+ if (isKotlinExcluded(attrs)) {
7476
+ return "framework";
7477
+ }
6655
7478
  return null;
6656
7479
  }
6657
7480
  function isRealPackageEntryPoint(filePath, packageEntryPoints) {
@@ -6690,6 +7513,33 @@ function isCppExcluded(attrs) {
6690
7513
  if (kind === "constant" && /\.(?:h|hpp)$/.test(filePath)) return true;
6691
7514
  return false;
6692
7515
  }
7516
+ function isKotlinExcluded(attrs) {
7517
+ const filePath = attrs.file || attrs.filePath || "";
7518
+ const name = attrs.name || "";
7519
+ if (!filePath.endsWith(".kt") && !filePath.endsWith(".kts")) return false;
7520
+ if (name === "main") return true;
7521
+ const androidLifecycle = [
7522
+ "onCreate",
7523
+ "onStart",
7524
+ "onResume",
7525
+ "onPause",
7526
+ "onStop",
7527
+ "onDestroy",
7528
+ "onCreateView",
7529
+ "onViewCreated",
7530
+ "onDestroyView",
7531
+ "onSaveInstanceState",
7532
+ "onRestoreInstanceState",
7533
+ "onActivityResult",
7534
+ "onRequestPermissionsResult",
7535
+ "onConfigurationChanged",
7536
+ "onNewIntent"
7537
+ ];
7538
+ if (androidLifecycle.includes(name)) return true;
7539
+ if (["readObject", "writeObject", "readResolve", "writeReplace"].includes(name)) return true;
7540
+ if (name.startsWith("operator")) return true;
7541
+ return false;
7542
+ }
6693
7543
 
6694
7544
  // src/dead-code/classifier.ts
6695
7545
  import path3 from "path";
@@ -6756,8 +7606,8 @@ function generateReason(symbol, confidence) {
6756
7606
  return "Potentially unused";
6757
7607
  }
6758
7608
  function isBarrelFile(filePath) {
6759
- const basename9 = path3.basename(filePath);
6760
- return basename9 === "index.ts" || basename9 === "index.js";
7609
+ const basename10 = path3.basename(filePath);
7610
+ return basename10 === "index.ts" || basename10 === "index.js";
6761
7611
  }
6762
7612
  function isTestFile2(filePath) {
6763
7613
  return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
@@ -6936,11 +7786,11 @@ function filterByConfidence(symbols, minConfidence) {
6936
7786
  }
6937
7787
 
6938
7788
  // src/docs/generator.ts
6939
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync15 } from "fs";
6940
- import { join as join17 } from "path";
7789
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync16 } from "fs";
7790
+ import { join as join18 } from "path";
6941
7791
 
6942
7792
  // src/docs/architecture.ts
6943
- import { dirname as dirname12 } from "path";
7793
+ import { dirname as dirname13 } from "path";
6944
7794
 
6945
7795
  // src/docs/templates.ts
6946
7796
  function header(text, level = 1) {
@@ -7091,7 +7941,7 @@ function generateModuleStructure(graph) {
7091
7941
  function getDirectoryStats(graph) {
7092
7942
  const dirMap = /* @__PURE__ */ new Map();
7093
7943
  graph.forEachNode((node, attrs) => {
7094
- const dir = dirname12(attrs.filePath);
7944
+ const dir = dirname13(attrs.filePath);
7095
7945
  if (dir === ".") return;
7096
7946
  if (!dirMap.has(dir)) {
7097
7947
  dirMap.set(dir, {
@@ -7116,7 +7966,7 @@ function getDirectoryStats(graph) {
7116
7966
  });
7117
7967
  const filesPerDir = /* @__PURE__ */ new Map();
7118
7968
  graph.forEachNode((node, attrs) => {
7119
- const dir = dirname12(attrs.filePath);
7969
+ const dir = dirname13(attrs.filePath);
7120
7970
  if (!filesPerDir.has(dir)) {
7121
7971
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
7122
7972
  }
@@ -7131,8 +7981,8 @@ function getDirectoryStats(graph) {
7131
7981
  graph.forEachEdge((edge, attrs, source, target) => {
7132
7982
  const sourceAttrs = graph.getNodeAttributes(source);
7133
7983
  const targetAttrs = graph.getNodeAttributes(target);
7134
- const sourceDir = dirname12(sourceAttrs.filePath);
7135
- const targetDir = dirname12(targetAttrs.filePath);
7984
+ const sourceDir = dirname13(sourceAttrs.filePath);
7985
+ const targetDir = dirname13(targetAttrs.filePath);
7136
7986
  if (sourceDir !== targetDir) {
7137
7987
  if (!dirEdges.has(sourceDir)) {
7138
7988
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -7339,7 +8189,7 @@ function detectCycles(graph) {
7339
8189
  }
7340
8190
 
7341
8191
  // src/docs/conventions.ts
7342
- import { basename as basename6, extname as extname7 } from "path";
8192
+ import { basename as basename7, extname as extname8 } from "path";
7343
8193
  function generateConventions(graph, projectRoot, version) {
7344
8194
  let output = "";
7345
8195
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -7377,7 +8227,7 @@ function generateFileOrganization(graph) {
7377
8227
  graph.forEachNode((node, attrs) => {
7378
8228
  if (!files.has(attrs.filePath)) {
7379
8229
  files.add(attrs.filePath);
7380
- const fileName = basename6(attrs.filePath);
8230
+ const fileName = basename7(attrs.filePath);
7381
8231
  if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
7382
8232
  barrelFileCount++;
7383
8233
  }
@@ -7436,7 +8286,7 @@ function generateNamingPatterns(graph) {
7436
8286
  graph.forEachNode((node, attrs) => {
7437
8287
  if (!files.has(attrs.filePath)) {
7438
8288
  files.add(attrs.filePath);
7439
- const fileName = basename6(attrs.filePath, extname7(attrs.filePath));
8289
+ const fileName = basename7(attrs.filePath, extname8(attrs.filePath));
7440
8290
  if (isCamelCase(fileName)) patterns.files.camelCase++;
7441
8291
  else if (isPascalCase(fileName)) patterns.files.PascalCase++;
7442
8292
  else if (isKebabCase(fileName)) patterns.files.kebabCase++;
@@ -8113,7 +8963,7 @@ function detectCyclesDetailed(graph) {
8113
8963
  }
8114
8964
 
8115
8965
  // src/docs/onboarding.ts
8116
- import { dirname as dirname13 } from "path";
8966
+ import { dirname as dirname14 } from "path";
8117
8967
  function generateOnboarding(graph, projectRoot, version) {
8118
8968
  let output = "";
8119
8969
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -8172,7 +9022,7 @@ function generateQuickOrientation(graph) {
8172
9022
  const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
8173
9023
  const dirs = /* @__PURE__ */ new Set();
8174
9024
  graph.forEachNode((node, attrs) => {
8175
- const dir = dirname13(attrs.filePath);
9025
+ const dir = dirname14(attrs.filePath);
8176
9026
  if (dir !== ".") {
8177
9027
  const topLevel = dir.split("/")[0];
8178
9028
  dirs.add(topLevel);
@@ -8276,7 +9126,7 @@ function generateModuleMap(graph) {
8276
9126
  function getDirectoryStats2(graph) {
8277
9127
  const dirMap = /* @__PURE__ */ new Map();
8278
9128
  graph.forEachNode((node, attrs) => {
8279
- const dir = dirname13(attrs.filePath);
9129
+ const dir = dirname14(attrs.filePath);
8280
9130
  if (dir === ".") return;
8281
9131
  if (!dirMap.has(dir)) {
8282
9132
  dirMap.set(dir, {
@@ -8291,7 +9141,7 @@ function getDirectoryStats2(graph) {
8291
9141
  });
8292
9142
  const filesPerDir = /* @__PURE__ */ new Map();
8293
9143
  graph.forEachNode((node, attrs) => {
8294
- const dir = dirname13(attrs.filePath);
9144
+ const dir = dirname14(attrs.filePath);
8295
9145
  if (!filesPerDir.has(dir)) {
8296
9146
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
8297
9147
  }
@@ -8306,8 +9156,8 @@ function getDirectoryStats2(graph) {
8306
9156
  graph.forEachEdge((edge, attrs, source, target) => {
8307
9157
  const sourceAttrs = graph.getNodeAttributes(source);
8308
9158
  const targetAttrs = graph.getNodeAttributes(target);
8309
- const sourceDir = dirname13(sourceAttrs.filePath);
8310
- const targetDir = dirname13(targetAttrs.filePath);
9159
+ const sourceDir = dirname14(sourceAttrs.filePath);
9160
+ const targetDir = dirname14(targetAttrs.filePath);
8311
9161
  if (sourceDir !== targetDir) {
8312
9162
  if (!dirEdges.has(sourceDir)) {
8313
9163
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -8388,7 +9238,7 @@ function detectClusters(graph) {
8388
9238
  const dirFiles = /* @__PURE__ */ new Map();
8389
9239
  const fileEdges = /* @__PURE__ */ new Map();
8390
9240
  graph.forEachNode((node, attrs) => {
8391
- const dir = dirname13(attrs.filePath);
9241
+ const dir = dirname14(attrs.filePath);
8392
9242
  if (!dirFiles.has(dir)) {
8393
9243
  dirFiles.set(dir, /* @__PURE__ */ new Set());
8394
9244
  }
@@ -8442,8 +9292,8 @@ function inferClusterName(files) {
8442
9292
  if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
8443
9293
  return capitalizeFirst2(sortedWords[0][0]);
8444
9294
  }
8445
- const commonDir = dirname13(files[0]);
8446
- if (files.every((f) => dirname13(f) === commonDir)) {
9295
+ const commonDir = dirname14(files[0]);
9296
+ if (files.every((f) => dirname14(f) === commonDir)) {
8447
9297
  return capitalizeFirst2(commonDir.split("/").pop() || "Core");
8448
9298
  }
8449
9299
  return "Core";
@@ -8501,7 +9351,7 @@ function generateDepwireUsage(projectRoot) {
8501
9351
  }
8502
9352
 
8503
9353
  // src/docs/files.ts
8504
- import { dirname as dirname14, basename as basename7 } from "path";
9354
+ import { dirname as dirname15, basename as basename8 } from "path";
8505
9355
  function generateFiles(graph, projectRoot, version) {
8506
9356
  let output = "";
8507
9357
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -8605,7 +9455,7 @@ function generateDirectoryBreakdown(graph) {
8605
9455
  const fileStats = getFileStats2(graph);
8606
9456
  const dirMap = /* @__PURE__ */ new Map();
8607
9457
  for (const file of fileStats) {
8608
- const dir = dirname14(file.filePath);
9458
+ const dir = dirname15(file.filePath);
8609
9459
  const topDir = dir === "." ? "." : dir.split("/")[0];
8610
9460
  if (!dirMap.has(topDir)) {
8611
9461
  dirMap.set(topDir, {
@@ -8620,7 +9470,7 @@ function generateDirectoryBreakdown(graph) {
8620
9470
  dirStats.symbolCount += file.symbolCount;
8621
9471
  if (file.totalConnections > dirStats.maxConnections) {
8622
9472
  dirStats.maxConnections = file.totalConnections;
8623
- dirStats.mostConnectedFile = basename7(file.filePath);
9473
+ dirStats.mostConnectedFile = basename8(file.filePath);
8624
9474
  }
8625
9475
  }
8626
9476
  if (dirMap.size === 0) {
@@ -9248,7 +10098,7 @@ function generateRecommendations(graph) {
9248
10098
  }
9249
10099
 
9250
10100
  // src/docs/tests.ts
9251
- import { basename as basename8, dirname as dirname15 } from "path";
10101
+ import { basename as basename9, dirname as dirname16 } from "path";
9252
10102
  function generateTests(graph, projectRoot, version) {
9253
10103
  let output = "";
9254
10104
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -9276,8 +10126,8 @@ function getFileCount8(graph) {
9276
10126
  return files.size;
9277
10127
  }
9278
10128
  function isTestFile3(filePath) {
9279
- const fileName = basename8(filePath).toLowerCase();
9280
- const dirPath = dirname15(filePath).toLowerCase();
10129
+ const fileName = basename9(filePath).toLowerCase();
10130
+ const dirPath = dirname16(filePath).toLowerCase();
9281
10131
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
9282
10132
  return true;
9283
10133
  }
@@ -9335,13 +10185,13 @@ function generateTestFileInventory(graph) {
9335
10185
  return output;
9336
10186
  }
9337
10187
  function matchTestToSource(testFile) {
9338
- const testFileName = basename8(testFile);
9339
- const testDir = dirname15(testFile);
10188
+ const testFileName = basename9(testFile);
10189
+ const testDir = dirname16(testFile);
9340
10190
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
9341
10191
  const possiblePaths = [];
9342
10192
  possiblePaths.push(testDir + "/" + sourceFileName);
9343
10193
  if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
9344
- const parentDir = dirname15(testDir);
10194
+ const parentDir = dirname16(testDir);
9345
10195
  possiblePaths.push(parentDir + "/" + sourceFileName);
9346
10196
  }
9347
10197
  if (testDir.includes("test")) {
@@ -9497,7 +10347,7 @@ function generateTestCoverageMap(graph) {
9497
10347
  const rows = mappings.slice(0, 30).map((m) => [
9498
10348
  `\`${m.sourceFile}\``,
9499
10349
  m.hasTest ? "\u2705" : "\u274C",
9500
- m.testFile ? `\`${basename8(m.testFile)}\`` : "-",
10350
+ m.testFile ? `\`${basename9(m.testFile)}\`` : "-",
9501
10351
  formatNumber(m.symbolCount)
9502
10352
  ]);
9503
10353
  let output = table(headers, rows);
@@ -9538,7 +10388,7 @@ function generateTestStatistics(graph) {
9538
10388
  `;
9539
10389
  const dirTestCoverage = /* @__PURE__ */ new Map();
9540
10390
  for (const sourceFile of sourceFiles) {
9541
- const dir = dirname15(sourceFile).split("/")[0];
10391
+ const dir = dirname16(sourceFile).split("/")[0];
9542
10392
  if (!dirTestCoverage.has(dir)) {
9543
10393
  dirTestCoverage.set(dir, { total: 0, tested: 0 });
9544
10394
  }
@@ -9561,7 +10411,7 @@ function generateTestStatistics(graph) {
9561
10411
  }
9562
10412
 
9563
10413
  // src/docs/history.ts
9564
- import { dirname as dirname16 } from "path";
10414
+ import { dirname as dirname17 } from "path";
9565
10415
  import { execSync } from "child_process";
9566
10416
  function generateHistory(graph, projectRoot, version) {
9567
10417
  let output = "";
@@ -9842,7 +10692,7 @@ function generateFeatureClusters(graph) {
9842
10692
  const dirFiles = /* @__PURE__ */ new Map();
9843
10693
  const fileEdges = /* @__PURE__ */ new Map();
9844
10694
  graph.forEachNode((node, attrs) => {
9845
- const dir = dirname16(attrs.filePath);
10695
+ const dir = dirname17(attrs.filePath);
9846
10696
  if (!dirFiles.has(dir)) {
9847
10697
  dirFiles.set(dir, /* @__PURE__ */ new Set());
9848
10698
  }
@@ -9924,7 +10774,7 @@ function capitalizeFirst3(str) {
9924
10774
  }
9925
10775
 
9926
10776
  // src/docs/current.ts
9927
- import { dirname as dirname17 } from "path";
10777
+ import { dirname as dirname18 } from "path";
9928
10778
  function generateCurrent(graph, projectRoot, version) {
9929
10779
  let output = "";
9930
10780
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -10062,7 +10912,7 @@ function generateCompleteFileIndex(graph) {
10062
10912
  fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
10063
10913
  const dirGroups = /* @__PURE__ */ new Map();
10064
10914
  for (const info of fileInfos) {
10065
- const dir = dirname17(info.filePath);
10915
+ const dir = dirname18(info.filePath);
10066
10916
  const topDir = dir === "." ? "root" : dir.split("/")[0];
10067
10917
  if (!dirGroups.has(topDir)) {
10068
10918
  dirGroups.set(topDir, []);
@@ -10273,8 +11123,8 @@ function getTopLevelDir2(filePath) {
10273
11123
  }
10274
11124
 
10275
11125
  // src/docs/status.ts
10276
- import { readFileSync as readFileSync13, existsSync as existsSync13 } from "fs";
10277
- import { resolve as resolve9 } from "path";
11126
+ import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
11127
+ import { resolve as resolve10 } from "path";
10278
11128
  function generateStatus(graph, projectRoot, version) {
10279
11129
  let output = "";
10280
11130
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -10307,16 +11157,16 @@ function getFileCount11(graph) {
10307
11157
  }
10308
11158
  function extractComments(projectRoot, filePath) {
10309
11159
  const comments = [];
10310
- const resolvedRoot = resolve9(projectRoot);
10311
- const fullPath = resolve9(resolvedRoot, filePath);
11160
+ const resolvedRoot = resolve10(projectRoot);
11161
+ const fullPath = resolve10(resolvedRoot, filePath);
10312
11162
  if (!fullPath.startsWith(resolvedRoot)) {
10313
11163
  return comments;
10314
11164
  }
10315
- if (!existsSync13(fullPath)) {
11165
+ if (!existsSync14(fullPath)) {
10316
11166
  return comments;
10317
11167
  }
10318
11168
  try {
10319
- const content = readFileSync13(fullPath, "utf-8");
11169
+ const content = readFileSync14(fullPath, "utf-8");
10320
11170
  const lines = content.split("\n");
10321
11171
  const patterns = [
10322
11172
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -10895,16 +11745,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
10895
11745
  }
10896
11746
 
10897
11747
  // src/docs/metadata.ts
10898
- import { existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync2 } from "fs";
10899
- import { resolve as resolve10 } from "path";
11748
+ import { existsSync as existsSync15, readFileSync as readFileSync15, writeFileSync as writeFileSync2 } from "fs";
11749
+ import { resolve as resolve11 } from "path";
10900
11750
  function loadMetadata(outputDir) {
10901
- const resolvedDir = resolve10(outputDir);
10902
- const metadataPath = resolve10(resolvedDir, "metadata.json");
10903
- if (!metadataPath.startsWith(resolvedDir) || !existsSync14(metadataPath)) {
11751
+ const resolvedDir = resolve11(outputDir);
11752
+ const metadataPath = resolve11(resolvedDir, "metadata.json");
11753
+ if (!metadataPath.startsWith(resolvedDir) || !existsSync15(metadataPath)) {
10904
11754
  return null;
10905
11755
  }
10906
11756
  try {
10907
- const content = readFileSync14(metadataPath, "utf-8");
11757
+ const content = readFileSync15(metadataPath, "utf-8");
10908
11758
  return JSON.parse(content);
10909
11759
  } catch (err) {
10910
11760
  console.error("Failed to load metadata:", err);
@@ -10912,8 +11762,8 @@ function loadMetadata(outputDir) {
10912
11762
  }
10913
11763
  }
10914
11764
  function saveMetadata(outputDir, metadata) {
10915
- const resolvedDir = resolve10(outputDir);
10916
- const metadataPath = resolve10(resolvedDir, "metadata.json");
11765
+ const resolvedDir = resolve11(outputDir);
11766
+ const metadataPath = resolve11(resolvedDir, "metadata.json");
10917
11767
  if (!metadataPath.startsWith(resolvedDir)) {
10918
11768
  throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
10919
11769
  }
@@ -10959,7 +11809,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
10959
11809
  const generated = [];
10960
11810
  const errors = [];
10961
11811
  try {
10962
- if (!existsSync15(options.outputDir)) {
11812
+ if (!existsSync16(options.outputDir)) {
10963
11813
  mkdirSync2(options.outputDir, { recursive: true });
10964
11814
  if (options.verbose) {
10965
11815
  console.log(`Created output directory: ${options.outputDir}`);
@@ -10998,7 +11848,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
10998
11848
  try {
10999
11849
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
11000
11850
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
11001
- const filePath = join17(options.outputDir, "ARCHITECTURE.md");
11851
+ const filePath = join18(options.outputDir, "ARCHITECTURE.md");
11002
11852
  writeFileSync3(filePath, content, "utf-8");
11003
11853
  generated.push("ARCHITECTURE.md");
11004
11854
  } catch (err) {
@@ -11009,7 +11859,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11009
11859
  try {
11010
11860
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
11011
11861
  const content = generateConventions(graph, projectRoot, version);
11012
- const filePath = join17(options.outputDir, "CONVENTIONS.md");
11862
+ const filePath = join18(options.outputDir, "CONVENTIONS.md");
11013
11863
  writeFileSync3(filePath, content, "utf-8");
11014
11864
  generated.push("CONVENTIONS.md");
11015
11865
  } catch (err) {
@@ -11020,7 +11870,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11020
11870
  try {
11021
11871
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
11022
11872
  const content = generateDependencies(graph, projectRoot, version);
11023
- const filePath = join17(options.outputDir, "DEPENDENCIES.md");
11873
+ const filePath = join18(options.outputDir, "DEPENDENCIES.md");
11024
11874
  writeFileSync3(filePath, content, "utf-8");
11025
11875
  generated.push("DEPENDENCIES.md");
11026
11876
  } catch (err) {
@@ -11031,7 +11881,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11031
11881
  try {
11032
11882
  if (options.verbose) console.log("Generating ONBOARDING.md...");
11033
11883
  const content = generateOnboarding(graph, projectRoot, version);
11034
- const filePath = join17(options.outputDir, "ONBOARDING.md");
11884
+ const filePath = join18(options.outputDir, "ONBOARDING.md");
11035
11885
  writeFileSync3(filePath, content, "utf-8");
11036
11886
  generated.push("ONBOARDING.md");
11037
11887
  } catch (err) {
@@ -11042,7 +11892,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11042
11892
  try {
11043
11893
  if (options.verbose) console.log("Generating FILES.md...");
11044
11894
  const content = generateFiles(graph, projectRoot, version);
11045
- const filePath = join17(options.outputDir, "FILES.md");
11895
+ const filePath = join18(options.outputDir, "FILES.md");
11046
11896
  writeFileSync3(filePath, content, "utf-8");
11047
11897
  generated.push("FILES.md");
11048
11898
  } catch (err) {
@@ -11053,7 +11903,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11053
11903
  try {
11054
11904
  if (options.verbose) console.log("Generating API_SURFACE.md...");
11055
11905
  const content = generateApiSurface(graph, projectRoot, version);
11056
- const filePath = join17(options.outputDir, "API_SURFACE.md");
11906
+ const filePath = join18(options.outputDir, "API_SURFACE.md");
11057
11907
  writeFileSync3(filePath, content, "utf-8");
11058
11908
  generated.push("API_SURFACE.md");
11059
11909
  } catch (err) {
@@ -11064,7 +11914,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11064
11914
  try {
11065
11915
  if (options.verbose) console.log("Generating ERRORS.md...");
11066
11916
  const content = generateErrors(graph, projectRoot, version);
11067
- const filePath = join17(options.outputDir, "ERRORS.md");
11917
+ const filePath = join18(options.outputDir, "ERRORS.md");
11068
11918
  writeFileSync3(filePath, content, "utf-8");
11069
11919
  generated.push("ERRORS.md");
11070
11920
  } catch (err) {
@@ -11075,7 +11925,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11075
11925
  try {
11076
11926
  if (options.verbose) console.log("Generating TESTS.md...");
11077
11927
  const content = generateTests(graph, projectRoot, version);
11078
- const filePath = join17(options.outputDir, "TESTS.md");
11928
+ const filePath = join18(options.outputDir, "TESTS.md");
11079
11929
  writeFileSync3(filePath, content, "utf-8");
11080
11930
  generated.push("TESTS.md");
11081
11931
  } catch (err) {
@@ -11086,7 +11936,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11086
11936
  try {
11087
11937
  if (options.verbose) console.log("Generating HISTORY.md...");
11088
11938
  const content = generateHistory(graph, projectRoot, version);
11089
- const filePath = join17(options.outputDir, "HISTORY.md");
11939
+ const filePath = join18(options.outputDir, "HISTORY.md");
11090
11940
  writeFileSync3(filePath, content, "utf-8");
11091
11941
  generated.push("HISTORY.md");
11092
11942
  } catch (err) {
@@ -11097,7 +11947,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11097
11947
  try {
11098
11948
  if (options.verbose) console.log("Generating CURRENT.md...");
11099
11949
  const content = generateCurrent(graph, projectRoot, version);
11100
- const filePath = join17(options.outputDir, "CURRENT.md");
11950
+ const filePath = join18(options.outputDir, "CURRENT.md");
11101
11951
  writeFileSync3(filePath, content, "utf-8");
11102
11952
  generated.push("CURRENT.md");
11103
11953
  } catch (err) {
@@ -11108,7 +11958,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11108
11958
  try {
11109
11959
  if (options.verbose) console.log("Generating STATUS.md...");
11110
11960
  const content = generateStatus(graph, projectRoot, version);
11111
- const filePath = join17(options.outputDir, "STATUS.md");
11961
+ const filePath = join18(options.outputDir, "STATUS.md");
11112
11962
  writeFileSync3(filePath, content, "utf-8");
11113
11963
  generated.push("STATUS.md");
11114
11964
  } catch (err) {
@@ -11119,7 +11969,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11119
11969
  try {
11120
11970
  if (options.verbose) console.log("Generating HEALTH.md...");
11121
11971
  const content = generateHealth(graph, projectRoot, version);
11122
- const filePath = join17(options.outputDir, "HEALTH.md");
11972
+ const filePath = join18(options.outputDir, "HEALTH.md");
11123
11973
  writeFileSync3(filePath, content, "utf-8");
11124
11974
  generated.push("HEALTH.md");
11125
11975
  } catch (err) {
@@ -11130,7 +11980,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
11130
11980
  try {
11131
11981
  if (options.verbose) console.log("Generating DEAD_CODE.md...");
11132
11982
  const content = generateDeadCode(graph, projectRoot, version);
11133
- const filePath = join17(options.outputDir, "DEAD_CODE.md");
11983
+ const filePath = join18(options.outputDir, "DEAD_CODE.md");
11134
11984
  writeFileSync3(filePath, content, "utf-8");
11135
11985
  generated.push("DEAD_CODE.md");
11136
11986
  } catch (err) {
@@ -11174,7 +12024,7 @@ function getFileCount13(graph) {
11174
12024
  }
11175
12025
 
11176
12026
  // src/simulation/engine.ts
11177
- import { dirname as dirname18, join as join18 } from "path";
12027
+ import { dirname as dirname19, join as join19 } from "path";
11178
12028
  function normalizePath2(p) {
11179
12029
  return p.replace(/^\.\//, "").replace(/\/+$/, "");
11180
12030
  }
@@ -11240,7 +12090,8 @@ var SimulationEngine = class {
11240
12090
  healthScore: afterHealth.score
11241
12091
  },
11242
12092
  diff,
11243
- healthDelta
12093
+ healthDelta,
12094
+ simulatedGraphInstance: clone
11244
12095
  };
11245
12096
  }
11246
12097
  // ── Action implementations ─────────────────────────────────────
@@ -11305,7 +12156,7 @@ var SimulationEngine = class {
11305
12156
  }
11306
12157
  }
11307
12158
  applyRename(clone, target, newName, brokenImports) {
11308
- const destination = join18(dirname18(target), newName);
12159
+ const destination = join19(dirname19(target), newName);
11309
12160
  this.applyMove(clone, target, destination, brokenImports);
11310
12161
  }
11311
12162
  applySplit(clone, target, newFile, symbols, brokenImports) {
@@ -11505,13 +12356,13 @@ var SimulationEngine = class {
11505
12356
  };
11506
12357
 
11507
12358
  // src/security/scanner.ts
11508
- import { existsSync as existsSync17 } from "fs";
11509
- import { join as join28 } from "path";
12359
+ import { existsSync as existsSync18 } from "fs";
12360
+ import { join as join29 } from "path";
11510
12361
 
11511
12362
  // src/security/checks/dependencies.ts
11512
12363
  import { execSync as execSync2 } from "child_process";
11513
- import { existsSync as existsSync16, readFileSync as readFileSync15, readdirSync as readdirSync8 } from "fs";
11514
- import { join as join19 } from "path";
12364
+ import { existsSync as existsSync17, readFileSync as readFileSync16, readdirSync as readdirSync9 } from "fs";
12365
+ import { join as join20 } from "path";
11515
12366
  function cvssToSeverity(score) {
11516
12367
  if (score >= 9) return "critical";
11517
12368
  if (score >= 7) return "high";
@@ -11521,18 +12372,18 @@ function cvssToSeverity(score) {
11521
12372
  async function checkDependencies(_files, projectRoot) {
11522
12373
  const findings = [];
11523
12374
  try {
11524
- if (existsSync16(join19(projectRoot, "package.json"))) {
12375
+ if (existsSync17(join20(projectRoot, "package.json"))) {
11525
12376
  findings.push(...checkNpmAudit(projectRoot));
11526
12377
  findings.push(...checkPackageJsonPatterns(projectRoot));
11527
12378
  findings.push(...checkPostinstallScripts(projectRoot));
11528
12379
  }
11529
- if (existsSync16(join19(projectRoot, "requirements.txt")) || existsSync16(join19(projectRoot, "pyproject.toml"))) {
12380
+ if (existsSync17(join20(projectRoot, "requirements.txt")) || existsSync17(join20(projectRoot, "pyproject.toml"))) {
11530
12381
  findings.push(...checkPipAudit(projectRoot));
11531
12382
  }
11532
- if (existsSync16(join19(projectRoot, "Cargo.toml"))) {
12383
+ if (existsSync17(join20(projectRoot, "Cargo.toml"))) {
11533
12384
  findings.push(...checkCargoAudit(projectRoot));
11534
12385
  }
11535
- if (existsSync16(join19(projectRoot, "go.mod"))) {
12386
+ if (existsSync17(join20(projectRoot, "go.mod"))) {
11536
12387
  findings.push(...checkGoVerify(projectRoot));
11537
12388
  }
11538
12389
  } catch (err) {
@@ -11621,8 +12472,8 @@ function checkNpmAudit(projectRoot) {
11621
12472
  function checkPackageJsonPatterns(projectRoot) {
11622
12473
  const findings = [];
11623
12474
  try {
11624
- const pkgPath = join19(projectRoot, "package.json");
11625
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
12475
+ const pkgPath = join20(projectRoot, "package.json");
12476
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
11626
12477
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
11627
12478
  for (const [name, version] of Object.entries(allDeps)) {
11628
12479
  if (version.startsWith("^") || version.startsWith("~")) {
@@ -11644,15 +12495,15 @@ function checkPackageJsonPatterns(projectRoot) {
11644
12495
  }
11645
12496
  function checkPostinstallScripts(projectRoot) {
11646
12497
  const findings = [];
11647
- const nodeModules = join19(projectRoot, "node_modules");
11648
- if (!existsSync16(nodeModules)) return findings;
12498
+ const nodeModules = join20(projectRoot, "node_modules");
12499
+ if (!existsSync17(nodeModules)) return findings;
11649
12500
  try {
11650
- const topLevelDeps = readdirSync8(nodeModules).filter((d) => !d.startsWith("."));
12501
+ const topLevelDeps = readdirSync9(nodeModules).filter((d) => !d.startsWith("."));
11651
12502
  for (const dep of topLevelDeps) {
11652
- const depPkgPath = join19(nodeModules, dep, "package.json");
11653
- if (!existsSync16(depPkgPath)) continue;
12503
+ const depPkgPath = join20(nodeModules, dep, "package.json");
12504
+ if (!existsSync17(depPkgPath)) continue;
11654
12505
  try {
11655
- const depPkg = JSON.parse(readFileSync15(depPkgPath, "utf-8"));
12506
+ const depPkg = JSON.parse(readFileSync16(depPkgPath, "utf-8"));
11656
12507
  const scripts = depPkg.scripts || {};
11657
12508
  if (scripts.postinstall || scripts.preinstall || scripts.install) {
11658
12509
  const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
@@ -11690,7 +12541,7 @@ function checkPipAudit(projectRoot) {
11690
12541
  id: "",
11691
12542
  severity: cvssToSeverity(vuln.cvss?.score || 5),
11692
12543
  vulnerabilityClass: "dependency-cve",
11693
- file: existsSync16(join19(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
12544
+ file: existsSync17(join20(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
11694
12545
  title: `Vulnerable Python dependency: ${vuln.name}`,
11695
12546
  description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
11696
12547
  attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
@@ -11787,8 +12638,8 @@ function checkGoVerify(projectRoot) {
11787
12638
  }
11788
12639
 
11789
12640
  // src/security/checks/injection.ts
11790
- import { readFileSync as readFileSync16 } from "fs";
11791
- import { join as join20 } from "path";
12641
+ import { readFileSync as readFileSync17 } from "fs";
12642
+ import { join as join21 } from "path";
11792
12643
  var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
11793
12644
  var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
11794
12645
  var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
@@ -11974,6 +12825,52 @@ var PATTERNS = [
11974
12825
  description: "popen() called with a variable argument \u2014 potential command injection.",
11975
12826
  attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands.",
11976
12827
  suggestedFix: "Avoid popen(). Use pipe/fork/exec with argument arrays instead."
12828
+ },
12829
+ // Kotlin injection patterns
12830
+ {
12831
+ regex: /["']SELECT\b[^"']*\$(?:\{|\w)/,
12832
+ title: "Kotlin SQL injection via string template",
12833
+ vulnClass: "code-injection",
12834
+ baseSeverity: "high",
12835
+ description: "SQL query built using Kotlin string templates \u2014 vulnerable to SQL injection.",
12836
+ attackScenario: "An attacker could inject SQL through interpolated variables to read, modify, or delete database data.",
12837
+ suggestedFix: "Use parameterized queries with PreparedStatement or your ORM's query builder."
12838
+ },
12839
+ {
12840
+ regex: /["'](?:INSERT|UPDATE|DELETE)\b[^"']*\$(?:\{|\w)/,
12841
+ title: "Kotlin SQL injection via string template",
12842
+ vulnClass: "code-injection",
12843
+ baseSeverity: "high",
12844
+ description: "SQL mutation query built using Kotlin string templates \u2014 vulnerable to SQL injection.",
12845
+ attackScenario: "An attacker could inject SQL through interpolated variables.",
12846
+ suggestedFix: "Use parameterized queries with PreparedStatement or your ORM's query builder."
12847
+ },
12848
+ {
12849
+ regex: /Runtime\.getRuntime\(\)\.exec\s*\(/,
12850
+ title: "Kotlin/Java command injection via Runtime.exec",
12851
+ vulnClass: "shell-injection",
12852
+ baseSeverity: "high",
12853
+ description: "Runtime.exec() executes a system command \u2014 vulnerable if user input reaches the argument.",
12854
+ attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
12855
+ suggestedFix: "Use ProcessBuilder with an argument array. Validate all input against a strict allowlist."
12856
+ },
12857
+ {
12858
+ regex: /\.csrf\(\)\s*\.?\s*disable\(\)/,
12859
+ title: "Spring Security CSRF protection disabled",
12860
+ vulnClass: "code-injection",
12861
+ baseSeverity: "medium",
12862
+ description: "CSRF protection has been explicitly disabled in Spring Security configuration.",
12863
+ attackScenario: "An attacker could forge cross-site requests to perform actions on behalf of authenticated users.",
12864
+ suggestedFix: "Only disable CSRF for stateless APIs using JWT. Keep CSRF enabled for session-based authentication."
12865
+ },
12866
+ {
12867
+ regex: /\.permitAll\(\).*(?:admin|manage|delete|config|setting)/i,
12868
+ title: "Spring Security permitAll on sensitive path",
12869
+ vulnClass: "code-injection",
12870
+ baseSeverity: "high",
12871
+ description: "permitAll() applied to a path that appears security-sensitive.",
12872
+ attackScenario: "An attacker could access administrative or destructive endpoints without authentication.",
12873
+ suggestedFix: 'Use .hasRole("ADMIN") or .authenticated() for sensitive endpoints.'
11977
12874
  }
11978
12875
  ];
11979
12876
  function shouldSkip(filePath) {
@@ -11990,7 +12887,7 @@ async function checkInjection(files, projectRoot) {
11990
12887
  if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
11991
12888
  let content;
11992
12889
  try {
11993
- content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
12890
+ content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
11994
12891
  } catch {
11995
12892
  continue;
11996
12893
  }
@@ -12028,8 +12925,8 @@ async function checkInjection(files, projectRoot) {
12028
12925
  }
12029
12926
 
12030
12927
  // src/security/checks/secrets.ts
12031
- import { readFileSync as readFileSync17 } from "fs";
12032
- import { join as join21 } from "path";
12928
+ import { readFileSync as readFileSync18 } from "fs";
12929
+ import { join as join22 } from "path";
12033
12930
  var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12034
12931
  var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
12035
12932
  var SECRET_PATTERNS = [
@@ -12062,7 +12959,7 @@ async function checkSecrets(files, projectRoot) {
12062
12959
  if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
12063
12960
  let content;
12064
12961
  try {
12065
- content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
12962
+ content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
12066
12963
  } catch {
12067
12964
  continue;
12068
12965
  }
@@ -12095,8 +12992,8 @@ async function checkSecrets(files, projectRoot) {
12095
12992
  }
12096
12993
 
12097
12994
  // src/security/checks/path-traversal.ts
12098
- import { readFileSync as readFileSync18 } from "fs";
12099
- import { join as join22 } from "path";
12995
+ import { readFileSync as readFileSync19 } from "fs";
12996
+ import { join as join23 } from "path";
12100
12997
  var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12101
12998
  var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
12102
12999
  var PATTERNS2 = [
@@ -12143,7 +13040,7 @@ async function checkPathTraversal(files, projectRoot) {
12143
13040
  if (shouldSkip3(file.filePath)) continue;
12144
13041
  let content;
12145
13042
  try {
12146
- content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
13043
+ content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
12147
13044
  } catch {
12148
13045
  continue;
12149
13046
  }
@@ -12185,8 +13082,8 @@ async function checkPathTraversal(files, projectRoot) {
12185
13082
  }
12186
13083
 
12187
13084
  // src/security/checks/auth.ts
12188
- import { readFileSync as readFileSync19 } from "fs";
12189
- import { join as join23 } from "path";
13085
+ import { readFileSync as readFileSync20 } from "fs";
13086
+ import { join as join24 } from "path";
12190
13087
  var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12191
13088
  function shouldSkip4(filePath) {
12192
13089
  return SKIP_DIRS4.some((d) => filePath.includes(d));
@@ -12202,7 +13099,7 @@ async function checkAuth(files, projectRoot) {
12202
13099
  if (shouldSkip4(file.filePath)) continue;
12203
13100
  let content;
12204
13101
  try {
12205
- content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
13102
+ content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
12206
13103
  } catch {
12207
13104
  continue;
12208
13105
  }
@@ -12293,8 +13190,8 @@ async function checkAuth(files, projectRoot) {
12293
13190
  }
12294
13191
 
12295
13192
  // src/security/checks/input-validation.ts
12296
- import { readFileSync as readFileSync20 } from "fs";
12297
- import { join as join24 } from "path";
13193
+ import { readFileSync as readFileSync21 } from "fs";
13194
+ import { join as join25 } from "path";
12298
13195
  var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12299
13196
  function shouldSkip5(filePath) {
12300
13197
  return SKIP_DIRS5.some((d) => filePath.includes(d));
@@ -12306,7 +13203,7 @@ async function checkInputValidation(files, projectRoot) {
12306
13203
  if (shouldSkip5(file.filePath)) continue;
12307
13204
  let content;
12308
13205
  try {
12309
- content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
13206
+ content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
12310
13207
  } catch {
12311
13208
  continue;
12312
13209
  }
@@ -12383,8 +13280,8 @@ async function checkInputValidation(files, projectRoot) {
12383
13280
  }
12384
13281
 
12385
13282
  // src/security/checks/information-disclosure.ts
12386
- import { readFileSync as readFileSync21 } from "fs";
12387
- import { join as join25 } from "path";
13283
+ import { readFileSync as readFileSync22 } from "fs";
13284
+ import { join as join26 } from "path";
12388
13285
  var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12389
13286
  function shouldSkip6(filePath) {
12390
13287
  return SKIP_DIRS6.some((d) => filePath.includes(d));
@@ -12396,7 +13293,7 @@ async function checkInformationDisclosure(files, projectRoot) {
12396
13293
  if (shouldSkip6(file.filePath)) continue;
12397
13294
  let content;
12398
13295
  try {
12399
- content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
13296
+ content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
12400
13297
  } catch {
12401
13298
  continue;
12402
13299
  }
@@ -12466,8 +13363,8 @@ async function checkInformationDisclosure(files, projectRoot) {
12466
13363
  }
12467
13364
 
12468
13365
  // src/security/checks/cryptography.ts
12469
- import { readFileSync as readFileSync22 } from "fs";
12470
- import { join as join26 } from "path";
13366
+ import { readFileSync as readFileSync23 } from "fs";
13367
+ import { join as join27 } from "path";
12471
13368
  var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12472
13369
  var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
12473
13370
  function shouldSkip7(filePath) {
@@ -12484,7 +13381,7 @@ async function checkCryptography(files, projectRoot) {
12484
13381
  if (shouldSkip7(file.filePath)) continue;
12485
13382
  let content;
12486
13383
  try {
12487
- content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
13384
+ content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
12488
13385
  } catch {
12489
13386
  continue;
12490
13387
  }
@@ -12612,6 +13509,58 @@ async function checkCryptography(files, projectRoot) {
12612
13509
  suggestedFix: "Load credentials from environment variables or a secure vault at runtime."
12613
13510
  });
12614
13511
  }
13512
+ if (/(?:val|var)\s+(?:password|secret|apiKey|api_key|token)\s*=\s*["']/i.test(line)) {
13513
+ findings.push({
13514
+ id: "",
13515
+ severity: "high",
13516
+ vulnerabilityClass: "cryptography",
13517
+ file: file.filePath,
13518
+ line: i + 1,
13519
+ title: "Hardcoded credentials in Kotlin source",
13520
+ description: "A password, secret, or API key is hardcoded as a string literal.",
13521
+ attackScenario: "An attacker with access to the binary or source could extract the credential.",
13522
+ suggestedFix: "Load credentials from environment variables or a secure vault at runtime."
13523
+ });
13524
+ }
13525
+ if (/\bRandom\s*\(\s*\)/.test(line) && isCryptoFile) {
13526
+ findings.push({
13527
+ id: "",
13528
+ severity: "medium",
13529
+ vulnerabilityClass: "cryptography",
13530
+ file: file.filePath,
13531
+ line: i + 1,
13532
+ title: "Insecure random in Kotlin security context",
13533
+ description: "kotlin.random.Random() is not cryptographically secure \u2014 its output can be predicted.",
13534
+ attackScenario: "An attacker could predict random values to forge tokens or bypass security checks.",
13535
+ suggestedFix: "Use java.security.SecureRandom for cryptographic purposes."
13536
+ });
13537
+ }
13538
+ if (/!!\s*\./.test(line) && isCryptoFile) {
13539
+ findings.push({
13540
+ id: "",
13541
+ severity: "medium",
13542
+ vulnerabilityClass: "cryptography",
13543
+ file: file.filePath,
13544
+ line: i + 1,
13545
+ title: "Not-null assertion (!!) in security-sensitive Kotlin code",
13546
+ description: "The !! operator can throw NullPointerException, potentially bypassing security checks.",
13547
+ attackScenario: "An attacker could trigger a null value to cause an exception that bypasses validation logic.",
13548
+ suggestedFix: "Use safe calls (?.) with proper null handling instead of !! assertions."
13549
+ });
13550
+ }
13551
+ if (/(?:val|var)\s+\w*[Uu]rl\w*\s*=\s*["']http:\/\/(?!(?:localhost|127\.))/.test(line)) {
13552
+ findings.push({
13553
+ id: "",
13554
+ severity: "medium",
13555
+ vulnerabilityClass: "cryptography",
13556
+ file: file.filePath,
13557
+ line: i + 1,
13558
+ title: "Hardcoded HTTP URL in Kotlin source",
13559
+ description: "An HTTP (not HTTPS) URL is hardcoded \u2014 data is transmitted unencrypted.",
13560
+ attackScenario: "An attacker on the network path could intercept, read, or modify data in transit.",
13561
+ suggestedFix: "Use HTTPS for all external URLs to ensure data confidentiality and integrity."
13562
+ });
13563
+ }
12615
13564
  }
12616
13565
  }
12617
13566
  } catch {
@@ -12620,8 +13569,8 @@ async function checkCryptography(files, projectRoot) {
12620
13569
  }
12621
13570
 
12622
13571
  // src/security/checks/frontend.ts
12623
- import { readFileSync as readFileSync23 } from "fs";
12624
- import { join as join27 } from "path";
13572
+ import { readFileSync as readFileSync24 } from "fs";
13573
+ import { join as join28 } from "path";
12625
13574
  var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
12626
13575
  function shouldSkip8(filePath) {
12627
13576
  return SKIP_DIRS8.some((d) => filePath.includes(d));
@@ -12637,7 +13586,7 @@ async function checkFrontend(files, projectRoot) {
12637
13586
  if (!isFrontendFile(file.filePath)) continue;
12638
13587
  let content;
12639
13588
  try {
12640
- content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
13589
+ content = readFileSync24(join28(projectRoot, file.filePath), "utf-8");
12641
13590
  } catch {
12642
13591
  continue;
12643
13592
  }
@@ -13097,13 +14046,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
13097
14046
  };
13098
14047
  }
13099
14048
  function detectPackageManager(projectRoot) {
13100
- if (existsSync17(join28(projectRoot, "package.json"))) return "npm";
13101
- if (existsSync17(join28(projectRoot, "requirements.txt"))) return "pip";
13102
- if (existsSync17(join28(projectRoot, "pyproject.toml"))) return "pip";
13103
- if (existsSync17(join28(projectRoot, "Cargo.toml"))) return "cargo";
13104
- if (existsSync17(join28(projectRoot, "go.mod"))) return "go";
13105
- if (existsSync17(join28(projectRoot, "pom.xml"))) return "maven";
13106
- if (existsSync17(join28(projectRoot, "build.gradle")) || existsSync17(join28(projectRoot, "build.gradle.kts"))) return "gradle";
14049
+ if (existsSync18(join29(projectRoot, "package.json"))) return "npm";
14050
+ if (existsSync18(join29(projectRoot, "requirements.txt"))) return "pip";
14051
+ if (existsSync18(join29(projectRoot, "pyproject.toml"))) return "pip";
14052
+ if (existsSync18(join29(projectRoot, "Cargo.toml"))) return "cargo";
14053
+ if (existsSync18(join29(projectRoot, "go.mod"))) return "go";
14054
+ if (existsSync18(join29(projectRoot, "pom.xml"))) return "maven";
14055
+ if (existsSync18(join29(projectRoot, "build.gradle")) || existsSync18(join29(projectRoot, "build.gradle.kts"))) return "gradle";
13107
14056
  return "unknown";
13108
14057
  }
13109
14058