@aiready/cli 0.9.26 → 0.9.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -6,9 +6,12 @@ import {
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
9
- import chalk from "chalk";
10
- import { writeFileSync } from "fs";
9
+ import { readFileSync as readFileSync3 } from "fs";
11
10
  import { join } from "path";
11
+
12
+ // src/commands/scan.ts
13
+ import chalk2 from "chalk";
14
+ import { resolve as resolvePath2 } from "path";
12
15
  import {
13
16
  loadMergedConfig,
14
17
  handleJSONOutput,
@@ -22,56 +25,117 @@ import {
22
25
  getRatingDisplay,
23
26
  parseWeightString
24
27
  } from "@aiready/core";
25
- import { readFileSync, existsSync, copyFileSync } from "fs";
28
+
29
+ // src/utils/helpers.ts
26
30
  import { resolve as resolvePath } from "path";
27
- var packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
28
- var program = new Command();
29
- program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
30
- AI READINESS SCORING:
31
- Get a 0-100 score indicating how AI-ready your codebase is.
32
- Use --score flag with any analysis command for detailed breakdown.
31
+ import { existsSync, readdirSync, statSync, readFileSync } from "fs";
32
+ import chalk from "chalk";
33
+ function getReportTimestamp() {
34
+ const now = /* @__PURE__ */ new Date();
35
+ const pad = (n) => String(n).padStart(2, "0");
36
+ return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
37
+ }
38
+ function findLatestScanReport(dirPath) {
39
+ const aireadyDir = resolvePath(dirPath, ".aiready");
40
+ if (!existsSync(aireadyDir)) {
41
+ return null;
42
+ }
43
+ let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
44
+ if (files.length === 0) {
45
+ files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
46
+ }
47
+ if (files.length === 0) {
48
+ return null;
49
+ }
50
+ const sortedFiles = files.map((f) => ({ name: f, path: resolvePath(aireadyDir, f), mtime: statSync(resolvePath(aireadyDir, f)).mtime })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
51
+ return sortedFiles[0].path;
52
+ }
53
+ function warnIfGraphCapExceeded(report, dirPath) {
54
+ try {
55
+ const { loadConfig } = __require("@aiready/core");
56
+ let graphConfig = { maxNodes: 400, maxEdges: 600 };
57
+ const configPath = resolvePath(dirPath, "aiready.json");
58
+ if (existsSync(configPath)) {
59
+ try {
60
+ const rawConfig = JSON.parse(readFileSync(configPath, "utf8"));
61
+ if (rawConfig.visualizer?.graph) {
62
+ graphConfig = {
63
+ maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
64
+ maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
65
+ };
66
+ }
67
+ } catch (e) {
68
+ }
69
+ }
70
+ const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
71
+ const edgeCount = report.context?.reduce((sum, ctx) => {
72
+ const relCount = ctx.relatedFiles?.length || 0;
73
+ const depCount = ctx.dependencies?.length || 0;
74
+ return sum + relCount + depCount;
75
+ }, 0) || 0;
76
+ if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
77
+ console.log("");
78
+ console.log(chalk.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
79
+ if (nodeCount > graphConfig.maxNodes) {
80
+ console.log(chalk.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
81
+ }
82
+ if (edgeCount > graphConfig.maxEdges) {
83
+ console.log(chalk.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
84
+ }
85
+ console.log(chalk.dim(` To increase limits, add to aiready.json:`));
86
+ console.log(chalk.dim(` {`));
87
+ console.log(chalk.dim(` "visualizer": {`));
88
+ console.log(chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
89
+ console.log(chalk.dim(` }`));
90
+ console.log(chalk.dim(` }`));
91
+ }
92
+ } catch (e) {
93
+ }
94
+ }
95
+ function generateMarkdownReport(report, elapsedTime) {
96
+ let markdown = `# Consistency Analysis Report
33
97
 
34
- EXAMPLES:
35
- $ aiready scan # Quick analysis of current directory
36
- $ aiready scan --score # Get AI Readiness Score (0-100)
37
- $ aiready scan --tools patterns # Run only pattern detection
38
- $ aiready patterns --similarity 0.6 # Custom similarity threshold
39
- $ aiready scan --output json --output-file results.json
98
+ `;
99
+ markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
100
+ `;
101
+ markdown += `**Analysis Time:** ${elapsedTime}s
40
102
 
41
- GETTING STARTED:
42
- 1. Run 'aiready scan' to analyze your codebase
43
- 2. Use 'aiready scan --score' for AI readiness assessment
44
- 3. Create aiready.json for persistent configuration
45
- 4. Set up CI/CD with '--threshold' for quality gates
103
+ `;
104
+ markdown += `## Summary
46
105
 
47
- CONFIGURATION:
48
- Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
49
- CLI options override config file settings
106
+ `;
107
+ markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
108
+ `;
109
+ markdown += `- **Total Issues:** ${report.summary.totalIssues}
110
+ `;
111
+ markdown += ` - Naming: ${report.summary.namingIssues}
112
+ `;
113
+ markdown += ` - Patterns: ${report.summary.patternIssues}
50
114
 
51
- Example aiready.json:
52
- {
53
- "scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
54
- "tools": {
55
- "pattern-detect": { "minSimilarity": 0.5 },
56
- "context-analyzer": { "maxContextBudget": 15000 }
57
- },
58
- "output": { "format": "json", "directory": ".aiready" }
115
+ `;
116
+ if (report.recommendations.length > 0) {
117
+ markdown += `## Recommendations
118
+
119
+ `;
120
+ report.recommendations.forEach((rec, i) => {
121
+ markdown += `${i + 1}. ${rec}
122
+ `;
123
+ });
59
124
  }
125
+ return markdown;
126
+ }
127
+ function truncateArray(arr, cap = 8) {
128
+ if (!Array.isArray(arr)) return "";
129
+ const shown = arr.slice(0, cap).map((v) => String(v));
130
+ const more = arr.length - shown.length;
131
+ return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
132
+ }
60
133
 
61
- VERSION: ${packageJson.version}
62
- DOCUMENTATION: https://aiready.dev/docs/cli
63
- GITHUB: https://github.com/caopengau/aiready-cli
64
- LANDING: https://github.com/caopengau/aiready-landing`);
65
- 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", "json").option("--output-file <path>", "Output file path (for json)").option("--no-score", "Disable calculating AI Readiness Score (enabled by default)").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", `
66
- EXAMPLES:
67
- $ aiready scan # Analyze all tools
68
- $ aiready scan --tools patterns,context # Skip consistency
69
- $ aiready scan --score --threshold 75 # CI/CD with threshold
70
- $ aiready scan --output json --output-file report.json
71
- `).action(async (directory, options) => {
72
- console.log(chalk.blue("\u{1F680} Starting AIReady unified analysis...\n"));
134
+ // src/commands/scan.ts
135
+ async function scanAction(directory, options) {
136
+ console.log(chalk2.blue("\u{1F680} Starting AIReady unified analysis...\n"));
73
137
  const startTime = Date.now();
74
- const resolvedDir = resolvePath(process.cwd(), directory || ".");
138
+ const resolvedDir = resolvePath2(process.cwd(), directory || ".");
75
139
  try {
76
140
  const defaults = {
77
141
  tools: ["patterns", "context", "consistency"],
@@ -93,19 +157,13 @@ EXAMPLES:
93
157
  const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
94
158
  finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
95
159
  }
96
- console.log(chalk.cyan("\n=== AIReady Run Preview ==="));
97
- console.log(chalk.white("Tools to run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
98
- console.log(chalk.white("Will use settings from config and defaults."));
99
- const truncate = (arr, cap = 8) => {
100
- if (!Array.isArray(arr)) return "";
101
- const shown = arr.slice(0, cap).map((v) => String(v));
102
- const more = arr.length - shown.length;
103
- return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
104
- };
105
- console.log(chalk.white("\nGeneral settings:"));
106
- if (finalOptions.rootDir) console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
107
- if (finalOptions.include) console.log(` include: ${chalk.bold(truncate(finalOptions.include, 6))}`);
108
- if (finalOptions.exclude) console.log(` exclude: ${chalk.bold(truncate(finalOptions.exclude, 6))}`);
160
+ console.log(chalk2.cyan("\n=== AIReady Run Preview ==="));
161
+ console.log(chalk2.white("Tools to run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
162
+ console.log(chalk2.white("Will use settings from config and defaults."));
163
+ console.log(chalk2.white("\nGeneral settings:"));
164
+ if (finalOptions.rootDir) console.log(` rootDir: ${chalk2.bold(String(finalOptions.rootDir))}`);
165
+ if (finalOptions.include) console.log(` include: ${chalk2.bold(truncateArray(finalOptions.include, 6))}`);
166
+ if (finalOptions.exclude) console.log(` exclude: ${chalk2.bold(truncateArray(finalOptions.exclude, 6))}`);
109
167
  if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
110
168
  const patternDetectConfig = finalOptions["pattern-detect"] || {
111
169
  minSimilarity: finalOptions.minSimilarity,
@@ -118,16 +176,16 @@ EXAMPLES:
118
176
  severity: finalOptions.severity,
119
177
  includeTests: finalOptions.includeTests
120
178
  };
121
- console.log(chalk.white("\nPattern-detect settings:"));
122
- console.log(` minSimilarity: ${chalk.bold(patternDetectConfig.minSimilarity ?? "default")}`);
123
- console.log(` minLines: ${chalk.bold(patternDetectConfig.minLines ?? "default")}`);
124
- if (patternDetectConfig.approx !== void 0) console.log(` approx: ${chalk.bold(String(patternDetectConfig.approx))}`);
125
- if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${chalk.bold(String(patternDetectConfig.minSharedTokens))}`);
126
- if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${chalk.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`);
127
- if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${chalk.bold(String(patternDetectConfig.batchSize))}`);
128
- if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${chalk.bold(String(patternDetectConfig.streamResults))}`);
129
- if (patternDetectConfig.severity !== void 0) console.log(` severity: ${chalk.bold(String(patternDetectConfig.severity))}`);
130
- if (patternDetectConfig.includeTests !== void 0) console.log(` includeTests: ${chalk.bold(String(patternDetectConfig.includeTests))}`);
179
+ console.log(chalk2.white("\nPattern-detect settings:"));
180
+ console.log(` minSimilarity: ${chalk2.bold(patternDetectConfig.minSimilarity ?? "default")}`);
181
+ console.log(` minLines: ${chalk2.bold(patternDetectConfig.minLines ?? "default")}`);
182
+ if (patternDetectConfig.approx !== void 0) console.log(` approx: ${chalk2.bold(String(patternDetectConfig.approx))}`);
183
+ if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${chalk2.bold(String(patternDetectConfig.minSharedTokens))}`);
184
+ if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${chalk2.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`);
185
+ if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${chalk2.bold(String(patternDetectConfig.batchSize))}`);
186
+ if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${chalk2.bold(String(patternDetectConfig.streamResults))}`);
187
+ if (patternDetectConfig.severity !== void 0) console.log(` severity: ${chalk2.bold(String(patternDetectConfig.severity))}`);
188
+ if (patternDetectConfig.includeTests !== void 0) console.log(` includeTests: ${chalk2.bold(String(patternDetectConfig.includeTests))}`);
131
189
  }
132
190
  if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
133
191
  const ca = finalOptions["context-analyzer"] || {
@@ -137,32 +195,32 @@ EXAMPLES:
137
195
  maxFragmentation: finalOptions.maxFragmentation,
138
196
  includeNodeModules: finalOptions.includeNodeModules
139
197
  };
140
- console.log(chalk.white("\nContext-analyzer settings:"));
141
- console.log(` maxDepth: ${chalk.bold(ca.maxDepth ?? "default")}`);
142
- console.log(` maxContextBudget: ${chalk.bold(ca.maxContextBudget ?? "default")}`);
143
- if (ca.minCohesion !== void 0) console.log(` minCohesion: ${chalk.bold(String(ca.minCohesion))}`);
144
- if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${chalk.bold(String(ca.maxFragmentation))}`);
145
- if (ca.includeNodeModules !== void 0) console.log(` includeNodeModules: ${chalk.bold(String(ca.includeNodeModules))}`);
198
+ console.log(chalk2.white("\nContext-analyzer settings:"));
199
+ console.log(` maxDepth: ${chalk2.bold(ca.maxDepth ?? "default")}`);
200
+ console.log(` maxContextBudget: ${chalk2.bold(ca.maxContextBudget ?? "default")}`);
201
+ if (ca.minCohesion !== void 0) console.log(` minCohesion: ${chalk2.bold(String(ca.minCohesion))}`);
202
+ if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${chalk2.bold(String(ca.maxFragmentation))}`);
203
+ if (ca.includeNodeModules !== void 0) console.log(` includeNodeModules: ${chalk2.bold(String(ca.includeNodeModules))}`);
146
204
  }
147
205
  if (finalOptions.consistency) {
148
206
  const c = finalOptions.consistency;
149
- console.log(chalk.white("\nConsistency settings:"));
150
- console.log(` checkNaming: ${chalk.bold(String(c.checkNaming ?? true))}`);
151
- console.log(` checkPatterns: ${chalk.bold(String(c.checkPatterns ?? true))}`);
152
- console.log(` checkArchitecture: ${chalk.bold(String(c.checkArchitecture ?? false))}`);
153
- if (c.minSeverity) console.log(` minSeverity: ${chalk.bold(c.minSeverity)}`);
154
- if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${chalk.bold(truncate(c.acceptedAbbreviations, 8))}`);
155
- if (c.shortWords) console.log(` shortWords: ${chalk.bold(truncate(c.shortWords, 8))}`);
207
+ console.log(chalk2.white("\nConsistency settings:"));
208
+ console.log(` checkNaming: ${chalk2.bold(String(c.checkNaming ?? true))}`);
209
+ console.log(` checkPatterns: ${chalk2.bold(String(c.checkPatterns ?? true))}`);
210
+ console.log(` checkArchitecture: ${chalk2.bold(String(c.checkArchitecture ?? false))}`);
211
+ if (c.minSeverity) console.log(` minSeverity: ${chalk2.bold(c.minSeverity)}`);
212
+ if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${chalk2.bold(truncateArray(c.acceptedAbbreviations, 8))}`);
213
+ if (c.shortWords) console.log(` shortWords: ${chalk2.bold(truncateArray(c.shortWords, 8))}`);
156
214
  }
157
- console.log(chalk.white("\nStarting analysis..."));
215
+ console.log(chalk2.white("\nStarting analysis..."));
158
216
  const progressCallback = (event) => {
159
- console.log(chalk.cyan(`
217
+ console.log(chalk2.cyan(`
160
218
  --- ${event.tool.toUpperCase()} RESULTS ---`));
161
219
  try {
162
220
  if (event.tool === "patterns") {
163
221
  const pr = event.data;
164
- console.log(` Duplicate patterns: ${chalk.bold(String(pr.duplicates?.length || 0))}`);
165
- console.log(` Files with pattern issues: ${chalk.bold(String(pr.results?.length || 0))}`);
222
+ console.log(` Duplicate patterns: ${chalk2.bold(String(pr.duplicates?.length || 0))}`);
223
+ console.log(` Files with pattern issues: ${chalk2.bold(String(pr.results?.length || 0))}`);
166
224
  if (pr.duplicates && pr.duplicates.length > 0) {
167
225
  pr.duplicates.slice(0, 5).forEach((d, i) => {
168
226
  console.log(` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`);
@@ -176,10 +234,10 @@ EXAMPLES:
176
234
  });
177
235
  }
178
236
  if (pr.groups && pr.groups.length >= 0) {
179
- console.log(` \u2705 Grouped ${chalk.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk.bold(String(pr.groups.length))} file pairs`);
237
+ console.log(` \u2705 Grouped ${chalk2.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk2.bold(String(pr.groups.length))} file pairs`);
180
238
  }
181
239
  if (pr.clusters && pr.clusters.length >= 0) {
182
- console.log(` \u2705 Created ${chalk.bold(String(pr.clusters.length))} refactor clusters`);
240
+ console.log(` \u2705 Created ${chalk2.bold(String(pr.clusters.length))} refactor clusters`);
183
241
  pr.clusters.slice(0, 3).forEach((cl, idx) => {
184
242
  const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
185
243
  console.log(` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`);
@@ -187,14 +245,14 @@ EXAMPLES:
187
245
  }
188
246
  } else if (event.tool === "context") {
189
247
  const cr = event.data;
190
- console.log(` Context issues found: ${chalk.bold(String(cr.length || 0))}`);
248
+ console.log(` Context issues found: ${chalk2.bold(String(cr.length || 0))}`);
191
249
  cr.slice(0, 5).forEach((c, i) => {
192
250
  const msg = c.message ? ` - ${c.message}` : "";
193
251
  console.log(` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`);
194
252
  });
195
253
  } else if (event.tool === "consistency") {
196
254
  const rep = event.data;
197
- console.log(` Consistency totalIssues: ${chalk.bold(String(rep.summary?.totalIssues || 0))}`);
255
+ console.log(` Consistency totalIssues: ${chalk2.bold(String(rep.summary?.totalIssues || 0))}`);
198
256
  if (rep.results && rep.results.length > 0) {
199
257
  const fileMap = /* @__PURE__ */ new Map();
200
258
  rep.results.forEach((r) => {
@@ -218,7 +276,7 @@ EXAMPLES:
218
276
  });
219
277
  const remaining = files.length - topFiles.length;
220
278
  if (remaining > 0) {
221
- console.log(chalk.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
279
+ console.log(chalk2.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
222
280
  }
223
281
  }
224
282
  }
@@ -226,15 +284,15 @@ EXAMPLES:
226
284
  }
227
285
  };
228
286
  const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
229
- console.log(chalk.cyan("\n=== AIReady Run Summary ==="));
230
- console.log(chalk.white("Tools run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
231
- console.log(chalk.cyan("\nResults summary:"));
232
- console.log(` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`);
233
- if (results.duplicates) console.log(` Duplicate patterns found: ${chalk.bold(String(results.duplicates.length || 0))}`);
234
- if (results.patterns) console.log(` Pattern files with issues: ${chalk.bold(String(results.patterns.length || 0))}`);
235
- if (results.context) console.log(` Context issues: ${chalk.bold(String(results.context.length || 0))}`);
236
- if (results.consistency) console.log(` Consistency issues: ${chalk.bold(String(results.consistency.summary.totalIssues || 0))}`);
237
- console.log(chalk.cyan("===========================\n"));
287
+ console.log(chalk2.cyan("\n=== AIReady Run Summary ==="));
288
+ console.log(chalk2.white("Tools run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
289
+ console.log(chalk2.cyan("\nResults summary:"));
290
+ console.log(` Total issues (all tools): ${chalk2.bold(String(results.summary.totalIssues || 0))}`);
291
+ if (results.duplicates) console.log(` Duplicate patterns found: ${chalk2.bold(String(results.duplicates.length || 0))}`);
292
+ if (results.patterns) console.log(` Pattern files with issues: ${chalk2.bold(String(results.patterns.length || 0))}`);
293
+ if (results.context) console.log(` Context issues: ${chalk2.bold(String(results.context.length || 0))}`);
294
+ if (results.consistency) console.log(` Consistency issues: ${chalk2.bold(String(results.consistency.summary.totalIssues || 0))}`);
295
+ console.log(chalk2.cyan("===========================\n"));
238
296
  const elapsedTime = getElapsedTime(startTime);
239
297
  let scoringResult;
240
298
  if (options.score || finalOptions.scoring?.showBreakdown) {
@@ -269,10 +327,10 @@ EXAMPLES:
269
327
  const cliWeights = parseWeightString(options.weights);
270
328
  if (toolScores.size > 0) {
271
329
  scoringResult = calculateOverallScore(toolScores, finalOptions, cliWeights.size ? cliWeights : void 0);
272
- console.log(chalk.bold("\n\u{1F4CA} AI Readiness Overall Score"));
330
+ console.log(chalk2.bold("\n\u{1F4CA} AI Readiness Overall Score"));
273
331
  console.log(` ${formatScore(scoringResult)}`);
274
332
  if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
275
- console.log(chalk.bold("\nTool breakdown:"));
333
+ console.log(chalk2.bold("\nTool breakdown:"));
276
334
  scoringResult.breakdown.forEach((tool) => {
277
335
  const rating = getRating(tool.score);
278
336
  const rd = getRatingDisplay(rating);
@@ -280,7 +338,7 @@ EXAMPLES:
280
338
  });
281
339
  console.log();
282
340
  if (finalOptions.scoring?.showBreakdown) {
283
- console.log(chalk.bold("Detailed tool breakdown:"));
341
+ console.log(chalk2.bold("Detailed tool breakdown:"));
284
342
  scoringResult.breakdown.forEach((tool) => {
285
343
  console.log(formatToolScore(tool));
286
344
  });
@@ -299,19 +357,130 @@ EXAMPLES:
299
357
  handleJSONOutput(outputData, outputPath, `\u2705 Report saved to ${outputPath}`);
300
358
  warnIfGraphCapExceeded(outputData, resolvedDir);
301
359
  }
360
+ const isCI = options.ci || process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
361
+ if (isCI && scoringResult) {
362
+ const threshold = options.threshold ? parseInt(options.threshold) : void 0;
363
+ const failOnLevel = options.failOn || "critical";
364
+ if (process.env.GITHUB_ACTIONS === "true") {
365
+ console.log(`
366
+ ::group::AI Readiness Score`);
367
+ console.log(`score=${scoringResult.overallScore}`);
368
+ if (scoringResult.breakdown) {
369
+ scoringResult.breakdown.forEach((tool) => {
370
+ console.log(`${tool.toolName}=${tool.score}`);
371
+ });
372
+ }
373
+ console.log("::endgroup::");
374
+ if (threshold && scoringResult.overallScore < threshold) {
375
+ console.log(`::error::AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`);
376
+ } else if (threshold) {
377
+ console.log(`::notice::AI Readiness Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`);
378
+ }
379
+ if (results.patterns) {
380
+ const criticalPatterns = results.patterns.flatMap(
381
+ (p) => p.issues.filter((i) => i.severity === "critical")
382
+ );
383
+ criticalPatterns.slice(0, 10).forEach((issue) => {
384
+ console.log(`::warning file=${issue.location?.file || "unknown"},line=${issue.location?.line || 1}::${issue.message}`);
385
+ });
386
+ }
387
+ }
388
+ let shouldFail = false;
389
+ let failReason = "";
390
+ if (threshold && scoringResult.overallScore < threshold) {
391
+ shouldFail = true;
392
+ failReason = `AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`;
393
+ }
394
+ if (failOnLevel !== "none") {
395
+ const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
396
+ const minSeverity = severityLevels[failOnLevel] || 4;
397
+ let criticalCount = 0;
398
+ let majorCount = 0;
399
+ if (results.patterns) {
400
+ results.patterns.forEach((p) => {
401
+ p.issues.forEach((i) => {
402
+ if (i.severity === "critical") criticalCount++;
403
+ if (i.severity === "major") majorCount++;
404
+ });
405
+ });
406
+ }
407
+ if (results.context) {
408
+ results.context.forEach((c) => {
409
+ if (c.severity === "critical") criticalCount++;
410
+ if (c.severity === "major") majorCount++;
411
+ });
412
+ }
413
+ if (results.consistency?.results) {
414
+ results.consistency.results.forEach((r) => {
415
+ r.issues?.forEach((i) => {
416
+ if (i.severity === "critical") criticalCount++;
417
+ if (i.severity === "major") majorCount++;
418
+ });
419
+ });
420
+ }
421
+ if (minSeverity >= 4 && criticalCount > 0) {
422
+ shouldFail = true;
423
+ failReason = `Found ${criticalCount} critical issues`;
424
+ } else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
425
+ shouldFail = true;
426
+ failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
427
+ }
428
+ }
429
+ if (shouldFail) {
430
+ console.log(chalk2.red("\n\u{1F6AB} PR BLOCKED: AI Readiness Check Failed"));
431
+ console.log(chalk2.red(` Reason: ${failReason}`));
432
+ console.log(chalk2.dim("\n Remediation steps:"));
433
+ console.log(chalk2.dim(" 1. Run `aiready scan` locally to see detailed issues"));
434
+ console.log(chalk2.dim(" 2. Fix the critical issues before merging"));
435
+ console.log(chalk2.dim(" 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing"));
436
+ process.exit(1);
437
+ } else {
438
+ console.log(chalk2.green("\n\u2705 PR PASSED: AI Readiness Check"));
439
+ if (threshold) {
440
+ console.log(chalk2.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
441
+ }
442
+ console.log(chalk2.dim("\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"));
443
+ }
444
+ }
302
445
  } catch (error) {
303
446
  handleCLIError(error, "Analysis");
304
447
  }
305
- });
306
- 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", `
448
+ }
449
+ var scanHelpText = `
307
450
  EXAMPLES:
308
- $ aiready patterns # Default analysis
309
- $ aiready patterns --similarity 0.6 # Stricter matching
310
- $ aiready patterns --min-lines 10 # Larger patterns only
311
- `).action(async (directory, options) => {
312
- console.log(chalk.blue("\u{1F50D} Analyzing patterns...\n"));
451
+ $ aiready scan # Analyze all tools
452
+ $ aiready scan --tools patterns,context # Skip consistency
453
+ $ aiready scan --score --threshold 75 # CI/CD with threshold
454
+ $ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
455
+ $ aiready scan --ci --fail-on major # Fail on major+ issues
456
+ $ aiready scan --output json --output-file report.json
457
+
458
+ CI/CD INTEGRATION (Gatekeeper Mode):
459
+ Use --ci for GitHub Actions integration:
460
+ - Outputs GitHub Actions annotations for PR checks
461
+ - Fails with exit code 1 if threshold not met
462
+ - Shows clear "blocked" message with remediation steps
463
+
464
+ Example GitHub Actions workflow:
465
+ - name: AI Readiness Check
466
+ run: aiready scan --ci --threshold 70
467
+ `;
468
+
469
+ // src/commands/patterns.ts
470
+ import chalk3 from "chalk";
471
+ import { resolve as resolvePath3 } from "path";
472
+ import {
473
+ loadMergedConfig as loadMergedConfig2,
474
+ handleJSONOutput as handleJSONOutput2,
475
+ handleCLIError as handleCLIError2,
476
+ getElapsedTime as getElapsedTime2,
477
+ resolveOutputPath as resolveOutputPath2,
478
+ formatToolScore as formatToolScore2
479
+ } from "@aiready/core";
480
+ async function patternsAction(directory, options) {
481
+ console.log(chalk3.blue("\u{1F50D} Analyzing patterns...\n"));
313
482
  const startTime = Date.now();
314
- const resolvedDir = resolvePath(process.cwd(), directory || ".");
483
+ const resolvedDir = resolvePath3(process.cwd(), directory || ".");
315
484
  try {
316
485
  const useSmartDefaults = !options.fullScan;
317
486
  const defaults = {
@@ -340,10 +509,10 @@ EXAMPLES:
340
509
  if (options.minSharedTokens) {
341
510
  cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
342
511
  }
343
- const finalOptions = await loadMergedConfig(resolvedDir, defaults, cliOptions);
512
+ const finalOptions = await loadMergedConfig2(resolvedDir, defaults, cliOptions);
344
513
  const { analyzePatterns, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
345
514
  const { results, duplicates } = await analyzePatterns(finalOptions);
346
- const elapsedTime = getElapsedTime(startTime);
515
+ const elapsedTime = getElapsedTime2(startTime);
347
516
  const summary = generateSummary(results);
348
517
  let patternScore;
349
518
  if (options.score) {
@@ -357,66 +526,84 @@ EXAMPLES:
357
526
  summary: { ...summary, executionTime: parseFloat(elapsedTime) },
358
527
  ...patternScore && { scoring: patternScore }
359
528
  };
360
- const outputPath = resolveOutputPath(
529
+ const outputPath = resolveOutputPath2(
361
530
  userOutputFile,
362
531
  `aiready-report-${getReportTimestamp()}.json`,
363
532
  resolvedDir
364
533
  );
365
- handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
534
+ handleJSONOutput2(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
366
535
  } else {
367
536
  const terminalWidth = process.stdout.columns || 80;
368
537
  const dividerWidth = Math.min(60, terminalWidth - 2);
369
538
  const divider = "\u2501".repeat(dividerWidth);
370
- console.log(chalk.cyan(divider));
371
- console.log(chalk.bold.white(" PATTERN ANALYSIS SUMMARY"));
372
- console.log(chalk.cyan(divider) + "\n");
373
- console.log(chalk.white(`\u{1F4C1} Files analyzed: ${chalk.bold(results.length)}`));
374
- console.log(chalk.yellow(`\u26A0 Duplicate patterns found: ${chalk.bold(summary.totalPatterns)}`));
375
- console.log(chalk.red(`\u{1F4B0} Token cost (wasted): ${chalk.bold(summary.totalTokenCost.toLocaleString())}`));
376
- console.log(chalk.gray(`\u23F1 Analysis time: ${chalk.bold(elapsedTime + "s")}`));
539
+ console.log(chalk3.cyan(divider));
540
+ console.log(chalk3.bold.white(" PATTERN ANALYSIS SUMMARY"));
541
+ console.log(chalk3.cyan(divider) + "\n");
542
+ console.log(chalk3.white(`\u{1F4C1} Files analyzed: ${chalk3.bold(results.length)}`));
543
+ console.log(chalk3.yellow(`\u26A0 Duplicate patterns found: ${chalk3.bold(summary.totalPatterns)}`));
544
+ console.log(chalk3.red(`\u{1F4B0} Token cost (wasted): ${chalk3.bold(summary.totalTokenCost.toLocaleString())}`));
545
+ console.log(chalk3.gray(`\u23F1 Analysis time: ${chalk3.bold(elapsedTime + "s")}`));
377
546
  const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
378
547
  if (sortedTypes.length > 0) {
379
- console.log(chalk.cyan("\n" + divider));
380
- console.log(chalk.bold.white(" PATTERNS BY TYPE"));
381
- console.log(chalk.cyan(divider) + "\n");
548
+ console.log(chalk3.cyan("\n" + divider));
549
+ console.log(chalk3.bold.white(" PATTERNS BY TYPE"));
550
+ console.log(chalk3.cyan(divider) + "\n");
382
551
  sortedTypes.forEach(([type, count]) => {
383
- console.log(` ${chalk.white(type.padEnd(15))} ${chalk.bold(count)}`);
552
+ console.log(` ${chalk3.white(type.padEnd(15))} ${chalk3.bold(count)}`);
384
553
  });
385
554
  }
386
555
  if (summary.totalPatterns > 0 && duplicates.length > 0) {
387
- console.log(chalk.cyan("\n" + divider));
388
- console.log(chalk.bold.white(" TOP DUPLICATE PATTERNS"));
389
- console.log(chalk.cyan(divider) + "\n");
556
+ console.log(chalk3.cyan("\n" + divider));
557
+ console.log(chalk3.bold.white(" TOP DUPLICATE PATTERNS"));
558
+ console.log(chalk3.cyan(divider) + "\n");
390
559
  const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
391
560
  topDuplicates.forEach((dup) => {
392
561
  const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
393
562
  const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
394
563
  const file1Name = dup.file1.split("/").pop() || dup.file1;
395
564
  const file2Name = dup.file2.split("/").pop() || dup.file2;
396
- console.log(`${severityIcon} ${severity}: ${chalk.bold(file1Name)} \u2194 ${chalk.bold(file2Name)}`);
397
- console.log(` Similarity: ${chalk.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${chalk.bold(dup.tokenCost.toLocaleString())} tokens each`);
398
- console.log(` Lines: ${chalk.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${chalk.cyan(dup.line2 + "-" + dup.endLine2)}
565
+ console.log(`${severityIcon} ${severity}: ${chalk3.bold(file1Name)} \u2194 ${chalk3.bold(file2Name)}`);
566
+ console.log(` Similarity: ${chalk3.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${chalk3.bold(dup.tokenCost.toLocaleString())} tokens each`);
567
+ console.log(` Lines: ${chalk3.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${chalk3.cyan(dup.line2 + "-" + dup.endLine2)}
399
568
  `);
400
569
  });
401
570
  } else {
402
- console.log(chalk.green("\n\u2728 Great! No duplicate patterns detected.\n"));
571
+ console.log(chalk3.green("\n\u2728 Great! No duplicate patterns detected.\n"));
403
572
  }
404
573
  if (patternScore) {
405
- console.log(chalk.cyan(divider));
406
- console.log(chalk.bold.white(" AI READINESS SCORE (Patterns)"));
407
- console.log(chalk.cyan(divider) + "\n");
408
- console.log(formatToolScore(patternScore));
574
+ console.log(chalk3.cyan(divider));
575
+ console.log(chalk3.bold.white(" AI READINESS SCORE (Patterns)"));
576
+ console.log(chalk3.cyan(divider) + "\n");
577
+ console.log(formatToolScore2(patternScore));
409
578
  console.log();
410
579
  }
411
580
  }
412
581
  } catch (error) {
413
- handleCLIError(error, "Pattern analysis");
582
+ handleCLIError2(error, "Pattern analysis");
414
583
  }
415
- });
416
- 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) => {
417
- console.log(chalk.blue("\u{1F9E0} Analyzing context costs...\n"));
584
+ }
585
+ var patternsHelpText = `
586
+ EXAMPLES:
587
+ $ aiready patterns # Default analysis
588
+ $ aiready patterns --similarity 0.6 # Stricter matching
589
+ $ aiready patterns --min-lines 10 # Larger patterns only
590
+ `;
591
+
592
+ // src/commands/context.ts
593
+ import chalk4 from "chalk";
594
+ import { resolve as resolvePath4 } from "path";
595
+ import {
596
+ loadMergedConfig as loadMergedConfig3,
597
+ handleJSONOutput as handleJSONOutput3,
598
+ handleCLIError as handleCLIError3,
599
+ getElapsedTime as getElapsedTime3,
600
+ resolveOutputPath as resolveOutputPath3,
601
+ formatToolScore as formatToolScore3
602
+ } from "@aiready/core";
603
+ async function contextAction(directory, options) {
604
+ console.log(chalk4.blue("\u{1F9E0} Analyzing context costs...\n"));
418
605
  const startTime = Date.now();
419
- const resolvedDir = resolvePath(process.cwd(), directory || ".");
606
+ const resolvedDir = resolvePath4(process.cwd(), directory || ".");
420
607
  try {
421
608
  const defaults = {
422
609
  maxDepth: 5,
@@ -428,7 +615,7 @@ program.command("context").description("Analyze context window costs and depende
428
615
  file: void 0
429
616
  }
430
617
  };
431
- let baseOptions = await loadMergedConfig(resolvedDir, defaults, {
618
+ let baseOptions = await loadMergedConfig3(resolvedDir, defaults, {
432
619
  maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
433
620
  maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
434
621
  include: options.include?.split(","),
@@ -447,7 +634,7 @@ program.command("context").description("Analyze context window costs and depende
447
634
  console.log("");
448
635
  const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
449
636
  const results = await analyzeContext(finalOptions);
450
- const elapsedTime = getElapsedTime(startTime);
637
+ const elapsedTime = getElapsedTime3(startTime);
451
638
  const summary = generateSummary(results);
452
639
  let contextScore;
453
640
  if (options.score) {
@@ -461,100 +648,113 @@ program.command("context").description("Analyze context window costs and depende
461
648
  summary: { ...summary, executionTime: parseFloat(elapsedTime) },
462
649
  ...contextScore && { scoring: contextScore }
463
650
  };
464
- const outputPath = resolveOutputPath(
651
+ const outputPath = resolveOutputPath3(
465
652
  userOutputFile,
466
653
  `aiready-report-${getReportTimestamp()}.json`,
467
654
  resolvedDir
468
655
  );
469
- handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
656
+ handleJSONOutput3(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
470
657
  } else {
471
658
  const terminalWidth = process.stdout.columns || 80;
472
659
  const dividerWidth = Math.min(60, terminalWidth - 2);
473
660
  const divider = "\u2501".repeat(dividerWidth);
474
- console.log(chalk.cyan(divider));
475
- console.log(chalk.bold.white(" CONTEXT ANALYSIS SUMMARY"));
476
- console.log(chalk.cyan(divider) + "\n");
477
- console.log(chalk.white(`\u{1F4C1} Files analyzed: ${chalk.bold(summary.totalFiles)}`));
478
- console.log(chalk.white(`\u{1F4CA} Total tokens: ${chalk.bold(summary.totalTokens.toLocaleString())}`));
479
- console.log(chalk.yellow(`\u{1F4B0} Avg context budget: ${chalk.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
480
- console.log(chalk.white(`\u23F1 Analysis time: ${chalk.bold(elapsedTime + "s")}
661
+ console.log(chalk4.cyan(divider));
662
+ console.log(chalk4.bold.white(" CONTEXT ANALYSIS SUMMARY"));
663
+ console.log(chalk4.cyan(divider) + "\n");
664
+ console.log(chalk4.white(`\u{1F4C1} Files analyzed: ${chalk4.bold(summary.totalFiles)}`));
665
+ console.log(chalk4.white(`\u{1F4CA} Total tokens: ${chalk4.bold(summary.totalTokens.toLocaleString())}`));
666
+ console.log(chalk4.yellow(`\u{1F4B0} Avg context budget: ${chalk4.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
667
+ console.log(chalk4.white(`\u23F1 Analysis time: ${chalk4.bold(elapsedTime + "s")}
481
668
  `));
482
669
  const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
483
670
  if (totalIssues > 0) {
484
- console.log(chalk.bold("\u26A0\uFE0F Issues Found:\n"));
671
+ console.log(chalk4.bold("\u26A0\uFE0F Issues Found:\n"));
485
672
  if (summary.criticalIssues > 0) {
486
- console.log(chalk.red(` \u{1F534} Critical: ${chalk.bold(summary.criticalIssues)}`));
673
+ console.log(chalk4.red(` \u{1F534} Critical: ${chalk4.bold(summary.criticalIssues)}`));
487
674
  }
488
675
  if (summary.majorIssues > 0) {
489
- console.log(chalk.yellow(` \u{1F7E1} Major: ${chalk.bold(summary.majorIssues)}`));
676
+ console.log(chalk4.yellow(` \u{1F7E1} Major: ${chalk4.bold(summary.majorIssues)}`));
490
677
  }
491
678
  if (summary.minorIssues > 0) {
492
- console.log(chalk.blue(` \u{1F535} Minor: ${chalk.bold(summary.minorIssues)}`));
679
+ console.log(chalk4.blue(` \u{1F535} Minor: ${chalk4.bold(summary.minorIssues)}`));
493
680
  }
494
- console.log(chalk.green(`
495
- \u{1F4A1} Potential savings: ${chalk.bold(summary.totalPotentialSavings.toLocaleString())} tokens
681
+ console.log(chalk4.green(`
682
+ \u{1F4A1} Potential savings: ${chalk4.bold(summary.totalPotentialSavings.toLocaleString())} tokens
496
683
  `));
497
684
  } else {
498
- console.log(chalk.green("\u2705 No significant issues found!\n"));
685
+ console.log(chalk4.green("\u2705 No significant issues found!\n"));
499
686
  }
500
687
  if (summary.deepFiles.length > 0) {
501
- console.log(chalk.bold("\u{1F4CF} Deep Import Chains:\n"));
502
- console.log(chalk.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
503
- console.log(chalk.gray(` Maximum depth: ${summary.maxImportDepth}
688
+ console.log(chalk4.bold("\u{1F4CF} Deep Import Chains:\n"));
689
+ console.log(chalk4.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
690
+ console.log(chalk4.gray(` Maximum depth: ${summary.maxImportDepth}
504
691
  `));
505
692
  summary.deepFiles.slice(0, 10).forEach((item) => {
506
693
  const fileName = item.file.split("/").slice(-2).join("/");
507
- console.log(` ${chalk.cyan("\u2192")} ${chalk.white(fileName)} ${chalk.dim(`(depth: ${item.depth})`)}`);
694
+ console.log(` ${chalk4.cyan("\u2192")} ${chalk4.white(fileName)} ${chalk4.dim(`(depth: ${item.depth})`)}`);
508
695
  });
509
696
  console.log();
510
697
  }
511
698
  if (summary.fragmentedModules.length > 0) {
512
- console.log(chalk.bold("\u{1F9E9} Fragmented Modules:\n"));
513
- console.log(chalk.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
699
+ console.log(chalk4.bold("\u{1F9E9} Fragmented Modules:\n"));
700
+ console.log(chalk4.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
514
701
  `));
515
702
  summary.fragmentedModules.slice(0, 10).forEach((module) => {
516
- console.log(` ${chalk.yellow("\u25CF")} ${chalk.white(module.domain)} - ${chalk.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
517
- console.log(chalk.dim(` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`));
703
+ console.log(` ${chalk4.yellow("\u25CF")} ${chalk4.white(module.domain)} - ${chalk4.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
704
+ console.log(chalk4.dim(` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`));
518
705
  });
519
706
  console.log();
520
707
  }
521
708
  if (summary.lowCohesionFiles.length > 0) {
522
- console.log(chalk.bold("\u{1F500} Low Cohesion Files:\n"));
523
- console.log(chalk.gray(` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
709
+ console.log(chalk4.bold("\u{1F500} Low Cohesion Files:\n"));
710
+ console.log(chalk4.gray(` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
524
711
  `));
525
712
  summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
526
713
  const fileName = item.file.split("/").slice(-2).join("/");
527
714
  const scorePercent = (item.score * 100).toFixed(0);
528
- const color = item.score < 0.4 ? chalk.red : chalk.yellow;
529
- console.log(` ${color("\u25CB")} ${chalk.white(fileName)} ${chalk.dim(`(${scorePercent}% cohesion)`)}`);
715
+ const color = item.score < 0.4 ? chalk4.red : chalk4.yellow;
716
+ console.log(` ${color("\u25CB")} ${chalk4.white(fileName)} ${chalk4.dim(`(${scorePercent}% cohesion)`)}`);
530
717
  });
531
718
  console.log();
532
719
  }
533
720
  if (summary.topExpensiveFiles.length > 0) {
534
- console.log(chalk.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
721
+ console.log(chalk4.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
535
722
  summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
536
723
  const fileName = item.file.split("/").slice(-2).join("/");
537
- const severityColor = item.severity === "critical" ? chalk.red : item.severity === "major" ? chalk.yellow : chalk.blue;
538
- console.log(` ${severityColor("\u25CF")} ${chalk.white(fileName)} ${chalk.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`);
724
+ const severityColor = item.severity === "critical" ? chalk4.red : item.severity === "major" ? chalk4.yellow : chalk4.blue;
725
+ console.log(` ${severityColor("\u25CF")} ${chalk4.white(fileName)} ${chalk4.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`);
539
726
  });
540
727
  console.log();
541
728
  }
542
729
  if (contextScore) {
543
- console.log(chalk.cyan(divider));
544
- console.log(chalk.bold.white(" AI READINESS SCORE (Context)"));
545
- console.log(chalk.cyan(divider) + "\n");
546
- console.log(formatToolScore(contextScore));
730
+ console.log(chalk4.cyan(divider));
731
+ console.log(chalk4.bold.white(" AI READINESS SCORE (Context)"));
732
+ console.log(chalk4.cyan(divider) + "\n");
733
+ console.log(formatToolScore3(contextScore));
547
734
  console.log();
548
735
  }
549
736
  }
550
737
  } catch (error) {
551
- handleCLIError(error, "Context analysis");
738
+ handleCLIError3(error, "Context analysis");
552
739
  }
553
- });
554
- 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) => {
555
- console.log(chalk.blue("\u{1F50D} Analyzing consistency...\n"));
740
+ }
741
+
742
+ // src/commands/consistency.ts
743
+ import chalk5 from "chalk";
744
+ import { writeFileSync } from "fs";
745
+ import { resolve as resolvePath5 } from "path";
746
+ import {
747
+ loadMergedConfig as loadMergedConfig4,
748
+ handleJSONOutput as handleJSONOutput4,
749
+ handleCLIError as handleCLIError4,
750
+ getElapsedTime as getElapsedTime4,
751
+ resolveOutputPath as resolveOutputPath4,
752
+ formatToolScore as formatToolScore4
753
+ } from "@aiready/core";
754
+ async function consistencyAction(directory, options) {
755
+ console.log(chalk5.blue("\u{1F50D} Analyzing consistency...\n"));
556
756
  const startTime = Date.now();
557
- const resolvedDir = resolvePath(process.cwd(), directory || ".");
757
+ const resolvedDir = resolvePath5(process.cwd(), directory || ".");
558
758
  try {
559
759
  const defaults = {
560
760
  checkNaming: true,
@@ -567,7 +767,7 @@ program.command("consistency").description("Check naming conventions and archite
567
767
  file: void 0
568
768
  }
569
769
  };
570
- const finalOptions = await loadMergedConfig(resolvedDir, defaults, {
770
+ const finalOptions = await loadMergedConfig4(resolvedDir, defaults, {
571
771
  checkNaming: options.naming !== false,
572
772
  checkPatterns: options.patterns !== false,
573
773
  minSeverity: options.minSeverity,
@@ -576,7 +776,7 @@ program.command("consistency").description("Check naming conventions and archite
576
776
  });
577
777
  const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
578
778
  const report = await analyzeConsistency(finalOptions);
579
- const elapsedTime = getElapsedTime(startTime);
779
+ const elapsedTime = getElapsedTime4(startTime);
580
780
  let consistencyScore;
581
781
  if (options.score) {
582
782
  const issues = report.results?.flatMap((r) => r.issues) || [];
@@ -593,32 +793,32 @@ program.command("consistency").description("Check naming conventions and archite
593
793
  },
594
794
  ...consistencyScore && { scoring: consistencyScore }
595
795
  };
596
- const outputPath = resolveOutputPath(
796
+ const outputPath = resolveOutputPath4(
597
797
  userOutputFile,
598
798
  `aiready-report-${getReportTimestamp()}.json`,
599
799
  resolvedDir
600
800
  );
601
- handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
801
+ handleJSONOutput4(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
602
802
  } else if (outputFormat === "markdown") {
603
803
  const markdown = generateMarkdownReport(report, elapsedTime);
604
- const outputPath = resolveOutputPath(
804
+ const outputPath = resolveOutputPath4(
605
805
  userOutputFile,
606
806
  `aiready-report-${getReportTimestamp()}.md`,
607
807
  resolvedDir
608
808
  );
609
809
  writeFileSync(outputPath, markdown);
610
- console.log(chalk.green(`\u2705 Report saved to ${outputPath}`));
810
+ console.log(chalk5.green(`\u2705 Report saved to ${outputPath}`));
611
811
  } else {
612
- console.log(chalk.bold("\n\u{1F4CA} Summary\n"));
613
- console.log(`Files Analyzed: ${chalk.cyan(report.summary.filesAnalyzed)}`);
614
- console.log(`Total Issues: ${chalk.yellow(report.summary.totalIssues)}`);
615
- console.log(` Naming: ${chalk.yellow(report.summary.namingIssues)}`);
616
- console.log(` Patterns: ${chalk.yellow(report.summary.patternIssues)}`);
617
- console.log(` Architecture: ${chalk.yellow(report.summary.architectureIssues || 0)}`);
618
- console.log(`Analysis Time: ${chalk.gray(elapsedTime + "s")}
812
+ console.log(chalk5.bold("\n\u{1F4CA} Summary\n"));
813
+ console.log(`Files Analyzed: ${chalk5.cyan(report.summary.filesAnalyzed)}`);
814
+ console.log(`Total Issues: ${chalk5.yellow(report.summary.totalIssues)}`);
815
+ console.log(` Naming: ${chalk5.yellow(report.summary.namingIssues)}`);
816
+ console.log(` Patterns: ${chalk5.yellow(report.summary.patternIssues)}`);
817
+ console.log(` Architecture: ${chalk5.yellow(report.summary.architectureIssues || 0)}`);
818
+ console.log(`Analysis Time: ${chalk5.gray(elapsedTime + "s")}
619
819
  `);
620
820
  if (report.summary.totalIssues === 0) {
621
- console.log(chalk.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
821
+ console.log(chalk5.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
622
822
  } else {
623
823
  const namingResults = report.results.filter(
624
824
  (r) => r.issues.some((i) => i.category === "naming")
@@ -627,17 +827,17 @@ program.command("consistency").description("Check naming conventions and archite
627
827
  (r) => r.issues.some((i) => i.category === "patterns")
628
828
  );
629
829
  if (namingResults.length > 0) {
630
- console.log(chalk.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
830
+ console.log(chalk5.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
631
831
  let shown = 0;
632
832
  for (const result of namingResults) {
633
833
  if (shown >= 5) break;
634
834
  for (const issue of result.issues) {
635
835
  if (shown >= 5) break;
636
- const severityColor = issue.severity === "critical" ? chalk.red : issue.severity === "major" ? chalk.yellow : issue.severity === "minor" ? chalk.blue : chalk.gray;
637
- console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk.dim(`${issue.location.file}:${issue.location.line}`)}`);
836
+ const severityColor = issue.severity === "critical" ? chalk5.red : issue.severity === "major" ? chalk5.yellow : issue.severity === "minor" ? chalk5.blue : chalk5.gray;
837
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk5.dim(`${issue.location.file}:${issue.location.line}`)}`);
638
838
  console.log(` ${issue.message}`);
639
839
  if (issue.suggestion) {
640
- console.log(` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`);
840
+ console.log(` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`);
641
841
  }
642
842
  console.log();
643
843
  shown++;
@@ -645,22 +845,22 @@ program.command("consistency").description("Check naming conventions and archite
645
845
  }
646
846
  const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
647
847
  if (remaining > 0) {
648
- console.log(chalk.dim(` ... and ${remaining} more issues
848
+ console.log(chalk5.dim(` ... and ${remaining} more issues
649
849
  `));
650
850
  }
651
851
  }
652
852
  if (patternResults.length > 0) {
653
- console.log(chalk.bold("\u{1F504} Pattern Issues\n"));
853
+ console.log(chalk5.bold("\u{1F504} Pattern Issues\n"));
654
854
  let shown = 0;
655
855
  for (const result of patternResults) {
656
856
  if (shown >= 5) break;
657
857
  for (const issue of result.issues) {
658
858
  if (shown >= 5) break;
659
- const severityColor = issue.severity === "critical" ? chalk.red : issue.severity === "major" ? chalk.yellow : issue.severity === "minor" ? chalk.blue : chalk.gray;
660
- console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk.dim(`${issue.location.file}:${issue.location.line}`)}`);
859
+ const severityColor = issue.severity === "critical" ? chalk5.red : issue.severity === "major" ? chalk5.yellow : issue.severity === "minor" ? chalk5.blue : chalk5.gray;
860
+ console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk5.dim(`${issue.location.file}:${issue.location.line}`)}`);
661
861
  console.log(` ${issue.message}`);
662
862
  if (issue.suggestion) {
663
- console.log(` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`);
863
+ console.log(` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`);
664
864
  }
665
865
  console.log();
666
866
  shown++;
@@ -668,12 +868,12 @@ program.command("consistency").description("Check naming conventions and archite
668
868
  }
669
869
  const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
670
870
  if (remaining > 0) {
671
- console.log(chalk.dim(` ... and ${remaining} more issues
871
+ console.log(chalk5.dim(` ... and ${remaining} more issues
672
872
  `));
673
873
  }
674
874
  }
675
875
  if (report.recommendations.length > 0) {
676
- console.log(chalk.bold("\u{1F4A1} Recommendations\n"));
876
+ console.log(chalk5.bold("\u{1F4A1} Recommendations\n"));
677
877
  report.recommendations.forEach((rec, i) => {
678
878
  console.log(`${i + 1}. ${rec}`);
679
879
  });
@@ -681,288 +881,35 @@ program.command("consistency").description("Check naming conventions and archite
681
881
  }
682
882
  }
683
883
  if (consistencyScore) {
684
- console.log(chalk.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
685
- console.log(formatToolScore(consistencyScore));
884
+ console.log(chalk5.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
885
+ console.log(formatToolScore4(consistencyScore));
686
886
  console.log();
687
887
  }
688
888
  }
689
889
  } catch (error) {
690
- handleCLIError(error, "Consistency analysis");
890
+ handleCLIError4(error, "Consistency analysis");
691
891
  }
692
- });
693
- function generateMarkdownReport(report, elapsedTime) {
694
- let markdown = `# Consistency Analysis Report
695
-
696
- `;
697
- markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
698
- `;
699
- markdown += `**Analysis Time:** ${elapsedTime}s
700
-
701
- `;
702
- markdown += `## Summary
703
-
704
- `;
705
- markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
706
- `;
707
- markdown += `- **Total Issues:** ${report.summary.totalIssues}
708
- `;
709
- markdown += ` - Naming: ${report.summary.namingIssues}
710
- `;
711
- markdown += ` - Patterns: ${report.summary.patternIssues}
712
-
713
- `;
714
- if (report.recommendations.length > 0) {
715
- markdown += `## Recommendations
716
-
717
- `;
718
- report.recommendations.forEach((rec, i) => {
719
- markdown += `${i + 1}. ${rec}
720
- `;
721
- });
722
- }
723
- return markdown;
724
892
  }
725
- function generateHTML(graph) {
726
- const payload = JSON.stringify(graph, null, 2);
727
- return `<!doctype html>
728
- <html>
729
- <head>
730
- <meta charset="utf-8" />
731
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
732
- <title>AIReady Visualization</title>
733
- <style>
734
- html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
735
- #container { display:flex; height:100vh }
736
- #panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
737
- #canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
738
- canvas { background: #0b1220; border-radius:8px }
739
- .stat { margin-bottom:12px }
740
- </style>
741
- </head>
742
- <body>
743
- <div id="container">
744
- <div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
745
- <div id="panel">
746
- <h2>AIReady Visualization</h2>
747
- <div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
748
- <div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
749
- <div class="stat"><strong>Legend</strong></div>
750
- <div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
751
- <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
752
- <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
753
- <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
754
- <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
755
- <div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
756
- <div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
757
- <div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
758
- </div>
759
- </div>
760
- </div>
761
-
762
- <script>
763
- const graphData = ${payload};
764
- document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
765
- document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
766
893
 
767
- const canvas = document.getElementById('canvas');
768
- const ctx = canvas.getContext('2d');
769
-
770
- const nodes = graphData.nodes.map((n, i) => ({
771
- ...n,
772
- x: canvas.width / 2 + Math.cos(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
773
- y: canvas.height / 2 + Math.sin(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
774
- }));
775
-
776
- function draw() {
777
- ctx.clearRect(0, 0, canvas.width, canvas.height);
778
-
779
- graphData.edges.forEach(edge => {
780
- const s = nodes.find(n => n.id === edge.source);
781
- const t = nodes.find(n => n.id === edge.target);
782
- if (!s || !t) return;
783
- if (edge.type === 'related') return;
784
- if (edge.type === 'similarity') {
785
- ctx.strokeStyle = '#fb7e81';
786
- ctx.lineWidth = 1.2;
787
- } else if (edge.type === 'dependency') {
788
- ctx.strokeStyle = '#84c1ff';
789
- ctx.lineWidth = 1.0;
790
- } else if (edge.type === 'reference') {
791
- ctx.strokeStyle = '#ffa500';
792
- ctx.lineWidth = 0.9;
793
- } else {
794
- ctx.strokeStyle = '#334155';
795
- ctx.lineWidth = 0.8;
796
- }
797
- ctx.beginPath();
798
- ctx.moveTo(s.x, s.y);
799
- ctx.lineTo(t.x, t.y);
800
- ctx.stroke();
801
- });
802
-
803
- const groups = {};
804
- nodes.forEach(n => {
805
- const g = n.group || '__default';
806
- if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
807
- groups[g].minX = Math.min(groups[g].minX, n.x);
808
- groups[g].minY = Math.min(groups[g].minY, n.y);
809
- groups[g].maxX = Math.max(groups[g].maxX, n.x);
810
- groups[g].maxY = Math.max(groups[g].maxY, n.y);
811
- });
812
-
813
- const groupRelations = {};
814
- graphData.edges.forEach(edge => {
815
- const sNode = nodes.find(n => n.id === edge.source);
816
- const tNode = nodes.find(n => n.id === edge.target);
817
- if (!sNode || !tNode) return;
818
- const g1 = sNode.group || '__default';
819
- const g2 = tNode.group || '__default';
820
- if (g1 === g2) return;
821
- const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
822
- groupRelations[key] = (groupRelations[key] || 0) + 1;
823
- });
824
-
825
- Object.keys(groupRelations).forEach(k => {
826
- const count = groupRelations[k];
827
- const [ga, gb] = k.split('::');
828
- if (!groups[ga] || !groups[gb]) return;
829
- const ax = (groups[ga].minX + groups[ga].maxX) / 2;
830
- const ay = (groups[ga].minY + groups[ga].maxY) / 2;
831
- const bx = (groups[gb].minX + groups[gb].maxX) / 2;
832
- const by = (groups[gb].minY + groups[gb].maxY) / 2;
833
- ctx.beginPath();
834
- ctx.strokeStyle = 'rgba(148,163,184,0.25)';
835
- ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
836
- ctx.moveTo(ax, ay);
837
- ctx.lineTo(bx, by);
838
- ctx.stroke();
839
- });
840
-
841
- Object.keys(groups).forEach(g => {
842
- if (g === '__default') return;
843
- const box = groups[g];
844
- const pad = 16;
845
- const x = box.minX - pad;
846
- const y = box.minY - pad;
847
- const w = (box.maxX - box.minX) + pad * 2;
848
- const h = (box.maxY - box.minY) + pad * 2;
849
- ctx.save();
850
- ctx.fillStyle = 'rgba(30,64,175,0.04)';
851
- ctx.strokeStyle = 'rgba(30,64,175,0.12)';
852
- ctx.lineWidth = 1.2;
853
- const r = 8;
854
- ctx.beginPath();
855
- ctx.moveTo(x + r, y);
856
- ctx.arcTo(x + w, y, x + w, y + h, r);
857
- ctx.arcTo(x + w, y + h, x, y + h, r);
858
- ctx.arcTo(x, y + h, x, y, r);
859
- ctx.arcTo(x, y, x + w, y, r);
860
- ctx.closePath();
861
- ctx.fill();
862
- ctx.stroke();
863
- ctx.restore();
864
- ctx.fillStyle = '#94a3b8';
865
- ctx.font = '11px sans-serif';
866
- ctx.fillText(g, x + 8, y + 14);
867
- });
868
-
869
- nodes.forEach(n => {
870
- const sizeVal = (n.size || n.value || 1);
871
- const r = 6 + (sizeVal / 2);
872
- ctx.beginPath();
873
- ctx.fillStyle = n.color || '#60a5fa';
874
- ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
875
- ctx.fill();
876
-
877
- ctx.fillStyle = '#e2e8f0';
878
- ctx.font = '11px sans-serif';
879
- ctx.textAlign = 'center';
880
- ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
881
- });
882
- }
883
-
884
- draw();
885
- </script>
886
- </body>
887
- </html>`;
888
- }
889
- function getReportTimestamp() {
890
- const now = /* @__PURE__ */ new Date();
891
- const pad = (n) => String(n).padStart(2, "0");
892
- return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
893
- }
894
- function findLatestScanReport(dirPath) {
895
- const aireadyDir = resolvePath(dirPath, ".aiready");
896
- if (!existsSync(aireadyDir)) {
897
- return null;
898
- }
899
- const { readdirSync, statSync } = __require("fs");
900
- let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
901
- if (files.length === 0) {
902
- files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
903
- }
904
- if (files.length === 0) {
905
- return null;
906
- }
907
- const sortedFiles = files.map((f) => ({ name: f, path: resolvePath(aireadyDir, f), mtime: statSync(resolvePath(aireadyDir, f)).mtime })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
908
- return sortedFiles[0].path;
909
- }
910
- function warnIfGraphCapExceeded(report, dirPath) {
911
- try {
912
- const { loadConfig } = __require("@aiready/core");
913
- const { existsSync: existsSync2, readFileSync: readFileSync2 } = __require("fs");
914
- const { resolve } = __require("path");
915
- let graphConfig = { maxNodes: 400, maxEdges: 600 };
916
- const configPath = resolve(dirPath, "aiready.json");
917
- if (existsSync2(configPath)) {
918
- try {
919
- const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
920
- if (rawConfig.visualizer?.graph) {
921
- graphConfig = {
922
- maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
923
- maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
924
- };
925
- }
926
- } catch (e) {
927
- }
928
- }
929
- const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
930
- const edgeCount = report.context?.reduce((sum, ctx) => {
931
- const relCount = ctx.relatedFiles?.length || 0;
932
- const depCount = ctx.dependencies?.length || 0;
933
- return sum + relCount + depCount;
934
- }, 0) || 0;
935
- if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
936
- console.log("");
937
- console.log(chalk.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
938
- if (nodeCount > graphConfig.maxNodes) {
939
- console.log(chalk.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
940
- }
941
- if (edgeCount > graphConfig.maxEdges) {
942
- console.log(chalk.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
943
- }
944
- console.log(chalk.dim(` To increase limits, add to aiready.json:`));
945
- console.log(chalk.dim(` {`));
946
- console.log(chalk.dim(` "visualizer": {`));
947
- console.log(chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
948
- console.log(chalk.dim(` }`));
949
- console.log(chalk.dim(` }`));
950
- }
951
- } catch (e) {
952
- }
953
- }
954
- async function handleVisualize(directory, options) {
894
+ // src/commands/visualize.ts
895
+ import chalk6 from "chalk";
896
+ import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, copyFileSync as copyFileSync2 } from "fs";
897
+ import { resolve as resolvePath6 } from "path";
898
+ import { spawn } from "child_process";
899
+ import { handleCLIError as handleCLIError5 } from "@aiready/core";
900
+ import { generateHTML } from "@aiready/core";
901
+ async function visualizeAction(directory, options) {
955
902
  try {
956
- const dirPath = resolvePath(process.cwd(), directory || ".");
957
- let reportPath = options.report ? resolvePath(dirPath, options.report) : null;
958
- if (!reportPath || !existsSync(reportPath)) {
903
+ const dirPath = resolvePath6(process.cwd(), directory || ".");
904
+ let reportPath = options.report ? resolvePath6(dirPath, options.report) : null;
905
+ if (!reportPath || !existsSync2(reportPath)) {
959
906
  const latestScan = findLatestScanReport(dirPath);
960
907
  if (latestScan) {
961
908
  reportPath = latestScan;
962
- console.log(chalk.dim(`Found latest report: ${latestScan.split("/").pop()}`));
909
+ console.log(chalk6.dim(`Found latest report: ${latestScan.split("/").pop()}`));
963
910
  } else {
964
- console.error(chalk.red("\u274C No AI readiness report found"));
965
- console.log(chalk.dim(`
911
+ console.error(chalk6.red("\u274C No AI readiness report found"));
912
+ console.log(chalk6.dim(`
966
913
  Generate a report with:
967
914
  aiready scan --output json
968
915
 
@@ -971,13 +918,13 @@ Or specify a custom report:
971
918
  return;
972
919
  }
973
920
  }
974
- const raw = readFileSync(reportPath, "utf8");
921
+ const raw = readFileSync2(reportPath, "utf8");
975
922
  const report = JSON.parse(raw);
976
- const configPath = resolvePath(dirPath, "aiready.json");
923
+ const configPath = resolvePath6(dirPath, "aiready.json");
977
924
  let graphConfig = { maxNodes: 400, maxEdges: 600 };
978
- if (existsSync(configPath)) {
925
+ if (existsSync2(configPath)) {
979
926
  try {
980
- const rawConfig = JSON.parse(readFileSync(configPath, "utf8"));
927
+ const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
981
928
  if (rawConfig.visualizer?.graph) {
982
929
  graphConfig = {
983
930
  maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
@@ -994,27 +941,26 @@ Or specify a custom report:
994
941
  const graph = GraphBuilder.buildFromReport(report, dirPath);
995
942
  if (options.dev) {
996
943
  try {
997
- const { spawn } = await import("child_process");
998
- const monorepoWebDir = resolvePath(dirPath, "packages/visualizer");
944
+ const monorepoWebDir = resolvePath6(dirPath, "packages/visualizer");
999
945
  let webDir = "";
1000
946
  let visualizerAvailable = false;
1001
- if (existsSync(monorepoWebDir)) {
947
+ if (existsSync2(monorepoWebDir)) {
1002
948
  webDir = monorepoWebDir;
1003
949
  visualizerAvailable = true;
1004
950
  } else {
1005
951
  const nodemodulesLocations = [
1006
- resolvePath(dirPath, "node_modules", "@aiready", "visualizer"),
1007
- resolvePath(process.cwd(), "node_modules", "@aiready", "visualizer")
952
+ resolvePath6(dirPath, "node_modules", "@aiready", "visualizer"),
953
+ resolvePath6(process.cwd(), "node_modules", "@aiready", "visualizer")
1008
954
  ];
1009
955
  let currentDir = dirPath;
1010
956
  while (currentDir !== "/" && currentDir !== ".") {
1011
- nodemodulesLocations.push(resolvePath(currentDir, "node_modules", "@aiready", "visualizer"));
1012
- const parent = resolvePath(currentDir, "..");
957
+ nodemodulesLocations.push(resolvePath6(currentDir, "node_modules", "@aiready", "visualizer"));
958
+ const parent = resolvePath6(currentDir, "..");
1013
959
  if (parent === currentDir) break;
1014
960
  currentDir = parent;
1015
961
  }
1016
962
  for (const location of nodemodulesLocations) {
1017
- if (existsSync(location) && existsSync(resolvePath(location, "package.json"))) {
963
+ if (existsSync2(location) && existsSync2(resolvePath6(location, "package.json"))) {
1018
964
  webDir = location;
1019
965
  visualizerAvailable = true;
1020
966
  break;
@@ -1023,7 +969,7 @@ Or specify a custom report:
1023
969
  if (!visualizerAvailable) {
1024
970
  try {
1025
971
  const vizPkgPath = __require.resolve("@aiready/visualizer/package.json");
1026
- webDir = resolvePath(vizPkgPath, "..");
972
+ webDir = resolvePath6(vizPkgPath, "..");
1027
973
  visualizerAvailable = true;
1028
974
  } catch (e) {
1029
975
  }
@@ -1031,17 +977,17 @@ Or specify a custom report:
1031
977
  }
1032
978
  const spawnCwd = webDir || process.cwd();
1033
979
  const nodeBinCandidate = process.execPath;
1034
- const nodeBin = existsSync(nodeBinCandidate) ? nodeBinCandidate : "node";
980
+ const nodeBin = existsSync2(nodeBinCandidate) ? nodeBinCandidate : "node";
1035
981
  if (!visualizerAvailable) {
1036
- console.error(chalk.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
1037
- console.log(chalk.dim("Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer"));
982
+ console.error(chalk6.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
983
+ console.log(chalk6.dim("Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer"));
1038
984
  return;
1039
985
  }
1040
986
  const { watch } = await import("fs");
1041
987
  const copyReportToViz = () => {
1042
988
  try {
1043
- const destPath = resolvePath(spawnCwd, "web", "report-data.json");
1044
- copyFileSync(reportPath, destPath);
989
+ const destPath = resolvePath6(spawnCwd, "web", "report-data.json");
990
+ copyFileSync2(reportPath, destPath);
1045
991
  console.log(`\u{1F4CB} Report synced to ${destPath}`);
1046
992
  } catch (e) {
1047
993
  console.error("Failed to sync report:", e);
@@ -1079,20 +1025,18 @@ Or specify a custom report:
1079
1025
  }
1080
1026
  console.log("Generating HTML...");
1081
1027
  const html = generateHTML(graph);
1082
- const outPath = resolvePath(dirPath, options.output || "packages/visualizer/visualization.html");
1083
- writeFileSync(outPath, html, "utf8");
1028
+ const outPath = resolvePath6(dirPath, options.output || "packages/visualizer/visualization.html");
1029
+ writeFileSync2(outPath, html, "utf8");
1084
1030
  console.log("Visualization written to:", outPath);
1085
1031
  if (options.open) {
1086
- const { exec } = await import("child_process");
1087
1032
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1088
- exec(`${opener} "${outPath}"`);
1033
+ spawn(opener, [`"${outPath}"`], { shell: true });
1089
1034
  }
1090
1035
  if (options.serve) {
1091
1036
  try {
1092
- const port = Number(options.serve) || 5173;
1037
+ const port = typeof options.serve === "number" ? options.serve : 5173;
1093
1038
  const http = await import("http");
1094
1039
  const fsp = await import("fs/promises");
1095
- const { exec } = await import("child_process");
1096
1040
  const server = http.createServer(async (req, res) => {
1097
1041
  try {
1098
1042
  const urlPath = req.url || "/";
@@ -1113,7 +1057,7 @@ Or specify a custom report:
1113
1057
  const addr = `http://localhost:${port}/`;
1114
1058
  console.log(`Local visualization server running at ${addr}`);
1115
1059
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1116
- exec(`${opener} "${addr}"`);
1060
+ spawn(opener, [`"${addr}"`], { shell: true });
1117
1061
  });
1118
1062
  process.on("SIGINT", () => {
1119
1063
  server.close();
@@ -1124,20 +1068,10 @@ Or specify a custom report:
1124
1068
  }
1125
1069
  }
1126
1070
  } catch (err) {
1127
- handleCLIError(err, "Visualization");
1071
+ handleCLIError5(err, "Visualization");
1128
1072
  }
1129
1073
  }
1130
- program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", true).addHelpText("after", `
1131
- EXAMPLES:
1132
- $ aiready visualise . # Auto-detects latest report
1133
- $ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
1134
- $ aiready visualise . --report report.json --dev
1135
- $ aiready visualise . --report report.json --serve 8080
1136
-
1137
- NOTES:
1138
- - Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
1139
- `).action(async (directory, options) => await handleVisualize(directory, options));
1140
- program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", false).addHelpText("after", `
1074
+ var visualizeHelpText = `
1141
1075
  EXAMPLES:
1142
1076
  $ aiready visualize . # Auto-detects latest report
1143
1077
  $ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
@@ -1156,5 +1090,73 @@ NOTES:
1156
1090
  reduce clutter and improve interactivity on large graphs.
1157
1091
  - For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
1158
1092
  allow the browser a moment to stabilize after load.
1159
- `).action(async (directory, options) => await handleVisualize(directory, options));
1093
+ `;
1094
+ var visualiseHelpText = `
1095
+ EXAMPLES:
1096
+ $ aiready visualise . # Auto-detects latest report
1097
+ $ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
1098
+ $ aiready visualise . --report report.json --dev
1099
+ $ aiready visualise . --report report.json --serve 8080
1100
+
1101
+ NOTES:
1102
+ - Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
1103
+ `;
1104
+
1105
+ // src/cli.ts
1106
+ var packageJson = JSON.parse(readFileSync3(join(__dirname, "../package.json"), "utf8"));
1107
+ var program = new Command();
1108
+ program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
1109
+ AI READINESS SCORING:
1110
+ Get a 0-100 score indicating how AI-ready your codebase is.
1111
+ Use --score flag with any analysis command for detailed breakdown.
1112
+
1113
+ EXAMPLES:
1114
+ $ aiready scan # Quick analysis of current directory
1115
+ $ aiready scan --score # Get AI Readiness Score (0-100)
1116
+ $ aiready scan --tools patterns # Run only pattern detection
1117
+ $ aiready patterns --similarity 0.6 # Custom similarity threshold
1118
+ $ aiready scan --output json --output-file results.json
1119
+
1120
+ GETTING STARTED:
1121
+ 1. Run 'aiready scan' to analyze your codebase
1122
+ 2. Use 'aiready scan --score' for AI readiness assessment
1123
+ 3. Create aiready.json for persistent configuration
1124
+ 4. Set up CI/CD with '--threshold' for quality gates
1125
+
1126
+ CONFIGURATION:
1127
+ Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
1128
+ CLI options override config file settings
1129
+
1130
+ Example aiready.json:
1131
+ {
1132
+ "scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
1133
+ "tools": {
1134
+ "pattern-detect": { "minSimilarity": 0.5 },
1135
+ "context-analyzer": { "maxContextBudget": 15000 }
1136
+ },
1137
+ "output": { "format": "json", "directory": ".aiready" }
1138
+ }
1139
+
1140
+ VERSION: ${packageJson.version}
1141
+ DOCUMENTATION: https://aiready.dev/docs/cli
1142
+ GITHUB: https://github.com/caopengau/aiready-cli
1143
+ LANDING: https://github.com/caopengau/aiready-landing`);
1144
+ 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", "json").option("--output-file <path>", "Output file path (for json)").option("--no-score", "Disable calculating AI Readiness Score (enabled by default)").option("--weights <weights>", "Custom scoring weights (patterns:40,context:35,consistency:25)").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option("--ci", "CI mode: GitHub Actions annotations, no colors, fail on threshold").option("--fail-on <level>", "Fail on issues: critical, major, any", "critical").addHelpText("after", scanHelpText).action(async (directory, options) => {
1145
+ await scanAction(directory, options);
1146
+ });
1147
+ 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", patternsHelpText).action(async (directory, options) => {
1148
+ await patternsAction(directory, options);
1149
+ });
1150
+ 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) => {
1151
+ await contextAction(directory, options);
1152
+ });
1153
+ 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) => {
1154
+ await consistencyAction(directory, options);
1155
+ });
1156
+ program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", true).addHelpText("after", visualiseHelpText).action(async (directory, options) => {
1157
+ await visualizeAction(directory, options);
1158
+ });
1159
+ program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", false).addHelpText("after", visualizeHelpText).action(async (directory, options) => {
1160
+ await visualizeAction(directory, options);
1161
+ });
1160
1162
  program.parse();