@aiready/core 0.9.26 → 0.9.27
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-JJ5JL5FX.mjs +408 -0
- package/dist/client.js +58 -2
- package/dist/client.mjs +11 -1
- package/dist/index.js +512 -20
- package/dist/index.mjs +459 -19
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CONTEXT_TIER_THRESHOLDS,
|
|
2
3
|
DEFAULT_TOOL_WEIGHTS,
|
|
3
4
|
LANGUAGE_EXTENSIONS,
|
|
4
5
|
Language,
|
|
5
6
|
ParseError,
|
|
7
|
+
SIZE_ADJUSTED_THRESHOLDS,
|
|
6
8
|
TOOL_NAME_MAP,
|
|
7
9
|
calculateOverallScore,
|
|
8
10
|
formatScore,
|
|
9
11
|
formatToolScore,
|
|
10
12
|
generateHTML,
|
|
13
|
+
getProjectSizeTier,
|
|
11
14
|
getRating,
|
|
12
15
|
getRatingDisplay,
|
|
16
|
+
getRatingWithContext,
|
|
17
|
+
getRecommendedThreshold,
|
|
13
18
|
getToolWeight,
|
|
14
19
|
normalizeToolName,
|
|
15
20
|
parseWeightString
|
|
16
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-JJ5JL5FX.mjs";
|
|
17
22
|
|
|
18
23
|
// src/utils/file-scanner.ts
|
|
19
24
|
import { glob } from "glob";
|
|
@@ -414,11 +419,87 @@ function getElapsedTime(startTime) {
|
|
|
414
419
|
}
|
|
415
420
|
|
|
416
421
|
// src/business-metrics.ts
|
|
422
|
+
var MODEL_PRICING_PRESETS = {
|
|
423
|
+
"gpt-4": {
|
|
424
|
+
name: "GPT-4",
|
|
425
|
+
pricePer1KInputTokens: 0.03,
|
|
426
|
+
pricePer1KOutputTokens: 0.06,
|
|
427
|
+
contextTier: "standard",
|
|
428
|
+
typicalQueriesPerDevPerDay: 40
|
|
429
|
+
},
|
|
430
|
+
"gpt-4o": {
|
|
431
|
+
name: "GPT-4o",
|
|
432
|
+
pricePer1KInputTokens: 5e-3,
|
|
433
|
+
pricePer1KOutputTokens: 0.015,
|
|
434
|
+
contextTier: "extended",
|
|
435
|
+
typicalQueriesPerDevPerDay: 60
|
|
436
|
+
},
|
|
437
|
+
"gpt-4o-mini": {
|
|
438
|
+
name: "GPT-4o mini",
|
|
439
|
+
pricePer1KInputTokens: 15e-5,
|
|
440
|
+
pricePer1KOutputTokens: 6e-4,
|
|
441
|
+
contextTier: "extended",
|
|
442
|
+
typicalQueriesPerDevPerDay: 120
|
|
443
|
+
},
|
|
444
|
+
"claude-3-5-sonnet": {
|
|
445
|
+
name: "Claude 3.5 Sonnet",
|
|
446
|
+
pricePer1KInputTokens: 3e-3,
|
|
447
|
+
pricePer1KOutputTokens: 0.015,
|
|
448
|
+
contextTier: "extended",
|
|
449
|
+
typicalQueriesPerDevPerDay: 80
|
|
450
|
+
},
|
|
451
|
+
"claude-3-7-sonnet": {
|
|
452
|
+
name: "Claude 3.7 Sonnet",
|
|
453
|
+
pricePer1KInputTokens: 3e-3,
|
|
454
|
+
pricePer1KOutputTokens: 0.015,
|
|
455
|
+
contextTier: "frontier",
|
|
456
|
+
typicalQueriesPerDevPerDay: 80
|
|
457
|
+
},
|
|
458
|
+
"claude-sonnet-4": {
|
|
459
|
+
name: "Claude Sonnet 4",
|
|
460
|
+
pricePer1KInputTokens: 3e-3,
|
|
461
|
+
pricePer1KOutputTokens: 0.015,
|
|
462
|
+
contextTier: "frontier",
|
|
463
|
+
typicalQueriesPerDevPerDay: 80
|
|
464
|
+
},
|
|
465
|
+
"gemini-1-5-pro": {
|
|
466
|
+
name: "Gemini 1.5 Pro",
|
|
467
|
+
pricePer1KInputTokens: 125e-5,
|
|
468
|
+
pricePer1KOutputTokens: 5e-3,
|
|
469
|
+
contextTier: "frontier",
|
|
470
|
+
typicalQueriesPerDevPerDay: 80
|
|
471
|
+
},
|
|
472
|
+
"gemini-2-0-flash": {
|
|
473
|
+
name: "Gemini 2.0 Flash",
|
|
474
|
+
pricePer1KInputTokens: 1e-4,
|
|
475
|
+
pricePer1KOutputTokens: 4e-4,
|
|
476
|
+
contextTier: "frontier",
|
|
477
|
+
typicalQueriesPerDevPerDay: 150
|
|
478
|
+
},
|
|
479
|
+
"copilot": {
|
|
480
|
+
name: "GitHub Copilot (subscription)",
|
|
481
|
+
// Amortized per-request cost for a $19/month plan at 80 queries/day
|
|
482
|
+
pricePer1KInputTokens: 1e-4,
|
|
483
|
+
pricePer1KOutputTokens: 1e-4,
|
|
484
|
+
contextTier: "extended",
|
|
485
|
+
typicalQueriesPerDevPerDay: 80
|
|
486
|
+
},
|
|
487
|
+
"cursor-pro": {
|
|
488
|
+
name: "Cursor Pro (subscription)",
|
|
489
|
+
pricePer1KInputTokens: 1e-4,
|
|
490
|
+
pricePer1KOutputTokens: 1e-4,
|
|
491
|
+
contextTier: "frontier",
|
|
492
|
+
typicalQueriesPerDevPerDay: 100
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
function getModelPreset(modelId) {
|
|
496
|
+
return MODEL_PRICING_PRESETS[modelId] ?? MODEL_PRICING_PRESETS["gpt-4o"];
|
|
497
|
+
}
|
|
417
498
|
var DEFAULT_COST_CONFIG = {
|
|
418
|
-
pricePer1KTokens:
|
|
419
|
-
//
|
|
420
|
-
queriesPerDevPerDay:
|
|
421
|
-
// Average AI queries per developer
|
|
499
|
+
pricePer1KTokens: 5e-3,
|
|
500
|
+
// GPT-4o input price (updated from GPT-4 era 0.01)
|
|
501
|
+
queriesPerDevPerDay: 60,
|
|
502
|
+
// Average AI queries per developer (updated: 40→60 as of 2026)
|
|
422
503
|
developerCount: 5,
|
|
423
504
|
// Default team size
|
|
424
505
|
daysPerMonth: 30
|
|
@@ -469,43 +550,63 @@ function calculateProductivityImpact(issues, hourlyRate = DEFAULT_HOURLY_RATE) {
|
|
|
469
550
|
}
|
|
470
551
|
function predictAcceptanceRate(toolOutputs) {
|
|
471
552
|
const factors = [];
|
|
553
|
+
const baseRate = 0.3;
|
|
472
554
|
const patterns = toolOutputs.get("pattern-detect");
|
|
473
555
|
if (patterns) {
|
|
474
|
-
const patternImpact = (patterns.score - 50) *
|
|
556
|
+
const patternImpact = (patterns.score - 50) * 3e-3;
|
|
475
557
|
factors.push({
|
|
476
558
|
name: "Semantic Duplication",
|
|
477
|
-
impact: Math.round(patternImpact)
|
|
559
|
+
impact: Math.round(patternImpact * 100)
|
|
478
560
|
});
|
|
479
561
|
}
|
|
480
562
|
const context = toolOutputs.get("context-analyzer");
|
|
481
563
|
if (context) {
|
|
482
|
-
const contextImpact = (context.score - 50) *
|
|
564
|
+
const contextImpact = (context.score - 50) * 4e-3;
|
|
483
565
|
factors.push({
|
|
484
566
|
name: "Context Efficiency",
|
|
485
|
-
impact: Math.round(contextImpact)
|
|
567
|
+
impact: Math.round(contextImpact * 100)
|
|
486
568
|
});
|
|
487
569
|
}
|
|
488
570
|
const consistency = toolOutputs.get("consistency");
|
|
489
571
|
if (consistency) {
|
|
490
|
-
const consistencyImpact = (consistency.score - 50) *
|
|
572
|
+
const consistencyImpact = (consistency.score - 50) * 2e-3;
|
|
491
573
|
factors.push({
|
|
492
574
|
name: "Code Consistency",
|
|
493
|
-
impact: Math.round(consistencyImpact)
|
|
575
|
+
impact: Math.round(consistencyImpact * 100)
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
const hallucinationRisk = toolOutputs.get("hallucination-risk");
|
|
579
|
+
if (hallucinationRisk) {
|
|
580
|
+
const hrImpact = (50 - hallucinationRisk.score) * 2e-3;
|
|
581
|
+
factors.push({
|
|
582
|
+
name: "Hallucination Risk",
|
|
583
|
+
impact: Math.round(hrImpact * 100)
|
|
494
584
|
});
|
|
495
585
|
}
|
|
496
|
-
const baseRate = 0.65;
|
|
497
586
|
const totalImpact = factors.reduce((sum, f) => sum + f.impact / 100, 0);
|
|
498
|
-
const rate = Math.max(0.
|
|
499
|
-
|
|
587
|
+
const rate = Math.max(0.05, Math.min(0.8, baseRate + totalImpact));
|
|
588
|
+
let confidence;
|
|
589
|
+
if (toolOutputs.size >= 4) confidence = 0.75;
|
|
590
|
+
else if (toolOutputs.size >= 3) confidence = 0.65;
|
|
591
|
+
else if (toolOutputs.size >= 2) confidence = 0.5;
|
|
592
|
+
else confidence = 0.35;
|
|
500
593
|
return {
|
|
501
594
|
rate: Math.round(rate * 100) / 100,
|
|
502
595
|
confidence,
|
|
503
596
|
factors
|
|
504
597
|
};
|
|
505
598
|
}
|
|
506
|
-
function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, consistencyScore, totalFiles) {
|
|
507
|
-
const
|
|
508
|
-
const
|
|
599
|
+
function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentation, consistencyScore, totalFiles, modelTier = "standard") {
|
|
600
|
+
const tierThresholds = CONTEXT_TIER_THRESHOLDS[modelTier];
|
|
601
|
+
const idealBudget = tierThresholds.idealTokens;
|
|
602
|
+
const criticalBudget = tierThresholds.criticalTokens;
|
|
603
|
+
const idealDepth = tierThresholds.idealDepth;
|
|
604
|
+
const budgetRange = criticalBudget - idealBudget;
|
|
605
|
+
const budgetFactor = Math.min(100, Math.max(
|
|
606
|
+
0,
|
|
607
|
+
(contextBudget - idealBudget) / budgetRange * 100
|
|
608
|
+
));
|
|
609
|
+
const depthFactor = Math.min(100, Math.max(0, (importDepth - idealDepth) * 10));
|
|
509
610
|
const fragmentationFactor = Math.min(100, Math.max(0, (fragmentation - 0.3) * 250));
|
|
510
611
|
const consistencyFactor = Math.min(100, Math.max(0, 100 - consistencyScore));
|
|
511
612
|
const fileFactor = Math.min(100, Math.max(0, (totalFiles - 50) / 5));
|
|
@@ -525,12 +626,12 @@ function calculateComprehensionDifficulty(contextBudget, importDepth, fragmentat
|
|
|
525
626
|
{
|
|
526
627
|
name: "Context Budget",
|
|
527
628
|
contribution: Math.round(budgetFactor * 0.35),
|
|
528
|
-
description: `${Math.round(contextBudget)} tokens required`
|
|
629
|
+
description: `${Math.round(contextBudget)} tokens required (${modelTier} model tier: ideal <${idealBudget.toLocaleString()})`
|
|
529
630
|
},
|
|
530
631
|
{
|
|
531
632
|
name: "Import Depth",
|
|
532
633
|
contribution: Math.round(depthFactor * 0.2),
|
|
533
|
-
description: `${importDepth.toFixed(1)} average levels`
|
|
634
|
+
description: `${importDepth.toFixed(1)} average levels (ideal <${idealDepth} for ${modelTier})`
|
|
534
635
|
},
|
|
535
636
|
{
|
|
536
637
|
name: "Code Fragmentation",
|
|
@@ -1475,6 +1576,334 @@ function calculateFutureProofScore(params) {
|
|
|
1475
1576
|
recommendations
|
|
1476
1577
|
};
|
|
1477
1578
|
}
|
|
1579
|
+
function calculateHallucinationRisk(params) {
|
|
1580
|
+
const {
|
|
1581
|
+
overloadedSymbols,
|
|
1582
|
+
magicLiterals,
|
|
1583
|
+
booleanTraps,
|
|
1584
|
+
implicitSideEffects,
|
|
1585
|
+
deepCallbacks,
|
|
1586
|
+
ambiguousNames,
|
|
1587
|
+
undocumentedExports,
|
|
1588
|
+
totalSymbols,
|
|
1589
|
+
totalExports
|
|
1590
|
+
} = params;
|
|
1591
|
+
if (totalSymbols === 0) {
|
|
1592
|
+
return {
|
|
1593
|
+
score: 0,
|
|
1594
|
+
rating: "minimal",
|
|
1595
|
+
signals: [],
|
|
1596
|
+
topRisk: "No symbols to analyze",
|
|
1597
|
+
recommendations: []
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
const overloadRatio = Math.min(1, overloadedSymbols / Math.max(1, totalSymbols));
|
|
1601
|
+
const overloadSignal = {
|
|
1602
|
+
name: "Symbol Overloading",
|
|
1603
|
+
count: overloadedSymbols,
|
|
1604
|
+
riskContribution: Math.round(overloadRatio * 100 * 0.25),
|
|
1605
|
+
// 25% weight
|
|
1606
|
+
description: `${overloadedSymbols} overloaded symbols \u2014 AI picks wrong signature`
|
|
1607
|
+
};
|
|
1608
|
+
const magicRatio = Math.min(1, magicLiterals / Math.max(1, totalSymbols * 2));
|
|
1609
|
+
const magicSignal = {
|
|
1610
|
+
name: "Magic Literals",
|
|
1611
|
+
count: magicLiterals,
|
|
1612
|
+
riskContribution: Math.round(magicRatio * 100 * 0.2),
|
|
1613
|
+
// 20% weight
|
|
1614
|
+
description: `${magicLiterals} unnamed constants \u2014 AI invents wrong values`
|
|
1615
|
+
};
|
|
1616
|
+
const trapRatio = Math.min(1, booleanTraps / Math.max(1, totalSymbols));
|
|
1617
|
+
const trapSignal = {
|
|
1618
|
+
name: "Boolean Traps",
|
|
1619
|
+
count: booleanTraps,
|
|
1620
|
+
riskContribution: Math.round(trapRatio * 100 * 0.2),
|
|
1621
|
+
// 20% weight
|
|
1622
|
+
description: `${booleanTraps} boolean trap parameters \u2014 AI inverts intent`
|
|
1623
|
+
};
|
|
1624
|
+
const sideEffectRatio = Math.min(1, implicitSideEffects / Math.max(1, totalExports));
|
|
1625
|
+
const sideEffectSignal = {
|
|
1626
|
+
name: "Implicit Side Effects",
|
|
1627
|
+
count: implicitSideEffects,
|
|
1628
|
+
riskContribution: Math.round(sideEffectRatio * 100 * 0.15),
|
|
1629
|
+
// 15% weight
|
|
1630
|
+
description: `${implicitSideEffects} functions with implicit side effects \u2014 AI misses contracts`
|
|
1631
|
+
};
|
|
1632
|
+
const callbackRatio = Math.min(1, deepCallbacks / Math.max(1, totalSymbols * 0.1));
|
|
1633
|
+
const callbackSignal = {
|
|
1634
|
+
name: "Callback Nesting",
|
|
1635
|
+
count: deepCallbacks,
|
|
1636
|
+
riskContribution: Math.round(callbackRatio * 100 * 0.1),
|
|
1637
|
+
// 10% weight
|
|
1638
|
+
description: `${deepCallbacks} deep callback chains \u2014 AI loses control flow context`
|
|
1639
|
+
};
|
|
1640
|
+
const ambiguousRatio = Math.min(1, ambiguousNames / Math.max(1, totalSymbols));
|
|
1641
|
+
const ambiguousSignal = {
|
|
1642
|
+
name: "Ambiguous Names",
|
|
1643
|
+
count: ambiguousNames,
|
|
1644
|
+
riskContribution: Math.round(ambiguousRatio * 100 * 0.1),
|
|
1645
|
+
// 10% weight
|
|
1646
|
+
description: `${ambiguousNames} non-descriptive identifiers \u2014 AI guesses wrong intent`
|
|
1647
|
+
};
|
|
1648
|
+
const undocRatio = Math.min(1, undocumentedExports / Math.max(1, totalExports));
|
|
1649
|
+
const undocSignal = {
|
|
1650
|
+
name: "Undocumented Exports",
|
|
1651
|
+
count: undocumentedExports,
|
|
1652
|
+
riskContribution: Math.round(undocRatio * 100 * 0.1),
|
|
1653
|
+
// 10% weight
|
|
1654
|
+
description: `${undocumentedExports} public functions without docs \u2014 AI fabricates behavior`
|
|
1655
|
+
};
|
|
1656
|
+
const signals = [
|
|
1657
|
+
overloadSignal,
|
|
1658
|
+
magicSignal,
|
|
1659
|
+
trapSignal,
|
|
1660
|
+
sideEffectSignal,
|
|
1661
|
+
callbackSignal,
|
|
1662
|
+
ambiguousSignal,
|
|
1663
|
+
undocSignal
|
|
1664
|
+
];
|
|
1665
|
+
const score = Math.min(100, signals.reduce((sum, s) => sum + s.riskContribution, 0));
|
|
1666
|
+
let rating;
|
|
1667
|
+
if (score < 10) rating = "minimal";
|
|
1668
|
+
else if (score < 25) rating = "low";
|
|
1669
|
+
else if (score < 50) rating = "moderate";
|
|
1670
|
+
else if (score < 75) rating = "high";
|
|
1671
|
+
else rating = "severe";
|
|
1672
|
+
const topSignal = signals.reduce((a, b) => a.riskContribution > b.riskContribution ? a : b);
|
|
1673
|
+
const topRisk = topSignal.riskContribution > 0 ? topSignal.description : "No significant hallucination risks detected";
|
|
1674
|
+
const recommendations = [];
|
|
1675
|
+
if (overloadSignal.riskContribution > 5) {
|
|
1676
|
+
recommendations.push(`Rename ${overloadedSymbols} overloaded symbols to unique, intent-revealing names`);
|
|
1677
|
+
}
|
|
1678
|
+
if (magicSignal.riskContribution > 5) {
|
|
1679
|
+
recommendations.push(`Extract ${magicLiterals} magic literals into named constants`);
|
|
1680
|
+
}
|
|
1681
|
+
if (trapSignal.riskContribution > 5) {
|
|
1682
|
+
recommendations.push(`Replace ${booleanTraps} boolean traps with named options objects`);
|
|
1683
|
+
}
|
|
1684
|
+
if (undocSignal.riskContribution > 5) {
|
|
1685
|
+
recommendations.push(`Add JSDoc/docstrings to ${undocumentedExports} undocumented public functions`);
|
|
1686
|
+
}
|
|
1687
|
+
if (sideEffectSignal.riskContribution > 5) {
|
|
1688
|
+
recommendations.push("Mark functions with side effects explicitly in their names or docs");
|
|
1689
|
+
}
|
|
1690
|
+
return {
|
|
1691
|
+
score: Math.round(score),
|
|
1692
|
+
rating,
|
|
1693
|
+
signals: signals.filter((s) => s.count > 0),
|
|
1694
|
+
topRisk,
|
|
1695
|
+
recommendations
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
function calculateAgentGrounding(params) {
|
|
1699
|
+
const {
|
|
1700
|
+
deepDirectories,
|
|
1701
|
+
totalDirectories,
|
|
1702
|
+
vagueFileNames,
|
|
1703
|
+
totalFiles,
|
|
1704
|
+
hasRootReadme,
|
|
1705
|
+
readmeIsFresh,
|
|
1706
|
+
barrelExports,
|
|
1707
|
+
untypedExports,
|
|
1708
|
+
totalExports,
|
|
1709
|
+
inconsistentDomainTerms,
|
|
1710
|
+
domainVocabularySize
|
|
1711
|
+
} = params;
|
|
1712
|
+
const deepDirRatio = totalDirectories > 0 ? deepDirectories / totalDirectories : 0;
|
|
1713
|
+
const structureClarityScore = Math.max(0, Math.round(100 - deepDirRatio * 80));
|
|
1714
|
+
const vagueRatio = totalFiles > 0 ? vagueFileNames / totalFiles : 0;
|
|
1715
|
+
const selfDocumentationScore = Math.max(0, Math.round(100 - vagueRatio * 90));
|
|
1716
|
+
let entryPointScore = 60;
|
|
1717
|
+
if (hasRootReadme) entryPointScore += 25;
|
|
1718
|
+
if (readmeIsFresh) entryPointScore += 10;
|
|
1719
|
+
const barrelRatio = totalFiles > 0 ? barrelExports / (totalFiles * 0.1) : 0;
|
|
1720
|
+
entryPointScore += Math.round(Math.min(5, barrelRatio * 5));
|
|
1721
|
+
entryPointScore = Math.min(100, entryPointScore);
|
|
1722
|
+
const untypedRatio = totalExports > 0 ? untypedExports / totalExports : 0;
|
|
1723
|
+
const apiClarityScore = Math.max(0, Math.round(100 - untypedRatio * 70));
|
|
1724
|
+
const inconsistencyRatio = domainVocabularySize > 0 ? inconsistentDomainTerms / domainVocabularySize : 0;
|
|
1725
|
+
const domainConsistencyScore = Math.max(0, Math.round(100 - inconsistencyRatio * 80));
|
|
1726
|
+
const score = Math.round(
|
|
1727
|
+
structureClarityScore * 0.2 + selfDocumentationScore * 0.25 + entryPointScore * 0.2 + apiClarityScore * 0.15 + domainConsistencyScore * 0.2
|
|
1728
|
+
);
|
|
1729
|
+
let rating;
|
|
1730
|
+
if (score >= 85) rating = "excellent";
|
|
1731
|
+
else if (score >= 70) rating = "good";
|
|
1732
|
+
else if (score >= 50) rating = "moderate";
|
|
1733
|
+
else if (score >= 30) rating = "poor";
|
|
1734
|
+
else rating = "disorienting";
|
|
1735
|
+
const recommendations = [];
|
|
1736
|
+
if (structureClarityScore < 70) {
|
|
1737
|
+
recommendations.push(`Flatten ${deepDirectories} overly-deep directories to improve agent navigation`);
|
|
1738
|
+
}
|
|
1739
|
+
if (selfDocumentationScore < 70) {
|
|
1740
|
+
recommendations.push(`Rename ${vagueFileNames} vague files (utils, helpers, misc) to domain-specific names`);
|
|
1741
|
+
}
|
|
1742
|
+
if (!hasRootReadme) {
|
|
1743
|
+
recommendations.push("Add a root README.md so agents understand the project context immediately");
|
|
1744
|
+
} else if (!readmeIsFresh) {
|
|
1745
|
+
recommendations.push("Update README.md \u2014 stale entry-point documentation disorients agents");
|
|
1746
|
+
}
|
|
1747
|
+
if (apiClarityScore < 70) {
|
|
1748
|
+
recommendations.push(`Add TypeScript types to ${untypedExports} untyped exports to improve API discoverability`);
|
|
1749
|
+
}
|
|
1750
|
+
if (domainConsistencyScore < 70) {
|
|
1751
|
+
recommendations.push(`Unify ${inconsistentDomainTerms} inconsistent domain terms \u2014 agents need one word per concept`);
|
|
1752
|
+
}
|
|
1753
|
+
return {
|
|
1754
|
+
score,
|
|
1755
|
+
rating,
|
|
1756
|
+
dimensions: {
|
|
1757
|
+
structureClarityScore,
|
|
1758
|
+
selfDocumentationScore,
|
|
1759
|
+
entryPointScore,
|
|
1760
|
+
apiClarityScore,
|
|
1761
|
+
domainConsistencyScore
|
|
1762
|
+
},
|
|
1763
|
+
recommendations
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
function calculateTestabilityIndex(params) {
|
|
1767
|
+
const {
|
|
1768
|
+
testFiles,
|
|
1769
|
+
sourceFiles,
|
|
1770
|
+
pureFunctions,
|
|
1771
|
+
totalFunctions,
|
|
1772
|
+
injectionPatterns,
|
|
1773
|
+
totalClasses,
|
|
1774
|
+
bloatedInterfaces,
|
|
1775
|
+
totalInterfaces,
|
|
1776
|
+
externalStateMutations,
|
|
1777
|
+
hasTestFramework
|
|
1778
|
+
} = params;
|
|
1779
|
+
const rawCoverageRatio = sourceFiles > 0 ? testFiles / sourceFiles : 0;
|
|
1780
|
+
const testCoverageRatio = Math.min(100, Math.round(rawCoverageRatio * 100));
|
|
1781
|
+
const purityRatio = totalFunctions > 0 ? pureFunctions / totalFunctions : 0.5;
|
|
1782
|
+
const purityScore = Math.round(purityRatio * 100);
|
|
1783
|
+
const injectionRatio = totalClasses > 0 ? injectionPatterns / totalClasses : 0.5;
|
|
1784
|
+
const dependencyInjectionScore = Math.round(Math.min(100, injectionRatio * 100));
|
|
1785
|
+
const bloatedRatio = totalInterfaces > 0 ? bloatedInterfaces / totalInterfaces : 0;
|
|
1786
|
+
const interfaceFocusScore = Math.max(0, Math.round(100 - bloatedRatio * 80));
|
|
1787
|
+
const mutationRatio = totalFunctions > 0 ? externalStateMutations / totalFunctions : 0;
|
|
1788
|
+
const observabilityScore = Math.max(0, Math.round(100 - mutationRatio * 100));
|
|
1789
|
+
const frameworkWeight = hasTestFramework ? 1 : 0.8;
|
|
1790
|
+
const rawScore = (testCoverageRatio * 0.3 + purityScore * 0.25 + dependencyInjectionScore * 0.2 + interfaceFocusScore * 0.1 + observabilityScore * 0.15) * frameworkWeight;
|
|
1791
|
+
const score = Math.max(0, Math.min(100, Math.round(rawScore)));
|
|
1792
|
+
let rating;
|
|
1793
|
+
if (score >= 85) rating = "excellent";
|
|
1794
|
+
else if (score >= 70) rating = "good";
|
|
1795
|
+
else if (score >= 50) rating = "moderate";
|
|
1796
|
+
else if (score >= 30) rating = "poor";
|
|
1797
|
+
else rating = "unverifiable";
|
|
1798
|
+
let aiChangeSafetyRating;
|
|
1799
|
+
if (rawCoverageRatio >= 0.5 && score >= 70) aiChangeSafetyRating = "safe";
|
|
1800
|
+
else if (rawCoverageRatio >= 0.2 && score >= 50) aiChangeSafetyRating = "moderate-risk";
|
|
1801
|
+
else if (rawCoverageRatio > 0) aiChangeSafetyRating = "high-risk";
|
|
1802
|
+
else aiChangeSafetyRating = "blind-risk";
|
|
1803
|
+
const recommendations = [];
|
|
1804
|
+
if (!hasTestFramework) {
|
|
1805
|
+
recommendations.push("Add a testing framework (Jest, Vitest, pytest) \u2014 AI changes cannot be verified without tests");
|
|
1806
|
+
}
|
|
1807
|
+
if (rawCoverageRatio < 0.3) {
|
|
1808
|
+
const neededTests = Math.round(sourceFiles * 0.3 - testFiles);
|
|
1809
|
+
recommendations.push(`Add ~${neededTests} test files to reach 30% coverage ratio \u2014 minimum for safe AI assistance`);
|
|
1810
|
+
}
|
|
1811
|
+
if (purityScore < 50) {
|
|
1812
|
+
recommendations.push("Extract pure functions from side-effectful code \u2014 pure functions are trivially AI-testable");
|
|
1813
|
+
}
|
|
1814
|
+
if (dependencyInjectionScore < 50 && totalClasses > 0) {
|
|
1815
|
+
recommendations.push("Adopt dependency injection \u2014 makes classes mockable and AI-generated code verifiable");
|
|
1816
|
+
}
|
|
1817
|
+
if (externalStateMutations > totalFunctions * 0.3) {
|
|
1818
|
+
recommendations.push("Reduce direct state mutations \u2014 return values instead to improve observability");
|
|
1819
|
+
}
|
|
1820
|
+
return {
|
|
1821
|
+
score,
|
|
1822
|
+
rating,
|
|
1823
|
+
dimensions: {
|
|
1824
|
+
testCoverageRatio,
|
|
1825
|
+
purityScore,
|
|
1826
|
+
dependencyInjectionScore,
|
|
1827
|
+
interfaceFocusScore,
|
|
1828
|
+
observabilityScore
|
|
1829
|
+
},
|
|
1830
|
+
aiChangeSafetyRating,
|
|
1831
|
+
recommendations
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
function calculateExtendedFutureProofScore(params) {
|
|
1835
|
+
const loadScore = 100 - params.cognitiveLoad.score;
|
|
1836
|
+
const entropyScore = 100 - params.patternEntropy.entropy * 100;
|
|
1837
|
+
const cohesionScore = params.conceptCohesion.score * 100;
|
|
1838
|
+
const hallucinationScore = 100 - params.hallucinationRisk.score;
|
|
1839
|
+
const groundingScore = params.agentGrounding.score;
|
|
1840
|
+
const testabilityScore = params.testability.score;
|
|
1841
|
+
const overall = Math.round(
|
|
1842
|
+
loadScore * 0.2 + entropyScore * 0.15 + cohesionScore * 0.15 + hallucinationScore * 0.2 + groundingScore * 0.15 + testabilityScore * 0.15
|
|
1843
|
+
);
|
|
1844
|
+
const factors = [
|
|
1845
|
+
{
|
|
1846
|
+
name: "Cognitive Load",
|
|
1847
|
+
impact: Math.round(loadScore - 50),
|
|
1848
|
+
description: params.cognitiveLoad.rating
|
|
1849
|
+
},
|
|
1850
|
+
{
|
|
1851
|
+
name: "Pattern Entropy",
|
|
1852
|
+
impact: Math.round(entropyScore - 50),
|
|
1853
|
+
description: params.patternEntropy.rating
|
|
1854
|
+
},
|
|
1855
|
+
{
|
|
1856
|
+
name: "Concept Cohesion",
|
|
1857
|
+
impact: Math.round(cohesionScore - 50),
|
|
1858
|
+
description: params.conceptCohesion.rating
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
name: "Hallucination Risk",
|
|
1862
|
+
impact: Math.round(hallucinationScore - 50),
|
|
1863
|
+
description: `${params.hallucinationRisk.rating} risk (${params.hallucinationRisk.score}/100 raw)`
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
name: "Agent Grounding",
|
|
1867
|
+
impact: Math.round(groundingScore - 50),
|
|
1868
|
+
description: params.agentGrounding.rating
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
name: "Testability",
|
|
1872
|
+
impact: Math.round(testabilityScore - 50),
|
|
1873
|
+
description: `${params.testability.rating} \u2014 AI changes are ${params.testability.aiChangeSafetyRating}`
|
|
1874
|
+
}
|
|
1875
|
+
];
|
|
1876
|
+
const recommendations = [];
|
|
1877
|
+
for (const rec of params.hallucinationRisk.recommendations) {
|
|
1878
|
+
recommendations.push({ action: rec, estimatedImpact: 8, priority: "high" });
|
|
1879
|
+
}
|
|
1880
|
+
for (const rec of params.agentGrounding.recommendations) {
|
|
1881
|
+
recommendations.push({ action: rec, estimatedImpact: 6, priority: "medium" });
|
|
1882
|
+
}
|
|
1883
|
+
for (const rec of params.testability.recommendations) {
|
|
1884
|
+
const priority = params.testability.aiChangeSafetyRating === "blind-risk" ? "high" : "medium";
|
|
1885
|
+
recommendations.push({ action: rec, estimatedImpact: 10, priority });
|
|
1886
|
+
}
|
|
1887
|
+
for (const rec of params.patternEntropy.recommendations) {
|
|
1888
|
+
recommendations.push({ action: rec, estimatedImpact: 5, priority: "low" });
|
|
1889
|
+
}
|
|
1890
|
+
const semanticDistanceAvg = params.semanticDistances && params.semanticDistances.length > 0 ? params.semanticDistances.reduce((s, d) => s + d.distance, 0) / params.semanticDistances.length : 0;
|
|
1891
|
+
return {
|
|
1892
|
+
toolName: "future-proof",
|
|
1893
|
+
score: overall,
|
|
1894
|
+
rawMetrics: {
|
|
1895
|
+
cognitiveLoadScore: params.cognitiveLoad.score,
|
|
1896
|
+
entropyScore: params.patternEntropy.entropy,
|
|
1897
|
+
cohesionScore: params.conceptCohesion.score,
|
|
1898
|
+
hallucinationRiskScore: params.hallucinationRisk.score,
|
|
1899
|
+
agentGroundingScore: params.agentGrounding.score,
|
|
1900
|
+
testabilityScore: params.testability.score,
|
|
1901
|
+
semanticDistanceAvg
|
|
1902
|
+
},
|
|
1903
|
+
factors,
|
|
1904
|
+
recommendations
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1478
1907
|
|
|
1479
1908
|
// src/utils/history.ts
|
|
1480
1909
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -1550,20 +1979,26 @@ function clearHistory(rootDir) {
|
|
|
1550
1979
|
}
|
|
1551
1980
|
}
|
|
1552
1981
|
export {
|
|
1982
|
+
CONTEXT_TIER_THRESHOLDS,
|
|
1553
1983
|
DEFAULT_COST_CONFIG,
|
|
1554
1984
|
DEFAULT_EXCLUDE,
|
|
1555
1985
|
DEFAULT_TOOL_WEIGHTS,
|
|
1556
1986
|
LANGUAGE_EXTENSIONS,
|
|
1557
1987
|
Language,
|
|
1988
|
+
MODEL_PRICING_PRESETS,
|
|
1558
1989
|
ParseError,
|
|
1559
1990
|
ParserFactory,
|
|
1560
1991
|
PythonParser,
|
|
1992
|
+
SIZE_ADJUSTED_THRESHOLDS,
|
|
1561
1993
|
TOOL_NAME_MAP,
|
|
1562
1994
|
TypeScriptParser,
|
|
1995
|
+
calculateAgentGrounding,
|
|
1563
1996
|
calculateCognitiveLoad,
|
|
1564
1997
|
calculateComprehensionDifficulty,
|
|
1565
1998
|
calculateConceptCohesion,
|
|
1999
|
+
calculateExtendedFutureProofScore,
|
|
1566
2000
|
calculateFutureProofScore,
|
|
2001
|
+
calculateHallucinationRisk,
|
|
1567
2002
|
calculateImportSimilarity,
|
|
1568
2003
|
calculateKnowledgeConcentration,
|
|
1569
2004
|
calculateMonthlyCost,
|
|
@@ -1574,6 +2009,7 @@ export {
|
|
|
1574
2009
|
calculateScoreTrend,
|
|
1575
2010
|
calculateSemanticDistance,
|
|
1576
2011
|
calculateTechnicalDebtInterest,
|
|
2012
|
+
calculateTestabilityIndex,
|
|
1577
2013
|
clearHistory,
|
|
1578
2014
|
estimateTokens,
|
|
1579
2015
|
exportHistory,
|
|
@@ -1589,9 +2025,13 @@ export {
|
|
|
1589
2025
|
getElapsedTime,
|
|
1590
2026
|
getFileExtension,
|
|
1591
2027
|
getHistorySummary,
|
|
2028
|
+
getModelPreset,
|
|
1592
2029
|
getParser,
|
|
2030
|
+
getProjectSizeTier,
|
|
1593
2031
|
getRating,
|
|
1594
2032
|
getRatingDisplay,
|
|
2033
|
+
getRatingWithContext,
|
|
2034
|
+
getRecommendedThreshold,
|
|
1595
2035
|
getSupportedLanguages,
|
|
1596
2036
|
getToolWeight,
|
|
1597
2037
|
handleCLIError,
|