@aiready/cli 0.9.40 → 0.9.43
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/.aiready/aiready-report-20260227-133806.json +40 -141
- package/.aiready/aiready-report-20260227-133938.json +40 -141
- package/.aiready/aiready-report-20260228-003433.json +7939 -0
- package/.aiready/aiready-report-20260228-003613.json +771 -0
- package/.github/FUNDING.yml +2 -2
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-lint.log +5 -0
- package/.turbo/turbo-test.log +5 -5
- package/CONTRIBUTING.md +11 -2
- package/dist/chunk-HLBKROD3.mjs +237 -0
- package/dist/chunk-LLJMKNBI.mjs +243 -0
- package/dist/cli.js +708 -184
- package/dist/cli.mjs +687 -178
- package/dist/index.js +24 -9
- package/dist/index.mjs +1 -1
- package/package.json +12 -12
- package/src/__tests__/cli.test.ts +1 -1
- package/src/cli.ts +114 -28
- package/src/commands/agent-grounding.ts +22 -7
- package/src/commands/ai-signal-clarity.ts +14 -9
- package/src/commands/consistency.ts +70 -30
- package/src/commands/context.ts +109 -39
- package/src/commands/deps-health.ts +15 -6
- package/src/commands/doc-drift.ts +10 -4
- package/src/commands/index.ts +6 -2
- package/src/commands/patterns.ts +67 -26
- package/src/commands/scan.ts +411 -126
- package/src/commands/testability.ts +22 -9
- package/src/commands/visualize.ts +102 -45
- package/src/index.ts +40 -10
- package/src/utils/helpers.ts +57 -32
|
@@ -8,9 +8,10 @@ import type { ToolScoringOutput } from '@aiready/core';
|
|
|
8
8
|
|
|
9
9
|
export async function testabilityAction(
|
|
10
10
|
directory: string,
|
|
11
|
-
options: any
|
|
11
|
+
options: any
|
|
12
12
|
): Promise<ToolScoringOutput | undefined> {
|
|
13
|
-
const { analyzeTestability, calculateTestabilityScore } =
|
|
13
|
+
const { analyzeTestability, calculateTestabilityScore } =
|
|
14
|
+
await import('@aiready/testability');
|
|
14
15
|
|
|
15
16
|
const config = await loadConfig(directory);
|
|
16
17
|
const merged = mergeConfigWithDefaults(config, {
|
|
@@ -31,13 +32,13 @@ export async function testabilityAction(
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const safetyIcons: Record<string, string> = {
|
|
34
|
-
|
|
35
|
+
safe: '✅',
|
|
35
36
|
'moderate-risk': '⚠️ ',
|
|
36
37
|
'high-risk': '🔴',
|
|
37
38
|
'blind-risk': '💀',
|
|
38
39
|
};
|
|
39
|
-
const safetyColors: Record<string,
|
|
40
|
-
|
|
40
|
+
const safetyColors: Record<string, (s: string) => string> = {
|
|
41
|
+
safe: chalk.green,
|
|
41
42
|
'moderate-risk': chalk.yellow,
|
|
42
43
|
'high-risk': chalk.red,
|
|
43
44
|
'blind-risk': chalk.bgRed.white,
|
|
@@ -47,13 +48,25 @@ export async function testabilityAction(
|
|
|
47
48
|
const icon = safetyIcons[safety] ?? '❓';
|
|
48
49
|
const color = safetyColors[safety] ?? chalk.white;
|
|
49
50
|
|
|
50
|
-
console.log(
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
console.log(
|
|
52
|
+
` 🧪 Testability: ${chalk.bold(scoring.score + '/100')} (${report.summary.rating})`
|
|
53
|
+
);
|
|
54
|
+
console.log(
|
|
55
|
+
` AI Change Safety: ${color(`${icon} ${safety.toUpperCase()}`)}`
|
|
56
|
+
);
|
|
57
|
+
console.log(
|
|
58
|
+
chalk.dim(
|
|
59
|
+
` Coverage: ${Math.round(report.summary.coverageRatio * 100)}% (${report.rawData.testFiles} test / ${report.rawData.sourceFiles} source files)`
|
|
60
|
+
)
|
|
61
|
+
);
|
|
53
62
|
|
|
54
63
|
// Critical blind-risk banner in the unified output
|
|
55
64
|
if (safety === 'blind-risk') {
|
|
56
|
-
console.log(
|
|
65
|
+
console.log(
|
|
66
|
+
chalk.red.bold(
|
|
67
|
+
'\n ⚠️ NO TESTS — AI changes to this codebase are completely unverifiable!\n'
|
|
68
|
+
)
|
|
69
|
+
);
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
return scoring;
|
|
@@ -18,20 +18,31 @@ interface VisualizeOptions {
|
|
|
18
18
|
dev?: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export async function visualizeAction(
|
|
21
|
+
export async function visualizeAction(
|
|
22
|
+
directory: string,
|
|
23
|
+
options: VisualizeOptions
|
|
24
|
+
) {
|
|
22
25
|
try {
|
|
23
26
|
const dirPath = resolvePath(process.cwd(), directory || '.');
|
|
24
|
-
let reportPath = options.report
|
|
25
|
-
|
|
27
|
+
let reportPath = options.report
|
|
28
|
+
? resolvePath(dirPath, options.report)
|
|
29
|
+
: null;
|
|
30
|
+
|
|
26
31
|
// If report not provided or not found, try to find latest scan report
|
|
27
32
|
if (!reportPath || !existsSync(reportPath)) {
|
|
28
33
|
const latestScan = findLatestScanReport(dirPath);
|
|
29
34
|
if (latestScan) {
|
|
30
35
|
reportPath = latestScan;
|
|
31
|
-
console.log(
|
|
36
|
+
console.log(
|
|
37
|
+
chalk.dim(`Found latest report: ${latestScan.split('/').pop()}`)
|
|
38
|
+
);
|
|
32
39
|
} else {
|
|
33
40
|
console.error(chalk.red('❌ No AI readiness report found'));
|
|
34
|
-
console.log(
|
|
41
|
+
console.log(
|
|
42
|
+
chalk.dim(
|
|
43
|
+
`\nGenerate a report with:\n aiready scan --output json\n\nOr specify a custom report:\n aiready visualise --report <path-to-report.json>`
|
|
44
|
+
)
|
|
45
|
+
);
|
|
35
46
|
return;
|
|
36
47
|
}
|
|
37
48
|
}
|
|
@@ -42,39 +53,42 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
|
|
|
42
53
|
// Load config to extract graph caps
|
|
43
54
|
const configPath = resolvePath(dirPath, 'aiready.json');
|
|
44
55
|
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
45
|
-
|
|
56
|
+
|
|
46
57
|
if (existsSync(configPath)) {
|
|
47
58
|
try {
|
|
48
59
|
const rawConfig = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
49
60
|
if (rawConfig.visualizer?.graph) {
|
|
50
61
|
graphConfig = {
|
|
51
|
-
maxNodes:
|
|
52
|
-
|
|
62
|
+
maxNodes:
|
|
63
|
+
rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
64
|
+
maxEdges:
|
|
65
|
+
rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges,
|
|
53
66
|
};
|
|
54
67
|
}
|
|
55
68
|
} catch (e) {
|
|
69
|
+
void e;
|
|
56
70
|
// Silently ignore parse errors and use defaults
|
|
57
71
|
}
|
|
58
72
|
}
|
|
59
|
-
|
|
73
|
+
|
|
60
74
|
// Store config in env for vite middleware to pass to client
|
|
61
75
|
const envVisualizerConfig = JSON.stringify(graphConfig);
|
|
62
76
|
process.env.AIREADY_VISUALIZER_CONFIG = envVisualizerConfig;
|
|
63
77
|
|
|
64
|
-
console.log(
|
|
78
|
+
console.log('Building graph from report...');
|
|
65
79
|
const { GraphBuilder } = await import('@aiready/visualizer/graph');
|
|
66
80
|
const graph = GraphBuilder.buildFromReport(report, dirPath);
|
|
67
81
|
|
|
68
82
|
// Check if --dev mode is requested and available
|
|
69
83
|
let useDevMode = options.dev || false;
|
|
70
84
|
let devServerStarted = false;
|
|
71
|
-
|
|
85
|
+
|
|
72
86
|
if (useDevMode) {
|
|
73
87
|
try {
|
|
74
88
|
const monorepoWebDir = resolvePath(dirPath, 'packages/visualizer');
|
|
75
89
|
let webDir = '';
|
|
76
90
|
let visualizerAvailable = false;
|
|
77
|
-
|
|
91
|
+
|
|
78
92
|
if (existsSync(monorepoWebDir)) {
|
|
79
93
|
webDir = monorepoWebDir;
|
|
80
94
|
visualizerAvailable = true;
|
|
@@ -82,41 +96,54 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
|
|
|
82
96
|
// Try to resolve installed @aiready/visualizer package from node_modules
|
|
83
97
|
const nodemodulesLocations: string[] = [
|
|
84
98
|
resolvePath(dirPath, 'node_modules', '@aiready', 'visualizer'),
|
|
85
|
-
resolvePath(
|
|
99
|
+
resolvePath(
|
|
100
|
+
process.cwd(),
|
|
101
|
+
'node_modules',
|
|
102
|
+
'@aiready',
|
|
103
|
+
'visualizer'
|
|
104
|
+
),
|
|
86
105
|
];
|
|
87
|
-
|
|
106
|
+
|
|
88
107
|
// Walk up directory tree to find node_modules in parent directories
|
|
89
108
|
let currentDir = dirPath;
|
|
90
109
|
while (currentDir !== '/' && currentDir !== '.') {
|
|
91
|
-
nodemodulesLocations.push(
|
|
110
|
+
nodemodulesLocations.push(
|
|
111
|
+
resolvePath(currentDir, 'node_modules', '@aiready', 'visualizer')
|
|
112
|
+
);
|
|
92
113
|
const parent = resolvePath(currentDir, '..');
|
|
93
114
|
if (parent === currentDir) break;
|
|
94
115
|
currentDir = parent;
|
|
95
116
|
}
|
|
96
|
-
|
|
117
|
+
|
|
97
118
|
for (const location of nodemodulesLocations) {
|
|
98
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
existsSync(location) &&
|
|
121
|
+
existsSync(resolvePath(location, 'package.json'))
|
|
122
|
+
) {
|
|
99
123
|
webDir = location;
|
|
100
124
|
visualizerAvailable = true;
|
|
101
125
|
break;
|
|
102
126
|
}
|
|
103
127
|
}
|
|
104
|
-
|
|
128
|
+
|
|
105
129
|
// Fallback: try require.resolve
|
|
106
130
|
if (!visualizerAvailable) {
|
|
107
131
|
try {
|
|
108
|
-
const vizPkgPath =
|
|
132
|
+
const vizPkgPath =
|
|
133
|
+
require.resolve('@aiready/visualizer/package.json');
|
|
109
134
|
webDir = resolvePath(vizPkgPath, '..');
|
|
110
135
|
visualizerAvailable = true;
|
|
111
|
-
} catch (
|
|
136
|
+
} catch (err) {
|
|
137
|
+
void err;
|
|
112
138
|
// Visualizer not found
|
|
113
139
|
}
|
|
114
140
|
}
|
|
115
141
|
}
|
|
116
|
-
|
|
142
|
+
|
|
117
143
|
// Check if web directory with vite config exists (required for dev mode)
|
|
118
|
-
const webViteConfigExists =
|
|
119
|
-
|
|
144
|
+
const webViteConfigExists =
|
|
145
|
+
webDir && existsSync(resolvePath(webDir, 'web', 'vite.config.ts'));
|
|
146
|
+
|
|
120
147
|
if (visualizerAvailable && webViteConfigExists) {
|
|
121
148
|
// Dev mode is available - start Vite dev server
|
|
122
149
|
const spawnCwd = webDir!;
|
|
@@ -132,10 +159,10 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
|
|
|
132
159
|
console.error('Failed to sync report:', e);
|
|
133
160
|
}
|
|
134
161
|
};
|
|
135
|
-
|
|
162
|
+
|
|
136
163
|
// Initial copy
|
|
137
164
|
copyReportToViz();
|
|
138
|
-
|
|
165
|
+
|
|
139
166
|
// Watch source report for changes
|
|
140
167
|
let watchTimeout: NodeJS.Timeout | null = null;
|
|
141
168
|
const reportWatcher = watch(reportPath, () => {
|
|
@@ -143,45 +170,71 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
|
|
|
143
170
|
watchTimeout = setTimeout(copyReportToViz, 100);
|
|
144
171
|
});
|
|
145
172
|
|
|
146
|
-
const envForSpawn = {
|
|
147
|
-
...process.env,
|
|
173
|
+
const envForSpawn = {
|
|
174
|
+
...process.env,
|
|
148
175
|
AIREADY_REPORT_PATH: reportPath,
|
|
149
|
-
AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
|
|
176
|
+
AIREADY_VISUALIZER_CONFIG: envVisualizerConfig,
|
|
150
177
|
};
|
|
151
|
-
const vite = spawn(
|
|
178
|
+
const vite = spawn('pnpm', ['run', 'dev:web'], {
|
|
179
|
+
cwd: spawnCwd,
|
|
180
|
+
stdio: 'inherit',
|
|
181
|
+
shell: true,
|
|
182
|
+
env: envForSpawn,
|
|
183
|
+
});
|
|
152
184
|
const onExit = () => {
|
|
153
|
-
try {
|
|
154
|
-
|
|
185
|
+
try {
|
|
186
|
+
reportWatcher.close();
|
|
187
|
+
} catch (err) {
|
|
188
|
+
void err;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
vite.kill();
|
|
192
|
+
} catch (err) {
|
|
193
|
+
void err;
|
|
194
|
+
}
|
|
155
195
|
process.exit(0);
|
|
156
196
|
};
|
|
157
|
-
process.on(
|
|
158
|
-
process.on(
|
|
197
|
+
process.on('SIGINT', onExit);
|
|
198
|
+
process.on('SIGTERM', onExit);
|
|
159
199
|
devServerStarted = true;
|
|
200
|
+
void devServerStarted;
|
|
160
201
|
return;
|
|
161
202
|
} else {
|
|
162
|
-
console.log(
|
|
163
|
-
|
|
203
|
+
console.log(
|
|
204
|
+
chalk.yellow(
|
|
205
|
+
'⚠️ Dev server not available (requires local @aiready/visualizer with web assets).'
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
console.log(
|
|
209
|
+
chalk.cyan(' Falling back to static HTML generation...\n')
|
|
210
|
+
);
|
|
164
211
|
useDevMode = false;
|
|
165
212
|
}
|
|
166
213
|
} catch (err) {
|
|
167
|
-
console.error(
|
|
168
|
-
console.log(
|
|
214
|
+
console.error('Failed to start dev server:', err);
|
|
215
|
+
console.log(
|
|
216
|
+
chalk.cyan(' Falling back to static HTML generation...\n')
|
|
217
|
+
);
|
|
169
218
|
useDevMode = false;
|
|
170
219
|
}
|
|
171
220
|
}
|
|
172
221
|
|
|
173
222
|
// Generate static HTML (default behavior or fallback from failed --dev)
|
|
174
|
-
console.log(
|
|
223
|
+
console.log('Generating HTML...');
|
|
175
224
|
const html = generateHTML(graph);
|
|
176
225
|
const defaultOutput = 'visualization.html';
|
|
177
226
|
const outPath = resolvePath(dirPath, options.output || defaultOutput);
|
|
178
227
|
writeFileSync(outPath, html, 'utf8');
|
|
179
228
|
console.log(chalk.green(`✅ Visualization written to: ${outPath}`));
|
|
180
|
-
|
|
181
229
|
|
|
182
230
|
if (options.open || options.serve) {
|
|
183
|
-
const opener =
|
|
184
|
-
|
|
231
|
+
const opener =
|
|
232
|
+
process.platform === 'darwin'
|
|
233
|
+
? 'open'
|
|
234
|
+
: process.platform === 'win32'
|
|
235
|
+
? 'start'
|
|
236
|
+
: 'xdg-open';
|
|
237
|
+
|
|
185
238
|
if (options.serve) {
|
|
186
239
|
try {
|
|
187
240
|
const port = typeof options.serve === 'number' ? options.serve : 5173;
|
|
@@ -193,13 +246,16 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
|
|
|
193
246
|
const urlPath = req.url || '/';
|
|
194
247
|
if (urlPath === '/' || urlPath === '/index.html') {
|
|
195
248
|
const content = await fsp.readFile(outPath, 'utf8');
|
|
196
|
-
res.writeHead(200, {
|
|
249
|
+
res.writeHead(200, {
|
|
250
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
251
|
+
});
|
|
197
252
|
res.end(content);
|
|
198
253
|
return;
|
|
199
254
|
}
|
|
200
255
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
201
256
|
res.end('Not found');
|
|
202
257
|
} catch (e: any) {
|
|
258
|
+
void e;
|
|
203
259
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
204
260
|
res.end('Server error');
|
|
205
261
|
}
|
|
@@ -207,7 +263,9 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
|
|
|
207
263
|
|
|
208
264
|
server.listen(port, () => {
|
|
209
265
|
const addr = `http://localhost:${port}/`;
|
|
210
|
-
console.log(
|
|
266
|
+
console.log(
|
|
267
|
+
chalk.cyan(`🌐 Local visualization server running at ${addr}`)
|
|
268
|
+
);
|
|
211
269
|
spawn(opener, [`"${addr}"`], { shell: true });
|
|
212
270
|
});
|
|
213
271
|
|
|
@@ -222,7 +280,6 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
|
|
|
222
280
|
spawn(opener, [`"${outPath}"`], { shell: true });
|
|
223
281
|
}
|
|
224
282
|
}
|
|
225
|
-
|
|
226
283
|
} catch (err: any) {
|
|
227
284
|
handleCLIError(err, 'Visualization');
|
|
228
285
|
}
|
|
@@ -254,4 +311,4 @@ EXAMPLES:
|
|
|
254
311
|
|
|
255
312
|
NOTES:
|
|
256
313
|
- Same options as 'visualize'. Use --serve to host the static HTML, or --dev for live reload.
|
|
257
|
-
`;
|
|
314
|
+
`;
|
package/src/index.ts
CHANGED
|
@@ -3,13 +3,23 @@ import { analyzeContext } from '@aiready/context-analyzer';
|
|
|
3
3
|
import { analyzeConsistency } from '@aiready/consistency';
|
|
4
4
|
import type { AnalysisResult, ScanOptions } from '@aiready/core';
|
|
5
5
|
import type { ContextAnalysisResult } from '@aiready/context-analyzer';
|
|
6
|
-
import type {
|
|
6
|
+
import type { DuplicatePattern } from '@aiready/pattern-detect';
|
|
7
7
|
import type { ConsistencyReport } from '@aiready/consistency';
|
|
8
8
|
|
|
9
9
|
import type { ConsistencyOptions } from '@aiready/consistency';
|
|
10
10
|
|
|
11
11
|
export interface UnifiedAnalysisOptions extends ScanOptions {
|
|
12
|
-
tools?: (
|
|
12
|
+
tools?: (
|
|
13
|
+
| 'patterns'
|
|
14
|
+
| 'context'
|
|
15
|
+
| 'consistency'
|
|
16
|
+
| 'doc-drift'
|
|
17
|
+
| 'deps-health'
|
|
18
|
+
| 'aiSignalClarity'
|
|
19
|
+
| 'grounding'
|
|
20
|
+
| 'testability'
|
|
21
|
+
| 'changeAmplification'
|
|
22
|
+
)[];
|
|
13
23
|
minSimilarity?: number;
|
|
14
24
|
minLines?: number;
|
|
15
25
|
maxCandidatesPerBlock?: number;
|
|
@@ -50,7 +60,8 @@ function sortBySeverity(results: AnalysisResult[]): AnalysisResult[] {
|
|
|
50
60
|
.map((file) => {
|
|
51
61
|
// Sort issues within each file by severity (most severe first)
|
|
52
62
|
const sortedIssues = [...file.issues].sort((a, b) => {
|
|
53
|
-
const severityDiff =
|
|
63
|
+
const severityDiff =
|
|
64
|
+
(severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
|
|
54
65
|
if (severityDiff !== 0) return severityDiff;
|
|
55
66
|
// If same severity, sort by line number
|
|
56
67
|
return (a.location?.line || 0) - (b.location?.line || 0);
|
|
@@ -59,8 +70,14 @@ function sortBySeverity(results: AnalysisResult[]): AnalysisResult[] {
|
|
|
59
70
|
})
|
|
60
71
|
.sort((a, b) => {
|
|
61
72
|
// Sort files by most severe issue first
|
|
62
|
-
const aMaxSeverity = Math.max(
|
|
63
|
-
|
|
73
|
+
const aMaxSeverity = Math.max(
|
|
74
|
+
...a.issues.map((i) => severityOrder[i.severity] || 0),
|
|
75
|
+
0
|
|
76
|
+
);
|
|
77
|
+
const bMaxSeverity = Math.max(
|
|
78
|
+
...b.issues.map((i) => severityOrder[i.severity] || 0),
|
|
79
|
+
0
|
|
80
|
+
);
|
|
64
81
|
if (aMaxSeverity !== bMaxSeverity) {
|
|
65
82
|
return bMaxSeverity - aMaxSeverity;
|
|
66
83
|
}
|
|
@@ -113,7 +130,8 @@ export async function analyzeUnified(
|
|
|
113
130
|
}
|
|
114
131
|
// Sort context results by severity (most severe first)
|
|
115
132
|
result.context = contextResults.sort((a, b) => {
|
|
116
|
-
const severityDiff =
|
|
133
|
+
const severityDiff =
|
|
134
|
+
(severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
|
|
117
135
|
if (severityDiff !== 0) return severityDiff;
|
|
118
136
|
// If same severity, sort by token cost (higher cost first)
|
|
119
137
|
if (a.tokenCost !== b.tokenCost) return b.tokenCost - a.tokenCost;
|
|
@@ -151,6 +169,7 @@ export async function analyzeUnified(
|
|
|
151
169
|
rootDir: options.rootDir,
|
|
152
170
|
include: options.include,
|
|
153
171
|
exclude: options.exclude,
|
|
172
|
+
onProgress: options.onProgress,
|
|
154
173
|
});
|
|
155
174
|
if (options.progressCallback) {
|
|
156
175
|
options.progressCallback({ tool: 'doc-drift', data: report });
|
|
@@ -166,6 +185,7 @@ export async function analyzeUnified(
|
|
|
166
185
|
rootDir: options.rootDir,
|
|
167
186
|
include: options.include,
|
|
168
187
|
exclude: options.exclude,
|
|
188
|
+
onProgress: options.onProgress,
|
|
169
189
|
});
|
|
170
190
|
if (options.progressCallback) {
|
|
171
191
|
options.progressCallback({ tool: 'deps-health', data: report });
|
|
@@ -176,17 +196,23 @@ export async function analyzeUnified(
|
|
|
176
196
|
|
|
177
197
|
// Run AI Signal Clarity analysis
|
|
178
198
|
if (tools.includes('aiSignalClarity')) {
|
|
179
|
-
const { analyzeAiSignalClarity } =
|
|
199
|
+
const { analyzeAiSignalClarity } =
|
|
200
|
+
await import('@aiready/ai-signal-clarity');
|
|
180
201
|
const report = await analyzeAiSignalClarity({
|
|
181
202
|
rootDir: options.rootDir,
|
|
182
203
|
include: options.include,
|
|
183
204
|
exclude: options.exclude,
|
|
205
|
+
onProgress: options.onProgress,
|
|
184
206
|
});
|
|
185
207
|
if (options.progressCallback) {
|
|
186
208
|
options.progressCallback({ tool: 'aiSignalClarity', data: report });
|
|
187
209
|
}
|
|
188
210
|
result.aiSignalClarity = report;
|
|
189
|
-
result.summary.totalIssues +=
|
|
211
|
+
result.summary.totalIssues +=
|
|
212
|
+
report.results?.reduce(
|
|
213
|
+
(sum: number, r: any) => sum + (r.issues?.length || 0),
|
|
214
|
+
0
|
|
215
|
+
) || 0;
|
|
190
216
|
}
|
|
191
217
|
|
|
192
218
|
// Run Agent Grounding analysis
|
|
@@ -196,6 +222,7 @@ export async function analyzeUnified(
|
|
|
196
222
|
rootDir: options.rootDir,
|
|
197
223
|
include: options.include,
|
|
198
224
|
exclude: options.exclude,
|
|
225
|
+
onProgress: options.onProgress,
|
|
199
226
|
});
|
|
200
227
|
if (options.progressCallback) {
|
|
201
228
|
options.progressCallback({ tool: 'grounding', data: report });
|
|
@@ -211,6 +238,7 @@ export async function analyzeUnified(
|
|
|
211
238
|
rootDir: options.rootDir,
|
|
212
239
|
include: options.include,
|
|
213
240
|
exclude: options.exclude,
|
|
241
|
+
onProgress: options.onProgress,
|
|
214
242
|
});
|
|
215
243
|
if (options.progressCallback) {
|
|
216
244
|
options.progressCallback({ tool: 'testability', data: report });
|
|
@@ -221,11 +249,13 @@ export async function analyzeUnified(
|
|
|
221
249
|
|
|
222
250
|
// Run Change Amplification analysis
|
|
223
251
|
if (tools.includes('changeAmplification')) {
|
|
224
|
-
const { analyzeChangeAmplification } =
|
|
252
|
+
const { analyzeChangeAmplification } =
|
|
253
|
+
await import('@aiready/change-amplification');
|
|
225
254
|
const report = await analyzeChangeAmplification({
|
|
226
255
|
rootDir: options.rootDir,
|
|
227
256
|
include: options.include,
|
|
228
257
|
exclude: options.exclude,
|
|
258
|
+
onProgress: options.onProgress,
|
|
229
259
|
});
|
|
230
260
|
if (options.progressCallback) {
|
|
231
261
|
options.progressCallback({ tool: 'changeAmplification', data: report });
|
|
@@ -283,4 +313,4 @@ export function generateUnifiedSummary(result: UnifiedAnalysisResult): string {
|
|
|
283
313
|
}
|
|
284
314
|
|
|
285
315
|
return output;
|
|
286
|
-
}
|
|
316
|
+
}
|
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, readdirSync, statSync, readFileSync
|
|
6
|
+
import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -25,35 +25,45 @@ export function findLatestScanReport(dirPath: string): string | null {
|
|
|
25
25
|
if (!existsSync(aireadyDir)) {
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
// Search for new format first, then legacy format
|
|
30
|
-
let files = readdirSync(aireadyDir).filter(
|
|
30
|
+
let files = readdirSync(aireadyDir).filter(
|
|
31
|
+
(f) => f.startsWith('aiready-report-') && f.endsWith('.json')
|
|
32
|
+
);
|
|
31
33
|
if (files.length === 0) {
|
|
32
|
-
files = readdirSync(aireadyDir).filter(
|
|
34
|
+
files = readdirSync(aireadyDir).filter(
|
|
35
|
+
(f) => f.startsWith('aiready-scan-') && f.endsWith('.json')
|
|
36
|
+
);
|
|
33
37
|
}
|
|
34
|
-
|
|
38
|
+
|
|
35
39
|
if (files.length === 0) {
|
|
36
40
|
return null;
|
|
37
41
|
}
|
|
38
|
-
|
|
42
|
+
|
|
39
43
|
// Sort by modification time, most recent first
|
|
40
44
|
const sortedFiles = files
|
|
41
|
-
.map(f => ({
|
|
45
|
+
.map((f) => ({
|
|
46
|
+
name: f,
|
|
47
|
+
path: resolvePath(aireadyDir, f),
|
|
48
|
+
mtime: statSync(resolvePath(aireadyDir, f)).mtime,
|
|
49
|
+
}))
|
|
42
50
|
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
43
|
-
|
|
51
|
+
|
|
44
52
|
return sortedFiles[0].path;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
/**
|
|
48
56
|
* Warn if graph caps may be exceeded
|
|
49
57
|
*/
|
|
50
|
-
export function warnIfGraphCapExceeded(report: any, dirPath: string) {
|
|
58
|
+
export async function warnIfGraphCapExceeded(report: any, dirPath: string) {
|
|
51
59
|
try {
|
|
52
60
|
// Use dynamic import and loadConfig to get the raw visualizer config
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
// Use dynamic import instead of require
|
|
62
|
+
const { loadConfig } = await import('@aiready/core');
|
|
63
|
+
void loadConfig;
|
|
64
|
+
|
|
55
65
|
let graphConfig = { maxNodes: 400, maxEdges: 600 };
|
|
56
|
-
|
|
66
|
+
|
|
57
67
|
// Try to read aiready.json synchronously
|
|
58
68
|
const configPath = resolvePath(dirPath, 'aiready.json');
|
|
59
69
|
if (existsSync(configPath)) {
|
|
@@ -61,47 +71,62 @@ export function warnIfGraphCapExceeded(report: any, dirPath: string) {
|
|
|
61
71
|
const rawConfig = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
62
72
|
if (rawConfig.visualizer?.graph) {
|
|
63
73
|
graphConfig = {
|
|
64
|
-
maxNodes:
|
|
65
|
-
|
|
74
|
+
maxNodes:
|
|
75
|
+
rawConfig.visualizer.graph.maxNodes ?? graphConfig.maxNodes,
|
|
76
|
+
maxEdges:
|
|
77
|
+
rawConfig.visualizer.graph.maxEdges ?? graphConfig.maxEdges,
|
|
66
78
|
};
|
|
67
79
|
}
|
|
68
|
-
} catch (
|
|
69
|
-
|
|
80
|
+
} catch (err) {
|
|
81
|
+
void err;
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
|
-
|
|
73
|
-
const nodeCount =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
|
|
85
|
+
const nodeCount =
|
|
86
|
+
(report.context?.length || 0) + (report.patterns?.length || 0);
|
|
87
|
+
const edgeCount =
|
|
88
|
+
report.context?.reduce((sum: number, ctx: any) => {
|
|
89
|
+
const relCount = ctx.relatedFiles?.length || 0;
|
|
90
|
+
const depCount = ctx.dependencies?.length || 0;
|
|
91
|
+
return sum + relCount + depCount;
|
|
92
|
+
}, 0) || 0;
|
|
93
|
+
|
|
80
94
|
if (nodeCount > graphConfig.maxNodes || edgeCount > graphConfig.maxEdges) {
|
|
81
95
|
console.log('');
|
|
82
|
-
console.log(
|
|
96
|
+
console.log(
|
|
97
|
+
chalk.yellow(`⚠️ Graph may be truncated at visualization time:`)
|
|
98
|
+
);
|
|
83
99
|
if (nodeCount > graphConfig.maxNodes) {
|
|
84
|
-
console.log(
|
|
100
|
+
console.log(
|
|
101
|
+
chalk.dim(` • Nodes: ${nodeCount} > limit ${graphConfig.maxNodes}`)
|
|
102
|
+
);
|
|
85
103
|
}
|
|
86
104
|
if (edgeCount > graphConfig.maxEdges) {
|
|
87
|
-
console.log(
|
|
105
|
+
console.log(
|
|
106
|
+
chalk.dim(` • Edges: ${edgeCount} > limit ${graphConfig.maxEdges}`)
|
|
107
|
+
);
|
|
88
108
|
}
|
|
89
109
|
console.log(chalk.dim(` To increase limits, add to aiready.json:`));
|
|
90
110
|
console.log(chalk.dim(` {`));
|
|
91
111
|
console.log(chalk.dim(` "visualizer": {`));
|
|
92
|
-
console.log(
|
|
112
|
+
console.log(
|
|
113
|
+
chalk.dim(` "graph": { "maxNodes": 2000, "maxEdges": 5000 }`)
|
|
114
|
+
);
|
|
93
115
|
console.log(chalk.dim(` }`));
|
|
94
116
|
console.log(chalk.dim(` }`));
|
|
95
117
|
}
|
|
96
|
-
} catch (
|
|
97
|
-
|
|
118
|
+
} catch (err) {
|
|
119
|
+
void err;
|
|
98
120
|
}
|
|
99
121
|
}
|
|
100
122
|
|
|
101
123
|
/**
|
|
102
124
|
* Generate markdown report for consistency command
|
|
103
125
|
*/
|
|
104
|
-
export function generateMarkdownReport(
|
|
126
|
+
export function generateMarkdownReport(
|
|
127
|
+
report: any,
|
|
128
|
+
elapsedTime: string
|
|
129
|
+
): string {
|
|
105
130
|
let markdown = `# Consistency Analysis Report\n\n`;
|
|
106
131
|
markdown += `**Generated:** ${new Date().toISOString()}\n`;
|
|
107
132
|
markdown += `**Analysis Time:** ${elapsedTime}s\n\n`;
|
|
@@ -130,4 +155,4 @@ export function truncateArray(arr: any[] | undefined, cap = 8): string {
|
|
|
130
155
|
const shown = arr.slice(0, cap).map((v) => String(v));
|
|
131
156
|
const more = arr.length - shown.length;
|
|
132
157
|
return shown.join(', ') + (more > 0 ? `, ... (+${more} more)` : '');
|
|
133
|
-
}
|
|
158
|
+
}
|