@aiready/cli 0.9.40 → 0.9.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aiready/aiready-report-20260227-133806.json +40 -141
- package/.aiready/aiready-report-20260227-133938.json +40 -141
- package/.github/FUNDING.yml +2 -2
- package/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-test.log +5 -5
- package/CONTRIBUTING.md +11 -2
- package/dist/chunk-HLBKROD3.mjs +237 -0
- package/dist/cli.js +688 -178
- package/dist/cli.mjs +679 -178
- package/dist/index.js +12 -3
- package/dist/index.mjs +1 -1
- package/package.json +12 -12
- package/src/__tests__/cli.test.ts +1 -1
- package/src/cli.ts +114 -28
- package/src/commands/agent-grounding.ts +22 -7
- package/src/commands/ai-signal-clarity.ts +13 -8
- package/src/commands/consistency.ts +69 -29
- package/src/commands/context.ts +108 -38
- package/src/commands/deps-health.ts +15 -6
- package/src/commands/doc-drift.ts +10 -4
- package/src/commands/index.ts +6 -2
- package/src/commands/patterns.ts +67 -26
- package/src/commands/scan.ts +399 -126
- package/src/commands/testability.ts +22 -9
- package/src/commands/visualize.ts +102 -45
- package/src/index.ts +34 -10
- package/src/utils/helpers.ts +57 -32
package/src/commands/scan.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { writeFileSync, readFileSync } from 'fs';
|
|
7
|
-
|
|
7
|
+
// 'join' was unused
|
|
8
8
|
import { resolve as resolvePath } from 'path';
|
|
9
9
|
import {
|
|
10
10
|
loadMergedConfig,
|
|
@@ -21,7 +21,11 @@ import {
|
|
|
21
21
|
type ToolScoringOutput,
|
|
22
22
|
} from '@aiready/core';
|
|
23
23
|
import { analyzeUnified } from '../index';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
getReportTimestamp,
|
|
26
|
+
warnIfGraphCapExceeded,
|
|
27
|
+
truncateArray,
|
|
28
|
+
} from '../utils/helpers';
|
|
25
29
|
|
|
26
30
|
interface ScanOptions {
|
|
27
31
|
tools?: string;
|
|
@@ -49,7 +53,17 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
49
53
|
try {
|
|
50
54
|
// Define defaults
|
|
51
55
|
const defaults = {
|
|
52
|
-
tools: [
|
|
56
|
+
tools: [
|
|
57
|
+
'patterns',
|
|
58
|
+
'context',
|
|
59
|
+
'consistency',
|
|
60
|
+
'aiSignalClarity',
|
|
61
|
+
'grounding',
|
|
62
|
+
'testability',
|
|
63
|
+
'doc-drift',
|
|
64
|
+
'deps-health',
|
|
65
|
+
'changeAmplification',
|
|
66
|
+
],
|
|
53
67
|
include: undefined,
|
|
54
68
|
exclude: undefined,
|
|
55
69
|
output: {
|
|
@@ -58,11 +72,14 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
58
72
|
},
|
|
59
73
|
};
|
|
60
74
|
|
|
61
|
-
let profileTools = options.tools
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
let profileTools = options.tools
|
|
76
|
+
? options.tools.split(',').map((t: string) => {
|
|
77
|
+
const tool = t.trim();
|
|
78
|
+
if (tool === 'hallucination' || tool === 'hallucination-risk')
|
|
79
|
+
return 'aiSignalClarity';
|
|
80
|
+
return tool;
|
|
81
|
+
})
|
|
82
|
+
: undefined;
|
|
66
83
|
if (options.profile) {
|
|
67
84
|
switch (options.profile.toLowerCase()) {
|
|
68
85
|
case 'agentic':
|
|
@@ -78,7 +95,11 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
78
95
|
profileTools = ['context', 'consistency', 'grounding'];
|
|
79
96
|
break;
|
|
80
97
|
default:
|
|
81
|
-
console.log(
|
|
98
|
+
console.log(
|
|
99
|
+
chalk.yellow(
|
|
100
|
+
`\n⚠️ Unknown profile '${options.profile}'. Using specified tools or defaults.`
|
|
101
|
+
)
|
|
102
|
+
);
|
|
82
103
|
}
|
|
83
104
|
}
|
|
84
105
|
|
|
@@ -91,28 +112,48 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
91
112
|
cliOverrides.tools = profileTools;
|
|
92
113
|
}
|
|
93
114
|
|
|
94
|
-
const baseOptions = await loadMergedConfig(
|
|
95
|
-
|
|
115
|
+
const baseOptions = (await loadMergedConfig(
|
|
116
|
+
resolvedDir,
|
|
117
|
+
defaults,
|
|
118
|
+
cliOverrides
|
|
119
|
+
)) as any;
|
|
96
120
|
|
|
97
121
|
// Apply smart defaults for pattern detection if patterns tool is enabled
|
|
98
122
|
let finalOptions = { ...baseOptions };
|
|
99
123
|
if (baseOptions.tools.includes('patterns')) {
|
|
100
124
|
const { getSmartDefaults } = await import('@aiready/pattern-detect');
|
|
101
|
-
const patternSmartDefaults = await getSmartDefaults(
|
|
125
|
+
const patternSmartDefaults = await getSmartDefaults(
|
|
126
|
+
resolvedDir,
|
|
127
|
+
baseOptions
|
|
128
|
+
);
|
|
102
129
|
// Merge deeply to preserve nested config
|
|
103
|
-
finalOptions = {
|
|
130
|
+
finalOptions = {
|
|
131
|
+
...patternSmartDefaults,
|
|
132
|
+
...finalOptions,
|
|
133
|
+
...baseOptions,
|
|
134
|
+
};
|
|
104
135
|
}
|
|
105
136
|
|
|
106
137
|
// Print pre-run summary with expanded settings (truncate long arrays)
|
|
107
138
|
console.log(chalk.cyan('\n=== AIReady Run Preview ==='));
|
|
108
|
-
console.log(
|
|
139
|
+
console.log(
|
|
140
|
+
chalk.white('Tools to run:'),
|
|
141
|
+
(finalOptions.tools || ['patterns', 'context', 'consistency']).join(', ')
|
|
142
|
+
);
|
|
109
143
|
console.log(chalk.white('Will use settings from config and defaults.'));
|
|
110
144
|
|
|
111
145
|
// Common top-level settings
|
|
112
146
|
console.log(chalk.white('\nGeneral settings:'));
|
|
113
|
-
if (finalOptions.rootDir)
|
|
114
|
-
|
|
115
|
-
if (finalOptions.
|
|
147
|
+
if (finalOptions.rootDir)
|
|
148
|
+
console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
|
|
149
|
+
if (finalOptions.include)
|
|
150
|
+
console.log(
|
|
151
|
+
` include: ${chalk.bold(truncateArray(finalOptions.include, 6))}`
|
|
152
|
+
);
|
|
153
|
+
if (finalOptions.exclude)
|
|
154
|
+
console.log(
|
|
155
|
+
` exclude: ${chalk.bold(truncateArray(finalOptions.exclude, 6))}`
|
|
156
|
+
);
|
|
116
157
|
|
|
117
158
|
if (finalOptions['pattern-detect'] || finalOptions.minSimilarity) {
|
|
118
159
|
const patternDetectConfig = finalOptions['pattern-detect'] || {
|
|
@@ -127,15 +168,40 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
127
168
|
includeTests: (finalOptions as any).includeTests,
|
|
128
169
|
};
|
|
129
170
|
console.log(chalk.white('\nPattern-detect settings:'));
|
|
130
|
-
console.log(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (patternDetectConfig.
|
|
137
|
-
|
|
138
|
-
|
|
171
|
+
console.log(
|
|
172
|
+
` minSimilarity: ${chalk.bold(patternDetectConfig.minSimilarity ?? 'default')}`
|
|
173
|
+
);
|
|
174
|
+
console.log(
|
|
175
|
+
` minLines: ${chalk.bold(patternDetectConfig.minLines ?? 'default')}`
|
|
176
|
+
);
|
|
177
|
+
if (patternDetectConfig.approx !== undefined)
|
|
178
|
+
console.log(
|
|
179
|
+
` approx: ${chalk.bold(String(patternDetectConfig.approx))}`
|
|
180
|
+
);
|
|
181
|
+
if (patternDetectConfig.minSharedTokens !== undefined)
|
|
182
|
+
console.log(
|
|
183
|
+
` minSharedTokens: ${chalk.bold(String(patternDetectConfig.minSharedTokens))}`
|
|
184
|
+
);
|
|
185
|
+
if (patternDetectConfig.maxCandidatesPerBlock !== undefined)
|
|
186
|
+
console.log(
|
|
187
|
+
` maxCandidatesPerBlock: ${chalk.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`
|
|
188
|
+
);
|
|
189
|
+
if (patternDetectConfig.batchSize !== undefined)
|
|
190
|
+
console.log(
|
|
191
|
+
` batchSize: ${chalk.bold(String(patternDetectConfig.batchSize))}`
|
|
192
|
+
);
|
|
193
|
+
if (patternDetectConfig.streamResults !== undefined)
|
|
194
|
+
console.log(
|
|
195
|
+
` streamResults: ${chalk.bold(String(patternDetectConfig.streamResults))}`
|
|
196
|
+
);
|
|
197
|
+
if (patternDetectConfig.severity !== undefined)
|
|
198
|
+
console.log(
|
|
199
|
+
` severity: ${chalk.bold(String(patternDetectConfig.severity))}`
|
|
200
|
+
);
|
|
201
|
+
if (patternDetectConfig.includeTests !== undefined)
|
|
202
|
+
console.log(
|
|
203
|
+
` includeTests: ${chalk.bold(String(patternDetectConfig.includeTests))}`
|
|
204
|
+
);
|
|
139
205
|
}
|
|
140
206
|
|
|
141
207
|
if (finalOptions['context-analyzer'] || finalOptions.maxDepth) {
|
|
@@ -148,21 +214,43 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
148
214
|
};
|
|
149
215
|
console.log(chalk.white('\nContext-analyzer settings:'));
|
|
150
216
|
console.log(` maxDepth: ${chalk.bold(ca.maxDepth ?? 'default')}`);
|
|
151
|
-
console.log(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (ca.
|
|
217
|
+
console.log(
|
|
218
|
+
` maxContextBudget: ${chalk.bold(ca.maxContextBudget ?? 'default')}`
|
|
219
|
+
);
|
|
220
|
+
if (ca.minCohesion !== undefined)
|
|
221
|
+
console.log(` minCohesion: ${chalk.bold(String(ca.minCohesion))}`);
|
|
222
|
+
if (ca.maxFragmentation !== undefined)
|
|
223
|
+
console.log(
|
|
224
|
+
` maxFragmentation: ${chalk.bold(String(ca.maxFragmentation))}`
|
|
225
|
+
);
|
|
226
|
+
if (ca.includeNodeModules !== undefined)
|
|
227
|
+
console.log(
|
|
228
|
+
` includeNodeModules: ${chalk.bold(String(ca.includeNodeModules))}`
|
|
229
|
+
);
|
|
155
230
|
}
|
|
156
231
|
|
|
157
232
|
if (finalOptions.consistency) {
|
|
158
233
|
const c = finalOptions.consistency;
|
|
159
234
|
console.log(chalk.white('\nConsistency settings:'));
|
|
160
|
-
console.log(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
235
|
+
console.log(
|
|
236
|
+
` checkNaming: ${chalk.bold(String(c.checkNaming ?? true))}`
|
|
237
|
+
);
|
|
238
|
+
console.log(
|
|
239
|
+
` checkPatterns: ${chalk.bold(String(c.checkPatterns ?? true))}`
|
|
240
|
+
);
|
|
241
|
+
console.log(
|
|
242
|
+
` checkArchitecture: ${chalk.bold(String(c.checkArchitecture ?? false))}`
|
|
243
|
+
);
|
|
244
|
+
if (c.minSeverity)
|
|
245
|
+
console.log(` minSeverity: ${chalk.bold(c.minSeverity)}`);
|
|
246
|
+
if (c.acceptedAbbreviations)
|
|
247
|
+
console.log(
|
|
248
|
+
` acceptedAbbreviations: ${chalk.bold(truncateArray(c.acceptedAbbreviations, 8))}`
|
|
249
|
+
);
|
|
250
|
+
if (c.shortWords)
|
|
251
|
+
console.log(
|
|
252
|
+
` shortWords: ${chalk.bold(truncateArray(c.shortWords, 8))}`
|
|
253
|
+
);
|
|
166
254
|
}
|
|
167
255
|
|
|
168
256
|
console.log(chalk.white('\nStarting analysis...'));
|
|
@@ -173,46 +261,71 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
173
261
|
try {
|
|
174
262
|
if (event.tool === 'patterns') {
|
|
175
263
|
const pr = event.data as any;
|
|
176
|
-
console.log(
|
|
177
|
-
|
|
264
|
+
console.log(
|
|
265
|
+
` Duplicate patterns: ${chalk.bold(String(pr.duplicates?.length || 0))}`
|
|
266
|
+
);
|
|
267
|
+
console.log(
|
|
268
|
+
` Files with pattern issues: ${chalk.bold(String(pr.results?.length || 0))}`
|
|
269
|
+
);
|
|
178
270
|
// show top duplicate summaries
|
|
179
271
|
if (pr.duplicates && pr.duplicates.length > 0) {
|
|
180
272
|
pr.duplicates.slice(0, 5).forEach((d: any, i: number) => {
|
|
181
|
-
console.log(
|
|
273
|
+
console.log(
|
|
274
|
+
` ${i + 1}. ${d.file1.split('/').pop()} ↔ ${d.file2.split('/').pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`
|
|
275
|
+
);
|
|
182
276
|
});
|
|
183
277
|
}
|
|
184
278
|
|
|
185
279
|
// show top files with pattern issues (sorted by issue count desc)
|
|
186
280
|
if (pr.results && pr.results.length > 0) {
|
|
187
281
|
console.log(` Top files with pattern issues:`);
|
|
188
|
-
const sortedByIssues = [...pr.results].sort(
|
|
282
|
+
const sortedByIssues = [...pr.results].sort(
|
|
283
|
+
(a: any, b: any) =>
|
|
284
|
+
(b.issues?.length || 0) - (a.issues?.length || 0)
|
|
285
|
+
);
|
|
189
286
|
sortedByIssues.slice(0, 5).forEach((r: any, i: number) => {
|
|
190
|
-
console.log(
|
|
287
|
+
console.log(
|
|
288
|
+
` ${i + 1}. ${r.fileName.split('/').pop()} - ${r.issues.length} issue(s)`
|
|
289
|
+
);
|
|
191
290
|
});
|
|
192
291
|
}
|
|
193
292
|
|
|
194
293
|
// Grouping and clusters summary (if available) — show after detailed findings
|
|
195
294
|
if (pr.groups && pr.groups.length >= 0) {
|
|
196
|
-
console.log(
|
|
295
|
+
console.log(
|
|
296
|
+
` ✅ Grouped ${chalk.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk.bold(String(pr.groups.length))} file pairs`
|
|
297
|
+
);
|
|
197
298
|
}
|
|
198
299
|
if (pr.clusters && pr.clusters.length >= 0) {
|
|
199
|
-
console.log(
|
|
300
|
+
console.log(
|
|
301
|
+
` ✅ Created ${chalk.bold(String(pr.clusters.length))} refactor clusters`
|
|
302
|
+
);
|
|
200
303
|
// show brief cluster summaries
|
|
201
304
|
pr.clusters.slice(0, 3).forEach((cl: any, idx: number) => {
|
|
202
|
-
const files = (cl.files || [])
|
|
203
|
-
|
|
305
|
+
const files = (cl.files || [])
|
|
306
|
+
.map((f: any) => f.path.split('/').pop())
|
|
307
|
+
.join(', ');
|
|
308
|
+
console.log(
|
|
309
|
+
` ${idx + 1}. ${files} (${cl.tokenCost || 'n/a'} tokens)`
|
|
310
|
+
);
|
|
204
311
|
});
|
|
205
312
|
}
|
|
206
313
|
} else if (event.tool === 'context') {
|
|
207
314
|
const cr = event.data as any[];
|
|
208
|
-
console.log(
|
|
315
|
+
console.log(
|
|
316
|
+
` Context issues found: ${chalk.bold(String(cr.length || 0))}`
|
|
317
|
+
);
|
|
209
318
|
cr.slice(0, 5).forEach((c: any, i: number) => {
|
|
210
319
|
const msg = c.message ? ` - ${c.message}` : '';
|
|
211
|
-
console.log(
|
|
320
|
+
console.log(
|
|
321
|
+
` ${i + 1}. ${c.file} (${c.severity || 'n/a'})${msg}`
|
|
322
|
+
);
|
|
212
323
|
});
|
|
213
324
|
} else if (event.tool === 'consistency') {
|
|
214
325
|
const rep = event.data as any;
|
|
215
|
-
console.log(
|
|
326
|
+
console.log(
|
|
327
|
+
` Consistency totalIssues: ${chalk.bold(String(rep.summary?.totalIssues || 0))}`
|
|
328
|
+
);
|
|
216
329
|
|
|
217
330
|
if (rep.results && rep.results.length > 0) {
|
|
218
331
|
// Group issues by file
|
|
@@ -226,71 +339,129 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
226
339
|
});
|
|
227
340
|
|
|
228
341
|
// Sort files by number of issues desc
|
|
229
|
-
const files = Array.from(fileMap.entries()).sort(
|
|
342
|
+
const files = Array.from(fileMap.entries()).sort(
|
|
343
|
+
(a, b) => b[1].length - a[1].length
|
|
344
|
+
);
|
|
230
345
|
const topFiles = files.slice(0, 10);
|
|
231
346
|
|
|
232
347
|
topFiles.forEach(([file, issues], idx) => {
|
|
233
348
|
// Count severities
|
|
234
|
-
const counts = issues.reduce(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
349
|
+
const counts = issues.reduce(
|
|
350
|
+
(acc: any, it: any) => {
|
|
351
|
+
const s = (it.severity || 'info').toLowerCase();
|
|
352
|
+
acc[s] = (acc[s] || 0) + 1;
|
|
353
|
+
return acc;
|
|
354
|
+
},
|
|
355
|
+
{} as Record<string, number>
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const sample =
|
|
359
|
+
issues.find(
|
|
360
|
+
(it: any) =>
|
|
361
|
+
it.severity === 'critical' || it.severity === 'major'
|
|
362
|
+
) || issues[0];
|
|
241
363
|
const sampleMsg = sample ? ` — ${sample.message}` : '';
|
|
242
364
|
|
|
243
|
-
console.log(
|
|
365
|
+
console.log(
|
|
366
|
+
` ${idx + 1}. ${file} — ${issues.length} issue(s) (critical:${counts.critical || 0} major:${counts.major || 0} minor:${counts.minor || 0} info:${counts.info || 0})${sampleMsg}`
|
|
367
|
+
);
|
|
244
368
|
});
|
|
245
369
|
|
|
246
370
|
const remaining = files.length - topFiles.length;
|
|
247
371
|
if (remaining > 0) {
|
|
248
|
-
console.log(
|
|
372
|
+
console.log(
|
|
373
|
+
chalk.dim(
|
|
374
|
+
` ... and ${remaining} more files with issues (use --output json for full details)`
|
|
375
|
+
)
|
|
376
|
+
);
|
|
249
377
|
}
|
|
250
378
|
}
|
|
251
379
|
} else if (event.tool === 'doc-drift') {
|
|
252
380
|
const dr = event.data as any;
|
|
253
|
-
console.log(
|
|
381
|
+
console.log(
|
|
382
|
+
` Issues found: ${chalk.bold(String(dr.issues?.length || 0))}`
|
|
383
|
+
);
|
|
254
384
|
if (dr.rawData) {
|
|
255
|
-
console.log(
|
|
256
|
-
|
|
385
|
+
console.log(
|
|
386
|
+
` Signature Mismatches: ${chalk.bold(dr.rawData.outdatedComments || 0)}`
|
|
387
|
+
);
|
|
388
|
+
console.log(
|
|
389
|
+
` Undocumented Complexity: ${chalk.bold(dr.rawData.undocumentedComplexity || 0)}`
|
|
390
|
+
);
|
|
257
391
|
}
|
|
258
392
|
} else if (event.tool === 'deps-health') {
|
|
259
393
|
const dr = event.data as any;
|
|
260
|
-
console.log(
|
|
394
|
+
console.log(
|
|
395
|
+
` Packages Analyzed: ${chalk.bold(String(dr.summary?.packagesAnalyzed || 0))}`
|
|
396
|
+
);
|
|
261
397
|
if (dr.rawData) {
|
|
262
|
-
console.log(
|
|
263
|
-
|
|
398
|
+
console.log(
|
|
399
|
+
` Deprecated Packages: ${chalk.bold(dr.rawData.deprecatedPackages || 0)}`
|
|
400
|
+
);
|
|
401
|
+
console.log(
|
|
402
|
+
` AI Cutoff Skew Score: ${chalk.bold(dr.rawData.trainingCutoffSkew?.toFixed(1) || 0)}`
|
|
403
|
+
);
|
|
264
404
|
}
|
|
265
|
-
} else if (
|
|
405
|
+
} else if (
|
|
406
|
+
event.tool === 'change-amplification' ||
|
|
407
|
+
event.tool === 'changeAmplification'
|
|
408
|
+
) {
|
|
266
409
|
const dr = event.data as any;
|
|
267
|
-
console.log(
|
|
410
|
+
console.log(
|
|
411
|
+
` Coupling issues: ${chalk.bold(String(dr.issues?.length || 0))}`
|
|
412
|
+
);
|
|
268
413
|
if (dr.summary) {
|
|
269
|
-
console.log(
|
|
414
|
+
console.log(
|
|
415
|
+
` Complexity Score: ${chalk.bold(dr.summary.score || 0)}/100`
|
|
416
|
+
);
|
|
270
417
|
}
|
|
271
418
|
}
|
|
272
419
|
} catch (err) {
|
|
273
|
-
|
|
420
|
+
void err;
|
|
274
421
|
}
|
|
275
422
|
};
|
|
276
423
|
|
|
277
|
-
const results = await analyzeUnified({
|
|
424
|
+
const results = await analyzeUnified({
|
|
425
|
+
...finalOptions,
|
|
426
|
+
progressCallback,
|
|
427
|
+
suppressToolConfig: true,
|
|
428
|
+
});
|
|
278
429
|
|
|
279
430
|
// Summarize tools and results to console
|
|
280
431
|
console.log(chalk.cyan('\n=== AIReady Run Summary ==='));
|
|
281
|
-
console.log(
|
|
432
|
+
console.log(
|
|
433
|
+
chalk.white('Tools run:'),
|
|
434
|
+
(finalOptions.tools || ['patterns', 'context', 'consistency']).join(', ')
|
|
435
|
+
);
|
|
282
436
|
|
|
283
437
|
// Results summary
|
|
284
438
|
console.log(chalk.cyan('\nResults summary:'));
|
|
285
|
-
console.log(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (results.
|
|
289
|
-
|
|
290
|
-
|
|
439
|
+
console.log(
|
|
440
|
+
` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`
|
|
441
|
+
);
|
|
442
|
+
if (results.duplicates)
|
|
443
|
+
console.log(
|
|
444
|
+
` Duplicate patterns found: ${chalk.bold(String(results.duplicates.length || 0))}`
|
|
445
|
+
);
|
|
446
|
+
if (results.patterns)
|
|
447
|
+
console.log(
|
|
448
|
+
` Pattern files with issues: ${chalk.bold(String(results.patterns.length || 0))}`
|
|
449
|
+
);
|
|
450
|
+
if (results.context)
|
|
451
|
+
console.log(
|
|
452
|
+
` Context issues: ${chalk.bold(String(results.context.length || 0))}`
|
|
453
|
+
);
|
|
454
|
+
console.log(
|
|
455
|
+
` Consistency issues: ${chalk.bold(String(results.consistency?.summary?.totalIssues || 0))}`
|
|
456
|
+
);
|
|
457
|
+
if (results.changeAmplification)
|
|
458
|
+
console.log(
|
|
459
|
+
` Change amplification: ${chalk.bold(String(results.changeAmplification.summary?.score || 0))}/100`
|
|
460
|
+
);
|
|
291
461
|
console.log(chalk.cyan('===========================\n'));
|
|
292
462
|
|
|
293
463
|
const elapsedTime = getElapsedTime(startTime);
|
|
464
|
+
void elapsedTime;
|
|
294
465
|
|
|
295
466
|
// Calculate score if requested: assemble per-tool scoring outputs
|
|
296
467
|
let scoringResult: ReturnType<typeof calculateOverallScore> | undefined;
|
|
@@ -299,70 +470,85 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
299
470
|
|
|
300
471
|
// Patterns score
|
|
301
472
|
if (results.duplicates) {
|
|
302
|
-
const { calculatePatternScore } =
|
|
473
|
+
const { calculatePatternScore } =
|
|
474
|
+
await import('@aiready/pattern-detect');
|
|
303
475
|
try {
|
|
304
|
-
const patternScore = calculatePatternScore(
|
|
476
|
+
const patternScore = calculatePatternScore(
|
|
477
|
+
results.duplicates,
|
|
478
|
+
results.patterns?.length || 0
|
|
479
|
+
);
|
|
305
480
|
toolScores.set('pattern-detect', patternScore);
|
|
306
481
|
} catch (err) {
|
|
307
|
-
|
|
482
|
+
void err;
|
|
308
483
|
}
|
|
309
484
|
}
|
|
310
485
|
|
|
311
486
|
// Context score
|
|
312
487
|
if (results.context) {
|
|
313
|
-
const { generateSummary: genContextSummary, calculateContextScore } =
|
|
488
|
+
const { generateSummary: genContextSummary, calculateContextScore } =
|
|
489
|
+
await import('@aiready/context-analyzer');
|
|
314
490
|
try {
|
|
315
491
|
const ctxSummary = genContextSummary(results.context);
|
|
316
492
|
const contextScore = calculateContextScore(ctxSummary);
|
|
317
493
|
toolScores.set('context-analyzer', contextScore);
|
|
318
494
|
} catch (err) {
|
|
319
|
-
|
|
495
|
+
void err;
|
|
320
496
|
}
|
|
321
497
|
}
|
|
322
498
|
|
|
323
499
|
// Consistency score
|
|
324
500
|
if (results.consistency) {
|
|
325
|
-
const { calculateConsistencyScore } =
|
|
501
|
+
const { calculateConsistencyScore } =
|
|
502
|
+
await import('@aiready/consistency');
|
|
326
503
|
try {
|
|
327
|
-
const issues =
|
|
504
|
+
const issues =
|
|
505
|
+
results.consistency.results?.flatMap((r: any) => r.issues) || [];
|
|
328
506
|
const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
|
|
329
|
-
const consistencyScore = calculateConsistencyScore(
|
|
507
|
+
const consistencyScore = calculateConsistencyScore(
|
|
508
|
+
issues,
|
|
509
|
+
totalFiles
|
|
510
|
+
);
|
|
330
511
|
toolScores.set('consistency', consistencyScore);
|
|
331
512
|
} catch (err) {
|
|
332
|
-
|
|
513
|
+
void err;
|
|
333
514
|
}
|
|
334
515
|
}
|
|
335
516
|
|
|
336
517
|
// AI signal clarity score
|
|
337
518
|
if (results.aiSignalClarity) {
|
|
338
|
-
const { calculateAiSignalClarityScore } =
|
|
519
|
+
const { calculateAiSignalClarityScore } =
|
|
520
|
+
await import('@aiready/ai-signal-clarity');
|
|
339
521
|
try {
|
|
340
|
-
const hrScore = calculateAiSignalClarityScore(
|
|
522
|
+
const hrScore = calculateAiSignalClarityScore(
|
|
523
|
+
results.aiSignalClarity
|
|
524
|
+
);
|
|
341
525
|
toolScores.set('ai-signal-clarity', hrScore);
|
|
342
526
|
} catch (err) {
|
|
343
|
-
|
|
527
|
+
void err;
|
|
344
528
|
}
|
|
345
529
|
}
|
|
346
530
|
|
|
347
531
|
// Agent grounding score
|
|
348
532
|
if (results.grounding) {
|
|
349
|
-
const { calculateGroundingScore } =
|
|
533
|
+
const { calculateGroundingScore } =
|
|
534
|
+
await import('@aiready/agent-grounding');
|
|
350
535
|
try {
|
|
351
536
|
const agScore = calculateGroundingScore(results.grounding);
|
|
352
537
|
toolScores.set('agent-grounding', agScore);
|
|
353
538
|
} catch (err) {
|
|
354
|
-
|
|
539
|
+
void err;
|
|
355
540
|
}
|
|
356
541
|
}
|
|
357
542
|
|
|
358
543
|
// Testability score
|
|
359
544
|
if (results.testability) {
|
|
360
|
-
const { calculateTestabilityScore } =
|
|
545
|
+
const { calculateTestabilityScore } =
|
|
546
|
+
await import('@aiready/testability');
|
|
361
547
|
try {
|
|
362
548
|
const tbScore = calculateTestabilityScore(results.testability);
|
|
363
549
|
toolScores.set('testability', tbScore);
|
|
364
550
|
} catch (err) {
|
|
365
|
-
|
|
551
|
+
void err;
|
|
366
552
|
}
|
|
367
553
|
}
|
|
368
554
|
|
|
@@ -373,7 +559,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
373
559
|
score: results.docDrift.summary.score,
|
|
374
560
|
rawMetrics: results.docDrift.rawData,
|
|
375
561
|
factors: [],
|
|
376
|
-
recommendations: (results.docDrift.recommendations || []).map(
|
|
562
|
+
recommendations: (results.docDrift.recommendations || []).map(
|
|
563
|
+
(action: string) => ({
|
|
564
|
+
action,
|
|
565
|
+
estimatedImpact: 5,
|
|
566
|
+
priority: 'medium',
|
|
567
|
+
})
|
|
568
|
+
),
|
|
377
569
|
});
|
|
378
570
|
}
|
|
379
571
|
|
|
@@ -384,7 +576,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
384
576
|
score: results.deps.summary.score,
|
|
385
577
|
rawMetrics: results.deps.rawData,
|
|
386
578
|
factors: [],
|
|
387
|
-
recommendations: (results.deps.recommendations || []).map(
|
|
579
|
+
recommendations: (results.deps.recommendations || []).map(
|
|
580
|
+
(action: string) => ({
|
|
581
|
+
action,
|
|
582
|
+
estimatedImpact: 5,
|
|
583
|
+
priority: 'medium',
|
|
584
|
+
})
|
|
585
|
+
),
|
|
388
586
|
});
|
|
389
587
|
}
|
|
390
588
|
|
|
@@ -395,17 +593,26 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
395
593
|
score: results.changeAmplification.summary.score,
|
|
396
594
|
rawMetrics: results.changeAmplification.rawData,
|
|
397
595
|
factors: [],
|
|
398
|
-
recommendations: (
|
|
596
|
+
recommendations: (
|
|
597
|
+
results.changeAmplification.recommendations || []
|
|
598
|
+
).map((action: string) => ({
|
|
599
|
+
action,
|
|
600
|
+
estimatedImpact: 5,
|
|
601
|
+
priority: 'medium',
|
|
602
|
+
})),
|
|
399
603
|
});
|
|
400
604
|
}
|
|
401
605
|
|
|
402
|
-
|
|
403
606
|
// Parse CLI weight overrides (if any)
|
|
404
607
|
const cliWeights = parseWeightString((options as any).weights);
|
|
405
608
|
|
|
406
609
|
// Only calculate overall score if we have at least one tool score
|
|
407
610
|
if (toolScores.size > 0) {
|
|
408
|
-
scoringResult = calculateOverallScore(
|
|
611
|
+
scoringResult = calculateOverallScore(
|
|
612
|
+
toolScores,
|
|
613
|
+
finalOptions,
|
|
614
|
+
cliWeights.size ? cliWeights : undefined
|
|
615
|
+
);
|
|
409
616
|
|
|
410
617
|
console.log(chalk.bold('\n📊 AI Readiness Overall Score'));
|
|
411
618
|
console.log(` ${formatScore(scoringResult)}`);
|
|
@@ -413,34 +620,59 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
413
620
|
// Check if we need to compare to a previous report
|
|
414
621
|
if (options.compareTo) {
|
|
415
622
|
try {
|
|
416
|
-
const prevReportStr = readFileSync(
|
|
623
|
+
const prevReportStr = readFileSync(
|
|
624
|
+
resolvePath(process.cwd(), options.compareTo),
|
|
625
|
+
'utf8'
|
|
626
|
+
);
|
|
417
627
|
const prevReport = JSON.parse(prevReportStr);
|
|
418
|
-
const prevScore =
|
|
628
|
+
const prevScore =
|
|
629
|
+
prevReport.scoring?.score || prevReport.scoring?.overallScore;
|
|
419
630
|
|
|
420
631
|
if (typeof prevScore === 'number') {
|
|
421
632
|
const diff = scoringResult.overall - prevScore;
|
|
422
633
|
const diffStr = diff > 0 ? `+${diff}` : String(diff);
|
|
423
634
|
console.log();
|
|
424
635
|
if (diff > 0) {
|
|
425
|
-
console.log(
|
|
636
|
+
console.log(
|
|
637
|
+
chalk.green(
|
|
638
|
+
` 📈 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
|
|
639
|
+
)
|
|
640
|
+
);
|
|
426
641
|
} else if (diff < 0) {
|
|
427
|
-
console.log(
|
|
642
|
+
console.log(
|
|
643
|
+
chalk.red(
|
|
644
|
+
` 📉 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
|
|
645
|
+
)
|
|
646
|
+
);
|
|
428
647
|
// Trend gating: if we regressed and CI is on or threshold is present, we could lower the threshold effectively,
|
|
429
648
|
// but for now, we just highlight the regression.
|
|
430
649
|
} else {
|
|
431
|
-
console.log(
|
|
650
|
+
console.log(
|
|
651
|
+
chalk.blue(
|
|
652
|
+
` ➖ Trend: No change compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
|
|
653
|
+
)
|
|
654
|
+
);
|
|
432
655
|
}
|
|
433
656
|
|
|
434
657
|
// Add trend info to scoringResult for programmatic use
|
|
435
658
|
(scoringResult as any).trend = {
|
|
436
659
|
previousScore: prevScore,
|
|
437
|
-
difference: diff
|
|
660
|
+
difference: diff,
|
|
438
661
|
};
|
|
439
662
|
} else {
|
|
440
|
-
console.log(
|
|
663
|
+
console.log(
|
|
664
|
+
chalk.yellow(
|
|
665
|
+
`\n ⚠️ Previous report at ${options.compareTo} does not contain an overall score.`
|
|
666
|
+
)
|
|
667
|
+
);
|
|
441
668
|
}
|
|
442
669
|
} catch (e) {
|
|
443
|
-
|
|
670
|
+
void e;
|
|
671
|
+
console.log(
|
|
672
|
+
chalk.yellow(
|
|
673
|
+
`\n ⚠️ Could not read or parse previous report at ${options.compareTo}.`
|
|
674
|
+
)
|
|
675
|
+
);
|
|
444
676
|
}
|
|
445
677
|
}
|
|
446
678
|
|
|
@@ -450,7 +682,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
450
682
|
scoringResult.breakdown.forEach((tool) => {
|
|
451
683
|
const rating = getRating(tool.score);
|
|
452
684
|
const rd = getRatingDisplay(rating);
|
|
453
|
-
console.log(
|
|
685
|
+
console.log(
|
|
686
|
+
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
|
|
687
|
+
);
|
|
454
688
|
});
|
|
455
689
|
console.log();
|
|
456
690
|
|
|
@@ -466,38 +700,56 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
466
700
|
}
|
|
467
701
|
|
|
468
702
|
// Persist JSON summary when output format is json
|
|
469
|
-
const outputFormat =
|
|
703
|
+
const outputFormat =
|
|
704
|
+
options.output || finalOptions.output?.format || 'console';
|
|
470
705
|
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
471
706
|
if (outputFormat === 'json') {
|
|
472
707
|
const timestamp = getReportTimestamp();
|
|
473
708
|
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
474
|
-
const outputPath = resolveOutputPath(
|
|
709
|
+
const outputPath = resolveOutputPath(
|
|
710
|
+
userOutputFile,
|
|
711
|
+
defaultFilename,
|
|
712
|
+
resolvedDir
|
|
713
|
+
);
|
|
475
714
|
const outputData = { ...results, scoring: scoringResult };
|
|
476
|
-
handleJSONOutput(
|
|
715
|
+
handleJSONOutput(
|
|
716
|
+
outputData,
|
|
717
|
+
outputPath,
|
|
718
|
+
`✅ Report saved to ${outputPath}`
|
|
719
|
+
);
|
|
477
720
|
|
|
478
721
|
// Warn if graph caps may be exceeded
|
|
479
|
-
warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
722
|
+
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
480
723
|
} else {
|
|
481
724
|
// Auto-persist report even in console mode for downstream tools
|
|
482
725
|
const timestamp = getReportTimestamp();
|
|
483
726
|
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
484
|
-
const outputPath = resolveOutputPath(
|
|
727
|
+
const outputPath = resolveOutputPath(
|
|
728
|
+
userOutputFile,
|
|
729
|
+
defaultFilename,
|
|
730
|
+
resolvedDir
|
|
731
|
+
);
|
|
485
732
|
const outputData = { ...results, scoring: scoringResult };
|
|
486
733
|
|
|
487
734
|
try {
|
|
488
735
|
writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
|
|
489
736
|
console.log(chalk.dim(`✅ Report auto-persisted to ${outputPath}`));
|
|
490
737
|
// Warn if graph caps may be exceeded
|
|
491
|
-
warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
738
|
+
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
492
739
|
} catch (err) {
|
|
493
|
-
|
|
740
|
+
void err;
|
|
494
741
|
}
|
|
495
742
|
}
|
|
496
743
|
|
|
497
744
|
// CI/CD Gatekeeper Mode
|
|
498
|
-
const isCI =
|
|
745
|
+
const isCI =
|
|
746
|
+
options.ci ||
|
|
747
|
+
process.env.CI === 'true' ||
|
|
748
|
+
process.env.GITHUB_ACTIONS === 'true';
|
|
499
749
|
if (isCI && scoringResult) {
|
|
500
|
-
const threshold = options.threshold
|
|
750
|
+
const threshold = options.threshold
|
|
751
|
+
? parseInt(options.threshold)
|
|
752
|
+
: undefined;
|
|
501
753
|
const failOnLevel = options.failOn || 'critical';
|
|
502
754
|
|
|
503
755
|
// Output GitHub Actions annotations
|
|
@@ -505,7 +757,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
505
757
|
console.log(`\n::group::AI Readiness Score`);
|
|
506
758
|
console.log(`score=${scoringResult.overall}`);
|
|
507
759
|
if (scoringResult.breakdown) {
|
|
508
|
-
scoringResult.breakdown.forEach(tool => {
|
|
760
|
+
scoringResult.breakdown.forEach((tool) => {
|
|
509
761
|
console.log(`${tool.toolName}=${tool.score}`);
|
|
510
762
|
});
|
|
511
763
|
}
|
|
@@ -513,9 +765,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
513
765
|
|
|
514
766
|
// Output annotation for score
|
|
515
767
|
if (threshold && scoringResult.overall < threshold) {
|
|
516
|
-
console.log(
|
|
768
|
+
console.log(
|
|
769
|
+
`::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`
|
|
770
|
+
);
|
|
517
771
|
} else if (threshold) {
|
|
518
|
-
console.log(
|
|
772
|
+
console.log(
|
|
773
|
+
`::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
|
|
774
|
+
);
|
|
519
775
|
}
|
|
520
776
|
|
|
521
777
|
// Output annotations for critical issues
|
|
@@ -524,7 +780,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
524
780
|
p.issues.filter((i: any) => i.severity === 'critical')
|
|
525
781
|
);
|
|
526
782
|
criticalPatterns.slice(0, 10).forEach((issue: any) => {
|
|
527
|
-
console.log(
|
|
783
|
+
console.log(
|
|
784
|
+
`::warning file=${issue.location?.file || 'unknown'},line=${issue.location?.line || 1}::${issue.message}`
|
|
785
|
+
);
|
|
528
786
|
});
|
|
529
787
|
}
|
|
530
788
|
}
|
|
@@ -542,7 +800,8 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
542
800
|
// Check fail-on severity
|
|
543
801
|
if (failOnLevel !== 'none') {
|
|
544
802
|
const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
|
|
545
|
-
const minSeverity =
|
|
803
|
+
const minSeverity =
|
|
804
|
+
severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
|
|
546
805
|
|
|
547
806
|
let criticalCount = 0;
|
|
548
807
|
let majorCount = 0;
|
|
@@ -573,7 +832,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
573
832
|
if (minSeverity >= 4 && criticalCount > 0) {
|
|
574
833
|
shouldFail = true;
|
|
575
834
|
failReason = `Found ${criticalCount} critical issues`;
|
|
576
|
-
} else if (minSeverity >= 3 &&
|
|
835
|
+
} else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
|
|
577
836
|
shouldFail = true;
|
|
578
837
|
failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
|
|
579
838
|
}
|
|
@@ -584,16 +843,30 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
584
843
|
console.log(chalk.red('\n🚫 PR BLOCKED: AI Readiness Check Failed'));
|
|
585
844
|
console.log(chalk.red(` Reason: ${failReason}`));
|
|
586
845
|
console.log(chalk.dim('\n Remediation steps:'));
|
|
587
|
-
console.log(
|
|
846
|
+
console.log(
|
|
847
|
+
chalk.dim(' 1. Run `aiready scan` locally to see detailed issues')
|
|
848
|
+
);
|
|
588
849
|
console.log(chalk.dim(' 2. Fix the critical issues before merging'));
|
|
589
|
-
console.log(
|
|
850
|
+
console.log(
|
|
851
|
+
chalk.dim(
|
|
852
|
+
' 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing'
|
|
853
|
+
)
|
|
854
|
+
);
|
|
590
855
|
process.exit(1);
|
|
591
856
|
} else {
|
|
592
857
|
console.log(chalk.green('\n✅ PR PASSED: AI Readiness Check'));
|
|
593
858
|
if (threshold) {
|
|
594
|
-
console.log(
|
|
859
|
+
console.log(
|
|
860
|
+
chalk.green(
|
|
861
|
+
` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
|
|
862
|
+
)
|
|
863
|
+
);
|
|
595
864
|
}
|
|
596
|
-
console.log(
|
|
865
|
+
console.log(
|
|
866
|
+
chalk.dim(
|
|
867
|
+
'\n 💡 Track historical trends: https://getaiready.dev — Team plan $99/mo'
|
|
868
|
+
)
|
|
869
|
+
);
|
|
597
870
|
}
|
|
598
871
|
}
|
|
599
872
|
} catch (error) {
|
|
@@ -628,4 +901,4 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
628
901
|
Example GitHub Actions workflow:
|
|
629
902
|
- name: AI Readiness Check
|
|
630
903
|
run: aiready scan --ci --threshold 70
|
|
631
|
-
`;
|
|
904
|
+
`;
|