@aiready/cli 0.14.3 → 0.14.5
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 +7 -7
- package/.turbo/turbo-lint.log +0 -32
- package/.turbo/turbo-test.log +35 -34
- package/aiready-report.json +30703 -0
- package/dist/cli.js +357 -344
- package/dist/cli.mjs +294 -280
- package/package.json +12 -12
- package/src/__tests__/cli.test.ts +1 -1
- package/src/__tests__/config-shape.test.ts +0 -1
- package/src/__tests__/unified.test.ts +1 -1
- package/src/commands/__tests__/extra-commands.test.ts +0 -1
- package/src/commands/__tests__/init.test.ts +56 -0
- package/src/commands/__tests__/scan.test.ts +1 -1
- package/src/commands/__tests__/upload.test.ts +0 -1
- package/src/commands/bug.ts +1 -2
- package/src/commands/init.ts +0 -4
- package/src/commands/patterns.ts +3 -1
- package/src/commands/report-formatter.ts +128 -0
- package/src/commands/scan.ts +18 -112
- package/src/commands/upload.ts +15 -13
- package/src/commands/visualize.ts +9 -1
- package/src/index.ts +18 -3
- package/src/utils/helpers.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/cli",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.5",
|
|
4
4
|
"description": "Unified CLI for AIReady analysis tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"chalk": "^5.3.0",
|
|
13
13
|
"commander": "^14.0.0",
|
|
14
|
-
"@aiready/agent-grounding": "0.13.
|
|
15
|
-
"@aiready/consistency": "0.20.
|
|
16
|
-
"@aiready/context-analyzer": "0.21.
|
|
17
|
-
"@aiready/
|
|
18
|
-
"@aiready/
|
|
19
|
-
"@aiready/
|
|
20
|
-
"@aiready/
|
|
21
|
-
"@aiready/
|
|
22
|
-
"@aiready/
|
|
23
|
-
"@aiready/
|
|
24
|
-
"@aiready/
|
|
14
|
+
"@aiready/agent-grounding": "0.13.4",
|
|
15
|
+
"@aiready/consistency": "0.20.4",
|
|
16
|
+
"@aiready/context-analyzer": "0.21.8",
|
|
17
|
+
"@aiready/deps": "0.13.4",
|
|
18
|
+
"@aiready/core": "0.23.4",
|
|
19
|
+
"@aiready/doc-drift": "0.13.4",
|
|
20
|
+
"@aiready/change-amplification": "0.13.4",
|
|
21
|
+
"@aiready/pattern-detect": "0.16.4",
|
|
22
|
+
"@aiready/testability": "0.6.4",
|
|
23
|
+
"@aiready/ai-signal-clarity": "0.13.4",
|
|
24
|
+
"@aiready/visualizer": "0.6.4"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^24.0.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { analyzeUnified, scoreUnified, generateUnifiedSummary } from '../index';
|
|
3
|
-
import { ToolRegistry
|
|
3
|
+
import { ToolRegistry } from '@aiready/core';
|
|
4
4
|
|
|
5
5
|
vi.mock('@aiready/core', async () => {
|
|
6
6
|
const actual = await vi.importActual('@aiready/core');
|
|
@@ -7,7 +7,6 @@ import { testabilityAction } from '../testability';
|
|
|
7
7
|
import { depsHealthAction } from '../deps-health';
|
|
8
8
|
import { patternsAction } from '../patterns';
|
|
9
9
|
import { contextAction } from '../context';
|
|
10
|
-
import * as core from '@aiready/core';
|
|
11
10
|
|
|
12
11
|
vi.mock('@aiready/core', async () => {
|
|
13
12
|
const actual = await vi.importActual('@aiready/core');
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { existsSync, readFileSync, unlinkSync, mkdirSync, rmSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { initAction } from '../init';
|
|
5
|
+
|
|
6
|
+
describe('initAction', () => {
|
|
7
|
+
const testDir = join(process.cwd(), 'temp-test-init');
|
|
8
|
+
const configPath = join(testDir, 'aiready.json');
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
if (!existsSync(testDir)) {
|
|
12
|
+
mkdirSync(testDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
// Mock process.cwd to use our test directory
|
|
15
|
+
vi.spyOn(process, 'cwd').mockReturnValue(testDir);
|
|
16
|
+
// Mock console.log to avoid noise
|
|
17
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
18
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.restoreAllMocks();
|
|
23
|
+
if (existsSync(configPath)) {
|
|
24
|
+
unlinkSync(configPath);
|
|
25
|
+
}
|
|
26
|
+
if (existsSync(testDir)) {
|
|
27
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should generate aiready.json without output field by default', async () => {
|
|
32
|
+
await initAction({});
|
|
33
|
+
|
|
34
|
+
expect(existsSync(configPath)).toBe(true);
|
|
35
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
36
|
+
expect(config).not.toHaveProperty('output');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should generate aiready.json without output field even with --full flag', async () => {
|
|
40
|
+
await initAction({ full: true });
|
|
41
|
+
|
|
42
|
+
expect(existsSync(configPath)).toBe(true);
|
|
43
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
44
|
+
expect(config).not.toHaveProperty('output');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should include scan, tools, and scoring sections', async () => {
|
|
48
|
+
await initAction({ full: true });
|
|
49
|
+
|
|
50
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
51
|
+
expect(config).toHaveProperty('scan');
|
|
52
|
+
expect(config).toHaveProperty('tools');
|
|
53
|
+
expect(config).toHaveProperty('scoring');
|
|
54
|
+
expect(config).toHaveProperty('visualizer');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -3,7 +3,7 @@ import { scanAction } from '../scan';
|
|
|
3
3
|
import * as core from '@aiready/core';
|
|
4
4
|
import * as index from '../../index';
|
|
5
5
|
import * as upload from '../upload';
|
|
6
|
-
import {
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
7
|
import { Severity } from '@aiready/core';
|
|
8
8
|
|
|
9
9
|
vi.mock('../../index', () => ({
|
package/src/commands/bug.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
2
|
|
|
4
3
|
import { execSync } from 'child_process';
|
|
5
4
|
|
|
@@ -38,7 +37,7 @@ Type: ${type}
|
|
|
38
37
|
console.log(chalk.green('✅ Issue Created Successfully!'));
|
|
39
38
|
console.log(chalk.cyan(output));
|
|
40
39
|
return;
|
|
41
|
-
} catch
|
|
40
|
+
} catch {
|
|
42
41
|
console.error(chalk.red('\n❌ Failed to submit via gh CLI.'));
|
|
43
42
|
console.log(
|
|
44
43
|
chalk.yellow(
|
package/src/commands/init.ts
CHANGED
package/src/commands/patterns.ts
CHANGED
|
@@ -86,7 +86,9 @@ export async function patternsAction(
|
|
|
86
86
|
const { analyzePatterns, generateSummary, calculatePatternScore } =
|
|
87
87
|
await import('@aiready/pattern-detect');
|
|
88
88
|
|
|
89
|
-
const { results, duplicates } = await analyzePatterns(
|
|
89
|
+
const { results, duplicates } = (await analyzePatterns(
|
|
90
|
+
finalOptions
|
|
91
|
+
)) as any;
|
|
90
92
|
|
|
91
93
|
const elapsedTime = getElapsedTime(startTime);
|
|
92
94
|
const summary = generateSummary(results);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {
|
|
3
|
+
Severity,
|
|
4
|
+
ScoringResult,
|
|
5
|
+
formatScore,
|
|
6
|
+
getRating,
|
|
7
|
+
getRatingDisplay,
|
|
8
|
+
} from '@aiready/core';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Handle console output for the scan results
|
|
12
|
+
*/
|
|
13
|
+
export function printScanSummary(results: any, startTime: number) {
|
|
14
|
+
console.log(chalk.cyan('\n=== AIReady Run Summary ==='));
|
|
15
|
+
console.log(
|
|
16
|
+
` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`
|
|
17
|
+
);
|
|
18
|
+
console.log(
|
|
19
|
+
` Execution time: ${chalk.bold(((Date.now() - startTime) / 1000).toFixed(2) + 's')}`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Print business impact analysis
|
|
25
|
+
*/
|
|
26
|
+
export function printBusinessImpact(roi: any, unifiedBudget: any) {
|
|
27
|
+
console.log(chalk.bold('\n💰 Business Impact Analysis (Monthly)'));
|
|
28
|
+
console.log(
|
|
29
|
+
` Potential Savings: ${chalk.green(chalk.bold('$' + roi.monthlySavings.toLocaleString()))}`
|
|
30
|
+
);
|
|
31
|
+
console.log(
|
|
32
|
+
` Productivity Gain: ${chalk.cyan(chalk.bold(roi.productivityGainHours + 'h'))} (est. dev time)`
|
|
33
|
+
);
|
|
34
|
+
console.log(
|
|
35
|
+
` Context Efficiency: ${chalk.yellow((unifiedBudget.efficiencyRatio * 100).toFixed(0) + '%')}`
|
|
36
|
+
);
|
|
37
|
+
console.log(
|
|
38
|
+
` Annual Value: ${chalk.bold('$' + roi.annualValue.toLocaleString())} (ROI Prediction)`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Print detailed scoring breakdown
|
|
44
|
+
*/
|
|
45
|
+
export function printScoring(
|
|
46
|
+
scoringResult: ScoringResult,
|
|
47
|
+
scoringProfile: string
|
|
48
|
+
) {
|
|
49
|
+
console.log(chalk.bold('\n📊 AI Readiness Overall Score'));
|
|
50
|
+
console.log(` ${formatScore(scoringResult)}`);
|
|
51
|
+
console.log(chalk.dim(` (Scoring Profile: ${scoringProfile})`));
|
|
52
|
+
|
|
53
|
+
if (scoringResult.breakdown) {
|
|
54
|
+
console.log(chalk.bold('\nTool breakdown:'));
|
|
55
|
+
scoringResult.breakdown.forEach((tool) => {
|
|
56
|
+
const rating = getRating(tool.score);
|
|
57
|
+
const emoji = getRatingDisplay(rating).emoji;
|
|
58
|
+
console.log(
|
|
59
|
+
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${emoji}`
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Top Actionable Recommendations
|
|
64
|
+
const allRecs = scoringResult.breakdown
|
|
65
|
+
.flatMap((t) =>
|
|
66
|
+
(t.recommendations || []).map((r) => ({ ...r, tool: t.toolName }))
|
|
67
|
+
)
|
|
68
|
+
.sort((a, b) => b.estimatedImpact - a.estimatedImpact)
|
|
69
|
+
.slice(0, 3);
|
|
70
|
+
|
|
71
|
+
if (allRecs.length > 0) {
|
|
72
|
+
console.log(chalk.bold('\n🎯 Top Actionable Recommendations:'));
|
|
73
|
+
allRecs.forEach((rec, i) => {
|
|
74
|
+
const priorityIcon =
|
|
75
|
+
rec.priority === 'high'
|
|
76
|
+
? '🔴'
|
|
77
|
+
: rec.priority === 'medium'
|
|
78
|
+
? '🟡'
|
|
79
|
+
: '🔵';
|
|
80
|
+
console.log(` ${i + 1}. ${priorityIcon} ${chalk.bold(rec.action)}`);
|
|
81
|
+
console.log(
|
|
82
|
+
` Impact: ${chalk.green(`+${rec.estimatedImpact} points`)} to ${rec.tool}`
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Normalize report mapping (CLI logic)
|
|
91
|
+
*/
|
|
92
|
+
export function mapToUnifiedReport(
|
|
93
|
+
res: any,
|
|
94
|
+
scoring: ScoringResult | undefined
|
|
95
|
+
) {
|
|
96
|
+
const allResults: any[] = [];
|
|
97
|
+
const totalFilesSet = new Set<string>();
|
|
98
|
+
let criticalCount = 0;
|
|
99
|
+
let majorCount = 0;
|
|
100
|
+
|
|
101
|
+
res.summary.toolsRun.forEach((toolId: string) => {
|
|
102
|
+
const spokeRes = res[toolId];
|
|
103
|
+
if (!spokeRes || !spokeRes.results) return;
|
|
104
|
+
|
|
105
|
+
spokeRes.results.forEach((r: any) => {
|
|
106
|
+
totalFilesSet.add(r.fileName);
|
|
107
|
+
allResults.push(r);
|
|
108
|
+
r.issues?.forEach((i: any) => {
|
|
109
|
+
if (i.severity === Severity.Critical || i.severity === 'critical')
|
|
110
|
+
criticalCount++;
|
|
111
|
+
if (i.severity === Severity.Major || i.severity === 'major')
|
|
112
|
+
majorCount++;
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...res,
|
|
119
|
+
results: allResults,
|
|
120
|
+
summary: {
|
|
121
|
+
...res.summary,
|
|
122
|
+
totalFiles: totalFilesSet.size,
|
|
123
|
+
criticalIssues: criticalCount,
|
|
124
|
+
majorIssues: majorCount,
|
|
125
|
+
},
|
|
126
|
+
scoring,
|
|
127
|
+
};
|
|
128
|
+
}
|
package/src/commands/scan.ts
CHANGED
|
@@ -9,28 +9,20 @@ import {
|
|
|
9
9
|
loadMergedConfig,
|
|
10
10
|
handleJSONOutput,
|
|
11
11
|
handleCLIError,
|
|
12
|
-
getElapsedTime,
|
|
13
12
|
resolveOutputPath,
|
|
14
|
-
formatScore,
|
|
15
|
-
formatToolScore,
|
|
16
|
-
calculateTokenBudget,
|
|
17
|
-
estimateCostFromBudget,
|
|
18
|
-
getModelPreset,
|
|
19
|
-
getRating,
|
|
20
|
-
getRatingDisplay,
|
|
21
13
|
getRepoMetadata,
|
|
22
|
-
|
|
23
|
-
IssueType,
|
|
14
|
+
calculateTokenBudget,
|
|
24
15
|
ToolName,
|
|
25
|
-
ToolRegistry,
|
|
26
16
|
emitIssuesAsAnnotations,
|
|
27
17
|
} from '@aiready/core';
|
|
28
18
|
import { analyzeUnified, scoreUnified, type ScoringResult } from '../index';
|
|
19
|
+
import { getReportTimestamp, warnIfGraphCapExceeded } from '../utils/helpers';
|
|
29
20
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
printScanSummary,
|
|
22
|
+
printBusinessImpact,
|
|
23
|
+
printScoring,
|
|
24
|
+
mapToUnifiedReport,
|
|
25
|
+
} from './report-formatter';
|
|
34
26
|
import { uploadAction } from './upload';
|
|
35
27
|
|
|
36
28
|
interface ScanOptions {
|
|
@@ -53,6 +45,14 @@ interface ScanOptions {
|
|
|
53
45
|
server?: string;
|
|
54
46
|
}
|
|
55
47
|
|
|
48
|
+
/**
|
|
49
|
+
* CLI action handler for the "scan" command.
|
|
50
|
+
* Runs a comprehensive AI-readiness analysis across multiple tools,
|
|
51
|
+
* including pattern detection, context analysis, and naming consistency.
|
|
52
|
+
*
|
|
53
|
+
* @param directory - The directory to analyze (defaults to ".")
|
|
54
|
+
* @param options - CLI options from commander
|
|
55
|
+
*/
|
|
56
56
|
export async function scanAction(directory: string, options: ScanOptions) {
|
|
57
57
|
console.log(chalk.blue('🚀 Starting AIReady unified analysis...\n'));
|
|
58
58
|
|
|
@@ -211,13 +211,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
211
211
|
suppressToolConfig: true,
|
|
212
212
|
});
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
console.log(
|
|
216
|
-
` Total issues (all tools): ${chalk.bold(String(results.summary.totalIssues || 0))}`
|
|
217
|
-
);
|
|
218
|
-
console.log(
|
|
219
|
-
` Execution time: ${chalk.bold(((Date.now() - startTime) / 1000).toFixed(2) + 's')}`
|
|
220
|
-
);
|
|
214
|
+
printScanSummary(results, startTime);
|
|
221
215
|
|
|
222
216
|
let scoringResult: ScoringResult | undefined;
|
|
223
217
|
if (options.score || finalOptions.scoring?.showBreakdown) {
|
|
@@ -230,9 +224,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
230
224
|
},
|
|
231
225
|
});
|
|
232
226
|
|
|
233
|
-
|
|
234
|
-
console.log(` ${formatScore(scoringResult)}`);
|
|
235
|
-
console.log(chalk.dim(` (Scoring Profile: ${scoringProfile})`));
|
|
227
|
+
printScoring(scoringResult, scoringProfile);
|
|
236
228
|
|
|
237
229
|
// Trend comparison logic
|
|
238
230
|
if (options.compareTo) {
|
|
@@ -315,19 +307,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
315
307
|
modelId: modelId,
|
|
316
308
|
});
|
|
317
309
|
|
|
318
|
-
|
|
319
|
-
console.log(
|
|
320
|
-
` Potential Savings: ${chalk.green(chalk.bold('$' + roi.monthlySavings.toLocaleString()))}`
|
|
321
|
-
);
|
|
322
|
-
console.log(
|
|
323
|
-
` Productivity Gain: ${chalk.cyan(chalk.bold(roi.productivityGainHours + 'h'))} (est. dev time)`
|
|
324
|
-
);
|
|
325
|
-
console.log(
|
|
326
|
-
` Context Efficiency: ${chalk.yellow((unifiedBudget.efficiencyRatio * 100).toFixed(0) + '%')}`
|
|
327
|
-
);
|
|
328
|
-
console.log(
|
|
329
|
-
` Annual Value: ${chalk.bold('$' + roi.annualValue.toLocaleString())} (ROI Prediction)`
|
|
330
|
-
);
|
|
310
|
+
printBusinessImpact(roi, unifiedBudget);
|
|
331
311
|
|
|
332
312
|
(results.summary as any).businessImpact = {
|
|
333
313
|
estimatedMonthlyWaste: roi.monthlySavings,
|
|
@@ -338,43 +318,6 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
338
318
|
(scoringResult as any).tokenBudget = unifiedBudget;
|
|
339
319
|
(scoringResult as any).businessROI = roi;
|
|
340
320
|
}
|
|
341
|
-
|
|
342
|
-
if (scoringResult.breakdown) {
|
|
343
|
-
console.log(chalk.bold('\nTool breakdown:'));
|
|
344
|
-
scoringResult.breakdown.forEach((tool) => {
|
|
345
|
-
const rating = getRating(tool.score);
|
|
346
|
-
const emoji = getRatingDisplay(rating).emoji;
|
|
347
|
-
console.log(
|
|
348
|
-
` - ${tool.toolName}: ${tool.score}/100 (${rating}) ${emoji}`
|
|
349
|
-
);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
// Top Actionable Recommendations
|
|
353
|
-
const allRecs = scoringResult.breakdown
|
|
354
|
-
.flatMap((t) =>
|
|
355
|
-
(t.recommendations || []).map((r) => ({ ...r, tool: t.toolName }))
|
|
356
|
-
)
|
|
357
|
-
.sort((a, b) => b.estimatedImpact - a.estimatedImpact)
|
|
358
|
-
.slice(0, 3);
|
|
359
|
-
|
|
360
|
-
if (allRecs.length > 0) {
|
|
361
|
-
console.log(chalk.bold('\n🎯 Top Actionable Recommendations:'));
|
|
362
|
-
allRecs.forEach((rec, i) => {
|
|
363
|
-
const priorityIcon =
|
|
364
|
-
rec.priority === 'high'
|
|
365
|
-
? '🔴'
|
|
366
|
-
: rec.priority === 'medium'
|
|
367
|
-
? '🟡'
|
|
368
|
-
: '🔵';
|
|
369
|
-
console.log(
|
|
370
|
-
` ${i + 1}. ${priorityIcon} ${chalk.bold(rec.action)}`
|
|
371
|
-
);
|
|
372
|
-
console.log(
|
|
373
|
-
` Impact: ${chalk.green(`+${rec.estimatedImpact} points`)} to ${rec.tool}`
|
|
374
|
-
);
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
321
|
}
|
|
379
322
|
|
|
380
323
|
console.log(
|
|
@@ -395,43 +338,6 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
395
338
|
)
|
|
396
339
|
);
|
|
397
340
|
|
|
398
|
-
// Normalized report mapping
|
|
399
|
-
const mapToUnifiedReport = (
|
|
400
|
-
res: any,
|
|
401
|
-
scoring: ScoringResult | undefined
|
|
402
|
-
) => {
|
|
403
|
-
const allResults: any[] = [];
|
|
404
|
-
const totalFilesSet = new Set<string>();
|
|
405
|
-
let criticalCount = 0;
|
|
406
|
-
let majorCount = 0;
|
|
407
|
-
|
|
408
|
-
res.summary.toolsRun.forEach((toolId: string) => {
|
|
409
|
-
const spokeRes = res[toolId];
|
|
410
|
-
if (!spokeRes || !spokeRes.results) return;
|
|
411
|
-
|
|
412
|
-
spokeRes.results.forEach((r: any) => {
|
|
413
|
-
totalFilesSet.add(r.fileName);
|
|
414
|
-
allResults.push(r);
|
|
415
|
-
r.issues?.forEach((i: any) => {
|
|
416
|
-
if (i.severity === Severity.Critical) criticalCount++;
|
|
417
|
-
if (i.severity === Severity.Major) majorCount++;
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
...res,
|
|
424
|
-
results: allResults,
|
|
425
|
-
summary: {
|
|
426
|
-
...res.summary,
|
|
427
|
-
totalFiles: totalFilesSet.size,
|
|
428
|
-
criticalIssues: criticalCount,
|
|
429
|
-
majorIssues: majorCount,
|
|
430
|
-
},
|
|
431
|
-
scoring,
|
|
432
|
-
};
|
|
433
|
-
};
|
|
434
|
-
|
|
435
341
|
const outputData = {
|
|
436
342
|
...mapToUnifiedReport(results, scoringResult),
|
|
437
343
|
repository: repoMetadata,
|
package/src/commands/upload.ts
CHANGED
|
@@ -51,7 +51,7 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
51
51
|
// Note: repoId is optional if the metadata contains it, but for now we'll require it or infer from metadata
|
|
52
52
|
const repoId = options.repoId || reportData.repository?.repoId;
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const response = await fetch(`${serverUrl}/api/analysis/upload`, {
|
|
55
55
|
method: 'POST',
|
|
56
56
|
headers: {
|
|
57
57
|
'Content-Type': 'application/json',
|
|
@@ -63,19 +63,21 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
63
63
|
}),
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
const contentType =
|
|
67
|
-
let
|
|
66
|
+
const contentType = response.headers.get('content-type');
|
|
67
|
+
let uploadResult: any = {};
|
|
68
68
|
|
|
69
69
|
if (contentType?.includes('application/json')) {
|
|
70
|
-
|
|
70
|
+
uploadResult = await response.json();
|
|
71
71
|
} else {
|
|
72
|
-
const text = await
|
|
73
|
-
|
|
72
|
+
const text = await response.text();
|
|
73
|
+
uploadResult = { error: text || response.statusText };
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
if (!
|
|
76
|
+
if (!response.ok) {
|
|
77
77
|
console.error(
|
|
78
|
-
chalk.red(
|
|
78
|
+
chalk.red(
|
|
79
|
+
`❌ Upload failed: ${uploadResult.error || response.statusText}`
|
|
80
|
+
)
|
|
79
81
|
);
|
|
80
82
|
|
|
81
83
|
// Special case for redirects or HTML error pages
|
|
@@ -85,7 +87,7 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
85
87
|
' Note: Received an HTML response. This often indicates a redirect (e.g., to a login page) or a server error.'
|
|
86
88
|
)
|
|
87
89
|
);
|
|
88
|
-
if (
|
|
90
|
+
if (uploadResult.error?.includes('Redirecting')) {
|
|
89
91
|
console.log(
|
|
90
92
|
chalk.dim(
|
|
91
93
|
' Detected redirect. Check if the API endpoint requires authentication or has changed.'
|
|
@@ -94,7 +96,7 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
if (
|
|
99
|
+
if (response.status === 401) {
|
|
98
100
|
console.log(
|
|
99
101
|
chalk.dim(' Hint: Your API key may be invalid or expired.')
|
|
100
102
|
);
|
|
@@ -106,9 +108,9 @@ export async function uploadAction(file: string, options: UploadOptions) {
|
|
|
106
108
|
console.log(chalk.green(`\n✅ Upload successful! (${duration}s)`));
|
|
107
109
|
console.log(chalk.cyan(` View results: ${serverUrl}/dashboard`));
|
|
108
110
|
|
|
109
|
-
if (
|
|
110
|
-
console.log(chalk.dim(` Analysis ID: ${
|
|
111
|
-
console.log(chalk.dim(` Score: ${
|
|
111
|
+
if (uploadResult.analysis) {
|
|
112
|
+
console.log(chalk.dim(` Analysis ID: ${uploadResult.analysis.id}`));
|
|
113
|
+
console.log(chalk.dim(` Score: ${uploadResult.analysis.aiScore}/100`));
|
|
112
114
|
}
|
|
113
115
|
} catch (error) {
|
|
114
116
|
handleCLIError(error, 'Upload');
|
|
@@ -17,6 +17,14 @@ interface VisualizeOptions {
|
|
|
17
17
|
dev?: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* CLI action handler for the "visualize" command.
|
|
22
|
+
* Generates an interactive dependency graph visualization of the project
|
|
23
|
+
* to help understand code structure and AI context usage.
|
|
24
|
+
*
|
|
25
|
+
* @param directory - The directory to analyze and visualize
|
|
26
|
+
* @param options - CLI options from commander
|
|
27
|
+
*/
|
|
20
28
|
export async function visualizeAction(
|
|
21
29
|
directory: string,
|
|
22
30
|
options: VisualizeOptions
|
|
@@ -220,7 +228,7 @@ export async function visualizeAction(
|
|
|
220
228
|
|
|
221
229
|
// Generate static HTML (default behavior or fallback from failed --dev)
|
|
222
230
|
console.log('Generating HTML...');
|
|
223
|
-
const html = generateHTML(graph);
|
|
231
|
+
const html = generateHTML(graph as any);
|
|
224
232
|
const defaultOutput = 'visualization.html';
|
|
225
233
|
const outPath = resolvePath(dirPath, options.output || defaultOutput);
|
|
226
234
|
writeFileSync(outPath, html, 'utf8');
|
package/src/index.ts
CHANGED
|
@@ -3,15 +3,12 @@ import {
|
|
|
3
3
|
ToolName,
|
|
4
4
|
calculateOverallScore,
|
|
5
5
|
calculateTokenBudget,
|
|
6
|
-
GLOBAL_SCAN_OPTIONS,
|
|
7
6
|
GLOBAL_INFRA_OPTIONS,
|
|
8
7
|
COMMON_FINE_TUNING_OPTIONS,
|
|
9
8
|
initializeParsers,
|
|
10
9
|
} from '@aiready/core';
|
|
11
10
|
import type {
|
|
12
|
-
AnalysisResult,
|
|
13
11
|
ScanOptions,
|
|
14
|
-
SpokeOutput,
|
|
15
12
|
ToolScoringOutput,
|
|
16
13
|
ScoringResult,
|
|
17
14
|
} from '@aiready/core';
|
|
@@ -29,16 +26,30 @@ import '@aiready/change-amplification';
|
|
|
29
26
|
|
|
30
27
|
export type { ToolScoringOutput, ScoringResult };
|
|
31
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Options for running a unified AI-readiness analysis across multiple tools.
|
|
31
|
+
* Extends base ScanOptions with CLI-specific configurations.
|
|
32
|
+
*/
|
|
32
33
|
export interface UnifiedAnalysisOptions extends ScanOptions {
|
|
34
|
+
/** Root directory for analysis */
|
|
33
35
|
rootDir: string;
|
|
36
|
+
/** List of tools to run (e.g. ['patterns', 'context']) */
|
|
34
37
|
tools?: string[];
|
|
38
|
+
/** Overrides for specific tool configurations */
|
|
35
39
|
toolConfigs?: Record<string, any>;
|
|
40
|
+
/** Minimum similarity threshold for pattern detection (0-1) */
|
|
36
41
|
minSimilarity?: number;
|
|
42
|
+
/** Minimum number of lines for a pattern to be considered */
|
|
37
43
|
minLines?: number;
|
|
44
|
+
/** Maximum number of candidates to check per code block */
|
|
38
45
|
maxCandidatesPerBlock?: number;
|
|
46
|
+
/** Minimum number of shared tokens for a match */
|
|
39
47
|
minSharedTokens?: number;
|
|
48
|
+
/** Whether to use optimized defaults based on project size/language */
|
|
40
49
|
useSmartDefaults?: boolean;
|
|
50
|
+
/** Specific options for naming consistency analysis */
|
|
41
51
|
consistency?: any;
|
|
52
|
+
/** Optional callback for tracking analysis progress */
|
|
42
53
|
progressCallback?: (event: {
|
|
43
54
|
tool: string;
|
|
44
55
|
data?: any;
|
|
@@ -48,6 +59,10 @@ export interface UnifiedAnalysisOptions extends ScanOptions {
|
|
|
48
59
|
}) => void;
|
|
49
60
|
}
|
|
50
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The consolidated result of a unified analysis across all requested tools.
|
|
64
|
+
* Contains tool-specific outputs, scoring, and a high-level summary.
|
|
65
|
+
*/
|
|
51
66
|
export interface UnifiedAnalysisResult {
|
|
52
67
|
// Dynamic keys based on ToolName
|
|
53
68
|
[key: string]: any;
|
package/src/utils/helpers.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { resolve as resolvePath } from 'path';
|
|
6
|
-
import { existsSync,
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { loadConfig, mergeConfigWithDefaults } from '@aiready/core';
|
|
9
9
|
import type { ToolScoringOutput } from '@aiready/core';
|