@aiready/cli 0.7.16 → 0.7.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/cli@0.7.16 build /Users/pengcao/projects/aiready/packages/cli
3
+ > @aiready/cli@0.7.18 build /Users/pengcao/projects/aiready/packages/cli
4
4
  > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
5
 
6
6
  CLI Building entry: src/cli.ts, src/index.ts
@@ -9,16 +9,16 @@
9
9
  CLI Target: es2020
10
10
  CJS Build start
11
11
  ESM Build start
12
- CJS dist/index.js 4.55 KB
13
- CJS dist/cli.js 28.47 KB
12
+ CJS dist/cli.js 35.44 KB
13
+ CJS dist/index.js 4.60 KB
14
14
  CJS ⚡️ Build success in 13ms
15
- ESM dist/cli.mjs 22.00 KB
16
15
  ESM dist/index.mjs 138.00 B
17
- ESM dist/chunk-AGAMURT4.mjs 3.42 KB
16
+ ESM dist/cli.mjs 28.57 KB
17
+ ESM dist/chunk-P3XAXCTK.mjs 3.46 KB
18
18
  ESM ⚡️ Build success in 13ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 451ms
20
+ DTS ⚡️ Build success in 501ms
21
21
  DTS dist/cli.d.ts 20.00 B
22
- DTS dist/index.d.ts 991.00 B
22
+ DTS dist/index.d.ts 1.06 KB
23
23
  DTS dist/cli.d.mts 20.00 B
24
- DTS dist/index.d.mts 991.00 B
24
+ DTS dist/index.d.mts 1.06 KB
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/cli@0.7.16 test /Users/pengcao/projects/aiready/packages/cli
3
+ > @aiready/cli@0.7.18 test /Users/pengcao/projects/aiready/packages/cli
4
4
  > vitest run
5
5
 
6
6
  [?25l
@@ -11,14 +11,14 @@
11
11
 
12
12
   Test Files 0 passed (1)
13
13
   Tests 0 passed (0)
14
-  Start at 18:01:42
14
+  Start at 23:10:25
15
15
   Duration 0ms
16
16
  [?2026l[?2026h
17
17
   ❯ src/__tests__/cli.test.ts 0/3
18
18
 
19
19
   Test Files 0 passed (1)
20
20
   Tests 0 passed (3)
21
-  Start at 18:01:42
21
+  Start at 23:10:25
22
22
   Duration 201ms
23
23
  [?2026l ✓ src/__tests__/cli.test.ts (3 tests) 2ms
24
24
  ✓ CLI Unified Analysis (3)
@@ -28,7 +28,7 @@
28
28
 
29
29
   Test Files  1 passed (1)
30
30
   Tests  3 passed (3)
31
-  Start at  18:01:42
32
-  Duration  278ms (transform 50ms, setup 0ms, import 205ms, tests 2ms, environment 0ms)
31
+  Start at  23:10:25
32
+  Duration  283ms (transform 53ms, setup 0ms, import 211ms, tests 2ms, environment 0ms)
33
33
 
34
34
  [?25h
@@ -0,0 +1,110 @@
1
+ // src/index.ts
2
+ import { analyzePatterns } from "@aiready/pattern-detect";
3
+ import { analyzeContext } from "@aiready/context-analyzer";
4
+ import { analyzeConsistency } from "@aiready/consistency";
5
+ var severityOrder = {
6
+ critical: 4,
7
+ major: 3,
8
+ minor: 2,
9
+ info: 1
10
+ };
11
+ function sortBySeverity(results) {
12
+ return results.map((file) => {
13
+ const sortedIssues = [...file.issues].sort((a, b) => {
14
+ const severityDiff = (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
15
+ if (severityDiff !== 0) return severityDiff;
16
+ return (a.location?.line || 0) - (b.location?.line || 0);
17
+ });
18
+ return { ...file, issues: sortedIssues };
19
+ }).sort((a, b) => {
20
+ const aMaxSeverity = Math.max(...a.issues.map((i) => severityOrder[i.severity] || 0), 0);
21
+ const bMaxSeverity = Math.max(...b.issues.map((i) => severityOrder[i.severity] || 0), 0);
22
+ if (aMaxSeverity !== bMaxSeverity) {
23
+ return bMaxSeverity - aMaxSeverity;
24
+ }
25
+ if (a.issues.length !== b.issues.length) {
26
+ return b.issues.length - a.issues.length;
27
+ }
28
+ return a.fileName.localeCompare(b.fileName);
29
+ });
30
+ }
31
+ async function analyzeUnified(options) {
32
+ const startTime = Date.now();
33
+ const tools = options.tools || ["patterns", "context", "consistency"];
34
+ const result = {
35
+ summary: {
36
+ totalIssues: 0,
37
+ toolsRun: tools,
38
+ executionTime: 0
39
+ }
40
+ };
41
+ if (tools.includes("patterns")) {
42
+ const patternResult = await analyzePatterns(options);
43
+ result.patterns = sortBySeverity(patternResult.results);
44
+ result.duplicates = patternResult.duplicates;
45
+ result.summary.totalIssues += patternResult.results.reduce(
46
+ (sum, file) => sum + file.issues.length,
47
+ 0
48
+ );
49
+ }
50
+ if (tools.includes("context")) {
51
+ const contextResults = await analyzeContext(options);
52
+ result.context = contextResults.sort((a, b) => {
53
+ const severityDiff = (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
54
+ if (severityDiff !== 0) return severityDiff;
55
+ if (a.tokenCost !== b.tokenCost) return b.tokenCost - a.tokenCost;
56
+ return b.fragmentationScore - a.fragmentationScore;
57
+ });
58
+ result.summary.totalIssues += result.context?.length || 0;
59
+ }
60
+ if (tools.includes("consistency")) {
61
+ const report = await analyzeConsistency({
62
+ rootDir: options.rootDir,
63
+ include: options.include,
64
+ exclude: options.exclude,
65
+ checkNaming: true,
66
+ checkPatterns: true,
67
+ minSeverity: "info"
68
+ });
69
+ if (report.results) {
70
+ report.results = sortBySeverity(report.results);
71
+ }
72
+ result.consistency = report;
73
+ result.summary.totalIssues += report.summary.totalIssues;
74
+ }
75
+ result.summary.executionTime = Date.now() - startTime;
76
+ return result;
77
+ }
78
+ function generateUnifiedSummary(result) {
79
+ const { summary } = result;
80
+ let output = `\u{1F680} AIReady Analysis Complete
81
+
82
+ `;
83
+ output += `\u{1F4CA} Summary:
84
+ `;
85
+ output += ` Tools run: ${summary.toolsRun.join(", ")}
86
+ `;
87
+ output += ` Total issues found: ${summary.totalIssues}
88
+ `;
89
+ output += ` Execution time: ${(summary.executionTime / 1e3).toFixed(2)}s
90
+
91
+ `;
92
+ if (result.patterns?.length) {
93
+ output += `\u{1F50D} Pattern Analysis: ${result.patterns.length} issues
94
+ `;
95
+ }
96
+ if (result.context?.length) {
97
+ output += `\u{1F9E0} Context Analysis: ${result.context.length} issues
98
+ `;
99
+ }
100
+ if (result.consistency) {
101
+ output += `\u{1F3F7}\uFE0F Consistency Analysis: ${result.consistency.summary.totalIssues} issues
102
+ `;
103
+ }
104
+ return output;
105
+ }
106
+
107
+ export {
108
+ analyzeUnified,
109
+ generateUnifiedSummary
110
+ };
package/dist/cli.js CHANGED
@@ -69,6 +69,7 @@ async function analyzeUnified(options) {
69
69
  if (tools.includes("patterns")) {
70
70
  const patternResult = await (0, import_pattern_detect.analyzePatterns)(options);
71
71
  result.patterns = sortBySeverity(patternResult.results);
72
+ result.duplicates = patternResult.duplicates;
72
73
  result.summary.totalIssues += patternResult.results.reduce(
73
74
  (sum, file) => sum + file.issues.length,
74
75
  0
@@ -140,7 +141,7 @@ var import_fs2 = require("fs");
140
141
  var packageJson = JSON.parse((0, import_fs2.readFileSync)((0, import_path.join)(__dirname, "../package.json"), "utf8"));
141
142
  var program = new import_commander.Command();
142
143
  program.name("aiready").description("AIReady - Unified AI-readiness analysis tools").version(packageJson.version).addHelpText("after", "\nCONFIGURATION:\n Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js\n CLI options override config file settings");
143
- program.command("scan").description("Run unified analysis on a codebase").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
144
+ program.command("scan").description("Run unified analysis on a codebase").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--weights <weights>", 'Override tool weights for scoring (e.g., "patterns:50,context:30,consistency:20")').option("--threshold <score>", "Minimum passing score for CI/CD (exits with code 1 if below)").action(async (directory, options) => {
144
145
  console.log(import_chalk.default.blue("\u{1F680} Starting AIReady unified analysis...\n"));
145
146
  const startTime = Date.now();
146
147
  try {
@@ -166,6 +167,47 @@ program.command("scan").description("Run unified analysis on a codebase").argume
166
167
  }
167
168
  const results = await analyzeUnified(finalOptions);
168
169
  const elapsedTime = (0, import_core.getElapsedTime)(startTime);
170
+ let scoringResult;
171
+ if (options.score || finalOptions.scoring?.showBreakdown) {
172
+ const toolScores = /* @__PURE__ */ new Map();
173
+ if (results.patterns && baseOptions.tools.includes("patterns")) {
174
+ const { calculatePatternScore } = await import("@aiready/pattern-detect");
175
+ const duplicates = results.duplicates || [];
176
+ const score = calculatePatternScore(duplicates, results.patterns.length);
177
+ toolScores.set("pattern-detect", score);
178
+ }
179
+ if (results.context && baseOptions.tools.includes("context")) {
180
+ const { calculateContextScore } = await import("@aiready/context-analyzer");
181
+ const summary = {
182
+ avgContextBudget: results.context.reduce((sum, r) => sum + r.contextBudget, 0) / results.context.length,
183
+ maxContextBudget: Math.max(...results.context.map((r) => r.contextBudget)),
184
+ avgImportDepth: results.context.reduce((sum, r) => sum + r.importDepth, 0) / results.context.length,
185
+ maxImportDepth: Math.max(...results.context.map((r) => r.importDepth)),
186
+ avgFragmentation: results.context.reduce((sum, r) => sum + r.fragmentationScore, 0) / results.context.length,
187
+ criticalIssues: results.context.filter((r) => r.severity === "critical").length,
188
+ majorIssues: results.context.filter((r) => r.severity === "major").length
189
+ };
190
+ const score = calculateContextScore(summary);
191
+ toolScores.set("context-analyzer", score);
192
+ }
193
+ if (results.consistency && baseOptions.tools.includes("consistency")) {
194
+ const { calculateConsistencyScore } = await import("@aiready/consistency");
195
+ const issues = results.consistency.results?.flatMap((r) => r.issues) || [];
196
+ const score = calculateConsistencyScore(issues, results.consistency.summary.filesAnalyzed);
197
+ toolScores.set("consistency", score);
198
+ }
199
+ const cliWeights = options.weights ? (0, import_core.parseWeightString)(options.weights) : void 0;
200
+ scoringResult = (0, import_core.calculateOverallScore)(toolScores, finalOptions, cliWeights);
201
+ if (options.threshold) {
202
+ const threshold = parseFloat(options.threshold);
203
+ if (scoringResult.overall < threshold) {
204
+ console.error(import_chalk.default.red(`
205
+ \u274C Score ${scoringResult.overall} is below threshold ${threshold}
206
+ `));
207
+ process.exit(1);
208
+ }
209
+ }
210
+ }
169
211
  const outputFormat = options.output || finalOptions.output?.format || "console";
170
212
  const userOutputFile = options.outputFile || finalOptions.output?.file;
171
213
  if (outputFormat === "json") {
@@ -174,7 +216,8 @@ program.command("scan").description("Run unified analysis on a codebase").argume
174
216
  summary: {
175
217
  ...results.summary,
176
218
  executionTime: parseFloat(elapsedTime)
177
- }
219
+ },
220
+ ...scoringResult && { scoring: scoringResult }
178
221
  };
179
222
  const outputPath = (0, import_core.resolveOutputPath)(
180
223
  userOutputFile,
@@ -184,12 +227,47 @@ program.command("scan").description("Run unified analysis on a codebase").argume
184
227
  (0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
185
228
  } else {
186
229
  console.log(generateUnifiedSummary(results));
230
+ if (scoringResult) {
231
+ const terminalWidth = process.stdout.columns || 80;
232
+ const dividerWidth = Math.min(60, terminalWidth - 2);
233
+ const divider = "\u2501".repeat(dividerWidth);
234
+ console.log(import_chalk.default.cyan("\n" + divider));
235
+ console.log(import_chalk.default.bold.white(" AI READINESS SCORE"));
236
+ console.log(import_chalk.default.cyan(divider) + "\n");
237
+ const { emoji, color } = (0, import_core.getRatingDisplay)(scoringResult.rating);
238
+ const scoreColor = color === "green" ? import_chalk.default.green : color === "blue" ? import_chalk.default.blue : color === "yellow" ? import_chalk.default.yellow : import_chalk.default.red;
239
+ console.log(` ${emoji} Overall Score: ${scoreColor.bold(scoringResult.overall + "/100")} (${import_chalk.default.bold(scoringResult.rating)})`);
240
+ console.log(` ${import_chalk.default.dim("Timestamp:")} ${new Date(scoringResult.timestamp).toLocaleString()}
241
+ `);
242
+ if (scoringResult.breakdown.length > 0) {
243
+ console.log(import_chalk.default.bold(" Component Scores:\n"));
244
+ scoringResult.breakdown.forEach((tool) => {
245
+ const toolEmoji = tool.toolName === "pattern-detect" ? "\u{1F50D}" : tool.toolName === "context-analyzer" ? "\u{1F9E0}" : "\u{1F3F7}\uFE0F";
246
+ const weight = scoringResult.calculation.weights[tool.toolName];
247
+ console.log(` ${toolEmoji} ${import_chalk.default.white(tool.toolName.padEnd(20))} ${scoreColor(tool.score + "/100")} ${import_chalk.default.dim(`(weight: ${weight})`)}`);
248
+ });
249
+ console.log();
250
+ }
251
+ console.log(import_chalk.default.dim(` Weighted Formula: ${scoringResult.calculation.formula}`));
252
+ console.log(import_chalk.default.dim(` Normalized Score: ${scoringResult.calculation.normalized}
253
+ `));
254
+ const allRecommendations = scoringResult.breakdown.flatMap((tool) => tool.recommendations || []).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0, 5);
255
+ if (allRecommendations.length > 0) {
256
+ console.log(import_chalk.default.bold(" Top Recommendations:\n"));
257
+ allRecommendations.forEach((rec, i) => {
258
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
259
+ console.log(` ${i + 1}. ${priorityIcon} ${rec.action}`);
260
+ console.log(` ${import_chalk.default.dim(`Impact: +${rec.estimatedImpact} points`)}
261
+ `);
262
+ });
263
+ }
264
+ }
187
265
  }
188
266
  } catch (error) {
189
267
  (0, import_core.handleCLIError)(error, "Analysis");
190
268
  }
191
269
  });
192
- program.command("patterns").description("Run pattern detection analysis").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option("--max-candidates <number>", "Maximum candidates per block (performance tuning)").option("--min-shared-tokens <number>", "Minimum shared tokens for candidates (performance tuning)").option("--full-scan", "Disable smart defaults for comprehensive analysis (slower)").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
270
+ program.command("patterns").description("Run pattern detection analysis").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option("--max-candidates <number>", "Maximum candidates per block (performance tuning)").option("--min-shared-tokens <number>", "Minimum shared tokens for candidates (performance tuning)").option("--full-scan", "Disable smart defaults for comprehensive analysis (slower)").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for patterns (0-100)").action(async (directory, options) => {
193
271
  console.log(import_chalk.default.blue("\u{1F50D} Analyzing patterns...\n"));
194
272
  const startTime = Date.now();
195
273
  try {
@@ -221,16 +299,21 @@ program.command("patterns").description("Run pattern detection analysis").argume
221
299
  cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
222
300
  }
223
301
  const finalOptions = await (0, import_core.loadMergedConfig)(directory, defaults, cliOptions);
224
- const { analyzePatterns: analyzePatterns2, generateSummary } = await import("@aiready/pattern-detect");
302
+ const { analyzePatterns: analyzePatterns2, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
225
303
  const { results, duplicates } = await analyzePatterns2(finalOptions);
226
304
  const elapsedTime = (0, import_core.getElapsedTime)(startTime);
227
305
  const summary = generateSummary(results);
306
+ let patternScore;
307
+ if (options.score) {
308
+ patternScore = calculatePatternScore(duplicates, results.length);
309
+ }
228
310
  const outputFormat = options.output || finalOptions.output?.format || "console";
229
311
  const userOutputFile = options.outputFile || finalOptions.output?.file;
230
312
  if (outputFormat === "json") {
231
313
  const outputData = {
232
314
  results,
233
- summary: { ...summary, executionTime: parseFloat(elapsedTime) }
315
+ summary: { ...summary, executionTime: parseFloat(elapsedTime) },
316
+ ...patternScore && { scoring: patternScore }
234
317
  };
235
318
  const outputPath = (0, import_core.resolveOutputPath)(
236
319
  userOutputFile,
@@ -276,12 +359,19 @@ program.command("patterns").description("Run pattern detection analysis").argume
276
359
  } else {
277
360
  console.log(import_chalk.default.green("\n\u2728 Great! No duplicate patterns detected.\n"));
278
361
  }
362
+ if (patternScore) {
363
+ console.log(import_chalk.default.cyan(divider));
364
+ console.log(import_chalk.default.bold.white(" AI READINESS SCORE (Patterns)"));
365
+ console.log(import_chalk.default.cyan(divider) + "\n");
366
+ console.log((0, import_core.formatToolScore)(patternScore));
367
+ console.log();
368
+ }
279
369
  }
280
370
  } catch (error) {
281
371
  (0, import_core.handleCLIError)(error, "Pattern analysis");
282
372
  }
283
373
  });
284
- program.command("context").description("Run context window cost analysis").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
374
+ program.command("context").description("Run context window cost analysis").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for context (0-100)").action(async (directory, options) => {
285
375
  console.log(import_chalk.default.blue("\u{1F9E0} Analyzing context costs...\n"));
286
376
  const startTime = Date.now();
287
377
  try {
@@ -312,16 +402,21 @@ program.command("context").description("Run context window cost analysis").argum
312
402
  console.log(` Max fragmentation: ${(finalOptions.maxFragmentation * 100).toFixed(1)}%`);
313
403
  console.log(` Analysis focus: ${finalOptions.focus}`);
314
404
  console.log("");
315
- const { analyzeContext: analyzeContext2, generateSummary } = await import("@aiready/context-analyzer");
405
+ const { analyzeContext: analyzeContext2, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
316
406
  const results = await analyzeContext2(finalOptions);
317
407
  const elapsedTime = (0, import_core.getElapsedTime)(startTime);
318
408
  const summary = generateSummary(results);
409
+ let contextScore;
410
+ if (options.score) {
411
+ contextScore = calculateContextScore(summary);
412
+ }
319
413
  const outputFormat = options.output || finalOptions.output?.format || "console";
320
414
  const userOutputFile = options.outputFile || finalOptions.output?.file;
321
415
  if (outputFormat === "json") {
322
416
  const outputData = {
323
417
  results,
324
- summary: { ...summary, executionTime: parseFloat(elapsedTime) }
418
+ summary: { ...summary, executionTime: parseFloat(elapsedTime) },
419
+ ...contextScore && { scoring: contextScore }
325
420
  };
326
421
  const outputPath = (0, import_core.resolveOutputPath)(
327
422
  userOutputFile,
@@ -401,12 +496,19 @@ program.command("context").description("Run context window cost analysis").argum
401
496
  });
402
497
  console.log();
403
498
  }
499
+ if (contextScore) {
500
+ console.log(import_chalk.default.cyan(divider));
501
+ console.log(import_chalk.default.bold.white(" AI READINESS SCORE (Context)"));
502
+ console.log(import_chalk.default.cyan(divider) + "\n");
503
+ console.log((0, import_core.formatToolScore)(contextScore));
504
+ console.log();
505
+ }
404
506
  }
405
507
  } catch (error) {
406
508
  (0, import_core.handleCLIError)(error, "Context analysis");
407
509
  }
408
510
  });
409
- program.command("consistency").description("Check naming, patterns, and architecture consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").action(async (directory, options) => {
511
+ program.command("consistency").description("Check naming, patterns, and architecture consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score for consistency (0-100)").action(async (directory, options) => {
410
512
  console.log(import_chalk.default.blue("\u{1F50D} Analyzing consistency...\n"));
411
513
  const startTime = Date.now();
412
514
  try {
@@ -428,9 +530,14 @@ program.command("consistency").description("Check naming, patterns, and architec
428
530
  include: options.include?.split(","),
429
531
  exclude: options.exclude?.split(",")
430
532
  });
431
- const { analyzeConsistency: analyzeConsistency2 } = await import("@aiready/consistency");
533
+ const { analyzeConsistency: analyzeConsistency2, calculateConsistencyScore } = await import("@aiready/consistency");
432
534
  const report = await analyzeConsistency2(finalOptions);
433
535
  const elapsedTime = (0, import_core.getElapsedTime)(startTime);
536
+ let consistencyScore;
537
+ if (options.score) {
538
+ const issues = report.results?.flatMap((r) => r.issues) || [];
539
+ consistencyScore = calculateConsistencyScore(issues, report.summary.filesAnalyzed);
540
+ }
434
541
  const outputFormat = options.output || finalOptions.output?.format || "console";
435
542
  const userOutputFile = options.outputFile || finalOptions.output?.file;
436
543
  if (outputFormat === "json") {
@@ -439,7 +546,8 @@ program.command("consistency").description("Check naming, patterns, and architec
439
546
  summary: {
440
547
  ...report.summary,
441
548
  executionTime: parseFloat(elapsedTime)
442
- }
549
+ },
550
+ ...consistencyScore && { scoring: consistencyScore }
443
551
  };
444
552
  const outputPath = (0, import_core.resolveOutputPath)(
445
553
  userOutputFile,
@@ -528,6 +636,11 @@ program.command("consistency").description("Check naming, patterns, and architec
528
636
  console.log();
529
637
  }
530
638
  }
639
+ if (consistencyScore) {
640
+ console.log(import_chalk.default.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
641
+ console.log((0, import_core.formatToolScore)(consistencyScore));
642
+ console.log();
643
+ }
531
644
  }
532
645
  } catch (error) {
533
646
  (0, import_core.handleCLIError)(error, "Consistency analysis");
package/dist/cli.mjs CHANGED
@@ -2,19 +2,29 @@
2
2
  import {
3
3
  analyzeUnified,
4
4
  generateUnifiedSummary
5
- } from "./chunk-AGAMURT4.mjs";
5
+ } from "./chunk-P3XAXCTK.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { Command } from "commander";
9
9
  import chalk from "chalk";
10
10
  import { writeFileSync } from "fs";
11
11
  import { join } from "path";
12
- import { loadMergedConfig, handleJSONOutput, handleCLIError, getElapsedTime, resolveOutputPath } from "@aiready/core";
12
+ import {
13
+ loadMergedConfig,
14
+ handleJSONOutput,
15
+ handleCLIError,
16
+ getElapsedTime,
17
+ resolveOutputPath,
18
+ calculateOverallScore,
19
+ formatToolScore,
20
+ getRatingDisplay,
21
+ parseWeightString
22
+ } from "@aiready/core";
13
23
  import { readFileSync } from "fs";
14
24
  var packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
15
25
  var program = new Command();
16
26
  program.name("aiready").description("AIReady - Unified AI-readiness analysis tools").version(packageJson.version).addHelpText("after", "\nCONFIGURATION:\n Supports config files: aiready.json, aiready.config.json, .aiready.json, .aireadyrc.json, aiready.config.js, .aireadyrc.js\n CLI options override config file settings");
17
- program.command("scan").description("Run unified analysis on a codebase").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
27
+ program.command("scan").description("Run unified analysis on a codebase").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score (0-100)").option("--weights <weights>", 'Override tool weights for scoring (e.g., "patterns:50,context:30,consistency:20")').option("--threshold <score>", "Minimum passing score for CI/CD (exits with code 1 if below)").action(async (directory, options) => {
18
28
  console.log(chalk.blue("\u{1F680} Starting AIReady unified analysis...\n"));
19
29
  const startTime = Date.now();
20
30
  try {
@@ -40,6 +50,47 @@ program.command("scan").description("Run unified analysis on a codebase").argume
40
50
  }
41
51
  const results = await analyzeUnified(finalOptions);
42
52
  const elapsedTime = getElapsedTime(startTime);
53
+ let scoringResult;
54
+ if (options.score || finalOptions.scoring?.showBreakdown) {
55
+ const toolScores = /* @__PURE__ */ new Map();
56
+ if (results.patterns && baseOptions.tools.includes("patterns")) {
57
+ const { calculatePatternScore } = await import("@aiready/pattern-detect");
58
+ const duplicates = results.duplicates || [];
59
+ const score = calculatePatternScore(duplicates, results.patterns.length);
60
+ toolScores.set("pattern-detect", score);
61
+ }
62
+ if (results.context && baseOptions.tools.includes("context")) {
63
+ const { calculateContextScore } = await import("@aiready/context-analyzer");
64
+ const summary = {
65
+ avgContextBudget: results.context.reduce((sum, r) => sum + r.contextBudget, 0) / results.context.length,
66
+ maxContextBudget: Math.max(...results.context.map((r) => r.contextBudget)),
67
+ avgImportDepth: results.context.reduce((sum, r) => sum + r.importDepth, 0) / results.context.length,
68
+ maxImportDepth: Math.max(...results.context.map((r) => r.importDepth)),
69
+ avgFragmentation: results.context.reduce((sum, r) => sum + r.fragmentationScore, 0) / results.context.length,
70
+ criticalIssues: results.context.filter((r) => r.severity === "critical").length,
71
+ majorIssues: results.context.filter((r) => r.severity === "major").length
72
+ };
73
+ const score = calculateContextScore(summary);
74
+ toolScores.set("context-analyzer", score);
75
+ }
76
+ if (results.consistency && baseOptions.tools.includes("consistency")) {
77
+ const { calculateConsistencyScore } = await import("@aiready/consistency");
78
+ const issues = results.consistency.results?.flatMap((r) => r.issues) || [];
79
+ const score = calculateConsistencyScore(issues, results.consistency.summary.filesAnalyzed);
80
+ toolScores.set("consistency", score);
81
+ }
82
+ const cliWeights = options.weights ? parseWeightString(options.weights) : void 0;
83
+ scoringResult = calculateOverallScore(toolScores, finalOptions, cliWeights);
84
+ if (options.threshold) {
85
+ const threshold = parseFloat(options.threshold);
86
+ if (scoringResult.overall < threshold) {
87
+ console.error(chalk.red(`
88
+ \u274C Score ${scoringResult.overall} is below threshold ${threshold}
89
+ `));
90
+ process.exit(1);
91
+ }
92
+ }
93
+ }
43
94
  const outputFormat = options.output || finalOptions.output?.format || "console";
44
95
  const userOutputFile = options.outputFile || finalOptions.output?.file;
45
96
  if (outputFormat === "json") {
@@ -48,7 +99,8 @@ program.command("scan").description("Run unified analysis on a codebase").argume
48
99
  summary: {
49
100
  ...results.summary,
50
101
  executionTime: parseFloat(elapsedTime)
51
- }
102
+ },
103
+ ...scoringResult && { scoring: scoringResult }
52
104
  };
53
105
  const outputPath = resolveOutputPath(
54
106
  userOutputFile,
@@ -58,12 +110,47 @@ program.command("scan").description("Run unified analysis on a codebase").argume
58
110
  handleJSONOutput(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
59
111
  } else {
60
112
  console.log(generateUnifiedSummary(results));
113
+ if (scoringResult) {
114
+ const terminalWidth = process.stdout.columns || 80;
115
+ const dividerWidth = Math.min(60, terminalWidth - 2);
116
+ const divider = "\u2501".repeat(dividerWidth);
117
+ console.log(chalk.cyan("\n" + divider));
118
+ console.log(chalk.bold.white(" AI READINESS SCORE"));
119
+ console.log(chalk.cyan(divider) + "\n");
120
+ const { emoji, color } = getRatingDisplay(scoringResult.rating);
121
+ const scoreColor = color === "green" ? chalk.green : color === "blue" ? chalk.blue : color === "yellow" ? chalk.yellow : chalk.red;
122
+ console.log(` ${emoji} Overall Score: ${scoreColor.bold(scoringResult.overall + "/100")} (${chalk.bold(scoringResult.rating)})`);
123
+ console.log(` ${chalk.dim("Timestamp:")} ${new Date(scoringResult.timestamp).toLocaleString()}
124
+ `);
125
+ if (scoringResult.breakdown.length > 0) {
126
+ console.log(chalk.bold(" Component Scores:\n"));
127
+ scoringResult.breakdown.forEach((tool) => {
128
+ const toolEmoji = tool.toolName === "pattern-detect" ? "\u{1F50D}" : tool.toolName === "context-analyzer" ? "\u{1F9E0}" : "\u{1F3F7}\uFE0F";
129
+ const weight = scoringResult.calculation.weights[tool.toolName];
130
+ console.log(` ${toolEmoji} ${chalk.white(tool.toolName.padEnd(20))} ${scoreColor(tool.score + "/100")} ${chalk.dim(`(weight: ${weight})`)}`);
131
+ });
132
+ console.log();
133
+ }
134
+ console.log(chalk.dim(` Weighted Formula: ${scoringResult.calculation.formula}`));
135
+ console.log(chalk.dim(` Normalized Score: ${scoringResult.calculation.normalized}
136
+ `));
137
+ const allRecommendations = scoringResult.breakdown.flatMap((tool) => tool.recommendations || []).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0, 5);
138
+ if (allRecommendations.length > 0) {
139
+ console.log(chalk.bold(" Top Recommendations:\n"));
140
+ allRecommendations.forEach((rec, i) => {
141
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
142
+ console.log(` ${i + 1}. ${priorityIcon} ${rec.action}`);
143
+ console.log(` ${chalk.dim(`Impact: +${rec.estimatedImpact} points`)}
144
+ `);
145
+ });
146
+ }
147
+ }
61
148
  }
62
149
  } catch (error) {
63
150
  handleCLIError(error, "Analysis");
64
151
  }
65
152
  });
66
- program.command("patterns").description("Run pattern detection analysis").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option("--max-candidates <number>", "Maximum candidates per block (performance tuning)").option("--min-shared-tokens <number>", "Minimum shared tokens for candidates (performance tuning)").option("--full-scan", "Disable smart defaults for comprehensive analysis (slower)").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
153
+ program.command("patterns").description("Run pattern detection analysis").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option("--max-candidates <number>", "Maximum candidates per block (performance tuning)").option("--min-shared-tokens <number>", "Minimum shared tokens for candidates (performance tuning)").option("--full-scan", "Disable smart defaults for comprehensive analysis (slower)").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for patterns (0-100)").action(async (directory, options) => {
67
154
  console.log(chalk.blue("\u{1F50D} Analyzing patterns...\n"));
68
155
  const startTime = Date.now();
69
156
  try {
@@ -95,16 +182,21 @@ program.command("patterns").description("Run pattern detection analysis").argume
95
182
  cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
96
183
  }
97
184
  const finalOptions = await loadMergedConfig(directory, defaults, cliOptions);
98
- const { analyzePatterns, generateSummary } = await import("@aiready/pattern-detect");
185
+ const { analyzePatterns, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
99
186
  const { results, duplicates } = await analyzePatterns(finalOptions);
100
187
  const elapsedTime = getElapsedTime(startTime);
101
188
  const summary = generateSummary(results);
189
+ let patternScore;
190
+ if (options.score) {
191
+ patternScore = calculatePatternScore(duplicates, results.length);
192
+ }
102
193
  const outputFormat = options.output || finalOptions.output?.format || "console";
103
194
  const userOutputFile = options.outputFile || finalOptions.output?.file;
104
195
  if (outputFormat === "json") {
105
196
  const outputData = {
106
197
  results,
107
- summary: { ...summary, executionTime: parseFloat(elapsedTime) }
198
+ summary: { ...summary, executionTime: parseFloat(elapsedTime) },
199
+ ...patternScore && { scoring: patternScore }
108
200
  };
109
201
  const outputPath = resolveOutputPath(
110
202
  userOutputFile,
@@ -150,12 +242,19 @@ program.command("patterns").description("Run pattern detection analysis").argume
150
242
  } else {
151
243
  console.log(chalk.green("\n\u2728 Great! No duplicate patterns detected.\n"));
152
244
  }
245
+ if (patternScore) {
246
+ console.log(chalk.cyan(divider));
247
+ console.log(chalk.bold.white(" AI READINESS SCORE (Patterns)"));
248
+ console.log(chalk.cyan(divider) + "\n");
249
+ console.log(formatToolScore(patternScore));
250
+ console.log();
251
+ }
153
252
  }
154
253
  } catch (error) {
155
254
  handleCLIError(error, "Pattern analysis");
156
255
  }
157
256
  });
158
- program.command("context").description("Run context window cost analysis").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
257
+ program.command("context").description("Run context window cost analysis").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option("--score", "Calculate and display AI Readiness Score for context (0-100)").action(async (directory, options) => {
159
258
  console.log(chalk.blue("\u{1F9E0} Analyzing context costs...\n"));
160
259
  const startTime = Date.now();
161
260
  try {
@@ -186,16 +285,21 @@ program.command("context").description("Run context window cost analysis").argum
186
285
  console.log(` Max fragmentation: ${(finalOptions.maxFragmentation * 100).toFixed(1)}%`);
187
286
  console.log(` Analysis focus: ${finalOptions.focus}`);
188
287
  console.log("");
189
- const { analyzeContext, generateSummary } = await import("@aiready/context-analyzer");
288
+ const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
190
289
  const results = await analyzeContext(finalOptions);
191
290
  const elapsedTime = getElapsedTime(startTime);
192
291
  const summary = generateSummary(results);
292
+ let contextScore;
293
+ if (options.score) {
294
+ contextScore = calculateContextScore(summary);
295
+ }
193
296
  const outputFormat = options.output || finalOptions.output?.format || "console";
194
297
  const userOutputFile = options.outputFile || finalOptions.output?.file;
195
298
  if (outputFormat === "json") {
196
299
  const outputData = {
197
300
  results,
198
- summary: { ...summary, executionTime: parseFloat(elapsedTime) }
301
+ summary: { ...summary, executionTime: parseFloat(elapsedTime) },
302
+ ...contextScore && { scoring: contextScore }
199
303
  };
200
304
  const outputPath = resolveOutputPath(
201
305
  userOutputFile,
@@ -275,12 +379,19 @@ program.command("context").description("Run context window cost analysis").argum
275
379
  });
276
380
  console.log();
277
381
  }
382
+ if (contextScore) {
383
+ console.log(chalk.cyan(divider));
384
+ console.log(chalk.bold.white(" AI READINESS SCORE (Context)"));
385
+ console.log(chalk.cyan(divider) + "\n");
386
+ console.log(formatToolScore(contextScore));
387
+ console.log();
388
+ }
278
389
  }
279
390
  } catch (error) {
280
391
  handleCLIError(error, "Context analysis");
281
392
  }
282
393
  });
283
- program.command("consistency").description("Check naming, patterns, and architecture consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").action(async (directory, options) => {
394
+ program.command("consistency").description("Check naming, patterns, and architecture consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score for consistency (0-100)").action(async (directory, options) => {
284
395
  console.log(chalk.blue("\u{1F50D} Analyzing consistency...\n"));
285
396
  const startTime = Date.now();
286
397
  try {
@@ -302,9 +413,14 @@ program.command("consistency").description("Check naming, patterns, and architec
302
413
  include: options.include?.split(","),
303
414
  exclude: options.exclude?.split(",")
304
415
  });
305
- const { analyzeConsistency } = await import("@aiready/consistency");
416
+ const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
306
417
  const report = await analyzeConsistency(finalOptions);
307
418
  const elapsedTime = getElapsedTime(startTime);
419
+ let consistencyScore;
420
+ if (options.score) {
421
+ const issues = report.results?.flatMap((r) => r.issues) || [];
422
+ consistencyScore = calculateConsistencyScore(issues, report.summary.filesAnalyzed);
423
+ }
308
424
  const outputFormat = options.output || finalOptions.output?.format || "console";
309
425
  const userOutputFile = options.outputFile || finalOptions.output?.file;
310
426
  if (outputFormat === "json") {
@@ -313,7 +429,8 @@ program.command("consistency").description("Check naming, patterns, and architec
313
429
  summary: {
314
430
  ...report.summary,
315
431
  executionTime: parseFloat(elapsedTime)
316
- }
432
+ },
433
+ ...consistencyScore && { scoring: consistencyScore }
317
434
  };
318
435
  const outputPath = resolveOutputPath(
319
436
  userOutputFile,
@@ -402,6 +519,11 @@ program.command("consistency").description("Check naming, patterns, and architec
402
519
  console.log();
403
520
  }
404
521
  }
522
+ if (consistencyScore) {
523
+ console.log(chalk.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
524
+ console.log(formatToolScore(consistencyScore));
525
+ console.log();
526
+ }
405
527
  }
406
528
  } catch (error) {
407
529
  handleCLIError(error, "Consistency analysis");
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ScanOptions, AnalysisResult } from '@aiready/core';
2
2
  import { ContextAnalysisResult } from '@aiready/context-analyzer';
3
+ import { DuplicatePattern } from '@aiready/pattern-detect';
3
4
  import { ConsistencyReport } from '@aiready/consistency';
4
5
 
5
6
  interface UnifiedAnalysisOptions extends ScanOptions {
@@ -12,6 +13,7 @@ interface UnifiedAnalysisOptions extends ScanOptions {
12
13
  }
13
14
  interface UnifiedAnalysisResult {
14
15
  patterns?: AnalysisResult[];
16
+ duplicates?: DuplicatePattern[];
15
17
  context?: ContextAnalysisResult[];
16
18
  consistency?: ConsistencyReport;
17
19
  summary: {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ScanOptions, AnalysisResult } from '@aiready/core';
2
2
  import { ContextAnalysisResult } from '@aiready/context-analyzer';
3
+ import { DuplicatePattern } from '@aiready/pattern-detect';
3
4
  import { ConsistencyReport } from '@aiready/consistency';
4
5
 
5
6
  interface UnifiedAnalysisOptions extends ScanOptions {
@@ -12,6 +13,7 @@ interface UnifiedAnalysisOptions extends ScanOptions {
12
13
  }
13
14
  interface UnifiedAnalysisResult {
14
15
  patterns?: AnalysisResult[];
16
+ duplicates?: DuplicatePattern[];
15
17
  context?: ContextAnalysisResult[];
16
18
  consistency?: ConsistencyReport;
17
19
  summary: {
package/dist/index.js CHANGED
@@ -66,6 +66,7 @@ async function analyzeUnified(options) {
66
66
  if (tools.includes("patterns")) {
67
67
  const patternResult = await (0, import_pattern_detect.analyzePatterns)(options);
68
68
  result.patterns = sortBySeverity(patternResult.results);
69
+ result.duplicates = patternResult.duplicates;
69
70
  result.summary.totalIssues += patternResult.results.reduce(
70
71
  (sum, file) => sum + file.issues.length,
71
72
  0
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  analyzeUnified,
3
3
  generateUnifiedSummary
4
- } from "./chunk-AGAMURT4.mjs";
4
+ } from "./chunk-P3XAXCTK.mjs";
5
5
  export {
6
6
  analyzeUnified,
7
7
  generateUnifiedSummary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/cli",
3
- "version": "0.7.16",
3
+ "version": "0.7.18",
4
4
  "description": "Unified CLI for AIReady analysis tools",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -9,16 +9,16 @@
9
9
  "aiready": "./dist/cli.js"
10
10
  },
11
11
  "dependencies": {
12
- "commander": "^12.1.0",
12
+ "commander": "^14.0.0",
13
13
  "chalk": "^5.3.0",
14
- "@aiready/core": "0.7.9",
15
- "@aiready/context-analyzer": "0.7.12",
16
- "@aiready/pattern-detect": "0.9.17",
17
- "@aiready/consistency": "0.6.12"
14
+ "@aiready/context-analyzer": "0.7.16",
15
+ "@aiready/consistency": "0.6.14",
16
+ "@aiready/core": "0.7.11",
17
+ "@aiready/pattern-detect": "0.9.20"
18
18
  },
19
19
  "devDependencies": {
20
20
  "tsup": "^8.3.5",
21
- "@types/node": "^20.0.0"
21
+ "@types/node": "^24.0.0"
22
22
  },
23
23
  "keywords": [
24
24
  "aiready",
package/src/cli.ts CHANGED
@@ -5,7 +5,20 @@ import { analyzeUnified, generateUnifiedSummary } from './index';
5
5
  import chalk from 'chalk';
6
6
  import { writeFileSync } from 'fs';
7
7
  import { join } from 'path';
8
- import { loadMergedConfig, handleJSONOutput, handleCLIError, getElapsedTime, resolveOutputPath } from '@aiready/core';
8
+ import {
9
+ loadMergedConfig,
10
+ handleJSONOutput,
11
+ handleCLIError,
12
+ getElapsedTime,
13
+ resolveOutputPath,
14
+ calculateOverallScore,
15
+ formatScore,
16
+ formatToolScore,
17
+ getRatingDisplay,
18
+ parseWeightString,
19
+ type AIReadyConfig,
20
+ type ToolScoringOutput,
21
+ } from '@aiready/core';
9
22
  import { readFileSync } from 'fs';
10
23
 
11
24
  const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
@@ -27,6 +40,9 @@ program
27
40
  .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
28
41
  .option('-o, --output <format>', 'Output format: console, json', 'console')
29
42
  .option('--output-file <path>', 'Output file path (for json)')
43
+ .option('--score', 'Calculate and display AI Readiness Score (0-100)')
44
+ .option('--weights <weights>', 'Override tool weights for scoring (e.g., "patterns:50,context:30,consistency:20")')
45
+ .option('--threshold <score>', 'Minimum passing score for CI/CD (exits with code 1 if below)')
30
46
  .action(async (directory, options) => {
31
47
  console.log(chalk.blue('🚀 Starting AIReady unified analysis...\n'));
32
48
 
@@ -63,6 +79,59 @@ program
63
79
 
64
80
  const elapsedTime = getElapsedTime(startTime);
65
81
 
82
+ // Calculate score if requested
83
+ let scoringResult: ReturnType<typeof calculateOverallScore> | undefined;
84
+ if (options.score || finalOptions.scoring?.showBreakdown) {
85
+ const toolScores: Map<string, ToolScoringOutput> = new Map();
86
+
87
+ // Collect scores from each tool that was run
88
+ if (results.patterns && baseOptions.tools.includes('patterns')) {
89
+ const { calculatePatternScore } = await import('@aiready/pattern-detect');
90
+ // Use the actual duplicates array which has tokenCost field
91
+ const duplicates = results.duplicates || [];
92
+ const score = calculatePatternScore(duplicates, results.patterns.length);
93
+ toolScores.set('pattern-detect', score);
94
+ }
95
+
96
+ if (results.context && baseOptions.tools.includes('context')) {
97
+ const { calculateContextScore } = await import('@aiready/context-analyzer');
98
+ // Calculate summary from context results
99
+ const summary = {
100
+ avgContextBudget: results.context.reduce((sum, r) => sum + r.contextBudget, 0) / results.context.length,
101
+ maxContextBudget: Math.max(...results.context.map(r => r.contextBudget)),
102
+ avgImportDepth: results.context.reduce((sum, r) => sum + r.importDepth, 0) / results.context.length,
103
+ maxImportDepth: Math.max(...results.context.map(r => r.importDepth)),
104
+ avgFragmentation: results.context.reduce((sum, r) => sum + r.fragmentationScore, 0) / results.context.length,
105
+ criticalIssues: results.context.filter(r => r.severity === 'critical').length,
106
+ majorIssues: results.context.filter(r => r.severity === 'major').length,
107
+ };
108
+ const score = calculateContextScore(summary as any);
109
+ toolScores.set('context-analyzer', score);
110
+ }
111
+
112
+ if (results.consistency && baseOptions.tools.includes('consistency')) {
113
+ const { calculateConsistencyScore } = await import('@aiready/consistency');
114
+ const issues = results.consistency.results?.flatMap((r: any) => r.issues) || [];
115
+ const score = calculateConsistencyScore(issues, results.consistency.summary.filesAnalyzed);
116
+ toolScores.set('consistency', score);
117
+ }
118
+
119
+ // Parse weight overrides from CLI
120
+ const cliWeights = options.weights ? parseWeightString(options.weights) : undefined;
121
+
122
+ // Calculate overall score
123
+ scoringResult = calculateOverallScore(toolScores, finalOptions as AIReadyConfig, cliWeights);
124
+
125
+ // Check threshold
126
+ if (options.threshold) {
127
+ const threshold = parseFloat(options.threshold);
128
+ if (scoringResult.overall < threshold) {
129
+ console.error(chalk.red(`\n❌ Score ${scoringResult.overall} is below threshold ${threshold}\n`));
130
+ process.exit(1);
131
+ }
132
+ }
133
+ }
134
+
66
135
  const outputFormat = options.output || finalOptions.output?.format || 'console';
67
136
  const userOutputFile = options.outputFile || finalOptions.output?.file;
68
137
 
@@ -73,6 +142,7 @@ program
73
142
  ...results.summary,
74
143
  executionTime: parseFloat(elapsedTime),
75
144
  },
145
+ ...(scoringResult && { scoring: scoringResult }),
76
146
  };
77
147
 
78
148
  const outputPath = resolveOutputPath(
@@ -85,6 +155,56 @@ program
85
155
  } else {
86
156
  // Console output
87
157
  console.log(generateUnifiedSummary(results));
158
+
159
+ // Display score if calculated
160
+ if (scoringResult) {
161
+ const terminalWidth = process.stdout.columns || 80;
162
+ const dividerWidth = Math.min(60, terminalWidth - 2);
163
+ const divider = '━'.repeat(dividerWidth);
164
+
165
+ console.log(chalk.cyan('\n' + divider));
166
+ console.log(chalk.bold.white(' AI READINESS SCORE'));
167
+ console.log(chalk.cyan(divider) + '\n');
168
+
169
+ const { emoji, color } = getRatingDisplay(scoringResult.rating);
170
+ const scoreColor = color === 'green' ? chalk.green :
171
+ color === 'blue' ? chalk.blue :
172
+ color === 'yellow' ? chalk.yellow : chalk.red;
173
+
174
+ console.log(` ${emoji} Overall Score: ${scoreColor.bold(scoringResult.overall + '/100')} (${chalk.bold(scoringResult.rating)})`); console.log(` ${chalk.dim('Timestamp:')} ${new Date(scoringResult.timestamp).toLocaleString()}\n`);
175
+
176
+ // Show breakdown by tool
177
+ if (scoringResult.breakdown.length > 0) {
178
+ console.log(chalk.bold(' Component Scores:\n'));
179
+ scoringResult.breakdown.forEach(tool => {
180
+ const toolEmoji = tool.toolName === 'pattern-detect' ? '🔍' :
181
+ tool.toolName === 'context-analyzer' ? '🧠' : '🏷️';
182
+ const weight = scoringResult.calculation.weights[tool.toolName];
183
+ console.log(` ${toolEmoji} ${chalk.white(tool.toolName.padEnd(20))} ${scoreColor(tool.score + '/100')} ${chalk.dim(`(weight: ${weight})`)}`);
184
+ });
185
+ console.log();
186
+ }
187
+
188
+ // Show calculation
189
+ console.log(chalk.dim(` Weighted Formula: ${scoringResult.calculation.formula}`));
190
+ console.log(chalk.dim(` Normalized Score: ${scoringResult.calculation.normalized}\n`));
191
+
192
+ // Show top recommendations across all tools
193
+ const allRecommendations = scoringResult.breakdown
194
+ .flatMap(tool => tool.recommendations || [])
195
+ .sort((a, b) => b.estimatedImpact - a.estimatedImpact)
196
+ .slice(0, 5);
197
+
198
+ if (allRecommendations.length > 0) {
199
+ console.log(chalk.bold(' Top Recommendations:\n'));
200
+ allRecommendations.forEach((rec, i) => {
201
+ const priorityIcon = rec.priority === 'high' ? '🔴' :
202
+ rec.priority === 'medium' ? '🟡' : '🔵';
203
+ console.log(` ${i + 1}. ${priorityIcon} ${rec.action}`);
204
+ console.log(` ${chalk.dim(`Impact: +${rec.estimatedImpact} points`)}\n`);
205
+ });
206
+ }
207
+ }
88
208
  }
89
209
  } catch (error) {
90
210
  handleCLIError(error, 'Analysis');
@@ -105,6 +225,7 @@ program
105
225
  .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
106
226
  .option('-o, --output <format>', 'Output format: console, json', 'console')
107
227
  .option('--output-file <path>', 'Output file path (for json)')
228
+ .option('--score', 'Calculate and display AI Readiness Score for patterns (0-100)')
108
229
  .action(async (directory, options) => {
109
230
  console.log(chalk.blue('🔍 Analyzing patterns...\n'));
110
231
 
@@ -150,12 +271,18 @@ program
150
271
 
151
272
  const finalOptions = await loadMergedConfig(directory, defaults, cliOptions);
152
273
 
153
- const { analyzePatterns, generateSummary } = await import('@aiready/pattern-detect');
274
+ const { analyzePatterns, generateSummary, calculatePatternScore } = await import('@aiready/pattern-detect');
154
275
 
155
276
  const { results, duplicates } = await analyzePatterns(finalOptions);
156
277
 
157
278
  const elapsedTime = getElapsedTime(startTime);
158
279
  const summary = generateSummary(results);
280
+
281
+ // Calculate score if requested
282
+ let patternScore: ToolScoringOutput | undefined;
283
+ if (options.score) {
284
+ patternScore = calculatePatternScore(duplicates, results.length);
285
+ }
159
286
 
160
287
  const outputFormat = options.output || finalOptions.output?.format || 'console';
161
288
  const userOutputFile = options.outputFile || finalOptions.output?.file;
@@ -164,6 +291,7 @@ program
164
291
  const outputData = {
165
292
  results,
166
293
  summary: { ...summary, executionTime: parseFloat(elapsedTime) },
294
+ ...(patternScore && { scoring: patternScore }),
167
295
  };
168
296
 
169
297
  const outputPath = resolveOutputPath(
@@ -225,6 +353,15 @@ program
225
353
  } else {
226
354
  console.log(chalk.green('\n✨ Great! No duplicate patterns detected.\n'));
227
355
  }
356
+
357
+ // Display score if calculated
358
+ if (patternScore) {
359
+ console.log(chalk.cyan(divider));
360
+ console.log(chalk.bold.white(' AI READINESS SCORE (Patterns)'));
361
+ console.log(chalk.cyan(divider) + '\n');
362
+ console.log(formatToolScore(patternScore));
363
+ console.log();
364
+ }
228
365
  }
229
366
  } catch (error) {
230
367
  handleCLIError(error, 'Pattern analysis');
@@ -241,6 +378,7 @@ program
241
378
  .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
242
379
  .option('-o, --output <format>', 'Output format: console, json', 'console')
243
380
  .option('--output-file <path>', 'Output file path (for json)')
381
+ .option('--score', 'Calculate and display AI Readiness Score for context (0-100)')
244
382
  .action(async (directory, options) => {
245
383
  console.log(chalk.blue('🧠 Analyzing context costs...\n'));
246
384
 
@@ -282,12 +420,18 @@ program
282
420
  console.log(` Analysis focus: ${finalOptions.focus}`);
283
421
  console.log('');
284
422
 
285
- const { analyzeContext, generateSummary } = await import('@aiready/context-analyzer');
423
+ const { analyzeContext, generateSummary, calculateContextScore } = await import('@aiready/context-analyzer');
286
424
 
287
425
  const results = await analyzeContext(finalOptions);
288
426
 
289
427
  const elapsedTime = getElapsedTime(startTime);
290
428
  const summary = generateSummary(results);
429
+
430
+ // Calculate score if requested
431
+ let contextScore: ToolScoringOutput | undefined;
432
+ if (options.score) {
433
+ contextScore = calculateContextScore(summary as any);
434
+ }
291
435
 
292
436
  const outputFormat = options.output || finalOptions.output?.format || 'console';
293
437
  const userOutputFile = options.outputFile || finalOptions.output?.file;
@@ -296,6 +440,7 @@ program
296
440
  const outputData = {
297
441
  results,
298
442
  summary: { ...summary, executionTime: parseFloat(elapsedTime) },
443
+ ...(contextScore && { scoring: contextScore }),
299
444
  };
300
445
 
301
446
  const outputPath = resolveOutputPath(
@@ -384,6 +529,15 @@ program
384
529
  });
385
530
  console.log();
386
531
  }
532
+
533
+ // Display score if calculated
534
+ if (contextScore) {
535
+ console.log(chalk.cyan(divider));
536
+ console.log(chalk.bold.white(' AI READINESS SCORE (Context)'));
537
+ console.log(chalk.cyan(divider) + '\n');
538
+ console.log(formatToolScore(contextScore));
539
+ console.log();
540
+ }
387
541
  }
388
542
  } catch (error) {
389
543
  handleCLIError(error, 'Context analysis');
@@ -403,6 +557,7 @@ program
403
557
  .option('--exclude <patterns>', 'File patterns to exclude (comma-separated)')
404
558
  .option('-o, --output <format>', 'Output format: console, json, markdown', 'console')
405
559
  .option('--output-file <path>', 'Output file path (for json/markdown)')
560
+ .option('--score', 'Calculate and display AI Readiness Score for consistency (0-100)')
406
561
  .action(async (directory, options) => {
407
562
  console.log(chalk.blue('🔍 Analyzing consistency...\n'));
408
563
 
@@ -431,11 +586,18 @@ program
431
586
  exclude: options.exclude?.split(','),
432
587
  });
433
588
 
434
- const { analyzeConsistency } = await import('@aiready/consistency');
589
+ const { analyzeConsistency, calculateConsistencyScore } = await import('@aiready/consistency');
435
590
 
436
591
  const report = await analyzeConsistency(finalOptions);
437
592
 
438
593
  const elapsedTime = getElapsedTime(startTime);
594
+
595
+ // Calculate score if requested
596
+ let consistencyScore: ToolScoringOutput | undefined;
597
+ if (options.score) {
598
+ const issues = report.results?.flatMap((r: any) => r.issues) || [];
599
+ consistencyScore = calculateConsistencyScore(issues, report.summary.filesAnalyzed);
600
+ }
439
601
 
440
602
  const outputFormat = options.output || finalOptions.output?.format || 'console';
441
603
  const userOutputFile = options.outputFile || finalOptions.output?.file;
@@ -447,6 +609,7 @@ program
447
609
  ...report.summary,
448
610
  executionTime: parseFloat(elapsedTime),
449
611
  },
612
+ ...(consistencyScore && { scoring: consistencyScore }),
450
613
  };
451
614
 
452
615
  const outputPath = resolveOutputPath(
@@ -545,6 +708,13 @@ program
545
708
  console.log();
546
709
  }
547
710
  }
711
+
712
+ // Display score if calculated
713
+ if (consistencyScore) {
714
+ console.log(chalk.bold('\n📊 AI Readiness Score (Consistency)\n'));
715
+ console.log(formatToolScore(consistencyScore));
716
+ console.log();
717
+ }
548
718
  }
549
719
  } catch (error) {
550
720
  handleCLIError(error, 'Consistency analysis');
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import { analyzeContext } from '@aiready/context-analyzer';
3
3
  import { analyzeConsistency } from '@aiready/consistency';
4
4
  import type { AnalysisResult, ScanOptions } from '@aiready/core';
5
5
  import type { ContextAnalysisResult } from '@aiready/context-analyzer';
6
- import type { PatternDetectOptions } from '@aiready/pattern-detect';
6
+ import type { PatternDetectOptions, DuplicatePattern } from '@aiready/pattern-detect';
7
7
  import type { ConsistencyReport } from '@aiready/consistency';
8
8
 
9
9
  export interface UnifiedAnalysisOptions extends ScanOptions {
@@ -17,6 +17,7 @@ export interface UnifiedAnalysisOptions extends ScanOptions {
17
17
 
18
18
  export interface UnifiedAnalysisResult {
19
19
  patterns?: AnalysisResult[];
20
+ duplicates?: DuplicatePattern[]; // Store actual duplicates for scoring
20
21
  context?: ContextAnalysisResult[];
21
22
  consistency?: ConsistencyReport;
22
23
  summary: {
@@ -80,6 +81,8 @@ export async function analyzeUnified(
80
81
  const patternResult = await analyzePatterns(options);
81
82
  // Sort results by severity
82
83
  result.patterns = sortBySeverity(patternResult.results);
84
+ // Store duplicates for scoring
85
+ result.duplicates = patternResult.duplicates;
83
86
  // Count actual issues, not file count
84
87
  result.summary.totalIssues += patternResult.results.reduce(
85
88
  (sum, file) => sum + file.issues.length,