285 lines
8.8 KiB
Python
285 lines
8.8 KiB
Python
"""BacklogAgent - Backlog management for VoIdea.
|
|
|
|
This agent:
|
|
- Creates and manages backlog items
|
|
- Tracks ideas, plans, tasks
|
|
- Prioritizes work
|
|
- Sends reminders
|
|
"""
|
|
|
|
import re
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
from uuid import UUID, uuid4
|
|
|
|
from sqlalchemy import select, update, delete
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.agents.base import AgentResult, AgentStatus, AgentTrigger, BaseAgent
|
|
from app.agents.models import BacklogItem
|
|
|
|
|
|
class BacklogAgent(BaseAgent):
|
|
"""Backlog management agent."""
|
|
|
|
name = "backlog_agent"
|
|
version = "1.0.0"
|
|
description = "Manages project backlog: ideas, tasks, plans"
|
|
triggers = [
|
|
AgentTrigger.MANUAL,
|
|
AgentTrigger.CRON,
|
|
AgentTrigger.EVENT,
|
|
]
|
|
|
|
def __init__(self, session: AsyncSession | None = None):
|
|
super().__init__()
|
|
self._session = session
|
|
|
|
async def run(self, context: dict[str, Any] | None = None) -> AgentResult:
|
|
"""Execute backlog management task.
|
|
|
|
Context can contain:
|
|
- action: str (create, list, update, delete, suggest)
|
|
- item_type: str (idea, plan, task, improvement)
|
|
- title: str
|
|
- description: str
|
|
- priority: str (low, medium, high, critical)
|
|
- source: str (opencode, admin_panel, user, agent)
|
|
"""
|
|
await self.set_running("backlog_management")
|
|
|
|
try:
|
|
action = context.get("action", "list") if context else "list"
|
|
|
|
if action == "create":
|
|
result = await self._create_item(context or {})
|
|
elif action == "list":
|
|
result = await self._list_items(context or {})
|
|
elif action == "update":
|
|
result = await self._update_item(context or {})
|
|
elif action == "delete":
|
|
result = await self._delete_item(context or {})
|
|
elif action == "suggest":
|
|
result = await self._suggest_items()
|
|
else:
|
|
result = await self._list_items({})
|
|
|
|
await self.set_idle()
|
|
return result
|
|
|
|
except Exception as e:
|
|
await self.set_error(str(e))
|
|
return AgentResult(
|
|
success=False,
|
|
message=f"BacklogAgent failed: {str(e)}",
|
|
errors=[str(e)],
|
|
)
|
|
|
|
async def health_check(self) -> bool:
|
|
"""Check if BacklogAgent is operational."""
|
|
return True
|
|
|
|
async def _create_item(self, context: dict[str, Any]) -> AgentResult:
|
|
"""Create a new backlog item."""
|
|
if not self._session:
|
|
return AgentResult(
|
|
success=False,
|
|
message="Database session not configured",
|
|
)
|
|
|
|
item_type = context.get("item_type", "task")
|
|
title = context.get("title", "Untitled")
|
|
description = context.get("description", "")
|
|
priority = context.get("priority", "medium")
|
|
source = context.get("source", "agent")
|
|
created_by = context.get("created_by", "BacklogAgent")
|
|
tags = context.get("tags", [])
|
|
block_ref = context.get("block_ref")
|
|
|
|
item = BacklogItem(
|
|
id=uuid4(),
|
|
item_type=item_type,
|
|
title=title,
|
|
description=description,
|
|
priority=priority,
|
|
status="pending",
|
|
source=source,
|
|
created_by=created_by,
|
|
tags=tags,
|
|
block_ref=block_ref,
|
|
)
|
|
|
|
self._session.add(item)
|
|
await self._session.commit()
|
|
|
|
return AgentResult(
|
|
success=True,
|
|
message=f"Created backlog item: {title}",
|
|
data={
|
|
"id": str(item.id),
|
|
"type": item_type,
|
|
"title": title,
|
|
"priority": priority,
|
|
},
|
|
)
|
|
|
|
async def _list_items(
|
|
self,
|
|
context: dict[str, Any],
|
|
limit: int = 50,
|
|
) -> AgentResult:
|
|
"""List backlog items."""
|
|
if not self._session:
|
|
return AgentResult(
|
|
success=False,
|
|
message="Database session not configured",
|
|
)
|
|
|
|
item_type = context.get("item_type")
|
|
status_filter = context.get("status")
|
|
priority_filter = context.get("priority")
|
|
|
|
query = select(BacklogItem).order_by(BacklogItem.created_at.desc())
|
|
|
|
if item_type:
|
|
query = query.where(BacklogItem.item_type == item_type)
|
|
if status_filter:
|
|
query = query.where(BacklogItem.status == status_filter)
|
|
if priority_filter:
|
|
query = query.where(BacklogItem.priority == priority_filter)
|
|
|
|
query = query.limit(limit)
|
|
|
|
result = await self._session.execute(query)
|
|
items = result.scalars().all()
|
|
|
|
items_data = [
|
|
{
|
|
"id": str(item.id),
|
|
"type": item.item_type,
|
|
"title": item.title,
|
|
"priority": item.priority,
|
|
"status": item.status,
|
|
"created_at": item.created_at.isoformat() if item.created_at else None,
|
|
}
|
|
for item in items
|
|
]
|
|
|
|
return AgentResult(
|
|
success=True,
|
|
message=f"Found {len(items)} backlog items",
|
|
data={"items": items_data, "count": len(items)},
|
|
)
|
|
|
|
async def _update_item(self, context: dict[str, Any]) -> AgentResult:
|
|
"""Update a backlog item."""
|
|
if not self._session:
|
|
return AgentResult(
|
|
success=False,
|
|
message="Database session not configured",
|
|
)
|
|
|
|
item_id = context.get("id")
|
|
if not item_id:
|
|
return AgentResult(
|
|
success=False,
|
|
message="Item ID required for update",
|
|
)
|
|
|
|
update_data = {}
|
|
for field in ["title", "description", "priority", "status", "tags"]:
|
|
if field in context:
|
|
update_data[field] = context[field]
|
|
|
|
if update_data:
|
|
stmt = (
|
|
update(BacklogItem)
|
|
.where(BacklogItem.id == UUID(item_id))
|
|
.values(**update_data)
|
|
)
|
|
await self._session.execute(stmt)
|
|
await self._session.commit()
|
|
|
|
return AgentResult(
|
|
success=True,
|
|
message=f"Updated backlog item: {item_id}",
|
|
data={"id": item_id, "updated": update_data},
|
|
)
|
|
|
|
async def _delete_item(self, context: dict[str, Any]) -> AgentResult:
|
|
"""Delete a backlog item."""
|
|
if not self._session:
|
|
return AgentResult(
|
|
success=False,
|
|
message="Database session not configured",
|
|
)
|
|
|
|
item_id = context.get("id")
|
|
if not item_id:
|
|
return AgentResult(
|
|
success=False,
|
|
message="Item ID required for delete",
|
|
)
|
|
|
|
stmt = delete(BacklogItem).where(BacklogItem.id == UUID(item_id))
|
|
await self._session.execute(stmt)
|
|
await self._session.commit()
|
|
|
|
return AgentResult(
|
|
success=True,
|
|
message=f"Deleted backlog item: {item_id}",
|
|
data={"id": item_id},
|
|
)
|
|
|
|
async def _suggest_items(self) -> AgentResult:
|
|
"""Suggest backlog items based on project needs."""
|
|
suggestions = [
|
|
{
|
|
"type": "task",
|
|
"title": "Set up PostgreSQL local database",
|
|
"priority": "high",
|
|
"reason": "Required for Block 2: Data",
|
|
},
|
|
{
|
|
"type": "task",
|
|
"title": "Configure environment variables",
|
|
"priority": "medium",
|
|
"reason": "Prerequisite for running app",
|
|
},
|
|
{
|
|
"type": "improvement",
|
|
"title": "Add pre-commit hooks",
|
|
"priority": "low",
|
|
"reason": "Improves code quality",
|
|
},
|
|
]
|
|
|
|
return AgentResult(
|
|
success=True,
|
|
message="Generated backlog suggestions",
|
|
data={"suggestions": suggestions},
|
|
)
|
|
|
|
async def get_metrics(self) -> dict[str, Any]:
|
|
"""Get BacklogAgent metrics."""
|
|
if not self._session:
|
|
return {
|
|
"agent_id": self.name,
|
|
"status": self.status.value,
|
|
}
|
|
|
|
try:
|
|
pending_count = await self._session.execute(
|
|
select(BacklogItem).where(BacklogItem.status == "pending")
|
|
)
|
|
return {
|
|
"agent_id": self.name,
|
|
"version": self.version,
|
|
"status": self.status.value,
|
|
"pending_items": pending_count.scalars().count(),
|
|
}
|
|
except Exception:
|
|
return {
|
|
"agent_id": self.name,
|
|
"status": self.status.value,
|
|
} |