@jungjaehoon/mama-core 1.1.1 → 1.1.3

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 (67) hide show
  1. package/dist/config-loader.d.ts +61 -0
  2. package/dist/config-loader.js +187 -0
  3. package/dist/db-adapter/base-adapter.d.ts +76 -0
  4. package/dist/db-adapter/base-adapter.js +11 -0
  5. package/dist/db-adapter/index.d.ts +24 -0
  6. package/dist/db-adapter/index.js +29 -0
  7. package/dist/db-adapter/sqlite-adapter.d.ts +73 -0
  8. package/dist/db-adapter/sqlite-adapter.js +330 -0
  9. package/dist/db-adapter/statement.d.ts +119 -0
  10. package/dist/db-adapter/statement.js +113 -0
  11. package/dist/db-manager.d.ts +252 -0
  12. package/dist/db-manager.js +622 -0
  13. package/dist/debug-logger.d.ts +32 -0
  14. package/dist/debug-logger.js +83 -0
  15. package/dist/decision-formatter.d.ts +172 -0
  16. package/dist/decision-formatter.js +894 -0
  17. package/dist/decision-tracker.d.ts +218 -0
  18. package/dist/decision-tracker.js +531 -0
  19. package/dist/embedding-cache.d.ts +83 -0
  20. package/dist/embedding-cache.js +182 -0
  21. package/dist/embedding-client.d.ts +43 -0
  22. package/dist/embedding-client.js +132 -0
  23. package/dist/embedding-server/index.d.ts +65 -0
  24. package/dist/embedding-server/index.js +397 -0
  25. package/dist/embedding-server/mobile/auth.d.ts +53 -0
  26. package/dist/embedding-server/mobile/auth.js +140 -0
  27. package/dist/embedding-server/mobile/daemon.d.ts +128 -0
  28. package/dist/embedding-server/mobile/daemon.js +303 -0
  29. package/dist/embedding-server/mobile/output-parser.d.ts +115 -0
  30. package/dist/embedding-server/mobile/output-parser.js +241 -0
  31. package/dist/embedding-server/mobile/session-api.d.ts +57 -0
  32. package/dist/embedding-server/mobile/session-api.js +261 -0
  33. package/dist/embedding-server/mobile/session-manager.d.ts +135 -0
  34. package/dist/embedding-server/mobile/session-manager.js +333 -0
  35. package/dist/embedding-server/mobile/websocket-handler.d.ts +127 -0
  36. package/dist/embedding-server/mobile/websocket-handler.js +435 -0
  37. package/dist/embeddings.d.ts +75 -0
  38. package/dist/embeddings.js +262 -0
  39. package/dist/errors.d.ts +131 -0
  40. package/dist/errors.js +225 -0
  41. package/dist/index.d.ts +32 -0
  42. package/dist/index.js +193 -0
  43. package/dist/mama-api.d.ts +954 -0
  44. package/dist/mama-api.js +2210 -0
  45. package/dist/memory-inject.d.ts +24 -0
  46. package/dist/memory-inject.js +116 -0
  47. package/dist/memory-store.d.ts +103 -0
  48. package/dist/memory-store.js +129 -0
  49. package/dist/notification-manager.d.ts +7 -0
  50. package/dist/notification-manager.js +12 -0
  51. package/dist/ollama-client.d.ts +51 -0
  52. package/dist/ollama-client.js +308 -0
  53. package/dist/outcome-tracker.d.ts +165 -0
  54. package/dist/outcome-tracker.js +315 -0
  55. package/dist/progress-indicator.d.ts +48 -0
  56. package/dist/progress-indicator.js +82 -0
  57. package/dist/query-intent.d.ts +27 -0
  58. package/dist/query-intent.js +144 -0
  59. package/dist/relevance-scorer.d.ts +124 -0
  60. package/dist/relevance-scorer.js +243 -0
  61. package/dist/test-utils.d.ts +66 -0
  62. package/dist/test-utils.js +166 -0
  63. package/dist/tier-validator.d.ts +55 -0
  64. package/dist/tier-validator.js +216 -0
  65. package/dist/time-formatter.d.ts +25 -0
  66. package/dist/time-formatter.js +93 -0
  67. package/package.json +3 -2
@@ -0,0 +1,622 @@
1
+ "use strict";
2
+ /**
3
+ * MAMA Database Manager (SQLite-only)
4
+ *
5
+ * SQLite-exclusive database interface for MAMA Plugin.
6
+ * Uses better-sqlite3 + sqlite-vec for local storage.
7
+ *
8
+ * PostgreSQL support is only available in the legacy mcp-server repository.
9
+ *
10
+ * Features:
11
+ * - WAL mode for better concurrency
12
+ * - synchronous=NORMAL for performance
13
+ * - Automatic migration management
14
+ * - Vector similarity search (when sqlite-vec available)
15
+ *
16
+ * @module db-manager
17
+ * @version 2.1 (Plugin - SQLite-only)
18
+ * @date 2026-02-01
19
+ * @source-of-truth packages/mama-core/src/db-manager.js (mama-core)
20
+ */
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.initDB = initDB;
26
+ exports.getDB = getDB;
27
+ exports.getAdapter = getAdapter;
28
+ exports.closeDB = closeDB;
29
+ exports.insertEmbedding = insertEmbedding;
30
+ exports.vectorSearch = vectorSearch;
31
+ exports.insertDecisionWithEmbedding = insertDecisionWithEmbedding;
32
+ exports.queryDecisionGraph = queryDecisionGraph;
33
+ exports.querySemanticEdges = querySemanticEdges;
34
+ exports.queryVectorSearch = queryVectorSearch;
35
+ exports.updateDecisionOutcome = updateDecisionOutcome;
36
+ exports.getPreparedStmt = getPreparedStmt;
37
+ exports.getDbPath = getDbPath;
38
+ exports.resetDBState = resetDBState;
39
+ exports.isTestMode = isTestMode;
40
+ const path_1 = __importDefault(require("path"));
41
+ const debug_logger_js_1 = require("./debug-logger.js");
42
+ const progress_indicator_js_1 = require("./progress-indicator.js");
43
+ const index_js_1 = require("./db-adapter/index.js");
44
+ // Database adapter instance (singleton)
45
+ let dbAdapter = null;
46
+ let dbConnection = null;
47
+ let isInitialized = false;
48
+ let initializingPromise = null; // Single-flight guard for concurrent callers
49
+ // Migration directory (moved to src/db/migrations for M1.2)
50
+ const MIGRATIONS_DIR = path_1.default.join(__dirname, '..', 'db', 'migrations');
51
+ /**
52
+ * Initialize SQLite database adapter and connect
53
+ *
54
+ * Lazy initialization: Only connects when first accessed
55
+ * Creates database file at ~/.claude/mama-memory.db by default
56
+ *
57
+ * Single-flight guard: Concurrent callers await the same promise
58
+ * to prevent multiple adapters/migrations running simultaneously.
59
+ *
60
+ * @returns SQLite database connection
61
+ */
62
+ async function initDB() {
63
+ // Already initialized - return immediately
64
+ if (isInitialized) {
65
+ return dbConnection;
66
+ }
67
+ // Single-flight guard: If initialization is in progress, wait for it
68
+ if (initializingPromise) {
69
+ return initializingPromise;
70
+ }
71
+ // Start initialization and store promise for concurrent callers
72
+ initializingPromise = (async () => {
73
+ try {
74
+ (0, progress_indicator_js_1.logSearching)('Initializing database...');
75
+ // Create SQLite adapter
76
+ dbAdapter = (0, index_js_1.createAdapter)();
77
+ // Connect to database
78
+ dbConnection = await dbAdapter.connect();
79
+ // Run migrations (includes 012-create-checkpoints-table.sql)
80
+ await dbAdapter.runMigrations(MIGRATIONS_DIR);
81
+ isInitialized = true;
82
+ (0, debug_logger_js_1.info)(`[db-manager] Database initialized (${dbAdapter.constructor.name})`);
83
+ (0, progress_indicator_js_1.logComplete)('Database ready');
84
+ return dbConnection;
85
+ }
86
+ catch (error) {
87
+ // Clear state on failure so retry is possible
88
+ initializingPromise = null;
89
+ dbAdapter = null;
90
+ dbConnection = null;
91
+ isInitialized = false;
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ throw new Error(`Failed to initialize database: ${message}`);
94
+ }
95
+ })();
96
+ return initializingPromise;
97
+ }
98
+ /**
99
+ * Get database connection (singleton pattern)
100
+ *
101
+ * Returns better-sqlite3 Database instance
102
+ *
103
+ * Note: Synchronous for backward compatibility with memory-store.js
104
+ * Will throw if database not initialized
105
+ *
106
+ * @returns SQLite database connection
107
+ */
108
+ function getDB() {
109
+ if (!dbConnection) {
110
+ throw new Error('Database not initialized. Call await initDB() first.');
111
+ }
112
+ return dbConnection;
113
+ }
114
+ /**
115
+ * Get database adapter instance
116
+ *
117
+ * Used for advanced operations (vectorSearch, insertEmbedding, etc.)
118
+ *
119
+ * @returns Adapter instance
120
+ */
121
+ function getAdapter() {
122
+ if (!dbAdapter) {
123
+ throw new Error('Database adapter not initialized. Call await initDB() first.');
124
+ }
125
+ return dbAdapter;
126
+ }
127
+ /**
128
+ * Close database connection
129
+ *
130
+ * Call this on process exit
131
+ */
132
+ async function closeDB() {
133
+ if (dbAdapter) {
134
+ await dbAdapter.disconnect();
135
+ dbAdapter = null;
136
+ dbConnection = null;
137
+ isInitialized = false;
138
+ initializingPromise = null; // Clear to allow re-initialization
139
+ (0, debug_logger_js_1.info)('[db-manager] Database connection closed');
140
+ }
141
+ }
142
+ /**
143
+ * Insert embedding into vector search table
144
+ *
145
+ * Uses sqlite-vec for vector similarity search
146
+ * Gracefully degrades if sqlite-vec is not available
147
+ *
148
+ * @param decisionRowid - SQLite rowid
149
+ * @param embedding - 384-dim embedding vector
150
+ */
151
+ async function insertEmbedding(decisionRowid, embedding) {
152
+ const adapter = getAdapter();
153
+ try {
154
+ adapter.insertEmbedding(decisionRowid, embedding);
155
+ }
156
+ catch (error) {
157
+ const message = error instanceof Error ? error.message : String(error);
158
+ // Graceful degradation: Log warning but don't fail
159
+ (0, debug_logger_js_1.error)(`[db-manager] Failed to insert embedding (vector search unavailable): ${message}`);
160
+ }
161
+ }
162
+ /**
163
+ * Perform vector similarity search
164
+ *
165
+ * Returns empty array if vector search not available (no keyword fallback)
166
+ *
167
+ * @param queryEmbedding - Query embedding (384-dim)
168
+ * @param limit - Max results to return (default: 5)
169
+ * @param threshold - Minimum similarity threshold (default: 0.7)
170
+ * @returns Array of decisions with similarity scores, or empty array
171
+ */
172
+ async function vectorSearch(queryEmbedding, limit = 5, threshold = 0.7) {
173
+ const adapter = getAdapter();
174
+ try {
175
+ // SQLite adapter returns null if sqlite-vec not available
176
+ const results = await adapter.vectorSearch(queryEmbedding, limit * 3);
177
+ if (!results || results.length === 0) {
178
+ return []; // No keyword fallback - fast fail
179
+ }
180
+ const stmt = adapter.prepare(`SELECT * FROM decisions WHERE rowid = ?`);
181
+ const decisions = [];
182
+ for (const row of results) {
183
+ const decision = stmt.get(row.rowid);
184
+ if (!decision) {
185
+ continue;
186
+ }
187
+ const similarity = row.similarity ?? Math.max(0, 1.0 - (row.distance ?? 1));
188
+ const distance = row.distance ?? Math.max(0, 1.0 - similarity);
189
+ if (similarity >= threshold) {
190
+ decisions.push({
191
+ ...decision,
192
+ distance,
193
+ similarity,
194
+ });
195
+ }
196
+ if (decisions.length >= limit) {
197
+ break;
198
+ }
199
+ }
200
+ return decisions;
201
+ }
202
+ catch (error) {
203
+ const message = error instanceof Error ? error.message : String(error);
204
+ (0, debug_logger_js_1.error)(`[db-manager] Vector search failed: ${message}`);
205
+ return []; // No keyword fallback - fast fail
206
+ }
207
+ }
208
+ /**
209
+ * Insert decision with embedding
210
+ *
211
+ * Combined operation: Insert decision + Generate embedding + Insert embedding
212
+ * SQLite-only implementation
213
+ *
214
+ * @param decision - Decision object
215
+ * @returns Decision ID
216
+ */
217
+ async function insertDecisionWithEmbedding(decision) {
218
+ const adapter = getAdapter();
219
+ const { generateEnhancedEmbedding } = await import('./embeddings.js');
220
+ try {
221
+ // Generate embedding BEFORE transaction (required for SQLite's sync transaction)
222
+ // Note: Redact topic for privacy - only log length
223
+ (0, debug_logger_js_1.info)(`[db-manager] Generating embedding for decision (topic length: ${decision.topic?.length || 0})`);
224
+ let embedding = null;
225
+ try {
226
+ embedding = await generateEnhancedEmbedding({
227
+ topic: decision.topic,
228
+ decision: decision.decision,
229
+ reasoning: decision.reasoning || undefined,
230
+ outcome: decision.outcome || undefined,
231
+ confidence: decision.confidence,
232
+ });
233
+ (0, debug_logger_js_1.info)(`[db-manager] Embedding generated: ${embedding ? embedding.length : 'null'} dimensions`);
234
+ }
235
+ catch (embGenErr) {
236
+ // Non-fatal: save decision without embedding (e.g. ONNX model unavailable on CI)
237
+ const message = embGenErr instanceof Error ? embGenErr.message : String(embGenErr);
238
+ (0, debug_logger_js_1.error)(`[db-manager] ⚠️ Embedding generation failed, saving without vector: ${message}`);
239
+ }
240
+ // SQLite: Synchronous transaction including embedding
241
+ adapter.transaction(() => {
242
+ // Prepare INSERT statement
243
+ const stmt = adapter.prepare(`
244
+ INSERT INTO decisions (
245
+ id, topic, decision, reasoning,
246
+ outcome, failure_reason, limitation,
247
+ user_involvement, session_id,
248
+ supersedes, superseded_by, refined_from,
249
+ confidence, created_at, updated_at,
250
+ needs_validation, validation_attempts, last_validated_at, usage_count,
251
+ trust_context, usage_success, usage_failure, time_saved,
252
+ evidence, alternatives, risks
253
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
254
+ `);
255
+ const insertResult = stmt.run(decision.id, decision.topic, decision.decision, decision.reasoning || null, decision.outcome || null, decision.failure_reason || null, decision.limitation || null, decision.user_involvement || null, decision.session_id || null, decision.supersedes || null, decision.superseded_by || null, decision.refined_from ? JSON.stringify(decision.refined_from) : null, decision.confidence !== undefined ? decision.confidence : 0.5,
256
+ // IMPORTANT: All timestamps are stored in milliseconds (Date.now()).
257
+ // The schema DEFAULT uses unixepoch() (seconds) but is never used
258
+ // since all inserts go through this function which always provides ms.
259
+ decision.created_at || Date.now(), decision.updated_at || Date.now(), decision.needs_validation !== undefined ? decision.needs_validation : 0, decision.validation_attempts || 0, decision.last_validated_at || null, decision.usage_count || 0, decision.trust_context || null, decision.usage_success || 0, decision.usage_failure || 0, decision.time_saved || 0, decision.evidence || null, decision.alternatives || null, decision.risks || null);
260
+ const rowid = Number(insertResult.lastInsertRowid);
261
+ // Insert embedding in same transaction to ensure rowid matching
262
+ (0, debug_logger_js_1.info)(`[db-manager] Vector search enabled: ${adapter.vectorSearchEnabled}`);
263
+ if (adapter.vectorSearchEnabled && embedding) {
264
+ try {
265
+ (0, debug_logger_js_1.info)(`[db-manager] Inserting embedding for rowid: ${rowid}`);
266
+ adapter.insertEmbedding(rowid, embedding);
267
+ (0, debug_logger_js_1.info)(`[db-manager] ✅ Embedding inserted successfully`);
268
+ }
269
+ catch (embErr) {
270
+ const message = embErr instanceof Error ? embErr.message : String(embErr);
271
+ // Log but don't fail transaction if embedding fails
272
+ (0, debug_logger_js_1.error)(`[db-manager] ❌ Embedding insert failed: ${message}`);
273
+ }
274
+ }
275
+ else {
276
+ (0, debug_logger_js_1.info)(`[db-manager] ⚠️ Vector search disabled, skipping embedding`);
277
+ }
278
+ return rowid;
279
+ });
280
+ if (process.env.MAMA_DEBUG) {
281
+ (0, debug_logger_js_1.info)(`[db-manager] Decision stored: ${decision.id}`);
282
+ }
283
+ return decision.id;
284
+ }
285
+ catch (error) {
286
+ const message = error instanceof Error ? error.message : String(error);
287
+ throw new Error(`Failed to insert decision with embedding: ${message}`);
288
+ }
289
+ }
290
+ /**
291
+ * Query decision graph for topic
292
+ *
293
+ * Recursive CTE to traverse supersedes chain
294
+ * SQLite implementation using WITH RECURSIVE
295
+ *
296
+ * @param topic - Decision topic to query
297
+ * @returns Array of decisions (ordered by recency)
298
+ */
299
+ async function queryDecisionGraph(topic) {
300
+ const adapter = getAdapter();
301
+ try {
302
+ // Story 014.14 Fix: Prioritize exact topic match over fuzzy matching
303
+ // First try exact match, then fallback to fuzzy if no results
304
+ // Try exact match first
305
+ let stmt = adapter.prepare(`
306
+ WITH RECURSIVE decision_chain AS (
307
+ -- Base case: Get current decision (not superseded)
308
+ SELECT * FROM decisions
309
+ WHERE topic = ? AND superseded_by IS NULL
310
+
311
+ UNION ALL
312
+
313
+ -- Recursive case: Get previous decisions
314
+ SELECT d.* FROM decisions d
315
+ JOIN decision_chain dc ON d.id = dc.supersedes
316
+ )
317
+ SELECT * FROM decision_chain
318
+ ORDER BY created_at DESC
319
+ `);
320
+ let decisions = stmt.all(topic);
321
+ // If no exact match, try fuzzy matching as fallback
322
+ if (decisions.length === 0) {
323
+ const topicKeyword = topic.split('_')[0];
324
+ stmt = adapter.prepare(`
325
+ WITH RECURSIVE decision_chain AS (
326
+ -- Base case: Get current decision (not superseded)
327
+ SELECT * FROM decisions
328
+ WHERE topic LIKE ? || '%' AND superseded_by IS NULL
329
+
330
+ UNION ALL
331
+
332
+ -- Recursive case: Get previous decisions
333
+ SELECT d.* FROM decisions d
334
+ JOIN decision_chain dc ON d.id = dc.supersedes
335
+ )
336
+ SELECT * FROM decision_chain
337
+ ORDER BY created_at DESC
338
+ `);
339
+ decisions = stmt.all(topicKeyword);
340
+ }
341
+ // Join with decision_edges to include relationships
342
+ // Prepare statement once outside loop for performance
343
+ const edgesStmt = adapter.prepare(`
344
+ SELECT * FROM decision_edges
345
+ WHERE from_id = ?
346
+ AND (approved_by_user = 1 OR approved_by_user IS NULL)
347
+ `);
348
+ for (const decision of decisions) {
349
+ decision.edges = edgesStmt.all(decision.id);
350
+ // Parse refined_from JSON if exists
351
+ if (decision.refined_from) {
352
+ try {
353
+ decision.refined_from =
354
+ typeof decision.refined_from === 'string'
355
+ ? JSON.parse(decision.refined_from)
356
+ : decision.refined_from;
357
+ }
358
+ catch {
359
+ decision.refined_from = [];
360
+ }
361
+ }
362
+ }
363
+ return decisions;
364
+ }
365
+ catch (error) {
366
+ const message = error instanceof Error ? error.message : String(error);
367
+ throw new Error(`Decision graph query failed: ${message}`);
368
+ }
369
+ }
370
+ /**
371
+ * Query semantic edges for a list of decisions
372
+ *
373
+ * Returns both outgoing (from_id) and incoming (to_id) edges
374
+ * for refines and contradicts relationships
375
+ *
376
+ * @param decisionIds - Decision IDs to query edges for
377
+ * @returns Categorized edges
378
+ */
379
+ async function querySemanticEdges(decisionIds) {
380
+ const adapter = getAdapter();
381
+ if (!decisionIds || decisionIds.length === 0) {
382
+ return {
383
+ refines: [],
384
+ refined_by: [],
385
+ contradicts: [],
386
+ contradicted_by: [],
387
+ // Story 2.1: Extended edge types
388
+ builds_on: [],
389
+ built_on_by: [],
390
+ debates: [],
391
+ debated_by: [],
392
+ synthesizes: [],
393
+ synthesized_by: [],
394
+ };
395
+ }
396
+ try {
397
+ // Build placeholders for IN clause
398
+ const placeholders = decisionIds.map(() => '?').join(',');
399
+ // Story 2.1: Include new edge types in query
400
+ const edgeTypes = ['refines', 'contradicts', 'builds_on', 'debates', 'synthesizes'];
401
+ const edgeTypePlaceholders = edgeTypes.map(() => '?').join(',');
402
+ // Query outgoing edges (from_id = decision)
403
+ const outgoingStmt = adapter.prepare(`
404
+ SELECT e.*, d.topic, d.decision, d.confidence, d.created_at
405
+ FROM decision_edges e
406
+ JOIN decisions d ON e.to_id = d.id
407
+ WHERE e.from_id IN (${placeholders})
408
+ AND e.relationship IN (${edgeTypePlaceholders})
409
+ AND (e.approved_by_user = 1 OR e.approved_by_user IS NULL)
410
+ ORDER BY e.created_at DESC
411
+ `);
412
+ const outgoingEdges = outgoingStmt.all(...decisionIds, ...edgeTypes);
413
+ // Query incoming edges (to_id = decision)
414
+ const incomingStmt = adapter.prepare(`
415
+ SELECT e.*, d.topic, d.decision, d.confidence, d.created_at
416
+ FROM decision_edges e
417
+ JOIN decisions d ON e.from_id = d.id
418
+ WHERE e.to_id IN (${placeholders})
419
+ AND e.relationship IN (${edgeTypePlaceholders})
420
+ AND (e.approved_by_user = 1 OR e.approved_by_user IS NULL)
421
+ ORDER BY e.created_at DESC
422
+ `);
423
+ const incomingEdges = incomingStmt.all(...decisionIds, ...edgeTypes);
424
+ // Categorize edges (original + v1.3 extended)
425
+ const refines = outgoingEdges.filter((e) => e.relationship === 'refines');
426
+ const refined_by = incomingEdges.filter((e) => e.relationship === 'refines');
427
+ const contradicts = outgoingEdges.filter((e) => e.relationship === 'contradicts');
428
+ const contradicted_by = incomingEdges.filter((e) => e.relationship === 'contradicts');
429
+ // Story 2.1: New edge type categories
430
+ const builds_on = outgoingEdges.filter((e) => e.relationship === 'builds_on');
431
+ const built_on_by = incomingEdges.filter((e) => e.relationship === 'builds_on');
432
+ const debates = outgoingEdges.filter((e) => e.relationship === 'debates');
433
+ const debated_by = incomingEdges.filter((e) => e.relationship === 'debates');
434
+ const synthesizes = outgoingEdges.filter((e) => e.relationship === 'synthesizes');
435
+ const synthesized_by = incomingEdges.filter((e) => e.relationship === 'synthesizes');
436
+ return {
437
+ refines,
438
+ refined_by,
439
+ contradicts,
440
+ contradicted_by,
441
+ builds_on,
442
+ built_on_by,
443
+ debates,
444
+ debated_by,
445
+ synthesizes,
446
+ synthesized_by,
447
+ };
448
+ }
449
+ catch (error) {
450
+ const message = error instanceof Error ? error.message : String(error);
451
+ throw new Error(`Semantic edges query failed: ${message}`);
452
+ }
453
+ }
454
+ /**
455
+ * Query vector search with time window and threshold
456
+ *
457
+ * Story 014.14: AC #1 - Vector Search for Related Decisions
458
+ *
459
+ * @param params - Search parameters
460
+ * @returns Results with similarity scores and decision data
461
+ */
462
+ async function queryVectorSearch(params) {
463
+ const { query, limit = 10, threshold = 0.75, timeWindow = 90 * 24 * 60 * 60 * 1000 } = params;
464
+ const adapter = getAdapter();
465
+ const { generateEmbedding } = await import('./embeddings.js');
466
+ try {
467
+ // Generate embedding for query
468
+ const embedding = await generateEmbedding(query);
469
+ const cutoffTime = Date.now() - timeWindow;
470
+ const candidates = await adapter.vectorSearch(embedding, limit * 5);
471
+ if (!candidates || candidates.length === 0) {
472
+ return [];
473
+ }
474
+ const stmt = adapter.prepare(`SELECT * FROM decisions WHERE rowid = ?`);
475
+ const results = [];
476
+ for (const candidate of candidates) {
477
+ const decision = stmt.get(candidate.rowid);
478
+ if (!decision) {
479
+ continue;
480
+ }
481
+ if (decision.created_at < cutoffTime) {
482
+ continue;
483
+ }
484
+ const similarity = candidate.similarity ?? Math.max(0, 1 - (candidate.distance ?? 1));
485
+ const distance = candidate.distance ?? Math.max(0, 1 - similarity);
486
+ if (similarity < threshold) {
487
+ continue;
488
+ }
489
+ results.push({
490
+ ...decision,
491
+ similarity,
492
+ distance,
493
+ });
494
+ if (results.length >= limit) {
495
+ break;
496
+ }
497
+ }
498
+ return results;
499
+ }
500
+ catch (error) {
501
+ const message = error instanceof Error ? error.message : String(error);
502
+ (0, debug_logger_js_1.error)(`[db-manager] queryVectorSearch failed: ${message}`);
503
+ return []; // Return empty array on error (graceful degradation)
504
+ }
505
+ }
506
+ /**
507
+ * Update decision outcome
508
+ *
509
+ * @param decisionId - Decision ID
510
+ * @param outcomeData - Outcome data
511
+ */
512
+ async function updateDecisionOutcome(decisionId, outcomeData) {
513
+ const adapter = getAdapter();
514
+ try {
515
+ const stmt = adapter.prepare(`
516
+ UPDATE decisions
517
+ SET
518
+ outcome = ?,
519
+ failure_reason = ?,
520
+ limitation = ?,
521
+ duration_days = ?,
522
+ confidence = COALESCE(?, confidence),
523
+ updated_at = ?
524
+ WHERE id = ?
525
+ `);
526
+ stmt.run(outcomeData.outcome ?? null, outcomeData.failure_reason ?? null, outcomeData.limitation ?? null, outcomeData.duration_days ?? null, outcomeData.confidence !== undefined ? outcomeData.confidence : null, Date.now(), decisionId);
527
+ (0, debug_logger_js_1.info)(`[db-manager] Decision outcome updated: ${decisionId} → ${outcomeData.outcome}`);
528
+ }
529
+ catch (error) {
530
+ const message = error instanceof Error ? error.message : String(error);
531
+ throw new Error(`Failed to update decision outcome: ${message}`);
532
+ }
533
+ }
534
+ /**
535
+ * Get prepared statement
536
+ *
537
+ * For backward compatibility with memory-store.js
538
+ * Returns a compatibility shim that proxies to adapter.prepare()
539
+ *
540
+ * @param sql - SQL statement
541
+ * @returns Statement-like object with run/get/all methods
542
+ */
543
+ function getPreparedStmt(sql) {
544
+ if (!dbAdapter) {
545
+ (0, debug_logger_js_1.warn)('[db-manager] getPreparedStmt() called before initialization');
546
+ // Return no-op object for feature detection (won't throw)
547
+ return {
548
+ run: () => ({ changes: 0, lastInsertRowid: 0 }),
549
+ get: () => null,
550
+ all: () => [],
551
+ };
552
+ }
553
+ // Proxy to adapter.prepare() for actual usage
554
+ try {
555
+ return dbAdapter.prepare(sql);
556
+ }
557
+ catch (error) {
558
+ const message = error instanceof Error ? error.message : String(error);
559
+ (0, debug_logger_js_1.warn)(`[db-manager] getPreparedStmt() failed: ${message}`);
560
+ // Return no-op object on error (graceful degradation)
561
+ return {
562
+ run: () => ({ changes: 0, lastInsertRowid: 0 }),
563
+ get: () => null,
564
+ all: () => [],
565
+ };
566
+ }
567
+ }
568
+ /**
569
+ * Get database file path
570
+ *
571
+ * @returns Actual database path or 'Not initialized'
572
+ */
573
+ function getDbPath() {
574
+ if (!dbAdapter) {
575
+ return 'Not initialized';
576
+ }
577
+ // Use adapter's getDbPath method if available, fallback to description
578
+ if (typeof dbAdapter.getDbPath === 'function') {
579
+ return dbAdapter.getDbPath();
580
+ }
581
+ // Fallback: try to get path from adapter properties
582
+ if (dbAdapter.dbPath) {
583
+ return dbAdapter.dbPath;
584
+ }
585
+ return `${dbAdapter.constructor.name} (path unavailable)`;
586
+ }
587
+ // Note: Removed auto-registered SIGINT/SIGTERM handlers that called process.exit(0)
588
+ // This was causing issues with host cleanup in parent processes.
589
+ // If graceful shutdown is needed, the host application should handle closeDB().
590
+ /**
591
+ * Reset database state for testing
592
+ *
593
+ * Resets internal state without closing connection.
594
+ * Use this in test teardown to allow re-initialization with different DB path.
595
+ *
596
+ * @param options - Reset options
597
+ * @param options.disconnect - If true, also disconnect adapter (default: true)
598
+ */
599
+ function resetDBState(options = {}) {
600
+ const { disconnect = true } = options;
601
+ if (disconnect && dbAdapter) {
602
+ try {
603
+ dbAdapter.disconnect();
604
+ }
605
+ catch {
606
+ // Ignore disconnect errors during reset
607
+ }
608
+ }
609
+ dbAdapter = null;
610
+ dbConnection = null;
611
+ isInitialized = false;
612
+ initializingPromise = null;
613
+ }
614
+ /**
615
+ * Check if running in test mode
616
+ *
617
+ * Returns true if MAMA_TEST_MODE or VITEST env vars are set
618
+ */
619
+ function isTestMode() {
620
+ return !!(process.env.MAMA_TEST_MODE || process.env.VITEST);
621
+ }
622
+ //# sourceMappingURL=db-manager.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * DebugLogger - Centralized logging for MAMA hooks
3
+ *
4
+ * CLAUDE.md Compliant:
5
+ * - NO console.log (use DebugLogger.info instead)
6
+ * - console.error/warn allowed but wrapped for consistency
7
+ *
8
+ * Features:
9
+ * - Log levels (DEBUG, INFO, WARN, ERROR)
10
+ * - Timestamp formatting
11
+ * - Environment-based filtering
12
+ * - Module/context tagging
13
+ */
14
+ export declare class DebugLogger {
15
+ private context;
16
+ private level;
17
+ constructor(context?: string);
18
+ private _getLogLevel;
19
+ private _shouldLog;
20
+ private _formatMessage;
21
+ debug(...args: unknown[]): void;
22
+ info(...args: unknown[]): void;
23
+ warn(...args: unknown[]): void;
24
+ error(...args: unknown[]): void;
25
+ }
26
+ declare const logger: DebugLogger;
27
+ export declare const debug: (...args: unknown[]) => void;
28
+ export declare const info: (...args: unknown[]) => void;
29
+ export declare const warn: (...args: unknown[]) => void;
30
+ export declare const error: (...args: unknown[]) => void;
31
+ export default logger;
32
+ //# sourceMappingURL=debug-logger.d.ts.map