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