@aiready/cli 0.9.27 → 0.9.29

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.
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Consistency command - Check naming conventions and architectural consistency
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { writeFileSync } from 'fs';
7
+ import { resolve as resolvePath } from 'path';
8
+ import {
9
+ loadMergedConfig,
10
+ handleJSONOutput,
11
+ handleCLIError,
12
+ getElapsedTime,
13
+ resolveOutputPath,
14
+ formatToolScore,
15
+ } from '@aiready/core';
16
+ import type { ToolScoringOutput } from '@aiready/core';
17
+ import { getReportTimestamp, generateMarkdownReport } from '../utils/helpers';
18
+
19
+ interface ConsistencyOptions {
20
+ naming?: boolean;
21
+ patterns?: boolean;
22
+ minSeverity?: string;
23
+ include?: string;
24
+ exclude?: string;
25
+ output?: string;
26
+ outputFile?: string;
27
+ score?: boolean;
28
+ }
29
+
30
+ export async function consistencyAction(directory: string, options: ConsistencyOptions) {
31
+ console.log(chalk.blue('šŸ” Analyzing consistency...\n'));
32
+
33
+ const startTime = Date.now();
34
+ const resolvedDir = resolvePath(process.cwd(), directory || '.');
35
+
36
+ try {
37
+ // Define defaults
38
+ const defaults = {
39
+ checkNaming: true,
40
+ checkPatterns: true,
41
+ minSeverity: 'info' as const,
42
+ include: undefined,
43
+ exclude: undefined,
44
+ output: {
45
+ format: 'console',
46
+ file: undefined,
47
+ },
48
+ };
49
+
50
+ // Load and merge config with CLI options
51
+ const finalOptions = await loadMergedConfig(resolvedDir, defaults, {
52
+ checkNaming: options.naming !== false,
53
+ checkPatterns: options.patterns !== false,
54
+ minSeverity: options.minSeverity,
55
+ include: options.include?.split(','),
56
+ exclude: options.exclude?.split(','),
57
+ });
58
+
59
+ const { analyzeConsistency, calculateConsistencyScore } = await import('@aiready/consistency');
60
+
61
+ const report = await analyzeConsistency(finalOptions);
62
+
63
+ const elapsedTime = getElapsedTime(startTime);
64
+
65
+ // Calculate score if requested
66
+ let consistencyScore: ToolScoringOutput | undefined;
67
+ if (options.score) {
68
+ const issues = report.results?.flatMap((r: any) => r.issues) || [];
69
+ consistencyScore = calculateConsistencyScore(issues, report.summary.filesAnalyzed);
70
+ }
71
+
72
+ const outputFormat = options.output || finalOptions.output?.format || 'console';
73
+ const userOutputFile = options.outputFile || finalOptions.output?.file;
74
+
75
+ if (outputFormat === 'json') {
76
+ const outputData = {
77
+ ...report,
78
+ summary: {
79
+ ...report.summary,
80
+ executionTime: parseFloat(elapsedTime),
81
+ },
82
+ ...(consistencyScore && { scoring: consistencyScore }),
83
+ };
84
+
85
+ const outputPath = resolveOutputPath(
86
+ userOutputFile,
87
+ `aiready-report-${getReportTimestamp()}.json`,
88
+ resolvedDir
89
+ );
90
+
91
+ handleJSONOutput(outputData, outputPath, `āœ… Results saved to ${outputPath}`);
92
+ } else if (outputFormat === 'markdown') {
93
+ // Markdown output
94
+ const markdown = generateMarkdownReport(report, elapsedTime);
95
+ const outputPath = resolveOutputPath(
96
+ userOutputFile,
97
+ `aiready-report-${getReportTimestamp()}.md`,
98
+ resolvedDir
99
+ );
100
+ writeFileSync(outputPath, markdown);
101
+ console.log(chalk.green(`āœ… Report saved to ${outputPath}`));
102
+ } else {
103
+ // Console output - format to match standalone CLI
104
+ console.log(chalk.bold('\nšŸ“Š Summary\n'));
105
+ console.log(`Files Analyzed: ${chalk.cyan(report.summary.filesAnalyzed)}`);
106
+ console.log(`Total Issues: ${chalk.yellow(report.summary.totalIssues)}`);
107
+ console.log(` Naming: ${chalk.yellow(report.summary.namingIssues)}`);
108
+ console.log(` Patterns: ${chalk.yellow(report.summary.patternIssues)}`);
109
+ console.log(` Architecture: ${chalk.yellow(report.summary.architectureIssues || 0)}`);
110
+ console.log(`Analysis Time: ${chalk.gray(elapsedTime + 's')}\n`);
111
+
112
+ if (report.summary.totalIssues === 0) {
113
+ console.log(chalk.green('✨ No consistency issues found! Your codebase is well-maintained.\n'));
114
+ } else {
115
+ // Group and display issues by category
116
+ const namingResults = report.results.filter((r: any) =>
117
+ r.issues.some((i: any) => i.category === 'naming')
118
+ );
119
+ const patternResults = report.results.filter((r: any) =>
120
+ r.issues.some((i: any) => i.category === 'patterns')
121
+ );
122
+
123
+ if (namingResults.length > 0) {
124
+ console.log(chalk.bold('šŸ·ļø Naming Issues\n'));
125
+ let shown = 0;
126
+ for (const result of namingResults) {
127
+ if (shown >= 5) break;
128
+ for (const issue of result.issues) {
129
+ if (shown >= 5) break;
130
+ const severityColor = issue.severity === 'critical' ? chalk.red :
131
+ issue.severity === 'major' ? chalk.yellow :
132
+ issue.severity === 'minor' ? chalk.blue : chalk.gray;
133
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk.dim(`${issue.location.file}:${issue.location.line}`)}`);
134
+ console.log(` ${issue.message}`);
135
+ if (issue.suggestion) {
136
+ console.log(` ${chalk.dim('→')} ${chalk.italic(issue.suggestion)}`);
137
+ }
138
+ console.log();
139
+ shown++;
140
+ }
141
+ }
142
+ const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
143
+ if (remaining > 0) {
144
+ console.log(chalk.dim(` ... and ${remaining} more issues\n`));
145
+ }
146
+ }
147
+
148
+ if (patternResults.length > 0) {
149
+ console.log(chalk.bold('šŸ”„ Pattern Issues\n'));
150
+ let shown = 0;
151
+ for (const result of patternResults) {
152
+ if (shown >= 5) break;
153
+ for (const issue of result.issues) {
154
+ if (shown >= 5) break;
155
+ const severityColor = issue.severity === 'critical' ? chalk.red :
156
+ issue.severity === 'major' ? chalk.yellow :
157
+ issue.severity === 'minor' ? chalk.blue : chalk.gray;
158
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk.dim(`${issue.location.file}:${issue.location.line}`)}`);
159
+ console.log(` ${issue.message}`);
160
+ if (issue.suggestion) {
161
+ console.log(` ${chalk.dim('→')} ${chalk.italic(issue.suggestion)}`);
162
+ }
163
+ console.log();
164
+ shown++;
165
+ }
166
+ }
167
+ const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
168
+ if (remaining > 0) {
169
+ console.log(chalk.dim(` ... and ${remaining} more issues\n`));
170
+ }
171
+ }
172
+
173
+ if (report.recommendations.length > 0) {
174
+ console.log(chalk.bold('šŸ’” Recommendations\n'));
175
+ report.recommendations.forEach((rec: string, i: number) => {
176
+ console.log(`${i + 1}. ${rec}`);
177
+ });
178
+ console.log();
179
+ }
180
+ }
181
+
182
+ // Display score if calculated
183
+ if (consistencyScore) {
184
+ console.log(chalk.bold('\nšŸ“Š AI Readiness Score (Consistency)\n'));
185
+ console.log(formatToolScore(consistencyScore));
186
+ console.log();
187
+ }
188
+ }
189
+ } catch (error) {
190
+ handleCLIError(error, 'Consistency analysis');
191
+ }
192
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Context command - Analyze context window costs and dependency fragmentation
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { resolve as resolvePath } from 'path';
7
+ import {
8
+ loadMergedConfig,
9
+ handleJSONOutput,
10
+ handleCLIError,
11
+ getElapsedTime,
12
+ resolveOutputPath,
13
+ formatToolScore,
14
+ } from '@aiready/core';
15
+ import type { ToolScoringOutput } from '@aiready/core';
16
+ import { getReportTimestamp } from '../utils/helpers';
17
+
18
+ interface ContextOptions {
19
+ maxDepth?: string;
20
+ maxContext?: string;
21
+ include?: string;
22
+ exclude?: string;
23
+ output?: string;
24
+ outputFile?: string;
25
+ score?: boolean;
26
+ }
27
+
28
+ export async function contextAction(directory: string, options: ContextOptions) {
29
+ console.log(chalk.blue('🧠 Analyzing context costs...\n'));
30
+
31
+ const startTime = Date.now();
32
+ const resolvedDir = resolvePath(process.cwd(), directory || '.');
33
+
34
+ try {
35
+ // Define defaults
36
+ const defaults = {
37
+ maxDepth: 5,
38
+ maxContextBudget: 10000,
39
+ include: undefined,
40
+ exclude: undefined,
41
+ output: {
42
+ format: 'console',
43
+ file: undefined,
44
+ },
45
+ };
46
+
47
+ // Load and merge config with CLI options
48
+ let baseOptions = await loadMergedConfig(resolvedDir, defaults, {
49
+ maxDepth: options.maxDepth ? parseInt(options.maxDepth) : undefined,
50
+ maxContextBudget: options.maxContext ? parseInt(options.maxContext) : undefined,
51
+ include: options.include?.split(','),
52
+ exclude: options.exclude?.split(','),
53
+ });
54
+
55
+ // Apply smart defaults for context analysis (always for individual context command)
56
+ let finalOptions: any = { ...baseOptions };
57
+ const { getSmartDefaults } = await import('@aiready/context-analyzer');
58
+ const contextSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
59
+ finalOptions = { ...contextSmartDefaults, ...finalOptions };
60
+
61
+ // Display configuration
62
+ console.log('šŸ“‹ Configuration:');
63
+ console.log(` Max depth: ${finalOptions.maxDepth}`);
64
+ console.log(` Max context budget: ${finalOptions.maxContextBudget}`);
65
+ console.log(` Min cohesion: ${(finalOptions.minCohesion * 100).toFixed(1)}%`);
66
+ console.log(` Max fragmentation: ${(finalOptions.maxFragmentation * 100).toFixed(1)}%`);
67
+ console.log(` Analysis focus: ${finalOptions.focus}`);
68
+ console.log('');
69
+
70
+ const { analyzeContext, generateSummary, calculateContextScore } = await import('@aiready/context-analyzer');
71
+
72
+ const results = await analyzeContext(finalOptions);
73
+
74
+ const elapsedTime = getElapsedTime(startTime);
75
+ const summary = generateSummary(results);
76
+
77
+ // Calculate score if requested
78
+ let contextScore: ToolScoringOutput | undefined;
79
+ if (options.score) {
80
+ contextScore = calculateContextScore(summary as any);
81
+ }
82
+
83
+ const outputFormat = options.output || finalOptions.output?.format || 'console';
84
+ const userOutputFile = options.outputFile || finalOptions.output?.file;
85
+
86
+ if (outputFormat === 'json') {
87
+ const outputData = {
88
+ results,
89
+ summary: { ...summary, executionTime: parseFloat(elapsedTime) },
90
+ ...(contextScore && { scoring: contextScore }),
91
+ };
92
+
93
+ const outputPath = resolveOutputPath(
94
+ userOutputFile,
95
+ `aiready-report-${getReportTimestamp()}.json`,
96
+ resolvedDir
97
+ );
98
+
99
+ handleJSONOutput(outputData, outputPath, `āœ… Results saved to ${outputPath}`);
100
+ } else {
101
+ // Console output - format the results nicely
102
+ const terminalWidth = process.stdout.columns || 80;
103
+ const dividerWidth = Math.min(60, terminalWidth - 2);
104
+ const divider = '━'.repeat(dividerWidth);
105
+
106
+ console.log(chalk.cyan(divider));
107
+ console.log(chalk.bold.white(' CONTEXT ANALYSIS SUMMARY'));
108
+ console.log(chalk.cyan(divider) + '\n');
109
+
110
+ console.log(chalk.white(`šŸ“ Files analyzed: ${chalk.bold(summary.totalFiles)}`));
111
+ console.log(chalk.white(`šŸ“Š Total tokens: ${chalk.bold(summary.totalTokens.toLocaleString())}`));
112
+ console.log(chalk.yellow(`šŸ’° Avg context budget: ${chalk.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
113
+ console.log(chalk.white(`ā± Analysis time: ${chalk.bold(elapsedTime + 's')}\n`));
114
+
115
+ // Issues summary
116
+ const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
117
+ if (totalIssues > 0) {
118
+ console.log(chalk.bold('āš ļø Issues Found:\n'));
119
+ if (summary.criticalIssues > 0) {
120
+ console.log(chalk.red(` šŸ”“ Critical: ${chalk.bold(summary.criticalIssues)}`));
121
+ }
122
+ if (summary.majorIssues > 0) {
123
+ console.log(chalk.yellow(` 🟔 Major: ${chalk.bold(summary.majorIssues)}`));
124
+ }
125
+ if (summary.minorIssues > 0) {
126
+ console.log(chalk.blue(` šŸ”µ Minor: ${chalk.bold(summary.minorIssues)}`));
127
+ }
128
+ console.log(chalk.green(`\n šŸ’” Potential savings: ${chalk.bold(summary.totalPotentialSavings.toLocaleString())} tokens\n`));
129
+ } else {
130
+ console.log(chalk.green('āœ… No significant issues found!\n'));
131
+ }
132
+
133
+ // Deep import chains
134
+ if (summary.deepFiles.length > 0) {
135
+ console.log(chalk.bold('šŸ“ Deep Import Chains:\n'));
136
+ console.log(chalk.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
137
+ console.log(chalk.gray(` Maximum depth: ${summary.maxImportDepth}\n`));
138
+ summary.deepFiles.slice(0, 10).forEach((item) => {
139
+ const fileName = item.file.split('/').slice(-2).join('/');
140
+ console.log(` ${chalk.cyan('→')} ${chalk.white(fileName)} ${chalk.dim(`(depth: ${item.depth})`)}`);
141
+ });
142
+ console.log();
143
+ }
144
+
145
+ // Fragmented modules
146
+ if (summary.fragmentedModules.length > 0) {
147
+ console.log(chalk.bold('🧩 Fragmented Modules:\n'));
148
+ console.log(chalk.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%\n`));
149
+ summary.fragmentedModules.slice(0, 10).forEach((module) => {
150
+ console.log(` ${chalk.yellow('ā—')} ${chalk.white(module.domain)} - ${chalk.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
151
+ console.log(chalk.dim(` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`));
152
+ });
153
+ console.log();
154
+ }
155
+
156
+ // Low cohesion files
157
+ if (summary.lowCohesionFiles.length > 0) {
158
+ console.log(chalk.bold('šŸ”€ Low Cohesion Files:\n'));
159
+ console.log(chalk.gray(` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%\n`));
160
+ summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
161
+ const fileName = item.file.split('/').slice(-2).join('/');
162
+ const scorePercent = (item.score * 100).toFixed(0);
163
+ const color = item.score < 0.4 ? chalk.red : chalk.yellow;
164
+ console.log(` ${color('ā—‹')} ${chalk.white(fileName)} ${chalk.dim(`(${scorePercent}% cohesion)`)}`);
165
+ });
166
+ console.log();
167
+ }
168
+
169
+ // Top expensive files
170
+ if (summary.topExpensiveFiles.length > 0) {
171
+ console.log(chalk.bold('šŸ’ø Most Expensive Files (Context Budget):\n'));
172
+ summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
173
+ const fileName = item.file.split('/').slice(-2).join('/');
174
+ const severityColor = item.severity === 'critical' ? chalk.red : item.severity === 'major' ? chalk.yellow : chalk.blue;
175
+ console.log(` ${severityColor('ā—')} ${chalk.white(fileName)} ${chalk.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`);
176
+ });
177
+ console.log();
178
+ }
179
+
180
+ // Display score if calculated
181
+ if (contextScore) {
182
+ console.log(chalk.cyan(divider));
183
+ console.log(chalk.bold.white(' AI READINESS SCORE (Context)'));
184
+ console.log(chalk.cyan(divider) + '\n');
185
+ console.log(formatToolScore(contextScore));
186
+ console.log();
187
+ }
188
+ }
189
+ } catch (error) {
190
+ handleCLIError(error, 'Context analysis');
191
+ }
192
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Command exports for CLI
3
+ */
4
+
5
+ export { scanAction, scanHelpText } from './scan';
6
+ export { patternsAction, patternsHelpText } from './patterns';
7
+ export { contextAction } from './context';
8
+ export { consistencyAction } from './consistency';
9
+ export { visualizeAction, visualizeHelpText, visualiseHelpText } from './visualize';
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Patterns command - Detect duplicate code patterns that confuse AI models
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { resolve as resolvePath } from 'path';
7
+ import {
8
+ loadMergedConfig,
9
+ handleJSONOutput,
10
+ handleCLIError,
11
+ getElapsedTime,
12
+ resolveOutputPath,
13
+ formatToolScore,
14
+ } from '@aiready/core';
15
+ import type { ToolScoringOutput } from '@aiready/core';
16
+ import { getReportTimestamp } from '../utils/helpers';
17
+
18
+ interface PatternsOptions {
19
+ similarity?: string;
20
+ minLines?: string;
21
+ maxCandidates?: string;
22
+ minSharedTokens?: string;
23
+ fullScan?: boolean;
24
+ include?: string;
25
+ exclude?: string;
26
+ output?: string;
27
+ outputFile?: string;
28
+ score?: boolean;
29
+ }
30
+
31
+ export async function patternsAction(directory: string, options: PatternsOptions) {
32
+ console.log(chalk.blue('šŸ” Analyzing patterns...\n'));
33
+
34
+ const startTime = Date.now();
35
+ const resolvedDir = resolvePath(process.cwd(), directory || '.');
36
+
37
+ try {
38
+ // Determine if smart defaults should be used
39
+ const useSmartDefaults = !options.fullScan;
40
+
41
+ // Define defaults (only for options not handled by smart defaults)
42
+ const defaults = {
43
+ useSmartDefaults,
44
+ include: undefined,
45
+ exclude: undefined,
46
+ output: {
47
+ format: 'console',
48
+ file: undefined,
49
+ },
50
+ };
51
+
52
+ // Set fallback defaults only if smart defaults are disabled
53
+ if (!useSmartDefaults) {
54
+ (defaults as any).minSimilarity = 0.4;
55
+ (defaults as any).minLines = 5;
56
+ }
57
+
58
+ // Load and merge config with CLI options
59
+ const cliOptions: any = {
60
+ minSimilarity: options.similarity ? parseFloat(options.similarity) : undefined,
61
+ minLines: options.minLines ? parseInt(options.minLines) : undefined,
62
+ useSmartDefaults,
63
+ include: options.include?.split(','),
64
+ exclude: options.exclude?.split(','),
65
+ };
66
+
67
+ // Only include performance tuning options if explicitly specified
68
+ if (options.maxCandidates) {
69
+ cliOptions.maxCandidatesPerBlock = parseInt(options.maxCandidates);
70
+ }
71
+ if (options.minSharedTokens) {
72
+ cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
73
+ }
74
+
75
+ const finalOptions = await loadMergedConfig(resolvedDir, defaults, cliOptions);
76
+
77
+ const { analyzePatterns, generateSummary, calculatePatternScore } = await import('@aiready/pattern-detect');
78
+
79
+ const { results, duplicates } = await analyzePatterns(finalOptions);
80
+
81
+ const elapsedTime = getElapsedTime(startTime);
82
+ const summary = generateSummary(results);
83
+
84
+ // Calculate score if requested
85
+ let patternScore: ToolScoringOutput | undefined;
86
+ if (options.score) {
87
+ patternScore = calculatePatternScore(duplicates, results.length);
88
+ }
89
+
90
+ const outputFormat = options.output || finalOptions.output?.format || 'console';
91
+ const userOutputFile = options.outputFile || finalOptions.output?.file;
92
+
93
+ if (outputFormat === 'json') {
94
+ const outputData = {
95
+ results,
96
+ summary: { ...summary, executionTime: parseFloat(elapsedTime) },
97
+ ...(patternScore && { scoring: patternScore }),
98
+ };
99
+
100
+ const outputPath = resolveOutputPath(
101
+ userOutputFile,
102
+ `aiready-report-${getReportTimestamp()}.json`,
103
+ resolvedDir
104
+ );
105
+
106
+ handleJSONOutput(outputData, outputPath, `āœ… Results saved to ${outputPath}`);
107
+ } else {
108
+ // Console output - format to match standalone CLI
109
+ const terminalWidth = process.stdout.columns || 80;
110
+ const dividerWidth = Math.min(60, terminalWidth - 2);
111
+ const divider = '━'.repeat(dividerWidth);
112
+
113
+ console.log(chalk.cyan(divider));
114
+ console.log(chalk.bold.white(' PATTERN ANALYSIS SUMMARY'));
115
+ console.log(chalk.cyan(divider) + '\n');
116
+
117
+ console.log(chalk.white(`šŸ“ Files analyzed: ${chalk.bold(results.length)}`));
118
+ console.log(chalk.yellow(`⚠ Duplicate patterns found: ${chalk.bold(summary.totalPatterns)}`));
119
+ console.log(chalk.red(`šŸ’° Token cost (wasted): ${chalk.bold(summary.totalTokenCost.toLocaleString())}`));
120
+ console.log(chalk.gray(`ā± Analysis time: ${chalk.bold(elapsedTime + 's')}`));
121
+
122
+ // Show breakdown by pattern type
123
+ const sortedTypes = Object.entries(summary.patternsByType || {})
124
+ .filter(([, count]) => count > 0)
125
+ .sort(([, a], [, b]) => (b as number) - (a as number));
126
+
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
+
136
+ // Show top duplicates
137
+ if (summary.totalPatterns > 0 && duplicates.length > 0) {
138
+ console.log(chalk.cyan('\n' + divider));
139
+ console.log(chalk.bold.white(' TOP DUPLICATE PATTERNS'));
140
+ console.log(chalk.cyan(divider) + '\n');
141
+
142
+ // Sort by similarity and take top 10
143
+ const topDuplicates = [...duplicates]
144
+ .sort((a, b) => b.similarity - a.similarity)
145
+ .slice(0, 10);
146
+
147
+ topDuplicates.forEach((dup) => {
148
+ const severity = dup.similarity > 0.95 ? 'CRITICAL' : dup.similarity > 0.9 ? 'HIGH' : 'MEDIUM';
149
+ const severityIcon = dup.similarity > 0.95 ? 'šŸ”“' : dup.similarity > 0.9 ? '🟔' : 'šŸ”µ';
150
+ const file1Name = dup.file1.split('/').pop() || dup.file1;
151
+ const file2Name = dup.file2.split('/').pop() || dup.file2;
152
+ console.log(`${severityIcon} ${severity}: ${chalk.bold(file1Name)} ↔ ${chalk.bold(file2Name)}`);
153
+ console.log(` Similarity: ${chalk.bold(Math.round(dup.similarity * 100) + '%')} | Wasted: ${chalk.bold(dup.tokenCost.toLocaleString())} tokens each`);
154
+ console.log(` Lines: ${chalk.cyan(dup.line1 + '-' + dup.endLine1)} ↔ ${chalk.cyan(dup.line2 + '-' + dup.endLine2)}\n`);
155
+ });
156
+ } else {
157
+ console.log(chalk.green('\n✨ Great! No duplicate patterns detected.\n'));
158
+ }
159
+
160
+ // Display score if calculated
161
+ if (patternScore) {
162
+ console.log(chalk.cyan(divider));
163
+ console.log(chalk.bold.white(' AI READINESS SCORE (Patterns)'));
164
+ console.log(chalk.cyan(divider) + '\n');
165
+ console.log(formatToolScore(patternScore));
166
+ console.log();
167
+ }
168
+ }
169
+ } catch (error) {
170
+ handleCLIError(error, 'Pattern analysis');
171
+ }
172
+ }
173
+
174
+ export const patternsHelpText = `
175
+ EXAMPLES:
176
+ $ aiready patterns # Default analysis
177
+ $ aiready patterns --similarity 0.6 # Stricter matching
178
+ $ aiready patterns --min-lines 10 # Larger patterns only
179
+ `;