truecourse 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/cli.mjs +8 -6
- package/db/migrations/0011_noisy_colleen_wing.sql +2 -0
- package/db/migrations/meta/0011_snapshot.json +2075 -0
- package/db/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/public/assets/index-B9lyLwLS.css +1 -0
- package/public/assets/{index-HTLMOO-T.js → index-DmLWAe9c.js} +85 -85
- package/public/index.html +2 -2
- package/server.mjs +524 -141
- package/public/assets/index-Dkc0tzWP.css +0 -1
package/server.mjs
CHANGED
|
@@ -31191,6 +31191,8 @@ var init_schema2 = __esm({
|
|
|
31191
31191
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
31192
31192
|
repoId: uuid("repo_id").notNull().references(() => repos.id, { onDelete: "cascade" }),
|
|
31193
31193
|
branch: text("branch"),
|
|
31194
|
+
status: text("status").notNull().default("completed"),
|
|
31195
|
+
// 'running' | 'cancelling' | 'completed' | 'cancelled' | 'failed'
|
|
31194
31196
|
architecture: text("architecture").notNull(),
|
|
31195
31197
|
// 'monolith' | 'microservices'
|
|
31196
31198
|
metadata: jsonb("metadata"),
|
|
@@ -48989,6 +48991,15 @@ function getAllIndexBaseNames() {
|
|
|
48989
48991
|
}
|
|
48990
48992
|
return names;
|
|
48991
48993
|
}
|
|
48994
|
+
function getMaxParameters(filePath) {
|
|
48995
|
+
const lang = detectLanguage(filePath);
|
|
48996
|
+
if (lang) {
|
|
48997
|
+
const config3 = LANGUAGE_CONFIGS.find((c) => c.name === lang);
|
|
48998
|
+
if (config3)
|
|
48999
|
+
return config3.thresholds.maxParameters;
|
|
49000
|
+
}
|
|
49001
|
+
return 5;
|
|
49002
|
+
}
|
|
48992
49003
|
function isBootstrapEntry(functionName, filePath) {
|
|
48993
49004
|
return LANGUAGE_CONFIGS.some((c) => c.bootstrap.functionNames.includes(functionName) && c.bootstrap.filePattern.test(filePath));
|
|
48994
49005
|
}
|
|
@@ -49063,6 +49074,9 @@ var init_language_config = __esm({
|
|
|
49063
49074
|
filePattern: /(?:^|[/\\])(?:app|main|index|server)\.\w+$/,
|
|
49064
49075
|
functionNames: ["start", "main", "bootstrap"]
|
|
49065
49076
|
},
|
|
49077
|
+
thresholds: {
|
|
49078
|
+
maxParameters: 5
|
|
49079
|
+
},
|
|
49066
49080
|
exportQuery: `
|
|
49067
49081
|
(export_statement) @export
|
|
49068
49082
|
`
|
|
@@ -49129,6 +49143,10 @@ var init_language_config = __esm({
|
|
|
49129
49143
|
filePattern: /(?:^|[/\\])(?:app|main|wsgi|asgi)\.\w+$/,
|
|
49130
49144
|
functionNames: ["start", "main", "__main__", "bootstrap"]
|
|
49131
49145
|
},
|
|
49146
|
+
thresholds: {
|
|
49147
|
+
maxParameters: 7
|
|
49148
|
+
// Python uses keyword args extensively
|
|
49149
|
+
},
|
|
49132
49150
|
importQuery: `
|
|
49133
49151
|
(import_statement) @import
|
|
49134
49152
|
(import_from_statement) @import
|
|
@@ -264143,6 +264161,15 @@ function isNestedInFunction2(node) {
|
|
|
264143
264161
|
}
|
|
264144
264162
|
return false;
|
|
264145
264163
|
}
|
|
264164
|
+
function isInsideClass(node) {
|
|
264165
|
+
let current = node.parent;
|
|
264166
|
+
while (current) {
|
|
264167
|
+
if (current.type === "class_definition")
|
|
264168
|
+
return true;
|
|
264169
|
+
current = current.parent;
|
|
264170
|
+
}
|
|
264171
|
+
return false;
|
|
264172
|
+
}
|
|
264146
264173
|
function extractDocstring(node) {
|
|
264147
264174
|
const body = node.childForFieldName("body");
|
|
264148
264175
|
if (!body)
|
|
@@ -264271,20 +264298,9 @@ function extractPythonParameters(paramsNode) {
|
|
|
264271
264298
|
});
|
|
264272
264299
|
break;
|
|
264273
264300
|
}
|
|
264274
|
-
case "list_splat_pattern":
|
|
264275
|
-
|
|
264276
|
-
if (name21) {
|
|
264277
|
-
parameters.push({ name: `*${name21}` });
|
|
264278
|
-
}
|
|
264279
|
-
break;
|
|
264280
|
-
}
|
|
264281
|
-
case "dictionary_splat_pattern": {
|
|
264282
|
-
const name21 = child.namedChildren[0]?.text;
|
|
264283
|
-
if (name21) {
|
|
264284
|
-
parameters.push({ name: `**${name21}` });
|
|
264285
|
-
}
|
|
264301
|
+
case "list_splat_pattern":
|
|
264302
|
+
case "dictionary_splat_pattern":
|
|
264286
264303
|
break;
|
|
264287
|
-
}
|
|
264288
264304
|
}
|
|
264289
264305
|
}
|
|
264290
264306
|
return parameters;
|
|
@@ -264298,6 +264314,8 @@ function extractPythonClasses(tree, filePath) {
|
|
|
264298
264314
|
if (seen.has(key))
|
|
264299
264315
|
return;
|
|
264300
264316
|
seen.add(key);
|
|
264317
|
+
if (isInsideClass(node))
|
|
264318
|
+
return;
|
|
264301
264319
|
const name21 = node.childForFieldName("name")?.text;
|
|
264302
264320
|
if (!name21)
|
|
264303
264321
|
return;
|
|
@@ -264395,11 +264413,12 @@ function extractClassProperties(body) {
|
|
|
264395
264413
|
continue;
|
|
264396
264414
|
if (expr.type === "assignment") {
|
|
264397
264415
|
const left = expr.childForFieldName("left");
|
|
264416
|
+
const typeNode = expr.childForFieldName("type");
|
|
264398
264417
|
const right = expr.childForFieldName("right");
|
|
264399
264418
|
if (left && left.type === "identifier") {
|
|
264400
264419
|
properties.push({
|
|
264401
264420
|
name: left.text,
|
|
264402
|
-
type: right?.text
|
|
264421
|
+
type: typeNode?.text || right?.text
|
|
264403
264422
|
});
|
|
264404
264423
|
}
|
|
264405
264424
|
} else if (expr.type === "type_alias_statement" || expr.type === "annotated_assignment") {
|
|
@@ -264553,6 +264572,21 @@ function extractPythonExports(tree, filePath) {
|
|
|
264553
264572
|
}
|
|
264554
264573
|
return exports2;
|
|
264555
264574
|
}
|
|
264575
|
+
const classesWithInstances = /* @__PURE__ */ new Set();
|
|
264576
|
+
for (const child of root.namedChildren) {
|
|
264577
|
+
if (child.type === "expression_statement") {
|
|
264578
|
+
const expr = child.namedChildren[0];
|
|
264579
|
+
if (expr?.type === "assignment") {
|
|
264580
|
+
const right = expr.childForFieldName("right");
|
|
264581
|
+
if (right?.type === "call") {
|
|
264582
|
+
const fn = right.childForFieldName("function");
|
|
264583
|
+
if (fn?.type === "identifier") {
|
|
264584
|
+
classesWithInstances.add(fn.text);
|
|
264585
|
+
}
|
|
264586
|
+
}
|
|
264587
|
+
}
|
|
264588
|
+
}
|
|
264589
|
+
}
|
|
264556
264590
|
for (const child of root.namedChildren) {
|
|
264557
264591
|
if (child.type === "function_definition") {
|
|
264558
264592
|
const name21 = child.childForFieldName("name")?.text;
|
|
@@ -264570,7 +264604,7 @@ function extractPythonExports(tree, filePath) {
|
|
|
264570
264604
|
}
|
|
264571
264605
|
} else if (child.type === "class_definition") {
|
|
264572
264606
|
const name21 = child.childForFieldName("name")?.text;
|
|
264573
|
-
if (name21 && !name21.startsWith("_")) {
|
|
264607
|
+
if (name21 && !name21.startsWith("_") && !classesWithInstances.has(name21)) {
|
|
264574
264608
|
exports2.push({ name: name21, isDefault: false });
|
|
264575
264609
|
}
|
|
264576
264610
|
} else if (child.type === "expression_statement") {
|
|
@@ -267317,6 +267351,10 @@ function hasAPILayerPatterns(analysis) {
|
|
|
267317
267351
|
const reasons = [];
|
|
267318
267352
|
for (const imp of analysis.imports) {
|
|
267319
267353
|
if (apiLayerPatterns.frameworks.some((fw) => matchesPattern(imp.source, fw))) {
|
|
267354
|
+
const importedNames = imp.specifiers.map((s) => s.name);
|
|
267355
|
+
if (importedNames.length > 0 && importedNames.every((n) => FRAMEWORK_UTILITY_IMPORTS.has(n))) {
|
|
267356
|
+
continue;
|
|
267357
|
+
}
|
|
267320
267358
|
reasons.push(`Imports API framework: ${imp.source}`);
|
|
267321
267359
|
return { match: true, reasons };
|
|
267322
267360
|
}
|
|
@@ -267333,8 +267371,12 @@ function hasAPILayerPatterns(analysis) {
|
|
|
267333
267371
|
return { match: true, reasons };
|
|
267334
267372
|
}
|
|
267335
267373
|
}
|
|
267374
|
+
const broadDirPatterns = /* @__PURE__ */ new Set(["**/api/**", "**/views/**"]);
|
|
267375
|
+
const hasRoutes = (analysis.routeRegistrations?.length ?? 0) > 0;
|
|
267336
267376
|
for (const pattern of apiLayerPatterns.filePatterns) {
|
|
267337
267377
|
if (minimatch(analysis.filePath, pattern)) {
|
|
267378
|
+
if (broadDirPatterns.has(pattern) && !hasRoutes)
|
|
267379
|
+
continue;
|
|
267338
267380
|
reasons.push(`File path matches API layer pattern: ${pattern}`);
|
|
267339
267381
|
return { match: true, reasons };
|
|
267340
267382
|
}
|
|
@@ -267394,12 +267436,32 @@ function calculateConfidence(layers2, reasons) {
|
|
|
267394
267436
|
}
|
|
267395
267437
|
return 0.7;
|
|
267396
267438
|
}
|
|
267439
|
+
var FRAMEWORK_UTILITY_IMPORTS;
|
|
267397
267440
|
var init_layer_detector = __esm({
|
|
267398
267441
|
"packages/analyzer/dist/layer-detector.js"() {
|
|
267399
267442
|
"use strict";
|
|
267400
267443
|
init_layer_patterns();
|
|
267401
267444
|
init_patterns();
|
|
267402
267445
|
init_esm3();
|
|
267446
|
+
FRAMEWORK_UTILITY_IMPORTS = /* @__PURE__ */ new Set([
|
|
267447
|
+
"HTTPException",
|
|
267448
|
+
"Depends",
|
|
267449
|
+
"Header",
|
|
267450
|
+
"Body",
|
|
267451
|
+
"Query",
|
|
267452
|
+
"Path",
|
|
267453
|
+
"Form",
|
|
267454
|
+
"File",
|
|
267455
|
+
"UploadFile",
|
|
267456
|
+
"status",
|
|
267457
|
+
"Response",
|
|
267458
|
+
"JSONResponse",
|
|
267459
|
+
"BackgroundTasks",
|
|
267460
|
+
"Security",
|
|
267461
|
+
"Cookie",
|
|
267462
|
+
"Request",
|
|
267463
|
+
"WebSocket"
|
|
267464
|
+
]);
|
|
267403
267465
|
}
|
|
267404
267466
|
});
|
|
267405
267467
|
|
|
@@ -267597,17 +267659,32 @@ function detectByEntryPoints(_rootPath, allFiles) {
|
|
|
267597
267659
|
if (entryPoints.length <= 1) {
|
|
267598
267660
|
return [];
|
|
267599
267661
|
}
|
|
267600
|
-
const
|
|
267601
|
-
|
|
267602
|
-
|
|
267603
|
-
|
|
267662
|
+
const entryDirs = entryPoints.map((e) => dirname4(e)).sort((a, b) => a.length - b.length);
|
|
267663
|
+
const topLevelDirs = [];
|
|
267664
|
+
for (const dir of entryDirs) {
|
|
267665
|
+
const isNested = topLevelDirs.some((parent) => dir.startsWith(parent + "/"));
|
|
267666
|
+
if (!isNested) {
|
|
267667
|
+
topLevelDirs.push(dir);
|
|
267668
|
+
}
|
|
267669
|
+
}
|
|
267670
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
267671
|
+
for (const dir of topLevelDirs) {
|
|
267672
|
+
const dirName = basename2(dir);
|
|
267673
|
+
nameCount.set(dirName, (nameCount.get(dirName) || 0) + 1);
|
|
267604
267674
|
}
|
|
267605
267675
|
const services2 = [];
|
|
267606
|
-
for (const
|
|
267676
|
+
for (const dir of topLevelDirs) {
|
|
267677
|
+
const files = allFiles.filter((f) => f.startsWith(dir));
|
|
267678
|
+
if (files.length === 0)
|
|
267679
|
+
continue;
|
|
267607
267680
|
const dirName = basename2(dir);
|
|
267608
267681
|
const isSrcDir = patterns.commonSourceDirectories.includes(dirName);
|
|
267609
267682
|
const serviceRoot = isSrcDir ? dirname4(dir) : dir;
|
|
267610
|
-
|
|
267683
|
+
let name21 = isSrcDir ? basename2(dirname4(dir)) : dirName;
|
|
267684
|
+
if ((nameCount.get(dirName) || 0) > 1) {
|
|
267685
|
+
const parentName = basename2(dirname4(dir));
|
|
267686
|
+
name21 = `${parentName}-${dirName}`;
|
|
267687
|
+
}
|
|
267611
267688
|
services2.push({
|
|
267612
267689
|
name: name21,
|
|
267613
267690
|
rootPath: dir,
|
|
@@ -267977,7 +268054,42 @@ var init_drizzle = __esm({
|
|
|
267977
268054
|
function parseSqlAlchemySchema(content) {
|
|
267978
268055
|
const tables = [];
|
|
267979
268056
|
const relations2 = [];
|
|
267980
|
-
const
|
|
268057
|
+
const rawLines = content.split("\n");
|
|
268058
|
+
const lines = [];
|
|
268059
|
+
let pending = "";
|
|
268060
|
+
let parenDepth = 0;
|
|
268061
|
+
for (const raw of rawLines) {
|
|
268062
|
+
if (parenDepth > 0) {
|
|
268063
|
+
pending += " " + raw.trim();
|
|
268064
|
+
for (const ch of raw) {
|
|
268065
|
+
if (ch === "(")
|
|
268066
|
+
parenDepth++;
|
|
268067
|
+
else if (ch === ")")
|
|
268068
|
+
parenDepth--;
|
|
268069
|
+
}
|
|
268070
|
+
if (parenDepth <= 0) {
|
|
268071
|
+
lines.push(pending);
|
|
268072
|
+
pending = "";
|
|
268073
|
+
parenDepth = 0;
|
|
268074
|
+
}
|
|
268075
|
+
} else {
|
|
268076
|
+
let depth = 0;
|
|
268077
|
+
for (const ch of raw) {
|
|
268078
|
+
if (ch === "(")
|
|
268079
|
+
depth++;
|
|
268080
|
+
else if (ch === ")")
|
|
268081
|
+
depth--;
|
|
268082
|
+
}
|
|
268083
|
+
if (depth > 0) {
|
|
268084
|
+
pending = raw;
|
|
268085
|
+
parenDepth = depth;
|
|
268086
|
+
} else {
|
|
268087
|
+
lines.push(raw);
|
|
268088
|
+
}
|
|
268089
|
+
}
|
|
268090
|
+
}
|
|
268091
|
+
if (pending)
|
|
268092
|
+
lines.push(pending);
|
|
267981
268093
|
const classToTable = /* @__PURE__ */ new Map();
|
|
267982
268094
|
{
|
|
267983
268095
|
let cls = null;
|
|
@@ -268047,13 +268159,38 @@ function parseSqlAlchemySchema(content) {
|
|
|
268047
268159
|
continue;
|
|
268048
268160
|
}
|
|
268049
268161
|
const columnMatch = trimmed2.match(/^(\w+)\s*=\s*Column\((.+)\)/);
|
|
268050
|
-
|
|
268051
|
-
|
|
268052
|
-
const
|
|
268053
|
-
|
|
268162
|
+
let mappedColMatch = null;
|
|
268163
|
+
if (!columnMatch) {
|
|
268164
|
+
const mcPrefix = trimmed2.match(/^(\w+)\s*:\s*Mapped\[/);
|
|
268165
|
+
if (mcPrefix) {
|
|
268166
|
+
const afterMapped = trimmed2.slice(mcPrefix[0].length);
|
|
268167
|
+
let depth = 1, idx = 0;
|
|
268168
|
+
while (idx < afterMapped.length && depth > 0) {
|
|
268169
|
+
if (afterMapped[idx] === "[")
|
|
268170
|
+
depth++;
|
|
268171
|
+
else if (afterMapped[idx] === "]")
|
|
268172
|
+
depth--;
|
|
268173
|
+
idx++;
|
|
268174
|
+
}
|
|
268175
|
+
if (depth === 0) {
|
|
268176
|
+
const mappedTypeStr = afterMapped.slice(0, idx - 1);
|
|
268177
|
+
const rest = afterMapped.slice(idx);
|
|
268178
|
+
const argsMatch = rest.match(/^\s*=\s*mapped_column\((.+)\)/);
|
|
268179
|
+
if (argsMatch) {
|
|
268180
|
+
mappedColMatch = [trimmed2, mcPrefix[1], mappedTypeStr, argsMatch[1]];
|
|
268181
|
+
}
|
|
268182
|
+
}
|
|
268183
|
+
}
|
|
268184
|
+
}
|
|
268185
|
+
const colMatch = columnMatch || mappedColMatch;
|
|
268186
|
+
if (colMatch) {
|
|
268187
|
+
const colName = colMatch[1];
|
|
268188
|
+
const colArgs = columnMatch ? colMatch[2] : colMatch[3];
|
|
268189
|
+
const mappedType = mappedColMatch ? colMatch[2] : null;
|
|
268190
|
+
const colType = mappedType ? mapMappedType(mappedType, colArgs) : mapSqlAlchemyType(colArgs);
|
|
268054
268191
|
const isPrimaryKey = colArgs.includes("primary_key=True");
|
|
268055
|
-
const isNullable = colArgs.includes("nullable=True") || !colArgs.includes("nullable=False") && !isPrimaryKey;
|
|
268056
|
-
const fkMatch = colArgs.match(/ForeignKey\(["'](\w+)\.(\w+)["']
|
|
268192
|
+
const isNullable = mappedType ? /Optional|None/.test(mappedType) : colArgs.includes("nullable=True") || !colArgs.includes("nullable=False") && !isPrimaryKey;
|
|
268193
|
+
const fkMatch = colArgs.match(/ForeignKey\(["'](\w+)\.(\w+)["'][^)]*\)/);
|
|
268057
268194
|
const isForeignKey = !!fkMatch;
|
|
268058
268195
|
const referencesTable = fkMatch?.[1];
|
|
268059
268196
|
const referencesColumn = fkMatch?.[2];
|
|
@@ -268086,7 +268223,7 @@ function parseSqlAlchemySchema(content) {
|
|
|
268086
268223
|
}
|
|
268087
268224
|
continue;
|
|
268088
268225
|
}
|
|
268089
|
-
const relMatch = trimmed2.match(/^(\w+)\s
|
|
268226
|
+
const relMatch = trimmed2.match(/^(\w+)\s*(?::\s*Mapped\[[^\]]+\]\s*)?=\s*relationship\(["'](\w+)["']/);
|
|
268090
268227
|
if (relMatch && currentTableName) {
|
|
268091
268228
|
const targetModel = relMatch[2];
|
|
268092
268229
|
const targetTable = classToTable.get(targetModel);
|
|
@@ -268131,6 +268268,23 @@ function mapSqlAlchemyType(colArgs) {
|
|
|
268131
268268
|
};
|
|
268132
268269
|
return typeMap[firstArg] || firstArg;
|
|
268133
268270
|
}
|
|
268271
|
+
function mapMappedType(mappedType, colArgs) {
|
|
268272
|
+
const argType = mapSqlAlchemyType(colArgs);
|
|
268273
|
+
if (argType && argType !== colArgs.split(",")[0].trim()) {
|
|
268274
|
+
return argType;
|
|
268275
|
+
}
|
|
268276
|
+
const inner = mappedType.replace(/Optional\[([^\]]+)\]/, "$1").replace(/List\[([^\]]+)\]/, "$1[]").replace(/Dict\[([^\]]+)\]/, "Json").replace(/list\[([^\]]+)\]/, "$1[]").replace(/dict\[([^\]]+)\]/, "Json").trim();
|
|
268277
|
+
const pyTypeMap = {
|
|
268278
|
+
"str": "String",
|
|
268279
|
+
"int": "Int",
|
|
268280
|
+
"float": "Float",
|
|
268281
|
+
"bool": "Boolean",
|
|
268282
|
+
"datetime": "DateTime",
|
|
268283
|
+
"date": "Date",
|
|
268284
|
+
"bytes": "Bytes"
|
|
268285
|
+
};
|
|
268286
|
+
return pyTypeMap[inner] || argType || inner;
|
|
268287
|
+
}
|
|
268134
268288
|
var init_sqlalchemy = __esm({
|
|
268135
268289
|
"packages/analyzer/dist/schema-parsers/sqlalchemy.js"() {
|
|
268136
268290
|
"use strict";
|
|
@@ -268401,7 +268555,7 @@ function extractModulesAndMethods(analyses2, layerDetails, fileDependencies) {
|
|
|
268401
268555
|
lineCount: cls.location.endLine - cls.location.startLine + 1
|
|
268402
268556
|
});
|
|
268403
268557
|
for (const method of namedMethods) {
|
|
268404
|
-
methods2.push(toMethodInfo(method, modName, serviceName, analysis.filePath));
|
|
268558
|
+
methods2.push(toMethodInfo(method, modName, serviceName, analysis.filePath, analysis.language));
|
|
268405
268559
|
}
|
|
268406
268560
|
addToFileModules(fileToModules, analysis.filePath, modName);
|
|
268407
268561
|
}
|
|
@@ -268428,7 +268582,7 @@ function extractModulesAndMethods(analyses2, layerDetails, fileDependencies) {
|
|
|
268428
268582
|
lineCount: totalFileLines(analysis)
|
|
268429
268583
|
});
|
|
268430
268584
|
for (const fn of extraFunctions) {
|
|
268431
|
-
methods2.push(toMethodInfo(fn, modName, serviceName, analysis.filePath));
|
|
268585
|
+
methods2.push(toMethodInfo(fn, modName, serviceName, analysis.filePath, analysis.language));
|
|
268432
268586
|
}
|
|
268433
268587
|
addToFileModules(fileToModules, analysis.filePath, modName);
|
|
268434
268588
|
}
|
|
@@ -268451,7 +268605,7 @@ function extractModulesAndMethods(analyses2, layerDetails, fileDependencies) {
|
|
|
268451
268605
|
lineCount: totalFileLines(analysis)
|
|
268452
268606
|
});
|
|
268453
268607
|
for (const fn of namedFunctions) {
|
|
268454
|
-
methods2.push(toMethodInfo(fn, modName, serviceName, analysis.filePath));
|
|
268608
|
+
methods2.push(toMethodInfo(fn, modName, serviceName, analysis.filePath, analysis.language));
|
|
268455
268609
|
}
|
|
268456
268610
|
addToFileModules(fileToModules, analysis.filePath, modName);
|
|
268457
268611
|
}
|
|
@@ -268466,10 +268620,11 @@ function extractModulesAndMethods(analyses2, layerDetails, fileDependencies) {
|
|
|
268466
268620
|
const methodDependencies = buildMethodDependencies(analyses2, modules2, methods2, fileLookup);
|
|
268467
268621
|
return { modules: modules2, methods: methods2, moduleDependencies, methodDependencies };
|
|
268468
268622
|
}
|
|
268469
|
-
function toMethodInfo(fn, moduleName2, serviceName, filePath) {
|
|
268623
|
+
function toMethodInfo(fn, moduleName2, serviceName, filePath, language) {
|
|
268470
268624
|
const params = fn.params.map((p) => p.type ? `${p.name}: ${p.type}` : p.name).join(", ");
|
|
268471
268625
|
const ret = fn.returnType ? `: ${fn.returnType}` : "";
|
|
268472
268626
|
const signature = `${fn.name}(${params})${ret}`;
|
|
268627
|
+
const isImplicitCall = language === "python" && fn.name.startsWith("__") && fn.name.endsWith("__") && fn.name !== "__init__" || language !== "python" && fn.name === "constructor" || void 0;
|
|
268473
268628
|
return {
|
|
268474
268629
|
name: fn.name,
|
|
268475
268630
|
moduleName: moduleName2,
|
|
@@ -268482,7 +268637,8 @@ function toMethodInfo(fn, moduleName2, serviceName, filePath) {
|
|
|
268482
268637
|
isExported: fn.isExported,
|
|
268483
268638
|
lineCount: fn.lineCount,
|
|
268484
268639
|
statementCount: fn.statementCount,
|
|
268485
|
-
maxNestingDepth: fn.maxNestingDepth
|
|
268640
|
+
maxNestingDepth: fn.maxNestingDepth,
|
|
268641
|
+
isImplicitCall
|
|
268486
268642
|
};
|
|
268487
268643
|
}
|
|
268488
268644
|
function deriveModuleName(analysis) {
|
|
@@ -268803,6 +268959,28 @@ function buildMethodDependencies(analyses2, allModules, allMethods, fileLookup)
|
|
|
268803
268959
|
}
|
|
268804
268960
|
}
|
|
268805
268961
|
}
|
|
268962
|
+
if (!targetMethod && calleeParts.length === 1) {
|
|
268963
|
+
for (const mod2 of allModules) {
|
|
268964
|
+
if (mod2.kind !== "class" || mod2.serviceName !== serviceName)
|
|
268965
|
+
continue;
|
|
268966
|
+
if (mod2.name === calleeMethodName) {
|
|
268967
|
+
targetMethod = methodLookup.get(`${mod2.serviceName}::${mod2.name}::__init__`);
|
|
268968
|
+
if (targetMethod)
|
|
268969
|
+
break;
|
|
268970
|
+
}
|
|
268971
|
+
}
|
|
268972
|
+
if (!targetMethod) {
|
|
268973
|
+
for (const mod2 of allModules) {
|
|
268974
|
+
if (mod2.kind !== "class" || mod2.serviceName === serviceName)
|
|
268975
|
+
continue;
|
|
268976
|
+
if (mod2.name === calleeMethodName) {
|
|
268977
|
+
targetMethod = methodLookup.get(`${mod2.serviceName}::${mod2.name}::__init__`);
|
|
268978
|
+
if (targetMethod)
|
|
268979
|
+
break;
|
|
268980
|
+
}
|
|
268981
|
+
}
|
|
268982
|
+
}
|
|
268983
|
+
}
|
|
268806
268984
|
if (!targetMethod)
|
|
268807
268985
|
continue;
|
|
268808
268986
|
if (!usedFallbackCaller && targetMethod.moduleName === callerMethod.moduleName && targetMethod.name === callerMethod.name && targetMethod.serviceName === callerMethod.serviceName && targetMethod.filePath === callerMethod.filePath)
|
|
@@ -270215,9 +270393,12 @@ var init_analysis_graph = __esm({
|
|
|
270215
270393
|
}
|
|
270216
270394
|
}
|
|
270217
270395
|
}
|
|
270396
|
+
const isEntryCandidate = (name21) => !name21.startsWith("_") && name21 !== "constructor";
|
|
270218
270397
|
this.entryPoints = [];
|
|
270219
270398
|
const servicesWithEntryPoints = /* @__PURE__ */ new Set();
|
|
270220
270399
|
for (const method of input.methods) {
|
|
270400
|
+
if (!isEntryCandidate(method.name))
|
|
270401
|
+
continue;
|
|
270221
270402
|
const mod2 = this.moduleByKey.get(`${method.serviceName}::${method.moduleName}`);
|
|
270222
270403
|
if (!mod2)
|
|
270223
270404
|
continue;
|
|
@@ -270233,6 +270414,8 @@ var init_analysis_graph = __esm({
|
|
|
270233
270414
|
for (const method of input.methods) {
|
|
270234
270415
|
if (servicesWithEntryPoints.has(method.serviceName))
|
|
270235
270416
|
continue;
|
|
270417
|
+
if (!isEntryCandidate(method.name))
|
|
270418
|
+
continue;
|
|
270236
270419
|
if (!method.isExported)
|
|
270237
270420
|
continue;
|
|
270238
270421
|
const mod2 = this.moduleByKey.get(`${method.serviceName}::${method.moduleName}`);
|
|
@@ -270854,7 +271037,34 @@ var init_rule_engine = __esm({
|
|
|
270854
271037
|
});
|
|
270855
271038
|
|
|
270856
271039
|
// packages/analyzer/dist/rules/module-rules-checker.js
|
|
270857
|
-
function
|
|
271040
|
+
function buildSameFileCalls(fileAnalyses) {
|
|
271041
|
+
const result = /* @__PURE__ */ new Map();
|
|
271042
|
+
if (!fileAnalyses)
|
|
271043
|
+
return result;
|
|
271044
|
+
for (const fa of fileAnalyses) {
|
|
271045
|
+
const callees = /* @__PURE__ */ new Set();
|
|
271046
|
+
for (const call of fa.calls) {
|
|
271047
|
+
if (!call.callee.includes(".")) {
|
|
271048
|
+
callees.add(call.callee);
|
|
271049
|
+
}
|
|
271050
|
+
if (call.arguments) {
|
|
271051
|
+
for (const arg of call.arguments) {
|
|
271052
|
+
if (/^[A-Za-z_]\w*$/.test(arg)) {
|
|
271053
|
+
callees.add(arg);
|
|
271054
|
+
}
|
|
271055
|
+
const kwMatch = arg.match(/^[A-Za-z_]\w*=([A-Za-z_]\w*)$/);
|
|
271056
|
+
if (kwMatch) {
|
|
271057
|
+
callees.add(kwMatch[1]);
|
|
271058
|
+
}
|
|
271059
|
+
}
|
|
271060
|
+
}
|
|
271061
|
+
}
|
|
271062
|
+
if (callees.size > 0)
|
|
271063
|
+
result.set(fa.filePath, callees);
|
|
271064
|
+
}
|
|
271065
|
+
return result;
|
|
271066
|
+
}
|
|
271067
|
+
function checkModuleRules(modules2, methods2, fileDependencies, enabledRules, moduleLevelDeps, dbConnectedModuleKeys, fileAnalyses, libraryServiceNames, entryPointFiles, methodLevelDeps) {
|
|
270858
271068
|
const violations2 = [];
|
|
270859
271069
|
const ruleKeys = new Set(enabledRules.filter((r) => r.type === "deterministic" && r.enabled).map((r) => r.key));
|
|
270860
271070
|
if (ruleKeys.has("arch/god-module")) {
|
|
@@ -270872,6 +271082,40 @@ function checkModuleRules(modules2, methods2, fileDependencies, enabledRules, mo
|
|
|
270872
271082
|
}
|
|
270873
271083
|
}
|
|
270874
271084
|
}
|
|
271085
|
+
const calledInOwnFile = buildSameFileCalls(fileAnalyses);
|
|
271086
|
+
const usedAsType = /* @__PURE__ */ new Set();
|
|
271087
|
+
function addType(typeStr2) {
|
|
271088
|
+
if (!typeStr2)
|
|
271089
|
+
return;
|
|
271090
|
+
const parts = typeStr2.split(/[|,\[\]()]+/).map((s) => s.trim()).filter(Boolean);
|
|
271091
|
+
for (const part of parts) {
|
|
271092
|
+
if (/^(str|int|float|bool|None|Any|string|number|void|undefined|null|Optional|Union|List|Dict|Set|Tuple|list|dict|set|tuple|type)$/i.test(part))
|
|
271093
|
+
continue;
|
|
271094
|
+
usedAsType.add(part);
|
|
271095
|
+
}
|
|
271096
|
+
}
|
|
271097
|
+
if (fileAnalyses) {
|
|
271098
|
+
for (const fa of fileAnalyses) {
|
|
271099
|
+
for (const fn of fa.functions) {
|
|
271100
|
+
for (const p of fn.params)
|
|
271101
|
+
addType(p.type);
|
|
271102
|
+
addType(fn.returnType);
|
|
271103
|
+
}
|
|
271104
|
+
for (const cls of fa.classes) {
|
|
271105
|
+
if (cls.superClass)
|
|
271106
|
+
usedAsType.add(cls.superClass);
|
|
271107
|
+
for (const iface of cls.interfaces || [])
|
|
271108
|
+
usedAsType.add(iface);
|
|
271109
|
+
for (const m of cls.methods) {
|
|
271110
|
+
for (const p of m.params)
|
|
271111
|
+
addType(p.type);
|
|
271112
|
+
addType(m.returnType);
|
|
271113
|
+
}
|
|
271114
|
+
for (const prop of cls.properties)
|
|
271115
|
+
addType(prop.type);
|
|
271116
|
+
}
|
|
271117
|
+
}
|
|
271118
|
+
}
|
|
270875
271119
|
if (ruleKeys.has("arch/unused-export")) {
|
|
270876
271120
|
const importedTargets = /* @__PURE__ */ new Set();
|
|
270877
271121
|
for (const dep of fileDependencies) {
|
|
@@ -270879,6 +271123,12 @@ function checkModuleRules(modules2, methods2, fileDependencies, enabledRules, mo
|
|
|
270879
271123
|
importedTargets.add(name21);
|
|
270880
271124
|
}
|
|
270881
271125
|
}
|
|
271126
|
+
if (methodLevelDeps) {
|
|
271127
|
+
for (const dep of methodLevelDeps) {
|
|
271128
|
+
importedTargets.add(dep.calleeModule);
|
|
271129
|
+
importedTargets.add(dep.calleeMethod);
|
|
271130
|
+
}
|
|
271131
|
+
}
|
|
270882
271132
|
const classModuleNames = new Set(modules2.filter((m) => m.kind === "class").map((m) => m.name));
|
|
270883
271133
|
const routeHandlerNames = /* @__PURE__ */ new Set();
|
|
270884
271134
|
if (fileAnalyses) {
|
|
@@ -270899,6 +271149,8 @@ function checkModuleRules(modules2, methods2, fileDependencies, enabledRules, mo
|
|
|
270899
271149
|
continue;
|
|
270900
271150
|
if (routeHandlerNames.has(method.name))
|
|
270901
271151
|
continue;
|
|
271152
|
+
if (calledInOwnFile.get(method.filePath)?.has(method.name))
|
|
271153
|
+
continue;
|
|
270902
271154
|
violations2.push({
|
|
270903
271155
|
ruleKey: "arch/unused-export",
|
|
270904
271156
|
title: `Unused export: ${method.name}`,
|
|
@@ -270915,6 +271167,8 @@ function checkModuleRules(modules2, methods2, fileDependencies, enabledRules, mo
|
|
|
270915
271167
|
if (mod2.kind === "class" && mod2.exportCount > 0 && !importedTargets.has(mod2.name)) {
|
|
270916
271168
|
if (entryPointFiles?.has(mod2.filePath))
|
|
270917
271169
|
continue;
|
|
271170
|
+
if (usedAsType.has(mod2.name))
|
|
271171
|
+
continue;
|
|
270918
271172
|
violations2.push({
|
|
270919
271173
|
ruleKey: "arch/unused-export",
|
|
270920
271174
|
title: `Unused export: ${mod2.name}`,
|
|
@@ -270933,11 +271187,32 @@ function checkModuleRules(modules2, methods2, fileDependencies, enabledRules, mo
|
|
|
270933
271187
|
connectedModules.add(`${dep.sourceService}::${dep.sourceModule}`);
|
|
270934
271188
|
connectedModules.add(`${dep.targetService}::${dep.targetModule}`);
|
|
270935
271189
|
}
|
|
271190
|
+
if (methodLevelDeps) {
|
|
271191
|
+
for (const dep of methodLevelDeps) {
|
|
271192
|
+
connectedModules.add(`${dep.callerService}::${dep.callerModule}`);
|
|
271193
|
+
connectedModules.add(`${dep.calleeService}::${dep.calleeModule}`);
|
|
271194
|
+
}
|
|
271195
|
+
}
|
|
271196
|
+
const moduleMethodNames = /* @__PURE__ */ new Map();
|
|
271197
|
+
for (const m of methods2) {
|
|
271198
|
+
const mKey = `${m.serviceName}::${m.moduleName}`;
|
|
271199
|
+
const arr = moduleMethodNames.get(mKey) || [];
|
|
271200
|
+
arr.push(m.name);
|
|
271201
|
+
moduleMethodNames.set(mKey, arr);
|
|
271202
|
+
}
|
|
270936
271203
|
for (const mod2 of modules2) {
|
|
270937
271204
|
const key = `${mod2.serviceName}::${mod2.name}`;
|
|
270938
271205
|
if (!connectedModules.has(key) && !dbConnectedModuleKeys?.has(key)) {
|
|
270939
271206
|
if (entryPointFiles?.has(mod2.filePath))
|
|
270940
271207
|
continue;
|
|
271208
|
+
if (usedAsType.has(mod2.name))
|
|
271209
|
+
continue;
|
|
271210
|
+
const sameFileRefs = calledInOwnFile.get(mod2.filePath);
|
|
271211
|
+
if (sameFileRefs) {
|
|
271212
|
+
const modMethods = moduleMethodNames.get(key) || [];
|
|
271213
|
+
if (sameFileRefs.has(mod2.name) || modMethods.some((m) => sameFileRefs.has(m)))
|
|
271214
|
+
continue;
|
|
271215
|
+
}
|
|
270941
271216
|
violations2.push({
|
|
270942
271217
|
ruleKey: "arch/dead-module",
|
|
270943
271218
|
title: `Dead module: ${mod2.name}`,
|
|
@@ -271035,9 +271310,10 @@ function checkModuleRules(modules2, methods2, fileDependencies, enabledRules, mo
|
|
|
271035
271310
|
}
|
|
271036
271311
|
return violations2;
|
|
271037
271312
|
}
|
|
271038
|
-
function checkMethodRules(methods2, enabledRules, methodLevelDeps, entryPointFiles) {
|
|
271313
|
+
function checkMethodRules(methods2, enabledRules, methodLevelDeps, entryPointFiles, fileAnalyses) {
|
|
271039
271314
|
const violations2 = [];
|
|
271040
271315
|
const ruleKeys = new Set(enabledRules.filter((r) => r.type === "deterministic" && r.enabled).map((r) => r.key));
|
|
271316
|
+
const calledInOwnFile = buildSameFileCalls(fileAnalyses);
|
|
271041
271317
|
if (ruleKeys.has("arch/long-method")) {
|
|
271042
271318
|
for (const method of methods2) {
|
|
271043
271319
|
if (method.statementCount != null && method.statementCount > LONG_METHOD_STATEMENTS) {
|
|
@@ -271056,11 +271332,12 @@ function checkMethodRules(methods2, enabledRules, methodLevelDeps, entryPointFil
|
|
|
271056
271332
|
}
|
|
271057
271333
|
if (ruleKeys.has("arch/too-many-parameters")) {
|
|
271058
271334
|
for (const method of methods2) {
|
|
271059
|
-
|
|
271335
|
+
const paramThreshold = getMaxParameters(method.filePath);
|
|
271336
|
+
if (method.paramCount >= paramThreshold) {
|
|
271060
271337
|
violations2.push({
|
|
271061
271338
|
ruleKey: "arch/too-many-parameters",
|
|
271062
271339
|
title: `Too many parameters: ${method.moduleName}.${method.name}`,
|
|
271063
|
-
description: `${method.name} has ${method.paramCount} parameters (threshold: ${
|
|
271340
|
+
description: `${method.name} has ${method.paramCount} parameters (threshold: ${paramThreshold}). Consider using an options object or splitting the function.`,
|
|
271064
271341
|
severity: "low",
|
|
271065
271342
|
serviceName: method.serviceName,
|
|
271066
271343
|
moduleName: method.moduleName,
|
|
@@ -271097,6 +271374,10 @@ function checkMethodRules(methods2, enabledRules, methodLevelDeps, entryPointFil
|
|
|
271097
271374
|
if (!connectedMethods.has(key)) {
|
|
271098
271375
|
if (entryPointFiles?.has(method.filePath))
|
|
271099
271376
|
continue;
|
|
271377
|
+
if (calledInOwnFile.get(method.filePath)?.has(method.name))
|
|
271378
|
+
continue;
|
|
271379
|
+
if (method.isImplicitCall)
|
|
271380
|
+
continue;
|
|
271100
271381
|
violations2.push({
|
|
271101
271382
|
ruleKey: "arch/dead-method",
|
|
271102
271383
|
title: `Dead method: ${method.moduleName}.${method.name}`,
|
|
@@ -271112,13 +271393,13 @@ function checkMethodRules(methods2, enabledRules, methodLevelDeps, entryPointFil
|
|
|
271112
271393
|
}
|
|
271113
271394
|
return violations2;
|
|
271114
271395
|
}
|
|
271115
|
-
var GOD_MODULE_THRESHOLD, LONG_METHOD_STATEMENTS,
|
|
271396
|
+
var GOD_MODULE_THRESHOLD, LONG_METHOD_STATEMENTS, DEEP_NESTING_THRESHOLD;
|
|
271116
271397
|
var init_module_rules_checker = __esm({
|
|
271117
271398
|
"packages/analyzer/dist/rules/module-rules-checker.js"() {
|
|
271118
271399
|
"use strict";
|
|
271400
|
+
init_language_config();
|
|
271119
271401
|
GOD_MODULE_THRESHOLD = 15;
|
|
271120
271402
|
LONG_METHOD_STATEMENTS = 30;
|
|
271121
|
-
TOO_MANY_PARAMS = 5;
|
|
271122
271403
|
DEEP_NESTING_THRESHOLD = 4;
|
|
271123
271404
|
}
|
|
271124
271405
|
});
|
|
@@ -271482,7 +271763,7 @@ var init_python5 = __esm({
|
|
|
271482
271763
|
return null;
|
|
271483
271764
|
const exceptIdx = children.indexOf(exceptKeyword);
|
|
271484
271765
|
const colonIdx = children.indexOf(colon);
|
|
271485
|
-
const hasCatchType = children.slice(exceptIdx + 1, colonIdx).some((c) => c.type === "identifier" || c.type === "as_pattern" || c.type === "dotted_name");
|
|
271766
|
+
const hasCatchType = children.slice(exceptIdx + 1, colonIdx).some((c) => c.type === "identifier" || c.type === "as_pattern" || c.type === "dotted_name" || c.type === "attribute" || c.type === "tuple");
|
|
271486
271767
|
if (!hasCatchType) {
|
|
271487
271768
|
return makeViolation(this.ruleKey, node, filePath, "high", "Bare except clause", "Bare `except:` catches all exceptions including KeyboardInterrupt and SystemExit. Use `except Exception:` instead.", sourceCode, "Replace `except:` with `except Exception:` or a more specific exception type.");
|
|
271488
271769
|
}
|
|
@@ -271565,9 +271846,10 @@ var init_universal = __esm({
|
|
|
271565
271846
|
nodeTypes: ["string", "template_string"],
|
|
271566
271847
|
visit(node, filePath, sourceCode) {
|
|
271567
271848
|
const text3 = node.text;
|
|
271568
|
-
const
|
|
271569
|
-
if (
|
|
271849
|
+
const stripped = text3.replace(/^[fFbBrRuU]*['"`]{1,3}|['"`]{1,3}$/g, "");
|
|
271850
|
+
if (stripped.length < 8)
|
|
271570
271851
|
return null;
|
|
271852
|
+
const value = stripped;
|
|
271571
271853
|
for (const pattern of SECRET_PATTERNS) {
|
|
271572
271854
|
if (pattern.test(value)) {
|
|
271573
271855
|
return makeViolation(this.ruleKey, node, filePath, "critical", "Hardcoded secret detected", "This string looks like a hardcoded API key, token, or password. Use environment variables instead.", sourceCode, "Move this secret to an environment variable and reference it via process.env.");
|
|
@@ -271575,6 +271857,9 @@ var init_universal = __esm({
|
|
|
271575
271857
|
}
|
|
271576
271858
|
const parent = node.parent;
|
|
271577
271859
|
if (parent) {
|
|
271860
|
+
if (parent.type === "pair" && parent.childForFieldName("key") === node) {
|
|
271861
|
+
return null;
|
|
271862
|
+
}
|
|
271578
271863
|
const varDeclarator = parent.type === "variable_declarator" ? parent : null;
|
|
271579
271864
|
const assignment = parent.type === "assignment_expression" || parent.type === "assignment" ? parent : null;
|
|
271580
271865
|
const propAssignment = parent.type === "pair" ? parent : null;
|
|
@@ -271582,7 +271867,9 @@ var init_universal = __esm({
|
|
|
271582
271867
|
if (nameNode) {
|
|
271583
271868
|
const name21 = nameNode.text.toLowerCase();
|
|
271584
271869
|
const secretNames = ["password", "passwd", "secret", "apikey", "api_key", "token", "auth_token", "access_token", "private_key"];
|
|
271585
|
-
|
|
271870
|
+
const isNonSecretName = /(?:uri|url|endpoint|type|scope|name|header|grant|method)/.test(name21);
|
|
271871
|
+
const isNonSecretValue = /https?:\/\//.test(value) || /^(true|false|null|undefined|localhost|None|True|False|Bearer)$/i.test(value) || /[[\]<>{}()#.=\s]/.test(value);
|
|
271872
|
+
if (secretNames.some((s) => name21.includes(s)) && value.length >= 8 && !isNonSecretName && !isNonSecretValue) {
|
|
271586
271873
|
return makeViolation(this.ruleKey, node, filePath, "critical", "Hardcoded secret detected", `Variable "${nameNode.text}" contains what appears to be a hardcoded secret. Use environment variables instead.`, sourceCode, "Move this secret to an environment variable.");
|
|
271587
271874
|
}
|
|
271588
271875
|
}
|
|
@@ -271745,6 +272032,7 @@ __export(dist_exports, {
|
|
|
271745
272032
|
getAllTestPatterns: () => getAllTestPatterns,
|
|
271746
272033
|
getLanguageConfig: () => getLanguageConfig,
|
|
271747
272034
|
getLspServerConfig: () => getLspServerConfig,
|
|
272035
|
+
getMaxParameters: () => getMaxParameters,
|
|
271748
272036
|
getParser: () => getParser,
|
|
271749
272037
|
hasLspServer: () => hasLspServer,
|
|
271750
272038
|
isBootstrapEntry: () => isBootstrapEntry,
|
|
@@ -327578,10 +327866,26 @@ function buildFlowTemplateVars(context2) {
|
|
|
327578
327866
|
stepList
|
|
327579
327867
|
};
|
|
327580
327868
|
}
|
|
327581
|
-
function getLangfuse() {
|
|
327869
|
+
async function getLangfuse() {
|
|
327582
327870
|
if (!(config.langfuse.publicKey && config.langfuse.secretKey)) {
|
|
327583
327871
|
return null;
|
|
327584
327872
|
}
|
|
327873
|
+
if (!langfuseChecked) {
|
|
327874
|
+
langfuseChecked = true;
|
|
327875
|
+
try {
|
|
327876
|
+
const res = await fetch(`${config.langfuse.baseUrl || "http://localhost:3001"}/api/public/health`, {
|
|
327877
|
+
signal: AbortSignal.timeout(3e3)
|
|
327878
|
+
});
|
|
327879
|
+
langfuseAvailable = res.ok;
|
|
327880
|
+
} catch {
|
|
327881
|
+
langfuseAvailable = false;
|
|
327882
|
+
}
|
|
327883
|
+
if (!langfuseAvailable) {
|
|
327884
|
+
console.log("[Langfuse] Unavailable \u2014 using local prompts for this session.");
|
|
327885
|
+
return null;
|
|
327886
|
+
}
|
|
327887
|
+
}
|
|
327888
|
+
if (!langfuseAvailable) return null;
|
|
327585
327889
|
if (!langfuseInstance) {
|
|
327586
327890
|
langfuseInstance = new Langfuse({
|
|
327587
327891
|
publicKey: config.langfuse.publicKey,
|
|
@@ -327592,7 +327896,7 @@ function getLangfuse() {
|
|
|
327592
327896
|
return langfuseInstance;
|
|
327593
327897
|
}
|
|
327594
327898
|
async function getPrompt(name21, variables) {
|
|
327595
|
-
const langfuse = getLangfuse();
|
|
327899
|
+
const langfuse = await getLangfuse();
|
|
327596
327900
|
const localDef = PROMPT_DEFINITIONS[name21];
|
|
327597
327901
|
if (langfuse) {
|
|
327598
327902
|
try {
|
|
@@ -327612,7 +327916,7 @@ async function getPrompt(name21, variables) {
|
|
|
327612
327916
|
}
|
|
327613
327917
|
return { text: text3, langfusePrompt: null };
|
|
327614
327918
|
}
|
|
327615
|
-
var PROMPT_DEFINITIONS, langfuseInstance;
|
|
327919
|
+
var PROMPT_DEFINITIONS, langfuseInstance, langfuseChecked, langfuseAvailable;
|
|
327616
327920
|
var init_prompts = __esm({
|
|
327617
327921
|
"apps/server/src/services/llm/prompts.ts"() {
|
|
327618
327922
|
"use strict";
|
|
@@ -327937,6 +328241,8 @@ When suggesting improvements, provide actionable advice that could be passed to
|
|
|
327937
328241
|
}
|
|
327938
328242
|
};
|
|
327939
328243
|
langfuseInstance = null;
|
|
328244
|
+
langfuseChecked = false;
|
|
328245
|
+
langfuseAvailable = false;
|
|
327940
328246
|
}
|
|
327941
328247
|
});
|
|
327942
328248
|
|
|
@@ -395890,7 +396196,9 @@ var MethodInfoSchema = external_exports.object({
|
|
|
395890
396196
|
isExported: external_exports.boolean(),
|
|
395891
396197
|
lineCount: external_exports.number().optional(),
|
|
395892
396198
|
statementCount: external_exports.number().optional(),
|
|
395893
|
-
maxNestingDepth: external_exports.number().optional()
|
|
396199
|
+
maxNestingDepth: external_exports.number().optional(),
|
|
396200
|
+
/** Method is called implicitly by the runtime (e.g., Python __init__, __str__, JS constructor) */
|
|
396201
|
+
isImplicitCall: external_exports.boolean().optional()
|
|
395894
396202
|
});
|
|
395895
396203
|
var ModuleLevelDependencySchema = external_exports.object({
|
|
395896
396204
|
sourceModule: external_exports.string(),
|
|
@@ -396202,7 +396510,8 @@ var CreateRepoSchema = external_exports.object({
|
|
|
396202
396510
|
});
|
|
396203
396511
|
var AnalyzeRepoSchema = external_exports.object({
|
|
396204
396512
|
branch: external_exports.string().optional(),
|
|
396205
|
-
codeReview: external_exports.boolean().optional().default(false)
|
|
396513
|
+
codeReview: external_exports.boolean().optional().default(false),
|
|
396514
|
+
deterministicOnly: external_exports.boolean().optional().default(false)
|
|
396206
396515
|
});
|
|
396207
396516
|
var GenerateViolationsSchema = external_exports.object({
|
|
396208
396517
|
analysisId: external_exports.string().uuid().optional()
|
|
@@ -400960,7 +401269,8 @@ function runDeterministicModuleChecks(result, enabledDeterministic) {
|
|
|
400960
401269
|
dbConnectedModuleKeys,
|
|
400961
401270
|
result.fileAnalyses,
|
|
400962
401271
|
libraryServiceNames,
|
|
400963
|
-
result.entryPointFiles
|
|
401272
|
+
result.entryPointFiles,
|
|
401273
|
+
result.methodLevelDependencies || []
|
|
400964
401274
|
);
|
|
400965
401275
|
}
|
|
400966
401276
|
function runDeterministicMethodChecks(result, enabledDeterministic) {
|
|
@@ -401007,6 +401317,7 @@ async function runAnalysis(repoPath, _branch, onProgress, options) {
|
|
|
401007
401317
|
const fileAnalyses = [];
|
|
401008
401318
|
const totalFiles = files.length;
|
|
401009
401319
|
for (let i = 0; i < totalFiles; i++) {
|
|
401320
|
+
if (options?.signal?.aborted) throw new DOMException("Analysis cancelled", "AbortError");
|
|
401010
401321
|
const file2 = files[i];
|
|
401011
401322
|
try {
|
|
401012
401323
|
const analysis = await analyzer.analyzeFile(file2);
|
|
@@ -401146,14 +401457,26 @@ init_drizzle_orm();
|
|
|
401146
401457
|
init_database();
|
|
401147
401458
|
init_schema2();
|
|
401148
401459
|
async function persistAnalysisResult(params) {
|
|
401149
|
-
const { repoId, branch, result, metadata, commitHash } = params;
|
|
401150
|
-
|
|
401151
|
-
|
|
401152
|
-
|
|
401153
|
-
|
|
401154
|
-
|
|
401155
|
-
|
|
401156
|
-
|
|
401460
|
+
const { repoId, branch, result, metadata, commitHash, existingAnalysisId } = params;
|
|
401461
|
+
let analysis;
|
|
401462
|
+
if (existingAnalysisId) {
|
|
401463
|
+
const [updated] = await db.update(analyses).set({
|
|
401464
|
+
status: "completed",
|
|
401465
|
+
architecture: result.architecture,
|
|
401466
|
+
metadata: metadata ?? result.metadata
|
|
401467
|
+
}).where(eq(analyses.id, existingAnalysisId)).returning();
|
|
401468
|
+
analysis = updated;
|
|
401469
|
+
} else {
|
|
401470
|
+
const [created] = await db.insert(analyses).values({
|
|
401471
|
+
repoId,
|
|
401472
|
+
branch: branch || null,
|
|
401473
|
+
status: "completed",
|
|
401474
|
+
architecture: result.architecture,
|
|
401475
|
+
metadata: metadata ?? result.metadata,
|
|
401476
|
+
commitHash: commitHash || null
|
|
401477
|
+
}).returning();
|
|
401478
|
+
analysis = created;
|
|
401479
|
+
}
|
|
401157
401480
|
const serviceIdMap = /* @__PURE__ */ new Map();
|
|
401158
401481
|
for (const svc of result.services) {
|
|
401159
401482
|
const [saved] = await db.insert(services).values({
|
|
@@ -402643,6 +402966,7 @@ async function runViolationPipeline(input) {
|
|
|
402643
402966
|
tracker,
|
|
402644
402967
|
provider: externalProvider,
|
|
402645
402968
|
includeCodeReview,
|
|
402969
|
+
deterministicOnly,
|
|
402646
402970
|
signal
|
|
402647
402971
|
} = input;
|
|
402648
402972
|
const allRules = await getEnabledRules();
|
|
@@ -402687,7 +403011,7 @@ async function runViolationPipeline(input) {
|
|
|
402687
403011
|
detViolationIdMap.set(`${category}::${v.ruleKey}::${v.serviceName}::${v.title}`, row.id);
|
|
402688
403012
|
}
|
|
402689
403013
|
const enabledCodeRules = allRules.filter((r) => r.category === "code" && r.type === "deterministic");
|
|
402690
|
-
const enabledLlmCodeRules = includeCodeReview ? allRules.filter((r) => r.category === "code" && r.type === "llm" && r.prompt) : [];
|
|
403014
|
+
const enabledLlmCodeRules = includeCodeReview && !deterministicOnly ? allRules.filter((r) => r.category === "code" && r.type === "llm" && r.prompt) : [];
|
|
402691
403015
|
const allCodeViolations = [];
|
|
402692
403016
|
const fileContents = /* @__PURE__ */ new Map();
|
|
402693
403017
|
const filesToScan = changedFileSet ? [...changedFileSet].map((relPath) => ({ filePath: relPath, resolve: true })) : (result.fileAnalyses || []).map((fa) => ({ filePath: fa.filePath, resolve: !path6.isAbsolute(fa.filePath) }));
|
|
@@ -402823,12 +403147,14 @@ async function runViolationPipeline(input) {
|
|
|
402823
403147
|
}
|
|
402824
403148
|
const architectureContext = `Architecture: ${result.architecture}
|
|
402825
403149
|
Services: ${result.services.map((s) => `${s.name} (${s.type})`).join(", ")}`;
|
|
402826
|
-
const provider = externalProvider ?? createLLMProvider();
|
|
403150
|
+
const provider = deterministicOnly ? void 0 : externalProvider ?? createLLMProvider();
|
|
402827
403151
|
const now2 = /* @__PURE__ */ new Date();
|
|
402828
403152
|
const allNewViolations = [];
|
|
402829
403153
|
const allResolvedViolationIds = [];
|
|
402830
|
-
|
|
402831
|
-
|
|
403154
|
+
if (!deterministicOnly) {
|
|
403155
|
+
tracker?.start("enrich", `Enriching ${allDetEntries.length} detections...`);
|
|
403156
|
+
tracker?.start("architecture");
|
|
403157
|
+
}
|
|
402832
403158
|
const deterministicPromise = (async () => {
|
|
402833
403159
|
let detectionsToEnrich;
|
|
402834
403160
|
if (previousDeterministicViolations.length > 0) {
|
|
@@ -402891,62 +403217,101 @@ Services: ${result.services.map((s) => `${s.name} (${s.type})`).join(", ")}`;
|
|
|
402891
403217
|
detectionsToEnrich = allDetEntries;
|
|
402892
403218
|
}
|
|
402893
403219
|
if (detectionsToEnrich.length > 0) {
|
|
402894
|
-
|
|
402895
|
-
|
|
402896
|
-
|
|
402897
|
-
|
|
402898
|
-
|
|
402899
|
-
|
|
402900
|
-
|
|
402901
|
-
|
|
402902
|
-
|
|
402903
|
-
|
|
402904
|
-
|
|
402905
|
-
|
|
402906
|
-
|
|
402907
|
-
|
|
402908
|
-
|
|
402909
|
-
|
|
402910
|
-
|
|
402911
|
-
|
|
402912
|
-
|
|
402913
|
-
|
|
402914
|
-
|
|
402915
|
-
|
|
402916
|
-
|
|
402917
|
-
|
|
402918
|
-
|
|
402919
|
-
|
|
402920
|
-
|
|
402921
|
-
|
|
402922
|
-
|
|
402923
|
-
|
|
402924
|
-
|
|
402925
|
-
|
|
402926
|
-
|
|
402927
|
-
|
|
402928
|
-
|
|
402929
|
-
|
|
402930
|
-
|
|
402931
|
-
|
|
402932
|
-
|
|
402933
|
-
|
|
402934
|
-
|
|
402935
|
-
|
|
402936
|
-
|
|
402937
|
-
|
|
402938
|
-
|
|
402939
|
-
|
|
402940
|
-
|
|
402941
|
-
|
|
402942
|
-
|
|
402943
|
-
|
|
402944
|
-
|
|
403220
|
+
if (deterministicOnly) {
|
|
403221
|
+
for (const det of detectionsToEnrich) {
|
|
403222
|
+
const violationId = v4_default();
|
|
403223
|
+
await db.insert(violations).values({
|
|
403224
|
+
id: violationId,
|
|
403225
|
+
repoId,
|
|
403226
|
+
analysisId,
|
|
403227
|
+
type: det.violationType,
|
|
403228
|
+
title: det.title,
|
|
403229
|
+
content: det.description,
|
|
403230
|
+
severity: det.severity,
|
|
403231
|
+
status: "new",
|
|
403232
|
+
targetServiceId: det.targetServiceId,
|
|
403233
|
+
targetModuleId: det.targetModuleId,
|
|
403234
|
+
targetMethodId: det.targetMethodId,
|
|
403235
|
+
fixPrompt: null,
|
|
403236
|
+
ruleKey: det.ruleKey,
|
|
403237
|
+
deterministicViolationId: det.detViolationId,
|
|
403238
|
+
firstSeenAnalysisId: analysisId,
|
|
403239
|
+
firstSeenAt: now2
|
|
403240
|
+
});
|
|
403241
|
+
allNewViolations.push({
|
|
403242
|
+
type: det.violationType,
|
|
403243
|
+
title: det.title,
|
|
403244
|
+
content: det.description,
|
|
403245
|
+
severity: det.severity,
|
|
403246
|
+
targetServiceId: det.targetServiceId,
|
|
403247
|
+
targetModuleId: det.targetModuleId,
|
|
403248
|
+
targetMethodId: det.targetMethodId,
|
|
403249
|
+
targetServiceName: det.serviceName || null,
|
|
403250
|
+
targetModuleName: det.moduleName || null,
|
|
403251
|
+
targetMethodName: det.methodName || null,
|
|
403252
|
+
fixPrompt: null,
|
|
403253
|
+
ruleKey: det.ruleKey
|
|
403254
|
+
});
|
|
403255
|
+
}
|
|
403256
|
+
emitProgress(88, "Deterministic violations persisted");
|
|
403257
|
+
} else {
|
|
403258
|
+
const detByViolationId = new Map(detectionsToEnrich.map((d) => [d.detViolationId, d]));
|
|
403259
|
+
const enrichmentResult = await provider.enrichDeterministicViolations(
|
|
403260
|
+
detectionsToEnrich.map((d) => ({
|
|
403261
|
+
id: d.detViolationId,
|
|
403262
|
+
ruleKey: d.ruleKey,
|
|
403263
|
+
title: d.title,
|
|
403264
|
+
description: d.description,
|
|
403265
|
+
severity: d.severity,
|
|
403266
|
+
category: d.category,
|
|
403267
|
+
serviceName: d.serviceName,
|
|
403268
|
+
moduleName: d.moduleName,
|
|
403269
|
+
methodName: d.methodName
|
|
403270
|
+
})),
|
|
403271
|
+
architectureContext
|
|
403272
|
+
);
|
|
403273
|
+
for (const enriched of enrichmentResult.enrichedViolations) {
|
|
403274
|
+
const det = detByViolationId.get(enriched.id);
|
|
403275
|
+
if (!det) continue;
|
|
403276
|
+
const violationId = v4_default();
|
|
403277
|
+
await db.insert(violations).values({
|
|
403278
|
+
id: violationId,
|
|
403279
|
+
repoId,
|
|
403280
|
+
analysisId,
|
|
403281
|
+
type: det.violationType,
|
|
403282
|
+
title: enriched.title,
|
|
403283
|
+
content: enriched.content,
|
|
403284
|
+
severity: det.severity,
|
|
403285
|
+
status: "new",
|
|
403286
|
+
targetServiceId: det.targetServiceId,
|
|
403287
|
+
targetModuleId: det.targetModuleId,
|
|
403288
|
+
targetMethodId: det.targetMethodId,
|
|
403289
|
+
fixPrompt: enriched.fixPrompt,
|
|
403290
|
+
ruleKey: det.ruleKey,
|
|
403291
|
+
deterministicViolationId: det.detViolationId,
|
|
403292
|
+
firstSeenAnalysisId: analysisId,
|
|
403293
|
+
firstSeenAt: now2
|
|
403294
|
+
});
|
|
403295
|
+
allNewViolations.push({
|
|
403296
|
+
type: det.violationType,
|
|
403297
|
+
title: enriched.title,
|
|
403298
|
+
content: enriched.content,
|
|
403299
|
+
severity: det.severity,
|
|
403300
|
+
targetServiceId: det.targetServiceId,
|
|
403301
|
+
targetModuleId: det.targetModuleId,
|
|
403302
|
+
targetMethodId: det.targetMethodId,
|
|
403303
|
+
targetServiceName: det.serviceName || null,
|
|
403304
|
+
targetModuleName: det.moduleName || null,
|
|
403305
|
+
targetMethodName: det.methodName || null,
|
|
403306
|
+
fixPrompt: enriched.fixPrompt,
|
|
403307
|
+
ruleKey: det.ruleKey
|
|
403308
|
+
});
|
|
403309
|
+
}
|
|
403310
|
+
tracker?.done("enrich", `${enrichmentResult.enrichedViolations.length} violations enriched`);
|
|
403311
|
+
emitProgress(88, "Deterministic violations enriched");
|
|
402945
403312
|
}
|
|
402946
|
-
tracker?.done("enrich", `${enrichmentResult.enrichedViolations.length} violations enriched`);
|
|
402947
|
-
emitProgress(88, "Deterministic violations enriched");
|
|
402948
403313
|
} else {
|
|
402949
|
-
tracker?.done("enrich", "No detections to enrich");
|
|
403314
|
+
if (!deterministicOnly) tracker?.done("enrich", "No detections to enrich");
|
|
402950
403315
|
}
|
|
402951
403316
|
})();
|
|
402952
403317
|
const enabledLlmRules = allRules.filter((r) => r.type === "llm" && r.prompt && r.category !== "code").map((r) => ({ key: r.key, name: r.name, severity: r.severity, prompt: r.prompt, category: r.category }));
|
|
@@ -403046,13 +403411,13 @@ Services: ${result.services.map((s) => `${s.name} (${s.type})`).join(", ")}`;
|
|
|
403046
403411
|
existingDatabaseViolations: hasLlmOnlyExistingViolations ? existingDatabaseViolations : void 0,
|
|
403047
403412
|
existingModuleViolations: hasLlmOnlyExistingViolations ? existingModuleViolations : void 0
|
|
403048
403413
|
};
|
|
403049
|
-
const llmCodePromise = llmCodeBatches.length > 0 ? provider.generateAllCodeViolations(llmCodeBatches) : Promise.resolve({ violations: [] });
|
|
403414
|
+
const llmCodePromise = llmCodeBatches.length > 0 && !deterministicOnly ? provider.generateAllCodeViolations(llmCodeBatches) : Promise.resolve({ violations: [] });
|
|
403050
403415
|
let serviceDescriptions = [];
|
|
403051
403416
|
let llmCodeResolvedIds = [];
|
|
403052
403417
|
let llmCodeUnchangedIds = [];
|
|
403053
|
-
emitProgress(86, "Analyzing architecture & modules...");
|
|
403418
|
+
if (!deterministicOnly) emitProgress(86, "Analyzing architecture & modules...");
|
|
403054
403419
|
let llmStepCount = 0;
|
|
403055
|
-
const llmRulePromise = (async () => {
|
|
403420
|
+
const llmRulePromise = deterministicOnly ? Promise.resolve() : (async () => {
|
|
403056
403421
|
if (hasLlmOnlyExistingViolations) {
|
|
403057
403422
|
const archResult = await generateViolationsWithLifecycle(violationInput, (step) => {
|
|
403058
403423
|
llmStepCount++;
|
|
@@ -403124,6 +403489,7 @@ Services: ${result.services.map((s) => `${s.name} (${s.type})`).join(", ")}`;
|
|
|
403124
403489
|
});
|
|
403125
403490
|
}
|
|
403126
403491
|
}
|
|
403492
|
+
tracker?.done("architecture");
|
|
403127
403493
|
})();
|
|
403128
403494
|
const codePromise = llmCodePromise;
|
|
403129
403495
|
const [detResult, llmResult] = await Promise.allSettled([deterministicPromise, llmRulePromise]);
|
|
@@ -403137,9 +403503,6 @@ Services: ${result.services.map((s) => `${s.name} (${s.type})`).join(", ")}`;
|
|
|
403137
403503
|
log2(`[Violations] LLM rule analysis failed: ${msg}`);
|
|
403138
403504
|
tracker?.error("architecture", `Failed: ${msg.slice(0, 80)}`);
|
|
403139
403505
|
}
|
|
403140
|
-
if (llmResult.status === "fulfilled") {
|
|
403141
|
-
tracker?.done("architecture");
|
|
403142
|
-
}
|
|
403143
403506
|
throwIfAborted(signal);
|
|
403144
403507
|
tracker?.start("persist", "Saving results...");
|
|
403145
403508
|
emitProgress(95, "Analysis complete");
|
|
@@ -403473,7 +403836,7 @@ router2.post(
|
|
|
403473
403836
|
if (!parsed.success) {
|
|
403474
403837
|
throw createAppError("Invalid request body", 400);
|
|
403475
403838
|
}
|
|
403476
|
-
const { codeReview: includeCodeReview } = parsed.data;
|
|
403839
|
+
const { codeReview: includeCodeReview, deterministicOnly } = parsed.data;
|
|
403477
403840
|
const [repo] = await db.select().from(repos).where(eq(repos.id, id)).limit(1);
|
|
403478
403841
|
if (!repo) {
|
|
403479
403842
|
throw createAppError("Repo not found", 404);
|
|
@@ -403481,22 +403844,31 @@ router2.post(
|
|
|
403481
403844
|
const git = await getGit(repo.path);
|
|
403482
403845
|
const branch = (await git.branch()).current || null;
|
|
403483
403846
|
const commitHash = (await git.revparse(["HEAD"])).trim();
|
|
403484
|
-
|
|
403485
|
-
|
|
403847
|
+
const [runningAnalysis] = await db.insert(analyses).values({
|
|
403848
|
+
repoId: id,
|
|
403849
|
+
branch,
|
|
403850
|
+
status: "running",
|
|
403851
|
+
architecture: "unknown",
|
|
403852
|
+
commitHash
|
|
403853
|
+
}).returning();
|
|
403854
|
+
res.status(202).json({ message: "Analysis started", repoId: id, branch, analysisId: runningAnalysis.id });
|
|
403855
|
+
const abortController = registerAnalysis(id, runningAnalysis.id);
|
|
403486
403856
|
try {
|
|
403487
403857
|
const trackerSteps = [
|
|
403488
403858
|
{ key: "parse", label: "Parsing repository" },
|
|
403489
403859
|
{ key: "detect", label: "Deterministic checks" },
|
|
403490
|
-
|
|
403491
|
-
|
|
403860
|
+
...!deterministicOnly ? [
|
|
403861
|
+
{ key: "enrich", label: "Enriching detections" },
|
|
403862
|
+
{ key: "architecture", label: "Architecture analysis" }
|
|
403863
|
+
] : [],
|
|
403492
403864
|
{ key: "persist", label: "Saving results" },
|
|
403493
|
-
|
|
403865
|
+
...!deterministicOnly && includeCodeReview ? [{ key: "code-review", label: "Code review" }] : []
|
|
403494
403866
|
];
|
|
403495
403867
|
const tracker = new StepTracker(id, trackerSteps);
|
|
403496
403868
|
tracker.start("parse", "Starting analysis...");
|
|
403497
403869
|
const result = await runAnalysis(repo.path, branch ?? void 0, (progress) => {
|
|
403498
403870
|
tracker.detail("parse", progress.detail ?? "Analyzing...");
|
|
403499
|
-
});
|
|
403871
|
+
}, { signal: abortController.signal });
|
|
403500
403872
|
const prevConditions = [eq(analyses.repoId, id), notDiffAnalysis2];
|
|
403501
403873
|
if (branch) prevConditions.push(eq(analyses.branch, branch));
|
|
403502
403874
|
const prevAnalyses = await db.select().from(analyses).where(and(...prevConditions)).orderBy(desc(analyses.createdAt)).limit(1);
|
|
@@ -403520,7 +403892,13 @@ router2.post(
|
|
|
403520
403892
|
for (const da of oldDiffAnalyses) {
|
|
403521
403893
|
await db.delete(analyses).where(eq(analyses.id, da.id));
|
|
403522
403894
|
}
|
|
403523
|
-
|
|
403895
|
+
if (abortController.signal.aborted) {
|
|
403896
|
+
await db.update(analyses).set({ status: "cancelled" }).where(eq(analyses.id, runningAnalysis.id));
|
|
403897
|
+
emitAnalysisCanceled(id);
|
|
403898
|
+
unregisterAnalysis(id);
|
|
403899
|
+
return;
|
|
403900
|
+
}
|
|
403901
|
+
const { analysisId: newAnalysisId, serviceIdMap, moduleIdMap, methodIdMap, dbIdMap } = await persistAnalysisResult({ repoId: id, branch, result, commitHash, metadata: { codeReview: includeCodeReview, deterministicOnly }, existingAnalysisId: runningAnalysis.id });
|
|
403524
403902
|
const analysis = { id: newAnalysisId };
|
|
403525
403903
|
const prevConditionsForLifecycle = [eq(analyses.repoId, id), notDiffAnalysis2];
|
|
403526
403904
|
if (branch) prevConditionsForLifecycle.push(eq(analyses.branch, branch));
|
|
@@ -403638,10 +404016,12 @@ router2.post(
|
|
|
403638
404016
|
}
|
|
403639
404017
|
}
|
|
403640
404018
|
tracker.done("parse", `${result.services.length} services, ${result.fileAnalyses?.length ?? 0} files`);
|
|
403641
|
-
const provider = createLLMProvider();
|
|
403642
|
-
provider
|
|
403643
|
-
|
|
403644
|
-
|
|
404019
|
+
const provider = deterministicOnly ? void 0 : createLLMProvider();
|
|
404020
|
+
if (provider) {
|
|
404021
|
+
provider.setAnalysisId(newAnalysisId);
|
|
404022
|
+
provider.setRepoId(id);
|
|
404023
|
+
provider.setAbortSignal(abortController.signal);
|
|
404024
|
+
}
|
|
403645
404025
|
let codeReviewPromise = null;
|
|
403646
404026
|
try {
|
|
403647
404027
|
const pipelineResult = await runViolationPipeline({
|
|
@@ -403658,7 +404038,8 @@ router2.post(
|
|
|
403658
404038
|
previousDeterministicViolations,
|
|
403659
404039
|
changedFileSet,
|
|
403660
404040
|
tracker,
|
|
403661
|
-
includeCodeReview,
|
|
404041
|
+
includeCodeReview: deterministicOnly ? false : includeCodeReview,
|
|
404042
|
+
deterministicOnly,
|
|
403662
404043
|
provider,
|
|
403663
404044
|
signal: abortController.signal
|
|
403664
404045
|
});
|
|
@@ -403676,7 +404057,7 @@ router2.post(
|
|
|
403676
404057
|
emitViolationsReady(id, analysis.id);
|
|
403677
404058
|
}
|
|
403678
404059
|
try {
|
|
403679
|
-
await provider
|
|
404060
|
+
await provider?.flushUsage();
|
|
403680
404061
|
} catch (usageError) {
|
|
403681
404062
|
console.error("[Usage] Failed to record usage:", usageError instanceof Error ? usageError.message : String(usageError));
|
|
403682
404063
|
}
|
|
@@ -403689,7 +404070,7 @@ router2.post(
|
|
|
403689
404070
|
if (codeReviewPromise) {
|
|
403690
404071
|
console.log(`[CodeReview] Started background code review for repo ${id}`);
|
|
403691
404072
|
emitCodeReviewProgress(id, analysis.id);
|
|
403692
|
-
codeReviewPromise.then(() => provider
|
|
404073
|
+
codeReviewPromise.then(() => provider?.flushUsage()).then(() => {
|
|
403693
404074
|
console.log(`[CodeReview] Completed for repo ${id}`);
|
|
403694
404075
|
emitCodeReviewReady(id, analysis.id);
|
|
403695
404076
|
}).catch((err) => {
|
|
@@ -403706,11 +404087,14 @@ router2.post(
|
|
|
403706
404087
|
} catch (error40) {
|
|
403707
404088
|
if (error40 instanceof DOMException && error40.name === "AbortError") {
|
|
403708
404089
|
console.log(`[Analysis] Cancelled for repo ${id}`);
|
|
404090
|
+
await db.update(analyses).set({ status: "cancelled" }).where(eq(analyses.id, runningAnalysis.id));
|
|
404091
|
+
emitAnalysisCanceled(id);
|
|
403709
404092
|
} else {
|
|
403710
404093
|
console.error(
|
|
403711
404094
|
`[Analysis] Failed for repo ${id}:`,
|
|
403712
404095
|
error40 instanceof Error ? error40.message : String(error40)
|
|
403713
404096
|
);
|
|
404097
|
+
await db.update(analyses).set({ status: "failed" }).where(eq(analyses.id, runningAnalysis.id));
|
|
403714
404098
|
emitAnalysisProgress(id, {
|
|
403715
404099
|
step: "error",
|
|
403716
404100
|
percent: -1,
|
|
@@ -403731,11 +404115,9 @@ router2.post(
|
|
|
403731
404115
|
const id = req.params.id;
|
|
403732
404116
|
const canceled = cancelAnalysis(id);
|
|
403733
404117
|
if (canceled) {
|
|
403734
|
-
|
|
403735
|
-
res.json({ message: "Analysis cancelled" });
|
|
403736
|
-
} else {
|
|
403737
|
-
throw createAppError("No active analysis for this repo", 404);
|
|
404118
|
+
await db.update(analyses).set({ status: "cancelling" }).where(and(eq(analyses.repoId, id), eq(analyses.status, "running")));
|
|
403738
404119
|
}
|
|
404120
|
+
res.json({ message: canceled ? "Analysis cancelling" : "No active analysis" });
|
|
403739
404121
|
} catch (error40) {
|
|
403740
404122
|
next(error40);
|
|
403741
404123
|
}
|
|
@@ -403809,6 +404191,7 @@ router2.get(
|
|
|
403809
404191
|
}
|
|
403810
404192
|
const analysisList = await db.select({
|
|
403811
404193
|
id: analyses.id,
|
|
404194
|
+
status: analyses.status,
|
|
403812
404195
|
branch: analyses.branch,
|
|
403813
404196
|
commitHash: analyses.commitHash,
|
|
403814
404197
|
architecture: analyses.architecture,
|