UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

137 lines (136 loc) 4.41 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { execSync } from "child_process"; import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import { logger } from "../core/monitoring/logger.js"; const STACKMEMORY_PROCESS_PATTERNS = [ "stackmemory", "ralph orchestrate", "ralph swarm", "ralph loop", "hooks start" ]; function getStackmemoryProcesses() { const processes = []; try { const psOutput = execSync( "ps -eo pid,lstart,command 2>/dev/null || ps -eo pid,start,args 2>/dev/null", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] } ); const lines = psOutput.trim().split("\n").slice(1); for (const line of lines) { const isStackmemory = STACKMEMORY_PROCESS_PATTERNS.some( (pattern) => line.toLowerCase().includes(pattern.toLowerCase()) ); if (!isStackmemory) continue; const match = line.match( /^\s*(\d+)\s+(\w+\s+\w+\s+\d+\s+[\d:]+\s+\d+)\s+(.+)$/ ); if (match) { const pid = parseInt(match[1], 10); const startTimeStr = match[2]; const command = match[3]; if (pid === process.pid) continue; const startTime = new Date(startTimeStr); const ageMs = Date.now() - startTime.getTime(); const ageHours = ageMs / (1e3 * 60 * 60); processes.push({ pid, command: command.slice(0, 100), // Truncate long commands startTime, ageHours }); } } } catch (error) { logger.warn("Failed to get process list:", error); } return processes; } function hasRecentLogActivity(proc, maxAgeHours) { const logDir = path.join(os.homedir(), ".stackmemory", "logs"); if (!fs.existsSync(logDir)) return false; try { const logFiles = fs.readdirSync(logDir).filter((f) => f.endsWith(".log")); for (const logFile of logFiles) { const logPath = path.join(logDir, logFile); const stats = fs.statSync(logPath); const logAgeHours = (Date.now() - stats.mtimeMs) / (1e3 * 60 * 60); if (logAgeHours < maxAgeHours) { try { const content = fs.readFileSync(logPath, "utf-8").slice(-1e4); if (content.includes(`pid:${proc.pid}`) || content.includes(`PID ${proc.pid}`)) { proc.logFile = logPath; proc.lastLogActivity = stats.mtime; return true; } } catch { } } } } catch (error) { logger.warn("Failed to check log activity:", error); } return false; } function findStaleProcesses(maxAgeHours = 24) { const allProcesses = getStackmemoryProcesses(); return allProcesses.filter((proc) => { if (proc.ageHours < maxAgeHours) return false; if (hasRecentLogActivity(proc, maxAgeHours)) return false; return true; }); } function killStaleProcesses(processes, dryRun = false) { const result = { found: processes, killed: [], errors: [] }; for (const proc of processes) { if (dryRun) { logger.info(`[DRY RUN] Would kill PID ${proc.pid}: ${proc.command}`); continue; } try { process.kill(proc.pid, "SIGTERM"); result.killed.push(proc.pid); logger.info(`Killed stale process ${proc.pid}: ${proc.command}`); } catch (error) { const err = error; if (err.code === "ESRCH") { result.killed.push(proc.pid); } else { result.errors.push({ pid: proc.pid, error: err.message || "Unknown error" }); logger.warn(`Failed to kill PID ${proc.pid}:`, err.message); } } } return result; } function cleanupStaleProcesses(options = {}) { const { maxAgeHours = 24, dryRun = false } = options; logger.info(`Looking for stale processes older than ${maxAgeHours}h...`); const staleProcesses = findStaleProcesses(maxAgeHours); if (staleProcesses.length === 0) { logger.info("No stale processes found"); return { found: [], killed: [], errors: [] }; } logger.info(`Found ${staleProcesses.length} stale process(es)`); return killStaleProcesses(staleProcesses, dryRun); } export { cleanupStaleProcesses, findStaleProcesses, getStackmemoryProcesses, killStaleProcesses }; //# sourceMappingURL=process-cleanup.js.map