import { existsSync } from 'node:fs'; import { homedir } from 'node:os'; import { join, resolve } from 'node:path'; /** * Overridable resolution inputs (tests inject tmp homes; production reads * the environment and the real home directory). */ export interface BrainHomeOptions { /** Explicit brain home; defaults to `MOSAIC_BRAIN_HOME`. */ readonly envBrainHome?: string; /** * Canonical homes used for adoption. Defaults derive from the real * `homedir()`: `{ brain: ~/.mosaic, configDefault: ~/.config/mosaic }`. */ readonly homes?: { readonly brain: string; readonly configDefault: string }; } /** * Brain-home resolution — the three-tree fleet split (stack canon * `docs/STRUCTURE-CANON.md` §2, first carried by the USC estate brain): * * config home (~/.config/mosaic) framework templates + dispatch state: * fleet/roles (baseline), fleet/roster.yaml, * fleet/run (heartbeats), fleet/services * brain home (~/.mosaic) user-owned fleet state, committed: * fleet/agents/.env.*, fleet/roles.local, * fleet/profiles working copies * * Resolution order: * 1. `MOSAIC_BRAIN_HOME` env (explicit, always wins) * 2. canonical `~/.mosaic` — adopted ONLY when mosaicHome is the real * default config home AND `~/.mosaic/fleet/agents` exists. Custom * `--mosaic-home` values (tests, sandboxes, canaries) never trigger * adoption, keeping them hermetic and deterministic. * 3. mosaicHome itself (legacy single-tree behavior). */ export function resolveBrainHome(mosaicHome: string, options: BrainHomeOptions = {}): string { const explicit = options.envBrainHome ?? process.env['MOSAIC_BRAIN_HOME']; if (explicit !== undefined && explicit.trim() !== '') { return explicit; } const homes = options.homes ?? { brain: join(homedir(), '.mosaic'), configDefault: join(homedir(), '.config', 'mosaic'), }; if (resolve(mosaicHome) !== resolve(homes.configDefault)) { return mosaicHome; } return existsSync(join(homes.brain, 'fleet', 'agents')) ? homes.brain : mosaicHome; } /** True when fleet state resolves somewhere other than the config home. */ export function brainHomeIsActive(mosaicHome: string, options: BrainHomeOptions = {}): boolean { return resolve(resolveBrainHome(mosaicHome, options)) !== resolve(mosaicHome); } /** Fleet state root (brain home when active, else the config home). */ export function fleetStateDir(mosaicHome: string, options: BrainHomeOptions = {}): string { return join(resolveBrainHome(mosaicHome, options), 'fleet'); } /** Seat launch envs — `/fleet/agents` when a brain is active. */ export function fleetAgentEnvDir(mosaicHome: string, options: BrainHomeOptions = {}): string { return join(fleetStateDir(mosaicHome, options), 'agents'); } /** PRESERVE-protected persona override layer — `/fleet/roles.local`. */ export function fleetRolesLocalDir(mosaicHome: string, options: BrainHomeOptions = {}): string { return join(fleetStateDir(mosaicHome, options), 'roles.local'); } /** System-type profiles (user working copies) — `/fleet/profiles`. */ export function fleetProfilesDir(mosaicHome: string, options: BrainHomeOptions = {}): string { return join(fleetStateDir(mosaicHome, options), 'profiles'); }