@aiready/cli 0.14.6 → 0.14.7
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 +6 -6
- package/package.json +12 -12
- package/dist/chunk-2QOU5KKW.mjs +0 -301
- package/dist/chunk-55ZUD52M.mjs +0 -290
- package/dist/chunk-7K3CNS6C.mjs +0 -234
- package/dist/chunk-7MDDDPX6.mjs +0 -273
- package/dist/chunk-BD7XZYET.mjs +0 -302
- package/dist/chunk-CZBMZD6J.mjs +0 -282
- package/dist/chunk-DWSC3FOY.mjs +0 -241
- package/dist/chunk-EPASFL7Y.mjs +0 -281
- package/dist/chunk-HWO2J4GA.mjs +0 -250
- package/dist/chunk-L4MJHD72.mjs +0 -239
- package/dist/chunk-LTUQDJPO.mjs +0 -229
- package/dist/chunk-MVEYWWJM.mjs +0 -1894
- package/dist/chunk-N56YAZVN.mjs +0 -194
- package/dist/chunk-OOQTTRPG.mjs +0 -280
- package/dist/chunk-XUNVVWID.mjs +0 -272
- package/dist/chunk-YBZKPKW3.mjs +0 -161
- package/dist/chunk-YNGTO2UX.mjs +0 -277
- package/dist/commands-QT3SFOMU.mjs +0 -43
package/dist/chunk-MVEYWWJM.mjs
DELETED
|
@@ -1,1894 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__require,
|
|
3
|
-
analyzeUnified,
|
|
4
|
-
scoreUnified
|
|
5
|
-
} from "./chunk-VOKP7FGM.mjs";
|
|
6
|
-
|
|
7
|
-
// src/commands/scan.ts
|
|
8
|
-
import chalk4 from "chalk";
|
|
9
|
-
import { writeFileSync, readFileSync as readFileSync2 } from "fs";
|
|
10
|
-
import { resolve as resolvePath3 } from "path";
|
|
11
|
-
import {
|
|
12
|
-
loadMergedConfig,
|
|
13
|
-
handleJSONOutput,
|
|
14
|
-
handleCLIError as handleCLIError2,
|
|
15
|
-
resolveOutputPath,
|
|
16
|
-
getRepoMetadata,
|
|
17
|
-
calculateTokenBudget,
|
|
18
|
-
ToolName,
|
|
19
|
-
emitIssuesAsAnnotations
|
|
20
|
-
} from "@aiready/core";
|
|
21
|
-
|
|
22
|
-
// src/utils/helpers.ts
|
|
23
|
-
import { resolve as resolvePath } from "path";
|
|
24
|
-
import { existsSync, readFileSync } from "fs";
|
|
25
|
-
import chalk from "chalk";
|
|
26
|
-
import { loadConfig, mergeConfigWithDefaults } from "@aiready/core";
|
|
27
|
-
import { findLatestReport } from "@aiready/core";
|
|
28
|
-
function getReportTimestamp() {
|
|
29
|
-
const now = /* @__PURE__ */ new Date();
|
|
30
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
31
|
-
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
32
|
-
}
|
|
33
|
-
async function warnIfGraphCapExceeded(report, dirPath) {
|
|
34
|
-
try {
|
|
35
|
-
const { loadConfig: loadConfig4 } = await import("@aiready/core");
|
|
36
|
-
void loadConfig4;
|
|
37
|
-
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
38
|
-
const configPath = resolvePath(dirPath, "aiready.json");
|
|
39
|
-
if (existsSync(configPath)) {
|
|
40
|
-
try {
|
|
41
|
-
const rawConfig = JSON.parse(readFileSync(configPath, "utf8"));
|
|
42
|
-
if (rawConfig.visualizer?.graph) {
|
|
43
|
-
graphConfig = {
|
|
44
|
-
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
45
|
-
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
void err;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const nodeCount = (report.context?.length || 0) + (report.patterns?.length || 0);
|
|
53
|
-
const edgeCount = report.context?.reduce((sum, ctx) => {
|
|
54
|
-
const relCount = ctx.relatedFiles?.length || 0;
|
|
55
|
-
const depCount = ctx.dependencies?.length || 0;
|
|
56
|
-
return sum + relCount + depCount;
|
|
57
|
-
}, 0) || 0;
|
|
58
|
-
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
59
|
-
console.log("");
|
|
60
|
-
console.log(
|
|
61
|
-
chalk.yellow(`\u26A0\uFE0F Graph may be truncated at visualization time:`)
|
|
62
|
-
);
|
|
63
|
-
if (nodeCount > graphConfig.maxNodes) {
|
|
64
|
-
console.log(
|
|
65
|
-
chalk.dim(` \u2022 Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`)
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
if (edgeCount > graphConfig.maxEdges) {
|
|
69
|
-
console.log(
|
|
70
|
-
chalk.dim(` \u2022 Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`)
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
console.log(chalk.dim(` To increase limits, add to aiready.json:`));
|
|
74
|
-
console.log(chalk.dim(` {`));
|
|
75
|
-
console.log(chalk.dim(` "visualizer": {`));
|
|
76
|
-
console.log(
|
|
77
|
-
chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`)
|
|
78
|
-
);
|
|
79
|
-
console.log(chalk.dim(` }`));
|
|
80
|
-
console.log(chalk.dim(` }`));
|
|
81
|
-
}
|
|
82
|
-
} catch (err) {
|
|
83
|
-
void err;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function generateMarkdownReport(report, elapsedTime) {
|
|
87
|
-
let markdown = `# Consistency Analysis Report
|
|
88
|
-
|
|
89
|
-
`;
|
|
90
|
-
markdown += `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
91
|
-
`;
|
|
92
|
-
markdown += `**Analysis Time:** ${elapsedTime}s
|
|
93
|
-
|
|
94
|
-
`;
|
|
95
|
-
markdown += `## Summary
|
|
96
|
-
|
|
97
|
-
`;
|
|
98
|
-
markdown += `- **Files Analyzed:** ${report.summary.filesAnalyzed}
|
|
99
|
-
`;
|
|
100
|
-
markdown += `- **Total Issues:** ${report.summary.totalIssues}
|
|
101
|
-
`;
|
|
102
|
-
markdown += ` - Naming: ${report.summary.namingIssues}
|
|
103
|
-
`;
|
|
104
|
-
markdown += ` - Patterns: ${report.summary.patternIssues}
|
|
105
|
-
|
|
106
|
-
`;
|
|
107
|
-
if (report.recommendations.length > 0) {
|
|
108
|
-
markdown += `## Recommendations
|
|
109
|
-
|
|
110
|
-
`;
|
|
111
|
-
report.recommendations.forEach((rec, i) => {
|
|
112
|
-
markdown += `${i + 1}. ${rec}
|
|
113
|
-
`;
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
return markdown;
|
|
117
|
-
}
|
|
118
|
-
async function loadMergedToolConfig(directory, defaults) {
|
|
119
|
-
const config = await loadConfig(directory);
|
|
120
|
-
return mergeConfigWithDefaults(config, defaults);
|
|
121
|
-
}
|
|
122
|
-
function buildCommonScanOptions(directory, options, extras = {}) {
|
|
123
|
-
return {
|
|
124
|
-
rootDir: directory,
|
|
125
|
-
include: options.include,
|
|
126
|
-
exclude: options.exclude,
|
|
127
|
-
...extras
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
async function runConfiguredToolCommand(params) {
|
|
131
|
-
const merged = await loadMergedToolConfig(params.directory, params.defaults);
|
|
132
|
-
const report = await params.analyze(
|
|
133
|
-
buildCommonScanOptions(
|
|
134
|
-
params.directory,
|
|
135
|
-
params.options,
|
|
136
|
-
params.getExtras(params.options, merged)
|
|
137
|
-
)
|
|
138
|
-
);
|
|
139
|
-
return {
|
|
140
|
-
report,
|
|
141
|
-
scoring: params.score(report)
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// src/commands/report-formatter.ts
|
|
146
|
-
import chalk2 from "chalk";
|
|
147
|
-
import {
|
|
148
|
-
Severity,
|
|
149
|
-
formatScore,
|
|
150
|
-
getRating,
|
|
151
|
-
getRatingDisplay
|
|
152
|
-
} from "@aiready/core";
|
|
153
|
-
function printScanSummary(results, startTime) {
|
|
154
|
-
console.log(chalk2.cyan("\n=== AIReady Run Summary ==="));
|
|
155
|
-
console.log(
|
|
156
|
-
` Total issues (all tools): ${chalk2.bold(String(results.summary.totalIssues || 0))}`
|
|
157
|
-
);
|
|
158
|
-
console.log(
|
|
159
|
-
` Execution time: ${chalk2.bold(((Date.now() - startTime) / 1e3).toFixed(2) + "s")}`
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
function printBusinessImpact(roi, unifiedBudget) {
|
|
163
|
-
console.log(chalk2.bold("\n\u{1F4B0} Business Impact Analysis (Monthly)"));
|
|
164
|
-
console.log(
|
|
165
|
-
` Potential Savings: ${chalk2.green(chalk2.bold("$" + roi.monthlySavings.toLocaleString()))}`
|
|
166
|
-
);
|
|
167
|
-
console.log(
|
|
168
|
-
` Productivity Gain: ${chalk2.cyan(chalk2.bold(roi.productivityGainHours + "h"))} (est. dev time)`
|
|
169
|
-
);
|
|
170
|
-
console.log(
|
|
171
|
-
` Context Efficiency: ${chalk2.yellow((unifiedBudget.efficiencyRatio * 100).toFixed(0) + "%")}`
|
|
172
|
-
);
|
|
173
|
-
console.log(
|
|
174
|
-
` Annual Value: ${chalk2.bold("$" + roi.annualValue.toLocaleString())} (ROI Prediction)`
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
function printScoring(scoringResult, scoringProfile) {
|
|
178
|
-
console.log(chalk2.bold("\n\u{1F4CA} AI Readiness Overall Score"));
|
|
179
|
-
console.log(` ${formatScore(scoringResult)}`);
|
|
180
|
-
console.log(chalk2.dim(` (Scoring Profile: ${scoringProfile})`));
|
|
181
|
-
if (scoringResult.breakdown) {
|
|
182
|
-
console.log(chalk2.bold("\nTool breakdown:"));
|
|
183
|
-
scoringResult.breakdown.forEach((tool) => {
|
|
184
|
-
const rating = getRating(tool.score);
|
|
185
|
-
const emoji = getRatingDisplay(rating).emoji;
|
|
186
|
-
console.log(
|
|
187
|
-
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${emoji}`
|
|
188
|
-
);
|
|
189
|
-
});
|
|
190
|
-
const allRecs = scoringResult.breakdown.flatMap(
|
|
191
|
-
(t) => (t.recommendations || []).map((r) => ({ ...r, tool: t.toolName }))
|
|
192
|
-
).sort((a, b) => b.estimatedImpact - a.estimatedImpact).slice(0, 3);
|
|
193
|
-
if (allRecs.length > 0) {
|
|
194
|
-
console.log(chalk2.bold("\n\u{1F3AF} Top Actionable Recommendations:"));
|
|
195
|
-
allRecs.forEach((rec, i) => {
|
|
196
|
-
const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
|
|
197
|
-
console.log(` ${i + 1}. ${priorityIcon} ${chalk2.bold(rec.action)}`);
|
|
198
|
-
console.log(
|
|
199
|
-
` Impact: ${chalk2.green(`+${rec.estimatedImpact} points`)} to ${rec.tool}`
|
|
200
|
-
);
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
function mapToUnifiedReport(res, scoring) {
|
|
206
|
-
const allResults = [];
|
|
207
|
-
const totalFilesSet = /* @__PURE__ */ new Set();
|
|
208
|
-
let criticalCount = 0;
|
|
209
|
-
let majorCount = 0;
|
|
210
|
-
res.summary.toolsRun.forEach((toolId) => {
|
|
211
|
-
const spokeRes = res[toolId];
|
|
212
|
-
if (!spokeRes || !spokeRes.results) return;
|
|
213
|
-
spokeRes.results.forEach((r) => {
|
|
214
|
-
totalFilesSet.add(r.fileName);
|
|
215
|
-
allResults.push(r);
|
|
216
|
-
r.issues?.forEach((i) => {
|
|
217
|
-
if (i.severity === Severity.Critical || i.severity === "critical")
|
|
218
|
-
criticalCount++;
|
|
219
|
-
if (i.severity === Severity.Major || i.severity === "major")
|
|
220
|
-
majorCount++;
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
return {
|
|
225
|
-
...res,
|
|
226
|
-
results: allResults,
|
|
227
|
-
summary: {
|
|
228
|
-
...res.summary,
|
|
229
|
-
totalFiles: totalFilesSet.size,
|
|
230
|
-
criticalIssues: criticalCount,
|
|
231
|
-
majorIssues: majorCount
|
|
232
|
-
},
|
|
233
|
-
scoring
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// src/commands/upload.ts
|
|
238
|
-
import fs from "fs";
|
|
239
|
-
import { resolve as resolvePath2 } from "path";
|
|
240
|
-
import chalk3 from "chalk";
|
|
241
|
-
import { handleCLIError } from "@aiready/core";
|
|
242
|
-
async function uploadAction(file, options) {
|
|
243
|
-
const startTime = Date.now();
|
|
244
|
-
const filePath = resolvePath2(process.cwd(), file);
|
|
245
|
-
const serverUrl = options.server || process.env.AIREADY_SERVER || "https://dev.platform.getaiready.dev";
|
|
246
|
-
const apiKey = options.apiKey || process.env.AIREADY_API_KEY;
|
|
247
|
-
if (!apiKey) {
|
|
248
|
-
console.error(chalk3.red("\u274C API Key is required for upload."));
|
|
249
|
-
console.log(
|
|
250
|
-
chalk3.dim(
|
|
251
|
-
" Set AIREADY_API_KEY environment variable or use --api-key flag."
|
|
252
|
-
)
|
|
253
|
-
);
|
|
254
|
-
console.log(
|
|
255
|
-
chalk3.dim(
|
|
256
|
-
" Get an API key from https://platform.getaiready.dev/dashboard"
|
|
257
|
-
)
|
|
258
|
-
);
|
|
259
|
-
process.exit(1);
|
|
260
|
-
}
|
|
261
|
-
if (!fs.existsSync(filePath)) {
|
|
262
|
-
console.error(chalk3.red(`\u274C File not found: ${filePath}`));
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
console.log(chalk3.blue(`\u{1F680} Uploading report to ${serverUrl}...`));
|
|
267
|
-
console.log(chalk3.dim(` Reading report from ${filePath}...`));
|
|
268
|
-
const reportContent = fs.readFileSync(filePath, "utf-8");
|
|
269
|
-
const reportData = JSON.parse(reportContent);
|
|
270
|
-
console.log(chalk3.dim(` Successfully parsed report JSON.`));
|
|
271
|
-
const repoId = options.repoId || reportData.repository?.repoId;
|
|
272
|
-
const response = await fetch(`${serverUrl}/api/analysis/upload`, {
|
|
273
|
-
method: "POST",
|
|
274
|
-
headers: {
|
|
275
|
-
"Content-Type": "application/json",
|
|
276
|
-
Authorization: `Bearer ${apiKey}`
|
|
277
|
-
},
|
|
278
|
-
body: JSON.stringify({
|
|
279
|
-
data: reportData,
|
|
280
|
-
repoId
|
|
281
|
-
// Might be null, server will handle mapping
|
|
282
|
-
})
|
|
283
|
-
});
|
|
284
|
-
const contentType = response.headers.get("content-type");
|
|
285
|
-
let uploadResult = {};
|
|
286
|
-
if (contentType?.includes("application/json")) {
|
|
287
|
-
uploadResult = await response.json();
|
|
288
|
-
} else {
|
|
289
|
-
const text = await response.text();
|
|
290
|
-
uploadResult = { error: text || response.statusText };
|
|
291
|
-
}
|
|
292
|
-
if (!response.ok) {
|
|
293
|
-
console.error(
|
|
294
|
-
chalk3.red(
|
|
295
|
-
`\u274C Upload failed: ${uploadResult.error || response.statusText}`
|
|
296
|
-
)
|
|
297
|
-
);
|
|
298
|
-
if (contentType?.includes("text/html")) {
|
|
299
|
-
console.log(
|
|
300
|
-
chalk3.yellow(
|
|
301
|
-
" Note: Received an HTML response. This often indicates a redirect (e.g., to a login page) or a server error."
|
|
302
|
-
)
|
|
303
|
-
);
|
|
304
|
-
if (uploadResult.error?.includes("Redirecting")) {
|
|
305
|
-
console.log(
|
|
306
|
-
chalk3.dim(
|
|
307
|
-
" Detected redirect. Check if the API endpoint requires authentication or has changed."
|
|
308
|
-
)
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (response.status === 401) {
|
|
313
|
-
console.log(
|
|
314
|
-
chalk3.dim(" Hint: Your API key may be invalid or expired.")
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
process.exit(1);
|
|
318
|
-
}
|
|
319
|
-
const duration = ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
320
|
-
console.log(chalk3.green(`
|
|
321
|
-
\u2705 Upload successful! (${duration}s)`));
|
|
322
|
-
console.log(chalk3.cyan(` View results: ${serverUrl}/dashboard`));
|
|
323
|
-
if (uploadResult.analysis) {
|
|
324
|
-
console.log(chalk3.dim(` Analysis ID: ${uploadResult.analysis.id}`));
|
|
325
|
-
console.log(chalk3.dim(` Score: ${uploadResult.analysis.aiScore}/100`));
|
|
326
|
-
}
|
|
327
|
-
} catch (error) {
|
|
328
|
-
handleCLIError(error, "Upload");
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
var uploadHelpText = `
|
|
332
|
-
EXAMPLES:
|
|
333
|
-
$ aiready upload report.json --api-key ar_...
|
|
334
|
-
$ aiready upload .aiready/latest.json
|
|
335
|
-
$ AIREADY_API_KEY=ar_... aiready upload report.json
|
|
336
|
-
|
|
337
|
-
ENVIRONMENT VARIABLES:
|
|
338
|
-
AIREADY_API_KEY Your platform API key
|
|
339
|
-
AIREADY_SERVER Custom platform URL (default: https://dev.platform.getaiready.dev)
|
|
340
|
-
`;
|
|
341
|
-
|
|
342
|
-
// src/commands/scan.ts
|
|
343
|
-
async function scanAction(directory, options) {
|
|
344
|
-
console.log(chalk4.blue("\u{1F680} Starting AIReady unified analysis...\n"));
|
|
345
|
-
const startTime = Date.now();
|
|
346
|
-
const resolvedDir = resolvePath3(process.cwd(), directory || ".");
|
|
347
|
-
const repoMetadata = getRepoMetadata(resolvedDir);
|
|
348
|
-
try {
|
|
349
|
-
const defaults = {
|
|
350
|
-
tools: [
|
|
351
|
-
"pattern-detect",
|
|
352
|
-
"context-analyzer",
|
|
353
|
-
"naming-consistency",
|
|
354
|
-
"ai-signal-clarity",
|
|
355
|
-
"agent-grounding",
|
|
356
|
-
"testability-index",
|
|
357
|
-
"doc-drift",
|
|
358
|
-
"dependency-health",
|
|
359
|
-
"change-amplification"
|
|
360
|
-
],
|
|
361
|
-
include: void 0,
|
|
362
|
-
exclude: void 0,
|
|
363
|
-
output: {
|
|
364
|
-
format: "console",
|
|
365
|
-
file: void 0
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
let profileTools = options.tools ? options.tools.split(",").map((t) => t.trim()) : void 0;
|
|
369
|
-
if (options.profile) {
|
|
370
|
-
switch (options.profile.toLowerCase()) {
|
|
371
|
-
case "agentic":
|
|
372
|
-
profileTools = [
|
|
373
|
-
ToolName.AiSignalClarity,
|
|
374
|
-
ToolName.AgentGrounding,
|
|
375
|
-
ToolName.TestabilityIndex
|
|
376
|
-
];
|
|
377
|
-
break;
|
|
378
|
-
case "cost":
|
|
379
|
-
profileTools = [ToolName.PatternDetect, ToolName.ContextAnalyzer];
|
|
380
|
-
break;
|
|
381
|
-
case "logic":
|
|
382
|
-
profileTools = [
|
|
383
|
-
ToolName.TestabilityIndex,
|
|
384
|
-
ToolName.NamingConsistency,
|
|
385
|
-
ToolName.ContextAnalyzer,
|
|
386
|
-
ToolName.PatternDetect,
|
|
387
|
-
ToolName.ChangeAmplification
|
|
388
|
-
];
|
|
389
|
-
break;
|
|
390
|
-
case "ui":
|
|
391
|
-
profileTools = [
|
|
392
|
-
ToolName.NamingConsistency,
|
|
393
|
-
ToolName.ContextAnalyzer,
|
|
394
|
-
ToolName.PatternDetect,
|
|
395
|
-
ToolName.DocDrift,
|
|
396
|
-
ToolName.AiSignalClarity
|
|
397
|
-
];
|
|
398
|
-
break;
|
|
399
|
-
case "security":
|
|
400
|
-
profileTools = [
|
|
401
|
-
ToolName.NamingConsistency,
|
|
402
|
-
ToolName.TestabilityIndex
|
|
403
|
-
];
|
|
404
|
-
break;
|
|
405
|
-
case "onboarding":
|
|
406
|
-
profileTools = [
|
|
407
|
-
ToolName.ContextAnalyzer,
|
|
408
|
-
ToolName.NamingConsistency,
|
|
409
|
-
ToolName.AgentGrounding
|
|
410
|
-
];
|
|
411
|
-
break;
|
|
412
|
-
default:
|
|
413
|
-
console.log(
|
|
414
|
-
chalk4.yellow(
|
|
415
|
-
`
|
|
416
|
-
\u26A0\uFE0F Unknown profile '${options.profile}'. Using defaults.`
|
|
417
|
-
)
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const cliOverrides = {
|
|
422
|
-
include: options.include?.split(","),
|
|
423
|
-
exclude: options.exclude?.split(",")
|
|
424
|
-
};
|
|
425
|
-
if (profileTools) cliOverrides.tools = profileTools;
|
|
426
|
-
const baseOptions = await loadMergedConfig(
|
|
427
|
-
resolvedDir,
|
|
428
|
-
defaults,
|
|
429
|
-
cliOverrides
|
|
430
|
-
);
|
|
431
|
-
const finalOptions = { ...baseOptions };
|
|
432
|
-
if (baseOptions.tools.includes(ToolName.PatternDetect) || baseOptions.tools.includes("patterns")) {
|
|
433
|
-
const { getSmartDefaults } = await import("@aiready/pattern-detect");
|
|
434
|
-
const patternSmartDefaults = await getSmartDefaults(
|
|
435
|
-
resolvedDir,
|
|
436
|
-
finalOptions.toolConfigs?.[ToolName.PatternDetect] || {}
|
|
437
|
-
);
|
|
438
|
-
if (!finalOptions.toolConfigs) finalOptions.toolConfigs = {};
|
|
439
|
-
finalOptions.toolConfigs[ToolName.PatternDetect] = {
|
|
440
|
-
...patternSmartDefaults,
|
|
441
|
-
...finalOptions.toolConfigs[ToolName.PatternDetect]
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
console.log(chalk4.cyan("\n=== AIReady Run Preview ==="));
|
|
445
|
-
console.log(
|
|
446
|
-
chalk4.white("Tools to run:"),
|
|
447
|
-
(finalOptions.tools || []).join(", ")
|
|
448
|
-
);
|
|
449
|
-
const progressCallback = (event) => {
|
|
450
|
-
if (event.message) {
|
|
451
|
-
process.stdout.write(`\r\x1B[K [${event.tool}] ${event.message}`);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
process.stdout.write("\r\x1B[K");
|
|
455
|
-
console.log(chalk4.cyan(`--- ${event.tool.toUpperCase()} RESULTS ---`));
|
|
456
|
-
const res = event.data;
|
|
457
|
-
if (res && res.summary) {
|
|
458
|
-
if (res.summary.totalIssues !== void 0)
|
|
459
|
-
console.log(` Issues found: ${chalk4.bold(res.summary.totalIssues)}`);
|
|
460
|
-
if (res.summary.score !== void 0)
|
|
461
|
-
console.log(` Tool Score: ${chalk4.bold(res.summary.score)}/100`);
|
|
462
|
-
if (res.summary.totalFiles !== void 0)
|
|
463
|
-
console.log(
|
|
464
|
-
` Files analyzed: ${chalk4.bold(res.summary.totalFiles)}`
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
const scoringProfile = options.profile || baseOptions.scoring?.profile || "default";
|
|
469
|
-
const results = await analyzeUnified({
|
|
470
|
-
...finalOptions,
|
|
471
|
-
progressCallback,
|
|
472
|
-
onProgress: () => {
|
|
473
|
-
},
|
|
474
|
-
suppressToolConfig: true
|
|
475
|
-
});
|
|
476
|
-
printScanSummary(results, startTime);
|
|
477
|
-
let scoringResult;
|
|
478
|
-
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
479
|
-
scoringResult = await scoreUnified(results, {
|
|
480
|
-
...finalOptions,
|
|
481
|
-
scoring: {
|
|
482
|
-
...finalOptions.scoring,
|
|
483
|
-
profile: scoringProfile
|
|
484
|
-
}
|
|
485
|
-
});
|
|
486
|
-
printScoring(scoringResult, scoringProfile);
|
|
487
|
-
if (options.compareTo) {
|
|
488
|
-
try {
|
|
489
|
-
const prevReport = JSON.parse(
|
|
490
|
-
readFileSync2(resolvePath3(process.cwd(), options.compareTo), "utf8")
|
|
491
|
-
);
|
|
492
|
-
const prevScore = prevReport.scoring?.overall || prevReport.scoring?.score;
|
|
493
|
-
if (typeof prevScore === "number") {
|
|
494
|
-
const diff = scoringResult.overall - prevScore;
|
|
495
|
-
const diffStr = diff > 0 ? `+${diff}` : String(diff);
|
|
496
|
-
if (diff > 0)
|
|
497
|
-
console.log(
|
|
498
|
-
chalk4.green(
|
|
499
|
-
` \u{1F4C8} Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`
|
|
500
|
-
)
|
|
501
|
-
);
|
|
502
|
-
else if (diff < 0)
|
|
503
|
-
console.log(
|
|
504
|
-
chalk4.red(
|
|
505
|
-
` \u{1F4C9} Trend: ${diffStr} compared to ${options.compareTo} (${prevScore} \u2192 ${scoringResult.overall})`
|
|
506
|
-
)
|
|
507
|
-
);
|
|
508
|
-
else
|
|
509
|
-
console.log(
|
|
510
|
-
chalk4.blue(
|
|
511
|
-
` \u2796 Trend: No change (${prevScore} \u2192 ${scoringResult.overall})`
|
|
512
|
-
)
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
} catch (e) {
|
|
516
|
-
void e;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
const totalWastedDuplication = (scoringResult.breakdown || []).reduce(
|
|
520
|
-
(sum, s) => sum + (s.tokenBudget?.wastedTokens.bySource.duplication || 0),
|
|
521
|
-
0
|
|
522
|
-
);
|
|
523
|
-
const totalWastedFragmentation = (scoringResult.breakdown || []).reduce(
|
|
524
|
-
(sum, s) => sum + (s.tokenBudget?.wastedTokens.bySource.fragmentation || 0),
|
|
525
|
-
0
|
|
526
|
-
);
|
|
527
|
-
const totalContext = Math.max(
|
|
528
|
-
...(scoringResult.breakdown || []).map(
|
|
529
|
-
(s) => s.tokenBudget?.totalContextTokens || 0
|
|
530
|
-
),
|
|
531
|
-
0
|
|
532
|
-
);
|
|
533
|
-
if (totalContext > 0) {
|
|
534
|
-
const unifiedBudget = calculateTokenBudget({
|
|
535
|
-
totalContextTokens: totalContext,
|
|
536
|
-
wastedTokens: {
|
|
537
|
-
duplication: totalWastedDuplication,
|
|
538
|
-
fragmentation: totalWastedFragmentation,
|
|
539
|
-
chattiness: totalContext * 0.1
|
|
540
|
-
// Default chattiness
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
const allIssues = [];
|
|
544
|
-
for (const toolId of results.summary.toolsRun) {
|
|
545
|
-
if (results[toolId]?.results) {
|
|
546
|
-
results[toolId].results.forEach((fileRes) => {
|
|
547
|
-
if (fileRes.issues) {
|
|
548
|
-
allIssues.push(...fileRes.issues);
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
const modelId = options.model || "claude-3-5-sonnet";
|
|
554
|
-
const roi = (await import("@aiready/core")).calculateBusinessROI({
|
|
555
|
-
tokenWaste: unifiedBudget.wastedTokens.total,
|
|
556
|
-
issues: allIssues,
|
|
557
|
-
modelId
|
|
558
|
-
});
|
|
559
|
-
printBusinessImpact(roi, unifiedBudget);
|
|
560
|
-
results.summary.businessImpact = {
|
|
561
|
-
estimatedMonthlyWaste: roi.monthlySavings,
|
|
562
|
-
potentialSavings: roi.monthlySavings,
|
|
563
|
-
productivityHours: roi.productivityGainHours
|
|
564
|
-
};
|
|
565
|
-
scoringResult.tokenBudget = unifiedBudget;
|
|
566
|
-
scoringResult.businessROI = roi;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
console.log(
|
|
570
|
-
chalk4.dim(
|
|
571
|
-
"\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
572
|
-
)
|
|
573
|
-
);
|
|
574
|
-
console.log(chalk4.dim("\u{1F4AC} Found a bug or have a metric idea?"));
|
|
575
|
-
console.log(chalk4.dim("\u{1F449} Copy/paste this to your AI agent:"));
|
|
576
|
-
console.log(
|
|
577
|
-
chalk4.cyan(
|
|
578
|
-
` "Any feedback for the tools? Please use 'aiready bug' to report \u2764\uFE0F"`
|
|
579
|
-
)
|
|
580
|
-
);
|
|
581
|
-
console.log(
|
|
582
|
-
chalk4.dim(
|
|
583
|
-
"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
584
|
-
)
|
|
585
|
-
);
|
|
586
|
-
const outputData = {
|
|
587
|
-
...mapToUnifiedReport(results, scoringResult),
|
|
588
|
-
repository: repoMetadata
|
|
589
|
-
};
|
|
590
|
-
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
591
|
-
const outputPath = resolveOutputPath(
|
|
592
|
-
options.outputFile || finalOptions.output?.file,
|
|
593
|
-
`aiready-report-${getReportTimestamp()}.json`,
|
|
594
|
-
resolvedDir
|
|
595
|
-
);
|
|
596
|
-
if (outputFormat === "json") {
|
|
597
|
-
handleJSONOutput(
|
|
598
|
-
outputData,
|
|
599
|
-
outputPath,
|
|
600
|
-
`\u2705 Report saved to ${outputPath}`
|
|
601
|
-
);
|
|
602
|
-
} else {
|
|
603
|
-
try {
|
|
604
|
-
writeFileSync(outputPath, JSON.stringify(outputData, null, 2));
|
|
605
|
-
console.log(chalk4.dim(`\u2705 Report auto-persisted to ${outputPath}`));
|
|
606
|
-
} catch (err) {
|
|
607
|
-
void err;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
if (options.upload) {
|
|
611
|
-
await uploadAction(outputPath, {
|
|
612
|
-
apiKey: options.apiKey,
|
|
613
|
-
server: options.server
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
await warnIfGraphCapExceeded(outputData, resolvedDir);
|
|
617
|
-
if (scoringResult) {
|
|
618
|
-
const threshold = options.threshold ? parseInt(options.threshold) : void 0;
|
|
619
|
-
const failOnLevel = options.failOn || "critical";
|
|
620
|
-
const isCI = options.ci || process.env.CI === "true";
|
|
621
|
-
let shouldFail = false;
|
|
622
|
-
let failReason = "";
|
|
623
|
-
const report = mapToUnifiedReport(results, scoringResult);
|
|
624
|
-
if (isCI && report.results && report.results.length > 0) {
|
|
625
|
-
console.log(
|
|
626
|
-
chalk4.cyan(
|
|
627
|
-
`
|
|
628
|
-
\u{1F4DD} Emitting GitHub Action annotations for ${report.results.length} issues...`
|
|
629
|
-
)
|
|
630
|
-
);
|
|
631
|
-
emitIssuesAsAnnotations(report.results);
|
|
632
|
-
}
|
|
633
|
-
if (threshold && scoringResult.overall < threshold) {
|
|
634
|
-
shouldFail = true;
|
|
635
|
-
failReason = `Score ${scoringResult.overall} < threshold ${threshold}`;
|
|
636
|
-
}
|
|
637
|
-
if (failOnLevel !== "none") {
|
|
638
|
-
if (failOnLevel === "critical" && report.summary.criticalIssues > 0) {
|
|
639
|
-
shouldFail = true;
|
|
640
|
-
failReason = `Found ${report.summary.criticalIssues} critical issues`;
|
|
641
|
-
} else if (failOnLevel === "major" && report.summary.criticalIssues + report.summary.majorIssues > 0) {
|
|
642
|
-
shouldFail = true;
|
|
643
|
-
failReason = `Found ${report.summary.criticalIssues} critical and ${report.summary.majorIssues} major issues`;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
if (shouldFail) {
|
|
647
|
-
console.log(chalk4.red(`
|
|
648
|
-
\u{1F6AB} SCAN FAILED: ${failReason}`));
|
|
649
|
-
process.exit(1);
|
|
650
|
-
} else {
|
|
651
|
-
console.log(chalk4.green("\n\u2705 SCAN PASSED"));
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
} catch (error) {
|
|
655
|
-
handleCLIError2(error, "Analysis");
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
var scanHelpText = `...`;
|
|
659
|
-
|
|
660
|
-
// src/commands/init.ts
|
|
661
|
-
import { writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
662
|
-
import { join } from "path";
|
|
663
|
-
import chalk5 from "chalk";
|
|
664
|
-
import { ToolName as ToolName2 } from "@aiready/core";
|
|
665
|
-
async function initAction(options) {
|
|
666
|
-
const fileExt = options.format === "js" ? "js" : "json";
|
|
667
|
-
const fileName = fileExt === "js" ? "aiready.config.js" : "aiready.json";
|
|
668
|
-
const filePath = join(process.cwd(), fileName);
|
|
669
|
-
if (existsSync2(filePath) && !options.force) {
|
|
670
|
-
console.error(
|
|
671
|
-
chalk5.red(`Error: ${fileName} already exists. Use --force to overwrite.`)
|
|
672
|
-
);
|
|
673
|
-
process.exit(1);
|
|
674
|
-
}
|
|
675
|
-
const baseConfig = {
|
|
676
|
-
scan: {
|
|
677
|
-
include: [
|
|
678
|
-
"src/**/*.ts",
|
|
679
|
-
"src/**/*.js",
|
|
680
|
-
"lib/**/*.ts",
|
|
681
|
-
"packages/*/src/**/*.ts"
|
|
682
|
-
],
|
|
683
|
-
exclude: [
|
|
684
|
-
"**/node_modules/**",
|
|
685
|
-
"**/dist/**",
|
|
686
|
-
"**/build/**",
|
|
687
|
-
"**/*.test.ts",
|
|
688
|
-
"**/*.spec.ts"
|
|
689
|
-
],
|
|
690
|
-
tools: [
|
|
691
|
-
ToolName2.PatternDetect,
|
|
692
|
-
ToolName2.ContextAnalyzer,
|
|
693
|
-
ToolName2.NamingConsistency,
|
|
694
|
-
ToolName2.AiSignalClarity,
|
|
695
|
-
ToolName2.AgentGrounding,
|
|
696
|
-
ToolName2.TestabilityIndex,
|
|
697
|
-
ToolName2.DocDrift,
|
|
698
|
-
ToolName2.DependencyHealth,
|
|
699
|
-
ToolName2.ChangeAmplification
|
|
700
|
-
]
|
|
701
|
-
},
|
|
702
|
-
tools: {
|
|
703
|
-
[ToolName2.PatternDetect]: {
|
|
704
|
-
minSimilarity: 0.8,
|
|
705
|
-
minLines: 5,
|
|
706
|
-
...options.full ? {
|
|
707
|
-
batchSize: 50,
|
|
708
|
-
approx: true,
|
|
709
|
-
minSharedTokens: 10,
|
|
710
|
-
maxCandidatesPerBlock: 100
|
|
711
|
-
} : {}
|
|
712
|
-
},
|
|
713
|
-
[ToolName2.ContextAnalyzer]: {
|
|
714
|
-
maxContextBudget: 128e3,
|
|
715
|
-
minCohesion: 0.6,
|
|
716
|
-
...options.full ? {
|
|
717
|
-
maxDepth: 7,
|
|
718
|
-
maxFragmentation: 0.4,
|
|
719
|
-
focus: "all",
|
|
720
|
-
includeNodeModules: false
|
|
721
|
-
} : {}
|
|
722
|
-
},
|
|
723
|
-
[ToolName2.NamingConsistency]: {
|
|
724
|
-
shortWords: ["id", "db", "ui", "ai"],
|
|
725
|
-
...options.full ? { acceptedAbbreviations: [], disableChecks: [] } : {}
|
|
726
|
-
},
|
|
727
|
-
[ToolName2.AiSignalClarity]: {
|
|
728
|
-
checkMagicLiterals: true,
|
|
729
|
-
checkBooleanTraps: true,
|
|
730
|
-
checkAmbiguousNames: true,
|
|
731
|
-
checkUndocumentedExports: true,
|
|
732
|
-
...options.full ? { checkImplicitSideEffects: false, checkDeepCallbacks: false } : {}
|
|
733
|
-
},
|
|
734
|
-
...options.full ? {
|
|
735
|
-
[ToolName2.AgentGrounding]: {
|
|
736
|
-
maxRecommendedDepth: 5,
|
|
737
|
-
readmeStaleDays: 30
|
|
738
|
-
},
|
|
739
|
-
[ToolName2.TestabilityIndex]: {
|
|
740
|
-
minCoverageRatio: 0.7,
|
|
741
|
-
testPatterns: ["**/*.test.ts", "**/__tests__/**"]
|
|
742
|
-
},
|
|
743
|
-
[ToolName2.DocDrift]: {
|
|
744
|
-
maxCommits: 50,
|
|
745
|
-
staleMonths: 3
|
|
746
|
-
},
|
|
747
|
-
[ToolName2.DependencyHealth]: {
|
|
748
|
-
trainingCutoffYear: 2023
|
|
749
|
-
}
|
|
750
|
-
} : {}
|
|
751
|
-
},
|
|
752
|
-
scoring: {
|
|
753
|
-
threshold: 70,
|
|
754
|
-
showBreakdown: true,
|
|
755
|
-
...options.full ? { profile: "default" } : {}
|
|
756
|
-
},
|
|
757
|
-
...options.full ? {
|
|
758
|
-
visualizer: {
|
|
759
|
-
groupingDirs: ["packages", "src", "lib"],
|
|
760
|
-
graph: {
|
|
761
|
-
maxNodes: 5e3,
|
|
762
|
-
maxEdges: 1e4
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
} : {}
|
|
766
|
-
};
|
|
767
|
-
const defaultConfig = baseConfig;
|
|
768
|
-
let content;
|
|
769
|
-
if (fileExt === "js") {
|
|
770
|
-
content = `/** @type {import('@aiready/core').AIReadyConfig} */
|
|
771
|
-
module.exports = ${JSON.stringify(
|
|
772
|
-
defaultConfig,
|
|
773
|
-
null,
|
|
774
|
-
2
|
|
775
|
-
)};
|
|
776
|
-
`;
|
|
777
|
-
} else {
|
|
778
|
-
content = JSON.stringify(defaultConfig, null, 2);
|
|
779
|
-
}
|
|
780
|
-
try {
|
|
781
|
-
writeFileSync2(filePath, content, "utf8");
|
|
782
|
-
console.log(
|
|
783
|
-
chalk5.green(`
|
|
784
|
-
\u2705 Created default configuration: ${chalk5.bold(fileName)}`)
|
|
785
|
-
);
|
|
786
|
-
console.log(
|
|
787
|
-
chalk5.cyan("You can now fine-tune your settings and run AIReady with:")
|
|
788
|
-
);
|
|
789
|
-
console.log(chalk5.white(` $ aiready scan
|
|
790
|
-
`));
|
|
791
|
-
} catch (error) {
|
|
792
|
-
console.error(chalk5.red(`Failed to write configuration file: ${error}`));
|
|
793
|
-
process.exit(1);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// src/commands/patterns.ts
|
|
798
|
-
import chalk6 from "chalk";
|
|
799
|
-
import { resolve as resolvePath4 } from "path";
|
|
800
|
-
import {
|
|
801
|
-
loadMergedConfig as loadMergedConfig2,
|
|
802
|
-
handleJSONOutput as handleJSONOutput2,
|
|
803
|
-
handleCLIError as handleCLIError3,
|
|
804
|
-
getElapsedTime,
|
|
805
|
-
resolveOutputPath as resolveOutputPath2,
|
|
806
|
-
formatToolScore
|
|
807
|
-
} from "@aiready/core";
|
|
808
|
-
async function patternsAction(directory, options) {
|
|
809
|
-
console.log(chalk6.blue("\u{1F50D} Analyzing patterns...\n"));
|
|
810
|
-
const startTime = Date.now();
|
|
811
|
-
const resolvedDir = resolvePath4(process.cwd(), directory || ".");
|
|
812
|
-
try {
|
|
813
|
-
const useSmartDefaults = !options.fullScan;
|
|
814
|
-
const defaults = {
|
|
815
|
-
useSmartDefaults,
|
|
816
|
-
include: void 0,
|
|
817
|
-
exclude: void 0,
|
|
818
|
-
output: {
|
|
819
|
-
format: "console",
|
|
820
|
-
file: void 0
|
|
821
|
-
}
|
|
822
|
-
};
|
|
823
|
-
if (!useSmartDefaults) {
|
|
824
|
-
defaults.minSimilarity = 0.4;
|
|
825
|
-
defaults.minLines = 5;
|
|
826
|
-
}
|
|
827
|
-
const cliOptions = {
|
|
828
|
-
minSimilarity: options.similarity ? parseFloat(options.similarity) : void 0,
|
|
829
|
-
minLines: options.minLines ? parseInt(options.minLines) : void 0,
|
|
830
|
-
useSmartDefaults,
|
|
831
|
-
include: options.include?.split(","),
|
|
832
|
-
exclude: options.exclude?.split(",")
|
|
833
|
-
};
|
|
834
|
-
if (options.maxCandidates) {
|
|
835
|
-
cliOptions.maxCandidatesPerBlock = parseInt(options.maxCandidates);
|
|
836
|
-
}
|
|
837
|
-
if (options.minSharedTokens) {
|
|
838
|
-
cliOptions.minSharedTokens = parseInt(options.minSharedTokens);
|
|
839
|
-
}
|
|
840
|
-
const finalOptions = await loadMergedConfig2(
|
|
841
|
-
resolvedDir,
|
|
842
|
-
defaults,
|
|
843
|
-
cliOptions
|
|
844
|
-
);
|
|
845
|
-
const { analyzePatterns, generateSummary, calculatePatternScore } = await import("@aiready/pattern-detect");
|
|
846
|
-
const { results, duplicates } = await analyzePatterns(
|
|
847
|
-
finalOptions
|
|
848
|
-
);
|
|
849
|
-
const elapsedTime = getElapsedTime(startTime);
|
|
850
|
-
const summary = generateSummary(results);
|
|
851
|
-
let patternScore;
|
|
852
|
-
if (options.score) {
|
|
853
|
-
patternScore = calculatePatternScore(duplicates, results.length);
|
|
854
|
-
}
|
|
855
|
-
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
856
|
-
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
857
|
-
if (outputFormat === "json") {
|
|
858
|
-
const outputData = {
|
|
859
|
-
results,
|
|
860
|
-
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
861
|
-
...patternScore && { scoring: patternScore }
|
|
862
|
-
};
|
|
863
|
-
const outputPath = resolveOutputPath2(
|
|
864
|
-
userOutputFile,
|
|
865
|
-
`aiready-report-${getReportTimestamp()}.json`,
|
|
866
|
-
resolvedDir
|
|
867
|
-
);
|
|
868
|
-
handleJSONOutput2(
|
|
869
|
-
outputData,
|
|
870
|
-
outputPath,
|
|
871
|
-
`\u2705 Results saved to ${outputPath}`
|
|
872
|
-
);
|
|
873
|
-
} else {
|
|
874
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
875
|
-
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
876
|
-
const divider = "\u2501".repeat(dividerWidth);
|
|
877
|
-
console.log(chalk6.cyan(divider));
|
|
878
|
-
console.log(chalk6.bold.white(" PATTERN ANALYSIS SUMMARY"));
|
|
879
|
-
console.log(chalk6.cyan(divider) + "\n");
|
|
880
|
-
console.log(
|
|
881
|
-
chalk6.white(`\u{1F4C1} Files analyzed: ${chalk6.bold(results.length)}`)
|
|
882
|
-
);
|
|
883
|
-
console.log(
|
|
884
|
-
chalk6.yellow(
|
|
885
|
-
`\u26A0 Duplicate patterns found: ${chalk6.bold(summary.totalPatterns)}`
|
|
886
|
-
)
|
|
887
|
-
);
|
|
888
|
-
console.log(
|
|
889
|
-
chalk6.red(
|
|
890
|
-
`\u{1F4B0} Token cost (wasted): ${chalk6.bold(summary.totalTokenCost.toLocaleString())}`
|
|
891
|
-
)
|
|
892
|
-
);
|
|
893
|
-
console.log(
|
|
894
|
-
chalk6.gray(`\u23F1 Analysis time: ${chalk6.bold(elapsedTime + "s")}`)
|
|
895
|
-
);
|
|
896
|
-
const sortedTypes = Object.entries(summary.patternsByType || {}).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a);
|
|
897
|
-
if (sortedTypes.length > 0) {
|
|
898
|
-
console.log(chalk6.cyan("\n" + divider));
|
|
899
|
-
console.log(chalk6.bold.white(" PATTERNS BY TYPE"));
|
|
900
|
-
console.log(chalk6.cyan(divider) + "\n");
|
|
901
|
-
sortedTypes.forEach(([type, count]) => {
|
|
902
|
-
console.log(` ${chalk6.white(type.padEnd(15))} ${chalk6.bold(count)}`);
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
if (summary.totalPatterns > 0 && duplicates.length > 0) {
|
|
906
|
-
console.log(chalk6.cyan("\n" + divider));
|
|
907
|
-
console.log(chalk6.bold.white(" TOP DUPLICATE PATTERNS"));
|
|
908
|
-
console.log(chalk6.cyan(divider) + "\n");
|
|
909
|
-
const topDuplicates = [...duplicates].sort((a, b) => b.similarity - a.similarity).slice(0, 10);
|
|
910
|
-
topDuplicates.forEach((dup) => {
|
|
911
|
-
const severity = dup.similarity > 0.95 ? "CRITICAL" : dup.similarity > 0.9 ? "HIGH" : "MEDIUM";
|
|
912
|
-
const severityIcon = dup.similarity > 0.95 ? "\u{1F534}" : dup.similarity > 0.9 ? "\u{1F7E1}" : "\u{1F535}";
|
|
913
|
-
const file1Name = dup.file1.split("/").pop() || dup.file1;
|
|
914
|
-
const file2Name = dup.file2.split("/").pop() || dup.file2;
|
|
915
|
-
console.log(
|
|
916
|
-
`${severityIcon} ${severity}: ${chalk6.bold(file1Name)} \u2194 ${chalk6.bold(file2Name)}`
|
|
917
|
-
);
|
|
918
|
-
console.log(
|
|
919
|
-
` Similarity: ${chalk6.bold(Math.round(dup.similarity * 100) + "%")} | Wasted: ${chalk6.bold(dup.tokenCost.toLocaleString())} tokens each`
|
|
920
|
-
);
|
|
921
|
-
console.log(
|
|
922
|
-
` Lines: ${chalk6.cyan(dup.line1 + "-" + dup.endLine1)} \u2194 ${chalk6.cyan(dup.line2 + "-" + dup.endLine2)}
|
|
923
|
-
`
|
|
924
|
-
);
|
|
925
|
-
});
|
|
926
|
-
} else {
|
|
927
|
-
console.log(
|
|
928
|
-
chalk6.green("\n\u2728 Great! No duplicate patterns detected.\n")
|
|
929
|
-
);
|
|
930
|
-
}
|
|
931
|
-
if (patternScore) {
|
|
932
|
-
console.log(chalk6.cyan(divider));
|
|
933
|
-
console.log(chalk6.bold.white(" AI READINESS SCORE (Patterns)"));
|
|
934
|
-
console.log(chalk6.cyan(divider) + "\n");
|
|
935
|
-
console.log(formatToolScore(patternScore));
|
|
936
|
-
console.log();
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
} catch (error) {
|
|
940
|
-
handleCLIError3(error, "Pattern analysis");
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
var patternsHelpText = `
|
|
944
|
-
EXAMPLES:
|
|
945
|
-
$ aiready patterns # Default analysis
|
|
946
|
-
$ aiready patterns --similarity 0.6 # Stricter matching
|
|
947
|
-
$ aiready patterns --min-lines 10 # Larger patterns only
|
|
948
|
-
`;
|
|
949
|
-
|
|
950
|
-
// src/commands/context.ts
|
|
951
|
-
import chalk7 from "chalk";
|
|
952
|
-
import { resolve as resolvePath5 } from "path";
|
|
953
|
-
import {
|
|
954
|
-
loadMergedConfig as loadMergedConfig3,
|
|
955
|
-
handleJSONOutput as handleJSONOutput3,
|
|
956
|
-
handleCLIError as handleCLIError4,
|
|
957
|
-
getElapsedTime as getElapsedTime2,
|
|
958
|
-
resolveOutputPath as resolveOutputPath3,
|
|
959
|
-
formatToolScore as formatToolScore2
|
|
960
|
-
} from "@aiready/core";
|
|
961
|
-
async function contextAction(directory, options) {
|
|
962
|
-
console.log(chalk7.blue("\u{1F9E0} Analyzing context costs...\n"));
|
|
963
|
-
const startTime = Date.now();
|
|
964
|
-
const resolvedDir = resolvePath5(process.cwd(), directory || ".");
|
|
965
|
-
try {
|
|
966
|
-
const defaults = {
|
|
967
|
-
maxDepth: 5,
|
|
968
|
-
maxContextBudget: 1e4,
|
|
969
|
-
include: void 0,
|
|
970
|
-
exclude: void 0,
|
|
971
|
-
output: {
|
|
972
|
-
format: "console",
|
|
973
|
-
file: void 0
|
|
974
|
-
}
|
|
975
|
-
};
|
|
976
|
-
const baseOptions = await loadMergedConfig3(resolvedDir, defaults, {
|
|
977
|
-
maxDepth: options.maxDepth ? parseInt(options.maxDepth) : void 0,
|
|
978
|
-
maxContextBudget: options.maxContext ? parseInt(options.maxContext) : void 0,
|
|
979
|
-
include: options.include?.split(","),
|
|
980
|
-
exclude: options.exclude?.split(",")
|
|
981
|
-
});
|
|
982
|
-
let finalOptions = { ...baseOptions };
|
|
983
|
-
const { getSmartDefaults } = await import("@aiready/context-analyzer");
|
|
984
|
-
const contextSmartDefaults = await getSmartDefaults(
|
|
985
|
-
resolvedDir,
|
|
986
|
-
baseOptions
|
|
987
|
-
);
|
|
988
|
-
finalOptions = { ...contextSmartDefaults, ...finalOptions };
|
|
989
|
-
console.log("\u{1F4CB} Configuration:");
|
|
990
|
-
console.log(` Max depth: ${finalOptions.maxDepth}`);
|
|
991
|
-
console.log(` Max context budget: ${finalOptions.maxContextBudget}`);
|
|
992
|
-
console.log(
|
|
993
|
-
` Min cohesion: ${(finalOptions.minCohesion * 100).toFixed(1)}%`
|
|
994
|
-
);
|
|
995
|
-
console.log(
|
|
996
|
-
` Max fragmentation: ${(finalOptions.maxFragmentation * 100).toFixed(1)}%`
|
|
997
|
-
);
|
|
998
|
-
console.log(` Analysis focus: ${finalOptions.focus}`);
|
|
999
|
-
console.log("");
|
|
1000
|
-
const { analyzeContext, generateSummary, calculateContextScore } = await import("@aiready/context-analyzer");
|
|
1001
|
-
const results = await analyzeContext(finalOptions);
|
|
1002
|
-
const elapsedTime = getElapsedTime2(startTime);
|
|
1003
|
-
const summary = generateSummary(results);
|
|
1004
|
-
let contextScore;
|
|
1005
|
-
if (options.score) {
|
|
1006
|
-
contextScore = calculateContextScore(summary);
|
|
1007
|
-
}
|
|
1008
|
-
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
1009
|
-
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
1010
|
-
if (outputFormat === "json") {
|
|
1011
|
-
const outputData = {
|
|
1012
|
-
results,
|
|
1013
|
-
summary: { ...summary, executionTime: parseFloat(elapsedTime) },
|
|
1014
|
-
...contextScore && { scoring: contextScore }
|
|
1015
|
-
};
|
|
1016
|
-
const outputPath = resolveOutputPath3(
|
|
1017
|
-
userOutputFile,
|
|
1018
|
-
`aiready-report-${getReportTimestamp()}.json`,
|
|
1019
|
-
resolvedDir
|
|
1020
|
-
);
|
|
1021
|
-
handleJSONOutput3(
|
|
1022
|
-
outputData,
|
|
1023
|
-
outputPath,
|
|
1024
|
-
`\u2705 Results saved to ${outputPath}`
|
|
1025
|
-
);
|
|
1026
|
-
} else {
|
|
1027
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
1028
|
-
const dividerWidth = Math.min(60, terminalWidth - 2);
|
|
1029
|
-
const divider = "\u2501".repeat(dividerWidth);
|
|
1030
|
-
console.log(chalk7.cyan(divider));
|
|
1031
|
-
console.log(chalk7.bold.white(" CONTEXT ANALYSIS SUMMARY"));
|
|
1032
|
-
console.log(chalk7.cyan(divider) + "\n");
|
|
1033
|
-
console.log(
|
|
1034
|
-
chalk7.white(`\u{1F4C1} Files analyzed: ${chalk7.bold(summary.totalFiles)}`)
|
|
1035
|
-
);
|
|
1036
|
-
console.log(
|
|
1037
|
-
chalk7.white(
|
|
1038
|
-
`\u{1F4CA} Total tokens: ${chalk7.bold(summary.totalTokens.toLocaleString())}`
|
|
1039
|
-
)
|
|
1040
|
-
);
|
|
1041
|
-
console.log(
|
|
1042
|
-
chalk7.yellow(
|
|
1043
|
-
`\u{1F4B0} Avg context budget: ${chalk7.bold(summary.avgContextBudget.toFixed(0))} tokens/file`
|
|
1044
|
-
)
|
|
1045
|
-
);
|
|
1046
|
-
console.log(
|
|
1047
|
-
chalk7.white(`\u23F1 Analysis time: ${chalk7.bold(elapsedTime + "s")}
|
|
1048
|
-
`)
|
|
1049
|
-
);
|
|
1050
|
-
const totalIssues = summary.criticalIssues + summary.majorIssues + summary.minorIssues;
|
|
1051
|
-
if (totalIssues > 0) {
|
|
1052
|
-
console.log(chalk7.bold("\u26A0\uFE0F Issues Found:\n"));
|
|
1053
|
-
if (summary.criticalIssues > 0) {
|
|
1054
|
-
console.log(
|
|
1055
|
-
chalk7.red(` \u{1F534} Critical: ${chalk7.bold(summary.criticalIssues)}`)
|
|
1056
|
-
);
|
|
1057
|
-
}
|
|
1058
|
-
if (summary.majorIssues > 0) {
|
|
1059
|
-
console.log(
|
|
1060
|
-
chalk7.yellow(` \u{1F7E1} Major: ${chalk7.bold(summary.majorIssues)}`)
|
|
1061
|
-
);
|
|
1062
|
-
}
|
|
1063
|
-
if (summary.minorIssues > 0) {
|
|
1064
|
-
console.log(
|
|
1065
|
-
chalk7.blue(` \u{1F535} Minor: ${chalk7.bold(summary.minorIssues)}`)
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
|
-
console.log(
|
|
1069
|
-
chalk7.green(
|
|
1070
|
-
`
|
|
1071
|
-
\u{1F4A1} Potential savings: ${chalk7.bold(summary.totalPotentialSavings.toLocaleString())} tokens
|
|
1072
|
-
`
|
|
1073
|
-
)
|
|
1074
|
-
);
|
|
1075
|
-
} else {
|
|
1076
|
-
console.log(chalk7.green("\u2705 No significant issues found!\n"));
|
|
1077
|
-
}
|
|
1078
|
-
if (summary.deepFiles.length > 0) {
|
|
1079
|
-
console.log(chalk7.bold("\u{1F4CF} Deep Import Chains:\n"));
|
|
1080
|
-
console.log(
|
|
1081
|
-
chalk7.gray(` Average depth: ${summary.avgImportDepth.toFixed(1)}`)
|
|
1082
|
-
);
|
|
1083
|
-
console.log(
|
|
1084
|
-
chalk7.gray(` Maximum depth: ${summary.maxImportDepth}
|
|
1085
|
-
`)
|
|
1086
|
-
);
|
|
1087
|
-
summary.deepFiles.slice(0, 10).forEach((item) => {
|
|
1088
|
-
const fileName = item.file.split("/").slice(-2).join("/");
|
|
1089
|
-
console.log(
|
|
1090
|
-
` ${chalk7.cyan("\u2192")} ${chalk7.white(fileName)} ${chalk7.dim(`(depth: ${item.depth})`)}`
|
|
1091
|
-
);
|
|
1092
|
-
});
|
|
1093
|
-
console.log();
|
|
1094
|
-
}
|
|
1095
|
-
if (summary.fragmentedModules.length > 0) {
|
|
1096
|
-
console.log(chalk7.bold("\u{1F9E9} Fragmented Modules:\n"));
|
|
1097
|
-
console.log(
|
|
1098
|
-
chalk7.gray(
|
|
1099
|
-
` Average fragmentation: ${(summary.avgFragmentation * 100).toFixed(0)}%
|
|
1100
|
-
`
|
|
1101
|
-
)
|
|
1102
|
-
);
|
|
1103
|
-
summary.fragmentedModules.slice(0, 10).forEach((module) => {
|
|
1104
|
-
console.log(
|
|
1105
|
-
` ${chalk7.yellow("\u25CF")} ${chalk7.white(module.domain)} - ${chalk7.dim(`${module.files.length} files, ${(module.fragmentationScore * 100).toFixed(0)}% scattered`)}`
|
|
1106
|
-
);
|
|
1107
|
-
console.log(
|
|
1108
|
-
chalk7.dim(
|
|
1109
|
-
` Token cost: ${module.totalTokens.toLocaleString()}, Cohesion: ${(module.avgCohesion * 100).toFixed(0)}%`
|
|
1110
|
-
)
|
|
1111
|
-
);
|
|
1112
|
-
});
|
|
1113
|
-
console.log();
|
|
1114
|
-
}
|
|
1115
|
-
if (summary.lowCohesionFiles.length > 0) {
|
|
1116
|
-
console.log(chalk7.bold("\u{1F500} Low Cohesion Files:\n"));
|
|
1117
|
-
console.log(
|
|
1118
|
-
chalk7.gray(
|
|
1119
|
-
` Average cohesion: ${(summary.avgCohesion * 100).toFixed(0)}%
|
|
1120
|
-
`
|
|
1121
|
-
)
|
|
1122
|
-
);
|
|
1123
|
-
summary.lowCohesionFiles.slice(0, 10).forEach((item) => {
|
|
1124
|
-
const fileName = item.file.split("/").slice(-2).join("/");
|
|
1125
|
-
const scorePercent = (item.score * 100).toFixed(0);
|
|
1126
|
-
const color = item.score < 0.4 ? chalk7.red : chalk7.yellow;
|
|
1127
|
-
console.log(
|
|
1128
|
-
` ${color("\u25CB")} ${chalk7.white(fileName)} ${chalk7.dim(`(${scorePercent}% cohesion)`)}`
|
|
1129
|
-
);
|
|
1130
|
-
});
|
|
1131
|
-
console.log();
|
|
1132
|
-
}
|
|
1133
|
-
if (summary.topExpensiveFiles.length > 0) {
|
|
1134
|
-
console.log(chalk7.bold("\u{1F4B8} Most Expensive Files (Context Budget):\n"));
|
|
1135
|
-
summary.topExpensiveFiles.slice(0, 10).forEach((item) => {
|
|
1136
|
-
const fileName = item.file.split("/").slice(-2).join("/");
|
|
1137
|
-
const severityColor = item.severity === "critical" ? chalk7.red : item.severity === "major" ? chalk7.yellow : chalk7.blue;
|
|
1138
|
-
console.log(
|
|
1139
|
-
` ${severityColor("\u25CF")} ${chalk7.white(fileName)} ${chalk7.dim(`(${item.contextBudget.toLocaleString()} tokens)`)}`
|
|
1140
|
-
);
|
|
1141
|
-
});
|
|
1142
|
-
console.log();
|
|
1143
|
-
}
|
|
1144
|
-
if (contextScore) {
|
|
1145
|
-
console.log(chalk7.cyan(divider));
|
|
1146
|
-
console.log(chalk7.bold.white(" AI READINESS SCORE (Context)"));
|
|
1147
|
-
console.log(chalk7.cyan(divider) + "\n");
|
|
1148
|
-
console.log(formatToolScore2(contextScore));
|
|
1149
|
-
console.log();
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
} catch (error) {
|
|
1153
|
-
handleCLIError4(error, "Context analysis");
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
// src/commands/consistency.ts
|
|
1158
|
-
import chalk8 from "chalk";
|
|
1159
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
1160
|
-
import { resolve as resolvePath6 } from "path";
|
|
1161
|
-
import {
|
|
1162
|
-
loadMergedConfig as loadMergedConfig4,
|
|
1163
|
-
handleJSONOutput as handleJSONOutput4,
|
|
1164
|
-
handleCLIError as handleCLIError5,
|
|
1165
|
-
getElapsedTime as getElapsedTime3,
|
|
1166
|
-
resolveOutputPath as resolveOutputPath4,
|
|
1167
|
-
formatToolScore as formatToolScore3
|
|
1168
|
-
} from "@aiready/core";
|
|
1169
|
-
async function consistencyAction(directory, options) {
|
|
1170
|
-
console.log(chalk8.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
1171
|
-
const startTime = Date.now();
|
|
1172
|
-
const resolvedDir = resolvePath6(process.cwd(), directory || ".");
|
|
1173
|
-
try {
|
|
1174
|
-
const defaults = {
|
|
1175
|
-
checkNaming: true,
|
|
1176
|
-
checkPatterns: true,
|
|
1177
|
-
minSeverity: "info",
|
|
1178
|
-
include: void 0,
|
|
1179
|
-
exclude: void 0,
|
|
1180
|
-
output: {
|
|
1181
|
-
format: "console",
|
|
1182
|
-
file: void 0
|
|
1183
|
-
}
|
|
1184
|
-
};
|
|
1185
|
-
const finalOptions = await loadMergedConfig4(resolvedDir, defaults, {
|
|
1186
|
-
checkNaming: options.naming !== false,
|
|
1187
|
-
checkPatterns: options.patterns !== false,
|
|
1188
|
-
minSeverity: options.minSeverity,
|
|
1189
|
-
include: options.include?.split(","),
|
|
1190
|
-
exclude: options.exclude?.split(",")
|
|
1191
|
-
});
|
|
1192
|
-
const { analyzeConsistency, calculateConsistencyScore } = await import("@aiready/consistency");
|
|
1193
|
-
const report = await analyzeConsistency(finalOptions);
|
|
1194
|
-
const elapsedTime = getElapsedTime3(startTime);
|
|
1195
|
-
let consistencyScore;
|
|
1196
|
-
if (options.score) {
|
|
1197
|
-
const issues = report.results?.flatMap((r) => r.issues) || [];
|
|
1198
|
-
consistencyScore = calculateConsistencyScore(
|
|
1199
|
-
issues,
|
|
1200
|
-
report.summary.filesAnalyzed
|
|
1201
|
-
);
|
|
1202
|
-
}
|
|
1203
|
-
const outputFormat = options.output || finalOptions.output?.format || "console";
|
|
1204
|
-
const userOutputFile = options.outputFile || finalOptions.output?.file;
|
|
1205
|
-
if (outputFormat === "json") {
|
|
1206
|
-
const outputData = {
|
|
1207
|
-
...report,
|
|
1208
|
-
summary: {
|
|
1209
|
-
...report.summary,
|
|
1210
|
-
executionTime: parseFloat(elapsedTime)
|
|
1211
|
-
},
|
|
1212
|
-
...consistencyScore && { scoring: consistencyScore }
|
|
1213
|
-
};
|
|
1214
|
-
const outputPath = resolveOutputPath4(
|
|
1215
|
-
userOutputFile,
|
|
1216
|
-
`aiready-report-${getReportTimestamp()}.json`,
|
|
1217
|
-
resolvedDir
|
|
1218
|
-
);
|
|
1219
|
-
handleJSONOutput4(
|
|
1220
|
-
outputData,
|
|
1221
|
-
outputPath,
|
|
1222
|
-
`\u2705 Results saved to ${outputPath}`
|
|
1223
|
-
);
|
|
1224
|
-
} else if (outputFormat === "markdown") {
|
|
1225
|
-
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
1226
|
-
const outputPath = resolveOutputPath4(
|
|
1227
|
-
userOutputFile,
|
|
1228
|
-
`aiready-report-${getReportTimestamp()}.md`,
|
|
1229
|
-
resolvedDir
|
|
1230
|
-
);
|
|
1231
|
-
writeFileSync3(outputPath, markdown);
|
|
1232
|
-
console.log(chalk8.green(`\u2705 Report saved to ${outputPath}`));
|
|
1233
|
-
} else {
|
|
1234
|
-
console.log(chalk8.bold("\n\u{1F4CA} Summary\n"));
|
|
1235
|
-
console.log(
|
|
1236
|
-
`Files Analyzed: ${chalk8.cyan(report.summary.filesAnalyzed)}`
|
|
1237
|
-
);
|
|
1238
|
-
console.log(`Total Issues: ${chalk8.yellow(report.summary.totalIssues)}`);
|
|
1239
|
-
console.log(` Naming: ${chalk8.yellow(report.summary.namingIssues)}`);
|
|
1240
|
-
console.log(` Patterns: ${chalk8.yellow(report.summary.patternIssues)}`);
|
|
1241
|
-
console.log(
|
|
1242
|
-
` Architecture: ${chalk8.yellow(report.summary.architectureIssues || 0)}`
|
|
1243
|
-
);
|
|
1244
|
-
console.log(`Analysis Time: ${chalk8.gray(elapsedTime + "s")}
|
|
1245
|
-
`);
|
|
1246
|
-
if (report.summary.totalIssues === 0) {
|
|
1247
|
-
console.log(
|
|
1248
|
-
chalk8.green(
|
|
1249
|
-
"\u2728 No consistency issues found! Your codebase is well-maintained.\n"
|
|
1250
|
-
)
|
|
1251
|
-
);
|
|
1252
|
-
} else {
|
|
1253
|
-
const namingResults = report.results.filter(
|
|
1254
|
-
(r) => r.issues.some((i) => i.category === "naming")
|
|
1255
|
-
);
|
|
1256
|
-
const patternResults = report.results.filter(
|
|
1257
|
-
(r) => r.issues.some((i) => i.category === "patterns")
|
|
1258
|
-
);
|
|
1259
|
-
if (namingResults.length > 0) {
|
|
1260
|
-
console.log(chalk8.bold("\u{1F3F7}\uFE0F Naming Issues\n"));
|
|
1261
|
-
let shown = 0;
|
|
1262
|
-
for (const result of namingResults) {
|
|
1263
|
-
if (shown >= 5) break;
|
|
1264
|
-
for (const issue of result.issues) {
|
|
1265
|
-
if (shown >= 5) break;
|
|
1266
|
-
const severityColor = issue.severity === "critical" ? chalk8.red : issue.severity === "major" ? chalk8.yellow : issue.severity === "minor" ? chalk8.blue : chalk8.gray;
|
|
1267
|
-
console.log(
|
|
1268
|
-
`${severityColor(issue.severity.toUpperCase())} ${chalk8.dim(`${issue.location.file}:${issue.location.line}`)}`
|
|
1269
|
-
);
|
|
1270
|
-
console.log(` ${issue.message}`);
|
|
1271
|
-
if (issue.suggestion) {
|
|
1272
|
-
console.log(
|
|
1273
|
-
` ${chalk8.dim("\u2192")} ${chalk8.italic(issue.suggestion)}`
|
|
1274
|
-
);
|
|
1275
|
-
}
|
|
1276
|
-
console.log();
|
|
1277
|
-
shown++;
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
const remaining = namingResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
1281
|
-
if (remaining > 0) {
|
|
1282
|
-
console.log(chalk8.dim(` ... and ${remaining} more issues
|
|
1283
|
-
`));
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
if (patternResults.length > 0) {
|
|
1287
|
-
console.log(chalk8.bold("\u{1F504} Pattern Issues\n"));
|
|
1288
|
-
let shown = 0;
|
|
1289
|
-
for (const result of patternResults) {
|
|
1290
|
-
if (shown >= 5) break;
|
|
1291
|
-
for (const issue of result.issues) {
|
|
1292
|
-
if (shown >= 5) break;
|
|
1293
|
-
const severityColor = issue.severity === "critical" ? chalk8.red : issue.severity === "major" ? chalk8.yellow : issue.severity === "minor" ? chalk8.blue : chalk8.gray;
|
|
1294
|
-
console.log(
|
|
1295
|
-
`${severityColor(issue.severity.toUpperCase())} ${chalk8.dim(`${issue.location.file}:${issue.location.line}`)}`
|
|
1296
|
-
);
|
|
1297
|
-
console.log(` ${issue.message}`);
|
|
1298
|
-
if (issue.suggestion) {
|
|
1299
|
-
console.log(
|
|
1300
|
-
` ${chalk8.dim("\u2192")} ${chalk8.italic(issue.suggestion)}`
|
|
1301
|
-
);
|
|
1302
|
-
}
|
|
1303
|
-
console.log();
|
|
1304
|
-
shown++;
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
const remaining = patternResults.reduce((sum, r) => sum + r.issues.length, 0) - shown;
|
|
1308
|
-
if (remaining > 0) {
|
|
1309
|
-
console.log(chalk8.dim(` ... and ${remaining} more issues
|
|
1310
|
-
`));
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
if (report.recommendations.length > 0) {
|
|
1314
|
-
console.log(chalk8.bold("\u{1F4A1} Recommendations\n"));
|
|
1315
|
-
report.recommendations.forEach((rec, i) => {
|
|
1316
|
-
console.log(`${i + 1}. ${rec}`);
|
|
1317
|
-
});
|
|
1318
|
-
console.log();
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
if (consistencyScore) {
|
|
1322
|
-
console.log(chalk8.bold("\n\u{1F4CA} AI Readiness Score (Consistency)\n"));
|
|
1323
|
-
console.log(formatToolScore3(consistencyScore));
|
|
1324
|
-
console.log();
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
} catch (error) {
|
|
1328
|
-
handleCLIError5(error, "Consistency analysis");
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
// src/commands/visualize.ts
|
|
1333
|
-
import chalk9 from "chalk";
|
|
1334
|
-
import { writeFileSync as writeFileSync4, readFileSync as readFileSync3, existsSync as existsSync3, copyFileSync } from "fs";
|
|
1335
|
-
import { resolve as resolvePath7 } from "path";
|
|
1336
|
-
import { spawn } from "child_process";
|
|
1337
|
-
import { handleCLIError as handleCLIError6 } from "@aiready/core";
|
|
1338
|
-
import { generateHTML, findLatestReport as findLatestReport2 } from "@aiready/core";
|
|
1339
|
-
async function visualizeAction(directory, options) {
|
|
1340
|
-
try {
|
|
1341
|
-
const dirPath = resolvePath7(process.cwd(), directory || ".");
|
|
1342
|
-
let reportPath = options.report ? resolvePath7(dirPath, options.report) : null;
|
|
1343
|
-
if (!reportPath || !existsSync3(reportPath)) {
|
|
1344
|
-
const latestScan = findLatestReport2(dirPath);
|
|
1345
|
-
if (latestScan) {
|
|
1346
|
-
reportPath = latestScan;
|
|
1347
|
-
console.log(
|
|
1348
|
-
chalk9.dim(`Found latest report: ${latestScan.split("/").pop()}`)
|
|
1349
|
-
);
|
|
1350
|
-
} else {
|
|
1351
|
-
console.error(chalk9.red("\u274C No AI readiness report found"));
|
|
1352
|
-
console.log(
|
|
1353
|
-
chalk9.dim(
|
|
1354
|
-
`
|
|
1355
|
-
Generate a report with:
|
|
1356
|
-
aiready scan --output json
|
|
1357
|
-
|
|
1358
|
-
Or specify a custom report:
|
|
1359
|
-
aiready visualise --report <path-to-report.json>`
|
|
1360
|
-
)
|
|
1361
|
-
);
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
const raw = readFileSync3(reportPath, "utf8");
|
|
1366
|
-
const report = JSON.parse(raw);
|
|
1367
|
-
const configPath = resolvePath7(dirPath, "aiready.json");
|
|
1368
|
-
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
1369
|
-
if (existsSync3(configPath)) {
|
|
1370
|
-
try {
|
|
1371
|
-
const rawConfig = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
1372
|
-
if (rawConfig.visualizer?.graph) {
|
|
1373
|
-
graphConfig = {
|
|
1374
|
-
maxNodes: rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
1375
|
-
maxEdges: rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
} catch (e) {
|
|
1379
|
-
void e;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
const envVisualizerConfig = JSON.stringify(graphConfig);
|
|
1383
|
-
process.env.AIREADY_VISUALIZER_CONFIG = envVisualizerConfig;
|
|
1384
|
-
console.log("Building graph from report...");
|
|
1385
|
-
const { GraphBuilder } = await import("@aiready/visualizer/graph");
|
|
1386
|
-
const graph = GraphBuilder.buildFromReport(report, dirPath);
|
|
1387
|
-
let useDevMode = options.dev || false;
|
|
1388
|
-
let devServerStarted = false;
|
|
1389
|
-
if (useDevMode) {
|
|
1390
|
-
try {
|
|
1391
|
-
const localWebDir = resolvePath7(dirPath, "packages/visualizer");
|
|
1392
|
-
let webDir = "";
|
|
1393
|
-
let visualizerAvailable = false;
|
|
1394
|
-
if (existsSync3(localWebDir)) {
|
|
1395
|
-
webDir = localWebDir;
|
|
1396
|
-
visualizerAvailable = true;
|
|
1397
|
-
} else {
|
|
1398
|
-
const nodemodulesLocations = [
|
|
1399
|
-
resolvePath7(dirPath, "node_modules", "@aiready", "visualizer"),
|
|
1400
|
-
resolvePath7(
|
|
1401
|
-
process.cwd(),
|
|
1402
|
-
"node_modules",
|
|
1403
|
-
"@aiready",
|
|
1404
|
-
"visualizer"
|
|
1405
|
-
)
|
|
1406
|
-
];
|
|
1407
|
-
let currentDir = dirPath;
|
|
1408
|
-
while (currentDir !== "/" && currentDir !== ".") {
|
|
1409
|
-
nodemodulesLocations.push(
|
|
1410
|
-
resolvePath7(currentDir, "node_modules", "@aiready", "visualizer")
|
|
1411
|
-
);
|
|
1412
|
-
const parent = resolvePath7(currentDir, "..");
|
|
1413
|
-
if (parent === currentDir) break;
|
|
1414
|
-
currentDir = parent;
|
|
1415
|
-
}
|
|
1416
|
-
for (const location of nodemodulesLocations) {
|
|
1417
|
-
if (existsSync3(location) && existsSync3(resolvePath7(location, "package.json"))) {
|
|
1418
|
-
webDir = location;
|
|
1419
|
-
visualizerAvailable = true;
|
|
1420
|
-
break;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
if (!visualizerAvailable) {
|
|
1424
|
-
try {
|
|
1425
|
-
const vizPkgPath = __require.resolve("@aiready/visualizer/package.json");
|
|
1426
|
-
webDir = resolvePath7(vizPkgPath, "..");
|
|
1427
|
-
visualizerAvailable = true;
|
|
1428
|
-
} catch (err) {
|
|
1429
|
-
void err;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
const webViteConfigExists = webDir && existsSync3(resolvePath7(webDir, "web", "vite.config.ts"));
|
|
1434
|
-
if (visualizerAvailable && webViteConfigExists) {
|
|
1435
|
-
const spawnCwd = webDir;
|
|
1436
|
-
const { watch } = await import("fs");
|
|
1437
|
-
const copyReportToViz = () => {
|
|
1438
|
-
try {
|
|
1439
|
-
const destPath = resolvePath7(spawnCwd, "web", "report-data.json");
|
|
1440
|
-
copyFileSync(reportPath, destPath);
|
|
1441
|
-
console.log(`\u{1F4CB} Report synced to ${destPath}`);
|
|
1442
|
-
} catch (e) {
|
|
1443
|
-
console.error("Failed to sync report:", e);
|
|
1444
|
-
}
|
|
1445
|
-
};
|
|
1446
|
-
copyReportToViz();
|
|
1447
|
-
let watchTimeout = null;
|
|
1448
|
-
const reportWatcher = watch(reportPath, () => {
|
|
1449
|
-
if (watchTimeout) clearTimeout(watchTimeout);
|
|
1450
|
-
watchTimeout = setTimeout(copyReportToViz, 100);
|
|
1451
|
-
});
|
|
1452
|
-
const envForSpawn = {
|
|
1453
|
-
...process.env,
|
|
1454
|
-
AIREADY_REPORT_PATH: reportPath,
|
|
1455
|
-
AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
|
|
1456
|
-
};
|
|
1457
|
-
const vite = spawn("pnpm", ["run", "dev:web"], {
|
|
1458
|
-
cwd: spawnCwd,
|
|
1459
|
-
stdio: "inherit",
|
|
1460
|
-
shell: true,
|
|
1461
|
-
env: envForSpawn
|
|
1462
|
-
});
|
|
1463
|
-
const onExit = () => {
|
|
1464
|
-
try {
|
|
1465
|
-
reportWatcher.close();
|
|
1466
|
-
} catch (err) {
|
|
1467
|
-
void err;
|
|
1468
|
-
}
|
|
1469
|
-
try {
|
|
1470
|
-
vite.kill();
|
|
1471
|
-
} catch (err) {
|
|
1472
|
-
void err;
|
|
1473
|
-
}
|
|
1474
|
-
process.exit(0);
|
|
1475
|
-
};
|
|
1476
|
-
process.on("SIGINT", onExit);
|
|
1477
|
-
process.on("SIGTERM", onExit);
|
|
1478
|
-
devServerStarted = true;
|
|
1479
|
-
void devServerStarted;
|
|
1480
|
-
return;
|
|
1481
|
-
} else {
|
|
1482
|
-
console.log(
|
|
1483
|
-
chalk9.yellow(
|
|
1484
|
-
"\u26A0\uFE0F Dev server not available (requires local @aiready/visualizer with web assets)."
|
|
1485
|
-
)
|
|
1486
|
-
);
|
|
1487
|
-
console.log(
|
|
1488
|
-
chalk9.cyan(" Falling back to static HTML generation...\n")
|
|
1489
|
-
);
|
|
1490
|
-
useDevMode = false;
|
|
1491
|
-
}
|
|
1492
|
-
} catch (err) {
|
|
1493
|
-
console.error("Failed to start dev server:", err);
|
|
1494
|
-
console.log(
|
|
1495
|
-
chalk9.cyan(" Falling back to static HTML generation...\n")
|
|
1496
|
-
);
|
|
1497
|
-
useDevMode = false;
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
console.log("Generating HTML...");
|
|
1501
|
-
const html = generateHTML(graph);
|
|
1502
|
-
const defaultOutput = "visualization.html";
|
|
1503
|
-
const outPath = resolvePath7(dirPath, options.output || defaultOutput);
|
|
1504
|
-
writeFileSync4(outPath, html, "utf8");
|
|
1505
|
-
console.log(chalk9.green(`\u2705 Visualization written to: ${outPath}`));
|
|
1506
|
-
if (options.open || options.serve) {
|
|
1507
|
-
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1508
|
-
if (options.serve) {
|
|
1509
|
-
try {
|
|
1510
|
-
const port = typeof options.serve === "number" ? options.serve : 5173;
|
|
1511
|
-
const http = await import("http");
|
|
1512
|
-
const fsp = await import("fs/promises");
|
|
1513
|
-
const server = http.createServer(async (req, res) => {
|
|
1514
|
-
try {
|
|
1515
|
-
const urlPath = req.url || "/";
|
|
1516
|
-
if (urlPath === "/" || urlPath === "/index.html") {
|
|
1517
|
-
const content = await fsp.readFile(outPath, "utf8");
|
|
1518
|
-
res.writeHead(200, {
|
|
1519
|
-
"Content-Type": "text/html; charset=utf-8"
|
|
1520
|
-
});
|
|
1521
|
-
res.end(content);
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1525
|
-
res.end("Not found");
|
|
1526
|
-
} catch (e) {
|
|
1527
|
-
void e;
|
|
1528
|
-
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1529
|
-
res.end("Server error");
|
|
1530
|
-
}
|
|
1531
|
-
});
|
|
1532
|
-
server.listen(port, () => {
|
|
1533
|
-
const addr = `http://localhost:${port}/`;
|
|
1534
|
-
console.log(
|
|
1535
|
-
chalk9.cyan(`\u{1F310} Local visualization server running at ${addr}`)
|
|
1536
|
-
);
|
|
1537
|
-
spawn(opener, [`"${addr}"`], { shell: true });
|
|
1538
|
-
});
|
|
1539
|
-
process.on("SIGINT", () => {
|
|
1540
|
-
server.close();
|
|
1541
|
-
process.exit(0);
|
|
1542
|
-
});
|
|
1543
|
-
} catch (err) {
|
|
1544
|
-
console.error("Failed to start local server:", err);
|
|
1545
|
-
}
|
|
1546
|
-
} else if (options.open) {
|
|
1547
|
-
spawn(opener, [`"${outPath}"`], { shell: true });
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
} catch (err) {
|
|
1551
|
-
handleCLIError6(err, "Visualization");
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
var visualizeHelpText = `
|
|
1555
|
-
EXAMPLES:
|
|
1556
|
-
$ aiready visualize . # Auto-detects latest report, generates HTML
|
|
1557
|
-
$ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
|
|
1558
|
-
$ aiready visualize . --report report.json -o out/visualization.html --open
|
|
1559
|
-
$ aiready visualize . --report report.json --serve
|
|
1560
|
-
$ aiready visualize . --report report.json --serve 8080
|
|
1561
|
-
$ aiready visualize . --report report.json --dev
|
|
1562
|
-
|
|
1563
|
-
NOTES:
|
|
1564
|
-
- The value passed to --report is interpreted relative to the directory argument (first positional).
|
|
1565
|
-
If the report is not found, the CLI will suggest running 'aiready scan' to generate it.
|
|
1566
|
-
- Default output path: visualization.html (in the current directory).
|
|
1567
|
-
- --serve starts a tiny single-file HTTP server (default port: 5173) and opens your browser.
|
|
1568
|
-
- --dev starts a Vite dev server with live reload (requires local @aiready/visualizer installation).
|
|
1569
|
-
When --dev is not available, it falls back to static HTML generation.
|
|
1570
|
-
`;
|
|
1571
|
-
var visualiseHelpText = `
|
|
1572
|
-
EXAMPLES:
|
|
1573
|
-
$ aiready visualise . # Auto-detects latest report
|
|
1574
|
-
$ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
|
|
1575
|
-
$ aiready visualise . --report report.json --serve 8080
|
|
1576
|
-
|
|
1577
|
-
NOTES:
|
|
1578
|
-
- Same options as 'visualize'. Use --serve to host the static HTML, or --dev for live reload.
|
|
1579
|
-
`;
|
|
1580
|
-
|
|
1581
|
-
// src/commands/shared/standard-tool-actions.ts
|
|
1582
|
-
import chalk10 from "chalk";
|
|
1583
|
-
|
|
1584
|
-
// src/commands/shared/configured-tool-action.ts
|
|
1585
|
-
async function runConfiguredToolAction(directory, options, config) {
|
|
1586
|
-
const { report, scoring } = await runConfiguredToolCommand({
|
|
1587
|
-
directory,
|
|
1588
|
-
options,
|
|
1589
|
-
defaults: config.defaults,
|
|
1590
|
-
analyze: config.analyze,
|
|
1591
|
-
getExtras: config.getExtras,
|
|
1592
|
-
score: config.score
|
|
1593
|
-
});
|
|
1594
|
-
if (options.output === "json") {
|
|
1595
|
-
return scoring;
|
|
1596
|
-
}
|
|
1597
|
-
config.render(report, scoring);
|
|
1598
|
-
return scoring;
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
// src/commands/shared/standard-tool-actions.ts
|
|
1602
|
-
async function aiSignalClarityAction(directory, options) {
|
|
1603
|
-
const { analyzeAiSignalClarity, calculateAiSignalClarityScore } = await import("@aiready/ai-signal-clarity");
|
|
1604
|
-
return runConfiguredToolAction(directory, options, {
|
|
1605
|
-
defaults: { minSeverity: "info" },
|
|
1606
|
-
analyze: analyzeAiSignalClarity,
|
|
1607
|
-
getExtras: (cmdOptions, merged) => ({
|
|
1608
|
-
minSeverity: cmdOptions.minSeverity ?? merged.minSeverity ?? "info"
|
|
1609
|
-
}),
|
|
1610
|
-
score: (toolReport) => calculateAiSignalClarityScore(toolReport),
|
|
1611
|
-
render: (report, scoring) => {
|
|
1612
|
-
const { summary } = report;
|
|
1613
|
-
const ratingColors = {
|
|
1614
|
-
minimal: chalk10.green,
|
|
1615
|
-
low: chalk10.cyan,
|
|
1616
|
-
moderate: chalk10.yellow,
|
|
1617
|
-
high: chalk10.red,
|
|
1618
|
-
severe: chalk10.bgRed.white
|
|
1619
|
-
};
|
|
1620
|
-
const color = ratingColors[summary.rating] ?? chalk10.white;
|
|
1621
|
-
console.log(
|
|
1622
|
-
` \u{1F9E0} AI Signal Clarity: ${chalk10.bold(scoring.score + "/100")} (${color(summary.rating)})`
|
|
1623
|
-
);
|
|
1624
|
-
console.log(` Top Risk: ${chalk10.italic(summary.topRisk)}`);
|
|
1625
|
-
if (summary.totalSignals > 0) {
|
|
1626
|
-
console.log(
|
|
1627
|
-
chalk10.dim(
|
|
1628
|
-
` ${summary.criticalSignals} critical ${summary.majorSignals} major ${summary.minorSignals} minor signals`
|
|
1629
|
-
)
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
// src/commands/agent-grounding.ts
|
|
1637
|
-
import chalk11 from "chalk";
|
|
1638
|
-
import { loadConfig as loadConfig2, mergeConfigWithDefaults as mergeConfigWithDefaults2 } from "@aiready/core";
|
|
1639
|
-
async function agentGroundingAction(directory, options) {
|
|
1640
|
-
const { analyzeAgentGrounding, calculateGroundingScore } = await import("@aiready/agent-grounding");
|
|
1641
|
-
const config = await loadConfig2(directory);
|
|
1642
|
-
const merged = mergeConfigWithDefaults2(config, {
|
|
1643
|
-
maxRecommendedDepth: 4,
|
|
1644
|
-
readmeStaleDays: 90
|
|
1645
|
-
});
|
|
1646
|
-
const report = await analyzeAgentGrounding({
|
|
1647
|
-
rootDir: directory,
|
|
1648
|
-
maxRecommendedDepth: options.maxDepth ?? merged.maxRecommendedDepth,
|
|
1649
|
-
readmeStaleDays: options.readmeStaleDays ?? merged.readmeStaleDays,
|
|
1650
|
-
include: options.include,
|
|
1651
|
-
exclude: options.exclude
|
|
1652
|
-
});
|
|
1653
|
-
const scoring = calculateGroundingScore(report);
|
|
1654
|
-
if (options.output === "json") {
|
|
1655
|
-
return scoring;
|
|
1656
|
-
}
|
|
1657
|
-
const scoreColor = (s) => s >= 85 ? chalk11.green : s >= 70 ? chalk11.cyan : s >= 50 ? chalk11.yellow : chalk11.red;
|
|
1658
|
-
void scoreColor;
|
|
1659
|
-
console.log(
|
|
1660
|
-
` \u{1F9ED} Agent Grounding: ${chalk11.bold(scoring.score + "/100")} (${report.summary.rating})`
|
|
1661
|
-
);
|
|
1662
|
-
const dims = report.summary.dimensions;
|
|
1663
|
-
const worstDim = Object.entries(dims).sort(([, a], [, b]) => a - b)[0];
|
|
1664
|
-
if (worstDim && worstDim[1] < 70) {
|
|
1665
|
-
const name = worstDim[0].replace(/([A-Z])/g, " $1").replace("Score", "").trim();
|
|
1666
|
-
console.log(
|
|
1667
|
-
chalk11.dim(` Weakest dimension: ${name} (${worstDim[1]}/100)`)
|
|
1668
|
-
);
|
|
1669
|
-
}
|
|
1670
|
-
return scoring;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
// src/commands/testability.ts
|
|
1674
|
-
import chalk12 from "chalk";
|
|
1675
|
-
import { loadConfig as loadConfig3, mergeConfigWithDefaults as mergeConfigWithDefaults3 } from "@aiready/core";
|
|
1676
|
-
async function testabilityAction(directory, options) {
|
|
1677
|
-
const { analyzeTestability, calculateTestabilityScore } = await import("@aiready/testability");
|
|
1678
|
-
const config = await loadConfig3(directory);
|
|
1679
|
-
const merged = mergeConfigWithDefaults3(config, {
|
|
1680
|
-
minCoverageRatio: 0.3
|
|
1681
|
-
});
|
|
1682
|
-
const report = await analyzeTestability({
|
|
1683
|
-
rootDir: directory,
|
|
1684
|
-
minCoverageRatio: options.minCoverageRatio ?? merged.minCoverageRatio,
|
|
1685
|
-
include: options.include,
|
|
1686
|
-
exclude: options.exclude
|
|
1687
|
-
});
|
|
1688
|
-
const scoring = calculateTestabilityScore(report);
|
|
1689
|
-
if (options.output === "json") {
|
|
1690
|
-
return scoring;
|
|
1691
|
-
}
|
|
1692
|
-
const safetyIcons = {
|
|
1693
|
-
safe: "\u2705",
|
|
1694
|
-
"moderate-risk": "\u26A0\uFE0F ",
|
|
1695
|
-
"high-risk": "\u{1F534}",
|
|
1696
|
-
"blind-risk": "\u{1F480}"
|
|
1697
|
-
};
|
|
1698
|
-
const safetyColors = {
|
|
1699
|
-
safe: chalk12.green,
|
|
1700
|
-
"moderate-risk": chalk12.yellow,
|
|
1701
|
-
"high-risk": chalk12.red,
|
|
1702
|
-
"blind-risk": chalk12.bgRed.white
|
|
1703
|
-
};
|
|
1704
|
-
const safety = report.summary.aiChangeSafetyRating;
|
|
1705
|
-
const icon = safetyIcons[safety] ?? "\u2753";
|
|
1706
|
-
const color = safetyColors[safety] ?? chalk12.white;
|
|
1707
|
-
console.log(
|
|
1708
|
-
` \u{1F9EA} Testability: ${chalk12.bold(scoring.score + "/100")} (${report.summary.rating})`
|
|
1709
|
-
);
|
|
1710
|
-
console.log(
|
|
1711
|
-
` AI Change Safety: ${color(`${icon} ${safety.toUpperCase()}`)}`
|
|
1712
|
-
);
|
|
1713
|
-
console.log(
|
|
1714
|
-
chalk12.dim(
|
|
1715
|
-
` Coverage: ${Math.round(report.summary.coverageRatio * 100)}% (${report.rawData.testFiles} test / ${report.rawData.sourceFiles} source files)`
|
|
1716
|
-
)
|
|
1717
|
-
);
|
|
1718
|
-
if (safety === "blind-risk") {
|
|
1719
|
-
console.log(
|
|
1720
|
-
chalk12.red.bold(
|
|
1721
|
-
"\n \u26A0\uFE0F NO TESTS \u2014 AI changes to this codebase are completely unverifiable!\n"
|
|
1722
|
-
)
|
|
1723
|
-
);
|
|
1724
|
-
}
|
|
1725
|
-
return scoring;
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
// src/commands/change-amplification.ts
|
|
1729
|
-
import { changeAmplificationAction } from "@aiready/change-amplification/dist/cli.js";
|
|
1730
|
-
|
|
1731
|
-
// src/commands/bug.ts
|
|
1732
|
-
import chalk13 from "chalk";
|
|
1733
|
-
import { execSync } from "child_process";
|
|
1734
|
-
async function bugAction(message, options) {
|
|
1735
|
-
const repoUrl = "https://github.com/caopengau/aiready-cli";
|
|
1736
|
-
const repoSlug = "caopengau/aiready-cli";
|
|
1737
|
-
if (message) {
|
|
1738
|
-
const type = options.type || "bug";
|
|
1739
|
-
const title = `[${type.toUpperCase()}] ${message}`;
|
|
1740
|
-
const label = type === "bug" ? "bug" : type === "feature" ? "enhancement" : "metric";
|
|
1741
|
-
const body = `
|
|
1742
|
-
## Description
|
|
1743
|
-
${message}
|
|
1744
|
-
|
|
1745
|
-
## Context
|
|
1746
|
-
Generated via AIReady CLI 'bug' command.
|
|
1747
|
-
Type: ${type}
|
|
1748
|
-
`.trim();
|
|
1749
|
-
if (options.submit) {
|
|
1750
|
-
console.log(chalk13.blue("\u{1F680} Submitting issue via GitHub CLI...\n"));
|
|
1751
|
-
try {
|
|
1752
|
-
execSync("gh auth status", { stdio: "ignore" });
|
|
1753
|
-
const command = `gh issue create --repo ${repoSlug} --title ${JSON.stringify(title)} --body ${JSON.stringify(body)} --label ${label}`;
|
|
1754
|
-
const output = execSync(command, { encoding: "utf8" }).trim();
|
|
1755
|
-
console.log(chalk13.green("\u2705 Issue Created Successfully!"));
|
|
1756
|
-
console.log(chalk13.cyan(output));
|
|
1757
|
-
return;
|
|
1758
|
-
} catch {
|
|
1759
|
-
console.error(chalk13.red("\n\u274C Failed to submit via gh CLI."));
|
|
1760
|
-
console.log(
|
|
1761
|
-
chalk13.yellow(
|
|
1762
|
-
' Make sure gh is installed and run "gh auth login".\n'
|
|
1763
|
-
)
|
|
1764
|
-
);
|
|
1765
|
-
console.log(chalk13.dim(" Falling back to URL generation..."));
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
const template = type === "bug" ? "bug_report.md" : type === "feature" ? "feature_request.md" : "new_metric_idea.md";
|
|
1769
|
-
const fullUrl = `${repoUrl}/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}&labels=${label}&template=${template}`;
|
|
1770
|
-
console.log(chalk13.green("\u{1F680} Issue Draft Prepared!\n"));
|
|
1771
|
-
console.log(chalk13.bold("Title: ") + title);
|
|
1772
|
-
console.log(chalk13.bold("Type: ") + type);
|
|
1773
|
-
console.log(chalk13.bold("\nClick the link below to submit this issue:"));
|
|
1774
|
-
console.log(chalk13.cyan(fullUrl));
|
|
1775
|
-
console.log(chalk13.bold("\n\u{1F916} [FOR AI AGENTS]:"));
|
|
1776
|
-
console.log(chalk13.dim(" You have successfully prepared a report."));
|
|
1777
|
-
console.log(
|
|
1778
|
-
chalk13.dim(
|
|
1779
|
-
" Please present the URL above to the user so they can finalize the submission."
|
|
1780
|
-
)
|
|
1781
|
-
);
|
|
1782
|
-
return;
|
|
1783
|
-
}
|
|
1784
|
-
const bugUrl = `${repoUrl}/issues/new?template=bug_report.md`;
|
|
1785
|
-
const featureUrl = `${repoUrl}/issues/new?template=feature_request.md`;
|
|
1786
|
-
const metricUrl = `${repoUrl}/issues/new?template=new_metric_idea.md`;
|
|
1787
|
-
console.log(chalk13.blue("\u{1F4AC} Feedback & Bug Reports\n"));
|
|
1788
|
-
console.log(` Report a Bug: ${chalk13.cyan(bugUrl)}`);
|
|
1789
|
-
console.log(` Request a Feature: ${chalk13.cyan(featureUrl)}`);
|
|
1790
|
-
console.log(` Suggest a Metric: ${chalk13.cyan(metricUrl)}`);
|
|
1791
|
-
console.log(chalk13.bold("\n\u{1F916} [FOR AI AGENTS]:"));
|
|
1792
|
-
console.log(chalk13.dim(" To prepare a specific report, run:"));
|
|
1793
|
-
console.log(
|
|
1794
|
-
chalk13.cyan(
|
|
1795
|
-
' aiready bug "your description here" --type bug|feature|metric'
|
|
1796
|
-
)
|
|
1797
|
-
);
|
|
1798
|
-
}
|
|
1799
|
-
var bugHelpText = `
|
|
1800
|
-
EXAMPLES:
|
|
1801
|
-
$ aiready bug # Show general links
|
|
1802
|
-
$ aiready bug "Naming check is too slow" # Prepare a pre-filled bug report
|
|
1803
|
-
$ aiready bug "Add CO2 impact metric" --type metric # Prepare a metric suggestion
|
|
1804
|
-
$ aiready bug "Fix typo in scan output" --submit # Submit directly via gh CLI
|
|
1805
|
-
`;
|
|
1806
|
-
|
|
1807
|
-
// src/commands/stack-sync.ts
|
|
1808
|
-
import { execSync as execSync2 } from "child_process";
|
|
1809
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1810
|
-
import { join as join2 } from "path";
|
|
1811
|
-
import chalk14 from "chalk";
|
|
1812
|
-
async function stackSyncAction(options) {
|
|
1813
|
-
const remote = options.remote || "https://github.com/caopengau/aiready-clawmore.git";
|
|
1814
|
-
const branch = options.branch || "main";
|
|
1815
|
-
console.log(chalk14.cyan("\u{1F50D} Checking stack environment..."));
|
|
1816
|
-
if (!existsSync4(join2(process.cwd(), ".git"))) {
|
|
1817
|
-
console.error(chalk14.red("Error: Not a git repository."));
|
|
1818
|
-
process.exit(1);
|
|
1819
|
-
}
|
|
1820
|
-
let remoteName = "upstream";
|
|
1821
|
-
try {
|
|
1822
|
-
const remotes = execSync2("git remote").toString().split("\n");
|
|
1823
|
-
const existingUpstream = remotes.find((r) => {
|
|
1824
|
-
try {
|
|
1825
|
-
const url = execSync2(`git remote get-url ${r}`).toString().trim();
|
|
1826
|
-
return url === remote;
|
|
1827
|
-
} catch {
|
|
1828
|
-
return false;
|
|
1829
|
-
}
|
|
1830
|
-
});
|
|
1831
|
-
if (existingUpstream) {
|
|
1832
|
-
remoteName = existingUpstream;
|
|
1833
|
-
console.log(chalk14.green(`\u2713 Found existing upstream remote: ${remoteName}`));
|
|
1834
|
-
} else {
|
|
1835
|
-
console.log(chalk14.yellow(`! Upstream remote not found. Adding ${remote} as 'upstream'...`));
|
|
1836
|
-
execSync2(`git remote add upstream ${remote}`);
|
|
1837
|
-
remoteName = "upstream";
|
|
1838
|
-
}
|
|
1839
|
-
} catch (error) {
|
|
1840
|
-
console.error(chalk14.red(`Failed to handle git remotes: ${error}`));
|
|
1841
|
-
process.exit(1);
|
|
1842
|
-
}
|
|
1843
|
-
console.log(chalk14.cyan(`\u{1F4E5} Fetching latest from ${remoteName}/${branch}...`));
|
|
1844
|
-
try {
|
|
1845
|
-
execSync2(`git fetch ${remoteName} ${branch}`, { stdio: "inherit" });
|
|
1846
|
-
} catch (error) {
|
|
1847
|
-
console.error(chalk14.red(`Failed to fetch from ${remoteName}: ${error}`));
|
|
1848
|
-
process.exit(1);
|
|
1849
|
-
}
|
|
1850
|
-
try {
|
|
1851
|
-
const localHash = execSync2("git rev-parse HEAD").toString().trim();
|
|
1852
|
-
const remoteHash = execSync2(`git rev-parse ${remoteName}/${branch}`).toString().trim();
|
|
1853
|
-
if (localHash === remoteHash) {
|
|
1854
|
-
console.log(chalk14.green("\u2705 Stack is up to date with upstream."));
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
console.log(chalk14.yellow(`
|
|
1858
|
-
\u{1F680} Updates available! Current: ${localHash.substring(0, 7)}, Upstream: ${remoteHash.substring(0, 7)}`));
|
|
1859
|
-
if (options.pull) {
|
|
1860
|
-
console.log(chalk14.cyan(`\u{1F504} Merging updates from ${remoteName}/${branch}...`));
|
|
1861
|
-
execSync2(`git merge ${remoteName}/${branch}`, { stdio: "inherit" });
|
|
1862
|
-
console.log(chalk14.green("\u2705 Successfully updated stack."));
|
|
1863
|
-
} else {
|
|
1864
|
-
console.log(chalk14.cyan("\nRun the following command to update your stack:"));
|
|
1865
|
-
console.log(chalk14.white(` $ aiready stack sync --pull
|
|
1866
|
-
`));
|
|
1867
|
-
}
|
|
1868
|
-
} catch (error) {
|
|
1869
|
-
console.error(chalk14.red(`Failed to compare versions or merge: ${error}`));
|
|
1870
|
-
process.exit(1);
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
export {
|
|
1875
|
-
uploadAction,
|
|
1876
|
-
uploadHelpText,
|
|
1877
|
-
scanAction,
|
|
1878
|
-
scanHelpText,
|
|
1879
|
-
initAction,
|
|
1880
|
-
patternsAction,
|
|
1881
|
-
patternsHelpText,
|
|
1882
|
-
contextAction,
|
|
1883
|
-
consistencyAction,
|
|
1884
|
-
visualizeAction,
|
|
1885
|
-
visualizeHelpText,
|
|
1886
|
-
visualiseHelpText,
|
|
1887
|
-
aiSignalClarityAction,
|
|
1888
|
-
agentGroundingAction,
|
|
1889
|
-
testabilityAction,
|
|
1890
|
-
changeAmplificationAction,
|
|
1891
|
-
bugAction,
|
|
1892
|
-
bugHelpText,
|
|
1893
|
-
stackSyncAction
|
|
1894
|
-
};
|