depwire-cli 1.1.8 → 1.2.0
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 +13 -4
- package/dist/{chunk-ZO3LWFDE.js → chunk-O5HXGEGE.js} +1034 -162
- package/dist/{chunk-M5BETAND.js → chunk-QRUJI7RX.js} +8 -8
- package/dist/index.js +2 -2
- package/dist/mcpb-entry.js +2 -2
- package/dist/parser/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/sdk.js +1 -1
- package/package.json +4 -2
|
@@ -39,8 +39,9 @@ function scanDirectory(rootDir, baseDir = rootDir) {
|
|
|
39
39
|
const isPhp = entry.endsWith(".php");
|
|
40
40
|
const isSwift = entry.endsWith(".swift");
|
|
41
41
|
const isMojo = entry.endsWith(".mojo") || entry.endsWith(".\u{1F525}");
|
|
42
|
+
const isRuby = entry.endsWith(".rb") || entry.endsWith(".rake") || entry.endsWith(".gemspec") || entry.endsWith(".ru") || entry === "Gemfile";
|
|
42
43
|
const isCppBuild = entry === "CMakeLists.txt" || entry === "conanfile.txt" || entry === "vcpkg.json";
|
|
43
|
-
if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isPhp || isSwift || isMojo || isCppBuild) {
|
|
44
|
+
if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isPhp || isSwift || isMojo || isRuby || isCppBuild) {
|
|
44
45
|
files.push(relative(rootDir, fullPath));
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -90,6 +91,8 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
90
91
|
// Swift (SPM)
|
|
91
92
|
"mojoproject.toml",
|
|
92
93
|
// Mojo
|
|
94
|
+
"Gemfile",
|
|
95
|
+
// Ruby (Bundler)
|
|
93
96
|
".git"
|
|
94
97
|
// Any git repo
|
|
95
98
|
];
|
|
@@ -123,11 +126,11 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
// src/parser/index.ts
|
|
126
|
-
import { readFileSync as
|
|
127
|
-
import { join as
|
|
129
|
+
import { readFileSync as readFileSync12, statSync as statSync9 } from "fs";
|
|
130
|
+
import { join as join16, resolve as resolve10 } from "path";
|
|
128
131
|
|
|
129
132
|
// src/parser/detect.ts
|
|
130
|
-
import { extname as
|
|
133
|
+
import { extname as extname11, basename as basename9 } from "path";
|
|
131
134
|
|
|
132
135
|
// src/parser/wasm-init.ts
|
|
133
136
|
import { Parser, Language } from "web-tree-sitter";
|
|
@@ -160,7 +163,8 @@ async function initParser() {
|
|
|
160
163
|
"cpp": "tree-sitter-cpp.wasm",
|
|
161
164
|
"kotlin": "tree-sitter-kotlin.wasm",
|
|
162
165
|
"php": "tree-sitter-php.wasm",
|
|
163
|
-
"swift": "tree-sitter-swift.wasm"
|
|
166
|
+
"swift": "tree-sitter-swift.wasm",
|
|
167
|
+
"ruby": "tree-sitter-ruby.wasm"
|
|
164
168
|
// Note: Mojo uses a pattern-based parser (no tree-sitter-mojo WASM available)
|
|
165
169
|
};
|
|
166
170
|
for (const [name, file] of Object.entries(grammarFiles)) {
|
|
@@ -7146,12 +7150,566 @@ function parseMojoProject(filePath, sourceCode, projectRoot) {
|
|
|
7146
7150
|
});
|
|
7147
7151
|
}
|
|
7148
7152
|
}
|
|
7149
|
-
return { filePath, symbols, edges };
|
|
7153
|
+
return { filePath, symbols, edges };
|
|
7154
|
+
}
|
|
7155
|
+
var mojoParser = {
|
|
7156
|
+
name: "mojo",
|
|
7157
|
+
extensions: [".mojo", ".\u{1F525}", "mojoproject.toml"],
|
|
7158
|
+
parseFile: parseMojoFile
|
|
7159
|
+
};
|
|
7160
|
+
|
|
7161
|
+
// src/parser/ruby.ts
|
|
7162
|
+
import { dirname as dirname14, join as join15, basename as basename8 } from "path";
|
|
7163
|
+
import { existsSync as existsSync14 } from "fs";
|
|
7164
|
+
function parseRubyFile(filePath, sourceCode, projectRoot) {
|
|
7165
|
+
if (basename8(filePath) === "Gemfile") {
|
|
7166
|
+
return parseGemfile(filePath, sourceCode, projectRoot);
|
|
7167
|
+
}
|
|
7168
|
+
const parser = getParser("ruby");
|
|
7169
|
+
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
7170
|
+
const context = {
|
|
7171
|
+
filePath,
|
|
7172
|
+
projectRoot,
|
|
7173
|
+
sourceCode,
|
|
7174
|
+
symbols: [],
|
|
7175
|
+
edges: [],
|
|
7176
|
+
currentScope: [],
|
|
7177
|
+
currentClass: null,
|
|
7178
|
+
currentModule: null,
|
|
7179
|
+
imports: /* @__PURE__ */ new Map(),
|
|
7180
|
+
isGemfile: false
|
|
7181
|
+
};
|
|
7182
|
+
walkNode13(tree.rootNode, context);
|
|
7183
|
+
return {
|
|
7184
|
+
filePath,
|
|
7185
|
+
symbols: context.symbols,
|
|
7186
|
+
edges: context.edges
|
|
7187
|
+
};
|
|
7188
|
+
}
|
|
7189
|
+
function walkNode13(node, context) {
|
|
7190
|
+
const handled = processNode13(node, context);
|
|
7191
|
+
if (handled) return;
|
|
7192
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
7193
|
+
const child = node.child(i);
|
|
7194
|
+
if (child) {
|
|
7195
|
+
walkNode13(child, context);
|
|
7196
|
+
}
|
|
7197
|
+
}
|
|
7198
|
+
}
|
|
7199
|
+
function processNode13(node, context) {
|
|
7200
|
+
switch (node.type) {
|
|
7201
|
+
case "class":
|
|
7202
|
+
processClassDeclaration8(node, context);
|
|
7203
|
+
return true;
|
|
7204
|
+
case "module":
|
|
7205
|
+
processModuleDeclaration(node, context);
|
|
7206
|
+
return true;
|
|
7207
|
+
case "method":
|
|
7208
|
+
processMethodDeclaration5(node, context);
|
|
7209
|
+
return true;
|
|
7210
|
+
case "singleton_method":
|
|
7211
|
+
processSingletonMethod(node, context);
|
|
7212
|
+
return true;
|
|
7213
|
+
case "assignment":
|
|
7214
|
+
processAssignment(node, context);
|
|
7215
|
+
return false;
|
|
7216
|
+
case "call":
|
|
7217
|
+
case "method_call":
|
|
7218
|
+
processCallExpression13(node, context);
|
|
7219
|
+
return false;
|
|
7220
|
+
case "command":
|
|
7221
|
+
processCommand(node, context);
|
|
7222
|
+
return false;
|
|
7223
|
+
case "command_call":
|
|
7224
|
+
processCommandCall(node, context);
|
|
7225
|
+
return false;
|
|
7226
|
+
case "constant":
|
|
7227
|
+
return false;
|
|
7228
|
+
case "block":
|
|
7229
|
+
case "do_block":
|
|
7230
|
+
return false;
|
|
7231
|
+
case "lambda":
|
|
7232
|
+
processLambda(node, context);
|
|
7233
|
+
return false;
|
|
7234
|
+
default:
|
|
7235
|
+
return false;
|
|
7236
|
+
}
|
|
7237
|
+
}
|
|
7238
|
+
function processClassDeclaration8(node, context) {
|
|
7239
|
+
const nameNode = findChildByType13(node, "constant") || findChildByType13(node, "scope_resolution");
|
|
7240
|
+
if (!nameNode) return;
|
|
7241
|
+
const name = nodeText12(nameNode, context);
|
|
7242
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
7243
|
+
context.symbols.push({
|
|
7244
|
+
id: symbolId,
|
|
7245
|
+
name,
|
|
7246
|
+
kind: "class",
|
|
7247
|
+
filePath: context.filePath,
|
|
7248
|
+
startLine: node.startPosition.row + 1,
|
|
7249
|
+
endLine: node.endPosition.row + 1,
|
|
7250
|
+
exported: true
|
|
7251
|
+
});
|
|
7252
|
+
const superclassNode = findChildByType13(node, "superclass");
|
|
7253
|
+
if (superclassNode) {
|
|
7254
|
+
const superName = nodeText12(superclassNode, context).replace(/^\s*<\s*/, "").trim();
|
|
7255
|
+
if (superName) {
|
|
7256
|
+
const baseId = resolveSymbol12(superName, context);
|
|
7257
|
+
if (baseId) {
|
|
7258
|
+
context.edges.push({
|
|
7259
|
+
source: symbolId,
|
|
7260
|
+
target: baseId,
|
|
7261
|
+
kind: "implements",
|
|
7262
|
+
filePath: context.filePath,
|
|
7263
|
+
line: node.startPosition.row + 1
|
|
7264
|
+
});
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7267
|
+
}
|
|
7268
|
+
const oldClass = context.currentClass;
|
|
7269
|
+
context.currentClass = name;
|
|
7270
|
+
context.currentScope.push(name);
|
|
7271
|
+
const body = findChildByType13(node, "body_statement");
|
|
7272
|
+
if (body) {
|
|
7273
|
+
walkNode13(body, context);
|
|
7274
|
+
}
|
|
7275
|
+
context.currentScope.pop();
|
|
7276
|
+
context.currentClass = oldClass;
|
|
7277
|
+
}
|
|
7278
|
+
function processModuleDeclaration(node, context) {
|
|
7279
|
+
const nameNode = findChildByType13(node, "constant") || findChildByType13(node, "scope_resolution");
|
|
7280
|
+
if (!nameNode) return;
|
|
7281
|
+
const name = nodeText12(nameNode, context);
|
|
7282
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
7283
|
+
context.symbols.push({
|
|
7284
|
+
id: symbolId,
|
|
7285
|
+
name,
|
|
7286
|
+
kind: "module",
|
|
7287
|
+
filePath: context.filePath,
|
|
7288
|
+
startLine: node.startPosition.row + 1,
|
|
7289
|
+
endLine: node.endPosition.row + 1,
|
|
7290
|
+
exported: true
|
|
7291
|
+
});
|
|
7292
|
+
const oldModule = context.currentModule;
|
|
7293
|
+
const oldClass = context.currentClass;
|
|
7294
|
+
context.currentModule = name;
|
|
7295
|
+
context.currentClass = name;
|
|
7296
|
+
context.currentScope.push(name);
|
|
7297
|
+
const body = findChildByType13(node, "body_statement");
|
|
7298
|
+
if (body) {
|
|
7299
|
+
walkNode13(body, context);
|
|
7300
|
+
}
|
|
7301
|
+
context.currentScope.pop();
|
|
7302
|
+
context.currentModule = oldModule;
|
|
7303
|
+
context.currentClass = oldClass;
|
|
7304
|
+
}
|
|
7305
|
+
function processMethodDeclaration5(node, context) {
|
|
7306
|
+
const nameNode = findChildByType13(node, "identifier");
|
|
7307
|
+
if (!nameNode) return;
|
|
7308
|
+
const name = nodeText12(nameNode, context);
|
|
7309
|
+
const scope = context.currentClass || void 0;
|
|
7310
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
7311
|
+
context.symbols.push({
|
|
7312
|
+
id: symbolId,
|
|
7313
|
+
name,
|
|
7314
|
+
kind: context.currentClass ? "method" : "function",
|
|
7315
|
+
filePath: context.filePath,
|
|
7316
|
+
startLine: node.startPosition.row + 1,
|
|
7317
|
+
endLine: node.endPosition.row + 1,
|
|
7318
|
+
exported: true,
|
|
7319
|
+
scope
|
|
7320
|
+
});
|
|
7321
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
7322
|
+
context.currentScope.push(scopeName);
|
|
7323
|
+
const body = findChildByType13(node, "body_statement");
|
|
7324
|
+
if (body) {
|
|
7325
|
+
walkNode13(body, context);
|
|
7326
|
+
}
|
|
7327
|
+
context.currentScope.pop();
|
|
7328
|
+
}
|
|
7329
|
+
function processSingletonMethod(node, context) {
|
|
7330
|
+
const nameNode = node.childCount > 2 ? node.child(node.childCount - 2) : null;
|
|
7331
|
+
let name = "";
|
|
7332
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
7333
|
+
const child = node.child(i);
|
|
7334
|
+
if (child && child.type === "identifier") {
|
|
7335
|
+
name = nodeText12(child, context);
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
if (!name) return;
|
|
7339
|
+
const scope = context.currentClass || void 0;
|
|
7340
|
+
const symbolId = scope ? `${context.filePath}::${scope}.self.${name}` : `${context.filePath}::self.${name}`;
|
|
7341
|
+
context.symbols.push({
|
|
7342
|
+
id: symbolId,
|
|
7343
|
+
name: `self.${name}`,
|
|
7344
|
+
kind: "method",
|
|
7345
|
+
filePath: context.filePath,
|
|
7346
|
+
startLine: node.startPosition.row + 1,
|
|
7347
|
+
endLine: node.endPosition.row + 1,
|
|
7348
|
+
exported: true,
|
|
7349
|
+
scope
|
|
7350
|
+
});
|
|
7351
|
+
const scopeName = scope ? `${scope}.self.${name}` : `self.${name}`;
|
|
7352
|
+
context.currentScope.push(scopeName);
|
|
7353
|
+
const body = findChildByType13(node, "body_statement");
|
|
7354
|
+
if (body) {
|
|
7355
|
+
walkNode13(body, context);
|
|
7356
|
+
}
|
|
7357
|
+
context.currentScope.pop();
|
|
7358
|
+
}
|
|
7359
|
+
function processAssignment(node, context) {
|
|
7360
|
+
const left = node.child(0);
|
|
7361
|
+
if (!left) return;
|
|
7362
|
+
const text = nodeText12(left, context);
|
|
7363
|
+
const scope = context.currentClass || void 0;
|
|
7364
|
+
if (left.type === "constant") {
|
|
7365
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${text}` : `${context.filePath}::${text}`;
|
|
7366
|
+
context.symbols.push({
|
|
7367
|
+
id: symbolId,
|
|
7368
|
+
name: text,
|
|
7369
|
+
kind: "constant",
|
|
7370
|
+
filePath: context.filePath,
|
|
7371
|
+
startLine: node.startPosition.row + 1,
|
|
7372
|
+
endLine: node.endPosition.row + 1,
|
|
7373
|
+
exported: true,
|
|
7374
|
+
scope
|
|
7375
|
+
});
|
|
7376
|
+
}
|
|
7377
|
+
if (left.type === "instance_variable") {
|
|
7378
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${text}` : `${context.filePath}::${text}`;
|
|
7379
|
+
context.symbols.push({
|
|
7380
|
+
id: symbolId,
|
|
7381
|
+
name: text,
|
|
7382
|
+
kind: "property",
|
|
7383
|
+
filePath: context.filePath,
|
|
7384
|
+
startLine: node.startPosition.row + 1,
|
|
7385
|
+
endLine: node.endPosition.row + 1,
|
|
7386
|
+
exported: false,
|
|
7387
|
+
scope
|
|
7388
|
+
});
|
|
7389
|
+
}
|
|
7390
|
+
if (left.type === "class_variable") {
|
|
7391
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${text}` : `${context.filePath}::${text}`;
|
|
7392
|
+
context.symbols.push({
|
|
7393
|
+
id: symbolId,
|
|
7394
|
+
name: text,
|
|
7395
|
+
kind: "property",
|
|
7396
|
+
filePath: context.filePath,
|
|
7397
|
+
startLine: node.startPosition.row + 1,
|
|
7398
|
+
endLine: node.endPosition.row + 1,
|
|
7399
|
+
exported: false,
|
|
7400
|
+
scope
|
|
7401
|
+
});
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
function processCallExpression13(node, context) {
|
|
7405
|
+
const text = nodeText12(node, context);
|
|
7406
|
+
const line = node.startPosition.row + 1;
|
|
7407
|
+
const requireMatch = text.match(/^require(?:_relative)?\s*\(?['"]([^'"]+)['"]\)?/);
|
|
7408
|
+
if (requireMatch) {
|
|
7409
|
+
processRequire(requireMatch[1], text.startsWith("require_relative"), context, line);
|
|
7410
|
+
return;
|
|
7411
|
+
}
|
|
7412
|
+
const mixinMatch = text.match(/^(?:include|extend|prepend)\s+([A-Z]\w*(?:::\w+)*)/);
|
|
7413
|
+
if (mixinMatch) {
|
|
7414
|
+
processMixin(mixinMatch[1], context, line);
|
|
7415
|
+
return;
|
|
7416
|
+
}
|
|
7417
|
+
const attrMatch = text.match(/^attr_(accessor|reader|writer)\s+(.+)/);
|
|
7418
|
+
if (attrMatch) {
|
|
7419
|
+
processAttrAccessor(attrMatch[2], context, line);
|
|
7420
|
+
return;
|
|
7421
|
+
}
|
|
7422
|
+
if (context.currentScope.length > 0) {
|
|
7423
|
+
const firstChild = node.child(0);
|
|
7424
|
+
let calleeName = null;
|
|
7425
|
+
if (firstChild && firstChild.type === "identifier") {
|
|
7426
|
+
calleeName = nodeText12(firstChild, context);
|
|
7427
|
+
}
|
|
7428
|
+
if (calleeName && !isBuiltin(calleeName)) {
|
|
7429
|
+
const callerId = getCurrentSymbolId13(context);
|
|
7430
|
+
if (callerId) {
|
|
7431
|
+
const calleeId = resolveSymbol12(calleeName, context);
|
|
7432
|
+
if (calleeId) {
|
|
7433
|
+
context.edges.push({
|
|
7434
|
+
source: callerId,
|
|
7435
|
+
target: calleeId,
|
|
7436
|
+
kind: "calls",
|
|
7437
|
+
filePath: context.filePath,
|
|
7438
|
+
line
|
|
7439
|
+
});
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
7443
|
+
}
|
|
7444
|
+
}
|
|
7445
|
+
function processCommand(node, context) {
|
|
7446
|
+
const text = nodeText12(node, context).trim();
|
|
7447
|
+
const line = node.startPosition.row + 1;
|
|
7448
|
+
const requireMatch = text.match(/^require(?:_relative)?\s+['"]([^'"]+)['"]/);
|
|
7449
|
+
if (requireMatch) {
|
|
7450
|
+
processRequire(requireMatch[1], text.startsWith("require_relative"), context, line);
|
|
7451
|
+
return;
|
|
7452
|
+
}
|
|
7453
|
+
const mixinMatch = text.match(/^(?:include|extend|prepend)\s+([A-Z]\w*(?:::\w+)*)/);
|
|
7454
|
+
if (mixinMatch) {
|
|
7455
|
+
processMixin(mixinMatch[1], context, line);
|
|
7456
|
+
return;
|
|
7457
|
+
}
|
|
7458
|
+
const attrMatch = text.match(/^attr_(accessor|reader|writer)\s+(.+)/);
|
|
7459
|
+
if (attrMatch) {
|
|
7460
|
+
processAttrAccessor(attrMatch[2], context, line);
|
|
7461
|
+
return;
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7464
|
+
function processCommandCall(node, context) {
|
|
7465
|
+
const text = nodeText12(node, context).trim();
|
|
7466
|
+
if (/Struct\.new/.test(text) || /OpenStruct\.new/.test(text)) {
|
|
7467
|
+
const parent = node.parent;
|
|
7468
|
+
if (parent && parent.type === "assignment") {
|
|
7469
|
+
const left = parent.child(0);
|
|
7470
|
+
if (left && left.type === "constant") {
|
|
7471
|
+
const name = nodeText12(left, context);
|
|
7472
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
7473
|
+
context.symbols.push({
|
|
7474
|
+
id: symbolId,
|
|
7475
|
+
name,
|
|
7476
|
+
kind: "class",
|
|
7477
|
+
filePath: context.filePath,
|
|
7478
|
+
startLine: node.startPosition.row + 1,
|
|
7479
|
+
endLine: node.endPosition.row + 1,
|
|
7480
|
+
exported: true
|
|
7481
|
+
});
|
|
7482
|
+
}
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
function processLambda(node, context) {
|
|
7487
|
+
const parent = node.parent;
|
|
7488
|
+
if (parent && parent.type === "assignment") {
|
|
7489
|
+
const left = parent.child(0);
|
|
7490
|
+
if (left) {
|
|
7491
|
+
const name = nodeText12(left, context);
|
|
7492
|
+
const scope = context.currentClass || void 0;
|
|
7493
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
7494
|
+
context.symbols.push({
|
|
7495
|
+
id: symbolId,
|
|
7496
|
+
name,
|
|
7497
|
+
kind: "function",
|
|
7498
|
+
filePath: context.filePath,
|
|
7499
|
+
startLine: node.startPosition.row + 1,
|
|
7500
|
+
endLine: node.endPosition.row + 1,
|
|
7501
|
+
exported: true,
|
|
7502
|
+
scope
|
|
7503
|
+
});
|
|
7504
|
+
}
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
function processRequire(path6, isRelative, context, line) {
|
|
7508
|
+
const resolvedPath = resolveRubyRequire(path6, isRelative, context.filePath, context.projectRoot);
|
|
7509
|
+
if (resolvedPath) {
|
|
7510
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
7511
|
+
const targetId = `${resolvedPath}::__file__`;
|
|
7512
|
+
context.edges.push({
|
|
7513
|
+
source: sourceId,
|
|
7514
|
+
target: targetId,
|
|
7515
|
+
kind: "imports",
|
|
7516
|
+
filePath: context.filePath,
|
|
7517
|
+
line
|
|
7518
|
+
});
|
|
7519
|
+
}
|
|
7520
|
+
const symbolId = `${context.filePath}::require:${path6}`;
|
|
7521
|
+
context.symbols.push({
|
|
7522
|
+
id: symbolId,
|
|
7523
|
+
name: path6,
|
|
7524
|
+
kind: "import",
|
|
7525
|
+
filePath: context.filePath,
|
|
7526
|
+
startLine: line,
|
|
7527
|
+
endLine: line,
|
|
7528
|
+
exported: false
|
|
7529
|
+
});
|
|
7530
|
+
}
|
|
7531
|
+
function processMixin(moduleName, context, line) {
|
|
7532
|
+
const scope = context.currentClass || void 0;
|
|
7533
|
+
const sourceId = scope ? `${context.filePath}::${scope}` : `${context.filePath}::__file__`;
|
|
7534
|
+
const targetId = resolveSymbol12(moduleName, context);
|
|
7535
|
+
if (targetId) {
|
|
7536
|
+
context.edges.push({
|
|
7537
|
+
source: sourceId,
|
|
7538
|
+
target: targetId,
|
|
7539
|
+
kind: "implements",
|
|
7540
|
+
filePath: context.filePath,
|
|
7541
|
+
line
|
|
7542
|
+
});
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
function processAttrAccessor(args, context, line) {
|
|
7546
|
+
const scope = context.currentClass || void 0;
|
|
7547
|
+
const symbols = args.match(/:\w+/g);
|
|
7548
|
+
if (!symbols) return;
|
|
7549
|
+
for (const sym of symbols) {
|
|
7550
|
+
const name = sym.slice(1);
|
|
7551
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
7552
|
+
context.symbols.push({
|
|
7553
|
+
id: symbolId,
|
|
7554
|
+
name,
|
|
7555
|
+
kind: "property",
|
|
7556
|
+
filePath: context.filePath,
|
|
7557
|
+
startLine: line,
|
|
7558
|
+
endLine: line,
|
|
7559
|
+
exported: true,
|
|
7560
|
+
scope
|
|
7561
|
+
});
|
|
7562
|
+
}
|
|
7563
|
+
}
|
|
7564
|
+
function parseGemfile(filePath, sourceCode, projectRoot) {
|
|
7565
|
+
const symbols = [];
|
|
7566
|
+
const edges = [];
|
|
7567
|
+
const lines = sourceCode.split("\n");
|
|
7568
|
+
symbols.push({
|
|
7569
|
+
id: `${filePath}::Gemfile`,
|
|
7570
|
+
name: "Gemfile",
|
|
7571
|
+
kind: "module",
|
|
7572
|
+
filePath,
|
|
7573
|
+
startLine: 1,
|
|
7574
|
+
endLine: lines.length,
|
|
7575
|
+
exported: true
|
|
7576
|
+
});
|
|
7577
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7578
|
+
const line = lines[i].trim();
|
|
7579
|
+
const lineNum = i + 1;
|
|
7580
|
+
const gemMatch = line.match(/^\s*gem\s+['"]([^'"]+)['"]/);
|
|
7581
|
+
if (gemMatch) {
|
|
7582
|
+
const gemName = gemMatch[1];
|
|
7583
|
+
symbols.push({
|
|
7584
|
+
id: `${filePath}::gem:${gemName}`,
|
|
7585
|
+
name: gemName,
|
|
7586
|
+
kind: "import",
|
|
7587
|
+
filePath,
|
|
7588
|
+
startLine: lineNum,
|
|
7589
|
+
endLine: lineNum,
|
|
7590
|
+
exported: false
|
|
7591
|
+
});
|
|
7592
|
+
}
|
|
7593
|
+
}
|
|
7594
|
+
return { filePath, symbols, edges };
|
|
7595
|
+
}
|
|
7596
|
+
function resolveRubyRequire(requirePath, isRelative, currentFile, projectRoot) {
|
|
7597
|
+
const extensions = [".rb", ""];
|
|
7598
|
+
if (isRelative) {
|
|
7599
|
+
const dir = dirname14(join15(projectRoot, currentFile));
|
|
7600
|
+
for (const ext of extensions) {
|
|
7601
|
+
const candidate = join15(dir, requirePath + ext);
|
|
7602
|
+
if (existsSync14(candidate)) {
|
|
7603
|
+
const rel = candidate.replace(projectRoot + "/", "");
|
|
7604
|
+
return rel;
|
|
7605
|
+
}
|
|
7606
|
+
}
|
|
7607
|
+
} else {
|
|
7608
|
+
const searchRoots = ["lib", "app", "app/models", "app/controllers", "app/services", ""];
|
|
7609
|
+
for (const root of searchRoots) {
|
|
7610
|
+
for (const ext of extensions) {
|
|
7611
|
+
const candidate = root ? join15(projectRoot, root, requirePath + ext) : join15(projectRoot, requirePath + ext);
|
|
7612
|
+
if (existsSync14(candidate)) {
|
|
7613
|
+
const rel = candidate.replace(projectRoot + "/", "");
|
|
7614
|
+
return rel;
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7617
|
+
}
|
|
7618
|
+
}
|
|
7619
|
+
return null;
|
|
7620
|
+
}
|
|
7621
|
+
function resolveSymbol12(name, context) {
|
|
7622
|
+
if (context.imports.has(name)) {
|
|
7623
|
+
return context.imports.get(name) || null;
|
|
7624
|
+
}
|
|
7625
|
+
const currentFileId = `${context.filePath}::${name}`;
|
|
7626
|
+
if (context.symbols.find((s) => s.id === currentFileId)) {
|
|
7627
|
+
return currentFileId;
|
|
7628
|
+
}
|
|
7629
|
+
if (context.currentClass) {
|
|
7630
|
+
const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
|
|
7631
|
+
if (context.symbols.find((s) => s.id === classMethodId)) {
|
|
7632
|
+
return classMethodId;
|
|
7633
|
+
}
|
|
7634
|
+
}
|
|
7635
|
+
return null;
|
|
7636
|
+
}
|
|
7637
|
+
function isBuiltin(name) {
|
|
7638
|
+
const builtins = /* @__PURE__ */ new Set([
|
|
7639
|
+
"puts",
|
|
7640
|
+
"print",
|
|
7641
|
+
"p",
|
|
7642
|
+
"pp",
|
|
7643
|
+
"warn",
|
|
7644
|
+
"raise",
|
|
7645
|
+
"fail",
|
|
7646
|
+
"require",
|
|
7647
|
+
"require_relative",
|
|
7648
|
+
"include",
|
|
7649
|
+
"extend",
|
|
7650
|
+
"prepend",
|
|
7651
|
+
"attr_accessor",
|
|
7652
|
+
"attr_reader",
|
|
7653
|
+
"attr_writer",
|
|
7654
|
+
"private",
|
|
7655
|
+
"protected",
|
|
7656
|
+
"public",
|
|
7657
|
+
"new",
|
|
7658
|
+
"initialize",
|
|
7659
|
+
"super",
|
|
7660
|
+
"self",
|
|
7661
|
+
"map",
|
|
7662
|
+
"each",
|
|
7663
|
+
"select",
|
|
7664
|
+
"reject",
|
|
7665
|
+
"reduce",
|
|
7666
|
+
"collect",
|
|
7667
|
+
"find",
|
|
7668
|
+
"detect",
|
|
7669
|
+
"any?",
|
|
7670
|
+
"all?",
|
|
7671
|
+
"none?",
|
|
7672
|
+
"count",
|
|
7673
|
+
"freeze",
|
|
7674
|
+
"dup",
|
|
7675
|
+
"clone",
|
|
7676
|
+
"nil?",
|
|
7677
|
+
"is_a?",
|
|
7678
|
+
"kind_of?",
|
|
7679
|
+
"respond_to?",
|
|
7680
|
+
"send",
|
|
7681
|
+
"class",
|
|
7682
|
+
"object_id",
|
|
7683
|
+
"to_s",
|
|
7684
|
+
"to_i",
|
|
7685
|
+
"to_f",
|
|
7686
|
+
"to_a",
|
|
7687
|
+
"to_h",
|
|
7688
|
+
"lambda",
|
|
7689
|
+
"proc",
|
|
7690
|
+
"block_given?",
|
|
7691
|
+
"yield"
|
|
7692
|
+
]);
|
|
7693
|
+
return builtins.has(name);
|
|
7694
|
+
}
|
|
7695
|
+
function findChildByType13(node, type) {
|
|
7696
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
7697
|
+
const child = node.child(i);
|
|
7698
|
+
if (child && child.type === type) return child;
|
|
7699
|
+
}
|
|
7700
|
+
return null;
|
|
7150
7701
|
}
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7702
|
+
function nodeText12(node, context) {
|
|
7703
|
+
return context.sourceCode.substring(node.startIndex, node.endIndex);
|
|
7704
|
+
}
|
|
7705
|
+
function getCurrentSymbolId13(context) {
|
|
7706
|
+
if (context.currentScope.length === 0) return null;
|
|
7707
|
+
return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
|
|
7708
|
+
}
|
|
7709
|
+
var rubyParser = {
|
|
7710
|
+
name: "ruby",
|
|
7711
|
+
extensions: [".rb", ".rake", ".gemspec", "Gemfile"],
|
|
7712
|
+
parseFile: parseRubyFile
|
|
7155
7713
|
};
|
|
7156
7714
|
|
|
7157
7715
|
// src/parser/detect.ts
|
|
@@ -7168,12 +7726,13 @@ var parsers = [
|
|
|
7168
7726
|
kotlinParser,
|
|
7169
7727
|
phpParser,
|
|
7170
7728
|
swiftParser,
|
|
7171
|
-
mojoParser
|
|
7729
|
+
mojoParser,
|
|
7730
|
+
rubyParser
|
|
7172
7731
|
];
|
|
7173
7732
|
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/;
|
|
7174
7733
|
function getParserForFile(filePath, content) {
|
|
7175
|
-
const ext =
|
|
7176
|
-
const fileName =
|
|
7734
|
+
const ext = extname11(filePath).toLowerCase();
|
|
7735
|
+
const fileName = basename9(filePath);
|
|
7177
7736
|
if (ext === ".h" && content) {
|
|
7178
7737
|
if (CPP_KEYWORDS.test(content)) {
|
|
7179
7738
|
return cppParser;
|
|
@@ -7191,7 +7750,7 @@ import { minimatch } from "minimatch";
|
|
|
7191
7750
|
var MAX_FILE_SIZE = 1e6;
|
|
7192
7751
|
function shouldParseFile(fullPath) {
|
|
7193
7752
|
try {
|
|
7194
|
-
const stats =
|
|
7753
|
+
const stats = statSync9(fullPath);
|
|
7195
7754
|
if (stats.size > MAX_FILE_SIZE) {
|
|
7196
7755
|
console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
|
|
7197
7756
|
return false;
|
|
@@ -7209,8 +7768,8 @@ async function parseProject(projectRoot, options) {
|
|
|
7209
7768
|
let errorFiles = 0;
|
|
7210
7769
|
for (const file of files) {
|
|
7211
7770
|
try {
|
|
7212
|
-
const fullPath =
|
|
7213
|
-
if (!
|
|
7771
|
+
const fullPath = join16(projectRoot, file);
|
|
7772
|
+
if (!resolve10(fullPath).startsWith(resolve10(projectRoot))) {
|
|
7214
7773
|
skippedFiles++;
|
|
7215
7774
|
continue;
|
|
7216
7775
|
}
|
|
@@ -7233,7 +7792,7 @@ async function parseProject(projectRoot, options) {
|
|
|
7233
7792
|
if (options?.verbose) {
|
|
7234
7793
|
console.error(`[Parser] Parsing: ${file}`);
|
|
7235
7794
|
}
|
|
7236
|
-
const sourceCode =
|
|
7795
|
+
const sourceCode = readFileSync12(fullPath, "utf-8");
|
|
7237
7796
|
const parser = getParserForFile(file, sourceCode);
|
|
7238
7797
|
if (!parser) {
|
|
7239
7798
|
console.error(`No parser found for file: ${file}`);
|
|
@@ -7262,8 +7821,8 @@ async function parseProject(projectRoot, options) {
|
|
|
7262
7821
|
}
|
|
7263
7822
|
|
|
7264
7823
|
// src/cross-language/detectors/rest-api.ts
|
|
7265
|
-
import { readFileSync as
|
|
7266
|
-
import { join as
|
|
7824
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
7825
|
+
import { join as join17, resolve as resolve11 } from "path";
|
|
7267
7826
|
function getLanguage(filePath) {
|
|
7268
7827
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
7269
7828
|
if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
|
|
@@ -7274,6 +7833,7 @@ function getLanguage(filePath) {
|
|
|
7274
7833
|
if (filePath.endsWith(".kt") || filePath.endsWith(".kts")) return "kotlin";
|
|
7275
7834
|
if (filePath.endsWith(".php")) return "php";
|
|
7276
7835
|
if (filePath.endsWith(".swift")) return "swift";
|
|
7836
|
+
if (filePath.endsWith(".rb") || filePath.endsWith(".rake") || filePath.endsWith(".ru") || filePath.endsWith(".gemspec")) return "ruby";
|
|
7277
7837
|
if (filePath.endsWith(".mojo") || filePath.endsWith(".\u{1F525}")) return "mojo";
|
|
7278
7838
|
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";
|
|
7279
7839
|
return "unknown";
|
|
@@ -7761,6 +8321,63 @@ function extractRouteDefinitions(source, filePath) {
|
|
|
7761
8321
|
}
|
|
7762
8322
|
}
|
|
7763
8323
|
}
|
|
8324
|
+
if (lang === "ruby") {
|
|
8325
|
+
const railsRouteMatch = line.match(/^\s*(get|post|put|patch|delete)\s+['"]([^'"]+)['"]/);
|
|
8326
|
+
if (railsRouteMatch) {
|
|
8327
|
+
const path6 = railsRouteMatch[2].startsWith("/") ? railsRouteMatch[2] : "/" + railsRouteMatch[2];
|
|
8328
|
+
routes.push({
|
|
8329
|
+
method: railsRouteMatch[1].toUpperCase(),
|
|
8330
|
+
path: path6,
|
|
8331
|
+
normalizedPath: normalizePath(path6),
|
|
8332
|
+
file: filePath,
|
|
8333
|
+
line: i + 1
|
|
8334
|
+
});
|
|
8335
|
+
}
|
|
8336
|
+
const sinatraMatch = line.match(/^\s*(get|post|put|patch|delete)\s+['"]([^'"]+)['"]\s+do/);
|
|
8337
|
+
if (sinatraMatch && !railsRouteMatch) {
|
|
8338
|
+
const path6 = sinatraMatch[2].startsWith("/") ? sinatraMatch[2] : "/" + sinatraMatch[2];
|
|
8339
|
+
routes.push({
|
|
8340
|
+
method: sinatraMatch[1].toUpperCase(),
|
|
8341
|
+
path: path6,
|
|
8342
|
+
normalizedPath: normalizePath(path6),
|
|
8343
|
+
file: filePath,
|
|
8344
|
+
line: i + 1
|
|
8345
|
+
});
|
|
8346
|
+
}
|
|
8347
|
+
const resourcesMatch = line.match(/^\s*resources?\s+:(\w+)/);
|
|
8348
|
+
if (resourcesMatch) {
|
|
8349
|
+
const resourcePath = "/" + resourcesMatch[1];
|
|
8350
|
+
routes.push({
|
|
8351
|
+
method: "ANY",
|
|
8352
|
+
path: resourcePath,
|
|
8353
|
+
normalizedPath: normalizePath(resourcePath),
|
|
8354
|
+
file: filePath,
|
|
8355
|
+
line: i + 1
|
|
8356
|
+
});
|
|
8357
|
+
}
|
|
8358
|
+
const rackMatch = line.match(/^\s*map\s+['"]([^'"]+)['"]/);
|
|
8359
|
+
if (rackMatch) {
|
|
8360
|
+
const path6 = rackMatch[1].startsWith("/") ? rackMatch[1] : "/" + rackMatch[1];
|
|
8361
|
+
routes.push({
|
|
8362
|
+
method: "ANY",
|
|
8363
|
+
path: path6,
|
|
8364
|
+
normalizedPath: normalizePath(path6),
|
|
8365
|
+
file: filePath,
|
|
8366
|
+
line: i + 1
|
|
8367
|
+
});
|
|
8368
|
+
}
|
|
8369
|
+
const grapeMatch = line.match(/^\s*(get|post|put|patch|delete)\s+['"]([^'"]+)['"]/);
|
|
8370
|
+
if (grapeMatch && !railsRouteMatch) {
|
|
8371
|
+
const path6 = grapeMatch[2].startsWith("/") ? grapeMatch[2] : "/" + grapeMatch[2];
|
|
8372
|
+
routes.push({
|
|
8373
|
+
method: grapeMatch[1].toUpperCase(),
|
|
8374
|
+
path: path6,
|
|
8375
|
+
normalizedPath: normalizePath(path6),
|
|
8376
|
+
file: filePath,
|
|
8377
|
+
line: i + 1
|
|
8378
|
+
});
|
|
8379
|
+
}
|
|
8380
|
+
}
|
|
7764
8381
|
if (lang === "cpp") {
|
|
7765
8382
|
const crowMatch = line.match(/CROW_ROUTE\s*\(\s*\w+\s*,\s*"([^"]+)"/);
|
|
7766
8383
|
if (crowMatch) {
|
|
@@ -7875,11 +8492,11 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
7875
8492
|
const allCalls = [];
|
|
7876
8493
|
const allRoutes = [];
|
|
7877
8494
|
for (const file of files) {
|
|
7878
|
-
const fullPath =
|
|
7879
|
-
if (!
|
|
8495
|
+
const fullPath = join17(projectRoot, file.filePath);
|
|
8496
|
+
if (!resolve11(fullPath).startsWith(resolve11(projectRoot))) continue;
|
|
7880
8497
|
let source;
|
|
7881
8498
|
try {
|
|
7882
|
-
source =
|
|
8499
|
+
source = readFileSync13(fullPath, "utf-8");
|
|
7883
8500
|
} catch {
|
|
7884
8501
|
continue;
|
|
7885
8502
|
}
|
|
@@ -7951,6 +8568,34 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
7951
8568
|
}
|
|
7952
8569
|
}
|
|
7953
8570
|
}
|
|
8571
|
+
if (lang === "ruby") {
|
|
8572
|
+
const rubyLines = source.split("\n");
|
|
8573
|
+
for (let i = 0; i < rubyLines.length; i++) {
|
|
8574
|
+
const line = rubyLines[i];
|
|
8575
|
+
const faradayMatch = line.match(/\w+\s*\.\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/i);
|
|
8576
|
+
if (faradayMatch) {
|
|
8577
|
+
const path6 = faradayMatch[2];
|
|
8578
|
+
if (isLocalApiPath(path6)) {
|
|
8579
|
+
allCalls.push({ method: faradayMatch[1].toUpperCase(), path: cleanPath(path6), file: file.filePath, line: i + 1 });
|
|
8580
|
+
}
|
|
8581
|
+
}
|
|
8582
|
+
const netHttpMatch = line.match(/Net::HTTP\s*\.\s*(get|post_form|post)\s*\(/i);
|
|
8583
|
+
if (netHttpMatch) {
|
|
8584
|
+
const uriMatch = line.match(/['"]([^'"]+)['"]/);
|
|
8585
|
+
if (uriMatch && isLocalApiPath(uriMatch[1])) {
|
|
8586
|
+
const method = netHttpMatch[1].toUpperCase().replace("POST_FORM", "POST");
|
|
8587
|
+
allCalls.push({ method, path: cleanPath(uriMatch[1]), file: file.filePath, line: i + 1 });
|
|
8588
|
+
}
|
|
8589
|
+
}
|
|
8590
|
+
const httpartyMatch = line.match(/(?:HTTParty|self)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/i);
|
|
8591
|
+
if (httpartyMatch) {
|
|
8592
|
+
const path6 = httpartyMatch[2];
|
|
8593
|
+
if (isLocalApiPath(path6)) {
|
|
8594
|
+
allCalls.push({ method: httpartyMatch[1].toUpperCase(), path: cleanPath(path6), file: file.filePath, line: i + 1 });
|
|
8595
|
+
}
|
|
8596
|
+
}
|
|
8597
|
+
}
|
|
8598
|
+
}
|
|
7954
8599
|
allRoutes.push(...extractRouteDefinitions(source, file.filePath));
|
|
7955
8600
|
}
|
|
7956
8601
|
for (const call of allCalls) {
|
|
@@ -7979,8 +8624,8 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
7979
8624
|
}
|
|
7980
8625
|
|
|
7981
8626
|
// src/cross-language/detectors/subprocess.ts
|
|
7982
|
-
import { readFileSync as
|
|
7983
|
-
import { join as
|
|
8627
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
8628
|
+
import { join as join18, resolve as resolve12, basename as basename10 } from "path";
|
|
7984
8629
|
var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
|
|
7985
8630
|
function getLanguage2(filePath) {
|
|
7986
8631
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
@@ -8079,16 +8724,16 @@ function detectSubprocessEdges(files, projectRoot) {
|
|
|
8079
8724
|
const knownFiles = new Set(files.map((f) => f.filePath));
|
|
8080
8725
|
const basenameMap = /* @__PURE__ */ new Map();
|
|
8081
8726
|
for (const f of files) {
|
|
8082
|
-
const base =
|
|
8727
|
+
const base = basename10(f.filePath);
|
|
8083
8728
|
if (!basenameMap.has(base)) basenameMap.set(base, []);
|
|
8084
8729
|
basenameMap.get(base).push(f.filePath);
|
|
8085
8730
|
}
|
|
8086
8731
|
for (const file of files) {
|
|
8087
|
-
const fullPath =
|
|
8088
|
-
if (!
|
|
8732
|
+
const fullPath = join18(projectRoot, file.filePath);
|
|
8733
|
+
if (!resolve12(fullPath).startsWith(resolve12(projectRoot))) continue;
|
|
8089
8734
|
let source;
|
|
8090
8735
|
try {
|
|
8091
|
-
source =
|
|
8736
|
+
source = readFileSync14(fullPath, "utf-8");
|
|
8092
8737
|
} catch {
|
|
8093
8738
|
continue;
|
|
8094
8739
|
}
|
|
@@ -8100,7 +8745,7 @@ function detectSubprocessEdges(files, projectRoot) {
|
|
|
8100
8745
|
targetFile = call.calledFile;
|
|
8101
8746
|
confidence = "high";
|
|
8102
8747
|
} else {
|
|
8103
|
-
const base =
|
|
8748
|
+
const base = basename10(call.calledFile);
|
|
8104
8749
|
const candidates = basenameMap.get(base);
|
|
8105
8750
|
if (candidates && candidates.length > 0) {
|
|
8106
8751
|
const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
|
|
@@ -8479,7 +9124,7 @@ function getArchitectureSummary(graph) {
|
|
|
8479
9124
|
}
|
|
8480
9125
|
|
|
8481
9126
|
// src/health/metrics.ts
|
|
8482
|
-
import { dirname as
|
|
9127
|
+
import { dirname as dirname15 } from "path";
|
|
8483
9128
|
function scoreToGrade(score) {
|
|
8484
9129
|
if (score >= 90) return "A";
|
|
8485
9130
|
if (score >= 80) return "B";
|
|
@@ -8512,8 +9157,8 @@ function calculateCouplingScore(graph) {
|
|
|
8512
9157
|
totalEdges++;
|
|
8513
9158
|
fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
|
|
8514
9159
|
fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
|
|
8515
|
-
const sourceDir =
|
|
8516
|
-
const targetDir =
|
|
9160
|
+
const sourceDir = dirname15(sourceAttrs.filePath).split("/")[0];
|
|
9161
|
+
const targetDir = dirname15(targetAttrs.filePath).split("/")[0];
|
|
8517
9162
|
if (sourceDir !== targetDir) {
|
|
8518
9163
|
crossDirEdges++;
|
|
8519
9164
|
}
|
|
@@ -8560,8 +9205,8 @@ function calculateCohesionScore(graph) {
|
|
|
8560
9205
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
8561
9206
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
8562
9207
|
if (sourceAttrs.filePath !== targetAttrs.filePath) {
|
|
8563
|
-
const sourceDir =
|
|
8564
|
-
const targetDir =
|
|
9208
|
+
const sourceDir = dirname15(sourceAttrs.filePath);
|
|
9209
|
+
const targetDir = dirname15(targetAttrs.filePath);
|
|
8565
9210
|
if (!dirEdges.has(sourceDir)) {
|
|
8566
9211
|
dirEdges.set(sourceDir, { internal: 0, total: 0 });
|
|
8567
9212
|
}
|
|
@@ -8843,8 +9488,8 @@ function calculateDepthScore(graph) {
|
|
|
8843
9488
|
}
|
|
8844
9489
|
|
|
8845
9490
|
// src/health/index.ts
|
|
8846
|
-
import { readFileSync as
|
|
8847
|
-
import { dirname as
|
|
9491
|
+
import { readFileSync as readFileSync15, writeFileSync, existsSync as existsSync15, mkdirSync } from "fs";
|
|
9492
|
+
import { dirname as dirname16, resolve as resolve13 } from "path";
|
|
8848
9493
|
function calculateHealthScore(graph, projectRoot) {
|
|
8849
9494
|
const coupling = calculateCouplingScore(graph);
|
|
8850
9495
|
const cohesion = calculateCohesionScore(graph);
|
|
@@ -8943,8 +9588,8 @@ function getHealthTrend(projectRoot, currentScore) {
|
|
|
8943
9588
|
}
|
|
8944
9589
|
}
|
|
8945
9590
|
function saveHealthHistory(projectRoot, report) {
|
|
8946
|
-
const resolvedRoot =
|
|
8947
|
-
const historyFile =
|
|
9591
|
+
const resolvedRoot = resolve13(projectRoot);
|
|
9592
|
+
const historyFile = resolve13(resolvedRoot, ".depwire", "health-history.json");
|
|
8948
9593
|
if (!historyFile.startsWith(resolvedRoot)) {
|
|
8949
9594
|
return;
|
|
8950
9595
|
}
|
|
@@ -8959,10 +9604,10 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
8959
9604
|
}))
|
|
8960
9605
|
};
|
|
8961
9606
|
let history = [];
|
|
8962
|
-
if (
|
|
9607
|
+
if (existsSync15(historyFile)) {
|
|
8963
9608
|
try {
|
|
8964
9609
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
8965
|
-
const content =
|
|
9610
|
+
const content = readFileSync15(historyFile, "utf-8");
|
|
8966
9611
|
history = JSON.parse(content);
|
|
8967
9612
|
} catch {
|
|
8968
9613
|
}
|
|
@@ -8971,19 +9616,19 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
8971
9616
|
if (history.length > 50) {
|
|
8972
9617
|
history = history.slice(-50);
|
|
8973
9618
|
}
|
|
8974
|
-
mkdirSync(
|
|
9619
|
+
mkdirSync(dirname16(historyFile), { recursive: true });
|
|
8975
9620
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
8976
9621
|
writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
|
|
8977
9622
|
}
|
|
8978
9623
|
function loadHealthHistory(projectRoot) {
|
|
8979
|
-
const resolvedRoot =
|
|
8980
|
-
const historyFile =
|
|
8981
|
-
if (!historyFile.startsWith(resolvedRoot) || !
|
|
9624
|
+
const resolvedRoot = resolve13(projectRoot);
|
|
9625
|
+
const historyFile = resolve13(resolvedRoot, ".depwire", "health-history.json");
|
|
9626
|
+
if (!historyFile.startsWith(resolvedRoot) || !existsSync15(historyFile)) {
|
|
8982
9627
|
return [];
|
|
8983
9628
|
}
|
|
8984
9629
|
try {
|
|
8985
9630
|
if (!historyFile.startsWith(resolvedRoot)) return [];
|
|
8986
|
-
const content =
|
|
9631
|
+
const content = readFileSync15(historyFile, "utf-8");
|
|
8987
9632
|
return JSON.parse(content);
|
|
8988
9633
|
} catch {
|
|
8989
9634
|
return [];
|
|
@@ -8992,7 +9637,7 @@ function loadHealthHistory(projectRoot) {
|
|
|
8992
9637
|
|
|
8993
9638
|
// src/dead-code/detector.ts
|
|
8994
9639
|
import path2 from "path";
|
|
8995
|
-
import { readFileSync as
|
|
9640
|
+
import { readFileSync as readFileSync16, existsSync as existsSync16 } from "fs";
|
|
8996
9641
|
function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
|
|
8997
9642
|
const deadSymbols = [];
|
|
8998
9643
|
const context = { graph, projectRoot };
|
|
@@ -9125,11 +9770,11 @@ function getPackageEntryPoints(projectRoot) {
|
|
|
9125
9770
|
const entryPoints = /* @__PURE__ */ new Set();
|
|
9126
9771
|
const resolvedRoot = path2.resolve(projectRoot);
|
|
9127
9772
|
const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
|
|
9128
|
-
if (!packageJsonPath.startsWith(resolvedRoot) || !
|
|
9773
|
+
if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync16(packageJsonPath)) {
|
|
9129
9774
|
return entryPoints;
|
|
9130
9775
|
}
|
|
9131
9776
|
try {
|
|
9132
|
-
const packageJson = JSON.parse(
|
|
9777
|
+
const packageJson = JSON.parse(readFileSync16(packageJsonPath, "utf-8"));
|
|
9133
9778
|
if (packageJson.main) {
|
|
9134
9779
|
entryPoints.add(path2.resolve(projectRoot, packageJson.main));
|
|
9135
9780
|
}
|
|
@@ -9195,6 +9840,9 @@ function shouldExclude(attrs, context, includeTests, packageEntryPoints) {
|
|
|
9195
9840
|
if (isMojoExcluded(attrs)) {
|
|
9196
9841
|
return "framework";
|
|
9197
9842
|
}
|
|
9843
|
+
if (isRubyExcluded(attrs)) {
|
|
9844
|
+
return "framework";
|
|
9845
|
+
}
|
|
9198
9846
|
return null;
|
|
9199
9847
|
}
|
|
9200
9848
|
function isRealPackageEntryPoint(filePath, packageEntryPoints) {
|
|
@@ -9407,6 +10055,50 @@ function isMojoExcluded(attrs) {
|
|
|
9407
10055
|
if (name === "main") return true;
|
|
9408
10056
|
return false;
|
|
9409
10057
|
}
|
|
10058
|
+
function isRubyExcluded(attrs) {
|
|
10059
|
+
const filePath = attrs.file || attrs.filePath || "";
|
|
10060
|
+
const name = attrs.name || "";
|
|
10061
|
+
if (!filePath.endsWith(".rb") && !filePath.endsWith(".rake") && !filePath.endsWith(".gemspec")) return false;
|
|
10062
|
+
const railsCallbacks = [
|
|
10063
|
+
"before_action",
|
|
10064
|
+
"after_action",
|
|
10065
|
+
"around_action",
|
|
10066
|
+
"before_filter",
|
|
10067
|
+
"after_filter",
|
|
10068
|
+
"around_filter"
|
|
10069
|
+
];
|
|
10070
|
+
if (railsCallbacks.includes(name)) return true;
|
|
10071
|
+
const arCallbacks = [
|
|
10072
|
+
"before_save",
|
|
10073
|
+
"after_save",
|
|
10074
|
+
"before_create",
|
|
10075
|
+
"after_create",
|
|
10076
|
+
"before_update",
|
|
10077
|
+
"after_update",
|
|
10078
|
+
"before_destroy",
|
|
10079
|
+
"after_destroy",
|
|
10080
|
+
"before_validation",
|
|
10081
|
+
"after_validation",
|
|
10082
|
+
"after_commit",
|
|
10083
|
+
"after_rollback",
|
|
10084
|
+
"after_initialize",
|
|
10085
|
+
"after_find"
|
|
10086
|
+
];
|
|
10087
|
+
if (arCallbacks.includes(name)) return true;
|
|
10088
|
+
if (filePath.endsWith(".rake") || name === "task") return true;
|
|
10089
|
+
if (["it", "describe", "context", "specify", "subject", "let", "let!", "before", "after"].includes(name)) return true;
|
|
10090
|
+
if (name.startsWith("test_")) return true;
|
|
10091
|
+
if (name === "included" || name === "class_methods") return true;
|
|
10092
|
+
if (name === "initialize") return true;
|
|
10093
|
+
if (name === "method_missing" || name === "respond_to_missing?") return true;
|
|
10094
|
+
if (name === "concern" || name === "concerning") return true;
|
|
10095
|
+
const policyMethods = ["index?", "show?", "create?", "new?", "update?", "edit?", "destroy?"];
|
|
10096
|
+
if (policyMethods.includes(name)) return true;
|
|
10097
|
+
const deviseMethods = ["authenticate!", "valid?", "authenticate_user!", "current_user"];
|
|
10098
|
+
if (deviseMethods.includes(name)) return true;
|
|
10099
|
+
if (name === "main") return true;
|
|
10100
|
+
return false;
|
|
10101
|
+
}
|
|
9410
10102
|
|
|
9411
10103
|
// src/dead-code/classifier.ts
|
|
9412
10104
|
import path3 from "path";
|
|
@@ -9473,8 +10165,8 @@ function generateReason(symbol, confidence) {
|
|
|
9473
10165
|
return "Potentially unused";
|
|
9474
10166
|
}
|
|
9475
10167
|
function isBarrelFile(filePath) {
|
|
9476
|
-
const
|
|
9477
|
-
return
|
|
10168
|
+
const basename14 = path3.basename(filePath);
|
|
10169
|
+
return basename14 === "index.ts" || basename14 === "index.js";
|
|
9478
10170
|
}
|
|
9479
10171
|
function isTestFile2(filePath) {
|
|
9480
10172
|
return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
|
|
@@ -9653,11 +10345,11 @@ function filterByConfidence(symbols, minConfidence) {
|
|
|
9653
10345
|
}
|
|
9654
10346
|
|
|
9655
10347
|
// src/docs/generator.ts
|
|
9656
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as
|
|
9657
|
-
import { join as
|
|
10348
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync19 } from "fs";
|
|
10349
|
+
import { join as join22 } from "path";
|
|
9658
10350
|
|
|
9659
10351
|
// src/docs/architecture.ts
|
|
9660
|
-
import { dirname as
|
|
10352
|
+
import { dirname as dirname17 } from "path";
|
|
9661
10353
|
|
|
9662
10354
|
// src/docs/templates.ts
|
|
9663
10355
|
function header(text, level = 1) {
|
|
@@ -9808,7 +10500,7 @@ function generateModuleStructure(graph) {
|
|
|
9808
10500
|
function getDirectoryStats(graph) {
|
|
9809
10501
|
const dirMap = /* @__PURE__ */ new Map();
|
|
9810
10502
|
graph.forEachNode((node, attrs) => {
|
|
9811
|
-
const dir =
|
|
10503
|
+
const dir = dirname17(attrs.filePath);
|
|
9812
10504
|
if (dir === ".") return;
|
|
9813
10505
|
if (!dirMap.has(dir)) {
|
|
9814
10506
|
dirMap.set(dir, {
|
|
@@ -9833,7 +10525,7 @@ function getDirectoryStats(graph) {
|
|
|
9833
10525
|
});
|
|
9834
10526
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
9835
10527
|
graph.forEachNode((node, attrs) => {
|
|
9836
|
-
const dir =
|
|
10528
|
+
const dir = dirname17(attrs.filePath);
|
|
9837
10529
|
if (!filesPerDir.has(dir)) {
|
|
9838
10530
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
9839
10531
|
}
|
|
@@ -9848,8 +10540,8 @@ function getDirectoryStats(graph) {
|
|
|
9848
10540
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
9849
10541
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
9850
10542
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
9851
|
-
const sourceDir =
|
|
9852
|
-
const targetDir =
|
|
10543
|
+
const sourceDir = dirname17(sourceAttrs.filePath);
|
|
10544
|
+
const targetDir = dirname17(targetAttrs.filePath);
|
|
9853
10545
|
if (sourceDir !== targetDir) {
|
|
9854
10546
|
if (!dirEdges.has(sourceDir)) {
|
|
9855
10547
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -10056,7 +10748,7 @@ function detectCycles(graph) {
|
|
|
10056
10748
|
}
|
|
10057
10749
|
|
|
10058
10750
|
// src/docs/conventions.ts
|
|
10059
|
-
import { basename as
|
|
10751
|
+
import { basename as basename11, extname as extname12 } from "path";
|
|
10060
10752
|
function generateConventions(graph, projectRoot, version) {
|
|
10061
10753
|
let output = "";
|
|
10062
10754
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -10094,7 +10786,7 @@ function generateFileOrganization(graph) {
|
|
|
10094
10786
|
graph.forEachNode((node, attrs) => {
|
|
10095
10787
|
if (!files.has(attrs.filePath)) {
|
|
10096
10788
|
files.add(attrs.filePath);
|
|
10097
|
-
const fileName =
|
|
10789
|
+
const fileName = basename11(attrs.filePath);
|
|
10098
10790
|
if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
|
|
10099
10791
|
barrelFileCount++;
|
|
10100
10792
|
}
|
|
@@ -10153,7 +10845,7 @@ function generateNamingPatterns(graph) {
|
|
|
10153
10845
|
graph.forEachNode((node, attrs) => {
|
|
10154
10846
|
if (!files.has(attrs.filePath)) {
|
|
10155
10847
|
files.add(attrs.filePath);
|
|
10156
|
-
const fileName =
|
|
10848
|
+
const fileName = basename11(attrs.filePath, extname12(attrs.filePath));
|
|
10157
10849
|
if (isCamelCase(fileName)) patterns.files.camelCase++;
|
|
10158
10850
|
else if (isPascalCase(fileName)) patterns.files.PascalCase++;
|
|
10159
10851
|
else if (isKebabCase(fileName)) patterns.files.kebabCase++;
|
|
@@ -10830,7 +11522,7 @@ function detectCyclesDetailed(graph) {
|
|
|
10830
11522
|
}
|
|
10831
11523
|
|
|
10832
11524
|
// src/docs/onboarding.ts
|
|
10833
|
-
import { dirname as
|
|
11525
|
+
import { dirname as dirname18 } from "path";
|
|
10834
11526
|
function generateOnboarding(graph, projectRoot, version) {
|
|
10835
11527
|
let output = "";
|
|
10836
11528
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -10889,7 +11581,7 @@ function generateQuickOrientation(graph) {
|
|
|
10889
11581
|
const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
|
|
10890
11582
|
const dirs = /* @__PURE__ */ new Set();
|
|
10891
11583
|
graph.forEachNode((node, attrs) => {
|
|
10892
|
-
const dir =
|
|
11584
|
+
const dir = dirname18(attrs.filePath);
|
|
10893
11585
|
if (dir !== ".") {
|
|
10894
11586
|
const topLevel = dir.split("/")[0];
|
|
10895
11587
|
dirs.add(topLevel);
|
|
@@ -10993,7 +11685,7 @@ function generateModuleMap(graph) {
|
|
|
10993
11685
|
function getDirectoryStats2(graph) {
|
|
10994
11686
|
const dirMap = /* @__PURE__ */ new Map();
|
|
10995
11687
|
graph.forEachNode((node, attrs) => {
|
|
10996
|
-
const dir =
|
|
11688
|
+
const dir = dirname18(attrs.filePath);
|
|
10997
11689
|
if (dir === ".") return;
|
|
10998
11690
|
if (!dirMap.has(dir)) {
|
|
10999
11691
|
dirMap.set(dir, {
|
|
@@ -11008,7 +11700,7 @@ function getDirectoryStats2(graph) {
|
|
|
11008
11700
|
});
|
|
11009
11701
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
11010
11702
|
graph.forEachNode((node, attrs) => {
|
|
11011
|
-
const dir =
|
|
11703
|
+
const dir = dirname18(attrs.filePath);
|
|
11012
11704
|
if (!filesPerDir.has(dir)) {
|
|
11013
11705
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
11014
11706
|
}
|
|
@@ -11023,8 +11715,8 @@ function getDirectoryStats2(graph) {
|
|
|
11023
11715
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
11024
11716
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
11025
11717
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
11026
|
-
const sourceDir =
|
|
11027
|
-
const targetDir =
|
|
11718
|
+
const sourceDir = dirname18(sourceAttrs.filePath);
|
|
11719
|
+
const targetDir = dirname18(targetAttrs.filePath);
|
|
11028
11720
|
if (sourceDir !== targetDir) {
|
|
11029
11721
|
if (!dirEdges.has(sourceDir)) {
|
|
11030
11722
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -11105,7 +11797,7 @@ function detectClusters(graph) {
|
|
|
11105
11797
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
11106
11798
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
11107
11799
|
graph.forEachNode((node, attrs) => {
|
|
11108
|
-
const dir =
|
|
11800
|
+
const dir = dirname18(attrs.filePath);
|
|
11109
11801
|
if (!dirFiles.has(dir)) {
|
|
11110
11802
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
11111
11803
|
}
|
|
@@ -11159,8 +11851,8 @@ function inferClusterName(files) {
|
|
|
11159
11851
|
if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
|
|
11160
11852
|
return capitalizeFirst2(sortedWords[0][0]);
|
|
11161
11853
|
}
|
|
11162
|
-
const commonDir =
|
|
11163
|
-
if (files.every((f) =>
|
|
11854
|
+
const commonDir = dirname18(files[0]);
|
|
11855
|
+
if (files.every((f) => dirname18(f) === commonDir)) {
|
|
11164
11856
|
return capitalizeFirst2(commonDir.split("/").pop() || "Core");
|
|
11165
11857
|
}
|
|
11166
11858
|
return "Core";
|
|
@@ -11218,7 +11910,7 @@ function generateDepwireUsage(projectRoot) {
|
|
|
11218
11910
|
}
|
|
11219
11911
|
|
|
11220
11912
|
// src/docs/files.ts
|
|
11221
|
-
import { dirname as
|
|
11913
|
+
import { dirname as dirname19, basename as basename12 } from "path";
|
|
11222
11914
|
function generateFiles(graph, projectRoot, version) {
|
|
11223
11915
|
let output = "";
|
|
11224
11916
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -11322,7 +12014,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
11322
12014
|
const fileStats = getFileStats2(graph);
|
|
11323
12015
|
const dirMap = /* @__PURE__ */ new Map();
|
|
11324
12016
|
for (const file of fileStats) {
|
|
11325
|
-
const dir =
|
|
12017
|
+
const dir = dirname19(file.filePath);
|
|
11326
12018
|
const topDir = dir === "." ? "." : dir.split("/")[0];
|
|
11327
12019
|
if (!dirMap.has(topDir)) {
|
|
11328
12020
|
dirMap.set(topDir, {
|
|
@@ -11337,7 +12029,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
11337
12029
|
dirStats.symbolCount += file.symbolCount;
|
|
11338
12030
|
if (file.totalConnections > dirStats.maxConnections) {
|
|
11339
12031
|
dirStats.maxConnections = file.totalConnections;
|
|
11340
|
-
dirStats.mostConnectedFile =
|
|
12032
|
+
dirStats.mostConnectedFile = basename12(file.filePath);
|
|
11341
12033
|
}
|
|
11342
12034
|
}
|
|
11343
12035
|
if (dirMap.size === 0) {
|
|
@@ -11965,7 +12657,7 @@ function generateRecommendations(graph) {
|
|
|
11965
12657
|
}
|
|
11966
12658
|
|
|
11967
12659
|
// src/docs/tests.ts
|
|
11968
|
-
import { basename as
|
|
12660
|
+
import { basename as basename13, dirname as dirname20 } from "path";
|
|
11969
12661
|
function generateTests(graph, projectRoot, version) {
|
|
11970
12662
|
let output = "";
|
|
11971
12663
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -11993,8 +12685,8 @@ function getFileCount8(graph) {
|
|
|
11993
12685
|
return files.size;
|
|
11994
12686
|
}
|
|
11995
12687
|
function isTestFile3(filePath) {
|
|
11996
|
-
const fileName =
|
|
11997
|
-
const dirPath =
|
|
12688
|
+
const fileName = basename13(filePath).toLowerCase();
|
|
12689
|
+
const dirPath = dirname20(filePath).toLowerCase();
|
|
11998
12690
|
if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
|
|
11999
12691
|
return true;
|
|
12000
12692
|
}
|
|
@@ -12052,13 +12744,13 @@ function generateTestFileInventory(graph) {
|
|
|
12052
12744
|
return output;
|
|
12053
12745
|
}
|
|
12054
12746
|
function matchTestToSource(testFile) {
|
|
12055
|
-
const testFileName =
|
|
12056
|
-
const testDir =
|
|
12747
|
+
const testFileName = basename13(testFile);
|
|
12748
|
+
const testDir = dirname20(testFile);
|
|
12057
12749
|
let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
|
|
12058
12750
|
const possiblePaths = [];
|
|
12059
12751
|
possiblePaths.push(testDir + "/" + sourceFileName);
|
|
12060
12752
|
if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
|
|
12061
|
-
const parentDir =
|
|
12753
|
+
const parentDir = dirname20(testDir);
|
|
12062
12754
|
possiblePaths.push(parentDir + "/" + sourceFileName);
|
|
12063
12755
|
}
|
|
12064
12756
|
if (testDir.includes("test")) {
|
|
@@ -12214,7 +12906,7 @@ function generateTestCoverageMap(graph) {
|
|
|
12214
12906
|
const rows = mappings.slice(0, 30).map((m) => [
|
|
12215
12907
|
`\`${m.sourceFile}\``,
|
|
12216
12908
|
m.hasTest ? "\u2705" : "\u274C",
|
|
12217
|
-
m.testFile ? `\`${
|
|
12909
|
+
m.testFile ? `\`${basename13(m.testFile)}\`` : "-",
|
|
12218
12910
|
formatNumber(m.symbolCount)
|
|
12219
12911
|
]);
|
|
12220
12912
|
let output = table(headers, rows);
|
|
@@ -12255,7 +12947,7 @@ function generateTestStatistics(graph) {
|
|
|
12255
12947
|
`;
|
|
12256
12948
|
const dirTestCoverage = /* @__PURE__ */ new Map();
|
|
12257
12949
|
for (const sourceFile of sourceFiles) {
|
|
12258
|
-
const dir =
|
|
12950
|
+
const dir = dirname20(sourceFile).split("/")[0];
|
|
12259
12951
|
if (!dirTestCoverage.has(dir)) {
|
|
12260
12952
|
dirTestCoverage.set(dir, { total: 0, tested: 0 });
|
|
12261
12953
|
}
|
|
@@ -12278,7 +12970,7 @@ function generateTestStatistics(graph) {
|
|
|
12278
12970
|
}
|
|
12279
12971
|
|
|
12280
12972
|
// src/docs/history.ts
|
|
12281
|
-
import { dirname as
|
|
12973
|
+
import { dirname as dirname21 } from "path";
|
|
12282
12974
|
import { execSync } from "child_process";
|
|
12283
12975
|
function generateHistory(graph, projectRoot, version) {
|
|
12284
12976
|
let output = "";
|
|
@@ -12559,7 +13251,7 @@ function generateFeatureClusters(graph) {
|
|
|
12559
13251
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
12560
13252
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
12561
13253
|
graph.forEachNode((node, attrs) => {
|
|
12562
|
-
const dir =
|
|
13254
|
+
const dir = dirname21(attrs.filePath);
|
|
12563
13255
|
if (!dirFiles.has(dir)) {
|
|
12564
13256
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
12565
13257
|
}
|
|
@@ -12641,7 +13333,7 @@ function capitalizeFirst3(str) {
|
|
|
12641
13333
|
}
|
|
12642
13334
|
|
|
12643
13335
|
// src/docs/current.ts
|
|
12644
|
-
import { dirname as
|
|
13336
|
+
import { dirname as dirname22 } from "path";
|
|
12645
13337
|
function generateCurrent(graph, projectRoot, version) {
|
|
12646
13338
|
let output = "";
|
|
12647
13339
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -12779,7 +13471,7 @@ function generateCompleteFileIndex(graph) {
|
|
|
12779
13471
|
fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
12780
13472
|
const dirGroups = /* @__PURE__ */ new Map();
|
|
12781
13473
|
for (const info of fileInfos) {
|
|
12782
|
-
const dir =
|
|
13474
|
+
const dir = dirname22(info.filePath);
|
|
12783
13475
|
const topDir = dir === "." ? "root" : dir.split("/")[0];
|
|
12784
13476
|
if (!dirGroups.has(topDir)) {
|
|
12785
13477
|
dirGroups.set(topDir, []);
|
|
@@ -12990,8 +13682,8 @@ function getTopLevelDir2(filePath) {
|
|
|
12990
13682
|
}
|
|
12991
13683
|
|
|
12992
13684
|
// src/docs/status.ts
|
|
12993
|
-
import { readFileSync as
|
|
12994
|
-
import { resolve as
|
|
13685
|
+
import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
|
|
13686
|
+
import { resolve as resolve14 } from "path";
|
|
12995
13687
|
function generateStatus(graph, projectRoot, version) {
|
|
12996
13688
|
let output = "";
|
|
12997
13689
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -13024,16 +13716,16 @@ function getFileCount11(graph) {
|
|
|
13024
13716
|
}
|
|
13025
13717
|
function extractComments(projectRoot, filePath) {
|
|
13026
13718
|
const comments = [];
|
|
13027
|
-
const resolvedRoot =
|
|
13028
|
-
const fullPath =
|
|
13719
|
+
const resolvedRoot = resolve14(projectRoot);
|
|
13720
|
+
const fullPath = resolve14(resolvedRoot, filePath);
|
|
13029
13721
|
if (!fullPath.startsWith(resolvedRoot)) {
|
|
13030
13722
|
return comments;
|
|
13031
13723
|
}
|
|
13032
|
-
if (!
|
|
13724
|
+
if (!existsSync17(fullPath)) {
|
|
13033
13725
|
return comments;
|
|
13034
13726
|
}
|
|
13035
13727
|
try {
|
|
13036
|
-
const content =
|
|
13728
|
+
const content = readFileSync17(fullPath, "utf-8");
|
|
13037
13729
|
const lines = content.split("\n");
|
|
13038
13730
|
const patterns = [
|
|
13039
13731
|
{ type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
|
|
@@ -13612,16 +14304,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
|
|
|
13612
14304
|
}
|
|
13613
14305
|
|
|
13614
14306
|
// src/docs/metadata.ts
|
|
13615
|
-
import { existsSync as
|
|
13616
|
-
import { resolve as
|
|
14307
|
+
import { existsSync as existsSync18, readFileSync as readFileSync18, writeFileSync as writeFileSync2 } from "fs";
|
|
14308
|
+
import { resolve as resolve15 } from "path";
|
|
13617
14309
|
function loadMetadata(outputDir) {
|
|
13618
|
-
const resolvedDir =
|
|
13619
|
-
const metadataPath =
|
|
13620
|
-
if (!metadataPath.startsWith(resolvedDir) || !
|
|
14310
|
+
const resolvedDir = resolve15(outputDir);
|
|
14311
|
+
const metadataPath = resolve15(resolvedDir, "metadata.json");
|
|
14312
|
+
if (!metadataPath.startsWith(resolvedDir) || !existsSync18(metadataPath)) {
|
|
13621
14313
|
return null;
|
|
13622
14314
|
}
|
|
13623
14315
|
try {
|
|
13624
|
-
const content =
|
|
14316
|
+
const content = readFileSync18(metadataPath, "utf-8");
|
|
13625
14317
|
return JSON.parse(content);
|
|
13626
14318
|
} catch (err) {
|
|
13627
14319
|
console.error("Failed to load metadata:", err);
|
|
@@ -13629,8 +14321,8 @@ function loadMetadata(outputDir) {
|
|
|
13629
14321
|
}
|
|
13630
14322
|
}
|
|
13631
14323
|
function saveMetadata(outputDir, metadata) {
|
|
13632
|
-
const resolvedDir =
|
|
13633
|
-
const metadataPath =
|
|
14324
|
+
const resolvedDir = resolve15(outputDir);
|
|
14325
|
+
const metadataPath = resolve15(resolvedDir, "metadata.json");
|
|
13634
14326
|
if (!metadataPath.startsWith(resolvedDir)) {
|
|
13635
14327
|
throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
|
|
13636
14328
|
}
|
|
@@ -13676,7 +14368,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13676
14368
|
const generated = [];
|
|
13677
14369
|
const errors = [];
|
|
13678
14370
|
try {
|
|
13679
|
-
if (!
|
|
14371
|
+
if (!existsSync19(options.outputDir)) {
|
|
13680
14372
|
mkdirSync2(options.outputDir, { recursive: true });
|
|
13681
14373
|
if (options.verbose) {
|
|
13682
14374
|
console.log(`Created output directory: ${options.outputDir}`);
|
|
@@ -13715,7 +14407,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13715
14407
|
try {
|
|
13716
14408
|
if (options.verbose) console.log("Generating ARCHITECTURE.md...");
|
|
13717
14409
|
const content = generateArchitecture(graph, projectRoot, version, parseTime);
|
|
13718
|
-
const filePath =
|
|
14410
|
+
const filePath = join22(options.outputDir, "ARCHITECTURE.md");
|
|
13719
14411
|
writeFileSync3(filePath, content, "utf-8");
|
|
13720
14412
|
generated.push("ARCHITECTURE.md");
|
|
13721
14413
|
} catch (err) {
|
|
@@ -13726,7 +14418,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13726
14418
|
try {
|
|
13727
14419
|
if (options.verbose) console.log("Generating CONVENTIONS.md...");
|
|
13728
14420
|
const content = generateConventions(graph, projectRoot, version);
|
|
13729
|
-
const filePath =
|
|
14421
|
+
const filePath = join22(options.outputDir, "CONVENTIONS.md");
|
|
13730
14422
|
writeFileSync3(filePath, content, "utf-8");
|
|
13731
14423
|
generated.push("CONVENTIONS.md");
|
|
13732
14424
|
} catch (err) {
|
|
@@ -13737,7 +14429,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13737
14429
|
try {
|
|
13738
14430
|
if (options.verbose) console.log("Generating DEPENDENCIES.md...");
|
|
13739
14431
|
const content = generateDependencies(graph, projectRoot, version);
|
|
13740
|
-
const filePath =
|
|
14432
|
+
const filePath = join22(options.outputDir, "DEPENDENCIES.md");
|
|
13741
14433
|
writeFileSync3(filePath, content, "utf-8");
|
|
13742
14434
|
generated.push("DEPENDENCIES.md");
|
|
13743
14435
|
} catch (err) {
|
|
@@ -13748,7 +14440,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13748
14440
|
try {
|
|
13749
14441
|
if (options.verbose) console.log("Generating ONBOARDING.md...");
|
|
13750
14442
|
const content = generateOnboarding(graph, projectRoot, version);
|
|
13751
|
-
const filePath =
|
|
14443
|
+
const filePath = join22(options.outputDir, "ONBOARDING.md");
|
|
13752
14444
|
writeFileSync3(filePath, content, "utf-8");
|
|
13753
14445
|
generated.push("ONBOARDING.md");
|
|
13754
14446
|
} catch (err) {
|
|
@@ -13759,7 +14451,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13759
14451
|
try {
|
|
13760
14452
|
if (options.verbose) console.log("Generating FILES.md...");
|
|
13761
14453
|
const content = generateFiles(graph, projectRoot, version);
|
|
13762
|
-
const filePath =
|
|
14454
|
+
const filePath = join22(options.outputDir, "FILES.md");
|
|
13763
14455
|
writeFileSync3(filePath, content, "utf-8");
|
|
13764
14456
|
generated.push("FILES.md");
|
|
13765
14457
|
} catch (err) {
|
|
@@ -13770,7 +14462,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13770
14462
|
try {
|
|
13771
14463
|
if (options.verbose) console.log("Generating API_SURFACE.md...");
|
|
13772
14464
|
const content = generateApiSurface(graph, projectRoot, version);
|
|
13773
|
-
const filePath =
|
|
14465
|
+
const filePath = join22(options.outputDir, "API_SURFACE.md");
|
|
13774
14466
|
writeFileSync3(filePath, content, "utf-8");
|
|
13775
14467
|
generated.push("API_SURFACE.md");
|
|
13776
14468
|
} catch (err) {
|
|
@@ -13781,7 +14473,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13781
14473
|
try {
|
|
13782
14474
|
if (options.verbose) console.log("Generating ERRORS.md...");
|
|
13783
14475
|
const content = generateErrors(graph, projectRoot, version);
|
|
13784
|
-
const filePath =
|
|
14476
|
+
const filePath = join22(options.outputDir, "ERRORS.md");
|
|
13785
14477
|
writeFileSync3(filePath, content, "utf-8");
|
|
13786
14478
|
generated.push("ERRORS.md");
|
|
13787
14479
|
} catch (err) {
|
|
@@ -13792,7 +14484,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13792
14484
|
try {
|
|
13793
14485
|
if (options.verbose) console.log("Generating TESTS.md...");
|
|
13794
14486
|
const content = generateTests(graph, projectRoot, version);
|
|
13795
|
-
const filePath =
|
|
14487
|
+
const filePath = join22(options.outputDir, "TESTS.md");
|
|
13796
14488
|
writeFileSync3(filePath, content, "utf-8");
|
|
13797
14489
|
generated.push("TESTS.md");
|
|
13798
14490
|
} catch (err) {
|
|
@@ -13803,7 +14495,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13803
14495
|
try {
|
|
13804
14496
|
if (options.verbose) console.log("Generating HISTORY.md...");
|
|
13805
14497
|
const content = generateHistory(graph, projectRoot, version);
|
|
13806
|
-
const filePath =
|
|
14498
|
+
const filePath = join22(options.outputDir, "HISTORY.md");
|
|
13807
14499
|
writeFileSync3(filePath, content, "utf-8");
|
|
13808
14500
|
generated.push("HISTORY.md");
|
|
13809
14501
|
} catch (err) {
|
|
@@ -13814,7 +14506,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13814
14506
|
try {
|
|
13815
14507
|
if (options.verbose) console.log("Generating CURRENT.md...");
|
|
13816
14508
|
const content = generateCurrent(graph, projectRoot, version);
|
|
13817
|
-
const filePath =
|
|
14509
|
+
const filePath = join22(options.outputDir, "CURRENT.md");
|
|
13818
14510
|
writeFileSync3(filePath, content, "utf-8");
|
|
13819
14511
|
generated.push("CURRENT.md");
|
|
13820
14512
|
} catch (err) {
|
|
@@ -13825,7 +14517,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13825
14517
|
try {
|
|
13826
14518
|
if (options.verbose) console.log("Generating STATUS.md...");
|
|
13827
14519
|
const content = generateStatus(graph, projectRoot, version);
|
|
13828
|
-
const filePath =
|
|
14520
|
+
const filePath = join22(options.outputDir, "STATUS.md");
|
|
13829
14521
|
writeFileSync3(filePath, content, "utf-8");
|
|
13830
14522
|
generated.push("STATUS.md");
|
|
13831
14523
|
} catch (err) {
|
|
@@ -13836,7 +14528,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13836
14528
|
try {
|
|
13837
14529
|
if (options.verbose) console.log("Generating HEALTH.md...");
|
|
13838
14530
|
const content = generateHealth(graph, projectRoot, version);
|
|
13839
|
-
const filePath =
|
|
14531
|
+
const filePath = join22(options.outputDir, "HEALTH.md");
|
|
13840
14532
|
writeFileSync3(filePath, content, "utf-8");
|
|
13841
14533
|
generated.push("HEALTH.md");
|
|
13842
14534
|
} catch (err) {
|
|
@@ -13847,7 +14539,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
13847
14539
|
try {
|
|
13848
14540
|
if (options.verbose) console.log("Generating DEAD_CODE.md...");
|
|
13849
14541
|
const content = generateDeadCode(graph, projectRoot, version);
|
|
13850
|
-
const filePath =
|
|
14542
|
+
const filePath = join22(options.outputDir, "DEAD_CODE.md");
|
|
13851
14543
|
writeFileSync3(filePath, content, "utf-8");
|
|
13852
14544
|
generated.push("DEAD_CODE.md");
|
|
13853
14545
|
} catch (err) {
|
|
@@ -13891,7 +14583,7 @@ function getFileCount13(graph) {
|
|
|
13891
14583
|
}
|
|
13892
14584
|
|
|
13893
14585
|
// src/simulation/engine.ts
|
|
13894
|
-
import { dirname as
|
|
14586
|
+
import { dirname as dirname23, join as join23 } from "path";
|
|
13895
14587
|
function normalizePath2(p) {
|
|
13896
14588
|
return p.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
13897
14589
|
}
|
|
@@ -14023,7 +14715,7 @@ var SimulationEngine = class {
|
|
|
14023
14715
|
}
|
|
14024
14716
|
}
|
|
14025
14717
|
applyRename(clone, target, newName, brokenImports) {
|
|
14026
|
-
const destination =
|
|
14718
|
+
const destination = join23(dirname23(target), newName);
|
|
14027
14719
|
this.applyMove(clone, target, destination, brokenImports);
|
|
14028
14720
|
}
|
|
14029
14721
|
applySplit(clone, target, newFile, symbols, brokenImports) {
|
|
@@ -14223,13 +14915,13 @@ var SimulationEngine = class {
|
|
|
14223
14915
|
};
|
|
14224
14916
|
|
|
14225
14917
|
// src/security/scanner.ts
|
|
14226
|
-
import { existsSync as
|
|
14227
|
-
import { join as
|
|
14918
|
+
import { existsSync as existsSync21 } from "fs";
|
|
14919
|
+
import { join as join33 } from "path";
|
|
14228
14920
|
|
|
14229
14921
|
// src/security/checks/dependencies.ts
|
|
14230
14922
|
import { execSync as execSync2 } from "child_process";
|
|
14231
|
-
import { existsSync as
|
|
14232
|
-
import { join as
|
|
14923
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19, readdirSync as readdirSync12 } from "fs";
|
|
14924
|
+
import { join as join24 } from "path";
|
|
14233
14925
|
function cvssToSeverity(score) {
|
|
14234
14926
|
if (score >= 9) return "critical";
|
|
14235
14927
|
if (score >= 7) return "high";
|
|
@@ -14239,18 +14931,18 @@ function cvssToSeverity(score) {
|
|
|
14239
14931
|
async function checkDependencies(_files, projectRoot) {
|
|
14240
14932
|
const findings = [];
|
|
14241
14933
|
try {
|
|
14242
|
-
if (
|
|
14934
|
+
if (existsSync20(join24(projectRoot, "package.json"))) {
|
|
14243
14935
|
findings.push(...checkNpmAudit(projectRoot));
|
|
14244
14936
|
findings.push(...checkPackageJsonPatterns(projectRoot));
|
|
14245
14937
|
findings.push(...checkPostinstallScripts(projectRoot));
|
|
14246
14938
|
}
|
|
14247
|
-
if (
|
|
14939
|
+
if (existsSync20(join24(projectRoot, "requirements.txt")) || existsSync20(join24(projectRoot, "pyproject.toml"))) {
|
|
14248
14940
|
findings.push(...checkPipAudit(projectRoot));
|
|
14249
14941
|
}
|
|
14250
|
-
if (
|
|
14942
|
+
if (existsSync20(join24(projectRoot, "Cargo.toml"))) {
|
|
14251
14943
|
findings.push(...checkCargoAudit(projectRoot));
|
|
14252
14944
|
}
|
|
14253
|
-
if (
|
|
14945
|
+
if (existsSync20(join24(projectRoot, "go.mod"))) {
|
|
14254
14946
|
findings.push(...checkGoVerify(projectRoot));
|
|
14255
14947
|
}
|
|
14256
14948
|
} catch (err) {
|
|
@@ -14339,8 +15031,8 @@ function checkNpmAudit(projectRoot) {
|
|
|
14339
15031
|
function checkPackageJsonPatterns(projectRoot) {
|
|
14340
15032
|
const findings = [];
|
|
14341
15033
|
try {
|
|
14342
|
-
const pkgPath =
|
|
14343
|
-
const pkg = JSON.parse(
|
|
15034
|
+
const pkgPath = join24(projectRoot, "package.json");
|
|
15035
|
+
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
14344
15036
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
14345
15037
|
for (const [name, version] of Object.entries(allDeps)) {
|
|
14346
15038
|
if (version.startsWith("^") || version.startsWith("~")) {
|
|
@@ -14362,15 +15054,15 @@ function checkPackageJsonPatterns(projectRoot) {
|
|
|
14362
15054
|
}
|
|
14363
15055
|
function checkPostinstallScripts(projectRoot) {
|
|
14364
15056
|
const findings = [];
|
|
14365
|
-
const nodeModules =
|
|
14366
|
-
if (!
|
|
15057
|
+
const nodeModules = join24(projectRoot, "node_modules");
|
|
15058
|
+
if (!existsSync20(nodeModules)) return findings;
|
|
14367
15059
|
try {
|
|
14368
|
-
const topLevelDeps =
|
|
15060
|
+
const topLevelDeps = readdirSync12(nodeModules).filter((d) => !d.startsWith("."));
|
|
14369
15061
|
for (const dep of topLevelDeps) {
|
|
14370
|
-
const depPkgPath =
|
|
14371
|
-
if (!
|
|
15062
|
+
const depPkgPath = join24(nodeModules, dep, "package.json");
|
|
15063
|
+
if (!existsSync20(depPkgPath)) continue;
|
|
14372
15064
|
try {
|
|
14373
|
-
const depPkg = JSON.parse(
|
|
15065
|
+
const depPkg = JSON.parse(readFileSync19(depPkgPath, "utf-8"));
|
|
14374
15066
|
const scripts = depPkg.scripts || {};
|
|
14375
15067
|
if (scripts.postinstall || scripts.preinstall || scripts.install) {
|
|
14376
15068
|
const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
|
|
@@ -14408,7 +15100,7 @@ function checkPipAudit(projectRoot) {
|
|
|
14408
15100
|
id: "",
|
|
14409
15101
|
severity: cvssToSeverity(vuln.cvss?.score || 5),
|
|
14410
15102
|
vulnerabilityClass: "dependency-cve",
|
|
14411
|
-
file:
|
|
15103
|
+
file: existsSync20(join24(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
|
|
14412
15104
|
title: `Vulnerable Python dependency: ${vuln.name}`,
|
|
14413
15105
|
description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
|
|
14414
15106
|
attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
|
|
@@ -14505,8 +15197,8 @@ function checkGoVerify(projectRoot) {
|
|
|
14505
15197
|
}
|
|
14506
15198
|
|
|
14507
15199
|
// src/security/checks/injection.ts
|
|
14508
|
-
import { readFileSync as
|
|
14509
|
-
import { join as
|
|
15200
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
15201
|
+
import { join as join25 } from "path";
|
|
14510
15202
|
var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
14511
15203
|
var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
|
|
14512
15204
|
var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
|
|
@@ -14876,6 +15568,106 @@ var PATTERNS = [
|
|
|
14876
15568
|
description: "SIMD store/load operations without explicit bounds checking \u2014 buffer overflow risk.",
|
|
14877
15569
|
attackScenario: "An attacker could trigger out-of-bounds SIMD operations to corrupt memory.",
|
|
14878
15570
|
suggestedFix: "Validate buffer size against SIMD width before store/load operations."
|
|
15571
|
+
},
|
|
15572
|
+
// Ruby injection patterns
|
|
15573
|
+
{
|
|
15574
|
+
regex: /(?:where|find_by_sql|execute)\s*\(\s*["'][^"']*#\{/,
|
|
15575
|
+
title: "Ruby string interpolation in database query method",
|
|
15576
|
+
vulnClass: "code-injection",
|
|
15577
|
+
baseSeverity: "high",
|
|
15578
|
+
description: "Database query built with string interpolation \u2014 values are not parameterized.",
|
|
15579
|
+
attackScenario: "An attacker could manipulate interpolated values to alter query logic.",
|
|
15580
|
+
suggestedFix: 'Use parameterized queries: Model.where("column = ?", value) or ActiveRecord query interface.'
|
|
15581
|
+
},
|
|
15582
|
+
{
|
|
15583
|
+
regex: /`[^`]*#\{/,
|
|
15584
|
+
title: "Ruby backtick command with string interpolation",
|
|
15585
|
+
vulnClass: "shell-injection",
|
|
15586
|
+
baseSeverity: "high",
|
|
15587
|
+
description: "Backtick command execution with interpolated values \u2014 potential for unintended command execution.",
|
|
15588
|
+
attackScenario: "An attacker could inject shell metacharacters through the interpolated variable.",
|
|
15589
|
+
suggestedFix: "Use Open3.capture3 with separate arguments instead of backtick interpolation."
|
|
15590
|
+
},
|
|
15591
|
+
{
|
|
15592
|
+
regex: /\b(?:system|exec)\s*\(\s*["'][^"']*#\{/,
|
|
15593
|
+
title: "Ruby system/exec with string interpolation",
|
|
15594
|
+
vulnClass: "shell-injection",
|
|
15595
|
+
baseSeverity: "high",
|
|
15596
|
+
description: "system() or exec() called with interpolated string \u2014 potential for unintended command execution.",
|
|
15597
|
+
attackScenario: "An attacker could inject shell metacharacters through the interpolated variable.",
|
|
15598
|
+
suggestedFix: 'Use system() with separate arguments: system("cmd", arg1, arg2) instead of string interpolation.'
|
|
15599
|
+
},
|
|
15600
|
+
{
|
|
15601
|
+
regex: /%x\{[^}]*#\{/,
|
|
15602
|
+
title: "Ruby %x{} command with string interpolation",
|
|
15603
|
+
vulnClass: "shell-injection",
|
|
15604
|
+
baseSeverity: "high",
|
|
15605
|
+
description: "%x{} command execution with interpolated values \u2014 potential for unintended command execution.",
|
|
15606
|
+
attackScenario: "An attacker could inject shell metacharacters through the interpolated variable.",
|
|
15607
|
+
suggestedFix: "Use Open3.capture3 with separate arguments instead of %x{} interpolation."
|
|
15608
|
+
},
|
|
15609
|
+
{
|
|
15610
|
+
regex: /\beval\s*\(\s*(?!['"])/,
|
|
15611
|
+
title: "Ruby eval() with dynamic input",
|
|
15612
|
+
vulnClass: "code-injection",
|
|
15613
|
+
baseSeverity: "high",
|
|
15614
|
+
description: "eval() executes arbitrary Ruby code from a variable \u2014 potential for unintended code execution.",
|
|
15615
|
+
attackScenario: "An attacker could inject malicious code if user input reaches eval().",
|
|
15616
|
+
suggestedFix: "Remove eval() and use safe alternatives (JSON.parse for data, specific parsers for expressions)."
|
|
15617
|
+
},
|
|
15618
|
+
{
|
|
15619
|
+
regex: /\b(?:instance_eval|class_eval)\s*\(\s*(?!['"])/,
|
|
15620
|
+
title: "Ruby instance_eval/class_eval with dynamic input",
|
|
15621
|
+
vulnClass: "code-injection",
|
|
15622
|
+
baseSeverity: "high",
|
|
15623
|
+
description: "instance_eval/class_eval executes code in object context \u2014 dangerous with dynamic input.",
|
|
15624
|
+
attackScenario: "An attacker could inject code that executes with elevated privileges in the object context.",
|
|
15625
|
+
suggestedFix: "Use instance_exec with a block instead of string evaluation."
|
|
15626
|
+
},
|
|
15627
|
+
{
|
|
15628
|
+
regex: /\b(?:send|public_send)\s*\(\s*(?:params|request|input|user)/i,
|
|
15629
|
+
title: "Ruby send/public_send with user-controlled method name",
|
|
15630
|
+
vulnClass: "code-injection",
|
|
15631
|
+
baseSeverity: "high",
|
|
15632
|
+
description: "send() called with user-controlled method name \u2014 could invoke unintended methods.",
|
|
15633
|
+
attackScenario: "An attacker could call arbitrary methods on the receiver by controlling the method name.",
|
|
15634
|
+
suggestedFix: "Validate the method name against a strict allowlist before passing to send()."
|
|
15635
|
+
},
|
|
15636
|
+
{
|
|
15637
|
+
regex: /File\.(?:read|write|delete|open)\s*\(\s*(?:params|request|input|user)/i,
|
|
15638
|
+
title: "Ruby file operation with user-controlled path",
|
|
15639
|
+
vulnClass: "code-injection",
|
|
15640
|
+
baseSeverity: "high",
|
|
15641
|
+
description: "File operation with user-controlled path \u2014 potential for unintended file access.",
|
|
15642
|
+
attackScenario: "An attacker could traverse directories to access or modify arbitrary files.",
|
|
15643
|
+
suggestedFix: "Validate and sanitize file paths. Use File.expand_path and check against an allowed directory."
|
|
15644
|
+
},
|
|
15645
|
+
{
|
|
15646
|
+
regex: /YAML\.load\s*\(\s*(?!.*safe)/i,
|
|
15647
|
+
title: "Ruby YAML.load with potentially unsafe input",
|
|
15648
|
+
vulnClass: "code-injection",
|
|
15649
|
+
baseSeverity: "high",
|
|
15650
|
+
description: "YAML.load can instantiate arbitrary Ruby objects \u2014 potential for unintended code execution.",
|
|
15651
|
+
attackScenario: "An attacker could craft a YAML payload that instantiates dangerous objects during deserialization.",
|
|
15652
|
+
suggestedFix: "Use YAML.safe_load instead of YAML.load to restrict allowed classes."
|
|
15653
|
+
},
|
|
15654
|
+
{
|
|
15655
|
+
regex: /Marshal\.load\s*\(/,
|
|
15656
|
+
title: "Ruby Marshal.load with potentially unsafe data",
|
|
15657
|
+
vulnClass: "code-injection",
|
|
15658
|
+
baseSeverity: "high",
|
|
15659
|
+
description: "Marshal.load deserializes arbitrary Ruby objects \u2014 potential for unintended code execution.",
|
|
15660
|
+
attackScenario: "An attacker could craft a marshaled payload that executes code during deserialization.",
|
|
15661
|
+
suggestedFix: "Use JSON.parse or MessagePack for data exchange. Never Marshal.load untrusted data."
|
|
15662
|
+
},
|
|
15663
|
+
{
|
|
15664
|
+
regex: /ERB\.new\s*\(\s*(?:params|request|input|user)/i,
|
|
15665
|
+
title: "Ruby ERB template with user-controlled input",
|
|
15666
|
+
vulnClass: "code-injection",
|
|
15667
|
+
baseSeverity: "high",
|
|
15668
|
+
description: "ERB template created from user input \u2014 potential for unintended code execution via template injection.",
|
|
15669
|
+
attackScenario: "An attacker could inject ERB tags to execute arbitrary Ruby code on the server.",
|
|
15670
|
+
suggestedFix: "Never pass user input directly to ERB.new. Use parameterized templates with safe escaping."
|
|
14879
15671
|
}
|
|
14880
15672
|
];
|
|
14881
15673
|
function shouldSkip(filePath) {
|
|
@@ -14892,7 +15684,7 @@ async function checkInjection(files, projectRoot) {
|
|
|
14892
15684
|
if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
|
|
14893
15685
|
let content;
|
|
14894
15686
|
try {
|
|
14895
|
-
content =
|
|
15687
|
+
content = readFileSync20(join25(projectRoot, file.filePath), "utf-8");
|
|
14896
15688
|
} catch {
|
|
14897
15689
|
continue;
|
|
14898
15690
|
}
|
|
@@ -14930,8 +15722,8 @@ async function checkInjection(files, projectRoot) {
|
|
|
14930
15722
|
}
|
|
14931
15723
|
|
|
14932
15724
|
// src/security/checks/secrets.ts
|
|
14933
|
-
import { readFileSync as
|
|
14934
|
-
import { join as
|
|
15725
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
15726
|
+
import { join as join26 } from "path";
|
|
14935
15727
|
var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
14936
15728
|
var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
|
|
14937
15729
|
var SECRET_PATTERNS = [
|
|
@@ -14964,7 +15756,7 @@ async function checkSecrets(files, projectRoot) {
|
|
|
14964
15756
|
if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
|
|
14965
15757
|
let content;
|
|
14966
15758
|
try {
|
|
14967
|
-
content =
|
|
15759
|
+
content = readFileSync21(join26(projectRoot, file.filePath), "utf-8");
|
|
14968
15760
|
} catch {
|
|
14969
15761
|
continue;
|
|
14970
15762
|
}
|
|
@@ -14997,8 +15789,8 @@ async function checkSecrets(files, projectRoot) {
|
|
|
14997
15789
|
}
|
|
14998
15790
|
|
|
14999
15791
|
// src/security/checks/path-traversal.ts
|
|
15000
|
-
import { readFileSync as
|
|
15001
|
-
import { join as
|
|
15792
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
15793
|
+
import { join as join27 } from "path";
|
|
15002
15794
|
var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
15003
15795
|
var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
|
|
15004
15796
|
var PATTERNS2 = [
|
|
@@ -15045,7 +15837,7 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
15045
15837
|
if (shouldSkip3(file.filePath)) continue;
|
|
15046
15838
|
let content;
|
|
15047
15839
|
try {
|
|
15048
|
-
content =
|
|
15840
|
+
content = readFileSync22(join27(projectRoot, file.filePath), "utf-8");
|
|
15049
15841
|
} catch {
|
|
15050
15842
|
continue;
|
|
15051
15843
|
}
|
|
@@ -15087,8 +15879,8 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
15087
15879
|
}
|
|
15088
15880
|
|
|
15089
15881
|
// src/security/checks/auth.ts
|
|
15090
|
-
import { readFileSync as
|
|
15091
|
-
import { join as
|
|
15882
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
15883
|
+
import { join as join28 } from "path";
|
|
15092
15884
|
var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
15093
15885
|
function shouldSkip4(filePath) {
|
|
15094
15886
|
return SKIP_DIRS4.some((d) => filePath.includes(d));
|
|
@@ -15104,7 +15896,7 @@ async function checkAuth(files, projectRoot) {
|
|
|
15104
15896
|
if (shouldSkip4(file.filePath)) continue;
|
|
15105
15897
|
let content;
|
|
15106
15898
|
try {
|
|
15107
|
-
content =
|
|
15899
|
+
content = readFileSync23(join28(projectRoot, file.filePath), "utf-8");
|
|
15108
15900
|
} catch {
|
|
15109
15901
|
continue;
|
|
15110
15902
|
}
|
|
@@ -15195,8 +15987,8 @@ async function checkAuth(files, projectRoot) {
|
|
|
15195
15987
|
}
|
|
15196
15988
|
|
|
15197
15989
|
// src/security/checks/input-validation.ts
|
|
15198
|
-
import { readFileSync as
|
|
15199
|
-
import { join as
|
|
15990
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
15991
|
+
import { join as join29 } from "path";
|
|
15200
15992
|
var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
15201
15993
|
function shouldSkip5(filePath) {
|
|
15202
15994
|
return SKIP_DIRS5.some((d) => filePath.includes(d));
|
|
@@ -15208,7 +16000,7 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
15208
16000
|
if (shouldSkip5(file.filePath)) continue;
|
|
15209
16001
|
let content;
|
|
15210
16002
|
try {
|
|
15211
|
-
content =
|
|
16003
|
+
content = readFileSync24(join29(projectRoot, file.filePath), "utf-8");
|
|
15212
16004
|
} catch {
|
|
15213
16005
|
continue;
|
|
15214
16006
|
}
|
|
@@ -15285,8 +16077,8 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
15285
16077
|
}
|
|
15286
16078
|
|
|
15287
16079
|
// src/security/checks/information-disclosure.ts
|
|
15288
|
-
import { readFileSync as
|
|
15289
|
-
import { join as
|
|
16080
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
16081
|
+
import { join as join30 } from "path";
|
|
15290
16082
|
var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
15291
16083
|
function shouldSkip6(filePath) {
|
|
15292
16084
|
return SKIP_DIRS6.some((d) => filePath.includes(d));
|
|
@@ -15298,7 +16090,7 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
15298
16090
|
if (shouldSkip6(file.filePath)) continue;
|
|
15299
16091
|
let content;
|
|
15300
16092
|
try {
|
|
15301
|
-
content =
|
|
16093
|
+
content = readFileSync25(join30(projectRoot, file.filePath), "utf-8");
|
|
15302
16094
|
} catch {
|
|
15303
16095
|
continue;
|
|
15304
16096
|
}
|
|
@@ -15368,8 +16160,8 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
15368
16160
|
}
|
|
15369
16161
|
|
|
15370
16162
|
// src/security/checks/cryptography.ts
|
|
15371
|
-
import { readFileSync as
|
|
15372
|
-
import { join as
|
|
16163
|
+
import { readFileSync as readFileSync26 } from "fs";
|
|
16164
|
+
import { join as join31 } from "path";
|
|
15373
16165
|
var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
15374
16166
|
var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
|
|
15375
16167
|
function shouldSkip7(filePath) {
|
|
@@ -15386,7 +16178,7 @@ async function checkCryptography(files, projectRoot) {
|
|
|
15386
16178
|
if (shouldSkip7(file.filePath)) continue;
|
|
15387
16179
|
let content;
|
|
15388
16180
|
try {
|
|
15389
|
-
content =
|
|
16181
|
+
content = readFileSync26(join31(projectRoot, file.filePath), "utf-8");
|
|
15390
16182
|
} catch {
|
|
15391
16183
|
continue;
|
|
15392
16184
|
}
|
|
@@ -15763,6 +16555,86 @@ async function checkCryptography(files, projectRoot) {
|
|
|
15763
16555
|
});
|
|
15764
16556
|
}
|
|
15765
16557
|
}
|
|
16558
|
+
if (/Digest::MD5/.test(line)) {
|
|
16559
|
+
findings.push({
|
|
16560
|
+
id: "",
|
|
16561
|
+
severity: isCryptoFile ? "high" : "medium",
|
|
16562
|
+
vulnerabilityClass: "cryptography",
|
|
16563
|
+
file: file.filePath,
|
|
16564
|
+
line: i + 1,
|
|
16565
|
+
title: "Weak hash algorithm: MD5 in Ruby",
|
|
16566
|
+
description: "MD5 is cryptographically broken \u2014 collisions can be generated in seconds.",
|
|
16567
|
+
attackScenario: "An attacker could generate MD5 collisions to bypass integrity checks or forge hashes.",
|
|
16568
|
+
suggestedFix: "Use Digest::SHA256 or bcrypt for password hashing."
|
|
16569
|
+
});
|
|
16570
|
+
}
|
|
16571
|
+
if (/Digest::SHA1/.test(line)) {
|
|
16572
|
+
findings.push({
|
|
16573
|
+
id: "",
|
|
16574
|
+
severity: isCryptoFile ? "high" : "medium",
|
|
16575
|
+
vulnerabilityClass: "cryptography",
|
|
16576
|
+
file: file.filePath,
|
|
16577
|
+
line: i + 1,
|
|
16578
|
+
title: "Weak hash algorithm: SHA-1 in Ruby",
|
|
16579
|
+
description: "SHA-1 has known collision attacks \u2014 should not be used for security purposes.",
|
|
16580
|
+
attackScenario: "An attacker could generate SHA-1 collisions to bypass integrity checks.",
|
|
16581
|
+
suggestedFix: "Use Digest::SHA256 or stronger for integrity checks."
|
|
16582
|
+
});
|
|
16583
|
+
}
|
|
16584
|
+
if (/\brand\s*\(/.test(line) && isCryptoFile) {
|
|
16585
|
+
findings.push({
|
|
16586
|
+
id: "",
|
|
16587
|
+
severity: "medium",
|
|
16588
|
+
vulnerabilityClass: "cryptography",
|
|
16589
|
+
file: file.filePath,
|
|
16590
|
+
line: i + 1,
|
|
16591
|
+
title: "Weak random: rand() in Ruby security context",
|
|
16592
|
+
description: "rand() is not cryptographically secure \u2014 its output can be predicted.",
|
|
16593
|
+
attackScenario: "An attacker could predict rand() values to forge tokens or bypass security checks.",
|
|
16594
|
+
suggestedFix: "Use SecureRandom.hex, SecureRandom.uuid, or SecureRandom.random_bytes for cryptographic purposes."
|
|
16595
|
+
});
|
|
16596
|
+
}
|
|
16597
|
+
if (/(?:password|secret|api_key|token)\s*=\s*['"][^'"]{4,}['"]/i.test(line)) {
|
|
16598
|
+
if (file.filePath.endsWith(".rb")) {
|
|
16599
|
+
findings.push({
|
|
16600
|
+
id: "",
|
|
16601
|
+
severity: "high",
|
|
16602
|
+
vulnerabilityClass: "cryptography",
|
|
16603
|
+
file: file.filePath,
|
|
16604
|
+
line: i + 1,
|
|
16605
|
+
title: "Hardcoded credentials in Ruby source",
|
|
16606
|
+
description: "A password, secret, or API key is hardcoded as a string literal.",
|
|
16607
|
+
attackScenario: "An attacker with access to the source could extract the credential.",
|
|
16608
|
+
suggestedFix: 'Load credentials from environment variables using ENV["KEY"] or Rails credentials.'
|
|
16609
|
+
});
|
|
16610
|
+
}
|
|
16611
|
+
}
|
|
16612
|
+
if (/verify_mode\s*=\s*OpenSSL::SSL::VERIFY_NONE/.test(line)) {
|
|
16613
|
+
findings.push({
|
|
16614
|
+
id: "",
|
|
16615
|
+
severity: "high",
|
|
16616
|
+
vulnerabilityClass: "cryptography",
|
|
16617
|
+
file: file.filePath,
|
|
16618
|
+
line: i + 1,
|
|
16619
|
+
title: "Ruby SSL verification disabled",
|
|
16620
|
+
description: "SSL certificate verification is disabled \u2014 connections are not authenticated.",
|
|
16621
|
+
attackScenario: "An attacker on the network could intercept and modify traffic without detection.",
|
|
16622
|
+
suggestedFix: "Use OpenSSL::SSL::VERIFY_PEER to verify server certificates."
|
|
16623
|
+
});
|
|
16624
|
+
}
|
|
16625
|
+
if (/OpenSSL::Cipher\s*\.\s*new\s*\(\s*['"](?:DES|RC4)/i.test(line)) {
|
|
16626
|
+
findings.push({
|
|
16627
|
+
id: "",
|
|
16628
|
+
severity: "high",
|
|
16629
|
+
vulnerabilityClass: "cryptography",
|
|
16630
|
+
file: file.filePath,
|
|
16631
|
+
line: i + 1,
|
|
16632
|
+
title: "Weak cipher algorithm in Ruby",
|
|
16633
|
+
description: "DES/RC4 ciphers are cryptographically weak and can be broken with modern hardware.",
|
|
16634
|
+
attackScenario: "An attacker could brute-force or exploit weaknesses in DES/RC4 to decrypt data.",
|
|
16635
|
+
suggestedFix: 'Use OpenSSL::Cipher.new("aes-256-gcm") for authenticated encryption.'
|
|
16636
|
+
});
|
|
16637
|
+
}
|
|
15766
16638
|
}
|
|
15767
16639
|
}
|
|
15768
16640
|
} catch {
|
|
@@ -15771,8 +16643,8 @@ async function checkCryptography(files, projectRoot) {
|
|
|
15771
16643
|
}
|
|
15772
16644
|
|
|
15773
16645
|
// src/security/checks/frontend.ts
|
|
15774
|
-
import { readFileSync as
|
|
15775
|
-
import { join as
|
|
16646
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
16647
|
+
import { join as join32 } from "path";
|
|
15776
16648
|
var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
15777
16649
|
function shouldSkip8(filePath) {
|
|
15778
16650
|
return SKIP_DIRS8.some((d) => filePath.includes(d));
|
|
@@ -15788,7 +16660,7 @@ async function checkFrontend(files, projectRoot) {
|
|
|
15788
16660
|
if (!isFrontendFile(file.filePath)) continue;
|
|
15789
16661
|
let content;
|
|
15790
16662
|
try {
|
|
15791
|
-
content =
|
|
16663
|
+
content = readFileSync27(join32(projectRoot, file.filePath), "utf-8");
|
|
15792
16664
|
} catch {
|
|
15793
16665
|
continue;
|
|
15794
16666
|
}
|
|
@@ -16248,13 +17120,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
|
|
|
16248
17120
|
};
|
|
16249
17121
|
}
|
|
16250
17122
|
function detectPackageManager(projectRoot) {
|
|
16251
|
-
if (
|
|
16252
|
-
if (
|
|
16253
|
-
if (
|
|
16254
|
-
if (
|
|
16255
|
-
if (
|
|
16256
|
-
if (
|
|
16257
|
-
if (
|
|
17123
|
+
if (existsSync21(join33(projectRoot, "package.json"))) return "npm";
|
|
17124
|
+
if (existsSync21(join33(projectRoot, "requirements.txt"))) return "pip";
|
|
17125
|
+
if (existsSync21(join33(projectRoot, "pyproject.toml"))) return "pip";
|
|
17126
|
+
if (existsSync21(join33(projectRoot, "Cargo.toml"))) return "cargo";
|
|
17127
|
+
if (existsSync21(join33(projectRoot, "go.mod"))) return "go";
|
|
17128
|
+
if (existsSync21(join33(projectRoot, "pom.xml"))) return "maven";
|
|
17129
|
+
if (existsSync21(join33(projectRoot, "build.gradle")) || existsSync21(join33(projectRoot, "build.gradle.kts"))) return "gradle";
|
|
16258
17130
|
return "unknown";
|
|
16259
17131
|
}
|
|
16260
17132
|
|