@aiready/core 0.19.5 → 0.21.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.
@@ -0,0 +1,582 @@
1
+ // src/types/schema.ts
2
+ import { z } from "zod";
3
+ var Severity = /* @__PURE__ */ ((Severity2) => {
4
+ Severity2["Critical"] = "critical";
5
+ Severity2["Major"] = "major";
6
+ Severity2["Minor"] = "minor";
7
+ Severity2["Info"] = "info";
8
+ return Severity2;
9
+ })(Severity || {});
10
+ var SeveritySchema = z.nativeEnum(Severity);
11
+ var ToolName = /* @__PURE__ */ ((ToolName2) => {
12
+ ToolName2["PatternDetect"] = "pattern-detect";
13
+ ToolName2["ContextAnalyzer"] = "context-analyzer";
14
+ ToolName2["NamingConsistency"] = "naming-consistency";
15
+ ToolName2["AiSignalClarity"] = "ai-signal-clarity";
16
+ ToolName2["AgentGrounding"] = "agent-grounding";
17
+ ToolName2["TestabilityIndex"] = "testability-index";
18
+ ToolName2["DocDrift"] = "doc-drift";
19
+ ToolName2["DependencyHealth"] = "dependency-health";
20
+ ToolName2["ChangeAmplification"] = "change-amplification";
21
+ ToolName2["CognitiveLoad"] = "cognitive-load";
22
+ ToolName2["PatternEntropy"] = "pattern-entropy";
23
+ ToolName2["ConceptCohesion"] = "concept-cohesion";
24
+ ToolName2["SemanticDistance"] = "semantic-distance";
25
+ return ToolName2;
26
+ })(ToolName || {});
27
+ var ToolNameSchema = z.nativeEnum(ToolName);
28
+ var FRIENDLY_TOOL_NAMES = {
29
+ ["pattern-detect" /* PatternDetect */]: "Semantic Duplicates",
30
+ ["context-analyzer" /* ContextAnalyzer */]: "Context Fragmentation",
31
+ ["naming-consistency" /* NamingConsistency */]: "Naming Consistency",
32
+ ["ai-signal-clarity" /* AiSignalClarity */]: "AI Signal Clarity",
33
+ ["agent-grounding" /* AgentGrounding */]: "Agent Grounding",
34
+ ["testability-index" /* TestabilityIndex */]: "Testability Index",
35
+ ["doc-drift" /* DocDrift */]: "Documentation Health",
36
+ ["dependency-health" /* DependencyHealth */]: "Dependency Health",
37
+ ["change-amplification" /* ChangeAmplification */]: "Change Amplification",
38
+ ["cognitive-load" /* CognitiveLoad */]: "Cognitive Load",
39
+ ["pattern-entropy" /* PatternEntropy */]: "Pattern Entropy",
40
+ ["concept-cohesion" /* ConceptCohesion */]: "Concept Cohesion",
41
+ ["semantic-distance" /* SemanticDistance */]: "Semantic Distance"
42
+ };
43
+ var IssueType = /* @__PURE__ */ ((IssueType2) => {
44
+ IssueType2["DuplicatePattern"] = "duplicate-pattern";
45
+ IssueType2["PatternInconsistency"] = "pattern-inconsistency";
46
+ IssueType2["ContextFragmentation"] = "context-fragmentation";
47
+ IssueType2["DependencyHealth"] = "dependency-health";
48
+ IssueType2["CircularDependency"] = "circular-dependency";
49
+ IssueType2["DocDrift"] = "doc-drift";
50
+ IssueType2["NamingInconsistency"] = "naming-inconsistency";
51
+ IssueType2["NamingQuality"] = "naming-quality";
52
+ IssueType2["ArchitectureInconsistency"] = "architecture-inconsistency";
53
+ IssueType2["DeadCode"] = "dead-code";
54
+ IssueType2["MissingTypes"] = "missing-types";
55
+ IssueType2["MagicLiteral"] = "magic-literal";
56
+ IssueType2["BooleanTrap"] = "boolean-trap";
57
+ IssueType2["AiSignalClarity"] = "ai-signal-clarity";
58
+ IssueType2["LowTestability"] = "low-testability";
59
+ IssueType2["AgentNavigationFailure"] = "agent-navigation-failure";
60
+ IssueType2["AmbiguousApi"] = "ambiguous-api";
61
+ IssueType2["ChangeAmplification"] = "change-amplification";
62
+ return IssueType2;
63
+ })(IssueType || {});
64
+ var IssueTypeSchema = z.nativeEnum(IssueType);
65
+ var AnalysisStatus = /* @__PURE__ */ ((AnalysisStatus2) => {
66
+ AnalysisStatus2["Processing"] = "processing";
67
+ AnalysisStatus2["Completed"] = "completed";
68
+ AnalysisStatus2["Failed"] = "failed";
69
+ return AnalysisStatus2;
70
+ })(AnalysisStatus || {});
71
+ var AnalysisStatusSchema = z.nativeEnum(AnalysisStatus);
72
+ var ModelTier = /* @__PURE__ */ ((ModelTier2) => {
73
+ ModelTier2["Compact"] = "compact";
74
+ ModelTier2["Standard"] = "standard";
75
+ ModelTier2["Extended"] = "extended";
76
+ ModelTier2["Frontier"] = "frontier";
77
+ return ModelTier2;
78
+ })(ModelTier || {});
79
+ var ModelTierSchema = z.nativeEnum(ModelTier);
80
+ var LocationSchema = z.object({
81
+ file: z.string(),
82
+ line: z.number(),
83
+ column: z.number().optional(),
84
+ endLine: z.number().optional(),
85
+ endColumn: z.number().optional()
86
+ });
87
+ var IssueSchema = z.object({
88
+ type: IssueTypeSchema,
89
+ severity: SeveritySchema,
90
+ message: z.string(),
91
+ location: LocationSchema,
92
+ suggestion: z.string().optional()
93
+ });
94
+ var MetricsSchema = z.object({
95
+ tokenCost: z.number().optional(),
96
+ complexityScore: z.number().optional(),
97
+ consistencyScore: z.number().optional(),
98
+ docFreshnessScore: z.number().optional(),
99
+ // AI agent readiness metrics (v0.12+)
100
+ aiSignalClarityScore: z.number().optional(),
101
+ agentGroundingScore: z.number().optional(),
102
+ testabilityScore: z.number().optional(),
103
+ docDriftScore: z.number().optional(),
104
+ dependencyHealthScore: z.number().optional(),
105
+ modelContextTier: ModelTierSchema.optional(),
106
+ // Business value metrics
107
+ estimatedMonthlyCost: z.number().optional(),
108
+ estimatedDeveloperHours: z.number().optional(),
109
+ comprehensionDifficultyIndex: z.number().optional(),
110
+ // Extended metrics for specific spokes
111
+ totalSymbols: z.number().optional(),
112
+ totalExports: z.number().optional()
113
+ });
114
+ var AnalysisResultSchema = z.object({
115
+ fileName: z.string(),
116
+ issues: z.array(IssueSchema),
117
+ metrics: MetricsSchema
118
+ });
119
+ var SpokeSummarySchema = z.object({
120
+ totalFiles: z.number().optional(),
121
+ totalIssues: z.number().optional(),
122
+ criticalIssues: z.number().optional(),
123
+ majorIssues: z.number().optional(),
124
+ score: z.number().optional()
125
+ }).catchall(z.any());
126
+ var SpokeOutputSchema = z.object({
127
+ results: z.array(AnalysisResultSchema),
128
+ summary: SpokeSummarySchema,
129
+ metadata: z.object({
130
+ toolName: z.string(),
131
+ version: z.string().optional(),
132
+ timestamp: z.string().optional()
133
+ }).catchall(z.any()).optional()
134
+ });
135
+ var UnifiedReportSchema = z.object({
136
+ summary: z.object({
137
+ totalFiles: z.number(),
138
+ totalIssues: z.number(),
139
+ criticalIssues: z.number(),
140
+ majorIssues: z.number()
141
+ }),
142
+ results: z.array(AnalysisResultSchema),
143
+ scoring: z.object({
144
+ overall: z.number(),
145
+ rating: z.string(),
146
+ timestamp: z.string(),
147
+ breakdown: z.array(
148
+ z.object({
149
+ toolName: z.union([ToolNameSchema, z.string()]),
150
+ score: z.number()
151
+ }).catchall(z.any())
152
+ )
153
+ }).optional()
154
+ }).catchall(z.any());
155
+
156
+ // src/types/language.ts
157
+ var Language = /* @__PURE__ */ ((Language2) => {
158
+ Language2["TypeScript"] = "typescript";
159
+ Language2["JavaScript"] = "javascript";
160
+ Language2["Python"] = "python";
161
+ Language2["Java"] = "java";
162
+ Language2["Go"] = "go";
163
+ Language2["Rust"] = "rust";
164
+ Language2["CSharp"] = "csharp";
165
+ return Language2;
166
+ })(Language || {});
167
+ var LANGUAGE_EXTENSIONS = {
168
+ ".ts": "typescript" /* TypeScript */,
169
+ ".tsx": "typescript" /* TypeScript */,
170
+ ".js": "javascript" /* JavaScript */,
171
+ ".jsx": "javascript" /* JavaScript */,
172
+ ".py": "python" /* Python */,
173
+ ".java": "java" /* Java */,
174
+ ".go": "go" /* Go */,
175
+ ".rs": "rust" /* Rust */,
176
+ ".cs": "csharp" /* CSharp */
177
+ };
178
+ var ParseError = class extends Error {
179
+ constructor(message, filePath, loc) {
180
+ super(message);
181
+ this.filePath = filePath;
182
+ this.loc = loc;
183
+ this.name = "ParseError";
184
+ }
185
+ };
186
+
187
+ // src/scoring.ts
188
+ var DEFAULT_TOOL_WEIGHTS = {
189
+ ["pattern-detect" /* PatternDetect */]: 22,
190
+ ["context-analyzer" /* ContextAnalyzer */]: 19,
191
+ ["naming-consistency" /* NamingConsistency */]: 14,
192
+ ["ai-signal-clarity" /* AiSignalClarity */]: 11,
193
+ ["agent-grounding" /* AgentGrounding */]: 10,
194
+ ["testability-index" /* TestabilityIndex */]: 10,
195
+ ["doc-drift" /* DocDrift */]: 8,
196
+ ["dependency-health" /* DependencyHealth */]: 6,
197
+ ["change-amplification" /* ChangeAmplification */]: 8
198
+ };
199
+ var TOOL_NAME_MAP = {
200
+ patterns: "pattern-detect" /* PatternDetect */,
201
+ "pattern-detect": "pattern-detect" /* PatternDetect */,
202
+ context: "context-analyzer" /* ContextAnalyzer */,
203
+ "context-analyzer": "context-analyzer" /* ContextAnalyzer */,
204
+ consistency: "naming-consistency" /* NamingConsistency */,
205
+ "naming-consistency": "naming-consistency" /* NamingConsistency */,
206
+ "ai-signal": "ai-signal-clarity" /* AiSignalClarity */,
207
+ "ai-signal-clarity": "ai-signal-clarity" /* AiSignalClarity */,
208
+ grounding: "agent-grounding" /* AgentGrounding */,
209
+ "agent-grounding": "agent-grounding" /* AgentGrounding */,
210
+ testability: "testability-index" /* TestabilityIndex */,
211
+ "testability-index": "testability-index" /* TestabilityIndex */,
212
+ "doc-drift": "doc-drift" /* DocDrift */,
213
+ "deps-health": "dependency-health" /* DependencyHealth */,
214
+ "dependency-health": "dependency-health" /* DependencyHealth */,
215
+ "change-amp": "change-amplification" /* ChangeAmplification */,
216
+ "change-amplification": "change-amplification" /* ChangeAmplification */
217
+ };
218
+ var CONTEXT_TIER_THRESHOLDS = {
219
+ compact: { idealTokens: 3e3, criticalTokens: 1e4, idealDepth: 4 },
220
+ standard: { idealTokens: 5e3, criticalTokens: 15e3, idealDepth: 5 },
221
+ extended: { idealTokens: 15e3, criticalTokens: 5e4, idealDepth: 7 },
222
+ frontier: { idealTokens: 5e4, criticalTokens: 15e4, idealDepth: 10 }
223
+ };
224
+ var SIZE_ADJUSTED_THRESHOLDS = {
225
+ xs: 80,
226
+ // < 50 files
227
+ small: 75,
228
+ // 50-200 files
229
+ medium: 70,
230
+ // 200-500 files
231
+ large: 65,
232
+ // 500-2000 files
233
+ enterprise: 58
234
+ // 2000+ files
235
+ };
236
+ function getProjectSizeTier(fileCount) {
237
+ if (fileCount < 50) return "xs";
238
+ if (fileCount < 200) return "small";
239
+ if (fileCount < 500) return "medium";
240
+ if (fileCount < 2e3) return "large";
241
+ return "enterprise";
242
+ }
243
+ function getRecommendedThreshold(fileCount, modelTier = "standard") {
244
+ const sizeTier = getProjectSizeTier(fileCount);
245
+ const base = SIZE_ADJUSTED_THRESHOLDS[sizeTier];
246
+ const modelBonus = modelTier === "frontier" ? -3 : modelTier === "extended" ? -2 : 0;
247
+ return base + modelBonus;
248
+ }
249
+ function normalizeToolName(shortName) {
250
+ return TOOL_NAME_MAP[shortName.toLowerCase()] || shortName;
251
+ }
252
+ function getToolWeight(toolName, toolConfig, cliOverride) {
253
+ if (cliOverride !== void 0) return cliOverride;
254
+ if (toolConfig?.scoreWeight !== void 0) return toolConfig.scoreWeight;
255
+ return DEFAULT_TOOL_WEIGHTS[toolName] || 5;
256
+ }
257
+ function parseWeightString(weightStr) {
258
+ const weights = /* @__PURE__ */ new Map();
259
+ if (!weightStr) return weights;
260
+ const pairs = weightStr.split(",");
261
+ for (const pair of pairs) {
262
+ const [toolShortName, weightStr2] = pair.split(":");
263
+ if (toolShortName && weightStr2) {
264
+ const toolName = normalizeToolName(toolShortName.trim());
265
+ const weight = parseInt(weightStr2.trim(), 10);
266
+ if (!isNaN(weight) && weight > 0) {
267
+ weights.set(toolName, weight);
268
+ }
269
+ }
270
+ }
271
+ return weights;
272
+ }
273
+ function calculateOverallScore(toolOutputs, config, cliWeights) {
274
+ if (toolOutputs.size === 0) {
275
+ throw new Error("No tool outputs provided for scoring");
276
+ }
277
+ const weights = /* @__PURE__ */ new Map();
278
+ for (const [toolName] of toolOutputs.entries()) {
279
+ const cliWeight = cliWeights?.get(toolName);
280
+ const configWeight = config?.tools?.[toolName]?.scoreWeight;
281
+ const weight = cliWeight ?? configWeight ?? DEFAULT_TOOL_WEIGHTS[toolName] ?? 5;
282
+ weights.set(toolName, weight);
283
+ }
284
+ let weightedSum = 0;
285
+ let totalWeight = 0;
286
+ const breakdown = [];
287
+ const toolsUsed = [];
288
+ const calculationWeights = {};
289
+ for (const [toolName, output] of toolOutputs.entries()) {
290
+ const weight = weights.get(toolName) || 5;
291
+ weightedSum += output.score * weight;
292
+ totalWeight += weight;
293
+ toolsUsed.push(toolName);
294
+ calculationWeights[toolName] = weight;
295
+ breakdown.push(output);
296
+ }
297
+ const overall = Math.round(weightedSum / totalWeight);
298
+ const rating = getRating(overall);
299
+ const formulaParts = Array.from(toolOutputs.entries()).map(
300
+ ([name, output]) => {
301
+ const w = weights.get(name) || 5;
302
+ return `(${output.score} \xD7 ${w})`;
303
+ }
304
+ );
305
+ const formulaStr = `[${formulaParts.join(" + ")}] / ${totalWeight} = ${overall}`;
306
+ return {
307
+ overall,
308
+ rating,
309
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
310
+ toolsUsed,
311
+ breakdown,
312
+ calculation: {
313
+ formula: formulaStr,
314
+ weights: calculationWeights,
315
+ normalized: formulaStr
316
+ }
317
+ };
318
+ }
319
+ function getRating(score) {
320
+ if (score >= 90) return "Excellent";
321
+ if (score >= 75) return "Good";
322
+ if (score >= 60) return "Fair";
323
+ if (score >= 40) return "Needs Work";
324
+ return "Critical";
325
+ }
326
+ function getRatingWithContext(score, fileCount, modelTier = "standard") {
327
+ const threshold = getRecommendedThreshold(fileCount, modelTier);
328
+ const normalized = score - threshold + 70;
329
+ return getRating(normalized);
330
+ }
331
+ function getRatingDisplay(rating) {
332
+ switch (rating) {
333
+ case "Excellent":
334
+ return { emoji: "\u2705", color: "green" };
335
+ case "Good":
336
+ return { emoji: "\u{1F44D}", color: "blue" };
337
+ case "Fair":
338
+ return { emoji: "\u26A0\uFE0F", color: "yellow" };
339
+ case "Needs Work":
340
+ return { emoji: "\u{1F528}", color: "orange" };
341
+ case "Critical":
342
+ return { emoji: "\u274C", color: "red" };
343
+ }
344
+ }
345
+ function formatScore(result) {
346
+ const { emoji } = getRatingDisplay(result.rating);
347
+ return `${result.overall}/100 (${result.rating}) ${emoji}`;
348
+ }
349
+ function formatToolScore(output) {
350
+ let result = ` Score: ${output.score}/100
351
+
352
+ `;
353
+ if (output.factors && output.factors.length > 0) {
354
+ result += ` Factors:
355
+ `;
356
+ output.factors.forEach((factor) => {
357
+ const impactSign = factor.impact > 0 ? "+" : "";
358
+ result += ` \u2022 ${factor.name}: ${impactSign}${factor.impact} - ${factor.description}
359
+ `;
360
+ });
361
+ result += "\n";
362
+ }
363
+ if (output.recommendations && output.recommendations.length > 0) {
364
+ result += ` Recommendations:
365
+ `;
366
+ output.recommendations.forEach((rec, i) => {
367
+ const priorityIcon = rec.priority === "high" ? "\u{1F534}" : rec.priority === "medium" ? "\u{1F7E1}" : "\u{1F535}";
368
+ result += ` ${i + 1}. ${priorityIcon} ${rec.action}
369
+ `;
370
+ result += ` Impact: +${rec.estimatedImpact} points
371
+
372
+ `;
373
+ });
374
+ }
375
+ return result;
376
+ }
377
+
378
+ // src/utils/visualization.ts
379
+ function generateHTML(graph) {
380
+ const payload = JSON.stringify(graph, null, 2);
381
+ return `<!doctype html>
382
+ <html>
383
+ <head>
384
+ <meta charset="utf-8" />
385
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
386
+ <title>AIReady Visualization</title>
387
+ <style>
388
+ html,body { height: 100%; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0 }
389
+ #container { display:flex; height:100vh }
390
+ #panel { width: 320px; padding: 16px; background: #071130; box-shadow: -2px 0 8px rgba(0,0,0,0.3); overflow:auto }
391
+ #canvasWrap { flex:1; display:flex; align-items:center; justify-content:center }
392
+ canvas { background: #0b1220; border-radius:8px }
393
+ .stat { margin-bottom:12px }
394
+ </style>
395
+ </head>
396
+ <body>
397
+ <div id="container">
398
+ <div id="canvasWrap"><canvas id="canvas" width="1200" height="800"></canvas></div>
399
+ <div id="panel">
400
+ <h2>AIReady Visualization</h2>
401
+ <div class="stat"><strong>Files:</strong> <span id="stat-files"></span></div>
402
+ <div class="stat"><strong>Dependencies:</strong> <span id="stat-deps"></span></div>
403
+ <div class="stat"><strong>Legend</strong></div>
404
+ <div style="font-size:13px;line-height:1.3;color:#cbd5e1;margin-top:8px">
405
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff4d4f;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Critical</strong>: highest severity issues.</div>
406
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ff9900;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Major</strong>: important issues.</div>
407
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#ffd666;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Minor</strong>: low priority issues.</div>
408
+ <div style="margin-bottom:8px"><span style="display:inline-block;width:12px;height:12px;background:#91d5ff;margin-right:8px;border:1px solid rgba(255,255,255,0.06)"></span><strong>Info</strong>: informational notes.</div>
409
+ <div style="margin-top:10px;color:#94a3b8"><strong>Node size</strong>: larger = higher token cost, more issues or dependency weight.</div>
410
+ <div style="margin-top:6px;color:#94a3b8"><strong>Proximity</strong>: nodes that are spatially close are more contextually related; relatedness is represented by distance and size rather than explicit edges.</div>
411
+ <div style="margin-top:6px;color:#94a3b8"><strong>Edge colors</strong>: <span style="color:#fb7e81">Similarity</span>, <span style="color:#84c1ff">Dependency</span>, <span style="color:#ffa500">Reference</span>, default <span style="color:#334155">Other</span>.</div>
412
+ </div>
413
+ </div>
414
+ </div>
415
+
416
+ <script>
417
+ const graphData = ${payload};
418
+ document.getElementById('stat-files').textContent = graphData.metadata.totalFiles;
419
+ document.getElementById('stat-deps').textContent = graphData.metadata.totalDependencies;
420
+
421
+ const canvas = document.getElementById('canvas');
422
+ const ctx = canvas.getContext('2d');
423
+
424
+ const nodes = graphData.nodes.map((n, i) => ({
425
+ ...n,
426
+ x: canvas.width / 2 + Math.cos(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
427
+ y: canvas.height / 2 + Math.sin(i / graphData.nodes.length * Math.PI * 2) * (Math.min(canvas.width, canvas.height) / 3),
428
+ }));
429
+
430
+ function draw() {
431
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
432
+
433
+ graphData.edges.forEach(edge => {
434
+ const s = nodes.find(n => n.id === edge.source);
435
+ const t = nodes.find(n => n.id === edge.target);
436
+ if (!s || !t) return;
437
+ if (edge.type === 'related') return;
438
+ if (edge.type === 'similarity') {
439
+ ctx.strokeStyle = '#fb7e81';
440
+ ctx.lineWidth = 1.2;
441
+ } else if (edge.type === 'dependency') {
442
+ ctx.strokeStyle = '#84c1ff';
443
+ ctx.lineWidth = 1.0;
444
+ } else if (edge.type === 'reference') {
445
+ ctx.strokeStyle = '#ffa500';
446
+ ctx.lineWidth = 0.9;
447
+ } else {
448
+ ctx.strokeStyle = '#334155';
449
+ ctx.lineWidth = 0.8;
450
+ }
451
+ ctx.beginPath();
452
+ ctx.moveTo(s.x, s.y);
453
+ ctx.lineTo(t.x, t.y);
454
+ ctx.stroke();
455
+ });
456
+
457
+ const groups = {};
458
+ nodes.forEach(n => {
459
+ const g = n.group || '__default';
460
+ if (!groups[g]) groups[g] = { minX: n.x, minY: n.y, maxX: n.x, maxY: n.y };
461
+ groups[g].minX = Math.min(groups[g].minX, n.x);
462
+ groups[g].minY = Math.min(groups[g].minY, n.y);
463
+ groups[g].maxX = Math.max(groups[g].maxX, n.x);
464
+ groups[g].maxY = Math.max(groups[g].maxY, n.y);
465
+ });
466
+
467
+ const groupRelations = {};
468
+ graphData.edges.forEach(edge => {
469
+ const sNode = nodes.find(n => n.id === edge.source);
470
+ const tNode = nodes.find(n => n.id === edge.target);
471
+ if (!sNode || !tNode) return;
472
+ const g1 = sNode.group || '__default';
473
+ const g2 = tNode.group || '__default';
474
+ if (g1 === g2) return;
475
+ const key = g1 < g2 ? g1 + '::' + g2 : g2 + '::' + g1;
476
+ groupRelations[key] = (groupRelations[key] || 0) + 1;
477
+ });
478
+
479
+ Object.keys(groupRelations).forEach(k => {
480
+ const count = groupRelations[k];
481
+ const [ga, gb] = k.split('::');
482
+ if (!groups[ga] || !groups[gb]) return;
483
+ const ax = (groups[ga].minX + groups[ga].maxX) / 2;
484
+ const ay = (groups[ga].minY + groups[ga].maxY) / 2;
485
+ const bx = (groups[gb].minX + groups[gb].maxX) / 2;
486
+ const by = (groups[gb].minY + groups[gb].maxY) / 2;
487
+ ctx.beginPath();
488
+ ctx.strokeStyle = 'rgba(148,163,184,0.25)';
489
+ ctx.lineWidth = Math.min(6, 0.6 + Math.sqrt(count));
490
+ ctx.moveTo(ax, ay);
491
+ ctx.lineTo(bx, by);
492
+ ctx.stroke();
493
+ });
494
+
495
+ Object.keys(groups).forEach(g => {
496
+ if (g === '__default') return;
497
+ const box = groups[g];
498
+ const pad = 16;
499
+ const x = box.minX - pad;
500
+ const y = box.minY - pad;
501
+ const w = (box.maxX - box.minX) + pad * 2;
502
+ const h = (box.maxY - box.minY) + pad * 2;
503
+ ctx.save();
504
+ ctx.fillStyle = 'rgba(30,64,175,0.04)';
505
+ ctx.strokeStyle = 'rgba(30,64,175,0.12)';
506
+ ctx.lineWidth = 1.2;
507
+ const r = 8;
508
+ ctx.beginPath();
509
+ ctx.moveTo(x + r, y);
510
+ ctx.arcTo(x + w, y, x + w, y + h, r);
511
+ ctx.arcTo(x + w, y + h, x, y + h, r);
512
+ ctx.arcTo(x, y + h, x, y, r);
513
+ ctx.arcTo(x, y, x + w, y, r);
514
+ ctx.closePath();
515
+ ctx.fill();
516
+ ctx.stroke();
517
+ ctx.restore();
518
+ ctx.fillStyle = '#94a3b8';
519
+ ctx.font = '11px sans-serif';
520
+ ctx.fillText(g, x + 8, y + 14);
521
+ });
522
+
523
+ nodes.forEach(n => {
524
+ const sizeVal = (n.size || n.value || 1);
525
+ const r = 6 + (sizeVal / 2);
526
+ ctx.beginPath();
527
+ ctx.fillStyle = n.color || '#60a5fa';
528
+ ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
529
+ ctx.fill();
530
+
531
+ ctx.fillStyle = '#e2e8f0';
532
+ ctx.font = '11px sans-serif';
533
+ ctx.textAlign = 'center';
534
+ ctx.fillText(n.label || n.id.split('/').slice(-1)[0], n.x, n.y + r + 12);
535
+ });
536
+ }
537
+
538
+ draw();
539
+ </script>
540
+ </body>
541
+ </html>`;
542
+ }
543
+
544
+ export {
545
+ Severity,
546
+ SeveritySchema,
547
+ ToolName,
548
+ ToolNameSchema,
549
+ FRIENDLY_TOOL_NAMES,
550
+ IssueType,
551
+ IssueTypeSchema,
552
+ AnalysisStatus,
553
+ AnalysisStatusSchema,
554
+ ModelTier,
555
+ ModelTierSchema,
556
+ LocationSchema,
557
+ IssueSchema,
558
+ MetricsSchema,
559
+ AnalysisResultSchema,
560
+ SpokeSummarySchema,
561
+ SpokeOutputSchema,
562
+ UnifiedReportSchema,
563
+ Language,
564
+ LANGUAGE_EXTENSIONS,
565
+ ParseError,
566
+ DEFAULT_TOOL_WEIGHTS,
567
+ TOOL_NAME_MAP,
568
+ CONTEXT_TIER_THRESHOLDS,
569
+ SIZE_ADJUSTED_THRESHOLDS,
570
+ getProjectSizeTier,
571
+ getRecommendedThreshold,
572
+ normalizeToolName,
573
+ getToolWeight,
574
+ parseWeightString,
575
+ calculateOverallScore,
576
+ getRating,
577
+ getRatingWithContext,
578
+ getRatingDisplay,
579
+ formatScore,
580
+ formatToolScore,
581
+ generateHTML
582
+ };
package/dist/client.d.mts CHANGED
@@ -163,6 +163,17 @@ declare const AnalysisResultSchema: z.ZodObject<{
163
163
  }, z.core.$strip>;
164
164
  }, z.core.$strip>;
165
165
  type AnalysisResult = z.infer<typeof AnalysisResultSchema>;
166
+ /**
167
+ * Standard spoke tool summary schema.
168
+ */
169
+ declare const SpokeSummarySchema: z.ZodObject<{
170
+ totalFiles: z.ZodOptional<z.ZodNumber>;
171
+ totalIssues: z.ZodOptional<z.ZodNumber>;
172
+ criticalIssues: z.ZodOptional<z.ZodNumber>;
173
+ majorIssues: z.ZodOptional<z.ZodNumber>;
174
+ score: z.ZodOptional<z.ZodNumber>;
175
+ }, z.core.$catchall<z.ZodAny>>;
176
+ type SpokeSummary = z.infer<typeof SpokeSummarySchema>;
166
177
  /**
167
178
  * Standard spoke tool output contract.
168
179
  */
@@ -200,11 +211,17 @@ declare const SpokeOutputSchema: z.ZodObject<{
200
211
  totalExports: z.ZodOptional<z.ZodNumber>;
201
212
  }, z.core.$strip>;
202
213
  }, z.core.$strip>>;
203
- summary: z.ZodAny;
214
+ summary: z.ZodObject<{
215
+ totalFiles: z.ZodOptional<z.ZodNumber>;
216
+ totalIssues: z.ZodOptional<z.ZodNumber>;
217
+ criticalIssues: z.ZodOptional<z.ZodNumber>;
218
+ majorIssues: z.ZodOptional<z.ZodNumber>;
219
+ score: z.ZodOptional<z.ZodNumber>;
220
+ }, z.core.$catchall<z.ZodAny>>;
204
221
  metadata: z.ZodOptional<z.ZodObject<{
205
222
  toolName: z.ZodString;
206
- version: z.ZodString;
207
- timestamp: z.ZodString;
223
+ version: z.ZodOptional<z.ZodString>;
224
+ timestamp: z.ZodOptional<z.ZodString>;
208
225
  }, z.core.$catchall<z.ZodAny>>>;
209
226
  }, z.core.$strip>;
210
227
  type SpokeOutput = z.infer<typeof SpokeOutputSchema>;
@@ -862,4 +879,4 @@ declare function formatToolScore(output: ToolScoringOutput): string;
862
879
  */
863
880
  declare function generateHTML(graph: GraphData): string;
864
881
 
865
- export { type AIReadyConfig, type AcceptancePrediction, type AnalysisResult, AnalysisResultSchema, AnalysisStatus, AnalysisStatusSchema, type BusinessReport, CONTEXT_TIER_THRESHOLDS, type CommonASTNode, type ComprehensionDifficulty, type CostConfig, DEFAULT_TOOL_WEIGHTS, type ExportInfo, FRIENDLY_TOOL_NAMES, type GraphData, type GraphEdge, type GraphIssueSeverity, type GraphMetadata, type GraphNode, type ImportInfo, type Issue, IssueSchema, IssueType, IssueTypeSchema, LANGUAGE_EXTENSIONS, Language, type LanguageConfig, type LanguageParser, type Location, LocationSchema, type Metrics, MetricsSchema, type ModelContextTier, ModelTier, ModelTierSchema, type NamingConvention, ParseError, type ParseResult, type ParseStatistics, type ProductivityImpact, type Report, SIZE_ADJUSTED_THRESHOLDS, type ScanOptions, type ScoringConfig, type ScoringResult, Severity, SeveritySchema, type SourceLocation, type SourceRange, type SpokeOutput, SpokeOutputSchema, TOOL_NAME_MAP, type TechnicalValueChain, type TechnicalValueChainSummary, type TokenBudget, ToolName, ToolNameSchema, type ToolScoringOutput, type UnifiedReport, UnifiedReportSchema, calculateOverallScore, formatScore, formatToolScore, generateHTML, getProjectSizeTier, getRating, getRatingDisplay, getRatingWithContext, getRecommendedThreshold, getToolWeight, normalizeToolName, parseWeightString };
882
+ export { type AIReadyConfig, type AcceptancePrediction, type AnalysisResult, AnalysisResultSchema, AnalysisStatus, AnalysisStatusSchema, type BusinessReport, CONTEXT_TIER_THRESHOLDS, type CommonASTNode, type ComprehensionDifficulty, type CostConfig, DEFAULT_TOOL_WEIGHTS, type ExportInfo, FRIENDLY_TOOL_NAMES, type GraphData, type GraphEdge, type GraphIssueSeverity, type GraphMetadata, type GraphNode, type ImportInfo, type Issue, IssueSchema, IssueType, IssueTypeSchema, LANGUAGE_EXTENSIONS, Language, type LanguageConfig, type LanguageParser, type Location, LocationSchema, type Metrics, MetricsSchema, type ModelContextTier, ModelTier, ModelTierSchema, type NamingConvention, ParseError, type ParseResult, type ParseStatistics, type ProductivityImpact, type Report, SIZE_ADJUSTED_THRESHOLDS, type ScanOptions, type ScoringConfig, type ScoringResult, Severity, SeveritySchema, type SourceLocation, type SourceRange, type SpokeOutput, SpokeOutputSchema, type SpokeSummary, SpokeSummarySchema, TOOL_NAME_MAP, type TechnicalValueChain, type TechnicalValueChainSummary, type TokenBudget, ToolName, ToolNameSchema, type ToolScoringOutput, type UnifiedReport, UnifiedReportSchema, calculateOverallScore, formatScore, formatToolScore, generateHTML, getProjectSizeTier, getRating, getRatingDisplay, getRatingWithContext, getRecommendedThreshold, getToolWeight, normalizeToolName, parseWeightString };
package/dist/client.d.ts CHANGED
@@ -163,6 +163,17 @@ declare const AnalysisResultSchema: z.ZodObject<{
163
163
  }, z.core.$strip>;
164
164
  }, z.core.$strip>;
165
165
  type AnalysisResult = z.infer<typeof AnalysisResultSchema>;
166
+ /**
167
+ * Standard spoke tool summary schema.
168
+ */
169
+ declare const SpokeSummarySchema: z.ZodObject<{
170
+ totalFiles: z.ZodOptional<z.ZodNumber>;
171
+ totalIssues: z.ZodOptional<z.ZodNumber>;
172
+ criticalIssues: z.ZodOptional<z.ZodNumber>;
173
+ majorIssues: z.ZodOptional<z.ZodNumber>;
174
+ score: z.ZodOptional<z.ZodNumber>;
175
+ }, z.core.$catchall<z.ZodAny>>;
176
+ type SpokeSummary = z.infer<typeof SpokeSummarySchema>;
166
177
  /**
167
178
  * Standard spoke tool output contract.
168
179
  */
@@ -200,11 +211,17 @@ declare const SpokeOutputSchema: z.ZodObject<{
200
211
  totalExports: z.ZodOptional<z.ZodNumber>;
201
212
  }, z.core.$strip>;
202
213
  }, z.core.$strip>>;
203
- summary: z.ZodAny;
214
+ summary: z.ZodObject<{
215
+ totalFiles: z.ZodOptional<z.ZodNumber>;
216
+ totalIssues: z.ZodOptional<z.ZodNumber>;
217
+ criticalIssues: z.ZodOptional<z.ZodNumber>;
218
+ majorIssues: z.ZodOptional<z.ZodNumber>;
219
+ score: z.ZodOptional<z.ZodNumber>;
220
+ }, z.core.$catchall<z.ZodAny>>;
204
221
  metadata: z.ZodOptional<z.ZodObject<{
205
222
  toolName: z.ZodString;
206
- version: z.ZodString;
207
- timestamp: z.ZodString;
223
+ version: z.ZodOptional<z.ZodString>;
224
+ timestamp: z.ZodOptional<z.ZodString>;
208
225
  }, z.core.$catchall<z.ZodAny>>>;
209
226
  }, z.core.$strip>;
210
227
  type SpokeOutput = z.infer<typeof SpokeOutputSchema>;
@@ -862,4 +879,4 @@ declare function formatToolScore(output: ToolScoringOutput): string;
862
879
  */
863
880
  declare function generateHTML(graph: GraphData): string;
864
881
 
865
- export { type AIReadyConfig, type AcceptancePrediction, type AnalysisResult, AnalysisResultSchema, AnalysisStatus, AnalysisStatusSchema, type BusinessReport, CONTEXT_TIER_THRESHOLDS, type CommonASTNode, type ComprehensionDifficulty, type CostConfig, DEFAULT_TOOL_WEIGHTS, type ExportInfo, FRIENDLY_TOOL_NAMES, type GraphData, type GraphEdge, type GraphIssueSeverity, type GraphMetadata, type GraphNode, type ImportInfo, type Issue, IssueSchema, IssueType, IssueTypeSchema, LANGUAGE_EXTENSIONS, Language, type LanguageConfig, type LanguageParser, type Location, LocationSchema, type Metrics, MetricsSchema, type ModelContextTier, ModelTier, ModelTierSchema, type NamingConvention, ParseError, type ParseResult, type ParseStatistics, type ProductivityImpact, type Report, SIZE_ADJUSTED_THRESHOLDS, type ScanOptions, type ScoringConfig, type ScoringResult, Severity, SeveritySchema, type SourceLocation, type SourceRange, type SpokeOutput, SpokeOutputSchema, TOOL_NAME_MAP, type TechnicalValueChain, type TechnicalValueChainSummary, type TokenBudget, ToolName, ToolNameSchema, type ToolScoringOutput, type UnifiedReport, UnifiedReportSchema, calculateOverallScore, formatScore, formatToolScore, generateHTML, getProjectSizeTier, getRating, getRatingDisplay, getRatingWithContext, getRecommendedThreshold, getToolWeight, normalizeToolName, parseWeightString };
882
+ export { type AIReadyConfig, type AcceptancePrediction, type AnalysisResult, AnalysisResultSchema, AnalysisStatus, AnalysisStatusSchema, type BusinessReport, CONTEXT_TIER_THRESHOLDS, type CommonASTNode, type ComprehensionDifficulty, type CostConfig, DEFAULT_TOOL_WEIGHTS, type ExportInfo, FRIENDLY_TOOL_NAMES, type GraphData, type GraphEdge, type GraphIssueSeverity, type GraphMetadata, type GraphNode, type ImportInfo, type Issue, IssueSchema, IssueType, IssueTypeSchema, LANGUAGE_EXTENSIONS, Language, type LanguageConfig, type LanguageParser, type Location, LocationSchema, type Metrics, MetricsSchema, type ModelContextTier, ModelTier, ModelTierSchema, type NamingConvention, ParseError, type ParseResult, type ParseStatistics, type ProductivityImpact, type Report, SIZE_ADJUSTED_THRESHOLDS, type ScanOptions, type ScoringConfig, type ScoringResult, Severity, SeveritySchema, type SourceLocation, type SourceRange, type SpokeOutput, SpokeOutputSchema, type SpokeSummary, SpokeSummarySchema, TOOL_NAME_MAP, type TechnicalValueChain, type TechnicalValueChainSummary, type TokenBudget, ToolName, ToolNameSchema, type ToolScoringOutput, type UnifiedReport, UnifiedReportSchema, calculateOverallScore, formatScore, formatToolScore, generateHTML, getProjectSizeTier, getRating, getRatingDisplay, getRatingWithContext, getRecommendedThreshold, getToolWeight, normalizeToolName, parseWeightString };
package/dist/client.js CHANGED
@@ -40,6 +40,7 @@ __export(client_exports, {
40
40
  Severity: () => Severity,
41
41
  SeveritySchema: () => SeveritySchema,
42
42
  SpokeOutputSchema: () => SpokeOutputSchema,
43
+ SpokeSummarySchema: () => SpokeSummarySchema,
43
44
  TOOL_NAME_MAP: () => TOOL_NAME_MAP,
44
45
  ToolName: () => ToolName,
45
46
  ToolNameSchema: () => ToolNameSchema,
@@ -177,13 +178,20 @@ var AnalysisResultSchema = import_zod.z.object({
177
178
  issues: import_zod.z.array(IssueSchema),
178
179
  metrics: MetricsSchema
179
180
  });
181
+ var SpokeSummarySchema = import_zod.z.object({
182
+ totalFiles: import_zod.z.number().optional(),
183
+ totalIssues: import_zod.z.number().optional(),
184
+ criticalIssues: import_zod.z.number().optional(),
185
+ majorIssues: import_zod.z.number().optional(),
186
+ score: import_zod.z.number().optional()
187
+ }).catchall(import_zod.z.any());
180
188
  var SpokeOutputSchema = import_zod.z.object({
181
189
  results: import_zod.z.array(AnalysisResultSchema),
182
- summary: import_zod.z.any(),
190
+ summary: SpokeSummarySchema,
183
191
  metadata: import_zod.z.object({
184
192
  toolName: import_zod.z.string(),
185
- version: import_zod.z.string(),
186
- timestamp: import_zod.z.string()
193
+ version: import_zod.z.string().optional(),
194
+ timestamp: import_zod.z.string().optional()
187
195
  }).catchall(import_zod.z.any()).optional()
188
196
  });
189
197
  var UnifiedReportSchema = import_zod.z.object({
@@ -616,6 +624,7 @@ function generateHTML(graph) {
616
624
  Severity,
617
625
  SeveritySchema,
618
626
  SpokeOutputSchema,
627
+ SpokeSummarySchema,
619
628
  TOOL_NAME_MAP,
620
629
  ToolName,
621
630
  ToolNameSchema,
package/dist/client.mjs CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  Severity,
20
20
  SeveritySchema,
21
21
  SpokeOutputSchema,
22
+ SpokeSummarySchema,
22
23
  TOOL_NAME_MAP,
23
24
  ToolName,
24
25
  ToolNameSchema,
@@ -35,7 +36,7 @@ import {
35
36
  getToolWeight,
36
37
  normalizeToolName,
37
38
  parseWeightString
38
- } from "./chunk-QAFB3HXQ.mjs";
39
+ } from "./chunk-YCA4FTEK.mjs";
39
40
  export {
40
41
  AnalysisResultSchema,
41
42
  AnalysisStatus,
@@ -57,6 +58,7 @@ export {
57
58
  Severity,
58
59
  SeveritySchema,
59
60
  SpokeOutputSchema,
61
+ SpokeSummarySchema,
60
62
  TOOL_NAME_MAP,
61
63
  ToolName,
62
64
  ToolNameSchema,
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { ScanOptions, AIReadyConfig, ModelContextTier, CostConfig, TokenBudget, ProductivityImpact, ToolScoringOutput, AcceptancePrediction, ComprehensionDifficulty, TechnicalValueChainSummary, TechnicalValueChain, LanguageParser, Language, ParseResult, NamingConvention } from './client.mjs';
2
- export { AnalysisResult, AnalysisResultSchema, AnalysisStatus, AnalysisStatusSchema, BusinessReport, CONTEXT_TIER_THRESHOLDS, CommonASTNode, DEFAULT_TOOL_WEIGHTS, ExportInfo, FRIENDLY_TOOL_NAMES, GraphData, GraphEdge, GraphIssueSeverity, GraphMetadata, GraphNode, ImportInfo, Issue, IssueSchema, IssueType, IssueTypeSchema, LANGUAGE_EXTENSIONS, LanguageConfig, Location, LocationSchema, Metrics, MetricsSchema, ModelTier, ModelTierSchema, ParseError, ParseStatistics, Report, SIZE_ADJUSTED_THRESHOLDS, ScoringConfig, ScoringResult, Severity, SeveritySchema, SourceLocation, SourceRange, SpokeOutput, SpokeOutputSchema, TOOL_NAME_MAP, ToolName, ToolNameSchema, UnifiedReport, UnifiedReportSchema, calculateOverallScore, formatScore, formatToolScore, generateHTML, getProjectSizeTier, getRating, getRatingDisplay, getRatingWithContext, getRecommendedThreshold, getToolWeight, normalizeToolName, parseWeightString } from './client.mjs';
1
+ import { ToolName, ScanOptions, SpokeOutput, ToolScoringOutput, AIReadyConfig, ModelContextTier, CostConfig, TokenBudget, ProductivityImpact, AcceptancePrediction, ComprehensionDifficulty, TechnicalValueChainSummary, TechnicalValueChain, LanguageParser, Language, ParseResult, NamingConvention } from './client.mjs';
2
+ export { AnalysisResult, AnalysisResultSchema, AnalysisStatus, AnalysisStatusSchema, BusinessReport, CONTEXT_TIER_THRESHOLDS, CommonASTNode, DEFAULT_TOOL_WEIGHTS, ExportInfo, FRIENDLY_TOOL_NAMES, GraphData, GraphEdge, GraphIssueSeverity, GraphMetadata, GraphNode, ImportInfo, Issue, IssueSchema, IssueType, IssueTypeSchema, LANGUAGE_EXTENSIONS, LanguageConfig, Location, LocationSchema, Metrics, MetricsSchema, ModelTier, ModelTierSchema, ParseError, ParseStatistics, Report, SIZE_ADJUSTED_THRESHOLDS, ScoringConfig, ScoringResult, Severity, SeveritySchema, SourceLocation, SourceRange, SpokeOutputSchema, SpokeSummary, SpokeSummarySchema, TOOL_NAME_MAP, ToolNameSchema, UnifiedReport, UnifiedReportSchema, calculateOverallScore, formatScore, formatToolScore, generateHTML, getProjectSizeTier, getRating, getRatingDisplay, getRatingWithContext, getRecommendedThreshold, getToolWeight, normalizeToolName, parseWeightString } from './client.mjs';
3
3
  import { z } from 'zod';
4
4
 
5
5
  /**
@@ -8,6 +8,22 @@ import { z } from 'zod';
8
8
  * changes in spokes don't break the CLI, Platform, or Visualizer.
9
9
  */
10
10
 
11
+ /**
12
+ * Tool Provider Interface
13
+ * Every AIReady spoke must implement this interface to be integrated into the CLI registry.
14
+ */
15
+ interface ToolProvider {
16
+ /** Canonical tool ID */
17
+ id: ToolName;
18
+ /** CLI aliases/shorthand for this tool */
19
+ alias: string[];
20
+ /** Primary analysis logic */
21
+ analyze: (options: ScanOptions) => Promise<SpokeOutput>;
22
+ /** Scoring logic for this tool's output */
23
+ score: (output: SpokeOutput, options: ScanOptions) => ToolScoringOutput;
24
+ /** Optional weight override for this tool */
25
+ defaultWeight?: number;
26
+ }
11
27
  /**
12
28
  * Validation utility to ensure a spoke's output matches the expected contract.
13
29
  * Used in spoke tests to catch breakages early.
@@ -25,6 +41,41 @@ declare function validateWithSchema<T>(schema: z.ZodSchema<T>, data: any): {
25
41
  errors?: string[];
26
42
  };
27
43
 
44
+ /**
45
+ * AIReady Tool Registry
46
+ *
47
+ * Central registry for all analysis tools. Decouples the CLI from
48
+ * individual tool packages and allows for easier extension.
49
+ */
50
+ declare class ToolRegistry {
51
+ private static getProviders;
52
+ static instanceId: number;
53
+ /**
54
+ * Register a new tool provider.
55
+ */
56
+ static register(provider: ToolProvider): void;
57
+ /**
58
+ * Get a provider by its canonical ID.
59
+ */
60
+ static get(id: ToolName): ToolProvider | undefined;
61
+ /**
62
+ * Get a provider by name or alias.
63
+ */
64
+ static find(nameOrAlias: string): ToolProvider | undefined;
65
+ /**
66
+ * Get all registered tool providers.
67
+ */
68
+ static getAll(): ToolProvider[];
69
+ /**
70
+ * Get all available tool IDs from the ToolName enum.
71
+ */
72
+ static getAvailableIds(): ToolName[];
73
+ /**
74
+ * Clear the registry (primarily for testing).
75
+ */
76
+ static clear(): void;
77
+ }
78
+
28
79
  declare const DEFAULT_EXCLUDE: string[];
29
80
  declare const VAGUE_FILE_NAMES: Set<string>;
30
81
  /**
@@ -800,4 +851,4 @@ declare function getRepoMetadata(directory: string): {
800
851
  author?: string;
801
852
  };
802
853
 
803
- export { AIReadyConfig, type ASTNode, AcceptancePrediction, type AgentGroundingScore, type AiSignalClarity, type AiSignalClaritySignal, type CLIOptions, type ChangeAmplificationScore, type CognitiveLoad, ComprehensionDifficulty, type ConceptCohesion, CostConfig, DEFAULT_COST_CONFIG, DEFAULT_EXCLUDE, type DependencyHealthScore, type DocDriftRisk, type ExportWithImports, type FileImport, type FileWithDomain, type KnowledgeConcentrationRisk, Language, LanguageParser, type LoadFactor, MODEL_PRICING_PRESETS, ModelContextTier, type ModelPricingPreset, NamingConvention, ParseResult, ParserFactory, type PatternEntropy, ProductivityImpact, PythonParser, SEVERITY_TIME_ESTIMATES, ScanOptions, type ScoreHistoryEntry, type ScoreTrend, type SemanticDistance, type TechnicalDebtInterest, TechnicalValueChain, TechnicalValueChainSummary, type TestabilityIndex, TokenBudget, ToolScoringOutput, TypeScriptParser, VAGUE_FILE_NAMES, calculateAgentGrounding, calculateAiSignalClarity, calculateBusinessROI, calculateChangeAmplification, calculateCognitiveLoad, calculateComprehensionDifficulty, calculateConceptCohesion, calculateDebtInterest, calculateDependencyHealth, calculateDocDrift, calculateExtendedFutureProofScore, calculateFutureProofScore, calculateImportSimilarity, calculateKnowledgeConcentration, calculateMonthlyCost, calculatePatternEntropy, calculateProductivityImpact, calculateSemanticDistance, calculateTechnicalValueChain, calculateTestabilityIndex, calculateTokenBudget, clearHistory, estimateCostFromBudget, estimateTokens, exportHistory, extractFunctions, extractImports, formatAcceptanceRate, formatCost, formatHours, generateValueChain, getElapsedTime, getFileCommitTimestamps, getFileExtension, getHistorySummary, getLineRangeLastModifiedCached, getModelPreset, getParser, getRepoMetadata, getSafetyIcon, getScoreBar, getSeverityColor, getSupportedLanguages, handleCLIError, handleJSONOutput, isFileSupported, isSourceFile, loadConfig, loadMergedConfig, loadScoreHistory, mergeConfigWithDefaults, parseCode, parseFileExports, predictAcceptanceRate, readFileContent, resolveOutputPath, saveScoreEntry, scanEntries, scanFiles, validateSpokeOutput, validateWithSchema };
854
+ export { AIReadyConfig, type ASTNode, AcceptancePrediction, type AgentGroundingScore, type AiSignalClarity, type AiSignalClaritySignal, type CLIOptions, type ChangeAmplificationScore, type CognitiveLoad, ComprehensionDifficulty, type ConceptCohesion, CostConfig, DEFAULT_COST_CONFIG, DEFAULT_EXCLUDE, type DependencyHealthScore, type DocDriftRisk, type ExportWithImports, type FileImport, type FileWithDomain, type KnowledgeConcentrationRisk, Language, LanguageParser, type LoadFactor, MODEL_PRICING_PRESETS, ModelContextTier, type ModelPricingPreset, NamingConvention, ParseResult, ParserFactory, type PatternEntropy, ProductivityImpact, PythonParser, SEVERITY_TIME_ESTIMATES, ScanOptions, type ScoreHistoryEntry, type ScoreTrend, type SemanticDistance, SpokeOutput, type TechnicalDebtInterest, TechnicalValueChain, TechnicalValueChainSummary, type TestabilityIndex, TokenBudget, ToolName, type ToolProvider, ToolRegistry, ToolScoringOutput, TypeScriptParser, VAGUE_FILE_NAMES, calculateAgentGrounding, calculateAiSignalClarity, calculateBusinessROI, calculateChangeAmplification, calculateCognitiveLoad, calculateComprehensionDifficulty, calculateConceptCohesion, calculateDebtInterest, calculateDependencyHealth, calculateDocDrift, calculateExtendedFutureProofScore, calculateFutureProofScore, calculateImportSimilarity, calculateKnowledgeConcentration, calculateMonthlyCost, calculatePatternEntropy, calculateProductivityImpact, calculateSemanticDistance, calculateTechnicalValueChain, calculateTestabilityIndex, calculateTokenBudget, clearHistory, estimateCostFromBudget, estimateTokens, exportHistory, extractFunctions, extractImports, formatAcceptanceRate, formatCost, formatHours, generateValueChain, getElapsedTime, getFileCommitTimestamps, getFileExtension, getHistorySummary, getLineRangeLastModifiedCached, getModelPreset, getParser, getRepoMetadata, getSafetyIcon, getScoreBar, getSeverityColor, getSupportedLanguages, handleCLIError, handleJSONOutput, isFileSupported, isSourceFile, loadConfig, loadMergedConfig, loadScoreHistory, mergeConfigWithDefaults, parseCode, parseFileExports, predictAcceptanceRate, readFileContent, resolveOutputPath, saveScoreEntry, scanEntries, scanFiles, validateSpokeOutput, validateWithSchema };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ScanOptions, AIReadyConfig, ModelContextTier, CostConfig, TokenBudget, ProductivityImpact, ToolScoringOutput, AcceptancePrediction, ComprehensionDifficulty, TechnicalValueChainSummary, TechnicalValueChain, LanguageParser, Language, ParseResult, NamingConvention } from './client.js';
2
- export { AnalysisResult, AnalysisResultSchema, AnalysisStatus, AnalysisStatusSchema, BusinessReport, CONTEXT_TIER_THRESHOLDS, CommonASTNode, DEFAULT_TOOL_WEIGHTS, ExportInfo, FRIENDLY_TOOL_NAMES, GraphData, GraphEdge, GraphIssueSeverity, GraphMetadata, GraphNode, ImportInfo, Issue, IssueSchema, IssueType, IssueTypeSchema, LANGUAGE_EXTENSIONS, LanguageConfig, Location, LocationSchema, Metrics, MetricsSchema, ModelTier, ModelTierSchema, ParseError, ParseStatistics, Report, SIZE_ADJUSTED_THRESHOLDS, ScoringConfig, ScoringResult, Severity, SeveritySchema, SourceLocation, SourceRange, SpokeOutput, SpokeOutputSchema, TOOL_NAME_MAP, ToolName, ToolNameSchema, UnifiedReport, UnifiedReportSchema, calculateOverallScore, formatScore, formatToolScore, generateHTML, getProjectSizeTier, getRating, getRatingDisplay, getRatingWithContext, getRecommendedThreshold, getToolWeight, normalizeToolName, parseWeightString } from './client.js';
1
+ import { ToolName, ScanOptions, SpokeOutput, ToolScoringOutput, AIReadyConfig, ModelContextTier, CostConfig, TokenBudget, ProductivityImpact, AcceptancePrediction, ComprehensionDifficulty, TechnicalValueChainSummary, TechnicalValueChain, LanguageParser, Language, ParseResult, NamingConvention } from './client.js';
2
+ export { AnalysisResult, AnalysisResultSchema, AnalysisStatus, AnalysisStatusSchema, BusinessReport, CONTEXT_TIER_THRESHOLDS, CommonASTNode, DEFAULT_TOOL_WEIGHTS, ExportInfo, FRIENDLY_TOOL_NAMES, GraphData, GraphEdge, GraphIssueSeverity, GraphMetadata, GraphNode, ImportInfo, Issue, IssueSchema, IssueType, IssueTypeSchema, LANGUAGE_EXTENSIONS, LanguageConfig, Location, LocationSchema, Metrics, MetricsSchema, ModelTier, ModelTierSchema, ParseError, ParseStatistics, Report, SIZE_ADJUSTED_THRESHOLDS, ScoringConfig, ScoringResult, Severity, SeveritySchema, SourceLocation, SourceRange, SpokeOutputSchema, SpokeSummary, SpokeSummarySchema, TOOL_NAME_MAP, ToolNameSchema, UnifiedReport, UnifiedReportSchema, calculateOverallScore, formatScore, formatToolScore, generateHTML, getProjectSizeTier, getRating, getRatingDisplay, getRatingWithContext, getRecommendedThreshold, getToolWeight, normalizeToolName, parseWeightString } from './client.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  /**
@@ -8,6 +8,22 @@ import { z } from 'zod';
8
8
  * changes in spokes don't break the CLI, Platform, or Visualizer.
9
9
  */
10
10
 
11
+ /**
12
+ * Tool Provider Interface
13
+ * Every AIReady spoke must implement this interface to be integrated into the CLI registry.
14
+ */
15
+ interface ToolProvider {
16
+ /** Canonical tool ID */
17
+ id: ToolName;
18
+ /** CLI aliases/shorthand for this tool */
19
+ alias: string[];
20
+ /** Primary analysis logic */
21
+ analyze: (options: ScanOptions) => Promise<SpokeOutput>;
22
+ /** Scoring logic for this tool's output */
23
+ score: (output: SpokeOutput, options: ScanOptions) => ToolScoringOutput;
24
+ /** Optional weight override for this tool */
25
+ defaultWeight?: number;
26
+ }
11
27
  /**
12
28
  * Validation utility to ensure a spoke's output matches the expected contract.
13
29
  * Used in spoke tests to catch breakages early.
@@ -25,6 +41,41 @@ declare function validateWithSchema<T>(schema: z.ZodSchema<T>, data: any): {
25
41
  errors?: string[];
26
42
  };
27
43
 
44
+ /**
45
+ * AIReady Tool Registry
46
+ *
47
+ * Central registry for all analysis tools. Decouples the CLI from
48
+ * individual tool packages and allows for easier extension.
49
+ */
50
+ declare class ToolRegistry {
51
+ private static getProviders;
52
+ static instanceId: number;
53
+ /**
54
+ * Register a new tool provider.
55
+ */
56
+ static register(provider: ToolProvider): void;
57
+ /**
58
+ * Get a provider by its canonical ID.
59
+ */
60
+ static get(id: ToolName): ToolProvider | undefined;
61
+ /**
62
+ * Get a provider by name or alias.
63
+ */
64
+ static find(nameOrAlias: string): ToolProvider | undefined;
65
+ /**
66
+ * Get all registered tool providers.
67
+ */
68
+ static getAll(): ToolProvider[];
69
+ /**
70
+ * Get all available tool IDs from the ToolName enum.
71
+ */
72
+ static getAvailableIds(): ToolName[];
73
+ /**
74
+ * Clear the registry (primarily for testing).
75
+ */
76
+ static clear(): void;
77
+ }
78
+
28
79
  declare const DEFAULT_EXCLUDE: string[];
29
80
  declare const VAGUE_FILE_NAMES: Set<string>;
30
81
  /**
@@ -800,4 +851,4 @@ declare function getRepoMetadata(directory: string): {
800
851
  author?: string;
801
852
  };
802
853
 
803
- export { AIReadyConfig, type ASTNode, AcceptancePrediction, type AgentGroundingScore, type AiSignalClarity, type AiSignalClaritySignal, type CLIOptions, type ChangeAmplificationScore, type CognitiveLoad, ComprehensionDifficulty, type ConceptCohesion, CostConfig, DEFAULT_COST_CONFIG, DEFAULT_EXCLUDE, type DependencyHealthScore, type DocDriftRisk, type ExportWithImports, type FileImport, type FileWithDomain, type KnowledgeConcentrationRisk, Language, LanguageParser, type LoadFactor, MODEL_PRICING_PRESETS, ModelContextTier, type ModelPricingPreset, NamingConvention, ParseResult, ParserFactory, type PatternEntropy, ProductivityImpact, PythonParser, SEVERITY_TIME_ESTIMATES, ScanOptions, type ScoreHistoryEntry, type ScoreTrend, type SemanticDistance, type TechnicalDebtInterest, TechnicalValueChain, TechnicalValueChainSummary, type TestabilityIndex, TokenBudget, ToolScoringOutput, TypeScriptParser, VAGUE_FILE_NAMES, calculateAgentGrounding, calculateAiSignalClarity, calculateBusinessROI, calculateChangeAmplification, calculateCognitiveLoad, calculateComprehensionDifficulty, calculateConceptCohesion, calculateDebtInterest, calculateDependencyHealth, calculateDocDrift, calculateExtendedFutureProofScore, calculateFutureProofScore, calculateImportSimilarity, calculateKnowledgeConcentration, calculateMonthlyCost, calculatePatternEntropy, calculateProductivityImpact, calculateSemanticDistance, calculateTechnicalValueChain, calculateTestabilityIndex, calculateTokenBudget, clearHistory, estimateCostFromBudget, estimateTokens, exportHistory, extractFunctions, extractImports, formatAcceptanceRate, formatCost, formatHours, generateValueChain, getElapsedTime, getFileCommitTimestamps, getFileExtension, getHistorySummary, getLineRangeLastModifiedCached, getModelPreset, getParser, getRepoMetadata, getSafetyIcon, getScoreBar, getSeverityColor, getSupportedLanguages, handleCLIError, handleJSONOutput, isFileSupported, isSourceFile, loadConfig, loadMergedConfig, loadScoreHistory, mergeConfigWithDefaults, parseCode, parseFileExports, predictAcceptanceRate, readFileContent, resolveOutputPath, saveScoreEntry, scanEntries, scanFiles, validateSpokeOutput, validateWithSchema };
854
+ export { AIReadyConfig, type ASTNode, AcceptancePrediction, type AgentGroundingScore, type AiSignalClarity, type AiSignalClaritySignal, type CLIOptions, type ChangeAmplificationScore, type CognitiveLoad, ComprehensionDifficulty, type ConceptCohesion, CostConfig, DEFAULT_COST_CONFIG, DEFAULT_EXCLUDE, type DependencyHealthScore, type DocDriftRisk, type ExportWithImports, type FileImport, type FileWithDomain, type KnowledgeConcentrationRisk, Language, LanguageParser, type LoadFactor, MODEL_PRICING_PRESETS, ModelContextTier, type ModelPricingPreset, NamingConvention, ParseResult, ParserFactory, type PatternEntropy, ProductivityImpact, PythonParser, SEVERITY_TIME_ESTIMATES, ScanOptions, type ScoreHistoryEntry, type ScoreTrend, type SemanticDistance, SpokeOutput, type TechnicalDebtInterest, TechnicalValueChain, TechnicalValueChainSummary, type TestabilityIndex, TokenBudget, ToolName, type ToolProvider, ToolRegistry, ToolScoringOutput, TypeScriptParser, VAGUE_FILE_NAMES, calculateAgentGrounding, calculateAiSignalClarity, calculateBusinessROI, calculateChangeAmplification, calculateCognitiveLoad, calculateComprehensionDifficulty, calculateConceptCohesion, calculateDebtInterest, calculateDependencyHealth, calculateDocDrift, calculateExtendedFutureProofScore, calculateFutureProofScore, calculateImportSimilarity, calculateKnowledgeConcentration, calculateMonthlyCost, calculatePatternEntropy, calculateProductivityImpact, calculateSemanticDistance, calculateTechnicalValueChain, calculateTestabilityIndex, calculateTokenBudget, clearHistory, estimateCostFromBudget, estimateTokens, exportHistory, extractFunctions, extractImports, formatAcceptanceRate, formatCost, formatHours, generateValueChain, getElapsedTime, getFileCommitTimestamps, getFileExtension, getHistorySummary, getLineRangeLastModifiedCached, getModelPreset, getParser, getRepoMetadata, getSafetyIcon, getScoreBar, getSeverityColor, getSupportedLanguages, handleCLIError, handleJSONOutput, isFileSupported, isSourceFile, loadConfig, loadMergedConfig, loadScoreHistory, mergeConfigWithDefaults, parseCode, parseFileExports, predictAcceptanceRate, readFileContent, resolveOutputPath, saveScoreEntry, scanEntries, scanFiles, validateSpokeOutput, validateWithSchema };
package/dist/index.js CHANGED
@@ -56,9 +56,11 @@ __export(index_exports, {
56
56
  Severity: () => Severity,
57
57
  SeveritySchema: () => SeveritySchema,
58
58
  SpokeOutputSchema: () => SpokeOutputSchema,
59
+ SpokeSummarySchema: () => SpokeSummarySchema,
59
60
  TOOL_NAME_MAP: () => TOOL_NAME_MAP,
60
61
  ToolName: () => ToolName,
61
62
  ToolNameSchema: () => ToolNameSchema,
63
+ ToolRegistry: () => ToolRegistry,
62
64
  TypeScriptParser: () => TypeScriptParser,
63
65
  UnifiedReportSchema: () => UnifiedReportSchema,
64
66
  VAGUE_FILE_NAMES: () => VAGUE_FILE_NAMES,
@@ -256,13 +258,20 @@ var AnalysisResultSchema = import_zod.z.object({
256
258
  issues: import_zod.z.array(IssueSchema),
257
259
  metrics: MetricsSchema
258
260
  });
261
+ var SpokeSummarySchema = import_zod.z.object({
262
+ totalFiles: import_zod.z.number().optional(),
263
+ totalIssues: import_zod.z.number().optional(),
264
+ criticalIssues: import_zod.z.number().optional(),
265
+ majorIssues: import_zod.z.number().optional(),
266
+ score: import_zod.z.number().optional()
267
+ }).catchall(import_zod.z.any());
259
268
  var SpokeOutputSchema = import_zod.z.object({
260
269
  results: import_zod.z.array(AnalysisResultSchema),
261
- summary: import_zod.z.any(),
270
+ summary: SpokeSummarySchema,
262
271
  metadata: import_zod.z.object({
263
272
  toolName: import_zod.z.string(),
264
- version: import_zod.z.string(),
265
- timestamp: import_zod.z.string()
273
+ version: import_zod.z.string().optional(),
274
+ timestamp: import_zod.z.string().optional()
266
275
  }).catchall(import_zod.z.any()).optional()
267
276
  });
268
277
  var UnifiedReportSchema = import_zod.z.object({
@@ -375,6 +384,65 @@ function validateWithSchema(schema, data) {
375
384
  };
376
385
  }
377
386
 
387
+ // src/registry.ts
388
+ var ToolRegistry = class {
389
+ static getProviders() {
390
+ const g = globalThis;
391
+ if (!g.__AIRE_TOOL_REGISTRY__) {
392
+ g.__AIRE_TOOL_REGISTRY__ = /* @__PURE__ */ new Map();
393
+ }
394
+ return g.__AIRE_TOOL_REGISTRY__;
395
+ }
396
+ /**
397
+ * Register a new tool provider.
398
+ */
399
+ static register(provider) {
400
+ console.log(
401
+ `[ToolRegistry#${this.instanceId}] Registering tool: ${provider.id} (${provider.alias.join(", ")})`
402
+ );
403
+ this.getProviders().set(provider.id, provider);
404
+ }
405
+ /**
406
+ * Get a provider by its canonical ID.
407
+ */
408
+ static get(id) {
409
+ return this.getProviders().get(id);
410
+ }
411
+ /**
412
+ * Get a provider by name or alias.
413
+ */
414
+ static find(nameOrAlias) {
415
+ const providers = this.getProviders();
416
+ const exact = providers.get(nameOrAlias);
417
+ if (exact) return exact;
418
+ for (const p of providers.values()) {
419
+ if (p.alias.includes(nameOrAlias)) return p;
420
+ }
421
+ return void 0;
422
+ }
423
+ /**
424
+ * Get all registered tool providers.
425
+ */
426
+ static getAll() {
427
+ return Array.from(this.getProviders().values());
428
+ }
429
+ /**
430
+ * Get all available tool IDs from the ToolName enum.
431
+ */
432
+ static getAvailableIds() {
433
+ return Object.values(ToolName).filter(
434
+ (v) => typeof v === "string"
435
+ );
436
+ }
437
+ /**
438
+ * Clear the registry (primarily for testing).
439
+ */
440
+ static clear() {
441
+ this.getProviders().clear();
442
+ }
443
+ };
444
+ ToolRegistry.instanceId = globalThis.Math.random();
445
+
378
446
  // src/utils/file-scanner.ts
379
447
  var import_glob = require("glob");
380
448
  var import_promises = require("fs/promises");
@@ -467,7 +535,13 @@ async function scanFiles(options) {
467
535
  ignoreFromFile = [];
468
536
  }
469
537
  }
470
- const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
538
+ const TEST_PATTERNS = [
539
+ "**/*.test.*",
540
+ "**/*.spec.*",
541
+ "**/__tests__/**",
542
+ "**/test/**",
543
+ "**/tests/**"
544
+ ];
471
545
  const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
472
546
  const finalExclude = [
473
547
  .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
@@ -488,12 +562,19 @@ async function scanFiles(options) {
488
562
  for (const gitignorePath of gitignoreFiles) {
489
563
  const gitTxt = await (0, import_promises.readFile)(gitignorePath, "utf-8");
490
564
  const gitignoreDir = (0, import_path.dirname)(gitignorePath);
491
- const relativePrefix = (0, import_path.relative)(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
565
+ const relativePrefix = (0, import_path.relative)(rootDir || ".", gitignoreDir).replace(
566
+ /\\/g,
567
+ "/"
568
+ );
492
569
  const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
493
570
  if (relativePrefix === "." || relativePrefix === "") {
494
571
  ig.add(patterns);
495
572
  } else {
496
- ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
573
+ ig.add(
574
+ patterns.map(
575
+ (p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`
576
+ )
577
+ );
497
578
  }
498
579
  }
499
580
  const filtered = files.filter((f) => {
@@ -521,9 +602,17 @@ async function scanEntries(options) {
521
602
  ignoreFromFile = [];
522
603
  }
523
604
  }
524
- const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
605
+ const TEST_PATTERNS = [
606
+ "**/*.test.*",
607
+ "**/*.spec.*",
608
+ "**/__tests__/**",
609
+ "**/test/**",
610
+ "**/tests/**"
611
+ ];
525
612
  const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
526
- const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])];
613
+ const finalExclude = [
614
+ .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
615
+ ];
527
616
  const dirs = await (0, import_glob.glob)("**/", {
528
617
  cwd: rootDir,
529
618
  ignore: finalExclude,
@@ -539,16 +628,23 @@ async function scanEntries(options) {
539
628
  for (const gitignorePath of gitignoreFiles) {
540
629
  const gitTxt = await (0, import_promises.readFile)(gitignorePath, "utf-8");
541
630
  const gitignoreDir = (0, import_path.dirname)(gitignorePath);
542
- const relativePrefix = (0, import_path.relative)(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
631
+ const relativePrefix = (0, import_path.relative)(rootDir || ".", gitignoreDir).replace(
632
+ /\\/g,
633
+ "/"
634
+ );
543
635
  const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
544
636
  if (relativePrefix === "." || relativePrefix === "") {
545
637
  ig.add(patterns);
546
638
  } else {
547
- ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
639
+ ig.add(
640
+ patterns.map(
641
+ (p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`
642
+ )
643
+ );
548
644
  }
549
645
  }
550
646
  const filteredDirs = dirs.filter((d) => {
551
- let rel = (0, import_path.relative)(rootDir || ".", d).replace(/\\/g, "/").replace(/\/$/, "");
647
+ const rel = (0, import_path.relative)(rootDir || ".", d).replace(/\\/g, "/").replace(/\/$/, "");
552
648
  if (rel === "") return true;
553
649
  return !ig.ignores(rel);
554
650
  });
@@ -2844,7 +2940,7 @@ function calculateChangeAmplification(params) {
2844
2940
  const hotspots = files.map((f) => ({ ...f, amplificationFactor: f.fanOut + f.fanIn * 0.5 })).sort((a, b) => b.amplificationFactor - a.amplificationFactor);
2845
2941
  const maxAmplification = hotspots[0].amplificationFactor;
2846
2942
  const avgAmplification = hotspots.reduce((sum, h) => sum + h.amplificationFactor, 0) / hotspots.length;
2847
- let score = Math.max(
2943
+ const score = Math.max(
2848
2944
  0,
2849
2945
  Math.min(
2850
2946
  100,
@@ -3190,9 +3286,11 @@ function getRepoMetadata(directory) {
3190
3286
  Severity,
3191
3287
  SeveritySchema,
3192
3288
  SpokeOutputSchema,
3289
+ SpokeSummarySchema,
3193
3290
  TOOL_NAME_MAP,
3194
3291
  ToolName,
3195
3292
  ToolNameSchema,
3293
+ ToolRegistry,
3196
3294
  TypeScriptParser,
3197
3295
  UnifiedReportSchema,
3198
3296
  VAGUE_FILE_NAMES,
package/dist/index.mjs CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  Severity,
20
20
  SeveritySchema,
21
21
  SpokeOutputSchema,
22
+ SpokeSummarySchema,
22
23
  TOOL_NAME_MAP,
23
24
  ToolName,
24
25
  ToolNameSchema,
@@ -35,7 +36,7 @@ import {
35
36
  getToolWeight,
36
37
  normalizeToolName,
37
38
  parseWeightString
38
- } from "./chunk-QAFB3HXQ.mjs";
39
+ } from "./chunk-YCA4FTEK.mjs";
39
40
 
40
41
  // src/types/contract.ts
41
42
  function validateSpokeOutput(toolName, output) {
@@ -95,6 +96,65 @@ function validateWithSchema(schema, data) {
95
96
  };
96
97
  }
97
98
 
99
+ // src/registry.ts
100
+ var ToolRegistry = class {
101
+ static getProviders() {
102
+ const g = globalThis;
103
+ if (!g.__AIRE_TOOL_REGISTRY__) {
104
+ g.__AIRE_TOOL_REGISTRY__ = /* @__PURE__ */ new Map();
105
+ }
106
+ return g.__AIRE_TOOL_REGISTRY__;
107
+ }
108
+ /**
109
+ * Register a new tool provider.
110
+ */
111
+ static register(provider) {
112
+ console.log(
113
+ `[ToolRegistry#${this.instanceId}] Registering tool: ${provider.id} (${provider.alias.join(", ")})`
114
+ );
115
+ this.getProviders().set(provider.id, provider);
116
+ }
117
+ /**
118
+ * Get a provider by its canonical ID.
119
+ */
120
+ static get(id) {
121
+ return this.getProviders().get(id);
122
+ }
123
+ /**
124
+ * Get a provider by name or alias.
125
+ */
126
+ static find(nameOrAlias) {
127
+ const providers = this.getProviders();
128
+ const exact = providers.get(nameOrAlias);
129
+ if (exact) return exact;
130
+ for (const p of providers.values()) {
131
+ if (p.alias.includes(nameOrAlias)) return p;
132
+ }
133
+ return void 0;
134
+ }
135
+ /**
136
+ * Get all registered tool providers.
137
+ */
138
+ static getAll() {
139
+ return Array.from(this.getProviders().values());
140
+ }
141
+ /**
142
+ * Get all available tool IDs from the ToolName enum.
143
+ */
144
+ static getAvailableIds() {
145
+ return Object.values(ToolName).filter(
146
+ (v) => typeof v === "string"
147
+ );
148
+ }
149
+ /**
150
+ * Clear the registry (primarily for testing).
151
+ */
152
+ static clear() {
153
+ this.getProviders().clear();
154
+ }
155
+ };
156
+ ToolRegistry.instanceId = globalThis.Math.random();
157
+
98
158
  // src/utils/file-scanner.ts
99
159
  import { glob } from "glob";
100
160
  import { readFile } from "fs/promises";
@@ -187,7 +247,13 @@ async function scanFiles(options) {
187
247
  ignoreFromFile = [];
188
248
  }
189
249
  }
190
- const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
250
+ const TEST_PATTERNS = [
251
+ "**/*.test.*",
252
+ "**/*.spec.*",
253
+ "**/__tests__/**",
254
+ "**/test/**",
255
+ "**/tests/**"
256
+ ];
191
257
  const baseExclude = options.includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
192
258
  const finalExclude = [
193
259
  .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
@@ -208,12 +274,19 @@ async function scanFiles(options) {
208
274
  for (const gitignorePath of gitignoreFiles) {
209
275
  const gitTxt = await readFile(gitignorePath, "utf-8");
210
276
  const gitignoreDir = dirname(gitignorePath);
211
- const relativePrefix = relative(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
277
+ const relativePrefix = relative(rootDir || ".", gitignoreDir).replace(
278
+ /\\/g,
279
+ "/"
280
+ );
212
281
  const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
213
282
  if (relativePrefix === "." || relativePrefix === "") {
214
283
  ig.add(patterns);
215
284
  } else {
216
- ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
285
+ ig.add(
286
+ patterns.map(
287
+ (p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`
288
+ )
289
+ );
217
290
  }
218
291
  }
219
292
  const filtered = files.filter((f) => {
@@ -241,9 +314,17 @@ async function scanEntries(options) {
241
314
  ignoreFromFile = [];
242
315
  }
243
316
  }
244
- const TEST_PATTERNS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/test/**", "**/tests/**"];
317
+ const TEST_PATTERNS = [
318
+ "**/*.test.*",
319
+ "**/*.spec.*",
320
+ "**/__tests__/**",
321
+ "**/test/**",
322
+ "**/tests/**"
323
+ ];
245
324
  const baseExclude = includeTests ? DEFAULT_EXCLUDE.filter((p) => !TEST_PATTERNS.includes(p)) : DEFAULT_EXCLUDE;
246
- const finalExclude = [.../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])];
325
+ const finalExclude = [
326
+ .../* @__PURE__ */ new Set([...exclude || [], ...ignoreFromFile, ...baseExclude])
327
+ ];
247
328
  const dirs = await glob("**/", {
248
329
  cwd: rootDir,
249
330
  ignore: finalExclude,
@@ -259,16 +340,23 @@ async function scanEntries(options) {
259
340
  for (const gitignorePath of gitignoreFiles) {
260
341
  const gitTxt = await readFile(gitignorePath, "utf-8");
261
342
  const gitignoreDir = dirname(gitignorePath);
262
- const relativePrefix = relative(rootDir || ".", gitignoreDir).replace(/\\/g, "/");
343
+ const relativePrefix = relative(rootDir || ".", gitignoreDir).replace(
344
+ /\\/g,
345
+ "/"
346
+ );
263
347
  const patterns = gitTxt.split(/\r?\n/).map((s) => s.trim()).filter(Boolean).filter((l) => !l.startsWith("#"));
264
348
  if (relativePrefix === "." || relativePrefix === "") {
265
349
  ig.add(patterns);
266
350
  } else {
267
- ig.add(patterns.map((p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`));
351
+ ig.add(
352
+ patterns.map(
353
+ (p) => p.startsWith("/") ? `${relativePrefix}${p}` : `${relativePrefix}/**/${p}`
354
+ )
355
+ );
268
356
  }
269
357
  }
270
358
  const filteredDirs = dirs.filter((d) => {
271
- let rel = relative(rootDir || ".", d).replace(/\\/g, "/").replace(/\/$/, "");
359
+ const rel = relative(rootDir || ".", d).replace(/\\/g, "/").replace(/\/$/, "");
272
360
  if (rel === "") return true;
273
361
  return !ig.ignores(rel);
274
362
  });
@@ -2207,7 +2295,7 @@ function calculateChangeAmplification(params) {
2207
2295
  const hotspots = files.map((f) => ({ ...f, amplificationFactor: f.fanOut + f.fanIn * 0.5 })).sort((a, b) => b.amplificationFactor - a.amplificationFactor);
2208
2296
  const maxAmplification = hotspots[0].amplificationFactor;
2209
2297
  const avgAmplification = hotspots.reduce((sum, h) => sum + h.amplificationFactor, 0) / hotspots.length;
2210
- let score = Math.max(
2298
+ const score = Math.max(
2211
2299
  0,
2212
2300
  Math.min(
2213
2301
  100,
@@ -2552,9 +2640,11 @@ export {
2552
2640
  Severity,
2553
2641
  SeveritySchema,
2554
2642
  SpokeOutputSchema,
2643
+ SpokeSummarySchema,
2555
2644
  TOOL_NAME_MAP,
2556
2645
  ToolName,
2557
2646
  ToolNameSchema,
2647
+ ToolRegistry,
2558
2648
  TypeScriptParser,
2559
2649
  UnifiedReportSchema,
2560
2650
  VAGUE_FILE_NAMES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/core",
3
- "version": "0.19.5",
3
+ "version": "0.21.0",
4
4
  "description": "Shared utilities for AIReady analysis tools",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",