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

89 lines (88 loc) 2.72 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { EventEmitter } from "events"; const DEFAULT_CONFIG = { contextPressure: { enabled: true, cooldownSec: 60 }, editRecovery: { enabled: true, cooldownSec: 0 }, retrievalQuality: { enabled: true, cooldownSec: 300 }, traceErrorChain: { enabled: true, cooldownSec: 30 }, harnessRegression: { enabled: true, cooldownSec: 0 }, sessionDrift: { enabled: true, cooldownSec: 120 } }; class FeedbackLoopEngine extends EventEmitter { config; lastFired = /* @__PURE__ */ new Map(); history = []; maxHistory = 200; constructor(config = {}) { super(); this.config = { ...DEFAULT_CONFIG, ...config }; } /** * Fire a loop if enabled and not in cooldown. * Returns the LoopEvent if fired, null if skipped. */ fire(loopName, trigger, data, action, outcome = "success") { const cfg = this.config[loopName]; if (!cfg?.enabled) return null; const now = Date.now(); const lastTime = this.lastFired.get(loopName) || 0; if (now - lastTime < cfg.cooldownSec * 1e3) return null; const event = { loop: loopName, trigger, timestamp: now, data, action, outcome }; this.lastFired.set(loopName, now); this.history.push(event); if (this.history.length > this.maxHistory) { this.history = this.history.slice(-this.maxHistory); } this.emit("loop", event); this.emit(`loop:${loopName}`, event); return event; } /** Get recent loop events, optionally filtered by loop name. */ getHistory(loopName, limit = 50) { const filtered = loopName ? this.history.filter((e) => e.loop === loopName) : this.history; return filtered.slice(-limit); } /** Get summary stats per loop. */ getStats() { const stats = {}; for (const event of this.history) { if (!stats[event.loop]) { stats[event.loop] = { fires: 0, successes: 0, errors: 0, lastFired: null }; } stats[event.loop].fires++; if (event.outcome === "success") stats[event.loop].successes++; if (event.outcome === "error") stats[event.loop].errors++; stats[event.loop].lastFired = event.timestamp; } return stats; } /** Update config at runtime. */ updateConfig(partial) { this.config = { ...this.config, ...partial }; } /** Get current config. */ getConfig() { return { ...this.config }; } } const feedbackLoops = new FeedbackLoopEngine(); export { DEFAULT_CONFIG, FeedbackLoopEngine, feedbackLoops };