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.
- package/README.md +6 -3
- package/dist/{chunk-5BQLGAUL.js → chunk-5D36PY3Q.js} +10 -7
- package/dist/{chunk-IYKS66CG.js → chunk-LV32EDYQ.js} +1163 -226
- package/dist/index.js +2 -2
- package/dist/mcpb-entry.js +2 -2
- package/dist/parser/grammars/tree-sitter-java.wasm +0 -0
- package/dist/sdk.js +1 -1
- package/package.json +2 -1
|
@@ -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
|
-
|
|
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
|
|
108
|
-
import { join as
|
|
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
|
|
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 =
|
|
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
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
const
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
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
|
-
|
|
3953
|
+
startLine: lineNum,
|
|
3954
|
+
endLine: lineNum,
|
|
3955
|
+
exported: false
|
|
3230
3956
|
});
|
|
3231
3957
|
}
|
|
3232
3958
|
}
|
|
3233
|
-
const
|
|
3234
|
-
if (
|
|
3235
|
-
const
|
|
3236
|
-
const
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
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
|
|
3252
|
-
const
|
|
3253
|
-
const
|
|
3254
|
-
|
|
3255
|
-
|
|
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
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
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
|
|
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
|
|
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 ===
|
|
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
|
|
3310
|
-
const text =
|
|
3311
|
-
if (!text
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3340
|
-
name: "
|
|
3341
|
-
extensions: [".
|
|
3342
|
-
parseFile:
|
|
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 =
|
|
3357
|
-
|
|
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 =
|
|
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 =
|
|
3384
|
-
if (!
|
|
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 =
|
|
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
|
|
3437
|
-
import { join as
|
|
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 =
|
|
3662
|
-
if (!
|
|
4502
|
+
const fullPath = join11(projectRoot, file.filePath);
|
|
4503
|
+
if (!resolve6(fullPath).startsWith(resolve6(projectRoot))) continue;
|
|
3663
4504
|
let source;
|
|
3664
4505
|
try {
|
|
3665
|
-
source =
|
|
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
|
|
3702
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
3807
|
-
if (!
|
|
4647
|
+
const fullPath = join12(projectRoot, file.filePath);
|
|
4648
|
+
if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) continue;
|
|
3808
4649
|
let source;
|
|
3809
4650
|
try {
|
|
3810
|
-
source =
|
|
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 =
|
|
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
|
|
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 =
|
|
4235
|
-
const targetDir =
|
|
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 =
|
|
4283
|
-
const targetDir =
|
|
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
|
|
4566
|
-
import { dirname as
|
|
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 =
|
|
4666
|
-
const historyFile =
|
|
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 (
|
|
5522
|
+
if (existsSync10(historyFile)) {
|
|
4682
5523
|
try {
|
|
4683
5524
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
4684
|
-
const content =
|
|
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(
|
|
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 =
|
|
4699
|
-
const historyFile =
|
|
4700
|
-
if (!historyFile.startsWith(resolvedRoot) || !
|
|
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 =
|
|
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
|
|
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) || !
|
|
5688
|
+
if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync11(packageJsonPath)) {
|
|
4848
5689
|
return entryPoints;
|
|
4849
5690
|
}
|
|
4850
5691
|
try {
|
|
4851
|
-
const packageJson = JSON.parse(
|
|
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
|
|
4993
|
-
return
|
|
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
|
|
5173
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5368
|
-
const targetDir =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
6543
|
-
const targetDir =
|
|
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 =
|
|
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 =
|
|
6679
|
-
if (files.every((f) =>
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
7513
|
-
const dirPath =
|
|
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 =
|
|
7572
|
-
const testDir =
|
|
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 =
|
|
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 ? `\`${
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
8510
|
-
import { resolve as
|
|
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 =
|
|
8544
|
-
const fullPath =
|
|
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 (!
|
|
9390
|
+
if (!existsSync12(fullPath)) {
|
|
8549
9391
|
return comments;
|
|
8550
9392
|
}
|
|
8551
9393
|
try {
|
|
8552
|
-
const content =
|
|
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
|
|
9132
|
-
import { resolve as
|
|
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 =
|
|
9135
|
-
const metadataPath =
|
|
9136
|
-
if (!metadataPath.startsWith(resolvedDir) || !
|
|
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 =
|
|
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 =
|
|
9149
|
-
const metadataPath =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
9742
|
-
import { join as
|
|
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
|
|
9747
|
-
import { join as
|
|
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 (
|
|
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 (
|
|
10604
|
+
if (existsSync15(join18(projectRoot, "requirements.txt")) || existsSync15(join18(projectRoot, "pyproject.toml"))) {
|
|
9763
10605
|
findings.push(...checkPipAudit(projectRoot));
|
|
9764
10606
|
}
|
|
9765
|
-
if (
|
|
10607
|
+
if (existsSync15(join18(projectRoot, "Cargo.toml"))) {
|
|
9766
10608
|
findings.push(...checkCargoAudit(projectRoot));
|
|
9767
10609
|
}
|
|
9768
|
-
if (
|
|
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 =
|
|
9858
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
9881
|
-
if (!
|
|
10722
|
+
const nodeModules = join18(projectRoot, "node_modules");
|
|
10723
|
+
if (!existsSync15(nodeModules)) return findings;
|
|
9882
10724
|
try {
|
|
9883
|
-
const topLevelDeps =
|
|
10725
|
+
const topLevelDeps = readdirSync7(nodeModules).filter((d) => !d.startsWith("."));
|
|
9884
10726
|
for (const dep of topLevelDeps) {
|
|
9885
|
-
const depPkgPath =
|
|
9886
|
-
if (!
|
|
10727
|
+
const depPkgPath = join18(nodeModules, dep, "package.json");
|
|
10728
|
+
if (!existsSync15(depPkgPath)) continue;
|
|
9887
10729
|
try {
|
|
9888
|
-
const depPkg = JSON.parse(
|
|
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:
|
|
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
|
|
10024
|
-
import { join as
|
|
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 =
|
|
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
|
|
10164
|
-
import { join as
|
|
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 =
|
|
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
|
|
10231
|
-
import { join as
|
|
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 =
|
|
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
|
|
10321
|
-
import { join as
|
|
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 =
|
|
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
|
|
10429
|
-
import { join as
|
|
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 =
|
|
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
|
|
10519
|
-
import { join as
|
|
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 =
|
|
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
|
|
10602
|
-
import { join as
|
|
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 =
|
|
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
|
|
10701
|
-
import { join as
|
|
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 =
|
|
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 (
|
|
11178
|
-
if (
|
|
11179
|
-
if (
|
|
11180
|
-
if (
|
|
11181
|
-
if (
|
|
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
|
|