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.
- package/LICENSE +661 -0
- package/README.md +290 -0
- package/package.json +117 -0
- package/plugin/dist/cli/contextkit.js +1259 -0
- package/plugin/dist/hooks/agentSpawn.js +1187 -0
- package/plugin/dist/hooks/kiro-hooks.js +1184 -0
- package/plugin/dist/hooks/postToolUse.js +1219 -0
- package/plugin/dist/hooks/stop.js +1163 -0
- package/plugin/dist/hooks/userPromptSubmit.js +1152 -0
- package/plugin/dist/index.js +2103 -0
- package/plugin/dist/sdk/index.js +1083 -0
- package/plugin/dist/servers/mcp-server.js +266 -0
- package/plugin/dist/services/search/ChromaManager.js +357 -0
- package/plugin/dist/services/search/HybridSearch.js +502 -0
- package/plugin/dist/services/search/index.js +511 -0
- package/plugin/dist/services/sqlite/Database.js +625 -0
- package/plugin/dist/services/sqlite/Observations.js +46 -0
- package/plugin/dist/services/sqlite/Prompts.js +39 -0
- package/plugin/dist/services/sqlite/Search.js +143 -0
- package/plugin/dist/services/sqlite/Sessions.js +60 -0
- package/plugin/dist/services/sqlite/Summaries.js +44 -0
- package/plugin/dist/services/sqlite/index.js +951 -0
- package/plugin/dist/shared/paths.js +315 -0
- package/plugin/dist/types/worker-types.js +0 -0
- package/plugin/dist/utils/logger.js +222 -0
- package/plugin/dist/viewer.html +252 -0
- package/plugin/dist/viewer.js +23965 -0
- package/plugin/dist/worker-service.js +1782 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
// src/shims/bun-sqlite.ts
|
|
2
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
3
|
+
var Database = class {
|
|
4
|
+
_db;
|
|
5
|
+
constructor(path, options) {
|
|
6
|
+
this._db = new BetterSqlite3(path, {
|
|
7
|
+
// better-sqlite3 crea il file di default (non serve 'create')
|
|
8
|
+
readonly: options?.readwrite === false ? true : false
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Esegui una query SQL senza risultati
|
|
13
|
+
*/
|
|
14
|
+
run(sql, params) {
|
|
15
|
+
const stmt = this._db.prepare(sql);
|
|
16
|
+
const result = params ? stmt.run(...params) : stmt.run();
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Prepara una query con interfaccia compatibile bun:sqlite
|
|
21
|
+
*/
|
|
22
|
+
query(sql) {
|
|
23
|
+
return new BunQueryCompat(this._db, sql);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Crea una transazione
|
|
27
|
+
*/
|
|
28
|
+
transaction(fn) {
|
|
29
|
+
return this._db.transaction(fn);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Chiudi la connessione
|
|
33
|
+
*/
|
|
34
|
+
close() {
|
|
35
|
+
this._db.close();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var BunQueryCompat = class {
|
|
39
|
+
_db;
|
|
40
|
+
_sql;
|
|
41
|
+
constructor(db, sql) {
|
|
42
|
+
this._db = db;
|
|
43
|
+
this._sql = sql;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Restituisce tutte le righe
|
|
47
|
+
*/
|
|
48
|
+
all(...params) {
|
|
49
|
+
const stmt = this._db.prepare(this._sql);
|
|
50
|
+
return params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Restituisce la prima riga o null
|
|
54
|
+
*/
|
|
55
|
+
get(...params) {
|
|
56
|
+
const stmt = this._db.prepare(this._sql);
|
|
57
|
+
return params.length > 0 ? stmt.get(...params) : stmt.get();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Esegui senza risultati
|
|
61
|
+
*/
|
|
62
|
+
run(...params) {
|
|
63
|
+
const stmt = this._db.prepare(this._sql);
|
|
64
|
+
return params.length > 0 ? stmt.run(...params) : stmt.run();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/shared/paths.ts
|
|
69
|
+
import { join as join2, dirname, basename } from "path";
|
|
70
|
+
import { homedir as homedir2 } from "os";
|
|
71
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
72
|
+
import { fileURLToPath } from "url";
|
|
73
|
+
|
|
74
|
+
// src/utils/logger.ts
|
|
75
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
76
|
+
import { join } from "path";
|
|
77
|
+
import { homedir } from "os";
|
|
78
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
79
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
80
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
81
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
82
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
83
|
+
LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
|
|
84
|
+
return LogLevel2;
|
|
85
|
+
})(LogLevel || {});
|
|
86
|
+
var DEFAULT_DATA_DIR = join(homedir(), ".contextkit");
|
|
87
|
+
var Logger = class {
|
|
88
|
+
level = null;
|
|
89
|
+
useColor;
|
|
90
|
+
logFilePath = null;
|
|
91
|
+
logFileInitialized = false;
|
|
92
|
+
constructor() {
|
|
93
|
+
this.useColor = process.stdout.isTTY ?? false;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Initialize log file path and ensure directory exists (lazy initialization)
|
|
97
|
+
*/
|
|
98
|
+
ensureLogFileInitialized() {
|
|
99
|
+
if (this.logFileInitialized) return;
|
|
100
|
+
this.logFileInitialized = true;
|
|
101
|
+
try {
|
|
102
|
+
const logsDir = join(DEFAULT_DATA_DIR, "logs");
|
|
103
|
+
if (!existsSync(logsDir)) {
|
|
104
|
+
mkdirSync(logsDir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
107
|
+
this.logFilePath = join(logsDir, `contextkit-${date}.log`);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("[LOGGER] Failed to initialize log file:", error);
|
|
110
|
+
this.logFilePath = null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Lazy-load log level from settings file
|
|
115
|
+
*/
|
|
116
|
+
getLevel() {
|
|
117
|
+
if (this.level === null) {
|
|
118
|
+
try {
|
|
119
|
+
const settingsPath = join(DEFAULT_DATA_DIR, "settings.json");
|
|
120
|
+
if (existsSync(settingsPath)) {
|
|
121
|
+
const settingsData = readFileSync(settingsPath, "utf-8");
|
|
122
|
+
const settings = JSON.parse(settingsData);
|
|
123
|
+
const envLevel = (settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
|
|
124
|
+
this.level = LogLevel[envLevel] ?? 1 /* INFO */;
|
|
125
|
+
} else {
|
|
126
|
+
this.level = 1 /* INFO */;
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
this.level = 1 /* INFO */;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return this.level;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Create correlation ID for tracking an observation through the pipeline
|
|
136
|
+
*/
|
|
137
|
+
correlationId(sessionId, observationNum) {
|
|
138
|
+
return `obs-${sessionId}-${observationNum}`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create session correlation ID
|
|
142
|
+
*/
|
|
143
|
+
sessionId(sessionId) {
|
|
144
|
+
return `session-${sessionId}`;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Format data for logging - create compact summaries instead of full dumps
|
|
148
|
+
*/
|
|
149
|
+
formatData(data) {
|
|
150
|
+
if (data === null || data === void 0) return "";
|
|
151
|
+
if (typeof data === "string") return data;
|
|
152
|
+
if (typeof data === "number") return data.toString();
|
|
153
|
+
if (typeof data === "boolean") return data.toString();
|
|
154
|
+
if (typeof data === "object") {
|
|
155
|
+
if (data instanceof Error) {
|
|
156
|
+
return this.getLevel() === 0 /* DEBUG */ ? `${data.message}
|
|
157
|
+
${data.stack}` : data.message;
|
|
158
|
+
}
|
|
159
|
+
if (Array.isArray(data)) {
|
|
160
|
+
return `[${data.length} items]`;
|
|
161
|
+
}
|
|
162
|
+
const keys = Object.keys(data);
|
|
163
|
+
if (keys.length === 0) return "{}";
|
|
164
|
+
if (keys.length <= 3) {
|
|
165
|
+
return JSON.stringify(data);
|
|
166
|
+
}
|
|
167
|
+
return `{${keys.length} keys: ${keys.slice(0, 3).join(", ")}...}`;
|
|
168
|
+
}
|
|
169
|
+
return String(data);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Format timestamp in local timezone (YYYY-MM-DD HH:MM:SS.mmm)
|
|
173
|
+
*/
|
|
174
|
+
formatTimestamp(date) {
|
|
175
|
+
const year = date.getFullYear();
|
|
176
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
177
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
178
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
179
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
180
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
181
|
+
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
182
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Core logging method
|
|
186
|
+
*/
|
|
187
|
+
log(level, component, message, context, data) {
|
|
188
|
+
if (level < this.getLevel()) return;
|
|
189
|
+
this.ensureLogFileInitialized();
|
|
190
|
+
const timestamp = this.formatTimestamp(/* @__PURE__ */ new Date());
|
|
191
|
+
const levelStr = LogLevel[level].padEnd(5);
|
|
192
|
+
const componentStr = component.padEnd(6);
|
|
193
|
+
let correlationStr = "";
|
|
194
|
+
if (context?.correlationId) {
|
|
195
|
+
correlationStr = `[${context.correlationId}] `;
|
|
196
|
+
} else if (context?.sessionId) {
|
|
197
|
+
correlationStr = `[session-${context.sessionId}] `;
|
|
198
|
+
}
|
|
199
|
+
let dataStr = "";
|
|
200
|
+
if (data !== void 0 && data !== null) {
|
|
201
|
+
if (data instanceof Error) {
|
|
202
|
+
dataStr = this.getLevel() === 0 /* DEBUG */ ? `
|
|
203
|
+
${data.message}
|
|
204
|
+
${data.stack}` : ` ${data.message}`;
|
|
205
|
+
} else if (this.getLevel() === 0 /* DEBUG */ && typeof data === "object") {
|
|
206
|
+
dataStr = "\n" + JSON.stringify(data, null, 2);
|
|
207
|
+
} else {
|
|
208
|
+
dataStr = " " + this.formatData(data);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
let contextStr = "";
|
|
212
|
+
if (context) {
|
|
213
|
+
const { sessionId, memorySessionId, correlationId, ...rest } = context;
|
|
214
|
+
if (Object.keys(rest).length > 0) {
|
|
215
|
+
const pairs = Object.entries(rest).map(([k, v]) => `${k}=${v}`);
|
|
216
|
+
contextStr = ` {${pairs.join(", ")}}`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const logLine = `[${timestamp}] [${levelStr}] [${componentStr}] ${correlationStr}${message}${contextStr}${dataStr}`;
|
|
220
|
+
if (this.logFilePath) {
|
|
221
|
+
try {
|
|
222
|
+
appendFileSync(this.logFilePath, logLine + "\n", "utf8");
|
|
223
|
+
} catch (error) {
|
|
224
|
+
process.stderr.write(`[LOGGER] Failed to write to log file: ${error}
|
|
225
|
+
`);
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
process.stderr.write(logLine + "\n");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Public logging methods
|
|
232
|
+
debug(component, message, context, data) {
|
|
233
|
+
this.log(0 /* DEBUG */, component, message, context, data);
|
|
234
|
+
}
|
|
235
|
+
info(component, message, context, data) {
|
|
236
|
+
this.log(1 /* INFO */, component, message, context, data);
|
|
237
|
+
}
|
|
238
|
+
warn(component, message, context, data) {
|
|
239
|
+
this.log(2 /* WARN */, component, message, context, data);
|
|
240
|
+
}
|
|
241
|
+
error(component, message, context, data) {
|
|
242
|
+
this.log(3 /* ERROR */, component, message, context, data);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Log data flow: input → processing
|
|
246
|
+
*/
|
|
247
|
+
dataIn(component, message, context, data) {
|
|
248
|
+
this.info(component, `\u2192 ${message}`, context, data);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Log data flow: processing → output
|
|
252
|
+
*/
|
|
253
|
+
dataOut(component, message, context, data) {
|
|
254
|
+
this.info(component, `\u2190 ${message}`, context, data);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Log successful completion
|
|
258
|
+
*/
|
|
259
|
+
success(component, message, context, data) {
|
|
260
|
+
this.info(component, `\u2713 ${message}`, context, data);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Log failure
|
|
264
|
+
*/
|
|
265
|
+
failure(component, message, context, data) {
|
|
266
|
+
this.error(component, `\u2717 ${message}`, context, data);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Log timing information
|
|
270
|
+
*/
|
|
271
|
+
timing(component, message, durationMs, context) {
|
|
272
|
+
this.info(component, `\u23F1 ${message}`, context, { duration: `${durationMs}ms` });
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Happy Path Error - logs when the expected "happy path" fails but we have a fallback
|
|
276
|
+
*/
|
|
277
|
+
happyPathError(component, message, context, data, fallback = "") {
|
|
278
|
+
const stack = new Error().stack || "";
|
|
279
|
+
const stackLines = stack.split("\n");
|
|
280
|
+
const callerLine = stackLines[2] || "";
|
|
281
|
+
const callerMatch = callerLine.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/);
|
|
282
|
+
const location = callerMatch ? `${callerMatch[1].split("/").pop()}:${callerMatch[2]}` : "unknown";
|
|
283
|
+
const enhancedContext = {
|
|
284
|
+
...context,
|
|
285
|
+
location
|
|
286
|
+
};
|
|
287
|
+
this.warn(component, `[HAPPY-PATH] ${message}`, enhancedContext, data);
|
|
288
|
+
return fallback;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
var logger = new Logger();
|
|
292
|
+
|
|
293
|
+
// src/shared/paths.ts
|
|
294
|
+
function getDirname() {
|
|
295
|
+
if (typeof __dirname !== "undefined") {
|
|
296
|
+
return __dirname;
|
|
297
|
+
}
|
|
298
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
299
|
+
}
|
|
300
|
+
var _dirname = getDirname();
|
|
301
|
+
var DATA_DIR = process.env.CONTEXTKIT_DATA_DIR || join2(homedir2(), ".contextkit");
|
|
302
|
+
var KIRO_CONFIG_DIR = process.env.KIRO_CONFIG_DIR || join2(homedir2(), ".kiro");
|
|
303
|
+
var PLUGIN_ROOT = join2(KIRO_CONFIG_DIR, "plugins", "contextkit");
|
|
304
|
+
var ARCHIVES_DIR = join2(DATA_DIR, "archives");
|
|
305
|
+
var LOGS_DIR = join2(DATA_DIR, "logs");
|
|
306
|
+
var TRASH_DIR = join2(DATA_DIR, "trash");
|
|
307
|
+
var BACKUPS_DIR = join2(DATA_DIR, "backups");
|
|
308
|
+
var MODES_DIR = join2(DATA_DIR, "modes");
|
|
309
|
+
var USER_SETTINGS_PATH = join2(DATA_DIR, "settings.json");
|
|
310
|
+
var DB_PATH = join2(DATA_DIR, "contextkit.db");
|
|
311
|
+
var VECTOR_DB_DIR = join2(DATA_DIR, "vector-db");
|
|
312
|
+
var OBSERVER_SESSIONS_DIR = join2(DATA_DIR, "observer-sessions");
|
|
313
|
+
var KIRO_SETTINGS_PATH = join2(KIRO_CONFIG_DIR, "settings.json");
|
|
314
|
+
var KIRO_CONTEXT_PATH = join2(KIRO_CONFIG_DIR, "context.md");
|
|
315
|
+
function ensureDir(dirPath) {
|
|
316
|
+
mkdirSync2(dirPath, { recursive: true });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/services/sqlite/Database.ts
|
|
320
|
+
var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
|
|
321
|
+
var SQLITE_CACHE_SIZE_PAGES = 1e4;
|
|
322
|
+
var dbInstance = null;
|
|
323
|
+
var ContextKitDatabase = class {
|
|
324
|
+
db;
|
|
325
|
+
constructor(dbPath = DB_PATH) {
|
|
326
|
+
if (dbPath !== ":memory:") {
|
|
327
|
+
ensureDir(DATA_DIR);
|
|
328
|
+
}
|
|
329
|
+
this.db = new Database(dbPath, { create: true, readwrite: true });
|
|
330
|
+
this.db.run("PRAGMA journal_mode = WAL");
|
|
331
|
+
this.db.run("PRAGMA synchronous = NORMAL");
|
|
332
|
+
this.db.run("PRAGMA foreign_keys = ON");
|
|
333
|
+
this.db.run("PRAGMA temp_store = memory");
|
|
334
|
+
this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
335
|
+
this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
336
|
+
const migrationRunner = new MigrationRunner(this.db);
|
|
337
|
+
migrationRunner.runAllMigrations();
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Close the database connection
|
|
341
|
+
*/
|
|
342
|
+
close() {
|
|
343
|
+
this.db.close();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
var DatabaseManager = class _DatabaseManager {
|
|
347
|
+
static instance;
|
|
348
|
+
db = null;
|
|
349
|
+
migrations = [];
|
|
350
|
+
static getInstance() {
|
|
351
|
+
if (!_DatabaseManager.instance) {
|
|
352
|
+
_DatabaseManager.instance = new _DatabaseManager();
|
|
353
|
+
}
|
|
354
|
+
return _DatabaseManager.instance;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Register a migration to be run during initialization
|
|
358
|
+
*/
|
|
359
|
+
registerMigration(migration) {
|
|
360
|
+
this.migrations.push(migration);
|
|
361
|
+
this.migrations.sort((a, b) => a.version - b.version);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Initialize database connection with optimized settings
|
|
365
|
+
*/
|
|
366
|
+
async initialize() {
|
|
367
|
+
if (this.db) {
|
|
368
|
+
return this.db;
|
|
369
|
+
}
|
|
370
|
+
ensureDir(DATA_DIR);
|
|
371
|
+
this.db = new Database(DB_PATH, { create: true, readwrite: true });
|
|
372
|
+
this.db.run("PRAGMA journal_mode = WAL");
|
|
373
|
+
this.db.run("PRAGMA synchronous = NORMAL");
|
|
374
|
+
this.db.run("PRAGMA foreign_keys = ON");
|
|
375
|
+
this.db.run("PRAGMA temp_store = memory");
|
|
376
|
+
this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
377
|
+
this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
378
|
+
this.initializeSchemaVersions();
|
|
379
|
+
await this.runMigrations();
|
|
380
|
+
dbInstance = this.db;
|
|
381
|
+
return this.db;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Get the current database connection
|
|
385
|
+
*/
|
|
386
|
+
getConnection() {
|
|
387
|
+
if (!this.db) {
|
|
388
|
+
throw new Error("Database not initialized. Call initialize() first.");
|
|
389
|
+
}
|
|
390
|
+
return this.db;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Execute a function within a transaction
|
|
394
|
+
*/
|
|
395
|
+
withTransaction(fn) {
|
|
396
|
+
const db = this.getConnection();
|
|
397
|
+
const transaction = db.transaction(fn);
|
|
398
|
+
return transaction(db);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Close the database connection
|
|
402
|
+
*/
|
|
403
|
+
close() {
|
|
404
|
+
if (this.db) {
|
|
405
|
+
this.db.close();
|
|
406
|
+
this.db = null;
|
|
407
|
+
dbInstance = null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Initialize the schema_versions table
|
|
412
|
+
*/
|
|
413
|
+
initializeSchemaVersions() {
|
|
414
|
+
if (!this.db) return;
|
|
415
|
+
this.db.run(`
|
|
416
|
+
CREATE TABLE IF NOT EXISTS schema_versions (
|
|
417
|
+
id INTEGER PRIMARY KEY,
|
|
418
|
+
version INTEGER UNIQUE NOT NULL,
|
|
419
|
+
applied_at TEXT NOT NULL
|
|
420
|
+
)
|
|
421
|
+
`);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Run all pending migrations
|
|
425
|
+
*/
|
|
426
|
+
async runMigrations() {
|
|
427
|
+
if (!this.db) return;
|
|
428
|
+
const query = this.db.query("SELECT version FROM schema_versions ORDER BY version");
|
|
429
|
+
const appliedVersions = query.all().map((row) => row.version);
|
|
430
|
+
const maxApplied = appliedVersions.length > 0 ? Math.max(...appliedVersions) : 0;
|
|
431
|
+
for (const migration of this.migrations) {
|
|
432
|
+
if (migration.version > maxApplied) {
|
|
433
|
+
logger.info("DB", `Applying migration ${migration.version}`);
|
|
434
|
+
const transaction = this.db.transaction(() => {
|
|
435
|
+
migration.up(this.db);
|
|
436
|
+
const insertQuery = this.db.query("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)");
|
|
437
|
+
insertQuery.run(migration.version, (/* @__PURE__ */ new Date()).toISOString());
|
|
438
|
+
});
|
|
439
|
+
transaction();
|
|
440
|
+
logger.info("DB", `Migration ${migration.version} applied successfully`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Get current schema version
|
|
446
|
+
*/
|
|
447
|
+
getCurrentVersion() {
|
|
448
|
+
if (!this.db) return 0;
|
|
449
|
+
const query = this.db.query("SELECT MAX(version) as version FROM schema_versions");
|
|
450
|
+
const result = query.get();
|
|
451
|
+
return result?.version || 0;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
var MigrationRunner = class {
|
|
455
|
+
db;
|
|
456
|
+
constructor(db) {
|
|
457
|
+
this.db = db;
|
|
458
|
+
}
|
|
459
|
+
runAllMigrations() {
|
|
460
|
+
this.db.run(`
|
|
461
|
+
CREATE TABLE IF NOT EXISTS schema_versions (
|
|
462
|
+
id INTEGER PRIMARY KEY,
|
|
463
|
+
version INTEGER UNIQUE NOT NULL,
|
|
464
|
+
applied_at TEXT NOT NULL
|
|
465
|
+
)
|
|
466
|
+
`);
|
|
467
|
+
const versionQuery = this.db.query("SELECT MAX(version) as version FROM schema_versions");
|
|
468
|
+
const result = versionQuery.get();
|
|
469
|
+
const currentVersion = result?.version || 0;
|
|
470
|
+
const migrations = this.getMigrations();
|
|
471
|
+
for (const migration of migrations) {
|
|
472
|
+
if (migration.version > currentVersion) {
|
|
473
|
+
logger.info("DB", `Applying migration ${migration.version}`);
|
|
474
|
+
const transaction = this.db.transaction(() => {
|
|
475
|
+
migration.up(this.db);
|
|
476
|
+
const insert = this.db.query("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)");
|
|
477
|
+
insert.run(migration.version, (/* @__PURE__ */ new Date()).toISOString());
|
|
478
|
+
});
|
|
479
|
+
transaction();
|
|
480
|
+
logger.info("DB", `Migration ${migration.version} applied successfully`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
getMigrations() {
|
|
485
|
+
return [
|
|
486
|
+
{
|
|
487
|
+
version: 1,
|
|
488
|
+
up: (db) => {
|
|
489
|
+
db.run(`
|
|
490
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
491
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
492
|
+
content_session_id TEXT NOT NULL UNIQUE,
|
|
493
|
+
project TEXT NOT NULL,
|
|
494
|
+
user_prompt TEXT NOT NULL,
|
|
495
|
+
memory_session_id TEXT,
|
|
496
|
+
status TEXT DEFAULT 'active',
|
|
497
|
+
started_at TEXT NOT NULL,
|
|
498
|
+
started_at_epoch INTEGER NOT NULL,
|
|
499
|
+
completed_at TEXT,
|
|
500
|
+
completed_at_epoch INTEGER
|
|
501
|
+
)
|
|
502
|
+
`);
|
|
503
|
+
db.run(`
|
|
504
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
505
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
506
|
+
memory_session_id TEXT NOT NULL,
|
|
507
|
+
project TEXT NOT NULL,
|
|
508
|
+
type TEXT NOT NULL,
|
|
509
|
+
title TEXT NOT NULL,
|
|
510
|
+
subtitle TEXT,
|
|
511
|
+
text TEXT,
|
|
512
|
+
narrative TEXT,
|
|
513
|
+
facts TEXT,
|
|
514
|
+
concepts TEXT,
|
|
515
|
+
files_read TEXT,
|
|
516
|
+
files_modified TEXT,
|
|
517
|
+
prompt_number INTEGER NOT NULL,
|
|
518
|
+
created_at TEXT NOT NULL,
|
|
519
|
+
created_at_epoch INTEGER NOT NULL
|
|
520
|
+
)
|
|
521
|
+
`);
|
|
522
|
+
db.run(`
|
|
523
|
+
CREATE TABLE IF NOT EXISTS summaries (
|
|
524
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
525
|
+
session_id TEXT NOT NULL,
|
|
526
|
+
project TEXT NOT NULL,
|
|
527
|
+
request TEXT,
|
|
528
|
+
investigated TEXT,
|
|
529
|
+
learned TEXT,
|
|
530
|
+
completed TEXT,
|
|
531
|
+
next_steps TEXT,
|
|
532
|
+
notes TEXT,
|
|
533
|
+
created_at TEXT NOT NULL,
|
|
534
|
+
created_at_epoch INTEGER NOT NULL
|
|
535
|
+
)
|
|
536
|
+
`);
|
|
537
|
+
db.run(`
|
|
538
|
+
CREATE TABLE IF NOT EXISTS prompts (
|
|
539
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
540
|
+
content_session_id TEXT NOT NULL,
|
|
541
|
+
project TEXT NOT NULL,
|
|
542
|
+
prompt_number INTEGER NOT NULL,
|
|
543
|
+
prompt_text TEXT NOT NULL,
|
|
544
|
+
created_at TEXT NOT NULL,
|
|
545
|
+
created_at_epoch INTEGER NOT NULL
|
|
546
|
+
)
|
|
547
|
+
`);
|
|
548
|
+
db.run(`
|
|
549
|
+
CREATE TABLE IF NOT EXISTS pending_messages (
|
|
550
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
551
|
+
content_session_id TEXT NOT NULL,
|
|
552
|
+
type TEXT NOT NULL,
|
|
553
|
+
data TEXT NOT NULL,
|
|
554
|
+
created_at TEXT NOT NULL,
|
|
555
|
+
created_at_epoch INTEGER NOT NULL
|
|
556
|
+
)
|
|
557
|
+
`);
|
|
558
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project)");
|
|
559
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_project ON observations(project)");
|
|
560
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(memory_session_id)");
|
|
561
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_summaries_session ON summaries(session_id)");
|
|
562
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_prompts_session ON prompts(content_session_id)");
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
version: 2,
|
|
567
|
+
up: (db) => {
|
|
568
|
+
db.run(`
|
|
569
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
|
|
570
|
+
title, text, narrative, concepts,
|
|
571
|
+
content='observations',
|
|
572
|
+
content_rowid='id'
|
|
573
|
+
)
|
|
574
|
+
`);
|
|
575
|
+
db.run(`
|
|
576
|
+
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
577
|
+
INSERT INTO observations_fts(rowid, title, text, narrative, concepts)
|
|
578
|
+
VALUES (new.id, new.title, new.text, new.narrative, new.concepts);
|
|
579
|
+
END
|
|
580
|
+
`);
|
|
581
|
+
db.run(`
|
|
582
|
+
CREATE TRIGGER IF NOT EXISTS observations_ad AFTER DELETE ON observations BEGIN
|
|
583
|
+
INSERT INTO observations_fts(observations_fts, rowid, title, text, narrative, concepts)
|
|
584
|
+
VALUES ('delete', old.id, old.title, old.text, old.narrative, old.concepts);
|
|
585
|
+
END
|
|
586
|
+
`);
|
|
587
|
+
db.run(`
|
|
588
|
+
CREATE TRIGGER IF NOT EXISTS observations_au AFTER UPDATE ON observations BEGIN
|
|
589
|
+
INSERT INTO observations_fts(observations_fts, rowid, title, text, narrative, concepts)
|
|
590
|
+
VALUES ('delete', old.id, old.title, old.text, old.narrative, old.concepts);
|
|
591
|
+
INSERT INTO observations_fts(rowid, title, text, narrative, concepts)
|
|
592
|
+
VALUES (new.id, new.title, new.text, new.narrative, new.concepts);
|
|
593
|
+
END
|
|
594
|
+
`);
|
|
595
|
+
db.run(`
|
|
596
|
+
INSERT INTO observations_fts(rowid, title, text, narrative, concepts)
|
|
597
|
+
SELECT id, title, text, narrative, concepts FROM observations
|
|
598
|
+
`);
|
|
599
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_type ON observations(type)");
|
|
600
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_epoch ON observations(created_at_epoch)");
|
|
601
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project ON summaries(project)");
|
|
602
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_summaries_epoch ON summaries(created_at_epoch)");
|
|
603
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project ON prompts(project)");
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
];
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
function getDatabase() {
|
|
610
|
+
if (!dbInstance) {
|
|
611
|
+
throw new Error("Database not initialized. Call DatabaseManager.getInstance().initialize() first.");
|
|
612
|
+
}
|
|
613
|
+
return dbInstance;
|
|
614
|
+
}
|
|
615
|
+
async function initializeDatabase() {
|
|
616
|
+
const manager = DatabaseManager.getInstance();
|
|
617
|
+
return await manager.initialize();
|
|
618
|
+
}
|
|
619
|
+
export {
|
|
620
|
+
ContextKitDatabase,
|
|
621
|
+
Database,
|
|
622
|
+
DatabaseManager,
|
|
623
|
+
getDatabase,
|
|
624
|
+
initializeDatabase
|
|
625
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/services/sqlite/Observations.ts
|
|
2
|
+
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
|
|
3
|
+
const now = /* @__PURE__ */ new Date();
|
|
4
|
+
const result = db.run(
|
|
5
|
+
`INSERT INTO observations
|
|
6
|
+
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
|
7
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
8
|
+
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
|
|
9
|
+
);
|
|
10
|
+
return Number(result.lastInsertRowid);
|
|
11
|
+
}
|
|
12
|
+
function getObservationsBySession(db, memorySessionId) {
|
|
13
|
+
const query = db.query(
|
|
14
|
+
"SELECT * FROM observations WHERE memory_session_id = ? ORDER BY prompt_number ASC"
|
|
15
|
+
);
|
|
16
|
+
return query.all(memorySessionId);
|
|
17
|
+
}
|
|
18
|
+
function getObservationsByProject(db, project, limit = 100) {
|
|
19
|
+
const query = db.query(
|
|
20
|
+
"SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
21
|
+
);
|
|
22
|
+
return query.all(project, limit);
|
|
23
|
+
}
|
|
24
|
+
function searchObservations(db, searchTerm, project) {
|
|
25
|
+
const sql = project ? `SELECT * FROM observations
|
|
26
|
+
WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
|
|
27
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
28
|
+
WHERE title LIKE ? OR text LIKE ? OR narrative LIKE ?
|
|
29
|
+
ORDER BY created_at_epoch DESC`;
|
|
30
|
+
const pattern = `%${searchTerm}%`;
|
|
31
|
+
const query = db.query(sql);
|
|
32
|
+
if (project) {
|
|
33
|
+
return query.all(project, pattern, pattern, pattern);
|
|
34
|
+
}
|
|
35
|
+
return query.all(pattern, pattern, pattern);
|
|
36
|
+
}
|
|
37
|
+
function deleteObservation(db, id) {
|
|
38
|
+
db.run("DELETE FROM observations WHERE id = ?", [id]);
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
createObservation,
|
|
42
|
+
deleteObservation,
|
|
43
|
+
getObservationsByProject,
|
|
44
|
+
getObservationsBySession,
|
|
45
|
+
searchObservations
|
|
46
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/services/sqlite/Prompts.ts
|
|
2
|
+
function createPrompt(db, contentSessionId, project, promptNumber, promptText) {
|
|
3
|
+
const now = /* @__PURE__ */ new Date();
|
|
4
|
+
const result = db.run(
|
|
5
|
+
`INSERT INTO prompts
|
|
6
|
+
(content_session_id, project, prompt_number, prompt_text, created_at, created_at_epoch)
|
|
7
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
8
|
+
[contentSessionId, project, promptNumber, promptText, now.toISOString(), now.getTime()]
|
|
9
|
+
);
|
|
10
|
+
return Number(result.lastInsertRowid);
|
|
11
|
+
}
|
|
12
|
+
function getPromptsBySession(db, contentSessionId) {
|
|
13
|
+
const query = db.query(
|
|
14
|
+
"SELECT * FROM prompts WHERE content_session_id = ? ORDER BY prompt_number ASC"
|
|
15
|
+
);
|
|
16
|
+
return query.all(contentSessionId);
|
|
17
|
+
}
|
|
18
|
+
function getPromptsByProject(db, project, limit = 100) {
|
|
19
|
+
const query = db.query(
|
|
20
|
+
"SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
21
|
+
);
|
|
22
|
+
return query.all(project, limit);
|
|
23
|
+
}
|
|
24
|
+
function getLatestPrompt(db, contentSessionId) {
|
|
25
|
+
const query = db.query(
|
|
26
|
+
"SELECT * FROM prompts WHERE content_session_id = ? ORDER BY prompt_number DESC LIMIT 1"
|
|
27
|
+
);
|
|
28
|
+
return query.get(contentSessionId);
|
|
29
|
+
}
|
|
30
|
+
function deletePrompt(db, id) {
|
|
31
|
+
db.run("DELETE FROM prompts WHERE id = ?", [id]);
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
createPrompt,
|
|
35
|
+
deletePrompt,
|
|
36
|
+
getLatestPrompt,
|
|
37
|
+
getPromptsByProject,
|
|
38
|
+
getPromptsBySession
|
|
39
|
+
};
|