@aiready/cli 0.9.40 → 0.9.43
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/.aiready/aiready-report-20260228-003433.json +7939 -0
- package/.aiready/aiready-report-20260228-003613.json +771 -0
- package/.github/FUNDING.yml +2 -2
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-lint.log +5 -0
- package/.turbo/turbo-test.log +5 -5
- package/CONTRIBUTING.md +11 -2
- package/dist/chunk-HLBKROD3.mjs +237 -0
- package/dist/chunk-LLJMKNBI.mjs +243 -0
- package/dist/cli.js +708 -184
- package/dist/cli.mjs +687 -178
- package/dist/index.js +24 -9
- 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 +14 -9
- package/src/commands/consistency.ts +70 -30
- package/src/commands/context.ts +109 -39
- 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 +411 -126
- package/src/commands/testability.ts +22 -9
- package/src/commands/visualize.ts +102 -45
- package/src/index.ts +40 -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,141 @@ 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
|
+
onProgress: (processed: number, total: number, message: string) => {
|
|
428
|
+
// Clear line and print progress
|
|
429
|
+
process.stdout.write(
|
|
430
|
+
`\r\x1b[K [${processed}/${total}] ${message}...`
|
|
431
|
+
);
|
|
432
|
+
if (processed === total) {
|
|
433
|
+
process.stdout.write('\n'); // Move to next line when done
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
suppressToolConfig: true,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Determine if we need to print a trailing newline because the last tool didn't finish normally or had 0 files
|
|
440
|
+
// But progressCallback already outputs `\n--- TOOL RESULTS ---` so it's fine.
|
|
278
441
|
|
|
279
442
|
// Summarize tools and results to console
|
|
280
443
|
console.log(chalk.cyan('\n=== AIReady Run Summary ==='));
|
|
281
|
-
console.log(
|
|
444
|
+
console.log(
|
|
445
|
+
chalk.white('Tools run:'),
|
|
446
|
+
(finalOptions.tools || ['patterns', 'context', 'consistency']).join(', ')
|
|
447
|
+
);
|
|
282
448
|
|
|
283
449
|
// Results summary
|
|
284
450
|
console.log(chalk.cyan('\nResults summary:'));
|
|
285
|
-
console.log(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (results.
|
|
289
|
-
|
|
290
|
-
|
|
451
|
+
console.log(
|
|
452
|
+
` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`
|
|
453
|
+
);
|
|
454
|
+
if (results.duplicates)
|
|
455
|
+
console.log(
|
|
456
|
+
` Duplicate patterns found: ${chalk.bold(String(results.duplicates.length || 0))}`
|
|
457
|
+
);
|
|
458
|
+
if (results.patterns)
|
|
459
|
+
console.log(
|
|
460
|
+
` Pattern files with issues: ${chalk.bold(String(results.patterns.length || 0))}`
|
|
461
|
+
);
|
|
462
|
+
if (results.context)
|
|
463
|
+
console.log(
|
|
464
|
+
` Context issues: ${chalk.bold(String(results.context.length || 0))}`
|
|
465
|
+
);
|
|
466
|
+
console.log(
|
|
467
|
+
` Consistency issues: ${chalk.bold(String(results.consistency?.summary?.totalIssues || 0))}`
|
|
468
|
+
);
|
|
469
|
+
if (results.changeAmplification)
|
|
470
|
+
console.log(
|
|
471
|
+
` Change amplification: ${chalk.bold(String(results.changeAmplification.summary?.score || 0))}/100`
|
|
472
|
+
);
|
|
291
473
|
console.log(chalk.cyan('===========================\n'));
|
|
292
474
|
|
|
293
475
|
const elapsedTime = getElapsedTime(startTime);
|
|
476
|
+
void elapsedTime;
|
|
294
477
|
|
|
295
478
|
// Calculate score if requested: assemble per-tool scoring outputs
|
|
296
479
|
let scoringResult: ReturnType<typeof calculateOverallScore> | undefined;
|
|
@@ -299,70 +482,85 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
299
482
|
|
|
300
483
|
// Patterns score
|
|
301
484
|
if (results.duplicates) {
|
|
302
|
-
const { calculatePatternScore } =
|
|
485
|
+
const { calculatePatternScore } =
|
|
486
|
+
await import('@aiready/pattern-detect');
|
|
303
487
|
try {
|
|
304
|
-
const patternScore = calculatePatternScore(
|
|
488
|
+
const patternScore = calculatePatternScore(
|
|
489
|
+
results.duplicates,
|
|
490
|
+
results.patterns?.length || 0
|
|
491
|
+
);
|
|
305
492
|
toolScores.set('pattern-detect', patternScore);
|
|
306
493
|
} catch (err) {
|
|
307
|
-
|
|
494
|
+
void err;
|
|
308
495
|
}
|
|
309
496
|
}
|
|
310
497
|
|
|
311
498
|
// Context score
|
|
312
499
|
if (results.context) {
|
|
313
|
-
const { generateSummary: genContextSummary, calculateContextScore } =
|
|
500
|
+
const { generateSummary: genContextSummary, calculateContextScore } =
|
|
501
|
+
await import('@aiready/context-analyzer');
|
|
314
502
|
try {
|
|
315
503
|
const ctxSummary = genContextSummary(results.context);
|
|
316
504
|
const contextScore = calculateContextScore(ctxSummary);
|
|
317
505
|
toolScores.set('context-analyzer', contextScore);
|
|
318
506
|
} catch (err) {
|
|
319
|
-
|
|
507
|
+
void err;
|
|
320
508
|
}
|
|
321
509
|
}
|
|
322
510
|
|
|
323
511
|
// Consistency score
|
|
324
512
|
if (results.consistency) {
|
|
325
|
-
const { calculateConsistencyScore } =
|
|
513
|
+
const { calculateConsistencyScore } =
|
|
514
|
+
await import('@aiready/consistency');
|
|
326
515
|
try {
|
|
327
|
-
const issues =
|
|
516
|
+
const issues =
|
|
517
|
+
results.consistency.results?.flatMap((r: any) => r.issues) || [];
|
|
328
518
|
const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
|
|
329
|
-
const consistencyScore = calculateConsistencyScore(
|
|
519
|
+
const consistencyScore = calculateConsistencyScore(
|
|
520
|
+
issues,
|
|
521
|
+
totalFiles
|
|
522
|
+
);
|
|
330
523
|
toolScores.set('consistency', consistencyScore);
|
|
331
524
|
} catch (err) {
|
|
332
|
-
|
|
525
|
+
void err;
|
|
333
526
|
}
|
|
334
527
|
}
|
|
335
528
|
|
|
336
529
|
// AI signal clarity score
|
|
337
530
|
if (results.aiSignalClarity) {
|
|
338
|
-
const { calculateAiSignalClarityScore } =
|
|
531
|
+
const { calculateAiSignalClarityScore } =
|
|
532
|
+
await import('@aiready/ai-signal-clarity');
|
|
339
533
|
try {
|
|
340
|
-
const hrScore = calculateAiSignalClarityScore(
|
|
534
|
+
const hrScore = calculateAiSignalClarityScore(
|
|
535
|
+
results.aiSignalClarity
|
|
536
|
+
);
|
|
341
537
|
toolScores.set('ai-signal-clarity', hrScore);
|
|
342
538
|
} catch (err) {
|
|
343
|
-
|
|
539
|
+
void err;
|
|
344
540
|
}
|
|
345
541
|
}
|
|
346
542
|
|
|
347
543
|
// Agent grounding score
|
|
348
544
|
if (results.grounding) {
|
|
349
|
-
const { calculateGroundingScore } =
|
|
545
|
+
const { calculateGroundingScore } =
|
|
546
|
+
await import('@aiready/agent-grounding');
|
|
350
547
|
try {
|
|
351
548
|
const agScore = calculateGroundingScore(results.grounding);
|
|
352
549
|
toolScores.set('agent-grounding', agScore);
|
|
353
550
|
} catch (err) {
|
|
354
|
-
|
|
551
|
+
void err;
|
|
355
552
|
}
|
|
356
553
|
}
|
|
357
554
|
|
|
358
555
|
// Testability score
|
|
359
556
|
if (results.testability) {
|
|
360
|
-
const { calculateTestabilityScore } =
|
|
557
|
+
const { calculateTestabilityScore } =
|
|
558
|
+
await import('@aiready/testability');
|
|
361
559
|
try {
|
|
362
560
|
const tbScore = calculateTestabilityScore(results.testability);
|
|
363
561
|
toolScores.set('testability', tbScore);
|
|
364
562
|
} catch (err) {
|
|
365
|
-
|
|
563
|
+
void err;
|
|
366
564
|
}
|
|
367
565
|
}
|
|
368
566
|
|
|
@@ -373,7 +571,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
373
571
|
score: results.docDrift.summary.score,
|
|
374
572
|
rawMetrics: results.docDrift.rawData,
|
|
375
573
|
factors: [],
|
|
376
|
-
recommendations: (results.docDrift.recommendations || []).map(
|
|
574
|
+
recommendations: (results.docDrift.recommendations || []).map(
|
|
575
|
+
(action: string) => ({
|
|
576
|
+
action,
|
|
577
|
+
estimatedImpact: 5,
|
|
578
|
+
priority: 'medium',
|
|
579
|
+
})
|
|
580
|
+
),
|
|
377
581
|
});
|
|
378
582
|
}
|
|
379
583
|
|
|
@@ -384,7 +588,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
384
588
|
score: results.deps.summary.score,
|
|
385
589
|
rawMetrics: results.deps.rawData,
|
|
386
590
|
factors: [],
|
|
387
|
-
recommendations: (results.deps.recommendations || []).map(
|
|
591
|
+
recommendations: (results.deps.recommendations || []).map(
|
|
592
|
+
(action: string) => ({
|
|
593
|
+
action,
|
|
594
|
+
estimatedImpact: 5,
|
|
595
|
+
priority: 'medium',
|
|
596
|
+
})
|
|
597
|
+
),
|
|
388
598
|
});
|
|
389
599
|
}
|
|
390
600
|
|
|
@@ -395,17 +605,26 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
395
605
|
score: results.changeAmplification.summary.score,
|
|
396
606
|
rawMetrics: results.changeAmplification.rawData,
|
|
397
607
|
factors: [],
|
|
398
|
-
recommendations: (
|
|
608
|
+
recommendations: (
|
|
609
|
+
results.changeAmplification.recommendations || []
|
|
610
|
+
).map((action: string) => ({
|
|
611
|
+
action,
|
|
612
|
+
estimatedImpact: 5,
|
|
613
|
+
priority: 'medium',
|
|
614
|
+
})),
|
|
399
615
|
});
|
|
400
616
|
}
|
|
401
617
|
|
|
402
|
-
|
|
403
618
|
// Parse CLI weight overrides (if any)
|
|
404
619
|
const cliWeights = parseWeightString((options as any).weights);
|
|
405
620
|
|
|
406
621
|
// Only calculate overall score if we have at least one tool score
|
|
407
622
|
if (toolScores.size > 0) {
|
|
408
|
-
scoringResult = calculateOverallScore(
|
|
623
|
+
scoringResult = calculateOverallScore(
|
|
624
|
+
toolScores,
|
|
625
|
+
finalOptions,
|
|
626
|
+
cliWeights.size ? cliWeights : undefined
|
|
627
|
+
);
|
|
409
628
|
|
|
410
629
|
console.log(chalk.bold('\n📊 AI Readiness Overall Score'));
|
|
411
630
|
console.log(` ${formatScore(scoringResult)}`);
|
|
@@ -413,34 +632,59 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
413
632
|
// Check if we need to compare to a previous report
|
|
414
633
|
if (options.compareTo) {
|
|
415
634
|
try {
|
|
416
|
-
const prevReportStr = readFileSync(
|
|
635
|
+
const prevReportStr = readFileSync(
|
|
636
|
+
resolvePath(process.cwd(), options.compareTo),
|
|
637
|
+
'utf8'
|
|
638
|
+
);
|
|
417
639
|
const prevReport = JSON.parse(prevReportStr);
|
|
418
|
-
const prevScore =
|
|
640
|
+
const prevScore =
|
|
641
|
+
prevReport.scoring?.score || prevReport.scoring?.overallScore;
|
|
419
642
|
|
|
420
643
|
if (typeof prevScore === 'number') {
|
|
421
644
|
const diff = scoringResult.overall - prevScore;
|
|
422
645
|
const diffStr = diff > 0 ? `+${diff}` : String(diff);
|
|
423
646
|
console.log();
|
|
424
647
|
if (diff > 0) {
|
|
425
|
-
console.log(
|
|
648
|
+
console.log(
|
|
649
|
+
chalk.green(
|
|
650
|
+
` 📈 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
|
|
651
|
+
)
|
|
652
|
+
);
|
|
426
653
|
} else if (diff < 0) {
|
|
427
|
-
console.log(
|
|
654
|
+
console.log(
|
|
655
|
+
chalk.red(
|
|
656
|
+
` 📉 Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
|
|
657
|
+
)
|
|
658
|
+
);
|
|
428
659
|
// Trend gating: if we regressed and CI is on or threshold is present, we could lower the threshold effectively,
|
|
429
660
|
// but for now, we just highlight the regression.
|
|
430
661
|
} else {
|
|
431
|
-
console.log(
|
|
662
|
+
console.log(
|
|
663
|
+
chalk.blue(
|
|
664
|
+
` ➖ Trend: No change compared to ${options.compareTo} (${prevScore} → ${scoringResult.overall})`
|
|
665
|
+
)
|
|
666
|
+
);
|
|
432
667
|
}
|
|
433
668
|
|
|
434
669
|
// Add trend info to scoringResult for programmatic use
|
|
435
670
|
(scoringResult as any).trend = {
|
|
436
671
|
previousScore: prevScore,
|
|
437
|
-
difference: diff
|
|
672
|
+
difference: diff,
|
|
438
673
|
};
|
|
439
674
|
} else {
|
|
440
|
-
console.log(
|
|
675
|
+
console.log(
|
|
676
|
+
chalk.yellow(
|
|
677
|
+
`\n ⚠️ Previous report at ${options.compareTo} does not contain an overall score.`
|
|
678
|
+
)
|
|
679
|
+
);
|
|
441
680
|
}
|
|
442
681
|
} catch (e) {
|
|
443
|
-
|
|
682
|
+
void e;
|
|
683
|
+
console.log(
|
|
684
|
+
chalk.yellow(
|
|
685
|
+
`\n ⚠️ Could not read or parse previous report at ${options.compareTo}.`
|
|
686
|
+
)
|
|
687
|
+
);
|
|
444
688
|
}
|
|
445
689
|
}
|
|
446
690
|
|
|
@@ -450,7 +694,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
450
694
|
scoringResult.breakdown.forEach((tool) => {
|
|
451
695
|
const rating = getRating(tool.score);
|
|
452
696
|
const rd = getRatingDisplay(rating);
|
|
453
|
-
console.log(
|
|
697
|
+
console.log(
|
|
698
|
+
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
|
|
699
|
+
);
|
|
454
700
|
});
|
|
455
701
|
console.log();
|
|
456
702
|
|
|
@@ -466,38 +712,56 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
466
712
|
}
|
|
467
713
|
|
|
468
714
|
// Persist JSON summary when output format is json
|
|
469
|
-
const outputFormat =
|
|
715
|
+
const outputFormat =
|
|
716
|
+
options.output || finalOptions.output?.format || 'console';
|
|
470
717
|
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
471
718
|
if (outputFormat === 'json') {
|
|
472
719
|
const timestamp = getReportTimestamp();
|
|
473
720
|
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
474
|
-
const outputPath = resolveOutputPath(
|
|
721
|
+
const outputPath = resolveOutputPath(
|
|
722
|
+
userOutputFile,
|
|
723
|
+
defaultFilename,
|
|
724
|
+
resolvedDir
|
|
725
|
+
);
|
|
475
726
|
const outputData = { ...results, scoring: scoringResult };
|
|
476
|
-
handleJSONOutput(
|
|
727
|
+
handleJSONOutput(
|
|
728
|
+
outputData,
|
|
729
|
+
outputPath,
|
|
730
|
+
`✅ Report saved to ${outputPath}`
|
|
731
|
+
);
|
|
477
732
|
|
|
478
733
|
// Warn if graph caps may be exceeded
|
|
479
|
-
warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
734
|
+
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
480
735
|
} else {
|
|
481
736
|
// Auto-persist report even in console mode for downstream tools
|
|
482
737
|
const timestamp = getReportTimestamp();
|
|
483
738
|
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
484
|
-
const outputPath = resolveOutputPath(
|
|
739
|
+
const outputPath = resolveOutputPath(
|
|
740
|
+
userOutputFile,
|
|
741
|
+
defaultFilename,
|
|
742
|
+
resolvedDir
|
|
743
|
+
);
|
|
485
744
|
const outputData = { ...results, scoring: scoringResult };
|
|
486
745
|
|
|
487
746
|
try {
|
|
488
747
|
writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
|
|
489
748
|
console.log(chalk.dim(`✅ Report auto-persisted to ${outputPath}`));
|
|
490
749
|
// Warn if graph caps may be exceeded
|
|
491
|
-
warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
750
|
+
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
492
751
|
} catch (err) {
|
|
493
|
-
|
|
752
|
+
void err;
|
|
494
753
|
}
|
|
495
754
|
}
|
|
496
755
|
|
|
497
756
|
// CI/CD Gatekeeper Mode
|
|
498
|
-
const isCI =
|
|
757
|
+
const isCI =
|
|
758
|
+
options.ci ||
|
|
759
|
+
process.env.CI === 'true' ||
|
|
760
|
+
process.env.GITHUB_ACTIONS === 'true';
|
|
499
761
|
if (isCI && scoringResult) {
|
|
500
|
-
const threshold = options.threshold
|
|
762
|
+
const threshold = options.threshold
|
|
763
|
+
? parseInt(options.threshold)
|
|
764
|
+
: undefined;
|
|
501
765
|
const failOnLevel = options.failOn || 'critical';
|
|
502
766
|
|
|
503
767
|
// Output GitHub Actions annotations
|
|
@@ -505,7 +769,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
505
769
|
console.log(`\n::group::AI Readiness Score`);
|
|
506
770
|
console.log(`score=${scoringResult.overall}`);
|
|
507
771
|
if (scoringResult.breakdown) {
|
|
508
|
-
scoringResult.breakdown.forEach(tool => {
|
|
772
|
+
scoringResult.breakdown.forEach((tool) => {
|
|
509
773
|
console.log(`${tool.toolName}=${tool.score}`);
|
|
510
774
|
});
|
|
511
775
|
}
|
|
@@ -513,9 +777,13 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
513
777
|
|
|
514
778
|
// Output annotation for score
|
|
515
779
|
if (threshold && scoringResult.overall < threshold) {
|
|
516
|
-
console.log(
|
|
780
|
+
console.log(
|
|
781
|
+
`::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`
|
|
782
|
+
);
|
|
517
783
|
} else if (threshold) {
|
|
518
|
-
console.log(
|
|
784
|
+
console.log(
|
|
785
|
+
`::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
|
|
786
|
+
);
|
|
519
787
|
}
|
|
520
788
|
|
|
521
789
|
// Output annotations for critical issues
|
|
@@ -524,7 +792,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
524
792
|
p.issues.filter((i: any) => i.severity === 'critical')
|
|
525
793
|
);
|
|
526
794
|
criticalPatterns.slice(0, 10).forEach((issue: any) => {
|
|
527
|
-
console.log(
|
|
795
|
+
console.log(
|
|
796
|
+
`::warning file=${issue.location?.file || 'unknown'},line=${issue.location?.line || 1}::${issue.message}`
|
|
797
|
+
);
|
|
528
798
|
});
|
|
529
799
|
}
|
|
530
800
|
}
|
|
@@ -542,7 +812,8 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
542
812
|
// Check fail-on severity
|
|
543
813
|
if (failOnLevel !== 'none') {
|
|
544
814
|
const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
|
|
545
|
-
const minSeverity =
|
|
815
|
+
const minSeverity =
|
|
816
|
+
severityLevels[failOnLevel as keyof typeof severityLevels] || 4;
|
|
546
817
|
|
|
547
818
|
let criticalCount = 0;
|
|
548
819
|
let majorCount = 0;
|
|
@@ -573,7 +844,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
573
844
|
if (minSeverity >= 4 && criticalCount > 0) {
|
|
574
845
|
shouldFail = true;
|
|
575
846
|
failReason = `Found ${criticalCount} critical issues`;
|
|
576
|
-
} else if (minSeverity >= 3 &&
|
|
847
|
+
} else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
|
|
577
848
|
shouldFail = true;
|
|
578
849
|
failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
|
|
579
850
|
}
|
|
@@ -584,16 +855,30 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
584
855
|
console.log(chalk.red('\n🚫 PR BLOCKED: AI Readiness Check Failed'));
|
|
585
856
|
console.log(chalk.red(` Reason: ${failReason}`));
|
|
586
857
|
console.log(chalk.dim('\n Remediation steps:'));
|
|
587
|
-
console.log(
|
|
858
|
+
console.log(
|
|
859
|
+
chalk.dim(' 1. Run `aiready scan` locally to see detailed issues')
|
|
860
|
+
);
|
|
588
861
|
console.log(chalk.dim(' 2. Fix the critical issues before merging'));
|
|
589
|
-
console.log(
|
|
862
|
+
console.log(
|
|
863
|
+
chalk.dim(
|
|
864
|
+
' 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing'
|
|
865
|
+
)
|
|
866
|
+
);
|
|
590
867
|
process.exit(1);
|
|
591
868
|
} else {
|
|
592
869
|
console.log(chalk.green('\n✅ PR PASSED: AI Readiness Check'));
|
|
593
870
|
if (threshold) {
|
|
594
|
-
console.log(
|
|
871
|
+
console.log(
|
|
872
|
+
chalk.green(
|
|
873
|
+
` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
|
|
874
|
+
)
|
|
875
|
+
);
|
|
595
876
|
}
|
|
596
|
-
console.log(
|
|
877
|
+
console.log(
|
|
878
|
+
chalk.dim(
|
|
879
|
+
'\n 💡 Track historical trends: https://getaiready.dev — Team plan $99/mo'
|
|
880
|
+
)
|
|
881
|
+
);
|
|
597
882
|
}
|
|
598
883
|
}
|
|
599
884
|
} catch (error) {
|
|
@@ -628,4 +913,4 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
628
913
|
Example GitHub Actions workflow:
|
|
629
914
|
- name: AI Readiness Check
|
|
630
915
|
run: aiready scan --ci --threshold 70
|
|
631
|
-
`;
|
|
916
|
+
`;
|