@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
144 lines (142 loc) • 4.83 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
function getTimeRange(period) {
const now = /* @__PURE__ */ new Date();
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
switch (period) {
case "today": {
return {
start: Math.floor(todayStart.getTime() / 1e3),
end: Math.floor(now.getTime() / 1e3),
label: `Today \u2014 ${todayStart.toISOString().slice(0, 10)}`
};
}
case "yesterday": {
const yesterdayStart = new Date(todayStart);
yesterdayStart.setDate(yesterdayStart.getDate() - 1);
return {
start: Math.floor(yesterdayStart.getTime() / 1e3),
end: Math.floor(todayStart.getTime() / 1e3),
label: `Yesterday \u2014 ${yesterdayStart.toISOString().slice(0, 10)}`
};
}
case "week": {
const weekStart = new Date(todayStart);
weekStart.setDate(weekStart.getDate() - 7);
return {
start: Math.floor(weekStart.getTime() / 1e3),
end: Math.floor(now.getTime() / 1e3),
label: `Week \u2014 ${weekStart.toISOString().slice(0, 10)} to ${todayStart.toISOString().slice(0, 10)}`
};
}
}
}
function formatDate(epoch) {
return new Date(epoch * 1e3).toISOString().slice(0, 10);
}
function generateChronologicalDigest(db, period, projectId) {
const { start, end, label } = getTimeRange(period);
let frames = db.prepare(
`SELECT frame_id, name, type, state, created_at, closed_at, inputs, outputs
FROM frames
WHERE project_id = ? AND created_at >= ? AND created_at < ?
ORDER BY created_at ASC`
).all(projectId, start, end);
if (frames.length === 0 && projectId !== "default") {
frames = db.prepare(
`SELECT frame_id, name, type, state, created_at, closed_at, inputs, outputs
FROM frames
WHERE project_id = 'default' AND created_at >= ? AND created_at < ?
ORDER BY created_at ASC`
).all(start, end);
}
if (frames.length === 0) {
return `# ${label}
No activity recorded.
`;
}
const frameIds = frames.map((f) => f.frame_id);
const placeholders = frameIds.map(() => "?").join(",");
const anchors = db.prepare(
`SELECT anchor_id, frame_id, type, text, priority, created_at
FROM anchors
WHERE frame_id IN (${placeholders})
ORDER BY priority DESC, created_at ASC`
).all(...frameIds);
const events = db.prepare(
`SELECT event_id, frame_id, event_type, payload, ts
FROM events
WHERE frame_id IN (${placeholders}) AND event_type IN ('tool_call', 'decision')
ORDER BY ts ASC`
).all(...frameIds);
const anchorsByFrame = /* @__PURE__ */ new Map();
for (const a of anchors) {
const list = anchorsByFrame.get(a.frame_id) || [];
list.push(a);
anchorsByFrame.set(a.frame_id, list);
}
const eventsByFrame = /* @__PURE__ */ new Map();
for (const e of events) {
const list = eventsByFrame.get(e.frame_id) || [];
list.push(e);
eventsByFrame.set(e.frame_id, list);
}
const framesByDate = /* @__PURE__ */ new Map();
for (const f of frames) {
const date = formatDate(f.created_at);
const list = framesByDate.get(date) || [];
list.push(f);
framesByDate.set(date, list);
}
const lines = [`# ${label}
`];
const renderFrame = (f) => {
lines.push(`## ${f.name} (${f.type}, ${f.state})`);
const frameAnchors = anchorsByFrame.get(f.frame_id) || [];
const frameEvents = eventsByFrame.get(f.frame_id) || [];
for (const a of frameAnchors.slice(0, 8)) {
lines.push(`- ${a.type}: ${a.text}`);
}
const files = /* @__PURE__ */ new Set();
for (const e of frameEvents) {
try {
const payload = JSON.parse(e.payload);
if (payload.arguments?.file_path)
files.add(payload.arguments.file_path);
if (payload.arguments?.path) files.add(payload.arguments.path);
} catch {
}
}
if (files.size > 0) {
lines.push(`- ${files.size} files touched`);
}
lines.push("");
};
if (period === "week") {
for (const [date, dateFrames] of framesByDate) {
lines.push(`### ${date}
`);
for (const f of dateFrames) {
renderFrame(f);
}
}
} else {
for (const f of frames) {
renderFrame(f);
}
}
const completed = frames.filter((f) => f.state === "completed").length;
const active = frames.filter((f) => f.state === "active").length;
lines.push("---");
lines.push(
`*${frames.length} frames total: ${completed} completed, ${active} active*`
);
lines.push(`*Generated: ${(/* @__PURE__ */ new Date()).toISOString()}*
`);
return lines.join("\n");
}
export {
generateChronologicalDigest
};