@aiready/cli 0.9.35 โ†’ 0.9.36

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.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Dependency health command for unified CLI
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { loadConfig, mergeConfigWithDefaults } from '@aiready/core';
7
+ import type { ToolScoringOutput } from '@aiready/core';
8
+
9
+ export async function depsHealthAction(
10
+ directory: string,
11
+ options: any,
12
+ ): Promise<ToolScoringOutput | undefined> {
13
+ const { analyzeDeps } = await import('@aiready/deps');
14
+
15
+ const config = await loadConfig(directory);
16
+ const merged = mergeConfigWithDefaults(config, {
17
+ trainingCutoffYear: 2023,
18
+ });
19
+
20
+ const report = await analyzeDeps({
21
+ rootDir: directory,
22
+ include: options.include,
23
+ exclude: options.exclude,
24
+ trainingCutoffYear: options.trainingCutoffYear ?? merged.trainingCutoffYear ?? 2023,
25
+ });
26
+
27
+ const scoring: ToolScoringOutput = {
28
+ toolName: 'dependency-health',
29
+ score: report.summary.score,
30
+ rawMetrics: report.rawData,
31
+ factors: [],
32
+ recommendations: report.recommendations.map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
33
+ };
34
+
35
+ if (options.output === 'json') {
36
+ return scoring;
37
+ }
38
+
39
+ const { summary } = report;
40
+ const ratingColors: Record<string, Function> = {
41
+ excellent: chalk.green,
42
+ good: chalk.blueBright,
43
+ moderate: chalk.yellow,
44
+ poor: chalk.red,
45
+ hazardous: chalk.bgRed.white,
46
+ };
47
+ const color = ratingColors[summary.rating] ?? chalk.white;
48
+ console.log(` ๐Ÿ“ฆ Dependency Health: ${chalk.bold(scoring.score + '/100 health')} (${color(summary.rating)})`);
49
+ if (report.issues.length > 0) {
50
+ console.log(chalk.dim(` Found ${report.issues.length} dependency issues.`));
51
+ } else {
52
+ console.log(chalk.dim(` Dependencies look healthy for AI assistance.`));
53
+ }
54
+
55
+ return scoring;
56
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Doc drift risk command for unified CLI
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { loadConfig, mergeConfigWithDefaults } from '@aiready/core';
7
+ import type { ToolScoringOutput } from '@aiready/core';
8
+
9
+ export async function docDriftAction(
10
+ directory: string,
11
+ options: any,
12
+ ): Promise<ToolScoringOutput | undefined> {
13
+ const { analyzeDocDrift } = await import('@aiready/doc-drift');
14
+
15
+ const config = await loadConfig(directory);
16
+ const merged = mergeConfigWithDefaults(config, {
17
+ staleMonths: 6,
18
+ });
19
+
20
+ const report = await analyzeDocDrift({
21
+ rootDir: directory,
22
+ include: options.include,
23
+ exclude: options.exclude,
24
+ staleMonths: options.staleMonths ?? merged.staleMonths ?? 6,
25
+ });
26
+
27
+ const scoring: ToolScoringOutput = {
28
+ toolName: 'doc-drift',
29
+ score: report.summary.score,
30
+ rawMetrics: report.rawData,
31
+ factors: [],
32
+ recommendations: report.recommendations.map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
33
+ };
34
+
35
+ if (options.output === 'json') {
36
+ return scoring;
37
+ }
38
+
39
+ const { summary } = report;
40
+ const ratingColors: Record<string, Function> = {
41
+ minimal: chalk.green,
42
+ low: chalk.cyan,
43
+ moderate: chalk.yellow,
44
+ high: chalk.red,
45
+ severe: chalk.bgRed.white,
46
+ };
47
+ const color = ratingColors[summary.rating] ?? chalk.white;
48
+ console.log(` ๐Ÿ“ Documentation Drift: ${chalk.bold(100 - scoring.score + '/100 health')} (${color(summary.rating)} risk)`);
49
+ if (report.issues.length > 0) {
50
+ console.log(chalk.dim(` Found ${report.issues.length} drift issues.`));
51
+ } else {
52
+ console.log(chalk.dim(` No documentation drift detected.`));
53
+ }
54
+
55
+ return scoring;
56
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Hallucination risk command for unified CLI
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { writeFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { resolveOutputPath, loadConfig, mergeConfigWithDefaults } from '@aiready/core';
9
+ import type { ToolScoringOutput } from '@aiready/core';
10
+
11
+ export async function hallucinationRiskAction(
12
+ directory: string,
13
+ options: any,
14
+ ): Promise<ToolScoringOutput | undefined> {
15
+ const { analyzeHallucinationRisk, calculateHallucinationScore } = await import('@aiready/hallucination-risk');
16
+
17
+ const config = await loadConfig(directory);
18
+ const merged = mergeConfigWithDefaults(config, {
19
+ minSeverity: 'info',
20
+ });
21
+
22
+ const report = await analyzeHallucinationRisk({
23
+ rootDir: directory,
24
+ minSeverity: options.minSeverity ?? merged.minSeverity ?? 'info',
25
+ include: options.include,
26
+ exclude: options.exclude,
27
+ });
28
+
29
+ const scoring = calculateHallucinationScore(report);
30
+
31
+ if (options.output === 'json') {
32
+ return scoring;
33
+ }
34
+
35
+ const { summary } = report;
36
+ const ratingColors: Record<string, Function> = {
37
+ minimal: chalk.green,
38
+ low: chalk.cyan,
39
+ moderate: chalk.yellow,
40
+ high: chalk.red,
41
+ severe: chalk.bgRed.white,
42
+ };
43
+ const color = ratingColors[summary.rating] ?? chalk.white;
44
+ console.log(` ๐Ÿง  Hallucination Risk: ${chalk.bold(scoring.score + '/100')} (${color(summary.rating)})`);
45
+ console.log(` Top Risk: ${chalk.italic(summary.topRisk)}`);
46
+ if (summary.totalSignals > 0) {
47
+ console.log(chalk.dim(` ${summary.criticalSignals} critical ${summary.majorSignals} major ${summary.minorSignals} minor signals`));
48
+ }
49
+
50
+ return scoring;
51
+ }
@@ -6,4 +6,7 @@ export { scanAction, scanHelpText } from './scan';
6
6
  export { patternsAction, patternsHelpText } from './patterns';
7
7
  export { contextAction } from './context';
8
8
  export { consistencyAction } from './consistency';
9
- export { visualizeAction, visualizeHelpText, visualiseHelpText } from './visualize';
9
+ export { visualizeAction, visualizeHelpText, visualiseHelpText } from './visualize';
10
+ export { hallucinationRiskAction } from './hallucination-risk';
11
+ export { agentGroundingAction } from './agent-grounding';
12
+ export { testabilityAction } from './testability';
@@ -3,14 +3,14 @@
3
3
  */
4
4
 
5
5
  import chalk from 'chalk';
6
- import { writeFileSync } from 'fs';
6
+ import { writeFileSync, readFileSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { resolve as resolvePath } from 'path';
9
- import {
10
- loadMergedConfig,
11
- handleJSONOutput,
12
- handleCLIError,
13
- getElapsedTime,
9
+ import {
10
+ loadMergedConfig,
11
+ handleJSONOutput,
12
+ handleCLIError,
13
+ getElapsedTime,
14
14
  resolveOutputPath,
15
15
  calculateOverallScore,
16
16
  formatScore,
@@ -25,6 +25,8 @@ import { getReportTimestamp, warnIfGraphCapExceeded, truncateArray } from '../ut
25
25
 
26
26
  interface ScanOptions {
27
27
  tools?: string;
28
+ profile?: string;
29
+ compareTo?: string;
28
30
  include?: string;
29
31
  exclude?: string;
30
32
  output?: string;
@@ -47,7 +49,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
47
49
  try {
48
50
  // Define defaults
49
51
  const defaults = {
50
- tools: ['patterns', 'context', 'consistency'],
52
+ tools: ['patterns', 'context', 'consistency', 'hallucination', 'grounding', 'testability', 'doc-drift', 'deps-health'],
51
53
  include: undefined,
52
54
  exclude: undefined,
53
55
  output: {
@@ -56,9 +58,29 @@ export async function scanAction(directory: string, options: ScanOptions) {
56
58
  },
57
59
  };
58
60
 
61
+ let profileTools = options.tools ? options.tools.split(',').map((t: string) => t.trim()) : undefined;
62
+ if (options.profile) {
63
+ switch (options.profile.toLowerCase()) {
64
+ case 'agentic':
65
+ profileTools = ['hallucination', 'grounding', 'testability'];
66
+ break;
67
+ case 'cost':
68
+ profileTools = ['patterns', 'context'];
69
+ break;
70
+ case 'security':
71
+ profileTools = ['consistency', 'testability'];
72
+ break;
73
+ case 'onboarding':
74
+ profileTools = ['context', 'consistency', 'grounding'];
75
+ break;
76
+ default:
77
+ console.log(chalk.yellow(`\nโš ๏ธ Unknown profile '${options.profile}'. Using specified tools or defaults.`));
78
+ }
79
+ }
80
+
59
81
  // Load and merge config with CLI options
60
82
  const baseOptions = await loadMergedConfig(resolvedDir, defaults, {
61
- tools: options.tools ? options.tools.split(',').map((t: string) => t.trim()) as ('patterns' | 'context' | 'consistency')[] : undefined,
83
+ tools: profileTools as any,
62
84
  include: options.include?.split(','),
63
85
  exclude: options.exclude?.split(','),
64
86
  }) as any;
@@ -210,7 +232,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
210
232
  const sample = issues.find((it: any) => it.severity === 'critical' || it.severity === 'major') || issues[0];
211
233
  const sampleMsg = sample ? ` โ€” ${sample.message}` : '';
212
234
 
213
- 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}`);
235
+ 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}`);
214
236
  });
215
237
 
216
238
  const remaining = files.length - topFiles.length;
@@ -218,6 +240,20 @@ export async function scanAction(directory: string, options: ScanOptions) {
218
240
  console.log(chalk.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
219
241
  }
220
242
  }
243
+ } else if (event.tool === 'doc-drift') {
244
+ const dr = event.data as any;
245
+ console.log(` Issues found: ${chalk.bold(String(dr.issues?.length || 0))}`);
246
+ if (dr.rawData) {
247
+ console.log(` Signature Mismatches: ${chalk.bold(dr.rawData.outdatedComments || 0)}`);
248
+ console.log(` Undocumented Complexity: ${chalk.bold(dr.rawData.undocumentedComplexity || 0)}`);
249
+ }
250
+ } else if (event.tool === 'deps-health') {
251
+ const dr = event.data as any;
252
+ console.log(` Packages Analyzed: ${chalk.bold(String(dr.summary?.packagesAnalyzed || 0))}`);
253
+ if (dr.rawData) {
254
+ console.log(` Deprecated Packages: ${chalk.bold(dr.rawData.deprecatedPackages || 0)}`);
255
+ console.log(` AI Cutoff Skew Score: ${chalk.bold(dr.rawData.trainingCutoffSkew?.toFixed(1) || 0)}`);
256
+ }
221
257
  }
222
258
  } catch (err) {
223
259
  // don't crash the run for progress printing errors
@@ -282,6 +318,61 @@ export async function scanAction(directory: string, options: ScanOptions) {
282
318
  }
283
319
  }
284
320
 
321
+ // Hallucination risk score
322
+ if (finalOptions.tools.includes('hallucination') || finalOptions.tools.includes('hallucination-risk')) {
323
+ try {
324
+ const { hallucinationRiskAction } = await import('./hallucination-risk');
325
+ const hrScore = await hallucinationRiskAction(resolvedDir, { ...finalOptions, output: 'json' });
326
+ if (hrScore) toolScores.set('hallucination-risk', hrScore);
327
+ } catch (err) {
328
+ // ignore if spoke not installed yet
329
+ }
330
+ }
331
+
332
+ // Agent grounding score
333
+ if (finalOptions.tools.includes('grounding') || finalOptions.tools.includes('agent-grounding')) {
334
+ try {
335
+ const { agentGroundingAction } = await import('./agent-grounding');
336
+ const agScore = await agentGroundingAction(resolvedDir, { ...finalOptions, output: 'json' });
337
+ if (agScore) toolScores.set('agent-grounding', agScore);
338
+ } catch (err) {
339
+ // ignore if spoke not installed yet
340
+ }
341
+ }
342
+
343
+ // Testability score
344
+ if (finalOptions.tools.includes('testability')) {
345
+ try {
346
+ const { testabilityAction } = await import('./testability');
347
+ const tbScore = await testabilityAction(resolvedDir, { ...finalOptions, output: 'json' });
348
+ if (tbScore) toolScores.set('testability', tbScore);
349
+ } catch (err) {
350
+ // ignore if spoke not installed yet
351
+ }
352
+ }
353
+
354
+ // Documentation Drift score
355
+ if (results.docDrift) {
356
+ toolScores.set('doc-drift', {
357
+ toolName: 'doc-drift',
358
+ score: results.docDrift.summary.score,
359
+ rawMetrics: results.docDrift.rawData,
360
+ factors: [],
361
+ recommendations: results.docDrift.recommendations.map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
362
+ });
363
+ }
364
+
365
+ // Dependency Health score
366
+ if (results.deps) {
367
+ toolScores.set('dependency-health', {
368
+ toolName: 'dependency-health',
369
+ score: results.deps.summary.score,
370
+ rawMetrics: results.deps.rawData,
371
+ factors: [],
372
+ recommendations: results.deps.recommendations.map((action: string) => ({ action, estimatedImpact: 5, priority: 'medium' }))
373
+ });
374
+ }
375
+
285
376
  // Parse CLI weight overrides (if any)
286
377
  const cliWeights = parseWeightString((options as any).weights);
287
378
 
@@ -292,6 +383,40 @@ export async function scanAction(directory: string, options: ScanOptions) {
292
383
  console.log(chalk.bold('\n๐Ÿ“Š AI Readiness Overall Score'));
293
384
  console.log(` ${formatScore(scoringResult)}`);
294
385
 
386
+ // Check if we need to compare to a previous report
387
+ if (options.compareTo) {
388
+ try {
389
+ const prevReportStr = readFileSync(resolvePath(process.cwd(), options.compareTo), 'utf8');
390
+ const prevReport = JSON.parse(prevReportStr);
391
+ const prevScore = prevReport.scoring?.score || prevReport.scoring?.overallScore;
392
+
393
+ if (typeof prevScore === 'number') {
394
+ const diff = scoringResult.overall - prevScore;
395
+ const diffStr = diff > 0 ? `+${diff}` : String(diff);
396
+ console.log();
397
+ if (diff > 0) {
398
+ console.log(chalk.green(` ๐Ÿ“ˆ Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} โ†’ ${scoringResult.overall})`));
399
+ } else if (diff < 0) {
400
+ console.log(chalk.red(` ๐Ÿ“‰ Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} โ†’ ${scoringResult.overall})`));
401
+ // Trend gating: if we regressed and CI is on or threshold is present, we could lower the threshold effectively,
402
+ // but for now, we just highlight the regression.
403
+ } else {
404
+ console.log(chalk.blue(` โž– Trend: No change compared to ${options.compareTo} (${prevScore} โ†’ ${scoringResult.overall})`));
405
+ }
406
+
407
+ // Add trend info to scoringResult for programmatic use
408
+ (scoringResult as any).trend = {
409
+ previousScore: prevScore,
410
+ difference: diff
411
+ };
412
+ } else {
413
+ console.log(chalk.yellow(`\n โš ๏ธ Previous report at ${options.compareTo} does not contain an overall score.`));
414
+ }
415
+ } catch (e) {
416
+ console.log(chalk.yellow(`\n โš ๏ธ Could not read or parse previous report at ${options.compareTo}.`));
417
+ }
418
+ }
419
+
295
420
  // Show concise breakdown; detailed breakdown only if config requests it
296
421
  if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
297
422
  console.log(chalk.bold('\nTool breakdown:'));
@@ -322,7 +447,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
322
447
  const outputPath = resolveOutputPath(userOutputFile, defaultFilename, resolvedDir);
323
448
  const outputData = { ...results, scoring: scoringResult };
324
449
  handleJSONOutput(outputData, outputPath, `โœ… Report saved to ${outputPath}`);
325
-
450
+
326
451
  // Warn if graph caps may be exceeded
327
452
  warnIfGraphCapExceeded(outputData, resolvedDir);
328
453
  }
@@ -332,28 +457,28 @@ export async function scanAction(directory: string, options: ScanOptions) {
332
457
  if (isCI && scoringResult) {
333
458
  const threshold = options.threshold ? parseInt(options.threshold) : undefined;
334
459
  const failOnLevel = options.failOn || 'critical';
335
-
460
+
336
461
  // Output GitHub Actions annotations
337
462
  if (process.env.GITHUB_ACTIONS === 'true') {
338
463
  console.log(`\n::group::AI Readiness Score`);
339
- console.log(`score=${scoringResult.overallScore}`);
464
+ console.log(`score=${scoringResult.overall}`);
340
465
  if (scoringResult.breakdown) {
341
466
  scoringResult.breakdown.forEach(tool => {
342
467
  console.log(`${tool.toolName}=${tool.score}`);
343
468
  });
344
469
  }
345
470
  console.log('::endgroup::');
346
-
471
+
347
472
  // Output annotation for score
348
- if (threshold && scoringResult.overallScore < threshold) {
349
- console.log(`::error::AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`);
473
+ if (threshold && scoringResult.overall < threshold) {
474
+ console.log(`::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`);
350
475
  } else if (threshold) {
351
- console.log(`::notice::AI Readiness Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`);
476
+ console.log(`::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`);
352
477
  }
353
-
478
+
354
479
  // Output annotations for critical issues
355
480
  if (results.patterns) {
356
- const criticalPatterns = results.patterns.flatMap((p: any) =>
481
+ const criticalPatterns = results.patterns.flatMap((p: any) =>
357
482
  p.issues.filter((i: any) => i.severity === 'critical')
358
483
  );
359
484
  criticalPatterns.slice(0, 10).forEach((issue: any) => {
@@ -365,21 +490,21 @@ export async function scanAction(directory: string, options: ScanOptions) {
365
490
  // Determine if we should fail
366
491
  let shouldFail = false;
367
492
  let failReason = '';
368
-
493
+
369
494
  // Check threshold
370
- if (threshold && scoringResult.overallScore < threshold) {
495
+ if (threshold && scoringResult.overall < threshold) {
371
496
  shouldFail = true;
372
- failReason = `AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`;
497
+ failReason = `AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`;
373
498
  }
374
-
499
+
375
500
  // Check fail-on severity
376
501
  if (failOnLevel !== 'none') {
377
502
  const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
378
503
  const minSeverity = severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
379
-
504
+
380
505
  let criticalCount = 0;
381
506
  let majorCount = 0;
382
-
507
+
383
508
  if (results.patterns) {
384
509
  results.patterns.forEach((p: any) => {
385
510
  p.issues.forEach((i: any) => {
@@ -402,7 +527,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
402
527
  });
403
528
  });
404
529
  }
405
-
530
+
406
531
  if (minSeverity >= 4 && criticalCount > 0) {
407
532
  shouldFail = true;
408
533
  failReason = `Found ${criticalCount} critical issues`;
@@ -411,7 +536,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
411
536
  failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
412
537
  }
413
538
  }
414
-
539
+
415
540
  // Output result
416
541
  if (shouldFail) {
417
542
  console.log(chalk.red('\n๐Ÿšซ PR BLOCKED: AI Readiness Check Failed'));
@@ -424,7 +549,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
424
549
  } else {
425
550
  console.log(chalk.green('\nโœ… PR PASSED: AI Readiness Check'));
426
551
  if (threshold) {
427
- console.log(chalk.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
552
+ console.log(chalk.green(` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`));
428
553
  }
429
554
  console.log(chalk.dim('\n ๐Ÿ’ก Track historical trends: https://getaiready.dev โ€” Team plan $99/mo'));
430
555
  }
@@ -438,11 +563,20 @@ export const scanHelpText = `
438
563
  EXAMPLES:
439
564
  $ aiready scan # Analyze all tools
440
565
  $ aiready scan --tools patterns,context # Skip consistency
566
+ $ aiready scan --profile agentic # Optimize for AI agent execution
567
+ $ aiready scan --profile security # Optimize for secure coding (testability)
568
+ $ aiready scan --compare-to prev-report.json # Compare trends against previous run
441
569
  $ aiready scan --score --threshold 75 # CI/CD with threshold
442
570
  $ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
443
571
  $ aiready scan --ci --fail-on major # Fail on major+ issues
444
572
  $ aiready scan --output json --output-file report.json
445
573
 
574
+ PROFILES:
575
+ agentic: hallucination, grounding, testability
576
+ cost: patterns, context
577
+ security: consistency, testability
578
+ onboarding: context, consistency, grounding
579
+
446
580
  CI/CD INTEGRATION (Gatekeeper Mode):
447
581
  Use --ci for GitHub Actions integration:
448
582
  - Outputs GitHub Actions annotations for PR checks
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Testability command for unified CLI
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { loadConfig, mergeConfigWithDefaults } from '@aiready/core';
7
+ import type { ToolScoringOutput } from '@aiready/core';
8
+
9
+ export async function testabilityAction(
10
+ directory: string,
11
+ options: any,
12
+ ): Promise<ToolScoringOutput | undefined> {
13
+ const { analyzeTestability, calculateTestabilityScore } = await import('@aiready/testability');
14
+
15
+ const config = await loadConfig(directory);
16
+ const merged = mergeConfigWithDefaults(config, {
17
+ minCoverageRatio: 0.3,
18
+ });
19
+
20
+ const report = await analyzeTestability({
21
+ rootDir: directory,
22
+ minCoverageRatio: options.minCoverageRatio ?? merged.minCoverageRatio,
23
+ include: options.include,
24
+ exclude: options.exclude,
25
+ });
26
+
27
+ const scoring = calculateTestabilityScore(report);
28
+
29
+ if (options.output === 'json') {
30
+ return scoring;
31
+ }
32
+
33
+ const safetyIcons: Record<string, string> = {
34
+ 'safe': 'โœ…',
35
+ 'moderate-risk': 'โš ๏ธ ',
36
+ 'high-risk': '๐Ÿ”ด',
37
+ 'blind-risk': '๐Ÿ’€',
38
+ };
39
+ const safetyColors: Record<string, Function> = {
40
+ 'safe': chalk.green,
41
+ 'moderate-risk': chalk.yellow,
42
+ 'high-risk': chalk.red,
43
+ 'blind-risk': chalk.bgRed.white,
44
+ };
45
+
46
+ const safety = report.summary.aiChangeSafetyRating;
47
+ const icon = safetyIcons[safety] ?? 'โ“';
48
+ const color = safetyColors[safety] ?? chalk.white;
49
+
50
+ console.log(` ๐Ÿงช Testability: ${chalk.bold(scoring.score + '/100')} (${report.summary.rating})`);
51
+ console.log(` AI Change Safety: ${color(`${icon} ${safety.toUpperCase()}`)}`);
52
+ console.log(chalk.dim(` Coverage: ${Math.round(report.summary.coverageRatio * 100)}% (${report.rawData.testFiles} test / ${report.rawData.sourceFiles} source files)`));
53
+
54
+ // Critical blind-risk banner in the unified output
55
+ if (safety === 'blind-risk') {
56
+ console.log(chalk.red.bold('\n โš ๏ธ NO TESTS โ€” AI changes to this codebase are completely unverifiable!\n'));
57
+ }
58
+
59
+ return scoring;
60
+ }
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ import type { ConsistencyReport } from '@aiready/consistency';
9
9
  import type { ConsistencyOptions } from '@aiready/consistency';
10
10
 
11
11
  export interface UnifiedAnalysisOptions extends ScanOptions {
12
- tools?: ('patterns' | 'context' | 'consistency')[];
12
+ tools?: ('patterns' | 'context' | 'consistency' | 'doc-drift' | 'deps-health')[];
13
13
  minSimilarity?: number;
14
14
  minLines?: number;
15
15
  maxCandidatesPerBlock?: number;
@@ -24,6 +24,8 @@ export interface UnifiedAnalysisResult {
24
24
  duplicates?: DuplicatePattern[]; // Store actual duplicates for scoring
25
25
  context?: ContextAnalysisResult[];
26
26
  consistency?: ConsistencyReport;
27
+ docDrift?: any;
28
+ deps?: any;
27
29
  summary: {
28
30
  totalIssues: number;
29
31
  toolsRun: string[];
@@ -138,6 +140,36 @@ export async function analyzeUnified(
138
140
  result.summary.totalIssues += report.summary.totalIssues;
139
141
  }
140
142
 
143
+ // Run Documentation Drift analysis
144
+ if (tools.includes('doc-drift')) {
145
+ const { analyzeDocDrift } = await import('@aiready/doc-drift');
146
+ const report = await analyzeDocDrift({
147
+ rootDir: options.rootDir,
148
+ include: options.include,
149
+ exclude: options.exclude,
150
+ });
151
+ if (options.progressCallback) {
152
+ options.progressCallback({ tool: 'doc-drift', data: report });
153
+ }
154
+ result.docDrift = report;
155
+ result.summary.totalIssues += report.issues?.length || 0;
156
+ }
157
+
158
+ // Run Dependency Health analysis
159
+ if (tools.includes('deps-health')) {
160
+ const { analyzeDeps } = await import('@aiready/deps');
161
+ const report = await analyzeDeps({
162
+ rootDir: options.rootDir,
163
+ include: options.include,
164
+ exclude: options.exclude,
165
+ });
166
+ if (options.progressCallback) {
167
+ options.progressCallback({ tool: 'deps-health', data: report });
168
+ }
169
+ result.deps = report;
170
+ result.summary.totalIssues += report.issues?.length || 0;
171
+ }
172
+
141
173
  result.summary.executionTime = Date.now() - startTime;
142
174
  return result;
143
175
  }
@@ -162,5 +194,13 @@ export function generateUnifiedSummary(result: UnifiedAnalysisResult): string {
162
194
  output += `๐Ÿท๏ธ Consistency Analysis: ${result.consistency.summary.totalIssues} issues\n`;
163
195
  }
164
196
 
197
+ if (result.docDrift) {
198
+ output += `๐Ÿ“ Doc Drift Analysis: ${result.docDrift.issues?.length || 0} issues\n`;
199
+ }
200
+
201
+ if (result.deps) {
202
+ output += `๐Ÿ“ฆ Dependency Health: ${result.deps.issues?.length || 0} issues\n`;
203
+ }
204
+
165
205
  return output;
166
206
  }