@aiready/cli 0.9.27 → 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,
@@ -20,71 +23,119 @@ import {
20
23
  formatToolScore,
21
24
  getRating,
22
25
  getRatingDisplay,
23
- parseWeightString,
24
- generateHTML
26
+ parseWeightString
25
27
  } from "@aiready/core";
26
- import { readFileSync, existsSync, copyFileSync } from "fs";
27
- import { resolve as resolvePath } from "path";
28
- var packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
29
- var program = new Command();
30
- program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
31
- AI READINESS SCORING:
32
- Get a 0-100 score indicating how AI-ready your codebase is.
33
- Use --score flag with any analysis command for detailed breakdown.
34
28
 
35
- EXAMPLES:
36
- $ aiready scan # Quick analysis of current directory
37
- $ aiready scan --score # Get AI Readiness Score (0-100)
38
- $ aiready scan --tools patterns # Run only pattern detection
39
- $ aiready patterns --similarity 0.6 # Custom similarity threshold
40
- $ aiready scan --output json --output-file results.json
29
+ // src/utils/helpers.ts
30
+ import { resolve as resolvePath } from "path";
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
41
97
 
42
- GETTING STARTED:
43
- 1. Run 'aiready scan' to analyze your codebase
44
- 2. Use 'aiready scan --score' for AI readiness assessment
45
- 3. Create aiready.json for persistent configuration
46
- 4. Set up CI/CD with '--threshold' for quality gates
98
+ `;
99
+ markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
100
+ `;
101
+ markdown += `**Analysis Time:** ${elapsedTime}s
47
102
 
48
- CONFIGURATION:
49
- Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
50
- CLI options override config file settings
103
+ `;
104
+ markdown += `## Summary
51
105
 
52
- Example aiready.json:
53
- {
54
- "scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
55
- "tools": {
56
- "pattern-detect": { "minSimilarity": 0.5 },
57
- "context-analyzer": { "maxContextBudget": 15000 }
58
- },
59
- "output": { "format": "json", "directory": ".aiready" }
60
- }
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}
61
114
 
62
- VERSION: ${packageJson.version}
63
- DOCUMENTATION: https://aiready.dev/docs/cli
64
- GITHUB: https://github.com/caopengau/aiready-cli
65
- LANDING: https://github.com/caopengau/aiready-landing`);
66
- 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", `
67
- EXAMPLES:
68
- $ aiready scan # Analyze all tools
69
- $ aiready scan --tools patterns,context # Skip consistency
70
- $ aiready scan --score --threshold 75 # CI/CD with threshold
71
- $ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
72
- $ aiready scan --ci --fail-on major # Fail on major+ issues
73
- $ aiready scan --output json --output-file report.json
115
+ `;
116
+ if (report.recommendations.length > 0) {
117
+ markdown += `## Recommendations
74
118
 
75
- CI/CD INTEGRATION (Gatekeeper Mode):
76
- Use --ci for GitHub Actions integration:
77
- - Outputs GitHub Actions annotations for PR checks
78
- - Fails with exit code 1 if threshold not met
79
- - Shows clear "blocked" message with remediation steps
119
+ `;
120
+ report.recommendations.forEach((rec, i) => {
121
+ markdown += `${i + 1}. ${rec}
122
+ `;
123
+ });
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
+ }
80
133
 
81
- Example GitHub Actions workflow:
82
- - name: AI Readiness Check
83
- run: aiready scan --ci --threshold 70
84
- `).action(async (directory, options) => {
85
- 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"));
86
137
  const startTime = Date.now();
87
- const resolvedDir = resolvePath(process.cwd(), directory || ".");
138
+ const resolvedDir = resolvePath2(process.cwd(), directory || ".");
88
139
  try {
89
140
  const defaults = {
90
141
  tools: ["patterns", "context", "consistency"],
@@ -106,19 +157,13 @@ CI/CD INTEGRATION (Gatekeeper Mode):
106
157
  const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
107
158
  finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
108
159
  }
109
- console.log(chalk.cyan("\n=== AIReady Run Preview ==="));
110
- console.log(chalk.white("Tools to run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
111
- console.log(chalk.white("Will use settings from config and defaults."));
112
- const truncate = (arr, cap = 8) => {
113
- if (!Array.isArray(arr)) return "";
114
- const shown = arr.slice(0, cap).map((v) => String(v));
115
- const more = arr.length - shown.length;
116
- return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
117
- };
118
- console.log(chalk.white("\nGeneral settings:"));
119
- if (finalOptions.rootDir) console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
120
- if (finalOptions.include) console.log(` include: ${chalk.bold(truncate(finalOptions.include, 6))}`);
121
- 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))}`);
122
167
  if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
123
168
  const patternDetectConfig = finalOptions["pattern-detect"] || {
124
169
  minSimilarity: finalOptions.minSimilarity,
@@ -131,16 +176,16 @@ CI/CD INTEGRATION (Gatekeeper Mode):
131
176
  severity: finalOptions.severity,
132
177
  includeTests: finalOptions.includeTests
133
178
  };
134
- console.log(chalk.white("\nPattern-detect settings:"));
135
- console.log(` minSimilarity: ${chalk.bold(patternDetectConfig.minSimilarity ?? "default")}`);
136
- console.log(` minLines: ${chalk.bold(patternDetectConfig.minLines ?? "default")}`);
137
- if (patternDetectConfig.approx !== void 0) console.log(` approx: ${chalk.bold(String(patternDetectConfig.approx))}`);
138
- if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${chalk.bold(String(patternDetectConfig.minSharedTokens))}`);
139
- if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${chalk.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`);
140
- if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${chalk.bold(String(patternDetectConfig.batchSize))}`);
141
- if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${chalk.bold(String(patternDetectConfig.streamResults))}`);
142
- if (patternDetectConfig.severity !== void 0) console.log(` severity: ${chalk.bold(String(patternDetectConfig.severity))}`);
143
- 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))}`);
144
189
  }
145
190
  if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
146
191
  const ca = finalOptions["context-analyzer"] || {
@@ -150,32 +195,32 @@ CI/CD INTEGRATION (Gatekeeper Mode):
150
195
  maxFragmentation: finalOptions.maxFragmentation,
151
196
  includeNodeModules: finalOptions.includeNodeModules
152
197
  };
153
- console.log(chalk.white("\nContext-analyzer settings:"));
154
- console.log(` maxDepth: ${chalk.bold(ca.maxDepth ?? "default")}`);
155
- console.log(` maxContextBudget: ${chalk.bold(ca.maxContextBudget ?? "default")}`);
156
- if (ca.minCohesion !== void 0) console.log(` minCohesion: ${chalk.bold(String(ca.minCohesion))}`);
157
- if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${chalk.bold(String(ca.maxFragmentation))}`);
158
- 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))}`);
159
204
  }
160
205
  if (finalOptions.consistency) {
161
206
  const c = finalOptions.consistency;
162
- console.log(chalk.white("\nConsistency settings:"));
163
- console.log(` checkNaming: ${chalk.bold(String(c.checkNaming ?? true))}`);
164
- console.log(` checkPatterns: ${chalk.bold(String(c.checkPatterns ?? true))}`);
165
- console.log(` checkArchitecture: ${chalk.bold(String(c.checkArchitecture ?? false))}`);
166
- if (c.minSeverity) console.log(` minSeverity: ${chalk.bold(c.minSeverity)}`);
167
- if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${chalk.bold(truncate(c.acceptedAbbreviations, 8))}`);
168
- 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))}`);
169
214
  }
170
- console.log(chalk.white("\nStarting analysis..."));
215
+ console.log(chalk2.white("\nStarting analysis..."));
171
216
  const progressCallback = (event) => {
172
- console.log(chalk.cyan(`
217
+ console.log(chalk2.cyan(`
173
218
  --- ${event.tool.toUpperCase()} RESULTS ---`));
174
219
  try {
175
220
  if (event.tool === "patterns") {
176
221
  const pr = event.data;
177
- console.log(` Duplicate patterns: ${chalk.bold(String(pr.duplicates?.length || 0))}`);
178
- 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))}`);
179
224
  if (pr.duplicates && pr.duplicates.length > 0) {
180
225
  pr.duplicates.slice(0, 5).forEach((d, i) => {
181
226
  console.log(` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`);
@@ -189,10 +234,10 @@ CI/CD INTEGRATION (Gatekeeper Mode):
189
234
  });
190
235
  }
191
236
  if (pr.groups && pr.groups.length >= 0) {
192
- 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`);
193
238
  }
194
239
  if (pr.clusters && pr.clusters.length >= 0) {
195
- 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`);
196
241
  pr.clusters.slice(0, 3).forEach((cl, idx) => {
197
242
  const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
198
243
  console.log(` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`);
@@ -200,14 +245,14 @@ CI/CD INTEGRATION (Gatekeeper Mode):
200
245
  }
201
246
  } else if (event.tool === "context") {
202
247
  const cr = event.data;
203
- console.log(` Context issues found: ${chalk.bold(String(cr.length || 0))}`);
248
+ console.log(` Context issues found: ${chalk2.bold(String(cr.length || 0))}`);
204
249
  cr.slice(0, 5).forEach((c, i) => {
205
250
  const msg = c.message ? ` - ${c.message}` : "";
206
251
  console.log(` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`);
207
252
  });
208
253
  } else if (event.tool === "consistency") {
209
254
  const rep = event.data;
210
- console.log(` Consistency totalIssues: ${chalk.bold(String(rep.summary?.totalIssues || 0))}`);
255
+ console.log(` Consistency totalIssues: ${chalk2.bold(String(rep.summary?.totalIssues || 0))}`);
211
256
  if (rep.results && rep.results.length > 0) {
212
257
  const fileMap = /* @__PURE__ */ new Map();
213
258
  rep.results.forEach((r) => {
@@ -231,7 +276,7 @@ CI/CD INTEGRATION (Gatekeeper Mode):
231
276
  });
232
277
  const remaining = files.length - topFiles.length;
233
278
  if (remaining > 0) {
234
- 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)`));
235
280
  }
236
281
  }
237
282
  }
@@ -239,15 +284,15 @@ CI/CD INTEGRATION (Gatekeeper Mode):
239
284
  }
240
285
  };
241
286
  const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
242
- console.log(chalk.cyan("\n=== AIReady Run Summary ==="));
243
- console.log(chalk.white("Tools run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
244
- console.log(chalk.cyan("\nResults summary:"));
245
- console.log(` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`);
246
- if (results.duplicates) console.log(` Duplicate patterns found: ${chalk.bold(String(results.duplicates.length || 0))}`);
247
- if (results.patterns) console.log(` Pattern files with issues: ${chalk.bold(String(results.patterns.length || 0))}`);
248
- if (results.context) console.log(` Context issues: ${chalk.bold(String(results.context.length || 0))}`);
249
- if (results.consistency) console.log(` Consistency issues: ${chalk.bold(String(results.consistency.summary.totalIssues || 0))}`);
250
- 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"));
251
296
  const elapsedTime = getElapsedTime(startTime);
252
297
  let scoringResult;
253
298
  if (options.score || finalOptions.scoring?.showBreakdown) {
@@ -282,10 +327,10 @@ CI/CD INTEGRATION (Gatekeeper Mode):
282
327
  const cliWeights = parseWeightString(options.weights);
283
328
  if (toolScores.size > 0) {
284
329
  scoringResult = calculateOverallScore(toolScores, finalOptions, cliWeights.size ? cliWeights : void 0);
285
- console.log(chalk.bold("\n\u{1F4CA} AI Readiness Overall Score"));
330
+ console.log(chalk2.bold("\n\u{1F4CA} AI Readiness Overall Score"));
286
331
  console.log(` ${formatScore(scoringResult)}`);
287
332
  if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
288
- console.log(chalk.bold("\nTool breakdown:"));
333
+ console.log(chalk2.bold("\nTool breakdown:"));
289
334
  scoringResult.breakdown.forEach((tool) => {
290
335
  const rating = getRating(tool.score);
291
336
  const rd = getRatingDisplay(rating);
@@ -293,7 +338,7 @@ CI/CD INTEGRATION (Gatekeeper Mode):
293
338
  });
294
339
  console.log();
295
340
  if (finalOptions.scoring?.showBreakdown) {
296
- console.log(chalk.bold("Detailed tool breakdown:"));
341
+ console.log(chalk2.bold("Detailed tool breakdown:"));
297
342
  scoringResult.breakdown.forEach((tool) => {
298
343
  console.log(formatToolScore(tool));
299
344
  });
@@ -382,34 +427,60 @@ CI/CD INTEGRATION (Gatekeeper Mode):
382
427
  }
383
428
  }
384
429
  if (shouldFail) {
385
- console.log(chalk.red("\n\u{1F6AB} PR BLOCKED: AI Readiness Check Failed"));
386
- console.log(chalk.red(` Reason: ${failReason}`));
387
- console.log(chalk.dim("\n Remediation steps:"));
388
- console.log(chalk.dim(" 1. Run `aiready scan` locally to see detailed issues"));
389
- console.log(chalk.dim(" 2. Fix the critical issues before merging"));
390
- console.log(chalk.dim(" 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing"));
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"));
391
436
  process.exit(1);
392
437
  } else {
393
- console.log(chalk.green("\n\u2705 PR PASSED: AI Readiness Check"));
438
+ console.log(chalk2.green("\n\u2705 PR PASSED: AI Readiness Check"));
394
439
  if (threshold) {
395
- console.log(chalk.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
440
+ console.log(chalk2.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
396
441
  }
397
- console.log(chalk.dim("\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"));
442
+ console.log(chalk2.dim("\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"));
398
443
  }
399
444
  }
400
445
  } catch (error) {
401
446
  handleCLIError(error, "Analysis");
402
447
  }
403
- });
404
- 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 = `
405
450
  EXAMPLES:
406
- $ aiready patterns # Default analysis
407
- $ aiready patterns --similarity 0.6 # Stricter matching
408
- $ aiready patterns --min-lines 10 # Larger patterns only
409
- `).action(async (directory, options) => {
410
- 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"));
411
482
  const startTime = Date.now();
412
- const resolvedDir = resolvePath(process.cwd(), directory || ".");
483
+ const resolvedDir = resolvePath3(process.cwd(), directory || ".");
413
484
  try {
414
485
  const useSmartDefaults = !options.fullScan;
415
486
  const defaults = {
@@ -438,10 +509,10 @@ EXAMPLES:
438
509
  if (options.minSharedTokens) {
439
510
  cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
440
511
  }
441
- const finalOptions = await loadMergedConfig(resolvedDir, defaults, cliOptions);
512
+ const finalOptions = await loadMergedConfig2(resolvedDir, defaults, cliOptions);
442
513
  const { analyzePatterns, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
443
514
  const { results, duplicates } = await analyzePatterns(finalOptions);
444
- const elapsedTime = getElapsedTime(startTime);
515
+ const elapsedTime = getElapsedTime2(startTime);
445
516
  const summary = generateSummary(results);
446
517
  let patternScore;
447
518
  if (options.score) {
@@ -455,66 +526,84 @@ EXAMPLES:
455
526
  summary: { ...summary, executionTime: parseFloat(elapsedTime) },
456
527
  ...patternScore && { scoring: patternScore }
457
528
  };
458
- const outputPath = resolveOutputPath(
529
+ const outputPath = resolveOutputPath2(
459
530
  userOutputFile,
460
531
  `aiready-report-${getReportTimestamp()}.json`,
461
532
  resolvedDir
462
533
  );
463
- handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
534
+ handleJSONOutput2(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
464
535
  } else {
465
536
  const terminalWidth = process.stdout.columns || 80;
466
537
  const dividerWidth = Math.min(60, terminalWidth - 2);
467
538
  const divider = "\u2501".repeat(dividerWidth);
468
- console.log(chalk.cyan(divider));
469
- console.log(chalk.bold.white(" PATTERN ANALYSIS SUMMARY"));
470
- console.log(chalk.cyan(divider) + "\n");
471
- console.log(chalk.white(`\u{1F4C1} Files analyzed: ${chalk.bold(results.length)}`));
472
- console.log(chalk.yellow(`\u26A0 Duplicate patterns found: ${chalk.bold(summary.totalPatterns)}`));
473
- console.log(chalk.red(`\u{1F4B0} Token cost (wasted): ${chalk.bold(summary.totalTokenCost.toLocaleString())}`));
474
- 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")}`));
475
546
  const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
476
547
  if (sortedTypes.length > 0) {
477
- console.log(chalk.cyan("\n" + divider));
478
- console.log(chalk.bold.white(" PATTERNS BY TYPE"));
479
- 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");
480
551
  sortedTypes.forEach(([type, count]) => {
481
- console.log(` ${chalk.white(type.padEnd(15))} ${chalk.bold(count)}`);
552
+ console.log(` ${chalk3.white(type.padEnd(15))} ${chalk3.bold(count)}`);
482
553
  });
483
554
  }
484
555
  if (summary.totalPatterns > 0 && duplicates.length > 0) {
485
- console.log(chalk.cyan("\n" + divider));
486
- console.log(chalk.bold.white(" TOP DUPLICATE PATTERNS"));
487
- 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");
488
559
  const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
489
560
  topDuplicates.forEach((dup) => {
490
561
  const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
491
562
  const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
492
563
  const file1Name = dup.file1.split("/").pop() || dup.file1;
493
564
  const file2Name = dup.file2.split("/").pop() || dup.file2;
494
- console.log(`${severityIcon} ${severity}: ${chalk.bold(file1Name)} \u2194 ${chalk.bold(file2Name)}`);
495
- console.log(` Similarity: ${chalk.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${chalk.bold(dup.tokenCost.toLocaleString())} tokens each`);
496
- 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)}
497
568
  `);
498
569
  });
499
570
  } else {
500
- 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"));
501
572
  }
502
573
  if (patternScore) {
503
- console.log(chalk.cyan(divider));
504
- console.log(chalk.bold.white(" AI READINESS SCORE (Patterns)"));
505
- console.log(chalk.cyan(divider) + "\n");
506
- 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));
507
578
  console.log();
508
579
  }
509
580
  }
510
581
  } catch (error) {
511
- handleCLIError(error, "Pattern analysis");
582
+ handleCLIError2(error, "Pattern analysis");
512
583
  }
513
- });
514
- 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) => {
515
- 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"));
516
605
  const startTime = Date.now();
517
- const resolvedDir = resolvePath(process.cwd(), directory || ".");
606
+ const resolvedDir = resolvePath4(process.cwd(), directory || ".");
518
607
  try {
519
608
  const defaults = {
520
609
  maxDepth: 5,
@@ -526,7 +615,7 @@ program.command("context").description("Analyze context window costs and depende
526
615
  file: void 0
527
616
  }
528
617
  };
529
- let baseOptions = await loadMergedConfig(resolvedDir, defaults, {
618
+ let baseOptions = await loadMergedConfig3(resolvedDir, defaults, {
530
619
  maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
531
620
  maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
532
621
  include: options.include?.split(","),
@@ -545,7 +634,7 @@ program.command("context").description("Analyze context window costs and depende
545
634
  console.log("");
546
635
  const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
547
636
  const results = await analyzeContext(finalOptions);
548
- const elapsedTime = getElapsedTime(startTime);
637
+ const elapsedTime = getElapsedTime3(startTime);
549
638
  const summary = generateSummary(results);
550
639
  let contextScore;
551
640
  if (options.score) {
@@ -559,100 +648,113 @@ program.command("context").description("Analyze context window costs and depende
559
648
  summary: { ...summary, executionTime: parseFloat(elapsedTime) },
560
649
  ...contextScore && { scoring: contextScore }
561
650
  };
562
- const outputPath = resolveOutputPath(
651
+ const outputPath = resolveOutputPath3(
563
652
  userOutputFile,
564
653
  `aiready-report-${getReportTimestamp()}.json`,
565
654
  resolvedDir
566
655
  );
567
- handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
656
+ handleJSONOutput3(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
568
657
  } else {
569
658
  const terminalWidth = process.stdout.columns || 80;
570
659
  const dividerWidth = Math.min(60, terminalWidth - 2);
571
660
  const divider = "\u2501".repeat(dividerWidth);
572
- console.log(chalk.cyan(divider));
573
- console.log(chalk.bold.white(" CONTEXT ANALYSIS SUMMARY"));
574
- console.log(chalk.cyan(divider) + "\n");
575
- console.log(chalk.white(`\u{1F4C1} Files analyzed: ${chalk.bold(summary.totalFiles)}`));
576
- console.log(chalk.white(`\u{1F4CA} Total tokens: ${chalk.bold(summary.totalTokens.toLocaleString())}`));
577
- console.log(chalk.yellow(`\u{1F4B0} Avg context budget: ${chalk.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
578
- 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")}
579
668
  `));
580
669
  const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
581
670
  if (totalIssues > 0) {
582
- console.log(chalk.bold("\u26A0\uFE0F Issues Found:\n"));
671
+ console.log(chalk4.bold("\u26A0\uFE0F Issues Found:\n"));
583
672
  if (summary.criticalIssues > 0) {
584
- console.log(chalk.red(` \u{1F534} Critical: ${chalk.bold(summary.criticalIssues)}`));
673
+ console.log(chalk4.red(` \u{1F534} Critical: ${chalk4.bold(summary.criticalIssues)}`));
585
674
  }
586
675
  if (summary.majorIssues > 0) {
587
- console.log(chalk.yellow(` \u{1F7E1} Major: ${chalk.bold(summary.majorIssues)}`));
676
+ console.log(chalk4.yellow(` \u{1F7E1} Major: ${chalk4.bold(summary.majorIssues)}`));
588
677
  }
589
678
  if (summary.minorIssues > 0) {
590
- console.log(chalk.blue(` \u{1F535} Minor: ${chalk.bold(summary.minorIssues)}`));
679
+ console.log(chalk4.blue(` \u{1F535} Minor: ${chalk4.bold(summary.minorIssues)}`));
591
680
  }
592
- console.log(chalk.green(`
593
- \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
594
683
  `));
595
684
  } else {
596
- console.log(chalk.green("\u2705 No significant issues found!\n"));
685
+ console.log(chalk4.green("\u2705 No significant issues found!\n"));
597
686
  }
598
687
  if (summary.deepFiles.length > 0) {
599
- console.log(chalk.bold("\u{1F4CF} Deep Import Chains:\n"));
600
- console.log(chalk.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
601
- 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}
602
691
  `));
603
692
  summary.deepFiles.slice(0, 10).forEach((item) => {
604
693
  const fileName = item.file.split("/").slice(-2).join("/");
605
- 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})`)}`);
606
695
  });
607
696
  console.log();
608
697
  }
609
698
  if (summary.fragmentedModules.length > 0) {
610
- console.log(chalk.bold("\u{1F9E9} Fragmented Modules:\n"));
611
- 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)}%
612
701
  `));
613
702
  summary.fragmentedModules.slice(0, 10).forEach((module) => {
614
- console.log(` ${chalk.yellow("\u25CF")} ${chalk.white(module.domain)} - ${chalk.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
615
- 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)}%`));
616
705
  });
617
706
  console.log();
618
707
  }
619
708
  if (summary.lowCohesionFiles.length > 0) {
620
- console.log(chalk.bold("\u{1F500} Low Cohesion Files:\n"));
621
- 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)}%
622
711
  `));
623
712
  summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
624
713
  const fileName = item.file.split("/").slice(-2).join("/");
625
714
  const scorePercent = (item.score * 100).toFixed(0);
626
- const color = item.score < 0.4 ? chalk.red : chalk.yellow;
627
- 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)`)}`);
628
717
  });
629
718
  console.log();
630
719
  }
631
720
  if (summary.topExpensiveFiles.length > 0) {
632
- 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"));
633
722
  summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
634
723
  const fileName = item.file.split("/").slice(-2).join("/");
635
- const severityColor = item.severity === "critical" ? chalk.red : item.severity === "major" ? chalk.yellow : chalk.blue;
636
- 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)`)}`);
637
726
  });
638
727
  console.log();
639
728
  }
640
729
  if (contextScore) {
641
- console.log(chalk.cyan(divider));
642
- console.log(chalk.bold.white(" AI READINESS SCORE (Context)"));
643
- console.log(chalk.cyan(divider) + "\n");
644
- 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));
645
734
  console.log();
646
735
  }
647
736
  }
648
737
  } catch (error) {
649
- handleCLIError(error, "Context analysis");
738
+ handleCLIError3(error, "Context analysis");
650
739
  }
651
- });
652
- 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) => {
653
- 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"));
654
756
  const startTime = Date.now();
655
- const resolvedDir = resolvePath(process.cwd(), directory || ".");
757
+ const resolvedDir = resolvePath5(process.cwd(), directory || ".");
656
758
  try {
657
759
  const defaults = {
658
760
  checkNaming: true,
@@ -665,7 +767,7 @@ program.command("consistency").description("Check naming conventions and archite
665
767
  file: void 0
666
768
  }
667
769
  };
668
- const finalOptions = await loadMergedConfig(resolvedDir, defaults, {
770
+ const finalOptions = await loadMergedConfig4(resolvedDir, defaults, {
669
771
  checkNaming: options.naming !== false,
670
772
  checkPatterns: options.patterns !== false,
671
773
  minSeverity: options.minSeverity,
@@ -674,7 +776,7 @@ program.command("consistency").description("Check naming conventions and archite
674
776
  });
675
777
  const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
676
778
  const report = await analyzeConsistency(finalOptions);
677
- const elapsedTime = getElapsedTime(startTime);
779
+ const elapsedTime = getElapsedTime4(startTime);
678
780
  let consistencyScore;
679
781
  if (options.score) {
680
782
  const issues = report.results?.flatMap((r) => r.issues) || [];
@@ -691,32 +793,32 @@ program.command("consistency").description("Check naming conventions and archite
691
793
  },
692
794
  ...consistencyScore && { scoring: consistencyScore }
693
795
  };
694
- const outputPath = resolveOutputPath(
796
+ const outputPath = resolveOutputPath4(
695
797
  userOutputFile,
696
798
  `aiready-report-${getReportTimestamp()}.json`,
697
799
  resolvedDir
698
800
  );
699
- handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
801
+ handleJSONOutput4(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
700
802
  } else if (outputFormat === "markdown") {
701
803
  const markdown = generateMarkdownReport(report, elapsedTime);
702
- const outputPath = resolveOutputPath(
804
+ const outputPath = resolveOutputPath4(
703
805
  userOutputFile,
704
806
  `aiready-report-${getReportTimestamp()}.md`,
705
807
  resolvedDir
706
808
  );
707
809
  writeFileSync(outputPath, markdown);
708
- console.log(chalk.green(`\u2705 Report saved to ${outputPath}`));
810
+ console.log(chalk5.green(`\u2705 Report saved to ${outputPath}`));
709
811
  } else {
710
- console.log(chalk.bold("\n\u{1F4CA} Summary\n"));
711
- console.log(`Files Analyzed: ${chalk.cyan(report.summary.filesAnalyzed)}`);
712
- console.log(`Total Issues: ${chalk.yellow(report.summary.totalIssues)}`);
713
- console.log(` Naming: ${chalk.yellow(report.summary.namingIssues)}`);
714
- console.log(` Patterns: ${chalk.yellow(report.summary.patternIssues)}`);
715
- console.log(` Architecture: ${chalk.yellow(report.summary.architectureIssues || 0)}`);
716
- 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")}
717
819
  `);
718
820
  if (report.summary.totalIssues === 0) {
719
- 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"));
720
822
  } else {
721
823
  const namingResults = report.results.filter(
722
824
  (r) => r.issues.some((i) => i.category === "naming")
@@ -725,17 +827,17 @@ program.command("consistency").description("Check naming conventions and archite
725
827
  (r) => r.issues.some((i) => i.category === "patterns")
726
828
  );
727
829
  if (namingResults.length > 0) {
728
- console.log(chalk.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
830
+ console.log(chalk5.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
729
831
  let shown = 0;
730
832
  for (const result of namingResults) {
731
833
  if (shown >= 5) break;
732
834
  for (const issue of result.issues) {
733
835
  if (shown >= 5) break;
734
- const severityColor = issue.severity === "critical" ? chalk.red : issue.severity === "major" ? chalk.yellow : issue.severity === "minor" ? chalk.blue : chalk.gray;
735
- 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}`)}`);
736
838
  console.log(` ${issue.message}`);
737
839
  if (issue.suggestion) {
738
- console.log(` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`);
840
+ console.log(` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`);
739
841
  }
740
842
  console.log();
741
843
  shown++;
@@ -743,22 +845,22 @@ program.command("consistency").description("Check naming conventions and archite
743
845
  }
744
846
  const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
745
847
  if (remaining > 0) {
746
- console.log(chalk.dim(` ... and ${remaining} more issues
848
+ console.log(chalk5.dim(` ... and ${remaining} more issues
747
849
  `));
748
850
  }
749
851
  }
750
852
  if (patternResults.length > 0) {
751
- console.log(chalk.bold("\u{1F504} Pattern Issues\n"));
853
+ console.log(chalk5.bold("\u{1F504} Pattern Issues\n"));
752
854
  let shown = 0;
753
855
  for (const result of patternResults) {
754
856
  if (shown >= 5) break;
755
857
  for (const issue of result.issues) {
756
858
  if (shown >= 5) break;
757
- const severityColor = issue.severity === "critical" ? chalk.red : issue.severity === "major" ? chalk.yellow : issue.severity === "minor" ? chalk.blue : chalk.gray;
758
- 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}`)}`);
759
861
  console.log(` ${issue.message}`);
760
862
  if (issue.suggestion) {
761
- console.log(` ${chalk.dim("\u2192")} ${chalk.italic(issue.suggestion)}`);
863
+ console.log(` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`);
762
864
  }
763
865
  console.log();
764
866
  shown++;
@@ -766,12 +868,12 @@ program.command("consistency").description("Check naming conventions and archite
766
868
  }
767
869
  const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
768
870
  if (remaining > 0) {
769
- console.log(chalk.dim(` ... and ${remaining} more issues
871
+ console.log(chalk5.dim(` ... and ${remaining} more issues
770
872
  `));
771
873
  }
772
874
  }
773
875
  if (report.recommendations.length > 0) {
774
- console.log(chalk.bold("\u{1F4A1} Recommendations\n"));
876
+ console.log(chalk5.bold("\u{1F4A1} Recommendations\n"));
775
877
  report.recommendations.forEach((rec, i) => {
776
878
  console.log(`${i + 1}. ${rec}`);
777
879
  });
@@ -779,124 +881,35 @@ program.command("consistency").description("Check naming conventions and archite
779
881
  }
780
882
  }
781
883
  if (consistencyScore) {
782
- console.log(chalk.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
783
- console.log(formatToolScore(consistencyScore));
884
+ console.log(chalk5.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
885
+ console.log(formatToolScore4(consistencyScore));
784
886
  console.log();
785
887
  }
786
888
  }
787
889
  } catch (error) {
788
- handleCLIError(error, "Consistency analysis");
890
+ handleCLIError4(error, "Consistency analysis");
789
891
  }
790
- });
791
- function generateMarkdownReport(report, elapsedTime) {
792
- let markdown = `# Consistency Analysis Report
793
-
794
- `;
795
- markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
796
- `;
797
- markdown += `**Analysis Time:** ${elapsedTime}s
798
-
799
- `;
800
- markdown += `## Summary
801
-
802
- `;
803
- markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
804
- `;
805
- markdown += `- **Total Issues:** ${report.summary.totalIssues}
806
- `;
807
- markdown += ` - Naming: ${report.summary.namingIssues}
808
- `;
809
- markdown += ` - Patterns: ${report.summary.patternIssues}
810
-
811
- `;
812
- if (report.recommendations.length > 0) {
813
- markdown += `## Recommendations
814
-
815
- `;
816
- report.recommendations.forEach((rec, i) => {
817
- markdown += `${i + 1}. ${rec}
818
- `;
819
- });
820
- }
821
- return markdown;
822
892
  }
823
- function getReportTimestamp() {
824
- const now = /* @__PURE__ */ new Date();
825
- const pad = (n) => String(n).padStart(2, "0");
826
- return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
827
- }
828
- function findLatestScanReport(dirPath) {
829
- const aireadyDir = resolvePath(dirPath, ".aiready");
830
- if (!existsSync(aireadyDir)) {
831
- return null;
832
- }
833
- const { readdirSync, statSync } = __require("fs");
834
- let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
835
- if (files.length === 0) {
836
- files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
837
- }
838
- if (files.length === 0) {
839
- return null;
840
- }
841
- 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());
842
- return sortedFiles[0].path;
843
- }
844
- function warnIfGraphCapExceeded(report, dirPath) {
845
- try {
846
- const { loadConfig } = __require("@aiready/core");
847
- const { existsSync: existsSync2, readFileSync: readFileSync2 } = __require("fs");
848
- const { resolve } = __require("path");
849
- let graphConfig = { maxNodes: 400, maxEdges: 600 };
850
- const configPath = resolve(dirPath, "aiready.json");
851
- if (existsSync2(configPath)) {
852
- try {
853
- const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
854
- if (rawConfig.visualizer?.graph) {
855
- graphConfig = {
856
- maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
857
- maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
858
- };
859
- }
860
- } catch (e) {
861
- }
862
- }
863
- const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
864
- const edgeCount = report.context?.reduce((sum, ctx) => {
865
- const relCount = ctx.relatedFiles?.length || 0;
866
- const depCount = ctx.dependencies?.length || 0;
867
- return sum + relCount + depCount;
868
- }, 0) || 0;
869
- if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
870
- console.log("");
871
- console.log(chalk.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
872
- if (nodeCount > graphConfig.maxNodes) {
873
- console.log(chalk.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
874
- }
875
- if (edgeCount > graphConfig.maxEdges) {
876
- console.log(chalk.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
877
- }
878
- console.log(chalk.dim(` To increase limits, add to aiready.json:`));
879
- console.log(chalk.dim(` {`));
880
- console.log(chalk.dim(` "visualizer": {`));
881
- console.log(chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
882
- console.log(chalk.dim(` }`));
883
- console.log(chalk.dim(` }`));
884
- }
885
- } catch (e) {
886
- }
887
- }
888
- async function handleVisualize(directory, options) {
893
+
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) {
889
902
  try {
890
- const dirPath = resolvePath(process.cwd(), directory || ".");
891
- let reportPath = options.report ? resolvePath(dirPath, options.report) : null;
892
- 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)) {
893
906
  const latestScan = findLatestScanReport(dirPath);
894
907
  if (latestScan) {
895
908
  reportPath = latestScan;
896
- console.log(chalk.dim(`Found latest report: ${latestScan.split("/").pop()}`));
909
+ console.log(chalk6.dim(`Found latest report: ${latestScan.split("/").pop()}`));
897
910
  } else {
898
- console.error(chalk.red("\u274C No AI readiness report found"));
899
- console.log(chalk.dim(`
911
+ console.error(chalk6.red("\u274C No AI readiness report found"));
912
+ console.log(chalk6.dim(`
900
913
  Generate a report with:
901
914
  aiready scan --output json
902
915
 
@@ -905,13 +918,13 @@ Or specify a custom report:
905
918
  return;
906
919
  }
907
920
  }
908
- const raw = readFileSync(reportPath, "utf8");
921
+ const raw = readFileSync2(reportPath, "utf8");
909
922
  const report = JSON.parse(raw);
910
- const configPath = resolvePath(dirPath, "aiready.json");
923
+ const configPath = resolvePath6(dirPath, "aiready.json");
911
924
  let graphConfig = { maxNodes: 400, maxEdges: 600 };
912
- if (existsSync(configPath)) {
925
+ if (existsSync2(configPath)) {
913
926
  try {
914
- const rawConfig = JSON.parse(readFileSync(configPath, "utf8"));
927
+ const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
915
928
  if (rawConfig.visualizer?.graph) {
916
929
  graphConfig = {
917
930
  maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
@@ -928,27 +941,26 @@ Or specify a custom report:
928
941
  const graph = GraphBuilder.buildFromReport(report, dirPath);
929
942
  if (options.dev) {
930
943
  try {
931
- const { spawn } = await import("child_process");
932
- const monorepoWebDir = resolvePath(dirPath, "packages/visualizer");
944
+ const monorepoWebDir = resolvePath6(dirPath, "packages/visualizer");
933
945
  let webDir = "";
934
946
  let visualizerAvailable = false;
935
- if (existsSync(monorepoWebDir)) {
947
+ if (existsSync2(monorepoWebDir)) {
936
948
  webDir = monorepoWebDir;
937
949
  visualizerAvailable = true;
938
950
  } else {
939
951
  const nodemodulesLocations = [
940
- resolvePath(dirPath, "node_modules", "@aiready", "visualizer"),
941
- resolvePath(process.cwd(), "node_modules", "@aiready", "visualizer")
952
+ resolvePath6(dirPath, "node_modules", "@aiready", "visualizer"),
953
+ resolvePath6(process.cwd(), "node_modules", "@aiready", "visualizer")
942
954
  ];
943
955
  let currentDir = dirPath;
944
956
  while (currentDir !== "/" && currentDir !== ".") {
945
- nodemodulesLocations.push(resolvePath(currentDir, "node_modules", "@aiready", "visualizer"));
946
- const parent = resolvePath(currentDir, "..");
957
+ nodemodulesLocations.push(resolvePath6(currentDir, "node_modules", "@aiready", "visualizer"));
958
+ const parent = resolvePath6(currentDir, "..");
947
959
  if (parent === currentDir) break;
948
960
  currentDir = parent;
949
961
  }
950
962
  for (const location of nodemodulesLocations) {
951
- if (existsSync(location) && existsSync(resolvePath(location, "package.json"))) {
963
+ if (existsSync2(location) && existsSync2(resolvePath6(location, "package.json"))) {
952
964
  webDir = location;
953
965
  visualizerAvailable = true;
954
966
  break;
@@ -957,7 +969,7 @@ Or specify a custom report:
957
969
  if (!visualizerAvailable) {
958
970
  try {
959
971
  const vizPkgPath = __require.resolve("@aiready/visualizer/package.json");
960
- webDir = resolvePath(vizPkgPath, "..");
972
+ webDir = resolvePath6(vizPkgPath, "..");
961
973
  visualizerAvailable = true;
962
974
  } catch (e) {
963
975
  }
@@ -965,17 +977,17 @@ Or specify a custom report:
965
977
  }
966
978
  const spawnCwd = webDir || process.cwd();
967
979
  const nodeBinCandidate = process.execPath;
968
- const nodeBin = existsSync(nodeBinCandidate) ? nodeBinCandidate : "node";
980
+ const nodeBin = existsSync2(nodeBinCandidate) ? nodeBinCandidate : "node";
969
981
  if (!visualizerAvailable) {
970
- console.error(chalk.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
971
- 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"));
972
984
  return;
973
985
  }
974
986
  const { watch } = await import("fs");
975
987
  const copyReportToViz = () => {
976
988
  try {
977
- const destPath = resolvePath(spawnCwd, "web", "report-data.json");
978
- copyFileSync(reportPath, destPath);
989
+ const destPath = resolvePath6(spawnCwd, "web", "report-data.json");
990
+ copyFileSync2(reportPath, destPath);
979
991
  console.log(`\u{1F4CB} Report synced to ${destPath}`);
980
992
  } catch (e) {
981
993
  console.error("Failed to sync report:", e);
@@ -1013,20 +1025,18 @@ Or specify a custom report:
1013
1025
  }
1014
1026
  console.log("Generating HTML...");
1015
1027
  const html = generateHTML(graph);
1016
- const outPath = resolvePath(dirPath, options.output || "packages/visualizer/visualization.html");
1017
- writeFileSync(outPath, html, "utf8");
1028
+ const outPath = resolvePath6(dirPath, options.output || "packages/visualizer/visualization.html");
1029
+ writeFileSync2(outPath, html, "utf8");
1018
1030
  console.log("Visualization written to:", outPath);
1019
1031
  if (options.open) {
1020
- const { exec } = await import("child_process");
1021
1032
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1022
- exec(`${opener} "${outPath}"`);
1033
+ spawn(opener, [`"${outPath}"`], { shell: true });
1023
1034
  }
1024
1035
  if (options.serve) {
1025
1036
  try {
1026
1037
  const port = typeof options.serve === "number" ? options.serve : 5173;
1027
1038
  const http = await import("http");
1028
1039
  const fsp = await import("fs/promises");
1029
- const { exec } = await import("child_process");
1030
1040
  const server = http.createServer(async (req, res) => {
1031
1041
  try {
1032
1042
  const urlPath = req.url || "/";
@@ -1047,7 +1057,7 @@ Or specify a custom report:
1047
1057
  const addr = `http://localhost:${port}/`;
1048
1058
  console.log(`Local visualization server running at ${addr}`);
1049
1059
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1050
- exec(`${opener} "${addr}"`);
1060
+ spawn(opener, [`"${addr}"`], { shell: true });
1051
1061
  });
1052
1062
  process.on("SIGINT", () => {
1053
1063
  server.close();
@@ -1058,20 +1068,10 @@ Or specify a custom report:
1058
1068
  }
1059
1069
  }
1060
1070
  } catch (err) {
1061
- handleCLIError(err, "Visualization");
1071
+ handleCLIError5(err, "Visualization");
1062
1072
  }
1063
1073
  }
1064
- 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", `
1065
- EXAMPLES:
1066
- $ aiready visualise . # Auto-detects latest report
1067
- $ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
1068
- $ aiready visualise . --report report.json --dev
1069
- $ aiready visualise . --report report.json --serve 8080
1070
-
1071
- NOTES:
1072
- - Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
1073
- `).action(async (directory, options) => await handleVisualize(directory, options));
1074
- 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 = `
1075
1075
  EXAMPLES:
1076
1076
  $ aiready visualize . # Auto-detects latest report
1077
1077
  $ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
@@ -1090,5 +1090,73 @@ NOTES:
1090
1090
  reduce clutter and improve interactivity on large graphs.
1091
1091
  - For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
1092
1092
  allow the browser a moment to stabilize after load.
1093
- `).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
+ });
1094
1162
  program.parse();