@aiready/core 0.9.23 → 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.mjs CHANGED
@@ -19,7 +19,8 @@ import {
19
19
  import { glob } from "glob";
20
20
  import { readFile } from "fs/promises";
21
21
  import { existsSync } from "fs";
22
- import { join } from "path";
22
+ import { join, relative } from "path";
23
+ import ignorePkg from "ignore";
23
24
  var DEFAULT_EXCLUDE = [
24
25
  // Dependencies
25
26
  "**/node_modules/**",
@@ -88,6 +89,22 @@ async function scanFiles(options) {
88
89
  ignore: finalExclude,
89
90
  absolute: true
90
91
  });
92
+ const gitignorePath = join(rootDir || ".", ".gitignore");
93
+ if (existsSync(gitignorePath)) {
94
+ try {
95
+ const gitTxt = await readFile(gitignorePath, "utf-8");
96
+ const ig = ignorePkg();
97
+ ig.add(gitTxt);
98
+ const filtered = files.filter((f) => {
99
+ let rel = relative(rootDir || ".", f).replace(/\\/g, "/");
100
+ if (rel === "") rel = f;
101
+ return !ig.ignores(rel);
102
+ });
103
+ return filtered;
104
+ } catch (e) {
105
+ return files;
106
+ }
107
+ }
91
108
  return files;
92
109
  }
93
110
  async function readFileContent(filePath) {
@@ -396,6 +413,361 @@ function getElapsedTime(startTime) {
396
413
  return ((Date.now() - startTime) / 1e3).toFixed(2);
397
414
  }
398
415
 
416
+ // src/business-metrics.ts
417
+ var DEFAULT_COST_CONFIG = {
418
+ pricePer1KTokens: 0.01,
419
+ // $0.01 per 1K tokens (GPT-4)
420
+ queriesPerDevPerDay: 50,
421
+ // Average AI queries per developer
422
+ developerCount: 5,
423
+ // Default team size
424
+ daysPerMonth: 30
425
+ };
426
+ var SEVERITY_TIME_ESTIMATES = {
427
+ critical: 4,
428
+ // Complex architectural issues
429
+ major: 2,
430
+ // Significant refactoring needed
431
+ minor: 0.5,
432
+ // Simple naming/style fixes
433
+ info: 0.25
434
+ // Documentation improvements
435
+ };
436
+ var DEFAULT_HOURLY_RATE = 75;
437
+ function calculateMonthlyCost(tokenWaste, config = {}) {
438
+ const cfg = { ...DEFAULT_COST_CONFIG, ...config };
439
+ const tokensPerDay = tokenWaste * cfg.queriesPerDevPerDay;
440
+ const tokensPerMonth = tokensPerDay * cfg.daysPerMonth;
441
+ const cost = tokensPerMonth / 1e3 * cfg.pricePer1KTokens;
442
+ return Math.round(cost * 100) / 100;
443
+ }
444
+ function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
445
+ const counts = {
446
+ critical: issues.filter((i) => i.severity === "critical").length,
447
+ major: issues.filter((i) => i.severity === "major").length,
448
+ minor: issues.filter((i) => i.severity === "minor").length,
449
+ info: issues.filter((i) => i.severity === "info").length
450
+ };
451
+ const hours = {
452
+ critical: counts.critical * SEVERITY_TIME_ESTIMATES.critical,
453
+ major: counts.major * SEVERITY_TIME_ESTIMATES.major,
454
+ minor: counts.minor * SEVERITY_TIME_ESTIMATES.minor,
455
+ info: counts.info * SEVERITY_TIME_ESTIMATES.info
456
+ };
457
+ const totalHours = hours.critical + hours.major + hours.minor + hours.info;
458
+ const totalCost = totalHours * hourlyRate;
459
+ return {
460
+ totalHours: Math.round(totalHours * 10) / 10,
461
+ hourlyRate,
462
+ totalCost: Math.round(totalCost),
463
+ bySeverity: {
464
+ critical: { hours: Math.round(hours.critical * 10) / 10, cost: Math.round(hours.critical * hourlyRate) },
465
+ major: { hours: Math.round(hours.major * 10) / 10, cost: Math.round(hours.major * hourlyRate) },
466
+ minor: { hours: Math.round(hours.minor * 10) / 10, cost: Math.round(hours.minor * hourlyRate) }
467
+ }
468
+ };
469
+ }
470
+ function predictAcceptanceRate(toolOutputs) {
471
+ const factors = [];
472
+ const patterns = toolOutputs.get("pattern-detect");
473
+ if (patterns) {
474
+ const patternImpact = (patterns.score - 50) * 0.3;
475
+ factors.push({
476
+ name: "Semantic Duplication",
477
+ impact: Math.round(patternImpact)
478
+ });
479
+ }
480
+ const context = toolOutputs.get("context-analyzer");
481
+ if (context) {
482
+ const contextImpact = (context.score - 50) * 0.4;
483
+ factors.push({
484
+ name: "Context Efficiency",
485
+ impact: Math.round(contextImpact)
486
+ });
487
+ }
488
+ const consistency = toolOutputs.get("consistency");
489
+ if (consistency) {
490
+ const consistencyImpact = (consistency.score - 50) * 0.3;
491
+ factors.push({
492
+ name: "Code Consistency",
493
+ impact: Math.round(consistencyImpact)
494
+ });
495
+ }
496
+ const baseRate = 0.65;
497
+ const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
498
+ const rate = Math.max(0.1, Math.min(0.95, baseRate + totalImpact));
499
+ const confidence = toolOutputs.size >= 3 ? 0.8 : toolOutputs.size >= 2 ? 0.6 : 0.4;
500
+ return {
501
+ rate: Math.round(rate * 100) / 100,
502
+ confidence,
503
+ factors
504
+ };
505
+ }
506
+ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, consistencyScore, totalFiles) {
507
+ const budgetFactor = Math.min(100, Math.max(0, (contextBudget - 5e3) / 250));
508
+ const depthFactor = Math.min(100, Math.max(0, (importDepth - 5) * 10));
509
+ const fragmentationFactor = Math.min(100, Math.max(0, (fragmentation - 0.3) * 250));
510
+ const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
511
+ const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
512
+ const score = Math.round(
513
+ budgetFactor * 0.35 + depthFactor * 0.2 + fragmentationFactor * 0.2 + consistencyFactor * 0.15 + fileFactor * 0.1
514
+ );
515
+ let rating;
516
+ if (score < 20) rating = "trivial";
517
+ else if (score < 40) rating = "easy";
518
+ else if (score < 60) rating = "moderate";
519
+ else if (score < 80) rating = "difficult";
520
+ else rating = "expert";
521
+ return {
522
+ score,
523
+ rating,
524
+ factors: [
525
+ {
526
+ name: "Context Budget",
527
+ contribution: Math.round(budgetFactor * 0.35),
528
+ description: `${Math.round(contextBudget)} tokens required`
529
+ },
530
+ {
531
+ name: "Import Depth",
532
+ contribution: Math.round(depthFactor * 0.2),
533
+ description: `${importDepth.toFixed(1)} average levels`
534
+ },
535
+ {
536
+ name: "Code Fragmentation",
537
+ contribution: Math.round(fragmentationFactor * 0.2),
538
+ description: `${(fragmentation * 100).toFixed(0)}% fragmentation`
539
+ },
540
+ {
541
+ name: "Consistency",
542
+ contribution: Math.round(consistencyFactor * 0.15),
543
+ description: `${consistencyScore}/100 consistency score`
544
+ },
545
+ {
546
+ name: "Project Scale",
547
+ contribution: Math.round(fileFactor * 0.1),
548
+ description: `${totalFiles} files analyzed`
549
+ }
550
+ ]
551
+ };
552
+ }
553
+ function formatCost(cost) {
554
+ if (cost < 1) {
555
+ return `$${cost.toFixed(2)}`;
556
+ } else if (cost < 1e3) {
557
+ return `$${cost.toFixed(0)}`;
558
+ } else {
559
+ return `$${(cost / 1e3).toFixed(1)}k`;
560
+ }
561
+ }
562
+ function formatHours(hours) {
563
+ if (hours < 1) {
564
+ return `${Math.round(hours * 60)}min`;
565
+ } else if (hours < 8) {
566
+ return `${hours.toFixed(1)}h`;
567
+ } else if (hours < 40) {
568
+ return `${Math.round(hours)}h`;
569
+ } else {
570
+ return `${(hours / 40).toFixed(1)} weeks`;
571
+ }
572
+ }
573
+ function formatAcceptanceRate(rate) {
574
+ return `${Math.round(rate * 100)}%`;
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
+ }
770
+
399
771
  // src/parsers/typescript-parser.ts
400
772
  import { parse as parse2 } from "@typescript-eslint/typescript-estree";
401
773
  var TypeScriptParser = class {
@@ -875,7 +1247,310 @@ function isFileSupported(filePath) {
875
1247
  function getSupportedLanguages() {
876
1248
  return ParserFactory.getInstance().getSupportedLanguages();
877
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
+ }
878
1552
  export {
1553
+ DEFAULT_COST_CONFIG,
879
1554
  DEFAULT_EXCLUDE,
880
1555
  DEFAULT_TOOL_WEIGHTS,
881
1556
  LANGUAGE_EXTENSIONS,
@@ -885,16 +1560,35 @@ export {
885
1560
  PythonParser,
886
1561
  TOOL_NAME_MAP,
887
1562
  TypeScriptParser,
1563
+ calculateCognitiveLoad,
1564
+ calculateComprehensionDifficulty,
1565
+ calculateConceptCohesion,
1566
+ calculateFutureProofScore,
888
1567
  calculateImportSimilarity,
1568
+ calculateKnowledgeConcentration,
1569
+ calculateMonthlyCost,
889
1570
  calculateOverallScore,
1571
+ calculatePatternEntropy,
1572
+ calculateProductivityImpact,
1573
+ calculateRemediationVelocity,
1574
+ calculateScoreTrend,
1575
+ calculateSemanticDistance,
1576
+ calculateTechnicalDebtInterest,
1577
+ clearHistory,
890
1578
  estimateTokens,
1579
+ exportHistory,
891
1580
  extractFunctions,
892
1581
  extractImports,
1582
+ formatAcceptanceRate,
1583
+ formatCost,
1584
+ formatHours,
893
1585
  formatScore,
894
1586
  formatToolScore,
895
1587
  generateHTML,
1588
+ getDebtBreakdown,
896
1589
  getElapsedTime,
897
1590
  getFileExtension,
1591
+ getHistorySummary,
898
1592
  getParser,
899
1593
  getRating,
900
1594
  getRatingDisplay,
@@ -906,12 +1600,15 @@ export {
906
1600
  isSourceFile,
907
1601
  loadConfig,
908
1602
  loadMergedConfig,
1603
+ loadScoreHistory,
909
1604
  mergeConfigWithDefaults,
910
1605
  normalizeToolName,
911
1606
  parseCode,
912
1607
  parseFileExports,
913
1608
  parseWeightString,
1609
+ predictAcceptanceRate,
914
1610
  readFileContent,
915
1611
  resolveOutputPath,
1612
+ saveScoreEntry,
916
1613
  scanFiles
917
1614
  };