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