@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.mjs
CHANGED
|
@@ -6,9 +6,12 @@ import {
|
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { Command } from "commander";
|
|
9
|
-
import
|
|
10
|
-
import { writeFileSync } from "fs";
|
|
9
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
11
10
|
import { join } from "path";
|
|
11
|
+
|
|
12
|
+
// src/commands/scan.ts
|
|
13
|
+
import chalk2 from "chalk";
|
|
14
|
+
import { resolve as resolvePath2 } from "path";
|
|
12
15
|
import {
|
|
13
16
|
loadMergedConfig,
|
|
14
17
|
handleJSONOutput,
|
|
@@ -20,71 +23,119 @@ import {
|
|
|
20
23
|
formatToolScore,
|
|
21
24
|
getRating,
|
|
22
25
|
getRatingDisplay,
|
|
23
|
-
parseWeightString
|
|
24
|
-
generateHTML
|
|
26
|
+
parseWeightString
|
|
25
27
|
} from "@aiready/core";
|
|
26
|
-
import { readFileSync, existsSync, copyFileSync } from "fs";
|
|
27
|
-
import { resolve as resolvePath } from "path";
|
|
28
|
-
var packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8"));
|
|
29
|
-
var program = new Command();
|
|
30
|
-
program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
|
|
31
|
-
AI READINESS SCORING:
|
|
32
|
-
Get a 0-100 score indicating how AI-ready your codebase is.
|
|
33
|
-
Use --score flag with any analysis command for detailed breakdown.
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
// src/utils/helpers.ts
|
|
30
|
+
import { resolve as resolvePath } from "path";
|
|
31
|
+
import { existsSync, readdirSync, statSync, readFileSync } from "fs";
|
|
32
|
+
import chalk from "chalk";
|
|
33
|
+
function getReportTimestamp() {
|
|
34
|
+
const now = /* @__PURE__ */ new Date();
|
|
35
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
36
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
37
|
+
}
|
|
38
|
+
function findLatestScanReport(dirPath) {
|
|
39
|
+
const aireadyDir = resolvePath(dirPath, ".aiready");
|
|
40
|
+
if (!existsSync(aireadyDir)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
|
|
44
|
+
if (files.length === 0) {
|
|
45
|
+
files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
|
|
46
|
+
}
|
|
47
|
+
if (files.length === 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const sortedFiles = files.map((f) => ({ name: f, path: resolvePath(aireadyDir, f), mtime: statSync(resolvePath(aireadyDir, f)).mtime })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
51
|
+
return sortedFiles[0].path;
|
|
52
|
+
}
|
|
53
|
+
function warnIfGraphCapExceeded(report, dirPath) {
|
|
54
|
+
try {
|
|
55
|
+
const { loadConfig } = __require("@aiready/core");
|
|
56
|
+
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
57
|
+
const configPath = resolvePath(dirPath, "aiready.json");
|
|
58
|
+
if (existsSync(configPath)) {
|
|
59
|
+
try {
|
|
60
|
+
const rawConfig = JSON.parse(readFileSync(configPath, "utf8"));
|
|
61
|
+
if (rawConfig.visualizer?.graph) {
|
|
62
|
+
graphConfig = {
|
|
63
|
+
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
64
|
+
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
|
|
71
|
+
const edgeCount = report.context?.reduce((sum, ctx) => {
|
|
72
|
+
const relCount = ctx.relatedFiles?.length || 0;
|
|
73
|
+
const depCount = ctx.dependencies?.length || 0;
|
|
74
|
+
return sum + relCount + depCount;
|
|
75
|
+
}, 0) || 0;
|
|
76
|
+
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
77
|
+
console.log("");
|
|
78
|
+
console.log(chalk.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
|
|
79
|
+
if (nodeCount > graphConfig.maxNodes) {
|
|
80
|
+
console.log(chalk.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
|
|
81
|
+
}
|
|
82
|
+
if (edgeCount > graphConfig.maxEdges) {
|
|
83
|
+
console.log(chalk.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
|
|
84
|
+
}
|
|
85
|
+
console.log(chalk.dim(` To increase limits, add to aiready.json:`));
|
|
86
|
+
console.log(chalk.dim(` {`));
|
|
87
|
+
console.log(chalk.dim(` "visualizer": {`));
|
|
88
|
+
console.log(chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
|
|
89
|
+
console.log(chalk.dim(` }`));
|
|
90
|
+
console.log(chalk.dim(` }`));
|
|
91
|
+
}
|
|
92
|
+
} catch (e) {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function generateMarkdownReport(report, elapsedTime) {
|
|
96
|
+
let markdown = `# Consistency Analysis Report
|
|
41
97
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
4. Set up CI/CD with '--threshold' for quality gates
|
|
98
|
+
`;
|
|
99
|
+
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
100
|
+
`;
|
|
101
|
+
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
47
102
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
CLI options override config file settings
|
|
103
|
+
`;
|
|
104
|
+
markdown += `## Summary
|
|
51
105
|
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
106
|
+
`;
|
|
107
|
+
markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
|
|
108
|
+
`;
|
|
109
|
+
markdown += `- **Total Issues:** ${report.summary.totalIssues}
|
|
110
|
+
`;
|
|
111
|
+
markdown += ` - Naming: ${report.summary.namingIssues}
|
|
112
|
+
`;
|
|
113
|
+
markdown += ` - Patterns: ${report.summary.patternIssues}
|
|
61
114
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
LANDING: https://github.com/caopengau/aiready-landing`);
|
|
66
|
-
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", `
|
|
67
|
-
EXAMPLES:
|
|
68
|
-
$ aiready scan # Analyze all tools
|
|
69
|
-
$ aiready scan --tools patterns,context # Skip consistency
|
|
70
|
-
$ aiready scan --score --threshold 75 # CI/CD with threshold
|
|
71
|
-
$ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
|
|
72
|
-
$ aiready scan --ci --fail-on major # Fail on major+ issues
|
|
73
|
-
$ aiready scan --output json --output-file report.json
|
|
115
|
+
`;
|
|
116
|
+
if (report.recommendations.length > 0) {
|
|
117
|
+
markdown += `## Recommendations
|
|
74
118
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
119
|
+
`;
|
|
120
|
+
report.recommendations.forEach((rec, i) => {
|
|
121
|
+
markdown += `${i + 1}. ${rec}
|
|
122
|
+
`;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return markdown;
|
|
126
|
+
}
|
|
127
|
+
function truncateArray(arr, cap = 8) {
|
|
128
|
+
if (!Array.isArray(arr)) return "";
|
|
129
|
+
const shown = arr.slice(0, cap).map((v) => String(v));
|
|
130
|
+
const more = arr.length - shown.length;
|
|
131
|
+
return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
|
|
132
|
+
}
|
|
80
133
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
`).action(async (directory, options) => {
|
|
85
|
-
console.log(chalk.blue("\u{1F680} Starting AIReady unified analysis...\n"));
|
|
134
|
+
// src/commands/scan.ts
|
|
135
|
+
async function scanAction(directory, options) {
|
|
136
|
+
console.log(chalk2.blue("\u{1F680} Starting AIReady unified analysis...\n"));
|
|
86
137
|
const startTime = Date.now();
|
|
87
|
-
const resolvedDir =
|
|
138
|
+
const resolvedDir = resolvePath2(process.cwd(), directory || ".");
|
|
88
139
|
try {
|
|
89
140
|
const defaults = {
|
|
90
141
|
tools: ["patterns", "context", "consistency"],
|
|
@@ -106,19 +157,13 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
106
157
|
const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
107
158
|
finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
|
|
108
159
|
}
|
|
109
|
-
console.log(
|
|
110
|
-
console.log(
|
|
111
|
-
console.log(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
|
|
117
|
-
};
|
|
118
|
-
console.log(chalk.white("\nGeneral settings:"));
|
|
119
|
-
if (finalOptions.rootDir) console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
|
|
120
|
-
if (finalOptions.include) console.log(` include: ${chalk.bold(truncate(finalOptions.include, 6))}`);
|
|
121
|
-
if (finalOptions.exclude) console.log(` exclude: ${chalk.bold(truncate(finalOptions.exclude, 6))}`);
|
|
160
|
+
console.log(chalk2.cyan("\n=== AIReady Run Preview ==="));
|
|
161
|
+
console.log(chalk2.white("Tools to run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
|
|
162
|
+
console.log(chalk2.white("Will use settings from config and defaults."));
|
|
163
|
+
console.log(chalk2.white("\nGeneral settings:"));
|
|
164
|
+
if (finalOptions.rootDir) console.log(` rootDir: ${chalk2.bold(String(finalOptions.rootDir))}`);
|
|
165
|
+
if (finalOptions.include) console.log(` include: ${chalk2.bold(truncateArray(finalOptions.include, 6))}`);
|
|
166
|
+
if (finalOptions.exclude) console.log(` exclude: ${chalk2.bold(truncateArray(finalOptions.exclude, 6))}`);
|
|
122
167
|
if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
|
|
123
168
|
const patternDetectConfig = finalOptions["pattern-detect"] || {
|
|
124
169
|
minSimilarity: finalOptions.minSimilarity,
|
|
@@ -131,16 +176,16 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
131
176
|
severity: finalOptions.severity,
|
|
132
177
|
includeTests: finalOptions.includeTests
|
|
133
178
|
};
|
|
134
|
-
console.log(
|
|
135
|
-
console.log(` minSimilarity: ${
|
|
136
|
-
console.log(` minLines: ${
|
|
137
|
-
if (patternDetectConfig.approx !== void 0) console.log(` approx: ${
|
|
138
|
-
if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${
|
|
139
|
-
if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${
|
|
140
|
-
if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${
|
|
141
|
-
if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${
|
|
142
|
-
if (patternDetectConfig.severity !== void 0) console.log(` severity: ${
|
|
143
|
-
if (patternDetectConfig.includeTests !== void 0) console.log(` includeTests: ${
|
|
179
|
+
console.log(chalk2.white("\nPattern-detect settings:"));
|
|
180
|
+
console.log(` minSimilarity: ${chalk2.bold(patternDetectConfig.minSimilarity ?? "default")}`);
|
|
181
|
+
console.log(` minLines: ${chalk2.bold(patternDetectConfig.minLines ?? "default")}`);
|
|
182
|
+
if (patternDetectConfig.approx !== void 0) console.log(` approx: ${chalk2.bold(String(patternDetectConfig.approx))}`);
|
|
183
|
+
if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${chalk2.bold(String(patternDetectConfig.minSharedTokens))}`);
|
|
184
|
+
if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${chalk2.bold(String(patternDetectConfig.maxCandidatesPerBlock))}`);
|
|
185
|
+
if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${chalk2.bold(String(patternDetectConfig.batchSize))}`);
|
|
186
|
+
if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${chalk2.bold(String(patternDetectConfig.streamResults))}`);
|
|
187
|
+
if (patternDetectConfig.severity !== void 0) console.log(` severity: ${chalk2.bold(String(patternDetectConfig.severity))}`);
|
|
188
|
+
if (patternDetectConfig.includeTests !== void 0) console.log(` includeTests: ${chalk2.bold(String(patternDetectConfig.includeTests))}`);
|
|
144
189
|
}
|
|
145
190
|
if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
|
|
146
191
|
const ca = finalOptions["context-analyzer"] || {
|
|
@@ -150,32 +195,32 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
150
195
|
maxFragmentation: finalOptions.maxFragmentation,
|
|
151
196
|
includeNodeModules: finalOptions.includeNodeModules
|
|
152
197
|
};
|
|
153
|
-
console.log(
|
|
154
|
-
console.log(` maxDepth: ${
|
|
155
|
-
console.log(` maxContextBudget: ${
|
|
156
|
-
if (ca.minCohesion !== void 0) console.log(` minCohesion: ${
|
|
157
|
-
if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${
|
|
158
|
-
if (ca.includeNodeModules !== void 0) console.log(` includeNodeModules: ${
|
|
198
|
+
console.log(chalk2.white("\nContext-analyzer settings:"));
|
|
199
|
+
console.log(` maxDepth: ${chalk2.bold(ca.maxDepth ?? "default")}`);
|
|
200
|
+
console.log(` maxContextBudget: ${chalk2.bold(ca.maxContextBudget ?? "default")}`);
|
|
201
|
+
if (ca.minCohesion !== void 0) console.log(` minCohesion: ${chalk2.bold(String(ca.minCohesion))}`);
|
|
202
|
+
if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${chalk2.bold(String(ca.maxFragmentation))}`);
|
|
203
|
+
if (ca.includeNodeModules !== void 0) console.log(` includeNodeModules: ${chalk2.bold(String(ca.includeNodeModules))}`);
|
|
159
204
|
}
|
|
160
205
|
if (finalOptions.consistency) {
|
|
161
206
|
const c = finalOptions.consistency;
|
|
162
|
-
console.log(
|
|
163
|
-
console.log(` checkNaming: ${
|
|
164
|
-
console.log(` checkPatterns: ${
|
|
165
|
-
console.log(` checkArchitecture: ${
|
|
166
|
-
if (c.minSeverity) console.log(` minSeverity: ${
|
|
167
|
-
if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${
|
|
168
|
-
if (c.shortWords) console.log(` shortWords: ${
|
|
207
|
+
console.log(chalk2.white("\nConsistency settings:"));
|
|
208
|
+
console.log(` checkNaming: ${chalk2.bold(String(c.checkNaming ?? true))}`);
|
|
209
|
+
console.log(` checkPatterns: ${chalk2.bold(String(c.checkPatterns ?? true))}`);
|
|
210
|
+
console.log(` checkArchitecture: ${chalk2.bold(String(c.checkArchitecture ?? false))}`);
|
|
211
|
+
if (c.minSeverity) console.log(` minSeverity: ${chalk2.bold(c.minSeverity)}`);
|
|
212
|
+
if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${chalk2.bold(truncateArray(c.acceptedAbbreviations, 8))}`);
|
|
213
|
+
if (c.shortWords) console.log(` shortWords: ${chalk2.bold(truncateArray(c.shortWords, 8))}`);
|
|
169
214
|
}
|
|
170
|
-
console.log(
|
|
215
|
+
console.log(chalk2.white("\nStarting analysis..."));
|
|
171
216
|
const progressCallback = (event) => {
|
|
172
|
-
console.log(
|
|
217
|
+
console.log(chalk2.cyan(`
|
|
173
218
|
--- ${event.tool.toUpperCase()} RESULTS ---`));
|
|
174
219
|
try {
|
|
175
220
|
if (event.tool === "patterns") {
|
|
176
221
|
const pr = event.data;
|
|
177
|
-
console.log(` Duplicate patterns: ${
|
|
178
|
-
console.log(` Files with pattern issues: ${
|
|
222
|
+
console.log(` Duplicate patterns: ${chalk2.bold(String(pr.duplicates?.length || 0))}`);
|
|
223
|
+
console.log(` Files with pattern issues: ${chalk2.bold(String(pr.results?.length || 0))}`);
|
|
179
224
|
if (pr.duplicates && pr.duplicates.length > 0) {
|
|
180
225
|
pr.duplicates.slice(0, 5).forEach((d, i) => {
|
|
181
226
|
console.log(` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`);
|
|
@@ -189,10 +234,10 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
189
234
|
});
|
|
190
235
|
}
|
|
191
236
|
if (pr.groups && pr.groups.length >= 0) {
|
|
192
|
-
console.log(` \u2705 Grouped ${
|
|
237
|
+
console.log(` \u2705 Grouped ${chalk2.bold(String(pr.duplicates?.length || 0))} duplicates into ${chalk2.bold(String(pr.groups.length))} file pairs`);
|
|
193
238
|
}
|
|
194
239
|
if (pr.clusters && pr.clusters.length >= 0) {
|
|
195
|
-
console.log(` \u2705 Created ${
|
|
240
|
+
console.log(` \u2705 Created ${chalk2.bold(String(pr.clusters.length))} refactor clusters`);
|
|
196
241
|
pr.clusters.slice(0, 3).forEach((cl, idx) => {
|
|
197
242
|
const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
|
|
198
243
|
console.log(` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`);
|
|
@@ -200,14 +245,14 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
200
245
|
}
|
|
201
246
|
} else if (event.tool === "context") {
|
|
202
247
|
const cr = event.data;
|
|
203
|
-
console.log(` Context issues found: ${
|
|
248
|
+
console.log(` Context issues found: ${chalk2.bold(String(cr.length || 0))}`);
|
|
204
249
|
cr.slice(0, 5).forEach((c, i) => {
|
|
205
250
|
const msg = c.message ? ` - ${c.message}` : "";
|
|
206
251
|
console.log(` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`);
|
|
207
252
|
});
|
|
208
253
|
} else if (event.tool === "consistency") {
|
|
209
254
|
const rep = event.data;
|
|
210
|
-
console.log(` Consistency totalIssues: ${
|
|
255
|
+
console.log(` Consistency totalIssues: ${chalk2.bold(String(rep.summary?.totalIssues || 0))}`);
|
|
211
256
|
if (rep.results && rep.results.length > 0) {
|
|
212
257
|
const fileMap = /* @__PURE__ */ new Map();
|
|
213
258
|
rep.results.forEach((r) => {
|
|
@@ -231,7 +276,7 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
231
276
|
});
|
|
232
277
|
const remaining = files.length - topFiles.length;
|
|
233
278
|
if (remaining > 0) {
|
|
234
|
-
console.log(
|
|
279
|
+
console.log(chalk2.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
|
|
235
280
|
}
|
|
236
281
|
}
|
|
237
282
|
}
|
|
@@ -239,15 +284,15 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
239
284
|
}
|
|
240
285
|
};
|
|
241
286
|
const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
|
|
242
|
-
console.log(
|
|
243
|
-
console.log(
|
|
244
|
-
console.log(
|
|
245
|
-
console.log(` Total issues (all tools): ${
|
|
246
|
-
if (results.duplicates) console.log(` Duplicate patterns found: ${
|
|
247
|
-
if (results.patterns) console.log(` Pattern files with issues: ${
|
|
248
|
-
if (results.context) console.log(` Context issues: ${
|
|
249
|
-
if (results.consistency) console.log(` Consistency issues: ${
|
|
250
|
-
console.log(
|
|
287
|
+
console.log(chalk2.cyan("\n=== AIReady Run Summary ==="));
|
|
288
|
+
console.log(chalk2.white("Tools run:"), (finalOptions.tools || ["patterns", "context", "consistency"]).join(", "));
|
|
289
|
+
console.log(chalk2.cyan("\nResults summary:"));
|
|
290
|
+
console.log(` Total issues (all tools): ${chalk2.bold(String(results.summary.totalIssues || 0))}`);
|
|
291
|
+
if (results.duplicates) console.log(` Duplicate patterns found: ${chalk2.bold(String(results.duplicates.length || 0))}`);
|
|
292
|
+
if (results.patterns) console.log(` Pattern files with issues: ${chalk2.bold(String(results.patterns.length || 0))}`);
|
|
293
|
+
if (results.context) console.log(` Context issues: ${chalk2.bold(String(results.context.length || 0))}`);
|
|
294
|
+
if (results.consistency) console.log(` Consistency issues: ${chalk2.bold(String(results.consistency.summary.totalIssues || 0))}`);
|
|
295
|
+
console.log(chalk2.cyan("===========================\n"));
|
|
251
296
|
const elapsedTime = getElapsedTime(startTime);
|
|
252
297
|
let scoringResult;
|
|
253
298
|
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
@@ -282,10 +327,10 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
282
327
|
const cliWeights = parseWeightString(options.weights);
|
|
283
328
|
if (toolScores.size > 0) {
|
|
284
329
|
scoringResult = calculateOverallScore(toolScores, finalOptions, cliWeights.size ? cliWeights : void 0);
|
|
285
|
-
console.log(
|
|
330
|
+
console.log(chalk2.bold("\n\u{1F4CA} AI Readiness Overall Score"));
|
|
286
331
|
console.log(` ${formatScore(scoringResult)}`);
|
|
287
332
|
if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
|
|
288
|
-
console.log(
|
|
333
|
+
console.log(chalk2.bold("\nTool breakdown:"));
|
|
289
334
|
scoringResult.breakdown.forEach((tool) => {
|
|
290
335
|
const rating = getRating(tool.score);
|
|
291
336
|
const rd = getRatingDisplay(rating);
|
|
@@ -293,7 +338,7 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
293
338
|
});
|
|
294
339
|
console.log();
|
|
295
340
|
if (finalOptions.scoring?.showBreakdown) {
|
|
296
|
-
console.log(
|
|
341
|
+
console.log(chalk2.bold("Detailed tool breakdown:"));
|
|
297
342
|
scoringResult.breakdown.forEach((tool) => {
|
|
298
343
|
console.log(formatToolScore(tool));
|
|
299
344
|
});
|
|
@@ -382,34 +427,60 @@ CI/CD INTEGRATION (Gatekeeper Mode):
|
|
|
382
427
|
}
|
|
383
428
|
}
|
|
384
429
|
if (shouldFail) {
|
|
385
|
-
console.log(
|
|
386
|
-
console.log(
|
|
387
|
-
console.log(
|
|
388
|
-
console.log(
|
|
389
|
-
console.log(
|
|
390
|
-
console.log(
|
|
430
|
+
console.log(chalk2.red("\n\u{1F6AB} PR BLOCKED: AI Readiness Check Failed"));
|
|
431
|
+
console.log(chalk2.red(` Reason: ${failReason}`));
|
|
432
|
+
console.log(chalk2.dim("\n Remediation steps:"));
|
|
433
|
+
console.log(chalk2.dim(" 1. Run `aiready scan` locally to see detailed issues"));
|
|
434
|
+
console.log(chalk2.dim(" 2. Fix the critical issues before merging"));
|
|
435
|
+
console.log(chalk2.dim(" 3. Consider upgrading to Team plan for historical tracking: https://getaiready.dev/pricing"));
|
|
391
436
|
process.exit(1);
|
|
392
437
|
} else {
|
|
393
|
-
console.log(
|
|
438
|
+
console.log(chalk2.green("\n\u2705 PR PASSED: AI Readiness Check"));
|
|
394
439
|
if (threshold) {
|
|
395
|
-
console.log(
|
|
440
|
+
console.log(chalk2.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
|
|
396
441
|
}
|
|
397
|
-
console.log(
|
|
442
|
+
console.log(chalk2.dim("\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"));
|
|
398
443
|
}
|
|
399
444
|
}
|
|
400
445
|
} catch (error) {
|
|
401
446
|
handleCLIError(error, "Analysis");
|
|
402
447
|
}
|
|
403
|
-
}
|
|
404
|
-
|
|
448
|
+
}
|
|
449
|
+
var scanHelpText = `
|
|
405
450
|
EXAMPLES:
|
|
406
|
-
$ aiready
|
|
407
|
-
$ aiready
|
|
408
|
-
$ aiready
|
|
409
|
-
|
|
410
|
-
|
|
451
|
+
$ aiready scan # Analyze all tools
|
|
452
|
+
$ aiready scan --tools patterns,context # Skip consistency
|
|
453
|
+
$ aiready scan --score --threshold 75 # CI/CD with threshold
|
|
454
|
+
$ aiready scan --ci --threshold 70 # GitHub Actions gatekeeper
|
|
455
|
+
$ aiready scan --ci --fail-on major # Fail on major+ issues
|
|
456
|
+
$ aiready scan --output json --output-file report.json
|
|
457
|
+
|
|
458
|
+
CI/CD INTEGRATION (Gatekeeper Mode):
|
|
459
|
+
Use --ci for GitHub Actions integration:
|
|
460
|
+
- Outputs GitHub Actions annotations for PR checks
|
|
461
|
+
- Fails with exit code 1 if threshold not met
|
|
462
|
+
- Shows clear "blocked" message with remediation steps
|
|
463
|
+
|
|
464
|
+
Example GitHub Actions workflow:
|
|
465
|
+
- name: AI Readiness Check
|
|
466
|
+
run: aiready scan --ci --threshold 70
|
|
467
|
+
`;
|
|
468
|
+
|
|
469
|
+
// src/commands/patterns.ts
|
|
470
|
+
import chalk3 from "chalk";
|
|
471
|
+
import { resolve as resolvePath3 } from "path";
|
|
472
|
+
import {
|
|
473
|
+
loadMergedConfig as loadMergedConfig2,
|
|
474
|
+
handleJSONOutput as handleJSONOutput2,
|
|
475
|
+
handleCLIError as handleCLIError2,
|
|
476
|
+
getElapsedTime as getElapsedTime2,
|
|
477
|
+
resolveOutputPath as resolveOutputPath2,
|
|
478
|
+
formatToolScore as formatToolScore2
|
|
479
|
+
} from "@aiready/core";
|
|
480
|
+
async function patternsAction(directory, options) {
|
|
481
|
+
console.log(chalk3.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
411
482
|
const startTime = Date.now();
|
|
412
|
-
const resolvedDir =
|
|
483
|
+
const resolvedDir = resolvePath3(process.cwd(), directory || ".");
|
|
413
484
|
try {
|
|
414
485
|
const useSmartDefaults = !options.fullScan;
|
|
415
486
|
const defaults = {
|
|
@@ -438,10 +509,10 @@ EXAMPLES:
|
|
|
438
509
|
if (options.minSharedTokens) {
|
|
439
510
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
440
511
|
}
|
|
441
|
-
const finalOptions = await
|
|
512
|
+
const finalOptions = await loadMergedConfig2(resolvedDir, defaults, cliOptions);
|
|
442
513
|
const { analyzePatterns, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
443
514
|
const { results, duplicates } = await analyzePatterns(finalOptions);
|
|
444
|
-
const elapsedTime =
|
|
515
|
+
const elapsedTime = getElapsedTime2(startTime);
|
|
445
516
|
const summary = generateSummary(results);
|
|
446
517
|
let patternScore;
|
|
447
518
|
if (options.score) {
|
|
@@ -455,66 +526,84 @@ EXAMPLES:
|
|
|
455
526
|
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
456
527
|
...patternScore && { scoring: patternScore }
|
|
457
528
|
};
|
|
458
|
-
const outputPath =
|
|
529
|
+
const outputPath = resolveOutputPath2(
|
|
459
530
|
userOutputFile,
|
|
460
531
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
461
532
|
resolvedDir
|
|
462
533
|
);
|
|
463
|
-
|
|
534
|
+
handleJSONOutput2(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
464
535
|
} else {
|
|
465
536
|
const terminalWidth = process.stdout.columns || 80;
|
|
466
537
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
467
538
|
const divider = "\u2501".repeat(dividerWidth);
|
|
468
|
-
console.log(
|
|
469
|
-
console.log(
|
|
470
|
-
console.log(
|
|
471
|
-
console.log(
|
|
472
|
-
console.log(
|
|
473
|
-
console.log(
|
|
474
|
-
console.log(
|
|
539
|
+
console.log(chalk3.cyan(divider));
|
|
540
|
+
console.log(chalk3.bold.white(" PATTERN ANALYSIS SUMMARY"));
|
|
541
|
+
console.log(chalk3.cyan(divider) + "\n");
|
|
542
|
+
console.log(chalk3.white(`\u{1F4C1} Files analyzed: ${chalk3.bold(results.length)}`));
|
|
543
|
+
console.log(chalk3.yellow(`\u26A0 Duplicate patterns found: ${chalk3.bold(summary.totalPatterns)}`));
|
|
544
|
+
console.log(chalk3.red(`\u{1F4B0} Token cost (wasted): ${chalk3.bold(summary.totalTokenCost.toLocaleString())}`));
|
|
545
|
+
console.log(chalk3.gray(`\u23F1 Analysis time: ${chalk3.bold(elapsedTime + "s")}`));
|
|
475
546
|
const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
476
547
|
if (sortedTypes.length > 0) {
|
|
477
|
-
console.log(
|
|
478
|
-
console.log(
|
|
479
|
-
console.log(
|
|
548
|
+
console.log(chalk3.cyan("\n" + divider));
|
|
549
|
+
console.log(chalk3.bold.white(" PATTERNS BY TYPE"));
|
|
550
|
+
console.log(chalk3.cyan(divider) + "\n");
|
|
480
551
|
sortedTypes.forEach(([type, count]) => {
|
|
481
|
-
console.log(` ${
|
|
552
|
+
console.log(` ${chalk3.white(type.padEnd(15))} ${chalk3.bold(count)}`);
|
|
482
553
|
});
|
|
483
554
|
}
|
|
484
555
|
if (summary.totalPatterns > 0 && duplicates.length > 0) {
|
|
485
|
-
console.log(
|
|
486
|
-
console.log(
|
|
487
|
-
console.log(
|
|
556
|
+
console.log(chalk3.cyan("\n" + divider));
|
|
557
|
+
console.log(chalk3.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
558
|
+
console.log(chalk3.cyan(divider) + "\n");
|
|
488
559
|
const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
|
|
489
560
|
topDuplicates.forEach((dup) => {
|
|
490
561
|
const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
|
|
491
562
|
const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
|
|
492
563
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
493
564
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
494
|
-
console.log(`${severityIcon} ${severity}: ${
|
|
495
|
-
console.log(` Similarity: ${
|
|
496
|
-
console.log(` Lines: ${
|
|
565
|
+
console.log(`${severityIcon} ${severity}: ${chalk3.bold(file1Name)} \u2194 ${chalk3.bold(file2Name)}`);
|
|
566
|
+
console.log(` Similarity: ${chalk3.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${chalk3.bold(dup.tokenCost.toLocaleString())} tokens each`);
|
|
567
|
+
console.log(` Lines: ${chalk3.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${chalk3.cyan(dup.line2 + "-" + dup.endLine2)}
|
|
497
568
|
`);
|
|
498
569
|
});
|
|
499
570
|
} else {
|
|
500
|
-
console.log(
|
|
571
|
+
console.log(chalk3.green("\n\u2728 Great! No duplicate patterns detected.\n"));
|
|
501
572
|
}
|
|
502
573
|
if (patternScore) {
|
|
503
|
-
console.log(
|
|
504
|
-
console.log(
|
|
505
|
-
console.log(
|
|
506
|
-
console.log(
|
|
574
|
+
console.log(chalk3.cyan(divider));
|
|
575
|
+
console.log(chalk3.bold.white(" AI READINESS SCORE (Patterns)"));
|
|
576
|
+
console.log(chalk3.cyan(divider) + "\n");
|
|
577
|
+
console.log(formatToolScore2(patternScore));
|
|
507
578
|
console.log();
|
|
508
579
|
}
|
|
509
580
|
}
|
|
510
581
|
} catch (error) {
|
|
511
|
-
|
|
582
|
+
handleCLIError2(error, "Pattern analysis");
|
|
512
583
|
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
|
|
584
|
+
}
|
|
585
|
+
var patternsHelpText = `
|
|
586
|
+
EXAMPLES:
|
|
587
|
+
$ aiready patterns # Default analysis
|
|
588
|
+
$ aiready patterns --similarity 0.6 # Stricter matching
|
|
589
|
+
$ aiready patterns --min-lines 10 # Larger patterns only
|
|
590
|
+
`;
|
|
591
|
+
|
|
592
|
+
// src/commands/context.ts
|
|
593
|
+
import chalk4 from "chalk";
|
|
594
|
+
import { resolve as resolvePath4 } from "path";
|
|
595
|
+
import {
|
|
596
|
+
loadMergedConfig as loadMergedConfig3,
|
|
597
|
+
handleJSONOutput as handleJSONOutput3,
|
|
598
|
+
handleCLIError as handleCLIError3,
|
|
599
|
+
getElapsedTime as getElapsedTime3,
|
|
600
|
+
resolveOutputPath as resolveOutputPath3,
|
|
601
|
+
formatToolScore as formatToolScore3
|
|
602
|
+
} from "@aiready/core";
|
|
603
|
+
async function contextAction(directory, options) {
|
|
604
|
+
console.log(chalk4.blue("\u{1F9E0} Analyzing context costs...\n"));
|
|
516
605
|
const startTime = Date.now();
|
|
517
|
-
const resolvedDir =
|
|
606
|
+
const resolvedDir = resolvePath4(process.cwd(), directory || ".");
|
|
518
607
|
try {
|
|
519
608
|
const defaults = {
|
|
520
609
|
maxDepth: 5,
|
|
@@ -526,7 +615,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
526
615
|
file: void 0
|
|
527
616
|
}
|
|
528
617
|
};
|
|
529
|
-
let baseOptions = await
|
|
618
|
+
let baseOptions = await loadMergedConfig3(resolvedDir, defaults, {
|
|
530
619
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
531
620
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
532
621
|
include: options.include?.split(","),
|
|
@@ -545,7 +634,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
545
634
|
console.log("");
|
|
546
635
|
const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
|
|
547
636
|
const results = await analyzeContext(finalOptions);
|
|
548
|
-
const elapsedTime =
|
|
637
|
+
const elapsedTime = getElapsedTime3(startTime);
|
|
549
638
|
const summary = generateSummary(results);
|
|
550
639
|
let contextScore;
|
|
551
640
|
if (options.score) {
|
|
@@ -559,100 +648,113 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
559
648
|
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
560
649
|
...contextScore && { scoring: contextScore }
|
|
561
650
|
};
|
|
562
|
-
const outputPath =
|
|
651
|
+
const outputPath = resolveOutputPath3(
|
|
563
652
|
userOutputFile,
|
|
564
653
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
565
654
|
resolvedDir
|
|
566
655
|
);
|
|
567
|
-
|
|
656
|
+
handleJSONOutput3(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
568
657
|
} else {
|
|
569
658
|
const terminalWidth = process.stdout.columns || 80;
|
|
570
659
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
571
660
|
const divider = "\u2501".repeat(dividerWidth);
|
|
572
|
-
console.log(
|
|
573
|
-
console.log(
|
|
574
|
-
console.log(
|
|
575
|
-
console.log(
|
|
576
|
-
console.log(
|
|
577
|
-
console.log(
|
|
578
|
-
console.log(
|
|
661
|
+
console.log(chalk4.cyan(divider));
|
|
662
|
+
console.log(chalk4.bold.white(" CONTEXT ANALYSIS SUMMARY"));
|
|
663
|
+
console.log(chalk4.cyan(divider) + "\n");
|
|
664
|
+
console.log(chalk4.white(`\u{1F4C1} Files analyzed: ${chalk4.bold(summary.totalFiles)}`));
|
|
665
|
+
console.log(chalk4.white(`\u{1F4CA} Total tokens: ${chalk4.bold(summary.totalTokens.toLocaleString())}`));
|
|
666
|
+
console.log(chalk4.yellow(`\u{1F4B0} Avg context budget: ${chalk4.bold(summary.avgContextBudget.toFixed(0))} tokens/file`));
|
|
667
|
+
console.log(chalk4.white(`\u23F1 Analysis time: ${chalk4.bold(elapsedTime + "s")}
|
|
579
668
|
`));
|
|
580
669
|
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
581
670
|
if (totalIssues > 0) {
|
|
582
|
-
console.log(
|
|
671
|
+
console.log(chalk4.bold("\u26A0\uFE0F Issues Found:\n"));
|
|
583
672
|
if (summary.criticalIssues > 0) {
|
|
584
|
-
console.log(
|
|
673
|
+
console.log(chalk4.red(` \u{1F534} Critical: ${chalk4.bold(summary.criticalIssues)}`));
|
|
585
674
|
}
|
|
586
675
|
if (summary.majorIssues > 0) {
|
|
587
|
-
console.log(
|
|
676
|
+
console.log(chalk4.yellow(` \u{1F7E1} Major: ${chalk4.bold(summary.majorIssues)}`));
|
|
588
677
|
}
|
|
589
678
|
if (summary.minorIssues > 0) {
|
|
590
|
-
console.log(
|
|
679
|
+
console.log(chalk4.blue(` \u{1F535} Minor: ${chalk4.bold(summary.minorIssues)}`));
|
|
591
680
|
}
|
|
592
|
-
console.log(
|
|
593
|
-
\u{1F4A1} Potential savings: ${
|
|
681
|
+
console.log(chalk4.green(`
|
|
682
|
+
\u{1F4A1} Potential savings: ${chalk4.bold(summary.totalPotentialSavings.toLocaleString())} tokens
|
|
594
683
|
`));
|
|
595
684
|
} else {
|
|
596
|
-
console.log(
|
|
685
|
+
console.log(chalk4.green("\u2705 No significant issues found!\n"));
|
|
597
686
|
}
|
|
598
687
|
if (summary.deepFiles.length > 0) {
|
|
599
|
-
console.log(
|
|
600
|
-
console.log(
|
|
601
|
-
console.log(
|
|
688
|
+
console.log(chalk4.bold("\u{1F4CF} Deep Import Chains:\n"));
|
|
689
|
+
console.log(chalk4.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`));
|
|
690
|
+
console.log(chalk4.gray(` Maximum depth: ${summary.maxImportDepth}
|
|
602
691
|
`));
|
|
603
692
|
summary.deepFiles.slice(0, 10).forEach((item) => {
|
|
604
693
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
605
|
-
console.log(` ${
|
|
694
|
+
console.log(` ${chalk4.cyan("\u2192")} ${chalk4.white(fileName)} ${chalk4.dim(`(depth: ${item.depth})`)}`);
|
|
606
695
|
});
|
|
607
696
|
console.log();
|
|
608
697
|
}
|
|
609
698
|
if (summary.fragmentedModules.length > 0) {
|
|
610
|
-
console.log(
|
|
611
|
-
console.log(
|
|
699
|
+
console.log(chalk4.bold("\u{1F9E9} Fragmented Modules:\n"));
|
|
700
|
+
console.log(chalk4.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
|
|
612
701
|
`));
|
|
613
702
|
summary.fragmentedModules.slice(0, 10).forEach((module) => {
|
|
614
|
-
console.log(` ${
|
|
615
|
-
console.log(
|
|
703
|
+
console.log(` ${chalk4.yellow("\u25CF")} ${chalk4.white(module.domain)} - ${chalk4.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`);
|
|
704
|
+
console.log(chalk4.dim(` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`));
|
|
616
705
|
});
|
|
617
706
|
console.log();
|
|
618
707
|
}
|
|
619
708
|
if (summary.lowCohesionFiles.length > 0) {
|
|
620
|
-
console.log(
|
|
621
|
-
console.log(
|
|
709
|
+
console.log(chalk4.bold("\u{1F500} Low Cohesion Files:\n"));
|
|
710
|
+
console.log(chalk4.gray(` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
|
|
622
711
|
`));
|
|
623
712
|
summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
|
|
624
713
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
625
714
|
const scorePercent = (item.score * 100).toFixed(0);
|
|
626
|
-
const color = item.score < 0.4 ?
|
|
627
|
-
console.log(` ${color("\u25CB")} ${
|
|
715
|
+
const color = item.score < 0.4 ? chalk4.red : chalk4.yellow;
|
|
716
|
+
console.log(` ${color("\u25CB")} ${chalk4.white(fileName)} ${chalk4.dim(`(${scorePercent}% cohesion)`)}`);
|
|
628
717
|
});
|
|
629
718
|
console.log();
|
|
630
719
|
}
|
|
631
720
|
if (summary.topExpensiveFiles.length > 0) {
|
|
632
|
-
console.log(
|
|
721
|
+
console.log(chalk4.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
|
|
633
722
|
summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
|
|
634
723
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
635
|
-
const severityColor = item.severity === "critical" ?
|
|
636
|
-
console.log(` ${severityColor("\u25CF")} ${
|
|
724
|
+
const severityColor = item.severity === "critical" ? chalk4.red : item.severity === "major" ? chalk4.yellow : chalk4.blue;
|
|
725
|
+
console.log(` ${severityColor("\u25CF")} ${chalk4.white(fileName)} ${chalk4.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`);
|
|
637
726
|
});
|
|
638
727
|
console.log();
|
|
639
728
|
}
|
|
640
729
|
if (contextScore) {
|
|
641
|
-
console.log(
|
|
642
|
-
console.log(
|
|
643
|
-
console.log(
|
|
644
|
-
console.log(
|
|
730
|
+
console.log(chalk4.cyan(divider));
|
|
731
|
+
console.log(chalk4.bold.white(" AI READINESS SCORE (Context)"));
|
|
732
|
+
console.log(chalk4.cyan(divider) + "\n");
|
|
733
|
+
console.log(formatToolScore3(contextScore));
|
|
645
734
|
console.log();
|
|
646
735
|
}
|
|
647
736
|
}
|
|
648
737
|
} catch (error) {
|
|
649
|
-
|
|
738
|
+
handleCLIError3(error, "Context analysis");
|
|
650
739
|
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/commands/consistency.ts
|
|
743
|
+
import chalk5 from "chalk";
|
|
744
|
+
import { writeFileSync } from "fs";
|
|
745
|
+
import { resolve as resolvePath5 } from "path";
|
|
746
|
+
import {
|
|
747
|
+
loadMergedConfig as loadMergedConfig4,
|
|
748
|
+
handleJSONOutput as handleJSONOutput4,
|
|
749
|
+
handleCLIError as handleCLIError4,
|
|
750
|
+
getElapsedTime as getElapsedTime4,
|
|
751
|
+
resolveOutputPath as resolveOutputPath4,
|
|
752
|
+
formatToolScore as formatToolScore4
|
|
753
|
+
} from "@aiready/core";
|
|
754
|
+
async function consistencyAction(directory, options) {
|
|
755
|
+
console.log(chalk5.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
654
756
|
const startTime = Date.now();
|
|
655
|
-
const resolvedDir =
|
|
757
|
+
const resolvedDir = resolvePath5(process.cwd(), directory || ".");
|
|
656
758
|
try {
|
|
657
759
|
const defaults = {
|
|
658
760
|
checkNaming: true,
|
|
@@ -665,7 +767,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
665
767
|
file: void 0
|
|
666
768
|
}
|
|
667
769
|
};
|
|
668
|
-
const finalOptions = await
|
|
770
|
+
const finalOptions = await loadMergedConfig4(resolvedDir, defaults, {
|
|
669
771
|
checkNaming: options.naming !== false,
|
|
670
772
|
checkPatterns: options.patterns !== false,
|
|
671
773
|
minSeverity: options.minSeverity,
|
|
@@ -674,7 +776,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
674
776
|
});
|
|
675
777
|
const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
|
|
676
778
|
const report = await analyzeConsistency(finalOptions);
|
|
677
|
-
const elapsedTime =
|
|
779
|
+
const elapsedTime = getElapsedTime4(startTime);
|
|
678
780
|
let consistencyScore;
|
|
679
781
|
if (options.score) {
|
|
680
782
|
const issues = report.results?.flatMap((r) => r.issues) || [];
|
|
@@ -691,32 +793,32 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
691
793
|
},
|
|
692
794
|
...consistencyScore && { scoring: consistencyScore }
|
|
693
795
|
};
|
|
694
|
-
const outputPath =
|
|
796
|
+
const outputPath = resolveOutputPath4(
|
|
695
797
|
userOutputFile,
|
|
696
798
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
697
799
|
resolvedDir
|
|
698
800
|
);
|
|
699
|
-
|
|
801
|
+
handleJSONOutput4(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
700
802
|
} else if (outputFormat === "markdown") {
|
|
701
803
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
702
|
-
const outputPath =
|
|
804
|
+
const outputPath = resolveOutputPath4(
|
|
703
805
|
userOutputFile,
|
|
704
806
|
`aiready-report-${getReportTimestamp()}.md`,
|
|
705
807
|
resolvedDir
|
|
706
808
|
);
|
|
707
809
|
writeFileSync(outputPath, markdown);
|
|
708
|
-
console.log(
|
|
810
|
+
console.log(chalk5.green(`\u2705 Report saved to ${outputPath}`));
|
|
709
811
|
} else {
|
|
710
|
-
console.log(
|
|
711
|
-
console.log(`Files Analyzed: ${
|
|
712
|
-
console.log(`Total Issues: ${
|
|
713
|
-
console.log(` Naming: ${
|
|
714
|
-
console.log(` Patterns: ${
|
|
715
|
-
console.log(` Architecture: ${
|
|
716
|
-
console.log(`Analysis Time: ${
|
|
812
|
+
console.log(chalk5.bold("\n\u{1F4CA} Summary\n"));
|
|
813
|
+
console.log(`Files Analyzed: ${chalk5.cyan(report.summary.filesAnalyzed)}`);
|
|
814
|
+
console.log(`Total Issues: ${chalk5.yellow(report.summary.totalIssues)}`);
|
|
815
|
+
console.log(` Naming: ${chalk5.yellow(report.summary.namingIssues)}`);
|
|
816
|
+
console.log(` Patterns: ${chalk5.yellow(report.summary.patternIssues)}`);
|
|
817
|
+
console.log(` Architecture: ${chalk5.yellow(report.summary.architectureIssues || 0)}`);
|
|
818
|
+
console.log(`Analysis Time: ${chalk5.gray(elapsedTime + "s")}
|
|
717
819
|
`);
|
|
718
820
|
if (report.summary.totalIssues === 0) {
|
|
719
|
-
console.log(
|
|
821
|
+
console.log(chalk5.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
|
|
720
822
|
} else {
|
|
721
823
|
const namingResults = report.results.filter(
|
|
722
824
|
(r) => r.issues.some((i) => i.category === "naming")
|
|
@@ -725,17 +827,17 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
725
827
|
(r) => r.issues.some((i) => i.category === "patterns")
|
|
726
828
|
);
|
|
727
829
|
if (namingResults.length > 0) {
|
|
728
|
-
console.log(
|
|
830
|
+
console.log(chalk5.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
|
|
729
831
|
let shown = 0;
|
|
730
832
|
for (const result of namingResults) {
|
|
731
833
|
if (shown >= 5) break;
|
|
732
834
|
for (const issue of result.issues) {
|
|
733
835
|
if (shown >= 5) break;
|
|
734
|
-
const severityColor = issue.severity === "critical" ?
|
|
735
|
-
console.log(`${severityColor(issue.severity.toUpperCase())} ${
|
|
836
|
+
const severityColor = issue.severity === "critical" ? chalk5.red : issue.severity === "major" ? chalk5.yellow : issue.severity === "minor" ? chalk5.blue : chalk5.gray;
|
|
837
|
+
console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk5.dim(`${issue.location.file}:${issue.location.line}`)}`);
|
|
736
838
|
console.log(` ${issue.message}`);
|
|
737
839
|
if (issue.suggestion) {
|
|
738
|
-
console.log(` ${
|
|
840
|
+
console.log(` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`);
|
|
739
841
|
}
|
|
740
842
|
console.log();
|
|
741
843
|
shown++;
|
|
@@ -743,22 +845,22 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
743
845
|
}
|
|
744
846
|
const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
745
847
|
if (remaining > 0) {
|
|
746
|
-
console.log(
|
|
848
|
+
console.log(chalk5.dim(` ... and ${remaining} more issues
|
|
747
849
|
`));
|
|
748
850
|
}
|
|
749
851
|
}
|
|
750
852
|
if (patternResults.length > 0) {
|
|
751
|
-
console.log(
|
|
853
|
+
console.log(chalk5.bold("\u{1F504} Pattern Issues\n"));
|
|
752
854
|
let shown = 0;
|
|
753
855
|
for (const result of patternResults) {
|
|
754
856
|
if (shown >= 5) break;
|
|
755
857
|
for (const issue of result.issues) {
|
|
756
858
|
if (shown >= 5) break;
|
|
757
|
-
const severityColor = issue.severity === "critical" ?
|
|
758
|
-
console.log(`${severityColor(issue.severity.toUpperCase())} ${
|
|
859
|
+
const severityColor = issue.severity === "critical" ? chalk5.red : issue.severity === "major" ? chalk5.yellow : issue.severity === "minor" ? chalk5.blue : chalk5.gray;
|
|
860
|
+
console.log(`${severityColor(issue.severity.toUpperCase())} ${chalk5.dim(`${issue.location.file}:${issue.location.line}`)}`);
|
|
759
861
|
console.log(` ${issue.message}`);
|
|
760
862
|
if (issue.suggestion) {
|
|
761
|
-
console.log(` ${
|
|
863
|
+
console.log(` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`);
|
|
762
864
|
}
|
|
763
865
|
console.log();
|
|
764
866
|
shown++;
|
|
@@ -766,12 +868,12 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
766
868
|
}
|
|
767
869
|
const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
768
870
|
if (remaining > 0) {
|
|
769
|
-
console.log(
|
|
871
|
+
console.log(chalk5.dim(` ... and ${remaining} more issues
|
|
770
872
|
`));
|
|
771
873
|
}
|
|
772
874
|
}
|
|
773
875
|
if (report.recommendations.length > 0) {
|
|
774
|
-
console.log(
|
|
876
|
+
console.log(chalk5.bold("\u{1F4A1} Recommendations\n"));
|
|
775
877
|
report.recommendations.forEach((rec, i) => {
|
|
776
878
|
console.log(`${i + 1}. ${rec}`);
|
|
777
879
|
});
|
|
@@ -779,124 +881,35 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
779
881
|
}
|
|
780
882
|
}
|
|
781
883
|
if (consistencyScore) {
|
|
782
|
-
console.log(
|
|
783
|
-
console.log(
|
|
884
|
+
console.log(chalk5.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
|
|
885
|
+
console.log(formatToolScore4(consistencyScore));
|
|
784
886
|
console.log();
|
|
785
887
|
}
|
|
786
888
|
}
|
|
787
889
|
} catch (error) {
|
|
788
|
-
|
|
890
|
+
handleCLIError4(error, "Consistency analysis");
|
|
789
891
|
}
|
|
790
|
-
});
|
|
791
|
-
function generateMarkdownReport(report, elapsedTime) {
|
|
792
|
-
let markdown = `# Consistency Analysis Report
|
|
793
|
-
|
|
794
|
-
`;
|
|
795
|
-
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
796
|
-
`;
|
|
797
|
-
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
798
|
-
|
|
799
|
-
`;
|
|
800
|
-
markdown += `## Summary
|
|
801
|
-
|
|
802
|
-
`;
|
|
803
|
-
markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
|
|
804
|
-
`;
|
|
805
|
-
markdown += `- **Total Issues:** ${report.summary.totalIssues}
|
|
806
|
-
`;
|
|
807
|
-
markdown += ` - Naming: ${report.summary.namingIssues}
|
|
808
|
-
`;
|
|
809
|
-
markdown += ` - Patterns: ${report.summary.patternIssues}
|
|
810
|
-
|
|
811
|
-
`;
|
|
812
|
-
if (report.recommendations.length > 0) {
|
|
813
|
-
markdown += `## Recommendations
|
|
814
|
-
|
|
815
|
-
`;
|
|
816
|
-
report.recommendations.forEach((rec, i) => {
|
|
817
|
-
markdown += `${i + 1}. ${rec}
|
|
818
|
-
`;
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
return markdown;
|
|
822
892
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
const { readdirSync, statSync } = __require("fs");
|
|
834
|
-
let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
|
|
835
|
-
if (files.length === 0) {
|
|
836
|
-
files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
|
|
837
|
-
}
|
|
838
|
-
if (files.length === 0) {
|
|
839
|
-
return null;
|
|
840
|
-
}
|
|
841
|
-
const sortedFiles = files.map((f) => ({ name: f, path: resolvePath(aireadyDir, f), mtime: statSync(resolvePath(aireadyDir, f)).mtime })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
842
|
-
return sortedFiles[0].path;
|
|
843
|
-
}
|
|
844
|
-
function warnIfGraphCapExceeded(report, dirPath) {
|
|
845
|
-
try {
|
|
846
|
-
const { loadConfig } = __require("@aiready/core");
|
|
847
|
-
const { existsSync: existsSync2, readFileSync: readFileSync2 } = __require("fs");
|
|
848
|
-
const { resolve } = __require("path");
|
|
849
|
-
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
850
|
-
const configPath = resolve(dirPath, "aiready.json");
|
|
851
|
-
if (existsSync2(configPath)) {
|
|
852
|
-
try {
|
|
853
|
-
const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
854
|
-
if (rawConfig.visualizer?.graph) {
|
|
855
|
-
graphConfig = {
|
|
856
|
-
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
857
|
-
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
} catch (e) {
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
|
|
864
|
-
const edgeCount = report.context?.reduce((sum, ctx) => {
|
|
865
|
-
const relCount = ctx.relatedFiles?.length || 0;
|
|
866
|
-
const depCount = ctx.dependencies?.length || 0;
|
|
867
|
-
return sum + relCount + depCount;
|
|
868
|
-
}, 0) || 0;
|
|
869
|
-
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
870
|
-
console.log("");
|
|
871
|
-
console.log(chalk.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
|
|
872
|
-
if (nodeCount > graphConfig.maxNodes) {
|
|
873
|
-
console.log(chalk.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
|
|
874
|
-
}
|
|
875
|
-
if (edgeCount > graphConfig.maxEdges) {
|
|
876
|
-
console.log(chalk.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
|
|
877
|
-
}
|
|
878
|
-
console.log(chalk.dim(` To increase limits, add to aiready.json:`));
|
|
879
|
-
console.log(chalk.dim(` {`));
|
|
880
|
-
console.log(chalk.dim(` "visualizer": {`));
|
|
881
|
-
console.log(chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
|
|
882
|
-
console.log(chalk.dim(` }`));
|
|
883
|
-
console.log(chalk.dim(` }`));
|
|
884
|
-
}
|
|
885
|
-
} catch (e) {
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
async function handleVisualize(directory, options) {
|
|
893
|
+
|
|
894
|
+
// src/commands/visualize.ts
|
|
895
|
+
import chalk6 from "chalk";
|
|
896
|
+
import { writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, copyFileSync as copyFileSync2 } from "fs";
|
|
897
|
+
import { resolve as resolvePath6 } from "path";
|
|
898
|
+
import { spawn } from "child_process";
|
|
899
|
+
import { handleCLIError as handleCLIError5 } from "@aiready/core";
|
|
900
|
+
import { generateHTML } from "@aiready/core";
|
|
901
|
+
async function visualizeAction(directory, options) {
|
|
889
902
|
try {
|
|
890
|
-
const dirPath =
|
|
891
|
-
let reportPath = options.report ?
|
|
892
|
-
if (!reportPath || !
|
|
903
|
+
const dirPath = resolvePath6(process.cwd(), directory || ".");
|
|
904
|
+
let reportPath = options.report ? resolvePath6(dirPath, options.report) : null;
|
|
905
|
+
if (!reportPath || !existsSync2(reportPath)) {
|
|
893
906
|
const latestScan = findLatestScanReport(dirPath);
|
|
894
907
|
if (latestScan) {
|
|
895
908
|
reportPath = latestScan;
|
|
896
|
-
console.log(
|
|
909
|
+
console.log(chalk6.dim(`Found latest report: ${latestScan.split("/").pop()}`));
|
|
897
910
|
} else {
|
|
898
|
-
console.error(
|
|
899
|
-
console.log(
|
|
911
|
+
console.error(chalk6.red("\u274C No AI readiness report found"));
|
|
912
|
+
console.log(chalk6.dim(`
|
|
900
913
|
Generate a report with:
|
|
901
914
|
aiready scan --output json
|
|
902
915
|
|
|
@@ -905,13 +918,13 @@ Or specify a custom report:
|
|
|
905
918
|
return;
|
|
906
919
|
}
|
|
907
920
|
}
|
|
908
|
-
const raw =
|
|
921
|
+
const raw = readFileSync2(reportPath, "utf8");
|
|
909
922
|
const report = JSON.parse(raw);
|
|
910
|
-
const configPath =
|
|
923
|
+
const configPath = resolvePath6(dirPath, "aiready.json");
|
|
911
924
|
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
912
|
-
if (
|
|
925
|
+
if (existsSync2(configPath)) {
|
|
913
926
|
try {
|
|
914
|
-
const rawConfig = JSON.parse(
|
|
927
|
+
const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
915
928
|
if (rawConfig.visualizer?.graph) {
|
|
916
929
|
graphConfig = {
|
|
917
930
|
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
@@ -928,27 +941,26 @@ Or specify a custom report:
|
|
|
928
941
|
const graph = GraphBuilder.buildFromReport(report, dirPath);
|
|
929
942
|
if (options.dev) {
|
|
930
943
|
try {
|
|
931
|
-
const
|
|
932
|
-
const monorepoWebDir = resolvePath(dirPath, "packages/visualizer");
|
|
944
|
+
const monorepoWebDir = resolvePath6(dirPath, "packages/visualizer");
|
|
933
945
|
let webDir = "";
|
|
934
946
|
let visualizerAvailable = false;
|
|
935
|
-
if (
|
|
947
|
+
if (existsSync2(monorepoWebDir)) {
|
|
936
948
|
webDir = monorepoWebDir;
|
|
937
949
|
visualizerAvailable = true;
|
|
938
950
|
} else {
|
|
939
951
|
const nodemodulesLocations = [
|
|
940
|
-
|
|
941
|
-
|
|
952
|
+
resolvePath6(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
953
|
+
resolvePath6(process.cwd(), "node_modules", "@aiready", "visualizer")
|
|
942
954
|
];
|
|
943
955
|
let currentDir = dirPath;
|
|
944
956
|
while (currentDir !== "/" && currentDir !== ".") {
|
|
945
|
-
nodemodulesLocations.push(
|
|
946
|
-
const parent =
|
|
957
|
+
nodemodulesLocations.push(resolvePath6(currentDir, "node_modules", "@aiready", "visualizer"));
|
|
958
|
+
const parent = resolvePath6(currentDir, "..");
|
|
947
959
|
if (parent === currentDir) break;
|
|
948
960
|
currentDir = parent;
|
|
949
961
|
}
|
|
950
962
|
for (const location of nodemodulesLocations) {
|
|
951
|
-
if (
|
|
963
|
+
if (existsSync2(location) && existsSync2(resolvePath6(location, "package.json"))) {
|
|
952
964
|
webDir = location;
|
|
953
965
|
visualizerAvailable = true;
|
|
954
966
|
break;
|
|
@@ -957,7 +969,7 @@ Or specify a custom report:
|
|
|
957
969
|
if (!visualizerAvailable) {
|
|
958
970
|
try {
|
|
959
971
|
const vizPkgPath = __require.resolve("@aiready/visualizer/package.json");
|
|
960
|
-
webDir =
|
|
972
|
+
webDir = resolvePath6(vizPkgPath, "..");
|
|
961
973
|
visualizerAvailable = true;
|
|
962
974
|
} catch (e) {
|
|
963
975
|
}
|
|
@@ -965,17 +977,17 @@ Or specify a custom report:
|
|
|
965
977
|
}
|
|
966
978
|
const spawnCwd = webDir || process.cwd();
|
|
967
979
|
const nodeBinCandidate = process.execPath;
|
|
968
|
-
const nodeBin =
|
|
980
|
+
const nodeBin = existsSync2(nodeBinCandidate) ? nodeBinCandidate : "node";
|
|
969
981
|
if (!visualizerAvailable) {
|
|
970
|
-
console.error(
|
|
971
|
-
console.log(
|
|
982
|
+
console.error(chalk6.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
|
|
983
|
+
console.log(chalk6.dim("Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer"));
|
|
972
984
|
return;
|
|
973
985
|
}
|
|
974
986
|
const { watch } = await import("fs");
|
|
975
987
|
const copyReportToViz = () => {
|
|
976
988
|
try {
|
|
977
|
-
const destPath =
|
|
978
|
-
|
|
989
|
+
const destPath = resolvePath6(spawnCwd, "web", "report-data.json");
|
|
990
|
+
copyFileSync2(reportPath, destPath);
|
|
979
991
|
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
980
992
|
} catch (e) {
|
|
981
993
|
console.error("Failed to sync report:", e);
|
|
@@ -1013,20 +1025,18 @@ Or specify a custom report:
|
|
|
1013
1025
|
}
|
|
1014
1026
|
console.log("Generating HTML...");
|
|
1015
1027
|
const html = generateHTML(graph);
|
|
1016
|
-
const outPath =
|
|
1017
|
-
|
|
1028
|
+
const outPath = resolvePath6(dirPath, options.output || "packages/visualizer/visualization.html");
|
|
1029
|
+
writeFileSync2(outPath, html, "utf8");
|
|
1018
1030
|
console.log("Visualization written to:", outPath);
|
|
1019
1031
|
if (options.open) {
|
|
1020
|
-
const { exec } = await import("child_process");
|
|
1021
1032
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1022
|
-
|
|
1033
|
+
spawn(opener, [`"${outPath}"`], { shell: true });
|
|
1023
1034
|
}
|
|
1024
1035
|
if (options.serve) {
|
|
1025
1036
|
try {
|
|
1026
1037
|
const port = typeof options.serve === "number" ? options.serve : 5173;
|
|
1027
1038
|
const http = await import("http");
|
|
1028
1039
|
const fsp = await import("fs/promises");
|
|
1029
|
-
const { exec } = await import("child_process");
|
|
1030
1040
|
const server = http.createServer(async (req, res) => {
|
|
1031
1041
|
try {
|
|
1032
1042
|
const urlPath = req.url || "/";
|
|
@@ -1047,7 +1057,7 @@ Or specify a custom report:
|
|
|
1047
1057
|
const addr = `http://localhost:${port}/`;
|
|
1048
1058
|
console.log(`Local visualization server running at ${addr}`);
|
|
1049
1059
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1050
|
-
|
|
1060
|
+
spawn(opener, [`"${addr}"`], { shell: true });
|
|
1051
1061
|
});
|
|
1052
1062
|
process.on("SIGINT", () => {
|
|
1053
1063
|
server.close();
|
|
@@ -1058,20 +1068,10 @@ Or specify a custom report:
|
|
|
1058
1068
|
}
|
|
1059
1069
|
}
|
|
1060
1070
|
} catch (err) {
|
|
1061
|
-
|
|
1071
|
+
handleCLIError5(err, "Visualization");
|
|
1062
1072
|
}
|
|
1063
1073
|
}
|
|
1064
|
-
|
|
1065
|
-
EXAMPLES:
|
|
1066
|
-
$ aiready visualise . # Auto-detects latest report
|
|
1067
|
-
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1068
|
-
$ aiready visualise . --report report.json --dev
|
|
1069
|
-
$ aiready visualise . --report report.json --serve 8080
|
|
1070
|
-
|
|
1071
|
-
NOTES:
|
|
1072
|
-
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
1073
|
-
`).action(async (directory, options) => await handleVisualize(directory, options));
|
|
1074
|
-
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", `
|
|
1074
|
+
var visualizeHelpText = `
|
|
1075
1075
|
EXAMPLES:
|
|
1076
1076
|
$ aiready visualize . # Auto-detects latest report
|
|
1077
1077
|
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
@@ -1090,5 +1090,73 @@ NOTES:
|
|
|
1090
1090
|
reduce clutter and improve interactivity on large graphs.
|
|
1091
1091
|
- For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
|
|
1092
1092
|
allow the browser a moment to stabilize after load.
|
|
1093
|
-
|
|
1093
|
+
`;
|
|
1094
|
+
var visualiseHelpText = `
|
|
1095
|
+
EXAMPLES:
|
|
1096
|
+
$ aiready visualise . # Auto-detects latest report
|
|
1097
|
+
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1098
|
+
$ aiready visualise . --report report.json --dev
|
|
1099
|
+
$ aiready visualise . --report report.json --serve 8080
|
|
1100
|
+
|
|
1101
|
+
NOTES:
|
|
1102
|
+
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
1103
|
+
`;
|
|
1104
|
+
|
|
1105
|
+
// src/cli.ts
|
|
1106
|
+
var packageJson = JSON.parse(readFileSync3(join(__dirname, "../package.json"), "utf8"));
|
|
1107
|
+
var program = new Command();
|
|
1108
|
+
program.name("aiready").description("AIReady - Assess and improve AI-readiness of codebases").version(packageJson.version).addHelpText("after", `
|
|
1109
|
+
AI READINESS SCORING:
|
|
1110
|
+
Get a 0-100 score indicating how AI-ready your codebase is.
|
|
1111
|
+
Use --score flag with any analysis command for detailed breakdown.
|
|
1112
|
+
|
|
1113
|
+
EXAMPLES:
|
|
1114
|
+
$ aiready scan # Quick analysis of current directory
|
|
1115
|
+
$ aiready scan --score # Get AI Readiness Score (0-100)
|
|
1116
|
+
$ aiready scan --tools patterns # Run only pattern detection
|
|
1117
|
+
$ aiready patterns --similarity 0.6 # Custom similarity threshold
|
|
1118
|
+
$ aiready scan --output json --output-file results.json
|
|
1119
|
+
|
|
1120
|
+
GETTING STARTED:
|
|
1121
|
+
1. Run 'aiready scan' to analyze your codebase
|
|
1122
|
+
2. Use 'aiready scan --score' for AI readiness assessment
|
|
1123
|
+
3. Create aiready.json for persistent configuration
|
|
1124
|
+
4. Set up CI/CD with '--threshold' for quality gates
|
|
1125
|
+
|
|
1126
|
+
CONFIGURATION:
|
|
1127
|
+
Config files (searched upward): aiready.json, .aiready.json, aiready.config.*
|
|
1128
|
+
CLI options override config file settings
|
|
1129
|
+
|
|
1130
|
+
Example aiready.json:
|
|
1131
|
+
{
|
|
1132
|
+
"scan": { "exclude": ["**/dist/**", "**/node_modules/**"] },
|
|
1133
|
+
"tools": {
|
|
1134
|
+
"pattern-detect": { "minSimilarity": 0.5 },
|
|
1135
|
+
"context-analyzer": { "maxContextBudget": 15000 }
|
|
1136
|
+
},
|
|
1137
|
+
"output": { "format": "json", "directory": ".aiready" }
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
VERSION: ${packageJson.version}
|
|
1141
|
+
DOCUMENTATION: https://aiready.dev/docs/cli
|
|
1142
|
+
GITHUB: https://github.com/caopengau/aiready-cli
|
|
1143
|
+
LANDING: https://github.com/caopengau/aiready-landing`);
|
|
1144
|
+
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) => {
|
|
1145
|
+
await scanAction(directory, options);
|
|
1146
|
+
});
|
|
1147
|
+
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) => {
|
|
1148
|
+
await patternsAction(directory, options);
|
|
1149
|
+
});
|
|
1150
|
+
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) => {
|
|
1151
|
+
await contextAction(directory, options);
|
|
1152
|
+
});
|
|
1153
|
+
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) => {
|
|
1154
|
+
await consistencyAction(directory, options);
|
|
1155
|
+
});
|
|
1156
|
+
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) => {
|
|
1157
|
+
await visualizeAction(directory, options);
|
|
1158
|
+
});
|
|
1159
|
+
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) => {
|
|
1160
|
+
await visualizeAction(directory, options);
|
|
1161
|
+
});
|
|
1094
1162
|
program.parse();
|