@aiready/cli 0.9.35 โ 0.9.38
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.
- package/.turbo/turbo-build.log +18 -7
- package/.turbo/turbo-test.log +5 -5
- package/dist/agent-grounding-DAOSU4MF.mjs +7 -0
- package/dist/chunk-G6SDH7ZS.mjs +126 -0
- package/dist/chunk-JQG7ZATX.mjs +211 -0
- package/dist/chunk-N4SLON5K.mjs +152 -0
- package/dist/chunk-RBWLQRKR.mjs +39 -0
- package/dist/chunk-XAF2EW5H.mjs +46 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/chunk-YIS6WTY5.mjs +35 -0
- package/dist/cli.js +223 -25
- package/dist/cli.mjs +153 -21
- package/dist/deps-health-UWVYJ7FZ.mjs +47 -0
- package/dist/doc-drift-G7MGAZAE.mjs +47 -0
- package/dist/hallucination-risk-XU6E7IGN.mjs +7 -0
- package/dist/index.js +95 -0
- package/dist/index.mjs +1 -1
- package/dist/testability-VDZJZ4MF.mjs +7 -0
- package/package.json +14 -9
- package/src/cli.ts +12 -4
- package/src/commands/agent-grounding.ts +47 -0
- package/src/commands/deps-health.ts +56 -0
- package/src/commands/doc-drift.ts +56 -0
- package/src/commands/hallucination-risk.ts +51 -0
- package/src/commands/index.ts +4 -1
- package/src/commands/scan.ts +161 -27
- package/src/commands/testability.ts +60 -0
- package/src/index.ts +101 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent grounding 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 agentGroundingAction(
|
|
10
|
+
directory: string,
|
|
11
|
+
options: any,
|
|
12
|
+
): Promise<ToolScoringOutput | undefined> {
|
|
13
|
+
const { analyzeAgentGrounding, calculateGroundingScore } = await import('@aiready/agent-grounding');
|
|
14
|
+
|
|
15
|
+
const config = await loadConfig(directory);
|
|
16
|
+
const merged = mergeConfigWithDefaults(config, {
|
|
17
|
+
maxRecommendedDepth: 4,
|
|
18
|
+
readmeStaleDays: 90,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const report = await analyzeAgentGrounding({
|
|
22
|
+
rootDir: directory,
|
|
23
|
+
maxRecommendedDepth: options.maxDepth ?? merged.maxRecommendedDepth,
|
|
24
|
+
readmeStaleDays: options.readmeStaleDays ?? merged.readmeStaleDays,
|
|
25
|
+
include: options.include,
|
|
26
|
+
exclude: options.exclude,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const scoring = calculateGroundingScore(report);
|
|
30
|
+
|
|
31
|
+
if (options.output === 'json') {
|
|
32
|
+
return scoring;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const scoreColor = (s: number) =>
|
|
36
|
+
s >= 85 ? chalk.green : s >= 70 ? chalk.cyan : s >= 50 ? chalk.yellow : chalk.red;
|
|
37
|
+
|
|
38
|
+
console.log(` ๐งญ Agent Grounding: ${chalk.bold(scoring.score + '/100')} (${report.summary.rating})`);
|
|
39
|
+
const dims = report.summary.dimensions;
|
|
40
|
+
const worstDim = Object.entries(dims).sort(([, a], [, b]) => a - b)[0];
|
|
41
|
+
if (worstDim && worstDim[1] < 70) {
|
|
42
|
+
const name = worstDim[0].replace(/([A-Z])/g, ' $1').replace('Score', '').trim();
|
|
43
|
+
console.log(chalk.dim(` Weakest dimension: ${name} (${worstDim[1]}/100)`));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return scoring;
|
|
47
|
+
}
|
|
@@ -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
|
+
}
|
package/src/commands/index.ts
CHANGED
|
@@ -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';
|
package/src/commands/scan.ts
CHANGED
|
@@ -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:
|
|
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 (results.hallucination) {
|
|
323
|
+
const { calculateHallucinationScore } = await import('@aiready/hallucination-risk');
|
|
324
|
+
try {
|
|
325
|
+
const hrScore = calculateHallucinationScore(results.hallucination);
|
|
326
|
+
toolScores.set('hallucination-risk', hrScore);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
// ignore
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Agent grounding score
|
|
333
|
+
if (results.grounding) {
|
|
334
|
+
const { calculateGroundingScore } = await import('@aiready/agent-grounding');
|
|
335
|
+
try {
|
|
336
|
+
const agScore = calculateGroundingScore(results.grounding);
|
|
337
|
+
toolScores.set('agent-grounding', agScore);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
// ignore
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Testability score
|
|
344
|
+
if (results.testability) {
|
|
345
|
+
const { calculateTestabilityScore } = await import('@aiready/testability');
|
|
346
|
+
try {
|
|
347
|
+
const tbScore = calculateTestabilityScore(results.testability);
|
|
348
|
+
toolScores.set('testability', tbScore);
|
|
349
|
+
} catch (err) {
|
|
350
|
+
// ignore
|
|
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.
|
|
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.
|
|
349
|
-
console.log(`::error::AI Readiness Score ${scoringResult.
|
|
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.
|
|
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.
|
|
495
|
+
if (threshold && scoringResult.overall < threshold) {
|
|
371
496
|
shouldFail = true;
|
|
372
|
-
failReason = `AI Readiness Score ${scoringResult.
|
|
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.
|
|
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
|
+
}
|