import { z } from 'zod'; export const TaskStatus = z.enum(['open', 'in_progress', 'review', 'done', 'cancelled']); export const DecisionStatus = z.enum(['proposed', 'accepted', 'rejected', 'superseded']); export const MemoryCategory = z.enum(['architecture', 'product', 'technical', 'implementation', 'lesson']); export const Priority = z.enum(['low', 'medium', 'high', 'critical']); export const Role = z.enum(['architect', 'implementer', 'reviewer', 'tester']); export const DelegationMode = z.enum(['manual', 'suggest', 'auto']); export const TaskSchema = z.object({ id: z.string().regex(/^TSK-\d{4}$/), title: z.string().min(1), description: z.string().default(''), status: TaskStatus, priority: Priority.default('medium'), role: Role.optional(), assignedTo: z.string().optional(), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), dueDate: z.string().datetime().optional(), tags: z.array(z.string()).default([]), acceptanceCriteria: z.array(z.string()).default([]), sourceHandoff: z.string().optional(), // Optional completion metadata (set via `task done --tokens --duration --by`) doneBy: z.string().optional(), doneTokens: z.number().int().nonnegative().optional(), doneDuration: z.number().int().nonnegative().optional(), }); export const HandoffSchema = z.object({ id: z.string().regex(/^HOF-\d{4}$/), fromRole: z.string().min(1), toRole: z.string().min(1), fromAgent: z.string().optional(), toAgent: z.string().optional(), taskId: z.string().optional(), summary: z.string().min(1), context: z.string().default(''), decisions: z.array(z.string()).default([]), openQuestions: z.array(z.string()).default([]), nextSteps: z.array(z.string()).default([]), createdAt: z.string().datetime(), }); export const DecisionSchema = z.object({ id: z.string().regex(/^DEC-\d{4}$/), title: z.string().min(1), status: DecisionStatus.default('accepted'), context: z.string().default(''), decision: z.string().min(1), consequences: z.array(z.string()).default([]), alternatives: z.array(z.string()).default([]), relatedDecisions: z.array(z.string()).default([]), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), }); export const MemorySchema = z.object({ id: z.string().min(1), title: z.string().min(1), category: MemoryCategory.default('technical'), content: z.string().min(1), tags: z.array(z.string()).default([]), relatedTasks: z.array(z.string()).default([]), relatedDecisions: z.array(z.string()).default([]), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), // Optional agent-supplied metadata (set via `memory add --tokens --duration --by`) tokens: z.number().int().nonnegative().optional(), duration: z.number().int().nonnegative().optional(), by: z.string().optional(), }); /** * Direct message between agents (architect↔implementer, or agent↔agent), * routed through the hub. Lightweight, non-task-bound — for questions, * clarifications and pings the handoff/memory entities don't cover. */ export const MessageSchema = z.object({ id: z.string().min(1), from: z.string().min(1), to: z.string().min(1), text: z.string().min(1), taskId: z.string().optional(), status: z.enum(['unread', 'read']).default('unread'), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), }); // Activity timeline item returned by GET /tasks/:id/activity export const ActivityItemSchema = z.object({ at: z.string().datetime(), kind: z.enum(['created', 'handoff', 'result', 'status']), actor: z.string(), summary: z.string(), meta: z.record(z.unknown()).optional(), }); export type ActivityItem = z.infer; export const StatusSchema = z.object({ generatedAt: z.string().datetime(), activeTasks: z.array(z.string()).default([]), blockedTasks: z.array(z.string()).default([]), recentDecisions: z.array(z.string()).default([]), recentHandoffs: z.array(z.string()).default([]), summary: z.string().default(''), }); export const RoleConfigSchema = z.object({ preferredAgent: z.string().min(1), description: z.string().optional(), }); /** * A named agent in the team roster. Lets the team view show agents (by name, * like backyard / mo / zied) with a fixed role + the model behind them — instead * of guessing role/identity from whatever tasks the agent happened to touch. */ export const AgentConfigSchema = z.object({ role: Role, /** Display model, e.g. "Opus 4.8", "Sonnet 4.6", "Kimi K2", "GPT-5 Codex". */ model: z.string().optional(), /** Provider/company for the logo: "anthropic" | "openai" | "moonshot" | … */ kind: z.string().optional(), description: z.string().optional(), /** Optional monthly cost budget in EUR — drives the budget bar on the board. */ budgetEur: z.number().nonnegative().optional(), }); export const ConfigSchema = z.object({ version: z.literal('1'), projectName: z.string().min(1), delegationMode: DelegationMode.default('suggest'), roles: z.record(z.string(), RoleConfigSchema), /** Named team roster: agent name → role + model + provider. */ agents: z.record(z.string(), AgentConfigSchema).optional(), serverUrl: z.string().url().optional(), }); export type Task = z.infer; export type Handoff = z.infer; export type Decision = z.infer; export type Memory = z.infer; export type Message = z.infer; export type Status = z.infer; export type Config = z.infer;