UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

177 lines (176 loc) 4.9 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { join } from "path"; import { homedir } from "os"; import { isProcessAlive } from "../utils/process-cleanup.js"; const DEFAULT_DAEMON_CONFIG = { version: "1.0.0", context: { enabled: true, interval: 15, // 15 minutes checkpointMessage: "Auto-checkpoint" }, linear: { enabled: false, // Disabled by default, requires setup interval: 60, // 60 minutes quietHours: { start: 22, end: 7 }, retryAttempts: 3, retryDelay: 3e4 }, maintenance: { enabled: true, interval: 360, // 6 hours staleFrameThresholdDays: 30, ftsRebuildInterval: 24, // hours embeddingBatchSize: 50, vacuumInterval: 168, // weekly gcIntervalSeconds: 60 }, memory: { enabled: true, interval: 0.5, // 30 seconds ramThreshold: 0.9, heapThreshold: 0.9, cooldownMinutes: 10 }, fileWatch: { enabled: false, // Disabled by default interval: 0, // Not interval-based paths: ["."], extensions: [".ts", ".js", ".tsx", ".jsx", ".py", ".go", ".rs"], ignore: ["node_modules", ".git", "dist", "build", ".stackmemory"], debounceMs: 2e3 }, heartbeatInterval: 60, // 1 minute inactivityTimeout: 0, // Disabled by default logLevel: "info" }; function getDaemonDir() { const dir = join(homedir(), ".stackmemory", "daemon"); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } return dir; } function getLogsDir() { const dir = join(homedir(), ".stackmemory", "logs"); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } return dir; } function getDaemonPaths() { const daemonDir = getDaemonDir(); const logsDir = getLogsDir(); return { pidFile: join(daemonDir, "daemon.pid"), statusFile: join(daemonDir, "daemon.status"), configFile: join(daemonDir, "config.json"), logFile: join(logsDir, "daemon.log") }; } function loadDaemonConfig() { const { configFile } = getDaemonPaths(); if (!existsSync(configFile)) { return { ...DEFAULT_DAEMON_CONFIG }; } try { const content = readFileSync(configFile, "utf8"); const config = JSON.parse(content); return { ...DEFAULT_DAEMON_CONFIG, ...config, context: { ...DEFAULT_DAEMON_CONFIG.context, ...config.context }, linear: { ...DEFAULT_DAEMON_CONFIG.linear, ...config.linear }, maintenance: { ...DEFAULT_DAEMON_CONFIG.maintenance, ...config.maintenance }, memory: { ...DEFAULT_DAEMON_CONFIG.memory, ...config.memory }, fileWatch: { ...DEFAULT_DAEMON_CONFIG.fileWatch, ...config.fileWatch } }; } catch { return { ...DEFAULT_DAEMON_CONFIG }; } } function saveDaemonConfig(config) { const { configFile } = getDaemonPaths(); const currentConfig = loadDaemonConfig(); const newConfig = { ...currentConfig, ...config, context: { ...currentConfig.context, ...config.context }, linear: { ...currentConfig.linear, ...config.linear }, maintenance: { ...currentConfig.maintenance, ...config.maintenance }, memory: { ...currentConfig.memory, ...config.memory }, fileWatch: { ...currentConfig.fileWatch, ...config.fileWatch } }; writeFileSync(configFile, JSON.stringify(newConfig, null, 2)); } function readDaemonStatus() { const { statusFile, pidFile } = getDaemonPaths(); const defaultStatus = { running: false, services: { context: { enabled: false }, linear: { enabled: false }, maintenance: { enabled: false }, memory: { enabled: false }, fileWatch: { enabled: false } }, errors: [] }; if (!existsSync(pidFile)) { return defaultStatus; } try { const pidContent = readFileSync(pidFile, "utf8").trim(); const pid = parseInt(pidContent, 10); if (!isProcessAlive(pid)) { return defaultStatus; } if (!existsSync(statusFile)) { return { ...defaultStatus, running: true, pid }; } const content = readFileSync(statusFile, "utf8"); const status = JSON.parse(content); return { ...status, running: true, pid, uptime: status.startTime ? Date.now() - status.startTime : void 0 }; } catch { return defaultStatus; } } function writeDaemonStatus(status) { const { statusFile } = getDaemonPaths(); const currentStatus = readDaemonStatus(); const newStatus = { ...currentStatus, ...status }; writeFileSync(statusFile, JSON.stringify(newStatus, null, 2)); } export { DEFAULT_DAEMON_CONFIG, getDaemonDir, getDaemonPaths, getLogsDir, loadDaemonConfig, readDaemonStatus, saveDaemonConfig, writeDaemonStatus };