@hir4ta/mneme 0.25.0 → 0.25.2

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.25.0",
4
+ "version": "0.25.2",
5
5
  "author": {
6
6
  "name": "hir4ta"
7
7
  },
package/README.ja.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mneme
2
2
 
3
- ![Version](https://img.shields.io/badge/version-0.25.0-blue)
3
+ ![Version](https://img.shields.io/badge/version-0.25.2-blue)
4
4
  ![Node.js](https://img.shields.io/badge/node-%3E%3D22.5.0-brightgreen)
5
5
  [![NPM Version](https://img.shields.io/npm/v/%40hir4ta%2Fmneme)](https://www.npmjs.com/package/@hir4ta/mneme)
6
6
  [![MIT License](https://img.shields.io/npm/l/%40hir4ta%2Fmneme)](https://github.com/hir4ta/mneme/blob/main/LICENSE)
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mneme
2
2
 
3
- ![Version](https://img.shields.io/badge/version-0.25.0-blue)
3
+ ![Version](https://img.shields.io/badge/version-0.25.2-blue)
4
4
  ![Node.js](https://img.shields.io/badge/node-%3E%3D22.5.0-brightgreen)
5
5
  [![NPM Version](https://img.shields.io/npm/v/%40hir4ta%2Fmneme)](https://www.npmjs.com/package/@hir4ta/mneme)
6
6
  [![MIT License](https://img.shields.io/npm/l/%40hir4ta%2Fmneme)](https://github.com/hir4ta/mneme/blob/main/LICENSE)
@@ -518,9 +518,16 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
518
518
  progressEvents.get(key)?.push(event);
519
519
  }
520
520
  }
521
+ const planContentEntries = entries.filter(
522
+ (e) => e.type === "user" && e.message?.role === "user" && !!e.planContent && typeof e.message?.content === "string"
523
+ ).map((e) => ({
524
+ timestamp: e.timestamp,
525
+ content: e.message?.content
526
+ }));
521
527
  const userMessages = entries.filter((e) => {
522
528
  if (e.type !== "user" || e.message?.role !== "user") return false;
523
529
  if (e.isMeta === true) return false;
530
+ if (e.planContent) return false;
524
531
  const content = e.message?.content;
525
532
  if (typeof content !== "string") return false;
526
533
  if (content.startsWith("<local-command-stdout>")) return false;
@@ -531,7 +538,7 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
531
538
  return {
532
539
  timestamp: e.timestamp,
533
540
  content,
534
- isCompactSummary: e.isCompactSummary || !!e.planContent || false,
541
+ isCompactSummary: e.isCompactSummary || false,
535
542
  slashCommand: extractSlashCommand(content)
536
543
  };
537
544
  });
@@ -581,7 +588,8 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
581
588
  const orphanedResponses = assistantMessages.filter(
582
589
  (a) => a.timestamp < firstUserTs
583
590
  );
584
- if (orphanedResponses.length > 0) {
591
+ const planEntry = planContentEntries.find((p) => p.timestamp <= firstUserTs);
592
+ if (orphanedResponses.length > 0 || planEntry) {
585
593
  const allToolDetails = orphanedResponses.flatMap((r) => r.toolDetails);
586
594
  const orphanedTimeKeys = new Set(
587
595
  orphanedResponses.map((r) => r.timestamp.slice(0, 16))
@@ -593,15 +601,18 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
593
601
  (k) => progressEvents.get(k) || []
594
602
  );
595
603
  interactions.push({
596
- timestamp: orphanedResponses[0].timestamp,
597
- user: "",
604
+ timestamp: orphanedResponses.length > 0 ? orphanedResponses[0].timestamp : planEntry?.timestamp ?? "",
605
+ // Include plan content for compact detection (UUID extraction)
606
+ user: planEntry?.content || "",
598
607
  thinking: orphanedResponses.filter((r) => r.thinking).map((r) => r.thinking).join("\n"),
599
608
  assistant: orphanedResponses.filter((r) => r.text).map((r) => r.text).join("\n"),
600
- isCompactSummary: false,
609
+ isCompactSummary: !!planEntry,
601
610
  isContinuation: true,
602
611
  toolsUsed: [...new Set(allToolDetails.map((t) => t.name))],
603
612
  toolDetails: allToolDetails,
604
- inPlanMode: isInPlanMode(orphanedResponses[0].timestamp) || void 0,
613
+ inPlanMode: isInPlanMode(
614
+ orphanedResponses[0]?.timestamp ?? planEntry?.timestamp ?? ""
615
+ ) || void 0,
605
616
  toolResults: allToolResults.length > 0 ? allToolResults : void 0,
606
617
  progressEvents: allProgressEvents.length > 0 ? allProgressEvents : void 0
607
618
  });
@@ -647,6 +658,50 @@ var IGNORED_PREFIXES = [
647
658
  ".claude/"
648
659
  ];
649
660
  var IGNORED_FILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"];
661
+ function ensureCompactSessionLink(projectPath, currentClaudeSessionId) {
662
+ const mnemeDir = path4.join(projectPath, ".mneme");
663
+ const pendingFile = path4.join(mnemeDir, ".pending-compact.json");
664
+ if (!fs5.existsSync(pendingFile)) return;
665
+ const sessionLinksDir = path4.join(mnemeDir, "session-links");
666
+ const linkFile = path4.join(sessionLinksDir, `${currentClaudeSessionId}.json`);
667
+ if (fs5.existsSync(linkFile)) return;
668
+ try {
669
+ const pending = JSON.parse(fs5.readFileSync(pendingFile, "utf8"));
670
+ const oldClaudeSessionId = pending.claudeSessionId || "";
671
+ const timestamp = pending.timestamp || "";
672
+ if (timestamp) {
673
+ const age = Date.now() - new Date(timestamp).getTime();
674
+ if (age > 5 * 60 * 1e3) return;
675
+ }
676
+ if (!oldClaudeSessionId || oldClaudeSessionId === currentClaudeSessionId) {
677
+ return;
678
+ }
679
+ const masterSessionId = resolveMnemeSessionId(
680
+ projectPath,
681
+ oldClaudeSessionId
682
+ );
683
+ if (!fs5.existsSync(sessionLinksDir)) {
684
+ fs5.mkdirSync(sessionLinksDir, { recursive: true });
685
+ }
686
+ fs5.writeFileSync(
687
+ linkFile,
688
+ JSON.stringify(
689
+ {
690
+ masterSessionId,
691
+ claudeSessionId: currentClaudeSessionId,
692
+ linkedAt: (/* @__PURE__ */ new Date()).toISOString()
693
+ },
694
+ null,
695
+ 2
696
+ )
697
+ );
698
+ console.error(
699
+ `[mneme] Save: compact continuation linked: ${currentClaudeSessionId} \u2192 ${masterSessionId}`
700
+ );
701
+ } catch (e) {
702
+ console.error(`[mneme] Error in ensureCompactSessionLink: ${e}`);
703
+ }
704
+ }
650
705
  function detectCompactContinuation(interactions, projectPath) {
651
706
  const compactInteraction = interactions.find((i) => i.isCompactSummary);
652
707
  if (!compactInteraction?.user) return null;
@@ -753,6 +808,7 @@ async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
753
808
  message: `Transcript not found: ${transcriptPath}`
754
809
  };
755
810
  }
811
+ ensureCompactSessionLink(projectPath, claudeSessionId);
756
812
  const dbPath = path4.join(projectPath, ".mneme", "local.db");
757
813
  const db = initDatabase(dbPath);
758
814
  let mnemeSessionId = resolveMnemeSessionId(projectPath, claudeSessionId);
@@ -522,9 +522,16 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
522
522
  progressEvents.get(key)?.push(event);
523
523
  }
524
524
  }
525
+ const planContentEntries = entries.filter(
526
+ (e) => e.type === "user" && e.message?.role === "user" && !!e.planContent && typeof e.message?.content === "string"
527
+ ).map((e) => ({
528
+ timestamp: e.timestamp,
529
+ content: e.message?.content
530
+ }));
525
531
  const userMessages = entries.filter((e) => {
526
532
  if (e.type !== "user" || e.message?.role !== "user") return false;
527
533
  if (e.isMeta === true) return false;
534
+ if (e.planContent) return false;
528
535
  const content = e.message?.content;
529
536
  if (typeof content !== "string") return false;
530
537
  if (content.startsWith("<local-command-stdout>")) return false;
@@ -535,7 +542,7 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
535
542
  return {
536
543
  timestamp: e.timestamp,
537
544
  content,
538
- isCompactSummary: e.isCompactSummary || !!e.planContent || false,
545
+ isCompactSummary: e.isCompactSummary || false,
539
546
  slashCommand: extractSlashCommand(content)
540
547
  };
541
548
  });
@@ -585,7 +592,8 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
585
592
  const orphanedResponses = assistantMessages.filter(
586
593
  (a) => a.timestamp < firstUserTs
587
594
  );
588
- if (orphanedResponses.length > 0) {
595
+ const planEntry = planContentEntries.find((p) => p.timestamp <= firstUserTs);
596
+ if (orphanedResponses.length > 0 || planEntry) {
589
597
  const allToolDetails = orphanedResponses.flatMap((r) => r.toolDetails);
590
598
  const orphanedTimeKeys = new Set(
591
599
  orphanedResponses.map((r) => r.timestamp.slice(0, 16))
@@ -597,15 +605,18 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
597
605
  (k) => progressEvents.get(k) || []
598
606
  );
599
607
  interactions.push({
600
- timestamp: orphanedResponses[0].timestamp,
601
- user: "",
608
+ timestamp: orphanedResponses.length > 0 ? orphanedResponses[0].timestamp : planEntry?.timestamp ?? "",
609
+ // Include plan content for compact detection (UUID extraction)
610
+ user: planEntry?.content || "",
602
611
  thinking: orphanedResponses.filter((r) => r.thinking).map((r) => r.thinking).join("\n"),
603
612
  assistant: orphanedResponses.filter((r) => r.text).map((r) => r.text).join("\n"),
604
- isCompactSummary: false,
613
+ isCompactSummary: !!planEntry,
605
614
  isContinuation: true,
606
615
  toolsUsed: [...new Set(allToolDetails.map((t) => t.name))],
607
616
  toolDetails: allToolDetails,
608
- inPlanMode: isInPlanMode(orphanedResponses[0].timestamp) || void 0,
617
+ inPlanMode: isInPlanMode(
618
+ orphanedResponses[0]?.timestamp ?? planEntry?.timestamp ?? ""
619
+ ) || void 0,
609
620
  toolResults: allToolResults.length > 0 ? allToolResults : void 0,
610
621
  progressEvents: allProgressEvents.length > 0 ? allProgressEvents : void 0
611
622
  });
@@ -651,6 +662,50 @@ var IGNORED_PREFIXES = [
651
662
  ".claude/"
652
663
  ];
653
664
  var IGNORED_FILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"];
665
+ function ensureCompactSessionLink(projectPath, currentClaudeSessionId) {
666
+ const mnemeDir = path4.join(projectPath, ".mneme");
667
+ const pendingFile = path4.join(mnemeDir, ".pending-compact.json");
668
+ if (!fs5.existsSync(pendingFile)) return;
669
+ const sessionLinksDir = path4.join(mnemeDir, "session-links");
670
+ const linkFile = path4.join(sessionLinksDir, `${currentClaudeSessionId}.json`);
671
+ if (fs5.existsSync(linkFile)) return;
672
+ try {
673
+ const pending = JSON.parse(fs5.readFileSync(pendingFile, "utf8"));
674
+ const oldClaudeSessionId = pending.claudeSessionId || "";
675
+ const timestamp = pending.timestamp || "";
676
+ if (timestamp) {
677
+ const age = Date.now() - new Date(timestamp).getTime();
678
+ if (age > 5 * 60 * 1e3) return;
679
+ }
680
+ if (!oldClaudeSessionId || oldClaudeSessionId === currentClaudeSessionId) {
681
+ return;
682
+ }
683
+ const masterSessionId = resolveMnemeSessionId(
684
+ projectPath,
685
+ oldClaudeSessionId
686
+ );
687
+ if (!fs5.existsSync(sessionLinksDir)) {
688
+ fs5.mkdirSync(sessionLinksDir, { recursive: true });
689
+ }
690
+ fs5.writeFileSync(
691
+ linkFile,
692
+ JSON.stringify(
693
+ {
694
+ masterSessionId,
695
+ claudeSessionId: currentClaudeSessionId,
696
+ linkedAt: (/* @__PURE__ */ new Date()).toISOString()
697
+ },
698
+ null,
699
+ 2
700
+ )
701
+ );
702
+ console.error(
703
+ `[mneme] Save: compact continuation linked: ${currentClaudeSessionId} \u2192 ${masterSessionId}`
704
+ );
705
+ } catch (e) {
706
+ console.error(`[mneme] Error in ensureCompactSessionLink: ${e}`);
707
+ }
708
+ }
654
709
  function detectCompactContinuation(interactions, projectPath) {
655
710
  const compactInteraction = interactions.find((i) => i.isCompactSummary);
656
711
  if (!compactInteraction?.user) return null;
@@ -757,6 +812,7 @@ async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
757
812
  message: `Transcript not found: ${transcriptPath}`
758
813
  };
759
814
  }
815
+ ensureCompactSessionLink(projectPath, claudeSessionId);
760
816
  const dbPath = path4.join(projectPath, ".mneme", "local.db");
761
817
  const db = initDatabase(dbPath);
762
818
  let mnemeSessionId = resolveMnemeSessionId(projectPath, claudeSessionId);