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
devloop/core/auto_fix.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Automatic fix application based on agent findings."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from devloop.core.config import AutonomousFixesConfig, config
|
|
8
|
+
from devloop.core.context_store import Finding, context_store
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AutoFix:
|
|
14
|
+
"""Automatically applies safe fixes based on agent findings."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._fix_history = set() # Track applied fixes to avoid duplicates
|
|
18
|
+
|
|
19
|
+
async def apply_safe_fixes(self) -> Dict[str, int]:
|
|
20
|
+
"""Apply all safe fixes from agent findings.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dict mapping agent types to number of fixes applied
|
|
24
|
+
"""
|
|
25
|
+
# Check if autonomous fixes are enabled
|
|
26
|
+
global_config = config.get_global_config()
|
|
27
|
+
if (
|
|
28
|
+
not global_config.autonomous_fixes
|
|
29
|
+
or not global_config.autonomous_fixes.enabled
|
|
30
|
+
):
|
|
31
|
+
logger.info("Autonomous fixes are disabled in configuration")
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
findings = await context_store.get_findings()
|
|
35
|
+
actionable_findings = [f for f in findings if f.auto_fixable]
|
|
36
|
+
applied_fixes: Dict[str, int] = {}
|
|
37
|
+
|
|
38
|
+
for finding in actionable_findings:
|
|
39
|
+
agent_type = finding.agent
|
|
40
|
+
if await self._apply_single_fix(
|
|
41
|
+
agent_type, finding, global_config.autonomous_fixes
|
|
42
|
+
):
|
|
43
|
+
applied_fixes[agent_type] = applied_fixes.get(agent_type, 0) + 1
|
|
44
|
+
await context_store.clear_findings(file_filter=finding.file)
|
|
45
|
+
|
|
46
|
+
return applied_fixes
|
|
47
|
+
|
|
48
|
+
async def _apply_single_fix(
|
|
49
|
+
self,
|
|
50
|
+
agent_type: str,
|
|
51
|
+
finding: Finding,
|
|
52
|
+
autonomous_fixes_config: AutonomousFixesConfig,
|
|
53
|
+
) -> bool:
|
|
54
|
+
"""Apply a single fix if it's safe to do so."""
|
|
55
|
+
if finding.id in self._fix_history:
|
|
56
|
+
return False # Already applied
|
|
57
|
+
|
|
58
|
+
# Check if fix is safe based on safety level
|
|
59
|
+
if not self._is_safe_for_config(
|
|
60
|
+
agent_type, finding, autonomous_fixes_config.safety_level
|
|
61
|
+
):
|
|
62
|
+
logger.info(
|
|
63
|
+
f"Skipping {agent_type} fix (not safe for current safety level): {finding.message}"
|
|
64
|
+
)
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
# Apply the fix
|
|
68
|
+
success = await self._execute_fix(agent_type, finding)
|
|
69
|
+
if success:
|
|
70
|
+
self._fix_history.add(finding.id)
|
|
71
|
+
logger.info(f"Applied {agent_type} fix: {finding.message}")
|
|
72
|
+
|
|
73
|
+
return success
|
|
74
|
+
|
|
75
|
+
def _is_safe_for_config(
|
|
76
|
+
self, agent_type: str, finding: Finding, safety_level: str
|
|
77
|
+
) -> bool:
|
|
78
|
+
"""Check if a finding is safe to apply based on the configured safety level."""
|
|
79
|
+
message = finding.message.lower()
|
|
80
|
+
|
|
81
|
+
# Always reject errors/failures
|
|
82
|
+
if any(
|
|
83
|
+
keyword in message for keyword in ["error", "failed", "timeout", "conflict"]
|
|
84
|
+
):
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
if safety_level == "safe_only":
|
|
88
|
+
return self._is_safe_only_fix(agent_type, finding)
|
|
89
|
+
elif safety_level == "medium_risk":
|
|
90
|
+
return self._is_medium_risk_fix(agent_type, finding)
|
|
91
|
+
elif safety_level == "all":
|
|
92
|
+
return True # Apply all fixes
|
|
93
|
+
else:
|
|
94
|
+
return False # Unknown safety level, be safe
|
|
95
|
+
|
|
96
|
+
def _is_safe_only_fix(self, agent_type: str, finding: Finding) -> bool:
|
|
97
|
+
"""Check if a finding is safe-only level."""
|
|
98
|
+
message = finding.message.lower()
|
|
99
|
+
|
|
100
|
+
if agent_type == "formatter":
|
|
101
|
+
# Only basic formatting issues
|
|
102
|
+
return "would format" in message or "needs formatting" in message
|
|
103
|
+
|
|
104
|
+
elif agent_type == "linter":
|
|
105
|
+
# Only very safe linting fixes
|
|
106
|
+
safe_patterns = [
|
|
107
|
+
"whitespace",
|
|
108
|
+
"indentation",
|
|
109
|
+
"trailing whitespace",
|
|
110
|
+
"blank line",
|
|
111
|
+
]
|
|
112
|
+
return any(pattern in message for pattern in safe_patterns)
|
|
113
|
+
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def _is_medium_risk_fix(self, agent_type: str, finding: Finding) -> bool:
|
|
117
|
+
"""Check if a finding is medium-risk level."""
|
|
118
|
+
message = finding.message.lower()
|
|
119
|
+
|
|
120
|
+
if agent_type == "formatter":
|
|
121
|
+
# All formatting fixes
|
|
122
|
+
return "would format" in message or "needs formatting" in message
|
|
123
|
+
|
|
124
|
+
elif agent_type == "linter":
|
|
125
|
+
# More linting fixes including imports
|
|
126
|
+
safe_patterns = [
|
|
127
|
+
"unused import",
|
|
128
|
+
"import sort",
|
|
129
|
+
"whitespace",
|
|
130
|
+
"indentation",
|
|
131
|
+
"trailing whitespace",
|
|
132
|
+
"blank line",
|
|
133
|
+
]
|
|
134
|
+
return any(pattern in message for pattern in safe_patterns)
|
|
135
|
+
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
async def _execute_fix(self, agent_type: str, finding: Finding) -> bool:
|
|
139
|
+
"""Execute the actual fix."""
|
|
140
|
+
try:
|
|
141
|
+
if agent_type == "formatter":
|
|
142
|
+
return await self._execute_formatter_fix(finding)
|
|
143
|
+
elif agent_type == "linter":
|
|
144
|
+
return await self._execute_linter_fix(finding)
|
|
145
|
+
else:
|
|
146
|
+
return False
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Error applying {agent_type} fix: {e}")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
async def _execute_formatter_fix(self, finding: Finding) -> bool:
|
|
152
|
+
"""Execute a formatter fix."""
|
|
153
|
+
file_path = finding.file
|
|
154
|
+
if not file_path:
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
path = Path(file_path)
|
|
158
|
+
formatter = finding.context.get("formatter", "black")
|
|
159
|
+
|
|
160
|
+
# Import the formatter agent logic
|
|
161
|
+
from devloop.agents.formatter import FormatterAgent
|
|
162
|
+
|
|
163
|
+
# Create a temporary formatter instance to run the fix
|
|
164
|
+
agent = FormatterAgent(
|
|
165
|
+
name="auto-formatter",
|
|
166
|
+
triggers=[],
|
|
167
|
+
event_bus=None,
|
|
168
|
+
config={
|
|
169
|
+
"formatOnSave": True,
|
|
170
|
+
"reportOnly": False,
|
|
171
|
+
"filePatterns": ["**/*"],
|
|
172
|
+
"formatters": {path.suffix.lstrip("."): formatter},
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Run the formatter directly
|
|
177
|
+
success, error = await agent._run_formatter(formatter, path)
|
|
178
|
+
return success
|
|
179
|
+
|
|
180
|
+
async def _execute_linter_fix(self, finding: Finding) -> bool:
|
|
181
|
+
"""Execute a linter fix."""
|
|
182
|
+
# For now, we'll implement basic auto-fixes
|
|
183
|
+
# In a full implementation, this would use ruff --fix or similar
|
|
184
|
+
file_path = finding.file
|
|
185
|
+
if not file_path:
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
message = finding.message.lower()
|
|
189
|
+
|
|
190
|
+
# Handle specific fix types
|
|
191
|
+
if "unused import" in message:
|
|
192
|
+
return await self._fix_unused_import(file_path, finding.context)
|
|
193
|
+
elif "import sort" in message:
|
|
194
|
+
return await self._fix_import_sort(file_path)
|
|
195
|
+
elif "whitespace" in message or "indentation" in message:
|
|
196
|
+
return await self._fix_whitespace(file_path, finding.context)
|
|
197
|
+
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
async def _fix_unused_import(self, file_path: str, context: Dict) -> bool:
|
|
201
|
+
"""Fix unused import."""
|
|
202
|
+
# This would need more sophisticated parsing
|
|
203
|
+
# For now, return False to be safe
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
async def _fix_import_sort(self, file_path: str) -> bool:
|
|
207
|
+
"""Fix import sorting."""
|
|
208
|
+
# Could use isort or similar
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
async def _fix_whitespace(self, file_path: str, context: Dict) -> bool:
|
|
212
|
+
"""Fix whitespace issues."""
|
|
213
|
+
# Could use autopep8 or similar
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# Global instance
|
|
218
|
+
auto_fix = AutoFix()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
async def apply_safe_fixes():
|
|
222
|
+
"""Convenience function to apply safe fixes."""
|
|
223
|
+
results = await auto_fix.apply_safe_fixes()
|
|
224
|
+
return results
|
devloop/core/config.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Configuration management for devloop."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class AutonomousFixesConfig:
|
|
12
|
+
"""Configuration for autonomous fix application."""
|
|
13
|
+
|
|
14
|
+
enabled: bool = False
|
|
15
|
+
safety_level: str = "safe_only"
|
|
16
|
+
|
|
17
|
+
def __post_init__(self):
|
|
18
|
+
if self.safety_level not in ["safe_only", "medium_risk", "all"]:
|
|
19
|
+
raise ValueError(f"Invalid safety_level: {self.safety_level}")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class GlobalConfig:
|
|
24
|
+
"""Global configuration."""
|
|
25
|
+
|
|
26
|
+
mode: str = "report-only"
|
|
27
|
+
max_concurrent_agents: int = 5
|
|
28
|
+
notification_level: str = "summary"
|
|
29
|
+
context_store_enabled: bool = True
|
|
30
|
+
context_store_path: str = ".devloop/context"
|
|
31
|
+
autonomous_fixes: Optional[AutonomousFixesConfig] = None
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
if self.mode not in ["report-only", "active"]:
|
|
35
|
+
raise ValueError(f"Invalid mode: {self.mode}")
|
|
36
|
+
if self.notification_level not in ["none", "summary", "detailed"]:
|
|
37
|
+
raise ValueError(f"Invalid notification_level: {self.notification_level}")
|
|
38
|
+
if self.autonomous_fixes is None:
|
|
39
|
+
self.autonomous_fixes = AutonomousFixesConfig()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Config:
|
|
43
|
+
"""Main configuration manager."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, config_path: str = ".devloop/agents.json"):
|
|
46
|
+
self.config_path = Path(config_path)
|
|
47
|
+
self._config: Optional[Dict[str, Any]] = None
|
|
48
|
+
|
|
49
|
+
def load(self) -> Dict[str, Any]:
|
|
50
|
+
"""Load configuration from file."""
|
|
51
|
+
# Always reload for development - remove caching for now
|
|
52
|
+
if not self.config_path.exists():
|
|
53
|
+
# Return default config
|
|
54
|
+
return self._get_default_config()
|
|
55
|
+
else:
|
|
56
|
+
try:
|
|
57
|
+
with open(self.config_path, "r") as f:
|
|
58
|
+
return json.load(f)
|
|
59
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
60
|
+
print(f"Warning: Could not load config from {self.config_path}: {e}")
|
|
61
|
+
return self._get_default_config()
|
|
62
|
+
|
|
63
|
+
def get_global_config(self) -> GlobalConfig:
|
|
64
|
+
"""Get global configuration."""
|
|
65
|
+
config = self.load()
|
|
66
|
+
global_config = config.get("global", {})
|
|
67
|
+
|
|
68
|
+
autonomous_fixes_config = global_config.get("autonomousFixes", {})
|
|
69
|
+
autonomous_fixes = AutonomousFixesConfig(
|
|
70
|
+
enabled=autonomous_fixes_config.get("enabled", False),
|
|
71
|
+
safety_level=autonomous_fixes_config.get("safetyLevel", "safe_only"),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return GlobalConfig(
|
|
75
|
+
mode=global_config.get("mode", "report-only"),
|
|
76
|
+
max_concurrent_agents=global_config.get("maxConcurrentAgents", 5),
|
|
77
|
+
notification_level=global_config.get("notificationLevel", "summary"),
|
|
78
|
+
context_store_enabled=global_config.get("contextStore", {}).get(
|
|
79
|
+
"enabled", True
|
|
80
|
+
),
|
|
81
|
+
context_store_path=global_config.get("contextStore", {}).get(
|
|
82
|
+
"path", ".devloop/context"
|
|
83
|
+
),
|
|
84
|
+
autonomous_fixes=autonomous_fixes,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def default_config() -> "Config":
|
|
89
|
+
"""Get default configuration instance."""
|
|
90
|
+
config = Config()
|
|
91
|
+
config._config = config._get_default_config()
|
|
92
|
+
return config
|
|
93
|
+
|
|
94
|
+
def save(self, path: Path) -> None:
|
|
95
|
+
"""Save configuration to file."""
|
|
96
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
with open(path, "w") as f:
|
|
98
|
+
json.dump(self._config, f, indent=2)
|
|
99
|
+
|
|
100
|
+
def _get_default_config(self) -> Dict[str, Any]:
|
|
101
|
+
"""Get default configuration."""
|
|
102
|
+
return {
|
|
103
|
+
"version": "1.0.0",
|
|
104
|
+
"enabled": True,
|
|
105
|
+
"agents": {
|
|
106
|
+
"linter": {
|
|
107
|
+
"enabled": True,
|
|
108
|
+
"triggers": ["file:modified", "file:created"],
|
|
109
|
+
"config": {
|
|
110
|
+
"autoFix": False,
|
|
111
|
+
"reportOnly": True,
|
|
112
|
+
"filePatterns": ["**/*.py", "**/*.js", "**/*.ts"],
|
|
113
|
+
"linters": {
|
|
114
|
+
"python": "ruff",
|
|
115
|
+
"javascript": "eslint",
|
|
116
|
+
"typescript": "eslint",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
"formatter": {
|
|
121
|
+
"enabled": True,
|
|
122
|
+
"triggers": ["file:modified"],
|
|
123
|
+
"config": {
|
|
124
|
+
"formatOnSave": False,
|
|
125
|
+
"reportOnly": True,
|
|
126
|
+
"filePatterns": ["**/*.py", "**/*.js", "**/*.ts"],
|
|
127
|
+
"formatters": {
|
|
128
|
+
"python": "black",
|
|
129
|
+
"javascript": "prettier",
|
|
130
|
+
"typescript": "prettier",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
"test-runner": {
|
|
135
|
+
"enabled": True,
|
|
136
|
+
"triggers": ["file:modified", "file:created"],
|
|
137
|
+
"config": {
|
|
138
|
+
"runOnSave": True,
|
|
139
|
+
"relatedTestsOnly": True,
|
|
140
|
+
"testFrameworks": {
|
|
141
|
+
"python": "pytest",
|
|
142
|
+
"javascript": "jest",
|
|
143
|
+
"typescript": "jest",
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
"agent-health-monitor": {
|
|
148
|
+
"enabled": True,
|
|
149
|
+
"triggers": ["agent:*:completed"],
|
|
150
|
+
"config": {"monitorAllAgents": True, "autoFixOnFailure": True},
|
|
151
|
+
},
|
|
152
|
+
"type-checker": {
|
|
153
|
+
"enabled": True,
|
|
154
|
+
"triggers": ["file:modified", "file:created"],
|
|
155
|
+
"config": {
|
|
156
|
+
"enabled_tools": ["mypy"],
|
|
157
|
+
"strict_mode": False,
|
|
158
|
+
"show_error_codes": True,
|
|
159
|
+
"exclude_patterns": ["test_*", "*_test.py", "*/tests/*"],
|
|
160
|
+
"max_issues": 50,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
"security-scanner": {
|
|
164
|
+
"enabled": True,
|
|
165
|
+
"triggers": ["file:modified", "file:created"],
|
|
166
|
+
"config": {
|
|
167
|
+
"enabled_tools": ["bandit"],
|
|
168
|
+
"severity_threshold": "medium",
|
|
169
|
+
"confidence_threshold": "medium",
|
|
170
|
+
"exclude_patterns": ["test_*", "*_test.py", "*/tests/*"],
|
|
171
|
+
"max_issues": 50,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
"git-commit-assistant": {
|
|
175
|
+
"enabled": True,
|
|
176
|
+
"triggers": ["git:pre-commit", "git:commit"],
|
|
177
|
+
"config": {
|
|
178
|
+
"conventional_commits": True,
|
|
179
|
+
"max_message_length": 72,
|
|
180
|
+
"include_breaking_changes": True,
|
|
181
|
+
"analyze_file_changes": True,
|
|
182
|
+
"auto_generate_scope": True,
|
|
183
|
+
"common_types": [
|
|
184
|
+
"feat",
|
|
185
|
+
"fix",
|
|
186
|
+
"docs",
|
|
187
|
+
"style",
|
|
188
|
+
"refactor",
|
|
189
|
+
"test",
|
|
190
|
+
"chore",
|
|
191
|
+
"perf",
|
|
192
|
+
"ci",
|
|
193
|
+
"build",
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
"performance-profiler": {
|
|
198
|
+
"enabled": True,
|
|
199
|
+
"triggers": ["file:modified", "file:created"],
|
|
200
|
+
"config": {
|
|
201
|
+
"complexity_threshold": 10,
|
|
202
|
+
"min_lines_threshold": 50,
|
|
203
|
+
"enabled_tools": ["radon"],
|
|
204
|
+
"exclude_patterns": [
|
|
205
|
+
"test_*",
|
|
206
|
+
"*_test.py",
|
|
207
|
+
"*/tests/*",
|
|
208
|
+
"__init__.py",
|
|
209
|
+
],
|
|
210
|
+
"max_issues": 50,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
"global": {
|
|
215
|
+
"mode": "report-only",
|
|
216
|
+
"maxConcurrentAgents": 5,
|
|
217
|
+
"notificationLevel": "summary",
|
|
218
|
+
"resourceLimits": {},
|
|
219
|
+
"logging": {},
|
|
220
|
+
"contextStore": {"enabled": True, "path": ".devloop/context"},
|
|
221
|
+
"autonomousFixes": {"enabled": True, "safetyLevel": "safe_only"},
|
|
222
|
+
},
|
|
223
|
+
"eventSystem": {"collectors": {}, "dispatcher": {}, "store": {}},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ConfigWrapper:
|
|
228
|
+
"""Wrapper around config dictionary to provide object-like access."""
|
|
229
|
+
|
|
230
|
+
def __init__(self, config_dict: Dict[str, Any]):
|
|
231
|
+
self._config = config_dict
|
|
232
|
+
|
|
233
|
+
def is_agent_enabled(self, agent_name: str) -> bool:
|
|
234
|
+
"""Check if an agent is enabled."""
|
|
235
|
+
agents = self._config.get("agents", {})
|
|
236
|
+
agent_config = agents.get(agent_name, {})
|
|
237
|
+
return agent_config.get("enabled", False)
|
|
238
|
+
|
|
239
|
+
def get_agent_config(self, agent_name: str) -> Optional[Dict[str, Any]]:
|
|
240
|
+
"""Get configuration for a specific agent."""
|
|
241
|
+
agents = self._config.get("agents", {})
|
|
242
|
+
return agents.get(agent_name)
|
|
243
|
+
|
|
244
|
+
def get_global_config(self) -> GlobalConfig:
|
|
245
|
+
"""Get global configuration."""
|
|
246
|
+
global_config = self._config.get("global", {})
|
|
247
|
+
autonomous_fixes_config = global_config.get("autonomousFixes", {})
|
|
248
|
+
autonomous_fixes = AutonomousFixesConfig(
|
|
249
|
+
enabled=autonomous_fixes_config.get("enabled", False),
|
|
250
|
+
safety_level=autonomous_fixes_config.get("safetyLevel", "safe_only"),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return GlobalConfig(
|
|
254
|
+
mode=global_config.get("mode", "report-only"),
|
|
255
|
+
max_concurrent_agents=global_config.get("maxConcurrentAgents", 5),
|
|
256
|
+
notification_level=global_config.get("notificationLevel", "summary"),
|
|
257
|
+
context_store_enabled=global_config.get("contextStore", {}).get(
|
|
258
|
+
"enabled", True
|
|
259
|
+
),
|
|
260
|
+
context_store_path=global_config.get("contextStore", {}).get(
|
|
261
|
+
"path", ".devloop/context"
|
|
262
|
+
),
|
|
263
|
+
autonomous_fixes=autonomous_fixes,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def agents(self):
|
|
267
|
+
"""Get agents dictionary."""
|
|
268
|
+
return self._config.get("agents", {})
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# Global config instance
|
|
272
|
+
config = Config()
|
devloop/core/context.py
ADDED
|
File without changes
|