@aiready/cli 0.14.10 → 0.14.11
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/.aiready/aiready-report-20260319-201106.json +5566 -0
- package/.aiready/aiready-report-20260319-201511.json +5566 -0
- package/.aiready/aiready-report-20260319-202017.json +5708 -0
- package/.turbo/turbo-build.log +29 -28
- package/.turbo/turbo-test.log +76 -74
- package/dist/cli.js +156 -43
- package/dist/cli.mjs +156 -43
- package/package.json +12 -12
- package/src/commands/__tests__/init.test.ts +6 -11
- package/src/commands/init.ts +76 -42
- package/src/commands/report-formatter.ts +133 -7
package/dist/cli.mjs
CHANGED
|
@@ -131,13 +131,91 @@ import {
|
|
|
131
131
|
getRating,
|
|
132
132
|
getRatingDisplay
|
|
133
133
|
} from "@aiready/core";
|
|
134
|
+
function generateProgressBar(score, width = 20) {
|
|
135
|
+
const filled = Math.round(score / 100 * width);
|
|
136
|
+
const empty = width - filled;
|
|
137
|
+
let color = chalk2.red;
|
|
138
|
+
if (score >= 90) color = chalk2.green;
|
|
139
|
+
else if (score >= 75) color = chalk2.cyan;
|
|
140
|
+
else if (score >= 60) color = chalk2.yellow;
|
|
141
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
142
|
+
return color(bar);
|
|
143
|
+
}
|
|
144
|
+
function countIssuesBySeverity(results) {
|
|
145
|
+
let critical = 0;
|
|
146
|
+
let major = 0;
|
|
147
|
+
let minor = 0;
|
|
148
|
+
if (results.summary?.toolsRun) {
|
|
149
|
+
for (const toolId of results.summary.toolsRun) {
|
|
150
|
+
const toolRes = results[toolId];
|
|
151
|
+
if (toolRes?.results) {
|
|
152
|
+
for (const fileRes of toolRes.results) {
|
|
153
|
+
if (fileRes.issues) {
|
|
154
|
+
for (const issue of fileRes.issues) {
|
|
155
|
+
const sev = issue.severity?.toLowerCase();
|
|
156
|
+
if (sev === "critical") critical++;
|
|
157
|
+
else if (sev === "major") major++;
|
|
158
|
+
else minor++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { critical, major, minor };
|
|
166
|
+
}
|
|
167
|
+
function getTopFilesWithIssues(results, limit = 5) {
|
|
168
|
+
const fileCounts = /* @__PURE__ */ new Map();
|
|
169
|
+
if (results.summary?.toolsRun) {
|
|
170
|
+
for (const toolId of results.summary.toolsRun) {
|
|
171
|
+
const toolRes = results[toolId];
|
|
172
|
+
if (toolRes?.results) {
|
|
173
|
+
for (const fileRes of toolRes.results) {
|
|
174
|
+
if (fileRes.issues?.length > 0) {
|
|
175
|
+
const current = fileCounts.get(fileRes.fileName) || 0;
|
|
176
|
+
fileCounts.set(fileRes.fileName, current + fileRes.issues.length);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return Array.from(fileCounts.entries()).map(([file, count]) => ({ file, count })).sort((a, b) => b.count - a.count).slice(0, limit);
|
|
183
|
+
}
|
|
134
184
|
function printScanSummary(results, startTime) {
|
|
185
|
+
const severity = countIssuesBySeverity(results);
|
|
186
|
+
const totalIssues = severity.critical + severity.major + severity.minor;
|
|
187
|
+
const topFiles = getTopFilesWithIssues(results);
|
|
135
188
|
console.log(chalk2.cyan("\n=== AIReady Run Summary ==="));
|
|
189
|
+
console.log(` Total issues: ${chalk2.bold(String(totalIssues))}`);
|
|
190
|
+
if (totalIssues > 0) {
|
|
191
|
+
console.log(chalk2.dim(" Severity breakdown:"));
|
|
192
|
+
if (severity.critical > 0) {
|
|
193
|
+
console.log(
|
|
194
|
+
` ${chalk2.red("\u25CF")} Critical: ${chalk2.bold(severity.critical)}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (severity.major > 0) {
|
|
198
|
+
console.log(
|
|
199
|
+
` ${chalk2.yellow("\u25CF")} Major: ${chalk2.bold(severity.major)}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (severity.minor > 0) {
|
|
203
|
+
console.log(
|
|
204
|
+
` ${chalk2.blue("\u25CF")} Minor: ${chalk2.bold(severity.minor)}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (topFiles.length > 0) {
|
|
209
|
+
console.log(chalk2.dim("\n Top files with issues:"));
|
|
210
|
+
topFiles.forEach((item) => {
|
|
211
|
+
console.log(
|
|
212
|
+
` ${chalk2.yellow("\u2192")} ${item.file}: ${chalk2.bold(item.count)} issues`
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
136
216
|
console.log(
|
|
137
|
-
`
|
|
138
|
-
)
|
|
139
|
-
console.log(
|
|
140
|
-
` Execution time: ${chalk2.bold(((Date.now() - startTime) / 1e3).toFixed(2) + "s")}`
|
|
217
|
+
`
|
|
218
|
+
Execution time: ${chalk2.bold(((Date.now() - startTime) / 1e3).toFixed(2) + "s")}`
|
|
141
219
|
);
|
|
142
220
|
}
|
|
143
221
|
function printBusinessImpact(roi, unifiedBudget) {
|
|
@@ -164,13 +242,14 @@ function printScoring(scoringResult, scoringProfile) {
|
|
|
164
242
|
scoringResult.breakdown.forEach((tool) => {
|
|
165
243
|
const rating = getRating(tool.score);
|
|
166
244
|
const emoji = getRatingDisplay(rating).emoji;
|
|
245
|
+
const progressBar = generateProgressBar(tool.score, 15);
|
|
167
246
|
console.log(
|
|
168
|
-
`
|
|
247
|
+
` ${progressBar} ${tool.score}/100 (${rating}) ${emoji} ${tool.toolName}`
|
|
169
248
|
);
|
|
170
249
|
});
|
|
171
250
|
const allRecs = scoringResult.breakdown.flatMap(
|
|
172
251
|
(t) => (t.recommendations ?? []).map((r) => ({ ...r, tool: t.toolName }))
|
|
173
|
-
).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0,
|
|
252
|
+
).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0, 5);
|
|
174
253
|
if (allRecs.length > 0) {
|
|
175
254
|
console.log(chalk2.bold("\n\u{1F3AF} Top Actionable Recommendations:"));
|
|
176
255
|
allRecs.forEach((rec, i) => {
|
|
@@ -654,6 +733,9 @@ async function initAction(options) {
|
|
|
654
733
|
process.exit(1);
|
|
655
734
|
}
|
|
656
735
|
const baseConfig = {
|
|
736
|
+
// Target quality score threshold (0-100)
|
|
737
|
+
threshold: 75,
|
|
738
|
+
// Global scan settings
|
|
657
739
|
scan: {
|
|
658
740
|
include: [
|
|
659
741
|
"src/**/*.ts",
|
|
@@ -680,6 +762,17 @@ async function initAction(options) {
|
|
|
680
762
|
ToolName2.ChangeAmplification
|
|
681
763
|
]
|
|
682
764
|
},
|
|
765
|
+
// Output preferences
|
|
766
|
+
output: {
|
|
767
|
+
format: "console",
|
|
768
|
+
showBreakdown: true,
|
|
769
|
+
saveTo: "aiready-report.json"
|
|
770
|
+
},
|
|
771
|
+
// Scoring profile and weights
|
|
772
|
+
scoring: {
|
|
773
|
+
profile: "balanced"
|
|
774
|
+
},
|
|
775
|
+
// Tool-specific configurations
|
|
683
776
|
tools: {
|
|
684
777
|
[ToolName2.PatternDetect]: {
|
|
685
778
|
minSimilarity: 0.8,
|
|
@@ -703,7 +796,33 @@ async function initAction(options) {
|
|
|
703
796
|
},
|
|
704
797
|
[ToolName2.NamingConsistency]: {
|
|
705
798
|
shortWords: ["id", "db", "ui", "ai"],
|
|
706
|
-
|
|
799
|
+
acceptedAbbreviations: [
|
|
800
|
+
"API",
|
|
801
|
+
"JSON",
|
|
802
|
+
"CSV",
|
|
803
|
+
"HTML",
|
|
804
|
+
"CSS",
|
|
805
|
+
"HTTP",
|
|
806
|
+
"URL",
|
|
807
|
+
"SDK",
|
|
808
|
+
"CLI",
|
|
809
|
+
"AI",
|
|
810
|
+
"ML",
|
|
811
|
+
"ID",
|
|
812
|
+
"DB",
|
|
813
|
+
"UI",
|
|
814
|
+
"UX",
|
|
815
|
+
"DOM",
|
|
816
|
+
"UUID",
|
|
817
|
+
"GUID",
|
|
818
|
+
"DEFAULT",
|
|
819
|
+
"MAX",
|
|
820
|
+
"MIN",
|
|
821
|
+
"config",
|
|
822
|
+
"INIT",
|
|
823
|
+
"SKILL"
|
|
824
|
+
],
|
|
825
|
+
...options.full ? { disableChecks: [] } : {}
|
|
707
826
|
},
|
|
708
827
|
[ToolName2.AiSignalClarity]: {
|
|
709
828
|
checkMagicLiterals: true,
|
|
@@ -712,48 +831,42 @@ async function initAction(options) {
|
|
|
712
831
|
checkUndocumentedExports: true,
|
|
713
832
|
...options.full ? { checkImplicitSideEffects: false, checkDeepCallbacks: false } : {}
|
|
714
833
|
},
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
scoring: {
|
|
734
|
-
threshold: 70,
|
|
735
|
-
showBreakdown: true,
|
|
736
|
-
...options.full ? { profile: "default" } : {}
|
|
834
|
+
[ToolName2.AgentGrounding]: {
|
|
835
|
+
maxRecommendedDepth: 5,
|
|
836
|
+
readmeStaleDays: 30
|
|
837
|
+
},
|
|
838
|
+
[ToolName2.TestabilityIndex]: {
|
|
839
|
+
minCoverageRatio: 0.7,
|
|
840
|
+
testPatterns: ["**/*.test.ts", "**/__tests__/**"]
|
|
841
|
+
},
|
|
842
|
+
[ToolName2.DocDrift]: {
|
|
843
|
+
maxCommits: 50,
|
|
844
|
+
staleMonths: 3
|
|
845
|
+
},
|
|
846
|
+
[ToolName2.DependencyHealth]: {
|
|
847
|
+
trainingCutoffYear: 2023
|
|
848
|
+
},
|
|
849
|
+
[ToolName2.ChangeAmplification]: {
|
|
850
|
+
// No specific options yet, uses global scan settings
|
|
851
|
+
}
|
|
737
852
|
},
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
853
|
+
// Visualizer settings (interactive graph)
|
|
854
|
+
visualizer: {
|
|
855
|
+
groupingDirs: ["packages", "src", "lib"],
|
|
856
|
+
graph: {
|
|
857
|
+
maxNodes: 5e3,
|
|
858
|
+
maxEdges: 1e4
|
|
745
859
|
}
|
|
746
|
-
}
|
|
860
|
+
}
|
|
747
861
|
};
|
|
748
862
|
const defaultConfig = baseConfig;
|
|
749
863
|
let content;
|
|
750
864
|
if (fileExt === "js") {
|
|
751
|
-
content = `/**
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
)};
|
|
865
|
+
content = `/**
|
|
866
|
+
* AIReady Configuration
|
|
867
|
+
* @type {import('@aiready/core').AIReadyConfig}
|
|
868
|
+
*/
|
|
869
|
+
module.exports = ${JSON.stringify(defaultConfig, null, 2)};
|
|
757
870
|
`;
|
|
758
871
|
} else {
|
|
759
872
|
content = JSON.stringify(defaultConfig, null, 2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/cli",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.11",
|
|
4
4
|
"description": "Unified CLI for AIReady analysis tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"chalk": "^5.3.0",
|
|
13
13
|
"commander": "^14.0.0",
|
|
14
|
-
"@aiready/
|
|
15
|
-
"@aiready/consistency": "0.20.
|
|
16
|
-
"@aiready/core": "0.23.
|
|
17
|
-
"@aiready/
|
|
18
|
-
"@aiready/
|
|
19
|
-
"@aiready/
|
|
20
|
-
"@aiready/
|
|
21
|
-
"@aiready/
|
|
22
|
-
"@aiready/
|
|
23
|
-
"@aiready/testability": "0.6.
|
|
24
|
-
"@aiready/
|
|
14
|
+
"@aiready/context-analyzer": "0.21.12",
|
|
15
|
+
"@aiready/consistency": "0.20.8",
|
|
16
|
+
"@aiready/core": "0.23.9",
|
|
17
|
+
"@aiready/agent-grounding": "0.13.8",
|
|
18
|
+
"@aiready/pattern-detect": "0.16.8",
|
|
19
|
+
"@aiready/deps": "0.13.9",
|
|
20
|
+
"@aiready/visualizer": "0.6.8",
|
|
21
|
+
"@aiready/change-amplification": "0.13.8",
|
|
22
|
+
"@aiready/doc-drift": "0.13.8",
|
|
23
|
+
"@aiready/testability": "0.6.8",
|
|
24
|
+
"@aiready/ai-signal-clarity": "0.13.9"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^24.0.0",
|
|
@@ -28,29 +28,24 @@ describe('initAction', () => {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it('should generate aiready.json
|
|
31
|
+
it('should generate aiready.json with output field by default', async () => {
|
|
32
32
|
await initAction({});
|
|
33
33
|
|
|
34
34
|
expect(existsSync(configPath)).toBe(true);
|
|
35
35
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
36
|
-
expect(config).
|
|
36
|
+
expect(config).toHaveProperty('output');
|
|
37
|
+
expect(config.output.format).toBe('console');
|
|
37
38
|
});
|
|
38
39
|
|
|
39
|
-
it('should
|
|
40
|
-
await initAction({ full: true });
|
|
41
|
-
|
|
42
|
-
expect(existsSync(configPath)).toBe(true);
|
|
43
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
44
|
-
expect(config).not.toHaveProperty('output');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should include scan, tools, and scoring sections', async () => {
|
|
40
|
+
it('should include scan, tools, scoring, and visualizer sections', async () => {
|
|
48
41
|
await initAction({ full: true });
|
|
49
42
|
|
|
50
43
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
51
44
|
expect(config).toHaveProperty('scan');
|
|
52
45
|
expect(config).toHaveProperty('tools');
|
|
53
46
|
expect(config).toHaveProperty('scoring');
|
|
47
|
+
expect(config).toHaveProperty('output');
|
|
54
48
|
expect(config).toHaveProperty('visualizer');
|
|
49
|
+
expect(config).toHaveProperty('threshold');
|
|
55
50
|
});
|
|
56
51
|
});
|
package/src/commands/init.ts
CHANGED
|
@@ -20,6 +20,10 @@ export async function initAction(options: {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const baseConfig = {
|
|
23
|
+
// Target quality score threshold (0-100)
|
|
24
|
+
threshold: 75,
|
|
25
|
+
|
|
26
|
+
// Global scan settings
|
|
23
27
|
scan: {
|
|
24
28
|
include: [
|
|
25
29
|
'src/**/*.ts',
|
|
@@ -46,6 +50,20 @@ export async function initAction(options: {
|
|
|
46
50
|
ToolName.ChangeAmplification,
|
|
47
51
|
],
|
|
48
52
|
},
|
|
53
|
+
|
|
54
|
+
// Output preferences
|
|
55
|
+
output: {
|
|
56
|
+
format: 'console',
|
|
57
|
+
showBreakdown: true,
|
|
58
|
+
saveTo: 'aiready-report.json',
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Scoring profile and weights
|
|
62
|
+
scoring: {
|
|
63
|
+
profile: 'balanced',
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// Tool-specific configurations
|
|
49
67
|
tools: {
|
|
50
68
|
[ToolName.PatternDetect]: {
|
|
51
69
|
minSimilarity: 0.8,
|
|
@@ -73,9 +91,33 @@ export async function initAction(options: {
|
|
|
73
91
|
},
|
|
74
92
|
[ToolName.NamingConsistency]: {
|
|
75
93
|
shortWords: ['id', 'db', 'ui', 'ai'],
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
acceptedAbbreviations: [
|
|
95
|
+
'API',
|
|
96
|
+
'JSON',
|
|
97
|
+
'CSV',
|
|
98
|
+
'HTML',
|
|
99
|
+
'CSS',
|
|
100
|
+
'HTTP',
|
|
101
|
+
'URL',
|
|
102
|
+
'SDK',
|
|
103
|
+
'CLI',
|
|
104
|
+
'AI',
|
|
105
|
+
'ML',
|
|
106
|
+
'ID',
|
|
107
|
+
'DB',
|
|
108
|
+
'UI',
|
|
109
|
+
'UX',
|
|
110
|
+
'DOM',
|
|
111
|
+
'UUID',
|
|
112
|
+
'GUID',
|
|
113
|
+
'DEFAULT',
|
|
114
|
+
'MAX',
|
|
115
|
+
'MIN',
|
|
116
|
+
'config',
|
|
117
|
+
'INIT',
|
|
118
|
+
'SKILL',
|
|
119
|
+
],
|
|
120
|
+
...(options.full ? { disableChecks: [] } : {}),
|
|
79
121
|
},
|
|
80
122
|
[ToolName.AiSignalClarity]: {
|
|
81
123
|
checkMagicLiterals: true,
|
|
@@ -86,53 +128,45 @@ export async function initAction(options: {
|
|
|
86
128
|
? { checkImplicitSideEffects: false, checkDeepCallbacks: false }
|
|
87
129
|
: {}),
|
|
88
130
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
: {}),
|
|
131
|
+
[ToolName.AgentGrounding]: {
|
|
132
|
+
maxRecommendedDepth: 5,
|
|
133
|
+
readmeStaleDays: 30,
|
|
134
|
+
},
|
|
135
|
+
[ToolName.TestabilityIndex]: {
|
|
136
|
+
minCoverageRatio: 0.7,
|
|
137
|
+
testPatterns: ['**/*.test.ts', '**/__tests__/**'],
|
|
138
|
+
},
|
|
139
|
+
[ToolName.DocDrift]: {
|
|
140
|
+
maxCommits: 50,
|
|
141
|
+
staleMonths: 3,
|
|
142
|
+
},
|
|
143
|
+
[ToolName.DependencyHealth]: {
|
|
144
|
+
trainingCutoffYear: 2023,
|
|
145
|
+
},
|
|
146
|
+
[ToolName.ChangeAmplification]: {
|
|
147
|
+
// No specific options yet, uses global scan settings
|
|
148
|
+
},
|
|
108
149
|
},
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
150
|
+
|
|
151
|
+
// Visualizer settings (interactive graph)
|
|
152
|
+
visualizer: {
|
|
153
|
+
groupingDirs: ['packages', 'src', 'lib'],
|
|
154
|
+
graph: {
|
|
155
|
+
maxNodes: 5000,
|
|
156
|
+
maxEdges: 10000,
|
|
157
|
+
},
|
|
113
158
|
},
|
|
114
|
-
...(options.full
|
|
115
|
-
? {
|
|
116
|
-
visualizer: {
|
|
117
|
-
groupingDirs: ['packages', 'src', 'lib'],
|
|
118
|
-
graph: {
|
|
119
|
-
maxNodes: 5000,
|
|
120
|
-
maxEdges: 10000,
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
}
|
|
124
|
-
: {}),
|
|
125
159
|
};
|
|
126
160
|
|
|
127
161
|
const defaultConfig = baseConfig;
|
|
128
162
|
|
|
129
163
|
let content: string;
|
|
130
164
|
if (fileExt === 'js') {
|
|
131
|
-
content = `/**
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
165
|
+
content = `/**
|
|
166
|
+
* AIReady Configuration
|
|
167
|
+
* @type {import('@aiready/core').AIReadyConfig}
|
|
168
|
+
*/
|
|
169
|
+
module.exports = ${JSON.stringify(defaultConfig, null, 2)};\n`;
|
|
136
170
|
} else {
|
|
137
171
|
content = JSON.stringify(defaultConfig, null, 2);
|
|
138
172
|
}
|
|
@@ -7,6 +7,95 @@ import {
|
|
|
7
7
|
getRatingDisplay,
|
|
8
8
|
} from '@aiready/core';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Generate a visual progress bar for a score.
|
|
12
|
+
*
|
|
13
|
+
* @param score - The score value (0-100)
|
|
14
|
+
* @param width - The width of the progress bar
|
|
15
|
+
* @returns A colored progress bar string
|
|
16
|
+
*/
|
|
17
|
+
function generateProgressBar(score: number, width: number = 20): string {
|
|
18
|
+
const filled = Math.round((score / 100) * width);
|
|
19
|
+
const empty = width - filled;
|
|
20
|
+
|
|
21
|
+
let color = chalk.red;
|
|
22
|
+
if (score >= 90) color = chalk.green;
|
|
23
|
+
else if (score >= 75) color = chalk.cyan;
|
|
24
|
+
else if (score >= 60) color = chalk.yellow;
|
|
25
|
+
|
|
26
|
+
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
27
|
+
return color(bar);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Count issues by severity level from all tool results.
|
|
32
|
+
*
|
|
33
|
+
* @param results - The unified results object
|
|
34
|
+
* @returns Object with counts for each severity level
|
|
35
|
+
*/
|
|
36
|
+
function countIssuesBySeverity(results: any): {
|
|
37
|
+
critical: number;
|
|
38
|
+
major: number;
|
|
39
|
+
minor: number;
|
|
40
|
+
} {
|
|
41
|
+
let critical = 0;
|
|
42
|
+
let major = 0;
|
|
43
|
+
let minor = 0;
|
|
44
|
+
|
|
45
|
+
if (results.summary?.toolsRun) {
|
|
46
|
+
for (const toolId of results.summary.toolsRun) {
|
|
47
|
+
const toolRes = results[toolId];
|
|
48
|
+
if (toolRes?.results) {
|
|
49
|
+
for (const fileRes of toolRes.results) {
|
|
50
|
+
if (fileRes.issues) {
|
|
51
|
+
for (const issue of fileRes.issues) {
|
|
52
|
+
const sev = issue.severity?.toLowerCase();
|
|
53
|
+
if (sev === 'critical') critical++;
|
|
54
|
+
else if (sev === 'major') major++;
|
|
55
|
+
else minor++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { critical, major, minor };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get top files with most issues.
|
|
68
|
+
*
|
|
69
|
+
* @param results - The unified results object
|
|
70
|
+
* @param limit - Maximum number of files to return
|
|
71
|
+
* @returns Array of files with issue counts
|
|
72
|
+
*/
|
|
73
|
+
function getTopFilesWithIssues(
|
|
74
|
+
results: any,
|
|
75
|
+
limit: number = 5
|
|
76
|
+
): Array<{ file: string; count: number }> {
|
|
77
|
+
const fileCounts = new Map<string, number>();
|
|
78
|
+
|
|
79
|
+
if (results.summary?.toolsRun) {
|
|
80
|
+
for (const toolId of results.summary.toolsRun) {
|
|
81
|
+
const toolRes = results[toolId];
|
|
82
|
+
if (toolRes?.results) {
|
|
83
|
+
for (const fileRes of toolRes.results) {
|
|
84
|
+
if (fileRes.issues?.length > 0) {
|
|
85
|
+
const current = fileCounts.get(fileRes.fileName) || 0;
|
|
86
|
+
fileCounts.set(fileRes.fileName, current + fileRes.issues.length);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return Array.from(fileCounts.entries())
|
|
94
|
+
.map(([file, count]) => ({ file, count }))
|
|
95
|
+
.sort((a, b) => b.count - a.count)
|
|
96
|
+
.slice(0, limit);
|
|
97
|
+
}
|
|
98
|
+
|
|
10
99
|
/**
|
|
11
100
|
* Handle console output for the scan results.
|
|
12
101
|
*
|
|
@@ -14,12 +103,48 @@ import {
|
|
|
14
103
|
* @param startTime - The timestamp when the scan started.
|
|
15
104
|
*/
|
|
16
105
|
export function printScanSummary(results: any, startTime: number) {
|
|
106
|
+
// Count issues by severity
|
|
107
|
+
const severity = countIssuesBySeverity(results);
|
|
108
|
+
const totalIssues = severity.critical + severity.major + severity.minor;
|
|
109
|
+
|
|
110
|
+
// Get top files with issues
|
|
111
|
+
const topFiles = getTopFilesWithIssues(results);
|
|
112
|
+
|
|
17
113
|
console.log(chalk.cyan('\n=== AIReady Run Summary ==='));
|
|
114
|
+
console.log(` Total issues: ${chalk.bold(String(totalIssues))}`);
|
|
115
|
+
|
|
116
|
+
// Severity breakdown
|
|
117
|
+
if (totalIssues > 0) {
|
|
118
|
+
console.log(chalk.dim(' Severity breakdown:'));
|
|
119
|
+
if (severity.critical > 0) {
|
|
120
|
+
console.log(
|
|
121
|
+
` ${chalk.red('●')} Critical: ${chalk.bold(severity.critical)}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (severity.major > 0) {
|
|
125
|
+
console.log(
|
|
126
|
+
` ${chalk.yellow('●')} Major: ${chalk.bold(severity.major)}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (severity.minor > 0) {
|
|
130
|
+
console.log(
|
|
131
|
+
` ${chalk.blue('●')} Minor: ${chalk.bold(severity.minor)}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Top files with issues
|
|
137
|
+
if (topFiles.length > 0) {
|
|
138
|
+
console.log(chalk.dim('\n Top files with issues:'));
|
|
139
|
+
topFiles.forEach((item) => {
|
|
140
|
+
console.log(
|
|
141
|
+
` ${chalk.yellow('→')} ${item.file}: ${chalk.bold(item.count)} issues`
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
18
146
|
console.log(
|
|
19
|
-
|
|
20
|
-
);
|
|
21
|
-
console.log(
|
|
22
|
-
` Execution time: ${chalk.bold(((Date.now() - startTime) / 1000).toFixed(2) + 's')}`
|
|
147
|
+
`\n Execution time: ${chalk.bold(((Date.now() - startTime) / 1000).toFixed(2) + 's')}`
|
|
23
148
|
);
|
|
24
149
|
}
|
|
25
150
|
|
|
@@ -64,18 +189,19 @@ export function printScoring(
|
|
|
64
189
|
scoringResult.breakdown.forEach((tool: any) => {
|
|
65
190
|
const rating = getRating(tool.score);
|
|
66
191
|
const emoji = getRatingDisplay(rating).emoji;
|
|
192
|
+
const progressBar = generateProgressBar(tool.score, 15);
|
|
67
193
|
console.log(
|
|
68
|
-
`
|
|
194
|
+
` ${progressBar} ${tool.score}/100 (${rating}) ${emoji} ${tool.toolName}`
|
|
69
195
|
);
|
|
70
196
|
});
|
|
71
197
|
|
|
72
|
-
// Top Actionable Recommendations
|
|
198
|
+
// Top Actionable Recommendations - increased from 3 to 5
|
|
73
199
|
const allRecs = scoringResult.breakdown
|
|
74
200
|
.flatMap((t: any) =>
|
|
75
201
|
(t.recommendations ?? []).map((r: any) => ({ ...r, tool: t.toolName }))
|
|
76
202
|
)
|
|
77
203
|
.sort((a: any, b: any) => b.estimatedImpact - a.estimatedImpact)
|
|
78
|
-
.slice(0,
|
|
204
|
+
.slice(0, 5); // Increased from 3 to 5
|
|
79
205
|
|
|
80
206
|
if (allRecs.length > 0) {
|
|
81
207
|
console.log(chalk.bold('\n🎯 Top Actionable Recommendations:'));
|