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,275 @@
|
|
|
1
|
+
"""Summary generator for dev-agent findings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime, timedelta, UTC
|
|
8
|
+
from typing import Any, Dict, List, Tuple
|
|
9
|
+
|
|
10
|
+
from .context_store import Finding, Severity, Tier, context_store
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AgentSummary:
|
|
17
|
+
"""Summary for a specific agent."""
|
|
18
|
+
|
|
19
|
+
agent_name: str
|
|
20
|
+
finding_count: int
|
|
21
|
+
severity_breakdown: Dict[str, int]
|
|
22
|
+
top_issues: List[Finding]
|
|
23
|
+
improvement_trend: str # "improving", "worsening", "stable"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SummaryReport:
|
|
28
|
+
"""Comprehensive summary report."""
|
|
29
|
+
|
|
30
|
+
scope: str
|
|
31
|
+
time_range: Tuple[datetime, datetime]
|
|
32
|
+
total_findings: int
|
|
33
|
+
|
|
34
|
+
# Breakdowns
|
|
35
|
+
by_agent: Dict[str, AgentSummary]
|
|
36
|
+
by_severity: Dict[str, int]
|
|
37
|
+
by_category: Dict[str, int]
|
|
38
|
+
|
|
39
|
+
# Trends
|
|
40
|
+
trends: Dict[str, Any]
|
|
41
|
+
|
|
42
|
+
# Priority items
|
|
43
|
+
critical_issues: List[Finding]
|
|
44
|
+
auto_fixable: List[Finding]
|
|
45
|
+
|
|
46
|
+
# Insights
|
|
47
|
+
insights: List[str]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SummaryGenerator:
|
|
51
|
+
"""Generate intelligent summaries of dev-agent findings."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, context_store_instance=None):
|
|
54
|
+
"""Initialize summary generator.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
context_store_instance: Context store to use (defaults to global)
|
|
58
|
+
"""
|
|
59
|
+
self.context_store = context_store_instance or context_store
|
|
60
|
+
|
|
61
|
+
async def generate_summary(
|
|
62
|
+
self, scope: str = "recent", filters: Dict[str, Any] = None
|
|
63
|
+
) -> SummaryReport:
|
|
64
|
+
"""Generate intelligent summary of findings.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
scope: Time scope ("recent", "today", "session", "all")
|
|
68
|
+
filters: Optional filters (agent, severity, category)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
SummaryReport with findings analysis
|
|
72
|
+
"""
|
|
73
|
+
filters = filters or {}
|
|
74
|
+
|
|
75
|
+
# Get time range for scope
|
|
76
|
+
time_range = self._get_time_range(scope)
|
|
77
|
+
|
|
78
|
+
# Get all findings within time range
|
|
79
|
+
findings = await self._get_findings_in_range(time_range)
|
|
80
|
+
|
|
81
|
+
# Apply filters
|
|
82
|
+
findings = self._filter_findings(findings, filters)
|
|
83
|
+
|
|
84
|
+
# Generate report
|
|
85
|
+
report = SummaryReport(
|
|
86
|
+
scope=scope,
|
|
87
|
+
time_range=time_range,
|
|
88
|
+
total_findings=len(findings),
|
|
89
|
+
by_agent=self._group_by_agent(findings),
|
|
90
|
+
by_severity=self._count_by_severity(findings),
|
|
91
|
+
by_category=self._count_by_category(findings),
|
|
92
|
+
trends=self._calculate_trends(findings, scope),
|
|
93
|
+
critical_issues=self._get_critical_issues(findings),
|
|
94
|
+
auto_fixable=self._get_auto_fixable(findings),
|
|
95
|
+
insights=self._generate_insights(findings, scope),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return report
|
|
99
|
+
|
|
100
|
+
def _get_time_range(self, scope: str) -> Tuple[datetime, datetime]:
|
|
101
|
+
"""Get datetime range for scope."""
|
|
102
|
+
now = datetime.now(UTC)
|
|
103
|
+
|
|
104
|
+
if scope == "recent":
|
|
105
|
+
# Last 24 hours
|
|
106
|
+
start = now - timedelta(hours=24)
|
|
107
|
+
elif scope == "today":
|
|
108
|
+
# Start of today
|
|
109
|
+
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
110
|
+
elif scope == "session":
|
|
111
|
+
# Last 4 hours (typical coding session)
|
|
112
|
+
start = now - timedelta(hours=4)
|
|
113
|
+
elif scope == "all":
|
|
114
|
+
# All time - use a very old date
|
|
115
|
+
start = datetime(2020, 1, 1, tzinfo=UTC)
|
|
116
|
+
else:
|
|
117
|
+
raise ValueError(f"Unknown scope: {scope}")
|
|
118
|
+
|
|
119
|
+
return (start, now)
|
|
120
|
+
|
|
121
|
+
async def _get_findings_in_range(
|
|
122
|
+
self, time_range: Tuple[datetime, datetime]
|
|
123
|
+
) -> List[Finding]:
|
|
124
|
+
"""Get all findings within time range."""
|
|
125
|
+
start_time, end_time = time_range
|
|
126
|
+
|
|
127
|
+
# Get findings from all tiers
|
|
128
|
+
all_findings = []
|
|
129
|
+
for tier in [Tier.IMMEDIATE, Tier.RELEVANT, Tier.BACKGROUND]:
|
|
130
|
+
tier_findings = await self.context_store.get_findings(tier=tier)
|
|
131
|
+
all_findings.extend(tier_findings)
|
|
132
|
+
|
|
133
|
+
# Filter by timestamp
|
|
134
|
+
filtered_findings = []
|
|
135
|
+
for finding in all_findings:
|
|
136
|
+
try:
|
|
137
|
+
finding_time = datetime.fromisoformat(
|
|
138
|
+
finding.timestamp.replace("Z", "+00:00")
|
|
139
|
+
)
|
|
140
|
+
if start_time <= finding_time <= end_time:
|
|
141
|
+
filtered_findings.append(finding)
|
|
142
|
+
except (ValueError, AttributeError):
|
|
143
|
+
# If timestamp parsing fails, include the finding
|
|
144
|
+
filtered_findings.append(finding)
|
|
145
|
+
|
|
146
|
+
return filtered_findings
|
|
147
|
+
|
|
148
|
+
def _filter_findings(
|
|
149
|
+
self, findings: List[Finding], filters: Dict[str, Any]
|
|
150
|
+
) -> List[Finding]:
|
|
151
|
+
"""Apply user-specified filters."""
|
|
152
|
+
if not filters:
|
|
153
|
+
return findings
|
|
154
|
+
|
|
155
|
+
filtered = findings
|
|
156
|
+
|
|
157
|
+
# Filter by agent
|
|
158
|
+
if "agent" in filters:
|
|
159
|
+
agent_filter = filters["agent"]
|
|
160
|
+
filtered = [f for f in filtered if f.agent == agent_filter]
|
|
161
|
+
|
|
162
|
+
# Filter by severity
|
|
163
|
+
if "severity" in filters:
|
|
164
|
+
severity_filter = filters["severity"]
|
|
165
|
+
try:
|
|
166
|
+
severity_enum = Severity(severity_filter)
|
|
167
|
+
filtered = [f for f in filtered if f.severity == severity_enum]
|
|
168
|
+
except ValueError:
|
|
169
|
+
logger.warning(f"Invalid severity filter: {severity_filter}")
|
|
170
|
+
|
|
171
|
+
# Filter by category
|
|
172
|
+
if "category" in filters:
|
|
173
|
+
category_filter = filters["category"]
|
|
174
|
+
filtered = [f for f in filtered if f.category == category_filter]
|
|
175
|
+
|
|
176
|
+
return filtered
|
|
177
|
+
|
|
178
|
+
def _group_by_agent(self, findings: List[Finding]) -> Dict[str, AgentSummary]:
|
|
179
|
+
"""Group findings by agent type."""
|
|
180
|
+
agent_groups: Dict[str, List[Finding]] = {}
|
|
181
|
+
for finding in findings:
|
|
182
|
+
agent_groups.setdefault(finding.agent, []).append(finding)
|
|
183
|
+
|
|
184
|
+
summaries = {}
|
|
185
|
+
for agent_name, agent_findings in agent_groups.items():
|
|
186
|
+
# Count by severity
|
|
187
|
+
severity_counts: Dict[str, int] = {}
|
|
188
|
+
for finding in agent_findings:
|
|
189
|
+
severity = finding.severity.value
|
|
190
|
+
severity_counts[severity] = severity_counts.get(severity, 0) + 1
|
|
191
|
+
|
|
192
|
+
# Get top 3 issues by severity (errors first, then warnings, etc.)
|
|
193
|
+
sorted_findings = sorted(
|
|
194
|
+
agent_findings,
|
|
195
|
+
key=lambda f: (
|
|
196
|
+
f.severity.value == "error",
|
|
197
|
+
f.severity.value == "warning",
|
|
198
|
+
),
|
|
199
|
+
reverse=True,
|
|
200
|
+
)
|
|
201
|
+
top_issues = sorted_findings[:3]
|
|
202
|
+
|
|
203
|
+
summaries[agent_name] = AgentSummary(
|
|
204
|
+
agent_name=agent_name,
|
|
205
|
+
finding_count=len(agent_findings),
|
|
206
|
+
severity_breakdown=severity_counts,
|
|
207
|
+
top_issues=top_issues,
|
|
208
|
+
improvement_trend="stable", # TODO: Implement trend calculation
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return summaries
|
|
212
|
+
|
|
213
|
+
def _count_by_severity(self, findings: List[Finding]) -> Dict[str, int]:
|
|
214
|
+
"""Count findings by severity."""
|
|
215
|
+
counts: Dict[str, int] = {}
|
|
216
|
+
for finding in findings:
|
|
217
|
+
severity = finding.severity.value
|
|
218
|
+
counts[severity] = counts.get(severity, 0) + 1
|
|
219
|
+
return counts
|
|
220
|
+
|
|
221
|
+
def _count_by_category(self, findings: List[Finding]) -> Dict[str, int]:
|
|
222
|
+
"""Count findings by category."""
|
|
223
|
+
counts: Dict[str, int] = {}
|
|
224
|
+
for finding in findings:
|
|
225
|
+
counts[finding.category] = counts.get(finding.category, 0) + 1
|
|
226
|
+
return counts
|
|
227
|
+
|
|
228
|
+
def _calculate_trends(self, findings: List[Finding], scope: str) -> Dict[str, Any]:
|
|
229
|
+
"""Calculate improvement/worsening trends."""
|
|
230
|
+
# TODO: Implement actual trend calculation comparing to previous periods
|
|
231
|
+
# For now, return basic stats
|
|
232
|
+
return {
|
|
233
|
+
"comparison_period": "previous_24h",
|
|
234
|
+
"change_percent": 0.0, # No change
|
|
235
|
+
"direction": "stable",
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
def _get_critical_issues(self, findings: List[Finding]) -> List[Finding]:
|
|
239
|
+
"""Get critical/blocking issues."""
|
|
240
|
+
return [f for f in findings if f.blocking or f.severity == Severity.ERROR]
|
|
241
|
+
|
|
242
|
+
def _get_auto_fixable(self, findings: List[Finding]) -> List[Finding]:
|
|
243
|
+
"""Get auto-fixable findings."""
|
|
244
|
+
return [f for f in findings if f.auto_fixable]
|
|
245
|
+
|
|
246
|
+
def _generate_insights(self, findings: List[Finding], scope: str) -> List[str]:
|
|
247
|
+
"""Generate actionable insights."""
|
|
248
|
+
insights = []
|
|
249
|
+
|
|
250
|
+
if not findings:
|
|
251
|
+
return ["No findings in the selected scope"]
|
|
252
|
+
|
|
253
|
+
# Count by agent
|
|
254
|
+
agent_counts: Dict[str, int] = {}
|
|
255
|
+
for finding in findings:
|
|
256
|
+
agent_counts[finding.agent] = agent_counts.get(finding.agent, 0) + 1
|
|
257
|
+
|
|
258
|
+
# Most active agent
|
|
259
|
+
if agent_counts:
|
|
260
|
+
top_agent = max(agent_counts.items(), key=lambda x: x[1])
|
|
261
|
+
insights.append(
|
|
262
|
+
f"Most active agent: {top_agent[0]} ({top_agent[1]} findings)"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Severity breakdown insight
|
|
266
|
+
error_count = sum(1 for f in findings if f.severity == Severity.ERROR)
|
|
267
|
+
if error_count > 0:
|
|
268
|
+
insights.append(f"{error_count} error-level findings requiring attention")
|
|
269
|
+
|
|
270
|
+
# Auto-fixable insight
|
|
271
|
+
auto_fixable_count = sum(1 for f in findings if f.auto_fixable)
|
|
272
|
+
if auto_fixable_count > 0:
|
|
273
|
+
insights.append(f"{auto_fixable_count} findings can be auto-fixed")
|
|
274
|
+
|
|
275
|
+
return insights
|