/** * `initBrain` — the idempotent runtime scaffold for the second brain (SB-S0 D1/D5). * * Creates the `brain/` + `ingest/` tree under the per-user data dir (via the * `dataRoot` resolver, so `LINKEDIN_STUDIO_DATA` relocates it for tests + real * installs). Idempotent + migration-safe: a directory that exists is left alone, * a file that exists is NEVER rewritten (compare-then-skip, never blind-write) — * so a second run is a no-op and a user-edited file is preserved. * * NO session-start wiring (SB-S2 owns that) — this only ships the invokable init. */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { dataRoot } from "./dataRoot.js"; import { foldUserProfile, serializeProfile } from "./profile.js"; const HERE = dirname(fileURLToPath(import.meta.url)); /** The shipped template that defines the canonical profile field-set. */ const TEMPLATE_PATH = join(HERE, "..", "..", "..", "config", "user-profile.template.md"); /** The runtime instance (if the user copied + filled the template). */ const INSTANCE_SUB = join("profile", "user-profile.md"); const SCAFFOLD_DIRS = ["brain/journal", "ingest/inbox", "ingest/published"]; function today(): string { return new Date().toISOString().slice(0, 10); } function indexSeed(): string { return `# Brain — Index (MOC) > Map of Content — one screen pointing at every tributary, with a freshness flag. > Generated by \`brain init\`; safe to hand-edit (a re-run never clobbers it). | Tributary | What it holds | Freshness | |-----------|---------------|-----------| | voice-samples | writing style | — | | specifics-bank | lived raw material | — | | trends | external signal | — | | analytics | performance | — | | ingest | inbox + published (the gold signal) | — | - [profile.md](profile.md) — semantic, two-layer (static + dynamic) profile - [operations.md](operations.md) — plans · ideas · the "who I am now" anchor - journal/ — episodic, append-only session log (the consolidation loop's source) `; } function operationsSeed(): string { return `# Operations > The operations centre — where you're headed now, what you're working on, what you might > do next. User-authored: the brain motor never writes here (a re-run of init never > re-touches an existing file, so your edits are safe). ## Who I am now (anchor) _As of YYYY-MM-DD:_ ## Plans ## Ideas `; } function profileSeed(): string { const templateText = readFileSync(TEMPLATE_PATH, "utf8"); const instancePath = dataRoot(INSTANCE_SUB); const instanceText = existsSync(instancePath) ? readFileSync(instancePath, "utf8") : undefined; return serializeProfile(foldUserProfile({ templateText, instanceText, today: today() })); } export interface InitResult { /** Data-relative paths created this run. */ created: string[]; /** Data-relative paths that already existed and were left untouched. */ skipped: string[]; } /** * Create the brain scaffold idempotently. Returns the created/skipped split. * File content seeds are computed lazily, so an already-present file costs no * template read or fold. */ export function initBrain(): InitResult { const created: string[] = []; const skipped: string[] = []; for (const sub of SCAFFOLD_DIRS) { const abs = dataRoot(sub); if (existsSync(abs)) { skipped.push(sub); } else { mkdirSync(abs, { recursive: true }); created.push(sub); } } const files: Array<{ sub: string; seed: () => string }> = [ { sub: "brain/profile.md", seed: profileSeed }, { sub: "brain/index.md", seed: indexSeed }, { sub: "brain/operations.md", seed: operationsSeed }, ]; for (const { sub, seed } of files) { const abs = dataRoot(sub); if (existsSync(abs)) { skipped.push(sub); continue; } mkdirSync(dirname(abs), { recursive: true }); writeFileSync(abs, seed(), "utf8"); created.push(sub); } return { created, skipped }; }