crackerjack 0.30.3__py3-none-any.whl → 0.31.4__py3-none-any.whl

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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -299
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +652 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +401 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +618 -928
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -0
  40. crackerjack/dynamic_config.py +94 -103
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +615 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +370 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/crackerjack.py +0 -3805
  151. crackerjack/pyproject.toml +0 -286
  152. crackerjack-0.30.3.dist-info/METADATA +0 -1290
  153. crackerjack-0.30.3.dist-info/RECORD +0 -16
  154. {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  155. {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,414 @@
1
+ """Intelligent Agent Registry System.
2
+
3
+ Unifies access to crackerjack agents, user agents, and system agents with smart
4
+ prioritization and capability mapping.
5
+ """
6
+
7
+ import logging
8
+ import typing as t
9
+ from dataclasses import dataclass
10
+ from enum import Enum
11
+ from pathlib import Path
12
+
13
+ from crackerjack.agents.base import SubAgent, agent_registry
14
+
15
+
16
+ class AgentSource(Enum):
17
+ """Source of an agent."""
18
+
19
+ CRACKERJACK = "crackerjack"
20
+ USER = "user"
21
+ SYSTEM = "system"
22
+
23
+
24
+ class AgentCapability(Enum):
25
+ """Agent capability categories."""
26
+
27
+ ARCHITECTURE = "architecture"
28
+ REFACTORING = "refactoring"
29
+ TESTING = "testing"
30
+ SECURITY = "security"
31
+ PERFORMANCE = "performance"
32
+ DOCUMENTATION = "documentation"
33
+ FORMATTING = "formatting"
34
+ DEBUGGING = "debugging"
35
+ CODE_ANALYSIS = "code_analysis"
36
+ PROJECT_MANAGEMENT = "project_management"
37
+
38
+
39
+ @dataclass
40
+ class AgentMetadata:
41
+ """Metadata about an agent."""
42
+
43
+ name: str
44
+ source: AgentSource
45
+ capabilities: set[AgentCapability]
46
+ priority: int # Higher is better
47
+ confidence_factor: float # Multiplier for confidence scores
48
+ description: str
49
+ model: str | None = None
50
+ tags: list[str] | None = None
51
+
52
+
53
+ @dataclass
54
+ class RegisteredAgent:
55
+ """A registered agent with metadata and access."""
56
+
57
+ metadata: AgentMetadata
58
+ agent: SubAgent | None = None # None for user/system agents
59
+ agent_path: Path | None = None # Path to user agent file
60
+ subagent_type: str | None = None # For system agents
61
+
62
+
63
+ class AgentRegistry:
64
+ """Registry of all available agents across all systems."""
65
+
66
+ def __init__(self) -> None:
67
+ self.logger = logging.getLogger(__name__)
68
+ self._agents: dict[str, RegisteredAgent] = {}
69
+ self._capability_map: dict[AgentCapability, list[str]] = {}
70
+ self._user_agent_cache: dict[str, dict[str, t.Any]] = {}
71
+
72
+ async def initialize(self) -> None:
73
+ """Initialize the registry with all available agents."""
74
+ self.logger.info("Initializing Intelligent Agent Registry")
75
+
76
+ # Load agents in priority order
77
+ await self._register_crackerjack_agents()
78
+ await self._register_user_agents()
79
+ await self._register_system_agents()
80
+
81
+ # Build capability mapping
82
+ self._build_capability_map()
83
+
84
+ self.logger.info(
85
+ f"Registry initialized with {len(self._agents)} agents: "
86
+ f"{len([a for a in self._agents.values() if a.metadata.source == AgentSource.CRACKERJACK])} crackerjack, "
87
+ f"{len([a for a in self._agents.values() if a.metadata.source == AgentSource.USER])} user, "
88
+ f"{len([a for a in self._agents.values() if a.metadata.source == AgentSource.SYSTEM])} system"
89
+ )
90
+
91
+ async def _register_crackerjack_agents(self) -> None:
92
+ """Register built-in crackerjack agents."""
93
+ self.logger.debug("Registering crackerjack agents")
94
+
95
+ # Get all registered crackerjack agents
96
+ from crackerjack.agents.base import AgentContext
97
+
98
+ # Create a dummy context to get agents
99
+ context = AgentContext(
100
+ project_path=Path.cwd(),
101
+ )
102
+
103
+ crackerjack_agents = agent_registry.create_all(context)
104
+
105
+ for agent in crackerjack_agents:
106
+ capabilities = self._infer_capabilities_from_agent(agent)
107
+
108
+ metadata = AgentMetadata(
109
+ name=agent.name,
110
+ source=AgentSource.CRACKERJACK,
111
+ capabilities=capabilities,
112
+ priority=100, # Highest priority
113
+ confidence_factor=1.0,
114
+ description=f"Built-in crackerjack {agent.__class__.__name__}",
115
+ )
116
+
117
+ registered = RegisteredAgent(
118
+ metadata=metadata,
119
+ agent=agent,
120
+ )
121
+
122
+ self._agents[agent.name] = registered
123
+ self.logger.debug(f"Registered crackerjack agent: {agent.name}")
124
+
125
+ async def _register_user_agents(self) -> None:
126
+ """Register user agents from ~/.claude/agents/."""
127
+ self.logger.debug("Registering user agents")
128
+
129
+ user_agents_dir = Path.home() / ".claude" / "agents"
130
+ if not user_agents_dir.exists():
131
+ self.logger.debug("No user agents directory found")
132
+ return
133
+
134
+ for agent_file in user_agents_dir.glob("*.md"):
135
+ try:
136
+ agent_data = await self._parse_user_agent_file(agent_file)
137
+ if agent_data:
138
+ capabilities = self._infer_capabilities_from_user_agent(agent_data)
139
+
140
+ metadata = AgentMetadata(
141
+ name=agent_data["name"],
142
+ source=AgentSource.USER,
143
+ capabilities=capabilities,
144
+ priority=80, # Second priority
145
+ confidence_factor=0.9,
146
+ description=agent_data.get("description", "User agent"),
147
+ model=agent_data.get("model"),
148
+ tags=agent_data.get("tags", []),
149
+ )
150
+
151
+ registered = RegisteredAgent(
152
+ metadata=metadata,
153
+ agent_path=agent_file,
154
+ )
155
+
156
+ self._agents[agent_data["name"]] = registered
157
+ self._user_agent_cache[agent_data["name"]] = agent_data
158
+ self.logger.debug(f"Registered user agent: {agent_data['name']}")
159
+
160
+ except Exception as e:
161
+ self.logger.warning(f"Failed to parse user agent {agent_file}: {e}")
162
+
163
+ async def _register_system_agents(self) -> None:
164
+ """Register built-in system agents from Task tool."""
165
+ self.logger.debug("Registering system agents")
166
+
167
+ # Known system agents from Task tool
168
+ system_agents = [
169
+ (
170
+ "general-purpose",
171
+ "General-purpose agent for researching complex questions",
172
+ ),
173
+ (
174
+ "statusline-setup",
175
+ "Configure the user's Claude Code status line setting",
176
+ ),
177
+ ("output-style-setup", "Create a Claude Code output style"),
178
+ ]
179
+
180
+ for agent_name, description in system_agents:
181
+ capabilities = self._infer_capabilities_from_system_agent(
182
+ agent_name, description
183
+ )
184
+
185
+ metadata = AgentMetadata(
186
+ name=agent_name,
187
+ source=AgentSource.SYSTEM,
188
+ capabilities=capabilities,
189
+ priority=60, # Lowest priority
190
+ confidence_factor=0.7,
191
+ description=description,
192
+ )
193
+
194
+ registered = RegisteredAgent(
195
+ metadata=metadata,
196
+ subagent_type=agent_name,
197
+ )
198
+
199
+ self._agents[agent_name] = registered
200
+ self.logger.debug(f"Registered system agent: {agent_name}")
201
+
202
+ async def _parse_user_agent_file(self, agent_file: Path) -> dict[str, t.Any] | None:
203
+ """Parse a user agent markdown file."""
204
+ try:
205
+ content = agent_file.read_text(encoding="utf-8")
206
+ return self._extract_agent_data_from_content(content)
207
+ except Exception as e:
208
+ self.logger.error(f"Error parsing agent file {agent_file}: {e}")
209
+ return None
210
+
211
+ def _extract_agent_data_from_content(self, content: str) -> dict[str, t.Any] | None:
212
+ """Extract agent data from file content."""
213
+ if not content.startswith("---\n"):
214
+ return None
215
+
216
+ lines = content.split("\n")
217
+ yaml_end = self._find_yaml_end_marker(lines)
218
+
219
+ if yaml_end == -1:
220
+ return None
221
+
222
+ return self._build_agent_data(lines, yaml_end)
223
+
224
+ def _find_yaml_end_marker(self, lines: list[str]) -> int:
225
+ """Find the end marker for YAML frontmatter."""
226
+ for i, line in enumerate(lines[1:], 1):
227
+ if line == "---":
228
+ return i
229
+ return -1
230
+
231
+ def _build_agent_data(self, lines: list[str], yaml_end: int) -> dict[str, t.Any]:
232
+ """Build agent data from parsed lines."""
233
+ yaml_lines = lines[1:yaml_end]
234
+ agent_data = {}
235
+
236
+ for line in yaml_lines:
237
+ if ":" in line:
238
+ key, value = line.split(":", 1)
239
+ agent_data[key.strip()] = value.strip()
240
+
241
+ agent_data["content"] = "\n".join(lines[yaml_end + 1 :])
242
+ return agent_data
243
+
244
+ def _infer_capabilities_from_agent(self, agent: SubAgent) -> set[AgentCapability]:
245
+ """Infer capabilities from a crackerjack agent."""
246
+ capabilities = set()
247
+
248
+ # Map agent class names to capabilities
249
+ class_name = agent.__class__.__name__.lower()
250
+
251
+ if "architect" in class_name:
252
+ capabilities.update(
253
+ {AgentCapability.ARCHITECTURE, AgentCapability.CODE_ANALYSIS}
254
+ )
255
+ if "refactor" in class_name:
256
+ capabilities.add(AgentCapability.REFACTORING)
257
+ if "test" in class_name:
258
+ capabilities.add(AgentCapability.TESTING)
259
+ if "security" in class_name:
260
+ capabilities.add(AgentCapability.SECURITY)
261
+ if "performance" in class_name:
262
+ capabilities.add(AgentCapability.PERFORMANCE)
263
+ if "documentation" in class_name or "doc" in class_name:
264
+ capabilities.add(AgentCapability.DOCUMENTATION)
265
+ if "format" in class_name:
266
+ capabilities.add(AgentCapability.FORMATTING)
267
+ if "import" in class_name:
268
+ capabilities.add(AgentCapability.CODE_ANALYSIS)
269
+ if "dry" in class_name:
270
+ capabilities.add(AgentCapability.REFACTORING)
271
+
272
+ # Fallback to general code analysis
273
+ if not capabilities:
274
+ capabilities.add(AgentCapability.CODE_ANALYSIS)
275
+
276
+ return capabilities
277
+
278
+ def _infer_capabilities_from_user_agent(
279
+ self, agent_data: dict[str, t.Any]
280
+ ) -> set[AgentCapability]:
281
+ """Infer capabilities from user agent metadata."""
282
+ capabilities = set()
283
+
284
+ name = agent_data.get("name", "").lower()
285
+ description = agent_data.get("description", "").lower()
286
+ content = agent_data.get("content", "").lower()
287
+
288
+ text = f"{name} {description} {content}"
289
+
290
+ # Keyword mapping
291
+ keyword_map = {
292
+ AgentCapability.ARCHITECTURE: [
293
+ "architect",
294
+ "design",
295
+ "structure",
296
+ "pattern",
297
+ ],
298
+ AgentCapability.REFACTORING: ["refactor", "clean", "improve", "optimize"],
299
+ AgentCapability.TESTING: ["test", "pytest", "coverage", "mock"],
300
+ AgentCapability.SECURITY: ["security", "secure", "vulnerability", "audit"],
301
+ AgentCapability.PERFORMANCE: [
302
+ "performance",
303
+ "speed",
304
+ "optimize",
305
+ "efficient",
306
+ ],
307
+ AgentCapability.DOCUMENTATION: ["document", "readme", "comment", "explain"],
308
+ AgentCapability.FORMATTING: ["format", "style", "lint", "ruff"],
309
+ AgentCapability.DEBUGGING: ["debug", "fix", "error", "troubleshoot"],
310
+ AgentCapability.CODE_ANALYSIS: ["analyze", "review", "inspect", "examine"],
311
+ AgentCapability.PROJECT_MANAGEMENT: [
312
+ "project",
313
+ "manage",
314
+ "organize",
315
+ "workflow",
316
+ ],
317
+ }
318
+
319
+ for capability, keywords in keyword_map.items():
320
+ if any(keyword in text for keyword in keywords):
321
+ capabilities.add(capability)
322
+
323
+ # Fallback
324
+ if not capabilities:
325
+ capabilities.add(AgentCapability.CODE_ANALYSIS)
326
+
327
+ return capabilities
328
+
329
+ def _infer_capabilities_from_system_agent(
330
+ self, name: str, description: str
331
+ ) -> set[AgentCapability]:
332
+ """Infer capabilities from system agent."""
333
+ capabilities = set()
334
+
335
+ text = f"{name} {description}".lower()
336
+
337
+ if "general" in text or "research" in text:
338
+ capabilities.add(AgentCapability.CODE_ANALYSIS)
339
+ if "statusline" in text or "setup" in text:
340
+ capabilities.add(AgentCapability.PROJECT_MANAGEMENT)
341
+ if "output" in text or "style" in text:
342
+ capabilities.add(AgentCapability.FORMATTING)
343
+
344
+ # Fallback
345
+ if not capabilities:
346
+ capabilities.add(AgentCapability.CODE_ANALYSIS)
347
+
348
+ return capabilities
349
+
350
+ def _build_capability_map(self) -> None:
351
+ """Build mapping from capabilities to agent names."""
352
+ self._capability_map.clear()
353
+
354
+ for agent_name, registered_agent in self._agents.items():
355
+ for capability in registered_agent.metadata.capabilities:
356
+ if capability not in self._capability_map:
357
+ self._capability_map[capability] = []
358
+ self._capability_map[capability].append(agent_name)
359
+
360
+ # Sort by priority within each capability
361
+ for agent_names in self._capability_map.values():
362
+ agent_names.sort(
363
+ key=lambda name: self._agents[name].metadata.priority, reverse=True
364
+ )
365
+
366
+ def get_agents_by_capability(
367
+ self, capability: AgentCapability
368
+ ) -> list[RegisteredAgent]:
369
+ """Get agents that have a specific capability, sorted by priority."""
370
+ agent_names = self._capability_map.get(capability, [])
371
+ return [self._agents[name] for name in agent_names]
372
+
373
+ def get_agent_by_name(self, name: str) -> RegisteredAgent | None:
374
+ """Get a specific agent by name."""
375
+ return self._agents.get(name)
376
+
377
+ def list_all_agents(self) -> list[RegisteredAgent]:
378
+ """List all registered agents, sorted by priority."""
379
+ agents = list(self._agents.values())
380
+ agents.sort(key=lambda a: a.metadata.priority, reverse=True)
381
+ return agents
382
+
383
+ def get_agent_stats(self) -> dict[str, t.Any]:
384
+ """Get statistics about registered agents."""
385
+ stats: dict[str, t.Any] = {
386
+ "total_agents": len(self._agents),
387
+ "by_source": {},
388
+ "by_capability": {},
389
+ }
390
+
391
+ # Count by source
392
+ for source in AgentSource:
393
+ count = len(
394
+ [a for a in self._agents.values() if a.metadata.source == source]
395
+ )
396
+ stats["by_source"][source.value] = count
397
+
398
+ # Count by capability
399
+ for capability in AgentCapability:
400
+ count = len(self._capability_map.get(capability, []))
401
+ stats["by_capability"][capability.value] = count
402
+
403
+ return stats
404
+
405
+
406
+ # Global registry instance
407
+ agent_registry_instance = AgentRegistry()
408
+
409
+
410
+ async def get_agent_registry() -> AgentRegistry:
411
+ """Get the initialized agent registry."""
412
+ if not agent_registry_instance._agents:
413
+ await agent_registry_instance.initialize()
414
+ return agent_registry_instance