@aiready/cli 0.9.27 → 0.9.29
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 +503 -455
- package/dist/cli.mjs +523 -453
- 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 +257 -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,
|
|
@@ -1020,29 +1011,30 @@ Or specify a custom report:
|
|
|
1020
1011
|
console.log("Building graph from report...");
|
|
1021
1012
|
const { GraphBuilder } = await import("@aiready/visualizer/graph");
|
|
1022
1013
|
const graph = GraphBuilder.buildFromReport(report, dirPath);
|
|
1023
|
-
|
|
1014
|
+
let useDevMode = options.dev || false;
|
|
1015
|
+
let devServerStarted = false;
|
|
1016
|
+
if (useDevMode) {
|
|
1024
1017
|
try {
|
|
1025
|
-
const
|
|
1026
|
-
const monorepoWebDir = (0, import_path2.resolve)(dirPath, "packages/visualizer");
|
|
1018
|
+
const monorepoWebDir = (0, import_path6.resolve)(dirPath, "packages/visualizer");
|
|
1027
1019
|
let webDir = "";
|
|
1028
1020
|
let visualizerAvailable = false;
|
|
1029
|
-
if ((0,
|
|
1021
|
+
if ((0, import_fs3.existsSync)(monorepoWebDir)) {
|
|
1030
1022
|
webDir = monorepoWebDir;
|
|
1031
1023
|
visualizerAvailable = true;
|
|
1032
1024
|
} else {
|
|
1033
1025
|
const nodemodulesLocations = [
|
|
1034
|
-
(0,
|
|
1035
|
-
(0,
|
|
1026
|
+
(0, import_path6.resolve)(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
1027
|
+
(0, import_path6.resolve)(process.cwd(), "node_modules", "@aiready", "visualizer")
|
|
1036
1028
|
];
|
|
1037
1029
|
let currentDir = dirPath;
|
|
1038
1030
|
while (currentDir !== "/" && currentDir !== ".") {
|
|
1039
|
-
nodemodulesLocations.push((0,
|
|
1040
|
-
const parent = (0,
|
|
1031
|
+
nodemodulesLocations.push((0, import_path6.resolve)(currentDir, "node_modules", "@aiready", "visualizer"));
|
|
1032
|
+
const parent = (0, import_path6.resolve)(currentDir, "..");
|
|
1041
1033
|
if (parent === currentDir) break;
|
|
1042
1034
|
currentDir = parent;
|
|
1043
1035
|
}
|
|
1044
1036
|
for (const location of nodemodulesLocations) {
|
|
1045
|
-
if ((0,
|
|
1037
|
+
if ((0, import_fs3.existsSync)(location) && (0, import_fs3.existsSync)((0, import_path6.resolve)(location, "package.json"))) {
|
|
1046
1038
|
webDir = location;
|
|
1047
1039
|
visualizerAvailable = true;
|
|
1048
1040
|
break;
|
|
@@ -1051,123 +1043,115 @@ Or specify a custom report:
|
|
|
1051
1043
|
if (!visualizerAvailable) {
|
|
1052
1044
|
try {
|
|
1053
1045
|
const vizPkgPath = require.resolve("@aiready/visualizer/package.json");
|
|
1054
|
-
webDir = (0,
|
|
1046
|
+
webDir = (0, import_path6.resolve)(vizPkgPath, "..");
|
|
1055
1047
|
visualizerAvailable = true;
|
|
1056
1048
|
} catch (e) {
|
|
1057
1049
|
}
|
|
1058
1050
|
}
|
|
1059
1051
|
}
|
|
1060
|
-
const
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1052
|
+
const webViteConfigExists = webDir && (0, import_fs3.existsSync)((0, import_path6.resolve)(webDir, "web", "vite.config.ts"));
|
|
1053
|
+
if (visualizerAvailable && webViteConfigExists) {
|
|
1054
|
+
const spawnCwd = webDir;
|
|
1055
|
+
const { watch } = await import("fs");
|
|
1056
|
+
const copyReportToViz = () => {
|
|
1057
|
+
try {
|
|
1058
|
+
const destPath = (0, import_path6.resolve)(spawnCwd, "web", "report-data.json");
|
|
1059
|
+
(0, import_fs3.copyFileSync)(reportPath, destPath);
|
|
1060
|
+
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1061
|
+
} catch (e) {
|
|
1062
|
+
console.error("Failed to sync report:", e);
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
copyReportToViz();
|
|
1066
|
+
let watchTimeout = null;
|
|
1067
|
+
const reportWatcher = watch(reportPath, () => {
|
|
1068
|
+
if (watchTimeout) clearTimeout(watchTimeout);
|
|
1069
|
+
watchTimeout = setTimeout(copyReportToViz, 100);
|
|
1070
|
+
});
|
|
1071
|
+
const envForSpawn = {
|
|
1072
|
+
...process.env,
|
|
1073
|
+
AIREADY_REPORT_PATH: reportPath,
|
|
1074
|
+
AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
|
|
1075
|
+
};
|
|
1076
|
+
const vite = (0, import_child_process.spawn)("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
|
|
1077
|
+
const onExit = () => {
|
|
1078
|
+
try {
|
|
1079
|
+
reportWatcher.close();
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
}
|
|
1082
|
+
try {
|
|
1083
|
+
vite.kill();
|
|
1084
|
+
} catch (e) {
|
|
1085
|
+
}
|
|
1086
|
+
process.exit(0);
|
|
1087
|
+
};
|
|
1088
|
+
process.on("SIGINT", onExit);
|
|
1089
|
+
process.on("SIGTERM", onExit);
|
|
1090
|
+
devServerStarted = true;
|
|
1066
1091
|
return;
|
|
1092
|
+
} else {
|
|
1093
|
+
console.log(import_chalk6.default.yellow("\u26A0\uFE0F Dev server not available (requires local @aiready/visualizer with web assets)."));
|
|
1094
|
+
console.log(import_chalk6.default.cyan(" Falling back to static HTML generation...\n"));
|
|
1095
|
+
useDevMode = false;
|
|
1067
1096
|
}
|
|
1068
|
-
const { watch } = await import("fs");
|
|
1069
|
-
const copyReportToViz = () => {
|
|
1070
|
-
try {
|
|
1071
|
-
const destPath = (0, import_path2.resolve)(spawnCwd, "web", "report-data.json");
|
|
1072
|
-
(0, import_fs2.copyFileSync)(reportPath, destPath);
|
|
1073
|
-
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1074
|
-
} catch (e) {
|
|
1075
|
-
console.error("Failed to sync report:", e);
|
|
1076
|
-
}
|
|
1077
|
-
};
|
|
1078
|
-
copyReportToViz();
|
|
1079
|
-
let watchTimeout = null;
|
|
1080
|
-
const reportWatcher = watch(reportPath, () => {
|
|
1081
|
-
if (watchTimeout) clearTimeout(watchTimeout);
|
|
1082
|
-
watchTimeout = setTimeout(copyReportToViz, 100);
|
|
1083
|
-
});
|
|
1084
|
-
const envForSpawn = {
|
|
1085
|
-
...process.env,
|
|
1086
|
-
AIREADY_REPORT_PATH: reportPath,
|
|
1087
|
-
AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
|
|
1088
|
-
};
|
|
1089
|
-
const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
|
|
1090
|
-
const onExit = () => {
|
|
1091
|
-
try {
|
|
1092
|
-
reportWatcher.close();
|
|
1093
|
-
} catch (e) {
|
|
1094
|
-
}
|
|
1095
|
-
try {
|
|
1096
|
-
vite.kill();
|
|
1097
|
-
} catch (e) {
|
|
1098
|
-
}
|
|
1099
|
-
process.exit(0);
|
|
1100
|
-
};
|
|
1101
|
-
process.on("SIGINT", onExit);
|
|
1102
|
-
process.on("SIGTERM", onExit);
|
|
1103
|
-
return;
|
|
1104
1097
|
} catch (err) {
|
|
1105
1098
|
console.error("Failed to start dev server:", err);
|
|
1099
|
+
console.log(import_chalk6.default.cyan(" Falling back to static HTML generation...\n"));
|
|
1100
|
+
useDevMode = false;
|
|
1106
1101
|
}
|
|
1107
1102
|
}
|
|
1108
1103
|
console.log("Generating HTML...");
|
|
1109
|
-
const html = (0,
|
|
1110
|
-
const
|
|
1111
|
-
(0,
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1104
|
+
const html = (0, import_core6.generateHTML)(graph);
|
|
1105
|
+
const defaultOutput = "visualization.html";
|
|
1106
|
+
const outPath = (0, import_path6.resolve)(dirPath, options.output || defaultOutput);
|
|
1107
|
+
(0, import_fs3.writeFileSync)(outPath, html, "utf8");
|
|
1108
|
+
console.log(import_chalk6.default.green(`\u2705 Visualization written to: ${outPath}`));
|
|
1109
|
+
if (options.open || options.serve) {
|
|
1115
1110
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
res.
|
|
1131
|
-
|
|
1111
|
+
if (options.serve) {
|
|
1112
|
+
try {
|
|
1113
|
+
const port = typeof options.serve === "number" ? options.serve : 5173;
|
|
1114
|
+
const http = await import("http");
|
|
1115
|
+
const fsp = await import("fs/promises");
|
|
1116
|
+
const server = http.createServer(async (req, res) => {
|
|
1117
|
+
try {
|
|
1118
|
+
const urlPath = req.url || "/";
|
|
1119
|
+
if (urlPath === "/" || urlPath === "/index.html") {
|
|
1120
|
+
const content = await fsp.readFile(outPath, "utf8");
|
|
1121
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1122
|
+
res.end(content);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1126
|
+
res.end("Not found");
|
|
1127
|
+
} catch (e) {
|
|
1128
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1129
|
+
res.end("Server error");
|
|
1132
1130
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
process.exit(0);
|
|
1149
|
-
});
|
|
1150
|
-
} catch (err) {
|
|
1151
|
-
console.error("Failed to start local server:", err);
|
|
1131
|
+
});
|
|
1132
|
+
server.listen(port, () => {
|
|
1133
|
+
const addr = `http://localhost:${port}/`;
|
|
1134
|
+
console.log(import_chalk6.default.cyan(`\u{1F310} Local visualization server running at ${addr}`));
|
|
1135
|
+
(0, import_child_process.spawn)(opener, [`"${addr}"`], { shell: true });
|
|
1136
|
+
});
|
|
1137
|
+
process.on("SIGINT", () => {
|
|
1138
|
+
server.close();
|
|
1139
|
+
process.exit(0);
|
|
1140
|
+
});
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
console.error("Failed to start local server:", err);
|
|
1143
|
+
}
|
|
1144
|
+
} else if (options.open) {
|
|
1145
|
+
(0, import_child_process.spawn)(opener, [`"${outPath}"`], { shell: true });
|
|
1152
1146
|
}
|
|
1153
1147
|
}
|
|
1154
1148
|
} catch (err) {
|
|
1155
|
-
(0,
|
|
1149
|
+
(0, import_core5.handleCLIError)(err, "Visualization");
|
|
1156
1150
|
}
|
|
1157
1151
|
}
|
|
1158
|
-
|
|
1152
|
+
var visualizeHelpText = `
|
|
1159
1153
|
EXAMPLES:
|
|
1160
|
-
$ aiready
|
|
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", `
|
|
1169
|
-
EXAMPLES:
|
|
1170
|
-
$ aiready visualize . # Auto-detects latest report
|
|
1154
|
+
$ aiready visualize . # Auto-detects latest report, generates HTML
|
|
1171
1155
|
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
1172
1156
|
$ aiready visualize . --report report.json -o out/visualization.html --open
|
|
1173
1157
|
$ aiready visualize . --report report.json --serve
|
|
@@ -1177,12 +1161,76 @@ EXAMPLES:
|
|
|
1177
1161
|
NOTES:
|
|
1178
1162
|
- The value passed to --report is interpreted relative to the directory argument (first positional).
|
|
1179
1163
|
If the report is not found, the CLI will suggest running 'aiready scan' to generate it.
|
|
1180
|
-
- Default output path:
|
|
1164
|
+
- Default output path: visualization.html (in the current directory).
|
|
1181
1165
|
- --serve starts a tiny single-file HTTP server (default port: 5173) and opens your browser.
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1166
|
+
- --dev starts a Vite dev server with live reload (requires local @aiready/visualizer installation).
|
|
1167
|
+
When --dev is not available, it falls back to static HTML generation.
|
|
1168
|
+
`;
|
|
1169
|
+
var visualiseHelpText = `
|
|
1170
|
+
EXAMPLES:
|
|
1171
|
+
$ aiready visualise . # Auto-detects latest report
|
|
1172
|
+
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1173
|
+
$ aiready visualise . --report report.json --serve 8080
|
|
1174
|
+
|
|
1175
|
+
NOTES:
|
|
1176
|
+
- Same options as 'visualize'. Use --serve to host the static HTML, or --dev for live reload.
|
|
1177
|
+
`;
|
|
1178
|
+
|
|
1179
|
+
// src/cli.ts
|
|
1180
|
+
var packageJson = JSON.parse((0, import_fs4.readFileSync)((0, import_path7.join)(__dirname, "../package.json"), "utf8"));
|
|
1181
|
+
var program = new import_commander.Command();
|
|
1182
|
+
program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
|
|
1183
|
+
AI READINESS SCORING:
|
|
1184
|
+
Get a 0-100 score indicating how AI-ready your codebase is.
|
|
1185
|
+
Use --score flag with any analysis command for detailed breakdown.
|
|
1186
|
+
|
|
1187
|
+
EXAMPLES:
|
|
1188
|
+
$ aiready scan # Quick analysis of current directory
|
|
1189
|
+
$ aiready scan --score # Get AI Readiness Score (0-100)
|
|
1190
|
+
$ aiready scan --tools patterns # Run only pattern detection
|
|
1191
|
+
$ aiready patterns --similarity 0.6 # Custom similarity threshold
|
|
1192
|
+
$ aiready scan --output json --output-file results.json
|
|
1193
|
+
|
|
1194
|
+
GETTING STARTED:
|
|
1195
|
+
1. Run 'aiready scan' to analyze your codebase
|
|
1196
|
+
2. Use 'aiready scan --score' for AI readiness assessment
|
|
1197
|
+
3. Create aiready.json for persistent configuration
|
|
1198
|
+
4. Set up CI/CD with '--threshold' for quality gates
|
|
1199
|
+
|
|
1200
|
+
CONFIGURATION:
|
|
1201
|
+
Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
|
|
1202
|
+
CLI options override config file settings
|
|
1203
|
+
|
|
1204
|
+
Example aiready.json:
|
|
1205
|
+
{
|
|
1206
|
+
"scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
|
|
1207
|
+
"tools": {
|
|
1208
|
+
"pattern-detect": { "minSimilarity": 0.5 },
|
|
1209
|
+
"context-analyzer": { "maxContextBudget": 15000 }
|
|
1210
|
+
},
|
|
1211
|
+
"output": { "format": "json", "directory": ".aiready" }
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
VERSION: ${packageJson.version}
|
|
1215
|
+
DOCUMENTATION: https://aiready.dev/docs/cli
|
|
1216
|
+
GITHUB: https://github.com/caopengau/aiready-cli
|
|
1217
|
+
LANDING: https://github.com/caopengau/aiready-landing`);
|
|
1218
|
+
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) => {
|
|
1219
|
+
await scanAction(directory, options);
|
|
1220
|
+
});
|
|
1221
|
+
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) => {
|
|
1222
|
+
await patternsAction(directory, options);
|
|
1223
|
+
});
|
|
1224
|
+
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) => {
|
|
1225
|
+
await contextAction(directory, options);
|
|
1226
|
+
});
|
|
1227
|
+
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) => {
|
|
1228
|
+
await consistencyAction(directory, options);
|
|
1229
|
+
});
|
|
1230
|
+
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) => {
|
|
1231
|
+
await visualizeAction(directory, options);
|
|
1232
|
+
});
|
|
1233
|
+
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) => {
|
|
1234
|
+
await visualizeAction(directory, options);
|
|
1235
|
+
});
|
|
1188
1236
|
program.parse();
|