"""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, }