@aiready/cli 0.9.26 → 0.9.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +17 -18
- package/dist/cli.js +499 -520
- package/dist/cli.mjs +517 -515
- package/package.json +4 -4
- package/src/cli.ts +51 -1332
- package/src/commands/consistency.ts +192 -0
- package/src/commands/context.ts +192 -0
- package/src/commands/index.ts +9 -0
- package/src/commands/patterns.ts +179 -0
- package/src/commands/scan.ts +455 -0
- package/src/commands/visualize.ts +253 -0
- package/src/utils/helpers.ts +133 -0
- package/tsconfig.json +5 -2
package/dist/cli.js
CHANGED
|
@@ -25,6 +25,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
27
|
var import_commander = require("commander");
|
|
28
|
+
var import_fs4 = require("fs");
|
|
29
|
+
var import_path7 = require("path");
|
|
30
|
+
|
|
31
|
+
// src/commands/scan.ts
|
|
32
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
33
|
+
var import_path2 = require("path");
|
|
34
|
+
var import_core = require("@aiready/core");
|
|
28
35
|
|
|
29
36
|
// src/index.ts
|
|
30
37
|
var import_pattern_detect = require("@aiready/pattern-detect");
|
|
@@ -112,59 +119,114 @@ async function analyzeUnified(options) {
|
|
|
112
119
|
return result;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
// src/
|
|
116
|
-
var import_chalk = __toESM(require("chalk"));
|
|
117
|
-
var import_fs = require("fs");
|
|
122
|
+
// src/utils/helpers.ts
|
|
118
123
|
var import_path = require("path");
|
|
119
|
-
var
|
|
120
|
-
var
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
var import_fs = require("fs");
|
|
125
|
+
var import_chalk = __toESM(require("chalk"));
|
|
126
|
+
function getReportTimestamp() {
|
|
127
|
+
const now = /* @__PURE__ */ new Date();
|
|
128
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
129
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
130
|
+
}
|
|
131
|
+
function findLatestScanReport(dirPath) {
|
|
132
|
+
const aireadyDir = (0, import_path.resolve)(dirPath, ".aiready");
|
|
133
|
+
if (!(0, import_fs.existsSync)(aireadyDir)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
let files = (0, import_fs.readdirSync)(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
|
|
137
|
+
if (files.length === 0) {
|
|
138
|
+
files = (0, import_fs.readdirSync)(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
|
|
139
|
+
}
|
|
140
|
+
if (files.length === 0) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const sortedFiles = files.map((f) => ({ name: f, path: (0, import_path.resolve)(aireadyDir, f), mtime: (0, import_fs.statSync)((0, import_path.resolve)(aireadyDir, f)).mtime })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
144
|
+
return sortedFiles[0].path;
|
|
145
|
+
}
|
|
146
|
+
function warnIfGraphCapExceeded(report, dirPath) {
|
|
147
|
+
try {
|
|
148
|
+
const { loadConfig } = require("@aiready/core");
|
|
149
|
+
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
150
|
+
const configPath = (0, import_path.resolve)(dirPath, "aiready.json");
|
|
151
|
+
if ((0, import_fs.existsSync)(configPath)) {
|
|
152
|
+
try {
|
|
153
|
+
const rawConfig = JSON.parse((0, import_fs.readFileSync)(configPath, "utf8"));
|
|
154
|
+
if (rawConfig.visualizer?.graph) {
|
|
155
|
+
graphConfig = {
|
|
156
|
+
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
157
|
+
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
|
|
164
|
+
const edgeCount = report.context?.reduce((sum, ctx) => {
|
|
165
|
+
const relCount = ctx.relatedFiles?.length || 0;
|
|
166
|
+
const depCount = ctx.dependencies?.length || 0;
|
|
167
|
+
return sum + relCount + depCount;
|
|
168
|
+
}, 0) || 0;
|
|
169
|
+
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
170
|
+
console.log("");
|
|
171
|
+
console.log(import_chalk.default.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
|
|
172
|
+
if (nodeCount > graphConfig.maxNodes) {
|
|
173
|
+
console.log(import_chalk.default.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
|
|
174
|
+
}
|
|
175
|
+
if (edgeCount > graphConfig.maxEdges) {
|
|
176
|
+
console.log(import_chalk.default.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
|
|
177
|
+
}
|
|
178
|
+
console.log(import_chalk.default.dim(` To increase limits, add to aiready.json:`));
|
|
179
|
+
console.log(import_chalk.default.dim(` {`));
|
|
180
|
+
console.log(import_chalk.default.dim(` "visualizer": {`));
|
|
181
|
+
console.log(import_chalk.default.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
|
|
182
|
+
console.log(import_chalk.default.dim(` }`));
|
|
183
|
+
console.log(import_chalk.default.dim(` }`));
|
|
184
|
+
}
|
|
185
|
+
} catch (e) {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function generateMarkdownReport(report, elapsedTime) {
|
|
189
|
+
let markdown = `# Consistency Analysis Report
|
|
128
190
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
$ aiready patterns --similarity 0.6 # Custom similarity threshold
|
|
134
|
-
$ aiready scan --output json --output-file results.json
|
|
191
|
+
`;
|
|
192
|
+
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
193
|
+
`;
|
|
194
|
+
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
135
195
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
2. Use 'aiready scan --score' for AI readiness assessment
|
|
139
|
-
3. Create aiready.json for persistent configuration
|
|
140
|
-
4. Set up CI/CD with '--threshold' for quality gates
|
|
196
|
+
`;
|
|
197
|
+
markdown += `## Summary
|
|
141
198
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
199
|
+
`;
|
|
200
|
+
markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
|
|
201
|
+
`;
|
|
202
|
+
markdown += `- **Total Issues:** ${report.summary.totalIssues}
|
|
203
|
+
`;
|
|
204
|
+
markdown += ` - Naming: ${report.summary.namingIssues}
|
|
205
|
+
`;
|
|
206
|
+
markdown += ` - Patterns: ${report.summary.patternIssues}
|
|
145
207
|
|
|
146
|
-
|
|
147
|
-
{
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
208
|
+
`;
|
|
209
|
+
if (report.recommendations.length > 0) {
|
|
210
|
+
markdown += `## Recommendations
|
|
211
|
+
|
|
212
|
+
`;
|
|
213
|
+
report.recommendations.forEach((rec, i) => {
|
|
214
|
+
markdown += `${i + 1}. ${rec}
|
|
215
|
+
`;
|
|
216
|
+
});
|
|
154
217
|
}
|
|
218
|
+
return markdown;
|
|
219
|
+
}
|
|
220
|
+
function truncateArray(arr, cap = 8) {
|
|
221
|
+
if (!Array.isArray(arr)) return "";
|
|
222
|
+
const shown = arr.slice(0, cap).map((v) => String(v));
|
|
223
|
+
const more = arr.length - shown.length;
|
|
224
|
+
return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
|
|
225
|
+
}
|
|
155
226
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
LANDING: https://github.com/caopengau/aiready-landing`);
|
|
160
|
-
program.command("scan").description("Run comprehensive AI-readiness analysis (patterns + context + consistency)").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").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", "json").option("--output-file <path>", "Output file path (for json)").option("--no-score", "Disable calculating AI Readiness Score (enabled by default)").option("--weights <weights>", "Custom scoring weights (patterns:40,context:35,consistency:25)").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").addHelpText("after", `
|
|
161
|
-
EXAMPLES:
|
|
162
|
-
$ aiready scan # Analyze all tools
|
|
163
|
-
$ aiready scan --tools patterns,context # Skip consistency
|
|
164
|
-
$ aiready scan --score --threshold 75 # CI/CD with threshold
|
|
165
|
-
$ aiready scan --output json --output-file report.json
|
|
166
|
-
`).action(async (directory, options) => {
|
|
167
|
-
console.log(import_chalk.default.blue("\u{1F680} Starting AIReady unified analysis...\n"));
|
|
227
|
+
// src/commands/scan.ts
|
|
228
|
+
async function scanAction(directory, options) {
|
|
229
|
+
console.log(import_chalk2.default.blue("\u{1F680} Starting AIReady unified analysis...\n"));
|
|
168
230
|
const startTime = Date.now();
|
|
169
231
|
const resolvedDir = (0, import_path2.resolve)(process.cwd(), directory || ".");
|
|
170
232
|
try {
|
|
@@ -188,19 +250,13 @@ EXAMPLES:
|
|
|
188
250
|
const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
189
251
|
finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
|
|
190
252
|
}
|
|
191
|
-
console.log(
|
|
192
|
-
console.log(
|
|
193
|
-
console.log(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
|
|
199
|
-
};
|
|
200
|
-
console.log(import_chalk.default.white("\nGeneral settings:"));
|
|
201
|
-
if (finalOptions.rootDir) console.log(` rootDir: ${import_chalk.default.bold(String(finalOptions.rootDir))}`);
|
|
202
|
-
if (finalOptions.include) console.log(` include: ${import_chalk.default.bold(truncate(finalOptions.include, 6))}`);
|
|
203
|
-
if (finalOptions.exclude) console.log(` exclude: ${import_chalk.default.bold(truncate(finalOptions.exclude, 6))}`);
|
|
253
|
+
console.log(import_chalk2.default.cyan("\n=== AIReady Run Preview ==="));
|
|
254
|
+
console.log(import_chalk2.default.white("Tools to run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
|
|
255
|
+
console.log(import_chalk2.default.white("Will use settings from config and defaults."));
|
|
256
|
+
console.log(import_chalk2.default.white("\nGeneral settings:"));
|
|
257
|
+
if (finalOptions.rootDir) console.log(` rootDir: ${import_chalk2.default.bold(String(finalOptions.rootDir))}`);
|
|
258
|
+
if (finalOptions.include) console.log(` include: ${import_chalk2.default.bold(truncateArray(finalOptions.include, 6))}`);
|
|
259
|
+
if (finalOptions.exclude) console.log(` exclude: ${import_chalk2.default.bold(truncateArray(finalOptions.exclude, 6))}`);
|
|
204
260
|
if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
|
|
205
261
|
const patternDetectConfig = finalOptions["pattern-detect"] || {
|
|
206
262
|
minSimilarity: finalOptions.minSimilarity,
|
|
@@ -213,16 +269,16 @@ EXAMPLES:
|
|
|
213
269
|
severity: finalOptions.severity,
|
|
214
270
|
includeTests: finalOptions.includeTests
|
|
215
271
|
};
|
|
216
|
-
console.log(
|
|
217
|
-
console.log(` minSimilarity: ${
|
|
218
|
-
console.log(` minLines: ${
|
|
219
|
-
if (patternDetectConfig.approx !== void 0) console.log(` approx: ${
|
|
220
|
-
if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${
|
|
221
|
-
if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${
|
|
222
|
-
if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${
|
|
223
|
-
if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${
|
|
224
|
-
if (patternDetectConfig.severity !== void 0) console.log(` severity: ${
|
|
225
|
-
if (patternDetectConfig.includeTests !== void 0) console.log(` includeTests: ${
|
|
272
|
+
console.log(import_chalk2.default.white("\nPattern-detect settings:"));
|
|
273
|
+
console.log(` minSimilarity: ${import_chalk2.default.bold(patternDetectConfig.minSimilarity ?? "default")}`);
|
|
274
|
+
console.log(` minLines: ${import_chalk2.default.bold(patternDetectConfig.minLines ?? "default")}`);
|
|
275
|
+
if (patternDetectConfig.approx !== void 0) console.log(` approx: ${import_chalk2.default.bold(String(patternDetectConfig.approx))}`);
|
|
276
|
+
if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${import_chalk2.default.bold(String(patternDetectConfig.minSharedTokens))}`);
|
|
277
|
+
if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${import_chalk2.default.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`);
|
|
278
|
+
if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${import_chalk2.default.bold(String(patternDetectConfig.batchSize))}`);
|
|
279
|
+
if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${import_chalk2.default.bold(String(patternDetectConfig.streamResults))}`);
|
|
280
|
+
if (patternDetectConfig.severity !== void 0) console.log(` severity: ${import_chalk2.default.bold(String(patternDetectConfig.severity))}`);
|
|
281
|
+
if (patternDetectConfig.includeTests !== void 0) console.log(` includeTests: ${import_chalk2.default.bold(String(patternDetectConfig.includeTests))}`);
|
|
226
282
|
}
|
|
227
283
|
if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
|
|
228
284
|
const ca = finalOptions["context-analyzer"] || {
|
|
@@ -232,32 +288,32 @@ EXAMPLES:
|
|
|
232
288
|
maxFragmentation: finalOptions.maxFragmentation,
|
|
233
289
|
includeNodeModules: finalOptions.includeNodeModules
|
|
234
290
|
};
|
|
235
|
-
console.log(
|
|
236
|
-
console.log(` maxDepth: ${
|
|
237
|
-
console.log(` maxContextBudget: ${
|
|
238
|
-
if (ca.minCohesion !== void 0) console.log(` minCohesion: ${
|
|
239
|
-
if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${
|
|
240
|
-
if (ca.includeNodeModules !== void 0) console.log(` includeNodeModules: ${
|
|
291
|
+
console.log(import_chalk2.default.white("\nContext-analyzer settings:"));
|
|
292
|
+
console.log(` maxDepth: ${import_chalk2.default.bold(ca.maxDepth ?? "default")}`);
|
|
293
|
+
console.log(` maxContextBudget: ${import_chalk2.default.bold(ca.maxContextBudget ?? "default")}`);
|
|
294
|
+
if (ca.minCohesion !== void 0) console.log(` minCohesion: ${import_chalk2.default.bold(String(ca.minCohesion))}`);
|
|
295
|
+
if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${import_chalk2.default.bold(String(ca.maxFragmentation))}`);
|
|
296
|
+
if (ca.includeNodeModules !== void 0) console.log(` includeNodeModules: ${import_chalk2.default.bold(String(ca.includeNodeModules))}`);
|
|
241
297
|
}
|
|
242
298
|
if (finalOptions.consistency) {
|
|
243
299
|
const c = finalOptions.consistency;
|
|
244
|
-
console.log(
|
|
245
|
-
console.log(` checkNaming: ${
|
|
246
|
-
console.log(` checkPatterns: ${
|
|
247
|
-
console.log(` checkArchitecture: ${
|
|
248
|
-
if (c.minSeverity) console.log(` minSeverity: ${
|
|
249
|
-
if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${
|
|
250
|
-
if (c.shortWords) console.log(` shortWords: ${
|
|
300
|
+
console.log(import_chalk2.default.white("\nConsistency settings:"));
|
|
301
|
+
console.log(` checkNaming: ${import_chalk2.default.bold(String(c.checkNaming ?? true))}`);
|
|
302
|
+
console.log(` checkPatterns: ${import_chalk2.default.bold(String(c.checkPatterns ?? true))}`);
|
|
303
|
+
console.log(` checkArchitecture: ${import_chalk2.default.bold(String(c.checkArchitecture ?? false))}`);
|
|
304
|
+
if (c.minSeverity) console.log(` minSeverity: ${import_chalk2.default.bold(c.minSeverity)}`);
|
|
305
|
+
if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${import_chalk2.default.bold(truncateArray(c.acceptedAbbreviations, 8))}`);
|
|
306
|
+
if (c.shortWords) console.log(` shortWords: ${import_chalk2.default.bold(truncateArray(c.shortWords, 8))}`);
|
|
251
307
|
}
|
|
252
|
-
console.log(
|
|
308
|
+
console.log(import_chalk2.default.white("\nStarting analysis..."));
|
|
253
309
|
const progressCallback = (event) => {
|
|
254
|
-
console.log(
|
|
310
|
+
console.log(import_chalk2.default.cyan(`
|
|
255
311
|
--- ${event.tool.toUpperCase()} RESULTS ---`));
|
|
256
312
|
try {
|
|
257
313
|
if (event.tool === "patterns") {
|
|
258
314
|
const pr = event.data;
|
|
259
|
-
console.log(` Duplicate patterns: ${
|
|
260
|
-
console.log(` Files with pattern issues: ${
|
|
315
|
+
console.log(` Duplicate patterns: ${import_chalk2.default.bold(String(pr.duplicates?.length || 0))}`);
|
|
316
|
+
console.log(` Files with pattern issues: ${import_chalk2.default.bold(String(pr.results?.length || 0))}`);
|
|
261
317
|
if (pr.duplicates && pr.duplicates.length > 0) {
|
|
262
318
|
pr.duplicates.slice(0, 5).forEach((d, i) => {
|
|
263
319
|
console.log(` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`);
|
|
@@ -271,10 +327,10 @@ EXAMPLES:
|
|
|
271
327
|
});
|
|
272
328
|
}
|
|
273
329
|
if (pr.groups && pr.groups.length >= 0) {
|
|
274
|
-
console.log(` \u2705 Grouped ${
|
|
330
|
+
console.log(` \u2705 Grouped ${import_chalk2.default.bold(String(pr.duplicates?.length || 0))} duplicates into ${import_chalk2.default.bold(String(pr.groups.length))} file pairs`);
|
|
275
331
|
}
|
|
276
332
|
if (pr.clusters && pr.clusters.length >= 0) {
|
|
277
|
-
console.log(` \u2705 Created ${
|
|
333
|
+
console.log(` \u2705 Created ${import_chalk2.default.bold(String(pr.clusters.length))} refactor clusters`);
|
|
278
334
|
pr.clusters.slice(0, 3).forEach((cl, idx) => {
|
|
279
335
|
const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
|
|
280
336
|
console.log(` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`);
|
|
@@ -282,14 +338,14 @@ EXAMPLES:
|
|
|
282
338
|
}
|
|
283
339
|
} else if (event.tool === "context") {
|
|
284
340
|
const cr = event.data;
|
|
285
|
-
console.log(` Context issues found: ${
|
|
341
|
+
console.log(` Context issues found: ${import_chalk2.default.bold(String(cr.length || 0))}`);
|
|
286
342
|
cr.slice(0, 5).forEach((c, i) => {
|
|
287
343
|
const msg = c.message ? ` - ${c.message}` : "";
|
|
288
344
|
console.log(` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`);
|
|
289
345
|
});
|
|
290
346
|
} else if (event.tool === "consistency") {
|
|
291
347
|
const rep = event.data;
|
|
292
|
-
console.log(` Consistency totalIssues: ${
|
|
348
|
+
console.log(` Consistency totalIssues: ${import_chalk2.default.bold(String(rep.summary?.totalIssues || 0))}`);
|
|
293
349
|
if (rep.results && rep.results.length > 0) {
|
|
294
350
|
const fileMap = /* @__PURE__ */ new Map();
|
|
295
351
|
rep.results.forEach((r) => {
|
|
@@ -313,7 +369,7 @@ EXAMPLES:
|
|
|
313
369
|
});
|
|
314
370
|
const remaining = files.length - topFiles.length;
|
|
315
371
|
if (remaining > 0) {
|
|
316
|
-
console.log(
|
|
372
|
+
console.log(import_chalk2.default.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
|
|
317
373
|
}
|
|
318
374
|
}
|
|
319
375
|
}
|
|
@@ -321,15 +377,15 @@ EXAMPLES:
|
|
|
321
377
|
}
|
|
322
378
|
};
|
|
323
379
|
const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
|
|
324
|
-
console.log(
|
|
325
|
-
console.log(
|
|
326
|
-
console.log(
|
|
327
|
-
console.log(` Total issues (all tools): ${
|
|
328
|
-
if (results.duplicates) console.log(` Duplicate patterns found: ${
|
|
329
|
-
if (results.patterns) console.log(` Pattern files with issues: ${
|
|
330
|
-
if (results.context) console.log(` Context issues: ${
|
|
331
|
-
if (results.consistency) console.log(` Consistency issues: ${
|
|
332
|
-
console.log(
|
|
380
|
+
console.log(import_chalk2.default.cyan("\n=== AIReady Run Summary ==="));
|
|
381
|
+
console.log(import_chalk2.default.white("Tools run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
|
|
382
|
+
console.log(import_chalk2.default.cyan("\nResults summary:"));
|
|
383
|
+
console.log(` Total issues (all tools): ${import_chalk2.default.bold(String(results.summary.totalIssues || 0))}`);
|
|
384
|
+
if (results.duplicates) console.log(` Duplicate patterns found: ${import_chalk2.default.bold(String(results.duplicates.length || 0))}`);
|
|
385
|
+
if (results.patterns) console.log(` Pattern files with issues: ${import_chalk2.default.bold(String(results.patterns.length || 0))}`);
|
|
386
|
+
if (results.context) console.log(` Context issues: ${import_chalk2.default.bold(String(results.context.length || 0))}`);
|
|
387
|
+
if (results.consistency) console.log(` Consistency issues: ${import_chalk2.default.bold(String(results.consistency.summary.totalIssues || 0))}`);
|
|
388
|
+
console.log(import_chalk2.default.cyan("===========================\n"));
|
|
333
389
|
const elapsedTime = (0, import_core.getElapsedTime)(startTime);
|
|
334
390
|
let scoringResult;
|
|
335
391
|
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
@@ -364,10 +420,10 @@ EXAMPLES:
|
|
|
364
420
|
const cliWeights = (0, import_core.parseWeightString)(options.weights);
|
|
365
421
|
if (toolScores.size > 0) {
|
|
366
422
|
scoringResult = (0, import_core.calculateOverallScore)(toolScores, finalOptions, cliWeights.size ? cliWeights : void 0);
|
|
367
|
-
console.log(
|
|
423
|
+
console.log(import_chalk2.default.bold("\n\u{1F4CA} AI Readiness Overall Score"));
|
|
368
424
|
console.log(` ${(0, import_core.formatScore)(scoringResult)}`);
|
|
369
425
|
if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
|
|
370
|
-
console.log(
|
|
426
|
+
console.log(import_chalk2.default.bold("\nTool breakdown:"));
|
|
371
427
|
scoringResult.breakdown.forEach((tool) => {
|
|
372
428
|
const rating = (0, import_core.getRating)(tool.score);
|
|
373
429
|
const rd = (0, import_core.getRatingDisplay)(rating);
|
|
@@ -375,7 +431,7 @@ EXAMPLES:
|
|
|
375
431
|
});
|
|
376
432
|
console.log();
|
|
377
433
|
if (finalOptions.scoring?.showBreakdown) {
|
|
378
|
-
console.log(
|
|
434
|
+
console.log(import_chalk2.default.bold("Detailed tool breakdown:"));
|
|
379
435
|
scoringResult.breakdown.forEach((tool) => {
|
|
380
436
|
console.log((0, import_core.formatToolScore)(tool));
|
|
381
437
|
});
|
|
@@ -394,19 +450,123 @@ EXAMPLES:
|
|
|
394
450
|
(0, import_core.handleJSONOutput)(outputData, outputPath, `\u2705 Report saved to ${outputPath}`);
|
|
395
451
|
warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
396
452
|
}
|
|
453
|
+
const isCI = options.ci || process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
454
|
+
if (isCI && scoringResult) {
|
|
455
|
+
const threshold = options.threshold ? parseInt(options.threshold) : void 0;
|
|
456
|
+
const failOnLevel = options.failOn || "critical";
|
|
457
|
+
if (process.env.GITHUB_ACTIONS === "true") {
|
|
458
|
+
console.log(`
|
|
459
|
+
::group::AI Readiness Score`);
|
|
460
|
+
console.log(`score=${scoringResult.overallScore}`);
|
|
461
|
+
if (scoringResult.breakdown) {
|
|
462
|
+
scoringResult.breakdown.forEach((tool) => {
|
|
463
|
+
console.log(`${tool.toolName}=${tool.score}`);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
console.log("::endgroup::");
|
|
467
|
+
if (threshold && scoringResult.overallScore < threshold) {
|
|
468
|
+
console.log(`::error::AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`);
|
|
469
|
+
} else if (threshold) {
|
|
470
|
+
console.log(`::notice::AI Readiness Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`);
|
|
471
|
+
}
|
|
472
|
+
if (results.patterns) {
|
|
473
|
+
const criticalPatterns = results.patterns.flatMap(
|
|
474
|
+
(p) => p.issues.filter((i) => i.severity === "critical")
|
|
475
|
+
);
|
|
476
|
+
criticalPatterns.slice(0, 10).forEach((issue) => {
|
|
477
|
+
console.log(`::warning file=${issue.location?.file || "unknown"},line=${issue.location?.line || 1}::${issue.message}`);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
let shouldFail = false;
|
|
482
|
+
let failReason = "";
|
|
483
|
+
if (threshold && scoringResult.overallScore < threshold) {
|
|
484
|
+
shouldFail = true;
|
|
485
|
+
failReason = `AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`;
|
|
486
|
+
}
|
|
487
|
+
if (failOnLevel !== "none") {
|
|
488
|
+
const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
|
|
489
|
+
const minSeverity = severityLevels[failOnLevel] || 4;
|
|
490
|
+
let criticalCount = 0;
|
|
491
|
+
let majorCount = 0;
|
|
492
|
+
if (results.patterns) {
|
|
493
|
+
results.patterns.forEach((p) => {
|
|
494
|
+
p.issues.forEach((i) => {
|
|
495
|
+
if (i.severity === "critical") criticalCount++;
|
|
496
|
+
if (i.severity === "major") majorCount++;
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
if (results.context) {
|
|
501
|
+
results.context.forEach((c) => {
|
|
502
|
+
if (c.severity === "critical") criticalCount++;
|
|
503
|
+
if (c.severity === "major") majorCount++;
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
if (results.consistency?.results) {
|
|
507
|
+
results.consistency.results.forEach((r) => {
|
|
508
|
+
r.issues?.forEach((i) => {
|
|
509
|
+
if (i.severity === "critical") criticalCount++;
|
|
510
|
+
if (i.severity === "major") majorCount++;
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
if (minSeverity >= 4 && criticalCount > 0) {
|
|
515
|
+
shouldFail = true;
|
|
516
|
+
failReason = `Found ${criticalCount} critical issues`;
|
|
517
|
+
} else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
|
|
518
|
+
shouldFail = true;
|
|
519
|
+
failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (shouldFail) {
|
|
523
|
+
console.log(import_chalk2.default.red("\n\u{1F6AB} PR BLOCKED: AI Readiness Check Failed"));
|
|
524
|
+
console.log(import_chalk2.default.red(` Reason: ${failReason}`));
|
|
525
|
+
console.log(import_chalk2.default.dim("\n Remediation steps:"));
|
|
526
|
+
console.log(import_chalk2.default.dim(" 1. Run `aiready scan` locally to see detailed issues"));
|
|
527
|
+
console.log(import_chalk2.default.dim(" 2. Fix the critical issues before merging"));
|
|
528
|
+
console.log(import_chalk2.default.dim(" 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing"));
|
|
529
|
+
process.exit(1);
|
|
530
|
+
} else {
|
|
531
|
+
console.log(import_chalk2.default.green("\n\u2705 PR PASSED: AI Readiness Check"));
|
|
532
|
+
if (threshold) {
|
|
533
|
+
console.log(import_chalk2.default.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
|
|
534
|
+
}
|
|
535
|
+
console.log(import_chalk2.default.dim("\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
397
538
|
} catch (error) {
|
|
398
539
|
(0, import_core.handleCLIError)(error, "Analysis");
|
|
399
540
|
}
|
|
400
|
-
}
|
|
401
|
-
|
|
541
|
+
}
|
|
542
|
+
var scanHelpText = `
|
|
402
543
|
EXAMPLES:
|
|
403
|
-
$ aiready
|
|
404
|
-
$ aiready
|
|
405
|
-
$ aiready
|
|
406
|
-
|
|
407
|
-
|
|
544
|
+
$ aiready scan # Analyze all tools
|
|
545
|
+
$ aiready scan --tools patterns,context # Skip consistency
|
|
546
|
+
$ aiready scan --score --threshold 75 # CI/CD with threshold
|
|
547
|
+
$ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
|
|
548
|
+
$ aiready scan --ci --fail-on major # Fail on major+ issues
|
|
549
|
+
$ aiready scan --output json --output-file report.json
|
|
550
|
+
|
|
551
|
+
CI/CD INTEGRATION (Gatekeeper Mode):
|
|
552
|
+
Use --ci for GitHub Actions integration:
|
|
553
|
+
- Outputs GitHub Actions annotations for PR checks
|
|
554
|
+
- Fails with exit code 1 if threshold not met
|
|
555
|
+
- Shows clear "blocked" message with remediation steps
|
|
556
|
+
|
|
557
|
+
Example GitHub Actions workflow:
|
|
558
|
+
- name: AI Readiness Check
|
|
559
|
+
run: aiready scan --ci --threshold 70
|
|
560
|
+
`;
|
|
561
|
+
|
|
562
|
+
// src/commands/patterns.ts
|
|
563
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
564
|
+
var import_path3 = require("path");
|
|
565
|
+
var import_core2 = require("@aiready/core");
|
|
566
|
+
async function patternsAction(directory, options) {
|
|
567
|
+
console.log(import_chalk3.default.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
408
568
|
const startTime = Date.now();
|
|
409
|
-
const resolvedDir = (0,
|
|
569
|
+
const resolvedDir = (0, import_path3.resolve)(process.cwd(), directory || ".");
|
|
410
570
|
try {
|
|
411
571
|
const useSmartDefaults = !options.fullScan;
|
|
412
572
|
const defaults = {
|
|
@@ -435,10 +595,10 @@ EXAMPLES:
|
|
|
435
595
|
if (options.minSharedTokens) {
|
|
436
596
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
437
597
|
}
|
|
438
|
-
const finalOptions = await (0,
|
|
598
|
+
const finalOptions = await (0, import_core2.loadMergedConfig)(resolvedDir, defaults, cliOptions);
|
|
439
599
|
const { analyzePatterns: analyzePatterns2, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
440
600
|
const { results, duplicates } = await analyzePatterns2(finalOptions);
|
|
441
|
-
const elapsedTime = (0,
|
|
601
|
+
const elapsedTime = (0, import_core2.getElapsedTime)(startTime);
|
|
442
602
|
const summary = generateSummary(results);
|
|
443
603
|
let patternScore;
|
|
444
604
|
if (options.score) {
|
|
@@ -452,66 +612,77 @@ EXAMPLES:
|
|
|
452
612
|
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
453
613
|
...patternScore && { scoring: patternScore }
|
|
454
614
|
};
|
|
455
|
-
const outputPath = (0,
|
|
615
|
+
const outputPath = (0, import_core2.resolveOutputPath)(
|
|
456
616
|
userOutputFile,
|
|
457
617
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
458
618
|
resolvedDir
|
|
459
619
|
);
|
|
460
|
-
(0,
|
|
620
|
+
(0, import_core2.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
461
621
|
} else {
|
|
462
622
|
const terminalWidth = process.stdout.columns || 80;
|
|
463
623
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
464
624
|
const divider = "\u2501".repeat(dividerWidth);
|
|
465
|
-
console.log(
|
|
466
|
-
console.log(
|
|
467
|
-
console.log(
|
|
468
|
-
console.log(
|
|
469
|
-
console.log(
|
|
470
|
-
console.log(
|
|
471
|
-
console.log(
|
|
625
|
+
console.log(import_chalk3.default.cyan(divider));
|
|
626
|
+
console.log(import_chalk3.default.bold.white(" PATTERN ANALYSIS SUMMARY"));
|
|
627
|
+
console.log(import_chalk3.default.cyan(divider) + "\n");
|
|
628
|
+
console.log(import_chalk3.default.white(`\u{1F4C1} Files analyzed: ${import_chalk3.default.bold(results.length)}`));
|
|
629
|
+
console.log(import_chalk3.default.yellow(`\u26A0 Duplicate patterns found: ${import_chalk3.default.bold(summary.totalPatterns)}`));
|
|
630
|
+
console.log(import_chalk3.default.red(`\u{1F4B0} Token cost (wasted): ${import_chalk3.default.bold(summary.totalTokenCost.toLocaleString())}`));
|
|
631
|
+
console.log(import_chalk3.default.gray(`\u23F1 Analysis time: ${import_chalk3.default.bold(elapsedTime + "s")}`));
|
|
472
632
|
const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
473
633
|
if (sortedTypes.length > 0) {
|
|
474
|
-
console.log(
|
|
475
|
-
console.log(
|
|
476
|
-
console.log(
|
|
634
|
+
console.log(import_chalk3.default.cyan("\n" + divider));
|
|
635
|
+
console.log(import_chalk3.default.bold.white(" PATTERNS BY TYPE"));
|
|
636
|
+
console.log(import_chalk3.default.cyan(divider) + "\n");
|
|
477
637
|
sortedTypes.forEach(([type, count]) => {
|
|
478
|
-
console.log(` ${
|
|
638
|
+
console.log(` ${import_chalk3.default.white(type.padEnd(15))} ${import_chalk3.default.bold(count)}`);
|
|
479
639
|
});
|
|
480
640
|
}
|
|
481
641
|
if (summary.totalPatterns > 0 && duplicates.length > 0) {
|
|
482
|
-
console.log(
|
|
483
|
-
console.log(
|
|
484
|
-
console.log(
|
|
642
|
+
console.log(import_chalk3.default.cyan("\n" + divider));
|
|
643
|
+
console.log(import_chalk3.default.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
644
|
+
console.log(import_chalk3.default.cyan(divider) + "\n");
|
|
485
645
|
const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
|
|
486
646
|
topDuplicates.forEach((dup) => {
|
|
487
647
|
const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
|
|
488
648
|
const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
|
|
489
649
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
490
650
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
491
|
-
console.log(`${severityIcon} ${severity}: ${
|
|
492
|
-
console.log(` Similarity: ${
|
|
493
|
-
console.log(` Lines: ${
|
|
651
|
+
console.log(`${severityIcon} ${severity}: ${import_chalk3.default.bold(file1Name)} \u2194 ${import_chalk3.default.bold(file2Name)}`);
|
|
652
|
+
console.log(` Similarity: ${import_chalk3.default.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${import_chalk3.default.bold(dup.tokenCost.toLocaleString())} tokens each`);
|
|
653
|
+
console.log(` Lines: ${import_chalk3.default.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${import_chalk3.default.cyan(dup.line2 + "-" + dup.endLine2)}
|
|
494
654
|
`);
|
|
495
655
|
});
|
|
496
656
|
} else {
|
|
497
|
-
console.log(
|
|
657
|
+
console.log(import_chalk3.default.green("\n\u2728 Great! No duplicate patterns detected.\n"));
|
|
498
658
|
}
|
|
499
659
|
if (patternScore) {
|
|
500
|
-
console.log(
|
|
501
|
-
console.log(
|
|
502
|
-
console.log(
|
|
503
|
-
console.log((0,
|
|
660
|
+
console.log(import_chalk3.default.cyan(divider));
|
|
661
|
+
console.log(import_chalk3.default.bold.white(" AI READINESS SCORE (Patterns)"));
|
|
662
|
+
console.log(import_chalk3.default.cyan(divider) + "\n");
|
|
663
|
+
console.log((0, import_core2.formatToolScore)(patternScore));
|
|
504
664
|
console.log();
|
|
505
665
|
}
|
|
506
666
|
}
|
|
507
667
|
} catch (error) {
|
|
508
|
-
(0,
|
|
668
|
+
(0, import_core2.handleCLIError)(error, "Pattern analysis");
|
|
509
669
|
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
670
|
+
}
|
|
671
|
+
var patternsHelpText = `
|
|
672
|
+
EXAMPLES:
|
|
673
|
+
$ aiready patterns # Default analysis
|
|
674
|
+
$ aiready patterns --similarity 0.6 # Stricter matching
|
|
675
|
+
$ aiready patterns --min-lines 10 # Larger patterns only
|
|
676
|
+
`;
|
|
677
|
+
|
|
678
|
+
// src/commands/context.ts
|
|
679
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
680
|
+
var import_path4 = require("path");
|
|
681
|
+
var import_core3 = require("@aiready/core");
|
|
682
|
+
async function contextAction(directory, options) {
|
|
683
|
+
console.log(import_chalk4.default.blue("\u{1F9E0} Analyzing context costs...\n"));
|
|
513
684
|
const startTime = Date.now();
|
|
514
|
-
const resolvedDir = (0,
|
|
685
|
+
const resolvedDir = (0, import_path4.resolve)(process.cwd(), directory || ".");
|
|
515
686
|
try {
|
|
516
687
|
const defaults = {
|
|
517
688
|
maxDepth: 5,
|
|
@@ -523,7 +694,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
523
694
|
file: void 0
|
|
524
695
|
}
|
|
525
696
|
};
|
|
526
|
-
let baseOptions = await (0,
|
|
697
|
+
let baseOptions = await (0, import_core3.loadMergedConfig)(resolvedDir, defaults, {
|
|
527
698
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
528
699
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
529
700
|
include: options.include?.split(","),
|
|
@@ -542,7 +713,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
542
713
|
console.log("");
|
|
543
714
|
const { analyzeContext: analyzeContext2, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
|
|
544
715
|
const results = await analyzeContext2(finalOptions);
|
|
545
|
-
const elapsedTime = (0,
|
|
716
|
+
const elapsedTime = (0, import_core3.getElapsedTime)(startTime);
|
|
546
717
|
const summary = generateSummary(results);
|
|
547
718
|
let contextScore;
|
|
548
719
|
if (options.score) {
|
|
@@ -556,100 +727,106 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
556
727
|
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
557
728
|
...contextScore && { scoring: contextScore }
|
|
558
729
|
};
|
|
559
|
-
const outputPath = (0,
|
|
730
|
+
const outputPath = (0, import_core3.resolveOutputPath)(
|
|
560
731
|
userOutputFile,
|
|
561
732
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
562
733
|
resolvedDir
|
|
563
734
|
);
|
|
564
|
-
(0,
|
|
735
|
+
(0, import_core3.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
565
736
|
} else {
|
|
566
737
|
const terminalWidth = process.stdout.columns || 80;
|
|
567
738
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
568
739
|
const divider = "\u2501".repeat(dividerWidth);
|
|
569
|
-
console.log(
|
|
570
|
-
console.log(
|
|
571
|
-
console.log(
|
|
572
|
-
console.log(
|
|
573
|
-
console.log(
|
|
574
|
-
console.log(
|
|
575
|
-
console.log(
|
|
740
|
+
console.log(import_chalk4.default.cyan(divider));
|
|
741
|
+
console.log(import_chalk4.default.bold.white(" CONTEXT ANALYSIS SUMMARY"));
|
|
742
|
+
console.log(import_chalk4.default.cyan(divider) + "\n");
|
|
743
|
+
console.log(import_chalk4.default.white(`\u{1F4C1} Files analyzed: ${import_chalk4.default.bold(summary.totalFiles)}`));
|
|
744
|
+
console.log(import_chalk4.default.white(`\u{1F4CA} Total tokens: ${import_chalk4.default.bold(summary.totalTokens.toLocaleString())}`));
|
|
745
|
+
console.log(import_chalk4.default.yellow(`\u{1F4B0} Avg context budget: ${import_chalk4.default.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
|
|
746
|
+
console.log(import_chalk4.default.white(`\u23F1 Analysis time: ${import_chalk4.default.bold(elapsedTime + "s")}
|
|
576
747
|
`));
|
|
577
748
|
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
578
749
|
if (totalIssues > 0) {
|
|
579
|
-
console.log(
|
|
750
|
+
console.log(import_chalk4.default.bold("\u26A0\uFE0F Issues Found:\n"));
|
|
580
751
|
if (summary.criticalIssues > 0) {
|
|
581
|
-
console.log(
|
|
752
|
+
console.log(import_chalk4.default.red(` \u{1F534} Critical: ${import_chalk4.default.bold(summary.criticalIssues)}`));
|
|
582
753
|
}
|
|
583
754
|
if (summary.majorIssues > 0) {
|
|
584
|
-
console.log(
|
|
755
|
+
console.log(import_chalk4.default.yellow(` \u{1F7E1} Major: ${import_chalk4.default.bold(summary.majorIssues)}`));
|
|
585
756
|
}
|
|
586
757
|
if (summary.minorIssues > 0) {
|
|
587
|
-
console.log(
|
|
758
|
+
console.log(import_chalk4.default.blue(` \u{1F535} Minor: ${import_chalk4.default.bold(summary.minorIssues)}`));
|
|
588
759
|
}
|
|
589
|
-
console.log(
|
|
590
|
-
\u{1F4A1} Potential savings: ${
|
|
760
|
+
console.log(import_chalk4.default.green(`
|
|
761
|
+
\u{1F4A1} Potential savings: ${import_chalk4.default.bold(summary.totalPotentialSavings.toLocaleString())} tokens
|
|
591
762
|
`));
|
|
592
763
|
} else {
|
|
593
|
-
console.log(
|
|
764
|
+
console.log(import_chalk4.default.green("\u2705 No significant issues found!\n"));
|
|
594
765
|
}
|
|
595
766
|
if (summary.deepFiles.length > 0) {
|
|
596
|
-
console.log(
|
|
597
|
-
console.log(
|
|
598
|
-
console.log(
|
|
767
|
+
console.log(import_chalk4.default.bold("\u{1F4CF} Deep Import Chains:\n"));
|
|
768
|
+
console.log(import_chalk4.default.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
|
|
769
|
+
console.log(import_chalk4.default.gray(` Maximum depth: ${summary.maxImportDepth}
|
|
599
770
|
`));
|
|
600
771
|
summary.deepFiles.slice(0, 10).forEach((item) => {
|
|
601
772
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
602
|
-
console.log(` ${
|
|
773
|
+
console.log(` ${import_chalk4.default.cyan("\u2192")} ${import_chalk4.default.white(fileName)} ${import_chalk4.default.dim(`(depth: ${item.depth})`)}`);
|
|
603
774
|
});
|
|
604
775
|
console.log();
|
|
605
776
|
}
|
|
606
777
|
if (summary.fragmentedModules.length > 0) {
|
|
607
|
-
console.log(
|
|
608
|
-
console.log(
|
|
778
|
+
console.log(import_chalk4.default.bold("\u{1F9E9} Fragmented Modules:\n"));
|
|
779
|
+
console.log(import_chalk4.default.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
|
|
609
780
|
`));
|
|
610
781
|
summary.fragmentedModules.slice(0, 10).forEach((module2) => {
|
|
611
|
-
console.log(` ${
|
|
612
|
-
console.log(
|
|
782
|
+
console.log(` ${import_chalk4.default.yellow("\u25CF")} ${import_chalk4.default.white(module2.domain)} - ${import_chalk4.default.dim(`${module2.files.length} files, ${(module2.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
|
|
783
|
+
console.log(import_chalk4.default.dim(` Token cost: ${module2.totalTokens.toLocaleString()}, Cohesion: ${(module2.avgCohesion * 100).toFixed(0)}%`));
|
|
613
784
|
});
|
|
614
785
|
console.log();
|
|
615
786
|
}
|
|
616
787
|
if (summary.lowCohesionFiles.length > 0) {
|
|
617
|
-
console.log(
|
|
618
|
-
console.log(
|
|
788
|
+
console.log(import_chalk4.default.bold("\u{1F500} Low Cohesion Files:\n"));
|
|
789
|
+
console.log(import_chalk4.default.gray(` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
|
|
619
790
|
`));
|
|
620
791
|
summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
|
|
621
792
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
622
793
|
const scorePercent = (item.score * 100).toFixed(0);
|
|
623
|
-
const color = item.score < 0.4 ?
|
|
624
|
-
console.log(` ${color("\u25CB")} ${
|
|
794
|
+
const color = item.score < 0.4 ? import_chalk4.default.red : import_chalk4.default.yellow;
|
|
795
|
+
console.log(` ${color("\u25CB")} ${import_chalk4.default.white(fileName)} ${import_chalk4.default.dim(`(${scorePercent}% cohesion)`)}`);
|
|
625
796
|
});
|
|
626
797
|
console.log();
|
|
627
798
|
}
|
|
628
799
|
if (summary.topExpensiveFiles.length > 0) {
|
|
629
|
-
console.log(
|
|
800
|
+
console.log(import_chalk4.default.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
|
|
630
801
|
summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
|
|
631
802
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
632
|
-
const severityColor = item.severity === "critical" ?
|
|
633
|
-
console.log(` ${severityColor("\u25CF")} ${
|
|
803
|
+
const severityColor = item.severity === "critical" ? import_chalk4.default.red : item.severity === "major" ? import_chalk4.default.yellow : import_chalk4.default.blue;
|
|
804
|
+
console.log(` ${severityColor("\u25CF")} ${import_chalk4.default.white(fileName)} ${import_chalk4.default.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`);
|
|
634
805
|
});
|
|
635
806
|
console.log();
|
|
636
807
|
}
|
|
637
808
|
if (contextScore) {
|
|
638
|
-
console.log(
|
|
639
|
-
console.log(
|
|
640
|
-
console.log(
|
|
641
|
-
console.log((0,
|
|
809
|
+
console.log(import_chalk4.default.cyan(divider));
|
|
810
|
+
console.log(import_chalk4.default.bold.white(" AI READINESS SCORE (Context)"));
|
|
811
|
+
console.log(import_chalk4.default.cyan(divider) + "\n");
|
|
812
|
+
console.log((0, import_core3.formatToolScore)(contextScore));
|
|
642
813
|
console.log();
|
|
643
814
|
}
|
|
644
815
|
}
|
|
645
816
|
} catch (error) {
|
|
646
|
-
(0,
|
|
817
|
+
(0, import_core3.handleCLIError)(error, "Context analysis");
|
|
647
818
|
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/commands/consistency.ts
|
|
822
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
823
|
+
var import_fs2 = require("fs");
|
|
824
|
+
var import_path5 = require("path");
|
|
825
|
+
var import_core4 = require("@aiready/core");
|
|
826
|
+
async function consistencyAction(directory, options) {
|
|
827
|
+
console.log(import_chalk5.default.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
651
828
|
const startTime = Date.now();
|
|
652
|
-
const resolvedDir = (0,
|
|
829
|
+
const resolvedDir = (0, import_path5.resolve)(process.cwd(), directory || ".");
|
|
653
830
|
try {
|
|
654
831
|
const defaults = {
|
|
655
832
|
checkNaming: true,
|
|
@@ -662,7 +839,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
662
839
|
file: void 0
|
|
663
840
|
}
|
|
664
841
|
};
|
|
665
|
-
const finalOptions = await (0,
|
|
842
|
+
const finalOptions = await (0, import_core4.loadMergedConfig)(resolvedDir, defaults, {
|
|
666
843
|
checkNaming: options.naming !== false,
|
|
667
844
|
checkPatterns: options.patterns !== false,
|
|
668
845
|
minSeverity: options.minSeverity,
|
|
@@ -671,7 +848,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
671
848
|
});
|
|
672
849
|
const { analyzeConsistency: analyzeConsistency2, calculateConsistencyScore } = await import("@aiready/consistency");
|
|
673
850
|
const report = await analyzeConsistency2(finalOptions);
|
|
674
|
-
const elapsedTime = (0,
|
|
851
|
+
const elapsedTime = (0, import_core4.getElapsedTime)(startTime);
|
|
675
852
|
let consistencyScore;
|
|
676
853
|
if (options.score) {
|
|
677
854
|
const issues = report.results?.flatMap((r) => r.issues) || [];
|
|
@@ -688,32 +865,32 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
688
865
|
},
|
|
689
866
|
...consistencyScore && { scoring: consistencyScore }
|
|
690
867
|
};
|
|
691
|
-
const outputPath = (0,
|
|
868
|
+
const outputPath = (0, import_core4.resolveOutputPath)(
|
|
692
869
|
userOutputFile,
|
|
693
870
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
694
871
|
resolvedDir
|
|
695
872
|
);
|
|
696
|
-
(0,
|
|
873
|
+
(0, import_core4.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
697
874
|
} else if (outputFormat === "markdown") {
|
|
698
875
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
699
|
-
const outputPath = (0,
|
|
876
|
+
const outputPath = (0, import_core4.resolveOutputPath)(
|
|
700
877
|
userOutputFile,
|
|
701
878
|
`aiready-report-${getReportTimestamp()}.md`,
|
|
702
879
|
resolvedDir
|
|
703
880
|
);
|
|
704
|
-
(0,
|
|
705
|
-
console.log(
|
|
881
|
+
(0, import_fs2.writeFileSync)(outputPath, markdown);
|
|
882
|
+
console.log(import_chalk5.default.green(`\u2705 Report saved to ${outputPath}`));
|
|
706
883
|
} else {
|
|
707
|
-
console.log(
|
|
708
|
-
console.log(`Files Analyzed: ${
|
|
709
|
-
console.log(`Total Issues: ${
|
|
710
|
-
console.log(` Naming: ${
|
|
711
|
-
console.log(` Patterns: ${
|
|
712
|
-
console.log(` Architecture: ${
|
|
713
|
-
console.log(`Analysis Time: ${
|
|
884
|
+
console.log(import_chalk5.default.bold("\n\u{1F4CA} Summary\n"));
|
|
885
|
+
console.log(`Files Analyzed: ${import_chalk5.default.cyan(report.summary.filesAnalyzed)}`);
|
|
886
|
+
console.log(`Total Issues: ${import_chalk5.default.yellow(report.summary.totalIssues)}`);
|
|
887
|
+
console.log(` Naming: ${import_chalk5.default.yellow(report.summary.namingIssues)}`);
|
|
888
|
+
console.log(` Patterns: ${import_chalk5.default.yellow(report.summary.patternIssues)}`);
|
|
889
|
+
console.log(` Architecture: ${import_chalk5.default.yellow(report.summary.architectureIssues || 0)}`);
|
|
890
|
+
console.log(`Analysis Time: ${import_chalk5.default.gray(elapsedTime + "s")}
|
|
714
891
|
`);
|
|
715
892
|
if (report.summary.totalIssues === 0) {
|
|
716
|
-
console.log(
|
|
893
|
+
console.log(import_chalk5.default.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
|
|
717
894
|
} else {
|
|
718
895
|
const namingResults = report.results.filter(
|
|
719
896
|
(r) => r.issues.some((i) => i.category === "naming")
|
|
@@ -722,17 +899,17 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
722
899
|
(r) => r.issues.some((i) => i.category === "patterns")
|
|
723
900
|
);
|
|
724
901
|
if (namingResults.length > 0) {
|
|
725
|
-
console.log(
|
|
902
|
+
console.log(import_chalk5.default.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
|
|
726
903
|
let shown = 0;
|
|
727
904
|
for (const result of namingResults) {
|
|
728
905
|
if (shown >= 5) break;
|
|
729
906
|
for (const issue of result.issues) {
|
|
730
907
|
if (shown >= 5) break;
|
|
731
|
-
const severityColor = issue.severity === "critical" ?
|
|
732
|
-
console.log(`${severityColor(issue.severity.toUpperCase())} ${
|
|
908
|
+
const severityColor = issue.severity === "critical" ? import_chalk5.default.red : issue.severity === "major" ? import_chalk5.default.yellow : issue.severity === "minor" ? import_chalk5.default.blue : import_chalk5.default.gray;
|
|
909
|
+
console.log(`${severityColor(issue.severity.toUpperCase())} ${import_chalk5.default.dim(`${issue.location.file}:${issue.location.line}`)}`);
|
|
733
910
|
console.log(` ${issue.message}`);
|
|
734
911
|
if (issue.suggestion) {
|
|
735
|
-
console.log(` ${
|
|
912
|
+
console.log(` ${import_chalk5.default.dim("\u2192")} ${import_chalk5.default.italic(issue.suggestion)}`);
|
|
736
913
|
}
|
|
737
914
|
console.log();
|
|
738
915
|
shown++;
|
|
@@ -740,22 +917,22 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
740
917
|
}
|
|
741
918
|
const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
742
919
|
if (remaining > 0) {
|
|
743
|
-
console.log(
|
|
920
|
+
console.log(import_chalk5.default.dim(` ... and ${remaining} more issues
|
|
744
921
|
`));
|
|
745
922
|
}
|
|
746
923
|
}
|
|
747
924
|
if (patternResults.length > 0) {
|
|
748
|
-
console.log(
|
|
925
|
+
console.log(import_chalk5.default.bold("\u{1F504} Pattern Issues\n"));
|
|
749
926
|
let shown = 0;
|
|
750
927
|
for (const result of patternResults) {
|
|
751
928
|
if (shown >= 5) break;
|
|
752
929
|
for (const issue of result.issues) {
|
|
753
930
|
if (shown >= 5) break;
|
|
754
|
-
const severityColor = issue.severity === "critical" ?
|
|
755
|
-
console.log(`${severityColor(issue.severity.toUpperCase())} ${
|
|
931
|
+
const severityColor = issue.severity === "critical" ? import_chalk5.default.red : issue.severity === "major" ? import_chalk5.default.yellow : issue.severity === "minor" ? import_chalk5.default.blue : import_chalk5.default.gray;
|
|
932
|
+
console.log(`${severityColor(issue.severity.toUpperCase())} ${import_chalk5.default.dim(`${issue.location.file}:${issue.location.line}`)}`);
|
|
756
933
|
console.log(` ${issue.message}`);
|
|
757
934
|
if (issue.suggestion) {
|
|
758
|
-
console.log(` ${
|
|
935
|
+
console.log(` ${import_chalk5.default.dim("\u2192")} ${import_chalk5.default.italic(issue.suggestion)}`);
|
|
759
936
|
}
|
|
760
937
|
console.log();
|
|
761
938
|
shown++;
|
|
@@ -763,12 +940,12 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
763
940
|
}
|
|
764
941
|
const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
765
942
|
if (remaining > 0) {
|
|
766
|
-
console.log(
|
|
943
|
+
console.log(import_chalk5.default.dim(` ... and ${remaining} more issues
|
|
767
944
|
`));
|
|
768
945
|
}
|
|
769
946
|
}
|
|
770
947
|
if (report.recommendations.length > 0) {
|
|
771
|
-
console.log(
|
|
948
|
+
console.log(import_chalk5.default.bold("\u{1F4A1} Recommendations\n"));
|
|
772
949
|
report.recommendations.forEach((rec, i) => {
|
|
773
950
|
console.log(`${i + 1}. ${rec}`);
|
|
774
951
|
});
|
|
@@ -776,288 +953,35 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
776
953
|
}
|
|
777
954
|
}
|
|
778
955
|
if (consistencyScore) {
|
|
779
|
-
console.log(
|
|
780
|
-
console.log((0,
|
|
956
|
+
console.log(import_chalk5.default.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
|
|
957
|
+
console.log((0, import_core4.formatToolScore)(consistencyScore));
|
|
781
958
|
console.log();
|
|
782
959
|
}
|
|
783
960
|
}
|
|
784
961
|
} catch (error) {
|
|
785
|
-
(0,
|
|
962
|
+
(0, import_core4.handleCLIError)(error, "Consistency analysis");
|
|
786
963
|
}
|
|
787
|
-
});
|
|
788
|
-
function generateMarkdownReport(report, elapsedTime) {
|
|
789
|
-
let markdown = `# Consistency Analysis Report
|
|
790
|
-
|
|
791
|
-
`;
|
|
792
|
-
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
793
|
-
`;
|
|
794
|
-
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
795
|
-
|
|
796
|
-
`;
|
|
797
|
-
markdown += `## Summary
|
|
798
|
-
|
|
799
|
-
`;
|
|
800
|
-
markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
|
|
801
|
-
`;
|
|
802
|
-
markdown += `- **Total Issues:** ${report.summary.totalIssues}
|
|
803
|
-
`;
|
|
804
|
-
markdown += ` - Naming: ${report.summary.namingIssues}
|
|
805
|
-
`;
|
|
806
|
-
markdown += ` - Patterns: ${report.summary.patternIssues}
|
|
807
|
-
|
|
808
|
-
`;
|
|
809
|
-
if (report.recommendations.length > 0) {
|
|
810
|
-
markdown += `## Recommendations
|
|
811
|
-
|
|
812
|
-
`;
|
|
813
|
-
report.recommendations.forEach((rec, i) => {
|
|
814
|
-
markdown += `${i + 1}. ${rec}
|
|
815
|
-
`;
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
return markdown;
|
|
819
964
|
}
|
|
820
|
-
function generateHTML(graph) {
|
|
821
|
-
const payload = JSON.stringify(graph, null, 2);
|
|
822
|
-
return `<!doctype html>
|
|
823
|
-
<html>
|
|
824
|
-
<head>
|
|
825
|
-
<meta charset="utf-8" />
|
|
826
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
827
|
-
<title>AIReady Visualization</title>
|
|
828
|
-
<style>
|
|
829
|
-
html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
|
|
830
|
-
#container { display:flex; height:100vh }
|
|
831
|
-
#panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
|
|
832
|
-
#canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
|
|
833
|
-
canvas { background: #0b1220; border-radius:8px }
|
|
834
|
-
.stat { margin-bottom:12px }
|
|
835
|
-
</style>
|
|
836
|
-
</head>
|
|
837
|
-
<body>
|
|
838
|
-
<div id="container">
|
|
839
|
-
<div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
|
|
840
|
-
<div id="panel">
|
|
841
|
-
<h2>AIReady Visualization</h2>
|
|
842
|
-
<div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
|
|
843
|
-
<div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
|
|
844
|
-
<div class="stat"><strong>Legend</strong></div>
|
|
845
|
-
<div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
|
|
846
|
-
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
|
|
847
|
-
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
|
|
848
|
-
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
|
|
849
|
-
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
|
|
850
|
-
<div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
|
|
851
|
-
<div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
|
|
852
|
-
<div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
|
|
853
|
-
</div>
|
|
854
|
-
</div>
|
|
855
|
-
</div>
|
|
856
|
-
|
|
857
|
-
<script>
|
|
858
|
-
const graphData = ${payload};
|
|
859
|
-
document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
|
|
860
|
-
document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
|
|
861
965
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
function draw() {
|
|
872
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
873
|
-
|
|
874
|
-
graphData.edges.forEach(edge => {
|
|
875
|
-
const s = nodes.find(n => n.id === edge.source);
|
|
876
|
-
const t = nodes.find(n => n.id === edge.target);
|
|
877
|
-
if (!s || !t) return;
|
|
878
|
-
if (edge.type === 'related') return;
|
|
879
|
-
if (edge.type === 'similarity') {
|
|
880
|
-
ctx.strokeStyle = '#fb7e81';
|
|
881
|
-
ctx.lineWidth = 1.2;
|
|
882
|
-
} else if (edge.type === 'dependency') {
|
|
883
|
-
ctx.strokeStyle = '#84c1ff';
|
|
884
|
-
ctx.lineWidth = 1.0;
|
|
885
|
-
} else if (edge.type === 'reference') {
|
|
886
|
-
ctx.strokeStyle = '#ffa500';
|
|
887
|
-
ctx.lineWidth = 0.9;
|
|
888
|
-
} else {
|
|
889
|
-
ctx.strokeStyle = '#334155';
|
|
890
|
-
ctx.lineWidth = 0.8;
|
|
891
|
-
}
|
|
892
|
-
ctx.beginPath();
|
|
893
|
-
ctx.moveTo(s.x, s.y);
|
|
894
|
-
ctx.lineTo(t.x, t.y);
|
|
895
|
-
ctx.stroke();
|
|
896
|
-
});
|
|
897
|
-
|
|
898
|
-
const groups = {};
|
|
899
|
-
nodes.forEach(n => {
|
|
900
|
-
const g = n.group || '__default';
|
|
901
|
-
if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
|
|
902
|
-
groups[g].minX = Math.min(groups[g].minX, n.x);
|
|
903
|
-
groups[g].minY = Math.min(groups[g].minY, n.y);
|
|
904
|
-
groups[g].maxX = Math.max(groups[g].maxX, n.x);
|
|
905
|
-
groups[g].maxY = Math.max(groups[g].maxY, n.y);
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
const groupRelations = {};
|
|
909
|
-
graphData.edges.forEach(edge => {
|
|
910
|
-
const sNode = nodes.find(n => n.id === edge.source);
|
|
911
|
-
const tNode = nodes.find(n => n.id === edge.target);
|
|
912
|
-
if (!sNode || !tNode) return;
|
|
913
|
-
const g1 = sNode.group || '__default';
|
|
914
|
-
const g2 = tNode.group || '__default';
|
|
915
|
-
if (g1 === g2) return;
|
|
916
|
-
const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
|
|
917
|
-
groupRelations[key] = (groupRelations[key] || 0) + 1;
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
Object.keys(groupRelations).forEach(k => {
|
|
921
|
-
const count = groupRelations[k];
|
|
922
|
-
const [ga, gb] = k.split('::');
|
|
923
|
-
if (!groups[ga] || !groups[gb]) return;
|
|
924
|
-
const ax = (groups[ga].minX + groups[ga].maxX) / 2;
|
|
925
|
-
const ay = (groups[ga].minY + groups[ga].maxY) / 2;
|
|
926
|
-
const bx = (groups[gb].minX + groups[gb].maxX) / 2;
|
|
927
|
-
const by = (groups[gb].minY + groups[gb].maxY) / 2;
|
|
928
|
-
ctx.beginPath();
|
|
929
|
-
ctx.strokeStyle = 'rgba(148,163,184,0.25)';
|
|
930
|
-
ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
|
|
931
|
-
ctx.moveTo(ax, ay);
|
|
932
|
-
ctx.lineTo(bx, by);
|
|
933
|
-
ctx.stroke();
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
Object.keys(groups).forEach(g => {
|
|
937
|
-
if (g === '__default') return;
|
|
938
|
-
const box = groups[g];
|
|
939
|
-
const pad = 16;
|
|
940
|
-
const x = box.minX - pad;
|
|
941
|
-
const y = box.minY - pad;
|
|
942
|
-
const w = (box.maxX - box.minX) + pad * 2;
|
|
943
|
-
const h = (box.maxY - box.minY) + pad * 2;
|
|
944
|
-
ctx.save();
|
|
945
|
-
ctx.fillStyle = 'rgba(30,64,175,0.04)';
|
|
946
|
-
ctx.strokeStyle = 'rgba(30,64,175,0.12)';
|
|
947
|
-
ctx.lineWidth = 1.2;
|
|
948
|
-
const r = 8;
|
|
949
|
-
ctx.beginPath();
|
|
950
|
-
ctx.moveTo(x + r, y);
|
|
951
|
-
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
952
|
-
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
953
|
-
ctx.arcTo(x, y + h, x, y, r);
|
|
954
|
-
ctx.arcTo(x, y, x + w, y, r);
|
|
955
|
-
ctx.closePath();
|
|
956
|
-
ctx.fill();
|
|
957
|
-
ctx.stroke();
|
|
958
|
-
ctx.restore();
|
|
959
|
-
ctx.fillStyle = '#94a3b8';
|
|
960
|
-
ctx.font = '11px sans-serif';
|
|
961
|
-
ctx.fillText(g, x + 8, y + 14);
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
nodes.forEach(n => {
|
|
965
|
-
const sizeVal = (n.size || n.value || 1);
|
|
966
|
-
const r = 6 + (sizeVal / 2);
|
|
967
|
-
ctx.beginPath();
|
|
968
|
-
ctx.fillStyle = n.color || '#60a5fa';
|
|
969
|
-
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
|
|
970
|
-
ctx.fill();
|
|
971
|
-
|
|
972
|
-
ctx.fillStyle = '#e2e8f0';
|
|
973
|
-
ctx.font = '11px sans-serif';
|
|
974
|
-
ctx.textAlign = 'center';
|
|
975
|
-
ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
draw();
|
|
980
|
-
</script>
|
|
981
|
-
</body>
|
|
982
|
-
</html>`;
|
|
983
|
-
}
|
|
984
|
-
function getReportTimestamp() {
|
|
985
|
-
const now = /* @__PURE__ */ new Date();
|
|
986
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
987
|
-
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
988
|
-
}
|
|
989
|
-
function findLatestScanReport(dirPath) {
|
|
990
|
-
const aireadyDir = (0, import_path2.resolve)(dirPath, ".aiready");
|
|
991
|
-
if (!(0, import_fs2.existsSync)(aireadyDir)) {
|
|
992
|
-
return null;
|
|
993
|
-
}
|
|
994
|
-
const { readdirSync, statSync } = require("fs");
|
|
995
|
-
let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
|
|
996
|
-
if (files.length === 0) {
|
|
997
|
-
files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
|
|
998
|
-
}
|
|
999
|
-
if (files.length === 0) {
|
|
1000
|
-
return null;
|
|
1001
|
-
}
|
|
1002
|
-
const sortedFiles = files.map((f) => ({ name: f, path: (0, import_path2.resolve)(aireadyDir, f), mtime: statSync((0, import_path2.resolve)(aireadyDir, f)).mtime })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
1003
|
-
return sortedFiles[0].path;
|
|
1004
|
-
}
|
|
1005
|
-
function warnIfGraphCapExceeded(report, dirPath) {
|
|
1006
|
-
try {
|
|
1007
|
-
const { loadConfig } = require("@aiready/core");
|
|
1008
|
-
const { existsSync: existsSync2, readFileSync: readFileSync2 } = require("fs");
|
|
1009
|
-
const { resolve } = require("path");
|
|
1010
|
-
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
1011
|
-
const configPath = resolve(dirPath, "aiready.json");
|
|
1012
|
-
if (existsSync2(configPath)) {
|
|
1013
|
-
try {
|
|
1014
|
-
const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
1015
|
-
if (rawConfig.visualizer?.graph) {
|
|
1016
|
-
graphConfig = {
|
|
1017
|
-
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
1018
|
-
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
} catch (e) {
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
|
|
1025
|
-
const edgeCount = report.context?.reduce((sum, ctx) => {
|
|
1026
|
-
const relCount = ctx.relatedFiles?.length || 0;
|
|
1027
|
-
const depCount = ctx.dependencies?.length || 0;
|
|
1028
|
-
return sum + relCount + depCount;
|
|
1029
|
-
}, 0) || 0;
|
|
1030
|
-
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
1031
|
-
console.log("");
|
|
1032
|
-
console.log(import_chalk.default.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
|
|
1033
|
-
if (nodeCount > graphConfig.maxNodes) {
|
|
1034
|
-
console.log(import_chalk.default.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
|
|
1035
|
-
}
|
|
1036
|
-
if (edgeCount > graphConfig.maxEdges) {
|
|
1037
|
-
console.log(import_chalk.default.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
|
|
1038
|
-
}
|
|
1039
|
-
console.log(import_chalk.default.dim(` To increase limits, add to aiready.json:`));
|
|
1040
|
-
console.log(import_chalk.default.dim(` {`));
|
|
1041
|
-
console.log(import_chalk.default.dim(` "visualizer": {`));
|
|
1042
|
-
console.log(import_chalk.default.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
|
|
1043
|
-
console.log(import_chalk.default.dim(` }`));
|
|
1044
|
-
console.log(import_chalk.default.dim(` }`));
|
|
1045
|
-
}
|
|
1046
|
-
} catch (e) {
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
async function handleVisualize(directory, options) {
|
|
966
|
+
// src/commands/visualize.ts
|
|
967
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
968
|
+
var import_fs3 = require("fs");
|
|
969
|
+
var import_path6 = require("path");
|
|
970
|
+
var import_child_process = require("child_process");
|
|
971
|
+
var import_core5 = require("@aiready/core");
|
|
972
|
+
var import_core6 = require("@aiready/core");
|
|
973
|
+
async function visualizeAction(directory, options) {
|
|
1050
974
|
try {
|
|
1051
|
-
const dirPath = (0,
|
|
1052
|
-
let reportPath = options.report ? (0,
|
|
1053
|
-
if (!reportPath || !(0,
|
|
975
|
+
const dirPath = (0, import_path6.resolve)(process.cwd(), directory || ".");
|
|
976
|
+
let reportPath = options.report ? (0, import_path6.resolve)(dirPath, options.report) : null;
|
|
977
|
+
if (!reportPath || !(0, import_fs3.existsSync)(reportPath)) {
|
|
1054
978
|
const latestScan = findLatestScanReport(dirPath);
|
|
1055
979
|
if (latestScan) {
|
|
1056
980
|
reportPath = latestScan;
|
|
1057
|
-
console.log(
|
|
981
|
+
console.log(import_chalk6.default.dim(`Found latest report: ${latestScan.split("/").pop()}`));
|
|
1058
982
|
} else {
|
|
1059
|
-
console.error(
|
|
1060
|
-
console.log(
|
|
983
|
+
console.error(import_chalk6.default.red("\u274C No AI readiness report found"));
|
|
984
|
+
console.log(import_chalk6.default.dim(`
|
|
1061
985
|
Generate a report with:
|
|
1062
986
|
aiready scan --output json
|
|
1063
987
|
|
|
@@ -1066,13 +990,13 @@ Or specify a custom report:
|
|
|
1066
990
|
return;
|
|
1067
991
|
}
|
|
1068
992
|
}
|
|
1069
|
-
const raw = (0,
|
|
993
|
+
const raw = (0, import_fs3.readFileSync)(reportPath, "utf8");
|
|
1070
994
|
const report = JSON.parse(raw);
|
|
1071
|
-
const configPath = (0,
|
|
995
|
+
const configPath = (0, import_path6.resolve)(dirPath, "aiready.json");
|
|
1072
996
|
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
1073
|
-
if ((0,
|
|
997
|
+
if ((0, import_fs3.existsSync)(configPath)) {
|
|
1074
998
|
try {
|
|
1075
|
-
const rawConfig = JSON.parse((0,
|
|
999
|
+
const rawConfig = JSON.parse((0, import_fs3.readFileSync)(configPath, "utf8"));
|
|
1076
1000
|
if (rawConfig.visualizer?.graph) {
|
|
1077
1001
|
graphConfig = {
|
|
1078
1002
|
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
@@ -1089,27 +1013,26 @@ Or specify a custom report:
|
|
|
1089
1013
|
const graph = GraphBuilder.buildFromReport(report, dirPath);
|
|
1090
1014
|
if (options.dev) {
|
|
1091
1015
|
try {
|
|
1092
|
-
const
|
|
1093
|
-
const monorepoWebDir = (0, import_path2.resolve)(dirPath, "packages/visualizer");
|
|
1016
|
+
const monorepoWebDir = (0, import_path6.resolve)(dirPath, "packages/visualizer");
|
|
1094
1017
|
let webDir = "";
|
|
1095
1018
|
let visualizerAvailable = false;
|
|
1096
|
-
if ((0,
|
|
1019
|
+
if ((0, import_fs3.existsSync)(monorepoWebDir)) {
|
|
1097
1020
|
webDir = monorepoWebDir;
|
|
1098
1021
|
visualizerAvailable = true;
|
|
1099
1022
|
} else {
|
|
1100
1023
|
const nodemodulesLocations = [
|
|
1101
|
-
(0,
|
|
1102
|
-
(0,
|
|
1024
|
+
(0, import_path6.resolve)(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
1025
|
+
(0, import_path6.resolve)(process.cwd(), "node_modules", "@aiready", "visualizer")
|
|
1103
1026
|
];
|
|
1104
1027
|
let currentDir = dirPath;
|
|
1105
1028
|
while (currentDir !== "/" && currentDir !== ".") {
|
|
1106
|
-
nodemodulesLocations.push((0,
|
|
1107
|
-
const parent = (0,
|
|
1029
|
+
nodemodulesLocations.push((0, import_path6.resolve)(currentDir, "node_modules", "@aiready", "visualizer"));
|
|
1030
|
+
const parent = (0, import_path6.resolve)(currentDir, "..");
|
|
1108
1031
|
if (parent === currentDir) break;
|
|
1109
1032
|
currentDir = parent;
|
|
1110
1033
|
}
|
|
1111
1034
|
for (const location of nodemodulesLocations) {
|
|
1112
|
-
if ((0,
|
|
1035
|
+
if ((0, import_fs3.existsSync)(location) && (0, import_fs3.existsSync)((0, import_path6.resolve)(location, "package.json"))) {
|
|
1113
1036
|
webDir = location;
|
|
1114
1037
|
visualizerAvailable = true;
|
|
1115
1038
|
break;
|
|
@@ -1118,7 +1041,7 @@ Or specify a custom report:
|
|
|
1118
1041
|
if (!visualizerAvailable) {
|
|
1119
1042
|
try {
|
|
1120
1043
|
const vizPkgPath = require.resolve("@aiready/visualizer/package.json");
|
|
1121
|
-
webDir = (0,
|
|
1044
|
+
webDir = (0, import_path6.resolve)(vizPkgPath, "..");
|
|
1122
1045
|
visualizerAvailable = true;
|
|
1123
1046
|
} catch (e) {
|
|
1124
1047
|
}
|
|
@@ -1126,17 +1049,17 @@ Or specify a custom report:
|
|
|
1126
1049
|
}
|
|
1127
1050
|
const spawnCwd = webDir || process.cwd();
|
|
1128
1051
|
const nodeBinCandidate = process.execPath;
|
|
1129
|
-
const nodeBin = (0,
|
|
1052
|
+
const nodeBin = (0, import_fs3.existsSync)(nodeBinCandidate) ? nodeBinCandidate : "node";
|
|
1130
1053
|
if (!visualizerAvailable) {
|
|
1131
|
-
console.error(
|
|
1132
|
-
console.log(
|
|
1054
|
+
console.error(import_chalk6.default.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
|
|
1055
|
+
console.log(import_chalk6.default.dim("Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer"));
|
|
1133
1056
|
return;
|
|
1134
1057
|
}
|
|
1135
1058
|
const { watch } = await import("fs");
|
|
1136
1059
|
const copyReportToViz = () => {
|
|
1137
1060
|
try {
|
|
1138
|
-
const destPath = (0,
|
|
1139
|
-
(0,
|
|
1061
|
+
const destPath = (0, import_path6.resolve)(spawnCwd, "web", "report-data.json");
|
|
1062
|
+
(0, import_fs3.copyFileSync)(reportPath, destPath);
|
|
1140
1063
|
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1141
1064
|
} catch (e) {
|
|
1142
1065
|
console.error("Failed to sync report:", e);
|
|
@@ -1153,7 +1076,7 @@ Or specify a custom report:
|
|
|
1153
1076
|
AIREADY_REPORT_PATH: reportPath,
|
|
1154
1077
|
AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
|
|
1155
1078
|
};
|
|
1156
|
-
const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
|
|
1079
|
+
const vite = (0, import_child_process.spawn)("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
|
|
1157
1080
|
const onExit = () => {
|
|
1158
1081
|
try {
|
|
1159
1082
|
reportWatcher.close();
|
|
@@ -1173,21 +1096,19 @@ Or specify a custom report:
|
|
|
1173
1096
|
}
|
|
1174
1097
|
}
|
|
1175
1098
|
console.log("Generating HTML...");
|
|
1176
|
-
const html = generateHTML(graph);
|
|
1177
|
-
const outPath = (0,
|
|
1178
|
-
(0,
|
|
1099
|
+
const html = (0, import_core6.generateHTML)(graph);
|
|
1100
|
+
const outPath = (0, import_path6.resolve)(dirPath, options.output || "packages/visualizer/visualization.html");
|
|
1101
|
+
(0, import_fs3.writeFileSync)(outPath, html, "utf8");
|
|
1179
1102
|
console.log("Visualization written to:", outPath);
|
|
1180
1103
|
if (options.open) {
|
|
1181
|
-
const { exec } = await import("child_process");
|
|
1182
1104
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1183
|
-
|
|
1105
|
+
(0, import_child_process.spawn)(opener, [`"${outPath}"`], { shell: true });
|
|
1184
1106
|
}
|
|
1185
1107
|
if (options.serve) {
|
|
1186
1108
|
try {
|
|
1187
|
-
const port =
|
|
1109
|
+
const port = typeof options.serve === "number" ? options.serve : 5173;
|
|
1188
1110
|
const http = await import("http");
|
|
1189
1111
|
const fsp = await import("fs/promises");
|
|
1190
|
-
const { exec } = await import("child_process");
|
|
1191
1112
|
const server = http.createServer(async (req, res) => {
|
|
1192
1113
|
try {
|
|
1193
1114
|
const urlPath = req.url || "/";
|
|
@@ -1208,7 +1129,7 @@ Or specify a custom report:
|
|
|
1208
1129
|
const addr = `http://localhost:${port}/`;
|
|
1209
1130
|
console.log(`Local visualization server running at ${addr}`);
|
|
1210
1131
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1211
|
-
|
|
1132
|
+
(0, import_child_process.spawn)(opener, [`"${addr}"`], { shell: true });
|
|
1212
1133
|
});
|
|
1213
1134
|
process.on("SIGINT", () => {
|
|
1214
1135
|
server.close();
|
|
@@ -1219,20 +1140,10 @@ Or specify a custom report:
|
|
|
1219
1140
|
}
|
|
1220
1141
|
}
|
|
1221
1142
|
} catch (err) {
|
|
1222
|
-
(0,
|
|
1143
|
+
(0, import_core5.handleCLIError)(err, "Visualization");
|
|
1223
1144
|
}
|
|
1224
1145
|
}
|
|
1225
|
-
|
|
1226
|
-
EXAMPLES:
|
|
1227
|
-
$ aiready visualise . # Auto-detects latest report
|
|
1228
|
-
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1229
|
-
$ aiready visualise . --report report.json --dev
|
|
1230
|
-
$ aiready visualise . --report report.json --serve 8080
|
|
1231
|
-
|
|
1232
|
-
NOTES:
|
|
1233
|
-
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
1234
|
-
`).action(async (directory, options) => await handleVisualize(directory, options));
|
|
1235
|
-
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", false).addHelpText("after", `
|
|
1146
|
+
var visualizeHelpText = `
|
|
1236
1147
|
EXAMPLES:
|
|
1237
1148
|
$ aiready visualize . # Auto-detects latest report
|
|
1238
1149
|
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
@@ -1251,5 +1162,73 @@ NOTES:
|
|
|
1251
1162
|
reduce clutter and improve interactivity on large graphs.
|
|
1252
1163
|
- For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
|
|
1253
1164
|
allow the browser a moment to stabilize after load.
|
|
1254
|
-
|
|
1165
|
+
`;
|
|
1166
|
+
var visualiseHelpText = `
|
|
1167
|
+
EXAMPLES:
|
|
1168
|
+
$ aiready visualise . # Auto-detects latest report
|
|
1169
|
+
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1170
|
+
$ aiready visualise . --report report.json --dev
|
|
1171
|
+
$ aiready visualise . --report report.json --serve 8080
|
|
1172
|
+
|
|
1173
|
+
NOTES:
|
|
1174
|
+
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
1175
|
+
`;
|
|
1176
|
+
|
|
1177
|
+
// src/cli.ts
|
|
1178
|
+
var packageJson = JSON.parse((0, import_fs4.readFileSync)((0, import_path7.join)(__dirname, "../package.json"), "utf8"));
|
|
1179
|
+
var program = new import_commander.Command();
|
|
1180
|
+
program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
|
|
1181
|
+
AI READINESS SCORING:
|
|
1182
|
+
Get a 0-100 score indicating how AI-ready your codebase is.
|
|
1183
|
+
Use --score flag with any analysis command for detailed breakdown.
|
|
1184
|
+
|
|
1185
|
+
EXAMPLES:
|
|
1186
|
+
$ aiready scan # Quick analysis of current directory
|
|
1187
|
+
$ aiready scan --score # Get AI Readiness Score (0-100)
|
|
1188
|
+
$ aiready scan --tools patterns # Run only pattern detection
|
|
1189
|
+
$ aiready patterns --similarity 0.6 # Custom similarity threshold
|
|
1190
|
+
$ aiready scan --output json --output-file results.json
|
|
1191
|
+
|
|
1192
|
+
GETTING STARTED:
|
|
1193
|
+
1. Run 'aiready scan' to analyze your codebase
|
|
1194
|
+
2. Use 'aiready scan --score' for AI readiness assessment
|
|
1195
|
+
3. Create aiready.json for persistent configuration
|
|
1196
|
+
4. Set up CI/CD with '--threshold' for quality gates
|
|
1197
|
+
|
|
1198
|
+
CONFIGURATION:
|
|
1199
|
+
Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
|
|
1200
|
+
CLI options override config file settings
|
|
1201
|
+
|
|
1202
|
+
Example aiready.json:
|
|
1203
|
+
{
|
|
1204
|
+
"scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
|
|
1205
|
+
"tools": {
|
|
1206
|
+
"pattern-detect": { "minSimilarity": 0.5 },
|
|
1207
|
+
"context-analyzer": { "maxContextBudget": 15000 }
|
|
1208
|
+
},
|
|
1209
|
+
"output": { "format": "json", "directory": ".aiready" }
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
VERSION: ${packageJson.version}
|
|
1213
|
+
DOCUMENTATION: https://aiready.dev/docs/cli
|
|
1214
|
+
GITHUB: https://github.com/caopengau/aiready-cli
|
|
1215
|
+
LANDING: https://github.com/caopengau/aiready-landing`);
|
|
1216
|
+
program.command("scan").description("Run comprehensive AI-readiness analysis (patterns + context + consistency)").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").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", "json").option("--output-file <path>", "Output file path (for json)").option("--no-score", "Disable calculating AI Readiness Score (enabled by default)").option("--weights <weights>", "Custom scoring weights (patterns:40,context:35,consistency:25)").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").option("--ci", "CI mode: GitHub Actions annotations, no colors, fail on threshold").option("--fail-on <level>", "Fail on issues: critical, major, any", "critical").addHelpText("after", scanHelpText).action(async (directory, options) => {
|
|
1217
|
+
await scanAction(directory, options);
|
|
1218
|
+
});
|
|
1219
|
+
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("--max-candidates <number>", "Maximum candidates per block (performance tuning)").option("--min-shared-tokens <number>", "Minimum shared tokens for candidates (performance tuning)").option("--full-scan", "Disable smart defaults for comprehensive analysis (slower)").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("--score", "Calculate and display AI Readiness Score for patterns (0-100)").addHelpText("after", patternsHelpText).action(async (directory, options) => {
|
|
1220
|
+
await patternsAction(directory, options);
|
|
1221
|
+
});
|
|
1222
|
+
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("--max-context <number>", "Maximum acceptable context budget (tokens)", "10000").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("--score", "Calculate and display AI Readiness Score for context (0-100)").action(async (directory, options) => {
|
|
1223
|
+
await contextAction(directory, options);
|
|
1224
|
+
});
|
|
1225
|
+
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("--min-severity <level>", "Minimum severity: info|minor|major|critical", "info").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, markdown", "console").option("--output-file <path>", "Output file path (for json/markdown)").option("--score", "Calculate and display AI Readiness Score for consistency (0-100)").action(async (directory, options) => {
|
|
1226
|
+
await consistencyAction(directory, options);
|
|
1227
|
+
});
|
|
1228
|
+
program.command("visualise").description("Alias for visualize (British spelling)").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", true).addHelpText("after", visualiseHelpText).action(async (directory, options) => {
|
|
1229
|
+
await visualizeAction(directory, options);
|
|
1230
|
+
});
|
|
1231
|
+
program.command("visualize").description("Generate interactive visualization from an AIReady report").argument("[directory]", "Directory to analyze", ".").option("--report <path>", "Report path (auto-detects latest .aiready/aiready-report-*.json if not provided)").option("-o, --output <path>", "Output HTML path (relative to directory)", "packages/visualizer/visualization.html").option("--open", "Open generated HTML in default browser").option("--serve [port]", "Start a local static server to serve the visualization (optional port number)", false).option("--dev", "Start Vite dev server (live reload) for interactive development", false).addHelpText("after", visualizeHelpText).action(async (directory, options) => {
|
|
1232
|
+
await visualizeAction(directory, options);
|
|
1233
|
+
});
|
|
1255
1234
|
program.parse();
|