/** * Shared context builders for session startup. * Used by LoadContext.ts (Claude Code) and the opencode plugin. */ import { existsSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; import { loadReflectNudge } from "../handlers/reflect-trigger"; import { loadAlgorithmReviewNudge } from "./algorithm-review"; import { loadAnalyzeNudge } from "./analyze-nudge"; import { readLearnings } from "./learning-store"; import { loadOpinionContext } from "./opinions"; import { paths, toPath } from "./paths"; import { loadActiveProjectsContext } from "./projects"; import { loadRecentNotes } from "./relationship"; import { loadFailurePatterns } from "./semi-static"; import * as settings from "./settings"; import { readFramePrinciples } from "./wisdom"; import { readProjectHistory } from "./work-tracking"; /** Load and concatenate loadAtStartup files */ function loadStartupFiles(): string { const files = settings.startupFiles(); if (files.length === 0) return ""; const sections: string[] = []; for (const file of files) { const resolved = toPath(file); if (!existsSync(resolved)) continue; try { const content = readFileSync(resolved, "utf-8").trim(); if (content) sections.push(content); } catch { /* skip unreadable files */ } } return sections.join("\n\n---\n\n"); } /** Load high-confidence wisdom principles for injection into system-reminder */ export function loadWisdomContext(): string { try { const principles = readFramePrinciples(); if (principles.length === 0) return ""; return ["## Crystallized Principles", ...principles.map((p) => `- ${p}`)].join("\n"); } catch { return ""; } } /** Load recent session learning files as digest, with detail for current project */ export function loadLearningDigest(): string { try { const cwd = process.cwd(); const entries = readLearnings(paths.sessionLearning(), 10); if (entries.length === 0) return ""; // This-project learnings are now in loadProjectHistoryContext(); only show cross-project here const other = entries.filter((e) => e.cwd !== cwd).slice(0, 5); if (other.length === 0) return ""; const lines: string[] = []; lines.push("## Other Recent Learnings"); for (const e of other) lines.push(`- ${e.title}`); return lines.join("\n"); } catch { return ""; } } /** Load self-model for session context injection */ function loadSelfModel(): string { try { const p = resolve(paths.memory(), "self-model", "current.md"); if (!existsSync(p)) return ""; const content = readFileSync(p, "utf-8").trim(); if (!content) return ""; return content; } catch { return ""; } } /** Load per-project session history for the current working directory */ function loadProjectHistoryContext(): string { try { const cwd = process.cwd(); const entries = readProjectHistory(cwd, 3); if (entries.length === 0) return ""; const lines: string[] = ["## This Project — Session History"]; for (const e of entries) { lines.push(`- **${e.title}** (${e.date})`); if (e.summary) lines.push(` ${e.summary.split("\n")[0].slice(0, 150)}`); } return lines.join("\n"); } catch { return ""; } } /** * Filter raw relationship note lines: * - O entries: stripped (loaded natively via digest) * - HTML comments: stripped, but cwd is extracted from session comments * - Session entries: kept only if block cwd matches current project (or legacy with no cwd) * - W entries and structural lines: always kept */ function filterRelationshipNotes(notes: string, cwd: string): string { const lines = notes.split("\n"); const out: string[] = []; let blockCwd: string | null = null; for (const line of lines) { if (/^## \d{2}:\d{2}/.test(line)) { blockCwd = null; out.push(line); continue; } const cwdMatch = new RegExp(/