claude-memory-layer 1.0.7 → 1.0.8

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 (38) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.history/package_20260201192048.json +47 -0
  3. package/dist/cli/index.js +569 -39
  4. package/dist/cli/index.js.map +4 -4
  5. package/dist/core/index.js +192 -5
  6. package/dist/core/index.js.map +4 -4
  7. package/dist/hooks/session-end.js +262 -18
  8. package/dist/hooks/session-end.js.map +4 -4
  9. package/dist/hooks/session-start.js +262 -18
  10. package/dist/hooks/session-start.js.map +4 -4
  11. package/dist/hooks/stop.js +262 -18
  12. package/dist/hooks/stop.js.map +4 -4
  13. package/dist/hooks/user-prompt-submit.js +262 -18
  14. package/dist/hooks/user-prompt-submit.js.map +4 -4
  15. package/dist/server/api/index.js +404 -39
  16. package/dist/server/api/index.js.map +4 -4
  17. package/dist/server/index.js +413 -46
  18. package/dist/server/index.js.map +4 -4
  19. package/dist/services/memory-service.js +269 -18
  20. package/dist/services/memory-service.js.map +4 -4
  21. package/dist/ui/index.html +495 -15
  22. package/package.json +2 -1
  23. package/scripts/build.ts +3 -2
  24. package/src/cli/index.ts +226 -0
  25. package/src/core/db-wrapper.ts +8 -1
  26. package/src/core/event-store.ts +52 -3
  27. package/src/core/graduation-worker.ts +171 -0
  28. package/src/core/graduation.ts +15 -2
  29. package/src/core/index.ts +1 -0
  30. package/src/core/retriever.ts +18 -0
  31. package/src/server/api/citations.ts +7 -3
  32. package/src/server/api/events.ts +7 -3
  33. package/src/server/api/search.ts +7 -3
  34. package/src/server/api/sessions.ts +7 -3
  35. package/src/server/api/stats.ts +129 -12
  36. package/src/server/index.ts +18 -9
  37. package/src/services/memory-service.ts +107 -19
  38. package/src/ui/index.html +495 -15
package/src/cli/index.ts CHANGED
@@ -6,6 +6,9 @@
6
6
 
7
7
  import { Command } from 'commander';
8
8
  import { exec } from 'child_process';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import * as os from 'os';
9
12
  import {
10
13
  getDefaultMemoryService,
11
14
  getMemoryServiceForProject
@@ -13,6 +16,92 @@ import {
13
16
  import { createSessionHistoryImporter } from '../services/session-history-importer.js';
14
17
  import { startServer, stopServer, isServerRunning } from '../server/index.js';
15
18
 
19
+ // ============================================================
20
+ // Hook Installation Utilities
21
+ // ============================================================
22
+
23
+ const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
24
+
25
+ interface ClaudeSettings {
26
+ hooks?: {
27
+ UserPromptSubmit?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
28
+ PostToolUse?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
29
+ SessionStart?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
30
+ Stop?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
31
+ };
32
+ [key: string]: unknown;
33
+ }
34
+
35
+ function getPluginPath(): string {
36
+ // Try to find the dist directory
37
+ const possiblePaths = [
38
+ path.join(__dirname, '..'), // When running from dist/cli
39
+ path.join(__dirname, '../..', 'dist'), // When running from src
40
+ path.join(process.cwd(), 'dist'), // Current working directory
41
+ ];
42
+
43
+ for (const p of possiblePaths) {
44
+ const hooksPath = path.join(p, 'hooks', 'user-prompt-submit.js');
45
+ if (fs.existsSync(hooksPath)) {
46
+ return p;
47
+ }
48
+ }
49
+
50
+ // Fallback to npm global installation path
51
+ return path.join(os.homedir(), '.npm-global', 'lib', 'node_modules', 'claude-memory-layer', 'dist');
52
+ }
53
+
54
+ function loadClaudeSettings(): ClaudeSettings {
55
+ try {
56
+ if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
57
+ const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf-8');
58
+ return JSON.parse(content);
59
+ }
60
+ } catch (error) {
61
+ console.error('Warning: Could not read existing settings:', error);
62
+ }
63
+ return {};
64
+ }
65
+
66
+ function saveClaudeSettings(settings: ClaudeSettings): void {
67
+ const dir = path.dirname(CLAUDE_SETTINGS_PATH);
68
+ if (!fs.existsSync(dir)) {
69
+ fs.mkdirSync(dir, { recursive: true });
70
+ }
71
+
72
+ // Atomic write
73
+ const tempPath = CLAUDE_SETTINGS_PATH + '.tmp';
74
+ fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2));
75
+ fs.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
76
+ }
77
+
78
+ function getHooksConfig(pluginPath: string): ClaudeSettings['hooks'] {
79
+ return {
80
+ UserPromptSubmit: [
81
+ {
82
+ matcher: '',
83
+ hooks: [
84
+ {
85
+ type: 'command',
86
+ command: `node ${path.join(pluginPath, 'hooks', 'user-prompt-submit.js')}`
87
+ }
88
+ ]
89
+ }
90
+ ],
91
+ PostToolUse: [
92
+ {
93
+ matcher: '',
94
+ hooks: [
95
+ {
96
+ type: 'command',
97
+ command: `node ${path.join(pluginPath, 'hooks', 'post-tool-use.js')}`
98
+ }
99
+ ]
100
+ }
101
+ ]
102
+ };
103
+ }
104
+
16
105
  const program = new Command();
17
106
 
18
107
  program
@@ -20,6 +109,143 @@ program
20
109
  .description('Claude Code Memory Plugin CLI')
21
110
  .version('1.0.0');
22
111
 
112
+ // ============================================================
113
+ // Install / Uninstall Commands
114
+ // ============================================================
115
+
116
+ /**
117
+ * Install command - register hooks with Claude Code
118
+ */
119
+ program
120
+ .command('install')
121
+ .description('Install hooks into Claude Code settings')
122
+ .option('--path <path>', 'Custom plugin path (defaults to auto-detect)')
123
+ .action(async (options) => {
124
+ try {
125
+ const pluginPath = options.path || getPluginPath();
126
+
127
+ // Verify hooks exist
128
+ const userPromptHook = path.join(pluginPath, 'hooks', 'user-prompt-submit.js');
129
+ if (!fs.existsSync(userPromptHook)) {
130
+ console.error(`\n❌ Hook files not found at: ${pluginPath}`);
131
+ console.error(' Make sure you have built the plugin with "npm run build"');
132
+ process.exit(1);
133
+ }
134
+
135
+ // Load existing settings
136
+ const settings = loadClaudeSettings();
137
+
138
+ // Add hooks (merge with existing)
139
+ const newHooks = getHooksConfig(pluginPath);
140
+ settings.hooks = {
141
+ ...settings.hooks,
142
+ ...newHooks
143
+ };
144
+
145
+ // Save settings
146
+ saveClaudeSettings(settings);
147
+
148
+ console.log('\n✅ Claude Memory Layer installed!\n');
149
+ console.log('Hooks registered:');
150
+ console.log(' - UserPromptSubmit: Memory retrieval on user input');
151
+ console.log(' - PostToolUse: Store tool observations\n');
152
+ console.log('Plugin path:', pluginPath);
153
+ console.log('\n⚠️ Restart Claude Code for changes to take effect.\n');
154
+ console.log('Commands:');
155
+ console.log(' claude-memory-layer dashboard - Open web dashboard');
156
+ console.log(' claude-memory-layer search - Search memories');
157
+ console.log(' claude-memory-layer stats - View statistics');
158
+ console.log(' claude-memory-layer uninstall - Remove hooks\n');
159
+ } catch (error) {
160
+ console.error('Install failed:', error);
161
+ process.exit(1);
162
+ }
163
+ });
164
+
165
+ /**
166
+ * Uninstall command - remove hooks from Claude Code
167
+ */
168
+ program
169
+ .command('uninstall')
170
+ .description('Remove hooks from Claude Code settings')
171
+ .action(async () => {
172
+ try {
173
+ // Load existing settings
174
+ const settings = loadClaudeSettings();
175
+
176
+ if (!settings.hooks) {
177
+ console.log('\n📋 No hooks installed.\n');
178
+ return;
179
+ }
180
+
181
+ // Remove our hooks
182
+ delete settings.hooks.UserPromptSubmit;
183
+ delete settings.hooks.PostToolUse;
184
+
185
+ // Clean up empty hooks object
186
+ if (Object.keys(settings.hooks).length === 0) {
187
+ delete settings.hooks;
188
+ }
189
+
190
+ // Save settings
191
+ saveClaudeSettings(settings);
192
+
193
+ console.log('\n✅ Claude Memory Layer uninstalled!\n');
194
+ console.log('Hooks removed from Claude Code settings.');
195
+ console.log('Your memory data is preserved and can be accessed with:');
196
+ console.log(' claude-memory-layer dashboard\n');
197
+ console.log('⚠️ Restart Claude Code for changes to take effect.\n');
198
+ } catch (error) {
199
+ console.error('Uninstall failed:', error);
200
+ process.exit(1);
201
+ }
202
+ });
203
+
204
+ /**
205
+ * Status command - check installation status
206
+ */
207
+ program
208
+ .command('status')
209
+ .description('Check plugin installation status')
210
+ .action(async () => {
211
+ try {
212
+ const settings = loadClaudeSettings();
213
+ const pluginPath = getPluginPath();
214
+
215
+ console.log('\n🧠 Claude Memory Layer Status\n');
216
+
217
+ // Check hooks
218
+ const hasUserPromptHook = settings.hooks?.UserPromptSubmit?.some(h =>
219
+ h.hooks?.some(hook => hook.command?.includes('user-prompt-submit'))
220
+ );
221
+ const hasPostToolHook = settings.hooks?.PostToolUse?.some(h =>
222
+ h.hooks?.some(hook => hook.command?.includes('post-tool-use'))
223
+ );
224
+
225
+ console.log('Hooks:');
226
+ console.log(` UserPromptSubmit: ${hasUserPromptHook ? '✅ Installed' : '❌ Not installed'}`);
227
+ console.log(` PostToolUse: ${hasPostToolHook ? '✅ Installed' : '❌ Not installed'}`);
228
+
229
+ // Check plugin files
230
+ const hooksExist = fs.existsSync(path.join(pluginPath, 'hooks', 'user-prompt-submit.js'));
231
+ console.log(`\nPlugin files: ${hooksExist ? '✅ Found' : '❌ Not found'}`);
232
+ console.log(` Path: ${pluginPath}`);
233
+
234
+ // Check dashboard
235
+ const dashboardRunning = await isServerRunning(37777);
236
+ console.log(`\nDashboard: ${dashboardRunning ? '✅ Running at http://localhost:37777' : '⏹️ Not running'}`);
237
+
238
+ if (!hasUserPromptHook || !hasPostToolHook) {
239
+ console.log('\n💡 Run "claude-memory-layer install" to set up hooks.\n');
240
+ } else {
241
+ console.log('\n✅ Plugin is fully installed and configured.\n');
242
+ }
243
+ } catch (error) {
244
+ console.error('Status check failed:', error);
245
+ process.exit(1);
246
+ }
247
+ });
248
+
23
249
  /**
24
250
  * Search command
25
251
  */
@@ -37,10 +37,17 @@ export function toDate(value: unknown): Date {
37
37
  return new Date(String(value));
38
38
  }
39
39
 
40
+ export interface DatabaseOptions {
41
+ readOnly?: boolean;
42
+ }
43
+
40
44
  /**
41
45
  * Creates a new DuckDB database with Promise-based API
42
46
  */
43
- export function createDatabase(path: string): Database {
47
+ export function createDatabase(path: string, options?: DatabaseOptions): Database {
48
+ if (options?.readOnly) {
49
+ return new duckdb.Database(path, { access_mode: 'READ_ONLY' });
50
+ }
44
51
  return new duckdb.Database(path);
45
52
  }
46
53
 
@@ -12,14 +12,20 @@ import {
12
12
  OutboxItem
13
13
  } from './types.js';
14
14
  import { makeCanonicalKey, makeDedupeKey } from './canonical-key.js';
15
- import { createDatabase, dbRun, dbAll, dbClose, toDate, type Database } from './db-wrapper.js';
15
+ import { createDatabase, dbRun, dbAll, dbClose, toDate, type Database, type DatabaseOptions } from './db-wrapper.js';
16
+
17
+ export interface EventStoreOptions extends DatabaseOptions {
18
+ // Additional options can be added here
19
+ }
16
20
 
17
21
  export class EventStore {
18
22
  private db: Database;
19
23
  private initialized = false;
24
+ private readonly readOnly: boolean;
20
25
 
21
- constructor(private dbPath: string) {
22
- this.db = createDatabase(dbPath);
26
+ constructor(private dbPath: string, options?: EventStoreOptions) {
27
+ this.readOnly = options?.readOnly ?? false;
28
+ this.db = createDatabase(dbPath, { readOnly: this.readOnly });
23
29
  }
24
30
 
25
31
  /**
@@ -28,6 +34,12 @@ export class EventStore {
28
34
  async initialize(): Promise<void> {
29
35
  if (this.initialized) return;
30
36
 
37
+ // In read-only mode, skip schema creation (tables already exist)
38
+ if (this.readOnly) {
39
+ this.initialized = true;
40
+ return;
41
+ }
42
+
31
43
  // L0 EventStore: Single Source of Truth (immutable, append-only)
32
44
  await dbRun(this.db, `
33
45
  CREATE TABLE IF NOT EXISTS events (
@@ -611,6 +623,43 @@ export class EventStore {
611
623
  return rows;
612
624
  }
613
625
 
626
+ /**
627
+ * Get events by memory level
628
+ */
629
+ async getEventsByLevel(level: string, options?: { limit?: number; offset?: number }): Promise<MemoryEvent[]> {
630
+ await this.initialize();
631
+
632
+ const limit = options?.limit || 50;
633
+ const offset = options?.offset || 0;
634
+
635
+ const rows = await dbAll<Record<string, unknown>>(
636
+ this.db,
637
+ `SELECT e.* FROM events e
638
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
639
+ WHERE ml.level = ?
640
+ ORDER BY e.timestamp DESC
641
+ LIMIT ? OFFSET ?`,
642
+ [level, limit, offset]
643
+ );
644
+
645
+ return rows.map(row => this.rowToEvent(row));
646
+ }
647
+
648
+ /**
649
+ * Get memory level for a specific event
650
+ */
651
+ async getEventLevel(eventId: string): Promise<string | null> {
652
+ await this.initialize();
653
+
654
+ const rows = await dbAll<{ level: string }>(
655
+ this.db,
656
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
657
+ [eventId]
658
+ );
659
+
660
+ return rows.length > 0 ? rows[0].level : null;
661
+ }
662
+
614
663
  // ============================================================
615
664
  // Endless Mode Helper Methods
616
665
  // ============================================================
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Graduation Worker
3
+ * Periodically evaluates memory events for promotion to higher levels
4
+ * L0 → L1 → L2 → L3 → L4 based on access patterns and confidence
5
+ */
6
+
7
+ import type { MemoryLevel } from './types.js';
8
+ import { EventStore } from './event-store.js';
9
+ import { GraduationPipeline } from './graduation.js';
10
+
11
+ export interface GraduationWorkerConfig {
12
+ /** How often to run graduation evaluation (ms) */
13
+ evaluationIntervalMs: number;
14
+ /** Batch size for graduation evaluation */
15
+ batchSize: number;
16
+ /** Minimum time between evaluations of the same event (ms) */
17
+ cooldownMs: number;
18
+ }
19
+
20
+ const DEFAULT_CONFIG: GraduationWorkerConfig = {
21
+ evaluationIntervalMs: 300000, // 5 minutes
22
+ batchSize: 50,
23
+ cooldownMs: 3600000 // 1 hour cooldown between evaluations
24
+ };
25
+
26
+ export class GraduationWorker {
27
+ private running = false;
28
+ private timeout: NodeJS.Timeout | null = null;
29
+ private lastEvaluated: Map<string, number> = new Map();
30
+
31
+ constructor(
32
+ private eventStore: EventStore,
33
+ private graduation: GraduationPipeline,
34
+ private config: GraduationWorkerConfig = DEFAULT_CONFIG
35
+ ) {}
36
+
37
+ /**
38
+ * Start the graduation worker
39
+ */
40
+ start(): void {
41
+ if (this.running) return;
42
+ this.running = true;
43
+ this.scheduleNext();
44
+ }
45
+
46
+ /**
47
+ * Stop the graduation worker
48
+ */
49
+ stop(): void {
50
+ this.running = false;
51
+ if (this.timeout) {
52
+ clearTimeout(this.timeout);
53
+ this.timeout = null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Check if currently running
59
+ */
60
+ isRunning(): boolean {
61
+ return this.running;
62
+ }
63
+
64
+ /**
65
+ * Force a graduation evaluation run
66
+ */
67
+ async forceRun(): Promise<GraduationRunResult> {
68
+ return await this.runGraduation();
69
+ }
70
+
71
+ /**
72
+ * Schedule the next graduation check
73
+ */
74
+ private scheduleNext(): void {
75
+ if (!this.running) return;
76
+
77
+ this.timeout = setTimeout(
78
+ () => this.run(),
79
+ this.config.evaluationIntervalMs
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Run graduation evaluation
85
+ */
86
+ private async run(): Promise<void> {
87
+ if (!this.running) return;
88
+
89
+ try {
90
+ await this.runGraduation();
91
+ } catch (error) {
92
+ console.error('Graduation error:', error);
93
+ }
94
+
95
+ this.scheduleNext();
96
+ }
97
+
98
+ /**
99
+ * Perform graduation evaluation across all levels
100
+ */
101
+ private async runGraduation(): Promise<GraduationRunResult> {
102
+ const result: GraduationRunResult = {
103
+ evaluated: 0,
104
+ graduated: 0,
105
+ byLevel: {}
106
+ };
107
+
108
+ const levels: MemoryLevel[] = ['L0', 'L1', 'L2', 'L3'];
109
+ const now = Date.now();
110
+
111
+ for (const level of levels) {
112
+ const events = await this.eventStore.getEventsByLevel(level, {
113
+ limit: this.config.batchSize
114
+ });
115
+
116
+ let levelGraduated = 0;
117
+
118
+ for (const event of events) {
119
+ // Check cooldown
120
+ const lastEval = this.lastEvaluated.get(event.id);
121
+ if (lastEval && (now - lastEval) < this.config.cooldownMs) {
122
+ continue;
123
+ }
124
+
125
+ result.evaluated++;
126
+ this.lastEvaluated.set(event.id, now);
127
+
128
+ const gradResult = await this.graduation.evaluateGraduation(event.id, level);
129
+
130
+ if (gradResult.success) {
131
+ result.graduated++;
132
+ levelGraduated++;
133
+ }
134
+ }
135
+
136
+ if (levelGraduated > 0) {
137
+ result.byLevel[level] = levelGraduated;
138
+ }
139
+ }
140
+
141
+ // Clean up old cooldown entries (keep last 1000)
142
+ if (this.lastEvaluated.size > 1000) {
143
+ const entries = Array.from(this.lastEvaluated.entries());
144
+ entries.sort((a, b) => b[1] - a[1]);
145
+ this.lastEvaluated = new Map(entries.slice(0, 1000));
146
+ }
147
+
148
+ return result;
149
+ }
150
+ }
151
+
152
+ export interface GraduationRunResult {
153
+ evaluated: number;
154
+ graduated: number;
155
+ byLevel: Record<string, number>;
156
+ }
157
+
158
+ /**
159
+ * Create a Graduation Worker instance
160
+ */
161
+ export function createGraduationWorker(
162
+ eventStore: EventStore,
163
+ graduation: GraduationPipeline,
164
+ config?: Partial<GraduationWorkerConfig>
165
+ ): GraduationWorker {
166
+ return new GraduationWorker(
167
+ eventStore,
168
+ graduation,
169
+ { ...DEFAULT_CONFIG, ...config }
170
+ );
171
+ }
@@ -84,18 +84,31 @@ export class GraduationPipeline {
84
84
  };
85
85
  }
86
86
 
87
+ // Track which sessions have accessed each event
88
+ private readonly sessionAccesses: Map<string, Set<string>> = new Map();
89
+
87
90
  /**
88
91
  * Record an access to an event (used for graduation scoring)
89
92
  */
90
93
  recordAccess(eventId: string, fromSessionId: string, confidence: number = 1.0): void {
91
94
  const existing = this.metrics.get(eventId);
92
95
 
96
+ // Track sessions that have accessed this event
97
+ if (!this.sessionAccesses.has(eventId)) {
98
+ this.sessionAccesses.set(eventId, new Set());
99
+ }
100
+ const sessions = this.sessionAccesses.get(eventId)!;
101
+ const isNewSession = !sessions.has(fromSessionId);
102
+ sessions.add(fromSessionId);
103
+
93
104
  if (existing) {
94
105
  existing.accessCount++;
95
106
  existing.lastAccessed = new Date();
96
107
  existing.confidence = Math.max(existing.confidence, confidence);
97
- // Track cross-session references
98
- // This would need more sophisticated tracking in production
108
+ // Update cross-session references count
109
+ if (isNewSession && sessions.size > 1) {
110
+ existing.crossSessionRefs = sessions.size - 1;
111
+ }
99
112
  } else {
100
113
  this.metrics.set(eventId, {
101
114
  eventId,
package/src/core/index.ts CHANGED
@@ -27,6 +27,7 @@ export * from './evidence-aligner.js';
27
27
  // Retrieval & Graduation
28
28
  export * from './retriever.js';
29
29
  export * from './graduation.js';
30
+ export * from './graduation-worker.js';
30
31
 
31
32
  // Task Entity System
32
33
  export * from './task/index.js';
@@ -9,6 +9,7 @@ import { Embedder } from './embedder.js';
9
9
  import { Matcher } from './matcher.js';
10
10
  import { SharedStore } from './shared-store.js';
11
11
  import { SharedVectorStore } from './shared-vector-store.js';
12
+ import { GraduationPipeline } from './graduation.js';
12
13
  import type { MemoryEvent, MatchResult, Config, SharedTroubleshootingEntry } from './types.js';
13
14
 
14
15
  export interface RetrievalOptions {
@@ -60,6 +61,7 @@ export class Retriever {
60
61
  private readonly matcher: Matcher;
61
62
  private sharedStore?: SharedStore;
62
63
  private sharedVectorStore?: SharedVectorStore;
64
+ private graduation?: GraduationPipeline;
63
65
 
64
66
  constructor(
65
67
  eventStore: EventStore,
@@ -76,6 +78,13 @@ export class Retriever {
76
78
  this.sharedVectorStore = sharedOptions?.sharedVectorStore;
77
79
  }
78
80
 
81
+ /**
82
+ * Set graduation pipeline for access tracking
83
+ */
84
+ setGraduationPipeline(graduation: GraduationPipeline): void {
85
+ this.graduation = graduation;
86
+ }
87
+
79
88
  /**
80
89
  * Set shared stores after construction
81
90
  */
@@ -237,6 +246,15 @@ export class Retriever {
237
246
  const event = await this.eventStore.getEvent(result.eventId);
238
247
  if (!event) continue;
239
248
 
249
+ // Record access for graduation scoring
250
+ if (this.graduation) {
251
+ this.graduation.recordAccess(
252
+ event.id,
253
+ options.sessionId || 'unknown',
254
+ result.score
255
+ );
256
+ }
257
+
240
258
  let sessionContext: string | undefined;
241
259
  if (options.includeSessionContext) {
242
260
  sessionContext = await this.getSessionContext(event.sessionId, event.id);
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Hono } from 'hono';
7
- import { getDefaultMemoryService } from '../../services/memory-service.js';
7
+ import { getReadOnlyMemoryService } from '../../services/memory-service.js';
8
8
  import { generateCitationId, parseCitationId } from '../../core/citation-generator.js';
9
9
 
10
10
  export const citationsRouter = new Hono();
@@ -15,9 +15,9 @@ citationsRouter.get('/:id', async (c) => {
15
15
 
16
16
  // Support both formats: "a7Bc3x" or "mem:a7Bc3x"
17
17
  const citationId = parseCitationId(id) || id;
18
+ const memoryService = getReadOnlyMemoryService();
18
19
 
19
20
  try {
20
- const memoryService = getDefaultMemoryService();
21
21
  await memoryService.initialize();
22
22
 
23
23
  // Search through recent events to find the one matching this citation ID
@@ -48,6 +48,8 @@ citationsRouter.get('/:id', async (c) => {
48
48
  });
49
49
  } catch (error) {
50
50
  return c.json({ error: (error as Error).message }, 500);
51
+ } finally {
52
+ await memoryService.shutdown();
51
53
  }
52
54
  });
53
55
 
@@ -55,9 +57,9 @@ citationsRouter.get('/:id', async (c) => {
55
57
  citationsRouter.get('/:id/related', async (c) => {
56
58
  const { id } = c.req.param();
57
59
  const citationId = parseCitationId(id) || id;
60
+ const memoryService = getReadOnlyMemoryService();
58
61
 
59
62
  try {
60
- const memoryService = getDefaultMemoryService();
61
63
  await memoryService.initialize();
62
64
 
63
65
  const recentEvents = await memoryService.getRecentEvents(10000);
@@ -97,5 +99,7 @@ citationsRouter.get('/:id/related', async (c) => {
97
99
  });
98
100
  } catch (error) {
99
101
  return c.json({ error: (error as Error).message }, 500);
102
+ } finally {
103
+ await memoryService.shutdown();
100
104
  }
101
105
  });
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Hono } from 'hono';
7
- import { getDefaultMemoryService } from '../../services/memory-service.js';
7
+ import { getReadOnlyMemoryService } from '../../services/memory-service.js';
8
8
 
9
9
  export const eventsRouter = new Hono();
10
10
 
@@ -14,9 +14,9 @@ eventsRouter.get('/', async (c) => {
14
14
  const eventType = c.req.query('type');
15
15
  const limit = parseInt(c.req.query('limit') || '100', 10);
16
16
  const offset = parseInt(c.req.query('offset') || '0', 10);
17
+ const memoryService = getReadOnlyMemoryService();
17
18
 
18
19
  try {
19
- const memoryService = getDefaultMemoryService();
20
20
  await memoryService.initialize();
21
21
 
22
22
  let events = await memoryService.getRecentEvents(limit + offset + 1000);
@@ -51,15 +51,17 @@ eventsRouter.get('/', async (c) => {
51
51
  });
52
52
  } catch (error) {
53
53
  return c.json({ error: (error as Error).message }, 500);
54
+ } finally {
55
+ await memoryService.shutdown();
54
56
  }
55
57
  });
56
58
 
57
59
  // GET /api/events/:id - Get event details
58
60
  eventsRouter.get('/:id', async (c) => {
59
61
  const { id } = c.req.param();
62
+ const memoryService = getReadOnlyMemoryService();
60
63
 
61
64
  try {
62
- const memoryService = getDefaultMemoryService();
63
65
  await memoryService.initialize();
64
66
 
65
67
  const recentEvents = await memoryService.getRecentEvents(10000);
@@ -97,5 +99,7 @@ eventsRouter.get('/:id', async (c) => {
97
99
  });
98
100
  } catch (error) {
99
101
  return c.json({ error: (error as Error).message }, 500);
102
+ } finally {
103
+ await memoryService.shutdown();
100
104
  }
101
105
  });