kiro-memory 1.0.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.
@@ -0,0 +1,511 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/services/sqlite/Observations.ts
12
+ var Observations_exports = {};
13
+ __export(Observations_exports, {
14
+ createObservation: () => createObservation,
15
+ deleteObservation: () => deleteObservation,
16
+ getObservationsByProject: () => getObservationsByProject,
17
+ getObservationsBySession: () => getObservationsBySession,
18
+ searchObservations: () => searchObservations
19
+ });
20
+ function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
21
+ const now = /* @__PURE__ */ new Date();
22
+ const result = db.run(
23
+ `INSERT INTO observations
24
+ (memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
25
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
26
+ [memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
27
+ );
28
+ return Number(result.lastInsertRowid);
29
+ }
30
+ function getObservationsBySession(db, memorySessionId) {
31
+ const query = db.query(
32
+ "SELECT * FROM observations WHERE memory_session_id = ? ORDER BY prompt_number ASC"
33
+ );
34
+ return query.all(memorySessionId);
35
+ }
36
+ function getObservationsByProject(db, project, limit = 100) {
37
+ const query = db.query(
38
+ "SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
39
+ );
40
+ return query.all(project, limit);
41
+ }
42
+ function searchObservations(db, searchTerm, project) {
43
+ const sql = project ? `SELECT * FROM observations
44
+ WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
45
+ ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
46
+ WHERE title LIKE ? OR text LIKE ? OR narrative LIKE ?
47
+ ORDER BY created_at_epoch DESC`;
48
+ const pattern = `%${searchTerm}%`;
49
+ const query = db.query(sql);
50
+ if (project) {
51
+ return query.all(project, pattern, pattern, pattern);
52
+ }
53
+ return query.all(pattern, pattern, pattern);
54
+ }
55
+ function deleteObservation(db, id) {
56
+ db.run("DELETE FROM observations WHERE id = ?", [id]);
57
+ }
58
+ var init_Observations = __esm({
59
+ "src/services/sqlite/Observations.ts"() {
60
+ "use strict";
61
+ }
62
+ });
63
+
64
+ // src/services/search/ChromaManager.ts
65
+ import { ChromaClient } from "chromadb";
66
+ import { join as join2 } from "path";
67
+ import { homedir as homedir2 } from "os";
68
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
69
+
70
+ // src/utils/logger.ts
71
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
72
+ import { join } from "path";
73
+ import { homedir } from "os";
74
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
75
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
76
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
77
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
78
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
79
+ LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
80
+ return LogLevel2;
81
+ })(LogLevel || {});
82
+ var DEFAULT_DATA_DIR = join(homedir(), ".contextkit");
83
+ var Logger = class {
84
+ level = null;
85
+ useColor;
86
+ logFilePath = null;
87
+ logFileInitialized = false;
88
+ constructor() {
89
+ this.useColor = process.stdout.isTTY ?? false;
90
+ }
91
+ /**
92
+ * Initialize log file path and ensure directory exists (lazy initialization)
93
+ */
94
+ ensureLogFileInitialized() {
95
+ if (this.logFileInitialized) return;
96
+ this.logFileInitialized = true;
97
+ try {
98
+ const logsDir = join(DEFAULT_DATA_DIR, "logs");
99
+ if (!existsSync(logsDir)) {
100
+ mkdirSync(logsDir, { recursive: true });
101
+ }
102
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
103
+ this.logFilePath = join(logsDir, `contextkit-${date}.log`);
104
+ } catch (error) {
105
+ console.error("[LOGGER] Failed to initialize log file:", error);
106
+ this.logFilePath = null;
107
+ }
108
+ }
109
+ /**
110
+ * Lazy-load log level from settings file
111
+ */
112
+ getLevel() {
113
+ if (this.level === null) {
114
+ try {
115
+ const settingsPath = join(DEFAULT_DATA_DIR, "settings.json");
116
+ if (existsSync(settingsPath)) {
117
+ const settingsData = readFileSync(settingsPath, "utf-8");
118
+ const settings = JSON.parse(settingsData);
119
+ const envLevel = (settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
120
+ this.level = LogLevel[envLevel] ?? 1 /* INFO */;
121
+ } else {
122
+ this.level = 1 /* INFO */;
123
+ }
124
+ } catch (error) {
125
+ this.level = 1 /* INFO */;
126
+ }
127
+ }
128
+ return this.level;
129
+ }
130
+ /**
131
+ * Create correlation ID for tracking an observation through the pipeline
132
+ */
133
+ correlationId(sessionId, observationNum) {
134
+ return `obs-${sessionId}-${observationNum}`;
135
+ }
136
+ /**
137
+ * Create session correlation ID
138
+ */
139
+ sessionId(sessionId) {
140
+ return `session-${sessionId}`;
141
+ }
142
+ /**
143
+ * Format data for logging - create compact summaries instead of full dumps
144
+ */
145
+ formatData(data) {
146
+ if (data === null || data === void 0) return "";
147
+ if (typeof data === "string") return data;
148
+ if (typeof data === "number") return data.toString();
149
+ if (typeof data === "boolean") return data.toString();
150
+ if (typeof data === "object") {
151
+ if (data instanceof Error) {
152
+ return this.getLevel() === 0 /* DEBUG */ ? `${data.message}
153
+ ${data.stack}` : data.message;
154
+ }
155
+ if (Array.isArray(data)) {
156
+ return `[${data.length} items]`;
157
+ }
158
+ const keys = Object.keys(data);
159
+ if (keys.length === 0) return "{}";
160
+ if (keys.length <= 3) {
161
+ return JSON.stringify(data);
162
+ }
163
+ return `{${keys.length} keys: ${keys.slice(0, 3).join(", ")}...}`;
164
+ }
165
+ return String(data);
166
+ }
167
+ /**
168
+ * Format timestamp in local timezone (YYYY-MM-DD HH:MM:SS.mmm)
169
+ */
170
+ formatTimestamp(date) {
171
+ const year = date.getFullYear();
172
+ const month = String(date.getMonth() + 1).padStart(2, "0");
173
+ const day = String(date.getDate()).padStart(2, "0");
174
+ const hours = String(date.getHours()).padStart(2, "0");
175
+ const minutes = String(date.getMinutes()).padStart(2, "0");
176
+ const seconds = String(date.getSeconds()).padStart(2, "0");
177
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
178
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
179
+ }
180
+ /**
181
+ * Core logging method
182
+ */
183
+ log(level, component, message, context, data) {
184
+ if (level < this.getLevel()) return;
185
+ this.ensureLogFileInitialized();
186
+ const timestamp = this.formatTimestamp(/* @__PURE__ */ new Date());
187
+ const levelStr = LogLevel[level].padEnd(5);
188
+ const componentStr = component.padEnd(6);
189
+ let correlationStr = "";
190
+ if (context?.correlationId) {
191
+ correlationStr = `[${context.correlationId}] `;
192
+ } else if (context?.sessionId) {
193
+ correlationStr = `[session-${context.sessionId}] `;
194
+ }
195
+ let dataStr = "";
196
+ if (data !== void 0 && data !== null) {
197
+ if (data instanceof Error) {
198
+ dataStr = this.getLevel() === 0 /* DEBUG */ ? `
199
+ ${data.message}
200
+ ${data.stack}` : ` ${data.message}`;
201
+ } else if (this.getLevel() === 0 /* DEBUG */ && typeof data === "object") {
202
+ dataStr = "\n" + JSON.stringify(data, null, 2);
203
+ } else {
204
+ dataStr = " " + this.formatData(data);
205
+ }
206
+ }
207
+ let contextStr = "";
208
+ if (context) {
209
+ const { sessionId, memorySessionId, correlationId, ...rest } = context;
210
+ if (Object.keys(rest).length > 0) {
211
+ const pairs = Object.entries(rest).map(([k, v]) => `${k}=${v}`);
212
+ contextStr = ` {${pairs.join(", ")}}`;
213
+ }
214
+ }
215
+ const logLine = `[${timestamp}] [${levelStr}] [${componentStr}] ${correlationStr}${message}${contextStr}${dataStr}`;
216
+ if (this.logFilePath) {
217
+ try {
218
+ appendFileSync(this.logFilePath, logLine + "\n", "utf8");
219
+ } catch (error) {
220
+ process.stderr.write(`[LOGGER] Failed to write to log file: ${error}
221
+ `);
222
+ }
223
+ } else {
224
+ process.stderr.write(logLine + "\n");
225
+ }
226
+ }
227
+ // Public logging methods
228
+ debug(component, message, context, data) {
229
+ this.log(0 /* DEBUG */, component, message, context, data);
230
+ }
231
+ info(component, message, context, data) {
232
+ this.log(1 /* INFO */, component, message, context, data);
233
+ }
234
+ warn(component, message, context, data) {
235
+ this.log(2 /* WARN */, component, message, context, data);
236
+ }
237
+ error(component, message, context, data) {
238
+ this.log(3 /* ERROR */, component, message, context, data);
239
+ }
240
+ /**
241
+ * Log data flow: input → processing
242
+ */
243
+ dataIn(component, message, context, data) {
244
+ this.info(component, `\u2192 ${message}`, context, data);
245
+ }
246
+ /**
247
+ * Log data flow: processing → output
248
+ */
249
+ dataOut(component, message, context, data) {
250
+ this.info(component, `\u2190 ${message}`, context, data);
251
+ }
252
+ /**
253
+ * Log successful completion
254
+ */
255
+ success(component, message, context, data) {
256
+ this.info(component, `\u2713 ${message}`, context, data);
257
+ }
258
+ /**
259
+ * Log failure
260
+ */
261
+ failure(component, message, context, data) {
262
+ this.error(component, `\u2717 ${message}`, context, data);
263
+ }
264
+ /**
265
+ * Log timing information
266
+ */
267
+ timing(component, message, durationMs, context) {
268
+ this.info(component, `\u23F1 ${message}`, context, { duration: `${durationMs}ms` });
269
+ }
270
+ /**
271
+ * Happy Path Error - logs when the expected "happy path" fails but we have a fallback
272
+ */
273
+ happyPathError(component, message, context, data, fallback = "") {
274
+ const stack = new Error().stack || "";
275
+ const stackLines = stack.split("\n");
276
+ const callerLine = stackLines[2] || "";
277
+ const callerMatch = callerLine.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/);
278
+ const location = callerMatch ? `${callerMatch[1].split("/").pop()}:${callerMatch[2]}` : "unknown";
279
+ const enhancedContext = {
280
+ ...context,
281
+ location
282
+ };
283
+ this.warn(component, `[HAPPY-PATH] ${message}`, enhancedContext, data);
284
+ return fallback;
285
+ }
286
+ };
287
+ var logger = new Logger();
288
+
289
+ // src/services/search/ChromaManager.ts
290
+ var VECTOR_DB_DIR = join2(homedir2(), ".contextkit", "vector-db");
291
+ var ChromaManager = class {
292
+ client;
293
+ collection = null;
294
+ isAvailable = false;
295
+ constructor() {
296
+ if (!existsSync2(VECTOR_DB_DIR)) {
297
+ mkdirSync2(VECTOR_DB_DIR, { recursive: true });
298
+ }
299
+ this.client = new ChromaClient({
300
+ path: process.env.CHROMADB_URL || "http://localhost:8000"
301
+ });
302
+ }
303
+ /**
304
+ * Initialize ChromaDB connection and collection
305
+ */
306
+ async initialize() {
307
+ try {
308
+ await this.client.heartbeat();
309
+ this.collection = await this.client.getOrCreateCollection({
310
+ name: "contextkit-observations",
311
+ metadata: { description: "ContextKit observation embeddings" }
312
+ });
313
+ this.isAvailable = true;
314
+ logger.info("CHROMA", "ChromaDB initialized successfully");
315
+ return true;
316
+ } catch (error) {
317
+ logger.warn("CHROMA", "ChromaDB not available, falling back to SQLite search", {}, error);
318
+ this.isAvailable = false;
319
+ return false;
320
+ }
321
+ }
322
+ /**
323
+ * Add observation embedding to ChromaDB
324
+ */
325
+ async addObservation(id, content, metadata) {
326
+ if (!this.isAvailable || !this.collection) {
327
+ logger.debug("CHROMA", "ChromaDB not available, skipping embedding");
328
+ return;
329
+ }
330
+ try {
331
+ await this.collection.add({
332
+ ids: [id],
333
+ documents: [content],
334
+ metadatas: [metadata]
335
+ });
336
+ logger.debug("CHROMA", `Added observation ${id} to vector DB`);
337
+ } catch (error) {
338
+ logger.error("CHROMA", `Failed to add observation ${id}`, {}, error);
339
+ }
340
+ }
341
+ /**
342
+ * Search observations by semantic similarity
343
+ */
344
+ async search(query, options = {}) {
345
+ if (!this.isAvailable || !this.collection) {
346
+ logger.debug("CHROMA", "ChromaDB not available, returning empty results");
347
+ return [];
348
+ }
349
+ try {
350
+ const where = options.project ? { project: options.project } : void 0;
351
+ const results = await this.collection.query({
352
+ queryTexts: [query],
353
+ nResults: options.limit || 10,
354
+ where
355
+ });
356
+ const hits = [];
357
+ if (results.ids && results.ids[0]) {
358
+ for (let i = 0; i < results.ids[0].length; i++) {
359
+ hits.push({
360
+ id: results.ids[0][i],
361
+ content: results.documents?.[0]?.[i] || "",
362
+ metadata: results.metadatas?.[0]?.[i] || {},
363
+ distance: results.distances?.[0]?.[i] || 0
364
+ });
365
+ }
366
+ }
367
+ logger.debug("CHROMA", `Search returned ${hits.length} results`);
368
+ return hits;
369
+ } catch (error) {
370
+ logger.error("CHROMA", "Search failed", {}, error);
371
+ return [];
372
+ }
373
+ }
374
+ /**
375
+ * Delete observation from ChromaDB
376
+ */
377
+ async deleteObservation(id) {
378
+ if (!this.isAvailable || !this.collection) {
379
+ return;
380
+ }
381
+ try {
382
+ await this.collection.delete({ ids: [id] });
383
+ logger.debug("CHROMA", `Deleted observation ${id}`);
384
+ } catch (error) {
385
+ logger.error("CHROMA", `Failed to delete observation ${id}`, {}, error);
386
+ }
387
+ }
388
+ /**
389
+ * Check if ChromaDB is available
390
+ */
391
+ isChromaAvailable() {
392
+ return this.isAvailable;
393
+ }
394
+ /**
395
+ * Get collection stats
396
+ */
397
+ async getStats() {
398
+ if (!this.isAvailable || !this.collection) {
399
+ return { count: 0 };
400
+ }
401
+ try {
402
+ const count = await this.collection.count();
403
+ return { count };
404
+ } catch (error) {
405
+ logger.error("CHROMA", "Failed to get stats", {}, error);
406
+ return { count: 0 };
407
+ }
408
+ }
409
+ };
410
+ var chromaManager = null;
411
+ function getChromaManager() {
412
+ if (!chromaManager) {
413
+ chromaManager = new ChromaManager();
414
+ }
415
+ return chromaManager;
416
+ }
417
+
418
+ // src/services/search/HybridSearch.ts
419
+ var HybridSearch = class {
420
+ chromaManager;
421
+ constructor() {
422
+ this.chromaManager = new ChromaManager();
423
+ }
424
+ /**
425
+ * Initialize search (connects to ChromaDB if available)
426
+ */
427
+ async initialize() {
428
+ await this.chromaManager.initialize();
429
+ }
430
+ /**
431
+ * Perform hybrid search combining vector and keyword results
432
+ */
433
+ async search(db, query, options = {}) {
434
+ const limit = options.limit || 10;
435
+ const results = [];
436
+ if (this.chromaManager.isChromaAvailable()) {
437
+ try {
438
+ const vectorResults = await this.chromaManager.search(query, {
439
+ project: options.project,
440
+ limit: Math.ceil(limit / 2)
441
+ });
442
+ for (const hit of vectorResults) {
443
+ results.push({
444
+ id: hit.id,
445
+ title: hit.metadata.title || "Untitled",
446
+ content: hit.content,
447
+ type: hit.metadata.type || "unknown",
448
+ project: hit.metadata.project || "unknown",
449
+ created_at: hit.metadata.created_at || (/* @__PURE__ */ new Date()).toISOString(),
450
+ score: 1 - hit.distance,
451
+ // Convert distance to similarity score
452
+ source: "vector"
453
+ });
454
+ }
455
+ logger.debug("SEARCH", `Vector search returned ${vectorResults.length} results`);
456
+ } catch (error) {
457
+ logger.warn("SEARCH", "Vector search failed, using keyword only", {}, error);
458
+ }
459
+ }
460
+ try {
461
+ const { searchObservations: searchObservations2 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
462
+ const keywordResults = searchObservations2(db, query, options.project);
463
+ for (const obs of keywordResults.slice(0, Math.ceil(limit / 2))) {
464
+ results.push({
465
+ id: String(obs.id),
466
+ title: obs.title,
467
+ content: obs.text || obs.narrative || "",
468
+ type: obs.type,
469
+ project: obs.project,
470
+ created_at: obs.created_at,
471
+ score: 0.5,
472
+ // Default score for keyword matches
473
+ source: "keyword"
474
+ });
475
+ }
476
+ logger.debug("SEARCH", `Keyword search returned ${keywordResults.length} results`);
477
+ } catch (error) {
478
+ logger.error("SEARCH", "Keyword search failed", {}, error);
479
+ }
480
+ const uniqueResults = this.deduplicateAndSort(results, limit);
481
+ return uniqueResults;
482
+ }
483
+ /**
484
+ * Remove duplicates and sort by score
485
+ */
486
+ deduplicateAndSort(results, limit) {
487
+ const seen = /* @__PURE__ */ new Set();
488
+ const unique = [];
489
+ for (const result of results) {
490
+ if (!seen.has(result.id)) {
491
+ seen.add(result.id);
492
+ unique.push(result);
493
+ }
494
+ }
495
+ unique.sort((a, b) => b.score - a.score);
496
+ return unique.slice(0, limit);
497
+ }
498
+ };
499
+ var hybridSearch = null;
500
+ function getHybridSearch() {
501
+ if (!hybridSearch) {
502
+ hybridSearch = new HybridSearch();
503
+ }
504
+ return hybridSearch;
505
+ }
506
+ export {
507
+ ChromaManager,
508
+ HybridSearch,
509
+ getChromaManager,
510
+ getHybridSearch
511
+ };