Implement @mosaic/brain — typed structured data service with MCP + REST API, JSON file backend, and schema validation via Zod. Collections: tasks, projects, events, agents, tickets, appreciations, missions, mission_tasks. MCP tools: brain_tasks, brain_projects, brain_events, brain_agents, brain_tickets, brain_today, brain_stale, brain_stats, brain_search, brain_audit, brain_missions, brain_mission, brain_mission_tasks, plus mutation tools for all collections. REST API mirrors MCP 1:1 at /v1/*. Bearer token auth with timing-safe comparison. Fastify server with per-request MCP instances (stateless HTTP transport). JSON file storage with proper-lockfile for concurrent access. Also adds Brain* types to @mosaic/types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
223 lines
6.9 KiB
TypeScript
223 lines
6.9 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
// === Enums ===
|
|
|
|
export const BrainDomainSchema = z.enum([
|
|
'work', 'software-dev', 'homelab', 'family', 'marriage',
|
|
'finances', 'fitness', 'music', 'home-improvement', 'woodworking',
|
|
'home', 'consulting', 'personal',
|
|
]);
|
|
|
|
export const TaskPrioritySchema = z.enum(['critical', 'high', 'medium', 'low']);
|
|
|
|
export const BrainTaskStatusSchema = z.enum([
|
|
'backlog', 'scheduled', 'in-progress', 'blocked', 'done', 'cancelled',
|
|
]);
|
|
|
|
export const BrainProjectStatusSchema = z.enum([
|
|
'planning', 'active', 'paused', 'blocked', 'completed', 'archived',
|
|
]);
|
|
|
|
export const BrainAgentStatusSchema = z.enum(['active', 'idle', 'blocked', 'completed']);
|
|
|
|
export const BrainEventTypeSchema = z.enum([
|
|
'meeting', 'deadline', 'maintenance', 'event', 'recurring',
|
|
'milestone', 'task', 'constraint', 'client-work', 'appointment',
|
|
'reminder', 'conflict', 'time-off',
|
|
]);
|
|
|
|
export const BrainEventStatusSchema = z.enum([
|
|
'scheduled', 'confirmed', 'tentative', 'completed', 'cancelled',
|
|
'done', 'blocked', 'postponed', 'deferred', 'in-progress',
|
|
'pending-approval', 'canceled', 'needs-resolution',
|
|
]);
|
|
|
|
export const BrainMissionStatusSchema = z.enum([
|
|
'planning', 'active', 'blocked', 'completed', 'cancelled',
|
|
]);
|
|
|
|
// === Record Schemas ===
|
|
|
|
export const BrainTaskSchema = z.object({
|
|
id: z.string().regex(/^[a-z0-9-]+$/),
|
|
title: z.string().min(1),
|
|
domain: BrainDomainSchema,
|
|
project: z.string().nullish(),
|
|
priority: TaskPrioritySchema,
|
|
status: BrainTaskStatusSchema,
|
|
progress: z.number().int().min(0).max(100).nullish(),
|
|
due: z.string().nullish(),
|
|
blocks: z.array(z.string()).optional().default([]),
|
|
blocked_by: z.array(z.string()).optional().default([]),
|
|
related: z.array(z.string()).optional().default([]),
|
|
canonical_source: z.string().nullish(),
|
|
assignee: z.string().nullish(),
|
|
created: z.string(),
|
|
updated: z.string(),
|
|
notes: z.string().nullish(),
|
|
notes_nontechnical: z.string().nullish(),
|
|
});
|
|
|
|
export const BrainProjectSchema = z.object({
|
|
id: z.string().regex(/^[a-z0-9-]+$/),
|
|
name: z.string().min(1),
|
|
description: z.string().nullish(),
|
|
domain: BrainDomainSchema,
|
|
status: BrainProjectStatusSchema,
|
|
priority: z.number().int().min(1).max(10),
|
|
progress: z.number().int().min(0).max(100).nullish(),
|
|
repo: z.string().nullish(),
|
|
branch: z.string().nullish(),
|
|
current_milestone: z.string().nullish(),
|
|
next_milestone: z.string().nullish(),
|
|
blocker: z.string().nullish(),
|
|
owner: z.string().nullish(),
|
|
docs_path: z.string().nullish(),
|
|
created: z.string(),
|
|
updated: z.string(),
|
|
notes: z.string().nullish(),
|
|
notes_nontechnical: z.string().nullish(),
|
|
});
|
|
|
|
export const BrainEventSchema = z.object({
|
|
id: z.string().regex(/^[a-z0-9-]+$/),
|
|
title: z.string().min(1),
|
|
date: z.string(),
|
|
end_date: z.string().nullish(),
|
|
time: z.string().nullish(),
|
|
end_time: z.string().nullish(),
|
|
domain: BrainDomainSchema,
|
|
type: BrainEventTypeSchema,
|
|
status: BrainEventStatusSchema.optional().default('scheduled'),
|
|
priority: TaskPrioritySchema.nullish(),
|
|
recur: z.boolean().nullish(),
|
|
recur_rate: z.string().nullish(),
|
|
recur_start: z.string().nullish(),
|
|
recur_end: z.string().nullish(),
|
|
location: z.string().nullish(),
|
|
project: z.string().nullish(),
|
|
related_task: z.string().nullish(),
|
|
related_tasks: z.array(z.string()).optional().default([]),
|
|
notes: z.string().nullish(),
|
|
gcal_id: z.string().nullish(),
|
|
ics_uid: z.string().nullish(),
|
|
});
|
|
|
|
export const BrainAgentSchema = z.object({
|
|
id: z.string(),
|
|
project: z.string(),
|
|
focus: z.string().nullish(),
|
|
repo: z.string().nullish(),
|
|
branch: z.string().nullish(),
|
|
status: BrainAgentStatusSchema,
|
|
workload: z.enum(['light', 'medium', 'heavy']).nullish(),
|
|
next_step: z.string().nullish(),
|
|
blocker: z.string().nullish(),
|
|
updated: z.string(),
|
|
});
|
|
|
|
export const BrainTicketSchema = z.object({
|
|
id: z.string(),
|
|
title: z.string().min(1),
|
|
status: z.number().int(),
|
|
priority: z.number().int(),
|
|
urgency: z.number().int(),
|
|
impact: z.number().int(),
|
|
date_creation: z.string(),
|
|
date_mod: z.string(),
|
|
content: z.string().nullish(),
|
|
assigned_to: z.string().nullish(),
|
|
});
|
|
|
|
export const BrainAppreciationSchema = z.object({
|
|
date: z.string(),
|
|
from: z.enum(['jason', 'melanie']),
|
|
to: z.enum(['jason', 'melanie']),
|
|
text: z.string().min(1),
|
|
});
|
|
|
|
export const BrainMissionSchema = z.object({
|
|
id: z.string(),
|
|
title: z.string().min(1),
|
|
project: z.string(),
|
|
prd_path: z.string().nullish(),
|
|
status: BrainMissionStatusSchema,
|
|
created: z.string(),
|
|
updated: z.string(),
|
|
notes: z.string().nullish(),
|
|
});
|
|
|
|
export const BrainMissionTaskSchema = z.object({
|
|
id: z.string(),
|
|
mission_id: z.string(),
|
|
title: z.string().min(1),
|
|
phase: z.string().nullish(),
|
|
status: BrainTaskStatusSchema,
|
|
priority: TaskPrioritySchema,
|
|
dependencies: z.array(z.string()).default([]),
|
|
assigned_to: z.string().nullish(),
|
|
pr: z.string().nullish(),
|
|
order: z.number().int(),
|
|
created: z.string(),
|
|
updated: z.string(),
|
|
completed_at: z.string().nullish(),
|
|
notes: z.string().nullish(),
|
|
});
|
|
|
|
// === Partial schemas for updates ===
|
|
|
|
export const BrainTaskUpdateSchema = BrainTaskSchema.partial().omit({ id: true });
|
|
export const BrainProjectUpdateSchema = BrainProjectSchema.partial().omit({ id: true });
|
|
export const BrainEventUpdateSchema = BrainEventSchema.partial().omit({ id: true });
|
|
export const BrainAgentUpdateSchema = BrainAgentSchema.partial().omit({ id: true });
|
|
export const BrainMissionUpdateSchema = BrainMissionSchema.partial().omit({ id: true });
|
|
export const BrainMissionTaskUpdateSchema = BrainMissionTaskSchema.partial().omit({ id: true, mission_id: true });
|
|
|
|
// === Collection file schemas ===
|
|
|
|
export const TasksFileSchema = z.object({
|
|
version: z.string(),
|
|
tasks: z.array(BrainTaskSchema),
|
|
});
|
|
|
|
export const ProjectsFileSchema = z.object({
|
|
version: z.string(),
|
|
projects: z.array(BrainProjectSchema),
|
|
});
|
|
|
|
export const EventsFileSchema = z.object({
|
|
version: z.string(),
|
|
domain: BrainDomainSchema,
|
|
events: z.array(BrainEventSchema),
|
|
overdue: z.array(z.object({
|
|
id: z.string(),
|
|
title: z.string(),
|
|
original_due: z.string().optional(),
|
|
domain: z.string(),
|
|
notes: z.string().nullish(),
|
|
})).default([]),
|
|
});
|
|
|
|
export const AgentsFileSchema = z.object({
|
|
version: z.string(),
|
|
agents: z.array(BrainAgentSchema),
|
|
});
|
|
|
|
export const MissionsFileSchema = z.object({
|
|
version: z.string(),
|
|
missions: z.array(BrainMissionSchema),
|
|
});
|
|
|
|
export const MissionTasksFileSchema = z.object({
|
|
version: z.string(),
|
|
mission_tasks: z.array(BrainMissionTaskSchema),
|
|
});
|
|
|
|
// Inferred types for convenience
|
|
export type BrainTaskInput = z.input<typeof BrainTaskSchema>;
|
|
export type BrainProjectInput = z.input<typeof BrainProjectSchema>;
|
|
export type BrainEventInput = z.input<typeof BrainEventSchema>;
|
|
export type BrainAgentInput = z.input<typeof BrainAgentSchema>;
|
|
export type BrainMissionInput = z.input<typeof BrainMissionSchema>;
|
|
export type BrainMissionTaskInput = z.input<typeof BrainMissionTaskSchema>;
|