@aiready/cli 0.10.4 โ 0.12.0
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 +9 -1
- package/.turbo/turbo-test.log +16 -4
- package/dist/chunk-N56YAZVN.mjs +194 -0
- package/dist/chunk-YBZKPKW3.mjs +161 -0
- package/dist/cli.js +198 -892
- package/dist/cli.mjs +87 -582
- package/dist/index.js +116 -353
- package/dist/index.mjs +1 -1
- package/package.json +12 -12
- package/src/.aiready/aiready-report-20260306-213155.json +24657 -0
- package/src/.aiready/aiready-report-20260306-213925.json +24657 -0
- package/src/.aiready/aiready-report-20260307-092852.json +50609 -0
- package/src/.aiready/aiready-report-20260307-094301.json +50609 -0
- package/src/__tests__/cli.test.ts +55 -29
- package/src/commands/scan.ts +108 -698
- package/src/index.ts +154 -436
- package/dist/chunk-EQ2HQSTJ.mjs +0 -414
- package/dist/chunk-R3O7QPKD.mjs +0 -419
- package/dist/chunk-VUCNUYI7.mjs +0 -417
package/src/index.ts
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
|
-
import { analyzePatterns } from '@aiready/pattern-detect';
|
|
2
|
-
import { analyzeContext } from '@aiready/context-analyzer';
|
|
3
|
-
import { analyzeConsistency } from '@aiready/consistency';
|
|
4
|
-
import type { AnalysisResult, ScanOptions, SpokeOutput } from '@aiready/core';
|
|
5
1
|
import {
|
|
2
|
+
ToolRegistry,
|
|
3
|
+
ToolName,
|
|
6
4
|
calculateOverallScore,
|
|
7
|
-
type ToolScoringOutput,
|
|
8
|
-
type ScoringResult,
|
|
9
5
|
calculateTokenBudget,
|
|
10
6
|
} from '@aiready/core';
|
|
7
|
+
import type {
|
|
8
|
+
AnalysisResult,
|
|
9
|
+
ScanOptions,
|
|
10
|
+
SpokeOutput,
|
|
11
|
+
ToolScoringOutput,
|
|
12
|
+
ScoringResult,
|
|
13
|
+
} from '@aiready/core';
|
|
14
|
+
|
|
11
15
|
export type { ToolScoringOutput, ScoringResult };
|
|
12
16
|
|
|
13
17
|
export interface UnifiedAnalysisOptions extends ScanOptions {
|
|
14
|
-
tools?:
|
|
15
|
-
| 'patterns'
|
|
16
|
-
| 'context'
|
|
17
|
-
| 'consistency'
|
|
18
|
-
| 'doc-drift'
|
|
19
|
-
| 'deps-health'
|
|
20
|
-
| 'ai-signal-clarity'
|
|
21
|
-
| 'agent-grounding'
|
|
22
|
-
| 'testability'
|
|
23
|
-
| 'change-amplification'
|
|
24
|
-
)[];
|
|
18
|
+
tools?: string[];
|
|
25
19
|
minSimilarity?: number;
|
|
26
20
|
minLines?: number;
|
|
27
21
|
maxCandidatesPerBlock?: number;
|
|
@@ -32,16 +26,8 @@ export interface UnifiedAnalysisOptions extends ScanOptions {
|
|
|
32
26
|
}
|
|
33
27
|
|
|
34
28
|
export interface UnifiedAnalysisResult {
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
contextAnalyzer?: SpokeOutput;
|
|
38
|
-
consistency?: SpokeOutput;
|
|
39
|
-
docDrift?: SpokeOutput;
|
|
40
|
-
dependencyHealth?: SpokeOutput;
|
|
41
|
-
aiSignalClarity?: any;
|
|
42
|
-
agentGrounding?: any;
|
|
43
|
-
testability?: any;
|
|
44
|
-
changeAmplification?: SpokeOutput;
|
|
29
|
+
// Dynamic keys based on ToolName
|
|
30
|
+
[key: string]: any;
|
|
45
31
|
|
|
46
32
|
summary: {
|
|
47
33
|
totalIssues: number;
|
|
@@ -51,424 +37,178 @@ export interface UnifiedAnalysisResult {
|
|
|
51
37
|
scoring?: ScoringResult;
|
|
52
38
|
}
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Mapping between ToolName and @aiready/ package names.
|
|
42
|
+
* Used for dynamic registration on-demand.
|
|
43
|
+
*/
|
|
44
|
+
const TOOL_PACKAGE_MAP: Record<string, string> = {
|
|
45
|
+
[ToolName.PatternDetect]: '@aiready/pattern-detect',
|
|
46
|
+
[ToolName.ContextAnalyzer]: '@aiready/context-analyzer',
|
|
47
|
+
[ToolName.NamingConsistency]: '@aiready/consistency',
|
|
48
|
+
[ToolName.AiSignalClarity]: '@aiready/ai-signal-clarity',
|
|
49
|
+
[ToolName.AgentGrounding]: '@aiready/agent-grounding',
|
|
50
|
+
[ToolName.TestabilityIndex]: '@aiready/testability',
|
|
51
|
+
[ToolName.DocDrift]: '@aiready/doc-drift',
|
|
52
|
+
[ToolName.DependencyHealth]: '@aiready/deps',
|
|
53
|
+
[ToolName.ChangeAmplification]: '@aiready/change-amplification',
|
|
54
|
+
// Aliases handled by registry
|
|
55
|
+
patterns: '@aiready/pattern-detect',
|
|
56
|
+
context: '@aiready/context-analyzer',
|
|
57
|
+
consistency: '@aiready/consistency',
|
|
60
58
|
};
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const sortedIssues = [...file.issues].sort((a, b) => {
|
|
67
|
-
const severityDiff =
|
|
68
|
-
(severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
|
|
69
|
-
if (severityDiff !== 0) return severityDiff;
|
|
70
|
-
// If same severity, sort by line number
|
|
71
|
-
return (a.location?.line || 0) - (b.location?.line || 0);
|
|
72
|
-
});
|
|
73
|
-
return { ...file, issues: sortedIssues };
|
|
74
|
-
})
|
|
75
|
-
.sort((a, b) => {
|
|
76
|
-
// Sort files by most severe issue first
|
|
77
|
-
const aMaxSeverity = Math.max(
|
|
78
|
-
...a.issues.map((i) => severityOrder[i.severity] || 0),
|
|
79
|
-
0
|
|
80
|
-
);
|
|
81
|
-
const bMaxSeverity = Math.max(
|
|
82
|
-
...b.issues.map((i) => severityOrder[i.severity] || 0),
|
|
83
|
-
0
|
|
84
|
-
);
|
|
85
|
-
if (aMaxSeverity !== bMaxSeverity) {
|
|
86
|
-
return bMaxSeverity - aMaxSeverity;
|
|
87
|
-
}
|
|
88
|
-
// If same max severity, sort by number of issues
|
|
89
|
-
if (a.issues.length !== b.issues.length) {
|
|
90
|
-
return b.issues.length - a.issues.length;
|
|
91
|
-
}
|
|
92
|
-
// Finally, sort alphabetically by filename
|
|
93
|
-
return a.fileName.localeCompare(b.fileName);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
60
|
+
/**
|
|
61
|
+
* AIReady Unified Analysis
|
|
62
|
+
* Orchestrates all registered tools via the ToolRegistry.
|
|
63
|
+
*/
|
|
97
64
|
export async function analyzeUnified(
|
|
98
65
|
options: UnifiedAnalysisOptions
|
|
99
66
|
): Promise<UnifiedAnalysisResult> {
|
|
100
67
|
const startTime = Date.now();
|
|
101
|
-
const
|
|
102
|
-
|
|
68
|
+
const requestedTools = options.tools || [
|
|
69
|
+
'patterns',
|
|
70
|
+
'context',
|
|
71
|
+
'consistency',
|
|
72
|
+
];
|
|
73
|
+
|
|
103
74
|
const result: UnifiedAnalysisResult = {
|
|
104
75
|
summary: {
|
|
105
76
|
totalIssues: 0,
|
|
106
|
-
toolsRun:
|
|
77
|
+
toolsRun: [],
|
|
107
78
|
executionTime: 0,
|
|
108
79
|
},
|
|
109
80
|
};
|
|
110
81
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
82
|
+
for (const toolName of requestedTools) {
|
|
83
|
+
let provider = ToolRegistry.find(toolName);
|
|
84
|
+
|
|
85
|
+
// Dynamic Loading: If provider not found, attempt to import the package
|
|
86
|
+
if (!provider) {
|
|
87
|
+
const packageName =
|
|
88
|
+
TOOL_PACKAGE_MAP[toolName] ||
|
|
89
|
+
(toolName.startsWith('@aiready/') ? toolName : `@aiready/${toolName}`);
|
|
90
|
+
try {
|
|
91
|
+
await import(packageName);
|
|
92
|
+
provider = ToolRegistry.find(toolName);
|
|
93
|
+
if (provider) {
|
|
94
|
+
console.log(
|
|
95
|
+
`โ
Successfully loaded tool provider: ${toolName} from ${packageName}`
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
console.log(
|
|
99
|
+
`โ ๏ธ Loaded ${packageName} but provider ${toolName} still not found in registry.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
} catch (err: any) {
|
|
103
|
+
console.log(
|
|
104
|
+
`โ Failed to dynamically load tool ${toolName} (${packageName}):`,
|
|
105
|
+
err.message
|
|
106
|
+
);
|
|
107
|
+
}
|
|
116
108
|
}
|
|
117
|
-
result.patternDetect = {
|
|
118
|
-
results: sortBySeverity(patternResult.results),
|
|
119
|
-
summary: patternResult.summary || {},
|
|
120
|
-
duplicates: patternResult.duplicates || [],
|
|
121
|
-
};
|
|
122
|
-
result.summary.totalIssues += patternResult.results.reduce(
|
|
123
|
-
(sum, file) => sum + file.issues.length,
|
|
124
|
-
0
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
109
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
110
|
+
if (!provider) {
|
|
111
|
+
console.warn(
|
|
112
|
+
`โ ๏ธ Warning: Tool provider for '${toolName}' not found. Skipping.`
|
|
113
|
+
);
|
|
114
|
+
continue;
|
|
133
115
|
}
|
|
134
|
-
const sorted = contextResults.sort((a, b) => {
|
|
135
|
-
const severityDiff =
|
|
136
|
-
(severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
|
|
137
|
-
if (severityDiff !== 0) return severityDiff;
|
|
138
|
-
if (a.tokenCost !== b.tokenCost) return b.tokenCost - a.tokenCost;
|
|
139
|
-
return b.fragmentationScore - a.fragmentationScore;
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const { generateSummary: genContextSummary } =
|
|
143
|
-
await import('@aiready/context-analyzer');
|
|
144
|
-
result.contextAnalyzer = {
|
|
145
|
-
results: sorted,
|
|
146
|
-
summary: genContextSummary(sorted),
|
|
147
|
-
};
|
|
148
|
-
result.summary.totalIssues += sorted.length;
|
|
149
|
-
}
|
|
150
116
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const consistencyOptions = {
|
|
154
|
-
rootDir: options.rootDir,
|
|
155
|
-
include: options.include,
|
|
156
|
-
exclude: options.exclude,
|
|
157
|
-
...(options.consistency || {}),
|
|
158
|
-
};
|
|
159
|
-
const report = await analyzeConsistency(consistencyOptions);
|
|
160
|
-
if (options.progressCallback) {
|
|
161
|
-
options.progressCallback({ tool: 'consistency', data: report });
|
|
162
|
-
}
|
|
163
|
-
result.consistency = {
|
|
164
|
-
results: report.results ? sortBySeverity(report.results) : [],
|
|
165
|
-
summary: report.summary,
|
|
166
|
-
};
|
|
167
|
-
result.summary.totalIssues += report.summary.totalIssues;
|
|
168
|
-
}
|
|
117
|
+
try {
|
|
118
|
+
const output = await provider.analyze(options);
|
|
169
119
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const report = await analyzeDocDrift({
|
|
174
|
-
rootDir: options.rootDir,
|
|
175
|
-
include: options.include,
|
|
176
|
-
exclude: options.exclude,
|
|
177
|
-
onProgress: options.onProgress,
|
|
178
|
-
});
|
|
179
|
-
if (options.progressCallback) {
|
|
180
|
-
options.progressCallback({ tool: 'doc-drift', data: report });
|
|
181
|
-
}
|
|
182
|
-
result.docDrift = {
|
|
183
|
-
results: (report as any).results || (report as any).issues || [],
|
|
184
|
-
summary: report.summary || {},
|
|
185
|
-
};
|
|
186
|
-
const issueCount =
|
|
187
|
-
(report as any).issues?.length ||
|
|
188
|
-
((report as any).results ? (report as any).results.length : 0);
|
|
189
|
-
result.summary.totalIssues += issueCount;
|
|
190
|
-
}
|
|
120
|
+
if (options.progressCallback) {
|
|
121
|
+
options.progressCallback({ tool: provider.id, data: output });
|
|
122
|
+
}
|
|
191
123
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const { analyzeDeps } = await import('@aiready/deps');
|
|
195
|
-
const report = await analyzeDeps({
|
|
196
|
-
rootDir: options.rootDir,
|
|
197
|
-
include: options.include,
|
|
198
|
-
exclude: options.exclude,
|
|
199
|
-
onProgress: options.onProgress,
|
|
200
|
-
});
|
|
201
|
-
if (options.progressCallback) {
|
|
202
|
-
options.progressCallback({ tool: 'deps-health', data: report });
|
|
203
|
-
}
|
|
204
|
-
result.dependencyHealth = {
|
|
205
|
-
results: (report as any).results || (report as any).issues || [],
|
|
206
|
-
summary: report.summary || {},
|
|
207
|
-
};
|
|
208
|
-
const issueCount =
|
|
209
|
-
(report as any).issues?.length ||
|
|
210
|
-
((report as any).results ? (report as any).results.length : 0);
|
|
211
|
-
result.summary.totalIssues += issueCount;
|
|
212
|
-
}
|
|
124
|
+
result[provider.id] = output;
|
|
125
|
+
result.summary.toolsRun.push(provider.id);
|
|
213
126
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const { analyzeAiSignalClarity } =
|
|
217
|
-
await import('@aiready/ai-signal-clarity');
|
|
218
|
-
const report = await analyzeAiSignalClarity({
|
|
219
|
-
rootDir: options.rootDir,
|
|
220
|
-
include: options.include,
|
|
221
|
-
exclude: options.exclude,
|
|
222
|
-
onProgress: options.onProgress,
|
|
223
|
-
});
|
|
224
|
-
if (options.progressCallback) {
|
|
225
|
-
options.progressCallback({ tool: 'ai-signal-clarity', data: report });
|
|
226
|
-
}
|
|
227
|
-
result.aiSignalClarity = {
|
|
228
|
-
...report,
|
|
229
|
-
results: report.results || report.issues || [],
|
|
230
|
-
summary: report.summary || {},
|
|
231
|
-
};
|
|
232
|
-
result.summary.totalIssues +=
|
|
233
|
-
(report.results || report.issues)?.reduce(
|
|
234
|
-
(sum: number, r: any) => sum + (r.issues?.length || 1),
|
|
127
|
+
const issueCount = output.results.reduce(
|
|
128
|
+
(sum: number, file: any) => sum + (file.issues?.length || 0),
|
|
235
129
|
0
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (options.progressCallback) {
|
|
249
|
-
options.progressCallback({ tool: 'agent-grounding', data: report });
|
|
250
|
-
}
|
|
251
|
-
result.agentGrounding = {
|
|
252
|
-
...(report as any),
|
|
253
|
-
results: (report as any).results || (report as any).issues || [],
|
|
254
|
-
summary: report.summary || {},
|
|
255
|
-
};
|
|
256
|
-
result.summary.totalIssues += (
|
|
257
|
-
(report as any).issues ||
|
|
258
|
-
(report as any).results ||
|
|
259
|
-
[]
|
|
260
|
-
).length;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Run Testability analysis
|
|
264
|
-
if (tools.includes('testability')) {
|
|
265
|
-
const { analyzeTestability } = await import('@aiready/testability');
|
|
266
|
-
const report = await analyzeTestability({
|
|
267
|
-
rootDir: options.rootDir,
|
|
268
|
-
include: options.include,
|
|
269
|
-
exclude: options.exclude,
|
|
270
|
-
onProgress: options.onProgress,
|
|
271
|
-
});
|
|
272
|
-
if (options.progressCallback) {
|
|
273
|
-
options.progressCallback({ tool: 'testability', data: report });
|
|
274
|
-
}
|
|
275
|
-
result.testability = {
|
|
276
|
-
...(report as any),
|
|
277
|
-
results: (report as any).results || (report as any).issues || [],
|
|
278
|
-
summary: report.summary || {},
|
|
279
|
-
};
|
|
280
|
-
result.summary.totalIssues += (
|
|
281
|
-
(report as any).issues ||
|
|
282
|
-
(report as any).results ||
|
|
283
|
-
[]
|
|
284
|
-
).length;
|
|
285
|
-
}
|
|
130
|
+
);
|
|
131
|
+
result.summary.totalIssues += issueCount;
|
|
132
|
+
|
|
133
|
+
// Robust backward compatibility fallbacks
|
|
134
|
+
// 1. Add all aliases as keys (e.g., 'patterns', 'context', 'consistency')
|
|
135
|
+
if (provider.alias && Array.isArray(provider.alias)) {
|
|
136
|
+
for (const alias of provider.alias) {
|
|
137
|
+
if (!result[alias]) {
|
|
138
|
+
(result as any)[alias] = output;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
286
142
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
});
|
|
297
|
-
if (options.progressCallback) {
|
|
298
|
-
options.progressCallback({ tool: 'change-amplification', data: report });
|
|
143
|
+
// 2. Add camelCase version of canonical ID (e.g., 'patternDetect', 'contextAnalyzer')
|
|
144
|
+
const camelCaseId = provider.id.replace(/-([a-z])/g, (g) =>
|
|
145
|
+
g[1].toUpperCase()
|
|
146
|
+
);
|
|
147
|
+
if (camelCaseId !== provider.id && !result[camelCaseId]) {
|
|
148
|
+
(result as any)[camelCaseId] = output;
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error(`โ Error running tool '${provider.id}':`, err);
|
|
299
152
|
}
|
|
300
|
-
result.changeAmplification = {
|
|
301
|
-
results: report.results || [],
|
|
302
|
-
summary: report.summary || {},
|
|
303
|
-
};
|
|
304
|
-
result.summary.totalIssues += report.summary?.totalIssues || 0;
|
|
305
153
|
}
|
|
306
154
|
|
|
307
155
|
result.summary.executionTime = Date.now() - startTime;
|
|
308
156
|
return result;
|
|
309
157
|
}
|
|
310
158
|
|
|
159
|
+
/**
|
|
160
|
+
* AIReady Unified Scoring
|
|
161
|
+
* Calculates scores for all analyzed tools.
|
|
162
|
+
*/
|
|
311
163
|
export async function scoreUnified(
|
|
312
164
|
results: UnifiedAnalysisResult,
|
|
313
165
|
options: UnifiedAnalysisOptions
|
|
314
166
|
): Promise<ScoringResult> {
|
|
315
167
|
const toolScores: Map<string, ToolScoringOutput> = new Map();
|
|
316
168
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
const patternScore = calculatePatternScore(
|
|
322
|
-
results.patternDetect.duplicates,
|
|
323
|
-
results.patternDetect.results?.length || 0
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
// Calculate token budget for patterns (waste = duplication)
|
|
327
|
-
const wastedTokens = results.patternDetect.duplicates.reduce(
|
|
328
|
-
(sum: number, d: any) => sum + (d.tokenCost || 0),
|
|
329
|
-
0
|
|
330
|
-
);
|
|
331
|
-
patternScore.tokenBudget = calculateTokenBudget({
|
|
332
|
-
totalContextTokens: wastedTokens * 2, // Estimated context
|
|
333
|
-
wastedTokens: {
|
|
334
|
-
duplication: wastedTokens,
|
|
335
|
-
fragmentation: 0,
|
|
336
|
-
chattiness: 0,
|
|
337
|
-
},
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
toolScores.set('pattern-detect', patternScore);
|
|
341
|
-
} catch (err) {
|
|
342
|
-
void err;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Context score
|
|
347
|
-
if (results.contextAnalyzer) {
|
|
348
|
-
const { calculateContextScore } = await import('@aiready/context-analyzer');
|
|
349
|
-
try {
|
|
350
|
-
const ctxSummary = results.contextAnalyzer.summary;
|
|
351
|
-
const contextScore = calculateContextScore(ctxSummary);
|
|
352
|
-
|
|
353
|
-
// Calculate token budget for context (waste = fragmentation + depth overhead)
|
|
354
|
-
contextScore.tokenBudget = calculateTokenBudget({
|
|
355
|
-
totalContextTokens: ctxSummary.totalTokens,
|
|
356
|
-
wastedTokens: {
|
|
357
|
-
duplication: 0,
|
|
358
|
-
fragmentation: ctxSummary.totalPotentialSavings || 0,
|
|
359
|
-
chattiness: 0,
|
|
360
|
-
},
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
toolScores.set('context-analyzer', contextScore);
|
|
364
|
-
} catch (err) {
|
|
365
|
-
void err;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
169
|
+
for (const toolId of results.summary.toolsRun) {
|
|
170
|
+
const provider = ToolRegistry.get(toolId as ToolName);
|
|
171
|
+
if (!provider) continue;
|
|
368
172
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const { calculateConsistencyScore } = await import('@aiready/consistency');
|
|
372
|
-
try {
|
|
373
|
-
const issues =
|
|
374
|
-
results.consistency.results?.flatMap((r: any) => r.issues) || [];
|
|
375
|
-
const totalFiles = results.consistency.summary?.filesAnalyzed || 0;
|
|
376
|
-
const consistencyScore = calculateConsistencyScore(issues, totalFiles);
|
|
377
|
-
toolScores.set('consistency', consistencyScore);
|
|
378
|
-
} catch (err) {
|
|
379
|
-
void err;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
173
|
+
const output = results[toolId];
|
|
174
|
+
if (!output) continue;
|
|
382
175
|
|
|
383
|
-
// AI signal clarity score
|
|
384
|
-
if (results.aiSignalClarity) {
|
|
385
|
-
const { calculateAiSignalClarityScore } =
|
|
386
|
-
await import('@aiready/ai-signal-clarity');
|
|
387
176
|
try {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
177
|
+
const toolScore = provider.score(output, options);
|
|
178
|
+
|
|
179
|
+
// Special handling for token budget calculation if not provided by tool
|
|
180
|
+
if (!toolScore.tokenBudget) {
|
|
181
|
+
if (toolId === ToolName.PatternDetect && (output as any).duplicates) {
|
|
182
|
+
const wastedTokens = (output as any).duplicates.reduce(
|
|
183
|
+
(sum: number, d: any) => sum + (d.tokenCost || 0),
|
|
184
|
+
0
|
|
185
|
+
);
|
|
186
|
+
toolScore.tokenBudget = calculateTokenBudget({
|
|
187
|
+
totalContextTokens: wastedTokens * 2,
|
|
188
|
+
wastedTokens: {
|
|
189
|
+
duplication: wastedTokens,
|
|
190
|
+
fragmentation: 0,
|
|
191
|
+
chattiness: 0,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
} else if (toolId === ToolName.ContextAnalyzer && output.summary) {
|
|
195
|
+
toolScore.tokenBudget = calculateTokenBudget({
|
|
196
|
+
totalContextTokens: output.summary.totalTokens,
|
|
197
|
+
wastedTokens: {
|
|
198
|
+
duplication: 0,
|
|
199
|
+
fragmentation: output.summary.totalPotentialSavings || 0,
|
|
200
|
+
chattiness: 0,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
406
205
|
|
|
407
|
-
|
|
408
|
-
if (results.testability) {
|
|
409
|
-
const { calculateTestabilityScore } = await import('@aiready/testability');
|
|
410
|
-
try {
|
|
411
|
-
const tbScore = calculateTestabilityScore(results.testability);
|
|
412
|
-
toolScores.set('testability', tbScore);
|
|
206
|
+
toolScores.set(toolId, toolScore);
|
|
413
207
|
} catch (err) {
|
|
414
|
-
|
|
208
|
+
console.error(`โ Error scoring tool '${toolId}':`, err);
|
|
415
209
|
}
|
|
416
210
|
}
|
|
417
211
|
|
|
418
|
-
// Documentation Drift score
|
|
419
|
-
if (results.docDrift) {
|
|
420
|
-
toolScores.set('doc-drift', {
|
|
421
|
-
toolName: 'doc-drift',
|
|
422
|
-
score:
|
|
423
|
-
results.docDrift.summary.score ||
|
|
424
|
-
results.docDrift.summary.totalScore ||
|
|
425
|
-
0,
|
|
426
|
-
rawMetrics: results.docDrift.summary,
|
|
427
|
-
factors: [],
|
|
428
|
-
recommendations: (results.docDrift.summary.recommendations || []).map(
|
|
429
|
-
(action: string) => ({
|
|
430
|
-
action,
|
|
431
|
-
estimatedImpact: 5,
|
|
432
|
-
priority: 'medium',
|
|
433
|
-
})
|
|
434
|
-
),
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Dependency Health score
|
|
439
|
-
if (results.dependencyHealth) {
|
|
440
|
-
toolScores.set('dependency-health', {
|
|
441
|
-
toolName: 'dependency-health',
|
|
442
|
-
score: results.dependencyHealth.summary.score || 0,
|
|
443
|
-
rawMetrics: results.dependencyHealth.summary,
|
|
444
|
-
factors: [],
|
|
445
|
-
recommendations: (
|
|
446
|
-
results.dependencyHealth.summary.recommendations || []
|
|
447
|
-
).map((action: string) => ({
|
|
448
|
-
action,
|
|
449
|
-
estimatedImpact: 5,
|
|
450
|
-
priority: 'medium',
|
|
451
|
-
})),
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Change Amplification score
|
|
456
|
-
if (results.changeAmplification) {
|
|
457
|
-
toolScores.set('change-amplification', {
|
|
458
|
-
toolName: 'change-amplification',
|
|
459
|
-
score: results.changeAmplification.summary.score || 0,
|
|
460
|
-
rawMetrics: results.changeAmplification.summary,
|
|
461
|
-
factors: [],
|
|
462
|
-
recommendations: (
|
|
463
|
-
results.changeAmplification.summary.recommendations || []
|
|
464
|
-
).map((action: string) => ({
|
|
465
|
-
action,
|
|
466
|
-
estimatedImpact: 5,
|
|
467
|
-
priority: 'medium',
|
|
468
|
-
})),
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
|
|
472
212
|
// Handle case where toolScores is empty
|
|
473
213
|
if (toolScores.size === 0) {
|
|
474
214
|
return {
|
|
@@ -488,6 +228,9 @@ export async function scoreUnified(
|
|
|
488
228
|
return calculateOverallScore(toolScores, options, undefined);
|
|
489
229
|
}
|
|
490
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Generate human-readable summary of unified results
|
|
233
|
+
*/
|
|
491
234
|
export function generateUnifiedSummary(result: UnifiedAnalysisResult): string {
|
|
492
235
|
const { summary } = result;
|
|
493
236
|
let output = `๐ AIReady Analysis Complete\n\n`;
|
|
@@ -496,40 +239,15 @@ export function generateUnifiedSummary(result: UnifiedAnalysisResult): string {
|
|
|
496
239
|
output += ` Total issues found: ${summary.totalIssues}\n`;
|
|
497
240
|
output += ` Execution time: ${(summary.executionTime / 1000).toFixed(2)}s\n\n`;
|
|
498
241
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
output += `๐ท๏ธ Consistency Analysis: ${result.consistency.summary.totalIssues} issues\n`;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (result.docDrift) {
|
|
512
|
-
output += `๐ Doc Drift Analysis: ${result.docDrift.results?.length || 0} issues\n`;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (result.dependencyHealth) {
|
|
516
|
-
output += `๐ฆ Dependency Health: ${result.dependencyHealth.results?.length || 0} issues\n`;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (result.aiSignalClarity) {
|
|
520
|
-
output += `๐ง AI Signal Clarity: ${result.aiSignalClarity.summary?.totalSignals || 0} signals\n`;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (result.agentGrounding) {
|
|
524
|
-
output += `๐งญ Agent Grounding: ${result.agentGrounding.results?.length || 0} issues\n`;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
if (result.testability) {
|
|
528
|
-
output += `๐งช Testability Index: ${result.testability.results?.length || 0} issues\n`;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (result.changeAmplification) {
|
|
532
|
-
output += `๐ฅ Change Amplification: ${result.changeAmplification.summary?.totalIssues || 0} cascading risks\n`;
|
|
242
|
+
for (const provider of ToolRegistry.getAll()) {
|
|
243
|
+
const toolResult = result[provider.id];
|
|
244
|
+
if (toolResult) {
|
|
245
|
+
const issueCount = toolResult.results.reduce(
|
|
246
|
+
(sum: number, r: any) => sum + (r.issues?.length || 0),
|
|
247
|
+
0
|
|
248
|
+
);
|
|
249
|
+
output += `โข ${provider.id}: ${issueCount} issues\n`;
|
|
250
|
+
}
|
|
533
251
|
}
|
|
534
252
|
|
|
535
253
|
return output;
|