@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/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
- // Standardized keys matching tool names
36
- patternDetect?: SpokeOutput & { duplicates: any[] };
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
- // Severity ordering (higher number = more severe)
55
- const severityOrder: Record<string, number> = {
56
- critical: 4,
57
- major: 3,
58
- minor: 2,
59
- info: 1,
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
- function sortBySeverity(results: AnalysisResult[]): AnalysisResult[] {
63
- return results
64
- .map((file) => {
65
- // Sort issues within each file by severity (most severe first)
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 tools = options.tools || ['patterns', 'context', 'consistency'];
102
- // Tools requested and effective options are used from `options`
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: tools,
77
+ toolsRun: [],
107
78
  executionTime: 0,
108
79
  },
109
80
  };
110
81
 
111
- // Run pattern detection
112
- if (tools.includes('patterns')) {
113
- const patternResult = await analyzePatterns(options);
114
- if (options.progressCallback) {
115
- options.progressCallback({ tool: 'patterns', data: patternResult });
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
- // Run context analysis
129
- if (tools.includes('context')) {
130
- const contextResults = await analyzeContext(options);
131
- if (options.progressCallback) {
132
- options.progressCallback({ tool: 'context', data: contextResults });
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
- // Run consistency analysis
152
- if (tools.includes('consistency')) {
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
- // Run Documentation Drift analysis
171
- if (tools.includes('doc-drift')) {
172
- const { analyzeDocDrift } = await import('@aiready/doc-drift');
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
- // Run Dependency Health analysis
193
- if (tools.includes('deps-health')) {
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
- // Run AI Signal Clarity analysis
215
- if (tools.includes('ai-signal-clarity')) {
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
- ) || 0;
237
- }
238
-
239
- // Run Agent Grounding analysis
240
- if (tools.includes('agent-grounding')) {
241
- const { analyzeAgentGrounding } = await import('@aiready/agent-grounding');
242
- const report = await analyzeAgentGrounding({
243
- rootDir: options.rootDir,
244
- include: options.include,
245
- exclude: options.exclude,
246
- onProgress: options.onProgress,
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
- // Run Change Amplification analysis
288
- if (tools.includes('change-amplification')) {
289
- const { analyzeChangeAmplification } =
290
- await import('@aiready/change-amplification');
291
- const report = await analyzeChangeAmplification({
292
- rootDir: options.rootDir,
293
- include: options.include,
294
- exclude: options.exclude,
295
- onProgress: options.onProgress,
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
- // Patterns score
318
- if (results.patternDetect) {
319
- const { calculatePatternScore } = await import('@aiready/pattern-detect');
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
- // Consistency score
370
- if (results.consistency) {
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 hrScore = calculateAiSignalClarityScore(results.aiSignalClarity);
389
- toolScores.set('ai-signal-clarity', hrScore);
390
- } catch (err) {
391
- void err;
392
- }
393
- }
394
-
395
- // Agent grounding score
396
- if (results.agentGrounding) {
397
- const { calculateGroundingScore } =
398
- await import('@aiready/agent-grounding');
399
- try {
400
- const agScore = calculateGroundingScore(results.agentGrounding);
401
- toolScores.set('agent-grounding', agScore);
402
- } catch (err) {
403
- void err;
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
- // Testability score
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
- void err;
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
- if (result.patternDetect) {
500
- output += `๐Ÿ” Pattern Analysis: ${result.patternDetect.results.length} issues\n`;
501
- }
502
-
503
- if (result.contextAnalyzer) {
504
- output += `๐Ÿง  Context Analysis: ${result.contextAnalyzer.results.length} issues\n`;
505
- }
506
-
507
- if (result.consistency) {
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;