from typing import Literal from pydantic import BaseModel, ConfigDict, Field, field_validator STATUSES = ("triage", "todo", "ready", "running", "blocked", "done", "archived") WorkspaceKind = Literal["scratch", "dir", "worktree"] class Task(BaseModel): model_config = ConfigDict(extra="allow") id: str title: str = "" status: str = "todo" assignee: str | None = None priority: int | None = None body: str | None = None age: str | None = None created_at: int | None = None started_at: int | None = None class TaskCreate(BaseModel): title: str = Field(min_length=1, max_length=200) body: str = "" assignee: str | None = Field(default=None, max_length=80) priority: int = Field(default=0, ge=-1000, le=1000) workspace_kind: WorkspaceKind = "scratch" workspace_path: str | None = None goal_mode: bool = False max_runtime_seconds: int | None = Field(default=None, ge=30, le=86400) @field_validator("title") @classmethod def clean_title(cls, value: str) -> str: value = value.strip() if not value: raise ValueError("title is required") if "\x00" in value: raise ValueError("title contains unsafe null byte") return value class CommentPayload(BaseModel): body: str = Field(min_length=1, max_length=10000) class AssignPayload(BaseModel): assignee: str = Field(min_length=1, max_length=80) class BlockPayload(BaseModel): reason: str = Field(min_length=1, max_length=1000)