@aiready/cli 0.9.26 → 0.9.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +17 -18
- package/dist/cli.js +499 -520
- package/dist/cli.mjs +517 -515
- package/package.json +4 -4
- package/src/cli.ts +51 -1332
- package/src/commands/consistency.ts +192 -0
- package/src/commands/context.ts +192 -0
- package/src/commands/index.ts +9 -0
- package/src/commands/patterns.ts +179 -0
- package/src/commands/scan.ts +455 -0
- package/src/commands/visualize.ts +253 -0
- package/src/utils/helpers.ts +133 -0
- package/tsconfig.json +5 -2
package/dist/cli.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,
|
|
@@ -22,56 +25,117 @@ import {
|
|
|
22
25
|
getRatingDisplay,
|
|
23
26
|
parseWeightString
|
|
24
27
|
} from "@aiready/core";
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
// src/utils/helpers.ts
|
|
26
30
|
import { resolve as resolvePath } from "path";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
33
97
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
$ aiready patterns --similarity 0.6 # Custom similarity threshold
|
|
39
|
-
$ aiready scan --output json --output-file results.json
|
|
98
|
+
`;
|
|
99
|
+
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
100
|
+
`;
|
|
101
|
+
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
40
102
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
2. Use 'aiready scan --score' for AI readiness assessment
|
|
44
|
-
3. Create aiready.json for persistent configuration
|
|
45
|
-
4. Set up CI/CD with '--threshold' for quality gates
|
|
103
|
+
`;
|
|
104
|
+
markdown += `## Summary
|
|
46
105
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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}
|
|
50
114
|
|
|
51
|
-
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
115
|
+
`;
|
|
116
|
+
if (report.recommendations.length > 0) {
|
|
117
|
+
markdown += `## Recommendations
|
|
118
|
+
|
|
119
|
+
`;
|
|
120
|
+
report.recommendations.forEach((rec, i) => {
|
|
121
|
+
markdown += `${i + 1}. ${rec}
|
|
122
|
+
`;
|
|
123
|
+
});
|
|
59
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
|
+
}
|
|
60
133
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
LANDING: https://github.com/caopengau/aiready-landing`);
|
|
65
|
-
program.command("scan").description("Run comprehensive AI-readiness analysis (patterns + context + consistency)").argument("[directory]", "Directory to analyze", ".").option("-t, --tools <tools>", "Tools to run (comma-separated: patterns,context,consistency)", "patterns,context,consistency").option("--include <patterns>", "File patterns to include (comma-separated)").option("--exclude <patterns>", "File patterns to exclude (comma-separated)").option("-o, --output <format>", "Output format: console, json", "json").option("--output-file <path>", "Output file path (for json)").option("--no-score", "Disable calculating AI Readiness Score (enabled by default)").option("--weights <weights>", "Custom scoring weights (patterns:40,context:35,consistency:25)").option("--threshold <score>", "Fail CI/CD if score below threshold (0-100)").addHelpText("after", `
|
|
66
|
-
EXAMPLES:
|
|
67
|
-
$ aiready scan # Analyze all tools
|
|
68
|
-
$ aiready scan --tools patterns,context # Skip consistency
|
|
69
|
-
$ aiready scan --score --threshold 75 # CI/CD with threshold
|
|
70
|
-
$ aiready scan --output json --output-file report.json
|
|
71
|
-
`).action(async (directory, options) => {
|
|
72
|
-
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"));
|
|
73
137
|
const startTime = Date.now();
|
|
74
|
-
const resolvedDir =
|
|
138
|
+
const resolvedDir = resolvePath2(process.cwd(), directory || ".");
|
|
75
139
|
try {
|
|
76
140
|
const defaults = {
|
|
77
141
|
tools: ["patterns", "context", "consistency"],
|
|
@@ -93,19 +157,13 @@ EXAMPLES:
|
|
|
93
157
|
const patternSmartDefaults = await getSmartDefaults(resolvedDir, baseOptions);
|
|
94
158
|
finalOptions = { ...patternSmartDefaults, ...finalOptions, ...baseOptions };
|
|
95
159
|
}
|
|
96
|
-
console.log(
|
|
97
|
-
console.log(
|
|
98
|
-
console.log(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return shown.join(", ") + (more > 0 ? `, ... (+${more} more)` : "");
|
|
104
|
-
};
|
|
105
|
-
console.log(chalk.white("\nGeneral settings:"));
|
|
106
|
-
if (finalOptions.rootDir) console.log(` rootDir: ${chalk.bold(String(finalOptions.rootDir))}`);
|
|
107
|
-
if (finalOptions.include) console.log(` include: ${chalk.bold(truncate(finalOptions.include, 6))}`);
|
|
108
|
-
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))}`);
|
|
109
167
|
if (finalOptions["pattern-detect"] || finalOptions.minSimilarity) {
|
|
110
168
|
const patternDetectConfig = finalOptions["pattern-detect"] || {
|
|
111
169
|
minSimilarity: finalOptions.minSimilarity,
|
|
@@ -118,16 +176,16 @@ EXAMPLES:
|
|
|
118
176
|
severity: finalOptions.severity,
|
|
119
177
|
includeTests: finalOptions.includeTests
|
|
120
178
|
};
|
|
121
|
-
console.log(
|
|
122
|
-
console.log(` minSimilarity: ${
|
|
123
|
-
console.log(` minLines: ${
|
|
124
|
-
if (patternDetectConfig.approx !== void 0) console.log(` approx: ${
|
|
125
|
-
if (patternDetectConfig.minSharedTokens !== void 0) console.log(` minSharedTokens: ${
|
|
126
|
-
if (patternDetectConfig.maxCandidatesPerBlock !== void 0) console.log(` maxCandidatesPerBlock: ${
|
|
127
|
-
if (patternDetectConfig.batchSize !== void 0) console.log(` batchSize: ${
|
|
128
|
-
if (patternDetectConfig.streamResults !== void 0) console.log(` streamResults: ${
|
|
129
|
-
if (patternDetectConfig.severity !== void 0) console.log(` severity: ${
|
|
130
|
-
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))}`);
|
|
131
189
|
}
|
|
132
190
|
if (finalOptions["context-analyzer"] || finalOptions.maxDepth) {
|
|
133
191
|
const ca = finalOptions["context-analyzer"] || {
|
|
@@ -137,32 +195,32 @@ EXAMPLES:
|
|
|
137
195
|
maxFragmentation: finalOptions.maxFragmentation,
|
|
138
196
|
includeNodeModules: finalOptions.includeNodeModules
|
|
139
197
|
};
|
|
140
|
-
console.log(
|
|
141
|
-
console.log(` maxDepth: ${
|
|
142
|
-
console.log(` maxContextBudget: ${
|
|
143
|
-
if (ca.minCohesion !== void 0) console.log(` minCohesion: ${
|
|
144
|
-
if (ca.maxFragmentation !== void 0) console.log(` maxFragmentation: ${
|
|
145
|
-
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))}`);
|
|
146
204
|
}
|
|
147
205
|
if (finalOptions.consistency) {
|
|
148
206
|
const c = finalOptions.consistency;
|
|
149
|
-
console.log(
|
|
150
|
-
console.log(` checkNaming: ${
|
|
151
|
-
console.log(` checkPatterns: ${
|
|
152
|
-
console.log(` checkArchitecture: ${
|
|
153
|
-
if (c.minSeverity) console.log(` minSeverity: ${
|
|
154
|
-
if (c.acceptedAbbreviations) console.log(` acceptedAbbreviations: ${
|
|
155
|
-
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))}`);
|
|
156
214
|
}
|
|
157
|
-
console.log(
|
|
215
|
+
console.log(chalk2.white("\nStarting analysis..."));
|
|
158
216
|
const progressCallback = (event) => {
|
|
159
|
-
console.log(
|
|
217
|
+
console.log(chalk2.cyan(`
|
|
160
218
|
--- ${event.tool.toUpperCase()} RESULTS ---`));
|
|
161
219
|
try {
|
|
162
220
|
if (event.tool === "patterns") {
|
|
163
221
|
const pr = event.data;
|
|
164
|
-
console.log(` Duplicate patterns: ${
|
|
165
|
-
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))}`);
|
|
166
224
|
if (pr.duplicates && pr.duplicates.length > 0) {
|
|
167
225
|
pr.duplicates.slice(0, 5).forEach((d, i) => {
|
|
168
226
|
console.log(` ${i + 1}. ${d.file1.split("/").pop()} \u2194 ${d.file2.split("/").pop()} (sim=${(d.similarity * 100).toFixed(1)}%)`);
|
|
@@ -176,10 +234,10 @@ EXAMPLES:
|
|
|
176
234
|
});
|
|
177
235
|
}
|
|
178
236
|
if (pr.groups && pr.groups.length >= 0) {
|
|
179
|
-
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`);
|
|
180
238
|
}
|
|
181
239
|
if (pr.clusters && pr.clusters.length >= 0) {
|
|
182
|
-
console.log(` \u2705 Created ${
|
|
240
|
+
console.log(` \u2705 Created ${chalk2.bold(String(pr.clusters.length))} refactor clusters`);
|
|
183
241
|
pr.clusters.slice(0, 3).forEach((cl, idx) => {
|
|
184
242
|
const files = (cl.files || []).map((f) => f.path.split("/").pop()).join(", ");
|
|
185
243
|
console.log(` ${idx + 1}. ${files} (${cl.tokenCost || "n/a"} tokens)`);
|
|
@@ -187,14 +245,14 @@ EXAMPLES:
|
|
|
187
245
|
}
|
|
188
246
|
} else if (event.tool === "context") {
|
|
189
247
|
const cr = event.data;
|
|
190
|
-
console.log(` Context issues found: ${
|
|
248
|
+
console.log(` Context issues found: ${chalk2.bold(String(cr.length || 0))}`);
|
|
191
249
|
cr.slice(0, 5).forEach((c, i) => {
|
|
192
250
|
const msg = c.message ? ` - ${c.message}` : "";
|
|
193
251
|
console.log(` ${i + 1}. ${c.file} (${c.severity || "n/a"})${msg}`);
|
|
194
252
|
});
|
|
195
253
|
} else if (event.tool === "consistency") {
|
|
196
254
|
const rep = event.data;
|
|
197
|
-
console.log(` Consistency totalIssues: ${
|
|
255
|
+
console.log(` Consistency totalIssues: ${chalk2.bold(String(rep.summary?.totalIssues || 0))}`);
|
|
198
256
|
if (rep.results && rep.results.length > 0) {
|
|
199
257
|
const fileMap = /* @__PURE__ */ new Map();
|
|
200
258
|
rep.results.forEach((r) => {
|
|
@@ -218,7 +276,7 @@ EXAMPLES:
|
|
|
218
276
|
});
|
|
219
277
|
const remaining = files.length - topFiles.length;
|
|
220
278
|
if (remaining > 0) {
|
|
221
|
-
console.log(
|
|
279
|
+
console.log(chalk2.dim(` ... and ${remaining} more files with issues (use --output json for full details)`));
|
|
222
280
|
}
|
|
223
281
|
}
|
|
224
282
|
}
|
|
@@ -226,15 +284,15 @@ EXAMPLES:
|
|
|
226
284
|
}
|
|
227
285
|
};
|
|
228
286
|
const results = await analyzeUnified({ ...finalOptions, progressCallback, suppressToolConfig: true });
|
|
229
|
-
console.log(
|
|
230
|
-
console.log(
|
|
231
|
-
console.log(
|
|
232
|
-
console.log(` Total issues (all tools): ${
|
|
233
|
-
if (results.duplicates) console.log(` Duplicate patterns found: ${
|
|
234
|
-
if (results.patterns) console.log(` Pattern files with issues: ${
|
|
235
|
-
if (results.context) console.log(` Context issues: ${
|
|
236
|
-
if (results.consistency) console.log(` Consistency issues: ${
|
|
237
|
-
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"));
|
|
238
296
|
const elapsedTime = getElapsedTime(startTime);
|
|
239
297
|
let scoringResult;
|
|
240
298
|
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
@@ -269,10 +327,10 @@ EXAMPLES:
|
|
|
269
327
|
const cliWeights = parseWeightString(options.weights);
|
|
270
328
|
if (toolScores.size > 0) {
|
|
271
329
|
scoringResult = calculateOverallScore(toolScores, finalOptions, cliWeights.size ? cliWeights : void 0);
|
|
272
|
-
console.log(
|
|
330
|
+
console.log(chalk2.bold("\n\u{1F4CA} AI Readiness Overall Score"));
|
|
273
331
|
console.log(` ${formatScore(scoringResult)}`);
|
|
274
332
|
if (scoringResult.breakdown && scoringResult.breakdown.length > 0) {
|
|
275
|
-
console.log(
|
|
333
|
+
console.log(chalk2.bold("\nTool breakdown:"));
|
|
276
334
|
scoringResult.breakdown.forEach((tool) => {
|
|
277
335
|
const rating = getRating(tool.score);
|
|
278
336
|
const rd = getRatingDisplay(rating);
|
|
@@ -280,7 +338,7 @@ EXAMPLES:
|
|
|
280
338
|
});
|
|
281
339
|
console.log();
|
|
282
340
|
if (finalOptions.scoring?.showBreakdown) {
|
|
283
|
-
console.log(
|
|
341
|
+
console.log(chalk2.bold("Detailed tool breakdown:"));
|
|
284
342
|
scoringResult.breakdown.forEach((tool) => {
|
|
285
343
|
console.log(formatToolScore(tool));
|
|
286
344
|
});
|
|
@@ -299,19 +357,130 @@ EXAMPLES:
|
|
|
299
357
|
handleJSONOutput(outputData, outputPath, `\u2705 Report saved to ${outputPath}`);
|
|
300
358
|
warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
301
359
|
}
|
|
360
|
+
const isCI = options.ci || process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
361
|
+
if (isCI && scoringResult) {
|
|
362
|
+
const threshold = options.threshold ? parseInt(options.threshold) : void 0;
|
|
363
|
+
const failOnLevel = options.failOn || "critical";
|
|
364
|
+
if (process.env.GITHUB_ACTIONS === "true") {
|
|
365
|
+
console.log(`
|
|
366
|
+
::group::AI Readiness Score`);
|
|
367
|
+
console.log(`score=${scoringResult.overallScore}`);
|
|
368
|
+
if (scoringResult.breakdown) {
|
|
369
|
+
scoringResult.breakdown.forEach((tool) => {
|
|
370
|
+
console.log(`${tool.toolName}=${tool.score}`);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
console.log("::endgroup::");
|
|
374
|
+
if (threshold && scoringResult.overallScore < threshold) {
|
|
375
|
+
console.log(`::error::AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`);
|
|
376
|
+
} else if (threshold) {
|
|
377
|
+
console.log(`::notice::AI Readiness Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`);
|
|
378
|
+
}
|
|
379
|
+
if (results.patterns) {
|
|
380
|
+
const criticalPatterns = results.patterns.flatMap(
|
|
381
|
+
(p) => p.issues.filter((i) => i.severity === "critical")
|
|
382
|
+
);
|
|
383
|
+
criticalPatterns.slice(0, 10).forEach((issue) => {
|
|
384
|
+
console.log(`::warning file=${issue.location?.file || "unknown"},line=${issue.location?.line || 1}::${issue.message}`);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
let shouldFail = false;
|
|
389
|
+
let failReason = "";
|
|
390
|
+
if (threshold && scoringResult.overallScore < threshold) {
|
|
391
|
+
shouldFail = true;
|
|
392
|
+
failReason = `AI Readiness Score ${scoringResult.overallScore} is below threshold ${threshold}`;
|
|
393
|
+
}
|
|
394
|
+
if (failOnLevel !== "none") {
|
|
395
|
+
const severityLevels = { critical: 4, major: 3, minor: 2, any: 1 };
|
|
396
|
+
const minSeverity = severityLevels[failOnLevel] || 4;
|
|
397
|
+
let criticalCount = 0;
|
|
398
|
+
let majorCount = 0;
|
|
399
|
+
if (results.patterns) {
|
|
400
|
+
results.patterns.forEach((p) => {
|
|
401
|
+
p.issues.forEach((i) => {
|
|
402
|
+
if (i.severity === "critical") criticalCount++;
|
|
403
|
+
if (i.severity === "major") majorCount++;
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (results.context) {
|
|
408
|
+
results.context.forEach((c) => {
|
|
409
|
+
if (c.severity === "critical") criticalCount++;
|
|
410
|
+
if (c.severity === "major") majorCount++;
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (results.consistency?.results) {
|
|
414
|
+
results.consistency.results.forEach((r) => {
|
|
415
|
+
r.issues?.forEach((i) => {
|
|
416
|
+
if (i.severity === "critical") criticalCount++;
|
|
417
|
+
if (i.severity === "major") majorCount++;
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
if (minSeverity >= 4 && criticalCount > 0) {
|
|
422
|
+
shouldFail = true;
|
|
423
|
+
failReason = `Found ${criticalCount} critical issues`;
|
|
424
|
+
} else if (minSeverity >= 3 && criticalCount + majorCount > 0) {
|
|
425
|
+
shouldFail = true;
|
|
426
|
+
failReason = `Found ${criticalCount} critical and ${majorCount} major issues`;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (shouldFail) {
|
|
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"));
|
|
436
|
+
process.exit(1);
|
|
437
|
+
} else {
|
|
438
|
+
console.log(chalk2.green("\n\u2705 PR PASSED: AI Readiness Check"));
|
|
439
|
+
if (threshold) {
|
|
440
|
+
console.log(chalk2.green(` Score: ${scoringResult.overallScore}/100 (threshold: ${threshold})`));
|
|
441
|
+
}
|
|
442
|
+
console.log(chalk2.dim("\n \u{1F4A1} Track historical trends: https://getaiready.dev \u2014 Team plan $99/mo"));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
302
445
|
} catch (error) {
|
|
303
446
|
handleCLIError(error, "Analysis");
|
|
304
447
|
}
|
|
305
|
-
}
|
|
306
|
-
|
|
448
|
+
}
|
|
449
|
+
var scanHelpText = `
|
|
307
450
|
EXAMPLES:
|
|
308
|
-
$ aiready
|
|
309
|
-
$ aiready
|
|
310
|
-
$ aiready
|
|
311
|
-
|
|
312
|
-
|
|
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"));
|
|
313
482
|
const startTime = Date.now();
|
|
314
|
-
const resolvedDir =
|
|
483
|
+
const resolvedDir = resolvePath3(process.cwd(), directory || ".");
|
|
315
484
|
try {
|
|
316
485
|
const useSmartDefaults = !options.fullScan;
|
|
317
486
|
const defaults = {
|
|
@@ -340,10 +509,10 @@ EXAMPLES:
|
|
|
340
509
|
if (options.minSharedTokens) {
|
|
341
510
|
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
342
511
|
}
|
|
343
|
-
const finalOptions = await
|
|
512
|
+
const finalOptions = await loadMergedConfig2(resolvedDir, defaults, cliOptions);
|
|
344
513
|
const { analyzePatterns, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
345
514
|
const { results, duplicates } = await analyzePatterns(finalOptions);
|
|
346
|
-
const elapsedTime =
|
|
515
|
+
const elapsedTime = getElapsedTime2(startTime);
|
|
347
516
|
const summary = generateSummary(results);
|
|
348
517
|
let patternScore;
|
|
349
518
|
if (options.score) {
|
|
@@ -357,66 +526,84 @@ EXAMPLES:
|
|
|
357
526
|
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
358
527
|
...patternScore && { scoring: patternScore }
|
|
359
528
|
};
|
|
360
|
-
const outputPath =
|
|
529
|
+
const outputPath = resolveOutputPath2(
|
|
361
530
|
userOutputFile,
|
|
362
531
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
363
532
|
resolvedDir
|
|
364
533
|
);
|
|
365
|
-
|
|
534
|
+
handleJSONOutput2(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
366
535
|
} else {
|
|
367
536
|
const terminalWidth = process.stdout.columns || 80;
|
|
368
537
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
369
538
|
const divider = "\u2501".repeat(dividerWidth);
|
|
370
|
-
console.log(
|
|
371
|
-
console.log(
|
|
372
|
-
console.log(
|
|
373
|
-
console.log(
|
|
374
|
-
console.log(
|
|
375
|
-
console.log(
|
|
376
|
-
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")}`));
|
|
377
546
|
const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
378
547
|
if (sortedTypes.length > 0) {
|
|
379
|
-
console.log(
|
|
380
|
-
console.log(
|
|
381
|
-
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");
|
|
382
551
|
sortedTypes.forEach(([type, count]) => {
|
|
383
|
-
console.log(` ${
|
|
552
|
+
console.log(` ${chalk3.white(type.padEnd(15))} ${chalk3.bold(count)}`);
|
|
384
553
|
});
|
|
385
554
|
}
|
|
386
555
|
if (summary.totalPatterns > 0 && duplicates.length > 0) {
|
|
387
|
-
console.log(
|
|
388
|
-
console.log(
|
|
389
|
-
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");
|
|
390
559
|
const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
|
|
391
560
|
topDuplicates.forEach((dup) => {
|
|
392
561
|
const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
|
|
393
562
|
const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
|
|
394
563
|
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
395
564
|
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
396
|
-
console.log(`${severityIcon} ${severity}: ${
|
|
397
|
-
console.log(` Similarity: ${
|
|
398
|
-
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)}
|
|
399
568
|
`);
|
|
400
569
|
});
|
|
401
570
|
} else {
|
|
402
|
-
console.log(
|
|
571
|
+
console.log(chalk3.green("\n\u2728 Great! No duplicate patterns detected.\n"));
|
|
403
572
|
}
|
|
404
573
|
if (patternScore) {
|
|
405
|
-
console.log(
|
|
406
|
-
console.log(
|
|
407
|
-
console.log(
|
|
408
|
-
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));
|
|
409
578
|
console.log();
|
|
410
579
|
}
|
|
411
580
|
}
|
|
412
581
|
} catch (error) {
|
|
413
|
-
|
|
582
|
+
handleCLIError2(error, "Pattern analysis");
|
|
414
583
|
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
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"));
|
|
418
605
|
const startTime = Date.now();
|
|
419
|
-
const resolvedDir =
|
|
606
|
+
const resolvedDir = resolvePath4(process.cwd(), directory || ".");
|
|
420
607
|
try {
|
|
421
608
|
const defaults = {
|
|
422
609
|
maxDepth: 5,
|
|
@@ -428,7 +615,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
428
615
|
file: void 0
|
|
429
616
|
}
|
|
430
617
|
};
|
|
431
|
-
let baseOptions = await
|
|
618
|
+
let baseOptions = await loadMergedConfig3(resolvedDir, defaults, {
|
|
432
619
|
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
433
620
|
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
434
621
|
include: options.include?.split(","),
|
|
@@ -447,7 +634,7 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
447
634
|
console.log("");
|
|
448
635
|
const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
|
|
449
636
|
const results = await analyzeContext(finalOptions);
|
|
450
|
-
const elapsedTime =
|
|
637
|
+
const elapsedTime = getElapsedTime3(startTime);
|
|
451
638
|
const summary = generateSummary(results);
|
|
452
639
|
let contextScore;
|
|
453
640
|
if (options.score) {
|
|
@@ -461,100 +648,113 @@ program.command("context").description("Analyze context window costs and depende
|
|
|
461
648
|
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
462
649
|
...contextScore && { scoring: contextScore }
|
|
463
650
|
};
|
|
464
|
-
const outputPath =
|
|
651
|
+
const outputPath = resolveOutputPath3(
|
|
465
652
|
userOutputFile,
|
|
466
653
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
467
654
|
resolvedDir
|
|
468
655
|
);
|
|
469
|
-
|
|
656
|
+
handleJSONOutput3(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
470
657
|
} else {
|
|
471
658
|
const terminalWidth = process.stdout.columns || 80;
|
|
472
659
|
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
473
660
|
const divider = "\u2501".repeat(dividerWidth);
|
|
474
|
-
console.log(
|
|
475
|
-
console.log(
|
|
476
|
-
console.log(
|
|
477
|
-
console.log(
|
|
478
|
-
console.log(
|
|
479
|
-
console.log(
|
|
480
|
-
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")}
|
|
481
668
|
`));
|
|
482
669
|
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
483
670
|
if (totalIssues > 0) {
|
|
484
|
-
console.log(
|
|
671
|
+
console.log(chalk4.bold("\u26A0\uFE0F Issues Found:\n"));
|
|
485
672
|
if (summary.criticalIssues > 0) {
|
|
486
|
-
console.log(
|
|
673
|
+
console.log(chalk4.red(` \u{1F534} Critical: ${chalk4.bold(summary.criticalIssues)}`));
|
|
487
674
|
}
|
|
488
675
|
if (summary.majorIssues > 0) {
|
|
489
|
-
console.log(
|
|
676
|
+
console.log(chalk4.yellow(` \u{1F7E1} Major: ${chalk4.bold(summary.majorIssues)}`));
|
|
490
677
|
}
|
|
491
678
|
if (summary.minorIssues > 0) {
|
|
492
|
-
console.log(
|
|
679
|
+
console.log(chalk4.blue(` \u{1F535} Minor: ${chalk4.bold(summary.minorIssues)}`));
|
|
493
680
|
}
|
|
494
|
-
console.log(
|
|
495
|
-
\u{1F4A1} Potential savings: ${
|
|
681
|
+
console.log(chalk4.green(`
|
|
682
|
+
\u{1F4A1} Potential savings: ${chalk4.bold(summary.totalPotentialSavings.toLocaleString())} tokens
|
|
496
683
|
`));
|
|
497
684
|
} else {
|
|
498
|
-
console.log(
|
|
685
|
+
console.log(chalk4.green("\u2705 No significant issues found!\n"));
|
|
499
686
|
}
|
|
500
687
|
if (summary.deepFiles.length > 0) {
|
|
501
|
-
console.log(
|
|
502
|
-
console.log(
|
|
503
|
-
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}
|
|
504
691
|
`));
|
|
505
692
|
summary.deepFiles.slice(0, 10).forEach((item) => {
|
|
506
693
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
507
|
-
console.log(` ${
|
|
694
|
+
console.log(` ${chalk4.cyan("\u2192")} ${chalk4.white(fileName)} ${chalk4.dim(`(depth: ${item.depth})`)}`);
|
|
508
695
|
});
|
|
509
696
|
console.log();
|
|
510
697
|
}
|
|
511
698
|
if (summary.fragmentedModules.length > 0) {
|
|
512
|
-
console.log(
|
|
513
|
-
console.log(
|
|
699
|
+
console.log(chalk4.bold("\u{1F9E9} Fragmented Modules:\n"));
|
|
700
|
+
console.log(chalk4.gray(` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
|
|
514
701
|
`));
|
|
515
702
|
summary.fragmentedModules.slice(0, 10).forEach((module) => {
|
|
516
|
-
console.log(` ${
|
|
517
|
-
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)}%`));
|
|
518
705
|
});
|
|
519
706
|
console.log();
|
|
520
707
|
}
|
|
521
708
|
if (summary.lowCohesionFiles.length > 0) {
|
|
522
|
-
console.log(
|
|
523
|
-
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)}%
|
|
524
711
|
`));
|
|
525
712
|
summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
|
|
526
713
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
527
714
|
const scorePercent = (item.score * 100).toFixed(0);
|
|
528
|
-
const color = item.score < 0.4 ?
|
|
529
|
-
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)`)}`);
|
|
530
717
|
});
|
|
531
718
|
console.log();
|
|
532
719
|
}
|
|
533
720
|
if (summary.topExpensiveFiles.length > 0) {
|
|
534
|
-
console.log(
|
|
721
|
+
console.log(chalk4.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
|
|
535
722
|
summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
|
|
536
723
|
const fileName = item.file.split("/").slice(-2).join("/");
|
|
537
|
-
const severityColor = item.severity === "critical" ?
|
|
538
|
-
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)`)}`);
|
|
539
726
|
});
|
|
540
727
|
console.log();
|
|
541
728
|
}
|
|
542
729
|
if (contextScore) {
|
|
543
|
-
console.log(
|
|
544
|
-
console.log(
|
|
545
|
-
console.log(
|
|
546
|
-
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));
|
|
547
734
|
console.log();
|
|
548
735
|
}
|
|
549
736
|
}
|
|
550
737
|
} catch (error) {
|
|
551
|
-
|
|
738
|
+
handleCLIError3(error, "Context analysis");
|
|
552
739
|
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
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"));
|
|
556
756
|
const startTime = Date.now();
|
|
557
|
-
const resolvedDir =
|
|
757
|
+
const resolvedDir = resolvePath5(process.cwd(), directory || ".");
|
|
558
758
|
try {
|
|
559
759
|
const defaults = {
|
|
560
760
|
checkNaming: true,
|
|
@@ -567,7 +767,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
567
767
|
file: void 0
|
|
568
768
|
}
|
|
569
769
|
};
|
|
570
|
-
const finalOptions = await
|
|
770
|
+
const finalOptions = await loadMergedConfig4(resolvedDir, defaults, {
|
|
571
771
|
checkNaming: options.naming !== false,
|
|
572
772
|
checkPatterns: options.patterns !== false,
|
|
573
773
|
minSeverity: options.minSeverity,
|
|
@@ -576,7 +776,7 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
576
776
|
});
|
|
577
777
|
const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
|
|
578
778
|
const report = await analyzeConsistency(finalOptions);
|
|
579
|
-
const elapsedTime =
|
|
779
|
+
const elapsedTime = getElapsedTime4(startTime);
|
|
580
780
|
let consistencyScore;
|
|
581
781
|
if (options.score) {
|
|
582
782
|
const issues = report.results?.flatMap((r) => r.issues) || [];
|
|
@@ -593,32 +793,32 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
593
793
|
},
|
|
594
794
|
...consistencyScore && { scoring: consistencyScore }
|
|
595
795
|
};
|
|
596
|
-
const outputPath =
|
|
796
|
+
const outputPath = resolveOutputPath4(
|
|
597
797
|
userOutputFile,
|
|
598
798
|
`aiready-report-${getReportTimestamp()}.json`,
|
|
599
799
|
resolvedDir
|
|
600
800
|
);
|
|
601
|
-
|
|
801
|
+
handleJSONOutput4(outputData, outputPath, `\u2705 Results saved to ${outputPath}`);
|
|
602
802
|
} else if (outputFormat === "markdown") {
|
|
603
803
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
604
|
-
const outputPath =
|
|
804
|
+
const outputPath = resolveOutputPath4(
|
|
605
805
|
userOutputFile,
|
|
606
806
|
`aiready-report-${getReportTimestamp()}.md`,
|
|
607
807
|
resolvedDir
|
|
608
808
|
);
|
|
609
809
|
writeFileSync(outputPath, markdown);
|
|
610
|
-
console.log(
|
|
810
|
+
console.log(chalk5.green(`\u2705 Report saved to ${outputPath}`));
|
|
611
811
|
} else {
|
|
612
|
-
console.log(
|
|
613
|
-
console.log(`Files Analyzed: ${
|
|
614
|
-
console.log(`Total Issues: ${
|
|
615
|
-
console.log(` Naming: ${
|
|
616
|
-
console.log(` Patterns: ${
|
|
617
|
-
console.log(` Architecture: ${
|
|
618
|
-
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")}
|
|
619
819
|
`);
|
|
620
820
|
if (report.summary.totalIssues === 0) {
|
|
621
|
-
console.log(
|
|
821
|
+
console.log(chalk5.green("\u2728 No consistency issues found! Your codebase is well-maintained.\n"));
|
|
622
822
|
} else {
|
|
623
823
|
const namingResults = report.results.filter(
|
|
624
824
|
(r) => r.issues.some((i) => i.category === "naming")
|
|
@@ -627,17 +827,17 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
627
827
|
(r) => r.issues.some((i) => i.category === "patterns")
|
|
628
828
|
);
|
|
629
829
|
if (namingResults.length > 0) {
|
|
630
|
-
console.log(
|
|
830
|
+
console.log(chalk5.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
|
|
631
831
|
let shown = 0;
|
|
632
832
|
for (const result of namingResults) {
|
|
633
833
|
if (shown >= 5) break;
|
|
634
834
|
for (const issue of result.issues) {
|
|
635
835
|
if (shown >= 5) break;
|
|
636
|
-
const severityColor = issue.severity === "critical" ?
|
|
637
|
-
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}`)}`);
|
|
638
838
|
console.log(` ${issue.message}`);
|
|
639
839
|
if (issue.suggestion) {
|
|
640
|
-
console.log(` ${
|
|
840
|
+
console.log(` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`);
|
|
641
841
|
}
|
|
642
842
|
console.log();
|
|
643
843
|
shown++;
|
|
@@ -645,22 +845,22 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
645
845
|
}
|
|
646
846
|
const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
647
847
|
if (remaining > 0) {
|
|
648
|
-
console.log(
|
|
848
|
+
console.log(chalk5.dim(` ... and ${remaining} more issues
|
|
649
849
|
`));
|
|
650
850
|
}
|
|
651
851
|
}
|
|
652
852
|
if (patternResults.length > 0) {
|
|
653
|
-
console.log(
|
|
853
|
+
console.log(chalk5.bold("\u{1F504} Pattern Issues\n"));
|
|
654
854
|
let shown = 0;
|
|
655
855
|
for (const result of patternResults) {
|
|
656
856
|
if (shown >= 5) break;
|
|
657
857
|
for (const issue of result.issues) {
|
|
658
858
|
if (shown >= 5) break;
|
|
659
|
-
const severityColor = issue.severity === "critical" ?
|
|
660
|
-
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}`)}`);
|
|
661
861
|
console.log(` ${issue.message}`);
|
|
662
862
|
if (issue.suggestion) {
|
|
663
|
-
console.log(` ${
|
|
863
|
+
console.log(` ${chalk5.dim("\u2192")} ${chalk5.italic(issue.suggestion)}`);
|
|
664
864
|
}
|
|
665
865
|
console.log();
|
|
666
866
|
shown++;
|
|
@@ -668,12 +868,12 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
668
868
|
}
|
|
669
869
|
const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
670
870
|
if (remaining > 0) {
|
|
671
|
-
console.log(
|
|
871
|
+
console.log(chalk5.dim(` ... and ${remaining} more issues
|
|
672
872
|
`));
|
|
673
873
|
}
|
|
674
874
|
}
|
|
675
875
|
if (report.recommendations.length > 0) {
|
|
676
|
-
console.log(
|
|
876
|
+
console.log(chalk5.bold("\u{1F4A1} Recommendations\n"));
|
|
677
877
|
report.recommendations.forEach((rec, i) => {
|
|
678
878
|
console.log(`${i + 1}. ${rec}`);
|
|
679
879
|
});
|
|
@@ -681,288 +881,35 @@ program.command("consistency").description("Check naming conventions and archite
|
|
|
681
881
|
}
|
|
682
882
|
}
|
|
683
883
|
if (consistencyScore) {
|
|
684
|
-
console.log(
|
|
685
|
-
console.log(
|
|
884
|
+
console.log(chalk5.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
|
|
885
|
+
console.log(formatToolScore4(consistencyScore));
|
|
686
886
|
console.log();
|
|
687
887
|
}
|
|
688
888
|
}
|
|
689
889
|
} catch (error) {
|
|
690
|
-
|
|
890
|
+
handleCLIError4(error, "Consistency analysis");
|
|
691
891
|
}
|
|
692
|
-
});
|
|
693
|
-
function generateMarkdownReport(report, elapsedTime) {
|
|
694
|
-
let markdown = `# Consistency Analysis Report
|
|
695
|
-
|
|
696
|
-
`;
|
|
697
|
-
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
698
|
-
`;
|
|
699
|
-
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
700
|
-
|
|
701
|
-
`;
|
|
702
|
-
markdown += `## Summary
|
|
703
|
-
|
|
704
|
-
`;
|
|
705
|
-
markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
|
|
706
|
-
`;
|
|
707
|
-
markdown += `- **Total Issues:** ${report.summary.totalIssues}
|
|
708
|
-
`;
|
|
709
|
-
markdown += ` - Naming: ${report.summary.namingIssues}
|
|
710
|
-
`;
|
|
711
|
-
markdown += ` - Patterns: ${report.summary.patternIssues}
|
|
712
|
-
|
|
713
|
-
`;
|
|
714
|
-
if (report.recommendations.length > 0) {
|
|
715
|
-
markdown += `## Recommendations
|
|
716
|
-
|
|
717
|
-
`;
|
|
718
|
-
report.recommendations.forEach((rec, i) => {
|
|
719
|
-
markdown += `${i + 1}. ${rec}
|
|
720
|
-
`;
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
return markdown;
|
|
724
892
|
}
|
|
725
|
-
function generateHTML(graph) {
|
|
726
|
-
const payload = JSON.stringify(graph, null, 2);
|
|
727
|
-
return `<!doctype html>
|
|
728
|
-
<html>
|
|
729
|
-
<head>
|
|
730
|
-
<meta charset="utf-8" />
|
|
731
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
732
|
-
<title>AIReady Visualization</title>
|
|
733
|
-
<style>
|
|
734
|
-
html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
|
|
735
|
-
#container { display:flex; height:100vh }
|
|
736
|
-
#panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
|
|
737
|
-
#canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
|
|
738
|
-
canvas { background: #0b1220; border-radius:8px }
|
|
739
|
-
.stat { margin-bottom:12px }
|
|
740
|
-
</style>
|
|
741
|
-
</head>
|
|
742
|
-
<body>
|
|
743
|
-
<div id="container">
|
|
744
|
-
<div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
|
|
745
|
-
<div id="panel">
|
|
746
|
-
<h2>AIReady Visualization</h2>
|
|
747
|
-
<div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
|
|
748
|
-
<div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
|
|
749
|
-
<div class="stat"><strong>Legend</strong></div>
|
|
750
|
-
<div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
|
|
751
|
-
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
|
|
752
|
-
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
|
|
753
|
-
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
|
|
754
|
-
<div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
|
|
755
|
-
<div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
|
|
756
|
-
<div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
|
|
757
|
-
<div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
|
|
758
|
-
</div>
|
|
759
|
-
</div>
|
|
760
|
-
</div>
|
|
761
|
-
|
|
762
|
-
<script>
|
|
763
|
-
const graphData = ${payload};
|
|
764
|
-
document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
|
|
765
|
-
document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
|
|
766
893
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
function draw() {
|
|
777
|
-
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
778
|
-
|
|
779
|
-
graphData.edges.forEach(edge => {
|
|
780
|
-
const s = nodes.find(n => n.id === edge.source);
|
|
781
|
-
const t = nodes.find(n => n.id === edge.target);
|
|
782
|
-
if (!s || !t) return;
|
|
783
|
-
if (edge.type === 'related') return;
|
|
784
|
-
if (edge.type === 'similarity') {
|
|
785
|
-
ctx.strokeStyle = '#fb7e81';
|
|
786
|
-
ctx.lineWidth = 1.2;
|
|
787
|
-
} else if (edge.type === 'dependency') {
|
|
788
|
-
ctx.strokeStyle = '#84c1ff';
|
|
789
|
-
ctx.lineWidth = 1.0;
|
|
790
|
-
} else if (edge.type === 'reference') {
|
|
791
|
-
ctx.strokeStyle = '#ffa500';
|
|
792
|
-
ctx.lineWidth = 0.9;
|
|
793
|
-
} else {
|
|
794
|
-
ctx.strokeStyle = '#334155';
|
|
795
|
-
ctx.lineWidth = 0.8;
|
|
796
|
-
}
|
|
797
|
-
ctx.beginPath();
|
|
798
|
-
ctx.moveTo(s.x, s.y);
|
|
799
|
-
ctx.lineTo(t.x, t.y);
|
|
800
|
-
ctx.stroke();
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
const groups = {};
|
|
804
|
-
nodes.forEach(n => {
|
|
805
|
-
const g = n.group || '__default';
|
|
806
|
-
if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
|
|
807
|
-
groups[g].minX = Math.min(groups[g].minX, n.x);
|
|
808
|
-
groups[g].minY = Math.min(groups[g].minY, n.y);
|
|
809
|
-
groups[g].maxX = Math.max(groups[g].maxX, n.x);
|
|
810
|
-
groups[g].maxY = Math.max(groups[g].maxY, n.y);
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
const groupRelations = {};
|
|
814
|
-
graphData.edges.forEach(edge => {
|
|
815
|
-
const sNode = nodes.find(n => n.id === edge.source);
|
|
816
|
-
const tNode = nodes.find(n => n.id === edge.target);
|
|
817
|
-
if (!sNode || !tNode) return;
|
|
818
|
-
const g1 = sNode.group || '__default';
|
|
819
|
-
const g2 = tNode.group || '__default';
|
|
820
|
-
if (g1 === g2) return;
|
|
821
|
-
const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
|
|
822
|
-
groupRelations[key] = (groupRelations[key] || 0) + 1;
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
Object.keys(groupRelations).forEach(k => {
|
|
826
|
-
const count = groupRelations[k];
|
|
827
|
-
const [ga, gb] = k.split('::');
|
|
828
|
-
if (!groups[ga] || !groups[gb]) return;
|
|
829
|
-
const ax = (groups[ga].minX + groups[ga].maxX) / 2;
|
|
830
|
-
const ay = (groups[ga].minY + groups[ga].maxY) / 2;
|
|
831
|
-
const bx = (groups[gb].minX + groups[gb].maxX) / 2;
|
|
832
|
-
const by = (groups[gb].minY + groups[gb].maxY) / 2;
|
|
833
|
-
ctx.beginPath();
|
|
834
|
-
ctx.strokeStyle = 'rgba(148,163,184,0.25)';
|
|
835
|
-
ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
|
|
836
|
-
ctx.moveTo(ax, ay);
|
|
837
|
-
ctx.lineTo(bx, by);
|
|
838
|
-
ctx.stroke();
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
Object.keys(groups).forEach(g => {
|
|
842
|
-
if (g === '__default') return;
|
|
843
|
-
const box = groups[g];
|
|
844
|
-
const pad = 16;
|
|
845
|
-
const x = box.minX - pad;
|
|
846
|
-
const y = box.minY - pad;
|
|
847
|
-
const w = (box.maxX - box.minX) + pad * 2;
|
|
848
|
-
const h = (box.maxY - box.minY) + pad * 2;
|
|
849
|
-
ctx.save();
|
|
850
|
-
ctx.fillStyle = 'rgba(30,64,175,0.04)';
|
|
851
|
-
ctx.strokeStyle = 'rgba(30,64,175,0.12)';
|
|
852
|
-
ctx.lineWidth = 1.2;
|
|
853
|
-
const r = 8;
|
|
854
|
-
ctx.beginPath();
|
|
855
|
-
ctx.moveTo(x + r, y);
|
|
856
|
-
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
857
|
-
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
858
|
-
ctx.arcTo(x, y + h, x, y, r);
|
|
859
|
-
ctx.arcTo(x, y, x + w, y, r);
|
|
860
|
-
ctx.closePath();
|
|
861
|
-
ctx.fill();
|
|
862
|
-
ctx.stroke();
|
|
863
|
-
ctx.restore();
|
|
864
|
-
ctx.fillStyle = '#94a3b8';
|
|
865
|
-
ctx.font = '11px sans-serif';
|
|
866
|
-
ctx.fillText(g, x + 8, y + 14);
|
|
867
|
-
});
|
|
868
|
-
|
|
869
|
-
nodes.forEach(n => {
|
|
870
|
-
const sizeVal = (n.size || n.value || 1);
|
|
871
|
-
const r = 6 + (sizeVal / 2);
|
|
872
|
-
ctx.beginPath();
|
|
873
|
-
ctx.fillStyle = n.color || '#60a5fa';
|
|
874
|
-
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
|
|
875
|
-
ctx.fill();
|
|
876
|
-
|
|
877
|
-
ctx.fillStyle = '#e2e8f0';
|
|
878
|
-
ctx.font = '11px sans-serif';
|
|
879
|
-
ctx.textAlign = 'center';
|
|
880
|
-
ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
draw();
|
|
885
|
-
</script>
|
|
886
|
-
</body>
|
|
887
|
-
</html>`;
|
|
888
|
-
}
|
|
889
|
-
function getReportTimestamp() {
|
|
890
|
-
const now = /* @__PURE__ */ new Date();
|
|
891
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
892
|
-
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
893
|
-
}
|
|
894
|
-
function findLatestScanReport(dirPath) {
|
|
895
|
-
const aireadyDir = resolvePath(dirPath, ".aiready");
|
|
896
|
-
if (!existsSync(aireadyDir)) {
|
|
897
|
-
return null;
|
|
898
|
-
}
|
|
899
|
-
const { readdirSync, statSync } = __require("fs");
|
|
900
|
-
let files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-report-") && f.endsWith(".json"));
|
|
901
|
-
if (files.length === 0) {
|
|
902
|
-
files = readdirSync(aireadyDir).filter((f) => f.startsWith("aiready-scan-") && f.endsWith(".json"));
|
|
903
|
-
}
|
|
904
|
-
if (files.length === 0) {
|
|
905
|
-
return null;
|
|
906
|
-
}
|
|
907
|
-
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());
|
|
908
|
-
return sortedFiles[0].path;
|
|
909
|
-
}
|
|
910
|
-
function warnIfGraphCapExceeded(report, dirPath) {
|
|
911
|
-
try {
|
|
912
|
-
const { loadConfig } = __require("@aiready/core");
|
|
913
|
-
const { existsSync: existsSync2, readFileSync: readFileSync2 } = __require("fs");
|
|
914
|
-
const { resolve } = __require("path");
|
|
915
|
-
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
916
|
-
const configPath = resolve(dirPath, "aiready.json");
|
|
917
|
-
if (existsSync2(configPath)) {
|
|
918
|
-
try {
|
|
919
|
-
const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
920
|
-
if (rawConfig.visualizer?.graph) {
|
|
921
|
-
graphConfig = {
|
|
922
|
-
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
923
|
-
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
} catch (e) {
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
|
|
930
|
-
const edgeCount = report.context?.reduce((sum, ctx) => {
|
|
931
|
-
const relCount = ctx.relatedFiles?.length || 0;
|
|
932
|
-
const depCount = ctx.dependencies?.length || 0;
|
|
933
|
-
return sum + relCount + depCount;
|
|
934
|
-
}, 0) || 0;
|
|
935
|
-
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
936
|
-
console.log("");
|
|
937
|
-
console.log(chalk.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`));
|
|
938
|
-
if (nodeCount > graphConfig.maxNodes) {
|
|
939
|
-
console.log(chalk.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`));
|
|
940
|
-
}
|
|
941
|
-
if (edgeCount > graphConfig.maxEdges) {
|
|
942
|
-
console.log(chalk.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`));
|
|
943
|
-
}
|
|
944
|
-
console.log(chalk.dim(` To increase limits, add to aiready.json:`));
|
|
945
|
-
console.log(chalk.dim(` {`));
|
|
946
|
-
console.log(chalk.dim(` "visualizer": {`));
|
|
947
|
-
console.log(chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`));
|
|
948
|
-
console.log(chalk.dim(` }`));
|
|
949
|
-
console.log(chalk.dim(` }`));
|
|
950
|
-
}
|
|
951
|
-
} catch (e) {
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
async function handleVisualize(directory, options) {
|
|
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) {
|
|
955
902
|
try {
|
|
956
|
-
const dirPath =
|
|
957
|
-
let reportPath = options.report ?
|
|
958
|
-
if (!reportPath || !
|
|
903
|
+
const dirPath = resolvePath6(process.cwd(), directory || ".");
|
|
904
|
+
let reportPath = options.report ? resolvePath6(dirPath, options.report) : null;
|
|
905
|
+
if (!reportPath || !existsSync2(reportPath)) {
|
|
959
906
|
const latestScan = findLatestScanReport(dirPath);
|
|
960
907
|
if (latestScan) {
|
|
961
908
|
reportPath = latestScan;
|
|
962
|
-
console.log(
|
|
909
|
+
console.log(chalk6.dim(`Found latest report: ${latestScan.split("/").pop()}`));
|
|
963
910
|
} else {
|
|
964
|
-
console.error(
|
|
965
|
-
console.log(
|
|
911
|
+
console.error(chalk6.red("\u274C No AI readiness report found"));
|
|
912
|
+
console.log(chalk6.dim(`
|
|
966
913
|
Generate a report with:
|
|
967
914
|
aiready scan --output json
|
|
968
915
|
|
|
@@ -971,13 +918,13 @@ Or specify a custom report:
|
|
|
971
918
|
return;
|
|
972
919
|
}
|
|
973
920
|
}
|
|
974
|
-
const raw =
|
|
921
|
+
const raw = readFileSync2(reportPath, "utf8");
|
|
975
922
|
const report = JSON.parse(raw);
|
|
976
|
-
const configPath =
|
|
923
|
+
const configPath = resolvePath6(dirPath, "aiready.json");
|
|
977
924
|
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
978
|
-
if (
|
|
925
|
+
if (existsSync2(configPath)) {
|
|
979
926
|
try {
|
|
980
|
-
const rawConfig = JSON.parse(
|
|
927
|
+
const rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
981
928
|
if (rawConfig.visualizer?.graph) {
|
|
982
929
|
graphConfig = {
|
|
983
930
|
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
@@ -994,27 +941,26 @@ Or specify a custom report:
|
|
|
994
941
|
const graph = GraphBuilder.buildFromReport(report, dirPath);
|
|
995
942
|
if (options.dev) {
|
|
996
943
|
try {
|
|
997
|
-
const
|
|
998
|
-
const monorepoWebDir = resolvePath(dirPath, "packages/visualizer");
|
|
944
|
+
const monorepoWebDir = resolvePath6(dirPath, "packages/visualizer");
|
|
999
945
|
let webDir = "";
|
|
1000
946
|
let visualizerAvailable = false;
|
|
1001
|
-
if (
|
|
947
|
+
if (existsSync2(monorepoWebDir)) {
|
|
1002
948
|
webDir = monorepoWebDir;
|
|
1003
949
|
visualizerAvailable = true;
|
|
1004
950
|
} else {
|
|
1005
951
|
const nodemodulesLocations = [
|
|
1006
|
-
|
|
1007
|
-
|
|
952
|
+
resolvePath6(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
953
|
+
resolvePath6(process.cwd(), "node_modules", "@aiready", "visualizer")
|
|
1008
954
|
];
|
|
1009
955
|
let currentDir = dirPath;
|
|
1010
956
|
while (currentDir !== "/" && currentDir !== ".") {
|
|
1011
|
-
nodemodulesLocations.push(
|
|
1012
|
-
const parent =
|
|
957
|
+
nodemodulesLocations.push(resolvePath6(currentDir, "node_modules", "@aiready", "visualizer"));
|
|
958
|
+
const parent = resolvePath6(currentDir, "..");
|
|
1013
959
|
if (parent === currentDir) break;
|
|
1014
960
|
currentDir = parent;
|
|
1015
961
|
}
|
|
1016
962
|
for (const location of nodemodulesLocations) {
|
|
1017
|
-
if (
|
|
963
|
+
if (existsSync2(location) && existsSync2(resolvePath6(location, "package.json"))) {
|
|
1018
964
|
webDir = location;
|
|
1019
965
|
visualizerAvailable = true;
|
|
1020
966
|
break;
|
|
@@ -1023,7 +969,7 @@ Or specify a custom report:
|
|
|
1023
969
|
if (!visualizerAvailable) {
|
|
1024
970
|
try {
|
|
1025
971
|
const vizPkgPath = __require.resolve("@aiready/visualizer/package.json");
|
|
1026
|
-
webDir =
|
|
972
|
+
webDir = resolvePath6(vizPkgPath, "..");
|
|
1027
973
|
visualizerAvailable = true;
|
|
1028
974
|
} catch (e) {
|
|
1029
975
|
}
|
|
@@ -1031,17 +977,17 @@ Or specify a custom report:
|
|
|
1031
977
|
}
|
|
1032
978
|
const spawnCwd = webDir || process.cwd();
|
|
1033
979
|
const nodeBinCandidate = process.execPath;
|
|
1034
|
-
const nodeBin =
|
|
980
|
+
const nodeBin = existsSync2(nodeBinCandidate) ? nodeBinCandidate : "node";
|
|
1035
981
|
if (!visualizerAvailable) {
|
|
1036
|
-
console.error(
|
|
1037
|
-
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"));
|
|
1038
984
|
return;
|
|
1039
985
|
}
|
|
1040
986
|
const { watch } = await import("fs");
|
|
1041
987
|
const copyReportToViz = () => {
|
|
1042
988
|
try {
|
|
1043
|
-
const destPath =
|
|
1044
|
-
|
|
989
|
+
const destPath = resolvePath6(spawnCwd, "web", "report-data.json");
|
|
990
|
+
copyFileSync2(reportPath, destPath);
|
|
1045
991
|
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1046
992
|
} catch (e) {
|
|
1047
993
|
console.error("Failed to sync report:", e);
|
|
@@ -1079,20 +1025,18 @@ Or specify a custom report:
|
|
|
1079
1025
|
}
|
|
1080
1026
|
console.log("Generating HTML...");
|
|
1081
1027
|
const html = generateHTML(graph);
|
|
1082
|
-
const outPath =
|
|
1083
|
-
|
|
1028
|
+
const outPath = resolvePath6(dirPath, options.output || "packages/visualizer/visualization.html");
|
|
1029
|
+
writeFileSync2(outPath, html, "utf8");
|
|
1084
1030
|
console.log("Visualization written to:", outPath);
|
|
1085
1031
|
if (options.open) {
|
|
1086
|
-
const { exec } = await import("child_process");
|
|
1087
1032
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1088
|
-
|
|
1033
|
+
spawn(opener, [`"${outPath}"`], { shell: true });
|
|
1089
1034
|
}
|
|
1090
1035
|
if (options.serve) {
|
|
1091
1036
|
try {
|
|
1092
|
-
const port =
|
|
1037
|
+
const port = typeof options.serve === "number" ? options.serve : 5173;
|
|
1093
1038
|
const http = await import("http");
|
|
1094
1039
|
const fsp = await import("fs/promises");
|
|
1095
|
-
const { exec } = await import("child_process");
|
|
1096
1040
|
const server = http.createServer(async (req, res) => {
|
|
1097
1041
|
try {
|
|
1098
1042
|
const urlPath = req.url || "/";
|
|
@@ -1113,7 +1057,7 @@ Or specify a custom report:
|
|
|
1113
1057
|
const addr = `http://localhost:${port}/`;
|
|
1114
1058
|
console.log(`Local visualization server running at ${addr}`);
|
|
1115
1059
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1116
|
-
|
|
1060
|
+
spawn(opener, [`"${addr}"`], { shell: true });
|
|
1117
1061
|
});
|
|
1118
1062
|
process.on("SIGINT", () => {
|
|
1119
1063
|
server.close();
|
|
@@ -1124,20 +1068,10 @@ Or specify a custom report:
|
|
|
1124
1068
|
}
|
|
1125
1069
|
}
|
|
1126
1070
|
} catch (err) {
|
|
1127
|
-
|
|
1071
|
+
handleCLIError5(err, "Visualization");
|
|
1128
1072
|
}
|
|
1129
1073
|
}
|
|
1130
|
-
|
|
1131
|
-
EXAMPLES:
|
|
1132
|
-
$ aiready visualise . # Auto-detects latest report
|
|
1133
|
-
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1134
|
-
$ aiready visualise . --report report.json --dev
|
|
1135
|
-
$ aiready visualise . --report report.json --serve 8080
|
|
1136
|
-
|
|
1137
|
-
NOTES:
|
|
1138
|
-
- Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
|
|
1139
|
-
`).action(async (directory, options) => await handleVisualize(directory, options));
|
|
1140
|
-
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 = `
|
|
1141
1075
|
EXAMPLES:
|
|
1142
1076
|
$ aiready visualize . # Auto-detects latest report
|
|
1143
1077
|
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
@@ -1156,5 +1090,73 @@ NOTES:
|
|
|
1156
1090
|
reduce clutter and improve interactivity on large graphs.
|
|
1157
1091
|
- For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
|
|
1158
1092
|
allow the browser a moment to stabilize after load.
|
|
1159
|
-
|
|
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
|
+
});
|
|
1160
1162
|
program.parse();
|