@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/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
- ` Total issues (all tools): ${chalk2.bold(String(results.summary.totalIssues ?? 0))}`
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
- ` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${emoji}`
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, 3);
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
- ...options.full ? { acceptedAbbreviations: [], disableChecks: [] } : {}
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
- ...options.full ? {
716
- [ToolName2.AgentGrounding]: {
717
- maxRecommendedDepth: 5,
718
- readmeStaleDays: 30
719
- },
720
- [ToolName2.TestabilityIndex]: {
721
- minCoverageRatio: 0.7,
722
- testPatterns: ["**/*.test.ts", "**/__tests__/**"]
723
- },
724
- [ToolName2.DocDrift]: {
725
- maxCommits: 50,
726
- staleMonths: 3
727
- },
728
- [ToolName2.DependencyHealth]: {
729
- trainingCutoffYear: 2023
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
- ...options.full ? {
739
- visualizer: {
740
- groupingDirs: ["packages", "src", "lib"],
741
- graph: {
742
- maxNodes: 5e3,
743
- maxEdges: 1e4
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 = `/** @type {import('@aiready/core').AIReadyConfig} */
752
- module.exports = ${JSON.stringify(
753
- defaultConfig,
754
- null,
755
- 2
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.10",
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/agent-grounding": "0.13.7",
15
- "@aiready/consistency": "0.20.7",
16
- "@aiready/core": "0.23.8",
17
- "@aiready/deps": "0.13.7",
18
- "@aiready/context-analyzer": "0.21.11",
19
- "@aiready/doc-drift": "0.13.7",
20
- "@aiready/change-amplification": "0.13.7",
21
- "@aiready/ai-signal-clarity": "0.13.8",
22
- "@aiready/pattern-detect": "0.16.7",
23
- "@aiready/testability": "0.6.7",
24
- "@aiready/visualizer": "0.6.7"
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 without output field by default', async () => {
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).not.toHaveProperty('output');
36
+ expect(config).toHaveProperty('output');
37
+ expect(config.output.format).toBe('console');
37
38
  });
38
39
 
39
- it('should generate aiready.json without output field even with --full flag', async () => {
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
  });
@@ -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
- ...(options.full
77
- ? { acceptedAbbreviations: [], disableChecks: [] }
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
- ...(options.full
90
- ? {
91
- [ToolName.AgentGrounding]: {
92
- maxRecommendedDepth: 5,
93
- readmeStaleDays: 30,
94
- },
95
- [ToolName.TestabilityIndex]: {
96
- minCoverageRatio: 0.7,
97
- testPatterns: ['**/*.test.ts', '**/__tests__/**'],
98
- },
99
- [ToolName.DocDrift]: {
100
- maxCommits: 50,
101
- staleMonths: 3,
102
- },
103
- [ToolName.DependencyHealth]: {
104
- trainingCutoffYear: 2023,
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
- scoring: {
110
- threshold: 70,
111
- showBreakdown: true,
112
- ...(options.full ? { profile: 'default' } : {}),
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 = `/** @type {import('@aiready/core').AIReadyConfig} */\nmodule.exports = ${JSON.stringify(
132
- defaultConfig,
133
- null,
134
- 2
135
- )};\n`;
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
- ` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues ?? 0))}`
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
- ` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${emoji}`
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, 3);
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:'));