@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.
88 lines (87 loc) • 2.64 kB
JavaScript
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";
import { watch, readFileSync, existsSync } from "fs";
import { join } from "path";
const HOME = process.env["HOME"] || "/tmp";
const DEFAULT_STATE_FILE = join(HOME, ".stackmemory", "sweep-state.json");
class SweepStateWatcher extends EventEmitter {
stateFile;
lastPredictionTs = 0;
lastPendingTs = 0;
watcher = null;
debounceTimer = null;
pollTimer = null;
constructor(stateFile) {
super();
this.stateFile = stateFile || DEFAULT_STATE_FILE;
}
start() {
if (this.watcher) return;
if (!existsSync(this.stateFile)) {
this.pollTimer = setInterval(() => {
if (existsSync(this.stateFile)) {
clearInterval(this.pollTimer);
this.pollTimer = null;
this.startWatching();
}
}, 1e3);
return;
}
this.startWatching();
}
startWatching() {
this.readState();
this.watcher = watch(this.stateFile, () => {
if (this.debounceTimer) clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => this.readState(), 100);
});
this.watcher.on("error", () => {
this.watcher?.close();
this.watcher = null;
setTimeout(() => this.start(), 1e3);
});
}
readState() {
try {
if (!existsSync(this.stateFile)) return;
const raw = readFileSync(this.stateFile, "utf-8");
const state = JSON.parse(raw);
if (state.pendingPrediction && state.pendingPrediction !== this.lastPendingTs) {
this.lastPendingTs = state.pendingPrediction;
this.emit("loading");
}
if (state.lastPrediction && state.lastPrediction.timestamp > this.lastPredictionTs) {
this.lastPredictionTs = state.lastPrediction.timestamp;
const event = {
file_path: state.lastPrediction.file_path,
prediction: state.lastPrediction.prediction,
latency_ms: state.lastPrediction.latency_ms,
timestamp: state.lastPrediction.timestamp
};
this.emit("prediction", event);
}
} catch {
}
}
stop() {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
this.debounceTimer = null;
}
if (this.watcher) {
this.watcher.close();
this.watcher = null;
}
}
}
export {
SweepStateWatcher
};
//# sourceMappingURL=state-watcher.js.map