claude-memory-layer 1.0.11 → 1.0.12

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 (99) hide show
  1. package/AGENTS.md +60 -0
  2. package/README.md +166 -2
  3. package/bootstrap-kb/decisions/decisions.md +244 -0
  4. package/bootstrap-kb/glossary/glossary.md +46 -0
  5. package/bootstrap-kb/modules/.claude-plugin.md +22 -0
  6. package/bootstrap-kb/modules/agents.md.md +15 -0
  7. package/bootstrap-kb/modules/claude.md.md +15 -0
  8. package/bootstrap-kb/modules/context.md.md +15 -0
  9. package/bootstrap-kb/modules/docs.md +18 -0
  10. package/bootstrap-kb/modules/handoff.md.md +15 -0
  11. package/bootstrap-kb/modules/package-lock.json.md +15 -0
  12. package/bootstrap-kb/modules/package.json.md +15 -0
  13. package/bootstrap-kb/modules/plan.md.md +15 -0
  14. package/bootstrap-kb/modules/readme.md.md +15 -0
  15. package/bootstrap-kb/modules/scripts.md +26 -0
  16. package/bootstrap-kb/modules/spec.md.md +15 -0
  17. package/bootstrap-kb/modules/specs.md +20 -0
  18. package/bootstrap-kb/modules/src.md +51 -0
  19. package/bootstrap-kb/modules/tests.md +42 -0
  20. package/bootstrap-kb/modules/tsconfig.json.md +15 -0
  21. package/bootstrap-kb/modules/vitest.config.ts.md +15 -0
  22. package/bootstrap-kb/overview/overview.md +40 -0
  23. package/bootstrap-kb/sources/manifest.json +950 -0
  24. package/bootstrap-kb/sources/manifest.md +227 -0
  25. package/bootstrap-kb/timeline/timeline.md +57 -0
  26. package/d.sh +3 -0
  27. package/deploy.sh +3 -0
  28. package/dist/cli/index.js +2389 -286
  29. package/dist/cli/index.js.map +4 -4
  30. package/dist/core/index.js +1017 -132
  31. package/dist/core/index.js.map +4 -4
  32. package/dist/hooks/post-tool-use.js +1347 -202
  33. package/dist/hooks/post-tool-use.js.map +4 -4
  34. package/dist/hooks/session-end.js +1339 -194
  35. package/dist/hooks/session-end.js.map +4 -4
  36. package/dist/hooks/session-start.js +1343 -198
  37. package/dist/hooks/session-start.js.map +4 -4
  38. package/dist/hooks/stop.js +1351 -206
  39. package/dist/hooks/stop.js.map +4 -4
  40. package/dist/hooks/user-prompt-submit.js +1347 -202
  41. package/dist/hooks/user-prompt-submit.js.map +4 -4
  42. package/dist/server/api/index.js +1436 -211
  43. package/dist/server/api/index.js.map +4 -4
  44. package/dist/server/index.js +1445 -220
  45. package/dist/server/index.js.map +4 -4
  46. package/dist/services/memory-service.js +1345 -199
  47. package/dist/services/memory-service.js.map +4 -4
  48. package/dist/ui/app.js +69 -2
  49. package/dist/ui/index.html +8 -0
  50. package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +271 -0
  51. package/docs/MEMU_ADOPTION.md +40 -0
  52. package/memory/.claude-plugin/commands/2026-02-25.md +263 -0
  53. package/memory/_index.md +405 -0
  54. package/memory/default/uncategorized/2026-02-25.md +4839 -0
  55. package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +142 -0
  56. package/memory/specs/citations-system/2026-02-25.md +1121 -0
  57. package/memory/specs/endless-mode/2026-02-25.md +1392 -0
  58. package/memory/specs/entity-edge-model/2026-02-25.md +1263 -0
  59. package/memory/specs/evidence-aligner-v2/2026-02-25.md +1028 -0
  60. package/memory/specs/mcp-desktop-integration/2026-02-25.md +1334 -0
  61. package/memory/specs/post-tool-use-hook/2026-02-25.md +1164 -0
  62. package/memory/specs/private-tags/2026-02-25.md +1057 -0
  63. package/memory/specs/progressive-disclosure/2026-02-25.md +1436 -0
  64. package/memory/specs/task-entity-system/2026-02-25.md +924 -0
  65. package/memory/specs/vector-outbox-v2/2026-02-25.md +1510 -0
  66. package/memory/specs/web-viewer-ui/2026-02-25.md +1709 -0
  67. package/package.json +2 -1
  68. package/scripts/build.ts +6 -0
  69. package/src/cli/index.ts +281 -2
  70. package/src/core/consolidated-store.ts +63 -1
  71. package/src/core/consolidation-worker.ts +115 -6
  72. package/src/core/event-store.ts +14 -0
  73. package/src/core/index.ts +1 -0
  74. package/src/core/ingest-interceptor.ts +80 -0
  75. package/src/core/markdown-mirror.ts +70 -0
  76. package/src/core/md-mirror.ts +92 -0
  77. package/src/core/mongo-sync-config.ts +165 -0
  78. package/src/core/mongo-sync-worker.ts +381 -0
  79. package/src/core/retriever.ts +540 -150
  80. package/src/core/sqlite-event-store.ts +350 -1
  81. package/src/core/tag-taxonomy.ts +51 -0
  82. package/src/core/types.ts +28 -0
  83. package/src/server/api/health.ts +53 -0
  84. package/src/server/api/index.ts +3 -1
  85. package/src/server/api/stats.ts +46 -1
  86. package/src/services/bootstrap-organizer.ts +443 -0
  87. package/src/services/codex-session-history-importer.ts +474 -0
  88. package/src/services/memory-service.ts +373 -68
  89. package/src/ui/app.js +69 -2
  90. package/src/ui/index.html +8 -0
  91. package/tests/bootstrap-organizer.test.ts +111 -0
  92. package/tests/consolidation-worker.test.ts +75 -0
  93. package/tests/ingest-interceptor.test.ts +38 -0
  94. package/tests/markdown-mirror.test.ts +85 -0
  95. package/tests/md-mirror.test.ts +50 -0
  96. package/tests/retriever-fallback-chain.test.ts +223 -0
  97. package/tests/retriever-strategy-scope.test.ts +97 -0
  98. package/tests/retriever.memu-adoption.test.ts +122 -0
  99. package/tests/sqlite-event-store-replication.test.ts +92 -0
@@ -0,0 +1,122 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Retriever } from '../src/core/retriever.js';
3
+ import type { MemoryEvent, MatchResult } from '../src/core/types.js';
4
+ import type { SearchResult } from '../src/core/vector-store.js';
5
+
6
+ class FakeEventStore {
7
+ constructor(private readonly events: Record<string, MemoryEvent>) {}
8
+
9
+ async getEvent(id: string): Promise<MemoryEvent | null> {
10
+ return this.events[id] || null;
11
+ }
12
+
13
+ async getSessionEvents(sessionId: string): Promise<MemoryEvent[]> {
14
+ return Object.values(this.events).filter((e) => e.sessionId === sessionId);
15
+ }
16
+
17
+ async getRecentEvents(): Promise<MemoryEvent[]> {
18
+ return Object.values(this.events);
19
+ }
20
+
21
+ async keywordSearch(query: string): Promise<Array<{ event: MemoryEvent; rank: number }>> {
22
+ const lowered = query.toLowerCase();
23
+ const matches = Object.values(this.events)
24
+ .filter((event) => event.content.toLowerCase().includes(lowered))
25
+ .map((event, index) => ({ event, rank: -(index + 1) }));
26
+ return matches;
27
+ }
28
+ }
29
+
30
+ class FakeVectorStore {
31
+ constructor(private readonly results: SearchResult[]) {}
32
+
33
+ async search(): Promise<SearchResult[]> {
34
+ return this.results;
35
+ }
36
+ }
37
+
38
+ class FakeEmbedder {
39
+ async embed(): Promise<{ vector: number[] }> {
40
+ return { vector: [0.1, 0.2, 0.3] };
41
+ }
42
+ }
43
+
44
+ class FakeMatcher {
45
+ matchSearchResults(results: SearchResult[]): MatchResult {
46
+ if (results.length === 0) {
47
+ return { match: null, confidence: 'none' };
48
+ }
49
+
50
+ const top = results[0];
51
+ return {
52
+ match: {
53
+ event: {
54
+ id: top.eventId,
55
+ eventType: top.eventType as MemoryEvent['eventType'],
56
+ sessionId: top.sessionId,
57
+ timestamp: new Date(top.timestamp),
58
+ content: top.content,
59
+ canonicalKey: top.eventId,
60
+ dedupeKey: top.eventId,
61
+ metadata: {}
62
+ },
63
+ score: top.score
64
+ },
65
+ confidence: 'suggested'
66
+ };
67
+ }
68
+ }
69
+
70
+ function makeEvent(id: string, content: string, metadata?: Record<string, unknown>): MemoryEvent {
71
+ return {
72
+ id,
73
+ eventType: 'user_prompt',
74
+ sessionId: 's1',
75
+ timestamp: new Date('2026-01-01T00:00:00.000Z'),
76
+ content,
77
+ canonicalKey: id,
78
+ dedupeKey: id,
79
+ metadata
80
+ };
81
+ }
82
+
83
+ describe('Retriever memU-inspired enhancements', () => {
84
+ it('applies metadata scope filter with hierarchical key path', async () => {
85
+ const e1 = makeEvent('e1', 'first memory', { scope: { project: { id: 'alpha' } } });
86
+ const e2 = makeEvent('e2', 'second memory', { scope: { project: { id: 'beta' } } });
87
+
88
+ const retriever = new Retriever(
89
+ new FakeEventStore({ e1, e2 }) as any,
90
+ new FakeVectorStore([
91
+ { id: '1', eventId: 'e1', content: e1.content, score: 0.9, sessionId: 's1', eventType: e1.eventType, timestamp: e1.timestamp.toISOString() },
92
+ { id: '2', eventId: 'e2', content: e2.content, score: 0.89, sessionId: 's1', eventType: e2.eventType, timestamp: e2.timestamp.toISOString() }
93
+ ]) as any,
94
+ new FakeEmbedder() as any,
95
+ new FakeMatcher() as any
96
+ );
97
+
98
+ const result = await retriever.retrieve('memory', {
99
+ scope: { metadata: { 'scope.project.id': 'alpha' } }
100
+ });
101
+
102
+ expect(result.memories).toHaveLength(1);
103
+ expect(result.memories[0].event.id).toBe('e1');
104
+ });
105
+
106
+ it('uses fast strategy keyword retrieval when requested', async () => {
107
+ const e1 = makeEvent('e1', 'fix deployment issue with nginx');
108
+ const e2 = makeEvent('e2', 'random unrelated text');
109
+
110
+ const retriever = new Retriever(
111
+ new FakeEventStore({ e1, e2 }) as any,
112
+ new FakeVectorStore([]) as any,
113
+ new FakeEmbedder() as any,
114
+ new FakeMatcher() as any
115
+ );
116
+
117
+ const result = await retriever.retrieve('deployment', { strategy: 'fast', topK: 5 });
118
+
119
+ expect(result.memories.length).toBeGreaterThan(0);
120
+ expect(result.memories[0].event.id).toBe('e1');
121
+ });
122
+ });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Tests for SQLiteEventStore replication helpers used by Mongo sync.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import * as os from 'os';
9
+
10
+ import { SQLiteEventStore } from '../src/core/sqlite-event-store.js';
11
+
12
+ describe('SQLiteEventStore replication helpers', () => {
13
+ let tempDir: string;
14
+ let storeA: SQLiteEventStore;
15
+ let storeB: SQLiteEventStore;
16
+
17
+ beforeEach(async () => {
18
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-memory-layer-test-'));
19
+ storeA = new SQLiteEventStore(path.join(tempDir, 'a.sqlite'));
20
+ storeB = new SQLiteEventStore(path.join(tempDir, 'b.sqlite'));
21
+ await storeA.initialize();
22
+ await storeB.initialize();
23
+ });
24
+
25
+ afterEach(() => {
26
+ try { storeA.close(); } catch {}
27
+ try { storeB.close(); } catch {}
28
+ fs.rmSync(tempDir, { recursive: true, force: true });
29
+ });
30
+
31
+ it('getEventsSinceRowid returns incremental batches in insertion order', async () => {
32
+ const sessionId = 'session-1';
33
+
34
+ await storeA.append({ eventType: 'user_prompt', sessionId, timestamp: new Date(), content: 'a' });
35
+ await storeA.append({ eventType: 'user_prompt', sessionId, timestamp: new Date(), content: 'b' });
36
+ await storeA.append({ eventType: 'user_prompt', sessionId, timestamp: new Date(), content: 'c' });
37
+
38
+ const batch1 = await storeA.getEventsSinceRowid(0, 10);
39
+ expect(batch1).toHaveLength(3);
40
+ expect(batch1.map(x => x.event.content)).toEqual(['a', 'b', 'c']);
41
+
42
+ // rowid should be strictly increasing
43
+ expect(batch1[0].rowid).toBeLessThan(batch1[1].rowid);
44
+ expect(batch1[1].rowid).toBeLessThan(batch1[2].rowid);
45
+
46
+ const lastRowid = batch1[2].rowid;
47
+
48
+ await storeA.append({ eventType: 'user_prompt', sessionId, timestamp: new Date(), content: 'd' });
49
+
50
+ const batch2 = await storeA.getEventsSinceRowid(lastRowid, 10);
51
+ expect(batch2).toHaveLength(1);
52
+ expect(batch2[0].event.content).toBe('d');
53
+ });
54
+
55
+ it('importEvents preserves stable IDs and is idempotent via dedupeKey', async () => {
56
+ const sessionId = 'session-2';
57
+ const appendRes = await storeA.append({
58
+ eventType: 'user_prompt',
59
+ sessionId,
60
+ timestamp: new Date(),
61
+ content: 'hello world'
62
+ });
63
+
64
+ expect(appendRes.success).toBe(true);
65
+ const sourceEvent = await storeA.getEvent(appendRes.eventId!);
66
+ expect(sourceEvent).not.toBeNull();
67
+
68
+ const imported1 = await storeB.importEvents([sourceEvent!]);
69
+ expect(imported1.inserted).toBe(1);
70
+ expect(imported1.skipped).toBe(0);
71
+
72
+ const importedEvent = await storeB.getEvent(sourceEvent!.id);
73
+ expect(importedEvent?.content).toBe('hello world');
74
+
75
+ // Importing again should be a no-op
76
+ const imported2 = await storeB.importEvents([sourceEvent!]);
77
+ expect(imported2.inserted).toBe(0);
78
+ expect(imported2.skipped).toBe(1);
79
+
80
+ // append() should treat it as duplicate due to event_dedup entry
81
+ const dup = await storeB.append({
82
+ eventType: 'user_prompt',
83
+ sessionId,
84
+ timestamp: new Date(),
85
+ content: 'hello world'
86
+ });
87
+ expect(dup.success).toBe(true);
88
+ expect(dup.isDuplicate).toBe(true);
89
+ expect(dup.eventId).toBe(sourceEvent!.id);
90
+ });
91
+ });
92
+