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