@aiready/cli 0.9.40 → 0.9.41

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,129 @@ 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
+ suppressToolConfig: true,
428
+ });
278
429
 
279
430
  // Summarize tools and results to console
280
431
  console.log(chalk.cyan('\n=== AIReady Run Summary ==='));
281
- console.log(chalk.white('Tools run:'), (finalOptions.tools || ['patterns', 'context', 'consistency']).join(', '));
432
+ console.log(
433
+ chalk.white('Tools run:'),
434
+ (finalOptions.tools || ['patterns', 'context', 'consistency']).join(', ')
435
+ );
282
436
 
283
437
  // Results summary
284
438
  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`);
439
+ console.log(
440
+ ` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`
441
+ );
442
+ if (results.duplicates)
443
+ console.log(
444
+ ` Duplicate patterns found: ${chalk.bold(String(results.duplicates.length || 0))}`
445
+ );
446
+ if (results.patterns)
447
+ console.log(
448
+ ` Pattern files with issues: ${chalk.bold(String(results.patterns.length || 0))}`
449
+ );
450
+ if (results.context)
451
+ console.log(
452
+ ` Context issues: ${chalk.bold(String(results.context.length || 0))}`
453
+ );
454
+ console.log(
455
+ ` Consistency issues: ${chalk.bold(String(results.consistency?.summary?.totalIssues || 0))}`
456
+ );
457
+ if (results.changeAmplification)
458
+ console.log(
459
+ ` Change amplification: ${chalk.bold(String(results.changeAmplification.summary?.score || 0))}/100`
460
+ );
291
461
  console.log(chalk.cyan('===========================\n'));
292
462
 
293
463
  const elapsedTime = getElapsedTime(startTime);
464
+ void elapsedTime;
294
465
 
295
466
  // Calculate score if requested: assemble per-tool scoring outputs
296
467
  let scoringResult: ReturnType<typeof calculateOverallScore> | undefined;
@@ -299,70 +470,85 @@ export async function scanAction(directory: string, options: ScanOptions) {
299
470
 
300
471
  // Patterns score
301
472
  if (results.duplicates) {
302
- const { calculatePatternScore } = await import('@aiready/pattern-detect');
473
+ const { calculatePatternScore } =
474
+ await import('@aiready/pattern-detect');
303
475
  try {
304
- const patternScore = calculatePatternScore(results.duplicates, results.patterns?.length || 0);
476
+ const patternScore = calculatePatternScore(
477
+ results.duplicates,
478
+ results.patterns?.length || 0
479
+ );
305
480
  toolScores.set('pattern-detect', patternScore);
306
481
  } catch (err) {
307
- // ignore scoring failures for a single tool
482
+ void err;
308
483
  }
309
484
  }
310
485
 
311
486
  // Context score
312
487
  if (results.context) {
313
- const { generateSummary: genContextSummary, calculateContextScore } = await import('@aiready/context-analyzer');
488
+ const { generateSummary: genContextSummary, calculateContextScore } =
489
+ await import('@aiready/context-analyzer');
314
490
  try {
315
491
  const ctxSummary = genContextSummary(results.context);
316
492
  const contextScore = calculateContextScore(ctxSummary);
317
493
  toolScores.set('context-analyzer', contextScore);
318
494
  } catch (err) {
319
- // ignore
495
+ void err;
320
496
  }
321
497
  }
322
498
 
323
499
  // Consistency score
324
500
  if (results.consistency) {
325
- const { calculateConsistencyScore } = await import('@aiready/consistency');
501
+ const { calculateConsistencyScore } =
502
+ await import('@aiready/consistency');
326
503
  try {
327
- const issues = results.consistency.results?.flatMap((r: any) => r.issues) || [];
504
+ const issues =
505
+ results.consistency.results?.flatMap((r: any) => r.issues) || [];
328
506
  const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
329
- const consistencyScore = calculateConsistencyScore(issues, totalFiles);
507
+ const consistencyScore = calculateConsistencyScore(
508
+ issues,
509
+ totalFiles
510
+ );
330
511
  toolScores.set('consistency', consistencyScore);
331
512
  } catch (err) {
332
- // ignore
513
+ void err;
333
514
  }
334
515
  }
335
516
 
336
517
  // AI signal clarity score
337
518
  if (results.aiSignalClarity) {
338
- const { calculateAiSignalClarityScore } = await import('@aiready/ai-signal-clarity');
519
+ const { calculateAiSignalClarityScore } =
520
+ await import('@aiready/ai-signal-clarity');
339
521
  try {
340
- const hrScore = calculateAiSignalClarityScore(results.aiSignalClarity);
522
+ const hrScore = calculateAiSignalClarityScore(
523
+ results.aiSignalClarity
524
+ );
341
525
  toolScores.set('ai-signal-clarity', hrScore);
342
526
  } catch (err) {
343
- // ignore
527
+ void err;
344
528
  }
345
529
  }
346
530
 
347
531
  // Agent grounding score
348
532
  if (results.grounding) {
349
- const { calculateGroundingScore } = await import('@aiready/agent-grounding');
533
+ const { calculateGroundingScore } =
534
+ await import('@aiready/agent-grounding');
350
535
  try {
351
536
  const agScore = calculateGroundingScore(results.grounding);
352
537
  toolScores.set('agent-grounding', agScore);
353
538
  } catch (err) {
354
- // ignore
539
+ void err;
355
540
  }
356
541
  }
357
542
 
358
543
  // Testability score
359
544
  if (results.testability) {
360
- const { calculateTestabilityScore } = await import('@aiready/testability');
545
+ const { calculateTestabilityScore } =
546
+ await import('@aiready/testability');
361
547
  try {
362
548
  const tbScore = calculateTestabilityScore(results.testability);
363
549
  toolScores.set('testability', tbScore);
364
550
  } catch (err) {
365
- // ignore
551
+ void err;
366
552
  }
367
553
  }
368
554
 
@@ -373,7 +559,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
373
559
  score: results.docDrift.summary.score,
374
560
  rawMetrics: results.docDrift.rawData,
375
561
  factors: [],
376
- recommendations: (results.docDrift.recommendations || []).map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
562
+ recommendations: (results.docDrift.recommendations || []).map(
563
+ (action: string) => ({
564
+ action,
565
+ estimatedImpact: 5,
566
+ priority: 'medium',
567
+ })
568
+ ),
377
569
  });
378
570
  }
379
571
 
@@ -384,7 +576,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
384
576
  score: results.deps.summary.score,
385
577
  rawMetrics: results.deps.rawData,
386
578
  factors: [],
387
- recommendations: (results.deps.recommendations || []).map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
579
+ recommendations: (results.deps.recommendations || []).map(
580
+ (action: string) => ({
581
+ action,
582
+ estimatedImpact: 5,
583
+ priority: 'medium',
584
+ })
585
+ ),
388
586
  });
389
587
  }
390
588
 
@@ -395,17 +593,26 @@ export async function scanAction(directory: string, options: ScanOptions) {
395
593
  score: results.changeAmplification.summary.score,
396
594
  rawMetrics: results.changeAmplification.rawData,
397
595
  factors: [],
398
- recommendations: (results.changeAmplification.recommendations || []).map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
596
+ recommendations: (
597
+ results.changeAmplification.recommendations || []
598
+ ).map((action: string) => ({
599
+ action,
600
+ estimatedImpact: 5,
601
+ priority: 'medium',
602
+ })),
399
603
  });
400
604
  }
401
605
 
402
-
403
606
  // Parse CLI weight overrides (if any)
404
607
  const cliWeights = parseWeightString((options as any).weights);
405
608
 
406
609
  // Only calculate overall score if we have at least one tool score
407
610
  if (toolScores.size > 0) {
408
- scoringResult = calculateOverallScore(toolScores, finalOptions, cliWeights.size ? cliWeights : undefined);
611
+ scoringResult = calculateOverallScore(
612
+ toolScores,
613
+ finalOptions,
614
+ cliWeights.size ? cliWeights : undefined
615
+ );
409
616
 
410
617
  console.log(chalk.bold('\n📊 AI Readiness Overall Score'));
411
618
  console.log(` ${formatScore(scoringResult)}`);
@@ -413,34 +620,59 @@ export async function scanAction(directory: string, options: ScanOptions) {
413
620
  // Check if we need to compare to a previous report
414
621
  if (options.compareTo) {
415
622
  try {
416
- const prevReportStr = readFileSync(resolvePath(process.cwd(), options.compareTo), 'utf8');
623
+ const prevReportStr = readFileSync(
624
+ resolvePath(process.cwd(), options.compareTo),
625
+ 'utf8'
626
+ );
417
627
  const prevReport = JSON.parse(prevReportStr);
418
- const prevScore = prevReport.scoring?.score || prevReport.scoring?.overallScore;
628
+ const prevScore =
629
+ prevReport.scoring?.score || prevReport.scoring?.overallScore;
419
630
 
420
631
  if (typeof prevScore === 'number') {
421
632
  const diff = scoringResult.overall - prevScore;
422
633
  const diffStr = diff > 0 ? `+${diff}` : String(diff);
423
634
  console.log();
424
635
  if (diff > 0) {
425
- console.log(chalk.green(` 📈 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`));
636
+ console.log(
637
+ chalk.green(
638
+ ` 📈 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
639
+ )
640
+ );
426
641
  } else if (diff < 0) {
427
- console.log(chalk.red(` 📉 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`));
642
+ console.log(
643
+ chalk.red(
644
+ ` 📉 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
645
+ )
646
+ );
428
647
  // Trend gating: if we regressed and CI is on or threshold is present, we could lower the threshold effectively,
429
648
  // but for now, we just highlight the regression.
430
649
  } else {
431
- console.log(chalk.blue(` ➖ Trend: No change compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`));
650
+ console.log(
651
+ chalk.blue(
652
+ ` ➖ Trend: No change compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
653
+ )
654
+ );
432
655
  }
433
656
 
434
657
  // Add trend info to scoringResult for programmatic use
435
658
  (scoringResult as any).trend = {
436
659
  previousScore: prevScore,
437
- difference: diff
660
+ difference: diff,
438
661
  };
439
662
  } else {
440
- console.log(chalk.yellow(`\n ⚠️ Previous report at ${options.compareTo} does not contain an overall score.`));
663
+ console.log(
664
+ chalk.yellow(
665
+ `\n ⚠️ Previous report at ${options.compareTo} does not contain an overall score.`
666
+ )
667
+ );
441
668
  }
442
669
  } catch (e) {
443
- console.log(chalk.yellow(`\n ⚠️ Could not read or parse previous report at ${options.compareTo}.`));
670
+ void e;
671
+ console.log(
672
+ chalk.yellow(
673
+ `\n ⚠️ Could not read or parse previous report at ${options.compareTo}.`
674
+ )
675
+ );
444
676
  }
445
677
  }
446
678
 
@@ -450,7 +682,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
450
682
  scoringResult.breakdown.forEach((tool) => {
451
683
  const rating = getRating(tool.score);
452
684
  const rd = getRatingDisplay(rating);
453
- console.log(` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`);
685
+ console.log(
686
+ ` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
687
+ );
454
688
  });
455
689
  console.log();
456
690
 
@@ -466,38 +700,56 @@ export async function scanAction(directory: string, options: ScanOptions) {
466
700
  }
467
701
 
468
702
  // Persist JSON summary when output format is json
469
- const outputFormat = options.output || finalOptions.output?.format || 'console';
703
+ const outputFormat =
704
+ options.output || finalOptions.output?.format || 'console';
470
705
  const userOutputFile = options.outputFile || finalOptions.output?.file;
471
706
  if (outputFormat === 'json') {
472
707
  const timestamp = getReportTimestamp();
473
708
  const defaultFilename = `aiready-report-${timestamp}.json`;
474
- const outputPath = resolveOutputPath(userOutputFile, defaultFilename, resolvedDir);
709
+ const outputPath = resolveOutputPath(
710
+ userOutputFile,
711
+ defaultFilename,
712
+ resolvedDir
713
+ );
475
714
  const outputData = { ...results, scoring: scoringResult };
476
- handleJSONOutput(outputData, outputPath, `✅ Report saved to ${outputPath}`);
715
+ handleJSONOutput(
716
+ outputData,
717
+ outputPath,
718
+ `✅ Report saved to ${outputPath}`
719
+ );
477
720
 
478
721
  // Warn if graph caps may be exceeded
479
- warnIfGraphCapExceeded(outputData, resolvedDir);
722
+ await warnIfGraphCapExceeded(outputData, resolvedDir);
480
723
  } else {
481
724
  // Auto-persist report even in console mode for downstream tools
482
725
  const timestamp = getReportTimestamp();
483
726
  const defaultFilename = `aiready-report-${timestamp}.json`;
484
- const outputPath = resolveOutputPath(userOutputFile, defaultFilename, resolvedDir);
727
+ const outputPath = resolveOutputPath(
728
+ userOutputFile,
729
+ defaultFilename,
730
+ resolvedDir
731
+ );
485
732
  const outputData = { ...results, scoring: scoringResult };
486
733
 
487
734
  try {
488
735
  writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
489
736
  console.log(chalk.dim(`✅ Report auto-persisted to ${outputPath}`));
490
737
  // Warn if graph caps may be exceeded
491
- warnIfGraphCapExceeded(outputData, resolvedDir);
738
+ await warnIfGraphCapExceeded(outputData, resolvedDir);
492
739
  } catch (err) {
493
- // failed to save report, but don't fail the command
740
+ void err;
494
741
  }
495
742
  }
496
743
 
497
744
  // CI/CD Gatekeeper Mode
498
- const isCI = options.ci || process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
745
+ const isCI =
746
+ options.ci ||
747
+ process.env.CI === 'true' ||
748
+ process.env.GITHUB_ACTIONS === 'true';
499
749
  if (isCI && scoringResult) {
500
- const threshold = options.threshold ? parseInt(options.threshold) : undefined;
750
+ const threshold = options.threshold
751
+ ? parseInt(options.threshold)
752
+ : undefined;
501
753
  const failOnLevel = options.failOn || 'critical';
502
754
 
503
755
  // Output GitHub Actions annotations
@@ -505,7 +757,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
505
757
  console.log(`\n::group::AI Readiness Score`);
506
758
  console.log(`score=${scoringResult.overall}`);
507
759
  if (scoringResult.breakdown) {
508
- scoringResult.breakdown.forEach(tool => {
760
+ scoringResult.breakdown.forEach((tool) => {
509
761
  console.log(`${tool.toolName}=${tool.score}`);
510
762
  });
511
763
  }
@@ -513,9 +765,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
513
765
 
514
766
  // Output annotation for score
515
767
  if (threshold && scoringResult.overall < threshold) {
516
- console.log(`::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`);
768
+ console.log(
769
+ `::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`
770
+ );
517
771
  } else if (threshold) {
518
- console.log(`::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`);
772
+ console.log(
773
+ `::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
774
+ );
519
775
  }
520
776
 
521
777
  // Output annotations for critical issues
@@ -524,7 +780,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
524
780
  p.issues.filter((i: any) => i.severity === 'critical')
525
781
  );
526
782
  criticalPatterns.slice(0, 10).forEach((issue: any) => {
527
- console.log(`::warning file=${issue.location?.file || 'unknown'},line=${issue.location?.line || 1}::${issue.message}`);
783
+ console.log(
784
+ `::warning file=${issue.location?.file || 'unknown'},line=${issue.location?.line || 1}::${issue.message}`
785
+ );
528
786
  });
529
787
  }
530
788
  }
@@ -542,7 +800,8 @@ export async function scanAction(directory: string, options: ScanOptions) {
542
800
  // Check fail-on severity
543
801
  if (failOnLevel !== 'none') {
544
802
  const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
545
- const minSeverity = severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
803
+ const minSeverity =
804
+ severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
546
805
 
547
806
  let criticalCount = 0;
548
807
  let majorCount = 0;
@@ -573,7 +832,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
573
832
  if (minSeverity >= 4 && criticalCount > 0) {
574
833
  shouldFail = true;
575
834
  failReason = `Found ${criticalCount} critical issues`;
576
- } else if (minSeverity >= 3 && (criticalCount + majorCount) > 0) {
835
+ } else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
577
836
  shouldFail = true;
578
837
  failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
579
838
  }
@@ -584,16 +843,30 @@ export async function scanAction(directory: string, options: ScanOptions) {
584
843
  console.log(chalk.red('\n🚫 PR BLOCKED: AI Readiness Check Failed'));
585
844
  console.log(chalk.red(` Reason: ${failReason}`));
586
845
  console.log(chalk.dim('\n Remediation steps:'));
587
- console.log(chalk.dim(' 1. Run `aiready scan` locally to see detailed issues'));
846
+ console.log(
847
+ chalk.dim(' 1. Run `aiready scan` locally to see detailed issues')
848
+ );
588
849
  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'));
850
+ console.log(
851
+ chalk.dim(
852
+ ' 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing'
853
+ )
854
+ );
590
855
  process.exit(1);
591
856
  } else {
592
857
  console.log(chalk.green('\n✅ PR PASSED: AI Readiness Check'));
593
858
  if (threshold) {
594
- console.log(chalk.green(` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`));
859
+ console.log(
860
+ chalk.green(
861
+ ` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
862
+ )
863
+ );
595
864
  }
596
- console.log(chalk.dim('\n 💡 Track historical trends: https://getaiready.dev — Team plan $99/mo'));
865
+ console.log(
866
+ chalk.dim(
867
+ '\n 💡 Track historical trends: https://getaiready.dev — Team plan $99/mo'
868
+ )
869
+ );
597
870
  }
598
871
  }
599
872
  } catch (error) {
@@ -628,4 +901,4 @@ CI/CD INTEGRATION (Gatekeeper Mode):
628
901
  Example GitHub Actions workflow:
629
902
  - name: AI Readiness Check
630
903
  run: aiready scan --ci --threshold 70
631
- `;
904
+ `;