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.
- devloop/__init__.py +3 -0
- devloop/agents/__init__.py +33 -0
- devloop/agents/agent_health_monitor.py +105 -0
- devloop/agents/ci_monitor.py +237 -0
- devloop/agents/code_rabbit.py +248 -0
- devloop/agents/doc_lifecycle.py +374 -0
- devloop/agents/echo.py +24 -0
- devloop/agents/file_logger.py +46 -0
- devloop/agents/formatter.py +511 -0
- devloop/agents/git_commit_assistant.py +421 -0
- devloop/agents/linter.py +399 -0
- devloop/agents/performance_profiler.py +284 -0
- devloop/agents/security_scanner.py +322 -0
- devloop/agents/snyk.py +292 -0
- devloop/agents/test_runner.py +484 -0
- devloop/agents/type_checker.py +242 -0
- devloop/cli/__init__.py +1 -0
- devloop/cli/commands/__init__.py +1 -0
- devloop/cli/commands/custom_agents.py +144 -0
- devloop/cli/commands/feedback.py +161 -0
- devloop/cli/commands/summary.py +50 -0
- devloop/cli/main.py +430 -0
- devloop/cli/main_v1.py +144 -0
- devloop/collectors/__init__.py +17 -0
- devloop/collectors/base.py +55 -0
- devloop/collectors/filesystem.py +126 -0
- devloop/collectors/git.py +171 -0
- devloop/collectors/manager.py +159 -0
- devloop/collectors/process.py +221 -0
- devloop/collectors/system.py +195 -0
- devloop/core/__init__.py +21 -0
- devloop/core/agent.py +206 -0
- devloop/core/agent_template.py +498 -0
- devloop/core/amp_integration.py +166 -0
- devloop/core/auto_fix.py +224 -0
- devloop/core/config.py +272 -0
- devloop/core/context.py +0 -0
- devloop/core/context_store.py +530 -0
- devloop/core/contextual_feedback.py +311 -0
- devloop/core/custom_agent.py +439 -0
- devloop/core/debug_trace.py +289 -0
- devloop/core/event.py +105 -0
- devloop/core/event_store.py +316 -0
- devloop/core/feedback.py +311 -0
- devloop/core/learning.py +351 -0
- devloop/core/manager.py +219 -0
- devloop/core/performance.py +433 -0
- devloop/core/proactive_feedback.py +302 -0
- devloop/core/summary_formatter.py +159 -0
- devloop/core/summary_generator.py +275 -0
- devloop-0.2.0.dist-info/METADATA +705 -0
- devloop-0.2.0.dist-info/RECORD +55 -0
- devloop-0.2.0.dist-info/WHEEL +4 -0
- devloop-0.2.0.dist-info/entry_points.txt +3 -0
- 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}")
|