devloop 0.2.0__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.
Files changed (55) hide show
  1. devloop/__init__.py +3 -0
  2. devloop/agents/__init__.py +33 -0
  3. devloop/agents/agent_health_monitor.py +105 -0
  4. devloop/agents/ci_monitor.py +237 -0
  5. devloop/agents/code_rabbit.py +248 -0
  6. devloop/agents/doc_lifecycle.py +374 -0
  7. devloop/agents/echo.py +24 -0
  8. devloop/agents/file_logger.py +46 -0
  9. devloop/agents/formatter.py +511 -0
  10. devloop/agents/git_commit_assistant.py +421 -0
  11. devloop/agents/linter.py +399 -0
  12. devloop/agents/performance_profiler.py +284 -0
  13. devloop/agents/security_scanner.py +322 -0
  14. devloop/agents/snyk.py +292 -0
  15. devloop/agents/test_runner.py +484 -0
  16. devloop/agents/type_checker.py +242 -0
  17. devloop/cli/__init__.py +1 -0
  18. devloop/cli/commands/__init__.py +1 -0
  19. devloop/cli/commands/custom_agents.py +144 -0
  20. devloop/cli/commands/feedback.py +161 -0
  21. devloop/cli/commands/summary.py +50 -0
  22. devloop/cli/main.py +430 -0
  23. devloop/cli/main_v1.py +144 -0
  24. devloop/collectors/__init__.py +17 -0
  25. devloop/collectors/base.py +55 -0
  26. devloop/collectors/filesystem.py +126 -0
  27. devloop/collectors/git.py +171 -0
  28. devloop/collectors/manager.py +159 -0
  29. devloop/collectors/process.py +221 -0
  30. devloop/collectors/system.py +195 -0
  31. devloop/core/__init__.py +21 -0
  32. devloop/core/agent.py +206 -0
  33. devloop/core/agent_template.py +498 -0
  34. devloop/core/amp_integration.py +166 -0
  35. devloop/core/auto_fix.py +224 -0
  36. devloop/core/config.py +272 -0
  37. devloop/core/context.py +0 -0
  38. devloop/core/context_store.py +530 -0
  39. devloop/core/contextual_feedback.py +311 -0
  40. devloop/core/custom_agent.py +439 -0
  41. devloop/core/debug_trace.py +289 -0
  42. devloop/core/event.py +105 -0
  43. devloop/core/event_store.py +316 -0
  44. devloop/core/feedback.py +311 -0
  45. devloop/core/learning.py +351 -0
  46. devloop/core/manager.py +219 -0
  47. devloop/core/performance.py +433 -0
  48. devloop/core/proactive_feedback.py +302 -0
  49. devloop/core/summary_formatter.py +159 -0
  50. devloop/core/summary_generator.py +275 -0
  51. devloop-0.2.0.dist-info/METADATA +705 -0
  52. devloop-0.2.0.dist-info/RECORD +55 -0
  53. devloop-0.2.0.dist-info/WHEEL +4 -0
  54. devloop-0.2.0.dist-info/entry_points.txt +3 -0
  55. devloop-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,498 @@
1
+ """Custom agent creation framework and templates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import aiofiles
6
+ import importlib.util
7
+ import inspect
8
+ import json
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from .agent import Agent
14
+ from .event import EventBus
15
+
16
+
17
+ @dataclass
18
+ class AgentTemplate:
19
+ """Template for creating custom agents."""
20
+
21
+ name: str
22
+ description: str
23
+ category: str
24
+ triggers: List[str]
25
+ config_schema: Dict[str, Any]
26
+ template_code: str
27
+
28
+ @classmethod
29
+ def from_dict(cls, data: Dict[str, Any]) -> AgentTemplate:
30
+ """Create template from dictionary."""
31
+ return cls(
32
+ name=data["name"],
33
+ description=data["description"],
34
+ category=data["category"],
35
+ triggers=data["triggers"],
36
+ config_schema=data["config_schema"],
37
+ template_code=data["template_code"],
38
+ )
39
+
40
+
41
+ class AgentTemplateRegistry:
42
+ """Registry of available agent templates."""
43
+
44
+ def __init__(self):
45
+ self.templates: Dict[str, AgentTemplate] = {}
46
+ self._load_builtin_templates()
47
+
48
+ def _load_builtin_templates(self) -> None:
49
+ """Load built-in agent templates."""
50
+ self.templates.update(
51
+ {
52
+ "file-watcher": AgentTemplate(
53
+ name="file-watcher",
54
+ description="Monitor specific file patterns and perform custom actions",
55
+ category="monitoring",
56
+ triggers=["file:modified", "file:created", "file:deleted"],
57
+ config_schema={
58
+ "type": "object",
59
+ "properties": {
60
+ "filePatterns": {
61
+ "type": "array",
62
+ "items": {"type": "string"},
63
+ "default": ["**/*.txt"],
64
+ },
65
+ "action": {
66
+ "type": "string",
67
+ "enum": ["log", "backup", "notify"],
68
+ "default": "log",
69
+ },
70
+ },
71
+ },
72
+ template_code='''
73
+ from devloop.core.agent import Agent, AgentResult
74
+
75
+ class FileWatcherAgent(Agent):
76
+ def __init__(self, name: str, triggers: list, event_bus, config: dict):
77
+ super().__init__(name, triggers, event_bus)
78
+ self.config = config
79
+
80
+ async def handle(self, event: Event) -> AgentResult:
81
+ file_path = event.payload.get("path", "")
82
+ action = self.config.get("action", "log")
83
+
84
+ # Check if file matches patterns
85
+ if not self._matches_pattern(file_path):
86
+ return AgentResult(
87
+ agent_name=self.name,
88
+ success=True,
89
+ duration=0.0,
90
+ message=f"File {file_path} doesn't match patterns"
91
+ )
92
+
93
+ if action == "log":
94
+ message = f"File {event.type.split(':')[1]}: {file_path}"
95
+ elif action == "backup":
96
+ # TODO: Implement backup logic
97
+ message = f"Backed up: {file_path}"
98
+ elif action == "notify":
99
+ # TODO: Implement notification logic
100
+ message = f"Notification sent for: {file_path}"
101
+ else:
102
+ message = f"Unknown action '{action}' for: {file_path}"
103
+
104
+ return AgentResult(
105
+ agent_name=self.name,
106
+ success=True,
107
+ duration=0.1,
108
+ message=message
109
+ )
110
+
111
+ def _matches_pattern(self, file_path: str) -> bool:
112
+ """Check if file path matches configured patterns."""
113
+ from fnmatch import fnmatch
114
+
115
+ patterns = self.config.get("filePatterns", [])
116
+ for pattern in patterns:
117
+ if fnmatch(file_path, pattern):
118
+ return True
119
+ return False
120
+ ''',
121
+ ),
122
+ "command-runner": AgentTemplate(
123
+ name="command-runner",
124
+ description="Run shell commands in response to events",
125
+ category="automation",
126
+ triggers=["file:modified", "git:commit"],
127
+ config_schema={
128
+ "type": "object",
129
+ "properties": {
130
+ "commands": {
131
+ "type": "array",
132
+ "items": {"type": "string"},
133
+ "default": ["echo 'Hello from custom agent!'"],
134
+ },
135
+ "workingDirectory": {"type": "string", "default": "."},
136
+ },
137
+ },
138
+ template_code="""
139
+ import subprocess
140
+ import asyncio
141
+ from devloop.core.agent import Agent, AgentResult
142
+
143
+ class CommandRunnerAgent(Agent):
144
+ def __init__(self, name: str, triggers: list, event_bus, config: dict):
145
+ super().__init__(name, triggers, event_bus)
146
+ self.config = config
147
+
148
+ async def handle(self, event: Event) -> AgentResult:
149
+ commands = self.config.get("commands", [])
150
+ cwd = self.config.get("workingDirectory", ".")
151
+
152
+ results = []
153
+ for cmd in commands:
154
+ try:
155
+ # Run command asynchronously
156
+ process = await asyncio.create_subprocess_shell(
157
+ cmd,
158
+ stdout=asyncio.subprocess.PIPE,
159
+ stderr=asyncio.subprocess.PIPE,
160
+ cwd=cwd
161
+ )
162
+ stdout, stderr = await process.communicate()
163
+
164
+ if process.returncode == 0:
165
+ results.append(f"✓ {cmd}")
166
+ else:
167
+ results.append(f"✗ {cmd}: {stderr.decode().strip()}")
168
+
169
+ except Exception as e:
170
+ results.append(f"✗ {cmd}: {str(e)}")
171
+
172
+ return AgentResult(
173
+ agent_name=self.name,
174
+ success=all("✓" in r for r in results),
175
+ duration=0.1,
176
+ message=f"Ran {len(commands)} commands: {'; '.join(results)}"
177
+ )
178
+ """,
179
+ ),
180
+ "data-processor": AgentTemplate(
181
+ name="data-processor",
182
+ description="Process and transform data files",
183
+ category="data",
184
+ triggers=["file:modified"],
185
+ config_schema={
186
+ "type": "object",
187
+ "properties": {
188
+ "inputFormat": {
189
+ "type": "string",
190
+ "enum": ["json", "csv", "txt"],
191
+ "default": "json",
192
+ },
193
+ "outputFormat": {
194
+ "type": "string",
195
+ "enum": ["json", "csv", "txt"],
196
+ "default": "json",
197
+ },
198
+ "transformations": {
199
+ "type": "array",
200
+ "items": {"type": "string"},
201
+ "default": [],
202
+ },
203
+ },
204
+ },
205
+ template_code='''
206
+ import json
207
+ import csv
208
+ from pathlib import Path
209
+ from devloop.core.agent import Agent, AgentResult
210
+
211
+ class DataProcessorAgent(Agent):
212
+ def __init__(self, name: str, triggers: list, event_bus, config: dict):
213
+ super().__init__(name, triggers, event_bus)
214
+ self.config = config
215
+
216
+ async def handle(self, event: Event) -> AgentResult:
217
+ file_path = event.payload.get("path", "")
218
+ if not file_path:
219
+ return AgentResult(
220
+ agent_name=self.name,
221
+ success=False,
222
+ duration=0.0,
223
+ message="No file path provided"
224
+ )
225
+
226
+ path = Path(file_path)
227
+ if not path.exists():
228
+ return AgentResult(
229
+ agent_name=self.name,
230
+ success=False,
231
+ duration=0.0,
232
+ message=f"File does not exist: {file_path}"
233
+ )
234
+
235
+ try:
236
+ # Read input data
237
+ input_format = self.config.get("inputFormat", "json")
238
+ data = await self._read_data(path, input_format)
239
+
240
+ # Apply transformations
241
+ transformations = self.config.get("transformations", [])
242
+ for transform in transformations:
243
+ data = await self._apply_transformation(data, transform)
244
+
245
+ # Write output data
246
+ output_format = self.config.get("outputFormat", "json")
247
+ output_path = path.with_suffix(f".processed{path.suffix}")
248
+ await self._write_data(output_path, data, output_format)
249
+
250
+ return AgentResult(
251
+ agent_name=self.name,
252
+ success=True,
253
+ duration=0.1,
254
+ message=f"Processed {file_path} -> {output_path}"
255
+ )
256
+
257
+ except Exception as e:
258
+ return AgentResult(
259
+ agent_name=self.name,
260
+ success=False,
261
+ duration=0.1,
262
+ message=f"Processing failed: {str(e)}"
263
+ )
264
+
265
+ async def _read_data(self, path: Path, format_type: str):
266
+ """Read data from file."""
267
+ async with aiofiles.open(path, 'r') as f:
268
+ content = await f.read()
269
+
270
+ if format_type == "json":
271
+ return json.loads(content)
272
+ elif format_type == "csv":
273
+ import io
274
+ return list(csv.DictReader(io.StringIO(content)))
275
+ else: # txt
276
+ return content.splitlines()
277
+
278
+ async def _write_data(self, path: Path, data, format_type: str):
279
+ """Write data to file."""
280
+ async with aiofiles.open(path, 'w') as f:
281
+ if format_type == "json":
282
+ await f.write(json.dumps(data, indent=2))
283
+ elif format_type == "csv":
284
+ if isinstance(data, list) and data:
285
+ writer = csv.DictWriter(f, fieldnames=data[0].keys())
286
+ writer.writeheader()
287
+ writer.writerows(data)
288
+ else: # txt
289
+ if isinstance(data, list):
290
+ await f.write('\\n'.join(data))
291
+ else:
292
+ await f.write(str(data))
293
+
294
+ async def _apply_transformation(self, data, transform: str):
295
+ """Apply a transformation to data."""
296
+ # Simple transformation examples
297
+ if transform == "uppercase":
298
+ if isinstance(data, str):
299
+ return data.upper()
300
+ elif isinstance(data, list):
301
+ return [str(item).upper() for item in data]
302
+ elif transform == "lowercase":
303
+ if isinstance(data, str):
304
+ return data.lower()
305
+ elif isinstance(data, list):
306
+ return [str(item).lower() for item in data]
307
+ elif transform == "sort":
308
+ if isinstance(data, list):
309
+ return sorted(data, key=str)
310
+ # Add more transformations as needed
311
+
312
+ return data
313
+ ''',
314
+ ),
315
+ }
316
+ )
317
+
318
+ def get_template(self, name: str) -> Optional[AgentTemplate]:
319
+ """Get a template by name."""
320
+ return self.templates.get(name)
321
+
322
+ def list_templates(self, category: Optional[str] = None) -> List[AgentTemplate]:
323
+ """List available templates, optionally filtered by category."""
324
+ templates = list(self.templates.values())
325
+ if category:
326
+ templates = [t for t in templates if t.category == category]
327
+ return templates
328
+
329
+ def get_categories(self) -> List[str]:
330
+ """Get list of available template categories."""
331
+ return list(set(t.category for t in self.templates.values()))
332
+
333
+
334
+ class AgentFactory:
335
+ """Factory for creating agents from templates and custom code."""
336
+
337
+ def __init__(self, template_registry: AgentTemplateRegistry):
338
+ self.template_registry = template_registry
339
+
340
+ async def create_from_template(
341
+ self,
342
+ template_name: str,
343
+ agent_name: str,
344
+ triggers: List[str],
345
+ event_bus: EventBus,
346
+ config: Dict[str, Any],
347
+ ) -> Optional[Agent]:
348
+ """Create an agent from a template."""
349
+ template = self.template_registry.get_template(template_name)
350
+ if not template:
351
+ return None
352
+
353
+ # Create a temporary module with the template code
354
+ spec = importlib.util.spec_from_loader(
355
+ f"custom_agent_{agent_name}", loader=None
356
+ )
357
+ if spec is None:
358
+ return None
359
+ module = importlib.util.module_from_spec(spec)
360
+
361
+ # Execute the template code in the module
362
+ # SECURITY: exec() is used for dynamic agent loading from trusted templates
363
+ # This is an intentional design decision for the agent framework
364
+ exec(template.template_code, module.__dict__) # nosec B102
365
+
366
+ # Find the agent class (assume it's the first Agent subclass)
367
+ agent_class = None
368
+ for name, obj in module.__dict__.items():
369
+ if inspect.isclass(obj) and issubclass(obj, Agent) and obj != Agent:
370
+ agent_class = obj
371
+ break
372
+
373
+ if not agent_class:
374
+ return None
375
+
376
+ # Create and return the agent instance
377
+ return agent_class(
378
+ name=agent_name,
379
+ triggers=triggers,
380
+ event_bus=event_bus,
381
+ )
382
+
383
+ async def create_from_file(
384
+ self,
385
+ file_path: Path,
386
+ agent_name: str,
387
+ triggers: List[str],
388
+ event_bus: EventBus,
389
+ config: Optional[Dict[str, Any]] = None,
390
+ ) -> Optional[Agent]:
391
+ """Create an agent from a Python file."""
392
+ if not file_path.exists():
393
+ return None
394
+
395
+ # Load the module
396
+ spec = importlib.util.spec_from_file_location(
397
+ f"custom_agent_{agent_name}", file_path
398
+ )
399
+ if not spec or not spec.loader:
400
+ return None
401
+
402
+ module = importlib.util.module_from_spec(spec)
403
+ spec.loader.exec_module(module)
404
+
405
+ # Find the agent class
406
+ agent_class = None
407
+ for name, obj in module.__dict__.items():
408
+ if inspect.isclass(obj) and issubclass(obj, Agent) and obj != Agent:
409
+ agent_class = obj
410
+ break
411
+
412
+ if not agent_class:
413
+ return None
414
+
415
+ # Create and return the agent instance
416
+ return agent_class(
417
+ name=agent_name,
418
+ triggers=triggers,
419
+ event_bus=event_bus,
420
+ )
421
+
422
+
423
+ class AgentMarketplace:
424
+ """Marketplace for sharing and discovering custom agents."""
425
+
426
+ def __init__(self, marketplace_path: Path):
427
+ self.marketplace_path = marketplace_path
428
+ self.marketplace_path.mkdir(parents=True, exist_ok=True)
429
+ self.index_file = marketplace_path / "index.json"
430
+ self.agents_dir = marketplace_path / "agents"
431
+
432
+ async def publish_agent(self, agent_file: Path, metadata: Dict[str, Any]) -> bool:
433
+ """Publish an agent to the marketplace."""
434
+ if not agent_file.exists():
435
+ return False
436
+
437
+ # Copy agent file to marketplace
438
+ agent_id = metadata.get("name", agent_file.stem)
439
+ agent_dir = self.agents_dir / agent_id
440
+ agent_dir.mkdir(exist_ok=True)
441
+
442
+ import shutil
443
+
444
+ shutil.copy2(agent_file, agent_dir / "agent.py")
445
+
446
+ # Save metadata
447
+ import time
448
+
449
+ metadata["published_at"] = metadata.get("published_at", time.time())
450
+ async with aiofiles.open(agent_dir / "metadata.json", "w") as f:
451
+ await f.write(json.dumps(metadata, indent=2))
452
+
453
+ # Update index
454
+ await self._update_index()
455
+ return True
456
+
457
+ async def download_agent(self, agent_id: str) -> Optional[Path]:
458
+ """Download an agent from the marketplace."""
459
+ agent_dir = self.agents_dir / agent_id
460
+ if not agent_dir.exists():
461
+ return None
462
+
463
+ return agent_dir / "agent.py"
464
+
465
+ async def list_agents(self, category: Optional[str] = None) -> List[Dict[str, Any]]:
466
+ """List available agents in the marketplace."""
467
+ if not self.index_file.exists():
468
+ return []
469
+
470
+ async with aiofiles.open(self.index_file, "r") as f:
471
+ content = await f.read()
472
+
473
+ try:
474
+ agents = json.loads(content)
475
+ if category:
476
+ agents = [a for a in agents if a.get("category") == category]
477
+ return agents
478
+ except json.JSONDecodeError:
479
+ return []
480
+
481
+ async def _update_index(self) -> None:
482
+ """Update the marketplace index."""
483
+ agents = []
484
+
485
+ for agent_dir in self.agents_dir.iterdir():
486
+ if agent_dir.is_dir():
487
+ metadata_file = agent_dir / "metadata.json"
488
+ if metadata_file.exists():
489
+ async with aiofiles.open(metadata_file, "r") as f:
490
+ try:
491
+ metadata = json.loads(await f.read())
492
+ metadata["id"] = agent_dir.name
493
+ agents.append(metadata)
494
+ except json.JSONDecodeError:
495
+ continue
496
+
497
+ async with aiofiles.open(self.index_file, "w") as f:
498
+ await f.write(json.dumps(agents, indent=2))
@@ -0,0 +1,166 @@
1
+ """Integration commands for Amp/Claude Code."""
2
+
3
+ from devloop.core.auto_fix import apply_safe_fixes
4
+ from devloop.core.config import config
5
+ from devloop.core.context_store import context_store
6
+ from devloop.core.summary_generator import SummaryGenerator
7
+ from devloop.core.summary_formatter import SummaryFormatter
8
+ from typing import Any, Callable, Coroutine, Dict
9
+
10
+
11
+ async def check_agent_findings():
12
+ """Check what findings agents have discovered."""
13
+ findings = await context_store.get_findings()
14
+
15
+ # Group findings by agent
16
+ findings_by_agent = {}
17
+ for finding in findings:
18
+ agent = finding.agent
19
+ if agent not in findings_by_agent:
20
+ findings_by_agent[agent] = []
21
+ findings_by_agent[agent].append(
22
+ {
23
+ "id": finding.id,
24
+ "file": finding.file,
25
+ "severity": finding.severity.value,
26
+ "message": finding.message,
27
+ "blocking": finding.blocking,
28
+ "auto_fixable": finding.auto_fixable,
29
+ }
30
+ )
31
+
32
+ summary = {
33
+ "total_findings": len(findings),
34
+ "findings_by_agent": {k: len(v) for k, v in findings_by_agent.items()},
35
+ "blockers": len([f for f in findings if f.blocking]),
36
+ "auto_fixable": len([f for f in findings if f.auto_fixable]),
37
+ }
38
+
39
+ return {
40
+ "summary": summary,
41
+ "findings": findings_by_agent,
42
+ }
43
+
44
+
45
+ async def apply_autonomous_fixes():
46
+ """Apply safe fixes automatically."""
47
+ global_config = config.get_global_config()
48
+
49
+ if not global_config.autonomous_fixes.enabled:
50
+ return {
51
+ "message": "Autonomous fixes are disabled in configuration",
52
+ "applied_fixes": {},
53
+ "total_applied": 0,
54
+ "config_status": {
55
+ "enabled": False,
56
+ "safety_level": global_config.autonomous_fixes.safety_level,
57
+ },
58
+ }
59
+
60
+ results = await apply_safe_fixes()
61
+
62
+ total_applied = sum(results.values())
63
+ message = f"Applied {total_applied} safe fixes"
64
+
65
+ if results:
66
+ details = []
67
+ for agent_type, count in results.items():
68
+ details.append(f"{count} {agent_type} fixes")
69
+ message += f": {', '.join(details)}"
70
+ else:
71
+ message += " (no safe fixes found)"
72
+
73
+ return {
74
+ "message": message,
75
+ "applied_fixes": results,
76
+ "total_applied": total_applied,
77
+ "config_status": {
78
+ "enabled": True,
79
+ "safety_level": global_config.autonomous_fixes.safety_level,
80
+ },
81
+ }
82
+
83
+
84
+ async def show_agent_status():
85
+ """Show current status of background agents."""
86
+ findings = await context_store.get_findings()
87
+
88
+ # Group findings by agent
89
+ findings_by_agent = {}
90
+ for finding in findings:
91
+ agent = finding.agent
92
+ if agent not in findings_by_agent:
93
+ findings_by_agent[agent] = []
94
+ findings_by_agent[agent].append(finding)
95
+
96
+ status = {
97
+ "agent_activity": {},
98
+ "total_findings": len(findings),
99
+ "findings_by_agent": {k: len(v) for k, v in findings_by_agent.items()},
100
+ }
101
+
102
+ # Show agent activity
103
+ for agent_type, agent_findings in findings_by_agent.items():
104
+ if agent_findings:
105
+ latest = max(agent_findings, key=lambda x: x.timestamp)
106
+ status["agent_activity"][agent_type] = {
107
+ "last_active": latest.timestamp,
108
+ "last_message": latest.message,
109
+ "total_findings": len(agent_findings),
110
+ "blocking_issues": len([f for f in agent_findings if f.blocking]),
111
+ }
112
+
113
+ # Show recent findings (last 3 per agent)
114
+ status["recent_findings"] = {}
115
+ for agent_type, agent_findings in findings_by_agent.items():
116
+ recent = sorted(agent_findings, key=lambda x: x.timestamp, reverse=True)[:3]
117
+ status["recent_findings"][agent_type] = [
118
+ {
119
+ "timestamp": f.timestamp,
120
+ "message": f.message,
121
+ "severity": f.severity.value,
122
+ "blocking": f.blocking,
123
+ }
124
+ for f in recent
125
+ ]
126
+
127
+ return status
128
+
129
+
130
+ async def generate_agent_summary(scope: str = "recent", filters: dict = None):
131
+ """Generate agent summary report for Amp/Claude Code slash command."""
132
+ if filters is None:
133
+ filters = {}
134
+
135
+ generator = SummaryGenerator(context_store)
136
+ report = await generator.generate_summary(scope, filters)
137
+
138
+ formatter = SummaryFormatter()
139
+ return {
140
+ "summary": formatter.format_json(report),
141
+ "formatted_report": formatter.format_markdown(report),
142
+ "quick_stats": {
143
+ "total_findings": report.total_findings,
144
+ "critical_issues": len(report.critical_issues),
145
+ "auto_fixable": len(report.auto_fixable),
146
+ "trend": report.trends.get("direction", "stable"),
147
+ },
148
+ }
149
+
150
+
151
+ # Amp subagent command mappings
152
+ AMP_COMMANDS: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = {
153
+ "check_agent_findings": check_agent_findings,
154
+ "apply_autonomous_fixes": apply_autonomous_fixes,
155
+ "show_agent_status": show_agent_status,
156
+ "generate_agent_summary": generate_agent_summary,
157
+ }
158
+
159
+
160
+ async def execute_amp_command(command: str, **kwargs) -> Any:
161
+ """Execute an Amp integration command."""
162
+ if command not in AMP_COMMANDS:
163
+ raise ValueError(f"Unknown command: {command}")
164
+
165
+ func: Callable[..., Coroutine[Any, Any, Any]] = AMP_COMMANDS[command]
166
+ return await func(**kwargs)