refactor: move types next to schema
This commit is contained in:
@@ -16,6 +16,7 @@ let customDailySlots: Array<{
|
|||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
activity: string;
|
activity: string;
|
||||||
|
notes: string;
|
||||||
}> | null = null;
|
}> | null = null;
|
||||||
let customAvailability: Array<{
|
let customAvailability: Array<{
|
||||||
start: string;
|
start: string;
|
||||||
@@ -27,8 +28,14 @@ function build48Slots(): Array<{
|
|||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
activity: string;
|
activity: string;
|
||||||
|
notes: string;
|
||||||
}> {
|
}> {
|
||||||
const slots: Array<{ start: string; end: string; activity: string }> = [];
|
const slots: Array<{
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
activity: string;
|
||||||
|
notes: string;
|
||||||
|
}> = [];
|
||||||
for (let i = 0; i < 48; i++) {
|
for (let i = 0; i < 48; i++) {
|
||||||
const startHour = Math.floor(i / 2);
|
const startHour = Math.floor(i / 2);
|
||||||
const startMin = (i % 2) * 30;
|
const startMin = (i % 2) * 30;
|
||||||
@@ -41,7 +48,7 @@ function build48Slots(): Array<{
|
|||||||
const endMin = ((i + 1) % 2) * 30;
|
const endMin = ((i + 1) % 2) * 30;
|
||||||
end = `${String(endHour).padStart(2, "0")}:${String(endMin).padStart(2, "0")}`;
|
end = `${String(endHour).padStart(2, "0")}:${String(endMin).padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
slots.push({ start, end, activity: `slot-${i}` });
|
slots.push({ start, end, activity: `slot-${i}`, notes: "" });
|
||||||
}
|
}
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
@@ -70,7 +77,7 @@ function buildAvailability(): Array<{
|
|||||||
const mockCall = mock(async <T>(model: unknown, options: any): Promise<T> => {
|
const mockCall = mock(async <T>(model: unknown, options: any): Promise<T> => {
|
||||||
llmCalls.push({ model, options });
|
llmCalls.push({ model, options });
|
||||||
if (options.jsonSchemaName === "daily-schedule") {
|
if (options.jsonSchemaName === "daily-schedule") {
|
||||||
return (customDailySlots ?? build48Slots()) as unknown as T;
|
return { items: customDailySlots ?? build48Slots() } as unknown as T;
|
||||||
}
|
}
|
||||||
if (options.jsonSchemaName === "monthly-schedule") {
|
if (options.jsonSchemaName === "monthly-schedule") {
|
||||||
if (customMonthlyDays) return customMonthlyDays as unknown as T;
|
if (customMonthlyDays) return customMonthlyDays as unknown as T;
|
||||||
@@ -150,16 +157,18 @@ describe("Brain.createDailySchedule", () => {
|
|||||||
const result = await brain.createDailySchedule(today, "focus on writing");
|
const result = await brain.createDailySchedule(today, "focus on writing");
|
||||||
|
|
||||||
expect(result).not.toBeNull();
|
expect(result).not.toBeNull();
|
||||||
expect(result).toHaveLength(48);
|
expect(result!.items).toHaveLength(48);
|
||||||
expect(result![0]).toEqual({
|
expect(result!.items[0]).toEqual({
|
||||||
start: "00:00",
|
start: "00:00",
|
||||||
end: "00:30",
|
end: "00:30",
|
||||||
activity: "slot-0",
|
activity: "slot-0",
|
||||||
|
notes: "",
|
||||||
});
|
});
|
||||||
expect(result![47]).toEqual({
|
expect(result!.items[47]).toEqual({
|
||||||
start: "23:30",
|
start: "23:30",
|
||||||
end: "24:00",
|
end: "24:00",
|
||||||
activity: "slot-47",
|
activity: "slot-47",
|
||||||
|
notes: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const llmCall = llmCalls.find(
|
const llmCall = llmCalls.find(
|
||||||
@@ -177,7 +186,7 @@ describe("Brain.createDailySchedule", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
expect(facts).toHaveLength(1);
|
expect(facts).toHaveLength(1);
|
||||||
expect(JSON.parse(facts[0]!.statement)).toHaveLength(48);
|
expect(JSON.parse(facts[0]!.statement).items).toHaveLength(48);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("S4: month wrap (June 30 -> July 1)", async () => {
|
test("S4: month wrap (June 30 -> July 1)", async () => {
|
||||||
@@ -306,7 +315,7 @@ describe("Brain.getTodayScheduledAvailability", () => {
|
|||||||
const todayKey = formatDateKey(today);
|
const todayKey = formatDateKey(today);
|
||||||
await brain.db.addFact({
|
await brain.db.addFact({
|
||||||
spaceName: brain.space.name,
|
spaceName: brain.space.name,
|
||||||
statement: JSON.stringify(build48Slots()),
|
statement: JSON.stringify({ items: build48Slots() }),
|
||||||
summary: "test daily",
|
summary: "test daily",
|
||||||
source: "test",
|
source: "test",
|
||||||
confidence: 1.0,
|
confidence: 1.0,
|
||||||
@@ -357,7 +366,7 @@ describe("Brain.removeScheduledAvailability", () => {
|
|||||||
const todayKey = formatDateKey(today);
|
const todayKey = formatDateKey(today);
|
||||||
await brain.db.addFact({
|
await brain.db.addFact({
|
||||||
spaceName: brain.space.name,
|
spaceName: brain.space.name,
|
||||||
statement: JSON.stringify(build48Slots()),
|
statement: JSON.stringify({ items: build48Slots() }),
|
||||||
summary: "test daily",
|
summary: "test daily",
|
||||||
source: "test",
|
source: "test",
|
||||||
confidence: 1.0,
|
confidence: 1.0,
|
||||||
@@ -426,7 +435,7 @@ describe("Brain.createDebug", () => {
|
|||||||
|
|
||||||
const schedule = await brain.createDailySchedule(today, "msg");
|
const schedule = await brain.createDailySchedule(today, "msg");
|
||||||
expect(schedule).not.toBeNull();
|
expect(schedule).not.toBeNull();
|
||||||
expect(schedule).toHaveLength(48);
|
expect(schedule!.items).toHaveLength(48);
|
||||||
|
|
||||||
const facts = await brain.db.getTopicFacts(`daily-schedule:${tomorrowKey}`, {
|
const facts = await brain.db.getTopicFacts(`daily-schedule:${tomorrowKey}`, {
|
||||||
spaceName: brain.space.name,
|
spaceName: brain.space.name,
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import {
|
|||||||
availabilitySchema,
|
availabilitySchema,
|
||||||
dailyScheduleSchema,
|
dailyScheduleSchema,
|
||||||
monthlyScheduleSchema,
|
monthlyScheduleSchema,
|
||||||
|
type Availability,
|
||||||
|
type DailySchedule,
|
||||||
|
type MonthlySchedule,
|
||||||
} from "@/openrouter/schema";
|
} from "@/openrouter/schema";
|
||||||
import { logger } from "@/utils/logger";
|
import { logger } from "@/utils/logger";
|
||||||
import { factExtractor } from "./factExtractor";
|
import { factExtractor } from "./factExtractor";
|
||||||
import { brainManager, type BrainItem } from "./manager";
|
import { brainManager, type BrainItem } from "./manager";
|
||||||
import {
|
import {
|
||||||
type Availability,
|
|
||||||
type DailySchedule,
|
|
||||||
type MonthlySchedule,
|
|
||||||
formatDateKey,
|
formatDateKey,
|
||||||
formatMonthKey,
|
formatMonthKey,
|
||||||
nextDay,
|
nextDay,
|
||||||
@@ -72,7 +72,7 @@ export class Brain {
|
|||||||
await this.db.addFact({
|
await this.db.addFact({
|
||||||
spaceName: this.space.name,
|
spaceName: this.space.name,
|
||||||
statement: JSON.stringify(schedule),
|
statement: JSON.stringify(schedule),
|
||||||
summary: `Daily schedule for ${dateKey} (${schedule.length} slots)`,
|
summary: `Daily schedule for ${dateKey} (${schedule.items.length} slots)`,
|
||||||
source: "createDailySchedule",
|
source: "createDailySchedule",
|
||||||
confidence: 1.0,
|
confidence: 1.0,
|
||||||
topics: [
|
topics: [
|
||||||
@@ -291,11 +291,11 @@ export class Brain {
|
|||||||
"PERSONA_BASE_SYSTEM_PROMPT",
|
"PERSONA_BASE_SYSTEM_PROMPT",
|
||||||
);
|
);
|
||||||
const generatedBaseSystemPrompt = await llm.call<string>(
|
const generatedBaseSystemPrompt = await llm.call<string>(
|
||||||
llm.models.identity,
|
llm.models.identity,
|
||||||
{
|
{
|
||||||
instruction: personaSystemInstruction,
|
instruction: personaSystemInstruction,
|
||||||
message: description,
|
message: description,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const personaSystemFixed = await loadPrompt(
|
const personaSystemFixed = await loadPrompt(
|
||||||
|
|||||||
@@ -1,25 +1,3 @@
|
|||||||
export type DailySlot = {
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
activity: string;
|
|
||||||
notes?: string;
|
|
||||||
};
|
|
||||||
export type DailySchedule = DailySlot[];
|
|
||||||
|
|
||||||
export type MonthlyDay = {
|
|
||||||
day: number;
|
|
||||||
summary: string;
|
|
||||||
};
|
|
||||||
export type MonthlySchedule = MonthlyDay[];
|
|
||||||
|
|
||||||
export type AvailabilityStatus = "online" | "do-not-disturb" | "offline";
|
|
||||||
export type Availability = {
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
status: AvailabilityStatus;
|
|
||||||
};
|
|
||||||
export type AvailabilityWindows = Availability[];
|
|
||||||
|
|
||||||
export function pad2(n: number): string {
|
export function pad2(n: number): string {
|
||||||
return n < 10 ? `0${n}` : `${n}`;
|
return n < 10 ? `0${n}` : `${n}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
||||||
import type { Availability, DailySchedule, MonthlySchedule } from "@/brain/schedule";
|
import type {
|
||||||
|
Availability,
|
||||||
|
DailySlot,
|
||||||
|
MonthlySchedule,
|
||||||
|
} from "@/openrouter/schema";
|
||||||
|
|
||||||
interface RecordedCall {
|
interface RecordedCall {
|
||||||
model: unknown;
|
model: unknown;
|
||||||
@@ -8,8 +12,8 @@ interface RecordedCall {
|
|||||||
|
|
||||||
const llmCalls: RecordedCall[] = [];
|
const llmCalls: RecordedCall[] = [];
|
||||||
|
|
||||||
function build48Slots(): DailySchedule {
|
function build48Slots(): DailySlot[] {
|
||||||
const slots: DailySchedule = [];
|
const slots: DailySlot[] = [];
|
||||||
for (let i = 0; i < 48; i++) {
|
for (let i = 0; i < 48; i++) {
|
||||||
const startHour = Math.floor(i / 2);
|
const startHour = Math.floor(i / 2);
|
||||||
const startMin = (i % 2) * 30;
|
const startMin = (i % 2) * 30;
|
||||||
@@ -22,7 +26,7 @@ function build48Slots(): DailySchedule {
|
|||||||
const endMin = ((i + 1) % 2) * 30;
|
const endMin = ((i + 1) % 2) * 30;
|
||||||
end = `${String(endHour).padStart(2, "0")}:${String(endMin).padStart(2, "0")}`;
|
end = `${String(endHour).padStart(2, "0")}:${String(endMin).padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
slots.push({ start, end, activity: `slot-${i}` });
|
slots.push({ start, end, activity: `slot-${i}`, notes: "" });
|
||||||
}
|
}
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
@@ -44,7 +48,8 @@ function buildMonthly(): MonthlySchedule {
|
|||||||
|
|
||||||
const mockCall = mock(async (_model: unknown, options: any) => {
|
const mockCall = mock(async (_model: unknown, options: any) => {
|
||||||
llmCalls.push({ model: _model, options });
|
llmCalls.push({ model: _model, options });
|
||||||
if (options.jsonSchemaName === "daily-schedule") return build48Slots();
|
if (options.jsonSchemaName === "daily-schedule")
|
||||||
|
return { items: build48Slots() };
|
||||||
if (options.jsonSchemaName === "monthly-schedule") {
|
if (options.jsonSchemaName === "monthly-schedule") {
|
||||||
const match = (options.message as string).match(/\((\d+) days\)/);
|
const match = (options.message as string).match(/\((\d+) days\)/);
|
||||||
const days = match ? parseInt(match[1]!, 10) : 30;
|
const days = match ? parseInt(match[1]!, 10) : 30;
|
||||||
@@ -100,7 +105,7 @@ describe("runDebugScheduleDaily", () => {
|
|||||||
if (!result.ok) throw new Error("expected ok");
|
if (!result.ok) throw new Error("expected ok");
|
||||||
|
|
||||||
expect(result.kind).toBe("daily");
|
expect(result.kind).toBe("daily");
|
||||||
expect(result.schedule).toHaveLength(48);
|
expect(result.schedule.items).toHaveLength(48);
|
||||||
expect(result.availability.length).toBeGreaterThan(0);
|
expect(result.availability.length).toBeGreaterThan(0);
|
||||||
expect(result.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
expect(result.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import type { Command } from "commander";
|
import type { Command } from "commander";
|
||||||
import ora from "ora";
|
import ora from "ora";
|
||||||
import { Brain } from "@/brain";
|
import { Brain } from "@/brain";
|
||||||
import { logger } from "@/utils/logger";
|
|
||||||
import {
|
import {
|
||||||
type Availability,
|
type Availability,
|
||||||
type DailySchedule,
|
type DailySchedule,
|
||||||
type MonthlySchedule,
|
type MonthlySchedule,
|
||||||
formatDateKey,
|
} from "@/openrouter/schema";
|
||||||
nextMonth,
|
import { logger } from "@/utils/logger";
|
||||||
pad2,
|
import { formatDateKey, nextMonth, pad2 } from "@/brain/schedule";
|
||||||
} from "@/brain/schedule";
|
|
||||||
|
|
||||||
export interface ScheduleOptions {
|
export interface ScheduleOptions {
|
||||||
message: string;
|
message: string;
|
||||||
@@ -59,7 +57,7 @@ export async function runDebugScheduleDaily(
|
|||||||
return { ok: false, error: "Daily schedule generation failed" };
|
return { ok: false, error: "Daily schedule generation failed" };
|
||||||
}
|
}
|
||||||
scheduleSpinner.succeed(
|
scheduleSpinner.succeed(
|
||||||
`Daily schedule generated (${schedule.length} slots)`,
|
`Daily schedule generated (${schedule.items.length} slots)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
printSection(
|
printSection(
|
||||||
|
|||||||
@@ -98,3 +98,46 @@ export const availabilitySchema = {
|
|||||||
required: ["start", "end", "status"],
|
required: ["start", "end", "status"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Types — co-located with their schemas.
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** A single 30-minute slot in a daily schedule. Matches `dailyScheduleSchema.items.items`. */
|
||||||
|
export type DailySlot = {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
activity: string;
|
||||||
|
notes: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A complete daily schedule: a wrapped object containing exactly 48 half-hour
|
||||||
|
* slots. Matches `dailyScheduleSchema` (the LLM is constrained to return the
|
||||||
|
* `{ items: [...] }` envelope).
|
||||||
|
*/
|
||||||
|
export type DailySchedule = {
|
||||||
|
items: DailySlot[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** A single day's summary inside a monthly schedule. Matches `monthlyScheduleSchema.items`. */
|
||||||
|
export type MonthlyDay = {
|
||||||
|
day: number;
|
||||||
|
summary: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** A complete monthly schedule. Matches `monthlyScheduleSchema`. */
|
||||||
|
export type MonthlySchedule = MonthlyDay[];
|
||||||
|
|
||||||
|
/** Reachability status for a single availability window. */
|
||||||
|
export type AvailabilityStatus = "online" | "do-not-disturb" | "offline";
|
||||||
|
|
||||||
|
/** A single availability window. Matches `availabilitySchema.items`. */
|
||||||
|
export type Availability = {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
status: AvailabilityStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The full set of availability windows for a day. Matches `availabilitySchema`. */
|
||||||
|
export type AvailabilityWindows = Availability[];
|
||||||
|
|||||||
Reference in New Issue
Block a user