@aiready/cli 0.4.4 → 0.5.0

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/cli@0.4.4 build /Users/pengcao/projects/aiready/packages/cli
3
+ > @aiready/cli@0.5.0 build /Users/pengcao/projects/aiready/packages/cli
4
4
  > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
5
 
6
6
  CLI Building entry: src/cli.ts, src/index.ts
@@ -9,15 +9,15 @@
9
9
  CLI Target: es2020
10
10
  CJS Build start
11
11
  ESM Build start
12
- CJS dist/cli.js 16.87 KB
13
12
  CJS dist/index.js 3.15 KB
14
- CJS ⚡️ Build success in 54ms
13
+ CJS dist/cli.js 27.02 KB
14
+ CJS ⚡️ Build success in 55ms
15
+ ESM dist/cli.mjs 21.95 KB
15
16
  ESM dist/chunk-VOB7SA3E.mjs 2.02 KB
16
17
  ESM dist/index.mjs 138.00 B
17
- ESM dist/cli.mjs 13.23 KB
18
18
  ESM ⚡️ Build success in 54ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 518ms
20
+ DTS ⚡️ Build success in 486ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
22
  DTS dist/index.d.ts 991.00 B
23
23
  DTS dist/cli.d.mts 20.00 B
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/cli@0.1.1 test /Users/pengcao/projects/aiready/packages/cli
3
+ > @aiready/cli@0.5.0 test /Users/pengcao/projects/aiready/packages/cli
4
4
  > vitest run
5
5
 
6
6
  [?25l
@@ -11,7 +11,7 @@
11
11
 
12
12
   Test Files 0 passed (1)
13
13
   Tests 0 passed (0)
14
-  Start at 03:19:15
14
+  Start at 07:47:38
15
15
   Duration 0ms
16
16
  [?2026l ✓ src/__tests__/cli.test.ts (3 tests) 2ms
17
17
  ✓ CLI Unified Analysis (3)
@@ -21,7 +21,7 @@
21
21
 
22
22
   Test Files  1 passed (1)
23
23
   Tests  3 passed (3)
24
-  Start at  03:19:15
25
-  Duration  104ms (transform 27ms, setup 0ms, import 36ms, tests 2ms, environment 0ms)
24
+  Start at  07:47:38
25
+  Duration  146ms (transform 42ms, setup 0ms, import 69ms, tests 2ms, environment 0ms)
26
26
 
27
27
  [?25h
package/dist/cli.js CHANGED
@@ -184,7 +184,7 @@ program.command("patterns").description("Run pattern detection analysis").argume
184
184
  }
185
185
  const finalOptions = (0, import_core.loadMergedConfig)(directory, defaults, cliOptions);
186
186
  const { analyzePatterns: analyzePatterns2, generateSummary } = await import("@aiready/pattern-detect");
187
- const { results } = await analyzePatterns2(finalOptions);
187
+ const { results, duplicates } = await analyzePatterns2(finalOptions);
188
188
  const elapsedTime = (0, import_core.getElapsedTime)(startTime);
189
189
  const summary = generateSummary(results);
190
190
  const outputFormat = options.output || finalOptions.output?.format || "console";
@@ -201,9 +201,43 @@ program.command("patterns").description("Run pattern detection analysis").argume
201
201
  );
202
202
  (0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
203
203
  } else {
204
- console.log(`Pattern Analysis Complete (${elapsedTime}s)`);
205
- console.log(`Found ${summary.totalPatterns} duplicate patterns`);
206
- console.log(`Total token cost: ${summary.totalTokenCost} tokens`);
204
+ const terminalWidth = process.stdout.columns || 80;
205
+ const dividerWidth = Math.min(60, terminalWidth - 2);
206
+ const divider = "\u2501".repeat(dividerWidth);
207
+ console.log(import_chalk.default.cyan(divider));
208
+ console.log(import_chalk.default.bold.white(" PATTERN ANALYSIS SUMMARY"));
209
+ console.log(import_chalk.default.cyan(divider) + "\n");
210
+ console.log(import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(results.length)}`));
211
+ console.log(import_chalk.default.yellow(`\u26A0 Duplicate patterns found: ${import_chalk.default.bold(summary.totalPatterns)}`));
212
+ console.log(import_chalk.default.red(`\u{1F4B0} Token cost (wasted): ${import_chalk.default.bold(summary.totalTokenCost.toLocaleString())}`));
213
+ console.log(import_chalk.default.gray(`\u23F1 Analysis time: ${import_chalk.default.bold(elapsedTime + "s")}`));
214
+ const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
215
+ if (sortedTypes.length > 0) {
216
+ console.log(import_chalk.default.cyan("\n" + divider));
217
+ console.log(import_chalk.default.bold.white(" PATTERNS BY TYPE"));
218
+ console.log(import_chalk.default.cyan(divider) + "\n");
219
+ sortedTypes.forEach(([type, count]) => {
220
+ console.log(` ${import_chalk.default.white(type.padEnd(15))} ${import_chalk.default.bold(count)}`);
221
+ });
222
+ }
223
+ if (summary.totalPatterns > 0 && duplicates.length > 0) {
224
+ console.log(import_chalk.default.cyan("\n" + divider));
225
+ console.log(import_chalk.default.bold.white(" TOP DUPLICATE PATTERNS"));
226
+ console.log(import_chalk.default.cyan(divider) + "\n");
227
+ const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
228
+ topDuplicates.forEach((dup) => {
229
+ const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
230
+ const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
231
+ const file1Name = dup.file1.split("/").pop() || dup.file1;
232
+ const file2Name = dup.file2.split("/").pop() || dup.file2;
233
+ console.log(`${severityIcon} ${severity}: ${import_chalk.default.bold(file1Name)} \u2194 ${import_chalk.default.bold(file2Name)}`);
234
+ console.log(` Similarity: ${import_chalk.default.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${import_chalk.default.bold(dup.tokenCost.toLocaleString())} tokens each`);
235
+ console.log(` Lines: ${import_chalk.default.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${import_chalk.default.cyan(dup.line2 + "-" + dup.endLine2)}
236
+ `);
237
+ });
238
+ } else {
239
+ console.log(import_chalk.default.green("\n\u2728 Great! No duplicate patterns detected.\n"));
240
+ }
207
241
  }
208
242
  } catch (error) {
209
243
  (0, import_core.handleCLIError)(error, "Pattern analysis");
@@ -258,11 +292,77 @@ program.command("context").description("Run context window cost analysis").argum
258
292
  );
259
293
  (0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
260
294
  } else {
261
- console.log(`Context Analysis Complete (${elapsedTime}s)`);
262
- console.log(`Files analyzed: ${summary.totalFiles}`);
263
- console.log(`Issues found: ${results.length}`);
264
- console.log(`Average cohesion: ${(summary.avgCohesion * 100).toFixed(1)}%`);
265
- console.log(`Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(1)}%`);
295
+ const terminalWidth = process.stdout.columns || 80;
296
+ const dividerWidth = Math.min(60, terminalWidth - 2);
297
+ const divider = "\u2501".repeat(dividerWidth);
298
+ console.log(import_chalk.default.cyan(divider));
299
+ console.log(import_chalk.default.bold.white(" CONTEXT ANALYSIS SUMMARY"));
300
+ console.log(import_chalk.default.cyan(divider) + "\n");
301
+ console.log(import_chalk.default.white(`\u{1F4C1} Files analyzed: ${import_chalk.default.bold(summary.totalFiles)}`));
302
+ console.log(import_chalk.default.white(`\u{1F4CA} Total tokens: ${import_chalk.default.bold(summary.totalTokens.toLocaleString())}`));
303
+ console.log(import_chalk.default.yellow(`\u{1F4B0} Avg context budget: ${import_chalk.default.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
304
+ console.log(import_chalk.default.white(`\u23F1 Analysis time: ${import_chalk.default.bold(elapsedTime + "s")}
305
+ `));
306
+ const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
307
+ if (totalIssues > 0) {
308
+ console.log(import_chalk.default.bold("\u26A0\uFE0F Issues Found:\n"));
309
+ if (summary.criticalIssues > 0) {
310
+ console.log(import_chalk.default.red(` \u{1F534} Critical: ${import_chalk.default.bold(summary.criticalIssues)}`));
311
+ }
312
+ if (summary.majorIssues > 0) {
313
+ console.log(import_chalk.default.yellow(` \u{1F7E1} Major: ${import_chalk.default.bold(summary.majorIssues)}`));
314
+ }
315
+ if (summary.minorIssues > 0) {
316
+ console.log(import_chalk.default.blue(` \u{1F535} Minor: ${import_chalk.default.bold(summary.minorIssues)}`));
317
+ }
318
+ console.log(import_chalk.default.green(`
319
+ \u{1F4A1} Potential savings: ${import_chalk.default.bold(summary.totalPotentialSavings.toLocaleString())} tokens
320
+ `));
321
+ } else {
322
+ console.log(import_chalk.default.green("\u2705 No significant issues found!\n"));
323
+ }
324
+ if (summary.deepFiles.length > 0) {
325
+ console.log(import_chalk.default.bold("\u{1F4CF} Deep Import Chains:\n"));
326
+ console.log(import_chalk.default.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
327
+ console.log(import_chalk.default.gray(` Maximum depth: ${summary.maxImportDepth}
328
+ `));
329
+ summary.deepFiles.slice(0, 10).forEach((item) => {
330
+ const fileName = item.file.split("/").slice(-2).join("/");
331
+ console.log(` ${import_chalk.default.cyan("\u2192")} ${import_chalk.default.white(fileName)} ${import_chalk.default.dim(`(depth: ${item.depth})`)}`);
332
+ });
333
+ console.log();
334
+ }
335
+ if (summary.fragmentedModules.length > 0) {
336
+ console.log(import_chalk.default.bold("\u{1F9E9} Fragmented Modules:\n"));
337
+ console.log(import_chalk.default.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
338
+ `));
339
+ summary.fragmentedModules.slice(0, 10).forEach((module2) => {
340
+ console.log(` ${import_chalk.default.yellow("\u25CF")} ${import_chalk.default.white(module2.domain)} - ${import_chalk.default.dim(`${module2.files.length} files, ${(module2.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
341
+ console.log(import_chalk.default.dim(` Token cost: ${module2.totalTokens.toLocaleString()}, Cohesion: ${(module2.avgCohesion * 100).toFixed(0)}%`));
342
+ });
343
+ console.log();
344
+ }
345
+ if (summary.lowCohesionFiles.length > 0) {
346
+ console.log(import_chalk.default.bold("\u{1F500} Low Cohesion Files:\n"));
347
+ console.log(import_chalk.default.gray(` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
348
+ `));
349
+ summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
350
+ const fileName = item.file.split("/").slice(-2).join("/");
351
+ const scorePercent = (item.score * 100).toFixed(0);
352
+ const color = item.score < 0.4 ? import_chalk.default.red : import_chalk.default.yellow;
353
+ console.log(` ${color("\u25CB")} ${import_chalk.default.white(fileName)} ${import_chalk.default.dim(`(${scorePercent}% cohesion)`)}`);
354
+ });
355
+ console.log();
356
+ }
357
+ if (summary.topExpensiveFiles.length > 0) {
358
+ console.log(import_chalk.default.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
359
+ summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
360
+ const fileName = item.file.split("/").slice(-2).join("/");
361
+ const severityColor = item.severity === "critical" ? import_chalk.default.red : item.severity === "major" ? import_chalk.default.yellow : import_chalk.default.blue;
362
+ console.log(` ${severityColor("\u25CF")} ${import_chalk.default.white(fileName)} ${import_chalk.default.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`);
363
+ });
364
+ console.log();
365
+ }
266
366
  }
267
367
  } catch (error) {
268
368
  (0, import_core.handleCLIError)(error, "Context analysis");
@@ -319,16 +419,76 @@ program.command("consistency").description("Check naming, patterns, and architec
319
419
  (0, import_fs.writeFileSync)(outputPath, markdown);
320
420
  console.log(import_chalk.default.green(`\u2705 Report saved to ${outputPath}`));
321
421
  } else {
322
- console.log(`Consistency Analysis Complete (${elapsedTime}s)`);
323
- console.log(`Files analyzed: ${report.summary.filesAnalyzed}`);
324
- console.log(`Total issues: ${report.summary.totalIssues}`);
325
- console.log(` Naming: ${report.summary.namingIssues}`);
326
- console.log(` Patterns: ${report.summary.patternIssues}`);
327
- if (report.recommendations.length > 0) {
328
- console.log(import_chalk.default.bold("\n\u{1F4A1} Recommendations:"));
329
- report.recommendations.forEach((rec, i) => {
330
- console.log(`${i + 1}. ${rec}`);
331
- });
422
+ console.log(import_chalk.default.bold("\n\u{1F4CA} Summary\n"));
423
+ console.log(`Files Analyzed: ${import_chalk.default.cyan(report.summary.filesAnalyzed)}`);
424
+ console.log(`Total Issues: ${import_chalk.default.yellow(report.summary.totalIssues)}`);
425
+ console.log(` Naming: ${import_chalk.default.yellow(report.summary.namingIssues)}`);
426
+ console.log(` Patterns: ${import_chalk.default.yellow(report.summary.patternIssues)}`);
427
+ console.log(` Architecture: ${import_chalk.default.yellow(report.summary.architectureIssues || 0)}`);
428
+ console.log(`Analysis Time: ${import_chalk.default.gray(elapsedTime + "s")}
429
+ `);
430
+ if (report.summary.totalIssues === 0) {
431
+ console.log(import_chalk.default.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
432
+ } else {
433
+ const namingResults = report.results.filter(
434
+ (r) => r.issues.some((i) => i.category === "naming")
435
+ );
436
+ const patternResults = report.results.filter(
437
+ (r) => r.issues.some((i) => i.category === "patterns")
438
+ );
439
+ if (namingResults.length > 0) {
440
+ console.log(import_chalk.default.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
441
+ let shown = 0;
442
+ for (const result of namingResults) {
443
+ if (shown >= 5) break;
444
+ for (const issue of result.issues) {
445
+ if (shown >= 5) break;
446
+ const severityColor = issue.severity === "critical" ? import_chalk.default.red : issue.severity === "major" ? import_chalk.default.yellow : issue.severity === "minor" ? import_chalk.default.blue : import_chalk.default.gray;
447
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${import_chalk.default.dim(`${issue.location.file}:${issue.location.line}`)}`);
448
+ console.log(` ${issue.message}`);
449
+ if (issue.suggestion) {
450
+ console.log(` ${import_chalk.default.dim("\u2192")} ${import_chalk.default.italic(issue.suggestion)}`);
451
+ }
452
+ console.log();
453
+ shown++;
454
+ }
455
+ }
456
+ const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
457
+ if (remaining > 0) {
458
+ console.log(import_chalk.default.dim(` ... and ${remaining} more issues
459
+ `));
460
+ }
461
+ }
462
+ if (patternResults.length > 0) {
463
+ console.log(import_chalk.default.bold("\u{1F504} Pattern Issues\n"));
464
+ let shown = 0;
465
+ for (const result of patternResults) {
466
+ if (shown >= 5) break;
467
+ for (const issue of result.issues) {
468
+ if (shown >= 5) break;
469
+ const severityColor = issue.severity === "critical" ? import_chalk.default.red : issue.severity === "major" ? import_chalk.default.yellow : issue.severity === "minor" ? import_chalk.default.blue : import_chalk.default.gray;
470
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${import_chalk.default.dim(`${issue.location.file}:${issue.location.line}`)}`);
471
+ console.log(` ${issue.message}`);
472
+ if (issue.suggestion) {
473
+ console.log(` ${import_chalk.default.dim("\u2192")} ${import_chalk.default.italic(issue.suggestion)}`);
474
+ }
475
+ console.log();
476
+ shown++;
477
+ }
478
+ }
479
+ const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
480
+ if (remaining > 0) {
481
+ console.log(import_chalk.default.dim(` ... and ${remaining} more issues
482
+ `));
483
+ }
484
+ }
485
+ if (report.recommendations.length > 0) {
486
+ console.log(import_chalk.default.bold("\u{1F4A1} Recommendations\n"));
487
+ report.recommendations.forEach((rec, i) => {
488
+ console.log(`${i + 1}. ${rec}`);
489
+ });
490
+ console.log();
491
+ }
332
492
  }
333
493
  }
334
494
  } catch (error) {
package/dist/cli.mjs CHANGED
@@ -96,7 +96,7 @@ program.command("patterns").description("Run pattern detection analysis").argume
96
96
  }
97
97
  const finalOptions = loadMergedConfig(directory, defaults, cliOptions);
98
98
  const { analyzePatterns, generateSummary } = await import("@aiready/pattern-detect");
99
- const { results } = await analyzePatterns(finalOptions);
99
+ const { results, duplicates } = await analyzePatterns(finalOptions);
100
100
  const elapsedTime = getElapsedTime(startTime);
101
101
  const summary = generateSummary(results);
102
102
  const outputFormat = options.output || finalOptions.output?.format || "console";
@@ -113,9 +113,43 @@ program.command("patterns").description("Run pattern detection analysis").argume
113
113
  );
114
114
  handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
115
115
  } else {
116
- console.log(`Pattern Analysis Complete (${elapsedTime}s)`);
117
- console.log(`Found ${summary.totalPatterns} duplicate patterns`);
118
- console.log(`Total token cost: ${summary.totalTokenCost} tokens`);
116
+ const terminalWidth = process.stdout.columns || 80;
117
+ const dividerWidth = Math.min(60, terminalWidth - 2);
118
+ const divider = "\u2501".repeat(dividerWidth);
119
+ console.log(chalk.cyan(divider));
120
+ console.log(chalk.bold.white(" PATTERN ANALYSIS SUMMARY"));
121
+ console.log(chalk.cyan(divider) + "\n");
122
+ console.log(chalk.white(`\u{1F4C1} Files analyzed: ${chalk.bold(results.length)}`));
123
+ console.log(chalk.yellow(`\u26A0 Duplicate patterns found: ${chalk.bold(summary.totalPatterns)}`));
124
+ console.log(chalk.red(`\u{1F4B0} Token cost (wasted): ${chalk.bold(summary.totalTokenCost.toLocaleString())}`));
125
+ console.log(chalk.gray(`\u23F1 Analysis time: ${chalk.bold(elapsedTime + "s")}`));
126
+ const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
127
+ if (sortedTypes.length > 0) {
128
+ console.log(chalk.cyan("\n" + divider));
129
+ console.log(chalk.bold.white(" PATTERNS BY TYPE"));
130
+ console.log(chalk.cyan(divider) + "\n");
131
+ sortedTypes.forEach(([type, count]) => {
132
+ console.log(` ${chalk.white(type.padEnd(15))} ${chalk.bold(count)}`);
133
+ });
134
+ }
135
+ if (summary.totalPatterns > 0 && duplicates.length > 0) {
136
+ console.log(chalk.cyan("\n" + divider));
137
+ console.log(chalk.bold.white(" TOP DUPLICATE PATTERNS"));
138
+ console.log(chalk.cyan(divider) + "\n");
139
+ const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
140
+ topDuplicates.forEach((dup) => {
141
+ const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
142
+ const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
143
+ const file1Name = dup.file1.split("/").pop() || dup.file1;
144
+ const file2Name = dup.file2.split("/").pop() || dup.file2;
145
+ console.log(`${severityIcon} ${severity}: ${chalk.bold(file1Name)} \u2194 ${chalk.bold(file2Name)}`);
146
+ console.log(` Similarity: ${chalk.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${chalk.bold(dup.tokenCost.toLocaleString())} tokens each`);
147
+ console.log(` Lines: ${chalk.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${chalk.cyan(dup.line2 + "-" + dup.endLine2)}
148
+ `);
149
+ });
150
+ } else {
151
+ console.log(chalk.green("\n\u2728 Great! No duplicate patterns detected.\n"));
152
+ }
119
153
  }
120
154
  } catch (error) {
121
155
  handleCLIError(error, "Pattern analysis");
@@ -170,11 +204,77 @@ program.command("context").description("Run context window cost analysis").argum
170
204
  );
171
205
  handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
172
206
  } else {
173
- console.log(`Context Analysis Complete (${elapsedTime}s)`);
174
- console.log(`Files analyzed: ${summary.totalFiles}`);
175
- console.log(`Issues found: ${results.length}`);
176
- console.log(`Average cohesion: ${(summary.avgCohesion * 100).toFixed(1)}%`);
177
- console.log(`Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(1)}%`);
207
+ const terminalWidth = process.stdout.columns || 80;
208
+ const dividerWidth = Math.min(60, terminalWidth - 2);
209
+ const divider = "\u2501".repeat(dividerWidth);
210
+ console.log(chalk.cyan(divider));
211
+ console.log(chalk.bold.white(" CONTEXT ANALYSIS SUMMARY"));
212
+ console.log(chalk.cyan(divider) + "\n");
213
+ console.log(chalk.white(`\u{1F4C1} Files analyzed: ${chalk.bold(summary.totalFiles)}`));
214
+ console.log(chalk.white(`\u{1F4CA} Total tokens: ${chalk.bold(summary.totalTokens.toLocaleString())}`));
215
+ console.log(chalk.yellow(`\u{1F4B0} Avg context budget: ${chalk.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
216
+ console.log(chalk.white(`\u23F1 Analysis time: ${chalk.bold(elapsedTime + "s")}
217
+ `));
218
+ const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
219
+ if (totalIssues > 0) {
220
+ console.log(chalk.bold("\u26A0\uFE0F Issues Found:\n"));
221
+ if (summary.criticalIssues > 0) {
222
+ console.log(chalk.red(` \u{1F534} Critical: ${chalk.bold(summary.criticalIssues)}`));
223
+ }
224
+ if (summary.majorIssues > 0) {
225
+ console.log(chalk.yellow(` \u{1F7E1} Major: ${chalk.bold(summary.majorIssues)}`));
226
+ }
227
+ if (summary.minorIssues > 0) {
228
+ console.log(chalk.blue(` \u{1F535} Minor: ${chalk.bold(summary.minorIssues)}`));
229
+ }
230
+ console.log(chalk.green(`
231
+ \u{1F4A1} Potential savings: ${chalk.bold(summary.totalPotentialSavings.toLocaleString())} tokens
232
+ `));
233
+ } else {
234
+ console.log(chalk.green("\u2705 No significant issues found!\n"));
235
+ }
236
+ if (summary.deepFiles.length > 0) {
237
+ console.log(chalk.bold("\u{1F4CF} Deep Import Chains:\n"));
238
+ console.log(chalk.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
239
+ console.log(chalk.gray(` Maximum depth: ${summary.maxImportDepth}
240
+ `));
241
+ summary.deepFiles.slice(0, 10).forEach((item) => {
242
+ const fileName = item.file.split("/").slice(-2).join("/");
243
+ console.log(` ${chalk.cyan("\u2192")} ${chalk.white(fileName)} ${chalk.dim(`(depth: ${item.depth})`)}`);
244
+ });
245
+ console.log();
246
+ }
247
+ if (summary.fragmentedModules.length > 0) {
248
+ console.log(chalk.bold("\u{1F9E9} Fragmented Modules:\n"));
249
+ console.log(chalk.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
250
+ `));
251
+ summary.fragmentedModules.slice(0, 10).forEach((module) => {
252
+ console.log(` ${chalk.yellow("\u25CF")} ${chalk.white(module.domain)} - ${chalk.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
253
+ console.log(chalk.dim(` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`));
254
+ });
255
+ console.log();
256
+ }
257
+ if (summary.lowCohesionFiles.length > 0) {
258
+ console.log(chalk.bold("\u{1F500} Low Cohesion Files:\n"));
259
+ console.log(chalk.gray(` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
260
+ `));
261
+ summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
262
+ const fileName = item.file.split("/").slice(-2).join("/");
263
+ const scorePercent = (item.score * 100).toFixed(0);
264
+ const color = item.score < 0.4 ? chalk.red : chalk.yellow;
265
+ console.log(` ${color("\u25CB")} ${chalk.white(fileName)} ${chalk.dim(`(${scorePercent}% cohesion)`)}`);
266
+ });
267
+ console.log();
268
+ }
269
+ if (summary.topExpensiveFiles.length > 0) {
270
+ console.log(chalk.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
271
+ summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
272
+ const fileName = item.file.split("/").slice(-2).join("/");
273
+ const severityColor = item.severity === "critical" ? chalk.red : item.severity === "major" ? chalk.yellow : chalk.blue;
274
+ console.log(` ${severityColor("\u25CF")} ${chalk.white(fileName)} ${chalk.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`);
275
+ });
276
+ console.log();
277
+ }
178
278
  }
179
279
  } catch (error) {
180
280
  handleCLIError(error, "Context analysis");
@@ -231,16 +331,76 @@ program.command("consistency").description("Check naming, patterns, and architec
231
331
  writeFileSync(outputPath, markdown);
232
332
  console.log(chalk.green(`\u2705 Report saved to ${outputPath}`));
233
333
  } else {
234
- console.log(`Consistency Analysis Complete (${elapsedTime}s)`);
235
- console.log(`Files analyzed: ${report.summary.filesAnalyzed}`);
236
- console.log(`Total issues: ${report.summary.totalIssues}`);
237
- console.log(` Naming: ${report.summary.namingIssues}`);
238
- console.log(` Patterns: ${report.summary.patternIssues}`);
239
- if (report.recommendations.length > 0) {
240
- console.log(chalk.bold("\n\u{1F4A1} Recommendations:"));
241
- report.recommendations.forEach((rec, i) => {
242
- console.log(`${i + 1}. ${rec}`);
243
- });
334
+ console.log(chalk.bold("\n\u{1F4CA} Summary\n"));
335
+ console.log(`Files Analyzed: ${chalk.cyan(report.summary.filesAnalyzed)}`);
336
+ console.log(`Total Issues: ${chalk.yellow(report.summary.totalIssues)}`);
337
+ console.log(` Naming: ${chalk.yellow(report.summary.namingIssues)}`);
338
+ console.log(` Patterns: ${chalk.yellow(report.summary.patternIssues)}`);
339
+ console.log(` Architecture: ${chalk.yellow(report.summary.architectureIssues || 0)}`);
340
+ console.log(`Analysis Time: ${chalk.gray(elapsedTime + "s")}
341
+ `);
342
+ if (report.summary.totalIssues === 0) {
343
+ console.log(chalk.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
344
+ } else {
345
+ const namingResults = report.results.filter(
346
+ (r) => r.issues.some((i) => i.category === "naming")
347
+ );
348
+ const patternResults = report.results.filter(
349
+ (r) => r.issues.some((i) => i.category === "patterns")
350
+ );
351
+ if (namingResults.length > 0) {
352
+ console.log(chalk.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
353
+ let shown = 0;
354
+ for (const result of namingResults) {
355
+ if (shown >= 5) break;
356
+ for (const issue of result.issues) {
357
+ if (shown >= 5) break;
358
+ const severityColor = issue.severity === "critical" ? chalk.red : issue.severity === "major" ? chalk.yellow : issue.severity === "minor" ? chalk.blue : chalk.gray;
359
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk.dim(`${issue.location.file}:${issue.location.line}`)}`);
360
+ console.log(` ${issue.message}`);
361
+ if (issue.suggestion) {
362
+ console.log(` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`);
363
+ }
364
+ console.log();
365
+ shown++;
366
+ }
367
+ }
368
+ const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
369
+ if (remaining > 0) {
370
+ console.log(chalk.dim(` ... and ${remaining} more issues
371
+ `));
372
+ }
373
+ }
374
+ if (patternResults.length > 0) {
375
+ console.log(chalk.bold("\u{1F504} Pattern Issues\n"));
376
+ let shown = 0;
377
+ for (const result of patternResults) {
378
+ if (shown >= 5) break;
379
+ for (const issue of result.issues) {
380
+ if (shown >= 5) break;
381
+ const severityColor = issue.severity === "critical" ? chalk.red : issue.severity === "major" ? chalk.yellow : issue.severity === "minor" ? chalk.blue : chalk.gray;
382
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk.dim(`${issue.location.file}:${issue.location.line}`)}`);
383
+ console.log(` ${issue.message}`);
384
+ if (issue.suggestion) {
385
+ console.log(` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`);
386
+ }
387
+ console.log();
388
+ shown++;
389
+ }
390
+ }
391
+ const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
392
+ if (remaining > 0) {
393
+ console.log(chalk.dim(` ... and ${remaining} more issues
394
+ `));
395
+ }
396
+ }
397
+ if (report.recommendations.length > 0) {
398
+ console.log(chalk.bold("\u{1F4A1} Recommendations\n"));
399
+ report.recommendations.forEach((rec, i) => {
400
+ console.log(`${i + 1}. ${rec}`);
401
+ });
402
+ console.log();
403
+ }
244
404
  }
245
405
  }
246
406
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/cli",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "description": "Unified CLI for AIReady analysis tools",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -11,10 +11,10 @@
11
11
  "dependencies": {
12
12
  "commander": "^12.1.0",
13
13
  "chalk": "^5.3.0",
14
- "@aiready/core": "0.5.5",
15
- "@aiready/pattern-detect": "0.8.4",
16
- "@aiready/context-analyzer": "0.4.5",
17
- "@aiready/consistency": "0.2.4"
14
+ "@aiready/pattern-detect": "0.9.0",
15
+ "@aiready/core": "0.5.6",
16
+ "@aiready/consistency": "0.3.0",
17
+ "@aiready/context-analyzer": "0.5.0"
18
18
  },
19
19
  "devDependencies": {
20
20
  "tsup": "^8.3.5",
@@ -3,7 +3,11 @@ import { analyzeUnified } from '../index';
3
3
 
4
4
  // Mock the individual tools
5
5
  vi.mock('@aiready/pattern-detect', () => ({
6
- analyzePatterns: vi.fn().mockResolvedValue([]),
6
+ analyzePatterns: vi.fn().mockResolvedValue({
7
+ results: [],
8
+ duplicates: [],
9
+ files: [],
10
+ }),
7
11
  generateSummary: vi.fn().mockReturnValue({
8
12
  totalDuplicateLines: 0,
9
13
  potentialSavings: 0,
package/src/cli.ts CHANGED
@@ -152,7 +152,7 @@ program
152
152
 
153
153
  const { analyzePatterns, generateSummary } = await import('@aiready/pattern-detect');
154
154
 
155
- const { results } = await analyzePatterns(finalOptions);
155
+ const { results, duplicates } = await analyzePatterns(finalOptions);
156
156
 
157
157
  const elapsedTime = getElapsedTime(startTime);
158
158
  const summary = generateSummary(results);
@@ -174,9 +174,57 @@ program
174
174
 
175
175
  handleJSONOutput(outputData, outputPath, `✅ Results saved to ${outputPath}`);
176
176
  } else {
177
- console.log(`Pattern Analysis Complete (${elapsedTime}s)`);
178
- console.log(`Found ${summary.totalPatterns} duplicate patterns`);
179
- console.log(`Total token cost: ${summary.totalTokenCost} tokens`);
177
+ // Console output - format to match standalone CLI
178
+ const terminalWidth = process.stdout.columns || 80;
179
+ const dividerWidth = Math.min(60, terminalWidth - 2);
180
+ const divider = '━'.repeat(dividerWidth);
181
+
182
+ console.log(chalk.cyan(divider));
183
+ console.log(chalk.bold.white(' PATTERN ANALYSIS SUMMARY'));
184
+ console.log(chalk.cyan(divider) + '\n');
185
+
186
+ console.log(chalk.white(`📁 Files analyzed: ${chalk.bold(results.length)}`));
187
+ console.log(chalk.yellow(`⚠ Duplicate patterns found: ${chalk.bold(summary.totalPatterns)}`));
188
+ console.log(chalk.red(`💰 Token cost (wasted): ${chalk.bold(summary.totalTokenCost.toLocaleString())}`));
189
+ console.log(chalk.gray(`⏱ Analysis time: ${chalk.bold(elapsedTime + 's')}`));
190
+
191
+ // Show breakdown by pattern type
192
+ const sortedTypes = Object.entries(summary.patternsByType || {})
193
+ .filter(([, count]) => count > 0)
194
+ .sort(([, a], [, b]) => (b as number) - (a as number));
195
+
196
+ if (sortedTypes.length > 0) {
197
+ console.log(chalk.cyan('\n' + divider));
198
+ console.log(chalk.bold.white(' PATTERNS BY TYPE'));
199
+ console.log(chalk.cyan(divider) + '\n');
200
+ sortedTypes.forEach(([type, count]) => {
201
+ console.log(` ${chalk.white(type.padEnd(15))} ${chalk.bold(count)}`);
202
+ });
203
+ }
204
+
205
+ // Show top duplicates
206
+ if (summary.totalPatterns > 0 && duplicates.length > 0) {
207
+ console.log(chalk.cyan('\n' + divider));
208
+ console.log(chalk.bold.white(' TOP DUPLICATE PATTERNS'));
209
+ console.log(chalk.cyan(divider) + '\n');
210
+
211
+ // Sort by similarity and take top 10
212
+ const topDuplicates = [...duplicates]
213
+ .sort((a, b) => b.similarity - a.similarity)
214
+ .slice(0, 10);
215
+
216
+ topDuplicates.forEach((dup) => {
217
+ const severity = dup.similarity > 0.95 ? 'CRITICAL' : dup.similarity > 0.9 ? 'HIGH' : 'MEDIUM';
218
+ const severityIcon = dup.similarity > 0.95 ? '🔴' : dup.similarity > 0.9 ? '🟡' : '🔵';
219
+ const file1Name = dup.file1.split('/').pop() || dup.file1;
220
+ const file2Name = dup.file2.split('/').pop() || dup.file2;
221
+ console.log(`${severityIcon} ${severity}: ${chalk.bold(file1Name)} ↔ ${chalk.bold(file2Name)}`);
222
+ console.log(` Similarity: ${chalk.bold(Math.round(dup.similarity * 100) + '%')} | Wasted: ${chalk.bold(dup.tokenCost.toLocaleString())} tokens each`);
223
+ console.log(` Lines: ${chalk.cyan(dup.line1 + '-' + dup.endLine1)} ↔ ${chalk.cyan(dup.line2 + '-' + dup.endLine2)}\n`);
224
+ });
225
+ } else {
226
+ console.log(chalk.green('\n✨ Great! No duplicate patterns detected.\n'));
227
+ }
180
228
  }
181
229
  } catch (error) {
182
230
  handleCLIError(error, 'Pattern analysis');
@@ -258,11 +306,84 @@ program
258
306
 
259
307
  handleJSONOutput(outputData, outputPath, `✅ Results saved to ${outputPath}`);
260
308
  } else {
261
- console.log(`Context Analysis Complete (${elapsedTime}s)`);
262
- console.log(`Files analyzed: ${summary.totalFiles}`);
263
- console.log(`Issues found: ${results.length}`);
264
- console.log(`Average cohesion: ${(summary.avgCohesion * 100).toFixed(1)}%`);
265
- console.log(`Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(1)}%`);
309
+ // Console output - format the results nicely
310
+ const terminalWidth = process.stdout.columns || 80;
311
+ const dividerWidth = Math.min(60, terminalWidth - 2);
312
+ const divider = '━'.repeat(dividerWidth);
313
+
314
+ console.log(chalk.cyan(divider));
315
+ console.log(chalk.bold.white(' CONTEXT ANALYSIS SUMMARY'));
316
+ console.log(chalk.cyan(divider) + '\n');
317
+
318
+ console.log(chalk.white(`📁 Files analyzed: ${chalk.bold(summary.totalFiles)}`));
319
+ console.log(chalk.white(`📊 Total tokens: ${chalk.bold(summary.totalTokens.toLocaleString())}`));
320
+ console.log(chalk.yellow(`💰 Avg context budget: ${chalk.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
321
+ console.log(chalk.white(`⏱ Analysis time: ${chalk.bold(elapsedTime + 's')}\n`));
322
+
323
+ // Issues summary
324
+ const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
325
+ if (totalIssues > 0) {
326
+ console.log(chalk.bold('⚠️ Issues Found:\n'));
327
+ if (summary.criticalIssues > 0) {
328
+ console.log(chalk.red(` 🔴 Critical: ${chalk.bold(summary.criticalIssues)}`));
329
+ }
330
+ if (summary.majorIssues > 0) {
331
+ console.log(chalk.yellow(` 🟡 Major: ${chalk.bold(summary.majorIssues)}`));
332
+ }
333
+ if (summary.minorIssues > 0) {
334
+ console.log(chalk.blue(` 🔵 Minor: ${chalk.bold(summary.minorIssues)}`));
335
+ }
336
+ console.log(chalk.green(`\n 💡 Potential savings: ${chalk.bold(summary.totalPotentialSavings.toLocaleString())} tokens\n`));
337
+ } else {
338
+ console.log(chalk.green('✅ No significant issues found!\n'));
339
+ }
340
+
341
+ // Deep import chains
342
+ if (summary.deepFiles.length > 0) {
343
+ console.log(chalk.bold('📏 Deep Import Chains:\n'));
344
+ console.log(chalk.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
345
+ console.log(chalk.gray(` Maximum depth: ${summary.maxImportDepth}\n`));
346
+ summary.deepFiles.slice(0, 10).forEach((item) => {
347
+ const fileName = item.file.split('/').slice(-2).join('/');
348
+ console.log(` ${chalk.cyan('→')} ${chalk.white(fileName)} ${chalk.dim(`(depth: ${item.depth})`)}`);
349
+ });
350
+ console.log();
351
+ }
352
+
353
+ // Fragmented modules
354
+ if (summary.fragmentedModules.length > 0) {
355
+ console.log(chalk.bold('🧩 Fragmented Modules:\n'));
356
+ console.log(chalk.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%\n`));
357
+ summary.fragmentedModules.slice(0, 10).forEach((module) => {
358
+ console.log(` ${chalk.yellow('●')} ${chalk.white(module.domain)} - ${chalk.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
359
+ console.log(chalk.dim(` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`));
360
+ });
361
+ console.log();
362
+ }
363
+
364
+ // Low cohesion files
365
+ if (summary.lowCohesionFiles.length > 0) {
366
+ console.log(chalk.bold('🔀 Low Cohesion Files:\n'));
367
+ console.log(chalk.gray(` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%\n`));
368
+ summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
369
+ const fileName = item.file.split('/').slice(-2).join('/');
370
+ const scorePercent = (item.score * 100).toFixed(0);
371
+ const color = item.score < 0.4 ? chalk.red : chalk.yellow;
372
+ console.log(` ${color('○')} ${chalk.white(fileName)} ${chalk.dim(`(${scorePercent}% cohesion)`)}`);
373
+ });
374
+ console.log();
375
+ }
376
+
377
+ // Top expensive files
378
+ if (summary.topExpensiveFiles.length > 0) {
379
+ console.log(chalk.bold('💸 Most Expensive Files (Context Budget):\n'));
380
+ summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
381
+ const fileName = item.file.split('/').slice(-2).join('/');
382
+ const severityColor = item.severity === 'critical' ? chalk.red : item.severity === 'major' ? chalk.yellow : chalk.blue;
383
+ console.log(` ${severityColor('●')} ${chalk.white(fileName)} ${chalk.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`);
384
+ });
385
+ console.log();
386
+ }
266
387
  }
267
388
  } catch (error) {
268
389
  handleCLIError(error, 'Context analysis');
@@ -346,17 +467,83 @@ program
346
467
  writeFileSync(outputPath, markdown);
347
468
  console.log(chalk.green(`✅ Report saved to ${outputPath}`));
348
469
  } else {
349
- console.log(`Consistency Analysis Complete (${elapsedTime}s)`);
350
- console.log(`Files analyzed: ${report.summary.filesAnalyzed}`);
351
- console.log(`Total issues: ${report.summary.totalIssues}`);
352
- console.log(` Naming: ${report.summary.namingIssues}`);
353
- console.log(` Patterns: ${report.summary.patternIssues}`);
354
-
355
- if (report.recommendations.length > 0) {
356
- console.log(chalk.bold('\n💡 Recommendations:'));
357
- report.recommendations.forEach((rec, i) => {
358
- console.log(`${i + 1}. ${rec}`);
359
- });
470
+ // Console output - format to match standalone CLI
471
+ console.log(chalk.bold('\n📊 Summary\n'));
472
+ console.log(`Files Analyzed: ${chalk.cyan(report.summary.filesAnalyzed)}`);
473
+ console.log(`Total Issues: ${chalk.yellow(report.summary.totalIssues)}`);
474
+ console.log(` Naming: ${chalk.yellow(report.summary.namingIssues)}`);
475
+ console.log(` Patterns: ${chalk.yellow(report.summary.patternIssues)}`);
476
+ console.log(` Architecture: ${chalk.yellow(report.summary.architectureIssues || 0)}`);
477
+ console.log(`Analysis Time: ${chalk.gray(elapsedTime + 's')}\n`);
478
+
479
+ if (report.summary.totalIssues === 0) {
480
+ console.log(chalk.green('✨ No consistency issues found! Your codebase is well-maintained.\n'));
481
+ } else {
482
+ // Group and display issues by category
483
+ const namingResults = report.results.filter((r: any) =>
484
+ r.issues.some((i: any) => i.category === 'naming')
485
+ );
486
+ const patternResults = report.results.filter((r: any) =>
487
+ r.issues.some((i: any) => i.category === 'patterns')
488
+ );
489
+
490
+ if (namingResults.length > 0) {
491
+ console.log(chalk.bold('🏷️ Naming Issues\n'));
492
+ let shown = 0;
493
+ for (const result of namingResults) {
494
+ if (shown >= 5) break;
495
+ for (const issue of result.issues) {
496
+ if (shown >= 5) break;
497
+ const severityColor = issue.severity === 'critical' ? chalk.red :
498
+ issue.severity === 'major' ? chalk.yellow :
499
+ issue.severity === 'minor' ? chalk.blue : chalk.gray;
500
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk.dim(`${issue.location.file}:${issue.location.line}`)}`);
501
+ console.log(` ${issue.message}`);
502
+ if (issue.suggestion) {
503
+ console.log(` ${chalk.dim('→')} ${chalk.italic(issue.suggestion)}`);
504
+ }
505
+ console.log();
506
+ shown++;
507
+ }
508
+ }
509
+ const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
510
+ if (remaining > 0) {
511
+ console.log(chalk.dim(` ... and ${remaining} more issues\n`));
512
+ }
513
+ }
514
+
515
+ if (patternResults.length > 0) {
516
+ console.log(chalk.bold('🔄 Pattern Issues\n'));
517
+ let shown = 0;
518
+ for (const result of patternResults) {
519
+ if (shown >= 5) break;
520
+ for (const issue of result.issues) {
521
+ if (shown >= 5) break;
522
+ const severityColor = issue.severity === 'critical' ? chalk.red :
523
+ issue.severity === 'major' ? chalk.yellow :
524
+ issue.severity === 'minor' ? chalk.blue : chalk.gray;
525
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk.dim(`${issue.location.file}:${issue.location.line}`)}`);
526
+ console.log(` ${issue.message}`);
527
+ if (issue.suggestion) {
528
+ console.log(` ${chalk.dim('→')} ${chalk.italic(issue.suggestion)}`);
529
+ }
530
+ console.log();
531
+ shown++;
532
+ }
533
+ }
534
+ const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
535
+ if (remaining > 0) {
536
+ console.log(chalk.dim(` ... and ${remaining} more issues\n`));
537
+ }
538
+ }
539
+
540
+ if (report.recommendations.length > 0) {
541
+ console.log(chalk.bold('💡 Recommendations\n'));
542
+ report.recommendations.forEach((rec: string, i: number) => {
543
+ console.log(`${i + 1}. ${rec}`);
544
+ });
545
+ console.log();
546
+ }
360
547
  }
361
548
  }
362
549
  } catch (error) {