@aiready/cli 0.14.4 → 0.14.6

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.
@@ -0,0 +1,1894 @@
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
+ };