feat(schema): add zod schemas for all entities
This commit is contained in:
parent
66f6d3b151
commit
c00ec16c84
92
src/core/schema.ts
Normal file
92
src/core/schema.ts
Normal file
@ -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<typeof TaskSchema>;
|
||||||
|
export type Handoff = z.infer<typeof HandoffSchema>;
|
||||||
|
export type Decision = z.infer<typeof DecisionSchema>;
|
||||||
|
export type Memory = z.infer<typeof MemorySchema>;
|
||||||
|
export type Status = z.infer<typeof StatusSchema>;
|
||||||
|
export type Config = z.infer<typeof ConfigSchema>;
|
||||||
28
tests/schema.test.ts
Normal file
28
tests/schema.test.ts
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user