@hir4ta/mneme 0.19.0 → 0.20.0

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mneme",
3
3
  "description": "A plugin that provides long-term memory for Claude Code. It automatically saves context lost during auto-compact, offering features for session restoration, recording technical decisions, and learning developer patterns.",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "author": {
6
6
  "name": "hir4ta"
7
7
  },
@@ -193,6 +193,26 @@ function updateSaveState(db, claudeSessionId, lastSavedTimestamp, lastSavedLine)
193
193
  `);
194
194
  stmt.run(lastSavedTimestamp, lastSavedLine, claudeSessionId);
195
195
  }
196
+ function extractSlashCommand(content) {
197
+ const match = content.match(/<command-name>([^<]+)<\/command-name>/);
198
+ return match ? match[1] : void 0;
199
+ }
200
+ function extractToolResultMeta(content) {
201
+ return content.filter((c) => c.type === "tool_result" && c.tool_use_id).map((c) => {
202
+ const contentStr = typeof c.content === "string" ? c.content : c.content ? JSON.stringify(c.content) : "";
203
+ const lineCount = contentStr.split("\n").length;
204
+ const filePathMatch = contentStr.match(
205
+ /^(?:\s*\d+[→|]\s*)?([^\n]+\.(ts|js|py|json|md|sql|sh|tsx|jsx))/
206
+ );
207
+ return {
208
+ toolUseId: c.tool_use_id || "",
209
+ success: !c.is_error,
210
+ contentLength: contentStr.length,
211
+ lineCount: lineCount > 1 ? lineCount : void 0,
212
+ filePath: filePathMatch ? filePathMatch[1] : void 0
213
+ };
214
+ });
215
+ }
196
216
  async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
197
217
  const fileStream = fs.createReadStream(transcriptPath);
198
218
  const rl = readline.createInterface({
@@ -211,18 +231,78 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
211
231
  }
212
232
  }
213
233
  }
234
+ const planModeEvents = [];
235
+ for (const entry of entries) {
236
+ if (entry.type === "assistant" && Array.isArray(entry.message?.content)) {
237
+ for (const c of entry.message.content) {
238
+ if (c.type === "tool_use") {
239
+ if (c.name === "EnterPlanMode") {
240
+ planModeEvents.push({ timestamp: entry.timestamp, entering: true });
241
+ } else if (c.name === "ExitPlanMode") {
242
+ planModeEvents.push({
243
+ timestamp: entry.timestamp,
244
+ entering: false
245
+ });
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ function isInPlanMode(timestamp) {
252
+ let inPlanMode = false;
253
+ for (const event of planModeEvents) {
254
+ if (event.timestamp > timestamp) break;
255
+ inPlanMode = event.entering;
256
+ }
257
+ return inPlanMode;
258
+ }
259
+ const progressEvents = /* @__PURE__ */ new Map();
260
+ for (const entry of entries) {
261
+ if (entry.type === "progress" && entry.data?.type) {
262
+ const event = {
263
+ type: entry.data.type,
264
+ timestamp: entry.timestamp,
265
+ hookEvent: entry.data.hookEvent,
266
+ hookName: entry.data.hookName,
267
+ toolName: entry.data.toolName
268
+ };
269
+ const key = entry.timestamp.slice(0, 16);
270
+ if (!progressEvents.has(key)) {
271
+ progressEvents.set(key, []);
272
+ }
273
+ progressEvents.get(key)?.push(event);
274
+ }
275
+ }
214
276
  const userMessages = entries.filter((e) => {
215
277
  if (e.type !== "user" || e.message?.role !== "user") return false;
278
+ if (e.isMeta === true) return false;
216
279
  const content = e.message?.content;
217
280
  if (typeof content !== "string") return false;
218
281
  if (content.startsWith("<local-command-stdout>")) return false;
219
282
  if (content.startsWith("<local-command-caveat>")) return false;
220
283
  return true;
221
- }).map((e) => ({
222
- timestamp: e.timestamp,
223
- content: e.message?.content,
224
- isCompactSummary: e.isCompactSummary || false
225
- }));
284
+ }).map((e) => {
285
+ const content = e.message?.content;
286
+ return {
287
+ timestamp: e.timestamp,
288
+ content,
289
+ isCompactSummary: e.isCompactSummary || false,
290
+ slashCommand: extractSlashCommand(content)
291
+ };
292
+ });
293
+ const toolResultsByTimestamp = /* @__PURE__ */ new Map();
294
+ for (const entry of entries) {
295
+ if (entry.type === "user" && Array.isArray(entry.message?.content)) {
296
+ const results = extractToolResultMeta(
297
+ entry.message.content
298
+ );
299
+ if (results.length > 0) {
300
+ const key = entry.timestamp.slice(0, 16);
301
+ const existing = toolResultsByTimestamp.get(key) || [];
302
+ toolResultsByTimestamp.set(key, [...existing, ...results]);
303
+ }
304
+ }
305
+ }
226
306
  const assistantMessages = entries.filter((e) => e.type === "assistant").map((e) => {
227
307
  const contentArray = e.message?.content;
228
308
  if (!Array.isArray(contentArray)) return null;
@@ -249,6 +329,7 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
249
329
  );
250
330
  if (turnResponses.length > 0) {
251
331
  const allToolDetails = turnResponses.flatMap((r) => r.toolDetails);
332
+ const timeKey = user.timestamp.slice(0, 16);
252
333
  interactions.push({
253
334
  timestamp: user.timestamp,
254
335
  user: user.content,
@@ -256,7 +337,12 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
256
337
  assistant: turnResponses.filter((r) => r.text).map((r) => r.text).join("\n"),
257
338
  isCompactSummary: user.isCompactSummary,
258
339
  toolsUsed: [...new Set(allToolDetails.map((t) => t.name))],
259
- toolDetails: allToolDetails
340
+ toolDetails: allToolDetails,
341
+ // New metadata
342
+ inPlanMode: isInPlanMode(user.timestamp) || void 0,
343
+ slashCommand: user.slashCommand,
344
+ toolResults: toolResultsByTimestamp.get(timeKey),
345
+ progressEvents: progressEvents.get(timeKey)
260
346
  });
261
347
  }
262
348
  }
@@ -366,7 +452,18 @@ async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
366
452
  try {
367
453
  const metadata = JSON.stringify({
368
454
  toolsUsed: interaction.toolsUsed,
369
- toolDetails: interaction.toolDetails
455
+ toolDetails: interaction.toolDetails,
456
+ // New metadata fields
457
+ ...interaction.inPlanMode && { inPlanMode: true },
458
+ ...interaction.slashCommand && {
459
+ slashCommand: interaction.slashCommand
460
+ },
461
+ ...interaction.toolResults && interaction.toolResults.length > 0 && {
462
+ toolResults: interaction.toolResults
463
+ },
464
+ ...interaction.progressEvents && interaction.progressEvents.length > 0 && {
465
+ progressEvents: interaction.progressEvents
466
+ }
370
467
  });
371
468
  insertStmt.run(
372
469
  mnemeSessionId,
@@ -385,6 +482,17 @@ async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
385
482
  );
386
483
  insertedCount++;
387
484
  if (interaction.assistant) {
485
+ const assistantMetadata = JSON.stringify({
486
+ toolsUsed: interaction.toolsUsed,
487
+ toolDetails: interaction.toolDetails,
488
+ ...interaction.inPlanMode && { inPlanMode: true },
489
+ ...interaction.toolResults && interaction.toolResults.length > 0 && {
490
+ toolResults: interaction.toolResults
491
+ },
492
+ ...interaction.progressEvents && interaction.progressEvents.length > 0 && {
493
+ progressEvents: interaction.progressEvents
494
+ }
495
+ });
388
496
  insertStmt.run(
389
497
  mnemeSessionId,
390
498
  claudeSessionId,
@@ -396,7 +504,7 @@ async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
396
504
  "assistant",
397
505
  interaction.assistant,
398
506
  interaction.thinking || null,
399
- null,
507
+ assistantMetadata,
400
508
  interaction.timestamp,
401
509
  0
402
510
  );