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

144 lines (142 loc) 4.83 kB
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 };