openskynet / src /skynet /continuity-tracker.ts
Darochin's picture
Add complete Skynet Brain Lab source tree
59936ca verified
import fs from "node:fs/promises";
import path from "node:path";
import type { SkynetNucleusState } from "./nucleus.js";
import type { SkynetStudyProgram } from "./study-program.js";
export type SkynetContinuityState = {
sessionKey: string;
updatedAt: number;
projectName: string;
cycleCount: number;
currentFocusKey: SkynetStudyProgram["focusKey"];
currentMode: SkynetNucleusState["mode"];
focusStreak: number;
retainedItemIds: string[];
modeShiftCount: number;
continuityScore: number;
};
function clamp01(value: number): number {
return Math.max(0, Math.min(1, value));
}
function sanitizeSessionKey(sessionKey: string): string {
return (sessionKey.trim() || "main").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 64) || "main";
}
function resolveContinuityJsonPath(params: { workspaceRoot: string; sessionKey: string }): string {
return path.join(
params.workspaceRoot,
".openskynet",
"skynet-continuity",
`${sanitizeSessionKey(params.sessionKey)}.json`,
);
}
function resolveContinuityMarkdownPath(workspaceRoot: string): string {
return path.join(workspaceRoot, "memory", "SKYNET_CONTINUITY.md");
}
async function loadPriorContinuityState(params: {
workspaceRoot: string;
sessionKey: string;
}): Promise<SkynetContinuityState | undefined> {
const filePath = resolveContinuityJsonPath(params);
try {
return JSON.parse(await fs.readFile(filePath, "utf-8")) as SkynetContinuityState;
} catch {
return undefined;
}
}
export function deriveSkynetContinuityState(params: {
sessionKey: string;
nucleus: SkynetNucleusState;
program: SkynetStudyProgram;
prior?: SkynetContinuityState;
}): SkynetContinuityState {
const prior = params.prior;
const currentItemIds = params.program.items.map((item) => item.id);
const retainedItemIds = prior
? currentItemIds.filter(
(itemId) =>
prior.retainedItemIds.includes(itemId) ||
params.program.items.some((item) => item.id === itemId),
)
: currentItemIds;
const retainedRatio =
currentItemIds.length > 0 ? retainedItemIds.length / currentItemIds.length : 0;
const sameFocus = prior?.currentFocusKey === params.program.focusKey;
const sameMode = prior?.currentMode === params.nucleus.mode;
const focusStreak = sameFocus ? (prior?.focusStreak ?? 0) + 1 : 1;
const cycleCount = (prior?.cycleCount ?? 0) + 1;
const modeShiftCount = (prior?.modeShiftCount ?? 0) + (sameMode || !prior ? 0 : 1);
const continuityScore = clamp01(
0.35 +
Math.min(focusStreak, 4) * 0.12 +
retainedRatio * 0.22 +
(sameMode ? 0.1 : 0) -
Math.min(modeShiftCount, 4) * 0.04,
);
return {
sessionKey: params.sessionKey,
updatedAt: Date.now(),
projectName: params.nucleus.name,
cycleCount,
currentFocusKey: params.program.focusKey,
currentMode: params.nucleus.mode,
focusStreak,
retainedItemIds,
modeShiftCount,
continuityScore,
};
}
function buildContinuityMarkdown(state: SkynetContinuityState): string {
return [
`# ${state.projectName.toUpperCase()} Continuity`,
"",
`Actualizado: ${new Date(state.updatedAt).toISOString()}`,
`Sesion: ${state.sessionKey}`,
`Ciclos observados: ${state.cycleCount}`,
`Focus actual: ${state.currentFocusKey}`,
`Modo actual: ${state.currentMode}`,
`Focus streak: ${state.focusStreak}`,
`Mode shifts: ${state.modeShiftCount}`,
`Continuity score: ${state.continuityScore.toFixed(2)}`,
"",
`Retained work items: ${state.retainedItemIds.join(", ") || "none"}`,
"",
].join("\n");
}
export async function syncSkynetContinuityState(params: {
workspaceRoot: string;
sessionKey: string;
nucleus: SkynetNucleusState;
program: SkynetStudyProgram;
}): Promise<SkynetContinuityState> {
const prior = await loadPriorContinuityState(params);
const state = deriveSkynetContinuityState({
sessionKey: params.sessionKey,
nucleus: params.nucleus,
program: params.program,
prior,
});
const jsonPath = resolveContinuityJsonPath(params);
const markdownPath = resolveContinuityMarkdownPath(params.workspaceRoot);
await fs.mkdir(path.dirname(jsonPath), { recursive: true });
await fs.mkdir(path.dirname(markdownPath), { recursive: true });
await fs.writeFile(jsonPath, JSON.stringify(state, null, 2) + "\n", "utf-8");
await fs.writeFile(markdownPath, buildContinuityMarkdown(state), "utf-8");
return state;
}
export function formatSkynetContinuityBlock(state?: SkynetContinuityState): string[] {
if (!state) {
return [];
}
return [
"",
`[${state.projectName} Continuity]`,
`Focus: ${state.currentFocusKey} (streak ${state.focusStreak})`,
`Mode: ${state.currentMode} | mode shifts ${state.modeShiftCount}`,
`Continuity score: ${state.continuityScore.toFixed(2)}`,
];
}