depwire-cli 1.0.0 → 1.0.1

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.
@@ -33,7 +33,8 @@ function scanDirectory(rootDir, baseDir = rootDir) {
33
33
  const isRust = entry.endsWith(".rs");
34
34
  const isC = entry.endsWith(".c") || entry.endsWith(".h");
35
35
  const isCSharp = entry.endsWith(".cs") || entry.endsWith(".csx") || entry.endsWith(".csproj");
36
- if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCSharp) {
36
+ const isJava = entry.endsWith(".java") || entry === "pom.xml" || entry === "build.gradle" || entry === "build.gradle.kts";
37
+ if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCSharp || isJava) {
37
38
  files.push(relative(rootDir, fullPath));
38
39
  }
39
40
  }
@@ -71,6 +72,10 @@ function findProjectRoot(startDir = process.cwd()) {
71
72
  // C/C++ (cmake-based)
72
73
  "configure.ac",
73
74
  // C/C++ (autotools)
75
+ "pom.xml",
76
+ // Java (Maven)
77
+ "build.gradle",
78
+ // Java (Gradle)
74
79
  ".git"
75
80
  // Any git repo
76
81
  ];
@@ -104,11 +109,11 @@ function findProjectRoot(startDir = process.cwd()) {
104
109
  }
105
110
 
106
111
  // src/parser/index.ts
107
- import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
108
- import { join as join9, resolve as resolve4 } from "path";
112
+ import { readFileSync as readFileSync7, statSync as statSync4 } from "fs";
113
+ import { join as join10, resolve as resolve5 } from "path";
109
114
 
110
115
  // src/parser/detect.ts
111
- import { extname as extname4 } from "path";
116
+ import { extname as extname5, basename as basename3 } from "path";
112
117
 
113
118
  // src/parser/wasm-init.ts
114
119
  import { Parser, Language } from "web-tree-sitter";
@@ -136,7 +141,8 @@ async function initParser() {
136
141
  "go": "tree-sitter-go.wasm",
137
142
  "rust": "tree-sitter-rust.wasm",
138
143
  "c": "tree-sitter-c.wasm",
139
- "c_sharp": "tree-sitter-c_sharp.wasm"
144
+ "c_sharp": "tree-sitter-c_sharp.wasm",
145
+ "java": "tree-sitter-java.wasm"
140
146
  };
141
147
  for (const [name, file] of Object.entries(grammarFiles)) {
142
148
  const wasmPath = path.join(grammarsDir, file);
@@ -3197,11 +3203,730 @@ function processCallExpression7(node, context) {
3197
3203
  });
3198
3204
  }
3199
3205
  }
3200
- function parseCsprojFile(filePath, sourceCode, projectRoot) {
3206
+ function parseCsprojFile(filePath, sourceCode, projectRoot) {
3207
+ const symbols = [];
3208
+ const edges = [];
3209
+ const lines = sourceCode.split("\n");
3210
+ const projectName = basename(filePath, ".csproj");
3211
+ symbols.push({
3212
+ id: `${filePath}::${projectName}`,
3213
+ name: projectName,
3214
+ kind: "module",
3215
+ filePath,
3216
+ startLine: 1,
3217
+ endLine: lines.length,
3218
+ exported: true
3219
+ });
3220
+ for (let i = 0; i < lines.length; i++) {
3221
+ const line = lines[i];
3222
+ const lineNum = i + 1;
3223
+ const projectRefMatch = line.match(/<ProjectReference\s+Include\s*=\s*"([^"]+)"/);
3224
+ if (projectRefMatch) {
3225
+ const refPath = projectRefMatch[1];
3226
+ const csprojDir = dirname7(join8(projectRoot, filePath));
3227
+ const resolvedRef = resolve3(csprojDir, refPath);
3228
+ const relativeRef = resolvedRef.startsWith(projectRoot + "/") ? resolvedRef.substring(projectRoot.length + 1) : null;
3229
+ if (relativeRef && existsSync8(resolvedRef)) {
3230
+ edges.push({
3231
+ source: `${filePath}::__file__`,
3232
+ target: `${relativeRef}::__file__`,
3233
+ kind: "imports",
3234
+ filePath,
3235
+ line: lineNum
3236
+ });
3237
+ }
3238
+ }
3239
+ const packageRefMatch = line.match(/<PackageReference\s+Include\s*=\s*"([^"]+)"/);
3240
+ if (packageRefMatch) {
3241
+ const packageName = packageRefMatch[1];
3242
+ const versionMatch = line.match(/Version\s*=\s*"([^"]+)"/);
3243
+ const version = versionMatch ? versionMatch[1] : "unknown";
3244
+ symbols.push({
3245
+ id: `${filePath}::pkg:${packageName}`,
3246
+ name: `${packageName}@${version}`,
3247
+ kind: "import",
3248
+ filePath,
3249
+ startLine: lineNum,
3250
+ endLine: lineNum,
3251
+ exported: false
3252
+ });
3253
+ }
3254
+ }
3255
+ return { filePath, symbols, edges };
3256
+ }
3257
+ function resolveCSharpNamespace(namespace, currentFile, projectRoot) {
3258
+ const namespacePath = namespace.replace(/\./g, "/");
3259
+ const candidates = [
3260
+ join8(projectRoot, namespacePath),
3261
+ join8(projectRoot, "src", namespacePath)
3262
+ ];
3263
+ for (const candidate of candidates) {
3264
+ if (existsSync8(candidate)) {
3265
+ try {
3266
+ const stats = statSync2(candidate);
3267
+ if (stats.isDirectory()) {
3268
+ const csFiles = readdirSync5(candidate).filter((f) => f.endsWith(".cs"));
3269
+ if (csFiles.length > 0) {
3270
+ const fullPath = join8(candidate, csFiles[0]);
3271
+ return fullPath.substring(projectRoot.length + 1);
3272
+ }
3273
+ }
3274
+ } catch {
3275
+ }
3276
+ }
3277
+ }
3278
+ return null;
3279
+ }
3280
+ function resolveSymbol6(name, context) {
3281
+ if (context.imports.has(name)) {
3282
+ return context.imports.get(name) || null;
3283
+ }
3284
+ const currentFileId = `${context.filePath}::${name}`;
3285
+ if (context.symbols.find((s) => s.id === currentFileId)) {
3286
+ return currentFileId;
3287
+ }
3288
+ if (context.currentClass) {
3289
+ const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
3290
+ if (context.symbols.find((s) => s.id === classMethodId)) {
3291
+ return classMethodId;
3292
+ }
3293
+ }
3294
+ return null;
3295
+ }
3296
+ function hasModifier(node, context, modifier) {
3297
+ for (let i = 0; i < node.childCount; i++) {
3298
+ const child = node.child(i);
3299
+ if (child && child.type === "modifier" && nodeText6(child, context) === modifier) {
3300
+ return true;
3301
+ }
3302
+ }
3303
+ if (modifier === "internal") {
3304
+ const hasExplicitAccess = ["public", "private", "protected", "internal"].some((m) => {
3305
+ for (let i = 0; i < node.childCount; i++) {
3306
+ const child = node.child(i);
3307
+ if (child && child.type === "modifier" && nodeText6(child, context) === m) return true;
3308
+ }
3309
+ return false;
3310
+ });
3311
+ return !hasExplicitAccess;
3312
+ }
3313
+ return false;
3314
+ }
3315
+ function extractBaseTypeName(node, context) {
3316
+ const text = nodeText6(node, context).trim();
3317
+ if (!text || text === ":" || text === ",") return null;
3318
+ const angleBracketIdx = text.indexOf("<");
3319
+ const name = angleBracketIdx > 0 ? text.substring(0, angleBracketIdx) : text;
3320
+ const dotIdx = name.lastIndexOf(".");
3321
+ return dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
3322
+ }
3323
+ function findChildByType7(node, type) {
3324
+ for (let i = 0; i < node.childCount; i++) {
3325
+ const child = node.child(i);
3326
+ if (child && child.type === type) return child;
3327
+ }
3328
+ return null;
3329
+ }
3330
+ function findChildrenByType2(node, type) {
3331
+ const results = [];
3332
+ for (let i = 0; i < node.childCount; i++) {
3333
+ const child = node.child(i);
3334
+ if (child && child.type === type) results.push(child);
3335
+ }
3336
+ return results;
3337
+ }
3338
+ function nodeText6(node, context) {
3339
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
3340
+ }
3341
+ function getCurrentSymbolId7(context) {
3342
+ if (context.currentScope.length === 0) return null;
3343
+ return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
3344
+ }
3345
+ var csharpParser = {
3346
+ name: "csharp",
3347
+ extensions: [".cs", ".csx", ".csproj"],
3348
+ parseFile: parseCSharpFile
3349
+ };
3350
+
3351
+ // src/parser/java.ts
3352
+ import { dirname as dirname8, join as join9, resolve as resolve4, basename as basename2 } from "path";
3353
+ import { existsSync as existsSync9, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
3354
+ function parseJavaFile(filePath, sourceCode, projectRoot) {
3355
+ if (filePath.endsWith("pom.xml")) {
3356
+ return parsePomXml(filePath, sourceCode, projectRoot);
3357
+ }
3358
+ if (filePath.endsWith("build.gradle") || filePath.endsWith("build.gradle.kts")) {
3359
+ return parseGradleBuild(filePath, sourceCode, projectRoot);
3360
+ }
3361
+ const parser = getParser("java");
3362
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
3363
+ const context = {
3364
+ filePath,
3365
+ projectRoot,
3366
+ sourceCode,
3367
+ symbols: [],
3368
+ edges: [],
3369
+ currentScope: [],
3370
+ currentClass: null,
3371
+ currentPackage: null,
3372
+ imports: /* @__PURE__ */ new Map(),
3373
+ isBuildFile: false
3374
+ };
3375
+ walkNode8(tree.rootNode, context);
3376
+ return {
3377
+ filePath,
3378
+ symbols: context.symbols,
3379
+ edges: context.edges
3380
+ };
3381
+ }
3382
+ function walkNode8(node, context) {
3383
+ processNode8(node, context);
3384
+ for (let i = 0; i < node.childCount; i++) {
3385
+ const child = node.child(i);
3386
+ if (child) {
3387
+ walkNode8(child, context);
3388
+ }
3389
+ }
3390
+ }
3391
+ function processNode8(node, context) {
3392
+ switch (node.type) {
3393
+ case "package_declaration":
3394
+ processPackageDeclaration(node, context);
3395
+ break;
3396
+ case "import_declaration":
3397
+ processImportDeclaration2(node, context);
3398
+ break;
3399
+ case "class_declaration":
3400
+ processClassDeclaration4(node, context);
3401
+ break;
3402
+ case "interface_declaration":
3403
+ processInterfaceDeclaration3(node, context);
3404
+ break;
3405
+ case "enum_declaration":
3406
+ processEnumDeclaration3(node, context);
3407
+ break;
3408
+ case "annotation_type_declaration":
3409
+ processAnnotationTypeDeclaration(node, context);
3410
+ break;
3411
+ case "record_declaration":
3412
+ processRecordDeclaration2(node, context);
3413
+ break;
3414
+ case "method_declaration":
3415
+ processMethodDeclaration3(node, context);
3416
+ break;
3417
+ case "constructor_declaration":
3418
+ processConstructorDeclaration2(node, context);
3419
+ break;
3420
+ case "field_declaration":
3421
+ processFieldDeclaration(node, context);
3422
+ break;
3423
+ case "constant_declaration":
3424
+ processConstantDeclaration(node, context);
3425
+ break;
3426
+ case "annotation_type_element_declaration":
3427
+ processAnnotationElement(node, context);
3428
+ break;
3429
+ case "method_invocation":
3430
+ processCallExpression8(node, context);
3431
+ break;
3432
+ case "object_creation_expression":
3433
+ processObjectCreation(node, context);
3434
+ break;
3435
+ case "lambda_expression":
3436
+ processLambdaExpression(node, context);
3437
+ break;
3438
+ }
3439
+ }
3440
+ function processPackageDeclaration(node, context) {
3441
+ const scopedIdent = findDescendantByTypes(node, ["scoped_identifier", "identifier"]);
3442
+ if (!scopedIdent) return;
3443
+ const name = nodeText7(scopedIdent, context);
3444
+ const symbolId = `${context.filePath}::${name}`;
3445
+ context.symbols.push({
3446
+ id: symbolId,
3447
+ name,
3448
+ kind: "module",
3449
+ filePath: context.filePath,
3450
+ startLine: node.startPosition.row + 1,
3451
+ endLine: node.endPosition.row + 1,
3452
+ exported: true
3453
+ });
3454
+ context.currentPackage = name;
3455
+ }
3456
+ function processImportDeclaration2(node, context) {
3457
+ const text = nodeText7(node, context).trim();
3458
+ const isStatic = text.includes("import static");
3459
+ const isWildcard = text.includes(".*");
3460
+ const scopedIdent = findDescendantByTypes(node, ["scoped_identifier", "identifier"]);
3461
+ if (!scopedIdent) return;
3462
+ let importPath = nodeText7(scopedIdent, context);
3463
+ const asterisk = findChildByType8(node, "asterisk");
3464
+ if (asterisk) {
3465
+ importPath = importPath + ".*";
3466
+ }
3467
+ const resolvedPath = resolveJavaImport(importPath, context.filePath, context.projectRoot);
3468
+ if (resolvedPath) {
3469
+ const sourceId = `${context.filePath}::__file__`;
3470
+ const targetId = `${resolvedPath}::__file__`;
3471
+ context.edges.push({
3472
+ source: sourceId,
3473
+ target: targetId,
3474
+ kind: "imports",
3475
+ filePath: context.filePath,
3476
+ line: node.startPosition.row + 1
3477
+ });
3478
+ const parts = importPath.split(".");
3479
+ if (!isWildcard) {
3480
+ const simpleName = parts[parts.length - 1];
3481
+ context.imports.set(simpleName, `${resolvedPath}::${simpleName}`);
3482
+ }
3483
+ }
3484
+ const symbolId = `${context.filePath}::import:${importPath}`;
3485
+ context.symbols.push({
3486
+ id: symbolId,
3487
+ name: importPath,
3488
+ kind: "import",
3489
+ filePath: context.filePath,
3490
+ startLine: node.startPosition.row + 1,
3491
+ endLine: node.endPosition.row + 1,
3492
+ exported: false
3493
+ });
3494
+ }
3495
+ function processClassDeclaration4(node, context) {
3496
+ processTypeDeclaration3(node, context, "class");
3497
+ }
3498
+ function processInterfaceDeclaration3(node, context) {
3499
+ processTypeDeclaration3(node, context, "interface");
3500
+ }
3501
+ function processRecordDeclaration2(node, context) {
3502
+ processTypeDeclaration3(node, context, "class");
3503
+ }
3504
+ function processAnnotationTypeDeclaration(node, context) {
3505
+ processTypeDeclaration3(node, context, "interface");
3506
+ }
3507
+ function processTypeDeclaration3(node, context, kind) {
3508
+ const nameNode = node.childForFieldName("name");
3509
+ if (!nameNode) return;
3510
+ let name = nodeText7(nameNode, context);
3511
+ const angleBracketIdx = name.indexOf("<");
3512
+ if (angleBracketIdx > 0) {
3513
+ name = name.substring(0, angleBracketIdx);
3514
+ }
3515
+ const exported = hasModifier2(node, context, "public");
3516
+ const scope = context.currentClass || void 0;
3517
+ const symbolId = `${context.filePath}::${name}`;
3518
+ context.symbols.push({
3519
+ id: symbolId,
3520
+ name,
3521
+ kind,
3522
+ filePath: context.filePath,
3523
+ startLine: node.startPosition.row + 1,
3524
+ endLine: node.endPosition.row + 1,
3525
+ exported,
3526
+ scope
3527
+ });
3528
+ const superclass = node.childForFieldName("superclass");
3529
+ if (superclass) {
3530
+ let baseName = extractTypeName3(superclass, context);
3531
+ if (baseName) {
3532
+ const baseId = resolveSymbol7(baseName, context);
3533
+ if (baseId) {
3534
+ context.edges.push({
3535
+ source: symbolId,
3536
+ target: baseId,
3537
+ kind: "inherits",
3538
+ filePath: context.filePath,
3539
+ line: superclass.startPosition.row + 1
3540
+ });
3541
+ }
3542
+ }
3543
+ }
3544
+ const interfaces = node.childForFieldName("interfaces");
3545
+ if (interfaces) {
3546
+ processInterfaceList(interfaces, symbolId, context);
3547
+ }
3548
+ const extendsInterfaces = node.childForFieldName("extends_interfaces") || findChildByType8(node, "extends_interfaces");
3549
+ if (extendsInterfaces) {
3550
+ processInterfaceList(extendsInterfaces, symbolId, context);
3551
+ }
3552
+ const oldClass = context.currentClass;
3553
+ context.currentClass = name;
3554
+ context.currentScope.push(name);
3555
+ const body = node.childForFieldName("body") || findChildByType8(node, "class_body") || findChildByType8(node, "interface_body") || findChildByType8(node, "enum_body") || findChildByType8(node, "annotation_type_body") || findChildByType8(node, "record_declaration_body");
3556
+ if (body) {
3557
+ walkNode8(body, context);
3558
+ }
3559
+ context.currentScope.pop();
3560
+ context.currentClass = oldClass;
3561
+ }
3562
+ function processInterfaceList(node, sourceId, context) {
3563
+ for (let i = 0; i < node.childCount; i++) {
3564
+ const child = node.child(i);
3565
+ if (!child) continue;
3566
+ if (child.type === "type_identifier" || child.type === "generic_type" || child.type === "scoped_type_identifier") {
3567
+ const baseName = extractTypeName3(child, context);
3568
+ if (baseName) {
3569
+ const baseId = resolveSymbol7(baseName, context);
3570
+ if (baseId) {
3571
+ context.edges.push({
3572
+ source: sourceId,
3573
+ target: baseId,
3574
+ kind: "implements",
3575
+ filePath: context.filePath,
3576
+ line: child.startPosition.row + 1
3577
+ });
3578
+ }
3579
+ }
3580
+ }
3581
+ }
3582
+ }
3583
+ function processEnumDeclaration3(node, context) {
3584
+ const nameNode = node.childForFieldName("name");
3585
+ if (!nameNode) return;
3586
+ const name = nodeText7(nameNode, context);
3587
+ const exported = hasModifier2(node, context, "public");
3588
+ const symbolId = `${context.filePath}::${name}`;
3589
+ context.symbols.push({
3590
+ id: symbolId,
3591
+ name,
3592
+ kind: "enum",
3593
+ filePath: context.filePath,
3594
+ startLine: node.startPosition.row + 1,
3595
+ endLine: node.endPosition.row + 1,
3596
+ exported
3597
+ });
3598
+ const body = node.childForFieldName("body") || findChildByType8(node, "enum_body");
3599
+ if (body) {
3600
+ const constants = findChildrenByType3(body, "enum_constant");
3601
+ for (const constant of constants) {
3602
+ const constNameNode = constant.childForFieldName("name");
3603
+ if (!constNameNode) continue;
3604
+ const constName = nodeText7(constNameNode, context);
3605
+ const constId = `${context.filePath}::${name}.${constName}`;
3606
+ context.symbols.push({
3607
+ id: constId,
3608
+ name: constName,
3609
+ kind: "constant",
3610
+ filePath: context.filePath,
3611
+ startLine: constant.startPosition.row + 1,
3612
+ endLine: constant.endPosition.row + 1,
3613
+ exported,
3614
+ scope: name
3615
+ });
3616
+ }
3617
+ const oldClass = context.currentClass;
3618
+ context.currentClass = name;
3619
+ context.currentScope.push(name);
3620
+ for (let i = 0; i < body.childCount; i++) {
3621
+ const child = body.child(i);
3622
+ if (child && child.type !== "enum_constant") {
3623
+ walkNode8(child, context);
3624
+ }
3625
+ }
3626
+ context.currentScope.pop();
3627
+ context.currentClass = oldClass;
3628
+ }
3629
+ }
3630
+ function processMethodDeclaration3(node, context) {
3631
+ const nameNode = node.childForFieldName("name");
3632
+ if (!nameNode) return;
3633
+ const name = nodeText7(nameNode, context);
3634
+ const exported = hasModifier2(node, context, "public");
3635
+ const scope = context.currentClass || void 0;
3636
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
3637
+ context.symbols.push({
3638
+ id: symbolId,
3639
+ name,
3640
+ kind: context.currentClass ? "method" : "function",
3641
+ filePath: context.filePath,
3642
+ startLine: node.startPosition.row + 1,
3643
+ endLine: node.endPosition.row + 1,
3644
+ exported,
3645
+ scope
3646
+ });
3647
+ const scopeName = scope ? `${scope}.${name}` : name;
3648
+ context.currentScope.push(scopeName);
3649
+ const body = node.childForFieldName("body");
3650
+ if (body) {
3651
+ walkNode8(body, context);
3652
+ }
3653
+ context.currentScope.pop();
3654
+ }
3655
+ function processConstructorDeclaration2(node, context) {
3656
+ const nameNode = node.childForFieldName("name");
3657
+ if (!nameNode) return;
3658
+ const name = nodeText7(nameNode, context);
3659
+ const exported = hasModifier2(node, context, "public");
3660
+ const scope = context.currentClass || void 0;
3661
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
3662
+ context.symbols.push({
3663
+ id: symbolId,
3664
+ name,
3665
+ kind: "method",
3666
+ filePath: context.filePath,
3667
+ startLine: node.startPosition.row + 1,
3668
+ endLine: node.endPosition.row + 1,
3669
+ exported,
3670
+ scope
3671
+ });
3672
+ const scopeName = scope ? `${scope}.${name}` : name;
3673
+ context.currentScope.push(scopeName);
3674
+ const body = node.childForFieldName("body");
3675
+ if (body) {
3676
+ walkNode8(body, context);
3677
+ }
3678
+ context.currentScope.pop();
3679
+ }
3680
+ function processFieldDeclaration(node, context) {
3681
+ const declarator = findDescendantByTypes(node, ["variable_declarator"]);
3682
+ if (!declarator) return;
3683
+ const nameNode = declarator.childForFieldName("name");
3684
+ if (!nameNode) return;
3685
+ const name = nodeText7(nameNode, context);
3686
+ const exported = hasModifier2(node, context, "public");
3687
+ const scope = context.currentClass || void 0;
3688
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
3689
+ const isConstant = hasModifier2(node, context, "static") && hasModifier2(node, context, "final");
3690
+ context.symbols.push({
3691
+ id: symbolId,
3692
+ name,
3693
+ kind: isConstant ? "constant" : "property",
3694
+ filePath: context.filePath,
3695
+ startLine: node.startPosition.row + 1,
3696
+ endLine: node.endPosition.row + 1,
3697
+ exported,
3698
+ scope
3699
+ });
3700
+ }
3701
+ function processConstantDeclaration(node, context) {
3702
+ const declarator = findDescendantByTypes(node, ["variable_declarator"]);
3703
+ if (!declarator) return;
3704
+ const nameNode = declarator.childForFieldName("name");
3705
+ if (!nameNode) return;
3706
+ const name = nodeText7(nameNode, context);
3707
+ const scope = context.currentClass || void 0;
3708
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
3709
+ context.symbols.push({
3710
+ id: symbolId,
3711
+ name,
3712
+ kind: "constant",
3713
+ filePath: context.filePath,
3714
+ startLine: node.startPosition.row + 1,
3715
+ endLine: node.endPosition.row + 1,
3716
+ exported: true,
3717
+ // Interface constants are always public
3718
+ scope
3719
+ });
3720
+ }
3721
+ function processAnnotationElement(node, context) {
3722
+ const nameNode = node.childForFieldName("name");
3723
+ if (!nameNode) return;
3724
+ const name = nodeText7(nameNode, context);
3725
+ const scope = context.currentClass || void 0;
3726
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
3727
+ context.symbols.push({
3728
+ id: symbolId,
3729
+ name,
3730
+ kind: "method",
3731
+ filePath: context.filePath,
3732
+ startLine: node.startPosition.row + 1,
3733
+ endLine: node.endPosition.row + 1,
3734
+ exported: true,
3735
+ scope
3736
+ });
3737
+ }
3738
+ function processCallExpression8(node, context) {
3739
+ const nameNode = node.childForFieldName("name");
3740
+ if (!nameNode) return;
3741
+ const calleeName = nodeText7(nameNode, context);
3742
+ const builtins = [
3743
+ "toString",
3744
+ "equals",
3745
+ "hashCode",
3746
+ "getClass",
3747
+ "println",
3748
+ "printf",
3749
+ "format",
3750
+ "parseInt",
3751
+ "valueOf",
3752
+ "length",
3753
+ "size",
3754
+ "get",
3755
+ "set",
3756
+ "add",
3757
+ "remove",
3758
+ "contains",
3759
+ "isEmpty",
3760
+ "stream",
3761
+ "collect",
3762
+ "map",
3763
+ "filter",
3764
+ "forEach",
3765
+ "of",
3766
+ "orElse",
3767
+ "orElseThrow",
3768
+ "isPresent",
3769
+ "ifPresent",
3770
+ "close",
3771
+ "flush",
3772
+ "write",
3773
+ "read"
3774
+ ];
3775
+ if (builtins.includes(calleeName)) return;
3776
+ const callerId = getCurrentSymbolId8(context);
3777
+ if (!callerId) return;
3778
+ const calleeId = resolveSymbol7(calleeName, context);
3779
+ if (calleeId) {
3780
+ context.edges.push({
3781
+ source: callerId,
3782
+ target: calleeId,
3783
+ kind: "calls",
3784
+ filePath: context.filePath,
3785
+ line: node.startPosition.row + 1
3786
+ });
3787
+ }
3788
+ }
3789
+ function processObjectCreation(node, context) {
3790
+ const typeNode = node.childForFieldName("type");
3791
+ if (!typeNode) return;
3792
+ const typeName = extractTypeName3(typeNode, context);
3793
+ if (!typeName) return;
3794
+ const callerId = getCurrentSymbolId8(context);
3795
+ if (!callerId) return;
3796
+ const targetId = resolveSymbol7(typeName, context);
3797
+ if (targetId) {
3798
+ context.edges.push({
3799
+ source: callerId,
3800
+ target: targetId,
3801
+ kind: "references",
3802
+ filePath: context.filePath,
3803
+ line: node.startPosition.row + 1
3804
+ });
3805
+ }
3806
+ const classBody = findChildByType8(node, "class_body");
3807
+ if (classBody) {
3808
+ const anonName = `<anonymous:${typeName}>`;
3809
+ const anonId = `${context.filePath}::${anonName}:${node.startPosition.row + 1}`;
3810
+ context.symbols.push({
3811
+ id: anonId,
3812
+ name: anonName,
3813
+ kind: "class",
3814
+ filePath: context.filePath,
3815
+ startLine: node.startPosition.row + 1,
3816
+ endLine: node.endPosition.row + 1,
3817
+ exported: false,
3818
+ scope: context.currentClass || void 0
3819
+ });
3820
+ const oldClass = context.currentClass;
3821
+ context.currentClass = anonName;
3822
+ context.currentScope.push(anonName);
3823
+ walkNode8(classBody, context);
3824
+ context.currentScope.pop();
3825
+ context.currentClass = oldClass;
3826
+ }
3827
+ }
3828
+ function processLambdaExpression(node, context) {
3829
+ const parent = node.parent;
3830
+ if (!parent) return;
3831
+ if (parent.type === "variable_declarator") {
3832
+ const nameNode = parent.childForFieldName("name");
3833
+ if (nameNode) {
3834
+ const name = nodeText7(nameNode, context);
3835
+ const scope = context.currentClass || void 0;
3836
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
3837
+ context.symbols.push({
3838
+ id: symbolId,
3839
+ name,
3840
+ kind: "function",
3841
+ filePath: context.filePath,
3842
+ startLine: node.startPosition.row + 1,
3843
+ endLine: node.endPosition.row + 1,
3844
+ exported: false,
3845
+ scope
3846
+ });
3847
+ }
3848
+ }
3849
+ }
3850
+ function parsePomXml(filePath, sourceCode, projectRoot) {
3851
+ const symbols = [];
3852
+ const edges = [];
3853
+ const lines = sourceCode.split("\n");
3854
+ const projectName = basename2(dirname8(join9(projectRoot, filePath)));
3855
+ symbols.push({
3856
+ id: `${filePath}::${projectName}`,
3857
+ name: projectName,
3858
+ kind: "module",
3859
+ filePath,
3860
+ startLine: 1,
3861
+ endLine: lines.length,
3862
+ exported: true
3863
+ });
3864
+ let inDependency = false;
3865
+ let groupId = "";
3866
+ let artifactId = "";
3867
+ let version = "";
3868
+ let depStartLine = 0;
3869
+ for (let i = 0; i < lines.length; i++) {
3870
+ const line = lines[i];
3871
+ const lineNum = i + 1;
3872
+ if (/<dependency>/.test(line)) {
3873
+ inDependency = true;
3874
+ groupId = "";
3875
+ artifactId = "";
3876
+ version = "";
3877
+ depStartLine = lineNum;
3878
+ }
3879
+ if (inDependency) {
3880
+ const gMatch = line.match(/<groupId>([^<]+)<\/groupId>/);
3881
+ if (gMatch) groupId = gMatch[1];
3882
+ const aMatch = line.match(/<artifactId>([^<]+)<\/artifactId>/);
3883
+ if (aMatch) artifactId = aMatch[1];
3884
+ const vMatch = line.match(/<version>([^<]+)<\/version>/);
3885
+ if (vMatch) version = vMatch[1];
3886
+ }
3887
+ if (/<\/dependency>/.test(line) && inDependency) {
3888
+ inDependency = false;
3889
+ if (groupId && artifactId) {
3890
+ const depName = `${groupId}:${artifactId}`;
3891
+ const displayVersion = version || "managed";
3892
+ symbols.push({
3893
+ id: `${filePath}::dep:${depName}`,
3894
+ name: `${depName}@${displayVersion}`,
3895
+ kind: "import",
3896
+ filePath,
3897
+ startLine: depStartLine,
3898
+ endLine: lineNum,
3899
+ exported: false
3900
+ });
3901
+ }
3902
+ }
3903
+ const moduleMatch = line.match(/<module>([^<]+)<\/module>/);
3904
+ if (moduleMatch) {
3905
+ const modulePath = moduleMatch[1];
3906
+ const pomDir = dirname8(join9(projectRoot, filePath));
3907
+ const resolvedModule = resolve4(pomDir, modulePath);
3908
+ const relativeModule = resolvedModule.startsWith(projectRoot + "/") ? resolvedModule.substring(projectRoot.length + 1) : null;
3909
+ if (relativeModule) {
3910
+ const modulePom = join9(relativeModule, "pom.xml");
3911
+ if (existsSync9(join9(projectRoot, modulePom))) {
3912
+ edges.push({
3913
+ source: `${filePath}::__file__`,
3914
+ target: `${modulePom}::__file__`,
3915
+ kind: "imports",
3916
+ filePath,
3917
+ line: lineNum
3918
+ });
3919
+ }
3920
+ }
3921
+ }
3922
+ }
3923
+ return { filePath, symbols, edges };
3924
+ }
3925
+ function parseGradleBuild(filePath, sourceCode, projectRoot) {
3201
3926
  const symbols = [];
3202
3927
  const edges = [];
3203
3928
  const lines = sourceCode.split("\n");
3204
- const projectName = basename(filePath, ".csproj");
3929
+ const projectName = basename2(dirname8(join9(projectRoot, filePath)));
3205
3930
  symbols.push({
3206
3931
  id: `${filePath}::${projectName}`,
3207
3932
  name: projectName,
@@ -3212,66 +3937,86 @@ function parseCsprojFile(filePath, sourceCode, projectRoot) {
3212
3937
  exported: true
3213
3938
  });
3214
3939
  for (let i = 0; i < lines.length; i++) {
3215
- const line = lines[i];
3940
+ const line = lines[i].trim();
3216
3941
  const lineNum = i + 1;
3217
- const projectRefMatch = line.match(/<ProjectReference\s+Include\s*=\s*"([^"]+)"/);
3218
- if (projectRefMatch) {
3219
- const refPath = projectRefMatch[1];
3220
- const csprojDir = dirname7(join8(projectRoot, filePath));
3221
- const resolvedRef = resolve3(csprojDir, refPath);
3222
- const relativeRef = resolvedRef.startsWith(projectRoot + "/") ? resolvedRef.substring(projectRoot.length + 1) : null;
3223
- if (relativeRef && existsSync8(resolvedRef)) {
3224
- edges.push({
3225
- source: `${filePath}::__file__`,
3226
- target: `${relativeRef}::__file__`,
3227
- kind: "imports",
3942
+ const depMatch = line.match(
3943
+ /(?:implementation|api|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|annotationProcessor)\s*[\(]?\s*['"]([^'"]+)['"]\s*[\)]?/
3944
+ );
3945
+ if (depMatch) {
3946
+ const depCoord = depMatch[1];
3947
+ if (!depCoord.startsWith(":")) {
3948
+ symbols.push({
3949
+ id: `${filePath}::dep:${depCoord}`,
3950
+ name: depCoord,
3951
+ kind: "import",
3228
3952
  filePath,
3229
- line: lineNum
3953
+ startLine: lineNum,
3954
+ endLine: lineNum,
3955
+ exported: false
3230
3956
  });
3231
3957
  }
3232
3958
  }
3233
- const packageRefMatch = line.match(/<PackageReference\s+Include\s*=\s*"([^"]+)"/);
3234
- if (packageRefMatch) {
3235
- const packageName = packageRefMatch[1];
3236
- const versionMatch = line.match(/Version\s*=\s*"([^"]+)"/);
3237
- const version = versionMatch ? versionMatch[1] : "unknown";
3238
- symbols.push({
3239
- id: `${filePath}::pkg:${packageName}`,
3240
- name: `${packageName}@${version}`,
3241
- kind: "import",
3242
- filePath,
3243
- startLine: lineNum,
3244
- endLine: lineNum,
3245
- exported: false
3246
- });
3959
+ const projectMatch = line.match(/project\s*\(\s*['":]+([^'")\s]+)['"]*\s*\)/);
3960
+ if (projectMatch) {
3961
+ const moduleName = projectMatch[1].replace(/^:/, "");
3962
+ const candidates = [
3963
+ join9(moduleName, "build.gradle"),
3964
+ join9(moduleName, "build.gradle.kts")
3965
+ ];
3966
+ for (const candidate of candidates) {
3967
+ if (existsSync9(join9(projectRoot, candidate))) {
3968
+ edges.push({
3969
+ source: `${filePath}::__file__`,
3970
+ target: `${candidate}::__file__`,
3971
+ kind: "imports",
3972
+ filePath,
3973
+ line: lineNum
3974
+ });
3975
+ break;
3976
+ }
3977
+ }
3247
3978
  }
3248
3979
  }
3249
3980
  return { filePath, symbols, edges };
3250
3981
  }
3251
- function resolveCSharpNamespace(namespace, currentFile, projectRoot) {
3252
- const namespacePath = namespace.replace(/\./g, "/");
3253
- const candidates = [
3254
- join8(projectRoot, namespacePath),
3255
- join8(projectRoot, "src", namespacePath)
3982
+ function resolveJavaImport(importPath, currentFile, projectRoot) {
3983
+ const cleanPath2 = importPath.replace(/\.\*$/, "");
3984
+ const javaPath = cleanPath2.replace(/\./g, "/") + ".java";
3985
+ const sourceRoots = [
3986
+ "",
3987
+ "src/main/java",
3988
+ "src",
3989
+ "app/src/main/java"
3256
3990
  ];
3257
- for (const candidate of candidates) {
3258
- if (existsSync8(candidate)) {
3259
- try {
3260
- const stats = statSync2(candidate);
3261
- if (stats.isDirectory()) {
3262
- const csFiles = readdirSync5(candidate).filter((f) => f.endsWith(".cs"));
3263
- if (csFiles.length > 0) {
3264
- const fullPath = join8(candidate, csFiles[0]);
3265
- return fullPath.substring(projectRoot.length + 1);
3991
+ for (const root of sourceRoots) {
3992
+ const candidate = root ? join9(root, javaPath) : javaPath;
3993
+ const fullPath = join9(projectRoot, candidate);
3994
+ if (existsSync9(fullPath)) {
3995
+ return candidate;
3996
+ }
3997
+ }
3998
+ if (importPath.endsWith(".*")) {
3999
+ const packagePath = cleanPath2.replace(/\./g, "/");
4000
+ for (const root of sourceRoots) {
4001
+ const candidate = root ? join9(root, packagePath) : packagePath;
4002
+ const fullPath = join9(projectRoot, candidate);
4003
+ if (existsSync9(fullPath)) {
4004
+ try {
4005
+ const stats = statSync3(fullPath);
4006
+ if (stats.isDirectory()) {
4007
+ const javaFiles = readdirSync6(fullPath).filter((f) => f.endsWith(".java"));
4008
+ if (javaFiles.length > 0) {
4009
+ return join9(candidate, javaFiles[0]);
4010
+ }
3266
4011
  }
4012
+ } catch {
3267
4013
  }
3268
- } catch {
3269
4014
  }
3270
4015
  }
3271
4016
  }
3272
4017
  return null;
3273
4018
  }
3274
- function resolveSymbol6(name, context) {
4019
+ function resolveSymbol7(name, context) {
3275
4020
  if (context.imports.has(name)) {
3276
4021
  return context.imports.get(name) || null;
3277
4022
  }
@@ -3287,41 +4032,40 @@ function resolveSymbol6(name, context) {
3287
4032
  }
3288
4033
  return null;
3289
4034
  }
3290
- function hasModifier(node, context, modifier) {
4035
+ function hasModifier2(node, context, modifier) {
4036
+ const modifiers = node.childForFieldName("modifiers") || findChildByType8(node, "modifiers");
4037
+ if (modifiers) {
4038
+ for (let i = 0; i < modifiers.childCount; i++) {
4039
+ const child = modifiers.child(i);
4040
+ if (child && nodeText7(child, context) === modifier) {
4041
+ return true;
4042
+ }
4043
+ }
4044
+ }
3291
4045
  for (let i = 0; i < node.childCount; i++) {
3292
4046
  const child = node.child(i);
3293
- if (child && child.type === "modifier" && nodeText6(child, context) === modifier) {
4047
+ if (child && child.type === modifier) {
3294
4048
  return true;
3295
4049
  }
3296
4050
  }
3297
- if (modifier === "internal") {
3298
- const hasExplicitAccess = ["public", "private", "protected", "internal"].some((m) => {
3299
- for (let i = 0; i < node.childCount; i++) {
3300
- const child = node.child(i);
3301
- if (child && child.type === "modifier" && nodeText6(child, context) === m) return true;
3302
- }
3303
- return false;
3304
- });
3305
- return !hasExplicitAccess;
3306
- }
3307
4051
  return false;
3308
4052
  }
3309
- function extractBaseTypeName(node, context) {
3310
- const text = nodeText6(node, context).trim();
3311
- if (!text || text === ":" || text === ",") return null;
4053
+ function extractTypeName3(node, context) {
4054
+ const text = nodeText7(node, context).trim();
4055
+ if (!text) return null;
3312
4056
  const angleBracketIdx = text.indexOf("<");
3313
4057
  const name = angleBracketIdx > 0 ? text.substring(0, angleBracketIdx) : text;
3314
4058
  const dotIdx = name.lastIndexOf(".");
3315
4059
  return dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
3316
4060
  }
3317
- function findChildByType7(node, type) {
4061
+ function findChildByType8(node, type) {
3318
4062
  for (let i = 0; i < node.childCount; i++) {
3319
4063
  const child = node.child(i);
3320
4064
  if (child && child.type === type) return child;
3321
4065
  }
3322
4066
  return null;
3323
4067
  }
3324
- function findChildrenByType2(node, type) {
4068
+ function findChildrenByType3(node, type) {
3325
4069
  const results = [];
3326
4070
  for (let i = 0; i < node.childCount; i++) {
3327
4071
  const child = node.child(i);
@@ -3329,17 +4073,27 @@ function findChildrenByType2(node, type) {
3329
4073
  }
3330
4074
  return results;
3331
4075
  }
3332
- function nodeText6(node, context) {
4076
+ function findDescendantByTypes(node, types) {
4077
+ for (let i = 0; i < node.childCount; i++) {
4078
+ const child = node.child(i);
4079
+ if (!child) continue;
4080
+ if (types.includes(child.type)) return child;
4081
+ const found = findDescendantByTypes(child, types);
4082
+ if (found) return found;
4083
+ }
4084
+ return null;
4085
+ }
4086
+ function nodeText7(node, context) {
3333
4087
  return context.sourceCode.substring(node.startIndex, node.endIndex);
3334
4088
  }
3335
- function getCurrentSymbolId7(context) {
4089
+ function getCurrentSymbolId8(context) {
3336
4090
  if (context.currentScope.length === 0) return null;
3337
4091
  return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
3338
4092
  }
3339
- var csharpParser = {
3340
- name: "csharp",
3341
- extensions: [".cs", ".csx", ".csproj"],
3342
- parseFile: parseCSharpFile
4093
+ var javaParser = {
4094
+ name: "java",
4095
+ extensions: [".java", "pom.xml", "build.gradle", "build.gradle.kts"],
4096
+ parseFile: parseJavaFile
3343
4097
  };
3344
4098
 
3345
4099
  // src/parser/detect.ts
@@ -3350,11 +4104,13 @@ var parsers = [
3350
4104
  goParser,
3351
4105
  rustParser,
3352
4106
  cParser,
3353
- csharpParser
4107
+ csharpParser,
4108
+ javaParser
3354
4109
  ];
3355
4110
  function getParserForFile(filePath) {
3356
- const ext = extname4(filePath).toLowerCase();
3357
- return parsers.find((p) => p.extensions.includes(ext)) || null;
4111
+ const ext = extname5(filePath).toLowerCase();
4112
+ const fileName = basename3(filePath);
4113
+ return parsers.find((p) => p.extensions.includes(ext) || p.extensions.includes(fileName)) || null;
3358
4114
  }
3359
4115
 
3360
4116
  // src/parser/index.ts
@@ -3362,7 +4118,7 @@ import { minimatch } from "minimatch";
3362
4118
  var MAX_FILE_SIZE = 1e6;
3363
4119
  function shouldParseFile(fullPath) {
3364
4120
  try {
3365
- const stats = statSync3(fullPath);
4121
+ const stats = statSync4(fullPath);
3366
4122
  if (stats.size > MAX_FILE_SIZE) {
3367
4123
  console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
3368
4124
  return false;
@@ -3380,8 +4136,8 @@ async function parseProject(projectRoot, options) {
3380
4136
  let errorFiles = 0;
3381
4137
  for (const file of files) {
3382
4138
  try {
3383
- const fullPath = join9(projectRoot, file);
3384
- if (!resolve4(fullPath).startsWith(resolve4(projectRoot))) {
4139
+ const fullPath = join10(projectRoot, file);
4140
+ if (!resolve5(fullPath).startsWith(resolve5(projectRoot))) {
3385
4141
  skippedFiles++;
3386
4142
  continue;
3387
4143
  }
@@ -3410,7 +4166,7 @@ async function parseProject(projectRoot, options) {
3410
4166
  skippedFiles++;
3411
4167
  continue;
3412
4168
  }
3413
- const sourceCode = readFileSync6(fullPath, "utf-8");
4169
+ const sourceCode = readFileSync7(fullPath, "utf-8");
3414
4170
  const parsed = parser.parseFile(file, sourceCode, projectRoot);
3415
4171
  parsedFiles.push(parsed);
3416
4172
  } catch (err) {
@@ -3433,14 +4189,15 @@ async function parseProject(projectRoot, options) {
3433
4189
  }
3434
4190
 
3435
4191
  // src/cross-language/detectors/rest-api.ts
3436
- import { readFileSync as readFileSync7 } from "fs";
3437
- import { join as join10, resolve as resolve5 } from "path";
4192
+ import { readFileSync as readFileSync8 } from "fs";
4193
+ import { join as join11, resolve as resolve6 } from "path";
3438
4194
  function getLanguage(filePath) {
3439
4195
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
3440
4196
  if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
3441
4197
  if (filePath.endsWith(".py")) return "python";
3442
4198
  if (filePath.endsWith(".go")) return "go";
3443
4199
  if (filePath.endsWith(".cs") || filePath.endsWith(".csx")) return "csharp";
4200
+ if (filePath.endsWith(".java")) return "java";
3444
4201
  return "unknown";
3445
4202
  }
3446
4203
  function normalizePath(routePath) {
@@ -3620,9 +4377,93 @@ function extractRouteDefinitions(source, filePath) {
3620
4377
  }
3621
4378
  }
3622
4379
  }
4380
+ if (lang === "java") {
4381
+ const springMethodMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
4382
+ if (springMethodMatch) {
4383
+ const method = springMethodMatch[1].toUpperCase();
4384
+ let path6 = springMethodMatch[2];
4385
+ const classPrefix = findClassLevelPrefix(source);
4386
+ if (classPrefix) path6 = classPrefix + path6;
4387
+ if (!path6.startsWith("/")) path6 = "/" + path6;
4388
+ routes.push({
4389
+ method,
4390
+ path: path6,
4391
+ normalizedPath: normalizePath(path6),
4392
+ file: filePath,
4393
+ line: i + 1
4394
+ });
4395
+ }
4396
+ if (!springMethodMatch) {
4397
+ const springNoPathMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*$/);
4398
+ if (springNoPathMatch) {
4399
+ const method = springNoPathMatch[1].toUpperCase();
4400
+ const classPrefix = findClassLevelPrefix(source);
4401
+ if (classPrefix) {
4402
+ routes.push({
4403
+ method,
4404
+ path: classPrefix,
4405
+ normalizedPath: normalizePath(classPrefix),
4406
+ file: filePath,
4407
+ line: i + 1
4408
+ });
4409
+ }
4410
+ }
4411
+ }
4412
+ const requestMappingMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
4413
+ if (requestMappingMatch) {
4414
+ let path6 = requestMappingMatch[1];
4415
+ if (!path6.startsWith("/")) path6 = "/" + path6;
4416
+ const methodMatch = line.match(/method\s*=\s*RequestMethod\.(\w+)/);
4417
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
4418
+ routes.push({
4419
+ method,
4420
+ path: path6,
4421
+ normalizedPath: normalizePath(path6),
4422
+ file: filePath,
4423
+ line: i + 1
4424
+ });
4425
+ }
4426
+ const jaxPathMatch = line.match(/@Path\s*\(\s*["']([^"']+)["']\s*\)/);
4427
+ if (jaxPathMatch) {
4428
+ let path6 = jaxPathMatch[1];
4429
+ if (!path6.startsWith("/")) path6 = "/" + path6;
4430
+ const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
4431
+ const prevLine = i > 0 ? lines[i - 1] : "";
4432
+ const jaxMethodMatch = (nextLine + prevLine).match(/@(GET|POST|PUT|DELETE|PATCH)/);
4433
+ const method = jaxMethodMatch ? jaxMethodMatch[1] : "ANY";
4434
+ routes.push({
4435
+ method,
4436
+ path: path6,
4437
+ normalizedPath: normalizePath(path6),
4438
+ file: filePath,
4439
+ line: i + 1
4440
+ });
4441
+ }
4442
+ const webFluxMatch = line.match(/(?:route|andRoute)\s*\(\s*(GET|POST|PUT|DELETE|PATCH)\s*\(\s*["']([^"']+)["']\s*\)/);
4443
+ if (webFluxMatch) {
4444
+ const path6 = webFluxMatch[2].startsWith("/") ? webFluxMatch[2] : "/" + webFluxMatch[2];
4445
+ routes.push({
4446
+ method: webFluxMatch[1].toUpperCase(),
4447
+ path: path6,
4448
+ normalizedPath: normalizePath(path6),
4449
+ file: filePath,
4450
+ line: i + 1
4451
+ });
4452
+ }
4453
+ }
3623
4454
  }
3624
4455
  return routes;
3625
4456
  }
4457
+ function findClassLevelPrefix(source) {
4458
+ const match = source.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
4459
+ if (match) {
4460
+ let path6 = match[1];
4461
+ if (!path6.startsWith("/")) path6 = "/" + path6;
4462
+ if (path6.endsWith("/") && path6.length > 1) path6 = path6.slice(0, -1);
4463
+ return path6;
4464
+ }
4465
+ return null;
4466
+ }
3626
4467
  function matchPaths(callPath, routeNormalized) {
3627
4468
  const normalizedCall = normalizePath(stripTrailingSlash(callPath));
3628
4469
  const normalizedRoute = stripTrailingSlash(routeNormalized);
@@ -3658,11 +4499,11 @@ function detectRestApiEdges(files, projectRoot) {
3658
4499
  const allCalls = [];
3659
4500
  const allRoutes = [];
3660
4501
  for (const file of files) {
3661
- const fullPath = join10(projectRoot, file.filePath);
3662
- if (!resolve5(fullPath).startsWith(resolve5(projectRoot))) continue;
4502
+ const fullPath = join11(projectRoot, file.filePath);
4503
+ if (!resolve6(fullPath).startsWith(resolve6(projectRoot))) continue;
3663
4504
  let source;
3664
4505
  try {
3665
- source = readFileSync7(fullPath, "utf-8");
4506
+ source = readFileSync8(fullPath, "utf-8");
3666
4507
  } catch {
3667
4508
  continue;
3668
4509
  }
@@ -3698,8 +4539,8 @@ function detectRestApiEdges(files, projectRoot) {
3698
4539
  }
3699
4540
 
3700
4541
  // src/cross-language/detectors/subprocess.ts
3701
- import { readFileSync as readFileSync8 } from "fs";
3702
- import { join as join11, resolve as resolve6, basename as basename2 } from "path";
4542
+ import { readFileSync as readFileSync9 } from "fs";
4543
+ import { join as join12, resolve as resolve7, basename as basename4 } from "path";
3703
4544
  var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
3704
4545
  function getLanguage2(filePath) {
3705
4546
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
@@ -3798,16 +4639,16 @@ function detectSubprocessEdges(files, projectRoot) {
3798
4639
  const knownFiles = new Set(files.map((f) => f.filePath));
3799
4640
  const basenameMap = /* @__PURE__ */ new Map();
3800
4641
  for (const f of files) {
3801
- const base = basename2(f.filePath);
4642
+ const base = basename4(f.filePath);
3802
4643
  if (!basenameMap.has(base)) basenameMap.set(base, []);
3803
4644
  basenameMap.get(base).push(f.filePath);
3804
4645
  }
3805
4646
  for (const file of files) {
3806
- const fullPath = join11(projectRoot, file.filePath);
3807
- if (!resolve6(fullPath).startsWith(resolve6(projectRoot))) continue;
4647
+ const fullPath = join12(projectRoot, file.filePath);
4648
+ if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) continue;
3808
4649
  let source;
3809
4650
  try {
3810
- source = readFileSync8(fullPath, "utf-8");
4651
+ source = readFileSync9(fullPath, "utf-8");
3811
4652
  } catch {
3812
4653
  continue;
3813
4654
  }
@@ -3819,7 +4660,7 @@ function detectSubprocessEdges(files, projectRoot) {
3819
4660
  targetFile = call.calledFile;
3820
4661
  confidence = "high";
3821
4662
  } else {
3822
- const base = basename2(call.calledFile);
4663
+ const base = basename4(call.calledFile);
3823
4664
  const candidates = basenameMap.get(base);
3824
4665
  if (candidates && candidates.length > 0) {
3825
4666
  const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
@@ -4198,7 +5039,7 @@ function getArchitectureSummary(graph) {
4198
5039
  }
4199
5040
 
4200
5041
  // src/health/metrics.ts
4201
- import { dirname as dirname8 } from "path";
5042
+ import { dirname as dirname9 } from "path";
4202
5043
  function scoreToGrade(score) {
4203
5044
  if (score >= 90) return "A";
4204
5045
  if (score >= 80) return "B";
@@ -4231,8 +5072,8 @@ function calculateCouplingScore(graph) {
4231
5072
  totalEdges++;
4232
5073
  fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
4233
5074
  fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
4234
- const sourceDir = dirname8(sourceAttrs.filePath).split("/")[0];
4235
- const targetDir = dirname8(targetAttrs.filePath).split("/")[0];
5075
+ const sourceDir = dirname9(sourceAttrs.filePath).split("/")[0];
5076
+ const targetDir = dirname9(targetAttrs.filePath).split("/")[0];
4236
5077
  if (sourceDir !== targetDir) {
4237
5078
  crossDirEdges++;
4238
5079
  }
@@ -4279,8 +5120,8 @@ function calculateCohesionScore(graph) {
4279
5120
  const sourceAttrs = graph.getNodeAttributes(source);
4280
5121
  const targetAttrs = graph.getNodeAttributes(target);
4281
5122
  if (sourceAttrs.filePath !== targetAttrs.filePath) {
4282
- const sourceDir = dirname8(sourceAttrs.filePath);
4283
- const targetDir = dirname8(targetAttrs.filePath);
5123
+ const sourceDir = dirname9(sourceAttrs.filePath);
5124
+ const targetDir = dirname9(targetAttrs.filePath);
4284
5125
  if (!dirEdges.has(sourceDir)) {
4285
5126
  dirEdges.set(sourceDir, { internal: 0, total: 0 });
4286
5127
  }
@@ -4562,8 +5403,8 @@ function calculateDepthScore(graph) {
4562
5403
  }
4563
5404
 
4564
5405
  // src/health/index.ts
4565
- import { readFileSync as readFileSync9, writeFileSync, existsSync as existsSync9, mkdirSync } from "fs";
4566
- import { dirname as dirname9, resolve as resolve7 } from "path";
5406
+ import { readFileSync as readFileSync10, writeFileSync, existsSync as existsSync10, mkdirSync } from "fs";
5407
+ import { dirname as dirname10, resolve as resolve8 } from "path";
4567
5408
  function calculateHealthScore(graph, projectRoot) {
4568
5409
  const coupling = calculateCouplingScore(graph);
4569
5410
  const cohesion = calculateCohesionScore(graph);
@@ -4662,8 +5503,8 @@ function getHealthTrend(projectRoot, currentScore) {
4662
5503
  }
4663
5504
  }
4664
5505
  function saveHealthHistory(projectRoot, report) {
4665
- const resolvedRoot = resolve7(projectRoot);
4666
- const historyFile = resolve7(resolvedRoot, ".depwire", "health-history.json");
5506
+ const resolvedRoot = resolve8(projectRoot);
5507
+ const historyFile = resolve8(resolvedRoot, ".depwire", "health-history.json");
4667
5508
  if (!historyFile.startsWith(resolvedRoot)) {
4668
5509
  return;
4669
5510
  }
@@ -4678,10 +5519,10 @@ function saveHealthHistory(projectRoot, report) {
4678
5519
  }))
4679
5520
  };
4680
5521
  let history = [];
4681
- if (existsSync9(historyFile)) {
5522
+ if (existsSync10(historyFile)) {
4682
5523
  try {
4683
5524
  if (!historyFile.startsWith(resolvedRoot)) return;
4684
- const content = readFileSync9(historyFile, "utf-8");
5525
+ const content = readFileSync10(historyFile, "utf-8");
4685
5526
  history = JSON.parse(content);
4686
5527
  } catch {
4687
5528
  }
@@ -4690,19 +5531,19 @@ function saveHealthHistory(projectRoot, report) {
4690
5531
  if (history.length > 50) {
4691
5532
  history = history.slice(-50);
4692
5533
  }
4693
- mkdirSync(dirname9(historyFile), { recursive: true });
5534
+ mkdirSync(dirname10(historyFile), { recursive: true });
4694
5535
  if (!historyFile.startsWith(resolvedRoot)) return;
4695
5536
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
4696
5537
  }
4697
5538
  function loadHealthHistory(projectRoot) {
4698
- const resolvedRoot = resolve7(projectRoot);
4699
- const historyFile = resolve7(resolvedRoot, ".depwire", "health-history.json");
4700
- if (!historyFile.startsWith(resolvedRoot) || !existsSync9(historyFile)) {
5539
+ const resolvedRoot = resolve8(projectRoot);
5540
+ const historyFile = resolve8(resolvedRoot, ".depwire", "health-history.json");
5541
+ if (!historyFile.startsWith(resolvedRoot) || !existsSync10(historyFile)) {
4701
5542
  return [];
4702
5543
  }
4703
5544
  try {
4704
5545
  if (!historyFile.startsWith(resolvedRoot)) return [];
4705
- const content = readFileSync9(historyFile, "utf-8");
5546
+ const content = readFileSync10(historyFile, "utf-8");
4706
5547
  return JSON.parse(content);
4707
5548
  } catch {
4708
5549
  return [];
@@ -4711,7 +5552,7 @@ function loadHealthHistory(projectRoot) {
4711
5552
 
4712
5553
  // src/dead-code/detector.ts
4713
5554
  import path2 from "path";
4714
- import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
5555
+ import { readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
4715
5556
  function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
4716
5557
  const deadSymbols = [];
4717
5558
  const context = { graph, projectRoot };
@@ -4844,11 +5685,11 @@ function getPackageEntryPoints(projectRoot) {
4844
5685
  const entryPoints = /* @__PURE__ */ new Set();
4845
5686
  const resolvedRoot = path2.resolve(projectRoot);
4846
5687
  const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
4847
- if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync10(packageJsonPath)) {
5688
+ if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync11(packageJsonPath)) {
4848
5689
  return entryPoints;
4849
5690
  }
4850
5691
  try {
4851
- const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
5692
+ const packageJson = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
4852
5693
  if (packageJson.main) {
4853
5694
  entryPoints.add(path2.resolve(projectRoot, packageJson.main));
4854
5695
  }
@@ -4921,7 +5762,8 @@ function isTypeDeclarationFile(filePath) {
4921
5762
  return filePath.endsWith(".d.ts");
4922
5763
  }
4923
5764
  function isFrameworkAutoLoadedFile(filePath) {
4924
- return filePath.includes("/pages/") || filePath.includes("/routes/") || filePath.includes("/middleware/") || filePath.includes("/commands/") || filePath.includes("/api/") || filePath.includes("/app/") || filePath.includes("/Controllers/") || filePath.includes("/Hubs/") || filePath.includes("/Migrations/");
5765
+ return filePath.includes("/pages/") || filePath.includes("/routes/") || filePath.includes("/middleware/") || filePath.includes("/commands/") || filePath.includes("/api/") || filePath.includes("/app/") || filePath.includes("/Controllers/") || filePath.includes("/Hubs/") || filePath.includes("/Migrations/") || // Java / Spring / Jakarta
5766
+ filePath.includes("/controller/") || filePath.includes("/controllers/") || filePath.includes("/service/") || filePath.includes("/repository/") || filePath.includes("/config/") || filePath.includes("/configuration/");
4925
5767
  }
4926
5768
 
4927
5769
  // src/dead-code/classifier.ts
@@ -4989,8 +5831,8 @@ function generateReason(symbol, confidence) {
4989
5831
  return "Potentially unused";
4990
5832
  }
4991
5833
  function isBarrelFile(filePath) {
4992
- const basename6 = path3.basename(filePath);
4993
- return basename6 === "index.ts" || basename6 === "index.js";
5834
+ const basename8 = path3.basename(filePath);
5835
+ return basename8 === "index.ts" || basename8 === "index.js";
4994
5836
  }
4995
5837
  function isTestFile2(filePath) {
4996
5838
  return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
@@ -5169,11 +6011,11 @@ function filterByConfidence(symbols, minConfidence) {
5169
6011
  }
5170
6012
 
5171
6013
  // src/docs/generator.ts
5172
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync13 } from "fs";
5173
- import { join as join15 } from "path";
6014
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync14 } from "fs";
6015
+ import { join as join16 } from "path";
5174
6016
 
5175
6017
  // src/docs/architecture.ts
5176
- import { dirname as dirname10 } from "path";
6018
+ import { dirname as dirname11 } from "path";
5177
6019
 
5178
6020
  // src/docs/templates.ts
5179
6021
  function header(text, level = 1) {
@@ -5324,7 +6166,7 @@ function generateModuleStructure(graph) {
5324
6166
  function getDirectoryStats(graph) {
5325
6167
  const dirMap = /* @__PURE__ */ new Map();
5326
6168
  graph.forEachNode((node, attrs) => {
5327
- const dir = dirname10(attrs.filePath);
6169
+ const dir = dirname11(attrs.filePath);
5328
6170
  if (dir === ".") return;
5329
6171
  if (!dirMap.has(dir)) {
5330
6172
  dirMap.set(dir, {
@@ -5349,7 +6191,7 @@ function getDirectoryStats(graph) {
5349
6191
  });
5350
6192
  const filesPerDir = /* @__PURE__ */ new Map();
5351
6193
  graph.forEachNode((node, attrs) => {
5352
- const dir = dirname10(attrs.filePath);
6194
+ const dir = dirname11(attrs.filePath);
5353
6195
  if (!filesPerDir.has(dir)) {
5354
6196
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
5355
6197
  }
@@ -5364,8 +6206,8 @@ function getDirectoryStats(graph) {
5364
6206
  graph.forEachEdge((edge, attrs, source, target) => {
5365
6207
  const sourceAttrs = graph.getNodeAttributes(source);
5366
6208
  const targetAttrs = graph.getNodeAttributes(target);
5367
- const sourceDir = dirname10(sourceAttrs.filePath);
5368
- const targetDir = dirname10(targetAttrs.filePath);
6209
+ const sourceDir = dirname11(sourceAttrs.filePath);
6210
+ const targetDir = dirname11(targetAttrs.filePath);
5369
6211
  if (sourceDir !== targetDir) {
5370
6212
  if (!dirEdges.has(sourceDir)) {
5371
6213
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -5572,7 +6414,7 @@ function detectCycles(graph) {
5572
6414
  }
5573
6415
 
5574
6416
  // src/docs/conventions.ts
5575
- import { basename as basename3, extname as extname5 } from "path";
6417
+ import { basename as basename5, extname as extname6 } from "path";
5576
6418
  function generateConventions(graph, projectRoot, version) {
5577
6419
  let output = "";
5578
6420
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -5610,7 +6452,7 @@ function generateFileOrganization(graph) {
5610
6452
  graph.forEachNode((node, attrs) => {
5611
6453
  if (!files.has(attrs.filePath)) {
5612
6454
  files.add(attrs.filePath);
5613
- const fileName = basename3(attrs.filePath);
6455
+ const fileName = basename5(attrs.filePath);
5614
6456
  if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
5615
6457
  barrelFileCount++;
5616
6458
  }
@@ -5669,7 +6511,7 @@ function generateNamingPatterns(graph) {
5669
6511
  graph.forEachNode((node, attrs) => {
5670
6512
  if (!files.has(attrs.filePath)) {
5671
6513
  files.add(attrs.filePath);
5672
- const fileName = basename3(attrs.filePath, extname5(attrs.filePath));
6514
+ const fileName = basename5(attrs.filePath, extname6(attrs.filePath));
5673
6515
  if (isCamelCase(fileName)) patterns.files.camelCase++;
5674
6516
  else if (isPascalCase(fileName)) patterns.files.PascalCase++;
5675
6517
  else if (isKebabCase(fileName)) patterns.files.kebabCase++;
@@ -6346,7 +7188,7 @@ function detectCyclesDetailed(graph) {
6346
7188
  }
6347
7189
 
6348
7190
  // src/docs/onboarding.ts
6349
- import { dirname as dirname11 } from "path";
7191
+ import { dirname as dirname12 } from "path";
6350
7192
  function generateOnboarding(graph, projectRoot, version) {
6351
7193
  let output = "";
6352
7194
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -6405,7 +7247,7 @@ function generateQuickOrientation(graph) {
6405
7247
  const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
6406
7248
  const dirs = /* @__PURE__ */ new Set();
6407
7249
  graph.forEachNode((node, attrs) => {
6408
- const dir = dirname11(attrs.filePath);
7250
+ const dir = dirname12(attrs.filePath);
6409
7251
  if (dir !== ".") {
6410
7252
  const topLevel = dir.split("/")[0];
6411
7253
  dirs.add(topLevel);
@@ -6509,7 +7351,7 @@ function generateModuleMap(graph) {
6509
7351
  function getDirectoryStats2(graph) {
6510
7352
  const dirMap = /* @__PURE__ */ new Map();
6511
7353
  graph.forEachNode((node, attrs) => {
6512
- const dir = dirname11(attrs.filePath);
7354
+ const dir = dirname12(attrs.filePath);
6513
7355
  if (dir === ".") return;
6514
7356
  if (!dirMap.has(dir)) {
6515
7357
  dirMap.set(dir, {
@@ -6524,7 +7366,7 @@ function getDirectoryStats2(graph) {
6524
7366
  });
6525
7367
  const filesPerDir = /* @__PURE__ */ new Map();
6526
7368
  graph.forEachNode((node, attrs) => {
6527
- const dir = dirname11(attrs.filePath);
7369
+ const dir = dirname12(attrs.filePath);
6528
7370
  if (!filesPerDir.has(dir)) {
6529
7371
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
6530
7372
  }
@@ -6539,8 +7381,8 @@ function getDirectoryStats2(graph) {
6539
7381
  graph.forEachEdge((edge, attrs, source, target) => {
6540
7382
  const sourceAttrs = graph.getNodeAttributes(source);
6541
7383
  const targetAttrs = graph.getNodeAttributes(target);
6542
- const sourceDir = dirname11(sourceAttrs.filePath);
6543
- const targetDir = dirname11(targetAttrs.filePath);
7384
+ const sourceDir = dirname12(sourceAttrs.filePath);
7385
+ const targetDir = dirname12(targetAttrs.filePath);
6544
7386
  if (sourceDir !== targetDir) {
6545
7387
  if (!dirEdges.has(sourceDir)) {
6546
7388
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -6621,7 +7463,7 @@ function detectClusters(graph) {
6621
7463
  const dirFiles = /* @__PURE__ */ new Map();
6622
7464
  const fileEdges = /* @__PURE__ */ new Map();
6623
7465
  graph.forEachNode((node, attrs) => {
6624
- const dir = dirname11(attrs.filePath);
7466
+ const dir = dirname12(attrs.filePath);
6625
7467
  if (!dirFiles.has(dir)) {
6626
7468
  dirFiles.set(dir, /* @__PURE__ */ new Set());
6627
7469
  }
@@ -6675,8 +7517,8 @@ function inferClusterName(files) {
6675
7517
  if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
6676
7518
  return capitalizeFirst2(sortedWords[0][0]);
6677
7519
  }
6678
- const commonDir = dirname11(files[0]);
6679
- if (files.every((f) => dirname11(f) === commonDir)) {
7520
+ const commonDir = dirname12(files[0]);
7521
+ if (files.every((f) => dirname12(f) === commonDir)) {
6680
7522
  return capitalizeFirst2(commonDir.split("/").pop() || "Core");
6681
7523
  }
6682
7524
  return "Core";
@@ -6734,7 +7576,7 @@ function generateDepwireUsage(projectRoot) {
6734
7576
  }
6735
7577
 
6736
7578
  // src/docs/files.ts
6737
- import { dirname as dirname12, basename as basename4 } from "path";
7579
+ import { dirname as dirname13, basename as basename6 } from "path";
6738
7580
  function generateFiles(graph, projectRoot, version) {
6739
7581
  let output = "";
6740
7582
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -6838,7 +7680,7 @@ function generateDirectoryBreakdown(graph) {
6838
7680
  const fileStats = getFileStats2(graph);
6839
7681
  const dirMap = /* @__PURE__ */ new Map();
6840
7682
  for (const file of fileStats) {
6841
- const dir = dirname12(file.filePath);
7683
+ const dir = dirname13(file.filePath);
6842
7684
  const topDir = dir === "." ? "." : dir.split("/")[0];
6843
7685
  if (!dirMap.has(topDir)) {
6844
7686
  dirMap.set(topDir, {
@@ -6853,7 +7695,7 @@ function generateDirectoryBreakdown(graph) {
6853
7695
  dirStats.symbolCount += file.symbolCount;
6854
7696
  if (file.totalConnections > dirStats.maxConnections) {
6855
7697
  dirStats.maxConnections = file.totalConnections;
6856
- dirStats.mostConnectedFile = basename4(file.filePath);
7698
+ dirStats.mostConnectedFile = basename6(file.filePath);
6857
7699
  }
6858
7700
  }
6859
7701
  if (dirMap.size === 0) {
@@ -7481,7 +8323,7 @@ function generateRecommendations(graph) {
7481
8323
  }
7482
8324
 
7483
8325
  // src/docs/tests.ts
7484
- import { basename as basename5, dirname as dirname13 } from "path";
8326
+ import { basename as basename7, dirname as dirname14 } from "path";
7485
8327
  function generateTests(graph, projectRoot, version) {
7486
8328
  let output = "";
7487
8329
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -7509,8 +8351,8 @@ function getFileCount8(graph) {
7509
8351
  return files.size;
7510
8352
  }
7511
8353
  function isTestFile3(filePath) {
7512
- const fileName = basename5(filePath).toLowerCase();
7513
- const dirPath = dirname13(filePath).toLowerCase();
8354
+ const fileName = basename7(filePath).toLowerCase();
8355
+ const dirPath = dirname14(filePath).toLowerCase();
7514
8356
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
7515
8357
  return true;
7516
8358
  }
@@ -7568,13 +8410,13 @@ function generateTestFileInventory(graph) {
7568
8410
  return output;
7569
8411
  }
7570
8412
  function matchTestToSource(testFile) {
7571
- const testFileName = basename5(testFile);
7572
- const testDir = dirname13(testFile);
8413
+ const testFileName = basename7(testFile);
8414
+ const testDir = dirname14(testFile);
7573
8415
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
7574
8416
  const possiblePaths = [];
7575
8417
  possiblePaths.push(testDir + "/" + sourceFileName);
7576
8418
  if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
7577
- const parentDir = dirname13(testDir);
8419
+ const parentDir = dirname14(testDir);
7578
8420
  possiblePaths.push(parentDir + "/" + sourceFileName);
7579
8421
  }
7580
8422
  if (testDir.includes("test")) {
@@ -7730,7 +8572,7 @@ function generateTestCoverageMap(graph) {
7730
8572
  const rows = mappings.slice(0, 30).map((m) => [
7731
8573
  `\`${m.sourceFile}\``,
7732
8574
  m.hasTest ? "\u2705" : "\u274C",
7733
- m.testFile ? `\`${basename5(m.testFile)}\`` : "-",
8575
+ m.testFile ? `\`${basename7(m.testFile)}\`` : "-",
7734
8576
  formatNumber(m.symbolCount)
7735
8577
  ]);
7736
8578
  let output = table(headers, rows);
@@ -7771,7 +8613,7 @@ function generateTestStatistics(graph) {
7771
8613
  `;
7772
8614
  const dirTestCoverage = /* @__PURE__ */ new Map();
7773
8615
  for (const sourceFile of sourceFiles) {
7774
- const dir = dirname13(sourceFile).split("/")[0];
8616
+ const dir = dirname14(sourceFile).split("/")[0];
7775
8617
  if (!dirTestCoverage.has(dir)) {
7776
8618
  dirTestCoverage.set(dir, { total: 0, tested: 0 });
7777
8619
  }
@@ -7794,7 +8636,7 @@ function generateTestStatistics(graph) {
7794
8636
  }
7795
8637
 
7796
8638
  // src/docs/history.ts
7797
- import { dirname as dirname14 } from "path";
8639
+ import { dirname as dirname15 } from "path";
7798
8640
  import { execSync } from "child_process";
7799
8641
  function generateHistory(graph, projectRoot, version) {
7800
8642
  let output = "";
@@ -8075,7 +8917,7 @@ function generateFeatureClusters(graph) {
8075
8917
  const dirFiles = /* @__PURE__ */ new Map();
8076
8918
  const fileEdges = /* @__PURE__ */ new Map();
8077
8919
  graph.forEachNode((node, attrs) => {
8078
- const dir = dirname14(attrs.filePath);
8920
+ const dir = dirname15(attrs.filePath);
8079
8921
  if (!dirFiles.has(dir)) {
8080
8922
  dirFiles.set(dir, /* @__PURE__ */ new Set());
8081
8923
  }
@@ -8157,7 +8999,7 @@ function capitalizeFirst3(str) {
8157
8999
  }
8158
9000
 
8159
9001
  // src/docs/current.ts
8160
- import { dirname as dirname15 } from "path";
9002
+ import { dirname as dirname16 } from "path";
8161
9003
  function generateCurrent(graph, projectRoot, version) {
8162
9004
  let output = "";
8163
9005
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -8295,7 +9137,7 @@ function generateCompleteFileIndex(graph) {
8295
9137
  fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
8296
9138
  const dirGroups = /* @__PURE__ */ new Map();
8297
9139
  for (const info of fileInfos) {
8298
- const dir = dirname15(info.filePath);
9140
+ const dir = dirname16(info.filePath);
8299
9141
  const topDir = dir === "." ? "root" : dir.split("/")[0];
8300
9142
  if (!dirGroups.has(topDir)) {
8301
9143
  dirGroups.set(topDir, []);
@@ -8506,8 +9348,8 @@ function getTopLevelDir2(filePath) {
8506
9348
  }
8507
9349
 
8508
9350
  // src/docs/status.ts
8509
- import { readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
8510
- import { resolve as resolve8 } from "path";
9351
+ import { readFileSync as readFileSync12, existsSync as existsSync12 } from "fs";
9352
+ import { resolve as resolve9 } from "path";
8511
9353
  function generateStatus(graph, projectRoot, version) {
8512
9354
  let output = "";
8513
9355
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -8540,16 +9382,16 @@ function getFileCount11(graph) {
8540
9382
  }
8541
9383
  function extractComments(projectRoot, filePath) {
8542
9384
  const comments = [];
8543
- const resolvedRoot = resolve8(projectRoot);
8544
- const fullPath = resolve8(resolvedRoot, filePath);
9385
+ const resolvedRoot = resolve9(projectRoot);
9386
+ const fullPath = resolve9(resolvedRoot, filePath);
8545
9387
  if (!fullPath.startsWith(resolvedRoot)) {
8546
9388
  return comments;
8547
9389
  }
8548
- if (!existsSync11(fullPath)) {
9390
+ if (!existsSync12(fullPath)) {
8549
9391
  return comments;
8550
9392
  }
8551
9393
  try {
8552
- const content = readFileSync11(fullPath, "utf-8");
9394
+ const content = readFileSync12(fullPath, "utf-8");
8553
9395
  const lines = content.split("\n");
8554
9396
  const patterns = [
8555
9397
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -9128,16 +9970,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
9128
9970
  }
9129
9971
 
9130
9972
  // src/docs/metadata.ts
9131
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync2 } from "fs";
9132
- import { resolve as resolve9 } from "path";
9973
+ import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "fs";
9974
+ import { resolve as resolve10 } from "path";
9133
9975
  function loadMetadata(outputDir) {
9134
- const resolvedDir = resolve9(outputDir);
9135
- const metadataPath = resolve9(resolvedDir, "metadata.json");
9136
- if (!metadataPath.startsWith(resolvedDir) || !existsSync12(metadataPath)) {
9976
+ const resolvedDir = resolve10(outputDir);
9977
+ const metadataPath = resolve10(resolvedDir, "metadata.json");
9978
+ if (!metadataPath.startsWith(resolvedDir) || !existsSync13(metadataPath)) {
9137
9979
  return null;
9138
9980
  }
9139
9981
  try {
9140
- const content = readFileSync12(metadataPath, "utf-8");
9982
+ const content = readFileSync13(metadataPath, "utf-8");
9141
9983
  return JSON.parse(content);
9142
9984
  } catch (err) {
9143
9985
  console.error("Failed to load metadata:", err);
@@ -9145,8 +9987,8 @@ function loadMetadata(outputDir) {
9145
9987
  }
9146
9988
  }
9147
9989
  function saveMetadata(outputDir, metadata) {
9148
- const resolvedDir = resolve9(outputDir);
9149
- const metadataPath = resolve9(resolvedDir, "metadata.json");
9990
+ const resolvedDir = resolve10(outputDir);
9991
+ const metadataPath = resolve10(resolvedDir, "metadata.json");
9150
9992
  if (!metadataPath.startsWith(resolvedDir)) {
9151
9993
  throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
9152
9994
  }
@@ -9192,7 +10034,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9192
10034
  const generated = [];
9193
10035
  const errors = [];
9194
10036
  try {
9195
- if (!existsSync13(options.outputDir)) {
10037
+ if (!existsSync14(options.outputDir)) {
9196
10038
  mkdirSync2(options.outputDir, { recursive: true });
9197
10039
  if (options.verbose) {
9198
10040
  console.log(`Created output directory: ${options.outputDir}`);
@@ -9231,7 +10073,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9231
10073
  try {
9232
10074
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
9233
10075
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
9234
- const filePath = join15(options.outputDir, "ARCHITECTURE.md");
10076
+ const filePath = join16(options.outputDir, "ARCHITECTURE.md");
9235
10077
  writeFileSync3(filePath, content, "utf-8");
9236
10078
  generated.push("ARCHITECTURE.md");
9237
10079
  } catch (err) {
@@ -9242,7 +10084,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9242
10084
  try {
9243
10085
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
9244
10086
  const content = generateConventions(graph, projectRoot, version);
9245
- const filePath = join15(options.outputDir, "CONVENTIONS.md");
10087
+ const filePath = join16(options.outputDir, "CONVENTIONS.md");
9246
10088
  writeFileSync3(filePath, content, "utf-8");
9247
10089
  generated.push("CONVENTIONS.md");
9248
10090
  } catch (err) {
@@ -9253,7 +10095,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9253
10095
  try {
9254
10096
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
9255
10097
  const content = generateDependencies(graph, projectRoot, version);
9256
- const filePath = join15(options.outputDir, "DEPENDENCIES.md");
10098
+ const filePath = join16(options.outputDir, "DEPENDENCIES.md");
9257
10099
  writeFileSync3(filePath, content, "utf-8");
9258
10100
  generated.push("DEPENDENCIES.md");
9259
10101
  } catch (err) {
@@ -9264,7 +10106,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9264
10106
  try {
9265
10107
  if (options.verbose) console.log("Generating ONBOARDING.md...");
9266
10108
  const content = generateOnboarding(graph, projectRoot, version);
9267
- const filePath = join15(options.outputDir, "ONBOARDING.md");
10109
+ const filePath = join16(options.outputDir, "ONBOARDING.md");
9268
10110
  writeFileSync3(filePath, content, "utf-8");
9269
10111
  generated.push("ONBOARDING.md");
9270
10112
  } catch (err) {
@@ -9275,7 +10117,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9275
10117
  try {
9276
10118
  if (options.verbose) console.log("Generating FILES.md...");
9277
10119
  const content = generateFiles(graph, projectRoot, version);
9278
- const filePath = join15(options.outputDir, "FILES.md");
10120
+ const filePath = join16(options.outputDir, "FILES.md");
9279
10121
  writeFileSync3(filePath, content, "utf-8");
9280
10122
  generated.push("FILES.md");
9281
10123
  } catch (err) {
@@ -9286,7 +10128,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9286
10128
  try {
9287
10129
  if (options.verbose) console.log("Generating API_SURFACE.md...");
9288
10130
  const content = generateApiSurface(graph, projectRoot, version);
9289
- const filePath = join15(options.outputDir, "API_SURFACE.md");
10131
+ const filePath = join16(options.outputDir, "API_SURFACE.md");
9290
10132
  writeFileSync3(filePath, content, "utf-8");
9291
10133
  generated.push("API_SURFACE.md");
9292
10134
  } catch (err) {
@@ -9297,7 +10139,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9297
10139
  try {
9298
10140
  if (options.verbose) console.log("Generating ERRORS.md...");
9299
10141
  const content = generateErrors(graph, projectRoot, version);
9300
- const filePath = join15(options.outputDir, "ERRORS.md");
10142
+ const filePath = join16(options.outputDir, "ERRORS.md");
9301
10143
  writeFileSync3(filePath, content, "utf-8");
9302
10144
  generated.push("ERRORS.md");
9303
10145
  } catch (err) {
@@ -9308,7 +10150,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9308
10150
  try {
9309
10151
  if (options.verbose) console.log("Generating TESTS.md...");
9310
10152
  const content = generateTests(graph, projectRoot, version);
9311
- const filePath = join15(options.outputDir, "TESTS.md");
10153
+ const filePath = join16(options.outputDir, "TESTS.md");
9312
10154
  writeFileSync3(filePath, content, "utf-8");
9313
10155
  generated.push("TESTS.md");
9314
10156
  } catch (err) {
@@ -9319,7 +10161,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9319
10161
  try {
9320
10162
  if (options.verbose) console.log("Generating HISTORY.md...");
9321
10163
  const content = generateHistory(graph, projectRoot, version);
9322
- const filePath = join15(options.outputDir, "HISTORY.md");
10164
+ const filePath = join16(options.outputDir, "HISTORY.md");
9323
10165
  writeFileSync3(filePath, content, "utf-8");
9324
10166
  generated.push("HISTORY.md");
9325
10167
  } catch (err) {
@@ -9330,7 +10172,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9330
10172
  try {
9331
10173
  if (options.verbose) console.log("Generating CURRENT.md...");
9332
10174
  const content = generateCurrent(graph, projectRoot, version);
9333
- const filePath = join15(options.outputDir, "CURRENT.md");
10175
+ const filePath = join16(options.outputDir, "CURRENT.md");
9334
10176
  writeFileSync3(filePath, content, "utf-8");
9335
10177
  generated.push("CURRENT.md");
9336
10178
  } catch (err) {
@@ -9341,7 +10183,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9341
10183
  try {
9342
10184
  if (options.verbose) console.log("Generating STATUS.md...");
9343
10185
  const content = generateStatus(graph, projectRoot, version);
9344
- const filePath = join15(options.outputDir, "STATUS.md");
10186
+ const filePath = join16(options.outputDir, "STATUS.md");
9345
10187
  writeFileSync3(filePath, content, "utf-8");
9346
10188
  generated.push("STATUS.md");
9347
10189
  } catch (err) {
@@ -9352,7 +10194,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9352
10194
  try {
9353
10195
  if (options.verbose) console.log("Generating HEALTH.md...");
9354
10196
  const content = generateHealth(graph, projectRoot, version);
9355
- const filePath = join15(options.outputDir, "HEALTH.md");
10197
+ const filePath = join16(options.outputDir, "HEALTH.md");
9356
10198
  writeFileSync3(filePath, content, "utf-8");
9357
10199
  generated.push("HEALTH.md");
9358
10200
  } catch (err) {
@@ -9363,7 +10205,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
9363
10205
  try {
9364
10206
  if (options.verbose) console.log("Generating DEAD_CODE.md...");
9365
10207
  const content = generateDeadCode(graph, projectRoot, version);
9366
- const filePath = join15(options.outputDir, "DEAD_CODE.md");
10208
+ const filePath = join16(options.outputDir, "DEAD_CODE.md");
9367
10209
  writeFileSync3(filePath, content, "utf-8");
9368
10210
  generated.push("DEAD_CODE.md");
9369
10211
  } catch (err) {
@@ -9407,7 +10249,7 @@ function getFileCount13(graph) {
9407
10249
  }
9408
10250
 
9409
10251
  // src/simulation/engine.ts
9410
- import { dirname as dirname16, join as join16 } from "path";
10252
+ import { dirname as dirname17, join as join17 } from "path";
9411
10253
  function normalizePath2(p) {
9412
10254
  return p.replace(/^\.\//, "").replace(/\/+$/, "");
9413
10255
  }
@@ -9538,7 +10380,7 @@ var SimulationEngine = class {
9538
10380
  }
9539
10381
  }
9540
10382
  applyRename(clone, target, newName, brokenImports) {
9541
- const destination = join16(dirname16(target), newName);
10383
+ const destination = join17(dirname17(target), newName);
9542
10384
  this.applyMove(clone, target, destination, brokenImports);
9543
10385
  }
9544
10386
  applySplit(clone, target, newFile, symbols, brokenImports) {
@@ -9738,13 +10580,13 @@ var SimulationEngine = class {
9738
10580
  };
9739
10581
 
9740
10582
  // src/security/scanner.ts
9741
- import { existsSync as existsSync15 } from "fs";
9742
- import { join as join26 } from "path";
10583
+ import { existsSync as existsSync16 } from "fs";
10584
+ import { join as join27 } from "path";
9743
10585
 
9744
10586
  // src/security/checks/dependencies.ts
9745
10587
  import { execSync as execSync2 } from "child_process";
9746
- import { existsSync as existsSync14, readFileSync as readFileSync13, readdirSync as readdirSync6 } from "fs";
9747
- import { join as join17 } from "path";
10588
+ import { existsSync as existsSync15, readFileSync as readFileSync14, readdirSync as readdirSync7 } from "fs";
10589
+ import { join as join18 } from "path";
9748
10590
  function cvssToSeverity(score) {
9749
10591
  if (score >= 9) return "critical";
9750
10592
  if (score >= 7) return "high";
@@ -9754,18 +10596,18 @@ function cvssToSeverity(score) {
9754
10596
  async function checkDependencies(_files, projectRoot) {
9755
10597
  const findings = [];
9756
10598
  try {
9757
- if (existsSync14(join17(projectRoot, "package.json"))) {
10599
+ if (existsSync15(join18(projectRoot, "package.json"))) {
9758
10600
  findings.push(...checkNpmAudit(projectRoot));
9759
10601
  findings.push(...checkPackageJsonPatterns(projectRoot));
9760
10602
  findings.push(...checkPostinstallScripts(projectRoot));
9761
10603
  }
9762
- if (existsSync14(join17(projectRoot, "requirements.txt")) || existsSync14(join17(projectRoot, "pyproject.toml"))) {
10604
+ if (existsSync15(join18(projectRoot, "requirements.txt")) || existsSync15(join18(projectRoot, "pyproject.toml"))) {
9763
10605
  findings.push(...checkPipAudit(projectRoot));
9764
10606
  }
9765
- if (existsSync14(join17(projectRoot, "Cargo.toml"))) {
10607
+ if (existsSync15(join18(projectRoot, "Cargo.toml"))) {
9766
10608
  findings.push(...checkCargoAudit(projectRoot));
9767
10609
  }
9768
- if (existsSync14(join17(projectRoot, "go.mod"))) {
10610
+ if (existsSync15(join18(projectRoot, "go.mod"))) {
9769
10611
  findings.push(...checkGoVerify(projectRoot));
9770
10612
  }
9771
10613
  } catch (err) {
@@ -9854,8 +10696,8 @@ function checkNpmAudit(projectRoot) {
9854
10696
  function checkPackageJsonPatterns(projectRoot) {
9855
10697
  const findings = [];
9856
10698
  try {
9857
- const pkgPath = join17(projectRoot, "package.json");
9858
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
10699
+ const pkgPath = join18(projectRoot, "package.json");
10700
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
9859
10701
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
9860
10702
  for (const [name, version] of Object.entries(allDeps)) {
9861
10703
  if (version.startsWith("^") || version.startsWith("~")) {
@@ -9877,15 +10719,15 @@ function checkPackageJsonPatterns(projectRoot) {
9877
10719
  }
9878
10720
  function checkPostinstallScripts(projectRoot) {
9879
10721
  const findings = [];
9880
- const nodeModules = join17(projectRoot, "node_modules");
9881
- if (!existsSync14(nodeModules)) return findings;
10722
+ const nodeModules = join18(projectRoot, "node_modules");
10723
+ if (!existsSync15(nodeModules)) return findings;
9882
10724
  try {
9883
- const topLevelDeps = readdirSync6(nodeModules).filter((d) => !d.startsWith("."));
10725
+ const topLevelDeps = readdirSync7(nodeModules).filter((d) => !d.startsWith("."));
9884
10726
  for (const dep of topLevelDeps) {
9885
- const depPkgPath = join17(nodeModules, dep, "package.json");
9886
- if (!existsSync14(depPkgPath)) continue;
10727
+ const depPkgPath = join18(nodeModules, dep, "package.json");
10728
+ if (!existsSync15(depPkgPath)) continue;
9887
10729
  try {
9888
- const depPkg = JSON.parse(readFileSync13(depPkgPath, "utf-8"));
10730
+ const depPkg = JSON.parse(readFileSync14(depPkgPath, "utf-8"));
9889
10731
  const scripts = depPkg.scripts || {};
9890
10732
  if (scripts.postinstall || scripts.preinstall || scripts.install) {
9891
10733
  const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
@@ -9923,7 +10765,7 @@ function checkPipAudit(projectRoot) {
9923
10765
  id: "",
9924
10766
  severity: cvssToSeverity(vuln.cvss?.score || 5),
9925
10767
  vulnerabilityClass: "dependency-cve",
9926
- file: existsSync14(join17(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
10768
+ file: existsSync15(join18(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
9927
10769
  title: `Vulnerable Python dependency: ${vuln.name}`,
9928
10770
  description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
9929
10771
  attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
@@ -10020,8 +10862,8 @@ function checkGoVerify(projectRoot) {
10020
10862
  }
10021
10863
 
10022
10864
  // src/security/checks/injection.ts
10023
- import { readFileSync as readFileSync14 } from "fs";
10024
- import { join as join18 } from "path";
10865
+ import { readFileSync as readFileSync15 } from "fs";
10866
+ import { join as join19 } from "path";
10025
10867
  var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
10026
10868
  var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
10027
10869
  var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
@@ -10106,6 +10948,70 @@ var PATTERNS = [
10106
10948
  description: "Database query built using fmt.Sprintf directly passed to db.Query.",
10107
10949
  attackScenario: "An attacker could inject SQL through interpolated values.",
10108
10950
  suggestedFix: 'Use parameterized queries: db.Query("SELECT ... WHERE id = ?", id)'
10951
+ },
10952
+ // Java-specific injection patterns
10953
+ {
10954
+ regex: /(?:executeQuery|executeUpdate|execute)\s*\(\s*["']?\s*(?:SELECT|INSERT|UPDATE|DELETE)\b[^"']*["']?\s*\+/i,
10955
+ title: "Java SQL injection via string concatenation",
10956
+ vulnClass: "code-injection",
10957
+ baseSeverity: "high",
10958
+ description: "SQL query built using string concatenation \u2014 vulnerable to SQL injection.",
10959
+ attackScenario: "An attacker could inject SQL through concatenated user input to read, modify, or delete database data.",
10960
+ suggestedFix: "Use PreparedStatement with parameterized queries: preparedStatement.setString(1, userInput)"
10961
+ },
10962
+ {
10963
+ regex: /Runtime\.getRuntime\(\)\.exec\s*\(/,
10964
+ title: "Java command injection via Runtime.exec",
10965
+ vulnClass: "shell-injection",
10966
+ baseSeverity: "high",
10967
+ description: "Runtime.exec() executes a system command \u2014 vulnerable if user input reaches the argument.",
10968
+ attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
10969
+ suggestedFix: "Use ProcessBuilder with an argument array. Validate all input against a strict allowlist."
10970
+ },
10971
+ {
10972
+ regex: /new\s+ProcessBuilder\s*\([^)]*(?:input|user|param|query|request|body|arg)/i,
10973
+ title: "Java command injection via ProcessBuilder with user input",
10974
+ vulnClass: "shell-injection",
10975
+ baseSeverity: "medium",
10976
+ description: "ProcessBuilder called with arguments that may originate from user input.",
10977
+ attackScenario: "An attacker could inject malicious arguments to the spawned process.",
10978
+ suggestedFix: "Validate all arguments against a strict allowlist before passing to ProcessBuilder."
10979
+ },
10980
+ {
10981
+ regex: /new\s+ObjectInputStream\s*\(/,
10982
+ title: "Java insecure deserialization via ObjectInputStream",
10983
+ vulnClass: "code-injection",
10984
+ baseSeverity: "high",
10985
+ description: "ObjectInputStream.readObject() deserializes arbitrary Java objects \u2014 potential RCE.",
10986
+ attackScenario: "An attacker could craft a malicious serialized object to achieve remote code execution.",
10987
+ suggestedFix: "Use a whitelist-based ObjectInputFilter, or switch to JSON/Protobuf for data exchange."
10988
+ },
10989
+ {
10990
+ regex: /DocumentBuilderFactory\.newInstance\(\)/,
10991
+ title: "Java XML External Entity (XXE) risk",
10992
+ vulnClass: "code-injection",
10993
+ baseSeverity: "medium",
10994
+ description: "DocumentBuilderFactory without FEATURE_SECURE_PROCESSING may allow XXE attacks.",
10995
+ attackScenario: "An attacker could inject external entity references in XML to read server files or perform SSRF.",
10996
+ suggestedFix: "Set FEATURE_SECURE_PROCESSING and disable external DTDs/entities: factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)"
10997
+ },
10998
+ {
10999
+ regex: /\.csrf\(\)\s*\.\s*disable\(\)/,
11000
+ title: "Spring Security CSRF protection disabled",
11001
+ vulnClass: "code-injection",
11002
+ baseSeverity: "medium",
11003
+ description: "CSRF protection has been explicitly disabled in Spring Security configuration.",
11004
+ attackScenario: "An attacker could forge cross-site requests to perform actions on behalf of authenticated users.",
11005
+ suggestedFix: "Only disable CSRF for stateless APIs using JWT. Keep CSRF enabled for session-based authentication."
11006
+ },
11007
+ {
11008
+ regex: /\.permitAll\(\).*(?:admin|manage|delete|config|setting)/i,
11009
+ title: "Spring Security permitAll on sensitive path",
11010
+ vulnClass: "code-injection",
11011
+ baseSeverity: "high",
11012
+ description: "permitAll() applied to a path that appears security-sensitive.",
11013
+ attackScenario: "An attacker could access administrative or destructive endpoints without authentication.",
11014
+ suggestedFix: 'Use .hasRole("ADMIN") or .authenticated() for sensitive endpoints.'
10109
11015
  }
10110
11016
  ];
10111
11017
  function shouldSkip(filePath) {
@@ -10122,7 +11028,7 @@ async function checkInjection(files, projectRoot) {
10122
11028
  if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
10123
11029
  let content;
10124
11030
  try {
10125
- content = readFileSync14(join18(projectRoot, file.filePath), "utf-8");
11031
+ content = readFileSync15(join19(projectRoot, file.filePath), "utf-8");
10126
11032
  } catch {
10127
11033
  continue;
10128
11034
  }
@@ -10160,8 +11066,8 @@ async function checkInjection(files, projectRoot) {
10160
11066
  }
10161
11067
 
10162
11068
  // src/security/checks/secrets.ts
10163
- import { readFileSync as readFileSync15 } from "fs";
10164
- import { join as join19 } from "path";
11069
+ import { readFileSync as readFileSync16 } from "fs";
11070
+ import { join as join20 } from "path";
10165
11071
  var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
10166
11072
  var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
10167
11073
  var SECRET_PATTERNS = [
@@ -10194,7 +11100,7 @@ async function checkSecrets(files, projectRoot) {
10194
11100
  if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
10195
11101
  let content;
10196
11102
  try {
10197
- content = readFileSync15(join19(projectRoot, file.filePath), "utf-8");
11103
+ content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
10198
11104
  } catch {
10199
11105
  continue;
10200
11106
  }
@@ -10227,8 +11133,8 @@ async function checkSecrets(files, projectRoot) {
10227
11133
  }
10228
11134
 
10229
11135
  // src/security/checks/path-traversal.ts
10230
- import { readFileSync as readFileSync16 } from "fs";
10231
- import { join as join20 } from "path";
11136
+ import { readFileSync as readFileSync17 } from "fs";
11137
+ import { join as join21 } from "path";
10232
11138
  var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
10233
11139
  var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
10234
11140
  var PATTERNS2 = [
@@ -10275,7 +11181,7 @@ async function checkPathTraversal(files, projectRoot) {
10275
11181
  if (shouldSkip3(file.filePath)) continue;
10276
11182
  let content;
10277
11183
  try {
10278
- content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
11184
+ content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
10279
11185
  } catch {
10280
11186
  continue;
10281
11187
  }
@@ -10317,8 +11223,8 @@ async function checkPathTraversal(files, projectRoot) {
10317
11223
  }
10318
11224
 
10319
11225
  // src/security/checks/auth.ts
10320
- import { readFileSync as readFileSync17 } from "fs";
10321
- import { join as join21 } from "path";
11226
+ import { readFileSync as readFileSync18 } from "fs";
11227
+ import { join as join22 } from "path";
10322
11228
  var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
10323
11229
  function shouldSkip4(filePath) {
10324
11230
  return SKIP_DIRS4.some((d) => filePath.includes(d));
@@ -10334,7 +11240,7 @@ async function checkAuth(files, projectRoot) {
10334
11240
  if (shouldSkip4(file.filePath)) continue;
10335
11241
  let content;
10336
11242
  try {
10337
- content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
11243
+ content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
10338
11244
  } catch {
10339
11245
  continue;
10340
11246
  }
@@ -10425,8 +11331,8 @@ async function checkAuth(files, projectRoot) {
10425
11331
  }
10426
11332
 
10427
11333
  // src/security/checks/input-validation.ts
10428
- import { readFileSync as readFileSync18 } from "fs";
10429
- import { join as join22 } from "path";
11334
+ import { readFileSync as readFileSync19 } from "fs";
11335
+ import { join as join23 } from "path";
10430
11336
  var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
10431
11337
  function shouldSkip5(filePath) {
10432
11338
  return SKIP_DIRS5.some((d) => filePath.includes(d));
@@ -10438,7 +11344,7 @@ async function checkInputValidation(files, projectRoot) {
10438
11344
  if (shouldSkip5(file.filePath)) continue;
10439
11345
  let content;
10440
11346
  try {
10441
- content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
11347
+ content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
10442
11348
  } catch {
10443
11349
  continue;
10444
11350
  }
@@ -10515,8 +11421,8 @@ async function checkInputValidation(files, projectRoot) {
10515
11421
  }
10516
11422
 
10517
11423
  // src/security/checks/information-disclosure.ts
10518
- import { readFileSync as readFileSync19 } from "fs";
10519
- import { join as join23 } from "path";
11424
+ import { readFileSync as readFileSync20 } from "fs";
11425
+ import { join as join24 } from "path";
10520
11426
  var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
10521
11427
  function shouldSkip6(filePath) {
10522
11428
  return SKIP_DIRS6.some((d) => filePath.includes(d));
@@ -10528,7 +11434,7 @@ async function checkInformationDisclosure(files, projectRoot) {
10528
11434
  if (shouldSkip6(file.filePath)) continue;
10529
11435
  let content;
10530
11436
  try {
10531
- content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
11437
+ content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
10532
11438
  } catch {
10533
11439
  continue;
10534
11440
  }
@@ -10598,9 +11504,10 @@ async function checkInformationDisclosure(files, projectRoot) {
10598
11504
  }
10599
11505
 
10600
11506
  // src/security/checks/cryptography.ts
10601
- import { readFileSync as readFileSync20 } from "fs";
10602
- import { join as join24 } from "path";
11507
+ import { readFileSync as readFileSync21 } from "fs";
11508
+ import { join as join25 } from "path";
10603
11509
  var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
11510
+ var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
10604
11511
  function shouldSkip7(filePath) {
10605
11512
  return SKIP_DIRS7.some((d) => filePath.includes(d));
10606
11513
  }
@@ -10615,7 +11522,7 @@ async function checkCryptography(files, projectRoot) {
10615
11522
  if (shouldSkip7(file.filePath)) continue;
10616
11523
  let content;
10617
11524
  try {
10618
- content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
11525
+ content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
10619
11526
  } catch {
10620
11527
  continue;
10621
11528
  }
@@ -10624,7 +11531,7 @@ async function checkCryptography(files, projectRoot) {
10624
11531
  for (let i = 0; i < lines.length; i++) {
10625
11532
  const line = lines[i];
10626
11533
  if (line.trimStart().startsWith("//") || line.trimStart().startsWith("#")) continue;
10627
- if (/createHash\s*\(\s*['"]md5['"]\s*\)/.test(line) || /hashlib\.md5\s*\(/.test(line)) {
11534
+ if (/createHash\s*\(\s*['"]md5['"]\s*\)/.test(line) || /hashlib\.md5\s*\(/.test(line) || /MessageDigest\.getInstance\s*\(\s*["']MD5["']\s*\)/.test(line)) {
10628
11535
  findings.push({
10629
11536
  id: "",
10630
11537
  severity: isCryptoFile ? "high" : "medium",
@@ -10637,7 +11544,7 @@ async function checkCryptography(files, projectRoot) {
10637
11544
  suggestedFix: "Use SHA-256 or SHA-3 for integrity checks. Use bcrypt, scrypt, or argon2 for password hashing."
10638
11545
  });
10639
11546
  }
10640
- if (/createHash\s*\(\s*['"]sha1['"]\s*\)/.test(line) || /hashlib\.sha1\s*\(/.test(line)) {
11547
+ if (/createHash\s*\(\s*['"]sha1['"]\s*\)/.test(line) || /hashlib\.sha1\s*\(/.test(line) || /MessageDigest\.getInstance\s*\(\s*["']SHA-?1["']\s*\)/.test(line)) {
10641
11548
  findings.push({
10642
11549
  id: "",
10643
11550
  severity: isCryptoFile ? "high" : "medium",
@@ -10650,6 +11557,34 @@ async function checkCryptography(files, projectRoot) {
10650
11557
  suggestedFix: "Use SHA-256 or SHA-3 for integrity checks. Use bcrypt, scrypt, or argon2 for password hashing."
10651
11558
  });
10652
11559
  }
11560
+ if (/Cipher\.getInstance\s*\(\s*["']DES/.test(line)) {
11561
+ findings.push({
11562
+ id: "",
11563
+ severity: "high",
11564
+ vulnerabilityClass: "cryptography",
11565
+ file: file.filePath,
11566
+ line: i + 1,
11567
+ title: "Weak cipher algorithm: DES",
11568
+ description: "DES uses a 56-bit key and can be brute-forced in hours.",
11569
+ attackScenario: "An attacker could brute-force DES-encrypted data to reveal plaintext.",
11570
+ suggestedFix: 'Use AES-256 with GCM mode: Cipher.getInstance("AES/GCM/NoPadding")'
11571
+ });
11572
+ }
11573
+ if (/(?:log|logger|LOG)\s*\.\s*(?:info|debug|warn|error|trace)\s*\([^)]*\+/.test(line)) {
11574
+ if (USER_INPUT_NAMES2.test(line)) {
11575
+ findings.push({
11576
+ id: "",
11577
+ severity: "medium",
11578
+ vulnerabilityClass: "cryptography",
11579
+ file: file.filePath,
11580
+ line: i + 1,
11581
+ title: "Potential log injection",
11582
+ description: "User-controlled input concatenated directly into log output.",
11583
+ attackScenario: "An attacker could inject newlines or control characters to forge log entries or hide malicious activity.",
11584
+ suggestedFix: 'Use parameterized logging: log.info("User: {}", userInput) instead of string concatenation.'
11585
+ });
11586
+ }
11587
+ }
10653
11588
  if (/Math\.random\(\)/.test(line) && isCryptoFile) {
10654
11589
  findings.push({
10655
11590
  id: "",
@@ -10697,8 +11632,8 @@ async function checkCryptography(files, projectRoot) {
10697
11632
  }
10698
11633
 
10699
11634
  // src/security/checks/frontend.ts
10700
- import { readFileSync as readFileSync21 } from "fs";
10701
- import { join as join25 } from "path";
11635
+ import { readFileSync as readFileSync22 } from "fs";
11636
+ import { join as join26 } from "path";
10702
11637
  var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
10703
11638
  function shouldSkip8(filePath) {
10704
11639
  return SKIP_DIRS8.some((d) => filePath.includes(d));
@@ -10714,7 +11649,7 @@ async function checkFrontend(files, projectRoot) {
10714
11649
  if (!isFrontendFile(file.filePath)) continue;
10715
11650
  let content;
10716
11651
  try {
10717
- content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
11652
+ content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
10718
11653
  } catch {
10719
11654
  continue;
10720
11655
  }
@@ -11174,11 +12109,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
11174
12109
  };
11175
12110
  }
11176
12111
  function detectPackageManager(projectRoot) {
11177
- if (existsSync15(join26(projectRoot, "package.json"))) return "npm";
11178
- if (existsSync15(join26(projectRoot, "requirements.txt"))) return "pip";
11179
- if (existsSync15(join26(projectRoot, "pyproject.toml"))) return "pip";
11180
- if (existsSync15(join26(projectRoot, "Cargo.toml"))) return "cargo";
11181
- if (existsSync15(join26(projectRoot, "go.mod"))) return "go";
12112
+ if (existsSync16(join27(projectRoot, "package.json"))) return "npm";
12113
+ if (existsSync16(join27(projectRoot, "requirements.txt"))) return "pip";
12114
+ if (existsSync16(join27(projectRoot, "pyproject.toml"))) return "pip";
12115
+ if (existsSync16(join27(projectRoot, "Cargo.toml"))) return "cargo";
12116
+ if (existsSync16(join27(projectRoot, "go.mod"))) return "go";
12117
+ if (existsSync16(join27(projectRoot, "pom.xml"))) return "maven";
12118
+ if (existsSync16(join27(projectRoot, "build.gradle")) || existsSync16(join27(projectRoot, "build.gradle.kts"))) return "gradle";
11182
12119
  return "unknown";
11183
12120
  }
11184
12121