from dataclasses import field, dataclass from pydantic import BaseModel class TeamMemberModel(BaseModel): role: str nick: str class Repo(BaseModel): id: str remote_url: str base_branch: str = "main" class ProjectContext(BaseModel): """Static, per-project knowledge injected into every decomposition.""" project_key: str # e.g. "ARCH" language: str = "" backend_framework: str = "" # "FastAPI", "Django", "Spring Boot" frontend_framework: str = "" # "React + Vite", "Vue 3" ui_library: str = "" # "shadcn/ui", "MUI", "Ant Design" database: str = "" # "PostgreSQL 16 via SQLAlchemy 2" orm: str = "" test_framework: str = "" # "pytest + httpx.AsyncClient" package_manager: str = "" # "uv", "poetry", "npm" auth: str = "" # "JWT via fastapi-users" deployment: str = "" # "Docker Compose on Hetzner" project_language: str = "english" default_responder: str | None = None repos: list[Repo] = field(default_factory=list) team: list[TeamMemberModel] = field(default_factory=list) conventions: list[str] = field(default_factory=list) # free-form notes extra: dict[str, str] = field(default_factory=dict) # anything else def to_prompt_text(self) -> str: lines = [f"## Project Context ({self.project_key})"] fields = [ ("Language", self.language), ("Backend framework", self.backend_framework), ("Frontend framework", self.frontend_framework), ("UI library", self.ui_library), ("Database", self.database), ("ORM", self.orm), ("Test framework", self.test_framework), ("Package manager", self.package_manager), ("Auth", self.auth), ("Deployment", self.deployment), ("Comment language", self.project_language), ] for label, value in fields: if value: lines.append(f"- {label}: {value}") for k, v in self.extra.items(): if v: lines.append(f"- {k}: {v}") if self.conventions: lines.append("") lines.append("Conventions:") lines.extend(f"- {c}" for c in self.conventions) if self.team: lines.append("**Team members** (address questions using the exact `nick`):") for m in self.team: lines.append(f"- `{m.nick}` — {m.role}") if self.default_responder: lines.append( f"**Default responder** (use this nick when no team member " f"clearly owns the question): `{self.default_responder}`" ) lines.append("") return "\n".join(lines)