diff --git a/src/core/schema.ts b/src/core/schema.ts new file mode 100644 index 0000000..451bb22 --- /dev/null +++ b/src/core/schema.ts @@ -0,0 +1,92 @@ +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+$/), + 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(), +}); + +export const HandoffSchema = z.object({ + id: z.string().regex(/^HOF-\d+$/), + 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+$/), + 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(), +}); + +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(), +}); + +export const ConfigSchema = z.object({ + version: z.literal('1'), + projectName: z.string().min(1), + delegationMode: DelegationMode.default('suggest'), + roles: z.record(z.string(), RoleConfigSchema), +}); + +export type Task = z.infer; +export type Handoff = z.infer; +export type Decision = z.infer; +export type Memory = z.infer; +export type Status = z.infer; +export type Config = z.infer; diff --git a/tests/schema.test.ts b/tests/schema.test.ts new file mode 100644 index 0000000..2583f59 --- /dev/null +++ b/tests/schema.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import { TaskSchema, HandoffSchema, DecisionSchema, MemorySchema, StatusSchema, ConfigSchema } from '../src/core/schema.js'; + +describe('schemas', () => { + it('validates a minimal task', () => { + const result = TaskSchema.safeParse({ + id: 'TSK-0001', + title: 'Implement DNS cache', + description: 'Add local cache', + status: 'open', + createdAt: '2026-06-24T10:00:00Z', + updatedAt: '2026-06-24T10:00:00Z', + }); + expect(result.success).toBe(true); + }); + + it('rejects an invalid task id', () => { + const result = TaskSchema.safeParse({ + id: 'task-1', + title: 'Implement DNS cache', + description: '', + status: 'open', + createdAt: '2026-06-24T10:00:00Z', + updatedAt: '2026-06-24T10:00:00Z', + }); + expect(result.success).toBe(false); + }); +});