Initial commit: VoIdeaAI - voice-first AI idea assistant
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
"""DocAgent - Documentation automation for VoIdea.
|
||||
|
||||
This agent automatically:
|
||||
- Creates README.md for new modules
|
||||
- Updates documentation when code changes
|
||||
- Generates docstrings
|
||||
- Maintains Runbook
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from app.agents.base import AgentResult, AgentStatus, AgentTrigger, BaseAgent
|
||||
from app.core.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
class DocAgent(BaseAgent):
|
||||
"""Documentation automation agent."""
|
||||
|
||||
name = "doc_agent"
|
||||
version = "1.0.0"
|
||||
description = "Automatically maintains project documentation"
|
||||
triggers = [
|
||||
AgentTrigger.MANUAL,
|
||||
AgentTrigger.PRE_COMMIT,
|
||||
AgentTrigger.PUSH,
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.project_root = Path(__file__).parent.parent.parent
|
||||
self.docs_dir = self.project_root / "docs"
|
||||
self.app_dir = self.project_root / "app"
|
||||
|
||||
async def run(self, context: dict[str, Any] | None = None) -> AgentResult:
|
||||
"""Execute documentation task.
|
||||
|
||||
Context can contain:
|
||||
- action: str (update_readme, generate_docs, etc.)
|
||||
- module: str (module path to document)
|
||||
- files: list[str] (changed files)
|
||||
"""
|
||||
await self.set_running("documentation")
|
||||
|
||||
try:
|
||||
action = context.get("action", "update_all") if context else "update_all"
|
||||
|
||||
if action == "update_readme":
|
||||
module = context.get("module", "")
|
||||
result = await self._update_module_readme(module)
|
||||
elif action == "generate_docs":
|
||||
result = await self._generate_docs()
|
||||
elif action == "update_session_context":
|
||||
result = await self._update_session_context(context or {})
|
||||
else:
|
||||
result = await self._update_all()
|
||||
|
||||
await self.set_idle()
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
await self.set_error(str(e))
|
||||
return AgentResult(
|
||||
success=False,
|
||||
message=f"DocAgent failed: {str(e)}",
|
||||
errors=[str(e)],
|
||||
)
|
||||
|
||||
async def health_check(self) -> bool:
|
||||
"""Check if DocAgent is operational."""
|
||||
try:
|
||||
return self.docs_dir.exists() and self.app_dir.exists()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def _update_module_readme(self, module_path: str) -> AgentResult:
|
||||
"""Update README.md for a specific module."""
|
||||
module_dir = self.app_dir / module_path if module_path else self.app_dir
|
||||
|
||||
if not module_dir.exists():
|
||||
return AgentResult(
|
||||
success=False,
|
||||
message=f"Module not found: {module_path}",
|
||||
)
|
||||
|
||||
files = list(module_dir.glob("*.py"))
|
||||
files = [f for f in files if f.name != "__init__.py"]
|
||||
|
||||
content = f"""# {module_path or 'app'} Module - VoIdea
|
||||
|
||||
## Overview
|
||||
|
||||
[Auto-generated documentation]
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
"""
|
||||
|
||||
for file in files:
|
||||
purpose = self._get_file_purpose(file.name)
|
||||
content += f"| `{file.name}` | {purpose} |\n"
|
||||
|
||||
readme_path = module_dir / "README.md"
|
||||
readme_path.write_text(content, encoding="utf-8")
|
||||
|
||||
return AgentResult(
|
||||
success=True,
|
||||
message=f"Updated README for {module_path or 'app'}",
|
||||
data={"module": module_path, "files_count": len(files)},
|
||||
)
|
||||
|
||||
async def _update_all(self) -> AgentResult:
|
||||
"""Update all documentation."""
|
||||
updated = []
|
||||
|
||||
for module_dir in self.app_dir.iterdir():
|
||||
if module_dir.is_dir() and (module_dir / "__init__.py").exists():
|
||||
result = await self._update_module_readme(module_dir.name)
|
||||
if result.success:
|
||||
updated.append(module_dir.name)
|
||||
|
||||
session_result = await self._update_session_context({})
|
||||
if session_result.success:
|
||||
updated.append("SESSION_CONTEXT")
|
||||
|
||||
return AgentResult(
|
||||
success=True,
|
||||
message=f"Updated {len(updated)} documentation files",
|
||||
data={"updated": updated},
|
||||
)
|
||||
|
||||
async def _generate_docs(self) -> AgentResult:
|
||||
"""Generate API documentation from docstrings."""
|
||||
docs_generated = 0
|
||||
endpoints = []
|
||||
|
||||
api_dir = self.app_dir / "api"
|
||||
if api_dir.exists():
|
||||
for file in api_dir.rglob("*.py"):
|
||||
if file.name == "__init__.py":
|
||||
continue
|
||||
docs_generated += 1
|
||||
endpoints.append(str(file.relative_to(self.app_dir)))
|
||||
|
||||
return AgentResult(
|
||||
success=True,
|
||||
message=f"Generated documentation for {docs_generated} files",
|
||||
data={"endpoints": endpoints, "count": docs_generated},
|
||||
)
|
||||
|
||||
async def _update_session_context(self, context: dict[str, Any]) -> AgentResult:
|
||||
"""Update SESSION_CONTEXT.md with current progress."""
|
||||
session_file = self.project_root / "SESSION_CONTEXT.md"
|
||||
|
||||
if not session_file.exists():
|
||||
return AgentResult(
|
||||
success=False,
|
||||
message="SESSION_CONTEXT.md not found",
|
||||
)
|
||||
|
||||
try:
|
||||
content = session_file.read_text(encoding="utf-8")
|
||||
|
||||
if "Last Updated" not in content:
|
||||
content = content.replace(
|
||||
"================================================================================",
|
||||
"Last Updated: {}\n================================================================================".format(
|
||||
datetime.now(timezone.utc).isoformat()
|
||||
),
|
||||
)
|
||||
|
||||
session_file.write_text(content, encoding="utf-8")
|
||||
|
||||
return AgentResult(
|
||||
success=True,
|
||||
message="SESSION_CONTEXT.md updated",
|
||||
data={"timestamp": datetime.now(timezone.utc).isoformat()},
|
||||
)
|
||||
except Exception as e:
|
||||
return AgentResult(
|
||||
success=False,
|
||||
message=f"Failed to update SESSION_CONTEXT: {str(e)}",
|
||||
errors=[str(e)],
|
||||
)
|
||||
|
||||
def _get_file_purpose(self, filename: str) -> str:
|
||||
"""Get file purpose based on naming convention."""
|
||||
purposes = {
|
||||
"main.py": "FastAPI application entry point",
|
||||
"config.py": "Configuration management",
|
||||
"models.py": "Data models",
|
||||
"schemas.py": "Pydantic schemas",
|
||||
"service.py": "Business logic",
|
||||
"repository.py": "Data access layer",
|
||||
"router.py": "API routes",
|
||||
"base.py": "Base classes",
|
||||
"exceptions.py": "Custom exceptions",
|
||||
"security.py": "Security utilities",
|
||||
"database.py": "Database setup",
|
||||
"dependencies.py": "FastAPI dependencies",
|
||||
}
|
||||
return purposes.get(filename, "Module file")
|
||||
|
||||
async def get_metrics(self) -> dict[str, Any]:
|
||||
"""Get DocAgent metrics."""
|
||||
from app.agents.models import AgentMetric
|
||||
|
||||
return {
|
||||
"agent_id": self.name,
|
||||
"version": self.version,
|
||||
"status": self.status.value,
|
||||
"last_run": self.last_run.isoformat() if self.last_run else None,
|
||||
}
|
||||
Reference in New Issue
Block a user