@aiready/core 0.9.32 → 0.9.35
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/dist/chunk-HFLFBA6F.mjs +410 -0
- package/dist/client.d.mts +3 -1
- package/dist/client.d.ts +3 -1
- package/dist/client.js +22 -20
- package/dist/client.mjs +1 -1
- package/dist/index.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +436 -106
- package/dist/index.mjs +419 -95
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ __export(index_exports, {
|
|
|
43
43
|
SIZE_ADJUSTED_THRESHOLDS: () => SIZE_ADJUSTED_THRESHOLDS,
|
|
44
44
|
TOOL_NAME_MAP: () => TOOL_NAME_MAP,
|
|
45
45
|
TypeScriptParser: () => TypeScriptParser,
|
|
46
|
+
VAGUE_FILE_NAMES: () => VAGUE_FILE_NAMES,
|
|
46
47
|
calculateAgentGrounding: () => calculateAgentGrounding,
|
|
47
48
|
calculateAiSignalClarity: () => calculateAiSignalClarity,
|
|
48
49
|
calculateChangeAmplification: () => calculateChangeAmplification,
|
|
@@ -77,8 +78,10 @@ __export(index_exports, {
|
|
|
77
78
|
generateHTML: () => generateHTML,
|
|
78
79
|
getDebtBreakdown: () => getDebtBreakdown,
|
|
79
80
|
getElapsedTime: () => getElapsedTime,
|
|
81
|
+
getFileCommitTimestamps: () => getFileCommitTimestamps,
|
|
80
82
|
getFileExtension: () => getFileExtension,
|
|
81
83
|
getHistorySummary: () => getHistorySummary,
|
|
84
|
+
getLineRangeLastModifiedCached: () => getLineRangeLastModifiedCached,
|
|
82
85
|
getModelPreset: () => getModelPreset,
|
|
83
86
|
getParser: () => getParser,
|
|
84
87
|
getProjectSizeTier: () => getProjectSizeTier,
|
|
@@ -104,6 +107,7 @@ __export(index_exports, {
|
|
|
104
107
|
readFileContent: () => readFileContent,
|
|
105
108
|
resolveOutputPath: () => resolveOutputPath,
|
|
106
109
|
saveScoreEntry: () => saveScoreEntry,
|
|
110
|
+
scanEntries: () => scanEntries,
|
|
107
111
|
scanFiles: () => scanFiles
|
|
108
112
|
});
|
|
109
113
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -159,6 +163,8 @@ var DEFAULT_EXCLUDE = [
|
|
|
159
163
|
"**/cdk.out/**",
|
|
160
164
|
// Framework-specific build dirs
|
|
161
165
|
"**/.next/**",
|
|
166
|
+
"**/.sst/**",
|
|
167
|
+
"**/.open-next/**",
|
|
162
168
|
"**/.nuxt/**",
|
|
163
169
|
"**/.vuepress/**",
|
|
164
170
|
"**/.cache/**",
|
|
@@ -190,6 +196,28 @@ var DEFAULT_EXCLUDE = [
|
|
|
190
196
|
"**/*.log",
|
|
191
197
|
"**/.DS_Store"
|
|
192
198
|
];
|
|
199
|
+
var VAGUE_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
200
|
+
"utils",
|
|
201
|
+
"helpers",
|
|
202
|
+
"helper",
|
|
203
|
+
"misc",
|
|
204
|
+
"common",
|
|
205
|
+
"shared",
|
|
206
|
+
"tools",
|
|
207
|
+
"util",
|
|
208
|
+
"lib",
|
|
209
|
+
"libs",
|
|
210
|
+
"stuff",
|
|
211
|
+
"functions",
|
|
212
|
+
"methods",
|
|
213
|
+
"handlers",
|
|
214
|
+
"data",
|
|
215
|
+
"temp",
|
|
216
|
+
"tmp",
|
|
217
|
+
"test-utils",
|
|
218
|
+
"test-helpers",
|
|
219
|
+
"mocks"
|
|
220
|
+
]);
|
|
193
221
|
async function scanFiles(options) {
|
|
194
222
|
const {
|
|
195
223
|
rootDir,
|
|
@@ -207,18 +235,35 @@ async function scanFiles(options) {
|
|
|
207
235
|
ignoreFromFile = [];
|
|
208
236
|
}
|
|
209
237
|
}
|
|
210
|
-
const
|
|
238
|
+
const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
|
|
239
|
+
const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
|
|
240
|
+
const finalExclude = [
|
|
241
|
+
.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
|
|
242
|
+
];
|
|
211
243
|
const files = await (0, import_glob.glob)(include, {
|
|
212
244
|
cwd: rootDir,
|
|
213
245
|
ignore: finalExclude,
|
|
214
246
|
absolute: true
|
|
215
247
|
});
|
|
216
|
-
const
|
|
217
|
-
|
|
248
|
+
const gitignoreFiles = await (0, import_glob.glob)("**/.gitignore", {
|
|
249
|
+
cwd: rootDir,
|
|
250
|
+
ignore: finalExclude,
|
|
251
|
+
absolute: true
|
|
252
|
+
});
|
|
253
|
+
if (gitignoreFiles.length > 0) {
|
|
218
254
|
try {
|
|
219
|
-
const gitTxt = await (0, import_promises.readFile)(gitignorePath, "utf-8");
|
|
220
255
|
const ig = (0, import_ignore.default)();
|
|
221
|
-
|
|
256
|
+
for (const gitignorePath of gitignoreFiles) {
|
|
257
|
+
const gitTxt = await (0, import_promises.readFile)(gitignorePath, "utf-8");
|
|
258
|
+
const gitignoreDir = (0, import_path.dirname)(gitignorePath);
|
|
259
|
+
const relativePrefix = (0, import_path.relative)(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
|
|
260
|
+
const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
|
|
261
|
+
if (relativePrefix === "." || relativePrefix === "") {
|
|
262
|
+
ig.add(patterns);
|
|
263
|
+
} else {
|
|
264
|
+
ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
222
267
|
const filtered = files.filter((f) => {
|
|
223
268
|
let rel = (0, import_path.relative)(rootDir || ".", f).replace(/\\/g, "/");
|
|
224
269
|
if (rel === "") rel = f;
|
|
@@ -231,6 +276,54 @@ async function scanFiles(options) {
|
|
|
231
276
|
}
|
|
232
277
|
return files;
|
|
233
278
|
}
|
|
279
|
+
async function scanEntries(options) {
|
|
280
|
+
const files = await scanFiles(options);
|
|
281
|
+
const { rootDir, include = ["**/*"], exclude, includeTests } = options;
|
|
282
|
+
const ignoreFilePath = (0, import_path.join)(rootDir || ".", ".aireadyignore");
|
|
283
|
+
let ignoreFromFile = [];
|
|
284
|
+
if ((0, import_fs.existsSync)(ignoreFilePath)) {
|
|
285
|
+
try {
|
|
286
|
+
const txt = await (0, import_promises.readFile)(ignoreFilePath, "utf-8");
|
|
287
|
+
ignoreFromFile = txt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#")).filter((l) => !l.startsWith("!"));
|
|
288
|
+
} catch (e) {
|
|
289
|
+
ignoreFromFile = [];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
|
|
293
|
+
const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
|
|
294
|
+
const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])];
|
|
295
|
+
const dirs = await (0, import_glob.glob)("**/", {
|
|
296
|
+
cwd: rootDir,
|
|
297
|
+
ignore: finalExclude,
|
|
298
|
+
absolute: true
|
|
299
|
+
});
|
|
300
|
+
const gitignoreFiles = await (0, import_glob.glob)("**/.gitignore", {
|
|
301
|
+
cwd: rootDir,
|
|
302
|
+
ignore: finalExclude,
|
|
303
|
+
absolute: true
|
|
304
|
+
});
|
|
305
|
+
if (gitignoreFiles.length > 0) {
|
|
306
|
+
const ig = (0, import_ignore.default)();
|
|
307
|
+
for (const gitignorePath of gitignoreFiles) {
|
|
308
|
+
const gitTxt = await (0, import_promises.readFile)(gitignorePath, "utf-8");
|
|
309
|
+
const gitignoreDir = (0, import_path.dirname)(gitignorePath);
|
|
310
|
+
const relativePrefix = (0, import_path.relative)(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
|
|
311
|
+
const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
|
|
312
|
+
if (relativePrefix === "." || relativePrefix === "") {
|
|
313
|
+
ig.add(patterns);
|
|
314
|
+
} else {
|
|
315
|
+
ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const filteredDirs = dirs.filter((d) => {
|
|
319
|
+
let rel = (0, import_path.relative)(rootDir || ".", d).replace(/\\/g, "/").replace(/\/$/, "");
|
|
320
|
+
if (rel === "") return true;
|
|
321
|
+
return !ig.ignores(rel);
|
|
322
|
+
});
|
|
323
|
+
return { files, dirs: filteredDirs };
|
|
324
|
+
}
|
|
325
|
+
return { files, dirs };
|
|
326
|
+
}
|
|
234
327
|
async function readFileContent(filePath) {
|
|
235
328
|
return (0, import_promises.readFile)(filePath, "utf-8");
|
|
236
329
|
}
|
|
@@ -455,7 +548,14 @@ async function loadConfig(rootDir) {
|
|
|
455
548
|
return config;
|
|
456
549
|
} catch (error) {
|
|
457
550
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
458
|
-
|
|
551
|
+
const e = new Error(
|
|
552
|
+
`Failed to load config from ${configPath}: ${errorMessage}`
|
|
553
|
+
);
|
|
554
|
+
try {
|
|
555
|
+
e.cause = error instanceof Error ? error : void 0;
|
|
556
|
+
} catch {
|
|
557
|
+
}
|
|
558
|
+
throw e;
|
|
459
559
|
}
|
|
460
560
|
}
|
|
461
561
|
}
|
|
@@ -707,26 +807,26 @@ function generateHTML(graph) {
|
|
|
707
807
|
var DEFAULT_TOOL_WEIGHTS = {
|
|
708
808
|
"pattern-detect": 22,
|
|
709
809
|
"context-analyzer": 19,
|
|
710
|
-
|
|
810
|
+
consistency: 14,
|
|
711
811
|
"ai-signal-clarity": 11,
|
|
712
812
|
"agent-grounding": 10,
|
|
713
|
-
|
|
813
|
+
testability: 10,
|
|
714
814
|
"doc-drift": 8,
|
|
715
|
-
|
|
815
|
+
deps: 6
|
|
716
816
|
};
|
|
717
817
|
var TOOL_NAME_MAP = {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
818
|
+
patterns: "pattern-detect",
|
|
819
|
+
context: "context-analyzer",
|
|
820
|
+
consistency: "consistency",
|
|
721
821
|
"AI signal clarity": "ai-signal-clarity",
|
|
722
822
|
"ai-signal-clarity": "ai-signal-clarity",
|
|
723
|
-
|
|
823
|
+
grounding: "agent-grounding",
|
|
724
824
|
"agent-grounding": "agent-grounding",
|
|
725
|
-
|
|
726
|
-
|
|
825
|
+
testability: "testability",
|
|
826
|
+
tests: "testability",
|
|
727
827
|
"doc-drift": "doc-drift",
|
|
728
|
-
|
|
729
|
-
|
|
828
|
+
docs: "doc-drift",
|
|
829
|
+
deps: "deps"
|
|
730
830
|
};
|
|
731
831
|
var CONTEXT_TIER_THRESHOLDS = {
|
|
732
832
|
compact: { idealTokens: 3e3, criticalTokens: 1e4, idealDepth: 4 },
|
|
@@ -735,15 +835,15 @@ var CONTEXT_TIER_THRESHOLDS = {
|
|
|
735
835
|
frontier: { idealTokens: 5e4, criticalTokens: 15e4, idealDepth: 10 }
|
|
736
836
|
};
|
|
737
837
|
var SIZE_ADJUSTED_THRESHOLDS = {
|
|
738
|
-
|
|
838
|
+
xs: 80,
|
|
739
839
|
// < 50 files
|
|
740
|
-
|
|
840
|
+
small: 75,
|
|
741
841
|
// 50-200 files
|
|
742
|
-
|
|
842
|
+
medium: 70,
|
|
743
843
|
// 200-500 files
|
|
744
|
-
|
|
844
|
+
large: 65,
|
|
745
845
|
// 500-2000 files
|
|
746
|
-
|
|
846
|
+
enterprise: 58
|
|
747
847
|
// 2000+ files
|
|
748
848
|
};
|
|
749
849
|
function getProjectSizeTier(fileCount) {
|
|
@@ -816,10 +916,12 @@ function calculateOverallScore(toolOutputs, config, cliWeights) {
|
|
|
816
916
|
}
|
|
817
917
|
const overall = Math.round(weightedSum / totalWeight);
|
|
818
918
|
const rating = getRating(overall);
|
|
819
|
-
const formulaParts = Array.from(toolOutputs.entries()).map(
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
919
|
+
const formulaParts = Array.from(toolOutputs.entries()).map(
|
|
920
|
+
([name, output]) => {
|
|
921
|
+
const w = weights.get(name) || 10;
|
|
922
|
+
return `(${output.score} \xD7 ${w})`;
|
|
923
|
+
}
|
|
924
|
+
);
|
|
823
925
|
const formulaStr = `[${formulaParts.join(" + ")}] / ${totalWeight} = ${overall}`;
|
|
824
926
|
return {
|
|
825
927
|
overall,
|
|
@@ -951,7 +1053,7 @@ var MODEL_PRICING_PRESETS = {
|
|
|
951
1053
|
contextTier: "frontier",
|
|
952
1054
|
typicalQueriesPerDevPerDay: 150
|
|
953
1055
|
},
|
|
954
|
-
|
|
1056
|
+
copilot: {
|
|
955
1057
|
name: "GitHub Copilot (subscription)",
|
|
956
1058
|
// Amortized per-request cost for a $19/month plan at 80 queries/day
|
|
957
1059
|
pricePer1KInputTokens: 1e-4,
|
|
@@ -1017,9 +1119,18 @@ function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
|
|
|
1017
1119
|
hourlyRate,
|
|
1018
1120
|
totalCost: Math.round(totalCost),
|
|
1019
1121
|
bySeverity: {
|
|
1020
|
-
critical: {
|
|
1021
|
-
|
|
1022
|
-
|
|
1122
|
+
critical: {
|
|
1123
|
+
hours: Math.round(hours.critical * 10) / 10,
|
|
1124
|
+
cost: Math.round(hours.critical * hourlyRate)
|
|
1125
|
+
},
|
|
1126
|
+
major: {
|
|
1127
|
+
hours: Math.round(hours.major * 10) / 10,
|
|
1128
|
+
cost: Math.round(hours.major * hourlyRate)
|
|
1129
|
+
},
|
|
1130
|
+
minor: {
|
|
1131
|
+
hours: Math.round(hours.minor * 10) / 10,
|
|
1132
|
+
cost: Math.round(hours.minor * hourlyRate)
|
|
1133
|
+
}
|
|
1023
1134
|
}
|
|
1024
1135
|
};
|
|
1025
1136
|
}
|
|
@@ -1077,12 +1188,18 @@ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentat
|
|
|
1077
1188
|
const criticalBudget = tierThresholds.criticalTokens;
|
|
1078
1189
|
const idealDepth = tierThresholds.idealDepth;
|
|
1079
1190
|
const budgetRange = criticalBudget - idealBudget;
|
|
1080
|
-
const budgetFactor = Math.min(
|
|
1081
|
-
|
|
1082
|
-
(contextBudget - idealBudget) / budgetRange * 100
|
|
1083
|
-
)
|
|
1084
|
-
const depthFactor = Math.min(
|
|
1085
|
-
|
|
1191
|
+
const budgetFactor = Math.min(
|
|
1192
|
+
100,
|
|
1193
|
+
Math.max(0, (contextBudget - idealBudget) / budgetRange * 100)
|
|
1194
|
+
);
|
|
1195
|
+
const depthFactor = Math.min(
|
|
1196
|
+
100,
|
|
1197
|
+
Math.max(0, (importDepth - idealDepth) * 10)
|
|
1198
|
+
);
|
|
1199
|
+
const fragmentationFactor = Math.min(
|
|
1200
|
+
100,
|
|
1201
|
+
Math.max(0, (fragmentation - 0.3) * 250)
|
|
1202
|
+
);
|
|
1086
1203
|
const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
|
|
1087
1204
|
const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
|
|
1088
1205
|
const score = Math.round(
|
|
@@ -1162,8 +1279,12 @@ function calculateScoreTrend(history) {
|
|
|
1162
1279
|
const now = /* @__PURE__ */ new Date();
|
|
1163
1280
|
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
1164
1281
|
const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
|
|
1165
|
-
const last30Days = history.filter(
|
|
1166
|
-
|
|
1282
|
+
const last30Days = history.filter(
|
|
1283
|
+
(e) => new Date(e.timestamp) >= thirtyDaysAgo
|
|
1284
|
+
);
|
|
1285
|
+
const last90Days = history.filter(
|
|
1286
|
+
(e) => new Date(e.timestamp) >= ninetyDaysAgo
|
|
1287
|
+
);
|
|
1167
1288
|
const currentScore = history[history.length - 1].overallScore;
|
|
1168
1289
|
const thirtyDaysAgoScore = last30Days[0]?.overallScore || currentScore;
|
|
1169
1290
|
const ninetyDaysAgoScore = last90Days[0]?.overallScore || thirtyDaysAgoScore;
|
|
@@ -1176,7 +1297,10 @@ function calculateScoreTrend(history) {
|
|
|
1176
1297
|
if (change30Days > 3) direction = "improving";
|
|
1177
1298
|
else if (change30Days < -3) direction = "degrading";
|
|
1178
1299
|
else direction = "stable";
|
|
1179
|
-
const projectedScore = Math.max(
|
|
1300
|
+
const projectedScore = Math.max(
|
|
1301
|
+
0,
|
|
1302
|
+
Math.min(100, currentScore + velocity * 4)
|
|
1303
|
+
);
|
|
1180
1304
|
return {
|
|
1181
1305
|
direction,
|
|
1182
1306
|
change30Days,
|
|
@@ -1237,9 +1361,13 @@ function calculateKnowledgeConcentration(files, authorData) {
|
|
|
1237
1361
|
recommendations: ["No files to analyze"]
|
|
1238
1362
|
};
|
|
1239
1363
|
}
|
|
1240
|
-
const orphanFiles = files.filter(
|
|
1364
|
+
const orphanFiles = files.filter(
|
|
1365
|
+
(f) => f.exports < 2 && f.imports < 2
|
|
1366
|
+
).length;
|
|
1241
1367
|
const avgExports = files.reduce((sum, f) => sum + f.exports, 0) / files.length;
|
|
1242
|
-
const uniqueConceptFiles = files.filter(
|
|
1368
|
+
const uniqueConceptFiles = files.filter(
|
|
1369
|
+
(f) => f.exports > avgExports * 2
|
|
1370
|
+
).length;
|
|
1243
1371
|
const totalExports = files.reduce((sum, f) => sum + f.exports, 0);
|
|
1244
1372
|
const concentrationRatio = totalExports > 0 ? uniqueConceptFiles / files.length : 0;
|
|
1245
1373
|
let singleAuthorFiles = 0;
|
|
@@ -1251,7 +1379,10 @@ function calculateKnowledgeConcentration(files, authorData) {
|
|
|
1251
1379
|
const orphanRisk = orphanFiles / files.length * 30;
|
|
1252
1380
|
const uniqueRisk = concentrationRatio * 40;
|
|
1253
1381
|
const singleAuthorRisk = authorData ? singleAuthorFiles / files.length * 30 : 0;
|
|
1254
|
-
const score = Math.min(
|
|
1382
|
+
const score = Math.min(
|
|
1383
|
+
100,
|
|
1384
|
+
Math.round(orphanRisk + uniqueRisk + singleAuthorRisk)
|
|
1385
|
+
);
|
|
1255
1386
|
let rating;
|
|
1256
1387
|
if (score < 20) rating = "low";
|
|
1257
1388
|
else if (score < 40) rating = "moderate";
|
|
@@ -1259,13 +1390,19 @@ function calculateKnowledgeConcentration(files, authorData) {
|
|
|
1259
1390
|
else rating = "critical";
|
|
1260
1391
|
const recommendations = [];
|
|
1261
1392
|
if (orphanFiles > files.length * 0.2) {
|
|
1262
|
-
recommendations.push(
|
|
1393
|
+
recommendations.push(
|
|
1394
|
+
`Reduce ${orphanFiles} orphan files by connecting them to main modules`
|
|
1395
|
+
);
|
|
1263
1396
|
}
|
|
1264
1397
|
if (uniqueConceptFiles > files.length * 0.1) {
|
|
1265
|
-
recommendations.push(
|
|
1398
|
+
recommendations.push(
|
|
1399
|
+
"Distribute high-export files into more focused modules"
|
|
1400
|
+
);
|
|
1266
1401
|
}
|
|
1267
1402
|
if (authorData && singleAuthorFiles > files.length * 0.3) {
|
|
1268
|
-
recommendations.push(
|
|
1403
|
+
recommendations.push(
|
|
1404
|
+
"Increase knowledge sharing to reduce single-author dependencies"
|
|
1405
|
+
);
|
|
1269
1406
|
}
|
|
1270
1407
|
return {
|
|
1271
1408
|
score,
|
|
@@ -1419,7 +1556,10 @@ var TypeScriptParser = class {
|
|
|
1419
1556
|
specifiers,
|
|
1420
1557
|
isTypeOnly,
|
|
1421
1558
|
loc: node.loc ? {
|
|
1422
|
-
start: {
|
|
1559
|
+
start: {
|
|
1560
|
+
line: node.loc.start.line,
|
|
1561
|
+
column: node.loc.start.column
|
|
1562
|
+
},
|
|
1423
1563
|
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
1424
1564
|
} : void 0
|
|
1425
1565
|
});
|
|
@@ -1430,11 +1570,16 @@ var TypeScriptParser = class {
|
|
|
1430
1570
|
extractExports(ast, imports) {
|
|
1431
1571
|
const exports2 = [];
|
|
1432
1572
|
const importedNames = new Set(
|
|
1433
|
-
imports.flatMap(
|
|
1573
|
+
imports.flatMap(
|
|
1574
|
+
(imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
|
|
1575
|
+
)
|
|
1434
1576
|
);
|
|
1435
1577
|
for (const node of ast.body) {
|
|
1436
1578
|
if (node.type === "ExportNamedDeclaration" && node.declaration) {
|
|
1437
|
-
const extracted = this.extractFromDeclaration(
|
|
1579
|
+
const extracted = this.extractFromDeclaration(
|
|
1580
|
+
node.declaration,
|
|
1581
|
+
importedNames
|
|
1582
|
+
);
|
|
1438
1583
|
exports2.push(...extracted);
|
|
1439
1584
|
} else if (node.type === "ExportDefaultDeclaration") {
|
|
1440
1585
|
let name = "default";
|
|
@@ -1450,7 +1595,10 @@ var TypeScriptParser = class {
|
|
|
1450
1595
|
name,
|
|
1451
1596
|
type,
|
|
1452
1597
|
loc: node.loc ? {
|
|
1453
|
-
start: {
|
|
1598
|
+
start: {
|
|
1599
|
+
line: node.loc.start.line,
|
|
1600
|
+
column: node.loc.start.column
|
|
1601
|
+
},
|
|
1454
1602
|
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
1455
1603
|
} : void 0
|
|
1456
1604
|
});
|
|
@@ -1472,7 +1620,10 @@ var TypeScriptParser = class {
|
|
|
1472
1620
|
line: declaration.loc.start.line,
|
|
1473
1621
|
column: declaration.loc.start.column
|
|
1474
1622
|
},
|
|
1475
|
-
end: {
|
|
1623
|
+
end: {
|
|
1624
|
+
line: declaration.loc.end.line,
|
|
1625
|
+
column: declaration.loc.end.column
|
|
1626
|
+
}
|
|
1476
1627
|
} : void 0
|
|
1477
1628
|
});
|
|
1478
1629
|
} else if (declaration.type === "ClassDeclaration" && declaration.id) {
|
|
@@ -1484,7 +1635,10 @@ var TypeScriptParser = class {
|
|
|
1484
1635
|
line: declaration.loc.start.line,
|
|
1485
1636
|
column: declaration.loc.start.column
|
|
1486
1637
|
},
|
|
1487
|
-
end: {
|
|
1638
|
+
end: {
|
|
1639
|
+
line: declaration.loc.end.line,
|
|
1640
|
+
column: declaration.loc.end.column
|
|
1641
|
+
}
|
|
1488
1642
|
} : void 0
|
|
1489
1643
|
});
|
|
1490
1644
|
} else if (declaration.type === "VariableDeclaration") {
|
|
@@ -1515,7 +1669,10 @@ var TypeScriptParser = class {
|
|
|
1515
1669
|
line: declaration.loc.start.line,
|
|
1516
1670
|
column: declaration.loc.start.column
|
|
1517
1671
|
},
|
|
1518
|
-
end: {
|
|
1672
|
+
end: {
|
|
1673
|
+
line: declaration.loc.end.line,
|
|
1674
|
+
column: declaration.loc.end.column
|
|
1675
|
+
}
|
|
1519
1676
|
} : void 0
|
|
1520
1677
|
});
|
|
1521
1678
|
} else if (declaration.type === "TSInterfaceDeclaration") {
|
|
@@ -1527,7 +1684,10 @@ var TypeScriptParser = class {
|
|
|
1527
1684
|
line: declaration.loc.start.line,
|
|
1528
1685
|
column: declaration.loc.start.column
|
|
1529
1686
|
},
|
|
1530
|
-
end: {
|
|
1687
|
+
end: {
|
|
1688
|
+
line: declaration.loc.end.line,
|
|
1689
|
+
column: declaration.loc.end.column
|
|
1690
|
+
}
|
|
1531
1691
|
} : void 0
|
|
1532
1692
|
});
|
|
1533
1693
|
}
|
|
@@ -1552,7 +1712,9 @@ var PythonParser = class {
|
|
|
1552
1712
|
try {
|
|
1553
1713
|
this.initialized = true;
|
|
1554
1714
|
} catch (error) {
|
|
1555
|
-
throw new Error(
|
|
1715
|
+
throw new Error(
|
|
1716
|
+
`Failed to initialize Python parser: ${error.message}`
|
|
1717
|
+
);
|
|
1556
1718
|
}
|
|
1557
1719
|
}
|
|
1558
1720
|
parse(code, filePath) {
|
|
@@ -1563,7 +1725,9 @@ var PythonParser = class {
|
|
|
1563
1725
|
exports: exports2,
|
|
1564
1726
|
imports,
|
|
1565
1727
|
language: "python" /* Python */,
|
|
1566
|
-
warnings: [
|
|
1728
|
+
warnings: [
|
|
1729
|
+
"Python parsing is currently using regex-based extraction. Tree-sitter support coming soon."
|
|
1730
|
+
]
|
|
1567
1731
|
};
|
|
1568
1732
|
} catch (error) {
|
|
1569
1733
|
throw new ParseError(
|
|
@@ -1658,7 +1822,7 @@ var PythonParser = class {
|
|
|
1658
1822
|
}
|
|
1659
1823
|
/**
|
|
1660
1824
|
* Regex-based export extraction (temporary implementation)
|
|
1661
|
-
*
|
|
1825
|
+
*
|
|
1662
1826
|
* Python doesn't have explicit exports like JavaScript.
|
|
1663
1827
|
* We extract:
|
|
1664
1828
|
* - Functions defined at module level (def)
|
|
@@ -1826,7 +1990,13 @@ function getSupportedLanguages() {
|
|
|
1826
1990
|
|
|
1827
1991
|
// src/future-proof-metrics.ts
|
|
1828
1992
|
function calculateCognitiveLoad(params) {
|
|
1829
|
-
const {
|
|
1993
|
+
const {
|
|
1994
|
+
linesOfCode,
|
|
1995
|
+
exportCount,
|
|
1996
|
+
importCount,
|
|
1997
|
+
uniqueConcepts,
|
|
1998
|
+
cyclomaticComplexity = 1
|
|
1999
|
+
} = params;
|
|
1830
2000
|
const sizeFactor = {
|
|
1831
2001
|
name: "Size Complexity",
|
|
1832
2002
|
score: Math.min(100, Math.max(0, (linesOfCode - 50) / 10)),
|
|
@@ -1852,7 +2022,12 @@ function calculateCognitiveLoad(params) {
|
|
|
1852
2022
|
weight: 0.2,
|
|
1853
2023
|
description: `${uniqueConcepts} unique concepts`
|
|
1854
2024
|
};
|
|
1855
|
-
const factors = [
|
|
2025
|
+
const factors = [
|
|
2026
|
+
sizeFactor,
|
|
2027
|
+
interfaceFactor,
|
|
2028
|
+
dependencyFactor,
|
|
2029
|
+
conceptFactor
|
|
2030
|
+
];
|
|
1856
2031
|
const score = factors.reduce((sum, f) => sum + f.score * f.weight, 0);
|
|
1857
2032
|
let rating;
|
|
1858
2033
|
if (score < 20) rating = "trivial";
|
|
@@ -1875,7 +2050,10 @@ function calculateCognitiveLoad(params) {
|
|
|
1875
2050
|
function calculateSemanticDistance(params) {
|
|
1876
2051
|
const { file1, file2, file1Domain, file2Domain, sharedDependencies } = params;
|
|
1877
2052
|
const domainDistance = file1Domain === file2Domain ? 0 : file1Domain && file2Domain ? 0.5 : 0.8;
|
|
1878
|
-
const importOverlap = sharedDependencies.length / Math.max(
|
|
2053
|
+
const importOverlap = sharedDependencies.length / Math.max(
|
|
2054
|
+
1,
|
|
2055
|
+
Math.min(params.file1Imports.length, params.file2Imports.length)
|
|
2056
|
+
);
|
|
1879
2057
|
const importDistance = 1 - importOverlap;
|
|
1880
2058
|
const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
|
|
1881
2059
|
let relationship;
|
|
@@ -1883,7 +2061,9 @@ function calculateSemanticDistance(params) {
|
|
|
1883
2061
|
else if (file1Domain === file2Domain) relationship = "same-domain";
|
|
1884
2062
|
else if (sharedDependencies.length > 0) relationship = "cross-domain";
|
|
1885
2063
|
else relationship = "unrelated";
|
|
1886
|
-
const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter(
|
|
2064
|
+
const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter(
|
|
2065
|
+
(s) => typeof s === "string" && s.length > 0
|
|
2066
|
+
);
|
|
1887
2067
|
return {
|
|
1888
2068
|
between: [file1, file2],
|
|
1889
2069
|
distance: Math.round(distance * 100) / 100,
|
|
@@ -1898,7 +2078,11 @@ function calculatePatternEntropy(files) {
|
|
|
1898
2078
|
domain: "unknown",
|
|
1899
2079
|
entropy: 0,
|
|
1900
2080
|
rating: "crystalline",
|
|
1901
|
-
distribution: {
|
|
2081
|
+
distribution: {
|
|
2082
|
+
locationCount: 0,
|
|
2083
|
+
dominantLocation: "",
|
|
2084
|
+
giniCoefficient: 0
|
|
2085
|
+
},
|
|
1902
2086
|
recommendations: ["No files to analyze"]
|
|
1903
2087
|
};
|
|
1904
2088
|
}
|
|
@@ -1938,10 +2122,14 @@ function calculatePatternEntropy(files) {
|
|
|
1938
2122
|
else rating = "chaotic";
|
|
1939
2123
|
const recommendations = [];
|
|
1940
2124
|
if (normalizedEntropy > 0.5) {
|
|
1941
|
-
recommendations.push(
|
|
2125
|
+
recommendations.push(
|
|
2126
|
+
`Consolidate ${files.length} files into fewer directories by domain`
|
|
2127
|
+
);
|
|
1942
2128
|
}
|
|
1943
2129
|
if (dirGroups.size > 5) {
|
|
1944
|
-
recommendations.push(
|
|
2130
|
+
recommendations.push(
|
|
2131
|
+
"Consider barrel exports to reduce directory navigation"
|
|
2132
|
+
);
|
|
1945
2133
|
}
|
|
1946
2134
|
if (gini > 0.5) {
|
|
1947
2135
|
recommendations.push("Redistribute files more evenly across directories");
|
|
@@ -1966,7 +2154,11 @@ function calculateConceptCohesion(params) {
|
|
|
1966
2154
|
return {
|
|
1967
2155
|
score: 1,
|
|
1968
2156
|
rating: "excellent",
|
|
1969
|
-
analysis: {
|
|
2157
|
+
analysis: {
|
|
2158
|
+
uniqueDomains: 0,
|
|
2159
|
+
domainConcentration: 0,
|
|
2160
|
+
exportPurposeClarity: 1
|
|
2161
|
+
}
|
|
1970
2162
|
};
|
|
1971
2163
|
}
|
|
1972
2164
|
const allDomains = [];
|
|
@@ -2072,7 +2264,10 @@ function calculateAiSignalClarity(params) {
|
|
|
2072
2264
|
recommendations: []
|
|
2073
2265
|
};
|
|
2074
2266
|
}
|
|
2075
|
-
const overloadRatio = Math.min(
|
|
2267
|
+
const overloadRatio = Math.min(
|
|
2268
|
+
1,
|
|
2269
|
+
overloadedSymbols / Math.max(1, totalSymbols)
|
|
2270
|
+
);
|
|
2076
2271
|
const overloadSignal = {
|
|
2077
2272
|
name: "Symbol Overloading",
|
|
2078
2273
|
count: overloadedSymbols,
|
|
@@ -2096,7 +2291,10 @@ function calculateAiSignalClarity(params) {
|
|
|
2096
2291
|
// 20% weight
|
|
2097
2292
|
description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
|
|
2098
2293
|
};
|
|
2099
|
-
const sideEffectRatio = Math.min(
|
|
2294
|
+
const sideEffectRatio = Math.min(
|
|
2295
|
+
1,
|
|
2296
|
+
implicitSideEffects / Math.max(1, totalExports)
|
|
2297
|
+
);
|
|
2100
2298
|
const sideEffectSignal = {
|
|
2101
2299
|
name: "Implicit Side Effects",
|
|
2102
2300
|
count: implicitSideEffects,
|
|
@@ -2104,7 +2302,10 @@ function calculateAiSignalClarity(params) {
|
|
|
2104
2302
|
// 15% weight
|
|
2105
2303
|
description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
|
|
2106
2304
|
};
|
|
2107
|
-
const callbackRatio = Math.min(
|
|
2305
|
+
const callbackRatio = Math.min(
|
|
2306
|
+
1,
|
|
2307
|
+
deepCallbacks / Math.max(1, totalSymbols * 0.1)
|
|
2308
|
+
);
|
|
2108
2309
|
const callbackSignal = {
|
|
2109
2310
|
name: "Callback Nesting",
|
|
2110
2311
|
count: deepCallbacks,
|
|
@@ -2112,7 +2313,10 @@ function calculateAiSignalClarity(params) {
|
|
|
2112
2313
|
// 10% weight
|
|
2113
2314
|
description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
|
|
2114
2315
|
};
|
|
2115
|
-
const ambiguousRatio = Math.min(
|
|
2316
|
+
const ambiguousRatio = Math.min(
|
|
2317
|
+
1,
|
|
2318
|
+
ambiguousNames / Math.max(1, totalSymbols)
|
|
2319
|
+
);
|
|
2116
2320
|
const ambiguousSignal = {
|
|
2117
2321
|
name: "Ambiguous Names",
|
|
2118
2322
|
count: ambiguousNames,
|
|
@@ -2120,7 +2324,10 @@ function calculateAiSignalClarity(params) {
|
|
|
2120
2324
|
// 10% weight
|
|
2121
2325
|
description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
|
|
2122
2326
|
};
|
|
2123
|
-
const undocRatio = Math.min(
|
|
2327
|
+
const undocRatio = Math.min(
|
|
2328
|
+
1,
|
|
2329
|
+
undocumentedExports / Math.max(1, totalExports)
|
|
2330
|
+
);
|
|
2124
2331
|
const undocSignal = {
|
|
2125
2332
|
name: "Undocumented Exports",
|
|
2126
2333
|
count: undocumentedExports,
|
|
@@ -2137,30 +2344,45 @@ function calculateAiSignalClarity(params) {
|
|
|
2137
2344
|
ambiguousSignal,
|
|
2138
2345
|
undocSignal
|
|
2139
2346
|
];
|
|
2140
|
-
const score = Math.min(
|
|
2347
|
+
const score = Math.min(
|
|
2348
|
+
100,
|
|
2349
|
+
signals.reduce((sum, s) => sum + s.riskContribution, 0)
|
|
2350
|
+
);
|
|
2141
2351
|
let rating;
|
|
2142
2352
|
if (score < 10) rating = "minimal";
|
|
2143
2353
|
else if (score < 25) rating = "low";
|
|
2144
2354
|
else if (score < 50) rating = "moderate";
|
|
2145
2355
|
else if (score < 75) rating = "high";
|
|
2146
2356
|
else rating = "severe";
|
|
2147
|
-
const topSignal = signals.reduce(
|
|
2357
|
+
const topSignal = signals.reduce(
|
|
2358
|
+
(a, b) => a.riskContribution > b.riskContribution ? a : b
|
|
2359
|
+
);
|
|
2148
2360
|
const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant AI signal claritys detected";
|
|
2149
2361
|
const recommendations = [];
|
|
2150
2362
|
if (overloadSignal.riskContribution > 5) {
|
|
2151
|
-
recommendations.push(
|
|
2363
|
+
recommendations.push(
|
|
2364
|
+
`Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`
|
|
2365
|
+
);
|
|
2152
2366
|
}
|
|
2153
2367
|
if (magicSignal.riskContribution > 5) {
|
|
2154
|
-
recommendations.push(
|
|
2368
|
+
recommendations.push(
|
|
2369
|
+
`Extract ${magicLiterals} magic literals into named constants`
|
|
2370
|
+
);
|
|
2155
2371
|
}
|
|
2156
2372
|
if (trapSignal.riskContribution > 5) {
|
|
2157
|
-
recommendations.push(
|
|
2373
|
+
recommendations.push(
|
|
2374
|
+
`Replace ${booleanTraps} boolean traps with named options objects`
|
|
2375
|
+
);
|
|
2158
2376
|
}
|
|
2159
2377
|
if (undocSignal.riskContribution > 5) {
|
|
2160
|
-
recommendations.push(
|
|
2378
|
+
recommendations.push(
|
|
2379
|
+
`Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`
|
|
2380
|
+
);
|
|
2161
2381
|
}
|
|
2162
2382
|
if (sideEffectSignal.riskContribution > 5) {
|
|
2163
|
-
recommendations.push(
|
|
2383
|
+
recommendations.push(
|
|
2384
|
+
"Mark functions with side effects explicitly in their names or docs"
|
|
2385
|
+
);
|
|
2164
2386
|
}
|
|
2165
2387
|
return {
|
|
2166
2388
|
score: Math.round(score),
|
|
@@ -2185,7 +2407,10 @@ function calculateAgentGrounding(params) {
|
|
|
2185
2407
|
domainVocabularySize
|
|
2186
2408
|
} = params;
|
|
2187
2409
|
const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
|
|
2188
|
-
const structureClarityScore = Math.max(
|
|
2410
|
+
const structureClarityScore = Math.max(
|
|
2411
|
+
0,
|
|
2412
|
+
Math.round(100 - deepDirRatio * 80)
|
|
2413
|
+
);
|
|
2189
2414
|
const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
|
|
2190
2415
|
const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
|
|
2191
2416
|
let entryPointScore = 60;
|
|
@@ -2197,7 +2422,10 @@ function calculateAgentGrounding(params) {
|
|
|
2197
2422
|
const untypedRatio = totalExports > 0 ? untypedExports / totalExports : 0;
|
|
2198
2423
|
const apiClarityScore = Math.max(0, Math.round(100 - untypedRatio * 70));
|
|
2199
2424
|
const inconsistencyRatio = domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize : 0;
|
|
2200
|
-
const domainConsistencyScore = Math.max(
|
|
2425
|
+
const domainConsistencyScore = Math.max(
|
|
2426
|
+
0,
|
|
2427
|
+
Math.round(100 - inconsistencyRatio * 80)
|
|
2428
|
+
);
|
|
2201
2429
|
const score = Math.round(
|
|
2202
2430
|
structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
|
|
2203
2431
|
);
|
|
@@ -2209,21 +2437,33 @@ function calculateAgentGrounding(params) {
|
|
|
2209
2437
|
else rating = "disorienting";
|
|
2210
2438
|
const recommendations = [];
|
|
2211
2439
|
if (structureClarityScore < 70) {
|
|
2212
|
-
recommendations.push(
|
|
2440
|
+
recommendations.push(
|
|
2441
|
+
`Flatten ${deepDirectories} overly-deep directories to improve agent navigation`
|
|
2442
|
+
);
|
|
2213
2443
|
}
|
|
2214
2444
|
if (selfDocumentationScore < 70) {
|
|
2215
|
-
recommendations.push(
|
|
2445
|
+
recommendations.push(
|
|
2446
|
+
`Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`
|
|
2447
|
+
);
|
|
2216
2448
|
}
|
|
2217
2449
|
if (!hasRootReadme) {
|
|
2218
|
-
recommendations.push(
|
|
2450
|
+
recommendations.push(
|
|
2451
|
+
"Add a root README.md so agents understand the project context immediately"
|
|
2452
|
+
);
|
|
2219
2453
|
} else if (!readmeIsFresh) {
|
|
2220
|
-
recommendations.push(
|
|
2454
|
+
recommendations.push(
|
|
2455
|
+
"Update README.md \u2014 stale entry-point documentation disorients agents"
|
|
2456
|
+
);
|
|
2221
2457
|
}
|
|
2222
2458
|
if (apiClarityScore < 70) {
|
|
2223
|
-
recommendations.push(
|
|
2459
|
+
recommendations.push(
|
|
2460
|
+
`Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`
|
|
2461
|
+
);
|
|
2224
2462
|
}
|
|
2225
2463
|
if (domainConsistencyScore < 70) {
|
|
2226
|
-
recommendations.push(
|
|
2464
|
+
recommendations.push(
|
|
2465
|
+
`Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`
|
|
2466
|
+
);
|
|
2227
2467
|
}
|
|
2228
2468
|
return {
|
|
2229
2469
|
score,
|
|
@@ -2256,7 +2496,9 @@ function calculateTestabilityIndex(params) {
|
|
|
2256
2496
|
const purityRatio = totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5;
|
|
2257
2497
|
const purityScore = Math.round(purityRatio * 100);
|
|
2258
2498
|
const injectionRatio = totalClasses > 0 ? injectionPatterns / totalClasses : 0.5;
|
|
2259
|
-
const dependencyInjectionScore = Math.round(
|
|
2499
|
+
const dependencyInjectionScore = Math.round(
|
|
2500
|
+
Math.min(100, injectionRatio * 100)
|
|
2501
|
+
);
|
|
2260
2502
|
const bloatedRatio = totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces : 0;
|
|
2261
2503
|
const interfaceFocusScore = Math.max(0, Math.round(100 - bloatedRatio * 80));
|
|
2262
2504
|
const mutationRatio = totalFunctions > 0 ? externalStateMutations / totalFunctions : 0;
|
|
@@ -2272,25 +2514,36 @@ function calculateTestabilityIndex(params) {
|
|
|
2272
2514
|
else rating = "unverifiable";
|
|
2273
2515
|
let aiChangeSafetyRating;
|
|
2274
2516
|
if (rawCoverageRatio >= 0.5 && score >= 70) aiChangeSafetyRating = "safe";
|
|
2275
|
-
else if (rawCoverageRatio >= 0.2 && score >= 50)
|
|
2517
|
+
else if (rawCoverageRatio >= 0.2 && score >= 50)
|
|
2518
|
+
aiChangeSafetyRating = "moderate-risk";
|
|
2276
2519
|
else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
|
|
2277
2520
|
else aiChangeSafetyRating = "blind-risk";
|
|
2278
2521
|
const recommendations = [];
|
|
2279
2522
|
if (!hasTestFramework) {
|
|
2280
|
-
recommendations.push(
|
|
2523
|
+
recommendations.push(
|
|
2524
|
+
"Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests"
|
|
2525
|
+
);
|
|
2281
2526
|
}
|
|
2282
2527
|
if (rawCoverageRatio < 0.3) {
|
|
2283
2528
|
const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
|
|
2284
|
-
recommendations.push(
|
|
2529
|
+
recommendations.push(
|
|
2530
|
+
`Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`
|
|
2531
|
+
);
|
|
2285
2532
|
}
|
|
2286
2533
|
if (purityScore < 50) {
|
|
2287
|
-
recommendations.push(
|
|
2534
|
+
recommendations.push(
|
|
2535
|
+
"Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
|
|
2536
|
+
);
|
|
2288
2537
|
}
|
|
2289
2538
|
if (dependencyInjectionScore < 50 && totalClasses > 0) {
|
|
2290
|
-
recommendations.push(
|
|
2539
|
+
recommendations.push(
|
|
2540
|
+
"Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable"
|
|
2541
|
+
);
|
|
2291
2542
|
}
|
|
2292
2543
|
if (externalStateMutations > totalFunctions * 0.3) {
|
|
2293
|
-
recommendations.push(
|
|
2544
|
+
recommendations.push(
|
|
2545
|
+
"Reduce direct state mutations \u2014 return values instead to improve observability"
|
|
2546
|
+
);
|
|
2294
2547
|
}
|
|
2295
2548
|
return {
|
|
2296
2549
|
score,
|
|
@@ -2307,7 +2560,12 @@ function calculateTestabilityIndex(params) {
|
|
|
2307
2560
|
};
|
|
2308
2561
|
}
|
|
2309
2562
|
function calculateDocDrift(params) {
|
|
2310
|
-
const {
|
|
2563
|
+
const {
|
|
2564
|
+
uncommentedExports,
|
|
2565
|
+
totalExports,
|
|
2566
|
+
outdatedComments,
|
|
2567
|
+
undocumentedComplexity
|
|
2568
|
+
} = params;
|
|
2311
2569
|
const uncommentedRatio = totalExports > 0 ? uncommentedExports / totalExports : 0;
|
|
2312
2570
|
const outdatedScore = Math.min(100, outdatedComments * 15);
|
|
2313
2571
|
const uncommentedScore = Math.min(100, uncommentedRatio * 100);
|
|
@@ -2324,13 +2582,19 @@ function calculateDocDrift(params) {
|
|
|
2324
2582
|
else rating = "severe";
|
|
2325
2583
|
const recommendations = [];
|
|
2326
2584
|
if (outdatedComments > 0) {
|
|
2327
|
-
recommendations.push(
|
|
2585
|
+
recommendations.push(
|
|
2586
|
+
`Update or remove ${outdatedComments} outdated comments that contradict the code.`
|
|
2587
|
+
);
|
|
2328
2588
|
}
|
|
2329
2589
|
if (uncommentedRatio > 0.3) {
|
|
2330
|
-
recommendations.push(
|
|
2590
|
+
recommendations.push(
|
|
2591
|
+
`Add JSDoc to ${uncommentedExports} uncommented exports.`
|
|
2592
|
+
);
|
|
2331
2593
|
}
|
|
2332
2594
|
if (undocumentedComplexity > 0) {
|
|
2333
|
-
recommendations.push(
|
|
2595
|
+
recommendations.push(
|
|
2596
|
+
`Explain the business logic for ${undocumentedComplexity} highly complex functions.`
|
|
2597
|
+
);
|
|
2334
2598
|
}
|
|
2335
2599
|
return {
|
|
2336
2600
|
score: finalScore,
|
|
@@ -2344,7 +2608,12 @@ function calculateDocDrift(params) {
|
|
|
2344
2608
|
};
|
|
2345
2609
|
}
|
|
2346
2610
|
function calculateDependencyHealth(params) {
|
|
2347
|
-
const {
|
|
2611
|
+
const {
|
|
2612
|
+
totalPackages,
|
|
2613
|
+
outdatedPackages,
|
|
2614
|
+
deprecatedPackages,
|
|
2615
|
+
trainingCutoffSkew
|
|
2616
|
+
} = params;
|
|
2348
2617
|
const outdatedRatio = totalPackages > 0 ? outdatedPackages / totalPackages : 0;
|
|
2349
2618
|
const deprecatedRatio = totalPackages > 0 ? deprecatedPackages / totalPackages : 0;
|
|
2350
2619
|
const outdatedScore = Math.max(0, 100 - outdatedRatio * 200);
|
|
@@ -2359,19 +2628,27 @@ function calculateDependencyHealth(params) {
|
|
|
2359
2628
|
else if (score >= 30) rating = "poor";
|
|
2360
2629
|
else rating = "hazardous";
|
|
2361
2630
|
let aiKnowledgeConfidence;
|
|
2362
|
-
if (trainingCutoffSkew < 0.2 && deprecatedPackages === 0)
|
|
2363
|
-
|
|
2631
|
+
if (trainingCutoffSkew < 0.2 && deprecatedPackages === 0)
|
|
2632
|
+
aiKnowledgeConfidence = "high";
|
|
2633
|
+
else if (trainingCutoffSkew < 0.5 && deprecatedPackages <= 2)
|
|
2634
|
+
aiKnowledgeConfidence = "moderate";
|
|
2364
2635
|
else if (trainingCutoffSkew < 0.8) aiKnowledgeConfidence = "low";
|
|
2365
2636
|
else aiKnowledgeConfidence = "blind";
|
|
2366
2637
|
const recommendations = [];
|
|
2367
2638
|
if (deprecatedPackages > 0) {
|
|
2368
|
-
recommendations.push(
|
|
2639
|
+
recommendations.push(
|
|
2640
|
+
`Replace ${deprecatedPackages} deprecated packages, as AI will struggle to find modern solutions.`
|
|
2641
|
+
);
|
|
2369
2642
|
}
|
|
2370
2643
|
if (outdatedRatio > 0.2) {
|
|
2371
|
-
recommendations.push(
|
|
2644
|
+
recommendations.push(
|
|
2645
|
+
`Update ${outdatedPackages} outdated packages to keep APIs aligned with AI training data.`
|
|
2646
|
+
);
|
|
2372
2647
|
}
|
|
2373
2648
|
if (trainingCutoffSkew > 0.5) {
|
|
2374
|
-
recommendations.push(
|
|
2649
|
+
recommendations.push(
|
|
2650
|
+
"High training cutoff skew detected. AI may hallucinate APIs that were introduced recently."
|
|
2651
|
+
);
|
|
2375
2652
|
}
|
|
2376
2653
|
return {
|
|
2377
2654
|
score,
|
|
@@ -2412,10 +2689,14 @@ function calculateChangeAmplification(params) {
|
|
|
2412
2689
|
else if (score < 90) rating = "contained";
|
|
2413
2690
|
const recommendations = [];
|
|
2414
2691
|
if (score < 70 && hotspots.length > 0) {
|
|
2415
|
-
recommendations.push(
|
|
2692
|
+
recommendations.push(
|
|
2693
|
+
`Refactor top hotspot '${hotspots[0].file}' to reduce coupling (fan-out: ${hotspots[0].fanOut}, fan-in: ${hotspots[0].fanIn}).`
|
|
2694
|
+
);
|
|
2416
2695
|
}
|
|
2417
2696
|
if (maxAmplification > 30) {
|
|
2418
|
-
recommendations.push(
|
|
2697
|
+
recommendations.push(
|
|
2698
|
+
`Break down key bottlenecks with amplification factor > 30.`
|
|
2699
|
+
);
|
|
2419
2700
|
}
|
|
2420
2701
|
return {
|
|
2421
2702
|
score: Math.round(score),
|
|
@@ -2497,7 +2778,11 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2497
2778
|
recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
|
|
2498
2779
|
}
|
|
2499
2780
|
for (const rec of params.agentGrounding.recommendations) {
|
|
2500
|
-
recommendations.push({
|
|
2781
|
+
recommendations.push({
|
|
2782
|
+
action: rec,
|
|
2783
|
+
estimatedImpact: 6,
|
|
2784
|
+
priority: "medium"
|
|
2785
|
+
});
|
|
2501
2786
|
}
|
|
2502
2787
|
for (const rec of params.testability.recommendations) {
|
|
2503
2788
|
const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
|
|
@@ -2508,12 +2793,20 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2508
2793
|
}
|
|
2509
2794
|
if (params.docDrift) {
|
|
2510
2795
|
for (const rec of params.docDrift.recommendations) {
|
|
2511
|
-
recommendations.push({
|
|
2796
|
+
recommendations.push({
|
|
2797
|
+
action: rec,
|
|
2798
|
+
estimatedImpact: 8,
|
|
2799
|
+
priority: "high"
|
|
2800
|
+
});
|
|
2512
2801
|
}
|
|
2513
2802
|
}
|
|
2514
2803
|
if (params.dependencyHealth) {
|
|
2515
2804
|
for (const rec of params.dependencyHealth.recommendations) {
|
|
2516
|
-
recommendations.push({
|
|
2805
|
+
recommendations.push({
|
|
2806
|
+
action: rec,
|
|
2807
|
+
estimatedImpact: 7,
|
|
2808
|
+
priority: "medium"
|
|
2809
|
+
});
|
|
2517
2810
|
}
|
|
2518
2811
|
}
|
|
2519
2812
|
const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
@@ -2609,6 +2902,39 @@ function clearHistory(rootDir) {
|
|
|
2609
2902
|
(0, import_fs4.writeFileSync)(historyPath, JSON.stringify([]));
|
|
2610
2903
|
}
|
|
2611
2904
|
}
|
|
2905
|
+
|
|
2906
|
+
// src/utils/history-git.ts
|
|
2907
|
+
var import_child_process = require("child_process");
|
|
2908
|
+
function getFileCommitTimestamps(file) {
|
|
2909
|
+
const lineStamps = {};
|
|
2910
|
+
try {
|
|
2911
|
+
const output = (0, import_child_process.execSync)(`git blame -t "${file}"`, {
|
|
2912
|
+
encoding: "utf-8",
|
|
2913
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2914
|
+
});
|
|
2915
|
+
const lines = output.split("\n");
|
|
2916
|
+
for (const line of lines) {
|
|
2917
|
+
if (!line) continue;
|
|
2918
|
+
const match = line.match(/^\S+\s+\(.*?(\d{10,})\s+[-+]\d+\s+(\d+)\)/);
|
|
2919
|
+
if (match) {
|
|
2920
|
+
const ts = parseInt(match[1], 10);
|
|
2921
|
+
const ln = parseInt(match[2], 10);
|
|
2922
|
+
lineStamps[ln] = ts;
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
} catch {
|
|
2926
|
+
}
|
|
2927
|
+
return lineStamps;
|
|
2928
|
+
}
|
|
2929
|
+
function getLineRangeLastModifiedCached(lineStamps, startLine, endLine) {
|
|
2930
|
+
let latest = 0;
|
|
2931
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
2932
|
+
if (lineStamps[i] && lineStamps[i] > latest) {
|
|
2933
|
+
latest = lineStamps[i];
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
return latest;
|
|
2937
|
+
}
|
|
2612
2938
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2613
2939
|
0 && (module.exports = {
|
|
2614
2940
|
CONTEXT_TIER_THRESHOLDS,
|
|
@@ -2624,6 +2950,7 @@ function clearHistory(rootDir) {
|
|
|
2624
2950
|
SIZE_ADJUSTED_THRESHOLDS,
|
|
2625
2951
|
TOOL_NAME_MAP,
|
|
2626
2952
|
TypeScriptParser,
|
|
2953
|
+
VAGUE_FILE_NAMES,
|
|
2627
2954
|
calculateAgentGrounding,
|
|
2628
2955
|
calculateAiSignalClarity,
|
|
2629
2956
|
calculateChangeAmplification,
|
|
@@ -2658,8 +2985,10 @@ function clearHistory(rootDir) {
|
|
|
2658
2985
|
generateHTML,
|
|
2659
2986
|
getDebtBreakdown,
|
|
2660
2987
|
getElapsedTime,
|
|
2988
|
+
getFileCommitTimestamps,
|
|
2661
2989
|
getFileExtension,
|
|
2662
2990
|
getHistorySummary,
|
|
2991
|
+
getLineRangeLastModifiedCached,
|
|
2663
2992
|
getModelPreset,
|
|
2664
2993
|
getParser,
|
|
2665
2994
|
getProjectSizeTier,
|
|
@@ -2685,5 +3014,6 @@ function clearHistory(rootDir) {
|
|
|
2685
3014
|
readFileContent,
|
|
2686
3015
|
resolveOutputPath,
|
|
2687
3016
|
saveScoreEntry,
|
|
3017
|
+
scanEntries,
|
|
2688
3018
|
scanFiles
|
|
2689
3019
|
});
|