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,242 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Type Checker Agent - Runs static type checking on code."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import subprocess # nosec B404 - Required for running type checking tools
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Any, List, Optional
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
from ..core.agent import Agent, AgentResult
|
|
12
|
+
from ..core.event import Event
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class TypeCheckerConfig:
|
|
17
|
+
"""Configuration for type checking."""
|
|
18
|
+
|
|
19
|
+
enabled_tools: List[str] = field(default_factory=lambda: ["mypy"])
|
|
20
|
+
strict_mode: bool = False
|
|
21
|
+
show_error_codes: bool = True
|
|
22
|
+
exclude_patterns: List[str] = field(
|
|
23
|
+
default_factory=lambda: ["test*", "*_test.py", "*/tests/*"]
|
|
24
|
+
)
|
|
25
|
+
max_issues: int = 50
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TypeCheckResult:
|
|
29
|
+
"""Type check result."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self, tool: str, issues: List[Dict[str, Any]], errors: List[str] = None
|
|
33
|
+
):
|
|
34
|
+
self.tool = tool
|
|
35
|
+
self.issues = issues
|
|
36
|
+
self.errors = errors or []
|
|
37
|
+
self.timestamp = None
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
40
|
+
return {
|
|
41
|
+
"tool": self.tool,
|
|
42
|
+
"issues_found": len(self.issues),
|
|
43
|
+
"issues": self.issues,
|
|
44
|
+
"errors": self.errors,
|
|
45
|
+
"severity_breakdown": self._get_severity_breakdown(),
|
|
46
|
+
"summary": f"Found {len(self.issues)} type issues",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def _get_severity_breakdown(self) -> Dict[str, int]:
|
|
50
|
+
breakdown = {"error": 0, "warning": 0, "note": 0, "unknown": 0}
|
|
51
|
+
for issue in self.issues:
|
|
52
|
+
severity = issue.get("severity", "unknown").lower()
|
|
53
|
+
breakdown[severity] = breakdown.get(severity, 0) + 1
|
|
54
|
+
return breakdown
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TypeCheckerAgent(Agent):
|
|
58
|
+
"""Agent for running type checkers on code."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, config: Dict[str, Any], event_bus):
|
|
61
|
+
super().__init__("type-checker", ["file:modified", "file:created"], event_bus)
|
|
62
|
+
self.config = TypeCheckerConfig(**config)
|
|
63
|
+
self.logger = logging.getLogger(f"agent.{self.name}")
|
|
64
|
+
|
|
65
|
+
async def handle(self, event: Event) -> AgentResult:
|
|
66
|
+
"""Handle file change events by running type checks."""
|
|
67
|
+
try:
|
|
68
|
+
file_path = event.payload.get("path")
|
|
69
|
+
if not file_path:
|
|
70
|
+
return AgentResult(
|
|
71
|
+
agent_name=self.name,
|
|
72
|
+
success=False,
|
|
73
|
+
duration=0.0,
|
|
74
|
+
message="No file path in event",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
path = Path(file_path)
|
|
78
|
+
if not path.exists():
|
|
79
|
+
return AgentResult(
|
|
80
|
+
agent_name=self.name,
|
|
81
|
+
success=False,
|
|
82
|
+
duration=0.0,
|
|
83
|
+
message=f"File does not exist: {file_path}",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Only check Python files for now
|
|
87
|
+
if path.suffix != ".py":
|
|
88
|
+
return AgentResult(
|
|
89
|
+
agent_name=self.name,
|
|
90
|
+
success=True,
|
|
91
|
+
duration=0.0,
|
|
92
|
+
message=f"Skipped non-Python file: {file_path}",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Check if file matches exclude patterns
|
|
96
|
+
if self._should_exclude_file(str(path)):
|
|
97
|
+
return AgentResult(
|
|
98
|
+
agent_name=self.name,
|
|
99
|
+
success=True,
|
|
100
|
+
duration=0.0,
|
|
101
|
+
message=f"Excluded file: {file_path}",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Run type check
|
|
105
|
+
results = await self._run_type_check(path)
|
|
106
|
+
|
|
107
|
+
agent_result = AgentResult(
|
|
108
|
+
agent_name=self.name,
|
|
109
|
+
success=True,
|
|
110
|
+
duration=0.0, # Would be calculated in real implementation
|
|
111
|
+
message=f"Type checked {file_path} with {results.tool}",
|
|
112
|
+
data={
|
|
113
|
+
"file": str(path),
|
|
114
|
+
"tool": results.tool,
|
|
115
|
+
"issues_found": len(results.issues),
|
|
116
|
+
"issues": results.issues,
|
|
117
|
+
"severity_breakdown": results._get_severity_breakdown(),
|
|
118
|
+
"errors": results.errors,
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return agent_result
|
|
123
|
+
except Exception as e:
|
|
124
|
+
self.logger.error(
|
|
125
|
+
f"Error handling type check for {event.payload.get('path', 'unknown')}: {e}",
|
|
126
|
+
exc_info=True,
|
|
127
|
+
)
|
|
128
|
+
return AgentResult(
|
|
129
|
+
agent_name=self.name,
|
|
130
|
+
success=False,
|
|
131
|
+
duration=0.0,
|
|
132
|
+
message=f"Type check failed: {str(e)}",
|
|
133
|
+
error=str(e),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def _should_exclude_file(self, file_path: str) -> bool:
|
|
137
|
+
"""Check if file should be excluded from type checking."""
|
|
138
|
+
if not self.config.exclude_patterns:
|
|
139
|
+
return False
|
|
140
|
+
for pattern in self.config.exclude_patterns:
|
|
141
|
+
if pattern.startswith("*") and pattern.endswith("*"):
|
|
142
|
+
if pattern[1:-1] in file_path:
|
|
143
|
+
return True
|
|
144
|
+
elif pattern.startswith("*"):
|
|
145
|
+
if file_path.endswith(pattern[1:]):
|
|
146
|
+
return True
|
|
147
|
+
elif pattern.endswith("*"):
|
|
148
|
+
if file_path.startswith(pattern[:-1]):
|
|
149
|
+
return True
|
|
150
|
+
elif pattern == file_path:
|
|
151
|
+
return True
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
async def _run_type_check(self, file_path: Path) -> TypeCheckResult:
|
|
155
|
+
"""Run type checking tools."""
|
|
156
|
+
results = []
|
|
157
|
+
|
|
158
|
+
# Try mypy first (most common Python type checker)
|
|
159
|
+
if "mypy" in self.config.enabled_tools:
|
|
160
|
+
mypy_result = self._run_mypy(file_path)
|
|
161
|
+
if mypy_result:
|
|
162
|
+
results.append(mypy_result)
|
|
163
|
+
|
|
164
|
+
# If no results from primary tools, return empty
|
|
165
|
+
if results:
|
|
166
|
+
return results[0] # Return first successful result
|
|
167
|
+
|
|
168
|
+
return TypeCheckResult("none", [], ["No type checking tools available"])
|
|
169
|
+
|
|
170
|
+
def _run_mypy(self, file_path: Path) -> Optional[TypeCheckResult]:
|
|
171
|
+
"""Run MyPy type checker."""
|
|
172
|
+
try:
|
|
173
|
+
# Check if mypy is available
|
|
174
|
+
result = subprocess.run(
|
|
175
|
+
[sys.executable, "-c", "import mypy"], capture_output=True, text=True
|
|
176
|
+
) # nosec B603 - Running trusted system Python with safe arguments
|
|
177
|
+
if result.returncode != 0:
|
|
178
|
+
return TypeCheckResult(
|
|
179
|
+
"mypy", [], ["MyPy not installed - run: pip install mypy"]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
cmd = [
|
|
183
|
+
sys.executable,
|
|
184
|
+
"-m",
|
|
185
|
+
"mypy",
|
|
186
|
+
str(file_path),
|
|
187
|
+
"--show-error-codes",
|
|
188
|
+
"--no-error-summary",
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
if self.config.strict_mode:
|
|
192
|
+
cmd.append("--strict")
|
|
193
|
+
|
|
194
|
+
# Run mypy in subprocess
|
|
195
|
+
result = subprocess.run(
|
|
196
|
+
cmd,
|
|
197
|
+
capture_output=True,
|
|
198
|
+
text=True,
|
|
199
|
+
cwd=file_path.parent,
|
|
200
|
+
) # nosec B603 - Running mypy with controlled command arguments
|
|
201
|
+
issues = []
|
|
202
|
+
|
|
203
|
+
# Parse mypy output (line by line)
|
|
204
|
+
output_lines = result.stdout.strip().split("\n")
|
|
205
|
+
for line in output_lines:
|
|
206
|
+
if line.strip() and not line.startswith("Success:"):
|
|
207
|
+
# Parse mypy error format: file:line: error: message [error-code]
|
|
208
|
+
parts = line.split(":", 3)
|
|
209
|
+
if len(parts) >= 4:
|
|
210
|
+
filename = parts[0].strip()
|
|
211
|
+
try:
|
|
212
|
+
line_number = int(parts[1].strip())
|
|
213
|
+
except ValueError:
|
|
214
|
+
line_number = 0
|
|
215
|
+
|
|
216
|
+
error_type = parts[2].strip()
|
|
217
|
+
message_and_code = parts[3].strip()
|
|
218
|
+
|
|
219
|
+
# Extract error code if present
|
|
220
|
+
error_code = ""
|
|
221
|
+
if "[" in message_and_code and "]" in message_and_code:
|
|
222
|
+
message, code_part = message_and_code.rsplit("[", 1)
|
|
223
|
+
error_code = code_part.rstrip("]")
|
|
224
|
+
message = message.strip()
|
|
225
|
+
else:
|
|
226
|
+
message = message_and_code
|
|
227
|
+
|
|
228
|
+
issues.append(
|
|
229
|
+
{
|
|
230
|
+
"filename": filename,
|
|
231
|
+
"line_number": line_number,
|
|
232
|
+
"severity": error_type,
|
|
233
|
+
"message": message,
|
|
234
|
+
"error_code": error_code,
|
|
235
|
+
"tool": "mypy",
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return TypeCheckResult("mypy", issues[: self.config.max_issues])
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
return TypeCheckResult("mypy", [], [f"MyPy execution error: {str(e)}"])
|
devloop/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI interface."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI commands package."""
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""CLI commands for custom agent management."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from devloop.core.custom_agent import (
|
|
12
|
+
AgentBuilder,
|
|
13
|
+
CustomAgentStore,
|
|
14
|
+
get_agent_template,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(help="Manage custom agents")
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.command()
|
|
22
|
+
def list_agents(
|
|
23
|
+
project_dir: Optional[Path] = typer.Option(None, help="Project directory"),
|
|
24
|
+
):
|
|
25
|
+
"""List all custom agents."""
|
|
26
|
+
|
|
27
|
+
async def _run():
|
|
28
|
+
nonlocal project_dir
|
|
29
|
+
if project_dir is None:
|
|
30
|
+
project_dir = Path.cwd()
|
|
31
|
+
|
|
32
|
+
storage_path = project_dir / ".devloop" / "custom_agents"
|
|
33
|
+
store = CustomAgentStore(storage_path)
|
|
34
|
+
|
|
35
|
+
agents = await store.get_all_agents()
|
|
36
|
+
|
|
37
|
+
if not agents:
|
|
38
|
+
console.print("[yellow]No custom agents found[/yellow]")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
table = Table(title="Custom Agents")
|
|
42
|
+
table.add_column("ID", style="cyan")
|
|
43
|
+
table.add_column("Name", style="green")
|
|
44
|
+
table.add_column("Type", style="blue")
|
|
45
|
+
table.add_column("Status", style="yellow")
|
|
46
|
+
|
|
47
|
+
for agent_id, agent in agents.items():
|
|
48
|
+
table.add_row(agent_id, agent.name, agent.agent_type, "Active")
|
|
49
|
+
|
|
50
|
+
console.print(table)
|
|
51
|
+
|
|
52
|
+
asyncio.run(_run())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.command()
|
|
56
|
+
def create(
|
|
57
|
+
name: str = typer.Argument(..., help="Agent name"),
|
|
58
|
+
agent_type: str = typer.Option("detector", help="Agent type"),
|
|
59
|
+
description: str = typer.Option("", help="Agent description"),
|
|
60
|
+
project_dir: Optional[Path] = typer.Option(None, help="Project directory"),
|
|
61
|
+
):
|
|
62
|
+
"""Create a new custom agent."""
|
|
63
|
+
|
|
64
|
+
async def _run():
|
|
65
|
+
nonlocal project_dir
|
|
66
|
+
if project_dir is None:
|
|
67
|
+
project_dir = Path.cwd()
|
|
68
|
+
|
|
69
|
+
# Get template
|
|
70
|
+
template = get_agent_template(agent_type)
|
|
71
|
+
if not template:
|
|
72
|
+
console.print(f"[red]Unknown agent type: {agent_type}[/red]")
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
|
|
75
|
+
# Build custom agent
|
|
76
|
+
builder = AgentBuilder()
|
|
77
|
+
agent = builder.build(
|
|
78
|
+
name=name,
|
|
79
|
+
agent_type=agent_type,
|
|
80
|
+
description=description or template.get("description", ""),
|
|
81
|
+
config=template.get("config", {}),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Store it
|
|
85
|
+
storage_path = project_dir / ".devloop" / "custom_agents"
|
|
86
|
+
store = CustomAgentStore(storage_path)
|
|
87
|
+
await store.save_agent(agent)
|
|
88
|
+
|
|
89
|
+
console.print(f"[green]✓[/green] Created custom agent: [cyan]{name}[/cyan]")
|
|
90
|
+
console.print(f" ID: {agent.id}")
|
|
91
|
+
console.print(f" Type: {agent_type}")
|
|
92
|
+
|
|
93
|
+
asyncio.run(_run())
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command()
|
|
97
|
+
def delete(
|
|
98
|
+
agent_id: str = typer.Argument(..., help="Agent ID"),
|
|
99
|
+
project_dir: Optional[Path] = typer.Option(None, help="Project directory"),
|
|
100
|
+
):
|
|
101
|
+
"""Delete a custom agent."""
|
|
102
|
+
|
|
103
|
+
async def _run():
|
|
104
|
+
nonlocal project_dir
|
|
105
|
+
if project_dir is None:
|
|
106
|
+
project_dir = Path.cwd()
|
|
107
|
+
|
|
108
|
+
storage_path = project_dir / ".devloop" / "custom_agents"
|
|
109
|
+
store = CustomAgentStore(storage_path)
|
|
110
|
+
|
|
111
|
+
await store.delete_agent(agent_id)
|
|
112
|
+
console.print(f"[green]✓[/green] Deleted agent: [cyan]{agent_id}[/cyan]")
|
|
113
|
+
|
|
114
|
+
asyncio.run(_run())
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@app.command()
|
|
118
|
+
def templates():
|
|
119
|
+
"""Show available agent templates."""
|
|
120
|
+
templates = {
|
|
121
|
+
"detector": {
|
|
122
|
+
"description": "Pattern detection agent",
|
|
123
|
+
"triggers": ["file:modified"],
|
|
124
|
+
},
|
|
125
|
+
"analyzer": {
|
|
126
|
+
"description": "Code analysis agent",
|
|
127
|
+
"triggers": ["file:modified"],
|
|
128
|
+
},
|
|
129
|
+
"generator": {
|
|
130
|
+
"description": "Code generation agent",
|
|
131
|
+
"triggers": ["command:generate"],
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
table = Table(title="Available Agent Templates")
|
|
136
|
+
table.add_column("Type", style="cyan")
|
|
137
|
+
table.add_column("Description", style="green")
|
|
138
|
+
table.add_column("Default Triggers", style="yellow")
|
|
139
|
+
|
|
140
|
+
for template_type, template_data in templates.items():
|
|
141
|
+
triggers = ", ".join(template_data["triggers"])
|
|
142
|
+
table.add_row(template_type, template_data["description"], triggers)
|
|
143
|
+
|
|
144
|
+
console.print(table)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""CLI commands for agent feedback and performance monitoring."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from devloop.core.feedback import FeedbackAPI, FeedbackStore
|
|
12
|
+
from devloop.core.performance import PerformanceMonitor
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="Feedback and performance monitoring")
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_feedback_api(project_dir: Path = None) -> FeedbackAPI:
|
|
19
|
+
"""Get feedback API instance for the project."""
|
|
20
|
+
if project_dir is None:
|
|
21
|
+
project_dir = Path.cwd()
|
|
22
|
+
|
|
23
|
+
storage_path = project_dir / ".devloop" / "feedback"
|
|
24
|
+
feedback_store = FeedbackStore(storage_path)
|
|
25
|
+
return FeedbackAPI(feedback_store)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_performance_monitor(project_dir: Path = None) -> PerformanceMonitor:
|
|
29
|
+
"""Get performance monitor instance for the project."""
|
|
30
|
+
if project_dir is None:
|
|
31
|
+
project_dir = Path.cwd()
|
|
32
|
+
|
|
33
|
+
storage_path = project_dir / ".devloop" / "performance"
|
|
34
|
+
return PerformanceMonitor(storage_path)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command()
|
|
38
|
+
def list_feedback(
|
|
39
|
+
agent: Optional[str] = typer.Option(None, help="Filter by agent name"),
|
|
40
|
+
limit: int = typer.Option(20, help="Maximum number of feedback items to show"),
|
|
41
|
+
project_dir: Optional[Path] = typer.Option(None, help="Project directory"),
|
|
42
|
+
):
|
|
43
|
+
"""List recent feedback for agents."""
|
|
44
|
+
|
|
45
|
+
async def _run():
|
|
46
|
+
feedback_api = get_feedback_api(project_dir)
|
|
47
|
+
|
|
48
|
+
if agent:
|
|
49
|
+
# Get feedback for specific agent
|
|
50
|
+
feedback_items = await feedback_api.feedback_store.get_feedback_for_agent(
|
|
51
|
+
agent
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
# Get all feedback
|
|
55
|
+
feedback_items = await feedback_api.feedback_store.get_all_feedback()
|
|
56
|
+
|
|
57
|
+
feedback_items = feedback_items[-limit:] # Get last N items
|
|
58
|
+
|
|
59
|
+
if not feedback_items:
|
|
60
|
+
console.print("[yellow]No feedback found[/yellow]")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
table = Table(title="Agent Feedback")
|
|
64
|
+
table.add_column("Timestamp", style="cyan")
|
|
65
|
+
table.add_column("Agent", style="green")
|
|
66
|
+
table.add_column("Type", style="blue")
|
|
67
|
+
table.add_column("Message", style="yellow")
|
|
68
|
+
|
|
69
|
+
for item in feedback_items:
|
|
70
|
+
table.add_row(
|
|
71
|
+
item.timestamp.isoformat(),
|
|
72
|
+
item.agent_name,
|
|
73
|
+
item.feedback_type,
|
|
74
|
+
item.message[:50] + "..." if len(item.message) > 50 else item.message,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
console.print(table)
|
|
78
|
+
|
|
79
|
+
asyncio.run(_run())
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.command()
|
|
83
|
+
def submit_feedback(
|
|
84
|
+
agent: str = typer.Option(..., help="Agent name"),
|
|
85
|
+
feedback_type: str = typer.Option(
|
|
86
|
+
"suggestion", help="Feedback type: suggestion|bug|improvement|other"
|
|
87
|
+
),
|
|
88
|
+
message: str = typer.Argument(..., help="Feedback message"),
|
|
89
|
+
project_dir: Optional[Path] = typer.Option(None, help="Project directory"),
|
|
90
|
+
):
|
|
91
|
+
"""Submit feedback for an agent."""
|
|
92
|
+
|
|
93
|
+
async def _run():
|
|
94
|
+
feedback_api = get_feedback_api(project_dir)
|
|
95
|
+
|
|
96
|
+
await feedback_api.submit_feedback(agent, feedback_type, message)
|
|
97
|
+
console.print(f"[green]✓[/green] Feedback submitted for [cyan]{agent}[/cyan]")
|
|
98
|
+
|
|
99
|
+
asyncio.run(_run())
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@app.command()
|
|
103
|
+
def performance_status(
|
|
104
|
+
project_dir: Optional[Path] = typer.Option(None, help="Project directory"),
|
|
105
|
+
):
|
|
106
|
+
"""Show agent performance metrics."""
|
|
107
|
+
|
|
108
|
+
async def _run():
|
|
109
|
+
monitor = get_performance_monitor(project_dir)
|
|
110
|
+
|
|
111
|
+
metrics = await monitor.get_performance_summary()
|
|
112
|
+
|
|
113
|
+
table = Table(title="Agent Performance Metrics")
|
|
114
|
+
table.add_column("Agent", style="cyan")
|
|
115
|
+
table.add_column("Success Rate", style="green")
|
|
116
|
+
table.add_column("Avg Duration (ms)", style="blue")
|
|
117
|
+
table.add_column("Total Runs", style="yellow")
|
|
118
|
+
|
|
119
|
+
for agent_name, data in metrics.items():
|
|
120
|
+
success_rate = (
|
|
121
|
+
f"{data.get('success_rate', 0):.1%}"
|
|
122
|
+
if data.get("success_rate") is not None
|
|
123
|
+
else "N/A"
|
|
124
|
+
)
|
|
125
|
+
avg_duration = (
|
|
126
|
+
f"{data.get('avg_duration', 0):.2f}"
|
|
127
|
+
if data.get("avg_duration") is not None
|
|
128
|
+
else "N/A"
|
|
129
|
+
)
|
|
130
|
+
total_runs = data.get("total_runs", 0)
|
|
131
|
+
|
|
132
|
+
table.add_row(agent_name, success_rate, avg_duration, str(total_runs))
|
|
133
|
+
|
|
134
|
+
console.print(table)
|
|
135
|
+
|
|
136
|
+
asyncio.run(_run())
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@app.command()
|
|
140
|
+
def performance_detail(
|
|
141
|
+
agent: str = typer.Option(..., help="Agent name"),
|
|
142
|
+
project_dir: Optional[Path] = typer.Option(None, help="Project directory"),
|
|
143
|
+
):
|
|
144
|
+
"""Show detailed performance metrics for an agent."""
|
|
145
|
+
|
|
146
|
+
async def _run():
|
|
147
|
+
monitor = get_performance_monitor(project_dir)
|
|
148
|
+
|
|
149
|
+
metrics = await monitor.get_agent_metrics(agent)
|
|
150
|
+
|
|
151
|
+
if not metrics:
|
|
152
|
+
console.print(f"[yellow]No metrics found for agent: {agent}[/yellow]")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
console.print(f"\n[bold]Performance Metrics for {agent}[/bold]")
|
|
156
|
+
console.print(f" Success Rate: {metrics.get('success_rate', 0):.1%}")
|
|
157
|
+
console.print(f" Average Duration: {metrics.get('avg_duration', 0):.2f}ms")
|
|
158
|
+
console.print(f" Total Runs: {metrics.get('total_runs', 0)}")
|
|
159
|
+
console.print(f" Last Run: {metrics.get('last_run', 'Never')}")
|
|
160
|
+
|
|
161
|
+
asyncio.run(_run())
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Agent summary CLI command."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from devloop.core.context_store import context_store
|
|
10
|
+
from devloop.core.summary_generator import SummaryGenerator
|
|
11
|
+
from devloop.core.summary_formatter import SummaryFormatter
|
|
12
|
+
|
|
13
|
+
app = typer.Typer()
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Summary formatting is now handled by SummaryFormatter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def agent_summary(
|
|
22
|
+
scope: str = typer.Argument(
|
|
23
|
+
"recent", help="Summary scope: recent|today|session|all"
|
|
24
|
+
),
|
|
25
|
+
agent: Optional[str] = typer.Option(None, "--agent", help="Filter by agent name"),
|
|
26
|
+
severity: Optional[str] = typer.Option(
|
|
27
|
+
None, "--severity", help="Filter by severity"
|
|
28
|
+
),
|
|
29
|
+
category: Optional[str] = typer.Option(
|
|
30
|
+
None, "--category", help="Filter by category"
|
|
31
|
+
),
|
|
32
|
+
):
|
|
33
|
+
"""Generate intelligent summary of dev-agent findings."""
|
|
34
|
+
filters = {}
|
|
35
|
+
if agent and agent is not None:
|
|
36
|
+
filters["agent"] = str(agent)
|
|
37
|
+
if severity and severity is not None:
|
|
38
|
+
filters["severity"] = str(severity)
|
|
39
|
+
if category and category is not None:
|
|
40
|
+
filters["category"] = str(category)
|
|
41
|
+
|
|
42
|
+
generator = SummaryGenerator(context_store)
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
report = asyncio.run(generator.generate_summary(scope, filters))
|
|
46
|
+
markdown_output = SummaryFormatter.format_markdown(report)
|
|
47
|
+
console.print(markdown_output)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
console.print(f"[red]Error generating summary: {e}[/red]")
|
|
50
|
+
raise typer.Exit(1)
|