openskynet / experiments /skynet /study-program.ts
Darochin's picture
Mirror OpenSkyNet workspace snapshot from Git HEAD
fc93158 verified
import fs from "node:fs/promises";
import path from "node:path";
import type {
OmegaStudySupervisorState,
OmegaStudyTrack,
} from "../../src/omega/study-supervisor.js";
import type { SkynetNucleusState } from "./nucleus.js";
export type SkynetStudyWorkItem = {
id: string;
title: string;
trackKey: OmegaStudyTrack["key"];
priority: number;
rationale: string;
deliverable: string;
doneWhen: string;
};
export type SkynetStudyProgram = {
sessionKey: string;
updatedAt: number;
projectName: string;
focusKey: OmegaStudyTrack["key"];
mode: SkynetNucleusState["mode"];
items: SkynetStudyWorkItem[];
};
function sanitizeSessionKey(sessionKey: string): string {
return (sessionKey.trim() || "main").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 64) || "main";
}
function clamp01(value: number): number {
return Math.max(0, Math.min(1, value));
}
function resolveStudyProgramJsonPath(params: {
workspaceRoot: string;
sessionKey: string;
}): string {
return path.join(
params.workspaceRoot,
".openskynet",
"skynet-study-program",
`${sanitizeSessionKey(params.sessionKey)}.json`,
);
}
function resolveStudyProgramMarkdownPath(workspaceRoot: string): string {
return path.join(workspaceRoot, "memory", "SKYNET_STUDY_PROGRAM.md");
}
function trackByKey(
tracks: OmegaStudyTrack[],
key: OmegaStudyTrack["key"],
): OmegaStudyTrack | undefined {
return tracks.find((track) => track.key === key);
}
function makeWorkItem(params: {
id: string;
title: string;
track: OmegaStudyTrack;
priority: number;
rationale: string;
deliverable: string;
doneWhen: string;
}): SkynetStudyWorkItem {
return {
id: params.id,
title: params.title,
trackKey: params.track.key,
priority: clamp01(params.priority),
rationale: params.rationale,
deliverable: params.deliverable,
doneWhen: params.doneWhen,
};
}
export function deriveSkynetStudyProgram(params: {
sessionKey: string;
supervisor: OmegaStudySupervisorState;
nucleus: SkynetNucleusState;
}): SkynetStudyProgram {
const focusTrack =
trackByKey(params.supervisor.tracks, params.supervisor.focus.key) ??
params.supervisor.tracks[0]!;
const secondaryTrack =
params.supervisor.tracks.find((track) => track.key !== focusTrack.key) ?? focusTrack;
const metabolismTrack =
trackByKey(params.supervisor.tracks, "cognitive_metabolism") ?? secondaryTrack;
const items: SkynetStudyWorkItem[] = [
makeWorkItem({
id: "focus-experiment",
title: `Empujar foco activo: ${focusTrack.title}`,
track: focusTrack,
priority: clamp01(focusTrack.priority + 0.08),
rationale: focusTrack.whyNow,
deliverable: focusTrack.nextExperiment,
doneWhen: focusTrack.successCriteria,
}),
makeWorkItem({
id: "stability-bridge",
title:
params.nucleus.mode === "reframe"
? "Reencuadrar el n煤cleo antes de insistir"
: "Traducir el foco en una pieza estable de arquitectura",
track: secondaryTrack,
priority: clamp01(secondaryTrack.priority + (params.nucleus.mode === "reframe" ? 0.1 : 0.03)),
rationale:
params.nucleus.mode === "reframe"
? "El n煤cleo detecta presi贸n o estancamiento; insistir sin reframe degradar铆a el estudio."
: "El foco necesita convertirse en estructura para no disiparse entre ciclos.",
deliverable:
params.nucleus.mode === "reframe"
? `Producir una nueva formulaci贸n ejecutiva o prototipo m铆nimo para ${secondaryTrack.title}.`
: `Convertir ${secondaryTrack.title} en m贸dulo, adapter o experimento persistente dentro del repo.`,
doneWhen:
params.nucleus.mode === "reframe"
? "El siguiente ciclo ya no repite el mismo atasco y el enfoque cambia materialmente."
: "Existe un artefacto nuevo y verificable, no solo notas.",
}),
makeWorkItem({
id: "evidence-loop",
title: "Cerrar el bucle emp铆rico del estudio",
track: metabolismTrack,
priority: clamp01(Math.max(0.45, metabolismTrack.priority)),
rationale: `${params.nucleus.name} no debe acumular teor铆a sin medici贸n; cada ciclo libre debe dejar evidencia o criterio de descarte.`,
deliverable:
"Agregar o actualizar un benchmark, test, logger o criterio de kill que mida si el estudio cambi贸 conducta futura.",
doneWhen:
"Queda una validaci贸n ejecutable o un criterio de descarte expl铆cito asociado al experimento activo.",
}),
].sort((left, right) => right.priority - left.priority || left.title.localeCompare(right.title));
return {
sessionKey: params.sessionKey,
updatedAt: Date.now(),
projectName: params.nucleus.name,
focusKey: focusTrack.key,
mode: params.nucleus.mode,
items,
};
}
function buildStudyProgramMarkdown(program: SkynetStudyProgram): string {
const lines = [
`# ${program.projectName.toUpperCase()} Study Program`,
"",
`Actualizado: ${new Date(program.updatedAt).toISOString()}`,
`Sesion: ${program.sessionKey}`,
`Modo del nucleo: ${program.mode}`,
"",
"## Work Items",
"",
];
program.items.forEach((item, index) => {
lines.push(`### ${index + 1}. ${item.title}`);
lines.push(`- Track: ${item.trackKey}`);
lines.push(`- Prioridad: ${item.priority.toFixed(2)}`);
lines.push(`- Rationale: ${item.rationale}`);
lines.push(`- Deliverable: ${item.deliverable}`);
lines.push(`- Done when: ${item.doneWhen}`);
lines.push("");
});
return lines.join("\n").trim() + "\n";
}
export async function syncSkynetStudyProgram(params: {
workspaceRoot: string;
sessionKey: string;
supervisor: OmegaStudySupervisorState;
nucleus: SkynetNucleusState;
}): Promise<SkynetStudyProgram> {
const program = deriveSkynetStudyProgram(params);
const jsonPath = resolveStudyProgramJsonPath({
workspaceRoot: params.workspaceRoot,
sessionKey: params.sessionKey,
});
const markdownPath = resolveStudyProgramMarkdownPath(params.workspaceRoot);
await fs.mkdir(path.dirname(jsonPath), { recursive: true });
await fs.mkdir(path.dirname(markdownPath), { recursive: true });
await fs.writeFile(jsonPath, JSON.stringify(program, null, 2) + "\n", "utf-8");
await fs.writeFile(markdownPath, buildStudyProgramMarkdown(program), "utf-8");
return program;
}
export function formatSkynetStudyProgramBlock(program?: SkynetStudyProgram): string[] {
if (!program || program.items.length === 0) {
return [];
}
const lines = ["", `[${program.projectName} Study Program]`];
const top = program.items[0]!;
lines.push(`Mode: ${program.mode}`);
lines.push(`Primary work item: ${top.title} (${top.priority.toFixed(2)})`);
lines.push(`Deliverable: ${top.deliverable}`);
lines.push(`Done when: ${top.doneWhen}`);
const secondary = program.items
.slice(1, 3)
.map((item) => `${item.title} (${item.priority.toFixed(2)})`);
if (secondary.length > 0) {
lines.push(`Secondary items: ${secondary.join(" | ")}`);
}
return lines;
}