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