@aiready/core 0.9.25 → 0.9.26
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/index.d.mts +263 -1
- package/dist/index.d.ts +263 -1
- package/dist/index.js +526 -0
- package/dist/index.mjs +511 -0
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -573,6 +573,200 @@ function formatHours(hours) {
|
|
|
573
573
|
function formatAcceptanceRate(rate) {
|
|
574
574
|
return `${Math.round(rate * 100)}%`;
|
|
575
575
|
}
|
|
576
|
+
function calculateScoreTrend(history) {
|
|
577
|
+
if (history.length < 2) {
|
|
578
|
+
return {
|
|
579
|
+
direction: "stable",
|
|
580
|
+
change30Days: 0,
|
|
581
|
+
change90Days: 0,
|
|
582
|
+
velocity: 0,
|
|
583
|
+
projectedScore: history[0]?.overallScore || 100
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
const now = /* @__PURE__ */ new Date();
|
|
587
|
+
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
588
|
+
const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3);
|
|
589
|
+
const last30Days = history.filter((e) => new Date(e.timestamp) >= thirtyDaysAgo);
|
|
590
|
+
const last90Days = history.filter((e) => new Date(e.timestamp) >= ninetyDaysAgo);
|
|
591
|
+
const currentScore = history[history.length - 1].overallScore;
|
|
592
|
+
const thirtyDaysAgoScore = last30Days[0]?.overallScore || currentScore;
|
|
593
|
+
const ninetyDaysAgoScore = last90Days[0]?.overallScore || thirtyDaysAgoScore;
|
|
594
|
+
const change30Days = currentScore - thirtyDaysAgoScore;
|
|
595
|
+
const change90Days = currentScore - ninetyDaysAgoScore;
|
|
596
|
+
const weeksOfData = Math.max(1, history.length / 7);
|
|
597
|
+
const totalChange = currentScore - history[0].overallScore;
|
|
598
|
+
const velocity = totalChange / weeksOfData;
|
|
599
|
+
let direction;
|
|
600
|
+
if (change30Days > 3) direction = "improving";
|
|
601
|
+
else if (change30Days < -3) direction = "degrading";
|
|
602
|
+
else direction = "stable";
|
|
603
|
+
const projectedScore = Math.max(0, Math.min(100, currentScore + velocity * 4));
|
|
604
|
+
return {
|
|
605
|
+
direction,
|
|
606
|
+
change30Days,
|
|
607
|
+
change90Days,
|
|
608
|
+
velocity: Math.round(velocity * 10) / 10,
|
|
609
|
+
projectedScore: Math.round(projectedScore)
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function calculateRemediationVelocity(history, currentIssues) {
|
|
613
|
+
if (history.length < 2) {
|
|
614
|
+
return {
|
|
615
|
+
issuesFixedThisWeek: 0,
|
|
616
|
+
avgIssuesPerWeek: 0,
|
|
617
|
+
trend: "stable",
|
|
618
|
+
estimatedCompletionWeeks: currentIssues > 0 ? Infinity : 0
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
const now = /* @__PURE__ */ new Date();
|
|
622
|
+
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
|
|
623
|
+
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1e3);
|
|
624
|
+
const thisWeek = history.filter((e) => new Date(e.timestamp) >= oneWeekAgo);
|
|
625
|
+
const lastWeek = history.filter(
|
|
626
|
+
(e) => new Date(e.timestamp) >= twoWeeksAgo && new Date(e.timestamp) < oneWeekAgo
|
|
627
|
+
);
|
|
628
|
+
const issuesFixedThisWeek = thisWeek.length > 1 ? thisWeek[0].totalIssues - thisWeek[thisWeek.length - 1].totalIssues : 0;
|
|
629
|
+
const totalIssuesFixed = history[0].totalIssues - history[history.length - 1].totalIssues;
|
|
630
|
+
const weeksOfData = Math.max(1, history.length / 7);
|
|
631
|
+
const avgIssuesPerWeek = totalIssuesFixed / weeksOfData;
|
|
632
|
+
let trend;
|
|
633
|
+
if (lastWeek.length > 1) {
|
|
634
|
+
const lastWeekFixed = lastWeek[0].totalIssues - lastWeek[lastWeek.length - 1].totalIssues;
|
|
635
|
+
if (issuesFixedThisWeek > lastWeekFixed * 1.2) trend = "accelerating";
|
|
636
|
+
else if (issuesFixedThisWeek < lastWeekFixed * 0.8) trend = "decelerating";
|
|
637
|
+
else trend = "stable";
|
|
638
|
+
} else {
|
|
639
|
+
trend = "stable";
|
|
640
|
+
}
|
|
641
|
+
const estimatedCompletionWeeks = avgIssuesPerWeek > 0 ? Math.ceil(currentIssues / avgIssuesPerWeek) : Infinity;
|
|
642
|
+
return {
|
|
643
|
+
issuesFixedThisWeek: Math.max(0, issuesFixedThisWeek),
|
|
644
|
+
avgIssuesPerWeek: Math.round(avgIssuesPerWeek * 10) / 10,
|
|
645
|
+
trend,
|
|
646
|
+
estimatedCompletionWeeks
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function calculateKnowledgeConcentration(files, authorData) {
|
|
650
|
+
if (files.length === 0) {
|
|
651
|
+
return {
|
|
652
|
+
score: 0,
|
|
653
|
+
rating: "low",
|
|
654
|
+
analysis: {
|
|
655
|
+
uniqueConceptFiles: 0,
|
|
656
|
+
totalFiles: 0,
|
|
657
|
+
concentrationRatio: 0,
|
|
658
|
+
singleAuthorFiles: 0,
|
|
659
|
+
orphanFiles: 0
|
|
660
|
+
},
|
|
661
|
+
recommendations: ["No files to analyze"]
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
const orphanFiles = files.filter((f) => f.exports < 2 && f.imports < 2).length;
|
|
665
|
+
const avgExports = files.reduce((sum, f) => sum + f.exports, 0) / files.length;
|
|
666
|
+
const uniqueConceptFiles = files.filter((f) => f.exports > avgExports * 2).length;
|
|
667
|
+
const totalExports = files.reduce((sum, f) => sum + f.exports, 0);
|
|
668
|
+
const concentrationRatio = totalExports > 0 ? uniqueConceptFiles / files.length : 0;
|
|
669
|
+
let singleAuthorFiles = 0;
|
|
670
|
+
if (authorData) {
|
|
671
|
+
for (const files2 of authorData.values()) {
|
|
672
|
+
if (files2.length === 1) singleAuthorFiles++;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const orphanRisk = orphanFiles / files.length * 30;
|
|
676
|
+
const uniqueRisk = concentrationRatio * 40;
|
|
677
|
+
const singleAuthorRisk = authorData ? singleAuthorFiles / files.length * 30 : 0;
|
|
678
|
+
const score = Math.min(100, Math.round(orphanRisk + uniqueRisk + singleAuthorRisk));
|
|
679
|
+
let rating;
|
|
680
|
+
if (score < 20) rating = "low";
|
|
681
|
+
else if (score < 40) rating = "moderate";
|
|
682
|
+
else if (score < 70) rating = "high";
|
|
683
|
+
else rating = "critical";
|
|
684
|
+
const recommendations = [];
|
|
685
|
+
if (orphanFiles > files.length * 0.2) {
|
|
686
|
+
recommendations.push(`Reduce ${orphanFiles} orphan files by connecting them to main modules`);
|
|
687
|
+
}
|
|
688
|
+
if (uniqueConceptFiles > files.length * 0.1) {
|
|
689
|
+
recommendations.push("Distribute high-export files into more focused modules");
|
|
690
|
+
}
|
|
691
|
+
if (authorData && singleAuthorFiles > files.length * 0.3) {
|
|
692
|
+
recommendations.push("Increase knowledge sharing to reduce single-author dependencies");
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
score,
|
|
696
|
+
rating,
|
|
697
|
+
analysis: {
|
|
698
|
+
uniqueConceptFiles,
|
|
699
|
+
totalFiles: files.length,
|
|
700
|
+
concentrationRatio: Math.round(concentrationRatio * 100) / 100,
|
|
701
|
+
singleAuthorFiles,
|
|
702
|
+
orphanFiles
|
|
703
|
+
},
|
|
704
|
+
recommendations
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function calculateTechnicalDebtInterest(params) {
|
|
708
|
+
const { currentMonthlyCost, issues, monthsOpen } = params;
|
|
709
|
+
const criticalCount = issues.filter((i) => i.severity === "critical").length;
|
|
710
|
+
const majorCount = issues.filter((i) => i.severity === "major").length;
|
|
711
|
+
const minorCount = issues.filter((i) => i.severity === "minor").length;
|
|
712
|
+
const severityWeight = (criticalCount * 3 + majorCount * 2 + minorCount * 1) / Math.max(1, issues.length);
|
|
713
|
+
const baseRate = 0.02 + severityWeight * 0.01;
|
|
714
|
+
const timeMultiplier = Math.max(1, 1 + monthsOpen * 0.1);
|
|
715
|
+
const monthlyRate = baseRate * timeMultiplier;
|
|
716
|
+
const projectDebt = (principal2, months) => {
|
|
717
|
+
let debt = principal2;
|
|
718
|
+
for (let i = 0; i < months; i++) {
|
|
719
|
+
debt = debt * (1 + monthlyRate);
|
|
720
|
+
}
|
|
721
|
+
return Math.round(debt);
|
|
722
|
+
};
|
|
723
|
+
const principal = currentMonthlyCost * 12;
|
|
724
|
+
const projections = {
|
|
725
|
+
months6: projectDebt(principal, 6),
|
|
726
|
+
months12: projectDebt(principal, 12),
|
|
727
|
+
months24: projectDebt(principal, 24)
|
|
728
|
+
};
|
|
729
|
+
return {
|
|
730
|
+
monthlyRate: Math.round(monthlyRate * 1e4) / 100,
|
|
731
|
+
annualRate: Math.round((Math.pow(1 + monthlyRate, 12) - 1) * 1e4) / 100,
|
|
732
|
+
principal,
|
|
733
|
+
projections,
|
|
734
|
+
monthlyCost: Math.round(currentMonthlyCost * (1 + monthlyRate) * 100) / 100
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function getDebtBreakdown(patternCost, contextCost, consistencyCost) {
|
|
738
|
+
const breakdowns = [
|
|
739
|
+
{
|
|
740
|
+
category: "Semantic Duplication",
|
|
741
|
+
currentCost: patternCost,
|
|
742
|
+
monthlyGrowthRate: 5,
|
|
743
|
+
// Grows as devs copy-paste
|
|
744
|
+
priority: patternCost > 1e3 ? "high" : "medium",
|
|
745
|
+
fixCost: patternCost * 3
|
|
746
|
+
// Fixing costs 3x current waste
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
category: "Context Fragmentation",
|
|
750
|
+
currentCost: contextCost,
|
|
751
|
+
monthlyGrowthRate: 3,
|
|
752
|
+
// Grows with new features
|
|
753
|
+
priority: contextCost > 500 ? "high" : "medium",
|
|
754
|
+
fixCost: contextCost * 2.5
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
category: "Consistency Issues",
|
|
758
|
+
currentCost: consistencyCost,
|
|
759
|
+
monthlyGrowthRate: 2,
|
|
760
|
+
// Grows with new devs
|
|
761
|
+
priority: consistencyCost > 200 ? "medium" : "low",
|
|
762
|
+
fixCost: consistencyCost * 1.5
|
|
763
|
+
}
|
|
764
|
+
];
|
|
765
|
+
return breakdowns.sort((a, b) => {
|
|
766
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
767
|
+
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
768
|
+
});
|
|
769
|
+
}
|
|
576
770
|
|
|
577
771
|
// src/parsers/typescript-parser.ts
|
|
578
772
|
import { parse as parse2 } from "@typescript-eslint/typescript-estree";
|
|
@@ -1053,6 +1247,308 @@ function isFileSupported(filePath) {
|
|
|
1053
1247
|
function getSupportedLanguages() {
|
|
1054
1248
|
return ParserFactory.getInstance().getSupportedLanguages();
|
|
1055
1249
|
}
|
|
1250
|
+
|
|
1251
|
+
// src/future-proof-metrics.ts
|
|
1252
|
+
function calculateCognitiveLoad(params) {
|
|
1253
|
+
const { linesOfCode, exportCount, importCount, uniqueConcepts, cyclomaticComplexity = 1 } = params;
|
|
1254
|
+
const sizeFactor = {
|
|
1255
|
+
name: "Size Complexity",
|
|
1256
|
+
score: Math.min(100, Math.max(0, (linesOfCode - 50) / 10)),
|
|
1257
|
+
weight: 0.3,
|
|
1258
|
+
description: `${linesOfCode} lines of code`
|
|
1259
|
+
};
|
|
1260
|
+
const interfaceFactor = {
|
|
1261
|
+
name: "Interface Complexity",
|
|
1262
|
+
score: Math.min(100, exportCount * 5),
|
|
1263
|
+
weight: 0.25,
|
|
1264
|
+
description: `${exportCount} exported concepts`
|
|
1265
|
+
};
|
|
1266
|
+
const dependencyFactor = {
|
|
1267
|
+
name: "Dependency Complexity",
|
|
1268
|
+
score: Math.min(100, importCount * 8),
|
|
1269
|
+
weight: 0.25,
|
|
1270
|
+
description: `${importCount} dependencies`
|
|
1271
|
+
};
|
|
1272
|
+
const conceptDensity = linesOfCode > 0 ? uniqueConcepts / linesOfCode : 0;
|
|
1273
|
+
const conceptFactor = {
|
|
1274
|
+
name: "Conceptual Density",
|
|
1275
|
+
score: Math.min(100, conceptDensity * 500),
|
|
1276
|
+
weight: 0.2,
|
|
1277
|
+
description: `${uniqueConcepts} unique concepts`
|
|
1278
|
+
};
|
|
1279
|
+
const factors = [sizeFactor, interfaceFactor, dependencyFactor, conceptFactor];
|
|
1280
|
+
const score = factors.reduce((sum, f) => sum + f.score * f.weight, 0);
|
|
1281
|
+
let rating;
|
|
1282
|
+
if (score < 20) rating = "trivial";
|
|
1283
|
+
else if (score < 40) rating = "easy";
|
|
1284
|
+
else if (score < 60) rating = "moderate";
|
|
1285
|
+
else if (score < 80) rating = "difficult";
|
|
1286
|
+
else rating = "expert";
|
|
1287
|
+
return {
|
|
1288
|
+
score: Math.round(score),
|
|
1289
|
+
rating,
|
|
1290
|
+
factors,
|
|
1291
|
+
rawValues: {
|
|
1292
|
+
size: linesOfCode,
|
|
1293
|
+
complexity: cyclomaticComplexity,
|
|
1294
|
+
dependencyCount: importCount,
|
|
1295
|
+
conceptCount: uniqueConcepts
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
function calculateSemanticDistance(params) {
|
|
1300
|
+
const { file1, file2, file1Domain, file2Domain, sharedDependencies } = params;
|
|
1301
|
+
const domainDistance = file1Domain === file2Domain ? 0 : file1Domain && file2Domain ? 0.5 : 0.8;
|
|
1302
|
+
const importOverlap = sharedDependencies.length / Math.max(1, Math.min(params.file1Imports.length, params.file2Imports.length));
|
|
1303
|
+
const importDistance = 1 - importOverlap;
|
|
1304
|
+
const distance = domainDistance * 0.4 + importDistance * 0.3 + (sharedDependencies.length > 0 ? 0 : 0.3);
|
|
1305
|
+
let relationship;
|
|
1306
|
+
if (file1 === file2) relationship = "same-file";
|
|
1307
|
+
else if (file1Domain === file2Domain) relationship = "same-domain";
|
|
1308
|
+
else if (sharedDependencies.length > 0) relationship = "cross-domain";
|
|
1309
|
+
else relationship = "unrelated";
|
|
1310
|
+
const pathItems = [file1Domain, ...sharedDependencies, file2Domain].filter((s) => typeof s === "string" && s.length > 0);
|
|
1311
|
+
return {
|
|
1312
|
+
between: [file1, file2],
|
|
1313
|
+
distance: Math.round(distance * 100) / 100,
|
|
1314
|
+
relationship,
|
|
1315
|
+
path: pathItems,
|
|
1316
|
+
reason: relationship === "same-domain" ? `Both in "${file1Domain}" domain` : relationship === "cross-domain" ? `Share ${sharedDependencies.length} dependency(ies)` : "No strong semantic relationship detected"
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
function calculatePatternEntropy(files) {
|
|
1320
|
+
if (files.length === 0) {
|
|
1321
|
+
return {
|
|
1322
|
+
domain: "unknown",
|
|
1323
|
+
entropy: 0,
|
|
1324
|
+
rating: "crystalline",
|
|
1325
|
+
distribution: { locationCount: 0, dominantLocation: "", giniCoefficient: 0 },
|
|
1326
|
+
recommendations: ["No files to analyze"]
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
const dirGroups = /* @__PURE__ */ new Map();
|
|
1330
|
+
for (const file of files) {
|
|
1331
|
+
const parts = file.path.split("/").slice(0, 4).join("/") || "root";
|
|
1332
|
+
dirGroups.set(parts, (dirGroups.get(parts) || 0) + 1);
|
|
1333
|
+
}
|
|
1334
|
+
const counts = Array.from(dirGroups.values());
|
|
1335
|
+
const total = counts.reduce((a, b) => a + b, 0);
|
|
1336
|
+
let entropy = 0;
|
|
1337
|
+
for (const count of counts) {
|
|
1338
|
+
const p = count / total;
|
|
1339
|
+
if (p > 0) entropy -= p * Math.log2(p);
|
|
1340
|
+
}
|
|
1341
|
+
const maxEntropy = Math.log2(dirGroups.size || 1);
|
|
1342
|
+
const normalizedEntropy = maxEntropy > 0 ? entropy / maxEntropy : 0;
|
|
1343
|
+
const sortedCounts = counts.sort((a, b) => a - b);
|
|
1344
|
+
let gini = 0;
|
|
1345
|
+
for (let i = 0; i < sortedCounts.length; i++) {
|
|
1346
|
+
gini += (2 * (i + 1) - sortedCounts.length - 1) * sortedCounts[i];
|
|
1347
|
+
}
|
|
1348
|
+
gini /= total * sortedCounts.length;
|
|
1349
|
+
let dominantLocation = "";
|
|
1350
|
+
let maxCount = 0;
|
|
1351
|
+
for (const [loc, count] of dirGroups.entries()) {
|
|
1352
|
+
if (count > maxCount) {
|
|
1353
|
+
maxCount = count;
|
|
1354
|
+
dominantLocation = loc;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
let rating;
|
|
1358
|
+
if (normalizedEntropy < 0.2) rating = "crystalline";
|
|
1359
|
+
else if (normalizedEntropy < 0.4) rating = "well-structured";
|
|
1360
|
+
else if (normalizedEntropy < 0.6) rating = "moderate";
|
|
1361
|
+
else if (normalizedEntropy < 0.8) rating = "fragmented";
|
|
1362
|
+
else rating = "chaotic";
|
|
1363
|
+
const recommendations = [];
|
|
1364
|
+
if (normalizedEntropy > 0.5) {
|
|
1365
|
+
recommendations.push(`Consolidate ${files.length} files into fewer directories by domain`);
|
|
1366
|
+
}
|
|
1367
|
+
if (dirGroups.size > 5) {
|
|
1368
|
+
recommendations.push("Consider barrel exports to reduce directory navigation");
|
|
1369
|
+
}
|
|
1370
|
+
if (gini > 0.5) {
|
|
1371
|
+
recommendations.push("Redistribute files more evenly across directories");
|
|
1372
|
+
}
|
|
1373
|
+
const firstFile = files.length > 0 ? files[0] : null;
|
|
1374
|
+
const domainValue = firstFile ? firstFile.domain : "mixed";
|
|
1375
|
+
return {
|
|
1376
|
+
domain: domainValue,
|
|
1377
|
+
entropy: Math.round(normalizedEntropy * 100) / 100,
|
|
1378
|
+
rating,
|
|
1379
|
+
distribution: {
|
|
1380
|
+
locationCount: dirGroups.size,
|
|
1381
|
+
dominantLocation,
|
|
1382
|
+
giniCoefficient: Math.round(gini * 100) / 100
|
|
1383
|
+
},
|
|
1384
|
+
recommendations
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function calculateConceptCohesion(params) {
|
|
1388
|
+
const { exports } = params;
|
|
1389
|
+
if (exports.length === 0) {
|
|
1390
|
+
return {
|
|
1391
|
+
score: 1,
|
|
1392
|
+
rating: "excellent",
|
|
1393
|
+
analysis: { uniqueDomains: 0, domainConcentration: 0, exportPurposeClarity: 1 }
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
const allDomains = [];
|
|
1397
|
+
for (const exp of exports) {
|
|
1398
|
+
if (exp.inferredDomain) allDomains.push(exp.inferredDomain);
|
|
1399
|
+
if (exp.domains) allDomains.push(...exp.domains);
|
|
1400
|
+
}
|
|
1401
|
+
const uniqueDomains = new Set(allDomains);
|
|
1402
|
+
const domainCounts = /* @__PURE__ */ new Map();
|
|
1403
|
+
for (const d of allDomains) {
|
|
1404
|
+
domainCounts.set(d, (domainCounts.get(d) || 0) + 1);
|
|
1405
|
+
}
|
|
1406
|
+
const maxCount = Math.max(...Array.from(domainCounts.values()), 1);
|
|
1407
|
+
const domainConcentration = maxCount / allDomains.length;
|
|
1408
|
+
const exportPurposeClarity = 1 - (uniqueDomains.size - 1) / Math.max(1, exports.length);
|
|
1409
|
+
const score = domainConcentration * 0.5 + exportPurposeClarity * 0.5;
|
|
1410
|
+
let rating;
|
|
1411
|
+
if (score > 0.8) rating = "excellent";
|
|
1412
|
+
else if (score > 0.6) rating = "good";
|
|
1413
|
+
else if (score > 0.4) rating = "moderate";
|
|
1414
|
+
else rating = "poor";
|
|
1415
|
+
return {
|
|
1416
|
+
score: Math.round(score * 100) / 100,
|
|
1417
|
+
rating,
|
|
1418
|
+
analysis: {
|
|
1419
|
+
uniqueDomains: uniqueDomains.size,
|
|
1420
|
+
domainConcentration: Math.round(domainConcentration * 100) / 100,
|
|
1421
|
+
exportPurposeClarity: Math.round(exportPurposeClarity * 100) / 100
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
function calculateFutureProofScore(params) {
|
|
1426
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
1427
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
1428
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
1429
|
+
const overall = Math.round(
|
|
1430
|
+
loadScore * 0.4 + entropyScore * 0.3 + cohesionScore * 0.3
|
|
1431
|
+
);
|
|
1432
|
+
const factors = [
|
|
1433
|
+
{
|
|
1434
|
+
name: "Cognitive Load",
|
|
1435
|
+
impact: Math.round(loadScore - 50),
|
|
1436
|
+
description: params.cognitiveLoad.rating
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
name: "Pattern Entropy",
|
|
1440
|
+
impact: Math.round(entropyScore - 50),
|
|
1441
|
+
description: params.patternEntropy.rating
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
name: "Concept Cohesion",
|
|
1445
|
+
impact: Math.round(cohesionScore - 50),
|
|
1446
|
+
description: params.conceptCohesion.rating
|
|
1447
|
+
}
|
|
1448
|
+
];
|
|
1449
|
+
const recommendations = [];
|
|
1450
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
1451
|
+
recommendations.push({
|
|
1452
|
+
action: rec,
|
|
1453
|
+
estimatedImpact: 5,
|
|
1454
|
+
priority: "medium"
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
if (params.conceptCohesion.rating === "poor") {
|
|
1458
|
+
recommendations.push({
|
|
1459
|
+
action: "Improve concept cohesion by grouping related exports",
|
|
1460
|
+
estimatedImpact: 8,
|
|
1461
|
+
priority: "high"
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
1465
|
+
return {
|
|
1466
|
+
toolName: "future-proof",
|
|
1467
|
+
score: overall,
|
|
1468
|
+
rawMetrics: {
|
|
1469
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
1470
|
+
entropyScore: params.patternEntropy.entropy,
|
|
1471
|
+
cohesionScore: params.conceptCohesion.score,
|
|
1472
|
+
semanticDistanceAvg
|
|
1473
|
+
},
|
|
1474
|
+
factors,
|
|
1475
|
+
recommendations
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// src/utils/history.ts
|
|
1480
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
1481
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
1482
|
+
function getHistoryPath(rootDir) {
|
|
1483
|
+
return join4(rootDir, ".aiready", "history.json");
|
|
1484
|
+
}
|
|
1485
|
+
function loadScoreHistory(rootDir) {
|
|
1486
|
+
const historyPath = getHistoryPath(rootDir);
|
|
1487
|
+
if (!existsSync4(historyPath)) {
|
|
1488
|
+
return [];
|
|
1489
|
+
}
|
|
1490
|
+
try {
|
|
1491
|
+
const data = readFileSync2(historyPath, "utf-8");
|
|
1492
|
+
return JSON.parse(data);
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
console.warn("Failed to load score history:", error);
|
|
1495
|
+
return [];
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
function saveScoreEntry(rootDir, entry) {
|
|
1499
|
+
const historyPath = getHistoryPath(rootDir);
|
|
1500
|
+
const historyDir = dirname3(historyPath);
|
|
1501
|
+
if (!existsSync4(historyDir)) {
|
|
1502
|
+
mkdirSync2(historyDir, { recursive: true });
|
|
1503
|
+
}
|
|
1504
|
+
const history = loadScoreHistory(rootDir);
|
|
1505
|
+
const newEntry = {
|
|
1506
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1507
|
+
...entry
|
|
1508
|
+
};
|
|
1509
|
+
const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1e3;
|
|
1510
|
+
const filteredHistory = history.filter(
|
|
1511
|
+
(e) => new Date(e.timestamp).getTime() > oneYearAgo
|
|
1512
|
+
);
|
|
1513
|
+
filteredHistory.push(newEntry);
|
|
1514
|
+
writeFileSync2(historyPath, JSON.stringify(filteredHistory, null, 2));
|
|
1515
|
+
}
|
|
1516
|
+
function getHistorySummary(rootDir) {
|
|
1517
|
+
const history = loadScoreHistory(rootDir);
|
|
1518
|
+
if (history.length === 0) {
|
|
1519
|
+
return {
|
|
1520
|
+
totalScans: 0,
|
|
1521
|
+
firstScan: null,
|
|
1522
|
+
lastScan: null,
|
|
1523
|
+
avgScore: 0
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
const scores = history.map((e) => e.overallScore);
|
|
1527
|
+
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
1528
|
+
return {
|
|
1529
|
+
totalScans: history.length,
|
|
1530
|
+
firstScan: history[0].timestamp,
|
|
1531
|
+
lastScan: history[history.length - 1].timestamp,
|
|
1532
|
+
avgScore: Math.round(avgScore)
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
function exportHistory(rootDir, format = "json") {
|
|
1536
|
+
const history = loadScoreHistory(rootDir);
|
|
1537
|
+
if (format === "csv") {
|
|
1538
|
+
const headers = "timestamp,overallScore,totalIssues,totalTokens,patternScore,contextScore,consistencyScore\n";
|
|
1539
|
+
const rows = history.map(
|
|
1540
|
+
(e) => `${e.timestamp},${e.overallScore},${e.totalIssues},${e.totalTokens},${e.breakdown?.["pattern-detect"] || ""},${e.breakdown?.["context-analyzer"] || ""},${e.breakdown?.["consistency"] || ""}`
|
|
1541
|
+
).join("\n");
|
|
1542
|
+
return headers + rows;
|
|
1543
|
+
}
|
|
1544
|
+
return JSON.stringify(history, null, 2);
|
|
1545
|
+
}
|
|
1546
|
+
function clearHistory(rootDir) {
|
|
1547
|
+
const historyPath = getHistoryPath(rootDir);
|
|
1548
|
+
if (existsSync4(historyPath)) {
|
|
1549
|
+
writeFileSync2(historyPath, JSON.stringify([]));
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1056
1552
|
export {
|
|
1057
1553
|
DEFAULT_COST_CONFIG,
|
|
1058
1554
|
DEFAULT_EXCLUDE,
|
|
@@ -1064,12 +1560,23 @@ export {
|
|
|
1064
1560
|
PythonParser,
|
|
1065
1561
|
TOOL_NAME_MAP,
|
|
1066
1562
|
TypeScriptParser,
|
|
1563
|
+
calculateCognitiveLoad,
|
|
1067
1564
|
calculateComprehensionDifficulty,
|
|
1565
|
+
calculateConceptCohesion,
|
|
1566
|
+
calculateFutureProofScore,
|
|
1068
1567
|
calculateImportSimilarity,
|
|
1568
|
+
calculateKnowledgeConcentration,
|
|
1069
1569
|
calculateMonthlyCost,
|
|
1070
1570
|
calculateOverallScore,
|
|
1571
|
+
calculatePatternEntropy,
|
|
1071
1572
|
calculateProductivityImpact,
|
|
1573
|
+
calculateRemediationVelocity,
|
|
1574
|
+
calculateScoreTrend,
|
|
1575
|
+
calculateSemanticDistance,
|
|
1576
|
+
calculateTechnicalDebtInterest,
|
|
1577
|
+
clearHistory,
|
|
1072
1578
|
estimateTokens,
|
|
1579
|
+
exportHistory,
|
|
1073
1580
|
extractFunctions,
|
|
1074
1581
|
extractImports,
|
|
1075
1582
|
formatAcceptanceRate,
|
|
@@ -1078,8 +1585,10 @@ export {
|
|
|
1078
1585
|
formatScore,
|
|
1079
1586
|
formatToolScore,
|
|
1080
1587
|
generateHTML,
|
|
1588
|
+
getDebtBreakdown,
|
|
1081
1589
|
getElapsedTime,
|
|
1082
1590
|
getFileExtension,
|
|
1591
|
+
getHistorySummary,
|
|
1083
1592
|
getParser,
|
|
1084
1593
|
getRating,
|
|
1085
1594
|
getRatingDisplay,
|
|
@@ -1091,6 +1600,7 @@ export {
|
|
|
1091
1600
|
isSourceFile,
|
|
1092
1601
|
loadConfig,
|
|
1093
1602
|
loadMergedConfig,
|
|
1603
|
+
loadScoreHistory,
|
|
1094
1604
|
mergeConfigWithDefaults,
|
|
1095
1605
|
normalizeToolName,
|
|
1096
1606
|
parseCode,
|
|
@@ -1099,5 +1609,6 @@ export {
|
|
|
1099
1609
|
predictAcceptanceRate,
|
|
1100
1610
|
readFileContent,
|
|
1101
1611
|
resolveOutputPath,
|
|
1612
|
+
saveScoreEntry,
|
|
1102
1613
|
scanFiles
|
|
1103
1614
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/core",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.26",
|
|
4
4
|
"description": "Shared utilities for AIReady analysis tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"typescript": "^5.9.3"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
|
-
"build": "tsup src/index.ts src/client.ts --format cjs,esm
|
|
55
|
-
"dev": "tsup src/index.ts src/client.ts --format cjs,esm --
|
|
54
|
+
"build": "tsup src/index.ts src/client.ts --format cjs,esm",
|
|
55
|
+
"dev": "tsup src/index.ts src/client.ts --format cjs,esm --watch",
|
|
56
56
|
"lint": "eslint src",
|
|
57
57
|
"clean": "rm -rf dist",
|
|
58
58
|
"release": "pnpm build && pnpm publish --no-git-checks"
|