@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/dist/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
__require,
|
|
4
4
|
analyzeUnified
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HLBKROD3.mjs";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { Command } from "commander";
|
|
@@ -12,7 +12,7 @@ import { fileURLToPath } from "url";
|
|
|
12
12
|
|
|
13
13
|
// src/commands/scan.ts
|
|
14
14
|
import chalk2 from "chalk";
|
|
15
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
15
|
+
import { writeFileSync, readFileSync as readFileSync2 } from "fs";
|
|
16
16
|
import { resolve as resolvePath2 } from "path";
|
|
17
17
|
import {
|
|
18
18
|
loadMergedConfig,
|
|
@@ -42,19 +42,28 @@ function findLatestScanReport(dirPath) {
|
|
|
42
42
|
if (!existsSync(aireadyDir)) {
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
|
-
let files = readdirSync(aireadyDir).filter(
|
|
45
|
+
let files = readdirSync(aireadyDir).filter(
|
|
46
|
+
(f) => f.startsWith("aiready-report-") && f.endsWith(".json")
|
|
47
|
+
);
|
|
46
48
|
if (files.length === 0) {
|
|
47
|
-
files = readdirSync(aireadyDir).filter(
|
|
49
|
+
files = readdirSync(aireadyDir).filter(
|
|
50
|
+
(f) => f.startsWith("aiready-scan-") && f.endsWith(".json")
|
|
51
|
+
);
|
|
48
52
|
}
|
|
49
53
|
if (files.length === 0) {
|
|
50
54
|
return null;
|
|
51
55
|
}
|
|
52
|
-
const sortedFiles = files.map((f) => ({
|
|
56
|
+
const sortedFiles = files.map((f) => ({
|
|
57
|
+
name: f,
|
|
58
|
+
path: resolvePath(aireadyDir, f),
|
|
59
|
+
mtime: statSync(resolvePath(aireadyDir, f)).mtime
|
|
60
|
+
})).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
53
61
|
return sortedFiles[0].path;
|
|
54
62
|
}
|
|
55
|
-
function warnIfGraphCapExceeded(report, dirPath) {
|
|
63
|
+
async function warnIfGraphCapExceeded(report, dirPath) {
|
|
56
64
|
try {
|
|
57
|
-
const { loadConfig: loadConfig4 } =
|
|
65
|
+
const { loadConfig: loadConfig4 } = await import("@aiready/core");
|
|
66
|
+
void loadConfig4;
|
|
58
67
|
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
59
68
|
const configPath = resolvePath(dirPath, "aiready.json");
|
|
60
69
|
if (existsSync(configPath)) {
|
|
@@ -66,7 +75,8 @@ function warnIfGraphCapExceeded(report, dirPath) {
|
|
|
66
75
|
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
67
76
|
};
|
|
68
77
|
}
|
|
69
|
-
} catch (
|
|
78
|
+
} catch (err) {
|
|
79
|
+
void err;
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
|
|
@@ -77,21 +87,30 @@ function warnIfGraphCapExceeded(report, dirPath) {
|
|
|
77
87
|
}, 0) || 0;
|
|
78
88
|
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
79
89
|
console.log("");
|
|
80
|
-
console.log(
|
|
90
|
+
console.log(
|
|
91
|
+
chalk.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`)
|
|
92
|
+
);
|
|
81
93
|
if (nodeCount > graphConfig.maxNodes) {
|
|
82
|
-
console.log(
|
|
94
|
+
console.log(
|
|
95
|
+
chalk.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`)
|
|
96
|
+
);
|
|
83
97
|
}
|
|
84
98
|
if (edgeCount > graphConfig.maxEdges) {
|
|
85
|
-
console.log(
|
|
99
|
+
console.log(
|
|
100
|
+
chalk.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`)
|
|
101
|
+
);
|
|
86
102
|
}
|
|
87
103
|
console.log(chalk.dim(` To increase limits, add to aiready.json:`));
|
|
88
104
|
console.log(chalk.dim(` {`));
|
|
89
105
|
console.log(chalk.dim(` "visualizer": {`));
|
|
90
|
-
console.log(
|
|
106
|
+
console.log(
|
|
107
|
+
chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`)
|
|
108
|
+
);
|
|
91
109
|
console.log(chalk.dim(` }`));
|
|
92
110
|
console.log(chalk.dim(` }`));
|
|
93
111
|
}
|
|
94
|
-
} catch (
|
|
112
|
+
} catch (err) {
|
|
113
|
+
void err;
|
|
95
114
|
}
|
|
96
115
|
}
|
|
97
116
|
function generateMarkdownReport(report, elapsedTime) {
|
|
@@ -140,17 +159,28 @@ async function scanAction(directory, options) {
|
|
|
140
159
|
const resolvedDir = resolvePath2(process.cwd(), directory || ".");
|
|
141
160
|
try {
|
|
142
161
|
const defaults = {
|
|
143
|
-
tools: [
|
|
162
|
+
tools: [
|
|
163
|
+
"patterns",
|
|
164
|
+
"context",
|
|
165
|
+
"consistency",
|
|
166
|
+
"aiSignalClarity",
|
|
167
|
+
"grounding",
|
|
168
|
+
"testability",
|
|
169
|
+
"doc-drift",
|
|
170
|
+
"deps-health",
|
|
171
|
+
"changeAmplification"
|
|
172
|
+
],
|
|
144
173
|
include: void 0,
|
|
145
174
|
exclude: void 0,
|
|
146
175
|
output: {
|
|
147
|
-
format: "
|
|
176
|
+
format: "console",
|
|
148
177
|
file: void 0
|
|
149
178
|
}
|
|
150
179
|
};
|
|
151
180
|
let profileTools = options.tools ? options.tools.split(",").map((t) => {
|
|
152
181
|
const tool = t.trim();
|
|
153
|
-
if (tool === "hallucination" || tool === "hallucination-risk")
|
|
182
|
+
if (tool === "hallucination" || tool === "hallucination-risk")
|
|
183
|
+
return "aiSignalClarity";
|
|
154
184
|
return tool;
|
|
155
185
|
}) : void 0;
|
|
156
186
|
if (options.profile) {
|
|
@@ -168,28 +198,56 @@ async function scanAction(directory, options) {
|
|
|
168
198
|
profileTools = ["context", "consistency", "grounding"];
|
|
169
199
|
break;
|
|
170
200
|
default:
|
|
171
|
-
console.log(
|
|
172
|
-
|
|
201
|
+
console.log(
|
|
202
|
+
chalk2.yellow(
|
|
203
|
+
`
|
|
204
|
+
\u26A0\uFE0F Unknown profile '${options.profile}'. Using specified tools or defaults.`
|
|
205
|
+
)
|
|
206
|
+
);
|
|
173
207
|
}
|
|
174
208
|
}
|
|
175
|
-
const
|
|
176
|
-
tools: profileTools,
|
|
209
|
+
const cliOverrides = {
|
|
177
210
|
include: options.include?.split(","),
|
|
178
211
|
exclude: options.exclude?.split(",")
|
|
179
|
-
}
|
|
212
|
+
};
|
|
213
|
+
if (profileTools) {
|
|
214
|
+
cliOverrides.tools = profileTools;
|
|
215
|
+
}
|
|
216
|
+
const baseOptions = await loadMergedConfig(
|
|
217
|
+
resolvedDir,
|
|
218
|
+
defaults,
|
|
219
|
+
cliOverrides
|
|
220
|
+
);
|
|
180
221
|
let finalOptions = { ...baseOptions };
|
|
181
222
|
if (baseOptions.tools.includes("patterns")) {
|
|
182
223
|
const { getSmartDefaults } = await import("@aiready/pattern-detect");
|
|
183
|
-
const patternSmartDefaults = await getSmartDefaults(
|
|
184
|
-
|
|
224
|
+
const patternSmartDefaults = await getSmartDefaults(
|
|
225
|
+
resolvedDir,
|
|
226
|
+
baseOptions
|
|
227
|
+
);
|
|
228
|
+
finalOptions = {
|
|
229
|
+
...patternSmartDefaults,
|
|
230
|
+
...finalOptions,
|
|
231
|
+
...baseOptions
|
|
232
|
+
};
|
|
185
233
|
}
|
|
186
234
|
console.log(chalk2.cyan("\n=== AIReady Run Preview ==="));
|
|
187
|
-
console.log(
|
|
235
|
+
console.log(
|
|
236
|
+
chalk2.white("Tools to run:"),
|
|
237
|
+
(finalOptions.tools || ["patterns", "context", "consistency"]).join(", ")
|
|
238
|
+
);
|
|
188
239
|
console.log(chalk2.white("Will use settings from config and defaults."));
|
|
189
240
|
console.log(chalk2.white("\nGeneral settings:"));
|
|
190
|
-
if (finalOptions.rootDir)
|
|
191
|
-
|
|
192
|
-
if (finalOptions.
|
|
241
|
+
if (finalOptions.rootDir)
|
|
242
|
+
console.log(` rootDir: ${chalk2.bold(String(finalOptions.rootDir))}`);
|
|
243
|
+
if (finalOptions.include)
|
|
244
|
+
console.log(
|
|
245
|
+
` include: ${chalk2.bold(truncateArray(finalOptions.include, 6))}`
|
|
246
|
+
);
|
|
247
|
+
if (finalOptions.exclude)
|
|
248
|
+
console.log(
|
|
249
|
+
` exclude: ${chalk2.bold(truncateArray(finalOptions.exclude, 6))}`
|
|
250
|
+
);
|
|
193
251
|
if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
|
|
194
252
|
const patternDetectConfig = finalOptions["pattern-detect"] || {
|
|
195
253
|
minSimilarity: finalOptions.minSimilarity,
|
|
@@ -203,15 +261,40 @@ async function scanAction(directory, options) {
|
|
|
203
261
|
includeTests: finalOptions.includeTests
|
|
204
262
|
};
|
|
205
263
|
console.log(chalk2.white("\nPattern-detect settings:"));
|
|
206
|
-
console.log(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (patternDetectConfig.
|
|
213
|
-
|
|
214
|
-
|
|
264
|
+
console.log(
|
|
265
|
+
` minSimilarity: ${chalk2.bold(patternDetectConfig.minSimilarity ?? "default")}`
|
|
266
|
+
);
|
|
267
|
+
console.log(
|
|
268
|
+
` minLines: ${chalk2.bold(patternDetectConfig.minLines ?? "default")}`
|
|
269
|
+
);
|
|
270
|
+
if (patternDetectConfig.approx !== void 0)
|
|
271
|
+
console.log(
|
|
272
|
+
` approx: ${chalk2.bold(String(patternDetectConfig.approx))}`
|
|
273
|
+
);
|
|
274
|
+
if (patternDetectConfig.minSharedTokens !== void 0)
|
|
275
|
+
console.log(
|
|
276
|
+
` minSharedTokens: ${chalk2.bold(String(patternDetectConfig.minSharedTokens))}`
|
|
277
|
+
);
|
|
278
|
+
if (patternDetectConfig.maxCandidatesPerBlock !== void 0)
|
|
279
|
+
console.log(
|
|
280
|
+
` maxCandidatesPerBlock: ${chalk2.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`
|
|
281
|
+
);
|
|
282
|
+
if (patternDetectConfig.batchSize !== void 0)
|
|
283
|
+
console.log(
|
|
284
|
+
` batchSize: ${chalk2.bold(String(patternDetectConfig.batchSize))}`
|
|
285
|
+
);
|
|
286
|
+
if (patternDetectConfig.streamResults !== void 0)
|
|
287
|
+
console.log(
|
|
288
|
+
` streamResults: ${chalk2.bold(String(patternDetectConfig.streamResults))}`
|
|
289
|
+
);
|
|
290
|
+
if (patternDetectConfig.severity !== void 0)
|
|
291
|
+
console.log(
|
|
292
|
+
` severity: ${chalk2.bold(String(patternDetectConfig.severity))}`
|
|
293
|
+
);
|
|
294
|
+
if (patternDetectConfig.includeTests !== void 0)
|
|
295
|
+
console.log(
|
|
296
|
+
` includeTests: ${chalk2.bold(String(patternDetectConfig.includeTests))}`
|
|
297
|
+
);
|
|
215
298
|
}
|
|
216
299
|
if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
|
|
217
300
|
const ca = finalOptions["context-analyzer"] || {
|
|
@@ -223,20 +306,42 @@ async function scanAction(directory, options) {
|
|
|
223
306
|
};
|
|
224
307
|
console.log(chalk2.white("\nContext-analyzer settings:"));
|
|
225
308
|
console.log(` maxDepth: ${chalk2.bold(ca.maxDepth ?? "default")}`);
|
|
226
|
-
console.log(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (ca.
|
|
309
|
+
console.log(
|
|
310
|
+
` maxContextBudget: ${chalk2.bold(ca.maxContextBudget ?? "default")}`
|
|
311
|
+
);
|
|
312
|
+
if (ca.minCohesion !== void 0)
|
|
313
|
+
console.log(` minCohesion: ${chalk2.bold(String(ca.minCohesion))}`);
|
|
314
|
+
if (ca.maxFragmentation !== void 0)
|
|
315
|
+
console.log(
|
|
316
|
+
` maxFragmentation: ${chalk2.bold(String(ca.maxFragmentation))}`
|
|
317
|
+
);
|
|
318
|
+
if (ca.includeNodeModules !== void 0)
|
|
319
|
+
console.log(
|
|
320
|
+
` includeNodeModules: ${chalk2.bold(String(ca.includeNodeModules))}`
|
|
321
|
+
);
|
|
230
322
|
}
|
|
231
323
|
if (finalOptions.consistency) {
|
|
232
324
|
const c = finalOptions.consistency;
|
|
233
325
|
console.log(chalk2.white("\nConsistency settings:"));
|
|
234
|
-
console.log(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
326
|
+
console.log(
|
|
327
|
+
` checkNaming: ${chalk2.bold(String(c.checkNaming ?? true))}`
|
|
328
|
+
);
|
|
329
|
+
console.log(
|
|
330
|
+
` checkPatterns: ${chalk2.bold(String(c.checkPatterns ?? true))}`
|
|
331
|
+
);
|
|
332
|
+
console.log(
|
|
333
|
+
` checkArchitecture: ${chalk2.bold(String(c.checkArchitecture ?? false))}`
|
|
334
|
+
);
|
|
335
|
+
if (c.minSeverity)
|
|
336
|
+
console.log(` minSeverity: ${chalk2.bold(c.minSeverity)}`);
|
|
337
|
+
if (c.acceptedAbbreviations)
|
|
338
|
+
console.log(
|
|
339
|
+
` acceptedAbbreviations: ${chalk2.bold(truncateArray(c.acceptedAbbreviations, 8))}`
|
|
340
|
+
);
|
|
341
|
+
if (c.shortWords)
|
|
342
|
+
console.log(
|
|
343
|
+
` shortWords: ${chalk2.bold(truncateArray(c.shortWords, 8))}`
|
|
344
|
+
);
|
|
240
345
|
}
|
|
241
346
|
console.log(chalk2.white("\nStarting analysis..."));
|
|
242
347
|
const progressCallback = (event) => {
|
|
@@ -245,40 +350,62 @@ async function scanAction(directory, options) {
|
|
|
245
350
|
try {
|
|
246
351
|
if (event.tool === "patterns") {
|
|
247
352
|
const pr = event.data;
|
|
248
|
-
console.log(
|
|
249
|
-
|
|
353
|
+
console.log(
|
|
354
|
+
` Duplicate patterns: ${chalk2.bold(String(pr.duplicates?.length || 0))}`
|
|
355
|
+
);
|
|
356
|
+
console.log(
|
|
357
|
+
` Files with pattern issues: ${chalk2.bold(String(pr.results?.length || 0))}`
|
|
358
|
+
);
|
|
250
359
|
if (pr.duplicates && pr.duplicates.length > 0) {
|
|
251
360
|
pr.duplicates.slice(0, 5).forEach((d, i) => {
|
|
252
|
-
console.log(
|
|
361
|
+
console.log(
|
|
362
|
+
` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`
|
|
363
|
+
);
|
|
253
364
|
});
|
|
254
365
|
}
|
|
255
366
|
if (pr.results && pr.results.length > 0) {
|
|
256
367
|
console.log(` Top files with pattern issues:`);
|
|
257
|
-
const sortedByIssues = [...pr.results].sort(
|
|
368
|
+
const sortedByIssues = [...pr.results].sort(
|
|
369
|
+
(a, b) => (b.issues?.length || 0) - (a.issues?.length || 0)
|
|
370
|
+
);
|
|
258
371
|
sortedByIssues.slice(0, 5).forEach((r, i) => {
|
|
259
|
-
console.log(
|
|
372
|
+
console.log(
|
|
373
|
+
` ${i + 1}. ${r.fileName.split("/").pop()} - ${r.issues.length} issue(s)`
|
|
374
|
+
);
|
|
260
375
|
});
|
|
261
376
|
}
|
|
262
377
|
if (pr.groups && pr.groups.length >= 0) {
|
|
263
|
-
console.log(
|
|
378
|
+
console.log(
|
|
379
|
+
` \u2705 Grouped ${chalk2.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk2.bold(String(pr.groups.length))} file pairs`
|
|
380
|
+
);
|
|
264
381
|
}
|
|
265
382
|
if (pr.clusters && pr.clusters.length >= 0) {
|
|
266
|
-
console.log(
|
|
383
|
+
console.log(
|
|
384
|
+
` \u2705 Created ${chalk2.bold(String(pr.clusters.length))} refactor clusters`
|
|
385
|
+
);
|
|
267
386
|
pr.clusters.slice(0, 3).forEach((cl, idx) => {
|
|
268
387
|
const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
|
|
269
|
-
console.log(
|
|
388
|
+
console.log(
|
|
389
|
+
` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`
|
|
390
|
+
);
|
|
270
391
|
});
|
|
271
392
|
}
|
|
272
393
|
} else if (event.tool === "context") {
|
|
273
394
|
const cr = event.data;
|
|
274
|
-
console.log(
|
|
395
|
+
console.log(
|
|
396
|
+
` Context issues found: ${chalk2.bold(String(cr.length || 0))}`
|
|
397
|
+
);
|
|
275
398
|
cr.slice(0, 5).forEach((c, i) => {
|
|
276
399
|
const msg = c.message ? ` - ${c.message}` : "";
|
|
277
|
-
console.log(
|
|
400
|
+
console.log(
|
|
401
|
+
` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`
|
|
402
|
+
);
|
|
278
403
|
});
|
|
279
404
|
} else if (event.tool === "consistency") {
|
|
280
405
|
const rep = event.data;
|
|
281
|
-
console.log(
|
|
406
|
+
console.log(
|
|
407
|
+
` Consistency totalIssues: ${chalk2.bold(String(rep.summary?.totalIssues || 0))}`
|
|
408
|
+
);
|
|
282
409
|
if (rep.results && rep.results.length > 0) {
|
|
283
410
|
const fileMap = /* @__PURE__ */ new Map();
|
|
284
411
|
rep.results.forEach((r) => {
|
|
@@ -288,61 +415,126 @@ async function scanAction(directory, options) {
|
|
|
288
415
|
fileMap.get(file).push(issue);
|
|
289
416
|
});
|
|
290
417
|
});
|
|
291
|
-
const files = Array.from(fileMap.entries()).sort(
|
|
418
|
+
const files = Array.from(fileMap.entries()).sort(
|
|
419
|
+
(a, b) => b[1].length - a[1].length
|
|
420
|
+
);
|
|
292
421
|
const topFiles = files.slice(0, 10);
|
|
293
422
|
topFiles.forEach(([file, issues], idx) => {
|
|
294
|
-
const counts = issues.reduce(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
423
|
+
const counts = issues.reduce(
|
|
424
|
+
(acc, it) => {
|
|
425
|
+
const s = (it.severity || "info").toLowerCase();
|
|
426
|
+
acc[s] = (acc[s] || 0) + 1;
|
|
427
|
+
return acc;
|
|
428
|
+
},
|
|
429
|
+
{}
|
|
430
|
+
);
|
|
431
|
+
const sample = issues.find(
|
|
432
|
+
(it) => it.severity === "critical" || it.severity === "major"
|
|
433
|
+
) || issues[0];
|
|
300
434
|
const sampleMsg = sample ? ` \u2014 ${sample.message}` : "";
|
|
301
|
-
console.log(
|
|
435
|
+
console.log(
|
|
436
|
+
` ${idx + 1}. ${file} \u2014 ${issues.length} issue(s) (critical:${counts.critical || 0} major:${counts.major || 0} minor:${counts.minor || 0} info:${counts.info || 0})${sampleMsg}`
|
|
437
|
+
);
|
|
302
438
|
});
|
|
303
439
|
const remaining = files.length - topFiles.length;
|
|
304
440
|
if (remaining > 0) {
|
|
305
|
-
console.log(
|
|
441
|
+
console.log(
|
|
442
|
+
chalk2.dim(
|
|
443
|
+
` ... and ${remaining} more files with issues (use --output json for full details)`
|
|
444
|
+
)
|
|
445
|
+
);
|
|
306
446
|
}
|
|
307
447
|
}
|
|
308
448
|
} else if (event.tool === "doc-drift") {
|
|
309
449
|
const dr = event.data;
|
|
310
|
-
console.log(
|
|
450
|
+
console.log(
|
|
451
|
+
` Issues found: ${chalk2.bold(String(dr.issues?.length || 0))}`
|
|
452
|
+
);
|
|
311
453
|
if (dr.rawData) {
|
|
312
|
-
console.log(
|
|
313
|
-
|
|
454
|
+
console.log(
|
|
455
|
+
` Signature Mismatches: ${chalk2.bold(dr.rawData.outdatedComments || 0)}`
|
|
456
|
+
);
|
|
457
|
+
console.log(
|
|
458
|
+
` Undocumented Complexity: ${chalk2.bold(dr.rawData.undocumentedComplexity || 0)}`
|
|
459
|
+
);
|
|
314
460
|
}
|
|
315
461
|
} else if (event.tool === "deps-health") {
|
|
316
462
|
const dr = event.data;
|
|
317
|
-
console.log(
|
|
463
|
+
console.log(
|
|
464
|
+
` Packages Analyzed: ${chalk2.bold(String(dr.summary?.packagesAnalyzed || 0))}`
|
|
465
|
+
);
|
|
318
466
|
if (dr.rawData) {
|
|
319
|
-
console.log(
|
|
320
|
-
|
|
467
|
+
console.log(
|
|
468
|
+
` Deprecated Packages: ${chalk2.bold(dr.rawData.deprecatedPackages || 0)}`
|
|
469
|
+
);
|
|
470
|
+
console.log(
|
|
471
|
+
` AI Cutoff Skew Score: ${chalk2.bold(dr.rawData.trainingCutoffSkew?.toFixed(1) || 0)}`
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
} else if (event.tool === "change-amplification" || event.tool === "changeAmplification") {
|
|
475
|
+
const dr = event.data;
|
|
476
|
+
console.log(
|
|
477
|
+
` Coupling issues: ${chalk2.bold(String(dr.issues?.length || 0))}`
|
|
478
|
+
);
|
|
479
|
+
if (dr.summary) {
|
|
480
|
+
console.log(
|
|
481
|
+
` Complexity Score: ${chalk2.bold(dr.summary.score || 0)}/100`
|
|
482
|
+
);
|
|
321
483
|
}
|
|
322
484
|
}
|
|
323
485
|
} catch (err) {
|
|
486
|
+
void err;
|
|
324
487
|
}
|
|
325
488
|
};
|
|
326
|
-
const results = await analyzeUnified({
|
|
489
|
+
const results = await analyzeUnified({
|
|
490
|
+
...finalOptions,
|
|
491
|
+
progressCallback,
|
|
492
|
+
suppressToolConfig: true
|
|
493
|
+
});
|
|
327
494
|
console.log(chalk2.cyan("\n=== AIReady Run Summary ==="));
|
|
328
|
-
console.log(
|
|
495
|
+
console.log(
|
|
496
|
+
chalk2.white("Tools run:"),
|
|
497
|
+
(finalOptions.tools || ["patterns", "context", "consistency"]).join(", ")
|
|
498
|
+
);
|
|
329
499
|
console.log(chalk2.cyan("\nResults summary:"));
|
|
330
|
-
console.log(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (results.
|
|
334
|
-
|
|
500
|
+
console.log(
|
|
501
|
+
` Total issues (all tools): ${chalk2.bold(String(results.summary.totalIssues || 0))}`
|
|
502
|
+
);
|
|
503
|
+
if (results.duplicates)
|
|
504
|
+
console.log(
|
|
505
|
+
` Duplicate patterns found: ${chalk2.bold(String(results.duplicates.length || 0))}`
|
|
506
|
+
);
|
|
507
|
+
if (results.patterns)
|
|
508
|
+
console.log(
|
|
509
|
+
` Pattern files with issues: ${chalk2.bold(String(results.patterns.length || 0))}`
|
|
510
|
+
);
|
|
511
|
+
if (results.context)
|
|
512
|
+
console.log(
|
|
513
|
+
` Context issues: ${chalk2.bold(String(results.context.length || 0))}`
|
|
514
|
+
);
|
|
515
|
+
console.log(
|
|
516
|
+
` Consistency issues: ${chalk2.bold(String(results.consistency?.summary?.totalIssues || 0))}`
|
|
517
|
+
);
|
|
518
|
+
if (results.changeAmplification)
|
|
519
|
+
console.log(
|
|
520
|
+
` Change amplification: ${chalk2.bold(String(results.changeAmplification.summary?.score || 0))}/100`
|
|
521
|
+
);
|
|
335
522
|
console.log(chalk2.cyan("===========================\n"));
|
|
336
523
|
const elapsedTime = getElapsedTime(startTime);
|
|
524
|
+
void elapsedTime;
|
|
337
525
|
let scoringResult;
|
|
338
526
|
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
339
527
|
const toolScores = /* @__PURE__ */ new Map();
|
|
340
528
|
if (results.duplicates) {
|
|
341
529
|
const { calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
342
530
|
try {
|
|
343
|
-
const patternScore = calculatePatternScore(
|
|
531
|
+
const patternScore = calculatePatternScore(
|
|
532
|
+
results.duplicates,
|
|
533
|
+
results.patterns?.length || 0
|
|
534
|
+
);
|
|
344
535
|
toolScores.set("pattern-detect", patternScore);
|
|
345
536
|
} catch (err) {
|
|
537
|
+
void err;
|
|
346
538
|
}
|
|
347
539
|
}
|
|
348
540
|
if (results.context) {
|
|
@@ -352,6 +544,7 @@ async function scanAction(directory, options) {
|
|
|
352
544
|
const contextScore = calculateContextScore(ctxSummary);
|
|
353
545
|
toolScores.set("context-analyzer", contextScore);
|
|
354
546
|
} catch (err) {
|
|
547
|
+
void err;
|
|
355
548
|
}
|
|
356
549
|
}
|
|
357
550
|
if (results.consistency) {
|
|
@@ -359,17 +552,24 @@ async function scanAction(directory, options) {
|
|
|
359
552
|
try {
|
|
360
553
|
const issues = results.consistency.results?.flatMap((r) => r.issues) || [];
|
|
361
554
|
const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
|
|
362
|
-
const consistencyScore = calculateConsistencyScore(
|
|
555
|
+
const consistencyScore = calculateConsistencyScore(
|
|
556
|
+
issues,
|
|
557
|
+
totalFiles
|
|
558
|
+
);
|
|
363
559
|
toolScores.set("consistency", consistencyScore);
|
|
364
560
|
} catch (err) {
|
|
561
|
+
void err;
|
|
365
562
|
}
|
|
366
563
|
}
|
|
367
564
|
if (results.aiSignalClarity) {
|
|
368
|
-
const {
|
|
565
|
+
const { calculateAiSignalClarityScore } = await import("@aiready/ai-signal-clarity");
|
|
369
566
|
try {
|
|
370
|
-
const hrScore =
|
|
567
|
+
const hrScore = calculateAiSignalClarityScore(
|
|
568
|
+
results.aiSignalClarity
|
|
569
|
+
);
|
|
371
570
|
toolScores.set("ai-signal-clarity", hrScore);
|
|
372
571
|
} catch (err) {
|
|
572
|
+
void err;
|
|
373
573
|
}
|
|
374
574
|
}
|
|
375
575
|
if (results.grounding) {
|
|
@@ -378,6 +578,7 @@ async function scanAction(directory, options) {
|
|
|
378
578
|
const agScore = calculateGroundingScore(results.grounding);
|
|
379
579
|
toolScores.set("agent-grounding", agScore);
|
|
380
580
|
} catch (err) {
|
|
581
|
+
void err;
|
|
381
582
|
}
|
|
382
583
|
}
|
|
383
584
|
if (results.testability) {
|
|
@@ -386,6 +587,7 @@ async function scanAction(directory, options) {
|
|
|
386
587
|
const tbScore = calculateTestabilityScore(results.testability);
|
|
387
588
|
toolScores.set("testability", tbScore);
|
|
388
589
|
} catch (err) {
|
|
590
|
+
void err;
|
|
389
591
|
}
|
|
390
592
|
}
|
|
391
593
|
if (results.docDrift) {
|
|
@@ -394,7 +596,13 @@ async function scanAction(directory, options) {
|
|
|
394
596
|
score: results.docDrift.summary.score,
|
|
395
597
|
rawMetrics: results.docDrift.rawData,
|
|
396
598
|
factors: [],
|
|
397
|
-
recommendations: results.docDrift.recommendations.map(
|
|
599
|
+
recommendations: (results.docDrift.recommendations || []).map(
|
|
600
|
+
(action) => ({
|
|
601
|
+
action,
|
|
602
|
+
estimatedImpact: 5,
|
|
603
|
+
priority: "medium"
|
|
604
|
+
})
|
|
605
|
+
)
|
|
398
606
|
});
|
|
399
607
|
}
|
|
400
608
|
if (results.deps) {
|
|
@@ -403,17 +611,43 @@ async function scanAction(directory, options) {
|
|
|
403
611
|
score: results.deps.summary.score,
|
|
404
612
|
rawMetrics: results.deps.rawData,
|
|
405
613
|
factors: [],
|
|
406
|
-
recommendations: results.deps.recommendations.map(
|
|
614
|
+
recommendations: (results.deps.recommendations || []).map(
|
|
615
|
+
(action) => ({
|
|
616
|
+
action,
|
|
617
|
+
estimatedImpact: 5,
|
|
618
|
+
priority: "medium"
|
|
619
|
+
})
|
|
620
|
+
)
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (results.changeAmplification) {
|
|
624
|
+
toolScores.set("change-amplification", {
|
|
625
|
+
toolName: "change-amplification",
|
|
626
|
+
score: results.changeAmplification.summary.score,
|
|
627
|
+
rawMetrics: results.changeAmplification.rawData,
|
|
628
|
+
factors: [],
|
|
629
|
+
recommendations: (results.changeAmplification.recommendations || []).map((action) => ({
|
|
630
|
+
action,
|
|
631
|
+
estimatedImpact: 5,
|
|
632
|
+
priority: "medium"
|
|
633
|
+
}))
|
|
407
634
|
});
|
|
408
635
|
}
|
|
409
636
|
const cliWeights = parseWeightString(options.weights);
|
|
410
637
|
if (toolScores.size > 0) {
|
|
411
|
-
scoringResult = calculateOverallScore(
|
|
638
|
+
scoringResult = calculateOverallScore(
|
|
639
|
+
toolScores,
|
|
640
|
+
finalOptions,
|
|
641
|
+
cliWeights.size ? cliWeights : void 0
|
|
642
|
+
);
|
|
412
643
|
console.log(chalk2.bold("\n\u{1F4CA} AI Readiness Overall Score"));
|
|
413
644
|
console.log(` ${formatScore(scoringResult)}`);
|
|
414
645
|
if (options.compareTo) {
|
|
415
646
|
try {
|
|
416
|
-
const prevReportStr = readFileSync2(
|
|
647
|
+
const prevReportStr = readFileSync2(
|
|
648
|
+
resolvePath2(process.cwd(), options.compareTo),
|
|
649
|
+
"utf8"
|
|
650
|
+
);
|
|
417
651
|
const prevReport = JSON.parse(prevReportStr);
|
|
418
652
|
const prevScore = prevReport.scoring?.score || prevReport.scoring?.overallScore;
|
|
419
653
|
if (typeof prevScore === "number") {
|
|
@@ -421,23 +655,44 @@ async function scanAction(directory, options) {
|
|
|
421
655
|
const diffStr = diff > 0 ? `+${diff}` : String(diff);
|
|
422
656
|
console.log();
|
|
423
657
|
if (diff > 0) {
|
|
424
|
-
console.log(
|
|
658
|
+
console.log(
|
|
659
|
+
chalk2.green(
|
|
660
|
+
` \u{1F4C8} Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`
|
|
661
|
+
)
|
|
662
|
+
);
|
|
425
663
|
} else if (diff < 0) {
|
|
426
|
-
console.log(
|
|
664
|
+
console.log(
|
|
665
|
+
chalk2.red(
|
|
666
|
+
` \u{1F4C9} Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`
|
|
667
|
+
)
|
|
668
|
+
);
|
|
427
669
|
} else {
|
|
428
|
-
console.log(
|
|
670
|
+
console.log(
|
|
671
|
+
chalk2.blue(
|
|
672
|
+
` \u2796 Trend: No change compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`
|
|
673
|
+
)
|
|
674
|
+
);
|
|
429
675
|
}
|
|
430
676
|
scoringResult.trend = {
|
|
431
677
|
previousScore: prevScore,
|
|
432
678
|
difference: diff
|
|
433
679
|
};
|
|
434
680
|
} else {
|
|
435
|
-
console.log(
|
|
436
|
-
|
|
681
|
+
console.log(
|
|
682
|
+
chalk2.yellow(
|
|
683
|
+
`
|
|
684
|
+
\u26A0\uFE0F Previous report at ${options.compareTo} does not contain an overall score.`
|
|
685
|
+
)
|
|
686
|
+
);
|
|
437
687
|
}
|
|
438
688
|
} catch (e) {
|
|
439
|
-
|
|
440
|
-
|
|
689
|
+
void e;
|
|
690
|
+
console.log(
|
|
691
|
+
chalk2.yellow(
|
|
692
|
+
`
|
|
693
|
+
\u26A0\uFE0F Could not read or parse previous report at ${options.compareTo}.`
|
|
694
|
+
)
|
|
695
|
+
);
|
|
441
696
|
}
|
|
442
697
|
}
|
|
443
698
|
if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
|
|
@@ -445,7 +700,9 @@ async function scanAction(directory, options) {
|
|
|
445
700
|
scoringResult.breakdown.forEach((tool) => {
|
|
446
701
|
const rating = getRating(tool.score);
|
|
447
702
|
const rd = getRatingDisplay(rating);
|
|
448
|
-
console.log(
|
|
703
|
+
console.log(
|
|
704
|
+
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${rd.emoji}`
|
|
705
|
+
);
|
|
449
706
|
});
|
|
450
707
|
console.log();
|
|
451
708
|
if (finalOptions.scoring?.showBreakdown) {
|
|
@@ -463,10 +720,34 @@ async function scanAction(directory, options) {
|
|
|
463
720
|
if (outputFormat === "json") {
|
|
464
721
|
const timestamp = getReportTimestamp();
|
|
465
722
|
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
466
|
-
const outputPath = resolveOutputPath(
|
|
723
|
+
const outputPath = resolveOutputPath(
|
|
724
|
+
userOutputFile,
|
|
725
|
+
defaultFilename,
|
|
726
|
+
resolvedDir
|
|
727
|
+
);
|
|
467
728
|
const outputData = { ...results, scoring: scoringResult };
|
|
468
|
-
handleJSONOutput(
|
|
469
|
-
|
|
729
|
+
handleJSONOutput(
|
|
730
|
+
outputData,
|
|
731
|
+
outputPath,
|
|
732
|
+
`\u2705 Report saved to ${outputPath}`
|
|
733
|
+
);
|
|
734
|
+
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
735
|
+
} else {
|
|
736
|
+
const timestamp = getReportTimestamp();
|
|
737
|
+
const defaultFilename = `aiready-report-${timestamp}.json`;
|
|
738
|
+
const outputPath = resolveOutputPath(
|
|
739
|
+
userOutputFile,
|
|
740
|
+
defaultFilename,
|
|
741
|
+
resolvedDir
|
|
742
|
+
);
|
|
743
|
+
const outputData = { ...results, scoring: scoringResult };
|
|
744
|
+
try {
|
|
745
|
+
writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
|
|
746
|
+
console.log(chalk2.dim(`\u2705 Report auto-persisted to ${outputPath}`));
|
|
747
|
+
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
748
|
+
} catch (err) {
|
|
749
|
+
void err;
|
|
750
|
+
}
|
|
470
751
|
}
|
|
471
752
|
const isCI = options.ci || process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
472
753
|
if (isCI && scoringResult) {
|
|
@@ -483,16 +764,22 @@ async function scanAction(directory, options) {
|
|
|
483
764
|
}
|
|
484
765
|
console.log("::endgroup::");
|
|
485
766
|
if (threshold && scoringResult.overall < threshold) {
|
|
486
|
-
console.log(
|
|
767
|
+
console.log(
|
|
768
|
+
`::error::AI Readiness Score ${scoringResult.overall} is below threshold ${threshold}`
|
|
769
|
+
);
|
|
487
770
|
} else if (threshold) {
|
|
488
|
-
console.log(
|
|
771
|
+
console.log(
|
|
772
|
+
`::notice::AI Readiness Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
|
|
773
|
+
);
|
|
489
774
|
}
|
|
490
775
|
if (results.patterns) {
|
|
491
776
|
const criticalPatterns = results.patterns.flatMap(
|
|
492
777
|
(p) => p.issues.filter((i) => i.severity === "critical")
|
|
493
778
|
);
|
|
494
779
|
criticalPatterns.slice(0, 10).forEach((issue) => {
|
|
495
|
-
console.log(
|
|
780
|
+
console.log(
|
|
781
|
+
`::warning file=${issue.location?.file || "unknown"},line=${issue.location?.line || 1}::${issue.message}`
|
|
782
|
+
);
|
|
496
783
|
});
|
|
497
784
|
}
|
|
498
785
|
}
|
|
@@ -541,16 +828,30 @@ async function scanAction(directory, options) {
|
|
|
541
828
|
console.log(chalk2.red("\n\u{1F6AB} PR BLOCKED: AI Readiness Check Failed"));
|
|
542
829
|
console.log(chalk2.red(` Reason: ${failReason}`));
|
|
543
830
|
console.log(chalk2.dim("\n Remediation steps:"));
|
|
544
|
-
console.log(
|
|
831
|
+
console.log(
|
|
832
|
+
chalk2.dim(" 1. Run `aiready scan` locally to see detailed issues")
|
|
833
|
+
);
|
|
545
834
|
console.log(chalk2.dim(" 2. Fix the critical issues before merging"));
|
|
546
|
-
console.log(
|
|
835
|
+
console.log(
|
|
836
|
+
chalk2.dim(
|
|
837
|
+
" 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing"
|
|
838
|
+
)
|
|
839
|
+
);
|
|
547
840
|
process.exit(1);
|
|
548
841
|
} else {
|
|
549
842
|
console.log(chalk2.green("\n\u2705 PR PASSED: AI Readiness Check"));
|
|
550
843
|
if (threshold) {
|
|
551
|
-
console.log(
|
|
844
|
+
console.log(
|
|
845
|
+
chalk2.green(
|
|
846
|
+
` Score: ${scoringResult.overall}/100 (threshold: ${threshold})`
|
|
847
|
+
)
|
|
848
|
+
);
|
|
552
849
|
}
|
|
553
|
-
console.log(
|
|
850
|
+
console.log(
|
|
851
|
+
chalk2.dim(
|
|
852
|
+
"\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"
|
|
853
|
+
)
|
|
854
|
+
);
|
|
554
855
|
}
|
|
555
856
|
}
|
|
556
857
|
} catch (error) {
|
|
@@ -629,7 +930,11 @@ async function patternsAction(directory, options) {
|
|
|
629
930
|
if (options.minSharedTokens) {
|
|
630
931
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
631
932
|
}
|
|
632
|
-
const finalOptions = await loadMergedConfig2(
|
|
933
|
+
const finalOptions = await loadMergedConfig2(
|
|
934
|
+
resolvedDir,
|
|
935
|
+
defaults,
|
|
936
|
+
cliOptions
|
|
937
|
+
);
|
|
633
938
|
const { analyzePatterns, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
634
939
|
const { results, duplicates } = await analyzePatterns(finalOptions);
|
|
635
940
|
const elapsedTime = getElapsedTime2(startTime);
|
|
@@ -651,7 +956,11 @@ async function patternsAction(directory, options) {
|
|
|
651
956
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
652
957
|
resolvedDir
|
|
653
958
|
);
|
|
654
|
-
handleJSONOutput2(
|
|
959
|
+
handleJSONOutput2(
|
|
960
|
+
outputData,
|
|
961
|
+
outputPath,
|
|
962
|
+
`\u2705 Results saved to ${outputPath}`
|
|
963
|
+
);
|
|
655
964
|
} else {
|
|
656
965
|
const terminalWidth = process.stdout.columns || 80;
|
|
657
966
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
@@ -659,10 +968,22 @@ async function patternsAction(directory, options) {
|
|
|
659
968
|
console.log(chalk3.cyan(divider));
|
|
660
969
|
console.log(chalk3.bold.white(" PATTERN ANALYSIS SUMMARY"));
|
|
661
970
|
console.log(chalk3.cyan(divider) + "\n");
|
|
662
|
-
console.log(
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
console.log(
|
|
971
|
+
console.log(
|
|
972
|
+
chalk3.white(`\u{1F4C1} Files analyzed: ${chalk3.bold(results.length)}`)
|
|
973
|
+
);
|
|
974
|
+
console.log(
|
|
975
|
+
chalk3.yellow(
|
|
976
|
+
`\u26A0 Duplicate patterns found: ${chalk3.bold(summary.totalPatterns)}`
|
|
977
|
+
)
|
|
978
|
+
);
|
|
979
|
+
console.log(
|
|
980
|
+
chalk3.red(
|
|
981
|
+
`\u{1F4B0} Token cost (wasted): ${chalk3.bold(summary.totalTokenCost.toLocaleString())}`
|
|
982
|
+
)
|
|
983
|
+
);
|
|
984
|
+
console.log(
|
|
985
|
+
chalk3.gray(`\u23F1 Analysis time: ${chalk3.bold(elapsedTime + "s")}`)
|
|
986
|
+
);
|
|
666
987
|
const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
667
988
|
if (sortedTypes.length > 0) {
|
|
668
989
|
console.log(chalk3.cyan("\n" + divider));
|
|
@@ -682,13 +1003,21 @@ async function patternsAction(directory, options) {
|
|
|
682
1003
|
const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
|
|
683
1004
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
684
1005
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
685
|
-
console.log(
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1006
|
+
console.log(
|
|
1007
|
+
`${severityIcon} ${severity}: ${chalk3.bold(file1Name)} \u2194 ${chalk3.bold(file2Name)}`
|
|
1008
|
+
);
|
|
1009
|
+
console.log(
|
|
1010
|
+
` Similarity: ${chalk3.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${chalk3.bold(dup.tokenCost.toLocaleString())} tokens each`
|
|
1011
|
+
);
|
|
1012
|
+
console.log(
|
|
1013
|
+
` Lines: ${chalk3.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${chalk3.cyan(dup.line2 + "-" + dup.endLine2)}
|
|
1014
|
+
`
|
|
1015
|
+
);
|
|
689
1016
|
});
|
|
690
1017
|
} else {
|
|
691
|
-
console.log(
|
|
1018
|
+
console.log(
|
|
1019
|
+
chalk3.green("\n\u2728 Great! No duplicate patterns detected.\n")
|
|
1020
|
+
);
|
|
692
1021
|
}
|
|
693
1022
|
if (patternScore) {
|
|
694
1023
|
console.log(chalk3.cyan(divider));
|
|
@@ -735,7 +1064,7 @@ async function contextAction(directory, options) {
|
|
|
735
1064
|
file: void 0
|
|
736
1065
|
}
|
|
737
1066
|
};
|
|
738
|
-
|
|
1067
|
+
const baseOptions = await loadMergedConfig3(resolvedDir, defaults, {
|
|
739
1068
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
740
1069
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
741
1070
|
include: options.include?.split(","),
|
|
@@ -743,13 +1072,20 @@ async function contextAction(directory, options) {
|
|
|
743
1072
|
});
|
|
744
1073
|
let finalOptions = { ...baseOptions };
|
|
745
1074
|
const { getSmartDefaults } = await import("@aiready/context-analyzer");
|
|
746
|
-
const contextSmartDefaults = await getSmartDefaults(
|
|
1075
|
+
const contextSmartDefaults = await getSmartDefaults(
|
|
1076
|
+
resolvedDir,
|
|
1077
|
+
baseOptions
|
|
1078
|
+
);
|
|
747
1079
|
finalOptions = { ...contextSmartDefaults, ...finalOptions };
|
|
748
1080
|
console.log("\u{1F4CB} Configuration:");
|
|
749
1081
|
console.log(` Max depth: ${finalOptions.maxDepth}`);
|
|
750
1082
|
console.log(` Max context budget: ${finalOptions.maxContextBudget}`);
|
|
751
|
-
console.log(
|
|
752
|
-
|
|
1083
|
+
console.log(
|
|
1084
|
+
` Min cohesion: ${(finalOptions.minCohesion * 100).toFixed(1)}%`
|
|
1085
|
+
);
|
|
1086
|
+
console.log(
|
|
1087
|
+
` Max fragmentation: ${(finalOptions.maxFragmentation * 100).toFixed(1)}%`
|
|
1088
|
+
);
|
|
753
1089
|
console.log(` Analysis focus: ${finalOptions.focus}`);
|
|
754
1090
|
console.log("");
|
|
755
1091
|
const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
|
|
@@ -773,7 +1109,11 @@ async function contextAction(directory, options) {
|
|
|
773
1109
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
774
1110
|
resolvedDir
|
|
775
1111
|
);
|
|
776
|
-
handleJSONOutput3(
|
|
1112
|
+
handleJSONOutput3(
|
|
1113
|
+
outputData,
|
|
1114
|
+
outputPath,
|
|
1115
|
+
`\u2705 Results saved to ${outputPath}`
|
|
1116
|
+
);
|
|
777
1117
|
} else {
|
|
778
1118
|
const terminalWidth = process.stdout.columns || 80;
|
|
779
1119
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
@@ -781,59 +1121,103 @@ async function contextAction(directory, options) {
|
|
|
781
1121
|
console.log(chalk4.cyan(divider));
|
|
782
1122
|
console.log(chalk4.bold.white(" CONTEXT ANALYSIS SUMMARY"));
|
|
783
1123
|
console.log(chalk4.cyan(divider) + "\n");
|
|
784
|
-
console.log(
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
console.log(
|
|
788
|
-
|
|
1124
|
+
console.log(
|
|
1125
|
+
chalk4.white(`\u{1F4C1} Files analyzed: ${chalk4.bold(summary.totalFiles)}`)
|
|
1126
|
+
);
|
|
1127
|
+
console.log(
|
|
1128
|
+
chalk4.white(
|
|
1129
|
+
`\u{1F4CA} Total tokens: ${chalk4.bold(summary.totalTokens.toLocaleString())}`
|
|
1130
|
+
)
|
|
1131
|
+
);
|
|
1132
|
+
console.log(
|
|
1133
|
+
chalk4.yellow(
|
|
1134
|
+
`\u{1F4B0} Avg context budget: ${chalk4.bold(summary.avgContextBudget.toFixed(0))} tokens/file`
|
|
1135
|
+
)
|
|
1136
|
+
);
|
|
1137
|
+
console.log(
|
|
1138
|
+
chalk4.white(`\u23F1 Analysis time: ${chalk4.bold(elapsedTime + "s")}
|
|
1139
|
+
`)
|
|
1140
|
+
);
|
|
789
1141
|
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
790
1142
|
if (totalIssues > 0) {
|
|
791
1143
|
console.log(chalk4.bold("\u26A0\uFE0F Issues Found:\n"));
|
|
792
1144
|
if (summary.criticalIssues > 0) {
|
|
793
|
-
console.log(
|
|
1145
|
+
console.log(
|
|
1146
|
+
chalk4.red(` \u{1F534} Critical: ${chalk4.bold(summary.criticalIssues)}`)
|
|
1147
|
+
);
|
|
794
1148
|
}
|
|
795
1149
|
if (summary.majorIssues > 0) {
|
|
796
|
-
console.log(
|
|
1150
|
+
console.log(
|
|
1151
|
+
chalk4.yellow(` \u{1F7E1} Major: ${chalk4.bold(summary.majorIssues)}`)
|
|
1152
|
+
);
|
|
797
1153
|
}
|
|
798
1154
|
if (summary.minorIssues > 0) {
|
|
799
|
-
console.log(
|
|
1155
|
+
console.log(
|
|
1156
|
+
chalk4.blue(` \u{1F535} Minor: ${chalk4.bold(summary.minorIssues)}`)
|
|
1157
|
+
);
|
|
800
1158
|
}
|
|
801
|
-
console.log(
|
|
1159
|
+
console.log(
|
|
1160
|
+
chalk4.green(
|
|
1161
|
+
`
|
|
802
1162
|
\u{1F4A1} Potential savings: ${chalk4.bold(summary.totalPotentialSavings.toLocaleString())} tokens
|
|
803
|
-
`
|
|
1163
|
+
`
|
|
1164
|
+
)
|
|
1165
|
+
);
|
|
804
1166
|
} else {
|
|
805
1167
|
console.log(chalk4.green("\u2705 No significant issues found!\n"));
|
|
806
1168
|
}
|
|
807
1169
|
if (summary.deepFiles.length > 0) {
|
|
808
1170
|
console.log(chalk4.bold("\u{1F4CF} Deep Import Chains:\n"));
|
|
809
|
-
console.log(
|
|
810
|
-
|
|
811
|
-
|
|
1171
|
+
console.log(
|
|
1172
|
+
chalk4.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`)
|
|
1173
|
+
);
|
|
1174
|
+
console.log(
|
|
1175
|
+
chalk4.gray(` Maximum depth: ${summary.maxImportDepth}
|
|
1176
|
+
`)
|
|
1177
|
+
);
|
|
812
1178
|
summary.deepFiles.slice(0, 10).forEach((item) => {
|
|
813
1179
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
814
|
-
console.log(
|
|
1180
|
+
console.log(
|
|
1181
|
+
` ${chalk4.cyan("\u2192")} ${chalk4.white(fileName)} ${chalk4.dim(`(depth: ${item.depth})`)}`
|
|
1182
|
+
);
|
|
815
1183
|
});
|
|
816
1184
|
console.log();
|
|
817
1185
|
}
|
|
818
1186
|
if (summary.fragmentedModules.length > 0) {
|
|
819
1187
|
console.log(chalk4.bold("\u{1F9E9} Fragmented Modules:\n"));
|
|
820
|
-
console.log(
|
|
821
|
-
|
|
1188
|
+
console.log(
|
|
1189
|
+
chalk4.gray(
|
|
1190
|
+
` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
|
|
1191
|
+
`
|
|
1192
|
+
)
|
|
1193
|
+
);
|
|
822
1194
|
summary.fragmentedModules.slice(0, 10).forEach((module) => {
|
|
823
|
-
console.log(
|
|
824
|
-
|
|
1195
|
+
console.log(
|
|
1196
|
+
` ${chalk4.yellow("\u25CF")} ${chalk4.white(module.domain)} - ${chalk4.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`
|
|
1197
|
+
);
|
|
1198
|
+
console.log(
|
|
1199
|
+
chalk4.dim(
|
|
1200
|
+
` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`
|
|
1201
|
+
)
|
|
1202
|
+
);
|
|
825
1203
|
});
|
|
826
1204
|
console.log();
|
|
827
1205
|
}
|
|
828
1206
|
if (summary.lowCohesionFiles.length > 0) {
|
|
829
1207
|
console.log(chalk4.bold("\u{1F500} Low Cohesion Files:\n"));
|
|
830
|
-
console.log(
|
|
831
|
-
|
|
1208
|
+
console.log(
|
|
1209
|
+
chalk4.gray(
|
|
1210
|
+
` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
|
|
1211
|
+
`
|
|
1212
|
+
)
|
|
1213
|
+
);
|
|
832
1214
|
summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
|
|
833
1215
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
834
1216
|
const scorePercent = (item.score * 100).toFixed(0);
|
|
835
1217
|
const color = item.score < 0.4 ? chalk4.red : chalk4.yellow;
|
|
836
|
-
console.log(
|
|
1218
|
+
console.log(
|
|
1219
|
+
` ${color("\u25CB")} ${chalk4.white(fileName)} ${chalk4.dim(`(${scorePercent}% cohesion)`)}`
|
|
1220
|
+
);
|
|
837
1221
|
});
|
|
838
1222
|
console.log();
|
|
839
1223
|
}
|
|
@@ -842,7 +1226,9 @@ async function contextAction(directory, options) {
|
|
|
842
1226
|
summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
|
|
843
1227
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
844
1228
|
const severityColor = item.severity === "critical" ? chalk4.red : item.severity === "major" ? chalk4.yellow : chalk4.blue;
|
|
845
|
-
console.log(
|
|
1229
|
+
console.log(
|
|
1230
|
+
` ${severityColor("\u25CF")} ${chalk4.white(fileName)} ${chalk4.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`
|
|
1231
|
+
);
|
|
846
1232
|
});
|
|
847
1233
|
console.log();
|
|
848
1234
|
}
|
|
@@ -900,7 +1286,10 @@ async function consistencyAction(directory, options) {
|
|
|
900
1286
|
let consistencyScore;
|
|
901
1287
|
if (options.score) {
|
|
902
1288
|
const issues = report.results?.flatMap((r) => r.issues) || [];
|
|
903
|
-
consistencyScore = calculateConsistencyScore(
|
|
1289
|
+
consistencyScore = calculateConsistencyScore(
|
|
1290
|
+
issues,
|
|
1291
|
+
report.summary.filesAnalyzed
|
|
1292
|
+
);
|
|
904
1293
|
}
|
|
905
1294
|
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
906
1295
|
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
@@ -918,7 +1307,11 @@ async function consistencyAction(directory, options) {
|
|
|
918
1307
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
919
1308
|
resolvedDir
|
|
920
1309
|
);
|
|
921
|
-
handleJSONOutput4(
|
|
1310
|
+
handleJSONOutput4(
|
|
1311
|
+
outputData,
|
|
1312
|
+
outputPath,
|
|
1313
|
+
`\u2705 Results saved to ${outputPath}`
|
|
1314
|
+
);
|
|
922
1315
|
} else if (outputFormat === "markdown") {
|
|
923
1316
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
924
1317
|
const outputPath = resolveOutputPath4(
|
|
@@ -930,15 +1323,23 @@ async function consistencyAction(directory, options) {
|
|
|
930
1323
|
console.log(chalk5.green(`\u2705 Report saved to ${outputPath}`));
|
|
931
1324
|
} else {
|
|
932
1325
|
console.log(chalk5.bold("\n\u{1F4CA} Summary\n"));
|
|
933
|
-
console.log(
|
|
1326
|
+
console.log(
|
|
1327
|
+
`Files Analyzed: ${chalk5.cyan(report.summary.filesAnalyzed)}`
|
|
1328
|
+
);
|
|
934
1329
|
console.log(`Total Issues: ${chalk5.yellow(report.summary.totalIssues)}`);
|
|
935
1330
|
console.log(` Naming: ${chalk5.yellow(report.summary.namingIssues)}`);
|
|
936
1331
|
console.log(` Patterns: ${chalk5.yellow(report.summary.patternIssues)}`);
|
|
937
|
-
console.log(
|
|
1332
|
+
console.log(
|
|
1333
|
+
` Architecture: ${chalk5.yellow(report.summary.architectureIssues || 0)}`
|
|
1334
|
+
);
|
|
938
1335
|
console.log(`Analysis Time: ${chalk5.gray(elapsedTime + "s")}
|
|
939
1336
|
`);
|
|
940
1337
|
if (report.summary.totalIssues === 0) {
|
|
941
|
-
console.log(
|
|
1338
|
+
console.log(
|
|
1339
|
+
chalk5.green(
|
|
1340
|
+
"\u2728 No consistency issues found! Your codebase is well-maintained.\n"
|
|
1341
|
+
)
|
|
1342
|
+
);
|
|
942
1343
|
} else {
|
|
943
1344
|
const namingResults = report.results.filter(
|
|
944
1345
|
(r) => r.issues.some((i) => i.category === "naming")
|
|
@@ -954,10 +1355,14 @@ async function consistencyAction(directory, options) {
|
|
|
954
1355
|
for (const issue of result.issues) {
|
|
955
1356
|
if (shown >= 5) break;
|
|
956
1357
|
const severityColor = issue.severity === "critical" ? chalk5.red : issue.severity === "major" ? chalk5.yellow : issue.severity === "minor" ? chalk5.blue : chalk5.gray;
|
|
957
|
-
console.log(
|
|
1358
|
+
console.log(
|
|
1359
|
+
`${severityColor(issue.severity.toUpperCase())} ${chalk5.dim(`${issue.location.file}:${issue.location.line}`)}`
|
|
1360
|
+
);
|
|
958
1361
|
console.log(` ${issue.message}`);
|
|
959
1362
|
if (issue.suggestion) {
|
|
960
|
-
console.log(
|
|
1363
|
+
console.log(
|
|
1364
|
+
` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`
|
|
1365
|
+
);
|
|
961
1366
|
}
|
|
962
1367
|
console.log();
|
|
963
1368
|
shown++;
|
|
@@ -977,10 +1382,14 @@ async function consistencyAction(directory, options) {
|
|
|
977
1382
|
for (const issue of result.issues) {
|
|
978
1383
|
if (shown >= 5) break;
|
|
979
1384
|
const severityColor = issue.severity === "critical" ? chalk5.red : issue.severity === "major" ? chalk5.yellow : issue.severity === "minor" ? chalk5.blue : chalk5.gray;
|
|
980
|
-
console.log(
|
|
1385
|
+
console.log(
|
|
1386
|
+
`${severityColor(issue.severity.toUpperCase())} ${chalk5.dim(`${issue.location.file}:${issue.location.line}`)}`
|
|
1387
|
+
);
|
|
981
1388
|
console.log(` ${issue.message}`);
|
|
982
1389
|
if (issue.suggestion) {
|
|
983
|
-
console.log(
|
|
1390
|
+
console.log(
|
|
1391
|
+
` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`
|
|
1392
|
+
);
|
|
984
1393
|
}
|
|
985
1394
|
console.log();
|
|
986
1395
|
shown++;
|
|
@@ -1013,7 +1422,7 @@ async function consistencyAction(directory, options) {
|
|
|
1013
1422
|
|
|
1014
1423
|
// src/commands/visualize.ts
|
|
1015
1424
|
import chalk6 from "chalk";
|
|
1016
|
-
import { writeFileSync as writeFileSync3, readFileSync as readFileSync3, existsSync as existsSync2, copyFileSync
|
|
1425
|
+
import { writeFileSync as writeFileSync3, readFileSync as readFileSync3, existsSync as existsSync2, copyFileSync } from "fs";
|
|
1017
1426
|
import { resolve as resolvePath6 } from "path";
|
|
1018
1427
|
import { spawn } from "child_process";
|
|
1019
1428
|
import { handleCLIError as handleCLIError5 } from "@aiready/core";
|
|
@@ -1026,15 +1435,21 @@ async function visualizeAction(directory, options) {
|
|
|
1026
1435
|
const latestScan = findLatestScanReport(dirPath);
|
|
1027
1436
|
if (latestScan) {
|
|
1028
1437
|
reportPath = latestScan;
|
|
1029
|
-
console.log(
|
|
1438
|
+
console.log(
|
|
1439
|
+
chalk6.dim(`Found latest report: ${latestScan.split("/").pop()}`)
|
|
1440
|
+
);
|
|
1030
1441
|
} else {
|
|
1031
1442
|
console.error(chalk6.red("\u274C No AI readiness report found"));
|
|
1032
|
-
console.log(
|
|
1443
|
+
console.log(
|
|
1444
|
+
chalk6.dim(
|
|
1445
|
+
`
|
|
1033
1446
|
Generate a report with:
|
|
1034
1447
|
aiready scan --output json
|
|
1035
1448
|
|
|
1036
1449
|
Or specify a custom report:
|
|
1037
|
-
aiready visualise --report <path-to-report.json>`
|
|
1450
|
+
aiready visualise --report <path-to-report.json>`
|
|
1451
|
+
)
|
|
1452
|
+
);
|
|
1038
1453
|
return;
|
|
1039
1454
|
}
|
|
1040
1455
|
}
|
|
@@ -1052,6 +1467,7 @@ Or specify a custom report:
|
|
|
1052
1467
|
};
|
|
1053
1468
|
}
|
|
1054
1469
|
} catch (e) {
|
|
1470
|
+
void e;
|
|
1055
1471
|
}
|
|
1056
1472
|
}
|
|
1057
1473
|
const envVisualizerConfig = JSON.stringify(graphConfig);
|
|
@@ -1072,11 +1488,18 @@ Or specify a custom report:
|
|
|
1072
1488
|
} else {
|
|
1073
1489
|
const nodemodulesLocations = [
|
|
1074
1490
|
resolvePath6(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
1075
|
-
resolvePath6(
|
|
1491
|
+
resolvePath6(
|
|
1492
|
+
process.cwd(),
|
|
1493
|
+
"node_modules",
|
|
1494
|
+
"@aiready",
|
|
1495
|
+
"visualizer"
|
|
1496
|
+
)
|
|
1076
1497
|
];
|
|
1077
1498
|
let currentDir = dirPath;
|
|
1078
1499
|
while (currentDir !== "/" && currentDir !== ".") {
|
|
1079
|
-
nodemodulesLocations.push(
|
|
1500
|
+
nodemodulesLocations.push(
|
|
1501
|
+
resolvePath6(currentDir, "node_modules", "@aiready", "visualizer")
|
|
1502
|
+
);
|
|
1080
1503
|
const parent = resolvePath6(currentDir, "..");
|
|
1081
1504
|
if (parent === currentDir) break;
|
|
1082
1505
|
currentDir = parent;
|
|
@@ -1093,7 +1516,8 @@ Or specify a custom report:
|
|
|
1093
1516
|
const vizPkgPath = __require.resolve("@aiready/visualizer/package.json");
|
|
1094
1517
|
webDir = resolvePath6(vizPkgPath, "..");
|
|
1095
1518
|
visualizerAvailable = true;
|
|
1096
|
-
} catch (
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
void err;
|
|
1097
1521
|
}
|
|
1098
1522
|
}
|
|
1099
1523
|
}
|
|
@@ -1104,7 +1528,7 @@ Or specify a custom report:
|
|
|
1104
1528
|
const copyReportToViz = () => {
|
|
1105
1529
|
try {
|
|
1106
1530
|
const destPath = resolvePath6(spawnCwd, "web", "report-data.json");
|
|
1107
|
-
|
|
1531
|
+
copyFileSync(reportPath, destPath);
|
|
1108
1532
|
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1109
1533
|
} catch (e) {
|
|
1110
1534
|
console.error("Failed to sync report:", e);
|
|
@@ -1121,30 +1545,46 @@ Or specify a custom report:
|
|
|
1121
1545
|
AIREADY_REPORT_PATH: reportPath,
|
|
1122
1546
|
AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
|
|
1123
1547
|
};
|
|
1124
|
-
const vite = spawn("pnpm", ["run", "dev:web"], {
|
|
1548
|
+
const vite = spawn("pnpm", ["run", "dev:web"], {
|
|
1549
|
+
cwd: spawnCwd,
|
|
1550
|
+
stdio: "inherit",
|
|
1551
|
+
shell: true,
|
|
1552
|
+
env: envForSpawn
|
|
1553
|
+
});
|
|
1125
1554
|
const onExit = () => {
|
|
1126
1555
|
try {
|
|
1127
1556
|
reportWatcher.close();
|
|
1128
|
-
} catch (
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
void err;
|
|
1129
1559
|
}
|
|
1130
1560
|
try {
|
|
1131
1561
|
vite.kill();
|
|
1132
|
-
} catch (
|
|
1562
|
+
} catch (err) {
|
|
1563
|
+
void err;
|
|
1133
1564
|
}
|
|
1134
1565
|
process.exit(0);
|
|
1135
1566
|
};
|
|
1136
1567
|
process.on("SIGINT", onExit);
|
|
1137
1568
|
process.on("SIGTERM", onExit);
|
|
1138
1569
|
devServerStarted = true;
|
|
1570
|
+
void devServerStarted;
|
|
1139
1571
|
return;
|
|
1140
1572
|
} else {
|
|
1141
|
-
console.log(
|
|
1142
|
-
|
|
1573
|
+
console.log(
|
|
1574
|
+
chalk6.yellow(
|
|
1575
|
+
"\u26A0\uFE0F Dev server not available (requires local @aiready/visualizer with web assets)."
|
|
1576
|
+
)
|
|
1577
|
+
);
|
|
1578
|
+
console.log(
|
|
1579
|
+
chalk6.cyan(" Falling back to static HTML generation...\n")
|
|
1580
|
+
);
|
|
1143
1581
|
useDevMode = false;
|
|
1144
1582
|
}
|
|
1145
1583
|
} catch (err) {
|
|
1146
1584
|
console.error("Failed to start dev server:", err);
|
|
1147
|
-
console.log(
|
|
1585
|
+
console.log(
|
|
1586
|
+
chalk6.cyan(" Falling back to static HTML generation...\n")
|
|
1587
|
+
);
|
|
1148
1588
|
useDevMode = false;
|
|
1149
1589
|
}
|
|
1150
1590
|
}
|
|
@@ -1166,20 +1606,25 @@ Or specify a custom report:
|
|
|
1166
1606
|
const urlPath = req.url || "/";
|
|
1167
1607
|
if (urlPath === "/" || urlPath === "/index.html") {
|
|
1168
1608
|
const content = await fsp.readFile(outPath, "utf8");
|
|
1169
|
-
res.writeHead(200, {
|
|
1609
|
+
res.writeHead(200, {
|
|
1610
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
1611
|
+
});
|
|
1170
1612
|
res.end(content);
|
|
1171
1613
|
return;
|
|
1172
1614
|
}
|
|
1173
1615
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1174
1616
|
res.end("Not found");
|
|
1175
1617
|
} catch (e) {
|
|
1618
|
+
void e;
|
|
1176
1619
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1177
1620
|
res.end("Server error");
|
|
1178
1621
|
}
|
|
1179
1622
|
});
|
|
1180
1623
|
server.listen(port, () => {
|
|
1181
1624
|
const addr = `http://localhost:${port}/`;
|
|
1182
|
-
console.log(
|
|
1625
|
+
console.log(
|
|
1626
|
+
chalk6.cyan(`\u{1F310} Local visualization server running at ${addr}`)
|
|
1627
|
+
);
|
|
1183
1628
|
spawn(opener, [`"${addr}"`], { shell: true });
|
|
1184
1629
|
});
|
|
1185
1630
|
process.on("SIGINT", () => {
|
|
@@ -1244,19 +1689,23 @@ var getDirname = () => {
|
|
|
1244
1689
|
if (typeof __dirname !== "undefined") return __dirname;
|
|
1245
1690
|
return dirname(fileURLToPath(import.meta.url));
|
|
1246
1691
|
};
|
|
1247
|
-
var packageJson = JSON.parse(
|
|
1692
|
+
var packageJson = JSON.parse(
|
|
1693
|
+
readFileSync4(join(getDirname(), "../package.json"), "utf8")
|
|
1694
|
+
);
|
|
1248
1695
|
var program = new Command();
|
|
1249
|
-
program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText(
|
|
1696
|
+
program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText(
|
|
1697
|
+
"after",
|
|
1698
|
+
`
|
|
1250
1699
|
AI READINESS SCORING:
|
|
1251
1700
|
Get a 0-100 score indicating how AI-ready your codebase is.
|
|
1252
1701
|
Use --score flag with any analysis command for detailed breakdown.
|
|
1253
1702
|
|
|
1254
1703
|
EXAMPLES:
|
|
1255
|
-
$ aiready scan #
|
|
1704
|
+
$ aiready scan # Comprehensive analysis of current directory
|
|
1256
1705
|
$ aiready scan --score # Get AI Readiness Score (0-100)
|
|
1257
1706
|
$ aiready scan --tools patterns # Run only pattern detection
|
|
1258
|
-
$ aiready
|
|
1259
|
-
$ aiready scan --output json
|
|
1707
|
+
$ npx @aiready/cli scan # Industry standard way to run standard scan
|
|
1708
|
+
$ aiready scan --output json # Output raw JSON for piping
|
|
1260
1709
|
|
|
1261
1710
|
GETTING STARTED:
|
|
1262
1711
|
1. Run 'aiready scan' to analyze your codebase
|
|
@@ -1281,23 +1730,105 @@ CONFIGURATION:
|
|
|
1281
1730
|
VERSION: ${packageJson.version}
|
|
1282
1731
|
DOCUMENTATION: https://aiready.dev/docs/cli
|
|
1283
1732
|
GITHUB: https://github.com/caopengau/aiready-cli
|
|
1284
|
-
LANDING: https://github.com/caopengau/aiready-landing`
|
|
1285
|
-
|
|
1733
|
+
LANDING: https://github.com/caopengau/aiready-landing`
|
|
1734
|
+
);
|
|
1735
|
+
program.command("scan").description(
|
|
1736
|
+
"Run comprehensive AI-readiness analysis (patterns + context + consistency)"
|
|
1737
|
+
).argument("[directory]", "Directory to analyze", ".").option(
|
|
1738
|
+
"-t, --tools <tools>",
|
|
1739
|
+
"Tools to run (comma-separated: patterns,context,consistency,doc-drift,deps-health,aiSignalClarity,grounding,testability,changeAmplification)"
|
|
1740
|
+
).option(
|
|
1741
|
+
"--profile <type>",
|
|
1742
|
+
"Scan profile to use (agentic, cost, security, onboarding)"
|
|
1743
|
+
).option(
|
|
1744
|
+
"--compare-to <path>",
|
|
1745
|
+
"Compare results against a previous AIReady report JSON"
|
|
1746
|
+
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option(
|
|
1747
|
+
"--no-score",
|
|
1748
|
+
"Disable calculating AI Readiness Score (enabled by default)"
|
|
1749
|
+
).option("--weights <weights>", "Custom scoring weights").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option(
|
|
1750
|
+
"--ci",
|
|
1751
|
+
"CI mode: GitHub Actions annotations, no colors, fail on threshold"
|
|
1752
|
+
).option(
|
|
1753
|
+
"--fail-on <level>",
|
|
1754
|
+
"Fail on issues: critical, major, any",
|
|
1755
|
+
"critical"
|
|
1756
|
+
).addHelpText("after", scanHelpText).action(async (directory, options) => {
|
|
1286
1757
|
await scanAction(directory, options);
|
|
1287
1758
|
});
|
|
1288
|
-
program.command("patterns").description("Detect duplicate code patterns that confuse AI models").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option(
|
|
1759
|
+
program.command("patterns").description("Detect duplicate code patterns that confuse AI models").argument("[directory]", "Directory to analyze", ".").option("-s, --similarity <number>", "Minimum similarity score (0-1)", "0.40").option("-l, --min-lines <number>", "Minimum lines to consider", "5").option(
|
|
1760
|
+
"--max-candidates <number>",
|
|
1761
|
+
"Maximum candidates per block (performance tuning)"
|
|
1762
|
+
).option(
|
|
1763
|
+
"--min-shared-tokens <number>",
|
|
1764
|
+
"Minimum shared tokens for candidates (performance tuning)"
|
|
1765
|
+
).option(
|
|
1766
|
+
"--full-scan",
|
|
1767
|
+
"Disable smart defaults for comprehensive analysis (slower)"
|
|
1768
|
+
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option(
|
|
1769
|
+
"--score",
|
|
1770
|
+
"Calculate and display AI Readiness Score for patterns (0-100)"
|
|
1771
|
+
).addHelpText("after", patternsHelpText).action(async (directory, options) => {
|
|
1289
1772
|
await patternsAction(directory, options);
|
|
1290
1773
|
});
|
|
1291
|
-
program.command("context").description("Analyze context window costs and dependency fragmentation").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option(
|
|
1774
|
+
program.command("context").description("Analyze context window costs and dependency fragmentation").argument("[directory]", "Directory to analyze", ".").option("--max-depth <number>", "Maximum acceptable import depth", "5").option(
|
|
1775
|
+
"--max-context <number>",
|
|
1776
|
+
"Maximum acceptable context budget (tokens)",
|
|
1777
|
+
"10000"
|
|
1778
|
+
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").option(
|
|
1779
|
+
"--score",
|
|
1780
|
+
"Calculate and display AI Readiness Score for context (0-100)"
|
|
1781
|
+
).action(async (directory, options) => {
|
|
1292
1782
|
await contextAction(directory, options);
|
|
1293
1783
|
});
|
|
1294
|
-
program.command("consistency").description("Check naming conventions and architectural consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option(
|
|
1784
|
+
program.command("consistency").description("Check naming conventions and architectural consistency").argument("[directory]", "Directory to analyze", ".").option("--naming", "Check naming conventions (default: true)").option("--no-naming", "Skip naming analysis").option("--patterns", "Check code patterns (default: true)").option("--no-patterns", "Skip pattern analysis").option(
|
|
1785
|
+
"--min-severity <level>",
|
|
1786
|
+
"Minimum severity: info|minor|major|critical",
|
|
1787
|
+
"info"
|
|
1788
|
+
).option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option(
|
|
1789
|
+
"-o, --output <format>",
|
|
1790
|
+
"Output format: console, json, markdown",
|
|
1791
|
+
"console"
|
|
1792
|
+
).option("--output-file <path>", "Output file path (for json/markdown)").option(
|
|
1793
|
+
"--score",
|
|
1794
|
+
"Calculate and display AI Readiness Score for consistency (0-100)"
|
|
1795
|
+
).action(async (directory, options) => {
|
|
1295
1796
|
await consistencyAction(directory, options);
|
|
1296
1797
|
});
|
|
1297
|
-
program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option(
|
|
1798
|
+
program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option(
|
|
1799
|
+
"--report <path>",
|
|
1800
|
+
"Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)"
|
|
1801
|
+
).option(
|
|
1802
|
+
"-o, --output <path>",
|
|
1803
|
+
"Output HTML path (relative to directory)",
|
|
1804
|
+
"packages/visualizer/visualization.html"
|
|
1805
|
+
).option("--open", "Open generated HTML in default browser").option(
|
|
1806
|
+
"--serve [port]",
|
|
1807
|
+
"Start a local static server to serve the visualization (optional port number)",
|
|
1808
|
+
false
|
|
1809
|
+
).option(
|
|
1810
|
+
"--dev",
|
|
1811
|
+
"Start Vite dev server (live reload) for interactive development",
|
|
1812
|
+
true
|
|
1813
|
+
).addHelpText("after", visualiseHelpText).action(async (directory, options) => {
|
|
1298
1814
|
await visualizeAction(directory, options);
|
|
1299
1815
|
});
|
|
1300
|
-
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option(
|
|
1816
|
+
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option(
|
|
1817
|
+
"--report <path>",
|
|
1818
|
+
"Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)"
|
|
1819
|
+
).option(
|
|
1820
|
+
"-o, --output <path>",
|
|
1821
|
+
"Output HTML path (relative to directory)",
|
|
1822
|
+
"packages/visualizer/visualization.html"
|
|
1823
|
+
).option("--open", "Open generated HTML in default browser").option(
|
|
1824
|
+
"--serve [port]",
|
|
1825
|
+
"Start a local static server to serve the visualization (optional port number)",
|
|
1826
|
+
false
|
|
1827
|
+
).option(
|
|
1828
|
+
"--dev",
|
|
1829
|
+
"Start Vite dev server (live reload) for interactive development",
|
|
1830
|
+
false
|
|
1831
|
+
).addHelpText("after", visualizeHelpText).action(async (directory, options) => {
|
|
1301
1832
|
await visualizeAction(directory, options);
|
|
1302
1833
|
});
|
|
1303
1834
|
program.command("change-amplification").description("Analyze graph metrics for change amplification").argument("[directory]", "Directory to analyze", ".").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "console").option("--output-file <path>", "Output file path (for json)").action(async (directory, options) => {
|