@aiready/cli 0.7.19 → 0.7.21

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.js CHANGED
@@ -68,6 +68,9 @@ async function analyzeUnified(options) {
68
68
  };
69
69
  if (tools.includes("patterns")) {
70
70
  const patternResult = await (0, import_pattern_detect.analyzePatterns)(options);
71
+ if (options.progressCallback) {
72
+ options.progressCallback({ tool: "patterns", data: patternResult });
73
+ }
71
74
  result.patterns = sortBySeverity(patternResult.results);
72
75
  result.duplicates = patternResult.duplicates;
73
76
  result.summary.totalIssues += patternResult.results.reduce(
@@ -77,6 +80,9 @@ async function analyzeUnified(options) {
77
80
  }
78
81
  if (tools.includes("context")) {
79
82
  const contextResults = await (0, import_context_analyzer.analyzeContext)(options);
83
+ if (options.progressCallback) {
84
+ options.progressCallback({ tool: "context", data: contextResults });
85
+ }
80
86
  result.context = contextResults.sort((a, b) => {
81
87
  const severityDiff = (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
82
88
  if (severityDiff !== 0) return severityDiff;
@@ -86,14 +92,16 @@ async function analyzeUnified(options) {
86
92
  result.summary.totalIssues += result.context?.length || 0;
87
93
  }
88
94
  if (tools.includes("consistency")) {
89
- const report = await (0, import_consistency.analyzeConsistency)({
95
+ const consistencyOptions = {
90
96
  rootDir: options.rootDir,
91
97
  include: options.include,
92
98
  exclude: options.exclude,
93
- checkNaming: true,
94
- checkPatterns: true,
95
- minSeverity: "info"
96
- });
99
+ ...options.consistency || {}
100
+ };
101
+ const report = await (0, import_consistency.analyzeConsistency)(consistencyOptions);
102
+ if (options.progressCallback) {
103
+ options.progressCallback({ tool: "consistency", data: report });
104
+ }
97
105
  if (report.results) {
98
106
  report.results = sortBySeverity(report.results);
99
107
  }
@@ -103,34 +111,6 @@ async function analyzeUnified(options) {
103
111
  result.summary.executionTime = Date.now() - startTime;
104
112
  return result;
105
113
  }
106
- function generateUnifiedSummary(result) {
107
- const { summary } = result;
108
- let output = `\u{1F680} AIReady Analysis Complete
109
-
110
- `;
111
- output += `\u{1F4CA} Summary:
112
- `;
113
- output += ` Tools run: ${summary.toolsRun.join(", ")}
114
- `;
115
- output += ` Total issues found: ${summary.totalIssues}
116
- `;
117
- output += ` Execution time: ${(summary.executionTime / 1e3).toFixed(2)}s
118
-
119
- `;
120
- if (result.patterns?.length) {
121
- output += `\u{1F50D} Pattern Analysis: ${result.patterns.length} issues
122
- `;
123
- }
124
- if (result.context?.length) {
125
- output += `\u{1F9E0} Context Analysis: ${result.context.length} issues
126
- `;
127
- }
128
- if (result.consistency) {
129
- output += `\u{1F3F7}\uFE0F Consistency Analysis: ${result.consistency.summary.totalIssues} issues
130
- `;
131
- }
132
- return output;
133
- }
134
114
 
135
115
  // src/cli.ts
136
116
  var import_chalk = __toESM(require("chalk"));
@@ -140,8 +120,49 @@ var import_core = require("@aiready/core");
140
120
  var import_fs2 = require("fs");
141
121
  var packageJson = JSON.parse((0, import_fs2.readFileSync)((0, import_path.join)(__dirname, "../package.json"), "utf8"));
142
122
  var program = new import_commander.Command();
143
- program.name("aiready").description("AIReady - Unified AI-readiness analysis tools").version(packageJson.version).addHelpText("after", "\nCONFIGURATION:\n Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js\n CLI options override config file settings");
144
- program.command("scan").description("Run unified analysis on a codebase").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--weights <weights>", 'Override tool weights for scoring (e.g., "patterns:50,context:30,consistency:20")').option("--threshold <score>", "Minimum passing score for CI/CD (exits with code 1 if below)").action(async (directory, options) => {
123
+ program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
124
+ AI READINESS SCORING:
125
+ Get a 0-100 score indicating how AI-ready your codebase is.
126
+ Use --score flag with any analysis command for detailed breakdown.
127
+
128
+ EXAMPLES:
129
+ $ aiready scan # Quick analysis of current directory
130
+ $ aiready scan --score # Get AI Readiness Score (0-100)
131
+ $ aiready scan --tools patterns # Run only pattern detection
132
+ $ aiready patterns --similarity 0.6 # Custom similarity threshold
133
+ $ aiready scan --output json --output-file results.json
134
+
135
+ GETTING STARTED:
136
+ 1. Run 'aiready scan' to analyze your codebase
137
+ 2. Use 'aiready scan --score' for AI readiness assessment
138
+ 3. Create aiready.json for persistent configuration
139
+ 4. Set up CI/CD with '--threshold' for quality gates
140
+
141
+ CONFIGURATION:
142
+ Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
143
+ CLI options override config file settings
144
+
145
+ Example aiready.json:
146
+ {
147
+ "scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
148
+ "tools": {
149
+ "pattern-detect": { "minSimilarity": 0.5 },
150
+ "context-analyzer": { "maxContextBudget": 15000 }
151
+ },
152
+ "output": { "format": "json", "directory": ".aiready" }
153
+ }
154
+
155
+ VERSION: ${packageJson.version}
156
+ DOCUMENTATION: https://aiready.dev/docs/cli
157
+ GITHUB: https://github.com/caopengau/aiready-cli
158
+ LANDING: https://github.com/caopengau/aiready-landing`);
159
+ program.command("scan").description("Run comprehensive AI-readiness analysis (patterns + context + consistency)").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100) with breakdown").option("--weights <weights>", "Custom scoring weights (patterns:40,context:35,consistency:25)").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").addHelpText("after", `
160
+ EXAMPLES:
161
+ $ aiready scan # Analyze all tools
162
+ $ aiready scan --tools patterns,context # Skip consistency
163
+ $ aiready scan --score --threshold 75 # CI/CD with threshold
164
+ $ aiready scan --output json --output-file report.json
165
+ `).action(async (directory, options) => {
145
166
  console.log(import_chalk.default.blue("\u{1F680} Starting AIReady unified analysis...\n"));
146
167
  const startTime = Date.now();
147
168
  try {
@@ -163,103 +184,210 @@ program.command("scan").description("Run unified analysis on a codebase").argume
163
184
  if (baseOptions.tools.includes("patterns")) {
164
185
  const { getSmartDefaults } = await import("@aiready/pattern-detect");
165
186
  const patternSmartDefaults = await getSmartDefaults(directory, baseOptions);
166
- finalOptions = { ...patternSmartDefaults, ...finalOptions };
187
+ finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
167
188
  }
168
- const results = await analyzeUnified(finalOptions);
189
+ console.log(import_chalk.default.cyan("\n=== AIReady Run Preview ==="));
190
+ console.log(import_chalk.default.white("Tools to run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
191
+ console.log(import_chalk.default.white("Will use settings from config and defaults."));
192
+ const truncate = (arr, cap = 8) => {
193
+ if (!Array.isArray(arr)) return "";
194
+ const shown = arr.slice(0, cap).map((v) => String(v));
195
+ const more = arr.length - shown.length;
196
+ return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
197
+ };
198
+ console.log(import_chalk.default.white("\nGeneral settings:"));
199
+ if (finalOptions.rootDir) console.log(` rootDir: ${import_chalk.default.bold(String(finalOptions.rootDir))}`);
200
+ if (finalOptions.include) console.log(` include: ${import_chalk.default.bold(truncate(finalOptions.include, 6))}`);
201
+ if (finalOptions.exclude) console.log(` exclude: ${import_chalk.default.bold(truncate(finalOptions.exclude, 6))}`);
202
+ if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
203
+ const pd = finalOptions["pattern-detect"] || {
204
+ minSimilarity: finalOptions.minSimilarity,
205
+ minLines: finalOptions.minLines,
206
+ approx: finalOptions.approx,
207
+ minSharedTokens: finalOptions.minSharedTokens,
208
+ maxCandidatesPerBlock: finalOptions.maxCandidatesPerBlock,
209
+ batchSize: finalOptions.batchSize,
210
+ streamResults: finalOptions.streamResults,
211
+ severity: finalOptions.severity,
212
+ includeTests: finalOptions.includeTests
213
+ };
214
+ console.log(import_chalk.default.white("\nPattern-detect settings:"));
215
+ console.log(` minSimilarity: ${import_chalk.default.bold(pd.minSimilarity ?? "default")}`);
216
+ console.log(` minLines: ${import_chalk.default.bold(pd.minLines ?? "default")}`);
217
+ if (pd.approx !== void 0) console.log(` approx: ${import_chalk.default.bold(String(pd.approx))}`);
218
+ if (pd.minSharedTokens !== void 0) console.log(` minSharedTokens: ${import_chalk.default.bold(String(pd.minSharedTokens))}`);
219
+ if (pd.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${import_chalk.default.bold(String(pd.maxCandidatesPerBlock))}`);
220
+ if (pd.batchSize !== void 0) console.log(` batchSize: ${import_chalk.default.bold(String(pd.batchSize))}`);
221
+ if (pd.streamResults !== void 0) console.log(` streamResults: ${import_chalk.default.bold(String(pd.streamResults))}`);
222
+ if (pd.severity !== void 0) console.log(` severity: ${import_chalk.default.bold(String(pd.severity))}`);
223
+ if (pd.includeTests !== void 0) console.log(` includeTests: ${import_chalk.default.bold(String(pd.includeTests))}`);
224
+ }
225
+ if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
226
+ const ca = finalOptions["context-analyzer"] || {
227
+ maxDepth: finalOptions.maxDepth,
228
+ maxContextBudget: finalOptions.maxContextBudget,
229
+ minCohesion: finalOptions.minCohesion,
230
+ maxFragmentation: finalOptions.maxFragmentation,
231
+ includeNodeModules: finalOptions.includeNodeModules
232
+ };
233
+ console.log(import_chalk.default.white("\nContext-analyzer settings:"));
234
+ console.log(` maxDepth: ${import_chalk.default.bold(ca.maxDepth ?? "default")}`);
235
+ console.log(` maxContextBudget: ${import_chalk.default.bold(ca.maxContextBudget ?? "default")}`);
236
+ if (ca.minCohesion !== void 0) console.log(` minCohesion: ${import_chalk.default.bold(String(ca.minCohesion))}`);
237
+ if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${import_chalk.default.bold(String(ca.maxFragmentation))}`);
238
+ if (ca.includeNodeModules !== void 0) console.log(` includeNodeModules: ${import_chalk.default.bold(String(ca.includeNodeModules))}`);
239
+ }
240
+ if (finalOptions.consistency) {
241
+ const c = finalOptions.consistency;
242
+ console.log(import_chalk.default.white("\nConsistency settings:"));
243
+ console.log(` checkNaming: ${import_chalk.default.bold(String(c.checkNaming ?? true))}`);
244
+ console.log(` checkPatterns: ${import_chalk.default.bold(String(c.checkPatterns ?? true))}`);
245
+ console.log(` checkArchitecture: ${import_chalk.default.bold(String(c.checkArchitecture ?? false))}`);
246
+ if (c.minSeverity) console.log(` minSeverity: ${import_chalk.default.bold(c.minSeverity)}`);
247
+ if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${import_chalk.default.bold(truncate(c.acceptedAbbreviations, 8))}`);
248
+ if (c.shortWords) console.log(` shortWords: ${import_chalk.default.bold(truncate(c.shortWords, 8))}`);
249
+ }
250
+ console.log(import_chalk.default.white("\nStarting analysis..."));
251
+ const progressCallback = (event) => {
252
+ console.log(import_chalk.default.cyan(`
253
+ --- ${event.tool.toUpperCase()} RESULTS ---`));
254
+ try {
255
+ if (event.tool === "patterns") {
256
+ const pr = event.data;
257
+ console.log(` Duplicate patterns: ${import_chalk.default.bold(String(pr.duplicates?.length || 0))}`);
258
+ console.log(` Files with pattern issues: ${import_chalk.default.bold(String(pr.results?.length || 0))}`);
259
+ if (pr.duplicates && pr.duplicates.length > 0) {
260
+ pr.duplicates.slice(0, 5).forEach((d, i) => {
261
+ console.log(` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`);
262
+ });
263
+ }
264
+ if (pr.results && pr.results.length > 0) {
265
+ console.log(` Top files with pattern issues:`);
266
+ const sortedByIssues = [...pr.results].sort((a, b) => (b.issues?.length || 0) - (a.issues?.length || 0));
267
+ sortedByIssues.slice(0, 5).forEach((r, i) => {
268
+ console.log(` ${i + 1}. ${r.fileName.split("/").pop()} - ${r.issues.length} issue(s)`);
269
+ });
270
+ }
271
+ if (pr.groups && pr.groups.length >= 0) {
272
+ console.log(` \u2705 Grouped ${import_chalk.default.bold(String(pr.duplicates?.length || 0))} duplicates into ${import_chalk.default.bold(String(pr.groups.length))} file pairs`);
273
+ }
274
+ if (pr.clusters && pr.clusters.length >= 0) {
275
+ console.log(` \u2705 Created ${import_chalk.default.bold(String(pr.clusters.length))} refactor clusters`);
276
+ pr.clusters.slice(0, 3).forEach((cl, idx) => {
277
+ const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
278
+ console.log(` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`);
279
+ });
280
+ }
281
+ } else if (event.tool === "context") {
282
+ const cr = event.data;
283
+ console.log(` Context issues found: ${import_chalk.default.bold(String(cr.length || 0))}`);
284
+ cr.slice(0, 5).forEach((c, i) => {
285
+ const msg = c.message ? ` - ${c.message}` : "";
286
+ console.log(` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`);
287
+ });
288
+ } else if (event.tool === "consistency") {
289
+ const rep = event.data;
290
+ console.log(` Consistency totalIssues: ${import_chalk.default.bold(String(rep.summary?.totalIssues || 0))}`);
291
+ if (rep.results && rep.results.length > 0) {
292
+ const fileMap = /* @__PURE__ */ new Map();
293
+ rep.results.forEach((r) => {
294
+ (r.issues || []).forEach((issue) => {
295
+ const file = issue.location?.file || r.file || "unknown";
296
+ if (!fileMap.has(file)) fileMap.set(file, []);
297
+ fileMap.get(file).push(issue);
298
+ });
299
+ });
300
+ const files = Array.from(fileMap.entries()).sort((a, b) => b[1].length - a[1].length);
301
+ const topFiles = files.slice(0, 10);
302
+ topFiles.forEach(([file, issues], idx) => {
303
+ const counts = issues.reduce((acc, it) => {
304
+ const s = (it.severity || "info").toLowerCase();
305
+ acc[s] = (acc[s] || 0) + 1;
306
+ return acc;
307
+ }, {});
308
+ const sample = issues.find((it) => it.severity === "critical" || it.severity === "major") || issues[0];
309
+ const sampleMsg = sample ? ` \u2014 ${sample.message}` : "";
310
+ console.log(` ${idx + 1}. ${file} \u2014 ${issues.length} issue(s) (critical:${counts.critical || 0} major:${counts.major || 0} minor:${counts.minor || 0} info:${counts.info || 0})${sampleMsg}`);
311
+ });
312
+ const remaining = files.length - topFiles.length;
313
+ if (remaining > 0) {
314
+ console.log(import_chalk.default.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
315
+ }
316
+ }
317
+ }
318
+ } catch (err) {
319
+ }
320
+ };
321
+ const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
322
+ console.log(import_chalk.default.cyan("\n=== AIReady Run Summary ==="));
323
+ console.log(import_chalk.default.white("Tools run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
324
+ console.log(import_chalk.default.cyan("\nResults summary:"));
325
+ console.log(` Total issues (all tools): ${import_chalk.default.bold(String(results.summary.totalIssues || 0))}`);
326
+ if (results.duplicates) console.log(` Duplicate patterns found: ${import_chalk.default.bold(String(results.duplicates.length || 0))}`);
327
+ if (results.patterns) console.log(` Pattern files with issues: ${import_chalk.default.bold(String(results.patterns.length || 0))}`);
328
+ if (results.context) console.log(` Context issues: ${import_chalk.default.bold(String(results.context.length || 0))}`);
329
+ if (results.consistency) console.log(` Consistency issues: ${import_chalk.default.bold(String(results.consistency.summary.totalIssues || 0))}`);
330
+ console.log(import_chalk.default.cyan("===========================\n"));
169
331
  const elapsedTime = (0, import_core.getElapsedTime)(startTime);
170
332
  let scoringResult;
171
333
  if (options.score || finalOptions.scoring?.showBreakdown) {
172
334
  const toolScores = /* @__PURE__ */ new Map();
173
- if (results.patterns && baseOptions.tools.includes("patterns")) {
335
+ if (results.duplicates) {
174
336
  const { calculatePatternScore } = await import("@aiready/pattern-detect");
175
- const duplicates = results.duplicates || [];
176
- const score = calculatePatternScore(duplicates, results.patterns.length);
177
- toolScores.set("pattern-detect", score);
337
+ try {
338
+ const patternScore = calculatePatternScore(results.duplicates, results.patterns?.length || 0);
339
+ toolScores.set("pattern-detect", patternScore);
340
+ } catch (err) {
341
+ }
178
342
  }
179
- if (results.context && baseOptions.tools.includes("context")) {
180
- const { calculateContextScore } = await import("@aiready/context-analyzer");
181
- const summary = {
182
- avgContextBudget: results.context.reduce((sum, r) => sum + r.contextBudget, 0) / results.context.length,
183
- maxContextBudget: Math.max(...results.context.map((r) => r.contextBudget)),
184
- avgImportDepth: results.context.reduce((sum, r) => sum + r.importDepth, 0) / results.context.length,
185
- maxImportDepth: Math.max(...results.context.map((r) => r.importDepth)),
186
- avgFragmentation: results.context.reduce((sum, r) => sum + r.fragmentationScore, 0) / results.context.length,
187
- criticalIssues: results.context.filter((r) => r.severity === "critical").length,
188
- majorIssues: results.context.filter((r) => r.severity === "major").length
189
- };
190
- const score = calculateContextScore(summary);
191
- toolScores.set("context-analyzer", score);
343
+ if (results.context) {
344
+ const { generateSummary: genContextSummary, calculateContextScore } = await import("@aiready/context-analyzer");
345
+ try {
346
+ const ctxSummary = genContextSummary(results.context);
347
+ const contextScore = calculateContextScore(ctxSummary);
348
+ toolScores.set("context-analyzer", contextScore);
349
+ } catch (err) {
350
+ }
192
351
  }
193
- if (results.consistency && baseOptions.tools.includes("consistency")) {
352
+ if (results.consistency) {
194
353
  const { calculateConsistencyScore } = await import("@aiready/consistency");
195
- const issues = results.consistency.results?.flatMap((r) => r.issues) || [];
196
- const score = calculateConsistencyScore(issues, results.consistency.summary.filesAnalyzed);
197
- toolScores.set("consistency", score);
198
- }
199
- const cliWeights = options.weights ? (0, import_core.parseWeightString)(options.weights) : void 0;
200
- scoringResult = (0, import_core.calculateOverallScore)(toolScores, finalOptions, cliWeights);
201
- if (options.threshold) {
202
- const threshold = parseFloat(options.threshold);
203
- if (scoringResult.overall < threshold) {
204
- console.error(import_chalk.default.red(`
205
- \u274C Score ${scoringResult.overall} is below threshold ${threshold}
206
- `));
207
- process.exit(1);
354
+ try {
355
+ const issues = results.consistency.results?.flatMap((r) => r.issues) || [];
356
+ const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
357
+ const consistencyScore = calculateConsistencyScore(issues, totalFiles);
358
+ toolScores.set("consistency", consistencyScore);
359
+ } catch (err) {
208
360
  }
209
361
  }
210
- }
211
- const outputFormat = options.output || finalOptions.output?.format || "console";
212
- const userOutputFile = options.outputFile || finalOptions.output?.file;
213
- if (outputFormat === "json") {
214
- const outputData = {
215
- ...results,
216
- summary: {
217
- ...results.summary,
218
- executionTime: parseFloat(elapsedTime)
219
- },
220
- ...scoringResult && { scoring: scoringResult }
221
- };
222
- const outputPath = (0, import_core.resolveOutputPath)(
223
- userOutputFile,
224
- `aiready-scan-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
225
- directory
226
- );
227
- (0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
228
- } else {
229
- console.log(generateUnifiedSummary(results));
230
- if (scoringResult) {
231
- const terminalWidth = process.stdout.columns || 80;
232
- const dividerWidth = Math.min(60, terminalWidth - 2);
233
- const divider = "\u2501".repeat(dividerWidth);
234
- console.log(import_chalk.default.cyan("\n" + divider));
235
- console.log(import_chalk.default.bold.white(" AI READINESS SCORE"));
236
- console.log(import_chalk.default.cyan(divider) + "\n");
237
- const { emoji, color } = (0, import_core.getRatingDisplay)(scoringResult.rating);
238
- const scoreColor = color === "green" ? import_chalk.default.green : color === "blue" ? import_chalk.default.blue : color === "yellow" ? import_chalk.default.yellow : import_chalk.default.red;
239
- console.log(` ${emoji} Overall Score: ${scoreColor.bold(scoringResult.overall + "/100")} (${import_chalk.default.bold(scoringResult.rating)})`);
240
- console.log(` ${import_chalk.default.dim("Timestamp:")} ${new Date(scoringResult.timestamp).toLocaleString()}
241
- `);
242
- if (scoringResult.breakdown.length > 0) {
243
- console.log(import_chalk.default.bold(" Component Scores:\n"));
362
+ const cliWeights = (0, import_core.parseWeightString)(options.weights);
363
+ if (toolScores.size > 0) {
364
+ scoringResult = (0, import_core.calculateOverallScore)(toolScores, finalOptions, cliWeights.size ? cliWeights : void 0);
365
+ console.log(import_chalk.default.bold("\n\u{1F4CA} AI Readiness Overall Score"));
366
+ console.log(` ${(0, import_core.formatScore)(scoringResult)}`);
367
+ if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
368
+ console.log(import_chalk.default.bold("\nTool breakdown:"));
244
369
  scoringResult.breakdown.forEach((tool) => {
245
- const toolEmoji = tool.toolName === "pattern-detect" ? "\u{1F50D}" : tool.toolName === "context-analyzer" ? "\u{1F9E0}" : "\u{1F3F7}\uFE0F";
246
- const weight = scoringResult.calculation.weights[tool.toolName];
247
- console.log(` ${toolEmoji} ${import_chalk.default.white(tool.toolName.padEnd(20))} ${scoreColor(tool.score + "/100")} ${import_chalk.default.dim(`(weight: ${weight})`)}`);
370
+ const rating = (0, import_core.getRating)(tool.score);
371
+ const rd = (0, import_core.getRatingDisplay)(rating);
372
+ console.log(` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`);
248
373
  });
249
374
  console.log();
375
+ if (finalOptions.scoring?.showBreakdown) {
376
+ console.log(import_chalk.default.bold("Detailed tool breakdown:"));
377
+ scoringResult.breakdown.forEach((tool) => {
378
+ console.log((0, import_core.formatToolScore)(tool));
379
+ });
380
+ console.log();
381
+ }
250
382
  }
251
- console.log(import_chalk.default.dim(` Weighted Formula: ${scoringResult.calculation.formula}`));
252
- console.log(import_chalk.default.dim(` Normalized Score: ${scoringResult.calculation.normalized}
253
- `));
254
- const allRecommendations = scoringResult.breakdown.flatMap((tool) => tool.recommendations || []).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0, 5);
255
- if (allRecommendations.length > 0) {
256
- console.log(import_chalk.default.bold(" Top Recommendations:\n"));
257
- allRecommendations.forEach((rec, i) => {
258
- const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
259
- console.log(` ${i + 1}. ${priorityIcon} ${rec.action}`);
260
- console.log(` ${import_chalk.default.dim(`Impact: +${rec.estimatedImpact} points`)}
261
- `);
262
- });
383
+ const outputFormat = options.output || finalOptions.output?.format || "console";
384
+ const userOutputFile = options.outputFile || finalOptions.output?.file;
385
+ if (outputFormat === "json") {
386
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
387
+ const defaultFilename = `aiready-scan-${dateStr}.json`;
388
+ const outputPath = (0, import_core.resolveOutputPath)(userOutputFile, defaultFilename, directory);
389
+ const outputData = { ...results, scoring: scoringResult };
390
+ (0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Summary saved to ${outputPath}`);
263
391
  }
264
392
  }
265
393
  }
@@ -267,7 +395,12 @@ program.command("scan").description("Run unified analysis on a codebase").argume
267
395
  (0, import_core.handleCLIError)(error, "Analysis");
268
396
  }
269
397
  });
270
- program.command("patterns").description("Run pattern detection analysis").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option("--max-candidates <number>", "Maximum candidates per block (performance tuning)").option("--min-shared-tokens <number>", "Minimum shared tokens for candidates (performance tuning)").option("--full-scan", "Disable smart defaults for comprehensive analysis (slower)").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for patterns (0-100)").action(async (directory, options) => {
398
+ program.command("patterns").description("Detect duplicate code patterns that confuse AI models").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option("--max-candidates <number>", "Maximum candidates per block (performance tuning)").option("--min-shared-tokens <number>", "Minimum shared tokens for candidates (performance tuning)").option("--full-scan", "Disable smart defaults for comprehensive analysis (slower)").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for patterns (0-100)").addHelpText("after", `
399
+ EXAMPLES:
400
+ $ aiready patterns # Default analysis
401
+ $ aiready patterns --similarity 0.6 # Stricter matching
402
+ $ aiready patterns --min-lines 10 # Larger patterns only
403
+ `).action(async (directory, options) => {
271
404
  console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
272
405
  const startTime = Date.now();
273
406
  try {
@@ -371,7 +504,7 @@ program.command("patterns").description("Run pattern detection analysis").argume
371
504
  (0, import_core.handleCLIError)(error, "Pattern analysis");
372
505
  }
373
506
  });
374
- program.command("context").description("Run context window cost analysis").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for context (0-100)").action(async (directory, options) => {
507
+ program.command("context").description("Analyze context window costs and dependency fragmentation").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for context (0-100)").action(async (directory, options) => {
375
508
  console.log(import_chalk.default.blue("\u{1F9E0} Analyzing context costs...\n"));
376
509
  const startTime = Date.now();
377
510
  try {
@@ -508,7 +641,7 @@ program.command("context").description("Run context window cost analysis").argum
508
641
  (0, import_core.handleCLIError)(error, "Context analysis");
509
642
  }
510
643
  });
511
- program.command("consistency").description("Check naming, patterns, and architecture consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score for consistency (0-100)").action(async (directory, options) => {
644
+ program.command("consistency").description("Check naming conventions and architectural consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score for consistency (0-100)").action(async (directory, options) => {
512
645
  console.log(import_chalk.default.blue("\u{1F50D} Analyzing consistency...\n"));
513
646
  const startTime = Date.now();
514
647
  try {