@bunkercache/opencode-memoir 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +117 -63
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -161,12 +161,16 @@ function resolveStoragePaths(worktree, projectId, config) {
161
161
  const relativePath = getGitignoreRelativePath(memoryDb.path, worktree);
162
162
  if (relativePath) {
163
163
  gitignorePaths.push(relativePath);
164
+ gitignorePaths.push(`${relativePath}-shm`);
165
+ gitignorePaths.push(`${relativePath}-wal`);
164
166
  }
165
167
  }
166
168
  if (historyDb?.isLocal && historyDb.manageGitignore && !sharedDatabase) {
167
169
  const relativePath = getGitignoreRelativePath(historyDb.path, worktree);
168
170
  if (relativePath && !gitignorePaths.includes(relativePath)) {
169
171
  gitignorePaths.push(relativePath);
172
+ gitignorePaths.push(`${relativePath}-shm`);
173
+ gitignorePaths.push(`${relativePath}-wal`);
170
174
  }
171
175
  }
172
176
  return {
@@ -1616,6 +1620,37 @@ class ChunkService {
1616
1620
  getRecentSummaryChunks(limit = 5) {
1617
1621
  return this.repository.getRecentSummaryChunks(limit);
1618
1622
  }
1623
+ getRecentChunks(options) {
1624
+ const limit = options?.limit ?? 10;
1625
+ let sql = `
1626
+ SELECT id, session_id, parent_id, depth, child_refs, content, summary,
1627
+ status, created_at, finalized_at, compacted_at, embedding
1628
+ FROM chunks
1629
+ `;
1630
+ const params = [];
1631
+ if (options?.sessionId) {
1632
+ sql += " WHERE session_id = ?";
1633
+ params.push(options.sessionId);
1634
+ }
1635
+ sql += " ORDER BY created_at DESC LIMIT ?";
1636
+ params.push(limit);
1637
+ const stmt = this.db.prepare(sql);
1638
+ const rows = stmt.all(...params);
1639
+ return rows.map((row) => ({
1640
+ id: row.id,
1641
+ sessionId: row.session_id,
1642
+ parentId: row.parent_id,
1643
+ depth: row.depth,
1644
+ childRefs: row.child_refs ? JSON.parse(row.child_refs) : null,
1645
+ content: JSON.parse(row.content),
1646
+ summary: row.summary,
1647
+ status: row.status,
1648
+ createdAt: row.created_at,
1649
+ finalizedAt: row.finalized_at,
1650
+ compactedAt: row.compacted_at,
1651
+ embedding: row.embedding
1652
+ }));
1653
+ }
1619
1654
  }
1620
1655
  var chunkService = null;
1621
1656
  function initializeChunkService(db, config) {
@@ -1637,33 +1672,7 @@ var MEMORY_NUDGE_MESSAGE = `[MEMORY TRIGGER DETECTED]
1637
1672
  The user wants you to remember something. Use the \`memoir\` tool with \`mode: "add"\` to save this information.
1638
1673
  Extract the key information and save it as a concise, searchable memory.
1639
1674
  Choose an appropriate type: "preference", "pattern", "gotcha", "fact", or "learned".`;
1640
- function formatChunkSummary(chunk) {
1641
- if (chunk.summary) {
1642
- return chunk.summary;
1643
- }
1644
- const messageCount = chunk.content.messages.length;
1645
- const files = chunk.content.metadata.files_modified?.length || 0;
1646
- const tools = chunk.content.metadata.tools_used?.slice(0, 3).join(", ") || "none";
1647
- return `${messageCount} messages, ${files} files modified, tools: ${tools}`;
1648
- }
1649
- function formatSessionHistory(chunks) {
1650
- if (chunks.length === 0) {
1651
- return "";
1652
- }
1653
- const lines = chunks.map((c) => {
1654
- const date = new Date(c.createdAt * 1000).toLocaleDateString();
1655
- return `- [${c.id}] (${date}): ${formatChunkSummary(c)}`;
1656
- });
1657
- return `
1658
- ## Recent Session History
1659
- The following past work may be relevant:
1660
-
1661
- ${lines.join(`
1662
- `)}
1663
-
1664
- Use \`memoir_expand({ chunk_id: "ch_xxx" })\` to see full details of any chunk.`;
1665
- }
1666
- function formatContextInjection(memories, recentChunks) {
1675
+ function formatContextInjection(memories) {
1667
1676
  const sections = [];
1668
1677
  if (memories.length > 0) {
1669
1678
  const memoryLines = memories.map((m) => `- [${m.type}] ${m.content}`);
@@ -1673,25 +1682,11 @@ The following memories are relevant to this conversation:
1673
1682
  ${memoryLines.join(`
1674
1683
  `)}`);
1675
1684
  }
1676
- const historySection = formatSessionHistory(recentChunks);
1677
- if (historySection) {
1678
- sections.push(historySection);
1679
- }
1680
1685
  if (sections.length > 0) {
1681
1686
  sections.push(`## Memoir Tools
1682
1687
  - \`memoir\` - Add or search project memories
1683
- - \`memoir_history\` - Search past sessions for relevant work (returns compact summaries)
1684
- - \`memoir_expand\` - Expand a [ch_xxx] reference to see full details
1685
- - Use \`preview_only: true\` to check size before full expansion
1686
-
1687
- **Context Budget Tip**: Expanded chunks can be large (1000-10000+ tokens each).
1688
- When exploring history or analyzing multiple chunks, consider delegating to a subagent:
1689
- \`\`\`
1690
- Task({
1691
- prompt: "Use memoir_history and memoir_expand to find and analyze past work on [topic]",
1692
- subagent_type: "explore"
1693
- })
1694
- \`\`\``);
1688
+ - \`memoir_history\` - Browse/search session history (current session by default, use all_sessions: true for past work)
1689
+ - \`memoir_expand\` - Expand a chunk ID to see full details`);
1695
1690
  }
1696
1691
  return sections.join(`
1697
1692
 
@@ -1722,13 +1717,8 @@ async function handleChatMessage(input, output) {
1722
1717
  if (isFirstMessage) {
1723
1718
  injectedSessions.add(sessionID);
1724
1719
  const memories = memoryService2.searchRelevant(messageText);
1725
- let recentChunks = [];
1726
- try {
1727
- const chunkService2 = getChunkService();
1728
- recentChunks = chunkService2.getRecentSummaryChunks(5);
1729
- } catch {}
1730
- if (memories.length > 0 || recentChunks.length > 0) {
1731
- const contextText = formatContextInjection(memories, recentChunks);
1720
+ if (memories.length > 0) {
1721
+ const contextText = formatContextInjection(memories);
1732
1722
  const contextPart = createSyntheticPart(sessionID, messageID || "", contextText);
1733
1723
  output.parts.unshift(contextPart);
1734
1724
  }
@@ -14556,28 +14546,89 @@ function estimateTokens2(text) {
14556
14546
  return Math.ceil(text.length / CHARS_PER_TOKEN2);
14557
14547
  }
14558
14548
  var historyTool = tool({
14559
- description: `Search session history for past work. Returns compact chunk summaries with IDs that can be expanded.
14549
+ description: `Browse or search session history. Returns chunk summaries with IDs for memoir_expand.
14550
+
14551
+ DEFAULTS: Searches current session only. Returns recent chunks if no query provided.
14552
+
14553
+ OPTIONS:
14554
+ - query: Search text (omit to list recent chunks)
14555
+ - all_sessions: true to include past sessions
14556
+ - limit: Max results (default 10)
14560
14557
 
14561
- CONTEXT BUDGET: Results are summaries (~50-200 tokens each). To view full chunk content, use memoir_expand. For deep exploration of multiple chunks, consider delegating to a subagent.`,
14558
+ Use memoir_expand({ chunk_id }) to see full content of any chunk.`,
14562
14559
  args: {
14563
- query: tool.schema.string().describe("Search query for finding relevant history"),
14564
- session_id: tool.schema.string().optional().describe("Limit search to a specific session"),
14565
- depth: tool.schema.number().optional().describe("Minimum depth to search (0 = original chunks, higher = summaries)"),
14566
- limit: tool.schema.number().optional().describe("Maximum number of results to return (default: 10, max recommended: 20)")
14560
+ query: tool.schema.string().optional().describe("Search query (omit to list recent chunks without searching)"),
14561
+ all_sessions: tool.schema.boolean().optional().describe("Include past sessions (default: current session only)"),
14562
+ session_ids: tool.schema.array(tool.schema.string()).optional().describe("Search specific session IDs"),
14563
+ depth: tool.schema.number().optional().describe("Minimum chunk depth (0=original, 1+=summaries)"),
14564
+ limit: tool.schema.number().optional().describe("Max results (default: 10)")
14567
14565
  },
14568
- async execute(args) {
14566
+ async execute(args, context) {
14569
14567
  const chunkService2 = getChunkService();
14570
- const results = chunkService2.search(args.query, {
14571
- sessionId: args.session_id,
14568
+ let sessionId;
14569
+ let searchScope;
14570
+ if (args.session_ids && args.session_ids.length > 0) {
14571
+ sessionId = args.session_ids[0];
14572
+ searchScope = `sessions: ${args.session_ids.join(", ")}`;
14573
+ } else if (args.all_sessions) {
14574
+ sessionId = undefined;
14575
+ searchScope = "all sessions";
14576
+ } else {
14577
+ sessionId = context.sessionID;
14578
+ searchScope = "current session";
14579
+ }
14580
+ const limit = args.limit ?? 10;
14581
+ const query = args.query?.trim();
14582
+ if (!query) {
14583
+ const recentChunks = chunkService2.getRecentChunks({
14584
+ sessionId,
14585
+ limit
14586
+ });
14587
+ if (recentChunks.length === 0) {
14588
+ return JSON.stringify({
14589
+ success: true,
14590
+ count: 0,
14591
+ scope: searchScope,
14592
+ mode: "recent",
14593
+ message: `No chunks found in ${searchScope}`,
14594
+ hint: args.all_sessions ? undefined : "Try with all_sessions: true to see past sessions"
14595
+ });
14596
+ }
14597
+ const formatted2 = recentChunks.map((c) => ({
14598
+ id: c.id,
14599
+ sessionId: c.sessionId,
14600
+ depth: c.depth,
14601
+ status: c.status,
14602
+ summary: c.summary || `${c.content.messages.length} messages`,
14603
+ created: new Date(c.createdAt * 1000).toISOString(),
14604
+ stats: {
14605
+ messages: c.content.messages.length,
14606
+ files_modified: c.content.metadata.files_modified?.length || 0
14607
+ }
14608
+ }));
14609
+ return JSON.stringify({
14610
+ success: true,
14611
+ count: formatted2.length,
14612
+ scope: searchScope,
14613
+ mode: "recent",
14614
+ chunks: formatted2,
14615
+ hint: 'Use memoir_expand({ chunk_id: "ch_xxx" }) to see full content.'
14616
+ });
14617
+ }
14618
+ const results = chunkService2.search(query, {
14619
+ sessionId,
14572
14620
  depth: args.depth,
14573
- limit: args.limit
14621
+ limit
14574
14622
  });
14575
14623
  if (results.length === 0) {
14576
14624
  return JSON.stringify({
14577
14625
  success: true,
14578
14626
  count: 0,
14579
- estimated_tokens: 50,
14580
- message: "No matching chunks found"
14627
+ scope: searchScope,
14628
+ mode: "search",
14629
+ query,
14630
+ message: `No matches for "${query}" in ${searchScope}`,
14631
+ hint: args.all_sessions ? undefined : "Try with all_sessions: true to search past sessions"
14581
14632
  });
14582
14633
  }
14583
14634
  const formatted = results.map((r) => {
@@ -14608,10 +14659,13 @@ CONTEXT BUDGET: Results are summaries (~50-200 tokens each). To view full chunk
14608
14659
  const response = {
14609
14660
  success: true,
14610
14661
  count: results.length,
14662
+ scope: searchScope,
14663
+ mode: "search",
14664
+ query,
14611
14665
  estimated_tokens: estimatedTokens,
14612
14666
  estimated_expanded_tokens: estimatedExpandedTokens,
14613
14667
  chunks: formatted,
14614
- hint: 'Use memoir_expand({ chunk_id: "ch_xxx", preview_only: true }) to check size before full expansion.'
14668
+ hint: 'Use memoir_expand({ chunk_id: "ch_xxx" }) to see full content.'
14615
14669
  };
14616
14670
  if (estimatedExpandedTokens > LARGE_RESULT_TOKEN_THRESHOLD && results.length > 3) {
14617
14671
  response.warning = `Expanding all ${results.length} results would use ~${estimatedExpandedTokens} tokens. Consider using memoir_expand with preview_only=true first, or delegate detailed analysis to a subagent.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunkercache/opencode-memoir",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "A smart memory management plugin for OpenCode that supports nested aggregation of memory, summaries, and file changes that compact in layers with upstream references for lookups.",
5
5
  "author": {
6
6
  "name": "Chris Tunbridge",