@aiready/cli 0.14.22 → 0.14.23
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-HUSUJEQJ.mjs +298 -0
- package/dist/cli.js +871 -840
- package/dist/cli.mjs +498 -453
- package/dist/index.js +0 -9
- package/dist/index.mjs +1 -1
- package/package.json +12 -12
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
__require,
|
|
4
4
|
analyzeUnified,
|
|
5
5
|
scoreUnified
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HUSUJEQJ.mjs";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { Command } from "commander";
|
|
@@ -12,17 +12,14 @@ import { join as join2, dirname } from "path";
|
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
13
|
|
|
14
14
|
// src/commands/scan.ts
|
|
15
|
-
import
|
|
16
|
-
import { writeFileSync
|
|
17
|
-
import { resolve as
|
|
15
|
+
import chalk6 from "chalk";
|
|
16
|
+
import { writeFileSync } from "fs";
|
|
17
|
+
import { resolve as resolvePath4 } from "path";
|
|
18
18
|
import {
|
|
19
|
-
|
|
20
|
-
handleJSONOutput,
|
|
19
|
+
handleJSONOutput as handleJSONOutput2,
|
|
21
20
|
handleCLIError as handleCLIError2,
|
|
22
21
|
resolveOutputPath,
|
|
23
22
|
getRepoMetadata,
|
|
24
|
-
calculateTokenBudget,
|
|
25
|
-
ToolName as ToolName2,
|
|
26
23
|
emitIssuesAsAnnotations
|
|
27
24
|
} from "@aiready/core";
|
|
28
25
|
|
|
@@ -31,12 +28,11 @@ import { resolve as resolvePath } from "path";
|
|
|
31
28
|
import { existsSync, readFileSync } from "fs";
|
|
32
29
|
import chalk from "chalk";
|
|
33
30
|
import { loadConfig, mergeConfigWithDefaults } from "@aiready/core";
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
31
|
+
import {
|
|
32
|
+
findLatestReport,
|
|
33
|
+
getReportTimestamp,
|
|
34
|
+
handleJSONOutput
|
|
35
|
+
} from "@aiready/core";
|
|
40
36
|
async function warnIfGraphCapExceeded(report, dirPath) {
|
|
41
37
|
try {
|
|
42
38
|
const { loadConfig: loadConfig4 } = await import("@aiready/core");
|
|
@@ -247,19 +243,22 @@ function printScoring(scoringResult, scoringProfile) {
|
|
|
247
243
|
` ${progressBar} ${tool.score}/100 (${rating}) ${emoji} ${tool.toolName}`
|
|
248
244
|
);
|
|
249
245
|
});
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
});
|
|
262
|
-
|
|
246
|
+
printTopRecommendations(scoringResult.breakdown);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function printTopRecommendations(breakdown) {
|
|
250
|
+
const allRecs = breakdown.flatMap(
|
|
251
|
+
(t) => (t.recommendations ?? []).map((r) => ({ ...r, tool: t.toolName }))
|
|
252
|
+
).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0, 5);
|
|
253
|
+
if (allRecs.length > 0) {
|
|
254
|
+
console.log(chalk2.bold("\n\u{1F3AF} Top Actionable Recommendations:"));
|
|
255
|
+
allRecs.forEach((rec, i) => {
|
|
256
|
+
const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
|
|
257
|
+
console.log(` ${i + 1}. ${priorityIcon} ${chalk2.bold(rec.action)}`);
|
|
258
|
+
console.log(
|
|
259
|
+
` Impact: ${chalk2.green(`+${rec.estimatedImpact} points`)} to ${rec.tool}`
|
|
260
|
+
);
|
|
261
|
+
});
|
|
263
262
|
}
|
|
264
263
|
}
|
|
265
264
|
function mapToUnifiedReport(res, scoring) {
|
|
@@ -399,6 +398,9 @@ ENVIRONMENT VARIABLES:
|
|
|
399
398
|
AIREADY_SERVER Custom platform URL (default: https://dev.platform.getaiready.dev)
|
|
400
399
|
`;
|
|
401
400
|
|
|
401
|
+
// src/commands/scan-config.ts
|
|
402
|
+
import { loadMergedConfig, ToolName as ToolName2 } from "@aiready/core";
|
|
403
|
+
|
|
402
404
|
// src/commands/scan-helpers.ts
|
|
403
405
|
import chalk4 from "chalk";
|
|
404
406
|
import { ToolName } from "@aiready/core";
|
|
@@ -470,180 +472,211 @@ function createProgressCallback() {
|
|
|
470
472
|
};
|
|
471
473
|
}
|
|
472
474
|
|
|
473
|
-
// src/commands/scan.ts
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
475
|
+
// src/commands/scan-config.ts
|
|
476
|
+
var SCAN_DEFAULTS = {
|
|
477
|
+
tools: [
|
|
478
|
+
"pattern-detect",
|
|
479
|
+
"context-analyzer",
|
|
480
|
+
"naming-consistency",
|
|
481
|
+
"ai-signal-clarity",
|
|
482
|
+
"agent-grounding",
|
|
483
|
+
"testability-index",
|
|
484
|
+
"doc-drift",
|
|
485
|
+
"dependency-health",
|
|
486
|
+
"change-amplification"
|
|
487
|
+
],
|
|
488
|
+
include: void 0,
|
|
489
|
+
exclude: void 0,
|
|
490
|
+
output: {
|
|
491
|
+
format: "console",
|
|
492
|
+
file: void 0
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
async function resolveScanConfig(resolvedDir, options) {
|
|
496
|
+
let profileTools = options.tools ? options.tools.split(",").map((t) => t.trim()) : void 0;
|
|
497
|
+
if (options.profile) {
|
|
498
|
+
profileTools = getProfileTools(options.profile);
|
|
499
|
+
}
|
|
500
|
+
const cliOverrides = {
|
|
501
|
+
include: options.include?.split(","),
|
|
502
|
+
exclude: options.exclude?.split(",")
|
|
503
|
+
};
|
|
504
|
+
if (profileTools) cliOverrides.tools = profileTools;
|
|
505
|
+
const baseOptions = await loadMergedConfig(
|
|
506
|
+
resolvedDir,
|
|
507
|
+
SCAN_DEFAULTS,
|
|
508
|
+
cliOverrides
|
|
509
|
+
);
|
|
510
|
+
const finalOptions = { ...baseOptions };
|
|
511
|
+
if (baseOptions.tools.includes(ToolName2.PatternDetect) || baseOptions.tools.includes("patterns")) {
|
|
512
|
+
const { getSmartDefaults } = await import("@aiready/pattern-detect");
|
|
513
|
+
const patternSmartDefaults = await getSmartDefaults(
|
|
509
514
|
resolvedDir,
|
|
510
|
-
|
|
511
|
-
cliOverrides
|
|
515
|
+
finalOptions.toolConfigs?.[ToolName2.PatternDetect] ?? {}
|
|
512
516
|
);
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
517
|
+
if (!finalOptions.toolConfigs) finalOptions.toolConfigs = {};
|
|
518
|
+
finalOptions.toolConfigs[ToolName2.PatternDetect] = {
|
|
519
|
+
...patternSmartDefaults,
|
|
520
|
+
...finalOptions.toolConfigs[ToolName2.PatternDetect]
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
return finalOptions;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/commands/scan-orchestrator.ts
|
|
527
|
+
import chalk5 from "chalk";
|
|
528
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
529
|
+
import { resolve as resolvePath3 } from "path";
|
|
530
|
+
import { calculateTokenBudget } from "@aiready/core";
|
|
531
|
+
async function runUnifiedScan(resolvedDir, finalOptions, options, startTime) {
|
|
532
|
+
console.log(chalk5.cyan("\n=== AIReady Run Preview ==="));
|
|
533
|
+
console.log(
|
|
534
|
+
chalk5.white("Tools to run:"),
|
|
535
|
+
(finalOptions.tools ?? []).join(", ")
|
|
536
|
+
);
|
|
537
|
+
const progressCallback = createProgressCallback();
|
|
538
|
+
const scoringProfile = options.profile ?? finalOptions.scoring?.profile ?? "default";
|
|
539
|
+
const results = await analyzeUnified({
|
|
540
|
+
...finalOptions,
|
|
541
|
+
progressCallback,
|
|
542
|
+
onProgress: () => {
|
|
543
|
+
},
|
|
544
|
+
suppressToolConfig: true
|
|
545
|
+
});
|
|
546
|
+
printScanSummary(results, startTime);
|
|
547
|
+
let scoringResult;
|
|
548
|
+
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
549
|
+
scoringResult = await scoreUnified(results, {
|
|
534
550
|
...finalOptions,
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
551
|
+
scoring: {
|
|
552
|
+
...finalOptions.scoring,
|
|
553
|
+
profile: scoringProfile
|
|
554
|
+
}
|
|
539
555
|
});
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
556
|
+
printScoring(scoringResult, scoringProfile);
|
|
557
|
+
if (options.compareTo) {
|
|
558
|
+
handleTrendComparison(options.compareTo, scoringResult);
|
|
559
|
+
}
|
|
560
|
+
await handleBusinessImpactMetrics(
|
|
561
|
+
results,
|
|
562
|
+
scoringResult,
|
|
563
|
+
options.model ?? "gpt-5.4-mini"
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
return { results, scoringResult };
|
|
567
|
+
}
|
|
568
|
+
function handleTrendComparison(baselinePath, currentScoring) {
|
|
569
|
+
try {
|
|
570
|
+
const prevReport = JSON.parse(
|
|
571
|
+
readFileSync2(resolvePath3(process.cwd(), baselinePath), "utf8")
|
|
572
|
+
);
|
|
573
|
+
const prevScore = prevReport.scoring?.overall ?? prevReport.scoring?.score;
|
|
574
|
+
if (typeof prevScore === "number") {
|
|
575
|
+
const diff = currentScoring.overall - prevScore;
|
|
576
|
+
const diffStr = diff > 0 ? `+${diff}` : String(diff);
|
|
577
|
+
if (diff > 0)
|
|
578
|
+
console.log(
|
|
579
|
+
chalk5.green(
|
|
580
|
+
` \u{1F4C8} Trend: ${diffStr} compared to ${baselinePath} (${prevScore} \u2192 ${currentScoring.overall})`
|
|
581
|
+
)
|
|
582
|
+
);
|
|
583
|
+
else if (diff < 0)
|
|
584
|
+
console.log(
|
|
585
|
+
chalk5.red(
|
|
586
|
+
` \u{1F4C9} Trend: ${diffStr} compared to ${baselinePath} (${prevScore} \u2192 ${currentScoring.overall})`
|
|
587
|
+
)
|
|
588
|
+
);
|
|
589
|
+
else
|
|
590
|
+
console.log(
|
|
591
|
+
chalk5.blue(
|
|
592
|
+
` \u2796 Trend: No change (${prevScore} \u2192 ${currentScoring.overall})`
|
|
593
|
+
)
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
} catch (e) {
|
|
597
|
+
void e;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async function handleBusinessImpactMetrics(results, scoringResult, modelId) {
|
|
601
|
+
const totalWastedDuplication = (scoringResult.breakdown ?? []).reduce(
|
|
602
|
+
(sum, s) => sum + (s.tokenBudget?.wastedTokens?.bySource?.duplication ?? 0),
|
|
603
|
+
0
|
|
604
|
+
);
|
|
605
|
+
const totalWastedFragmentation = (scoringResult.breakdown ?? []).reduce(
|
|
606
|
+
(sum, s) => sum + (s.tokenBudget?.wastedTokens?.bySource?.fragmentation ?? 0),
|
|
607
|
+
0
|
|
608
|
+
);
|
|
609
|
+
const totalContext = Math.max(
|
|
610
|
+
...(scoringResult.breakdown ?? []).map(
|
|
611
|
+
(s) => s.tokenBudget?.totalContextTokens ?? 0
|
|
612
|
+
),
|
|
613
|
+
0
|
|
614
|
+
);
|
|
615
|
+
if (totalContext > 0) {
|
|
616
|
+
const unifiedBudget = calculateTokenBudget({
|
|
617
|
+
totalContextTokens: totalContext,
|
|
618
|
+
wastedTokens: {
|
|
619
|
+
duplication: totalWastedDuplication,
|
|
620
|
+
fragmentation: totalWastedFragmentation,
|
|
621
|
+
chattiness: totalContext * 0.1
|
|
622
|
+
// Default chattiness
|
|
582
623
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
)
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
);
|
|
591
|
-
const totalContext = Math.max(
|
|
592
|
-
...(scoringResult.breakdown ?? []).map(
|
|
593
|
-
(s) => s.tokenBudget?.totalContextTokens ?? 0
|
|
594
|
-
),
|
|
595
|
-
0
|
|
596
|
-
);
|
|
597
|
-
if (totalContext > 0) {
|
|
598
|
-
const unifiedBudget = calculateTokenBudget({
|
|
599
|
-
totalContextTokens: totalContext,
|
|
600
|
-
wastedTokens: {
|
|
601
|
-
duplication: totalWastedDuplication,
|
|
602
|
-
fragmentation: totalWastedFragmentation,
|
|
603
|
-
chattiness: totalContext * 0.1
|
|
604
|
-
// Default chattiness
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
const allIssues = [];
|
|
608
|
-
for (const toolId of results.summary.toolsRun) {
|
|
609
|
-
if (results[toolId]?.results) {
|
|
610
|
-
results[toolId].results.forEach((fileRes) => {
|
|
611
|
-
if (fileRes.issues) {
|
|
612
|
-
allIssues.push(...fileRes.issues);
|
|
613
|
-
}
|
|
614
|
-
});
|
|
624
|
+
});
|
|
625
|
+
const allIssues = [];
|
|
626
|
+
for (const toolId of results.summary.toolsRun) {
|
|
627
|
+
if (results[toolId]?.results) {
|
|
628
|
+
results[toolId].results.forEach((fileRes) => {
|
|
629
|
+
if (fileRes.issues) {
|
|
630
|
+
allIssues.push(...fileRes.issues);
|
|
615
631
|
}
|
|
616
|
-
}
|
|
617
|
-
const modelId = options.model ?? "gpt-5.4-mini";
|
|
618
|
-
const roi = (await import("@aiready/core")).calculateBusinessROI({
|
|
619
|
-
tokenWaste: unifiedBudget.wastedTokens.total,
|
|
620
|
-
issues: allIssues,
|
|
621
|
-
modelId
|
|
622
632
|
});
|
|
623
|
-
printBusinessImpact(roi, unifiedBudget);
|
|
624
|
-
results.summary.businessImpact = {
|
|
625
|
-
estimatedMonthlyWaste: roi.monthlySavings,
|
|
626
|
-
potentialSavings: roi.monthlySavings,
|
|
627
|
-
productivityHours: roi.productivityGainHours
|
|
628
|
-
};
|
|
629
|
-
scoringResult.tokenBudget = unifiedBudget;
|
|
630
|
-
scoringResult.businessROI = roi;
|
|
631
633
|
}
|
|
632
634
|
}
|
|
635
|
+
const { calculateBusinessROI } = await import("@aiready/core");
|
|
636
|
+
const roi = calculateBusinessROI({
|
|
637
|
+
tokenWaste: unifiedBudget.wastedTokens.total,
|
|
638
|
+
issues: allIssues,
|
|
639
|
+
modelId
|
|
640
|
+
});
|
|
641
|
+
printBusinessImpact(roi, unifiedBudget);
|
|
642
|
+
results.summary.businessImpact = {
|
|
643
|
+
estimatedMonthlyWaste: roi.monthlySavings,
|
|
644
|
+
potentialSavings: roi.monthlySavings,
|
|
645
|
+
productivityHours: roi.productivityGainHours
|
|
646
|
+
};
|
|
647
|
+
scoringResult.tokenBudget = unifiedBudget;
|
|
648
|
+
scoringResult.businessROI = roi;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/commands/scan.ts
|
|
653
|
+
async function scanAction(directory, options) {
|
|
654
|
+
console.log(chalk6.blue("\u{1F680} Starting AIReady unified analysis...\n"));
|
|
655
|
+
const startTime = Date.now();
|
|
656
|
+
const resolvedDir = resolvePath4(process.cwd(), directory ?? ".");
|
|
657
|
+
const repoMetadata = getRepoMetadata(resolvedDir);
|
|
658
|
+
try {
|
|
659
|
+
const finalOptions = await resolveScanConfig(resolvedDir, options);
|
|
660
|
+
const { results, scoringResult } = await runUnifiedScan(
|
|
661
|
+
resolvedDir,
|
|
662
|
+
finalOptions,
|
|
663
|
+
options,
|
|
664
|
+
startTime
|
|
665
|
+
);
|
|
633
666
|
console.log(
|
|
634
|
-
|
|
667
|
+
chalk6.dim(
|
|
635
668
|
"\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
636
669
|
)
|
|
637
670
|
);
|
|
638
|
-
console.log(
|
|
639
|
-
console.log(
|
|
671
|
+
console.log(chalk6.dim("\u{1F4AC} Found a bug or have a metric idea?"));
|
|
672
|
+
console.log(chalk6.dim("\u{1F449} Copy/paste this to your AI agent:"));
|
|
640
673
|
console.log(
|
|
641
|
-
|
|
674
|
+
chalk6.cyan(
|
|
642
675
|
` "Any feedback for the tools? Please use 'aiready bug' to report \u2764\uFE0F"`
|
|
643
676
|
)
|
|
644
677
|
);
|
|
645
678
|
console.log(
|
|
646
|
-
|
|
679
|
+
chalk6.dim(
|
|
647
680
|
"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
648
681
|
)
|
|
649
682
|
);
|
|
@@ -658,7 +691,7 @@ async function scanAction(directory, options) {
|
|
|
658
691
|
resolvedDir
|
|
659
692
|
);
|
|
660
693
|
if (outputFormat === "json") {
|
|
661
|
-
|
|
694
|
+
handleJSONOutput2(
|
|
662
695
|
outputData,
|
|
663
696
|
outputPath,
|
|
664
697
|
`\u2705 Report saved to ${outputPath}`
|
|
@@ -666,7 +699,7 @@ async function scanAction(directory, options) {
|
|
|
666
699
|
} else {
|
|
667
700
|
try {
|
|
668
701
|
writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
|
|
669
|
-
console.log(
|
|
702
|
+
console.log(chalk6.dim(`\u2705 Report auto-persisted to ${outputPath}`));
|
|
670
703
|
} catch (err) {
|
|
671
704
|
void err;
|
|
672
705
|
}
|
|
@@ -678,53 +711,75 @@ async function scanAction(directory, options) {
|
|
|
678
711
|
});
|
|
679
712
|
}
|
|
680
713
|
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
681
|
-
|
|
682
|
-
const threshold = options.threshold ? parseInt(options.threshold) : void 0;
|
|
683
|
-
const failOnLevel = options.failOn ?? "critical";
|
|
684
|
-
const isCI = options.ci ?? process.env.CI === "true";
|
|
685
|
-
let shouldFail = false;
|
|
686
|
-
let failReason = "";
|
|
687
|
-
const report = mapToUnifiedReport(results, scoringResult);
|
|
688
|
-
if (isCI && report.results && report.results.length > 0) {
|
|
689
|
-
console.log(
|
|
690
|
-
chalk5.cyan(
|
|
691
|
-
`
|
|
692
|
-
\u{1F4DD} Emitting GitHub Action annotations for ${report.results.length} issues...`
|
|
693
|
-
)
|
|
694
|
-
);
|
|
695
|
-
emitIssuesAsAnnotations(report.results);
|
|
696
|
-
}
|
|
697
|
-
if (threshold && scoringResult.overall < threshold) {
|
|
698
|
-
shouldFail = true;
|
|
699
|
-
failReason = `Score ${scoringResult.overall} < threshold ${threshold}`;
|
|
700
|
-
}
|
|
701
|
-
if (failOnLevel !== "none") {
|
|
702
|
-
if (failOnLevel === "critical" && report.summary.criticalIssues > 0) {
|
|
703
|
-
shouldFail = true;
|
|
704
|
-
failReason = `Found ${report.summary.criticalIssues} critical issues`;
|
|
705
|
-
} else if (failOnLevel === "major" && report.summary.criticalIssues + report.summary.majorIssues > 0) {
|
|
706
|
-
shouldFail = true;
|
|
707
|
-
failReason = `Found ${report.summary.criticalIssues} critical and ${report.summary.majorIssues} major issues`;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
if (shouldFail) {
|
|
711
|
-
console.log(chalk5.red(`
|
|
712
|
-
\u{1F6AB} SCAN FAILED: ${failReason}`));
|
|
713
|
-
process.exit(1);
|
|
714
|
-
} else {
|
|
715
|
-
console.log(chalk5.green("\n\u2705 SCAN PASSED"));
|
|
716
|
-
}
|
|
717
|
-
}
|
|
714
|
+
await handleGatekeeper(outputData, scoringResult, options, results);
|
|
718
715
|
} catch (error) {
|
|
719
716
|
handleCLIError2(error, "Analysis");
|
|
720
717
|
}
|
|
721
718
|
}
|
|
722
|
-
|
|
719
|
+
async function handleGatekeeper(outputData, scoringResult, options, results) {
|
|
720
|
+
if (!scoringResult) return;
|
|
721
|
+
const threshold = options.threshold ? parseInt(options.threshold) : void 0;
|
|
722
|
+
const failOnLevel = options.failOn ?? "critical";
|
|
723
|
+
const isCI = options.ci ?? process.env.CI === "true";
|
|
724
|
+
let shouldFail = false;
|
|
725
|
+
let failReason = "";
|
|
726
|
+
const report = mapToUnifiedReport(results, scoringResult);
|
|
727
|
+
if (isCI && report.results && report.results.length > 0) {
|
|
728
|
+
console.log(
|
|
729
|
+
chalk6.cyan(
|
|
730
|
+
`
|
|
731
|
+
\u{1F4DD} Emitting GitHub Action annotations for ${report.results.length} issues...`
|
|
732
|
+
)
|
|
733
|
+
);
|
|
734
|
+
emitIssuesAsAnnotations(report.results);
|
|
735
|
+
}
|
|
736
|
+
if (threshold && scoringResult.overall < threshold) {
|
|
737
|
+
shouldFail = true;
|
|
738
|
+
failReason = `Score ${scoringResult.overall} < threshold ${threshold}`;
|
|
739
|
+
}
|
|
740
|
+
if (failOnLevel !== "none") {
|
|
741
|
+
if (failOnLevel === "critical" && report.summary.criticalIssues > 0) {
|
|
742
|
+
shouldFail = true;
|
|
743
|
+
failReason = `Found ${report.summary.criticalIssues} critical issues`;
|
|
744
|
+
} else if (failOnLevel === "major" && report.summary.criticalIssues + report.summary.majorIssues > 0) {
|
|
745
|
+
shouldFail = true;
|
|
746
|
+
failReason = `Found ${report.summary.criticalIssues} critical and ${report.summary.majorIssues} major issues`;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (shouldFail) {
|
|
750
|
+
console.log(chalk6.red(`
|
|
751
|
+
\u{1F6AB} SCAN FAILED: ${failReason}`));
|
|
752
|
+
process.exit(1);
|
|
753
|
+
} else {
|
|
754
|
+
console.log(chalk6.green("\n\u2705 SCAN PASSED"));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
var SCAN_HELP_TEXT = `
|
|
758
|
+
Run a comprehensive AI-readiness scan of your codebase.
|
|
759
|
+
|
|
760
|
+
${chalk6.bold("Examples:")}
|
|
761
|
+
$ aiready scan .
|
|
762
|
+
$ aiready scan src --profile agentic
|
|
763
|
+
$ aiready scan . --threshold 80 --fail-on critical
|
|
764
|
+
$ aiready scan . --output json --output-file report.json
|
|
765
|
+
|
|
766
|
+
${chalk6.bold("Profiles:")}
|
|
767
|
+
agentic - Focus on AI signal clarity and agent grounding
|
|
768
|
+
cost - Focus on token budget and pattern reuse
|
|
769
|
+
logic - Focus on testability and naming consistency
|
|
770
|
+
ui - Focus on component naming and documentation
|
|
771
|
+
security - Focus on naming and testability
|
|
772
|
+
onboarding - Focus on context and grounding
|
|
773
|
+
|
|
774
|
+
${chalk6.bold("CI/CD Integration:")}
|
|
775
|
+
Use --threshold and --fail-on to use AIReady as a quality gate in your CI pipelines.
|
|
776
|
+
When running in GitHub Actions, it will automatically emit annotations for found issues.
|
|
777
|
+
`;
|
|
723
778
|
|
|
724
779
|
// src/commands/init.ts
|
|
725
780
|
import { writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
726
781
|
import { join } from "path";
|
|
727
|
-
import
|
|
782
|
+
import chalk7 from "chalk";
|
|
728
783
|
import { ToolName as ToolName3 } from "@aiready/core";
|
|
729
784
|
async function initAction(options) {
|
|
730
785
|
const fileExt = options.format === "js" ? "js" : "json";
|
|
@@ -732,7 +787,7 @@ async function initAction(options) {
|
|
|
732
787
|
const filePath = join(process.cwd(), fileName);
|
|
733
788
|
if (existsSync2(filePath) && !options.force) {
|
|
734
789
|
console.error(
|
|
735
|
-
|
|
790
|
+
chalk7.red(`Error: ${fileName} already exists. Use --force to overwrite.`)
|
|
736
791
|
);
|
|
737
792
|
process.exit(1);
|
|
738
793
|
}
|
|
@@ -954,35 +1009,36 @@ module.exports = ${JSON.stringify(defaultConfig, null, 2)};
|
|
|
954
1009
|
try {
|
|
955
1010
|
writeFileSync2(filePath, content, "utf8");
|
|
956
1011
|
console.log(
|
|
957
|
-
|
|
958
|
-
\u2705 Created default configuration: ${
|
|
1012
|
+
chalk7.green(`
|
|
1013
|
+
\u2705 Created default configuration: ${chalk7.bold(fileName)}`)
|
|
959
1014
|
);
|
|
960
1015
|
console.log(
|
|
961
|
-
|
|
1016
|
+
chalk7.cyan("You can now fine-tune your settings and run AIReady with:")
|
|
962
1017
|
);
|
|
963
|
-
console.log(
|
|
1018
|
+
console.log(chalk7.white(` $ aiready scan
|
|
964
1019
|
`));
|
|
965
1020
|
} catch (error) {
|
|
966
|
-
console.error(
|
|
1021
|
+
console.error(chalk7.red(`Failed to write configuration file: ${error}`));
|
|
967
1022
|
process.exit(1);
|
|
968
1023
|
}
|
|
969
1024
|
}
|
|
970
1025
|
|
|
971
1026
|
// src/commands/patterns.ts
|
|
972
|
-
import
|
|
973
|
-
import { resolve as resolvePath4 } from "path";
|
|
1027
|
+
import chalk8 from "chalk";
|
|
974
1028
|
import {
|
|
975
|
-
loadMergedConfig as loadMergedConfig2,
|
|
976
|
-
handleJSONOutput as handleJSONOutput2,
|
|
977
1029
|
handleCLIError as handleCLIError3,
|
|
978
1030
|
getElapsedTime,
|
|
979
|
-
|
|
980
|
-
|
|
1031
|
+
formatToolScore,
|
|
1032
|
+
printTerminalHeader,
|
|
1033
|
+
getTerminalDivider,
|
|
1034
|
+
prepareActionConfig,
|
|
1035
|
+
resolveOutputFormat,
|
|
1036
|
+
formatStandardReport,
|
|
1037
|
+
handleStandardJSONOutput
|
|
981
1038
|
} from "@aiready/core";
|
|
982
1039
|
async function patternsAction(directory, options) {
|
|
983
|
-
console.log(
|
|
1040
|
+
console.log(chalk8.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
984
1041
|
const startTime = Date.now();
|
|
985
|
-
const resolvedDir = resolvePath4(process.cwd(), directory ?? ".");
|
|
986
1042
|
try {
|
|
987
1043
|
const useSmartDefaults = !options.fullScan;
|
|
988
1044
|
const defaults = {
|
|
@@ -1011,8 +1067,8 @@ async function patternsAction(directory, options) {
|
|
|
1011
1067
|
if (options.minSharedTokens) {
|
|
1012
1068
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
1013
1069
|
}
|
|
1014
|
-
const finalOptions = await
|
|
1015
|
-
|
|
1070
|
+
const { resolvedDir, finalOptions } = await prepareActionConfig(
|
|
1071
|
+
directory,
|
|
1016
1072
|
defaults,
|
|
1017
1073
|
cliOptions
|
|
1018
1074
|
);
|
|
@@ -1026,60 +1082,53 @@ async function patternsAction(directory, options) {
|
|
|
1026
1082
|
if (options.score) {
|
|
1027
1083
|
patternScore = calculatePatternScore(duplicates, results.length);
|
|
1028
1084
|
}
|
|
1029
|
-
const outputFormat
|
|
1030
|
-
|
|
1085
|
+
const { format: outputFormat, file: userOutputFile } = resolveOutputFormat(
|
|
1086
|
+
options,
|
|
1087
|
+
finalOptions
|
|
1088
|
+
);
|
|
1031
1089
|
if (outputFormat === "json") {
|
|
1032
|
-
const outputData = {
|
|
1090
|
+
const outputData = formatStandardReport({
|
|
1033
1091
|
results,
|
|
1034
|
-
summary
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
`aiready-report-${getReportTimestamp()}.json`,
|
|
1040
|
-
resolvedDir
|
|
1041
|
-
);
|
|
1042
|
-
handleJSONOutput2(
|
|
1092
|
+
summary,
|
|
1093
|
+
elapsedTime,
|
|
1094
|
+
score: patternScore
|
|
1095
|
+
});
|
|
1096
|
+
handleStandardJSONOutput({
|
|
1043
1097
|
outputData,
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
);
|
|
1098
|
+
outputFile: userOutputFile,
|
|
1099
|
+
resolvedDir
|
|
1100
|
+
});
|
|
1047
1101
|
} else {
|
|
1048
|
-
|
|
1049
|
-
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
1050
|
-
const divider = "\u2501".repeat(dividerWidth);
|
|
1051
|
-
console.log(chalk7.cyan(divider));
|
|
1052
|
-
console.log(chalk7.bold.white(" PATTERN ANALYSIS SUMMARY"));
|
|
1053
|
-
console.log(chalk7.cyan(divider) + "\n");
|
|
1102
|
+
printTerminalHeader("PATTERN ANALYSIS SUMMARY");
|
|
1054
1103
|
console.log(
|
|
1055
|
-
|
|
1104
|
+
chalk8.white(`\u{1F4C1} Files analyzed: ${chalk8.bold(results.length)}`)
|
|
1056
1105
|
);
|
|
1057
1106
|
console.log(
|
|
1058
|
-
|
|
1059
|
-
`\u26A0 Duplicate patterns found: ${
|
|
1107
|
+
chalk8.yellow(
|
|
1108
|
+
`\u26A0 Duplicate patterns found: ${chalk8.bold(summary.totalPatterns)}`
|
|
1060
1109
|
)
|
|
1061
1110
|
);
|
|
1062
1111
|
console.log(
|
|
1063
|
-
|
|
1064
|
-
`\u{1F4B0} Token cost (wasted): ${
|
|
1112
|
+
chalk8.red(
|
|
1113
|
+
`\u{1F4B0} Token cost (wasted): ${chalk8.bold(summary.totalTokenCost.toLocaleString())}`
|
|
1065
1114
|
)
|
|
1066
1115
|
);
|
|
1067
1116
|
console.log(
|
|
1068
|
-
|
|
1117
|
+
chalk8.gray(`\u23F1 Analysis time: ${chalk8.bold(elapsedTime + "s")}`)
|
|
1069
1118
|
);
|
|
1070
1119
|
const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
1071
1120
|
if (sortedTypes.length > 0) {
|
|
1072
|
-
console.log(
|
|
1073
|
-
console.log(
|
|
1074
|
-
console.log(
|
|
1121
|
+
console.log("\n" + getTerminalDivider());
|
|
1122
|
+
console.log(chalk8.bold.white(" PATTERNS BY TYPE"));
|
|
1123
|
+
console.log(getTerminalDivider() + "\n");
|
|
1075
1124
|
sortedTypes.forEach(([type, count]) => {
|
|
1076
|
-
console.log(` ${
|
|
1125
|
+
console.log(` ${chalk8.white(type.padEnd(15))} ${chalk8.bold(count)}`);
|
|
1077
1126
|
});
|
|
1078
1127
|
}
|
|
1079
1128
|
if (summary.totalPatterns > 0 && duplicates.length > 0) {
|
|
1080
|
-
console.log(
|
|
1081
|
-
console.log(
|
|
1082
|
-
console.log(
|
|
1129
|
+
console.log("\n" + getTerminalDivider());
|
|
1130
|
+
console.log(chalk8.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
1131
|
+
console.log(getTerminalDivider() + "\n");
|
|
1083
1132
|
const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
|
|
1084
1133
|
topDuplicates.forEach((dup) => {
|
|
1085
1134
|
const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
|
|
@@ -1087,25 +1136,25 @@ async function patternsAction(directory, options) {
|
|
|
1087
1136
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
1088
1137
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
1089
1138
|
console.log(
|
|
1090
|
-
`${severityIcon} ${severity}: ${
|
|
1139
|
+
`${severityIcon} ${severity}: ${chalk8.bold(file1Name)} \u2194 ${chalk8.bold(file2Name)}`
|
|
1091
1140
|
);
|
|
1092
1141
|
console.log(
|
|
1093
|
-
` Similarity: ${
|
|
1142
|
+
` Similarity: ${chalk8.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${chalk8.bold(dup.tokenCost.toLocaleString())} tokens each`
|
|
1094
1143
|
);
|
|
1095
1144
|
console.log(
|
|
1096
|
-
` Lines: ${
|
|
1145
|
+
` Lines: ${chalk8.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${chalk8.cyan(dup.line2 + "-" + dup.endLine2)}
|
|
1097
1146
|
`
|
|
1098
1147
|
);
|
|
1099
1148
|
});
|
|
1100
1149
|
} else {
|
|
1101
1150
|
console.log(
|
|
1102
|
-
|
|
1151
|
+
chalk8.green("\n\u2728 Great! No duplicate patterns detected.\n")
|
|
1103
1152
|
);
|
|
1104
1153
|
}
|
|
1105
1154
|
if (patternScore) {
|
|
1106
|
-
console.log(
|
|
1107
|
-
console.log(
|
|
1108
|
-
console.log(
|
|
1155
|
+
console.log(getTerminalDivider());
|
|
1156
|
+
console.log(chalk8.bold.white(" AI READINESS SCORE (Patterns)"));
|
|
1157
|
+
console.log(getTerminalDivider() + "\n");
|
|
1109
1158
|
console.log(formatToolScore(patternScore));
|
|
1110
1159
|
console.log();
|
|
1111
1160
|
}
|
|
@@ -1122,20 +1171,19 @@ EXAMPLES:
|
|
|
1122
1171
|
`;
|
|
1123
1172
|
|
|
1124
1173
|
// src/commands/context.ts
|
|
1125
|
-
import
|
|
1126
|
-
import { resolve as resolvePath5 } from "path";
|
|
1174
|
+
import chalk9 from "chalk";
|
|
1127
1175
|
import {
|
|
1128
|
-
loadMergedConfig as loadMergedConfig3,
|
|
1129
|
-
handleJSONOutput as handleJSONOutput3,
|
|
1130
1176
|
handleCLIError as handleCLIError4,
|
|
1131
1177
|
getElapsedTime as getElapsedTime2,
|
|
1132
|
-
|
|
1133
|
-
|
|
1178
|
+
formatToolScore as formatToolScore2,
|
|
1179
|
+
prepareActionConfig as prepareActionConfig2,
|
|
1180
|
+
resolveOutputFormat as resolveOutputFormat2,
|
|
1181
|
+
formatStandardReport as formatStandardReport2,
|
|
1182
|
+
handleStandardJSONOutput as handleStandardJSONOutput2
|
|
1134
1183
|
} from "@aiready/core";
|
|
1135
1184
|
async function contextAction(directory, options) {
|
|
1136
|
-
console.log(
|
|
1185
|
+
console.log(chalk9.blue("\u{1F9E0} Analyzing context costs...\n"));
|
|
1137
1186
|
const startTime = Date.now();
|
|
1138
|
-
const resolvedDir = resolvePath5(process.cwd(), directory ?? ".");
|
|
1139
1187
|
try {
|
|
1140
1188
|
const defaults = {
|
|
1141
1189
|
maxDepth: 5,
|
|
@@ -1147,7 +1195,7 @@ async function contextAction(directory, options) {
|
|
|
1147
1195
|
file: void 0
|
|
1148
1196
|
}
|
|
1149
1197
|
};
|
|
1150
|
-
const baseOptions = await
|
|
1198
|
+
const { resolvedDir, finalOptions: baseOptions } = await prepareActionConfig2(directory, defaults, {
|
|
1151
1199
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
1152
1200
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
1153
1201
|
include: options.include?.split(","),
|
|
@@ -1179,107 +1227,105 @@ async function contextAction(directory, options) {
|
|
|
1179
1227
|
if (options.score) {
|
|
1180
1228
|
contextScore = calculateContextScore(summary);
|
|
1181
1229
|
}
|
|
1182
|
-
const outputFormat
|
|
1183
|
-
|
|
1230
|
+
const { format: outputFormat, file: userOutputFile } = resolveOutputFormat2(
|
|
1231
|
+
options,
|
|
1232
|
+
finalOptions
|
|
1233
|
+
);
|
|
1184
1234
|
if (outputFormat === "json") {
|
|
1185
|
-
const outputData = {
|
|
1235
|
+
const outputData = formatStandardReport2({
|
|
1186
1236
|
results,
|
|
1187
|
-
summary
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
`aiready-report-${getReportTimestamp()}.json`,
|
|
1193
|
-
resolvedDir
|
|
1194
|
-
);
|
|
1195
|
-
handleJSONOutput3(
|
|
1237
|
+
summary,
|
|
1238
|
+
elapsedTime,
|
|
1239
|
+
score: contextScore
|
|
1240
|
+
});
|
|
1241
|
+
handleStandardJSONOutput2({
|
|
1196
1242
|
outputData,
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
);
|
|
1243
|
+
outputFile: userOutputFile,
|
|
1244
|
+
resolvedDir
|
|
1245
|
+
});
|
|
1200
1246
|
} else {
|
|
1201
1247
|
const terminalWidth = process.stdout.columns ?? 80;
|
|
1202
1248
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
1203
1249
|
const divider = "\u2501".repeat(dividerWidth);
|
|
1204
|
-
console.log(
|
|
1205
|
-
console.log(
|
|
1206
|
-
console.log(
|
|
1250
|
+
console.log(chalk9.cyan(divider));
|
|
1251
|
+
console.log(chalk9.bold.white(" CONTEXT ANALYSIS SUMMARY"));
|
|
1252
|
+
console.log(chalk9.cyan(divider) + "\n");
|
|
1207
1253
|
console.log(
|
|
1208
|
-
|
|
1254
|
+
chalk9.white(`\u{1F4C1} Files analyzed: ${chalk9.bold(summary.totalFiles)}`)
|
|
1209
1255
|
);
|
|
1210
1256
|
console.log(
|
|
1211
|
-
|
|
1212
|
-
`\u{1F4CA} Total tokens: ${
|
|
1257
|
+
chalk9.white(
|
|
1258
|
+
`\u{1F4CA} Total tokens: ${chalk9.bold(summary.totalTokens.toLocaleString())}`
|
|
1213
1259
|
)
|
|
1214
1260
|
);
|
|
1215
1261
|
console.log(
|
|
1216
|
-
|
|
1217
|
-
`\u{1F4B0} Avg context budget: ${
|
|
1262
|
+
chalk9.yellow(
|
|
1263
|
+
`\u{1F4B0} Avg context budget: ${chalk9.bold(summary.avgContextBudget.toFixed(0))} tokens/file`
|
|
1218
1264
|
)
|
|
1219
1265
|
);
|
|
1220
1266
|
console.log(
|
|
1221
|
-
|
|
1267
|
+
chalk9.white(`\u23F1 Analysis time: ${chalk9.bold(elapsedTime + "s")}
|
|
1222
1268
|
`)
|
|
1223
1269
|
);
|
|
1224
1270
|
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
1225
1271
|
if (totalIssues > 0) {
|
|
1226
|
-
console.log(
|
|
1272
|
+
console.log(chalk9.bold("\u26A0\uFE0F Issues Found:\n"));
|
|
1227
1273
|
if (summary.criticalIssues > 0) {
|
|
1228
1274
|
console.log(
|
|
1229
|
-
|
|
1275
|
+
chalk9.red(` \u{1F534} Critical: ${chalk9.bold(summary.criticalIssues)}`)
|
|
1230
1276
|
);
|
|
1231
1277
|
}
|
|
1232
1278
|
if (summary.majorIssues > 0) {
|
|
1233
1279
|
console.log(
|
|
1234
|
-
|
|
1280
|
+
chalk9.yellow(` \u{1F7E1} Major: ${chalk9.bold(summary.majorIssues)}`)
|
|
1235
1281
|
);
|
|
1236
1282
|
}
|
|
1237
1283
|
if (summary.minorIssues > 0) {
|
|
1238
1284
|
console.log(
|
|
1239
|
-
|
|
1285
|
+
chalk9.blue(` \u{1F535} Minor: ${chalk9.bold(summary.minorIssues)}`)
|
|
1240
1286
|
);
|
|
1241
1287
|
}
|
|
1242
1288
|
console.log(
|
|
1243
|
-
|
|
1289
|
+
chalk9.green(
|
|
1244
1290
|
`
|
|
1245
|
-
\u{1F4A1} Potential savings: ${
|
|
1291
|
+
\u{1F4A1} Potential savings: ${chalk9.bold(summary.totalPotentialSavings.toLocaleString())} tokens
|
|
1246
1292
|
`
|
|
1247
1293
|
)
|
|
1248
1294
|
);
|
|
1249
1295
|
} else {
|
|
1250
|
-
console.log(
|
|
1296
|
+
console.log(chalk9.green("\u2705 No significant issues found!\n"));
|
|
1251
1297
|
}
|
|
1252
1298
|
if (summary.deepFiles.length > 0) {
|
|
1253
|
-
console.log(
|
|
1299
|
+
console.log(chalk9.bold("\u{1F4CF} Deep Import Chains:\n"));
|
|
1254
1300
|
console.log(
|
|
1255
|
-
|
|
1301
|
+
chalk9.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`)
|
|
1256
1302
|
);
|
|
1257
1303
|
console.log(
|
|
1258
|
-
|
|
1304
|
+
chalk9.gray(` Maximum depth: ${summary.maxImportDepth}
|
|
1259
1305
|
`)
|
|
1260
1306
|
);
|
|
1261
1307
|
summary.deepFiles.slice(0, 10).forEach((item) => {
|
|
1262
1308
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
1263
1309
|
console.log(
|
|
1264
|
-
` ${
|
|
1310
|
+
` ${chalk9.cyan("\u2192")} ${chalk9.white(fileName)} ${chalk9.dim(`(depth: ${item.depth})`)}`
|
|
1265
1311
|
);
|
|
1266
1312
|
});
|
|
1267
1313
|
console.log();
|
|
1268
1314
|
}
|
|
1269
1315
|
if (summary.fragmentedModules.length > 0) {
|
|
1270
|
-
console.log(
|
|
1316
|
+
console.log(chalk9.bold("\u{1F9E9} Fragmented Modules:\n"));
|
|
1271
1317
|
console.log(
|
|
1272
|
-
|
|
1318
|
+
chalk9.gray(
|
|
1273
1319
|
` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
|
|
1274
1320
|
`
|
|
1275
1321
|
)
|
|
1276
1322
|
);
|
|
1277
1323
|
summary.fragmentedModules.slice(0, 10).forEach((module) => {
|
|
1278
1324
|
console.log(
|
|
1279
|
-
` ${
|
|
1325
|
+
` ${chalk9.yellow("\u25CF")} ${chalk9.white(module.domain)} - ${chalk9.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`
|
|
1280
1326
|
);
|
|
1281
1327
|
console.log(
|
|
1282
|
-
|
|
1328
|
+
chalk9.dim(
|
|
1283
1329
|
` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`
|
|
1284
1330
|
)
|
|
1285
1331
|
);
|
|
@@ -1287,9 +1333,9 @@ async function contextAction(directory, options) {
|
|
|
1287
1333
|
console.log();
|
|
1288
1334
|
}
|
|
1289
1335
|
if (summary.lowCohesionFiles.length > 0) {
|
|
1290
|
-
console.log(
|
|
1336
|
+
console.log(chalk9.bold("\u{1F500} Low Cohesion Files:\n"));
|
|
1291
1337
|
console.log(
|
|
1292
|
-
|
|
1338
|
+
chalk9.gray(
|
|
1293
1339
|
` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
|
|
1294
1340
|
`
|
|
1295
1341
|
)
|
|
@@ -1297,28 +1343,28 @@ async function contextAction(directory, options) {
|
|
|
1297
1343
|
summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
|
|
1298
1344
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
1299
1345
|
const scorePercent = (item.score * 100).toFixed(0);
|
|
1300
|
-
const color = item.score < 0.4 ?
|
|
1346
|
+
const color = item.score < 0.4 ? chalk9.red : chalk9.yellow;
|
|
1301
1347
|
console.log(
|
|
1302
|
-
` ${color("\u25CB")} ${
|
|
1348
|
+
` ${color("\u25CB")} ${chalk9.white(fileName)} ${chalk9.dim(`(${scorePercent}% cohesion)`)}`
|
|
1303
1349
|
);
|
|
1304
1350
|
});
|
|
1305
1351
|
console.log();
|
|
1306
1352
|
}
|
|
1307
1353
|
if (summary.topExpensiveFiles.length > 0) {
|
|
1308
|
-
console.log(
|
|
1354
|
+
console.log(chalk9.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
|
|
1309
1355
|
summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
|
|
1310
1356
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
1311
|
-
const severityColor = item.severity === "critical" ?
|
|
1357
|
+
const severityColor = item.severity === "critical" ? chalk9.red : item.severity === "major" ? chalk9.yellow : chalk9.blue;
|
|
1312
1358
|
console.log(
|
|
1313
|
-
` ${severityColor("\u25CF")} ${
|
|
1359
|
+
` ${severityColor("\u25CF")} ${chalk9.white(fileName)} ${chalk9.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`
|
|
1314
1360
|
);
|
|
1315
1361
|
});
|
|
1316
1362
|
console.log();
|
|
1317
1363
|
}
|
|
1318
1364
|
if (contextScore) {
|
|
1319
|
-
console.log(
|
|
1320
|
-
console.log(
|
|
1321
|
-
console.log(
|
|
1365
|
+
console.log(chalk9.cyan(divider));
|
|
1366
|
+
console.log(chalk9.bold.white(" AI READINESS SCORE (Context)"));
|
|
1367
|
+
console.log(chalk9.cyan(divider) + "\n");
|
|
1322
1368
|
console.log(formatToolScore2(contextScore));
|
|
1323
1369
|
console.log();
|
|
1324
1370
|
}
|
|
@@ -1329,21 +1375,21 @@ async function contextAction(directory, options) {
|
|
|
1329
1375
|
}
|
|
1330
1376
|
|
|
1331
1377
|
// src/commands/consistency.ts
|
|
1332
|
-
import
|
|
1378
|
+
import chalk10 from "chalk";
|
|
1333
1379
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
1334
|
-
import { resolve as resolvePath6 } from "path";
|
|
1335
1380
|
import {
|
|
1336
|
-
loadMergedConfig as loadMergedConfig4,
|
|
1337
|
-
handleJSONOutput as handleJSONOutput4,
|
|
1338
1381
|
handleCLIError as handleCLIError5,
|
|
1339
1382
|
getElapsedTime as getElapsedTime3,
|
|
1340
|
-
resolveOutputPath as
|
|
1341
|
-
formatToolScore as formatToolScore3
|
|
1383
|
+
resolveOutputPath as resolveOutputPath2,
|
|
1384
|
+
formatToolScore as formatToolScore3,
|
|
1385
|
+
prepareActionConfig as prepareActionConfig3,
|
|
1386
|
+
resolveOutputFormat as resolveOutputFormat3,
|
|
1387
|
+
formatStandardReport as formatStandardReport3,
|
|
1388
|
+
handleStandardJSONOutput as handleStandardJSONOutput3
|
|
1342
1389
|
} from "@aiready/core";
|
|
1343
1390
|
async function consistencyAction(directory, options) {
|
|
1344
|
-
console.log(
|
|
1391
|
+
console.log(chalk10.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
1345
1392
|
const startTime = Date.now();
|
|
1346
|
-
const resolvedDir = resolvePath6(process.cwd(), directory ?? ".");
|
|
1347
1393
|
try {
|
|
1348
1394
|
const defaults = {
|
|
1349
1395
|
checkNaming: true,
|
|
@@ -1356,13 +1402,17 @@ async function consistencyAction(directory, options) {
|
|
|
1356
1402
|
file: void 0
|
|
1357
1403
|
}
|
|
1358
1404
|
};
|
|
1359
|
-
const finalOptions = await
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1405
|
+
const { resolvedDir, finalOptions } = await prepareActionConfig3(
|
|
1406
|
+
directory,
|
|
1407
|
+
defaults,
|
|
1408
|
+
{
|
|
1409
|
+
checkNaming: options.naming !== false,
|
|
1410
|
+
checkPatterns: options.patterns !== false,
|
|
1411
|
+
minSeverity: options.minSeverity,
|
|
1412
|
+
include: options.include?.split(","),
|
|
1413
|
+
exclude: options.exclude?.split(",")
|
|
1414
|
+
}
|
|
1415
|
+
);
|
|
1366
1416
|
const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
|
|
1367
1417
|
const report = await analyzeConsistency(finalOptions);
|
|
1368
1418
|
const elapsedTime = getElapsedTime3(startTime);
|
|
@@ -1374,52 +1424,47 @@ async function consistencyAction(directory, options) {
|
|
|
1374
1424
|
report.summary.filesAnalyzed
|
|
1375
1425
|
);
|
|
1376
1426
|
}
|
|
1377
|
-
const outputFormat
|
|
1378
|
-
|
|
1427
|
+
const { format: outputFormat, file: userOutputFile } = resolveOutputFormat3(
|
|
1428
|
+
options,
|
|
1429
|
+
finalOptions
|
|
1430
|
+
);
|
|
1379
1431
|
if (outputFormat === "json") {
|
|
1380
|
-
const outputData = {
|
|
1381
|
-
|
|
1382
|
-
summary:
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
};
|
|
1388
|
-
const outputPath = resolveOutputPath4(
|
|
1389
|
-
userOutputFile,
|
|
1390
|
-
`aiready-report-${getReportTimestamp()}.json`,
|
|
1391
|
-
resolvedDir
|
|
1392
|
-
);
|
|
1393
|
-
handleJSONOutput4(
|
|
1432
|
+
const outputData = formatStandardReport3({
|
|
1433
|
+
report,
|
|
1434
|
+
summary: report.summary,
|
|
1435
|
+
elapsedTime,
|
|
1436
|
+
score: consistencyScore
|
|
1437
|
+
});
|
|
1438
|
+
handleStandardJSONOutput3({
|
|
1394
1439
|
outputData,
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
);
|
|
1440
|
+
outputFile: userOutputFile,
|
|
1441
|
+
resolvedDir
|
|
1442
|
+
});
|
|
1398
1443
|
} else if (outputFormat === "markdown") {
|
|
1399
1444
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
1400
|
-
const outputPath =
|
|
1445
|
+
const outputPath = resolveOutputPath2(
|
|
1401
1446
|
userOutputFile,
|
|
1402
1447
|
`aiready-report-${getReportTimestamp()}.md`,
|
|
1403
1448
|
resolvedDir
|
|
1404
1449
|
);
|
|
1405
1450
|
writeFileSync3(outputPath, markdown);
|
|
1406
|
-
console.log(
|
|
1451
|
+
console.log(chalk10.green(`\u2705 Report saved to ${outputPath}`));
|
|
1407
1452
|
} else {
|
|
1408
|
-
console.log(
|
|
1453
|
+
console.log(chalk10.bold("\n\u{1F4CA} Summary\n"));
|
|
1409
1454
|
console.log(
|
|
1410
|
-
`Files Analyzed: ${
|
|
1455
|
+
`Files Analyzed: ${chalk10.cyan(report.summary.filesAnalyzed)}`
|
|
1411
1456
|
);
|
|
1412
|
-
console.log(`Total Issues: ${
|
|
1413
|
-
console.log(` Naming: ${
|
|
1414
|
-
console.log(` Patterns: ${
|
|
1457
|
+
console.log(`Total Issues: ${chalk10.yellow(report.summary.totalIssues)}`);
|
|
1458
|
+
console.log(` Naming: ${chalk10.yellow(report.summary.namingIssues)}`);
|
|
1459
|
+
console.log(` Patterns: ${chalk10.yellow(report.summary.patternIssues)}`);
|
|
1415
1460
|
console.log(
|
|
1416
|
-
` Architecture: ${
|
|
1461
|
+
` Architecture: ${chalk10.yellow(report.summary.architectureIssues ?? 0)}`
|
|
1417
1462
|
);
|
|
1418
|
-
console.log(`Analysis Time: ${
|
|
1463
|
+
console.log(`Analysis Time: ${chalk10.gray(elapsedTime + "s")}
|
|
1419
1464
|
`);
|
|
1420
1465
|
if (report.summary.totalIssues === 0) {
|
|
1421
1466
|
console.log(
|
|
1422
|
-
|
|
1467
|
+
chalk10.green(
|
|
1423
1468
|
"\u2728 No consistency issues found! Your codebase is well-maintained.\n"
|
|
1424
1469
|
)
|
|
1425
1470
|
);
|
|
@@ -1431,20 +1476,20 @@ async function consistencyAction(directory, options) {
|
|
|
1431
1476
|
(r) => r.issues.some((i) => i.category === "patterns")
|
|
1432
1477
|
);
|
|
1433
1478
|
if (namingResults.length > 0) {
|
|
1434
|
-
console.log(
|
|
1479
|
+
console.log(chalk10.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
|
|
1435
1480
|
let shown = 0;
|
|
1436
1481
|
for (const namingFileResult of namingResults) {
|
|
1437
1482
|
if (shown >= 5) break;
|
|
1438
1483
|
for (const issue of namingFileResult.issues) {
|
|
1439
1484
|
if (shown >= 5) break;
|
|
1440
|
-
const severityColor = issue.severity === "critical" ?
|
|
1485
|
+
const severityColor = issue.severity === "critical" ? chalk10.red : issue.severity === "major" ? chalk10.yellow : issue.severity === "minor" ? chalk10.blue : chalk10.gray;
|
|
1441
1486
|
console.log(
|
|
1442
|
-
`${severityColor(issue.severity.toUpperCase())} ${
|
|
1487
|
+
`${severityColor(issue.severity.toUpperCase())} ${chalk10.dim(`${issue.location.file}:${issue.location.line}`)}`
|
|
1443
1488
|
);
|
|
1444
1489
|
console.log(` ${issue.message}`);
|
|
1445
1490
|
if (issue.suggestion) {
|
|
1446
1491
|
console.log(
|
|
1447
|
-
` ${
|
|
1492
|
+
` ${chalk10.dim("\u2192")} ${chalk10.italic(issue.suggestion)}`
|
|
1448
1493
|
);
|
|
1449
1494
|
}
|
|
1450
1495
|
console.log();
|
|
@@ -1453,25 +1498,25 @@ async function consistencyAction(directory, options) {
|
|
|
1453
1498
|
}
|
|
1454
1499
|
const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
1455
1500
|
if (remaining > 0) {
|
|
1456
|
-
console.log(
|
|
1501
|
+
console.log(chalk10.dim(` ... and ${remaining} more issues
|
|
1457
1502
|
`));
|
|
1458
1503
|
}
|
|
1459
1504
|
}
|
|
1460
1505
|
if (patternResults.length > 0) {
|
|
1461
|
-
console.log(
|
|
1506
|
+
console.log(chalk10.bold("\u{1F504} Pattern Issues\n"));
|
|
1462
1507
|
let shown = 0;
|
|
1463
1508
|
for (const patternFileResult of patternResults) {
|
|
1464
1509
|
if (shown >= 5) break;
|
|
1465
1510
|
for (const issue of patternFileResult.issues) {
|
|
1466
1511
|
if (shown >= 5) break;
|
|
1467
|
-
const severityColor = issue.severity === "critical" ?
|
|
1512
|
+
const severityColor = issue.severity === "critical" ? chalk10.red : issue.severity === "major" ? chalk10.yellow : issue.severity === "minor" ? chalk10.blue : chalk10.gray;
|
|
1468
1513
|
console.log(
|
|
1469
|
-
`${severityColor(issue.severity.toUpperCase())} ${
|
|
1514
|
+
`${severityColor(issue.severity.toUpperCase())} ${chalk10.dim(`${issue.location.file}:${issue.location.line}`)}`
|
|
1470
1515
|
);
|
|
1471
1516
|
console.log(` ${issue.message}`);
|
|
1472
1517
|
if (issue.suggestion) {
|
|
1473
1518
|
console.log(
|
|
1474
|
-
` ${
|
|
1519
|
+
` ${chalk10.dim("\u2192")} ${chalk10.italic(issue.suggestion)}`
|
|
1475
1520
|
);
|
|
1476
1521
|
}
|
|
1477
1522
|
console.log();
|
|
@@ -1480,12 +1525,12 @@ async function consistencyAction(directory, options) {
|
|
|
1480
1525
|
}
|
|
1481
1526
|
const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
1482
1527
|
if (remaining > 0) {
|
|
1483
|
-
console.log(
|
|
1528
|
+
console.log(chalk10.dim(` ... and ${remaining} more issues
|
|
1484
1529
|
`));
|
|
1485
1530
|
}
|
|
1486
1531
|
}
|
|
1487
1532
|
if (report.recommendations.length > 0) {
|
|
1488
|
-
console.log(
|
|
1533
|
+
console.log(chalk10.bold("\u{1F4A1} Recommendations\n"));
|
|
1489
1534
|
report.recommendations.forEach((rec, i) => {
|
|
1490
1535
|
console.log(`${i + 1}. ${rec}`);
|
|
1491
1536
|
});
|
|
@@ -1493,7 +1538,7 @@ async function consistencyAction(directory, options) {
|
|
|
1493
1538
|
}
|
|
1494
1539
|
}
|
|
1495
1540
|
if (consistencyScore) {
|
|
1496
|
-
console.log(
|
|
1541
|
+
console.log(chalk10.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
|
|
1497
1542
|
console.log(formatToolScore3(consistencyScore));
|
|
1498
1543
|
console.log();
|
|
1499
1544
|
}
|
|
@@ -1504,27 +1549,27 @@ async function consistencyAction(directory, options) {
|
|
|
1504
1549
|
}
|
|
1505
1550
|
|
|
1506
1551
|
// src/commands/visualize.ts
|
|
1507
|
-
import
|
|
1552
|
+
import chalk11 from "chalk";
|
|
1508
1553
|
import { writeFileSync as writeFileSync4, readFileSync as readFileSync3, existsSync as existsSync3, copyFileSync } from "fs";
|
|
1509
|
-
import { resolve as
|
|
1554
|
+
import { resolve as resolvePath5 } from "path";
|
|
1510
1555
|
import { spawn } from "child_process";
|
|
1511
1556
|
import { handleCLIError as handleCLIError6 } from "@aiready/core";
|
|
1512
1557
|
import { generateHTML, findLatestReport as findLatestReport2 } from "@aiready/core";
|
|
1513
1558
|
async function visualizeAction(directory, options) {
|
|
1514
1559
|
try {
|
|
1515
|
-
const dirPath =
|
|
1516
|
-
let reportPath = options.report ?
|
|
1560
|
+
const dirPath = resolvePath5(process.cwd(), directory ?? ".");
|
|
1561
|
+
let reportPath = options.report ? resolvePath5(dirPath, options.report) : null;
|
|
1517
1562
|
if (!reportPath || !existsSync3(reportPath)) {
|
|
1518
1563
|
const latestScan = findLatestReport2(dirPath);
|
|
1519
1564
|
if (latestScan) {
|
|
1520
1565
|
reportPath = latestScan;
|
|
1521
1566
|
console.log(
|
|
1522
|
-
|
|
1567
|
+
chalk11.dim(`Found latest report: ${latestScan.split("/").pop()}`)
|
|
1523
1568
|
);
|
|
1524
1569
|
} else {
|
|
1525
|
-
console.error(
|
|
1570
|
+
console.error(chalk11.red("\u274C No AI readiness report found"));
|
|
1526
1571
|
console.log(
|
|
1527
|
-
|
|
1572
|
+
chalk11.dim(
|
|
1528
1573
|
`
|
|
1529
1574
|
Generate a report with:
|
|
1530
1575
|
aiready scan --output json
|
|
@@ -1538,7 +1583,7 @@ Or specify a custom report:
|
|
|
1538
1583
|
}
|
|
1539
1584
|
const raw = readFileSync3(reportPath, "utf8");
|
|
1540
1585
|
const report = JSON.parse(raw);
|
|
1541
|
-
const configPath =
|
|
1586
|
+
const configPath = resolvePath5(dirPath, "aiready.json");
|
|
1542
1587
|
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
1543
1588
|
if (existsSync3(configPath)) {
|
|
1544
1589
|
try {
|
|
@@ -1562,7 +1607,7 @@ Or specify a custom report:
|
|
|
1562
1607
|
let devServerStarted = false;
|
|
1563
1608
|
if (useDevMode) {
|
|
1564
1609
|
try {
|
|
1565
|
-
const localWebDir =
|
|
1610
|
+
const localWebDir = resolvePath5(dirPath, "packages/visualizer");
|
|
1566
1611
|
let webDir = "";
|
|
1567
1612
|
let visualizerAvailable = false;
|
|
1568
1613
|
if (existsSync3(localWebDir)) {
|
|
@@ -1570,8 +1615,8 @@ Or specify a custom report:
|
|
|
1570
1615
|
visualizerAvailable = true;
|
|
1571
1616
|
} else {
|
|
1572
1617
|
const nodemodulesLocations = [
|
|
1573
|
-
|
|
1574
|
-
|
|
1618
|
+
resolvePath5(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
1619
|
+
resolvePath5(
|
|
1575
1620
|
process.cwd(),
|
|
1576
1621
|
"node_modules",
|
|
1577
1622
|
"@aiready",
|
|
@@ -1581,14 +1626,14 @@ Or specify a custom report:
|
|
|
1581
1626
|
let currentDir = dirPath;
|
|
1582
1627
|
while (currentDir !== "/" && currentDir !== ".") {
|
|
1583
1628
|
nodemodulesLocations.push(
|
|
1584
|
-
|
|
1629
|
+
resolvePath5(currentDir, "node_modules", "@aiready", "visualizer")
|
|
1585
1630
|
);
|
|
1586
|
-
const parent =
|
|
1631
|
+
const parent = resolvePath5(currentDir, "..");
|
|
1587
1632
|
if (parent === currentDir) break;
|
|
1588
1633
|
currentDir = parent;
|
|
1589
1634
|
}
|
|
1590
1635
|
for (const location of nodemodulesLocations) {
|
|
1591
|
-
if (existsSync3(location) && existsSync3(
|
|
1636
|
+
if (existsSync3(location) && existsSync3(resolvePath5(location, "package.json"))) {
|
|
1592
1637
|
webDir = location;
|
|
1593
1638
|
visualizerAvailable = true;
|
|
1594
1639
|
break;
|
|
@@ -1597,20 +1642,20 @@ Or specify a custom report:
|
|
|
1597
1642
|
if (!visualizerAvailable) {
|
|
1598
1643
|
try {
|
|
1599
1644
|
const vizPkgPath = __require.resolve("@aiready/visualizer/package.json");
|
|
1600
|
-
webDir =
|
|
1645
|
+
webDir = resolvePath5(vizPkgPath, "..");
|
|
1601
1646
|
visualizerAvailable = true;
|
|
1602
1647
|
} catch (err) {
|
|
1603
1648
|
void err;
|
|
1604
1649
|
}
|
|
1605
1650
|
}
|
|
1606
1651
|
}
|
|
1607
|
-
const webViteConfigExists = webDir && existsSync3(
|
|
1652
|
+
const webViteConfigExists = webDir && existsSync3(resolvePath5(webDir, "web", "vite.config.ts"));
|
|
1608
1653
|
if (visualizerAvailable && webViteConfigExists) {
|
|
1609
1654
|
const spawnCwd = webDir;
|
|
1610
1655
|
const { watch } = await import("fs");
|
|
1611
1656
|
const copyReportToViz = () => {
|
|
1612
1657
|
try {
|
|
1613
|
-
const destPath =
|
|
1658
|
+
const destPath = resolvePath5(spawnCwd, "web", "report-data.json");
|
|
1614
1659
|
copyFileSync(reportPath, destPath);
|
|
1615
1660
|
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1616
1661
|
} catch (e) {
|
|
@@ -1654,19 +1699,19 @@ Or specify a custom report:
|
|
|
1654
1699
|
return;
|
|
1655
1700
|
} else {
|
|
1656
1701
|
console.log(
|
|
1657
|
-
|
|
1702
|
+
chalk11.yellow(
|
|
1658
1703
|
"\u26A0\uFE0F Dev server not available (requires local @aiready/visualizer with web assets)."
|
|
1659
1704
|
)
|
|
1660
1705
|
);
|
|
1661
1706
|
console.log(
|
|
1662
|
-
|
|
1707
|
+
chalk11.cyan(" Falling back to static HTML generation...\n")
|
|
1663
1708
|
);
|
|
1664
1709
|
useDevMode = false;
|
|
1665
1710
|
}
|
|
1666
1711
|
} catch (err) {
|
|
1667
1712
|
console.error("Failed to start dev server:", err);
|
|
1668
1713
|
console.log(
|
|
1669
|
-
|
|
1714
|
+
chalk11.cyan(" Falling back to static HTML generation...\n")
|
|
1670
1715
|
);
|
|
1671
1716
|
useDevMode = false;
|
|
1672
1717
|
}
|
|
@@ -1674,9 +1719,9 @@ Or specify a custom report:
|
|
|
1674
1719
|
console.log("Generating HTML...");
|
|
1675
1720
|
const html = generateHTML(graph);
|
|
1676
1721
|
const defaultOutput = "visualization.html";
|
|
1677
|
-
const outPath =
|
|
1722
|
+
const outPath = resolvePath5(dirPath, options.output ?? defaultOutput);
|
|
1678
1723
|
writeFileSync4(outPath, html, "utf8");
|
|
1679
|
-
console.log(
|
|
1724
|
+
console.log(chalk11.green(`\u2705 Visualization written to: ${outPath}`));
|
|
1680
1725
|
if (options.open || options.serve) {
|
|
1681
1726
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1682
1727
|
if (options.serve) {
|
|
@@ -1706,7 +1751,7 @@ Or specify a custom report:
|
|
|
1706
1751
|
server.listen(port, () => {
|
|
1707
1752
|
const addr = `http://localhost:${port}/`;
|
|
1708
1753
|
console.log(
|
|
1709
|
-
|
|
1754
|
+
chalk11.cyan(`\u{1F310} Local visualization server running at ${addr}`)
|
|
1710
1755
|
);
|
|
1711
1756
|
spawn(opener, [`"${addr}"`], { shell: true });
|
|
1712
1757
|
});
|
|
@@ -1753,14 +1798,14 @@ NOTES:
|
|
|
1753
1798
|
`;
|
|
1754
1799
|
|
|
1755
1800
|
// src/commands/shared/standard-tool-actions.ts
|
|
1756
|
-
import
|
|
1801
|
+
import chalk12 from "chalk";
|
|
1757
1802
|
|
|
1758
1803
|
// src/commands/agent-grounding.ts
|
|
1759
|
-
import
|
|
1804
|
+
import chalk13 from "chalk";
|
|
1760
1805
|
import { loadConfig as loadConfig2, mergeConfigWithDefaults as mergeConfigWithDefaults2 } from "@aiready/core";
|
|
1761
1806
|
|
|
1762
1807
|
// src/commands/testability.ts
|
|
1763
|
-
import
|
|
1808
|
+
import chalk14 from "chalk";
|
|
1764
1809
|
import { loadConfig as loadConfig3, mergeConfigWithDefaults as mergeConfigWithDefaults3 } from "@aiready/core";
|
|
1765
1810
|
async function testabilityAction(directory, options) {
|
|
1766
1811
|
const { analyzeTestability, calculateTestabilityScore } = await import("@aiready/testability");
|
|
@@ -1785,28 +1830,28 @@ async function testabilityAction(directory, options) {
|
|
|
1785
1830
|
"blind-risk": "\u{1F480}"
|
|
1786
1831
|
};
|
|
1787
1832
|
const safetyColors = {
|
|
1788
|
-
safe:
|
|
1789
|
-
"moderate-risk":
|
|
1790
|
-
"high-risk":
|
|
1791
|
-
"blind-risk":
|
|
1833
|
+
safe: chalk14.green,
|
|
1834
|
+
"moderate-risk": chalk14.yellow,
|
|
1835
|
+
"high-risk": chalk14.red,
|
|
1836
|
+
"blind-risk": chalk14.bgRed.white
|
|
1792
1837
|
};
|
|
1793
1838
|
const safety = report.summary.aiChangeSafetyRating;
|
|
1794
1839
|
const icon = safetyIcons[safety] ?? "\u2753";
|
|
1795
|
-
const color = safetyColors[safety] ??
|
|
1840
|
+
const color = safetyColors[safety] ?? chalk14.white;
|
|
1796
1841
|
console.log(
|
|
1797
|
-
` \u{1F9EA} Testability: ${
|
|
1842
|
+
` \u{1F9EA} Testability: ${chalk14.bold(scoring.score + "/100")} (${report.summary.rating})`
|
|
1798
1843
|
);
|
|
1799
1844
|
console.log(
|
|
1800
1845
|
` AI Change Safety: ${color(`${icon} ${safety.toUpperCase()}`)}`
|
|
1801
1846
|
);
|
|
1802
1847
|
console.log(
|
|
1803
|
-
|
|
1848
|
+
chalk14.dim(
|
|
1804
1849
|
` Coverage: ${Math.round(report.summary.coverageRatio * 100)}% (${report.rawData.testFiles} test / ${report.rawData.sourceFiles} source files)`
|
|
1805
1850
|
)
|
|
1806
1851
|
);
|
|
1807
1852
|
if (safety === "blind-risk") {
|
|
1808
1853
|
console.log(
|
|
1809
|
-
|
|
1854
|
+
chalk14.red.bold(
|
|
1810
1855
|
"\n \u26A0\uFE0F NO TESTS \u2014 AI changes to this codebase are completely unverifiable!\n"
|
|
1811
1856
|
)
|
|
1812
1857
|
);
|
|
@@ -1818,7 +1863,7 @@ async function testabilityAction(directory, options) {
|
|
|
1818
1863
|
import { changeAmplificationAction } from "@aiready/change-amplification/dist/cli.js";
|
|
1819
1864
|
|
|
1820
1865
|
// src/commands/bug.ts
|
|
1821
|
-
import
|
|
1866
|
+
import chalk15 from "chalk";
|
|
1822
1867
|
import { execSync } from "child_process";
|
|
1823
1868
|
async function bugAction(message, options) {
|
|
1824
1869
|
const repoUrl = "https://github.com/caopengau/aiready-cli";
|
|
@@ -1836,35 +1881,35 @@ Generated via AIReady CLI 'bug' command.
|
|
|
1836
1881
|
Type: ${type}
|
|
1837
1882
|
`.trim();
|
|
1838
1883
|
if (options.submit) {
|
|
1839
|
-
console.log(
|
|
1884
|
+
console.log(chalk15.blue("\u{1F680} Submitting issue via GitHub CLI...\n"));
|
|
1840
1885
|
try {
|
|
1841
1886
|
execSync("gh auth status", { stdio: "ignore" });
|
|
1842
1887
|
const command = `gh issue create --repo ${repoSlug} --title ${JSON.stringify(title)} --body ${JSON.stringify(body)} --label ${label}`;
|
|
1843
1888
|
const output = execSync(command, { encoding: "utf8" }).trim();
|
|
1844
|
-
console.log(
|
|
1845
|
-
console.log(
|
|
1889
|
+
console.log(chalk15.green("\u2705 Issue Created Successfully!"));
|
|
1890
|
+
console.log(chalk15.cyan(output));
|
|
1846
1891
|
return;
|
|
1847
1892
|
} catch {
|
|
1848
|
-
console.error(
|
|
1893
|
+
console.error(chalk15.red("\n\u274C Failed to submit via gh CLI."));
|
|
1849
1894
|
console.log(
|
|
1850
|
-
|
|
1895
|
+
chalk15.yellow(
|
|
1851
1896
|
' Make sure gh is installed and run "gh auth login".\n'
|
|
1852
1897
|
)
|
|
1853
1898
|
);
|
|
1854
|
-
console.log(
|
|
1899
|
+
console.log(chalk15.dim(" Falling back to URL generation..."));
|
|
1855
1900
|
}
|
|
1856
1901
|
}
|
|
1857
1902
|
const template = type === "bug" ? "bug_report.md" : type === "feature" ? "feature_request.md" : "new_metric_idea.md";
|
|
1858
1903
|
const fullUrl = `${repoUrl}/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}&labels=${label}&template=${template}`;
|
|
1859
|
-
console.log(
|
|
1860
|
-
console.log(
|
|
1861
|
-
console.log(
|
|
1862
|
-
console.log(
|
|
1863
|
-
console.log(
|
|
1864
|
-
console.log(
|
|
1865
|
-
console.log(
|
|
1904
|
+
console.log(chalk15.green("\u{1F680} Issue Draft Prepared!\n"));
|
|
1905
|
+
console.log(chalk15.bold("Title: ") + title);
|
|
1906
|
+
console.log(chalk15.bold("Type: ") + type);
|
|
1907
|
+
console.log(chalk15.bold("\nClick the link below to submit this issue:"));
|
|
1908
|
+
console.log(chalk15.cyan(fullUrl));
|
|
1909
|
+
console.log(chalk15.bold("\n\u{1F916} [FOR AI AGENTS]:"));
|
|
1910
|
+
console.log(chalk15.dim(" You have successfully prepared a report."));
|
|
1866
1911
|
console.log(
|
|
1867
|
-
|
|
1912
|
+
chalk15.dim(
|
|
1868
1913
|
" Please present the URL above to the user so they can finalize the submission."
|
|
1869
1914
|
)
|
|
1870
1915
|
);
|
|
@@ -1873,14 +1918,14 @@ Type: ${type}
|
|
|
1873
1918
|
const bugUrl = `${repoUrl}/issues/new?template=bug_report.md`;
|
|
1874
1919
|
const featureUrl = `${repoUrl}/issues/new?template=feature_request.md`;
|
|
1875
1920
|
const metricUrl = `${repoUrl}/issues/new?template=new_metric_idea.md`;
|
|
1876
|
-
console.log(
|
|
1877
|
-
console.log(` Report a Bug: ${
|
|
1878
|
-
console.log(` Request a Feature: ${
|
|
1879
|
-
console.log(` Suggest a Metric: ${
|
|
1880
|
-
console.log(
|
|
1881
|
-
console.log(
|
|
1921
|
+
console.log(chalk15.blue("\u{1F4AC} Feedback & Bug Reports\n"));
|
|
1922
|
+
console.log(` Report a Bug: ${chalk15.cyan(bugUrl)}`);
|
|
1923
|
+
console.log(` Request a Feature: ${chalk15.cyan(featureUrl)}`);
|
|
1924
|
+
console.log(` Suggest a Metric: ${chalk15.cyan(metricUrl)}`);
|
|
1925
|
+
console.log(chalk15.bold("\n\u{1F916} [FOR AI AGENTS]:"));
|
|
1926
|
+
console.log(chalk15.dim(" To prepare a specific report, run:"));
|
|
1882
1927
|
console.log(
|
|
1883
|
-
|
|
1928
|
+
chalk15.cyan(
|
|
1884
1929
|
' aiready bug "your description here" --type bug|feature|metric'
|
|
1885
1930
|
)
|
|
1886
1931
|
);
|
|
@@ -1954,7 +1999,7 @@ program.command("scan").description(
|
|
|
1954
1999
|
).option(
|
|
1955
2000
|
"--compare-to <path>",
|
|
1956
2001
|
"Compare results against a previous AIReady report JSON"
|
|
1957
|
-
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)"
|
|
2002
|
+
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--no-score", "Disable calculating AI Readiness Score").option("--weights <weights>", "Custom scoring weights").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option(
|
|
1958
2003
|
"--ci",
|
|
1959
2004
|
"CI mode: GitHub Actions annotations, no colors, fail on threshold"
|
|
1960
2005
|
).option(
|
|
@@ -1980,7 +2025,7 @@ program.command("patterns").description("Detect duplicate code patterns that con
|
|
|
1980
2025
|
).option(
|
|
1981
2026
|
"--full-scan",
|
|
1982
2027
|
"Disable smart defaults for comprehensive analysis (slower)"
|
|
1983
|
-
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)"
|
|
2028
|
+
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--no-score", "Disable calculating AI Readiness Score").addHelpText("after", PATTERNS_HELP_TEXT).action(async (directory, options) => {
|
|
1984
2029
|
await patternsAction(directory, options);
|
|
1985
2030
|
});
|
|
1986
2031
|
program.command("context").description("Analyze context window costs and dependency fragmentation").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option(
|