@aiready/cli 0.9.40 → 0.9.43

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.
@@ -4,7 +4,7 @@
4
4
 
5
5
  import chalk from 'chalk';
6
6
  import { writeFileSync, readFileSync } from 'fs';
7
- import { join } from 'path';
7
+ // 'join' was unused
8
8
  import { resolve as resolvePath } from 'path';
9
9
  import {
10
10
  loadMergedConfig,
@@ -21,7 +21,11 @@ import {
21
21
  type ToolScoringOutput,
22
22
  } from '@aiready/core';
23
23
  import { analyzeUnified } from '../index';
24
- import { getReportTimestamp, warnIfGraphCapExceeded, truncateArray } from '../utils/helpers';
24
+ import {
25
+ getReportTimestamp,
26
+ warnIfGraphCapExceeded,
27
+ truncateArray,
28
+ } from '../utils/helpers';
25
29
 
26
30
  interface ScanOptions {
27
31
  tools?: string;
@@ -49,7 +53,17 @@ export async function scanAction(directory: string, options: ScanOptions) {
49
53
  try {
50
54
  // Define defaults
51
55
  const defaults = {
52
- tools: ['patterns', 'context', 'consistency', 'aiSignalClarity', 'grounding', 'testability', 'doc-drift', 'deps-health', 'changeAmplification'],
56
+ tools: [
57
+ 'patterns',
58
+ 'context',
59
+ 'consistency',
60
+ 'aiSignalClarity',
61
+ 'grounding',
62
+ 'testability',
63
+ 'doc-drift',
64
+ 'deps-health',
65
+ 'changeAmplification',
66
+ ],
53
67
  include: undefined,
54
68
  exclude: undefined,
55
69
  output: {
@@ -58,11 +72,14 @@ export async function scanAction(directory: string, options: ScanOptions) {
58
72
  },
59
73
  };
60
74
 
61
- let profileTools = options.tools ? options.tools.split(',').map((t: string) => {
62
- const tool = t.trim();
63
- if (tool === 'hallucination' || tool === 'hallucination-risk') return 'aiSignalClarity';
64
- return tool;
65
- }) : undefined;
75
+ let profileTools = options.tools
76
+ ? options.tools.split(',').map((t: string) => {
77
+ const tool = t.trim();
78
+ if (tool === 'hallucination' || tool === 'hallucination-risk')
79
+ return 'aiSignalClarity';
80
+ return tool;
81
+ })
82
+ : undefined;
66
83
  if (options.profile) {
67
84
  switch (options.profile.toLowerCase()) {
68
85
  case 'agentic':
@@ -78,7 +95,11 @@ export async function scanAction(directory: string, options: ScanOptions) {
78
95
  profileTools = ['context', 'consistency', 'grounding'];
79
96
  break;
80
97
  default:
81
- console.log(chalk.yellow(`\n⚠️ Unknown profile '${options.profile}'. Using specified tools or defaults.`));
98
+ console.log(
99
+ chalk.yellow(
100
+ `\n⚠️ Unknown profile '${options.profile}'. Using specified tools or defaults.`
101
+ )
102
+ );
82
103
  }
83
104
  }
84
105
 
@@ -91,28 +112,48 @@ export async function scanAction(directory: string, options: ScanOptions) {
91
112
  cliOverrides.tools = profileTools;
92
113
  }
93
114
 
94
- const baseOptions = await loadMergedConfig(resolvedDir, defaults, cliOverrides) as any;
95
-
115
+ const baseOptions = (await loadMergedConfig(
116
+ resolvedDir,
117
+ defaults,
118
+ cliOverrides
119
+ )) as any;
96
120
 
97
121
  // Apply smart defaults for pattern detection if patterns tool is enabled
98
122
  let finalOptions = { ...baseOptions };
99
123
  if (baseOptions.tools.includes('patterns')) {
100
124
  const { getSmartDefaults } = await import('@aiready/pattern-detect');
101
- const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
125
+ const patternSmartDefaults = await getSmartDefaults(
126
+ resolvedDir,
127
+ baseOptions
128
+ );
102
129
  // Merge deeply to preserve nested config
103
- finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
130
+ finalOptions = {
131
+ ...patternSmartDefaults,
132
+ ...finalOptions,
133
+ ...baseOptions,
134
+ };
104
135
  }
105
136
 
106
137
  // Print pre-run summary with expanded settings (truncate long arrays)
107
138
  console.log(chalk.cyan('\n=== AIReady Run Preview ==='));
108
- console.log(chalk.white('Tools to run:'), (finalOptions.tools || ['patterns', 'context', 'consistency']).join(', '));
139
+ console.log(
140
+ chalk.white('Tools to run:'),
141
+ (finalOptions.tools || ['patterns', 'context', 'consistency']).join(', ')
142
+ );
109
143
  console.log(chalk.white('Will use settings from config and defaults.'));
110
144
 
111
145
  // Common top-level settings
112
146
  console.log(chalk.white('\nGeneral settings:'));
113
- if (finalOptions.rootDir) console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
114
- if (finalOptions.include) console.log(` include: ${chalk.bold(truncateArray(finalOptions.include, 6))}`);
115
- if (finalOptions.exclude) console.log(` exclude: ${chalk.bold(truncateArray(finalOptions.exclude, 6))}`);
147
+ if (finalOptions.rootDir)
148
+ console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
149
+ if (finalOptions.include)
150
+ console.log(
151
+ ` include: ${chalk.bold(truncateArray(finalOptions.include, 6))}`
152
+ );
153
+ if (finalOptions.exclude)
154
+ console.log(
155
+ ` exclude: ${chalk.bold(truncateArray(finalOptions.exclude, 6))}`
156
+ );
116
157
 
117
158
  if (finalOptions['pattern-detect'] || finalOptions.minSimilarity) {
118
159
  const patternDetectConfig = finalOptions['pattern-detect'] || {
@@ -127,15 +168,40 @@ export async function scanAction(directory: string, options: ScanOptions) {
127
168
  includeTests: (finalOptions as any).includeTests,
128
169
  };
129
170
  console.log(chalk.white('\nPattern-detect settings:'));
130
- console.log(` minSimilarity: ${chalk.bold(patternDetectConfig.minSimilarity ?? 'default')}`);
131
- console.log(` minLines: ${chalk.bold(patternDetectConfig.minLines ?? 'default')}`);
132
- if (patternDetectConfig.approx !== undefined) console.log(` approx: ${chalk.bold(String(patternDetectConfig.approx))}`);
133
- if (patternDetectConfig.minSharedTokens !== undefined) console.log(` minSharedTokens: ${chalk.bold(String(patternDetectConfig.minSharedTokens))}`);
134
- if (patternDetectConfig.maxCandidatesPerBlock !== undefined) console.log(` maxCandidatesPerBlock: ${chalk.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`);
135
- if (patternDetectConfig.batchSize !== undefined) console.log(` batchSize: ${chalk.bold(String(patternDetectConfig.batchSize))}`);
136
- if (patternDetectConfig.streamResults !== undefined) console.log(` streamResults: ${chalk.bold(String(patternDetectConfig.streamResults))}`);
137
- if (patternDetectConfig.severity !== undefined) console.log(` severity: ${chalk.bold(String(patternDetectConfig.severity))}`);
138
- if (patternDetectConfig.includeTests !== undefined) console.log(` includeTests: ${chalk.bold(String(patternDetectConfig.includeTests))}`);
171
+ console.log(
172
+ ` minSimilarity: ${chalk.bold(patternDetectConfig.minSimilarity ?? 'default')}`
173
+ );
174
+ console.log(
175
+ ` minLines: ${chalk.bold(patternDetectConfig.minLines ?? 'default')}`
176
+ );
177
+ if (patternDetectConfig.approx !== undefined)
178
+ console.log(
179
+ ` approx: ${chalk.bold(String(patternDetectConfig.approx))}`
180
+ );
181
+ if (patternDetectConfig.minSharedTokens !== undefined)
182
+ console.log(
183
+ ` minSharedTokens: ${chalk.bold(String(patternDetectConfig.minSharedTokens))}`
184
+ );
185
+ if (patternDetectConfig.maxCandidatesPerBlock !== undefined)
186
+ console.log(
187
+ ` maxCandidatesPerBlock: ${chalk.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`
188
+ );
189
+ if (patternDetectConfig.batchSize !== undefined)
190
+ console.log(
191
+ ` batchSize: ${chalk.bold(String(patternDetectConfig.batchSize))}`
192
+ );
193
+ if (patternDetectConfig.streamResults !== undefined)
194
+ console.log(
195
+ ` streamResults: ${chalk.bold(String(patternDetectConfig.streamResults))}`
196
+ );
197
+ if (patternDetectConfig.severity !== undefined)
198
+ console.log(
199
+ ` severity: ${chalk.bold(String(patternDetectConfig.severity))}`
200
+ );
201
+ if (patternDetectConfig.includeTests !== undefined)
202
+ console.log(
203
+ ` includeTests: ${chalk.bold(String(patternDetectConfig.includeTests))}`
204
+ );
139
205
  }
140
206
 
141
207
  if (finalOptions['context-analyzer'] || finalOptions.maxDepth) {
@@ -148,21 +214,43 @@ export async function scanAction(directory: string, options: ScanOptions) {
148
214
  };
149
215
  console.log(chalk.white('\nContext-analyzer settings:'));
150
216
  console.log(` maxDepth: ${chalk.bold(ca.maxDepth ?? 'default')}`);
151
- console.log(` maxContextBudget: ${chalk.bold(ca.maxContextBudget ?? 'default')}`);
152
- if (ca.minCohesion !== undefined) console.log(` minCohesion: ${chalk.bold(String(ca.minCohesion))}`);
153
- if (ca.maxFragmentation !== undefined) console.log(` maxFragmentation: ${chalk.bold(String(ca.maxFragmentation))}`);
154
- if (ca.includeNodeModules !== undefined) console.log(` includeNodeModules: ${chalk.bold(String(ca.includeNodeModules))}`);
217
+ console.log(
218
+ ` maxContextBudget: ${chalk.bold(ca.maxContextBudget ?? 'default')}`
219
+ );
220
+ if (ca.minCohesion !== undefined)
221
+ console.log(` minCohesion: ${chalk.bold(String(ca.minCohesion))}`);
222
+ if (ca.maxFragmentation !== undefined)
223
+ console.log(
224
+ ` maxFragmentation: ${chalk.bold(String(ca.maxFragmentation))}`
225
+ );
226
+ if (ca.includeNodeModules !== undefined)
227
+ console.log(
228
+ ` includeNodeModules: ${chalk.bold(String(ca.includeNodeModules))}`
229
+ );
155
230
  }
156
231
 
157
232
  if (finalOptions.consistency) {
158
233
  const c = finalOptions.consistency;
159
234
  console.log(chalk.white('\nConsistency settings:'));
160
- console.log(` checkNaming: ${chalk.bold(String(c.checkNaming ?? true))}`);
161
- console.log(` checkPatterns: ${chalk.bold(String(c.checkPatterns ?? true))}`);
162
- console.log(` checkArchitecture: ${chalk.bold(String(c.checkArchitecture ?? false))}`);
163
- if (c.minSeverity) console.log(` minSeverity: ${chalk.bold(c.minSeverity)}`);
164
- if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${chalk.bold(truncateArray(c.acceptedAbbreviations, 8))}`);
165
- if (c.shortWords) console.log(` shortWords: ${chalk.bold(truncateArray(c.shortWords, 8))}`);
235
+ console.log(
236
+ ` checkNaming: ${chalk.bold(String(c.checkNaming ?? true))}`
237
+ );
238
+ console.log(
239
+ ` checkPatterns: ${chalk.bold(String(c.checkPatterns ?? true))}`
240
+ );
241
+ console.log(
242
+ ` checkArchitecture: ${chalk.bold(String(c.checkArchitecture ?? false))}`
243
+ );
244
+ if (c.minSeverity)
245
+ console.log(` minSeverity: ${chalk.bold(c.minSeverity)}`);
246
+ if (c.acceptedAbbreviations)
247
+ console.log(
248
+ ` acceptedAbbreviations: ${chalk.bold(truncateArray(c.acceptedAbbreviations, 8))}`
249
+ );
250
+ if (c.shortWords)
251
+ console.log(
252
+ ` shortWords: ${chalk.bold(truncateArray(c.shortWords, 8))}`
253
+ );
166
254
  }
167
255
 
168
256
  console.log(chalk.white('\nStarting analysis...'));
@@ -173,46 +261,71 @@ export async function scanAction(directory: string, options: ScanOptions) {
173
261
  try {
174
262
  if (event.tool === 'patterns') {
175
263
  const pr = event.data as any;
176
- console.log(` Duplicate patterns: ${chalk.bold(String(pr.duplicates?.length || 0))}`);
177
- console.log(` Files with pattern issues: ${chalk.bold(String(pr.results?.length || 0))}`);
264
+ console.log(
265
+ ` Duplicate patterns: ${chalk.bold(String(pr.duplicates?.length || 0))}`
266
+ );
267
+ console.log(
268
+ ` Files with pattern issues: ${chalk.bold(String(pr.results?.length || 0))}`
269
+ );
178
270
  // show top duplicate summaries
179
271
  if (pr.duplicates && pr.duplicates.length > 0) {
180
272
  pr.duplicates.slice(0, 5).forEach((d: any, i: number) => {
181
- console.log(` ${i + 1}. ${d.file1.split('/').pop()} ↔ ${d.file2.split('/').pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`);
273
+ console.log(
274
+ ` ${i + 1}. ${d.file1.split('/').pop()} ↔ ${d.file2.split('/').pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`
275
+ );
182
276
  });
183
277
  }
184
278
 
185
279
  // show top files with pattern issues (sorted by issue count desc)
186
280
  if (pr.results && pr.results.length > 0) {
187
281
  console.log(` Top files with pattern issues:`);
188
- const sortedByIssues = [...pr.results].sort((a: any, b: any) => (b.issues?.length || 0) - (a.issues?.length || 0));
282
+ const sortedByIssues = [...pr.results].sort(
283
+ (a: any, b: any) =>
284
+ (b.issues?.length || 0) - (a.issues?.length || 0)
285
+ );
189
286
  sortedByIssues.slice(0, 5).forEach((r: any, i: number) => {
190
- console.log(` ${i + 1}. ${r.fileName.split('/').pop()} - ${r.issues.length} issue(s)`);
287
+ console.log(
288
+ ` ${i + 1}. ${r.fileName.split('/').pop()} - ${r.issues.length} issue(s)`
289
+ );
191
290
  });
192
291
  }
193
292
 
194
293
  // Grouping and clusters summary (if available) — show after detailed findings
195
294
  if (pr.groups && pr.groups.length >= 0) {
196
- console.log(` ✅ Grouped ${chalk.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk.bold(String(pr.groups.length))} file pairs`);
295
+ console.log(
296
+ ` ✅ Grouped ${chalk.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk.bold(String(pr.groups.length))} file pairs`
297
+ );
197
298
  }
198
299
  if (pr.clusters && pr.clusters.length >= 0) {
199
- console.log(` ✅ Created ${chalk.bold(String(pr.clusters.length))} refactor clusters`);
300
+ console.log(
301
+ ` ✅ Created ${chalk.bold(String(pr.clusters.length))} refactor clusters`
302
+ );
200
303
  // show brief cluster summaries
201
304
  pr.clusters.slice(0, 3).forEach((cl: any, idx: number) => {
202
- const files = (cl.files || []).map((f: any) => f.path.split('/').pop()).join(', ');
203
- console.log(` ${idx + 1}. ${files} (${cl.tokenCost || 'n/a'} tokens)`);
305
+ const files = (cl.files || [])
306
+ .map((f: any) => f.path.split('/').pop())
307
+ .join(', ');
308
+ console.log(
309
+ ` ${idx + 1}. ${files} (${cl.tokenCost || 'n/a'} tokens)`
310
+ );
204
311
  });
205
312
  }
206
313
  } else if (event.tool === 'context') {
207
314
  const cr = event.data as any[];
208
- console.log(` Context issues found: ${chalk.bold(String(cr.length || 0))}`);
315
+ console.log(
316
+ ` Context issues found: ${chalk.bold(String(cr.length || 0))}`
317
+ );
209
318
  cr.slice(0, 5).forEach((c: any, i: number) => {
210
319
  const msg = c.message ? ` - ${c.message}` : '';
211
- console.log(` ${i + 1}. ${c.file} (${c.severity || 'n/a'})${msg}`);
320
+ console.log(
321
+ ` ${i + 1}. ${c.file} (${c.severity || 'n/a'})${msg}`
322
+ );
212
323
  });
213
324
  } else if (event.tool === 'consistency') {
214
325
  const rep = event.data as any;
215
- console.log(` Consistency totalIssues: ${chalk.bold(String(rep.summary?.totalIssues || 0))}`);
326
+ console.log(
327
+ ` Consistency totalIssues: ${chalk.bold(String(rep.summary?.totalIssues || 0))}`
328
+ );
216
329
 
217
330
  if (rep.results && rep.results.length > 0) {
218
331
  // Group issues by file
@@ -226,71 +339,141 @@ export async function scanAction(directory: string, options: ScanOptions) {
226
339
  });
227
340
 
228
341
  // Sort files by number of issues desc
229
- const files = Array.from(fileMap.entries()).sort((a, b) => b[1].length - a[1].length);
342
+ const files = Array.from(fileMap.entries()).sort(
343
+ (a, b) => b[1].length - a[1].length
344
+ );
230
345
  const topFiles = files.slice(0, 10);
231
346
 
232
347
  topFiles.forEach(([file, issues], idx) => {
233
348
  // Count severities
234
- const counts = issues.reduce((acc: any, it: any) => {
235
- const s = (it.severity || 'info').toLowerCase();
236
- acc[s] = (acc[s] || 0) + 1;
237
- return acc;
238
- }, {} as Record<string, number>);
239
-
240
- const sample = issues.find((it: any) => it.severity === 'critical' || it.severity === 'major') || issues[0];
349
+ const counts = issues.reduce(
350
+ (acc: any, it: any) => {
351
+ const s = (it.severity || 'info').toLowerCase();
352
+ acc[s] = (acc[s] || 0) + 1;
353
+ return acc;
354
+ },
355
+ {} as Record<string, number>
356
+ );
357
+
358
+ const sample =
359
+ issues.find(
360
+ (it: any) =>
361
+ it.severity === 'critical' || it.severity === 'major'
362
+ ) || issues[0];
241
363
  const sampleMsg = sample ? ` — ${sample.message}` : '';
242
364
 
243
- console.log(` ${idx + 1}. ${file} — ${issues.length} issue(s) (critical:${counts.critical || 0} major:${counts.major || 0} minor:${counts.minor || 0} info:${counts.info || 0})${sampleMsg}`);
365
+ console.log(
366
+ ` ${idx + 1}. ${file} — ${issues.length} issue(s) (critical:${counts.critical || 0} major:${counts.major || 0} minor:${counts.minor || 0} info:${counts.info || 0})${sampleMsg}`
367
+ );
244
368
  });
245
369
 
246
370
  const remaining = files.length - topFiles.length;
247
371
  if (remaining > 0) {
248
- console.log(chalk.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
372
+ console.log(
373
+ chalk.dim(
374
+ ` ... and ${remaining} more files with issues (use --output json for full details)`
375
+ )
376
+ );
249
377
  }
250
378
  }
251
379
  } else if (event.tool === 'doc-drift') {
252
380
  const dr = event.data as any;
253
- console.log(` Issues found: ${chalk.bold(String(dr.issues?.length || 0))}`);
381
+ console.log(
382
+ ` Issues found: ${chalk.bold(String(dr.issues?.length || 0))}`
383
+ );
254
384
  if (dr.rawData) {
255
- console.log(` Signature Mismatches: ${chalk.bold(dr.rawData.outdatedComments || 0)}`);
256
- console.log(` Undocumented Complexity: ${chalk.bold(dr.rawData.undocumentedComplexity || 0)}`);
385
+ console.log(
386
+ ` Signature Mismatches: ${chalk.bold(dr.rawData.outdatedComments || 0)}`
387
+ );
388
+ console.log(
389
+ ` Undocumented Complexity: ${chalk.bold(dr.rawData.undocumentedComplexity || 0)}`
390
+ );
257
391
  }
258
392
  } else if (event.tool === 'deps-health') {
259
393
  const dr = event.data as any;
260
- console.log(` Packages Analyzed: ${chalk.bold(String(dr.summary?.packagesAnalyzed || 0))}`);
394
+ console.log(
395
+ ` Packages Analyzed: ${chalk.bold(String(dr.summary?.packagesAnalyzed || 0))}`
396
+ );
261
397
  if (dr.rawData) {
262
- console.log(` Deprecated Packages: ${chalk.bold(dr.rawData.deprecatedPackages || 0)}`);
263
- console.log(` AI Cutoff Skew Score: ${chalk.bold(dr.rawData.trainingCutoffSkew?.toFixed(1) || 0)}`);
398
+ console.log(
399
+ ` Deprecated Packages: ${chalk.bold(dr.rawData.deprecatedPackages || 0)}`
400
+ );
401
+ console.log(
402
+ ` AI Cutoff Skew Score: ${chalk.bold(dr.rawData.trainingCutoffSkew?.toFixed(1) || 0)}`
403
+ );
264
404
  }
265
- } else if (event.tool === 'change-amplification' || event.tool === 'changeAmplification') {
405
+ } else if (
406
+ event.tool === 'change-amplification' ||
407
+ event.tool === 'changeAmplification'
408
+ ) {
266
409
  const dr = event.data as any;
267
- console.log(` Coupling issues: ${chalk.bold(String(dr.issues?.length || 0))}`);
410
+ console.log(
411
+ ` Coupling issues: ${chalk.bold(String(dr.issues?.length || 0))}`
412
+ );
268
413
  if (dr.summary) {
269
- console.log(` Complexity Score: ${chalk.bold(dr.summary.score || 0)}/100`);
414
+ console.log(
415
+ ` Complexity Score: ${chalk.bold(dr.summary.score || 0)}/100`
416
+ );
270
417
  }
271
418
  }
272
419
  } catch (err) {
273
- // don't crash the run for progress printing errors
420
+ void err;
274
421
  }
275
422
  };
276
423
 
277
- const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
424
+ const results = await analyzeUnified({
425
+ ...finalOptions,
426
+ progressCallback,
427
+ onProgress: (processed: number, total: number, message: string) => {
428
+ // Clear line and print progress
429
+ process.stdout.write(
430
+ `\r\x1b[K [${processed}/${total}] ${message}...`
431
+ );
432
+ if (processed === total) {
433
+ process.stdout.write('\n'); // Move to next line when done
434
+ }
435
+ },
436
+ suppressToolConfig: true,
437
+ });
438
+
439
+ // Determine if we need to print a trailing newline because the last tool didn't finish normally or had 0 files
440
+ // But progressCallback already outputs `\n--- TOOL RESULTS ---` so it's fine.
278
441
 
279
442
  // Summarize tools and results to console
280
443
  console.log(chalk.cyan('\n=== AIReady Run Summary ==='));
281
- console.log(chalk.white('Tools run:'), (finalOptions.tools || ['patterns', 'context', 'consistency']).join(', '));
444
+ console.log(
445
+ chalk.white('Tools run:'),
446
+ (finalOptions.tools || ['patterns', 'context', 'consistency']).join(', ')
447
+ );
282
448
 
283
449
  // Results summary
284
450
  console.log(chalk.cyan('\nResults summary:'));
285
- console.log(` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`);
286
- if (results.duplicates) console.log(` Duplicate patterns found: ${chalk.bold(String(results.duplicates.length || 0))}`);
287
- if (results.patterns) console.log(` Pattern files with issues: ${chalk.bold(String(results.patterns.length || 0))}`);
288
- if (results.context) console.log(` Context issues: ${chalk.bold(String(results.context.length || 0))}`);
289
- console.log(` Consistency issues: ${chalk.bold(String(results.consistency?.summary?.totalIssues || 0))}`);
290
- if (results.changeAmplification) console.log(` Change amplification: ${chalk.bold(String(results.changeAmplification.summary?.score || 0))}/100`);
451
+ console.log(
452
+ ` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`
453
+ );
454
+ if (results.duplicates)
455
+ console.log(
456
+ ` Duplicate patterns found: ${chalk.bold(String(results.duplicates.length || 0))}`
457
+ );
458
+ if (results.patterns)
459
+ console.log(
460
+ ` Pattern files with issues: ${chalk.bold(String(results.patterns.length || 0))}`
461
+ );
462
+ if (results.context)
463
+ console.log(
464
+ ` Context issues: ${chalk.bold(String(results.context.length || 0))}`
465
+ );
466
+ console.log(
467
+ ` Consistency issues: ${chalk.bold(String(results.consistency?.summary?.totalIssues || 0))}`
468
+ );
469
+ if (results.changeAmplification)
470
+ console.log(
471
+ ` Change amplification: ${chalk.bold(String(results.changeAmplification.summary?.score || 0))}/100`
472
+ );
291
473
  console.log(chalk.cyan('===========================\n'));
292
474
 
293
475
  const elapsedTime = getElapsedTime(startTime);
476
+ void elapsedTime;
294
477
 
295
478
  // Calculate score if requested: assemble per-tool scoring outputs
296
479
  let scoringResult: ReturnType<typeof calculateOverallScore> | undefined;
@@ -299,70 +482,85 @@ export async function scanAction(directory: string, options: ScanOptions) {
299
482
 
300
483
  // Patterns score
301
484
  if (results.duplicates) {
302
- const { calculatePatternScore } = await import('@aiready/pattern-detect');
485
+ const { calculatePatternScore } =
486
+ await import('@aiready/pattern-detect');
303
487
  try {
304
- const patternScore = calculatePatternScore(results.duplicates, results.patterns?.length || 0);
488
+ const patternScore = calculatePatternScore(
489
+ results.duplicates,
490
+ results.patterns?.length || 0
491
+ );
305
492
  toolScores.set('pattern-detect', patternScore);
306
493
  } catch (err) {
307
- // ignore scoring failures for a single tool
494
+ void err;
308
495
  }
309
496
  }
310
497
 
311
498
  // Context score
312
499
  if (results.context) {
313
- const { generateSummary: genContextSummary, calculateContextScore } = await import('@aiready/context-analyzer');
500
+ const { generateSummary: genContextSummary, calculateContextScore } =
501
+ await import('@aiready/context-analyzer');
314
502
  try {
315
503
  const ctxSummary = genContextSummary(results.context);
316
504
  const contextScore = calculateContextScore(ctxSummary);
317
505
  toolScores.set('context-analyzer', contextScore);
318
506
  } catch (err) {
319
- // ignore
507
+ void err;
320
508
  }
321
509
  }
322
510
 
323
511
  // Consistency score
324
512
  if (results.consistency) {
325
- const { calculateConsistencyScore } = await import('@aiready/consistency');
513
+ const { calculateConsistencyScore } =
514
+ await import('@aiready/consistency');
326
515
  try {
327
- const issues = results.consistency.results?.flatMap((r: any) => r.issues) || [];
516
+ const issues =
517
+ results.consistency.results?.flatMap((r: any) => r.issues) || [];
328
518
  const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
329
- const consistencyScore = calculateConsistencyScore(issues, totalFiles);
519
+ const consistencyScore = calculateConsistencyScore(
520
+ issues,
521
+ totalFiles
522
+ );
330
523
  toolScores.set('consistency', consistencyScore);
331
524
  } catch (err) {
332
- // ignore
525
+ void err;
333
526
  }
334
527
  }
335
528
 
336
529
  // AI signal clarity score
337
530
  if (results.aiSignalClarity) {
338
- const { calculateAiSignalClarityScore } = await import('@aiready/ai-signal-clarity');
531
+ const { calculateAiSignalClarityScore } =
532
+ await import('@aiready/ai-signal-clarity');
339
533
  try {
340
- const hrScore = calculateAiSignalClarityScore(results.aiSignalClarity);
534
+ const hrScore = calculateAiSignalClarityScore(
535
+ results.aiSignalClarity
536
+ );
341
537
  toolScores.set('ai-signal-clarity', hrScore);
342
538
  } catch (err) {
343
- // ignore
539
+ void err;
344
540
  }
345
541
  }
346
542
 
347
543
  // Agent grounding score
348
544
  if (results.grounding) {
349
- const { calculateGroundingScore } = await import('@aiready/agent-grounding');
545
+ const { calculateGroundingScore } =
546
+ await import('@aiready/agent-grounding');
350
547
  try {
351
548
  const agScore = calculateGroundingScore(results.grounding);
352
549
  toolScores.set('agent-grounding', agScore);
353
550
  } catch (err) {
354
- // ignore
551
+ void err;
355
552
  }
356
553
  }
357
554
 
358
555
  // Testability score
359
556
  if (results.testability) {
360
- const { calculateTestabilityScore } = await import('@aiready/testability');
557
+ const { calculateTestabilityScore } =
558
+ await import('@aiready/testability');
361
559
  try {
362
560
  const tbScore = calculateTestabilityScore(results.testability);
363
561
  toolScores.set('testability', tbScore);
364
562
  } catch (err) {
365
- // ignore
563
+ void err;
366
564
  }
367
565
  }
368
566
 
@@ -373,7 +571,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
373
571
  score: results.docDrift.summary.score,
374
572
  rawMetrics: results.docDrift.rawData,
375
573
  factors: [],
376
- recommendations: (results.docDrift.recommendations || []).map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
574
+ recommendations: (results.docDrift.recommendations || []).map(
575
+ (action: string) => ({
576
+ action,
577
+ estimatedImpact: 5,
578
+ priority: 'medium',
579
+ })
580
+ ),
377
581
  });
378
582
  }
379
583
 
@@ -384,7 +588,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
384
588
  score: results.deps.summary.score,
385
589
  rawMetrics: results.deps.rawData,
386
590
  factors: [],
387
- recommendations: (results.deps.recommendations || []).map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
591
+ recommendations: (results.deps.recommendations || []).map(
592
+ (action: string) => ({
593
+ action,
594
+ estimatedImpact: 5,
595
+ priority: 'medium',
596
+ })
597
+ ),
388
598
  });
389
599
  }
390
600
 
@@ -395,17 +605,26 @@ export async function scanAction(directory: string, options: ScanOptions) {
395
605
  score: results.changeAmplification.summary.score,
396
606
  rawMetrics: results.changeAmplification.rawData,
397
607
  factors: [],
398
- recommendations: (results.changeAmplification.recommendations || []).map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
608
+ recommendations: (
609
+ results.changeAmplification.recommendations || []
610
+ ).map((action: string) => ({
611
+ action,
612
+ estimatedImpact: 5,
613
+ priority: 'medium',
614
+ })),
399
615
  });
400
616
  }
401
617
 
402
-
403
618
  // Parse CLI weight overrides (if any)
404
619
  const cliWeights = parseWeightString((options as any).weights);
405
620
 
406
621
  // Only calculate overall score if we have at least one tool score
407
622
  if (toolScores.size > 0) {
408
- scoringResult = calculateOverallScore(toolScores, finalOptions, cliWeights.size ? cliWeights : undefined);
623
+ scoringResult = calculateOverallScore(
624
+ toolScores,
625
+ finalOptions,
626
+ cliWeights.size ? cliWeights : undefined
627
+ );
409
628
 
410
629
  console.log(chalk.bold('\n📊 AI Readiness Overall Score'));
411
630
  console.log(` ${formatScore(scoringResult)}`);
@@ -413,34 +632,59 @@ export async function scanAction(directory: string, options: ScanOptions) {
413
632
  // Check if we need to compare to a previous report
414
633
  if (options.compareTo) {
415
634
  try {
416
- const prevReportStr = readFileSync(resolvePath(process.cwd(), options.compareTo), 'utf8');
635
+ const prevReportStr = readFileSync(
636
+ resolvePath(process.cwd(), options.compareTo),
637
+ 'utf8'
638
+ );
417
639
  const prevReport = JSON.parse(prevReportStr);
418
- const prevScore = prevReport.scoring?.score || prevReport.scoring?.overallScore;
640
+ const prevScore =
641
+ prevReport.scoring?.score || prevReport.scoring?.overallScore;
419
642
 
420
643
  if (typeof prevScore === 'number') {
421
644
  const diff = scoringResult.overall - prevScore;
422
645
  const diffStr = diff > 0 ? `+${diff}` : String(diff);
423
646
  console.log();
424
647
  if (diff > 0) {
425
- console.log(chalk.green(` 📈 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`));
648
+ console.log(
649
+ chalk.green(
650
+ ` 📈 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
651
+ )
652
+ );
426
653
  } else if (diff < 0) {
427
- console.log(chalk.red(` 📉 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`));
654
+ console.log(
655
+ chalk.red(
656
+ ` 📉 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
657
+ )
658
+ );
428
659
  // Trend gating: if we regressed and CI is on or threshold is present, we could lower the threshold effectively,
429
660
  // but for now, we just highlight the regression.
430
661
  } else {
431
- console.log(chalk.blue(` ➖ Trend: No change compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`));
662
+ console.log(
663
+ chalk.blue(
664
+ ` ➖ Trend: No change compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
665
+ )
666
+ );
432
667
  }
433
668
 
434
669
  // Add trend info to scoringResult for programmatic use
435
670
  (scoringResult as any).trend = {
436
671
  previousScore: prevScore,
437
- difference: diff
672
+ difference: diff,
438
673
  };
439
674
  } else {
440
- console.log(chalk.yellow(`\n ⚠️ Previous report at ${options.compareTo} does not contain an overall score.`));
675
+ console.log(
676
+ chalk.yellow(
677
+ `\n ⚠️ Previous report at ${options.compareTo} does not contain an overall score.`
678
+ )
679
+ );
441
680
  }
442
681
  } catch (e) {
443
- console.log(chalk.yellow(`\n ⚠️ Could not read or parse previous report at ${options.compareTo}.`));
682
+ void e;
683
+ console.log(
684
+ chalk.yellow(
685
+ `\n ⚠️ Could not read or parse previous report at ${options.compareTo}.`
686
+ )
687
+ );
444
688
  }
445
689
  }
446
690
 
@@ -450,7 +694,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
450
694
  scoringResult.breakdown.forEach((tool) => {
451
695
  const rating = getRating(tool.score);
452
696
  const rd = getRatingDisplay(rating);
453
- console.log(` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`);
697
+ console.log(
698
+ ` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
699
+ );
454
700
  });
455
701
  console.log();
456
702
 
@@ -466,38 +712,56 @@ export async function scanAction(directory: string, options: ScanOptions) {
466
712
  }
467
713
 
468
714
  // Persist JSON summary when output format is json
469
- const outputFormat = options.output || finalOptions.output?.format || 'console';
715
+ const outputFormat =
716
+ options.output || finalOptions.output?.format || 'console';
470
717
  const userOutputFile = options.outputFile || finalOptions.output?.file;
471
718
  if (outputFormat === 'json') {
472
719
  const timestamp = getReportTimestamp();
473
720
  const defaultFilename = `aiready-report-${timestamp}.json`;
474
- const outputPath = resolveOutputPath(userOutputFile, defaultFilename, resolvedDir);
721
+ const outputPath = resolveOutputPath(
722
+ userOutputFile,
723
+ defaultFilename,
724
+ resolvedDir
725
+ );
475
726
  const outputData = { ...results, scoring: scoringResult };
476
- handleJSONOutput(outputData, outputPath, `✅ Report saved to ${outputPath}`);
727
+ handleJSONOutput(
728
+ outputData,
729
+ outputPath,
730
+ `✅ Report saved to ${outputPath}`
731
+ );
477
732
 
478
733
  // Warn if graph caps may be exceeded
479
- warnIfGraphCapExceeded(outputData, resolvedDir);
734
+ await warnIfGraphCapExceeded(outputData, resolvedDir);
480
735
  } else {
481
736
  // Auto-persist report even in console mode for downstream tools
482
737
  const timestamp = getReportTimestamp();
483
738
  const defaultFilename = `aiready-report-${timestamp}.json`;
484
- const outputPath = resolveOutputPath(userOutputFile, defaultFilename, resolvedDir);
739
+ const outputPath = resolveOutputPath(
740
+ userOutputFile,
741
+ defaultFilename,
742
+ resolvedDir
743
+ );
485
744
  const outputData = { ...results, scoring: scoringResult };
486
745
 
487
746
  try {
488
747
  writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
489
748
  console.log(chalk.dim(`✅ Report auto-persisted to ${outputPath}`));
490
749
  // Warn if graph caps may be exceeded
491
- warnIfGraphCapExceeded(outputData, resolvedDir);
750
+ await warnIfGraphCapExceeded(outputData, resolvedDir);
492
751
  } catch (err) {
493
- // failed to save report, but don't fail the command
752
+ void err;
494
753
  }
495
754
  }
496
755
 
497
756
  // CI/CD Gatekeeper Mode
498
- const isCI = options.ci || process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
757
+ const isCI =
758
+ options.ci ||
759
+ process.env.CI === 'true' ||
760
+ process.env.GITHUB_ACTIONS === 'true';
499
761
  if (isCI && scoringResult) {
500
- const threshold = options.threshold ? parseInt(options.threshold) : undefined;
762
+ const threshold = options.threshold
763
+ ? parseInt(options.threshold)
764
+ : undefined;
501
765
  const failOnLevel = options.failOn || 'critical';
502
766
 
503
767
  // Output GitHub Actions annotations
@@ -505,7 +769,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
505
769
  console.log(`\n::group::AI Readiness Score`);
506
770
  console.log(`score=${scoringResult.overall}`);
507
771
  if (scoringResult.breakdown) {
508
- scoringResult.breakdown.forEach(tool => {
772
+ scoringResult.breakdown.forEach((tool) => {
509
773
  console.log(`${tool.toolName}=${tool.score}`);
510
774
  });
511
775
  }
@@ -513,9 +777,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
513
777
 
514
778
  // Output annotation for score
515
779
  if (threshold && scoringResult.overall < threshold) {
516
- console.log(`::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`);
780
+ console.log(
781
+ `::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`
782
+ );
517
783
  } else if (threshold) {
518
- console.log(`::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`);
784
+ console.log(
785
+ `::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
786
+ );
519
787
  }
520
788
 
521
789
  // Output annotations for critical issues
@@ -524,7 +792,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
524
792
  p.issues.filter((i: any) => i.severity === 'critical')
525
793
  );
526
794
  criticalPatterns.slice(0, 10).forEach((issue: any) => {
527
- console.log(`::warning file=${issue.location?.file || 'unknown'},line=${issue.location?.line || 1}::${issue.message}`);
795
+ console.log(
796
+ `::warning file=${issue.location?.file || 'unknown'},line=${issue.location?.line || 1}::${issue.message}`
797
+ );
528
798
  });
529
799
  }
530
800
  }
@@ -542,7 +812,8 @@ export async function scanAction(directory: string, options: ScanOptions) {
542
812
  // Check fail-on severity
543
813
  if (failOnLevel !== 'none') {
544
814
  const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
545
- const minSeverity = severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
815
+ const minSeverity =
816
+ severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
546
817
 
547
818
  let criticalCount = 0;
548
819
  let majorCount = 0;
@@ -573,7 +844,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
573
844
  if (minSeverity >= 4 && criticalCount > 0) {
574
845
  shouldFail = true;
575
846
  failReason = `Found ${criticalCount} critical issues`;
576
- } else if (minSeverity >= 3 && (criticalCount + majorCount) > 0) {
847
+ } else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
577
848
  shouldFail = true;
578
849
  failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
579
850
  }
@@ -584,16 +855,30 @@ export async function scanAction(directory: string, options: ScanOptions) {
584
855
  console.log(chalk.red('\n🚫 PR BLOCKED: AI Readiness Check Failed'));
585
856
  console.log(chalk.red(` Reason: ${failReason}`));
586
857
  console.log(chalk.dim('\n Remediation steps:'));
587
- console.log(chalk.dim(' 1. Run `aiready scan` locally to see detailed issues'));
858
+ console.log(
859
+ chalk.dim(' 1. Run `aiready scan` locally to see detailed issues')
860
+ );
588
861
  console.log(chalk.dim(' 2. Fix the critical issues before merging'));
589
- console.log(chalk.dim(' 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing'));
862
+ console.log(
863
+ chalk.dim(
864
+ ' 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing'
865
+ )
866
+ );
590
867
  process.exit(1);
591
868
  } else {
592
869
  console.log(chalk.green('\n✅ PR PASSED: AI Readiness Check'));
593
870
  if (threshold) {
594
- console.log(chalk.green(` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`));
871
+ console.log(
872
+ chalk.green(
873
+ ` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
874
+ )
875
+ );
595
876
  }
596
- console.log(chalk.dim('\n 💡 Track historical trends: https://getaiready.dev — Team plan $99/mo'));
877
+ console.log(
878
+ chalk.dim(
879
+ '\n 💡 Track historical trends: https://getaiready.dev — Team plan $99/mo'
880
+ )
881
+ );
597
882
  }
598
883
  }
599
884
  } catch (error) {
@@ -628,4 +913,4 @@ CI/CD INTEGRATION (Gatekeeper Mode):
628
913
  Example GitHub Actions workflow:
629
914
  - name: AI Readiness Check
630
915
  run: aiready scan --ci --threshold 70
631
- `;
916
+ `;