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,311 @@
|
|
|
1
|
+
"""Contextual feedback inference from developer behavior patterns."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from .event import Event, EventBus
|
|
12
|
+
from .feedback import FeedbackAPI, FeedbackType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class DeveloperAction:
|
|
17
|
+
"""Represents a developer action that can provide implicit feedback."""
|
|
18
|
+
|
|
19
|
+
action_type: (
|
|
20
|
+
str # 'file_save', 'file_edit', 'cursor_move', 'suggestion_accept', etc.
|
|
21
|
+
)
|
|
22
|
+
file_path: Optional[str]
|
|
23
|
+
timestamp: float
|
|
24
|
+
context: Optional[Dict[str, Any]] = None
|
|
25
|
+
|
|
26
|
+
def __post_init__(self):
|
|
27
|
+
if self.context is None:
|
|
28
|
+
self.context = {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ContextualFeedbackEngine:
|
|
32
|
+
"""Engine that infers feedback from developer behavior patterns."""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self, event_bus: EventBus, feedback_api: FeedbackAPI, project_dir: Path
|
|
36
|
+
):
|
|
37
|
+
self.event_bus = event_bus
|
|
38
|
+
self.feedback_api = feedback_api
|
|
39
|
+
self.project_dir = project_dir
|
|
40
|
+
|
|
41
|
+
# Track recent developer actions
|
|
42
|
+
self.recent_actions: List[DeveloperAction] = []
|
|
43
|
+
self.action_window = 300 # 5 minutes window for action correlation
|
|
44
|
+
|
|
45
|
+
# Track agent actions to correlate with developer responses
|
|
46
|
+
self.recent_agent_actions: List[Dict[str, Any]] = []
|
|
47
|
+
self.agent_action_window = 600 # 10 minutes
|
|
48
|
+
|
|
49
|
+
# File interaction patterns
|
|
50
|
+
self.file_interaction_times: Dict[str, List[float]] = {}
|
|
51
|
+
self.file_last_modified: Dict[str, float] = {}
|
|
52
|
+
|
|
53
|
+
# Initialize event subscriptions
|
|
54
|
+
self._setup_event_listeners()
|
|
55
|
+
|
|
56
|
+
def _setup_event_listeners(self):
|
|
57
|
+
"""Set up listeners for various developer actions."""
|
|
58
|
+
# Listen for agent completion events to correlate with developer actions
|
|
59
|
+
asyncio.create_task(
|
|
60
|
+
self.event_bus.subscribe("agent:*:completed", self._on_agent_completed)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Listen for file system events that indicate developer activity
|
|
64
|
+
asyncio.create_task(self.event_bus.subscribe("file:*", self._on_file_event))
|
|
65
|
+
|
|
66
|
+
# Could be extended to listen for IDE events like cursor movements, selections, etc.
|
|
67
|
+
|
|
68
|
+
async def _on_agent_completed(self, event: Event) -> None:
|
|
69
|
+
"""Handle agent completion and look for correlated developer feedback."""
|
|
70
|
+
agent_name = event.payload.get("agent_name")
|
|
71
|
+
success = event.payload.get("success", False)
|
|
72
|
+
duration = event.payload.get("duration", 0)
|
|
73
|
+
|
|
74
|
+
# Store agent action for correlation
|
|
75
|
+
agent_action = {
|
|
76
|
+
"agent_name": agent_name,
|
|
77
|
+
"success": success,
|
|
78
|
+
"duration": duration,
|
|
79
|
+
"timestamp": time.time(),
|
|
80
|
+
"event": event.payload,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
self.recent_agent_actions.append(agent_action)
|
|
84
|
+
|
|
85
|
+
# Clean old agent actions
|
|
86
|
+
cutoff = time.time() - self.agent_action_window
|
|
87
|
+
self.recent_agent_actions = [
|
|
88
|
+
action
|
|
89
|
+
for action in self.recent_agent_actions
|
|
90
|
+
if action["timestamp"] > cutoff
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# Check for immediate feedback patterns
|
|
94
|
+
await self._analyze_immediate_feedback(agent_action)
|
|
95
|
+
|
|
96
|
+
async def _on_file_event(self, event: Event) -> None:
|
|
97
|
+
"""Handle file system events as developer actions."""
|
|
98
|
+
file_path = event.payload.get("path")
|
|
99
|
+
if not file_path:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
action = DeveloperAction(
|
|
103
|
+
action_type=f"file_{event.type.split(':')[1]}", # file_modified, file_created, etc.
|
|
104
|
+
file_path=file_path,
|
|
105
|
+
timestamp=time.time(),
|
|
106
|
+
context={"event_payload": event.payload},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
self.recent_actions.append(action)
|
|
110
|
+
|
|
111
|
+
# Track file interaction patterns
|
|
112
|
+
if file_path not in self.file_interaction_times:
|
|
113
|
+
self.file_interaction_times[file_path] = []
|
|
114
|
+
|
|
115
|
+
self.file_interaction_times[file_path].append(action.timestamp)
|
|
116
|
+
|
|
117
|
+
# Keep only recent interactions (last 24 hours)
|
|
118
|
+
cutoff = time.time() - 86400
|
|
119
|
+
self.file_interaction_times[file_path] = [
|
|
120
|
+
t for t in self.file_interaction_times[file_path] if t > cutoff
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
# Update last modified time
|
|
124
|
+
if event.type == "file:modified":
|
|
125
|
+
self.file_last_modified[file_path] = action.timestamp
|
|
126
|
+
|
|
127
|
+
# Analyze patterns
|
|
128
|
+
await self._analyze_file_patterns(file_path, action)
|
|
129
|
+
|
|
130
|
+
async def _analyze_immediate_feedback(self, agent_action: Dict[str, Any]) -> None:
|
|
131
|
+
"""Analyze immediate feedback patterns after agent actions."""
|
|
132
|
+
agent_time = agent_action["timestamp"]
|
|
133
|
+
|
|
134
|
+
# Look for developer actions within 30 seconds after agent action
|
|
135
|
+
immediate_window = 30
|
|
136
|
+
recent_actions = [
|
|
137
|
+
action
|
|
138
|
+
for action in self.recent_actions
|
|
139
|
+
if agent_time <= action.timestamp <= agent_time + immediate_window
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
if not recent_actions:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# Analyze the pattern of immediate actions
|
|
146
|
+
file_actions = [a for a in recent_actions if a.action_type.startswith("file_")]
|
|
147
|
+
|
|
148
|
+
if file_actions:
|
|
149
|
+
# Developer made file changes shortly after agent action
|
|
150
|
+
# This could indicate engagement with agent results
|
|
151
|
+
await self._infer_feedback_from_file_changes(agent_action, file_actions)
|
|
152
|
+
|
|
153
|
+
async def _analyze_file_patterns(
|
|
154
|
+
self, file_path: str, latest_action: DeveloperAction
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Analyze file interaction patterns to infer feedback."""
|
|
157
|
+
if file_path not in self.file_interaction_times:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
interactions = self.file_interaction_times[file_path]
|
|
161
|
+
|
|
162
|
+
# Look for patterns that might indicate satisfaction/dissatisfaction
|
|
163
|
+
if len(interactions) >= 3:
|
|
164
|
+
# Calculate interaction frequency
|
|
165
|
+
time_span = interactions[-1] - interactions[0]
|
|
166
|
+
if time_span > 0:
|
|
167
|
+
frequency = len(interactions) / time_span # interactions per second
|
|
168
|
+
|
|
169
|
+
# High frequency of interactions might indicate dissatisfaction
|
|
170
|
+
# (developer repeatedly editing the same file)
|
|
171
|
+
if frequency > 0.01: # More than 1 interaction per 100 seconds
|
|
172
|
+
await self._infer_feedback_from_interaction_pattern(
|
|
173
|
+
file_path, frequency, "high_frequency_editing"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Check if file was modified shortly after being touched by agents
|
|
177
|
+
last_modified = self.file_last_modified.get(file_path, 0)
|
|
178
|
+
if latest_action.timestamp - last_modified < 60: # Modified within 1 minute
|
|
179
|
+
await self._infer_feedback_from_quick_modification(file_path, latest_action)
|
|
180
|
+
|
|
181
|
+
async def _infer_feedback_from_file_changes(
|
|
182
|
+
self, agent_action: Dict[str, Any], file_actions: List[DeveloperAction]
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Infer feedback from file changes after agent action."""
|
|
185
|
+
|
|
186
|
+
# If developer modifies files that agent just processed, it might indicate
|
|
187
|
+
# they're refining the agent's work (mixed feedback)
|
|
188
|
+
modified_files = set(
|
|
189
|
+
a.file_path for a in file_actions if a.action_type == "file_modified"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if modified_files:
|
|
193
|
+
# Submit neutral/mixed feedback
|
|
194
|
+
await self.feedback_api.submit_feedback(
|
|
195
|
+
agent_name=agent_action["agent_name"],
|
|
196
|
+
event_type="file_interaction",
|
|
197
|
+
feedback_type=FeedbackType.RATING,
|
|
198
|
+
value=3, # Neutral rating
|
|
199
|
+
comment="Developer refined agent output",
|
|
200
|
+
context={
|
|
201
|
+
"agent_action": agent_action,
|
|
202
|
+
"modified_files": list(modified_files),
|
|
203
|
+
"inference_type": "file_changes_after_agent",
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
async def _infer_feedback_from_interaction_pattern(
|
|
208
|
+
self, file_path: str, frequency: float, pattern_type: str
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Infer feedback from file interaction patterns."""
|
|
211
|
+
# Find agents that recently worked on this file
|
|
212
|
+
recent_agents = []
|
|
213
|
+
for agent_action in self.recent_agent_actions[-10:]: # Last 10 agent actions
|
|
214
|
+
# This is a simplified check - in reality we'd need to correlate
|
|
215
|
+
# agent actions with specific files
|
|
216
|
+
recent_agents.append(agent_action["agent_name"])
|
|
217
|
+
|
|
218
|
+
if not recent_agents:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
# High frequency editing might indicate dissatisfaction
|
|
222
|
+
if frequency > 0.02: # Very high frequency
|
|
223
|
+
feedback_value = 2 # Low rating
|
|
224
|
+
comment = f"High frequency file editing detected ({frequency:.3f} interactions/sec)"
|
|
225
|
+
elif frequency > 0.01: # Moderately high
|
|
226
|
+
feedback_value = 3 # Neutral rating
|
|
227
|
+
comment = f"Moderate file interaction frequency ({frequency:.3f} interactions/sec)"
|
|
228
|
+
else:
|
|
229
|
+
return # Normal frequency, no inference needed
|
|
230
|
+
|
|
231
|
+
# Submit feedback for the most recent agent
|
|
232
|
+
most_recent_agent = recent_agents[-1]
|
|
233
|
+
await self.feedback_api.submit_feedback(
|
|
234
|
+
agent_name=most_recent_agent,
|
|
235
|
+
event_type="file_interaction_pattern",
|
|
236
|
+
feedback_type=FeedbackType.RATING,
|
|
237
|
+
value=feedback_value,
|
|
238
|
+
comment=comment,
|
|
239
|
+
context={
|
|
240
|
+
"file_path": file_path,
|
|
241
|
+
"interaction_frequency": frequency,
|
|
242
|
+
"pattern_type": pattern_type,
|
|
243
|
+
"inference_type": "interaction_pattern",
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
async def _infer_feedback_from_quick_modification(
|
|
248
|
+
self, file_path: str, action: DeveloperAction
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Infer feedback from quick file modifications."""
|
|
251
|
+
# Find the most recent agent that might have worked on this file
|
|
252
|
+
# This is simplified - in a real implementation we'd track agent-file associations
|
|
253
|
+
recent_agent = None
|
|
254
|
+
for agent_action in reversed(self.recent_agent_actions[-5:]):
|
|
255
|
+
# Simplified check - look for agents that completed recently
|
|
256
|
+
if time.time() - agent_action["timestamp"] < 300: # Within 5 minutes
|
|
257
|
+
recent_agent = agent_action["agent_name"]
|
|
258
|
+
break
|
|
259
|
+
|
|
260
|
+
if not recent_agent:
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
# Quick modification might indicate the developer is actively working
|
|
264
|
+
# with the agent's output, which is generally positive
|
|
265
|
+
await self.feedback_api.submit_feedback(
|
|
266
|
+
agent_name=recent_agent,
|
|
267
|
+
event_type="quick_file_modification",
|
|
268
|
+
feedback_type=FeedbackType.THUMBS_UP,
|
|
269
|
+
value=True,
|
|
270
|
+
comment="Developer quickly engaged with agent output",
|
|
271
|
+
context={
|
|
272
|
+
"file_path": file_path,
|
|
273
|
+
"time_since_agent": time.time() - (recent_agent and 0 or time.time()),
|
|
274
|
+
"inference_type": "quick_modification",
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
async def get_contextual_insights(self, agent_name: str) -> Dict[str, Any]:
|
|
279
|
+
"""Get contextual insights about developer behavior patterns."""
|
|
280
|
+
# Analyze recent actions for patterns
|
|
281
|
+
cutoff_time = time.time() - 3600 # Last hour
|
|
282
|
+
|
|
283
|
+
agent_related_actions = []
|
|
284
|
+
for action in self.recent_agent_actions:
|
|
285
|
+
if action["agent_name"] == agent_name and action["timestamp"] > cutoff_time:
|
|
286
|
+
agent_related_actions.append(action)
|
|
287
|
+
|
|
288
|
+
# Count correlated developer actions
|
|
289
|
+
correlated_file_actions = 0
|
|
290
|
+
for agent_action in agent_related_actions:
|
|
291
|
+
agent_time = agent_action["timestamp"]
|
|
292
|
+
# Count developer actions within 5 minutes after agent action
|
|
293
|
+
correlated = sum(
|
|
294
|
+
1
|
|
295
|
+
for action in self.recent_actions
|
|
296
|
+
if agent_time <= action.timestamp <= agent_time + 300
|
|
297
|
+
)
|
|
298
|
+
correlated_file_actions += correlated
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
"agent_name": agent_name,
|
|
302
|
+
"time_window_hours": 1,
|
|
303
|
+
"agent_actions_count": len(agent_related_actions),
|
|
304
|
+
"correlated_developer_actions": correlated_file_actions,
|
|
305
|
+
"inference_types": [
|
|
306
|
+
"file_changes",
|
|
307
|
+
"interaction_patterns",
|
|
308
|
+
"quick_modifications",
|
|
309
|
+
],
|
|
310
|
+
"confidence_level": "medium", # Could be improved with ML
|
|
311
|
+
}
|