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.

174 lines (173 loc) 5.99 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { Command } from "commander"; import Database from "better-sqlite3"; import { join } from "path"; import { existsSync } from "fs"; import { z } from "zod"; const SearchQuerySchema = z.string().min(1, "Search query is required").max(500, "Search query too long (max 500 characters)").transform((val) => { return val.replace(/[%_\\]/g, "\\$&"); }); const LimitSchema = z.string().transform((val) => parseInt(val, 10)).pipe(z.number().int().min(1).max(100).default(20)); function createSearchCommand() { const search = new Command("search").alias("find").description("Search across tasks and context").argument("<query>", "Search query").option("-t, --tasks", "Search only tasks").option("-c, --context", "Search only context").option("-l, --limit <n>", "Limit results", "20").action(async (rawQuery, options) => { const projectRoot = process.cwd(); const dbPath = join(projectRoot, ".stackmemory", "context.db"); if (!existsSync(dbPath)) { console.log( '\u274C StackMemory not initialized. Run "stackmemory init" first.' ); return; } let query; let limit; try { query = SearchQuerySchema.parse(rawQuery); limit = LimitSchema.parse(options.limit); } catch (error) { if (error instanceof z.ZodError) { console.error("\u274C Invalid input:", error.errors[0].message); } else { console.error("\u274C Invalid input"); } return; } const db = new Database(dbPath); const searchTasks = !options.context || options.tasks; const searchContext = !options.tasks || options.context; console.log(` \u{1F50D} Searching for "${rawQuery}"... `); let totalResults = 0; if (searchTasks) { try { const tasks = db.prepare( ` SELECT id, title, description, status, priority, created_at FROM task_cache WHERE title LIKE ? OR description LIKE ? ORDER BY created_at DESC LIMIT ? ` ).all(`%${query}%`, `%${query}%`, limit); if (tasks.length > 0) { console.log(`\u{1F4CB} Tasks (${tasks.length}) `); const priorityIcon = { urgent: "\u{1F534}", high: "\u{1F7E0}", medium: "\u{1F7E1}", low: "\u{1F7E2}" }; const statusIcon = { pending: "\u23F3", in_progress: "\u{1F504}", completed: "\u2705", blocked: "\u{1F6AB}" }; tasks.forEach((task) => { const pIcon = priorityIcon[task.priority] || "\u26AA"; const sIcon = statusIcon[task.status] || "\u26AA"; console.log(`${sIcon} ${pIcon} ${task.title}`); if (task.description) { const desc = task.description.split("\n")[0]; const matchIdx = desc.toLowerCase().indexOf(query.toLowerCase()); if (matchIdx >= 0) { const start = Math.max(0, matchIdx - 20); const end = Math.min( desc.length, matchIdx + query.length + 20 ); const snippet = (start > 0 ? "..." : "") + desc.slice(start, end) + (end < desc.length ? "..." : ""); console.log(` ${snippet}`); } } }); console.log(""); totalResults += tasks.length; } } catch (error) { } } if (searchContext) { try { const contexts = db.prepare( ` SELECT id, type, name, metadata, created_at FROM frames WHERE name LIKE ? OR metadata LIKE ? ORDER BY created_at DESC LIMIT ? ` ).all(`%${query}%`, `%${query}%`, limit); if (contexts.length > 0) { console.log(`\u{1F4C1} Context Frames (${contexts.length}) `); const typeIcon = { session: "\u{1F537}", task: "\u{1F4CB}", command: "\u26A1", file: "\u{1F4C4}", decision: "\u{1F4A1}" }; contexts.forEach((ctx) => { const icon = typeIcon[ctx.type] || "\u{1F4E6}"; const date = new Date(ctx.created_at * 1e3).toLocaleDateString(); console.log( `${icon} [${ctx.type}] ${ctx.name || ctx.id.slice(0, 10)}` ); console.log(` Created: ${date}`); }); console.log(""); totalResults += contexts.length; } } catch (error) { } } if (searchContext) { try { const events = db.prepare( ` SELECT id, type, data, timestamp FROM events WHERE data LIKE ? ORDER BY timestamp DESC LIMIT ? ` ).all(`%${query}%`, limit); if (events.length > 0) { console.log(`\u{1F4DD} Events (${events.length}) `); events.forEach((evt) => { const date = new Date(evt.timestamp * 1e3).toLocaleDateString(); let data = {}; try { data = JSON.parse(evt.data); } catch { } const summary = data.content || data.message || data.decision || evt.type; console.log(`\u26A1 [${evt.type}] ${String(summary).slice(0, 60)}`); console.log(` ${date}`); }); console.log(""); totalResults += events.length; } } catch (error) { } } db.close(); if (totalResults === 0) { console.log("No results found.\n"); } else { console.log(`Found ${totalResults} results. `); } }); return search; } export { createSearchCommand }; //# sourceMappingURL=search.js.map