@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.mjs
CHANGED
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
getToolWeight,
|
|
19
19
|
normalizeToolName,
|
|
20
20
|
parseWeightString
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-HFLFBA6F.mjs";
|
|
22
22
|
|
|
23
23
|
// src/utils/file-scanner.ts
|
|
24
24
|
import { glob } from "glob";
|
|
25
25
|
import { readFile } from "fs/promises";
|
|
26
26
|
import { existsSync } from "fs";
|
|
27
|
-
import { join, relative } from "path";
|
|
27
|
+
import { join, relative, dirname } from "path";
|
|
28
28
|
import ignorePkg from "ignore";
|
|
29
29
|
var DEFAULT_EXCLUDE = [
|
|
30
30
|
// Dependencies
|
|
@@ -40,6 +40,8 @@ var DEFAULT_EXCLUDE = [
|
|
|
40
40
|
"**/cdk.out/**",
|
|
41
41
|
// Framework-specific build dirs
|
|
42
42
|
"**/.next/**",
|
|
43
|
+
"**/.sst/**",
|
|
44
|
+
"**/.open-next/**",
|
|
43
45
|
"**/.nuxt/**",
|
|
44
46
|
"**/.vuepress/**",
|
|
45
47
|
"**/.cache/**",
|
|
@@ -71,6 +73,28 @@ var DEFAULT_EXCLUDE = [
|
|
|
71
73
|
"**/*.log",
|
|
72
74
|
"**/.DS_Store"
|
|
73
75
|
];
|
|
76
|
+
var VAGUE_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
77
|
+
"utils",
|
|
78
|
+
"helpers",
|
|
79
|
+
"helper",
|
|
80
|
+
"misc",
|
|
81
|
+
"common",
|
|
82
|
+
"shared",
|
|
83
|
+
"tools",
|
|
84
|
+
"util",
|
|
85
|
+
"lib",
|
|
86
|
+
"libs",
|
|
87
|
+
"stuff",
|
|
88
|
+
"functions",
|
|
89
|
+
"methods",
|
|
90
|
+
"handlers",
|
|
91
|
+
"data",
|
|
92
|
+
"temp",
|
|
93
|
+
"tmp",
|
|
94
|
+
"test-utils",
|
|
95
|
+
"test-helpers",
|
|
96
|
+
"mocks"
|
|
97
|
+
]);
|
|
74
98
|
async function scanFiles(options) {
|
|
75
99
|
const {
|
|
76
100
|
rootDir,
|
|
@@ -88,18 +112,35 @@ async function scanFiles(options) {
|
|
|
88
112
|
ignoreFromFile = [];
|
|
89
113
|
}
|
|
90
114
|
}
|
|
91
|
-
const
|
|
115
|
+
const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
|
|
116
|
+
const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
|
|
117
|
+
const finalExclude = [
|
|
118
|
+
.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
|
|
119
|
+
];
|
|
92
120
|
const files = await glob(include, {
|
|
93
121
|
cwd: rootDir,
|
|
94
122
|
ignore: finalExclude,
|
|
95
123
|
absolute: true
|
|
96
124
|
});
|
|
97
|
-
const
|
|
98
|
-
|
|
125
|
+
const gitignoreFiles = await glob("**/.gitignore", {
|
|
126
|
+
cwd: rootDir,
|
|
127
|
+
ignore: finalExclude,
|
|
128
|
+
absolute: true
|
|
129
|
+
});
|
|
130
|
+
if (gitignoreFiles.length > 0) {
|
|
99
131
|
try {
|
|
100
|
-
const gitTxt = await readFile(gitignorePath, "utf-8");
|
|
101
132
|
const ig = ignorePkg();
|
|
102
|
-
|
|
133
|
+
for (const gitignorePath of gitignoreFiles) {
|
|
134
|
+
const gitTxt = await readFile(gitignorePath, "utf-8");
|
|
135
|
+
const gitignoreDir = dirname(gitignorePath);
|
|
136
|
+
const relativePrefix = relative(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
|
|
137
|
+
const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
|
|
138
|
+
if (relativePrefix === "." || relativePrefix === "") {
|
|
139
|
+
ig.add(patterns);
|
|
140
|
+
} else {
|
|
141
|
+
ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
103
144
|
const filtered = files.filter((f) => {
|
|
104
145
|
let rel = relative(rootDir || ".", f).replace(/\\/g, "/");
|
|
105
146
|
if (rel === "") rel = f;
|
|
@@ -112,6 +153,54 @@ async function scanFiles(options) {
|
|
|
112
153
|
}
|
|
113
154
|
return files;
|
|
114
155
|
}
|
|
156
|
+
async function scanEntries(options) {
|
|
157
|
+
const files = await scanFiles(options);
|
|
158
|
+
const { rootDir, include = ["**/*"], exclude, includeTests } = options;
|
|
159
|
+
const ignoreFilePath = join(rootDir || ".", ".aireadyignore");
|
|
160
|
+
let ignoreFromFile = [];
|
|
161
|
+
if (existsSync(ignoreFilePath)) {
|
|
162
|
+
try {
|
|
163
|
+
const txt = await readFile(ignoreFilePath, "utf-8");
|
|
164
|
+
ignoreFromFile = txt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#")).filter((l) => !l.startsWith("!"));
|
|
165
|
+
} catch (e) {
|
|
166
|
+
ignoreFromFile = [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
|
|
170
|
+
const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
|
|
171
|
+
const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])];
|
|
172
|
+
const dirs = await glob("**/", {
|
|
173
|
+
cwd: rootDir,
|
|
174
|
+
ignore: finalExclude,
|
|
175
|
+
absolute: true
|
|
176
|
+
});
|
|
177
|
+
const gitignoreFiles = await glob("**/.gitignore", {
|
|
178
|
+
cwd: rootDir,
|
|
179
|
+
ignore: finalExclude,
|
|
180
|
+
absolute: true
|
|
181
|
+
});
|
|
182
|
+
if (gitignoreFiles.length > 0) {
|
|
183
|
+
const ig = ignorePkg();
|
|
184
|
+
for (const gitignorePath of gitignoreFiles) {
|
|
185
|
+
const gitTxt = await readFile(gitignorePath, "utf-8");
|
|
186
|
+
const gitignoreDir = dirname(gitignorePath);
|
|
187
|
+
const relativePrefix = relative(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
|
|
188
|
+
const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
|
|
189
|
+
if (relativePrefix === "." || relativePrefix === "") {
|
|
190
|
+
ig.add(patterns);
|
|
191
|
+
} else {
|
|
192
|
+
ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const filteredDirs = dirs.filter((d) => {
|
|
196
|
+
let rel = relative(rootDir || ".", d).replace(/\\/g, "/").replace(/\/$/, "");
|
|
197
|
+
if (rel === "") return true;
|
|
198
|
+
return !ig.ignores(rel);
|
|
199
|
+
});
|
|
200
|
+
return { files, dirs: filteredDirs };
|
|
201
|
+
}
|
|
202
|
+
return { files, dirs };
|
|
203
|
+
}
|
|
115
204
|
async function readFileContent(filePath) {
|
|
116
205
|
return readFile(filePath, "utf-8");
|
|
117
206
|
}
|
|
@@ -304,7 +393,7 @@ function estimateTokens(text) {
|
|
|
304
393
|
|
|
305
394
|
// src/utils/config.ts
|
|
306
395
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
307
|
-
import { join as join2, resolve, dirname } from "path";
|
|
396
|
+
import { join as join2, resolve, dirname as dirname2 } from "path";
|
|
308
397
|
import { pathToFileURL } from "url";
|
|
309
398
|
var CONFIG_FILES = [
|
|
310
399
|
"aiready.json",
|
|
@@ -336,11 +425,18 @@ async function loadConfig(rootDir) {
|
|
|
336
425
|
return config;
|
|
337
426
|
} catch (error) {
|
|
338
427
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
339
|
-
|
|
428
|
+
const e = new Error(
|
|
429
|
+
`Failed to load config from ${configPath}: ${errorMessage}`
|
|
430
|
+
);
|
|
431
|
+
try {
|
|
432
|
+
e.cause = error instanceof Error ? error : void 0;
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
throw e;
|
|
340
436
|
}
|
|
341
437
|
}
|
|
342
438
|
}
|
|
343
|
-
const parent =
|
|
439
|
+
const parent = dirname2(currentDir);
|
|
344
440
|
if (parent === currentDir) {
|
|
345
441
|
break;
|
|
346
442
|
}
|
|
@@ -373,7 +469,7 @@ function mergeConfigWithDefaults(userConfig, defaults) {
|
|
|
373
469
|
|
|
374
470
|
// src/utils/cli-helpers.ts
|
|
375
471
|
import { writeFileSync, mkdirSync, existsSync as existsSync3 } from "fs";
|
|
376
|
-
import { join as join3, dirname as
|
|
472
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
377
473
|
function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()) {
|
|
378
474
|
let outputPath;
|
|
379
475
|
if (userPath) {
|
|
@@ -382,7 +478,7 @@ function resolveOutputPath(userPath, defaultFilename, workingDir = process.cwd()
|
|
|
382
478
|
const aireadyDir = join3(workingDir, ".aiready");
|
|
383
479
|
outputPath = join3(aireadyDir, defaultFilename);
|
|
384
480
|
}
|
|
385
|
-
const parentDir =
|
|
481
|
+
const parentDir = dirname3(outputPath);
|
|
386
482
|
if (!existsSync3(parentDir)) {
|
|
387
483
|
mkdirSync(parentDir, { recursive: true });
|
|
388
484
|
}
|
|
@@ -400,7 +496,7 @@ async function loadMergedConfig(directory, defaults, cliOptions) {
|
|
|
400
496
|
}
|
|
401
497
|
function handleJSONOutput(data, outputFile, successMessage) {
|
|
402
498
|
if (outputFile) {
|
|
403
|
-
const dir =
|
|
499
|
+
const dir = dirname3(outputFile);
|
|
404
500
|
if (!existsSync3(dir)) {
|
|
405
501
|
mkdirSync(dir, { recursive: true });
|
|
406
502
|
}
|
|
@@ -476,7 +572,7 @@ var MODEL_PRICING_PRESETS = {
|
|
|
476
572
|
contextTier: "frontier",
|
|
477
573
|
typicalQueriesPerDevPerDay: 150
|
|
478
574
|
},
|
|
479
|
-
|
|
575
|
+
copilot: {
|
|
480
576
|
name: "GitHub Copilot (subscription)",
|
|
481
577
|
// Amortized per-request cost for a $19/month plan at 80 queries/day
|
|
482
578
|
pricePer1KInputTokens: 1e-4,
|
|
@@ -542,9 +638,18 @@ function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
|
|
|
542
638
|
hourlyRate,
|
|
543
639
|
totalCost: Math.round(totalCost),
|
|
544
640
|
bySeverity: {
|
|
545
|
-
critical: {
|
|
546
|
-
|
|
547
|
-
|
|
641
|
+
critical: {
|
|
642
|
+
hours: Math.round(hours.critical * 10) / 10,
|
|
643
|
+
cost: Math.round(hours.critical * hourlyRate)
|
|
644
|
+
},
|
|
645
|
+
major: {
|
|
646
|
+
hours: Math.round(hours.major * 10) / 10,
|
|
647
|
+
cost: Math.round(hours.major * hourlyRate)
|
|
648
|
+
},
|
|
649
|
+
minor: {
|
|
650
|
+
hours: Math.round(hours.minor * 10) / 10,
|
|
651
|
+
cost: Math.round(hours.minor * hourlyRate)
|
|
652
|
+
}
|
|
548
653
|
}
|
|
549
654
|
};
|
|
550
655
|
}
|
|
@@ -602,12 +707,18 @@ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentat
|
|
|
602
707
|
const criticalBudget = tierThresholds.criticalTokens;
|
|
603
708
|
const idealDepth = tierThresholds.idealDepth;
|
|
604
709
|
const budgetRange = criticalBudget - idealBudget;
|
|
605
|
-
const budgetFactor = Math.min(
|
|
606
|
-
|
|
607
|
-
(contextBudget - idealBudget) / budgetRange * 100
|
|
608
|
-
)
|
|
609
|
-
const depthFactor = Math.min(
|
|
610
|
-
|
|
710
|
+
const budgetFactor = Math.min(
|
|
711
|
+
100,
|
|
712
|
+
Math.max(0, (contextBudget - idealBudget) / budgetRange * 100)
|
|
713
|
+
);
|
|
714
|
+
const depthFactor = Math.min(
|
|
715
|
+
100,
|
|
716
|
+
Math.max(0, (importDepth - idealDepth) * 10)
|
|
717
|
+
);
|
|
718
|
+
const fragmentationFactor = Math.min(
|
|
719
|
+
100,
|
|
720
|
+
Math.max(0, (fragmentation - 0.3) * 250)
|
|
721
|
+
);
|
|
611
722
|
const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
|
|
612
723
|
const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
|
|
613
724
|
const score = Math.round(
|
|
@@ -687,8 +798,12 @@ function calculateScoreTrend(history) {
|
|
|
687
798
|
const now = /* @__PURE__ */ new Date();
|
|
688
799
|
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
689
800
|
const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
|
|
690
|
-
const last30Days = history.filter(
|
|
691
|
-
|
|
801
|
+
const last30Days = history.filter(
|
|
802
|
+
(e) => new Date(e.timestamp) >= thirtyDaysAgo
|
|
803
|
+
);
|
|
804
|
+
const last90Days = history.filter(
|
|
805
|
+
(e) => new Date(e.timestamp) >= ninetyDaysAgo
|
|
806
|
+
);
|
|
692
807
|
const currentScore = history[history.length - 1].overallScore;
|
|
693
808
|
const thirtyDaysAgoScore = last30Days[0]?.overallScore || currentScore;
|
|
694
809
|
const ninetyDaysAgoScore = last90Days[0]?.overallScore || thirtyDaysAgoScore;
|
|
@@ -701,7 +816,10 @@ function calculateScoreTrend(history) {
|
|
|
701
816
|
if (change30Days > 3) direction = "improving";
|
|
702
817
|
else if (change30Days < -3) direction = "degrading";
|
|
703
818
|
else direction = "stable";
|
|
704
|
-
const projectedScore = Math.max(
|
|
819
|
+
const projectedScore = Math.max(
|
|
820
|
+
0,
|
|
821
|
+
Math.min(100, currentScore + velocity * 4)
|
|
822
|
+
);
|
|
705
823
|
return {
|
|
706
824
|
direction,
|
|
707
825
|
change30Days,
|
|
@@ -762,9 +880,13 @@ function calculateKnowledgeConcentration(files, authorData) {
|
|
|
762
880
|
recommendations: ["No files to analyze"]
|
|
763
881
|
};
|
|
764
882
|
}
|
|
765
|
-
const orphanFiles = files.filter(
|
|
883
|
+
const orphanFiles = files.filter(
|
|
884
|
+
(f) => f.exports < 2 && f.imports < 2
|
|
885
|
+
).length;
|
|
766
886
|
const avgExports = files.reduce((sum, f) => sum + f.exports, 0) / files.length;
|
|
767
|
-
const uniqueConceptFiles = files.filter(
|
|
887
|
+
const uniqueConceptFiles = files.filter(
|
|
888
|
+
(f) => f.exports > avgExports * 2
|
|
889
|
+
).length;
|
|
768
890
|
const totalExports = files.reduce((sum, f) => sum + f.exports, 0);
|
|
769
891
|
const concentrationRatio = totalExports > 0 ? uniqueConceptFiles / files.length : 0;
|
|
770
892
|
let singleAuthorFiles = 0;
|
|
@@ -776,7 +898,10 @@ function calculateKnowledgeConcentration(files, authorData) {
|
|
|
776
898
|
const orphanRisk = orphanFiles / files.length * 30;
|
|
777
899
|
const uniqueRisk = concentrationRatio * 40;
|
|
778
900
|
const singleAuthorRisk = authorData ? singleAuthorFiles / files.length * 30 : 0;
|
|
779
|
-
const score = Math.min(
|
|
901
|
+
const score = Math.min(
|
|
902
|
+
100,
|
|
903
|
+
Math.round(orphanRisk + uniqueRisk + singleAuthorRisk)
|
|
904
|
+
);
|
|
780
905
|
let rating;
|
|
781
906
|
if (score < 20) rating = "low";
|
|
782
907
|
else if (score < 40) rating = "moderate";
|
|
@@ -784,13 +909,19 @@ function calculateKnowledgeConcentration(files, authorData) {
|
|
|
784
909
|
else rating = "critical";
|
|
785
910
|
const recommendations = [];
|
|
786
911
|
if (orphanFiles > files.length * 0.2) {
|
|
787
|
-
recommendations.push(
|
|
912
|
+
recommendations.push(
|
|
913
|
+
`Reduce ${orphanFiles} orphan files by connecting them to main modules`
|
|
914
|
+
);
|
|
788
915
|
}
|
|
789
916
|
if (uniqueConceptFiles > files.length * 0.1) {
|
|
790
|
-
recommendations.push(
|
|
917
|
+
recommendations.push(
|
|
918
|
+
"Distribute high-export files into more focused modules"
|
|
919
|
+
);
|
|
791
920
|
}
|
|
792
921
|
if (authorData && singleAuthorFiles > files.length * 0.3) {
|
|
793
|
-
recommendations.push(
|
|
922
|
+
recommendations.push(
|
|
923
|
+
"Increase knowledge sharing to reduce single-author dependencies"
|
|
924
|
+
);
|
|
794
925
|
}
|
|
795
926
|
return {
|
|
796
927
|
score,
|
|
@@ -944,7 +1075,10 @@ var TypeScriptParser = class {
|
|
|
944
1075
|
specifiers,
|
|
945
1076
|
isTypeOnly,
|
|
946
1077
|
loc: node.loc ? {
|
|
947
|
-
start: {
|
|
1078
|
+
start: {
|
|
1079
|
+
line: node.loc.start.line,
|
|
1080
|
+
column: node.loc.start.column
|
|
1081
|
+
},
|
|
948
1082
|
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
949
1083
|
} : void 0
|
|
950
1084
|
});
|
|
@@ -955,11 +1089,16 @@ var TypeScriptParser = class {
|
|
|
955
1089
|
extractExports(ast, imports) {
|
|
956
1090
|
const exports = [];
|
|
957
1091
|
const importedNames = new Set(
|
|
958
|
-
imports.flatMap(
|
|
1092
|
+
imports.flatMap(
|
|
1093
|
+
(imp) => imp.specifiers.filter((s) => s !== "*" && s !== "default")
|
|
1094
|
+
)
|
|
959
1095
|
);
|
|
960
1096
|
for (const node of ast.body) {
|
|
961
1097
|
if (node.type === "ExportNamedDeclaration" && node.declaration) {
|
|
962
|
-
const extracted = this.extractFromDeclaration(
|
|
1098
|
+
const extracted = this.extractFromDeclaration(
|
|
1099
|
+
node.declaration,
|
|
1100
|
+
importedNames
|
|
1101
|
+
);
|
|
963
1102
|
exports.push(...extracted);
|
|
964
1103
|
} else if (node.type === "ExportDefaultDeclaration") {
|
|
965
1104
|
let name = "default";
|
|
@@ -975,7 +1114,10 @@ var TypeScriptParser = class {
|
|
|
975
1114
|
name,
|
|
976
1115
|
type,
|
|
977
1116
|
loc: node.loc ? {
|
|
978
|
-
start: {
|
|
1117
|
+
start: {
|
|
1118
|
+
line: node.loc.start.line,
|
|
1119
|
+
column: node.loc.start.column
|
|
1120
|
+
},
|
|
979
1121
|
end: { line: node.loc.end.line, column: node.loc.end.column }
|
|
980
1122
|
} : void 0
|
|
981
1123
|
});
|
|
@@ -997,7 +1139,10 @@ var TypeScriptParser = class {
|
|
|
997
1139
|
line: declaration.loc.start.line,
|
|
998
1140
|
column: declaration.loc.start.column
|
|
999
1141
|
},
|
|
1000
|
-
end: {
|
|
1142
|
+
end: {
|
|
1143
|
+
line: declaration.loc.end.line,
|
|
1144
|
+
column: declaration.loc.end.column
|
|
1145
|
+
}
|
|
1001
1146
|
} : void 0
|
|
1002
1147
|
});
|
|
1003
1148
|
} else if (declaration.type === "ClassDeclaration" && declaration.id) {
|
|
@@ -1009,7 +1154,10 @@ var TypeScriptParser = class {
|
|
|
1009
1154
|
line: declaration.loc.start.line,
|
|
1010
1155
|
column: declaration.loc.start.column
|
|
1011
1156
|
},
|
|
1012
|
-
end: {
|
|
1157
|
+
end: {
|
|
1158
|
+
line: declaration.loc.end.line,
|
|
1159
|
+
column: declaration.loc.end.column
|
|
1160
|
+
}
|
|
1013
1161
|
} : void 0
|
|
1014
1162
|
});
|
|
1015
1163
|
} else if (declaration.type === "VariableDeclaration") {
|
|
@@ -1040,7 +1188,10 @@ var TypeScriptParser = class {
|
|
|
1040
1188
|
line: declaration.loc.start.line,
|
|
1041
1189
|
column: declaration.loc.start.column
|
|
1042
1190
|
},
|
|
1043
|
-
end: {
|
|
1191
|
+
end: {
|
|
1192
|
+
line: declaration.loc.end.line,
|
|
1193
|
+
column: declaration.loc.end.column
|
|
1194
|
+
}
|
|
1044
1195
|
} : void 0
|
|
1045
1196
|
});
|
|
1046
1197
|
} else if (declaration.type === "TSInterfaceDeclaration") {
|
|
@@ -1052,7 +1203,10 @@ var TypeScriptParser = class {
|
|
|
1052
1203
|
line: declaration.loc.start.line,
|
|
1053
1204
|
column: declaration.loc.start.column
|
|
1054
1205
|
},
|
|
1055
|
-
end: {
|
|
1206
|
+
end: {
|
|
1207
|
+
line: declaration.loc.end.line,
|
|
1208
|
+
column: declaration.loc.end.column
|
|
1209
|
+
}
|
|
1056
1210
|
} : void 0
|
|
1057
1211
|
});
|
|
1058
1212
|
}
|
|
@@ -1077,7 +1231,9 @@ var PythonParser = class {
|
|
|
1077
1231
|
try {
|
|
1078
1232
|
this.initialized = true;
|
|
1079
1233
|
} catch (error) {
|
|
1080
|
-
throw new Error(
|
|
1234
|
+
throw new Error(
|
|
1235
|
+
`Failed to initialize Python parser: ${error.message}`
|
|
1236
|
+
);
|
|
1081
1237
|
}
|
|
1082
1238
|
}
|
|
1083
1239
|
parse(code, filePath) {
|
|
@@ -1088,7 +1244,9 @@ var PythonParser = class {
|
|
|
1088
1244
|
exports,
|
|
1089
1245
|
imports,
|
|
1090
1246
|
language: "python" /* Python */,
|
|
1091
|
-
warnings: [
|
|
1247
|
+
warnings: [
|
|
1248
|
+
"Python parsing is currently using regex-based extraction. Tree-sitter support coming soon."
|
|
1249
|
+
]
|
|
1092
1250
|
};
|
|
1093
1251
|
} catch (error) {
|
|
1094
1252
|
throw new ParseError(
|
|
@@ -1183,7 +1341,7 @@ var PythonParser = class {
|
|
|
1183
1341
|
}
|
|
1184
1342
|
/**
|
|
1185
1343
|
* Regex-based export extraction (temporary implementation)
|
|
1186
|
-
*
|
|
1344
|
+
*
|
|
1187
1345
|
* Python doesn't have explicit exports like JavaScript.
|
|
1188
1346
|
* We extract:
|
|
1189
1347
|
* - Functions defined at module level (def)
|
|
@@ -1351,7 +1509,13 @@ function getSupportedLanguages() {
|
|
|
1351
1509
|
|
|
1352
1510
|
// src/future-proof-metrics.ts
|
|
1353
1511
|
function calculateCognitiveLoad(params) {
|
|
1354
|
-
const {
|
|
1512
|
+
const {
|
|
1513
|
+
linesOfCode,
|
|
1514
|
+
exportCount,
|
|
1515
|
+
importCount,
|
|
1516
|
+
uniqueConcepts,
|
|
1517
|
+
cyclomaticComplexity = 1
|
|
1518
|
+
} = params;
|
|
1355
1519
|
const sizeFactor = {
|
|
1356
1520
|
name: "Size Complexity",
|
|
1357
1521
|
score: Math.min(100, Math.max(0, (linesOfCode - 50) / 10)),
|
|
@@ -1377,7 +1541,12 @@ function calculateCognitiveLoad(params) {
|
|
|
1377
1541
|
weight: 0.2,
|
|
1378
1542
|
description: `${uniqueConcepts} unique concepts`
|
|
1379
1543
|
};
|
|
1380
|
-
const factors = [
|
|
1544
|
+
const factors = [
|
|
1545
|
+
sizeFactor,
|
|
1546
|
+
interfaceFactor,
|
|
1547
|
+
dependencyFactor,
|
|
1548
|
+
conceptFactor
|
|
1549
|
+
];
|
|
1381
1550
|
const score = factors.reduce((sum, f) => sum + f.score * f.weight, 0);
|
|
1382
1551
|
let rating;
|
|
1383
1552
|
if (score < 20) rating = "trivial";
|
|
@@ -1400,7 +1569,10 @@ function calculateCognitiveLoad(params) {
|
|
|
1400
1569
|
function calculateSemanticDistance(params) {
|
|
1401
1570
|
const { file1, file2, file1Domain, file2Domain, sharedDependencies } = params;
|
|
1402
1571
|
const domainDistance = file1Domain === file2Domain ? 0 : file1Domain && file2Domain ? 0.5 : 0.8;
|
|
1403
|
-
const importOverlap = sharedDependencies.length / Math.max(
|
|
1572
|
+
const importOverlap = sharedDependencies.length / Math.max(
|
|
1573
|
+
1,
|
|
1574
|
+
Math.min(params.file1Imports.length, params.file2Imports.length)
|
|
1575
|
+
);
|
|
1404
1576
|
const importDistance = 1 - importOverlap;
|
|
1405
1577
|
const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
|
|
1406
1578
|
let relationship;
|
|
@@ -1408,7 +1580,9 @@ function calculateSemanticDistance(params) {
|
|
|
1408
1580
|
else if (file1Domain === file2Domain) relationship = "same-domain";
|
|
1409
1581
|
else if (sharedDependencies.length > 0) relationship = "cross-domain";
|
|
1410
1582
|
else relationship = "unrelated";
|
|
1411
|
-
const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter(
|
|
1583
|
+
const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter(
|
|
1584
|
+
(s) => typeof s === "string" && s.length > 0
|
|
1585
|
+
);
|
|
1412
1586
|
return {
|
|
1413
1587
|
between: [file1, file2],
|
|
1414
1588
|
distance: Math.round(distance * 100) / 100,
|
|
@@ -1423,7 +1597,11 @@ function calculatePatternEntropy(files) {
|
|
|
1423
1597
|
domain: "unknown",
|
|
1424
1598
|
entropy: 0,
|
|
1425
1599
|
rating: "crystalline",
|
|
1426
|
-
distribution: {
|
|
1600
|
+
distribution: {
|
|
1601
|
+
locationCount: 0,
|
|
1602
|
+
dominantLocation: "",
|
|
1603
|
+
giniCoefficient: 0
|
|
1604
|
+
},
|
|
1427
1605
|
recommendations: ["No files to analyze"]
|
|
1428
1606
|
};
|
|
1429
1607
|
}
|
|
@@ -1463,10 +1641,14 @@ function calculatePatternEntropy(files) {
|
|
|
1463
1641
|
else rating = "chaotic";
|
|
1464
1642
|
const recommendations = [];
|
|
1465
1643
|
if (normalizedEntropy > 0.5) {
|
|
1466
|
-
recommendations.push(
|
|
1644
|
+
recommendations.push(
|
|
1645
|
+
`Consolidate ${files.length} files into fewer directories by domain`
|
|
1646
|
+
);
|
|
1467
1647
|
}
|
|
1468
1648
|
if (dirGroups.size > 5) {
|
|
1469
|
-
recommendations.push(
|
|
1649
|
+
recommendations.push(
|
|
1650
|
+
"Consider barrel exports to reduce directory navigation"
|
|
1651
|
+
);
|
|
1470
1652
|
}
|
|
1471
1653
|
if (gini > 0.5) {
|
|
1472
1654
|
recommendations.push("Redistribute files more evenly across directories");
|
|
@@ -1491,7 +1673,11 @@ function calculateConceptCohesion(params) {
|
|
|
1491
1673
|
return {
|
|
1492
1674
|
score: 1,
|
|
1493
1675
|
rating: "excellent",
|
|
1494
|
-
analysis: {
|
|
1676
|
+
analysis: {
|
|
1677
|
+
uniqueDomains: 0,
|
|
1678
|
+
domainConcentration: 0,
|
|
1679
|
+
exportPurposeClarity: 1
|
|
1680
|
+
}
|
|
1495
1681
|
};
|
|
1496
1682
|
}
|
|
1497
1683
|
const allDomains = [];
|
|
@@ -1597,7 +1783,10 @@ function calculateAiSignalClarity(params) {
|
|
|
1597
1783
|
recommendations: []
|
|
1598
1784
|
};
|
|
1599
1785
|
}
|
|
1600
|
-
const overloadRatio = Math.min(
|
|
1786
|
+
const overloadRatio = Math.min(
|
|
1787
|
+
1,
|
|
1788
|
+
overloadedSymbols / Math.max(1, totalSymbols)
|
|
1789
|
+
);
|
|
1601
1790
|
const overloadSignal = {
|
|
1602
1791
|
name: "Symbol Overloading",
|
|
1603
1792
|
count: overloadedSymbols,
|
|
@@ -1621,7 +1810,10 @@ function calculateAiSignalClarity(params) {
|
|
|
1621
1810
|
// 20% weight
|
|
1622
1811
|
description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
|
|
1623
1812
|
};
|
|
1624
|
-
const sideEffectRatio = Math.min(
|
|
1813
|
+
const sideEffectRatio = Math.min(
|
|
1814
|
+
1,
|
|
1815
|
+
implicitSideEffects / Math.max(1, totalExports)
|
|
1816
|
+
);
|
|
1625
1817
|
const sideEffectSignal = {
|
|
1626
1818
|
name: "Implicit Side Effects",
|
|
1627
1819
|
count: implicitSideEffects,
|
|
@@ -1629,7 +1821,10 @@ function calculateAiSignalClarity(params) {
|
|
|
1629
1821
|
// 15% weight
|
|
1630
1822
|
description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
|
|
1631
1823
|
};
|
|
1632
|
-
const callbackRatio = Math.min(
|
|
1824
|
+
const callbackRatio = Math.min(
|
|
1825
|
+
1,
|
|
1826
|
+
deepCallbacks / Math.max(1, totalSymbols * 0.1)
|
|
1827
|
+
);
|
|
1633
1828
|
const callbackSignal = {
|
|
1634
1829
|
name: "Callback Nesting",
|
|
1635
1830
|
count: deepCallbacks,
|
|
@@ -1637,7 +1832,10 @@ function calculateAiSignalClarity(params) {
|
|
|
1637
1832
|
// 10% weight
|
|
1638
1833
|
description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
|
|
1639
1834
|
};
|
|
1640
|
-
const ambiguousRatio = Math.min(
|
|
1835
|
+
const ambiguousRatio = Math.min(
|
|
1836
|
+
1,
|
|
1837
|
+
ambiguousNames / Math.max(1, totalSymbols)
|
|
1838
|
+
);
|
|
1641
1839
|
const ambiguousSignal = {
|
|
1642
1840
|
name: "Ambiguous Names",
|
|
1643
1841
|
count: ambiguousNames,
|
|
@@ -1645,7 +1843,10 @@ function calculateAiSignalClarity(params) {
|
|
|
1645
1843
|
// 10% weight
|
|
1646
1844
|
description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
|
|
1647
1845
|
};
|
|
1648
|
-
const undocRatio = Math.min(
|
|
1846
|
+
const undocRatio = Math.min(
|
|
1847
|
+
1,
|
|
1848
|
+
undocumentedExports / Math.max(1, totalExports)
|
|
1849
|
+
);
|
|
1649
1850
|
const undocSignal = {
|
|
1650
1851
|
name: "Undocumented Exports",
|
|
1651
1852
|
count: undocumentedExports,
|
|
@@ -1662,30 +1863,45 @@ function calculateAiSignalClarity(params) {
|
|
|
1662
1863
|
ambiguousSignal,
|
|
1663
1864
|
undocSignal
|
|
1664
1865
|
];
|
|
1665
|
-
const score = Math.min(
|
|
1866
|
+
const score = Math.min(
|
|
1867
|
+
100,
|
|
1868
|
+
signals.reduce((sum, s) => sum + s.riskContribution, 0)
|
|
1869
|
+
);
|
|
1666
1870
|
let rating;
|
|
1667
1871
|
if (score < 10) rating = "minimal";
|
|
1668
1872
|
else if (score < 25) rating = "low";
|
|
1669
1873
|
else if (score < 50) rating = "moderate";
|
|
1670
1874
|
else if (score < 75) rating = "high";
|
|
1671
1875
|
else rating = "severe";
|
|
1672
|
-
const topSignal = signals.reduce(
|
|
1876
|
+
const topSignal = signals.reduce(
|
|
1877
|
+
(a, b) => a.riskContribution > b.riskContribution ? a : b
|
|
1878
|
+
);
|
|
1673
1879
|
const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant AI signal claritys detected";
|
|
1674
1880
|
const recommendations = [];
|
|
1675
1881
|
if (overloadSignal.riskContribution > 5) {
|
|
1676
|
-
recommendations.push(
|
|
1882
|
+
recommendations.push(
|
|
1883
|
+
`Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`
|
|
1884
|
+
);
|
|
1677
1885
|
}
|
|
1678
1886
|
if (magicSignal.riskContribution > 5) {
|
|
1679
|
-
recommendations.push(
|
|
1887
|
+
recommendations.push(
|
|
1888
|
+
`Extract ${magicLiterals} magic literals into named constants`
|
|
1889
|
+
);
|
|
1680
1890
|
}
|
|
1681
1891
|
if (trapSignal.riskContribution > 5) {
|
|
1682
|
-
recommendations.push(
|
|
1892
|
+
recommendations.push(
|
|
1893
|
+
`Replace ${booleanTraps} boolean traps with named options objects`
|
|
1894
|
+
);
|
|
1683
1895
|
}
|
|
1684
1896
|
if (undocSignal.riskContribution > 5) {
|
|
1685
|
-
recommendations.push(
|
|
1897
|
+
recommendations.push(
|
|
1898
|
+
`Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`
|
|
1899
|
+
);
|
|
1686
1900
|
}
|
|
1687
1901
|
if (sideEffectSignal.riskContribution > 5) {
|
|
1688
|
-
recommendations.push(
|
|
1902
|
+
recommendations.push(
|
|
1903
|
+
"Mark functions with side effects explicitly in their names or docs"
|
|
1904
|
+
);
|
|
1689
1905
|
}
|
|
1690
1906
|
return {
|
|
1691
1907
|
score: Math.round(score),
|
|
@@ -1710,7 +1926,10 @@ function calculateAgentGrounding(params) {
|
|
|
1710
1926
|
domainVocabularySize
|
|
1711
1927
|
} = params;
|
|
1712
1928
|
const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
|
|
1713
|
-
const structureClarityScore = Math.max(
|
|
1929
|
+
const structureClarityScore = Math.max(
|
|
1930
|
+
0,
|
|
1931
|
+
Math.round(100 - deepDirRatio * 80)
|
|
1932
|
+
);
|
|
1714
1933
|
const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
|
|
1715
1934
|
const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
|
|
1716
1935
|
let entryPointScore = 60;
|
|
@@ -1722,7 +1941,10 @@ function calculateAgentGrounding(params) {
|
|
|
1722
1941
|
const untypedRatio = totalExports > 0 ? untypedExports / totalExports : 0;
|
|
1723
1942
|
const apiClarityScore = Math.max(0, Math.round(100 - untypedRatio * 70));
|
|
1724
1943
|
const inconsistencyRatio = domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize : 0;
|
|
1725
|
-
const domainConsistencyScore = Math.max(
|
|
1944
|
+
const domainConsistencyScore = Math.max(
|
|
1945
|
+
0,
|
|
1946
|
+
Math.round(100 - inconsistencyRatio * 80)
|
|
1947
|
+
);
|
|
1726
1948
|
const score = Math.round(
|
|
1727
1949
|
structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
|
|
1728
1950
|
);
|
|
@@ -1734,21 +1956,33 @@ function calculateAgentGrounding(params) {
|
|
|
1734
1956
|
else rating = "disorienting";
|
|
1735
1957
|
const recommendations = [];
|
|
1736
1958
|
if (structureClarityScore < 70) {
|
|
1737
|
-
recommendations.push(
|
|
1959
|
+
recommendations.push(
|
|
1960
|
+
`Flatten ${deepDirectories} overly-deep directories to improve agent navigation`
|
|
1961
|
+
);
|
|
1738
1962
|
}
|
|
1739
1963
|
if (selfDocumentationScore < 70) {
|
|
1740
|
-
recommendations.push(
|
|
1964
|
+
recommendations.push(
|
|
1965
|
+
`Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`
|
|
1966
|
+
);
|
|
1741
1967
|
}
|
|
1742
1968
|
if (!hasRootReadme) {
|
|
1743
|
-
recommendations.push(
|
|
1969
|
+
recommendations.push(
|
|
1970
|
+
"Add a root README.md so agents understand the project context immediately"
|
|
1971
|
+
);
|
|
1744
1972
|
} else if (!readmeIsFresh) {
|
|
1745
|
-
recommendations.push(
|
|
1973
|
+
recommendations.push(
|
|
1974
|
+
"Update README.md \u2014 stale entry-point documentation disorients agents"
|
|
1975
|
+
);
|
|
1746
1976
|
}
|
|
1747
1977
|
if (apiClarityScore < 70) {
|
|
1748
|
-
recommendations.push(
|
|
1978
|
+
recommendations.push(
|
|
1979
|
+
`Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`
|
|
1980
|
+
);
|
|
1749
1981
|
}
|
|
1750
1982
|
if (domainConsistencyScore < 70) {
|
|
1751
|
-
recommendations.push(
|
|
1983
|
+
recommendations.push(
|
|
1984
|
+
`Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`
|
|
1985
|
+
);
|
|
1752
1986
|
}
|
|
1753
1987
|
return {
|
|
1754
1988
|
score,
|
|
@@ -1781,7 +2015,9 @@ function calculateTestabilityIndex(params) {
|
|
|
1781
2015
|
const purityRatio = totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5;
|
|
1782
2016
|
const purityScore = Math.round(purityRatio * 100);
|
|
1783
2017
|
const injectionRatio = totalClasses > 0 ? injectionPatterns / totalClasses : 0.5;
|
|
1784
|
-
const dependencyInjectionScore = Math.round(
|
|
2018
|
+
const dependencyInjectionScore = Math.round(
|
|
2019
|
+
Math.min(100, injectionRatio * 100)
|
|
2020
|
+
);
|
|
1785
2021
|
const bloatedRatio = totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces : 0;
|
|
1786
2022
|
const interfaceFocusScore = Math.max(0, Math.round(100 - bloatedRatio * 80));
|
|
1787
2023
|
const mutationRatio = totalFunctions > 0 ? externalStateMutations / totalFunctions : 0;
|
|
@@ -1797,25 +2033,36 @@ function calculateTestabilityIndex(params) {
|
|
|
1797
2033
|
else rating = "unverifiable";
|
|
1798
2034
|
let aiChangeSafetyRating;
|
|
1799
2035
|
if (rawCoverageRatio >= 0.5 && score >= 70) aiChangeSafetyRating = "safe";
|
|
1800
|
-
else if (rawCoverageRatio >= 0.2 && score >= 50)
|
|
2036
|
+
else if (rawCoverageRatio >= 0.2 && score >= 50)
|
|
2037
|
+
aiChangeSafetyRating = "moderate-risk";
|
|
1801
2038
|
else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
|
|
1802
2039
|
else aiChangeSafetyRating = "blind-risk";
|
|
1803
2040
|
const recommendations = [];
|
|
1804
2041
|
if (!hasTestFramework) {
|
|
1805
|
-
recommendations.push(
|
|
2042
|
+
recommendations.push(
|
|
2043
|
+
"Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests"
|
|
2044
|
+
);
|
|
1806
2045
|
}
|
|
1807
2046
|
if (rawCoverageRatio < 0.3) {
|
|
1808
2047
|
const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
|
|
1809
|
-
recommendations.push(
|
|
2048
|
+
recommendations.push(
|
|
2049
|
+
`Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`
|
|
2050
|
+
);
|
|
1810
2051
|
}
|
|
1811
2052
|
if (purityScore < 50) {
|
|
1812
|
-
recommendations.push(
|
|
2053
|
+
recommendations.push(
|
|
2054
|
+
"Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable"
|
|
2055
|
+
);
|
|
1813
2056
|
}
|
|
1814
2057
|
if (dependencyInjectionScore < 50 && totalClasses > 0) {
|
|
1815
|
-
recommendations.push(
|
|
2058
|
+
recommendations.push(
|
|
2059
|
+
"Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable"
|
|
2060
|
+
);
|
|
1816
2061
|
}
|
|
1817
2062
|
if (externalStateMutations > totalFunctions * 0.3) {
|
|
1818
|
-
recommendations.push(
|
|
2063
|
+
recommendations.push(
|
|
2064
|
+
"Reduce direct state mutations \u2014 return values instead to improve observability"
|
|
2065
|
+
);
|
|
1819
2066
|
}
|
|
1820
2067
|
return {
|
|
1821
2068
|
score,
|
|
@@ -1832,7 +2079,12 @@ function calculateTestabilityIndex(params) {
|
|
|
1832
2079
|
};
|
|
1833
2080
|
}
|
|
1834
2081
|
function calculateDocDrift(params) {
|
|
1835
|
-
const {
|
|
2082
|
+
const {
|
|
2083
|
+
uncommentedExports,
|
|
2084
|
+
totalExports,
|
|
2085
|
+
outdatedComments,
|
|
2086
|
+
undocumentedComplexity
|
|
2087
|
+
} = params;
|
|
1836
2088
|
const uncommentedRatio = totalExports > 0 ? uncommentedExports / totalExports : 0;
|
|
1837
2089
|
const outdatedScore = Math.min(100, outdatedComments * 15);
|
|
1838
2090
|
const uncommentedScore = Math.min(100, uncommentedRatio * 100);
|
|
@@ -1849,13 +2101,19 @@ function calculateDocDrift(params) {
|
|
|
1849
2101
|
else rating = "severe";
|
|
1850
2102
|
const recommendations = [];
|
|
1851
2103
|
if (outdatedComments > 0) {
|
|
1852
|
-
recommendations.push(
|
|
2104
|
+
recommendations.push(
|
|
2105
|
+
`Update or remove ${outdatedComments} outdated comments that contradict the code.`
|
|
2106
|
+
);
|
|
1853
2107
|
}
|
|
1854
2108
|
if (uncommentedRatio > 0.3) {
|
|
1855
|
-
recommendations.push(
|
|
2109
|
+
recommendations.push(
|
|
2110
|
+
`Add JSDoc to ${uncommentedExports} uncommented exports.`
|
|
2111
|
+
);
|
|
1856
2112
|
}
|
|
1857
2113
|
if (undocumentedComplexity > 0) {
|
|
1858
|
-
recommendations.push(
|
|
2114
|
+
recommendations.push(
|
|
2115
|
+
`Explain the business logic for ${undocumentedComplexity} highly complex functions.`
|
|
2116
|
+
);
|
|
1859
2117
|
}
|
|
1860
2118
|
return {
|
|
1861
2119
|
score: finalScore,
|
|
@@ -1869,7 +2127,12 @@ function calculateDocDrift(params) {
|
|
|
1869
2127
|
};
|
|
1870
2128
|
}
|
|
1871
2129
|
function calculateDependencyHealth(params) {
|
|
1872
|
-
const {
|
|
2130
|
+
const {
|
|
2131
|
+
totalPackages,
|
|
2132
|
+
outdatedPackages,
|
|
2133
|
+
deprecatedPackages,
|
|
2134
|
+
trainingCutoffSkew
|
|
2135
|
+
} = params;
|
|
1873
2136
|
const outdatedRatio = totalPackages > 0 ? outdatedPackages / totalPackages : 0;
|
|
1874
2137
|
const deprecatedRatio = totalPackages > 0 ? deprecatedPackages / totalPackages : 0;
|
|
1875
2138
|
const outdatedScore = Math.max(0, 100 - outdatedRatio * 200);
|
|
@@ -1884,19 +2147,27 @@ function calculateDependencyHealth(params) {
|
|
|
1884
2147
|
else if (score >= 30) rating = "poor";
|
|
1885
2148
|
else rating = "hazardous";
|
|
1886
2149
|
let aiKnowledgeConfidence;
|
|
1887
|
-
if (trainingCutoffSkew < 0.2 && deprecatedPackages === 0)
|
|
1888
|
-
|
|
2150
|
+
if (trainingCutoffSkew < 0.2 && deprecatedPackages === 0)
|
|
2151
|
+
aiKnowledgeConfidence = "high";
|
|
2152
|
+
else if (trainingCutoffSkew < 0.5 && deprecatedPackages <= 2)
|
|
2153
|
+
aiKnowledgeConfidence = "moderate";
|
|
1889
2154
|
else if (trainingCutoffSkew < 0.8) aiKnowledgeConfidence = "low";
|
|
1890
2155
|
else aiKnowledgeConfidence = "blind";
|
|
1891
2156
|
const recommendations = [];
|
|
1892
2157
|
if (deprecatedPackages > 0) {
|
|
1893
|
-
recommendations.push(
|
|
2158
|
+
recommendations.push(
|
|
2159
|
+
`Replace ${deprecatedPackages} deprecated packages, as AI will struggle to find modern solutions.`
|
|
2160
|
+
);
|
|
1894
2161
|
}
|
|
1895
2162
|
if (outdatedRatio > 0.2) {
|
|
1896
|
-
recommendations.push(
|
|
2163
|
+
recommendations.push(
|
|
2164
|
+
`Update ${outdatedPackages} outdated packages to keep APIs aligned with AI training data.`
|
|
2165
|
+
);
|
|
1897
2166
|
}
|
|
1898
2167
|
if (trainingCutoffSkew > 0.5) {
|
|
1899
|
-
recommendations.push(
|
|
2168
|
+
recommendations.push(
|
|
2169
|
+
"High training cutoff skew detected. AI may hallucinate APIs that were introduced recently."
|
|
2170
|
+
);
|
|
1900
2171
|
}
|
|
1901
2172
|
return {
|
|
1902
2173
|
score,
|
|
@@ -1937,10 +2208,14 @@ function calculateChangeAmplification(params) {
|
|
|
1937
2208
|
else if (score < 90) rating = "contained";
|
|
1938
2209
|
const recommendations = [];
|
|
1939
2210
|
if (score < 70 && hotspots.length > 0) {
|
|
1940
|
-
recommendations.push(
|
|
2211
|
+
recommendations.push(
|
|
2212
|
+
`Refactor top hotspot '${hotspots[0].file}' to reduce coupling (fan-out: ${hotspots[0].fanOut}, fan-in: ${hotspots[0].fanIn}).`
|
|
2213
|
+
);
|
|
1941
2214
|
}
|
|
1942
2215
|
if (maxAmplification > 30) {
|
|
1943
|
-
recommendations.push(
|
|
2216
|
+
recommendations.push(
|
|
2217
|
+
`Break down key bottlenecks with amplification factor > 30.`
|
|
2218
|
+
);
|
|
1944
2219
|
}
|
|
1945
2220
|
return {
|
|
1946
2221
|
score: Math.round(score),
|
|
@@ -2022,7 +2297,11 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2022
2297
|
recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
|
|
2023
2298
|
}
|
|
2024
2299
|
for (const rec of params.agentGrounding.recommendations) {
|
|
2025
|
-
recommendations.push({
|
|
2300
|
+
recommendations.push({
|
|
2301
|
+
action: rec,
|
|
2302
|
+
estimatedImpact: 6,
|
|
2303
|
+
priority: "medium"
|
|
2304
|
+
});
|
|
2026
2305
|
}
|
|
2027
2306
|
for (const rec of params.testability.recommendations) {
|
|
2028
2307
|
const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
|
|
@@ -2033,12 +2312,20 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2033
2312
|
}
|
|
2034
2313
|
if (params.docDrift) {
|
|
2035
2314
|
for (const rec of params.docDrift.recommendations) {
|
|
2036
|
-
recommendations.push({
|
|
2315
|
+
recommendations.push({
|
|
2316
|
+
action: rec,
|
|
2317
|
+
estimatedImpact: 8,
|
|
2318
|
+
priority: "high"
|
|
2319
|
+
});
|
|
2037
2320
|
}
|
|
2038
2321
|
}
|
|
2039
2322
|
if (params.dependencyHealth) {
|
|
2040
2323
|
for (const rec of params.dependencyHealth.recommendations) {
|
|
2041
|
-
recommendations.push({
|
|
2324
|
+
recommendations.push({
|
|
2325
|
+
action: rec,
|
|
2326
|
+
estimatedImpact: 7,
|
|
2327
|
+
priority: "medium"
|
|
2328
|
+
});
|
|
2042
2329
|
}
|
|
2043
2330
|
}
|
|
2044
2331
|
const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
@@ -2063,7 +2350,7 @@ function calculateExtendedFutureProofScore(params) {
|
|
|
2063
2350
|
|
|
2064
2351
|
// src/utils/history.ts
|
|
2065
2352
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
2066
|
-
import { join as join4, dirname as
|
|
2353
|
+
import { join as join4, dirname as dirname4 } from "path";
|
|
2067
2354
|
function getHistoryPath(rootDir) {
|
|
2068
2355
|
return join4(rootDir, ".aiready", "history.json");
|
|
2069
2356
|
}
|
|
@@ -2082,7 +2369,7 @@ function loadScoreHistory(rootDir) {
|
|
|
2082
2369
|
}
|
|
2083
2370
|
function saveScoreEntry(rootDir, entry) {
|
|
2084
2371
|
const historyPath = getHistoryPath(rootDir);
|
|
2085
|
-
const historyDir =
|
|
2372
|
+
const historyDir = dirname4(historyPath);
|
|
2086
2373
|
if (!existsSync4(historyDir)) {
|
|
2087
2374
|
mkdirSync2(historyDir, { recursive: true });
|
|
2088
2375
|
}
|
|
@@ -2134,6 +2421,39 @@ function clearHistory(rootDir) {
|
|
|
2134
2421
|
writeFileSync2(historyPath, JSON.stringify([]));
|
|
2135
2422
|
}
|
|
2136
2423
|
}
|
|
2424
|
+
|
|
2425
|
+
// src/utils/history-git.ts
|
|
2426
|
+
import { execSync } from "child_process";
|
|
2427
|
+
function getFileCommitTimestamps(file) {
|
|
2428
|
+
const lineStamps = {};
|
|
2429
|
+
try {
|
|
2430
|
+
const output = execSync(`git blame -t "${file}"`, {
|
|
2431
|
+
encoding: "utf-8",
|
|
2432
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2433
|
+
});
|
|
2434
|
+
const lines = output.split("\n");
|
|
2435
|
+
for (const line of lines) {
|
|
2436
|
+
if (!line) continue;
|
|
2437
|
+
const match = line.match(/^\S+\s+\(.*?(\d{10,})\s+[-+]\d+\s+(\d+)\)/);
|
|
2438
|
+
if (match) {
|
|
2439
|
+
const ts = parseInt(match[1], 10);
|
|
2440
|
+
const ln = parseInt(match[2], 10);
|
|
2441
|
+
lineStamps[ln] = ts;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
} catch {
|
|
2445
|
+
}
|
|
2446
|
+
return lineStamps;
|
|
2447
|
+
}
|
|
2448
|
+
function getLineRangeLastModifiedCached(lineStamps, startLine, endLine) {
|
|
2449
|
+
let latest = 0;
|
|
2450
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
2451
|
+
if (lineStamps[i] && lineStamps[i] > latest) {
|
|
2452
|
+
latest = lineStamps[i];
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
return latest;
|
|
2456
|
+
}
|
|
2137
2457
|
export {
|
|
2138
2458
|
CONTEXT_TIER_THRESHOLDS,
|
|
2139
2459
|
DEFAULT_COST_CONFIG,
|
|
@@ -2148,6 +2468,7 @@ export {
|
|
|
2148
2468
|
SIZE_ADJUSTED_THRESHOLDS,
|
|
2149
2469
|
TOOL_NAME_MAP,
|
|
2150
2470
|
TypeScriptParser,
|
|
2471
|
+
VAGUE_FILE_NAMES,
|
|
2151
2472
|
calculateAgentGrounding,
|
|
2152
2473
|
calculateAiSignalClarity,
|
|
2153
2474
|
calculateChangeAmplification,
|
|
@@ -2182,8 +2503,10 @@ export {
|
|
|
2182
2503
|
generateHTML,
|
|
2183
2504
|
getDebtBreakdown,
|
|
2184
2505
|
getElapsedTime,
|
|
2506
|
+
getFileCommitTimestamps,
|
|
2185
2507
|
getFileExtension,
|
|
2186
2508
|
getHistorySummary,
|
|
2509
|
+
getLineRangeLastModifiedCached,
|
|
2187
2510
|
getModelPreset,
|
|
2188
2511
|
getParser,
|
|
2189
2512
|
getProjectSizeTier,
|
|
@@ -2209,5 +2532,6 @@ export {
|
|
|
2209
2532
|
readFileContent,
|
|
2210
2533
|
resolveOutputPath,
|
|
2211
2534
|
saveScoreEntry,
|
|
2535
|
+
scanEntries,
|
|
2212
2536
|
scanFiles
|
|
2213
2537
|
};
|