@aiready/cli 0.9.39 → 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,20 +53,33 @@ 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'],
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: {
56
- format: 'json',
70
+ format: 'console',
57
71
  file: undefined,
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,37 +95,65 @@ 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
 
85
106
  // Load and merge config with CLI options
86
- const baseOptions = await loadMergedConfig(resolvedDir, defaults, {
87
- tools: profileTools as any,
107
+ const cliOverrides: any = {
88
108
  include: options.include?.split(','),
89
109
  exclude: options.exclude?.split(','),
90
- }) as any;
110
+ };
111
+ if (profileTools) {
112
+ cliOverrides.tools = profileTools;
113
+ }
91
114
 
115
+ const baseOptions = (await loadMergedConfig(
116
+ resolvedDir,
117
+ defaults,
118
+ cliOverrides
119
+ )) as any;
92
120
 
93
121
  // Apply smart defaults for pattern detection if patterns tool is enabled
94
122
  let finalOptions = { ...baseOptions };
95
123
  if (baseOptions.tools.includes('patterns')) {
96
124
  const { getSmartDefaults } = await import('@aiready/pattern-detect');
97
- const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
125
+ const patternSmartDefaults = await getSmartDefaults(
126
+ resolvedDir,
127
+ baseOptions
128
+ );
98
129
  // Merge deeply to preserve nested config
99
- finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
130
+ finalOptions = {
131
+ ...patternSmartDefaults,
132
+ ...finalOptions,
133
+ ...baseOptions,
134
+ };
100
135
  }
101
136
 
102
137
  // Print pre-run summary with expanded settings (truncate long arrays)
103
138
  console.log(chalk.cyan('\n=== AIReady Run Preview ==='));
104
- 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
+ );
105
143
  console.log(chalk.white('Will use settings from config and defaults.'));
106
144
 
107
145
  // Common top-level settings
108
146
  console.log(chalk.white('\nGeneral settings:'));
109
- if (finalOptions.rootDir) console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
110
- if (finalOptions.include) console.log(` include: ${chalk.bold(truncateArray(finalOptions.include, 6))}`);
111
- 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
+ );
112
157
 
113
158
  if (finalOptions['pattern-detect'] || finalOptions.minSimilarity) {
114
159
  const patternDetectConfig = finalOptions['pattern-detect'] || {
@@ -123,15 +168,40 @@ export async function scanAction(directory: string, options: ScanOptions) {
123
168
  includeTests: (finalOptions as any).includeTests,
124
169
  };
125
170
  console.log(chalk.white('\nPattern-detect settings:'));
126
- console.log(` minSimilarity: ${chalk.bold(patternDetectConfig.minSimilarity ?? 'default')}`);
127
- console.log(` minLines: ${chalk.bold(patternDetectConfig.minLines ?? 'default')}`);
128
- if (patternDetectConfig.approx !== undefined) console.log(` approx: ${chalk.bold(String(patternDetectConfig.approx))}`);
129
- if (patternDetectConfig.minSharedTokens !== undefined) console.log(` minSharedTokens: ${chalk.bold(String(patternDetectConfig.minSharedTokens))}`);
130
- if (patternDetectConfig.maxCandidatesPerBlock !== undefined) console.log(` maxCandidatesPerBlock: ${chalk.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`);
131
- if (patternDetectConfig.batchSize !== undefined) console.log(` batchSize: ${chalk.bold(String(patternDetectConfig.batchSize))}`);
132
- if (patternDetectConfig.streamResults !== undefined) console.log(` streamResults: ${chalk.bold(String(patternDetectConfig.streamResults))}`);
133
- if (patternDetectConfig.severity !== undefined) console.log(` severity: ${chalk.bold(String(patternDetectConfig.severity))}`);
134
- 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
+ );
135
205
  }
136
206
 
137
207
  if (finalOptions['context-analyzer'] || finalOptions.maxDepth) {
@@ -144,21 +214,43 @@ export async function scanAction(directory: string, options: ScanOptions) {
144
214
  };
145
215
  console.log(chalk.white('\nContext-analyzer settings:'));
146
216
  console.log(` maxDepth: ${chalk.bold(ca.maxDepth ?? 'default')}`);
147
- console.log(` maxContextBudget: ${chalk.bold(ca.maxContextBudget ?? 'default')}`);
148
- if (ca.minCohesion !== undefined) console.log(` minCohesion: ${chalk.bold(String(ca.minCohesion))}`);
149
- if (ca.maxFragmentation !== undefined) console.log(` maxFragmentation: ${chalk.bold(String(ca.maxFragmentation))}`);
150
- 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
+ );
151
230
  }
152
231
 
153
232
  if (finalOptions.consistency) {
154
233
  const c = finalOptions.consistency;
155
234
  console.log(chalk.white('\nConsistency settings:'));
156
- console.log(` checkNaming: ${chalk.bold(String(c.checkNaming ?? true))}`);
157
- console.log(` checkPatterns: ${chalk.bold(String(c.checkPatterns ?? true))}`);
158
- console.log(` checkArchitecture: ${chalk.bold(String(c.checkArchitecture ?? false))}`);
159
- if (c.minSeverity) console.log(` minSeverity: ${chalk.bold(c.minSeverity)}`);
160
- if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${chalk.bold(truncateArray(c.acceptedAbbreviations, 8))}`);
161
- 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
+ );
162
254
  }
163
255
 
164
256
  console.log(chalk.white('\nStarting analysis...'));
@@ -169,46 +261,71 @@ export async function scanAction(directory: string, options: ScanOptions) {
169
261
  try {
170
262
  if (event.tool === 'patterns') {
171
263
  const pr = event.data as any;
172
- console.log(` Duplicate patterns: ${chalk.bold(String(pr.duplicates?.length || 0))}`);
173
- 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
+ );
174
270
  // show top duplicate summaries
175
271
  if (pr.duplicates && pr.duplicates.length > 0) {
176
272
  pr.duplicates.slice(0, 5).forEach((d: any, i: number) => {
177
- 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
+ );
178
276
  });
179
277
  }
180
278
 
181
279
  // show top files with pattern issues (sorted by issue count desc)
182
280
  if (pr.results && pr.results.length > 0) {
183
281
  console.log(` Top files with pattern issues:`);
184
- 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
+ );
185
286
  sortedByIssues.slice(0, 5).forEach((r: any, i: number) => {
186
- 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
+ );
187
290
  });
188
291
  }
189
292
 
190
293
  // Grouping and clusters summary (if available) — show after detailed findings
191
294
  if (pr.groups && pr.groups.length >= 0) {
192
- 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
+ );
193
298
  }
194
299
  if (pr.clusters && pr.clusters.length >= 0) {
195
- 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
+ );
196
303
  // show brief cluster summaries
197
304
  pr.clusters.slice(0, 3).forEach((cl: any, idx: number) => {
198
- const files = (cl.files || []).map((f: any) => f.path.split('/').pop()).join(', ');
199
- 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
+ );
200
311
  });
201
312
  }
202
313
  } else if (event.tool === 'context') {
203
314
  const cr = event.data as any[];
204
- 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
+ );
205
318
  cr.slice(0, 5).forEach((c: any, i: number) => {
206
319
  const msg = c.message ? ` - ${c.message}` : '';
207
- 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
+ );
208
323
  });
209
324
  } else if (event.tool === 'consistency') {
210
325
  const rep = event.data as any;
211
- 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
+ );
212
329
 
213
330
  if (rep.results && rep.results.length > 0) {
214
331
  // Group issues by file
@@ -222,64 +339,129 @@ export async function scanAction(directory: string, options: ScanOptions) {
222
339
  });
223
340
 
224
341
  // Sort files by number of issues desc
225
- 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
+ );
226
345
  const topFiles = files.slice(0, 10);
227
346
 
228
347
  topFiles.forEach(([file, issues], idx) => {
229
348
  // Count severities
230
- const counts = issues.reduce((acc: any, it: any) => {
231
- const s = (it.severity || 'info').toLowerCase();
232
- acc[s] = (acc[s] || 0) + 1;
233
- return acc;
234
- }, {} as Record<string, number>);
235
-
236
- 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];
237
363
  const sampleMsg = sample ? ` — ${sample.message}` : '';
238
364
 
239
- 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
+ );
240
368
  });
241
369
 
242
370
  const remaining = files.length - topFiles.length;
243
371
  if (remaining > 0) {
244
- 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
+ );
245
377
  }
246
378
  }
247
379
  } else if (event.tool === 'doc-drift') {
248
380
  const dr = event.data as any;
249
- 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
+ );
250
384
  if (dr.rawData) {
251
- console.log(` Signature Mismatches: ${chalk.bold(dr.rawData.outdatedComments || 0)}`);
252
- 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
+ );
253
391
  }
254
392
  } else if (event.tool === 'deps-health') {
255
393
  const dr = event.data as any;
256
- 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
+ );
257
397
  if (dr.rawData) {
258
- console.log(` Deprecated Packages: ${chalk.bold(dr.rawData.deprecatedPackages || 0)}`);
259
- 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
+ );
404
+ }
405
+ } else if (
406
+ event.tool === 'change-amplification' ||
407
+ event.tool === 'changeAmplification'
408
+ ) {
409
+ const dr = event.data as any;
410
+ console.log(
411
+ ` Coupling issues: ${chalk.bold(String(dr.issues?.length || 0))}`
412
+ );
413
+ if (dr.summary) {
414
+ console.log(
415
+ ` Complexity Score: ${chalk.bold(dr.summary.score || 0)}/100`
416
+ );
260
417
  }
261
418
  }
262
419
  } catch (err) {
263
- // don't crash the run for progress printing errors
420
+ void err;
264
421
  }
265
422
  };
266
423
 
267
- const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
424
+ const results = await analyzeUnified({
425
+ ...finalOptions,
426
+ progressCallback,
427
+ suppressToolConfig: true,
428
+ });
268
429
 
269
430
  // Summarize tools and results to console
270
431
  console.log(chalk.cyan('\n=== AIReady Run Summary ==='));
271
- 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
+ );
272
436
 
273
437
  // Results summary
274
438
  console.log(chalk.cyan('\nResults summary:'));
275
- console.log(` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`);
276
- if (results.duplicates) console.log(` Duplicate patterns found: ${chalk.bold(String(results.duplicates.length || 0))}`);
277
- if (results.patterns) console.log(` Pattern files with issues: ${chalk.bold(String(results.patterns.length || 0))}`);
278
- if (results.context) console.log(` Context issues: ${chalk.bold(String(results.context.length || 0))}`);
279
- if (results.consistency) console.log(` Consistency issues: ${chalk.bold(String(results.consistency.summary.totalIssues || 0))}`);
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
+ );
280
461
  console.log(chalk.cyan('===========================\n'));
281
462
 
282
463
  const elapsedTime = getElapsedTime(startTime);
464
+ void elapsedTime;
283
465
 
284
466
  // Calculate score if requested: assemble per-tool scoring outputs
285
467
  let scoringResult: ReturnType<typeof calculateOverallScore> | undefined;
@@ -288,70 +470,85 @@ export async function scanAction(directory: string, options: ScanOptions) {
288
470
 
289
471
  // Patterns score
290
472
  if (results.duplicates) {
291
- const { calculatePatternScore } = await import('@aiready/pattern-detect');
473
+ const { calculatePatternScore } =
474
+ await import('@aiready/pattern-detect');
292
475
  try {
293
- const patternScore = calculatePatternScore(results.duplicates, results.patterns?.length || 0);
476
+ const patternScore = calculatePatternScore(
477
+ results.duplicates,
478
+ results.patterns?.length || 0
479
+ );
294
480
  toolScores.set('pattern-detect', patternScore);
295
481
  } catch (err) {
296
- // ignore scoring failures for a single tool
482
+ void err;
297
483
  }
298
484
  }
299
485
 
300
486
  // Context score
301
487
  if (results.context) {
302
- const { generateSummary: genContextSummary, calculateContextScore } = await import('@aiready/context-analyzer');
488
+ const { generateSummary: genContextSummary, calculateContextScore } =
489
+ await import('@aiready/context-analyzer');
303
490
  try {
304
491
  const ctxSummary = genContextSummary(results.context);
305
492
  const contextScore = calculateContextScore(ctxSummary);
306
493
  toolScores.set('context-analyzer', contextScore);
307
494
  } catch (err) {
308
- // ignore
495
+ void err;
309
496
  }
310
497
  }
311
498
 
312
499
  // Consistency score
313
500
  if (results.consistency) {
314
- const { calculateConsistencyScore } = await import('@aiready/consistency');
501
+ const { calculateConsistencyScore } =
502
+ await import('@aiready/consistency');
315
503
  try {
316
- const issues = results.consistency.results?.flatMap((r: any) => r.issues) || [];
504
+ const issues =
505
+ results.consistency.results?.flatMap((r: any) => r.issues) || [];
317
506
  const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
318
- const consistencyScore = calculateConsistencyScore(issues, totalFiles);
507
+ const consistencyScore = calculateConsistencyScore(
508
+ issues,
509
+ totalFiles
510
+ );
319
511
  toolScores.set('consistency', consistencyScore);
320
512
  } catch (err) {
321
- // ignore
513
+ void err;
322
514
  }
323
515
  }
324
516
 
325
517
  // AI signal clarity score
326
518
  if (results.aiSignalClarity) {
327
- const { calculateHallucinationScore } = await import('@aiready/ai-signal-clarity');
519
+ const { calculateAiSignalClarityScore } =
520
+ await import('@aiready/ai-signal-clarity');
328
521
  try {
329
- const hrScore = calculateHallucinationScore(results.aiSignalClarity);
522
+ const hrScore = calculateAiSignalClarityScore(
523
+ results.aiSignalClarity
524
+ );
330
525
  toolScores.set('ai-signal-clarity', hrScore);
331
526
  } catch (err) {
332
- // ignore
527
+ void err;
333
528
  }
334
529
  }
335
530
 
336
531
  // Agent grounding score
337
532
  if (results.grounding) {
338
- const { calculateGroundingScore } = await import('@aiready/agent-grounding');
533
+ const { calculateGroundingScore } =
534
+ await import('@aiready/agent-grounding');
339
535
  try {
340
536
  const agScore = calculateGroundingScore(results.grounding);
341
537
  toolScores.set('agent-grounding', agScore);
342
538
  } catch (err) {
343
- // ignore
539
+ void err;
344
540
  }
345
541
  }
346
542
 
347
543
  // Testability score
348
544
  if (results.testability) {
349
- const { calculateTestabilityScore } = await import('@aiready/testability');
545
+ const { calculateTestabilityScore } =
546
+ await import('@aiready/testability');
350
547
  try {
351
548
  const tbScore = calculateTestabilityScore(results.testability);
352
549
  toolScores.set('testability', tbScore);
353
550
  } catch (err) {
354
- // ignore
551
+ void err;
355
552
  }
356
553
  }
357
554
 
@@ -362,7 +559,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
362
559
  score: results.docDrift.summary.score,
363
560
  rawMetrics: results.docDrift.rawData,
364
561
  factors: [],
365
- 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
+ ),
366
569
  });
367
570
  }
368
571
 
@@ -373,7 +576,30 @@ export async function scanAction(directory: string, options: ScanOptions) {
373
576
  score: results.deps.summary.score,
374
577
  rawMetrics: results.deps.rawData,
375
578
  factors: [],
376
- 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
+ ),
586
+ });
587
+ }
588
+
589
+ // Change Amplification score
590
+ if (results.changeAmplification) {
591
+ toolScores.set('change-amplification', {
592
+ toolName: 'change-amplification',
593
+ score: results.changeAmplification.summary.score,
594
+ rawMetrics: results.changeAmplification.rawData,
595
+ factors: [],
596
+ recommendations: (
597
+ results.changeAmplification.recommendations || []
598
+ ).map((action: string) => ({
599
+ action,
600
+ estimatedImpact: 5,
601
+ priority: 'medium',
602
+ })),
377
603
  });
378
604
  }
379
605
 
@@ -382,7 +608,11 @@ export async function scanAction(directory: string, options: ScanOptions) {
382
608
 
383
609
  // Only calculate overall score if we have at least one tool score
384
610
  if (toolScores.size > 0) {
385
- scoringResult = calculateOverallScore(toolScores, finalOptions, cliWeights.size ? cliWeights : undefined);
611
+ scoringResult = calculateOverallScore(
612
+ toolScores,
613
+ finalOptions,
614
+ cliWeights.size ? cliWeights : undefined
615
+ );
386
616
 
387
617
  console.log(chalk.bold('\n📊 AI Readiness Overall Score'));
388
618
  console.log(` ${formatScore(scoringResult)}`);
@@ -390,34 +620,59 @@ export async function scanAction(directory: string, options: ScanOptions) {
390
620
  // Check if we need to compare to a previous report
391
621
  if (options.compareTo) {
392
622
  try {
393
- const prevReportStr = readFileSync(resolvePath(process.cwd(), options.compareTo), 'utf8');
623
+ const prevReportStr = readFileSync(
624
+ resolvePath(process.cwd(), options.compareTo),
625
+ 'utf8'
626
+ );
394
627
  const prevReport = JSON.parse(prevReportStr);
395
- const prevScore = prevReport.scoring?.score || prevReport.scoring?.overallScore;
628
+ const prevScore =
629
+ prevReport.scoring?.score || prevReport.scoring?.overallScore;
396
630
 
397
631
  if (typeof prevScore === 'number') {
398
632
  const diff = scoringResult.overall - prevScore;
399
633
  const diffStr = diff > 0 ? `+${diff}` : String(diff);
400
634
  console.log();
401
635
  if (diff > 0) {
402
- 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
+ );
403
641
  } else if (diff < 0) {
404
- 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
+ );
405
647
  // Trend gating: if we regressed and CI is on or threshold is present, we could lower the threshold effectively,
406
648
  // but for now, we just highlight the regression.
407
649
  } else {
408
- 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
+ );
409
655
  }
410
656
 
411
657
  // Add trend info to scoringResult for programmatic use
412
658
  (scoringResult as any).trend = {
413
659
  previousScore: prevScore,
414
- difference: diff
660
+ difference: diff,
415
661
  };
416
662
  } else {
417
- 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
+ );
418
668
  }
419
669
  } catch (e) {
420
- 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
+ );
421
676
  }
422
677
  }
423
678
 
@@ -427,7 +682,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
427
682
  scoringResult.breakdown.forEach((tool) => {
428
683
  const rating = getRating(tool.score);
429
684
  const rd = getRatingDisplay(rating);
430
- console.log(` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`);
685
+ console.log(
686
+ ` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
687
+ );
431
688
  });
432
689
  console.log();
433
690
 
@@ -443,23 +700,56 @@ export async function scanAction(directory: string, options: ScanOptions) {
443
700
  }
444
701
 
445
702
  // Persist JSON summary when output format is json
446
- const outputFormat = options.output || finalOptions.output?.format || 'console';
703
+ const outputFormat =
704
+ options.output || finalOptions.output?.format || 'console';
447
705
  const userOutputFile = options.outputFile || finalOptions.output?.file;
448
706
  if (outputFormat === 'json') {
449
707
  const timestamp = getReportTimestamp();
450
708
  const defaultFilename = `aiready-report-${timestamp}.json`;
451
- const outputPath = resolveOutputPath(userOutputFile, defaultFilename, resolvedDir);
709
+ const outputPath = resolveOutputPath(
710
+ userOutputFile,
711
+ defaultFilename,
712
+ resolvedDir
713
+ );
452
714
  const outputData = { ...results, scoring: scoringResult };
453
- handleJSONOutput(outputData, outputPath, `✅ Report saved to ${outputPath}`);
715
+ handleJSONOutput(
716
+ outputData,
717
+ outputPath,
718
+ `✅ Report saved to ${outputPath}`
719
+ );
454
720
 
455
721
  // Warn if graph caps may be exceeded
456
- warnIfGraphCapExceeded(outputData, resolvedDir);
722
+ await warnIfGraphCapExceeded(outputData, resolvedDir);
723
+ } else {
724
+ // Auto-persist report even in console mode for downstream tools
725
+ const timestamp = getReportTimestamp();
726
+ const defaultFilename = `aiready-report-${timestamp}.json`;
727
+ const outputPath = resolveOutputPath(
728
+ userOutputFile,
729
+ defaultFilename,
730
+ resolvedDir
731
+ );
732
+ const outputData = { ...results, scoring: scoringResult };
733
+
734
+ try {
735
+ writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
736
+ console.log(chalk.dim(`✅ Report auto-persisted to ${outputPath}`));
737
+ // Warn if graph caps may be exceeded
738
+ await warnIfGraphCapExceeded(outputData, resolvedDir);
739
+ } catch (err) {
740
+ void err;
741
+ }
457
742
  }
458
743
 
459
744
  // CI/CD Gatekeeper Mode
460
- 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';
461
749
  if (isCI && scoringResult) {
462
- const threshold = options.threshold ? parseInt(options.threshold) : undefined;
750
+ const threshold = options.threshold
751
+ ? parseInt(options.threshold)
752
+ : undefined;
463
753
  const failOnLevel = options.failOn || 'critical';
464
754
 
465
755
  // Output GitHub Actions annotations
@@ -467,7 +757,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
467
757
  console.log(`\n::group::AI Readiness Score`);
468
758
  console.log(`score=${scoringResult.overall}`);
469
759
  if (scoringResult.breakdown) {
470
- scoringResult.breakdown.forEach(tool => {
760
+ scoringResult.breakdown.forEach((tool) => {
471
761
  console.log(`${tool.toolName}=${tool.score}`);
472
762
  });
473
763
  }
@@ -475,9 +765,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
475
765
 
476
766
  // Output annotation for score
477
767
  if (threshold && scoringResult.overall < threshold) {
478
- 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
+ );
479
771
  } else if (threshold) {
480
- 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
+ );
481
775
  }
482
776
 
483
777
  // Output annotations for critical issues
@@ -486,7 +780,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
486
780
  p.issues.filter((i: any) => i.severity === 'critical')
487
781
  );
488
782
  criticalPatterns.slice(0, 10).forEach((issue: any) => {
489
- 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
+ );
490
786
  });
491
787
  }
492
788
  }
@@ -504,7 +800,8 @@ export async function scanAction(directory: string, options: ScanOptions) {
504
800
  // Check fail-on severity
505
801
  if (failOnLevel !== 'none') {
506
802
  const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
507
- const minSeverity = severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
803
+ const minSeverity =
804
+ severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
508
805
 
509
806
  let criticalCount = 0;
510
807
  let majorCount = 0;
@@ -535,7 +832,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
535
832
  if (minSeverity >= 4 && criticalCount > 0) {
536
833
  shouldFail = true;
537
834
  failReason = `Found ${criticalCount} critical issues`;
538
- } else if (minSeverity >= 3 && (criticalCount + majorCount) > 0) {
835
+ } else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
539
836
  shouldFail = true;
540
837
  failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
541
838
  }
@@ -546,16 +843,30 @@ export async function scanAction(directory: string, options: ScanOptions) {
546
843
  console.log(chalk.red('\n🚫 PR BLOCKED: AI Readiness Check Failed'));
547
844
  console.log(chalk.red(` Reason: ${failReason}`));
548
845
  console.log(chalk.dim('\n Remediation steps:'));
549
- 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
+ );
550
849
  console.log(chalk.dim(' 2. Fix the critical issues before merging'));
551
- 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
+ );
552
855
  process.exit(1);
553
856
  } else {
554
857
  console.log(chalk.green('\n✅ PR PASSED: AI Readiness Check'));
555
858
  if (threshold) {
556
- 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
+ );
557
864
  }
558
- 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
+ );
559
870
  }
560
871
  }
561
872
  } catch (error) {
@@ -590,4 +901,4 @@ CI/CD INTEGRATION (Gatekeeper Mode):
590
901
  Example GitHub Actions workflow:
591
902
  - name: AI Readiness Check
592
903
  run: aiready scan --ci --threshold 70
593
- `;
904
+ `;