@aiready/cli 0.9.27 → 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/dist/cli.js +423 -377
- package/dist/cli.mjs +442 -374
- package/package.json +3 -3
- package/src/cli.ts +49 -1284
- 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,71 +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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
135
190
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
4. Set up CI/CD with '--threshold' for quality gates
|
|
191
|
+
`;
|
|
192
|
+
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
193
|
+
`;
|
|
194
|
+
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
141
195
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
CLI options override config file settings
|
|
196
|
+
`;
|
|
197
|
+
markdown += `## Summary
|
|
145
198
|
|
|
146
|
-
|
|
147
|
-
{
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
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}
|
|
155
207
|
|
|
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)").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", `
|
|
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 --ci --threshold 70 # GitHub Actions gatekeeper
|
|
166
|
-
$ aiready scan --ci --fail-on major # Fail on major+ issues
|
|
167
|
-
$ aiready scan --output json --output-file report.json
|
|
208
|
+
`;
|
|
209
|
+
if (report.recommendations.length > 0) {
|
|
210
|
+
markdown += `## Recommendations
|
|
168
211
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
212
|
+
`;
|
|
213
|
+
report.recommendations.forEach((rec, i) => {
|
|
214
|
+
markdown += `${i + 1}. ${rec}
|
|
215
|
+
`;
|
|
216
|
+
});
|
|
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
|
+
}
|
|
174
226
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`).action(async (directory, options) => {
|
|
179
|
-
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"));
|
|
180
230
|
const startTime = Date.now();
|
|
181
231
|
const resolvedDir = (0, import_path2.resolve)(process.cwd(), directory || ".");
|
|
182
232
|
try {
|
|
@@ -200,19 +250,13 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
200
250
|
const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
201
251
|
finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
|
|
202
252
|
}
|
|
203
|
-
console.log(
|
|
204
|
-
console.log(
|
|
205
|
-
console.log(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
|
|
211
|
-
};
|
|
212
|
-
console.log(import_chalk.default.white("\nGeneral settings:"));
|
|
213
|
-
if (finalOptions.rootDir) console.log(` rootDir: ${import_chalk.default.bold(String(finalOptions.rootDir))}`);
|
|
214
|
-
if (finalOptions.include) console.log(` include: ${import_chalk.default.bold(truncate(finalOptions.include, 6))}`);
|
|
215
|
-
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))}`);
|
|
216
260
|
if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
|
|
217
261
|
const patternDetectConfig = finalOptions["pattern-detect"] || {
|
|
218
262
|
minSimilarity: finalOptions.minSimilarity,
|
|
@@ -225,16 +269,16 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
225
269
|
severity: finalOptions.severity,
|
|
226
270
|
includeTests: finalOptions.includeTests
|
|
227
271
|
};
|
|
228
|
-
console.log(
|
|
229
|
-
console.log(` minSimilarity: ${
|
|
230
|
-
console.log(` minLines: ${
|
|
231
|
-
if (patternDetectConfig.approx !== void 0) console.log(` approx: ${
|
|
232
|
-
if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${
|
|
233
|
-
if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${
|
|
234
|
-
if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${
|
|
235
|
-
if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${
|
|
236
|
-
if (patternDetectConfig.severity !== void 0) console.log(` severity: ${
|
|
237
|
-
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))}`);
|
|
238
282
|
}
|
|
239
283
|
if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
|
|
240
284
|
const ca = finalOptions["context-analyzer"] || {
|
|
@@ -244,32 +288,32 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
244
288
|
maxFragmentation: finalOptions.maxFragmentation,
|
|
245
289
|
includeNodeModules: finalOptions.includeNodeModules
|
|
246
290
|
};
|
|
247
|
-
console.log(
|
|
248
|
-
console.log(` maxDepth: ${
|
|
249
|
-
console.log(` maxContextBudget: ${
|
|
250
|
-
if (ca.minCohesion !== void 0) console.log(` minCohesion: ${
|
|
251
|
-
if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${
|
|
252
|
-
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))}`);
|
|
253
297
|
}
|
|
254
298
|
if (finalOptions.consistency) {
|
|
255
299
|
const c = finalOptions.consistency;
|
|
256
|
-
console.log(
|
|
257
|
-
console.log(` checkNaming: ${
|
|
258
|
-
console.log(` checkPatterns: ${
|
|
259
|
-
console.log(` checkArchitecture: ${
|
|
260
|
-
if (c.minSeverity) console.log(` minSeverity: ${
|
|
261
|
-
if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${
|
|
262
|
-
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))}`);
|
|
263
307
|
}
|
|
264
|
-
console.log(
|
|
308
|
+
console.log(import_chalk2.default.white("\nStarting analysis..."));
|
|
265
309
|
const progressCallback = (event) => {
|
|
266
|
-
console.log(
|
|
310
|
+
console.log(import_chalk2.default.cyan(`
|
|
267
311
|
--- ${event.tool.toUpperCase()} RESULTS ---`));
|
|
268
312
|
try {
|
|
269
313
|
if (event.tool === "patterns") {
|
|
270
314
|
const pr = event.data;
|
|
271
|
-
console.log(` Duplicate patterns: ${
|
|
272
|
-
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))}`);
|
|
273
317
|
if (pr.duplicates && pr.duplicates.length > 0) {
|
|
274
318
|
pr.duplicates.slice(0, 5).forEach((d, i) => {
|
|
275
319
|
console.log(` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`);
|
|
@@ -283,10 +327,10 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
283
327
|
});
|
|
284
328
|
}
|
|
285
329
|
if (pr.groups && pr.groups.length >= 0) {
|
|
286
|
-
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`);
|
|
287
331
|
}
|
|
288
332
|
if (pr.clusters && pr.clusters.length >= 0) {
|
|
289
|
-
console.log(` \u2705 Created ${
|
|
333
|
+
console.log(` \u2705 Created ${import_chalk2.default.bold(String(pr.clusters.length))} refactor clusters`);
|
|
290
334
|
pr.clusters.slice(0, 3).forEach((cl, idx) => {
|
|
291
335
|
const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
|
|
292
336
|
console.log(` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`);
|
|
@@ -294,14 +338,14 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
294
338
|
}
|
|
295
339
|
} else if (event.tool === "context") {
|
|
296
340
|
const cr = event.data;
|
|
297
|
-
console.log(` Context issues found: ${
|
|
341
|
+
console.log(` Context issues found: ${import_chalk2.default.bold(String(cr.length || 0))}`);
|
|
298
342
|
cr.slice(0, 5).forEach((c, i) => {
|
|
299
343
|
const msg = c.message ? ` - ${c.message}` : "";
|
|
300
344
|
console.log(` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`);
|
|
301
345
|
});
|
|
302
346
|
} else if (event.tool === "consistency") {
|
|
303
347
|
const rep = event.data;
|
|
304
|
-
console.log(` Consistency totalIssues: ${
|
|
348
|
+
console.log(` Consistency totalIssues: ${import_chalk2.default.bold(String(rep.summary?.totalIssues || 0))}`);
|
|
305
349
|
if (rep.results && rep.results.length > 0) {
|
|
306
350
|
const fileMap = /* @__PURE__ */ new Map();
|
|
307
351
|
rep.results.forEach((r) => {
|
|
@@ -325,7 +369,7 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
325
369
|
});
|
|
326
370
|
const remaining = files.length - topFiles.length;
|
|
327
371
|
if (remaining > 0) {
|
|
328
|
-
console.log(
|
|
372
|
+
console.log(import_chalk2.default.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
|
|
329
373
|
}
|
|
330
374
|
}
|
|
331
375
|
}
|
|
@@ -333,15 +377,15 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
333
377
|
}
|
|
334
378
|
};
|
|
335
379
|
const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
|
|
336
|
-
console.log(
|
|
337
|
-
console.log(
|
|
338
|
-
console.log(
|
|
339
|
-
console.log(` Total issues (all tools): ${
|
|
340
|
-
if (results.duplicates) console.log(` Duplicate patterns found: ${
|
|
341
|
-
if (results.patterns) console.log(` Pattern files with issues: ${
|
|
342
|
-
if (results.context) console.log(` Context issues: ${
|
|
343
|
-
if (results.consistency) console.log(` Consistency issues: ${
|
|
344
|
-
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"));
|
|
345
389
|
const elapsedTime = (0, import_core.getElapsedTime)(startTime);
|
|
346
390
|
let scoringResult;
|
|
347
391
|
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
@@ -376,10 +420,10 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
376
420
|
const cliWeights = (0, import_core.parseWeightString)(options.weights);
|
|
377
421
|
if (toolScores.size > 0) {
|
|
378
422
|
scoringResult = (0, import_core.calculateOverallScore)(toolScores, finalOptions, cliWeights.size ? cliWeights : void 0);
|
|
379
|
-
console.log(
|
|
423
|
+
console.log(import_chalk2.default.bold("\n\u{1F4CA} AI Readiness Overall Score"));
|
|
380
424
|
console.log(` ${(0, import_core.formatScore)(scoringResult)}`);
|
|
381
425
|
if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
|
|
382
|
-
console.log(
|
|
426
|
+
console.log(import_chalk2.default.bold("\nTool breakdown:"));
|
|
383
427
|
scoringResult.breakdown.forEach((tool) => {
|
|
384
428
|
const rating = (0, import_core.getRating)(tool.score);
|
|
385
429
|
const rd = (0, import_core.getRatingDisplay)(rating);
|
|
@@ -387,7 +431,7 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
387
431
|
});
|
|
388
432
|
console.log();
|
|
389
433
|
if (finalOptions.scoring?.showBreakdown) {
|
|
390
|
-
console.log(
|
|
434
|
+
console.log(import_chalk2.default.bold("Detailed tool breakdown:"));
|
|
391
435
|
scoringResult.breakdown.forEach((tool) => {
|
|
392
436
|
console.log((0, import_core.formatToolScore)(tool));
|
|
393
437
|
});
|
|
@@ -476,34 +520,53 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
476
520
|
}
|
|
477
521
|
}
|
|
478
522
|
if (shouldFail) {
|
|
479
|
-
console.log(
|
|
480
|
-
console.log(
|
|
481
|
-
console.log(
|
|
482
|
-
console.log(
|
|
483
|
-
console.log(
|
|
484
|
-
console.log(
|
|
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"));
|
|
485
529
|
process.exit(1);
|
|
486
530
|
} else {
|
|
487
|
-
console.log(
|
|
531
|
+
console.log(import_chalk2.default.green("\n\u2705 PR PASSED: AI Readiness Check"));
|
|
488
532
|
if (threshold) {
|
|
489
|
-
console.log(
|
|
533
|
+
console.log(import_chalk2.default.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
|
|
490
534
|
}
|
|
491
|
-
console.log(
|
|
535
|
+
console.log(import_chalk2.default.dim("\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"));
|
|
492
536
|
}
|
|
493
537
|
}
|
|
494
538
|
} catch (error) {
|
|
495
539
|
(0, import_core.handleCLIError)(error, "Analysis");
|
|
496
540
|
}
|
|
497
|
-
}
|
|
498
|
-
|
|
541
|
+
}
|
|
542
|
+
var scanHelpText = `
|
|
499
543
|
EXAMPLES:
|
|
500
|
-
$ aiready
|
|
501
|
-
$ aiready
|
|
502
|
-
$ aiready
|
|
503
|
-
|
|
504
|
-
|
|
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"));
|
|
505
568
|
const startTime = Date.now();
|
|
506
|
-
const resolvedDir = (0,
|
|
569
|
+
const resolvedDir = (0, import_path3.resolve)(process.cwd(), directory || ".");
|
|
507
570
|
try {
|
|
508
571
|
const useSmartDefaults = !options.fullScan;
|
|
509
572
|
const defaults = {
|
|
@@ -532,10 +595,10 @@ EXAMPLES:
|
|
|
532
595
|
if (options.minSharedTokens) {
|
|
533
596
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
534
597
|
}
|
|
535
|
-
const finalOptions = await (0,
|
|
598
|
+
const finalOptions = await (0, import_core2.loadMergedConfig)(resolvedDir, defaults, cliOptions);
|
|
536
599
|
const { analyzePatterns: analyzePatterns2, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
537
600
|
const { results, duplicates } = await analyzePatterns2(finalOptions);
|
|
538
|
-
const elapsedTime = (0,
|
|
601
|
+
const elapsedTime = (0, import_core2.getElapsedTime)(startTime);
|
|
539
602
|
const summary = generateSummary(results);
|
|
540
603
|
let patternScore;
|
|
541
604
|
if (options.score) {
|
|
@@ -549,66 +612,77 @@ EXAMPLES:
|
|
|
549
612
|
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
550
613
|
...patternScore && { scoring: patternScore }
|
|
551
614
|
};
|
|
552
|
-
const outputPath = (0,
|
|
615
|
+
const outputPath = (0, import_core2.resolveOutputPath)(
|
|
553
616
|
userOutputFile,
|
|
554
617
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
555
618
|
resolvedDir
|
|
556
619
|
);
|
|
557
|
-
(0,
|
|
620
|
+
(0, import_core2.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
558
621
|
} else {
|
|
559
622
|
const terminalWidth = process.stdout.columns || 80;
|
|
560
623
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
561
624
|
const divider = "\u2501".repeat(dividerWidth);
|
|
562
|
-
console.log(
|
|
563
|
-
console.log(
|
|
564
|
-
console.log(
|
|
565
|
-
console.log(
|
|
566
|
-
console.log(
|
|
567
|
-
console.log(
|
|
568
|
-
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")}`));
|
|
569
632
|
const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
570
633
|
if (sortedTypes.length > 0) {
|
|
571
|
-
console.log(
|
|
572
|
-
console.log(
|
|
573
|
-
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");
|
|
574
637
|
sortedTypes.forEach(([type, count]) => {
|
|
575
|
-
console.log(` ${
|
|
638
|
+
console.log(` ${import_chalk3.default.white(type.padEnd(15))} ${import_chalk3.default.bold(count)}`);
|
|
576
639
|
});
|
|
577
640
|
}
|
|
578
641
|
if (summary.totalPatterns > 0 && duplicates.length > 0) {
|
|
579
|
-
console.log(
|
|
580
|
-
console.log(
|
|
581
|
-
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");
|
|
582
645
|
const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
|
|
583
646
|
topDuplicates.forEach((dup) => {
|
|
584
647
|
const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
|
|
585
648
|
const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
|
|
586
649
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
587
650
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
588
|
-
console.log(`${severityIcon} ${severity}: ${
|
|
589
|
-
console.log(` Similarity: ${
|
|
590
|
-
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)}
|
|
591
654
|
`);
|
|
592
655
|
});
|
|
593
656
|
} else {
|
|
594
|
-
console.log(
|
|
657
|
+
console.log(import_chalk3.default.green("\n\u2728 Great! No duplicate patterns detected.\n"));
|
|
595
658
|
}
|
|
596
659
|
if (patternScore) {
|
|
597
|
-
console.log(
|
|
598
|
-
console.log(
|
|
599
|
-
console.log(
|
|
600
|
-
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));
|
|
601
664
|
console.log();
|
|
602
665
|
}
|
|
603
666
|
}
|
|
604
667
|
} catch (error) {
|
|
605
|
-
(0,
|
|
668
|
+
(0, import_core2.handleCLIError)(error, "Pattern analysis");
|
|
606
669
|
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
|
|
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"));
|
|
610
684
|
const startTime = Date.now();
|
|
611
|
-
const resolvedDir = (0,
|
|
685
|
+
const resolvedDir = (0, import_path4.resolve)(process.cwd(), directory || ".");
|
|
612
686
|
try {
|
|
613
687
|
const defaults = {
|
|
614
688
|
maxDepth: 5,
|
|
@@ -620,7 +694,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
620
694
|
file: void 0
|
|
621
695
|
}
|
|
622
696
|
};
|
|
623
|
-
let baseOptions = await (0,
|
|
697
|
+
let baseOptions = await (0, import_core3.loadMergedConfig)(resolvedDir, defaults, {
|
|
624
698
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
625
699
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
626
700
|
include: options.include?.split(","),
|
|
@@ -639,7 +713,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
639
713
|
console.log("");
|
|
640
714
|
const { analyzeContext: analyzeContext2, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
|
|
641
715
|
const results = await analyzeContext2(finalOptions);
|
|
642
|
-
const elapsedTime = (0,
|
|
716
|
+
const elapsedTime = (0, import_core3.getElapsedTime)(startTime);
|
|
643
717
|
const summary = generateSummary(results);
|
|
644
718
|
let contextScore;
|
|
645
719
|
if (options.score) {
|
|
@@ -653,100 +727,106 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
653
727
|
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
654
728
|
...contextScore && { scoring: contextScore }
|
|
655
729
|
};
|
|
656
|
-
const outputPath = (0,
|
|
730
|
+
const outputPath = (0, import_core3.resolveOutputPath)(
|
|
657
731
|
userOutputFile,
|
|
658
732
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
659
733
|
resolvedDir
|
|
660
734
|
);
|
|
661
|
-
(0,
|
|
735
|
+
(0, import_core3.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
662
736
|
} else {
|
|
663
737
|
const terminalWidth = process.stdout.columns || 80;
|
|
664
738
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
665
739
|
const divider = "\u2501".repeat(dividerWidth);
|
|
666
|
-
console.log(
|
|
667
|
-
console.log(
|
|
668
|
-
console.log(
|
|
669
|
-
console.log(
|
|
670
|
-
console.log(
|
|
671
|
-
console.log(
|
|
672
|
-
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")}
|
|
673
747
|
`));
|
|
674
748
|
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
675
749
|
if (totalIssues > 0) {
|
|
676
|
-
console.log(
|
|
750
|
+
console.log(import_chalk4.default.bold("\u26A0\uFE0F Issues Found:\n"));
|
|
677
751
|
if (summary.criticalIssues > 0) {
|
|
678
|
-
console.log(
|
|
752
|
+
console.log(import_chalk4.default.red(` \u{1F534} Critical: ${import_chalk4.default.bold(summary.criticalIssues)}`));
|
|
679
753
|
}
|
|
680
754
|
if (summary.majorIssues > 0) {
|
|
681
|
-
console.log(
|
|
755
|
+
console.log(import_chalk4.default.yellow(` \u{1F7E1} Major: ${import_chalk4.default.bold(summary.majorIssues)}`));
|
|
682
756
|
}
|
|
683
757
|
if (summary.minorIssues > 0) {
|
|
684
|
-
console.log(
|
|
758
|
+
console.log(import_chalk4.default.blue(` \u{1F535} Minor: ${import_chalk4.default.bold(summary.minorIssues)}`));
|
|
685
759
|
}
|
|
686
|
-
console.log(
|
|
687
|
-
\u{1F4A1} Potential savings: ${
|
|
760
|
+
console.log(import_chalk4.default.green(`
|
|
761
|
+
\u{1F4A1} Potential savings: ${import_chalk4.default.bold(summary.totalPotentialSavings.toLocaleString())} tokens
|
|
688
762
|
`));
|
|
689
763
|
} else {
|
|
690
|
-
console.log(
|
|
764
|
+
console.log(import_chalk4.default.green("\u2705 No significant issues found!\n"));
|
|
691
765
|
}
|
|
692
766
|
if (summary.deepFiles.length > 0) {
|
|
693
|
-
console.log(
|
|
694
|
-
console.log(
|
|
695
|
-
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}
|
|
696
770
|
`));
|
|
697
771
|
summary.deepFiles.slice(0, 10).forEach((item) => {
|
|
698
772
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
699
|
-
console.log(` ${
|
|
773
|
+
console.log(` ${import_chalk4.default.cyan("\u2192")} ${import_chalk4.default.white(fileName)} ${import_chalk4.default.dim(`(depth: ${item.depth})`)}`);
|
|
700
774
|
});
|
|
701
775
|
console.log();
|
|
702
776
|
}
|
|
703
777
|
if (summary.fragmentedModules.length > 0) {
|
|
704
|
-
console.log(
|
|
705
|
-
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)}%
|
|
706
780
|
`));
|
|
707
781
|
summary.fragmentedModules.slice(0, 10).forEach((module2) => {
|
|
708
|
-
console.log(` ${
|
|
709
|
-
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)}%`));
|
|
710
784
|
});
|
|
711
785
|
console.log();
|
|
712
786
|
}
|
|
713
787
|
if (summary.lowCohesionFiles.length > 0) {
|
|
714
|
-
console.log(
|
|
715
|
-
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)}%
|
|
716
790
|
`));
|
|
717
791
|
summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
|
|
718
792
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
719
793
|
const scorePercent = (item.score * 100).toFixed(0);
|
|
720
|
-
const color = item.score < 0.4 ?
|
|
721
|
-
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)`)}`);
|
|
722
796
|
});
|
|
723
797
|
console.log();
|
|
724
798
|
}
|
|
725
799
|
if (summary.topExpensiveFiles.length > 0) {
|
|
726
|
-
console.log(
|
|
800
|
+
console.log(import_chalk4.default.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
|
|
727
801
|
summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
|
|
728
802
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
729
|
-
const severityColor = item.severity === "critical" ?
|
|
730
|
-
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)`)}`);
|
|
731
805
|
});
|
|
732
806
|
console.log();
|
|
733
807
|
}
|
|
734
808
|
if (contextScore) {
|
|
735
|
-
console.log(
|
|
736
|
-
console.log(
|
|
737
|
-
console.log(
|
|
738
|
-
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));
|
|
739
813
|
console.log();
|
|
740
814
|
}
|
|
741
815
|
}
|
|
742
816
|
} catch (error) {
|
|
743
|
-
(0,
|
|
817
|
+
(0, import_core3.handleCLIError)(error, "Context analysis");
|
|
744
818
|
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
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"));
|
|
748
828
|
const startTime = Date.now();
|
|
749
|
-
const resolvedDir = (0,
|
|
829
|
+
const resolvedDir = (0, import_path5.resolve)(process.cwd(), directory || ".");
|
|
750
830
|
try {
|
|
751
831
|
const defaults = {
|
|
752
832
|
checkNaming: true,
|
|
@@ -759,7 +839,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
759
839
|
file: void 0
|
|
760
840
|
}
|
|
761
841
|
};
|
|
762
|
-
const finalOptions = await (0,
|
|
842
|
+
const finalOptions = await (0, import_core4.loadMergedConfig)(resolvedDir, defaults, {
|
|
763
843
|
checkNaming: options.naming !== false,
|
|
764
844
|
checkPatterns: options.patterns !== false,
|
|
765
845
|
minSeverity: options.minSeverity,
|
|
@@ -768,7 +848,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
768
848
|
});
|
|
769
849
|
const { analyzeConsistency: analyzeConsistency2, calculateConsistencyScore } = await import("@aiready/consistency");
|
|
770
850
|
const report = await analyzeConsistency2(finalOptions);
|
|
771
|
-
const elapsedTime = (0,
|
|
851
|
+
const elapsedTime = (0, import_core4.getElapsedTime)(startTime);
|
|
772
852
|
let consistencyScore;
|
|
773
853
|
if (options.score) {
|
|
774
854
|
const issues = report.results?.flatMap((r) => r.issues) || [];
|
|
@@ -785,32 +865,32 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
785
865
|
},
|
|
786
866
|
...consistencyScore && { scoring: consistencyScore }
|
|
787
867
|
};
|
|
788
|
-
const outputPath = (0,
|
|
868
|
+
const outputPath = (0, import_core4.resolveOutputPath)(
|
|
789
869
|
userOutputFile,
|
|
790
870
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
791
871
|
resolvedDir
|
|
792
872
|
);
|
|
793
|
-
(0,
|
|
873
|
+
(0, import_core4.handleJSONOutput)(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
794
874
|
} else if (outputFormat === "markdown") {
|
|
795
875
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
796
|
-
const outputPath = (0,
|
|
876
|
+
const outputPath = (0, import_core4.resolveOutputPath)(
|
|
797
877
|
userOutputFile,
|
|
798
878
|
`aiready-report-${getReportTimestamp()}.md`,
|
|
799
879
|
resolvedDir
|
|
800
880
|
);
|
|
801
|
-
(0,
|
|
802
|
-
console.log(
|
|
881
|
+
(0, import_fs2.writeFileSync)(outputPath, markdown);
|
|
882
|
+
console.log(import_chalk5.default.green(`\u2705 Report saved to ${outputPath}`));
|
|
803
883
|
} else {
|
|
804
|
-
console.log(
|
|
805
|
-
console.log(`Files Analyzed: ${
|
|
806
|
-
console.log(`Total Issues: ${
|
|
807
|
-
console.log(` Naming: ${
|
|
808
|
-
console.log(` Patterns: ${
|
|
809
|
-
console.log(` Architecture: ${
|
|
810
|
-
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")}
|
|
811
891
|
`);
|
|
812
892
|
if (report.summary.totalIssues === 0) {
|
|
813
|
-
console.log(
|
|
893
|
+
console.log(import_chalk5.default.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
|
|
814
894
|
} else {
|
|
815
895
|
const namingResults = report.results.filter(
|
|
816
896
|
(r) => r.issues.some((i) => i.category === "naming")
|
|
@@ -819,17 +899,17 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
819
899
|
(r) => r.issues.some((i) => i.category === "patterns")
|
|
820
900
|
);
|
|
821
901
|
if (namingResults.length > 0) {
|
|
822
|
-
console.log(
|
|
902
|
+
console.log(import_chalk5.default.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
|
|
823
903
|
let shown = 0;
|
|
824
904
|
for (const result of namingResults) {
|
|
825
905
|
if (shown >= 5) break;
|
|
826
906
|
for (const issue of result.issues) {
|
|
827
907
|
if (shown >= 5) break;
|
|
828
|
-
const severityColor = issue.severity === "critical" ?
|
|
829
|
-
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}`)}`);
|
|
830
910
|
console.log(` ${issue.message}`);
|
|
831
911
|
if (issue.suggestion) {
|
|
832
|
-
console.log(` ${
|
|
912
|
+
console.log(` ${import_chalk5.default.dim("\u2192")} ${import_chalk5.default.italic(issue.suggestion)}`);
|
|
833
913
|
}
|
|
834
914
|
console.log();
|
|
835
915
|
shown++;
|
|
@@ -837,22 +917,22 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
837
917
|
}
|
|
838
918
|
const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
839
919
|
if (remaining > 0) {
|
|
840
|
-
console.log(
|
|
920
|
+
console.log(import_chalk5.default.dim(` ... and ${remaining} more issues
|
|
841
921
|
`));
|
|
842
922
|
}
|
|
843
923
|
}
|
|
844
924
|
if (patternResults.length > 0) {
|
|
845
|
-
console.log(
|
|
925
|
+
console.log(import_chalk5.default.bold("\u{1F504} Pattern Issues\n"));
|
|
846
926
|
let shown = 0;
|
|
847
927
|
for (const result of patternResults) {
|
|
848
928
|
if (shown >= 5) break;
|
|
849
929
|
for (const issue of result.issues) {
|
|
850
930
|
if (shown >= 5) break;
|
|
851
|
-
const severityColor = issue.severity === "critical" ?
|
|
852
|
-
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}`)}`);
|
|
853
933
|
console.log(` ${issue.message}`);
|
|
854
934
|
if (issue.suggestion) {
|
|
855
|
-
console.log(` ${
|
|
935
|
+
console.log(` ${import_chalk5.default.dim("\u2192")} ${import_chalk5.default.italic(issue.suggestion)}`);
|
|
856
936
|
}
|
|
857
937
|
console.log();
|
|
858
938
|
shown++;
|
|
@@ -860,12 +940,12 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
860
940
|
}
|
|
861
941
|
const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
862
942
|
if (remaining > 0) {
|
|
863
|
-
console.log(
|
|
943
|
+
console.log(import_chalk5.default.dim(` ... and ${remaining} more issues
|
|
864
944
|
`));
|
|
865
945
|
}
|
|
866
946
|
}
|
|
867
947
|
if (report.recommendations.length > 0) {
|
|
868
|
-
console.log(
|
|
948
|
+
console.log(import_chalk5.default.bold("\u{1F4A1} Recommendations\n"));
|
|
869
949
|
report.recommendations.forEach((rec, i) => {
|
|
870
950
|
console.log(`${i + 1}. ${rec}`);
|
|
871
951
|
});
|
|
@@ -873,124 +953,35 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
873
953
|
}
|
|
874
954
|
}
|
|
875
955
|
if (consistencyScore) {
|
|
876
|
-
console.log(
|
|
877
|
-
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));
|
|
878
958
|
console.log();
|
|
879
959
|
}
|
|
880
960
|
}
|
|
881
961
|
} catch (error) {
|
|
882
|
-
(0,
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
function generateMarkdownReport(report, elapsedTime) {
|
|
886
|
-
let markdown = `# Consistency Analysis Report
|
|
887
|
-
|
|
888
|
-
`;
|
|
889
|
-
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
890
|
-
`;
|
|
891
|
-
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
892
|
-
|
|
893
|
-
`;
|
|
894
|
-
markdown += `## Summary
|
|
895
|
-
|
|
896
|
-
`;
|
|
897
|
-
markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
|
|
898
|
-
`;
|
|
899
|
-
markdown += `- **Total Issues:** ${report.summary.totalIssues}
|
|
900
|
-
`;
|
|
901
|
-
markdown += ` - Naming: ${report.summary.namingIssues}
|
|
902
|
-
`;
|
|
903
|
-
markdown += ` - Patterns: ${report.summary.patternIssues}
|
|
904
|
-
|
|
905
|
-
`;
|
|
906
|
-
if (report.recommendations.length > 0) {
|
|
907
|
-
markdown += `## Recommendations
|
|
908
|
-
|
|
909
|
-
`;
|
|
910
|
-
report.recommendations.forEach((rec, i) => {
|
|
911
|
-
markdown += `${i + 1}. ${rec}
|
|
912
|
-
`;
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
return markdown;
|
|
916
|
-
}
|
|
917
|
-
function getReportTimestamp() {
|
|
918
|
-
const now = /* @__PURE__ */ new Date();
|
|
919
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
920
|
-
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
921
|
-
}
|
|
922
|
-
function findLatestScanReport(dirPath) {
|
|
923
|
-
const aireadyDir = (0, import_path2.resolve)(dirPath, ".aiready");
|
|
924
|
-
if (!(0, import_fs2.existsSync)(aireadyDir)) {
|
|
925
|
-
return null;
|
|
926
|
-
}
|
|
927
|
-
const { readdirSync, statSync } = require("fs");
|
|
928
|
-
let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
|
|
929
|
-
if (files.length === 0) {
|
|
930
|
-
files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
|
|
931
|
-
}
|
|
932
|
-
if (files.length === 0) {
|
|
933
|
-
return null;
|
|
934
|
-
}
|
|
935
|
-
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());
|
|
936
|
-
return sortedFiles[0].path;
|
|
937
|
-
}
|
|
938
|
-
function warnIfGraphCapExceeded(report, dirPath) {
|
|
939
|
-
try {
|
|
940
|
-
const { loadConfig } = require("@aiready/core");
|
|
941
|
-
const { existsSync: existsSync2, readFileSync: readFileSync2 } = require("fs");
|
|
942
|
-
const { resolve } = require("path");
|
|
943
|
-
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
944
|
-
const configPath = resolve(dirPath, "aiready.json");
|
|
945
|
-
if (existsSync2(configPath)) {
|
|
946
|
-
try {
|
|
947
|
-
const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
948
|
-
if (rawConfig.visualizer?.graph) {
|
|
949
|
-
graphConfig = {
|
|
950
|
-
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
951
|
-
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
} catch (e) {
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
|
|
958
|
-
const edgeCount = report.context?.reduce((sum, ctx) => {
|
|
959
|
-
const relCount = ctx.relatedFiles?.length || 0;
|
|
960
|
-
const depCount = ctx.dependencies?.length || 0;
|
|
961
|
-
return sum + relCount + depCount;
|
|
962
|
-
}, 0) || 0;
|
|
963
|
-
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
964
|
-
console.log("");
|
|
965
|
-
console.log(import_chalk.default.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
|
|
966
|
-
if (nodeCount > graphConfig.maxNodes) {
|
|
967
|
-
console.log(import_chalk.default.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
|
|
968
|
-
}
|
|
969
|
-
if (edgeCount > graphConfig.maxEdges) {
|
|
970
|
-
console.log(import_chalk.default.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
|
|
971
|
-
}
|
|
972
|
-
console.log(import_chalk.default.dim(` To increase limits, add to aiready.json:`));
|
|
973
|
-
console.log(import_chalk.default.dim(` {`));
|
|
974
|
-
console.log(import_chalk.default.dim(` "visualizer": {`));
|
|
975
|
-
console.log(import_chalk.default.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
|
|
976
|
-
console.log(import_chalk.default.dim(` }`));
|
|
977
|
-
console.log(import_chalk.default.dim(` }`));
|
|
978
|
-
}
|
|
979
|
-
} catch (e) {
|
|
962
|
+
(0, import_core4.handleCLIError)(error, "Consistency analysis");
|
|
980
963
|
}
|
|
981
964
|
}
|
|
982
|
-
|
|
965
|
+
|
|
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) {
|
|
983
974
|
try {
|
|
984
|
-
const dirPath = (0,
|
|
985
|
-
let reportPath = options.report ? (0,
|
|
986
|
-
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)) {
|
|
987
978
|
const latestScan = findLatestScanReport(dirPath);
|
|
988
979
|
if (latestScan) {
|
|
989
980
|
reportPath = latestScan;
|
|
990
|
-
console.log(
|
|
981
|
+
console.log(import_chalk6.default.dim(`Found latest report: ${latestScan.split("/").pop()}`));
|
|
991
982
|
} else {
|
|
992
|
-
console.error(
|
|
993
|
-
console.log(
|
|
983
|
+
console.error(import_chalk6.default.red("\u274C No AI readiness report found"));
|
|
984
|
+
console.log(import_chalk6.default.dim(`
|
|
994
985
|
Generate a report with:
|
|
995
986
|
aiready scan --output json
|
|
996
987
|
|
|
@@ -999,13 +990,13 @@ Or specify a custom report:
|
|
|
999
990
|
return;
|
|
1000
991
|
}
|
|
1001
992
|
}
|
|
1002
|
-
const raw = (0,
|
|
993
|
+
const raw = (0, import_fs3.readFileSync)(reportPath, "utf8");
|
|
1003
994
|
const report = JSON.parse(raw);
|
|
1004
|
-
const configPath = (0,
|
|
995
|
+
const configPath = (0, import_path6.resolve)(dirPath, "aiready.json");
|
|
1005
996
|
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
1006
|
-
if ((0,
|
|
997
|
+
if ((0, import_fs3.existsSync)(configPath)) {
|
|
1007
998
|
try {
|
|
1008
|
-
const rawConfig = JSON.parse((0,
|
|
999
|
+
const rawConfig = JSON.parse((0, import_fs3.readFileSync)(configPath, "utf8"));
|
|
1009
1000
|
if (rawConfig.visualizer?.graph) {
|
|
1010
1001
|
graphConfig = {
|
|
1011
1002
|
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
@@ -1022,27 +1013,26 @@ Or specify a custom report:
|
|
|
1022
1013
|
const graph = GraphBuilder.buildFromReport(report, dirPath);
|
|
1023
1014
|
if (options.dev) {
|
|
1024
1015
|
try {
|
|
1025
|
-
const
|
|
1026
|
-
const monorepoWebDir = (0, import_path2.resolve)(dirPath, "packages/visualizer");
|
|
1016
|
+
const monorepoWebDir = (0, import_path6.resolve)(dirPath, "packages/visualizer");
|
|
1027
1017
|
let webDir = "";
|
|
1028
1018
|
let visualizerAvailable = false;
|
|
1029
|
-
if ((0,
|
|
1019
|
+
if ((0, import_fs3.existsSync)(monorepoWebDir)) {
|
|
1030
1020
|
webDir = monorepoWebDir;
|
|
1031
1021
|
visualizerAvailable = true;
|
|
1032
1022
|
} else {
|
|
1033
1023
|
const nodemodulesLocations = [
|
|
1034
|
-
(0,
|
|
1035
|
-
(0,
|
|
1024
|
+
(0, import_path6.resolve)(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
1025
|
+
(0, import_path6.resolve)(process.cwd(), "node_modules", "@aiready", "visualizer")
|
|
1036
1026
|
];
|
|
1037
1027
|
let currentDir = dirPath;
|
|
1038
1028
|
while (currentDir !== "/" && currentDir !== ".") {
|
|
1039
|
-
nodemodulesLocations.push((0,
|
|
1040
|
-
const parent = (0,
|
|
1029
|
+
nodemodulesLocations.push((0, import_path6.resolve)(currentDir, "node_modules", "@aiready", "visualizer"));
|
|
1030
|
+
const parent = (0, import_path6.resolve)(currentDir, "..");
|
|
1041
1031
|
if (parent === currentDir) break;
|
|
1042
1032
|
currentDir = parent;
|
|
1043
1033
|
}
|
|
1044
1034
|
for (const location of nodemodulesLocations) {
|
|
1045
|
-
if ((0,
|
|
1035
|
+
if ((0, import_fs3.existsSync)(location) && (0, import_fs3.existsSync)((0, import_path6.resolve)(location, "package.json"))) {
|
|
1046
1036
|
webDir = location;
|
|
1047
1037
|
visualizerAvailable = true;
|
|
1048
1038
|
break;
|
|
@@ -1051,7 +1041,7 @@ Or specify a custom report:
|
|
|
1051
1041
|
if (!visualizerAvailable) {
|
|
1052
1042
|
try {
|
|
1053
1043
|
const vizPkgPath = require.resolve("@aiready/visualizer/package.json");
|
|
1054
|
-
webDir = (0,
|
|
1044
|
+
webDir = (0, import_path6.resolve)(vizPkgPath, "..");
|
|
1055
1045
|
visualizerAvailable = true;
|
|
1056
1046
|
} catch (e) {
|
|
1057
1047
|
}
|
|
@@ -1059,17 +1049,17 @@ Or specify a custom report:
|
|
|
1059
1049
|
}
|
|
1060
1050
|
const spawnCwd = webDir || process.cwd();
|
|
1061
1051
|
const nodeBinCandidate = process.execPath;
|
|
1062
|
-
const nodeBin = (0,
|
|
1052
|
+
const nodeBin = (0, import_fs3.existsSync)(nodeBinCandidate) ? nodeBinCandidate : "node";
|
|
1063
1053
|
if (!visualizerAvailable) {
|
|
1064
|
-
console.error(
|
|
1065
|
-
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"));
|
|
1066
1056
|
return;
|
|
1067
1057
|
}
|
|
1068
1058
|
const { watch } = await import("fs");
|
|
1069
1059
|
const copyReportToViz = () => {
|
|
1070
1060
|
try {
|
|
1071
|
-
const destPath = (0,
|
|
1072
|
-
(0,
|
|
1061
|
+
const destPath = (0, import_path6.resolve)(spawnCwd, "web", "report-data.json");
|
|
1062
|
+
(0, import_fs3.copyFileSync)(reportPath, destPath);
|
|
1073
1063
|
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1074
1064
|
} catch (e) {
|
|
1075
1065
|
console.error("Failed to sync report:", e);
|
|
@@ -1086,7 +1076,7 @@ Or specify a custom report:
|
|
|
1086
1076
|
AIREADY_REPORT_PATH: reportPath,
|
|
1087
1077
|
AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
|
|
1088
1078
|
};
|
|
1089
|
-
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 });
|
|
1090
1080
|
const onExit = () => {
|
|
1091
1081
|
try {
|
|
1092
1082
|
reportWatcher.close();
|
|
@@ -1106,21 +1096,19 @@ Or specify a custom report:
|
|
|
1106
1096
|
}
|
|
1107
1097
|
}
|
|
1108
1098
|
console.log("Generating HTML...");
|
|
1109
|
-
const html = (0,
|
|
1110
|
-
const outPath = (0,
|
|
1111
|
-
(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");
|
|
1112
1102
|
console.log("Visualization written to:", outPath);
|
|
1113
1103
|
if (options.open) {
|
|
1114
|
-
const { exec } = await import("child_process");
|
|
1115
1104
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1116
|
-
|
|
1105
|
+
(0, import_child_process.spawn)(opener, [`"${outPath}"`], { shell: true });
|
|
1117
1106
|
}
|
|
1118
1107
|
if (options.serve) {
|
|
1119
1108
|
try {
|
|
1120
1109
|
const port = typeof options.serve === "number" ? options.serve : 5173;
|
|
1121
1110
|
const http = await import("http");
|
|
1122
1111
|
const fsp = await import("fs/promises");
|
|
1123
|
-
const { exec } = await import("child_process");
|
|
1124
1112
|
const server = http.createServer(async (req, res) => {
|
|
1125
1113
|
try {
|
|
1126
1114
|
const urlPath = req.url || "/";
|
|
@@ -1141,7 +1129,7 @@ Or specify a custom report:
|
|
|
1141
1129
|
const addr = `http://localhost:${port}/`;
|
|
1142
1130
|
console.log(`Local visualization server running at ${addr}`);
|
|
1143
1131
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1144
|
-
|
|
1132
|
+
(0, import_child_process.spawn)(opener, [`"${addr}"`], { shell: true });
|
|
1145
1133
|
});
|
|
1146
1134
|
process.on("SIGINT", () => {
|
|
1147
1135
|
server.close();
|
|
@@ -1152,20 +1140,10 @@ Or specify a custom report:
|
|
|
1152
1140
|
}
|
|
1153
1141
|
}
|
|
1154
1142
|
} catch (err) {
|
|
1155
|
-
(0,
|
|
1143
|
+
(0, import_core5.handleCLIError)(err, "Visualization");
|
|
1156
1144
|
}
|
|
1157
1145
|
}
|
|
1158
|
-
|
|
1159
|
-
EXAMPLES:
|
|
1160
|
-
$ aiready visualise . # Auto-detects latest report
|
|
1161
|
-
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1162
|
-
$ aiready visualise . --report report.json --dev
|
|
1163
|
-
$ aiready visualise . --report report.json --serve 8080
|
|
1164
|
-
|
|
1165
|
-
NOTES:
|
|
1166
|
-
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
1167
|
-
`).action(async (directory, options) => await handleVisualize(directory, options));
|
|
1168
|
-
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 = `
|
|
1169
1147
|
EXAMPLES:
|
|
1170
1148
|
$ aiready visualize . # Auto-detects latest report
|
|
1171
1149
|
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
@@ -1184,5 +1162,73 @@ NOTES:
|
|
|
1184
1162
|
reduce clutter and improve interactivity on large graphs.
|
|
1185
1163
|
- For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
|
|
1186
1164
|
allow the browser a moment to stabilize after load.
|
|
1187
|
-
|
|
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
|
+
});
|
|
1188
1234
|
program.parse();
|