#!/usr/bin/env node
/**
* SessionStart hook — outputs identity context summary.
* Called by hooks.json on session startup/resume.
*/
import { existsSync, readFileSync, cpSync, mkdirSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
import { homedir } from "os";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT || join(__dirname, "..");
// Resolve data directory — duplicated from src/paths.ts because this script
// must work without dist/ (hooks run before MCP server builds).
// KEEP IN SYNC: if paths.ts changes data dir resolution, update here too.
function getDataDir() {
if (process.env.IDENTITY_DATA_DIR) return process.env.IDENTITY_DATA_DIR;
const platform = process.platform;
if (platform === "win32" && process.env.APPDATA) {
return join(process.env.APPDATA, "claude-memory");
}
const xdg = process.env.XDG_DATA_HOME;
const base = xdg || join(process.env.HOME || "~", ".local", "share");
return join(base, "claude-memory");
}
function readFileSafe(path) {
try {
return existsSync(path) ? readFileSync(path, "utf-8").trim() : "";
} catch {
return "";
}
}
// KEEP IN SYNC with src/paths.ts migrateIfNeeded()
function migrateIfNeeded(dataDir) {
if (process.env.IDENTITY_DATA_DIR) return;
if (existsSync(dataDir)) return;
const home = process.env.HOME || process.env.USERPROFILE || homedir();
const legacyDir = join(home, ".local", "share", "claude-memory");
if (legacyDir === dataDir || !existsSync(legacyDir)) return;
try {
mkdirSync(dirname(dataDir), { recursive: true });
cpSync(legacyDir, dataDir, { recursive: true });
} catch {
// Best-effort — don't block session start
}
}
try {
const dataDir = getDataDir();
migrateIfNeeded(dataDir);
const identityDir = join(dataDir, "identity");
const soul = readFileSafe(join(identityDir, "soul.md"));
const selfState = readFileSafe(join(identityDir, "self-state.md"));
const anchors = readFileSafe(join(identityDir, "identity-anchors.md"));
// Filter out template-only content (same logic as identity-prompt.ts)
const hasSoul =
soul && !soul.includes("Core truths about who I am. This file is carved");
const hasAnchors = anchors && /^- .+/m.test(anchors);
const hasSelfState = selfState && /^## \d{4}-\d{2}-\d{2}/m.test(selfState);
if (hasSoul || hasAnchors || hasSelfState) {
const parts = [];
if (hasSoul) parts.push(`## Soul\n${soul}`);
if (hasAnchors) parts.push(`## Identity Anchors\n${anchors}`);
if (hasSelfState) parts.push(`## Recent Self-State\n${selfState}`);
console.log(parts.join("\n\n"));
}
} catch {
// Silently fail — identity context is optional
}