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,439 @@
1
+ """Custom agent creation framework for Phase 3."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from abc import ABC, abstractmethod
7
+ from dataclasses import dataclass, field
8
+ from enum import Enum
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional
11
+ from uuid import uuid4
12
+
13
+ import aiofiles
14
+
15
+
16
+ class CustomAgentType(Enum):
17
+ """Types of custom agents that can be created."""
18
+
19
+ PATTERN_MATCHER = "pattern_matcher"
20
+ FILE_PROCESSOR = "file_processor"
21
+ OUTPUT_ANALYZER = "output_analyzer"
22
+ COMPOSITE = "composite"
23
+
24
+
25
+ @dataclass
26
+ class CustomAgentConfig:
27
+ """Configuration for a custom agent."""
28
+
29
+ id: str
30
+ name: str
31
+ description: str
32
+ agent_type: CustomAgentType
33
+ enabled: bool = True
34
+ triggers: List[str] = field(default_factory=list)
35
+ config: Dict[str, Any] = field(default_factory=dict)
36
+ metadata: Dict[str, Any] = field(default_factory=dict)
37
+ created_at: Optional[float] = None
38
+ updated_at: Optional[float] = None
39
+
40
+
41
+ class AgentBuilder:
42
+ """Builder for creating custom agents programmatically."""
43
+
44
+ def __init__(self, name: str, agent_type: CustomAgentType):
45
+ """Initialize agent builder.
46
+
47
+ Args:
48
+ name: Name of the custom agent
49
+ agent_type: Type of custom agent
50
+ """
51
+ self.name = name
52
+ self.agent_type = agent_type
53
+ self.description = ""
54
+ self.triggers: List[str] = []
55
+ self.config: Dict[str, Any] = {}
56
+ self.metadata: Dict[str, Any] = {}
57
+
58
+ def with_description(self, description: str) -> AgentBuilder:
59
+ """Add description."""
60
+ self.description = description
61
+ return self
62
+
63
+ def with_triggers(self, *triggers: str) -> AgentBuilder:
64
+ """Add event triggers."""
65
+ self.triggers.extend(triggers)
66
+ return self
67
+
68
+ def with_config(self, **config_items) -> AgentBuilder:
69
+ """Add configuration items."""
70
+ self.config.update(config_items)
71
+ return self
72
+
73
+ def with_metadata(self, **metadata_items) -> AgentBuilder:
74
+ """Add metadata."""
75
+ self.metadata.update(metadata_items)
76
+ return self
77
+
78
+ def build(self) -> CustomAgentConfig:
79
+ """Build the custom agent configuration."""
80
+ return CustomAgentConfig(
81
+ id=str(uuid4()),
82
+ name=self.name,
83
+ description=self.description,
84
+ agent_type=self.agent_type,
85
+ triggers=self.triggers,
86
+ config=self.config,
87
+ metadata=self.metadata,
88
+ )
89
+
90
+
91
+ class CustomAgentStore:
92
+ """Storage for custom agent definitions."""
93
+
94
+ def __init__(self, storage_path: Path):
95
+ """Initialize custom agent store.
96
+
97
+ Args:
98
+ storage_path: Path to store custom agent definitions
99
+ """
100
+ self.storage_path = storage_path
101
+ self.storage_path.mkdir(parents=True, exist_ok=True)
102
+ self.agents_file = storage_path / "agents.json"
103
+
104
+ async def save_agent(self, config: CustomAgentConfig) -> None:
105
+ """Save a custom agent configuration.
106
+
107
+ Args:
108
+ config: Agent configuration to save
109
+ """
110
+ agents = await self._load_all_agents()
111
+
112
+ # Update or add
113
+ agents[config.id] = {
114
+ "id": config.id,
115
+ "name": config.name,
116
+ "description": config.description,
117
+ "agent_type": config.agent_type.value,
118
+ "enabled": config.enabled,
119
+ "triggers": config.triggers,
120
+ "config": config.config,
121
+ "metadata": config.metadata,
122
+ "created_at": config.created_at,
123
+ "updated_at": config.updated_at,
124
+ }
125
+
126
+ async with aiofiles.open(self.agents_file, "w") as f:
127
+ await f.write(json.dumps(agents, indent=2))
128
+
129
+ async def get_agent(self, agent_id: str) -> Optional[CustomAgentConfig]:
130
+ """Get a custom agent by ID.
131
+
132
+ Args:
133
+ agent_id: Agent ID to retrieve
134
+
135
+ Returns:
136
+ Agent configuration or None if not found
137
+ """
138
+ agents = await self._load_all_agents()
139
+
140
+ if agent_id not in agents:
141
+ return None
142
+
143
+ data = agents[agent_id]
144
+ return CustomAgentConfig(
145
+ id=data["id"],
146
+ name=data["name"],
147
+ description=data["description"],
148
+ agent_type=CustomAgentType(data["agent_type"]),
149
+ enabled=data.get("enabled", True),
150
+ triggers=data.get("triggers", []),
151
+ config=data.get("config", {}),
152
+ metadata=data.get("metadata", {}),
153
+ created_at=data.get("created_at"),
154
+ updated_at=data.get("updated_at"),
155
+ )
156
+
157
+ async def get_all_agents(self) -> List[CustomAgentConfig]:
158
+ """Get all custom agents.
159
+
160
+ Returns:
161
+ List of all custom agent configurations
162
+ """
163
+ agents_data = await self._load_all_agents()
164
+ agents = []
165
+
166
+ for data in agents_data.values():
167
+ agents.append(
168
+ CustomAgentConfig(
169
+ id=data["id"],
170
+ name=data["name"],
171
+ description=data["description"],
172
+ agent_type=CustomAgentType(data["agent_type"]),
173
+ enabled=data.get("enabled", True),
174
+ triggers=data.get("triggers", []),
175
+ config=data.get("config", {}),
176
+ metadata=data.get("metadata", {}),
177
+ created_at=data.get("created_at"),
178
+ updated_at=data.get("updated_at"),
179
+ )
180
+ )
181
+
182
+ return agents
183
+
184
+ async def delete_agent(self, agent_id: str) -> bool:
185
+ """Delete a custom agent.
186
+
187
+ Args:
188
+ agent_id: Agent ID to delete
189
+
190
+ Returns:
191
+ True if deleted, False if not found
192
+ """
193
+ agents = await self._load_all_agents()
194
+
195
+ if agent_id not in agents:
196
+ return False
197
+
198
+ del agents[agent_id]
199
+
200
+ async with aiofiles.open(self.agents_file, "w") as f:
201
+ await f.write(json.dumps(agents, indent=2))
202
+
203
+ return True
204
+
205
+ async def list_agents_by_type(
206
+ self, agent_type: CustomAgentType
207
+ ) -> List[CustomAgentConfig]:
208
+ """Get agents by type.
209
+
210
+ Args:
211
+ agent_type: Type of agents to retrieve
212
+
213
+ Returns:
214
+ List of agents matching the type
215
+ """
216
+ agents_data = await self._load_all_agents()
217
+ agents = []
218
+
219
+ for data in agents_data.values():
220
+ if CustomAgentType(data["agent_type"]) == agent_type:
221
+ agents.append(
222
+ CustomAgentConfig(
223
+ id=data["id"],
224
+ name=data["name"],
225
+ description=data["description"],
226
+ agent_type=agent_type,
227
+ enabled=data.get("enabled", True),
228
+ triggers=data.get("triggers", []),
229
+ config=data.get("config", {}),
230
+ metadata=data.get("metadata", {}),
231
+ created_at=data.get("created_at"),
232
+ updated_at=data.get("updated_at"),
233
+ )
234
+ )
235
+
236
+ return agents
237
+
238
+ async def _load_all_agents(self) -> Dict[str, Dict[str, Any]]:
239
+ """Load all agents from file.
240
+
241
+ Returns:
242
+ Dictionary of agents indexed by ID
243
+ """
244
+ if not self.agents_file.exists():
245
+ return {}
246
+
247
+ async with aiofiles.open(self.agents_file, "r") as f:
248
+ content = await f.read()
249
+
250
+ try:
251
+ return json.loads(content)
252
+ except json.JSONDecodeError:
253
+ return {}
254
+
255
+
256
+ class AgentTemplate(ABC):
257
+ """Base template for creating custom agents."""
258
+
259
+ def __init__(self, config: CustomAgentConfig):
260
+ """Initialize agent template.
261
+
262
+ Args:
263
+ config: Agent configuration
264
+ """
265
+ self.config = config
266
+ self.id = config.id
267
+ self.name = config.name
268
+ self.agent_type = config.agent_type
269
+
270
+ @abstractmethod
271
+ async def execute(self, event_data: Dict[str, Any]) -> Dict[str, Any]:
272
+ """Execute the custom agent.
273
+
274
+ Args:
275
+ event_data: Event data to process
276
+
277
+ Returns:
278
+ Result of agent execution
279
+ """
280
+ pass
281
+
282
+ async def should_handle(self, event_type: str) -> bool:
283
+ """Check if this agent should handle the event.
284
+
285
+ Args:
286
+ event_type: Type of event
287
+
288
+ Returns:
289
+ True if agent should handle the event
290
+ """
291
+ if not self.config.enabled:
292
+ return False
293
+
294
+ return event_type in self.config.triggers
295
+
296
+
297
+ class PatternMatcherAgent(AgentTemplate):
298
+ """Custom agent that matches patterns in files."""
299
+
300
+ async def execute(self, event_data: Dict[str, Any]) -> Dict[str, Any]:
301
+ """Execute pattern matching.
302
+
303
+ Args:
304
+ event_data: Event data containing file information
305
+
306
+ Returns:
307
+ Matches found in files
308
+ """
309
+ patterns = self.config.config.get("patterns", [])
310
+ file_path = event_data.get("file_path", "")
311
+
312
+ if not file_path or not patterns:
313
+ return {"matches": []}
314
+
315
+ matches = []
316
+ try:
317
+ path = Path(file_path)
318
+ if path.exists() and path.is_file():
319
+ content = path.read_text()
320
+
321
+ import re
322
+
323
+ for pattern in patterns:
324
+ regex = re.compile(pattern)
325
+ for match in regex.finditer(content):
326
+ matches.append(
327
+ {
328
+ "pattern": pattern,
329
+ "match": match.group(),
330
+ "position": match.start(),
331
+ }
332
+ )
333
+ except Exception as e:
334
+ return {"error": str(e), "matches": []}
335
+
336
+ return {"matches": matches}
337
+
338
+
339
+ class FileProcessorAgent(AgentTemplate):
340
+ """Custom agent that processes files."""
341
+
342
+ async def execute(self, event_data: Dict[str, Any]) -> Dict[str, Any]:
343
+ """Execute file processing.
344
+
345
+ Args:
346
+ event_data: Event data containing file information
347
+
348
+ Returns:
349
+ Processing results
350
+ """
351
+ file_path = event_data.get("file_path", "")
352
+ operation = self.config.config.get("operation", "read")
353
+
354
+ try:
355
+ path = Path(file_path)
356
+
357
+ if operation == "read" and path.exists():
358
+ content = path.read_text()
359
+ return {
360
+ "status": "success",
361
+ "operation": "read",
362
+ "file_size": len(content),
363
+ "lines": len(content.splitlines()),
364
+ }
365
+ elif operation == "analyze" and path.exists():
366
+ content = path.read_text()
367
+ lines = content.splitlines()
368
+ return {
369
+ "status": "success",
370
+ "operation": "analyze",
371
+ "total_lines": len(lines),
372
+ "empty_lines": len([line for line in lines if not line.strip()]),
373
+ "comment_lines": len(
374
+ [line for line in lines if line.strip().startswith("#")]
375
+ ),
376
+ }
377
+ else:
378
+ return {"status": "error", "message": f"File not found: {file_path}"}
379
+
380
+ except Exception as e:
381
+ return {"status": "error", "message": str(e)}
382
+
383
+
384
+ class OutputAnalyzerAgent(AgentTemplate):
385
+ """Custom agent that analyzes command output."""
386
+
387
+ async def execute(self, event_data: Dict[str, Any]) -> Dict[str, Any]:
388
+ """Analyze output.
389
+
390
+ Args:
391
+ event_data: Event data containing output information
392
+
393
+ Returns:
394
+ Analysis results
395
+ """
396
+ output = event_data.get("output", "")
397
+ patterns = self.config.config.get("error_patterns", [])
398
+
399
+ analysis = {
400
+ "output_length": len(output),
401
+ "line_count": len(output.splitlines()),
402
+ "errors_found": 0,
403
+ "warnings_found": 0,
404
+ }
405
+
406
+ if patterns:
407
+ import re
408
+
409
+ for pattern in patterns:
410
+ regex = re.compile(pattern)
411
+ matches = regex.findall(output)
412
+ if matches:
413
+ analysis["errors_found"] += len(matches)
414
+
415
+ # Simple heuristic analysis
416
+ lower_output = output.lower()
417
+ analysis["warnings_found"] = lower_output.count("warning")
418
+ analysis["errors_found"] += lower_output.count("error")
419
+
420
+ return analysis
421
+
422
+
423
+ def get_agent_template(config: CustomAgentConfig) -> AgentTemplate:
424
+ """Get the appropriate template instance for a custom agent.
425
+
426
+ Args:
427
+ config: Agent configuration
428
+
429
+ Returns:
430
+ Agent template instance
431
+ """
432
+ if config.agent_type == CustomAgentType.PATTERN_MATCHER:
433
+ return PatternMatcherAgent(config)
434
+ elif config.agent_type == CustomAgentType.FILE_PROCESSOR:
435
+ return FileProcessorAgent(config)
436
+ elif config.agent_type == CustomAgentType.OUTPUT_ANALYZER:
437
+ return OutputAnalyzerAgent(config)
438
+ else:
439
+ raise ValueError(f"Unsupported agent type: {config.agent_type}")