crackerjack 0.29.0__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 (158) 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 -253
  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 +670 -0
  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 +577 -0
  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/.pre-commit-config-ai.yaml +0 -149
  151. crackerjack/.pre-commit-config-fast.yaml +0 -69
  152. crackerjack/.pre-commit-config.yaml +0 -114
  153. crackerjack/crackerjack.py +0 -4140
  154. crackerjack/pyproject.toml +0 -285
  155. crackerjack-0.29.0.dist-info/METADATA +0 -1289
  156. crackerjack-0.29.0.dist-info/RECORD +0 -17
  157. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  158. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,502 @@
1
+ """Smart Agent Selection Engine.
2
+
3
+ Intelligently selects the best agents for tasks based on context analysis,
4
+ capability matching, and priority scoring.
5
+ """
6
+
7
+ import logging
8
+ import re
9
+ import typing as t
10
+ from dataclasses import dataclass
11
+ from enum import Enum
12
+
13
+ from .agent_registry import (
14
+ AgentCapability,
15
+ AgentRegistry,
16
+ RegisteredAgent,
17
+ get_agent_registry,
18
+ )
19
+
20
+
21
+ class TaskContext(Enum):
22
+ """Context categories for tasks."""
23
+
24
+ CODE_QUALITY = "code_quality"
25
+ ARCHITECTURE = "architecture"
26
+ TESTING = "testing"
27
+ REFACTORING = "refactoring"
28
+ DOCUMENTATION = "documentation"
29
+ SECURITY = "security"
30
+ PERFORMANCE = "performance"
31
+ DEBUGGING = "debugging"
32
+ PROJECT_SETUP = "project_setup"
33
+ GENERAL = "general"
34
+
35
+
36
+ @dataclass
37
+ class TaskDescription:
38
+ """Description of a task to be executed."""
39
+
40
+ description: str
41
+ context: TaskContext | None = None
42
+ keywords: list[str] | None = None
43
+ file_patterns: list[str] | None = None
44
+ error_types: list[str] | None = None
45
+ priority: int = 50 # 0-100, higher is more important
46
+
47
+
48
+ @dataclass
49
+ class AgentScore:
50
+ """Score for an agent's suitability for a task."""
51
+
52
+ agent: RegisteredAgent
53
+ base_score: float # 0-1 based on capability match
54
+ context_score: float # 0-1 based on context match
55
+ priority_bonus: float # 0-1 based on agent priority
56
+ confidence_factor: float # Multiplier from agent metadata
57
+ final_score: float # Combined weighted score
58
+ reasoning: str # Explanation of the score
59
+
60
+
61
+ class AgentSelector:
62
+ """Intelligent agent selection engine."""
63
+
64
+ def __init__(self, registry: AgentRegistry | None = None) -> None:
65
+ self.logger = logging.getLogger(__name__)
66
+ self.registry = registry
67
+ self._task_patterns: dict[str, list[AgentCapability]] = {}
68
+ self._initialize_task_patterns()
69
+
70
+ def _initialize_task_patterns(self) -> None:
71
+ """Initialize patterns for task analysis."""
72
+ self._task_patterns = {
73
+ # Architecture patterns
74
+ r"architect|design|structure|pattern|refactor.*complex": [
75
+ AgentCapability.ARCHITECTURE,
76
+ AgentCapability.REFACTORING,
77
+ ],
78
+ # Code quality patterns
79
+ r"refurb|ruff|format|lint|style|clean.*code": [
80
+ AgentCapability.FORMATTING,
81
+ AgentCapability.CODE_ANALYSIS,
82
+ ],
83
+ # Testing patterns
84
+ r"test|pytest|coverage|mock|fixture": [
85
+ AgentCapability.TESTING,
86
+ ],
87
+ # Security patterns
88
+ r"security|vulnerability|audit|bandit|safe": [
89
+ AgentCapability.SECURITY,
90
+ ],
91
+ # Performance patterns
92
+ r"performance|optimize|speed|efficient|complexity": [
93
+ AgentCapability.PERFORMANCE,
94
+ AgentCapability.CODE_ANALYSIS,
95
+ ],
96
+ # Documentation patterns
97
+ r"document|readme|comment|explain|changelog": [
98
+ AgentCapability.DOCUMENTATION,
99
+ ],
100
+ # Refactoring patterns
101
+ r"refactor|improve|simplify|dry.*violation": [
102
+ AgentCapability.REFACTORING,
103
+ AgentCapability.CODE_ANALYSIS,
104
+ ],
105
+ # Debugging patterns
106
+ r"debug|fix|error|bug|failure": [
107
+ AgentCapability.DEBUGGING,
108
+ AgentCapability.CODE_ANALYSIS,
109
+ ],
110
+ }
111
+
112
+ async def get_registry(self) -> AgentRegistry:
113
+ """Get or initialize the agent registry."""
114
+ if self.registry is None:
115
+ self.registry = await get_agent_registry()
116
+ return self.registry
117
+
118
+ async def select_best_agent(
119
+ self,
120
+ task: TaskDescription,
121
+ max_candidates: int = 5,
122
+ ) -> AgentScore | None:
123
+ """Select the single best agent for a task."""
124
+ candidates = await self.select_agents(task, max_candidates)
125
+ return candidates[0] if candidates else None
126
+
127
+ async def select_agents(
128
+ self,
129
+ task: TaskDescription,
130
+ max_candidates: int = 3,
131
+ ) -> list[AgentScore]:
132
+ """Select the best agents for a task, ordered by score."""
133
+ registry = await self.get_registry()
134
+
135
+ # Analyze task to determine required capabilities
136
+ required_capabilities = self._analyze_task_capabilities(task)
137
+
138
+ # Score all agents
139
+ scores: list[AgentScore] = []
140
+
141
+ for agent in registry.list_all_agents():
142
+ score = await self._score_agent_for_task(agent, task, required_capabilities)
143
+ if score.final_score > 0.1: # Minimum threshold
144
+ scores.append(score)
145
+
146
+ # Sort by final score (descending)
147
+ scores.sort(key=lambda s: s.final_score, reverse=True)
148
+
149
+ # Return top candidates
150
+ selected = scores[:max_candidates]
151
+
152
+ self.logger.debug(
153
+ f"Selected {len(selected)} agents for task '{task.description[:50]}...': "
154
+ f"{[f'{s.agent.metadata.name}({s.final_score:.2f})' for s in selected]}"
155
+ )
156
+
157
+ return selected
158
+
159
+ def _analyze_task_capabilities(self, task: TaskDescription) -> set[AgentCapability]:
160
+ """Analyze a task to determine required capabilities."""
161
+ capabilities = set()
162
+
163
+ # Apply different analysis methods
164
+ capabilities.update(self._analyze_text_patterns(task))
165
+ capabilities.update(self._analyze_context(task))
166
+ capabilities.update(self._analyze_file_patterns(task))
167
+ capabilities.update(self._analyze_error_types(task))
168
+
169
+ # Fallback
170
+ return capabilities or {AgentCapability.CODE_ANALYSIS}
171
+
172
+ def _analyze_text_patterns(self, task: TaskDescription) -> set[AgentCapability]:
173
+ """Analyze text patterns in task description and keywords."""
174
+ text = task.description.lower()
175
+ if task.keywords:
176
+ text += " " + " ".join(task.keywords).lower()
177
+
178
+ capabilities = set()
179
+ for pattern, caps in self._task_patterns.items():
180
+ if re.search(pattern, text, re.IGNORECASE):
181
+ capabilities.update(caps)
182
+
183
+ return capabilities
184
+
185
+ def _analyze_context(self, task: TaskDescription) -> set[AgentCapability]:
186
+ """Analyze task context to determine capabilities."""
187
+ if not task.context:
188
+ return set()
189
+
190
+ context_map = {
191
+ TaskContext.CODE_QUALITY: [
192
+ AgentCapability.CODE_ANALYSIS,
193
+ AgentCapability.FORMATTING,
194
+ ],
195
+ TaskContext.ARCHITECTURE: [
196
+ AgentCapability.ARCHITECTURE,
197
+ AgentCapability.CODE_ANALYSIS,
198
+ ],
199
+ TaskContext.TESTING: [AgentCapability.TESTING],
200
+ TaskContext.REFACTORING: [
201
+ AgentCapability.REFACTORING,
202
+ AgentCapability.CODE_ANALYSIS,
203
+ ],
204
+ TaskContext.DOCUMENTATION: [AgentCapability.DOCUMENTATION],
205
+ TaskContext.SECURITY: [AgentCapability.SECURITY],
206
+ TaskContext.PERFORMANCE: [
207
+ AgentCapability.PERFORMANCE,
208
+ AgentCapability.CODE_ANALYSIS,
209
+ ],
210
+ TaskContext.DEBUGGING: [
211
+ AgentCapability.DEBUGGING,
212
+ AgentCapability.CODE_ANALYSIS,
213
+ ],
214
+ TaskContext.PROJECT_SETUP: [AgentCapability.PROJECT_MANAGEMENT],
215
+ TaskContext.GENERAL: [AgentCapability.CODE_ANALYSIS],
216
+ }
217
+
218
+ return set(context_map.get(task.context, []))
219
+
220
+ def _analyze_file_patterns(self, task: TaskDescription) -> set[AgentCapability]:
221
+ """Analyze file patterns to determine capabilities."""
222
+ if not task.file_patterns:
223
+ return set()
224
+
225
+ capabilities = set()
226
+ for pattern in task.file_patterns:
227
+ pattern_lower = pattern.lower()
228
+ if any(ext in pattern_lower for ext in (".py", ".pyi")):
229
+ capabilities.add(AgentCapability.CODE_ANALYSIS)
230
+ if any(test in pattern_lower for test in ("test_", "_test", "tests/")):
231
+ capabilities.add(AgentCapability.TESTING)
232
+
233
+ return capabilities
234
+
235
+ def _analyze_error_types(self, task: TaskDescription) -> set[AgentCapability]:
236
+ """Analyze error types to determine capabilities."""
237
+ if not task.error_types:
238
+ return set()
239
+
240
+ capabilities = set()
241
+ for error_type in task.error_types:
242
+ error_lower = error_type.lower()
243
+ if "furb" in error_lower or "refurb" in error_lower:
244
+ capabilities.update(
245
+ [AgentCapability.REFACTORING, AgentCapability.CODE_ANALYSIS]
246
+ )
247
+ elif "test" in error_lower:
248
+ capabilities.add(AgentCapability.TESTING)
249
+ elif "type" in error_lower or "pyright" in error_lower:
250
+ capabilities.add(AgentCapability.CODE_ANALYSIS)
251
+ elif "security" in error_lower or "bandit" in error_lower:
252
+ capabilities.add(AgentCapability.SECURITY)
253
+
254
+ return capabilities
255
+
256
+ async def _score_agent_for_task(
257
+ self,
258
+ agent: RegisteredAgent,
259
+ task: TaskDescription,
260
+ required_capabilities: set[AgentCapability],
261
+ ) -> AgentScore:
262
+ """Score an agent's suitability for a task."""
263
+ # Base score: capability overlap
264
+ agent_capabilities = agent.metadata.capabilities
265
+ overlap = len(required_capabilities & agent_capabilities)
266
+ max_overlap = len(required_capabilities)
267
+
268
+ base_score = overlap / max_overlap if max_overlap > 0 else 0.0
269
+
270
+ # Context score: how well the agent matches the task context
271
+ context_score = self._calculate_context_score(agent, task)
272
+
273
+ # Priority bonus: normalized agent priority (0-1)
274
+ priority_bonus = min(agent.metadata.priority / 100.0, 1.0)
275
+
276
+ # Confidence factor from agent metadata
277
+ confidence_factor = agent.metadata.confidence_factor
278
+
279
+ # Calculate weighted final score
280
+ weights = {
281
+ "base": 0.4, # Capability match is most important
282
+ "context": 0.3, # Context matching
283
+ "priority": 0.2, # Agent priority (source-based)
284
+ "bonus": 0.1, # Additional considerations
285
+ }
286
+
287
+ weighted_score = (
288
+ base_score * weights["base"]
289
+ + context_score * weights["context"]
290
+ + priority_bonus * weights["priority"]
291
+ )
292
+
293
+ # Apply confidence factor
294
+ final_score = weighted_score * confidence_factor
295
+
296
+ # Generate reasoning
297
+ reasoning = self._generate_score_reasoning(
298
+ agent, base_score, context_score, priority_bonus, required_capabilities
299
+ )
300
+
301
+ return AgentScore(
302
+ agent=agent,
303
+ base_score=base_score,
304
+ context_score=context_score,
305
+ priority_bonus=priority_bonus,
306
+ confidence_factor=confidence_factor,
307
+ final_score=final_score,
308
+ reasoning=reasoning,
309
+ )
310
+
311
+ def _calculate_context_score(
312
+ self, agent: RegisteredAgent, task: TaskDescription
313
+ ) -> float:
314
+ """Calculate how well an agent matches the task context."""
315
+ score = 0.0
316
+
317
+ agent_name_lower = agent.metadata.name.lower()
318
+ task_text = task.description.lower()
319
+
320
+ score += self._score_name_matches(agent_name_lower, task_text)
321
+ score += self._score_description_matches(agent, task_text)
322
+ score += self._score_keyword_matches(agent, task)
323
+ score += self._score_special_patterns(agent_name_lower, task_text)
324
+
325
+ return min(score, 1.0)
326
+
327
+ def _score_name_matches(self, agent_name_lower: str, task_text: str) -> float:
328
+ """Score direct name matches between agent and task."""
329
+ if any(keyword in agent_name_lower for keyword in task_text.split()):
330
+ return 0.3
331
+ return 0.0
332
+
333
+ def _score_description_matches(
334
+ self, agent: RegisteredAgent, task_text: str
335
+ ) -> float:
336
+ """Score description word overlap."""
337
+ if not agent.metadata.description:
338
+ return 0.0
339
+
340
+ desc_words = set(agent.metadata.description.lower().split())
341
+ task_words = set(task_text.split())
342
+ common_words = desc_words & task_words
343
+
344
+ if common_words:
345
+ return len(common_words) / max(len(task_words), 1) * 0.2
346
+ return 0.0
347
+
348
+ def _score_keyword_matches(
349
+ self, agent: RegisteredAgent, task: TaskDescription
350
+ ) -> float:
351
+ """Score keyword/tag overlap."""
352
+ if not task.keywords or not agent.metadata.tags:
353
+ return 0.0
354
+
355
+ task_keywords = {k.lower() for k in task.keywords}
356
+ agent_tags = {t.lower() for t in agent.metadata.tags}
357
+ overlap = len(task_keywords & agent_tags)
358
+
359
+ if overlap > 0:
360
+ return overlap / len(task_keywords) * 0.3
361
+ return 0.0
362
+
363
+ def _score_special_patterns(self, agent_name_lower: str, task_text: str) -> float:
364
+ """Score special pattern bonuses."""
365
+ score = 0.0
366
+
367
+ if "architect" in agent_name_lower and (
368
+ "architect" in task_text or "design" in task_text
369
+ ):
370
+ score += 0.2
371
+ if "refactor" in agent_name_lower and "refurb" in task_text:
372
+ score += 0.2
373
+ if "test" in agent_name_lower and "test" in task_text:
374
+ score += 0.2
375
+
376
+ return score
377
+
378
+ def _generate_score_reasoning(
379
+ self,
380
+ agent: RegisteredAgent,
381
+ base_score: float,
382
+ context_score: float,
383
+ priority_bonus: float,
384
+ required_capabilities: set[AgentCapability],
385
+ ) -> str:
386
+ """Generate human-readable reasoning for the score."""
387
+ parts = []
388
+
389
+ # Capability match
390
+ overlap = len(required_capabilities & agent.metadata.capabilities)
391
+ parts.append(f"Capabilities: {overlap}/{len(required_capabilities)} match")
392
+
393
+ # Context relevance
394
+ if context_score > 0.5:
395
+ parts.append("High context relevance")
396
+ elif context_score > 0.2:
397
+ parts.append("Moderate context relevance")
398
+ else:
399
+ parts.append("Low context relevance")
400
+
401
+ # Source priority
402
+ source_desc = {
403
+ "crackerjack": "Built-in specialist",
404
+ "user": "User agent",
405
+ "system": "System agent",
406
+ }
407
+ parts.append(source_desc.get(agent.metadata.source.value, "Unknown source"))
408
+
409
+ # Special strengths
410
+ if agent.metadata.capabilities:
411
+ top_caps = list(agent.metadata.capabilities)[:2]
412
+ cap_names = [cap.value.replace("_", " ") for cap in top_caps]
413
+ parts.append(f"Strengths: {', '.join(cap_names)}")
414
+
415
+ return " | ".join(parts)
416
+
417
+ async def analyze_task_complexity(self, task: TaskDescription) -> dict[str, t.Any]:
418
+ """Analyze task complexity and provide recommendations."""
419
+ registry = await self.get_registry()
420
+ required_capabilities = self._analyze_task_capabilities(task)
421
+
422
+ # Get all agents that could handle this task
423
+ all_scores = []
424
+ for agent in registry.list_all_agents():
425
+ score = await self._score_agent_for_task(agent, task, required_capabilities)
426
+ if score.final_score > 0.1:
427
+ all_scores.append(score)
428
+
429
+ all_scores.sort(key=lambda s: s.final_score, reverse=True)
430
+
431
+ analysis = {
432
+ "required_capabilities": [cap.value for cap in required_capabilities],
433
+ "complexity_level": self._assess_complexity(
434
+ required_capabilities, all_scores
435
+ ),
436
+ "candidate_count": len(all_scores),
437
+ "top_agents": [
438
+ {
439
+ "name": score.agent.metadata.name,
440
+ "source": score.agent.metadata.source.value,
441
+ "score": score.final_score,
442
+ "reasoning": score.reasoning,
443
+ }
444
+ for score in all_scores[:5]
445
+ ],
446
+ "recommendations": self._generate_recommendations(
447
+ required_capabilities, all_scores
448
+ ),
449
+ }
450
+
451
+ return analysis
452
+
453
+ def _assess_complexity(
454
+ self, capabilities: set[AgentCapability], scores: list[AgentScore]
455
+ ) -> str:
456
+ """Assess the complexity level of a task."""
457
+ if len(capabilities) >= 4:
458
+ return "high"
459
+ elif len(capabilities) >= 2:
460
+ return "medium"
461
+ elif not scores or scores[0].final_score < 0.3:
462
+ return "high" # No good matches = complex
463
+
464
+ return "low"
465
+
466
+ def _generate_recommendations(
467
+ self,
468
+ capabilities: set[AgentCapability],
469
+ scores: list[AgentScore],
470
+ ) -> list[str]:
471
+ """Generate recommendations based on analysis."""
472
+ recommendations = []
473
+
474
+ if not scores:
475
+ recommendations.append(
476
+ "No suitable agents found - consider manual approach"
477
+ )
478
+ return recommendations
479
+
480
+ top_score = scores[0].final_score
481
+
482
+ if top_score > 0.8:
483
+ recommendations.append(
484
+ "Excellent agent match found - high confidence execution"
485
+ )
486
+ elif top_score > 0.6:
487
+ recommendations.append("Good agent match - should handle task well")
488
+ elif top_score > 0.4:
489
+ recommendations.append("Moderate match - may need supervision")
490
+ else:
491
+ recommendations.append("Weak matches - consider alternative approaches")
492
+
493
+ # Multi-agent recommendations
494
+ if len(capabilities) > 2:
495
+ recommendations.append("Consider multi-agent approach for complex task")
496
+
497
+ # Source diversity
498
+ sources = {score.agent.metadata.source for score in scores[:3]}
499
+ if len(sources) > 1:
500
+ recommendations.append("Multiple agent sources available for redundancy")
501
+
502
+ return recommendations