@aiready/cli 0.10.6 ā 0.12.0
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 +8 -8
- package/.turbo/turbo-lint.log +9 -1
- package/.turbo/turbo-test.log +16 -4
- package/dist/chunk-N56YAZVN.mjs +194 -0
- package/dist/chunk-YBZKPKW3.mjs +161 -0
- package/dist/cli.js +198 -910
- package/dist/cli.mjs +86 -582
- package/dist/index.js +116 -371
- package/dist/index.mjs +1 -1
- package/package.json +12 -12
- package/src/.aiready/aiready-report-20260307-092852.json +50609 -0
- package/src/.aiready/aiready-report-20260307-094301.json +50609 -0
- package/src/__tests__/cli.test.ts +55 -29
- package/src/commands/scan.ts +107 -699
- package/src/index.ts +154 -455
- package/dist/chunk-EQ2HQSTJ.mjs +0 -414
- package/dist/chunk-R3O7QPKD.mjs +0 -419
- package/dist/chunk-VQCWYJYJ.mjs +0 -438
- package/dist/chunk-VUCNUYI7.mjs +0 -417
package/src/commands/scan.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scan command - Run comprehensive AI-readiness analysis
|
|
2
|
+
* Scan command - Run comprehensive AI-readiness analysis using the tool registry
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { writeFileSync, readFileSync } from 'fs';
|
|
7
|
-
// 'join' was unused
|
|
8
7
|
import { resolve as resolvePath } from 'path';
|
|
9
8
|
import {
|
|
10
9
|
loadMergedConfig,
|
|
@@ -12,7 +11,6 @@ import {
|
|
|
12
11
|
handleCLIError,
|
|
13
12
|
getElapsedTime,
|
|
14
13
|
resolveOutputPath,
|
|
15
|
-
calculateOverallScore,
|
|
16
14
|
formatScore,
|
|
17
15
|
formatToolScore,
|
|
18
16
|
calculateTokenBudget,
|
|
@@ -20,12 +18,11 @@ import {
|
|
|
20
18
|
getModelPreset,
|
|
21
19
|
getRating,
|
|
22
20
|
getRatingDisplay,
|
|
23
|
-
parseWeightString,
|
|
24
21
|
getRepoMetadata,
|
|
25
22
|
Severity,
|
|
26
23
|
IssueType,
|
|
27
24
|
ToolName,
|
|
28
|
-
|
|
25
|
+
ToolRegistry,
|
|
29
26
|
} from '@aiready/core';
|
|
30
27
|
import { analyzeUnified, scoreUnified, type ScoringResult } from '../index';
|
|
31
28
|
import {
|
|
@@ -59,24 +56,21 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
59
56
|
console.log(chalk.blue('š Starting AIReady unified analysis...\n'));
|
|
60
57
|
|
|
61
58
|
const startTime = Date.now();
|
|
62
|
-
// Resolve directory to absolute path to ensure .aiready/ is created in the right location
|
|
63
59
|
const resolvedDir = resolvePath(process.cwd(), directory || '.');
|
|
64
|
-
|
|
65
|
-
// Extract repo metadata for linkage
|
|
66
60
|
const repoMetadata = getRepoMetadata(resolvedDir);
|
|
67
61
|
|
|
68
62
|
try {
|
|
69
|
-
// Define defaults
|
|
63
|
+
// Define defaults using canonical IDs
|
|
70
64
|
const defaults = {
|
|
71
65
|
tools: [
|
|
72
|
-
'
|
|
73
|
-
'context',
|
|
74
|
-
'consistency',
|
|
66
|
+
'pattern-detect',
|
|
67
|
+
'context-analyzer',
|
|
68
|
+
'naming-consistency',
|
|
75
69
|
'ai-signal-clarity',
|
|
76
70
|
'agent-grounding',
|
|
77
|
-
'testability',
|
|
71
|
+
'testability-index',
|
|
78
72
|
'doc-drift',
|
|
79
|
-
'
|
|
73
|
+
'dependency-health',
|
|
80
74
|
'change-amplification',
|
|
81
75
|
],
|
|
82
76
|
include: undefined,
|
|
@@ -87,49 +81,49 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
87
81
|
},
|
|
88
82
|
};
|
|
89
83
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (tool === 'hallucination' || tool === 'hallucination-risk')
|
|
94
|
-
return 'aiSignalClarity';
|
|
95
|
-
return tool;
|
|
96
|
-
})
|
|
84
|
+
// Map profile to tool IDs
|
|
85
|
+
let profileTools: string[] | undefined = options.tools
|
|
86
|
+
? options.tools.split(',').map((t) => t.trim())
|
|
97
87
|
: undefined;
|
|
98
88
|
if (options.profile) {
|
|
99
89
|
switch (options.profile.toLowerCase()) {
|
|
100
90
|
case 'agentic':
|
|
101
91
|
profileTools = [
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
ToolName.AiSignalClarity,
|
|
93
|
+
ToolName.AgentGrounding,
|
|
94
|
+
ToolName.TestabilityIndex,
|
|
105
95
|
];
|
|
106
96
|
break;
|
|
107
97
|
case 'cost':
|
|
108
|
-
profileTools = [
|
|
98
|
+
profileTools = [ToolName.PatternDetect, ToolName.ContextAnalyzer];
|
|
109
99
|
break;
|
|
110
100
|
case 'security':
|
|
111
|
-
profileTools = [
|
|
101
|
+
profileTools = [
|
|
102
|
+
ToolName.NamingConsistency,
|
|
103
|
+
ToolName.TestabilityIndex,
|
|
104
|
+
];
|
|
112
105
|
break;
|
|
113
106
|
case 'onboarding':
|
|
114
|
-
profileTools = [
|
|
107
|
+
profileTools = [
|
|
108
|
+
ToolName.ContextAnalyzer,
|
|
109
|
+
ToolName.NamingConsistency,
|
|
110
|
+
ToolName.AgentGrounding,
|
|
111
|
+
];
|
|
115
112
|
break;
|
|
116
113
|
default:
|
|
117
114
|
console.log(
|
|
118
115
|
chalk.yellow(
|
|
119
|
-
`\nā ļø Unknown profile '${options.profile}'. Using
|
|
116
|
+
`\nā ļø Unknown profile '${options.profile}'. Using defaults.`
|
|
120
117
|
)
|
|
121
118
|
);
|
|
122
119
|
}
|
|
123
120
|
}
|
|
124
121
|
|
|
125
|
-
// Load and merge config with CLI options
|
|
126
122
|
const cliOverrides: any = {
|
|
127
123
|
include: options.include?.split(','),
|
|
128
124
|
exclude: options.exclude?.split(','),
|
|
129
125
|
};
|
|
130
|
-
if (profileTools)
|
|
131
|
-
cliOverrides.tools = profileTools;
|
|
132
|
-
}
|
|
126
|
+
if (profileTools) cliOverrides.tools = profileTools;
|
|
133
127
|
|
|
134
128
|
const baseOptions = (await loadMergedConfig(
|
|
135
129
|
resolvedDir,
|
|
@@ -137,15 +131,17 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
137
131
|
cliOverrides
|
|
138
132
|
)) as any;
|
|
139
133
|
|
|
140
|
-
// Apply smart defaults for pattern detection if
|
|
134
|
+
// Apply smart defaults for pattern detection if requested
|
|
141
135
|
let finalOptions = { ...baseOptions };
|
|
142
|
-
if (
|
|
136
|
+
if (
|
|
137
|
+
baseOptions.tools.includes(ToolName.PatternDetect) ||
|
|
138
|
+
baseOptions.tools.includes('patterns')
|
|
139
|
+
) {
|
|
143
140
|
const { getSmartDefaults } = await import('@aiready/pattern-detect');
|
|
144
141
|
const patternSmartDefaults = await getSmartDefaults(
|
|
145
142
|
resolvedDir,
|
|
146
143
|
baseOptions
|
|
147
144
|
);
|
|
148
|
-
// Merge deeply to preserve nested config
|
|
149
145
|
finalOptions = {
|
|
150
146
|
...patternSmartDefaults,
|
|
151
147
|
...finalOptions,
|
|
@@ -153,291 +149,25 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
153
149
|
};
|
|
154
150
|
}
|
|
155
151
|
|
|
156
|
-
// Print pre-run summary with expanded settings (truncate long arrays)
|
|
157
152
|
console.log(chalk.cyan('\n=== AIReady Run Preview ==='));
|
|
158
153
|
console.log(
|
|
159
154
|
chalk.white('Tools to run:'),
|
|
160
|
-
(finalOptions.tools || [
|
|
155
|
+
(finalOptions.tools || []).join(', ')
|
|
161
156
|
);
|
|
162
|
-
console.log(chalk.white('Will use settings from config and defaults.'));
|
|
163
|
-
|
|
164
|
-
// Common top-level settings
|
|
165
|
-
console.log(chalk.white('\nGeneral settings:'));
|
|
166
|
-
if (finalOptions.rootDir)
|
|
167
|
-
console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
|
|
168
|
-
if (finalOptions.include)
|
|
169
|
-
console.log(
|
|
170
|
-
` include: ${chalk.bold(truncateArray(finalOptions.include, 6))}`
|
|
171
|
-
);
|
|
172
|
-
if (finalOptions.exclude)
|
|
173
|
-
console.log(
|
|
174
|
-
` exclude: ${chalk.bold(truncateArray(finalOptions.exclude, 6))}`
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
if (finalOptions['pattern-detect'] || finalOptions.minSimilarity) {
|
|
178
|
-
const patternDetectConfig = finalOptions['pattern-detect'] || {
|
|
179
|
-
minSimilarity: finalOptions.minSimilarity,
|
|
180
|
-
minLines: finalOptions.minLines,
|
|
181
|
-
approx: finalOptions.approx,
|
|
182
|
-
minSharedTokens: finalOptions.minSharedTokens,
|
|
183
|
-
maxCandidatesPerBlock: finalOptions.maxCandidatesPerBlock,
|
|
184
|
-
batchSize: finalOptions.batchSize,
|
|
185
|
-
streamResults: finalOptions.streamResults,
|
|
186
|
-
severity: (finalOptions as any).severity,
|
|
187
|
-
includeTests: (finalOptions as any).includeTests,
|
|
188
|
-
};
|
|
189
|
-
console.log(chalk.white('\nPattern-detect settings:'));
|
|
190
|
-
console.log(
|
|
191
|
-
` minSimilarity: ${chalk.bold(patternDetectConfig.minSimilarity ?? 'default')}`
|
|
192
|
-
);
|
|
193
|
-
console.log(
|
|
194
|
-
` minLines: ${chalk.bold(patternDetectConfig.minLines ?? 'default')}`
|
|
195
|
-
);
|
|
196
|
-
if (patternDetectConfig.approx !== undefined)
|
|
197
|
-
console.log(
|
|
198
|
-
` approx: ${chalk.bold(String(patternDetectConfig.approx))}`
|
|
199
|
-
);
|
|
200
|
-
if (patternDetectConfig.minSharedTokens !== undefined)
|
|
201
|
-
console.log(
|
|
202
|
-
` minSharedTokens: ${chalk.bold(String(patternDetectConfig.minSharedTokens))}`
|
|
203
|
-
);
|
|
204
|
-
if (patternDetectConfig.maxCandidatesPerBlock !== undefined)
|
|
205
|
-
console.log(
|
|
206
|
-
` maxCandidatesPerBlock: ${chalk.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`
|
|
207
|
-
);
|
|
208
|
-
if (patternDetectConfig.batchSize !== undefined)
|
|
209
|
-
console.log(
|
|
210
|
-
` batchSize: ${chalk.bold(String(patternDetectConfig.batchSize))}`
|
|
211
|
-
);
|
|
212
|
-
if (patternDetectConfig.streamResults !== undefined)
|
|
213
|
-
console.log(
|
|
214
|
-
` streamResults: ${chalk.bold(String(patternDetectConfig.streamResults))}`
|
|
215
|
-
);
|
|
216
|
-
if (patternDetectConfig.severity !== undefined)
|
|
217
|
-
console.log(
|
|
218
|
-
` severity: ${chalk.bold(String(patternDetectConfig.severity))}`
|
|
219
|
-
);
|
|
220
|
-
if (patternDetectConfig.includeTests !== undefined)
|
|
221
|
-
console.log(
|
|
222
|
-
` includeTests: ${chalk.bold(String(patternDetectConfig.includeTests))}`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (finalOptions['context-analyzer'] || finalOptions.maxDepth) {
|
|
227
|
-
const ca = finalOptions['context-analyzer'] || {
|
|
228
|
-
maxDepth: finalOptions.maxDepth,
|
|
229
|
-
maxContextBudget: finalOptions.maxContextBudget,
|
|
230
|
-
minCohesion: (finalOptions as any).minCohesion,
|
|
231
|
-
maxFragmentation: (finalOptions as any).maxFragmentation,
|
|
232
|
-
includeNodeModules: (finalOptions as any).includeNodeModules,
|
|
233
|
-
};
|
|
234
|
-
console.log(chalk.white('\nContext-analyzer settings:'));
|
|
235
|
-
console.log(` maxDepth: ${chalk.bold(ca.maxDepth ?? 'default')}`);
|
|
236
|
-
console.log(
|
|
237
|
-
` maxContextBudget: ${chalk.bold(ca.maxContextBudget ?? 'default')}`
|
|
238
|
-
);
|
|
239
|
-
if (ca.minCohesion !== undefined)
|
|
240
|
-
console.log(` minCohesion: ${chalk.bold(String(ca.minCohesion))}`);
|
|
241
|
-
if (ca.maxFragmentation !== undefined)
|
|
242
|
-
console.log(
|
|
243
|
-
` maxFragmentation: ${chalk.bold(String(ca.maxFragmentation))}`
|
|
244
|
-
);
|
|
245
|
-
if (ca.includeNodeModules !== undefined)
|
|
246
|
-
console.log(
|
|
247
|
-
` includeNodeModules: ${chalk.bold(String(ca.includeNodeModules))}`
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
157
|
|
|
251
|
-
|
|
252
|
-
const c = finalOptions.consistency;
|
|
253
|
-
console.log(chalk.white('\nConsistency settings:'));
|
|
254
|
-
console.log(
|
|
255
|
-
` checkNaming: ${chalk.bold(String(c.checkNaming ?? true))}`
|
|
256
|
-
);
|
|
257
|
-
console.log(
|
|
258
|
-
` checkPatterns: ${chalk.bold(String(c.checkPatterns ?? true))}`
|
|
259
|
-
);
|
|
260
|
-
console.log(
|
|
261
|
-
` checkArchitecture: ${chalk.bold(String(c.checkArchitecture ?? false))}`
|
|
262
|
-
);
|
|
263
|
-
if (c.minSeverity)
|
|
264
|
-
console.log(` minSeverity: ${chalk.bold(c.minSeverity)}`);
|
|
265
|
-
if (c.acceptedAbbreviations)
|
|
266
|
-
console.log(
|
|
267
|
-
` acceptedAbbreviations: ${chalk.bold(truncateArray(c.acceptedAbbreviations, 8))}`
|
|
268
|
-
);
|
|
269
|
-
if (c.shortWords)
|
|
270
|
-
console.log(
|
|
271
|
-
` shortWords: ${chalk.bold(truncateArray(c.shortWords, 8))}`
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
console.log(chalk.white('\nStarting analysis...'));
|
|
276
|
-
|
|
277
|
-
// Progress callback to surface per-tool output as each tool finishes
|
|
158
|
+
// Dynamic progress callback
|
|
278
159
|
const progressCallback = (event: { tool: string; data: any }) => {
|
|
279
160
|
console.log(chalk.cyan(`\n--- ${event.tool.toUpperCase()} RESULTS ---`));
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
161
|
+
const res = event.data;
|
|
162
|
+
if (res.summary) {
|
|
163
|
+
if (res.summary.totalIssues !== undefined)
|
|
164
|
+
console.log(` Issues found: ${chalk.bold(res.summary.totalIssues)}`);
|
|
165
|
+
if (res.summary.score !== undefined)
|
|
166
|
+
console.log(` Tool Score: ${chalk.bold(res.summary.score)}/100`);
|
|
167
|
+
if (res.summary.totalFiles !== undefined)
|
|
283
168
|
console.log(
|
|
284
|
-
`
|
|
169
|
+
` Files analyzed: ${chalk.bold(res.summary.totalFiles)}`
|
|
285
170
|
);
|
|
286
|
-
console.log(
|
|
287
|
-
` Files with pattern issues: ${chalk.bold(String(pr.results?.length || 0))}`
|
|
288
|
-
);
|
|
289
|
-
// show top duplicate summaries
|
|
290
|
-
if (pr.duplicates && pr.duplicates.length > 0) {
|
|
291
|
-
pr.duplicates.slice(0, 5).forEach((d: any, i: number) => {
|
|
292
|
-
console.log(
|
|
293
|
-
` ${i + 1}. ${d.file1.split('/').pop()} ā ${d.file2.split('/').pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`
|
|
294
|
-
);
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// show top files with pattern issues (sorted by issue count desc)
|
|
299
|
-
if (pr.results && pr.results.length > 0) {
|
|
300
|
-
console.log(` Top files with pattern issues:`);
|
|
301
|
-
const sortedByIssues = [...pr.results].sort(
|
|
302
|
-
(a: any, b: any) =>
|
|
303
|
-
(b.issues?.length || 0) - (a.issues?.length || 0)
|
|
304
|
-
);
|
|
305
|
-
sortedByIssues.slice(0, 5).forEach((r: any, i: number) => {
|
|
306
|
-
console.log(
|
|
307
|
-
` ${i + 1}. ${r.fileName.split('/').pop()} - ${r.issues.length} issue(s)`
|
|
308
|
-
);
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Grouping and clusters summary (if available) ā show after detailed findings
|
|
313
|
-
if (pr.groups && pr.groups.length >= 0) {
|
|
314
|
-
console.log(
|
|
315
|
-
` ā
Grouped ${chalk.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk.bold(String(pr.groups.length))} file pairs`
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
if (pr.clusters && pr.clusters.length >= 0) {
|
|
319
|
-
console.log(
|
|
320
|
-
` ā
Created ${chalk.bold(String(pr.clusters.length))} refactor clusters`
|
|
321
|
-
);
|
|
322
|
-
// show brief cluster summaries
|
|
323
|
-
pr.clusters.slice(0, 3).forEach((cl: any, idx: number) => {
|
|
324
|
-
const files = (cl.files || [])
|
|
325
|
-
.map((f: any) => f.path.split('/').pop())
|
|
326
|
-
.join(', ');
|
|
327
|
-
console.log(
|
|
328
|
-
` ${idx + 1}. ${files} (${cl.tokenCost || 'n/a'} tokens)`
|
|
329
|
-
);
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
} else if (event.tool === 'context') {
|
|
333
|
-
const cr = event.data as any[];
|
|
334
|
-
console.log(
|
|
335
|
-
` Context issues found: ${chalk.bold(String(cr.length || 0))}`
|
|
336
|
-
);
|
|
337
|
-
cr.slice(0, 5).forEach((c: any, i: number) => {
|
|
338
|
-
const msg = c.message ? ` - ${c.message}` : '';
|
|
339
|
-
console.log(
|
|
340
|
-
` ${i + 1}. ${c.file} (${c.severity || 'n/a'})${msg}`
|
|
341
|
-
);
|
|
342
|
-
});
|
|
343
|
-
} else if (event.tool === 'consistency') {
|
|
344
|
-
const rep = event.data as any;
|
|
345
|
-
console.log(
|
|
346
|
-
` Consistency totalIssues: ${chalk.bold(String(rep.summary?.totalIssues || 0))}`
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
if (rep.results && rep.results.length > 0) {
|
|
350
|
-
// Group issues by file
|
|
351
|
-
const fileMap = new Map<string, any[]>();
|
|
352
|
-
rep.results.forEach((r: any) => {
|
|
353
|
-
(r.issues || []).forEach((issue: any) => {
|
|
354
|
-
const file = issue.location?.file || r.file || 'unknown';
|
|
355
|
-
if (!fileMap.has(file)) fileMap.set(file, []);
|
|
356
|
-
fileMap.get(file)!.push(issue);
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
// Sort files by number of issues desc
|
|
361
|
-
const files = Array.from(fileMap.entries()).sort(
|
|
362
|
-
(a, b) => b[1].length - a[1].length
|
|
363
|
-
);
|
|
364
|
-
const topFiles = files.slice(0, 10);
|
|
365
|
-
|
|
366
|
-
topFiles.forEach(([file, issues], idx) => {
|
|
367
|
-
// Count severities
|
|
368
|
-
const counts = issues.reduce(
|
|
369
|
-
(acc: any, it: any) => {
|
|
370
|
-
const s = (it.severity || Severity.Info).toLowerCase();
|
|
371
|
-
acc[s] = (acc[s] || 0) + 1;
|
|
372
|
-
return acc;
|
|
373
|
-
},
|
|
374
|
-
{} as Record<string, number>
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
const sample =
|
|
378
|
-
issues.find(
|
|
379
|
-
(it: any) =>
|
|
380
|
-
it.severity === Severity.Critical ||
|
|
381
|
-
it.severity === Severity.Major
|
|
382
|
-
) || issues[0];
|
|
383
|
-
const sampleMsg = sample ? ` ā ${sample.message}` : '';
|
|
384
|
-
|
|
385
|
-
console.log(
|
|
386
|
-
` ${idx + 1}. ${file} ā ${issues.length} issue(s) (critical:${counts[Severity.Critical] || 0} major:${counts[Severity.Major] || 0} minor:${counts[Severity.Minor] || 0} info:${counts[Severity.Info] || 0})${sampleMsg}`
|
|
387
|
-
);
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
const remaining = files.length - topFiles.length;
|
|
391
|
-
if (remaining > 0) {
|
|
392
|
-
console.log(
|
|
393
|
-
chalk.dim(
|
|
394
|
-
` ... and ${remaining} more files with issues (use --output json for full details)`
|
|
395
|
-
)
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
} else if (event.tool === 'doc-drift') {
|
|
400
|
-
const dr = event.data as any;
|
|
401
|
-
console.log(
|
|
402
|
-
` Issues found: ${chalk.bold(String(dr.issues?.length || 0))}`
|
|
403
|
-
);
|
|
404
|
-
if (dr.rawData) {
|
|
405
|
-
console.log(
|
|
406
|
-
` Signature Mismatches: ${chalk.bold(dr.rawData.outdatedComments || 0)}`
|
|
407
|
-
);
|
|
408
|
-
console.log(
|
|
409
|
-
` Undocumented Complexity: ${chalk.bold(dr.rawData.undocumentedComplexity || 0)}`
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
} else if (event.tool === 'deps-health') {
|
|
413
|
-
const dr = event.data as any;
|
|
414
|
-
console.log(
|
|
415
|
-
` Packages Analyzed: ${chalk.bold(String(dr.summary?.packagesAnalyzed || 0))}`
|
|
416
|
-
);
|
|
417
|
-
if (dr.rawData) {
|
|
418
|
-
console.log(
|
|
419
|
-
` Deprecated Packages: ${chalk.bold(dr.rawData.deprecatedPackages || 0)}`
|
|
420
|
-
);
|
|
421
|
-
console.log(
|
|
422
|
-
` AI Cutoff Skew Score: ${chalk.bold(dr.rawData.trainingCutoffSkew?.toFixed(1) || 0)}`
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
} else if (
|
|
426
|
-
event.tool === 'change-amplification' ||
|
|
427
|
-
event.tool === 'changeAmplification'
|
|
428
|
-
) {
|
|
429
|
-
const dr = event.data as any;
|
|
430
|
-
console.log(
|
|
431
|
-
` Coupling issues: ${chalk.bold(String(dr.issues?.length || 0))}`
|
|
432
|
-
);
|
|
433
|
-
if (dr.summary) {
|
|
434
|
-
console.log(
|
|
435
|
-
` Complexity Score: ${chalk.bold(dr.summary.score || 0)}/100`
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
} catch (err) {
|
|
440
|
-
void err;
|
|
441
171
|
}
|
|
442
172
|
};
|
|
443
173
|
|
|
@@ -445,57 +175,19 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
445
175
|
...finalOptions,
|
|
446
176
|
progressCallback,
|
|
447
177
|
onProgress: (processed: number, total: number, message: string) => {
|
|
448
|
-
// Clear line and print progress
|
|
449
178
|
process.stdout.write(
|
|
450
179
|
`\r\x1b[K [${processed}/${total}] ${message}...`
|
|
451
180
|
);
|
|
452
|
-
if (processed === total)
|
|
453
|
-
process.stdout.write('\n'); // Move to next line when done
|
|
454
|
-
}
|
|
181
|
+
if (processed === total) process.stdout.write('\n');
|
|
455
182
|
},
|
|
456
183
|
suppressToolConfig: true,
|
|
457
184
|
});
|
|
458
185
|
|
|
459
|
-
// Determine if we need to print a trailing newline because the last tool didn't finish normally or had 0 files
|
|
460
|
-
// But progressCallback already outputs `\n--- TOOL RESULTS ---` so it's fine.
|
|
461
|
-
|
|
462
|
-
// Summarize tools and results to console
|
|
463
186
|
console.log(chalk.cyan('\n=== AIReady Run Summary ==='));
|
|
464
|
-
console.log(
|
|
465
|
-
chalk.white('Tools run:'),
|
|
466
|
-
(finalOptions.tools || ['patterns', 'context', 'consistency']).join(', ')
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
console.log(chalk.cyan('\nResults summary:'));
|
|
470
187
|
console.log(
|
|
471
188
|
` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`
|
|
472
189
|
);
|
|
473
|
-
if (results[ToolName.PatternDetect]) {
|
|
474
|
-
console.log(
|
|
475
|
-
` Duplicate patterns found: ${chalk.bold(String(results[ToolName.PatternDetect].duplicates?.length || 0))}`
|
|
476
|
-
);
|
|
477
|
-
console.log(
|
|
478
|
-
` Pattern files with issues: ${chalk.bold(String(results[ToolName.PatternDetect].results.length || 0))}`
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
if (results[ToolName.ContextAnalyzer])
|
|
482
|
-
console.log(
|
|
483
|
-
` Context issues: ${chalk.bold(String(results[ToolName.ContextAnalyzer].results.length || 0))}`
|
|
484
|
-
);
|
|
485
|
-
if (results[ToolName.NamingConsistency])
|
|
486
|
-
console.log(
|
|
487
|
-
` Consistency issues: ${chalk.bold(String(results[ToolName.NamingConsistency].summary?.totalIssues || 0))}`
|
|
488
|
-
);
|
|
489
|
-
if (results[ToolName.ChangeAmplification])
|
|
490
|
-
console.log(
|
|
491
|
-
` Change amplification: ${chalk.bold(String(results[ToolName.ChangeAmplification].summary?.score || 0))}/100`
|
|
492
|
-
);
|
|
493
|
-
console.log(chalk.cyan('===========================\n'));
|
|
494
190
|
|
|
495
|
-
const elapsedTime = getElapsedTime(startTime);
|
|
496
|
-
void elapsedTime;
|
|
497
|
-
|
|
498
|
-
// Calculate score if requested: assemble per-tool scoring outputs
|
|
499
191
|
let scoringResult: ScoringResult | undefined;
|
|
500
192
|
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
501
193
|
scoringResult = await scoreUnified(results, finalOptions);
|
|
@@ -503,69 +195,42 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
503
195
|
console.log(chalk.bold('\nš AI Readiness Overall Score'));
|
|
504
196
|
console.log(` ${formatScore(scoringResult)}`);
|
|
505
197
|
|
|
506
|
-
//
|
|
507
|
-
// Note: weights are already handled inside scoreUnified via finalOptions and calculateOverallScore
|
|
508
|
-
|
|
509
|
-
// Check if we need to compare to a previous report
|
|
198
|
+
// Trend comparison logic
|
|
510
199
|
if (options.compareTo) {
|
|
511
200
|
try {
|
|
512
|
-
const
|
|
513
|
-
resolvePath(process.cwd(), options.compareTo),
|
|
514
|
-
'utf8'
|
|
201
|
+
const prevReport = JSON.parse(
|
|
202
|
+
readFileSync(resolvePath(process.cwd(), options.compareTo), 'utf8')
|
|
515
203
|
);
|
|
516
|
-
const prevReport = JSON.parse(prevReportStr);
|
|
517
204
|
const prevScore =
|
|
518
|
-
prevReport.scoring?.
|
|
519
|
-
|
|
205
|
+
prevReport.scoring?.overall || prevReport.scoring?.score;
|
|
520
206
|
if (typeof prevScore === 'number') {
|
|
521
207
|
const diff = scoringResult.overall - prevScore;
|
|
522
208
|
const diffStr = diff > 0 ? `+${diff}` : String(diff);
|
|
523
|
-
|
|
524
|
-
if (diff > 0) {
|
|
209
|
+
if (diff > 0)
|
|
525
210
|
console.log(
|
|
526
211
|
chalk.green(
|
|
527
212
|
` š Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} ā ${scoringResult.overall})`
|
|
528
213
|
)
|
|
529
214
|
);
|
|
530
|
-
|
|
215
|
+
else if (diff < 0)
|
|
531
216
|
console.log(
|
|
532
217
|
chalk.red(
|
|
533
218
|
` š Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} ā ${scoringResult.overall})`
|
|
534
219
|
)
|
|
535
220
|
);
|
|
536
|
-
|
|
537
|
-
// but for now, we just highlight the regression.
|
|
538
|
-
} else {
|
|
221
|
+
else
|
|
539
222
|
console.log(
|
|
540
223
|
chalk.blue(
|
|
541
|
-
` ā Trend: No change
|
|
224
|
+
` ā Trend: No change (${prevScore} ā ${scoringResult.overall})`
|
|
542
225
|
)
|
|
543
226
|
);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Add trend info to scoringResult for programmatic use
|
|
547
|
-
(scoringResult as any).trend = {
|
|
548
|
-
previousScore: prevScore,
|
|
549
|
-
difference: diff,
|
|
550
|
-
};
|
|
551
|
-
} else {
|
|
552
|
-
console.log(
|
|
553
|
-
chalk.yellow(
|
|
554
|
-
`\n ā ļø Previous report at ${options.compareTo} does not contain an overall score.`
|
|
555
|
-
)
|
|
556
|
-
);
|
|
557
227
|
}
|
|
558
228
|
} catch (e) {
|
|
559
229
|
void e;
|
|
560
|
-
console.log(
|
|
561
|
-
chalk.yellow(
|
|
562
|
-
`\n ā ļø Could not read or parse previous report at ${options.compareTo}.`
|
|
563
|
-
)
|
|
564
|
-
);
|
|
565
230
|
}
|
|
566
231
|
}
|
|
567
232
|
|
|
568
|
-
//
|
|
233
|
+
// Token Budget & Cost Logic
|
|
569
234
|
const totalWastedDuplication = (scoringResult.breakdown || []).reduce(
|
|
570
235
|
(sum, s) =>
|
|
571
236
|
sum + (s.tokenBudget?.wastedTokens.bySource.duplication || 0),
|
|
@@ -579,7 +244,8 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
579
244
|
const totalContext = Math.max(
|
|
580
245
|
...(scoringResult.breakdown || []).map(
|
|
581
246
|
(s) => s.tokenBudget?.totalContextTokens || 0
|
|
582
|
-
)
|
|
247
|
+
),
|
|
248
|
+
0
|
|
583
249
|
);
|
|
584
250
|
|
|
585
251
|
if (totalContext > 0) {
|
|
@@ -591,42 +257,20 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
591
257
|
chattiness: 0,
|
|
592
258
|
},
|
|
593
259
|
});
|
|
594
|
-
|
|
595
|
-
const targetModel = options.model || 'claude-4.6';
|
|
596
|
-
const modelPreset = getModelPreset(targetModel);
|
|
260
|
+
const modelPreset = getModelPreset(options.model || 'claude-4.6');
|
|
597
261
|
const costEstimate = estimateCostFromBudget(unifiedBudget, modelPreset);
|
|
598
262
|
|
|
599
|
-
|
|
600
|
-
const filled = Math.round(unifiedBudget.efficiencyRatio * barWidth);
|
|
601
|
-
const bar =
|
|
602
|
-
chalk.green('ā'.repeat(filled)) +
|
|
603
|
-
chalk.dim('ā'.repeat(barWidth - filled));
|
|
604
|
-
|
|
605
|
-
console.log(chalk.bold('\nš AI Token Budget Analysis (v0.13)'));
|
|
606
|
-
console.log(
|
|
607
|
-
` Efficiency: [${bar}] ${(unifiedBudget.efficiencyRatio * 100).toFixed(0)}%`
|
|
608
|
-
);
|
|
609
|
-
console.log(
|
|
610
|
-
` Total Context: ${chalk.bold(unifiedBudget.totalContextTokens.toLocaleString())} tokens`
|
|
611
|
-
);
|
|
263
|
+
console.log(chalk.bold('\nš AI Token Budget Analysis'));
|
|
612
264
|
console.log(
|
|
613
|
-
`
|
|
265
|
+
` Efficiency: ${(unifiedBudget.efficiencyRatio * 100).toFixed(0)}%`
|
|
614
266
|
);
|
|
615
|
-
console.log(` Waste Breakdown:`);
|
|
616
267
|
console.log(
|
|
617
|
-
`
|
|
268
|
+
` Wasted Tokens: ${chalk.red(unifiedBudget.wastedTokens.total.toLocaleString())}`
|
|
618
269
|
);
|
|
619
270
|
console.log(
|
|
620
|
-
`
|
|
621
|
-
);
|
|
622
|
-
console.log(
|
|
623
|
-
` Potential Savings: ${chalk.green(unifiedBudget.potentialRetrievableTokens.toLocaleString())} tokens retrievable`
|
|
624
|
-
);
|
|
625
|
-
console.log(
|
|
626
|
-
`\n Est. Monthly Cost (${modelPreset.name}): ${chalk.bold('$' + costEstimate.total)} [range: $${costEstimate.range[0]}-$${costEstimate.range[1]}]`
|
|
271
|
+
` Est. Monthly Cost (${modelPreset.name}): ${chalk.bold('$' + costEstimate.total)}`
|
|
627
272
|
);
|
|
628
273
|
|
|
629
|
-
// Attach unified budget to report for JSON persistence
|
|
630
274
|
(scoringResult as any).tokenBudget = unifiedBudget;
|
|
631
275
|
(scoringResult as any).costEstimate = {
|
|
632
276
|
model: modelPreset.name,
|
|
@@ -634,122 +278,38 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
634
278
|
};
|
|
635
279
|
}
|
|
636
280
|
|
|
637
|
-
|
|
638
|
-
if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
|
|
281
|
+
if (scoringResult.breakdown) {
|
|
639
282
|
console.log(chalk.bold('\nTool breakdown:'));
|
|
640
283
|
scoringResult.breakdown.forEach((tool) => {
|
|
641
284
|
const rating = getRating(tool.score);
|
|
642
|
-
|
|
643
|
-
console.log(
|
|
644
|
-
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
|
|
645
|
-
);
|
|
285
|
+
console.log(` - ${tool.toolName}: ${tool.score}/100 (${rating})`);
|
|
646
286
|
});
|
|
647
|
-
console.log();
|
|
648
|
-
|
|
649
|
-
if (finalOptions.scoring?.showBreakdown) {
|
|
650
|
-
console.log(chalk.bold('Detailed tool breakdown:'));
|
|
651
|
-
scoringResult.breakdown.forEach((tool) => {
|
|
652
|
-
console.log(formatToolScore(tool));
|
|
653
|
-
});
|
|
654
|
-
console.log();
|
|
655
|
-
}
|
|
656
287
|
}
|
|
657
288
|
}
|
|
658
289
|
|
|
659
|
-
//
|
|
290
|
+
// Normalized report mapping
|
|
660
291
|
const mapToUnifiedReport = (
|
|
661
292
|
res: any,
|
|
662
293
|
scoring: ScoringResult | undefined
|
|
663
294
|
) => {
|
|
664
295
|
const allResults: any[] = [];
|
|
665
|
-
|
|
296
|
+
const totalFilesSet = new Set<string>();
|
|
666
297
|
let criticalCount = 0;
|
|
667
298
|
let majorCount = 0;
|
|
668
299
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
spokeRes: any,
|
|
672
|
-
defaultType: IssueType = IssueType.AiSignalClarity
|
|
673
|
-
) => {
|
|
300
|
+
res.summary.toolsRun.forEach((toolId: string) => {
|
|
301
|
+
const spokeRes = res[toolId];
|
|
674
302
|
if (!spokeRes || !spokeRes.results) return;
|
|
675
|
-
spokeRes.results.forEach((r: any) => {
|
|
676
|
-
const fileName = r.fileName || r.file || 'unknown';
|
|
677
|
-
totalFilesSet.add(fileName);
|
|
678
|
-
|
|
679
|
-
// Enforce strict AnalysisResult schema
|
|
680
|
-
const normalizedResult = {
|
|
681
|
-
fileName,
|
|
682
|
-
issues: [] as any[],
|
|
683
|
-
metrics: r.metrics || { tokenCost: r.tokenCost || 0 },
|
|
684
|
-
};
|
|
685
|
-
|
|
686
|
-
if (r.issues && Array.isArray(r.issues)) {
|
|
687
|
-
r.issues.forEach((i: any) => {
|
|
688
|
-
const normalizedIssue =
|
|
689
|
-
typeof i === 'string'
|
|
690
|
-
? {
|
|
691
|
-
type: defaultType,
|
|
692
|
-
severity: (r.severity || Severity.Info) as Severity,
|
|
693
|
-
message: i,
|
|
694
|
-
location: { file: fileName, line: 1 },
|
|
695
|
-
}
|
|
696
|
-
: {
|
|
697
|
-
type: i.type || defaultType,
|
|
698
|
-
severity: (i.severity ||
|
|
699
|
-
r.severity ||
|
|
700
|
-
Severity.Info) as Severity,
|
|
701
|
-
message: i.message || String(i),
|
|
702
|
-
location: i.location || { file: fileName, line: 1 },
|
|
703
|
-
suggestion: i.suggestion,
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
if (
|
|
707
|
-
normalizedIssue.severity === Severity.Critical ||
|
|
708
|
-
normalizedIssue.severity === 'critical'
|
|
709
|
-
)
|
|
710
|
-
criticalCount++;
|
|
711
|
-
if (
|
|
712
|
-
normalizedIssue.severity === Severity.Major ||
|
|
713
|
-
normalizedIssue.severity === 'major'
|
|
714
|
-
)
|
|
715
|
-
majorCount++;
|
|
716
|
-
|
|
717
|
-
normalizedResult.issues.push(normalizedIssue);
|
|
718
|
-
});
|
|
719
|
-
} else if (r.severity) {
|
|
720
|
-
// handle context-analyzer style if issues missing but severity present
|
|
721
|
-
const normalizedIssue = {
|
|
722
|
-
type: defaultType,
|
|
723
|
-
severity: r.severity as Severity,
|
|
724
|
-
message: r.message || 'General issue',
|
|
725
|
-
location: { file: fileName, line: 1 },
|
|
726
|
-
};
|
|
727
|
-
if (
|
|
728
|
-
normalizedIssue.severity === Severity.Critical ||
|
|
729
|
-
normalizedIssue.severity === 'critical'
|
|
730
|
-
)
|
|
731
|
-
criticalCount++;
|
|
732
|
-
if (
|
|
733
|
-
normalizedIssue.severity === Severity.Major ||
|
|
734
|
-
normalizedIssue.severity === 'major'
|
|
735
|
-
)
|
|
736
|
-
majorCount++;
|
|
737
|
-
normalizedResult.issues.push(normalizedIssue);
|
|
738
|
-
}
|
|
739
303
|
|
|
740
|
-
|
|
304
|
+
spokeRes.results.forEach((r: any) => {
|
|
305
|
+
totalFilesSet.add(r.fileName);
|
|
306
|
+
allResults.push(r);
|
|
307
|
+
r.issues?.forEach((i: any) => {
|
|
308
|
+
if (i.severity === Severity.Critical) criticalCount++;
|
|
309
|
+
if (i.severity === Severity.Major) majorCount++;
|
|
310
|
+
});
|
|
741
311
|
});
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
collect(res[ToolName.PatternDetect], IssueType.DuplicatePattern);
|
|
745
|
-
collect(res[ToolName.ContextAnalyzer], IssueType.ContextFragmentation);
|
|
746
|
-
collect(res[ToolName.NamingConsistency], IssueType.NamingInconsistency);
|
|
747
|
-
collect(res[ToolName.DocDrift], IssueType.DocDrift);
|
|
748
|
-
collect(res[ToolName.DependencyHealth], IssueType.DependencyHealth);
|
|
749
|
-
collect(res[ToolName.AiSignalClarity], IssueType.AiSignalClarity);
|
|
750
|
-
collect(res[ToolName.AgentGrounding], IssueType.AgentNavigationFailure);
|
|
751
|
-
collect(res[ToolName.TestabilityIndex], IssueType.LowTestability);
|
|
752
|
-
collect(res[ToolName.ChangeAmplification], IssueType.ChangeAmplification);
|
|
312
|
+
});
|
|
753
313
|
|
|
754
314
|
return {
|
|
755
315
|
...res,
|
|
@@ -760,205 +320,82 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
760
320
|
criticalIssues: criticalCount,
|
|
761
321
|
majorIssues: majorCount,
|
|
762
322
|
},
|
|
763
|
-
scoring
|
|
323
|
+
scoring,
|
|
764
324
|
};
|
|
765
325
|
};
|
|
766
326
|
|
|
767
|
-
|
|
327
|
+
const outputData = {
|
|
328
|
+
...mapToUnifiedReport(results, scoringResult),
|
|
329
|
+
repository: repoMetadata,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// Output persistence
|
|
768
333
|
const outputFormat =
|
|
769
334
|
options.output || finalOptions.output?.format || 'console';
|
|
770
|
-
const
|
|
335
|
+
const outputPath = resolveOutputPath(
|
|
336
|
+
options.outputFile || finalOptions.output?.file,
|
|
337
|
+
`aiready-report-${getReportTimestamp()}.json`,
|
|
338
|
+
resolvedDir
|
|
339
|
+
);
|
|
340
|
+
|
|
771
341
|
if (outputFormat === 'json') {
|
|
772
|
-
const timestamp = getReportTimestamp();
|
|
773
|
-
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
774
|
-
const outputPath = resolveOutputPath(
|
|
775
|
-
userOutputFile,
|
|
776
|
-
defaultFilename,
|
|
777
|
-
resolvedDir
|
|
778
|
-
);
|
|
779
|
-
const outputData = {
|
|
780
|
-
...mapToUnifiedReport(results, scoringResult),
|
|
781
|
-
repository: repoMetadata,
|
|
782
|
-
};
|
|
783
342
|
handleJSONOutput(
|
|
784
343
|
outputData,
|
|
785
344
|
outputPath,
|
|
786
345
|
`ā
Report saved to ${outputPath}`
|
|
787
346
|
);
|
|
788
|
-
|
|
789
|
-
// Automatic Upload
|
|
790
|
-
if (options.upload) {
|
|
791
|
-
console.log(chalk.blue('\nš¤ Automatic upload triggered...'));
|
|
792
|
-
await uploadAction(outputPath, {
|
|
793
|
-
apiKey: options.apiKey,
|
|
794
|
-
server: options.server,
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// Warn if graph caps may be exceeded
|
|
799
|
-
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
800
347
|
} else {
|
|
801
|
-
// Auto-persist report even in console mode for downstream tools
|
|
802
|
-
const timestamp = getReportTimestamp();
|
|
803
|
-
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
804
|
-
const outputPath = resolveOutputPath(
|
|
805
|
-
userOutputFile,
|
|
806
|
-
defaultFilename,
|
|
807
|
-
resolvedDir
|
|
808
|
-
);
|
|
809
|
-
const outputData = {
|
|
810
|
-
...mapToUnifiedReport(results, scoringResult),
|
|
811
|
-
repository: repoMetadata,
|
|
812
|
-
};
|
|
813
|
-
|
|
814
348
|
try {
|
|
815
349
|
writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
|
|
816
350
|
console.log(chalk.dim(`ā
Report auto-persisted to ${outputPath}`));
|
|
817
|
-
|
|
818
|
-
// Automatic Upload (from auto-persistent report)
|
|
819
|
-
if (options.upload) {
|
|
820
|
-
console.log(chalk.blue('\nš¤ Automatic upload triggered...'));
|
|
821
|
-
await uploadAction(outputPath, {
|
|
822
|
-
apiKey: options.apiKey,
|
|
823
|
-
server: options.server,
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
// Warn if graph caps may be exceeded
|
|
827
|
-
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
828
351
|
} catch (err) {
|
|
829
352
|
void err;
|
|
830
353
|
}
|
|
831
354
|
}
|
|
832
355
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
356
|
+
if (options.upload) {
|
|
357
|
+
await uploadAction(outputPath, {
|
|
358
|
+
apiKey: options.apiKey,
|
|
359
|
+
server: options.server,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
363
|
+
|
|
364
|
+
// CI/CD Gatekeeper logic
|
|
365
|
+
const isCI = options.ci || process.env.CI === 'true';
|
|
838
366
|
if (isCI && scoringResult) {
|
|
839
367
|
const threshold = options.threshold
|
|
840
368
|
? parseInt(options.threshold)
|
|
841
369
|
: undefined;
|
|
842
370
|
const failOnLevel = options.failOn || 'critical';
|
|
843
371
|
|
|
844
|
-
// Output GitHub Actions annotations
|
|
845
|
-
if (process.env.GITHUB_ACTIONS === 'true') {
|
|
846
|
-
console.log(`\n::group::AI Readiness Score`);
|
|
847
|
-
console.log(`score=${scoringResult.overall}`);
|
|
848
|
-
if (scoringResult.breakdown) {
|
|
849
|
-
scoringResult.breakdown.forEach((tool) => {
|
|
850
|
-
console.log(`${tool.toolName}=${tool.score}`);
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
console.log('::endgroup::');
|
|
854
|
-
|
|
855
|
-
// Output annotation for score
|
|
856
|
-
if (threshold && scoringResult.overall < threshold) {
|
|
857
|
-
console.log(
|
|
858
|
-
`::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`
|
|
859
|
-
);
|
|
860
|
-
} else if (threshold) {
|
|
861
|
-
console.log(
|
|
862
|
-
`::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// Output annotations for critical issues
|
|
867
|
-
if (results[ToolName.PatternDetect]) {
|
|
868
|
-
const criticalPatterns = results[
|
|
869
|
-
ToolName.PatternDetect
|
|
870
|
-
].results.flatMap((p: any) =>
|
|
871
|
-
p.issues.filter((i: any) => i.severity === Severity.Critical)
|
|
872
|
-
);
|
|
873
|
-
|
|
874
|
-
criticalPatterns.slice(0, 10).forEach((issue: any) => {
|
|
875
|
-
console.log(
|
|
876
|
-
`::warning file=${issue.location?.file || 'unknown'},line=${issue.location?.line || 1}::${issue.message}`
|
|
877
|
-
);
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// Determine if we should fail
|
|
883
372
|
let shouldFail = false;
|
|
884
373
|
let failReason = '';
|
|
885
374
|
|
|
886
|
-
// Check threshold
|
|
887
375
|
if (threshold && scoringResult.overall < threshold) {
|
|
888
376
|
shouldFail = true;
|
|
889
|
-
failReason = `
|
|
377
|
+
failReason = `Score ${scoringResult.overall} < threshold ${threshold}`;
|
|
890
378
|
}
|
|
891
379
|
|
|
892
|
-
|
|
380
|
+
const report = mapToUnifiedReport(results, scoringResult);
|
|
893
381
|
if (failOnLevel !== 'none') {
|
|
894
|
-
|
|
895
|
-
const minSeverity =
|
|
896
|
-
severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
|
|
897
|
-
|
|
898
|
-
let criticalCount = 0;
|
|
899
|
-
let majorCount = 0;
|
|
900
|
-
|
|
901
|
-
if (results[ToolName.PatternDetect]) {
|
|
902
|
-
results[ToolName.PatternDetect].results.forEach((p: any) => {
|
|
903
|
-
p.issues.forEach((i: any) => {
|
|
904
|
-
if (i.severity === Severity.Critical) criticalCount++;
|
|
905
|
-
if (i.severity === Severity.Major) majorCount++;
|
|
906
|
-
});
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
if (results[ToolName.ContextAnalyzer]) {
|
|
910
|
-
results[ToolName.ContextAnalyzer].results.forEach((c: any) => {
|
|
911
|
-
if (c.severity === Severity.Critical) criticalCount++;
|
|
912
|
-
if (c.severity === Severity.Major) majorCount++;
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
if (results[ToolName.NamingConsistency]) {
|
|
916
|
-
results[ToolName.NamingConsistency].results.forEach((r: any) => {
|
|
917
|
-
r.issues?.forEach((i: any) => {
|
|
918
|
-
if (i.severity === Severity.Critical) criticalCount++;
|
|
919
|
-
if (i.severity === Severity.Major) majorCount++;
|
|
920
|
-
});
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
if (minSeverity >= 4 && criticalCount > 0) {
|
|
382
|
+
if (failOnLevel === 'critical' && report.summary.criticalIssues > 0) {
|
|
925
383
|
shouldFail = true;
|
|
926
|
-
failReason = `Found ${
|
|
927
|
-
} else if (
|
|
384
|
+
failReason = `Found ${report.summary.criticalIssues} critical issues`;
|
|
385
|
+
} else if (
|
|
386
|
+
failOnLevel === 'major' &&
|
|
387
|
+
report.summary.criticalIssues + report.summary.majorIssues > 0
|
|
388
|
+
) {
|
|
928
389
|
shouldFail = true;
|
|
929
|
-
failReason = `Found ${
|
|
390
|
+
failReason = `Found ${report.summary.criticalIssues} critical and ${report.summary.majorIssues} major issues`;
|
|
930
391
|
}
|
|
931
392
|
}
|
|
932
393
|
|
|
933
|
-
// Output result
|
|
934
394
|
if (shouldFail) {
|
|
935
|
-
console.log(chalk.red(
|
|
936
|
-
console.log(chalk.red(` Reason: ${failReason}`));
|
|
937
|
-
console.log(chalk.dim('\n Remediation steps:'));
|
|
938
|
-
console.log(
|
|
939
|
-
chalk.dim(' 1. Run `aiready scan` locally to see detailed issues')
|
|
940
|
-
);
|
|
941
|
-
console.log(chalk.dim(' 2. Fix the critical issues before merging'));
|
|
942
|
-
console.log(
|
|
943
|
-
chalk.dim(
|
|
944
|
-
' 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing'
|
|
945
|
-
)
|
|
946
|
-
);
|
|
395
|
+
console.log(chalk.red(`\nš« PR BLOCKED: ${failReason}`));
|
|
947
396
|
process.exit(1);
|
|
948
397
|
} else {
|
|
949
|
-
console.log(chalk.green('\nā
PR PASSED
|
|
950
|
-
if (threshold) {
|
|
951
|
-
console.log(
|
|
952
|
-
chalk.green(
|
|
953
|
-
` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
|
|
954
|
-
)
|
|
955
|
-
);
|
|
956
|
-
}
|
|
957
|
-
console.log(
|
|
958
|
-
chalk.dim(
|
|
959
|
-
'\n š” Track historical trends: https://getaiready.dev ā Team plan $99/mo'
|
|
960
|
-
)
|
|
961
|
-
);
|
|
398
|
+
console.log(chalk.green('\nā
PR PASSED'));
|
|
962
399
|
}
|
|
963
400
|
}
|
|
964
401
|
} catch (error) {
|
|
@@ -966,33 +403,4 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
966
403
|
}
|
|
967
404
|
}
|
|
968
405
|
|
|
969
|
-
export const scanHelpText =
|
|
970
|
-
EXAMPLES:
|
|
971
|
-
$ aiready scan # Analyze all tools
|
|
972
|
-
$ aiready scan --tools patterns,context # Skip consistency
|
|
973
|
-
$ aiready scan --profile agentic # Optimize for AI agent execution
|
|
974
|
-
$ aiready scan --profile security # Optimize for secure coding (testability)
|
|
975
|
-
$ aiready scan --compare-to prev-report.json # Compare trends against previous run
|
|
976
|
-
$ aiready scan --score --threshold 75 # CI/CD with threshold
|
|
977
|
-
$ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
|
|
978
|
-
$ aiready scan --ci --fail-on major # Fail on major+ issues
|
|
979
|
-
$ aiready scan --output json --output-file report.json
|
|
980
|
-
$ aiready scan --upload --api-key ar_... # Automatic platform upload
|
|
981
|
-
$ aiready scan --upload --server custom-url.com # Upload to custom platform
|
|
982
|
-
|
|
983
|
-
PROFILES:
|
|
984
|
-
agentic: aiSignalClarity, grounding, testability
|
|
985
|
-
cost: patterns, context
|
|
986
|
-
security: consistency, testability
|
|
987
|
-
onboarding: context, consistency, grounding
|
|
988
|
-
|
|
989
|
-
CI/CD INTEGRATION (Gatekeeper Mode):
|
|
990
|
-
Use --ci for GitHub Actions integration:
|
|
991
|
-
- Outputs GitHub Actions annotations for PR checks
|
|
992
|
-
- Fails with exit code 1 if threshold not met
|
|
993
|
-
- Shows clear "blocked" message with remediation steps
|
|
994
|
-
|
|
995
|
-
Example GitHub Actions workflow:
|
|
996
|
-
- name: AI Readiness Check
|
|
997
|
-
run: aiready scan --ci --threshold 70
|
|
998
|
-
`;
|
|
406
|
+
export const scanHelpText = `...`;
|