kiro-memory 1.6.0 → 1.7.1
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/README.md +105 -99
- package/package.json +14 -7
- package/plugin/dist/cli/contextkit.js +2661 -497
- package/plugin/dist/hooks/agentSpawn.js +1455 -189
- package/plugin/dist/hooks/kiro-hooks.js +1389 -156
- package/plugin/dist/hooks/postToolUse.js +1451 -174
- package/plugin/dist/hooks/stop.js +1426 -170
- package/plugin/dist/hooks/userPromptSubmit.js +1418 -170
- package/plugin/dist/index.js +1406 -172
- package/plugin/dist/sdk/index.js +1389 -155
- package/plugin/dist/servers/mcp-server.js +203 -2
- package/plugin/dist/services/search/EmbeddingService.js +363 -0
- package/plugin/dist/services/search/HybridSearch.js +703 -151
- package/plugin/dist/services/search/ScoringEngine.js +75 -0
- package/plugin/dist/services/search/VectorSearch.js +512 -0
- package/plugin/dist/services/search/index.js +776 -64
- package/plugin/dist/services/sqlite/Database.js +49 -0
- package/plugin/dist/services/sqlite/Observations.js +70 -6
- package/plugin/dist/services/sqlite/Search.js +92 -8
- package/plugin/dist/services/sqlite/Summaries.js +8 -5
- package/plugin/dist/services/sqlite/index.js +384 -18
- package/plugin/dist/types/worker-types.js +6 -0
- package/plugin/dist/viewer.js +369 -69
- package/plugin/dist/worker-service.js +1496 -148
|
@@ -1,11 +1,753 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';const require = createRequire(import.meta.url);
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
5
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
6
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
7
|
}) : x)(function(x) {
|
|
6
8
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
9
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
10
|
});
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/utils/logger.ts
|
|
20
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
import { homedir } from "os";
|
|
23
|
+
var LogLevel, DEFAULT_DATA_DIR, Logger, logger;
|
|
24
|
+
var init_logger = __esm({
|
|
25
|
+
"src/utils/logger.ts"() {
|
|
26
|
+
"use strict";
|
|
27
|
+
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
28
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
29
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
30
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
31
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
32
|
+
LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
|
|
33
|
+
return LogLevel2;
|
|
34
|
+
})(LogLevel || {});
|
|
35
|
+
DEFAULT_DATA_DIR = join(homedir(), ".contextkit");
|
|
36
|
+
Logger = class {
|
|
37
|
+
level = null;
|
|
38
|
+
useColor;
|
|
39
|
+
logFilePath = null;
|
|
40
|
+
logFileInitialized = false;
|
|
41
|
+
constructor() {
|
|
42
|
+
this.useColor = process.stdout.isTTY ?? false;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Initialize log file path and ensure directory exists (lazy initialization)
|
|
46
|
+
*/
|
|
47
|
+
ensureLogFileInitialized() {
|
|
48
|
+
if (this.logFileInitialized) return;
|
|
49
|
+
this.logFileInitialized = true;
|
|
50
|
+
try {
|
|
51
|
+
const logsDir = join(DEFAULT_DATA_DIR, "logs");
|
|
52
|
+
if (!existsSync(logsDir)) {
|
|
53
|
+
mkdirSync(logsDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
56
|
+
this.logFilePath = join(logsDir, `kiro-memory-${date}.log`);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error("[LOGGER] Failed to initialize log file:", error);
|
|
59
|
+
this.logFilePath = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Lazy-load log level from settings file
|
|
64
|
+
*/
|
|
65
|
+
getLevel() {
|
|
66
|
+
if (this.level === null) {
|
|
67
|
+
try {
|
|
68
|
+
const settingsPath = join(DEFAULT_DATA_DIR, "settings.json");
|
|
69
|
+
if (existsSync(settingsPath)) {
|
|
70
|
+
const settingsData = readFileSync(settingsPath, "utf-8");
|
|
71
|
+
const settings = JSON.parse(settingsData);
|
|
72
|
+
const envLevel = (settings.KIRO_MEMORY_LOG_LEVEL || settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
|
|
73
|
+
this.level = LogLevel[envLevel] ?? 1 /* INFO */;
|
|
74
|
+
} else {
|
|
75
|
+
this.level = 1 /* INFO */;
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.level = 1 /* INFO */;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return this.level;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create correlation ID for tracking an observation through the pipeline
|
|
85
|
+
*/
|
|
86
|
+
correlationId(sessionId, observationNum) {
|
|
87
|
+
return `obs-${sessionId}-${observationNum}`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create session correlation ID
|
|
91
|
+
*/
|
|
92
|
+
sessionId(sessionId) {
|
|
93
|
+
return `session-${sessionId}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Format data for logging - create compact summaries instead of full dumps
|
|
97
|
+
*/
|
|
98
|
+
formatData(data) {
|
|
99
|
+
if (data === null || data === void 0) return "";
|
|
100
|
+
if (typeof data === "string") return data;
|
|
101
|
+
if (typeof data === "number") return data.toString();
|
|
102
|
+
if (typeof data === "boolean") return data.toString();
|
|
103
|
+
if (typeof data === "object") {
|
|
104
|
+
if (data instanceof Error) {
|
|
105
|
+
return this.getLevel() === 0 /* DEBUG */ ? `${data.message}
|
|
106
|
+
${data.stack}` : data.message;
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(data)) {
|
|
109
|
+
return `[${data.length} items]`;
|
|
110
|
+
}
|
|
111
|
+
const keys = Object.keys(data);
|
|
112
|
+
if (keys.length === 0) return "{}";
|
|
113
|
+
if (keys.length <= 3) {
|
|
114
|
+
return JSON.stringify(data);
|
|
115
|
+
}
|
|
116
|
+
return `{${keys.length} keys: ${keys.slice(0, 3).join(", ")}...}`;
|
|
117
|
+
}
|
|
118
|
+
return String(data);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Format timestamp in local timezone (YYYY-MM-DD HH:MM:SS.mmm)
|
|
122
|
+
*/
|
|
123
|
+
formatTimestamp(date) {
|
|
124
|
+
const year = date.getFullYear();
|
|
125
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
126
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
127
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
128
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
129
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
130
|
+
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
131
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Core logging method
|
|
135
|
+
*/
|
|
136
|
+
log(level, component, message, context, data) {
|
|
137
|
+
if (level < this.getLevel()) return;
|
|
138
|
+
this.ensureLogFileInitialized();
|
|
139
|
+
const timestamp = this.formatTimestamp(/* @__PURE__ */ new Date());
|
|
140
|
+
const levelStr = LogLevel[level].padEnd(5);
|
|
141
|
+
const componentStr = component.padEnd(6);
|
|
142
|
+
let correlationStr = "";
|
|
143
|
+
if (context?.correlationId) {
|
|
144
|
+
correlationStr = `[${context.correlationId}] `;
|
|
145
|
+
} else if (context?.sessionId) {
|
|
146
|
+
correlationStr = `[session-${context.sessionId}] `;
|
|
147
|
+
}
|
|
148
|
+
let dataStr = "";
|
|
149
|
+
if (data !== void 0 && data !== null) {
|
|
150
|
+
if (data instanceof Error) {
|
|
151
|
+
dataStr = this.getLevel() === 0 /* DEBUG */ ? `
|
|
152
|
+
${data.message}
|
|
153
|
+
${data.stack}` : ` ${data.message}`;
|
|
154
|
+
} else if (this.getLevel() === 0 /* DEBUG */ && typeof data === "object") {
|
|
155
|
+
dataStr = "\n" + JSON.stringify(data, null, 2);
|
|
156
|
+
} else {
|
|
157
|
+
dataStr = " " + this.formatData(data);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
let contextStr = "";
|
|
161
|
+
if (context) {
|
|
162
|
+
const { sessionId, memorySessionId, correlationId, ...rest } = context;
|
|
163
|
+
if (Object.keys(rest).length > 0) {
|
|
164
|
+
const pairs = Object.entries(rest).map(([k, v]) => `${k}=${v}`);
|
|
165
|
+
contextStr = ` {${pairs.join(", ")}}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const logLine = `[${timestamp}] [${levelStr}] [${componentStr}] ${correlationStr}${message}${contextStr}${dataStr}`;
|
|
169
|
+
if (this.logFilePath) {
|
|
170
|
+
try {
|
|
171
|
+
appendFileSync(this.logFilePath, logLine + "\n", "utf8");
|
|
172
|
+
} catch (error) {
|
|
173
|
+
process.stderr.write(`[LOGGER] Failed to write to log file: ${error}
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
process.stderr.write(logLine + "\n");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Public logging methods
|
|
181
|
+
debug(component, message, context, data) {
|
|
182
|
+
this.log(0 /* DEBUG */, component, message, context, data);
|
|
183
|
+
}
|
|
184
|
+
info(component, message, context, data) {
|
|
185
|
+
this.log(1 /* INFO */, component, message, context, data);
|
|
186
|
+
}
|
|
187
|
+
warn(component, message, context, data) {
|
|
188
|
+
this.log(2 /* WARN */, component, message, context, data);
|
|
189
|
+
}
|
|
190
|
+
error(component, message, context, data) {
|
|
191
|
+
this.log(3 /* ERROR */, component, message, context, data);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Log data flow: input → processing
|
|
195
|
+
*/
|
|
196
|
+
dataIn(component, message, context, data) {
|
|
197
|
+
this.info(component, `\u2192 ${message}`, context, data);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Log data flow: processing → output
|
|
201
|
+
*/
|
|
202
|
+
dataOut(component, message, context, data) {
|
|
203
|
+
this.info(component, `\u2190 ${message}`, context, data);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Log successful completion
|
|
207
|
+
*/
|
|
208
|
+
success(component, message, context, data) {
|
|
209
|
+
this.info(component, `\u2713 ${message}`, context, data);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Log failure
|
|
213
|
+
*/
|
|
214
|
+
failure(component, message, context, data) {
|
|
215
|
+
this.error(component, `\u2717 ${message}`, context, data);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Log timing information
|
|
219
|
+
*/
|
|
220
|
+
timing(component, message, durationMs, context) {
|
|
221
|
+
this.info(component, `\u23F1 ${message}`, context, { duration: `${durationMs}ms` });
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Happy Path Error - logs when the expected "happy path" fails but we have a fallback
|
|
225
|
+
*/
|
|
226
|
+
happyPathError(component, message, context, data, fallback = "") {
|
|
227
|
+
const stack = new Error().stack || "";
|
|
228
|
+
const stackLines = stack.split("\n");
|
|
229
|
+
const callerLine = stackLines[2] || "";
|
|
230
|
+
const callerMatch = callerLine.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/);
|
|
231
|
+
const location = callerMatch ? `${callerMatch[1].split("/").pop()}:${callerMatch[2]}` : "unknown";
|
|
232
|
+
const enhancedContext = {
|
|
233
|
+
...context,
|
|
234
|
+
location
|
|
235
|
+
};
|
|
236
|
+
this.warn(component, `[HAPPY-PATH] ${message}`, enhancedContext, data);
|
|
237
|
+
return fallback;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
logger = new Logger();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// src/services/sqlite/Observations.ts
|
|
245
|
+
var Observations_exports = {};
|
|
246
|
+
__export(Observations_exports, {
|
|
247
|
+
consolidateObservations: () => consolidateObservations,
|
|
248
|
+
createObservation: () => createObservation,
|
|
249
|
+
deleteObservation: () => deleteObservation,
|
|
250
|
+
getObservationsByProject: () => getObservationsByProject,
|
|
251
|
+
getObservationsBySession: () => getObservationsBySession,
|
|
252
|
+
searchObservations: () => searchObservations,
|
|
253
|
+
updateLastAccessed: () => updateLastAccessed
|
|
254
|
+
});
|
|
255
|
+
function escapeLikePattern(input) {
|
|
256
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
257
|
+
}
|
|
258
|
+
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber) {
|
|
259
|
+
const now = /* @__PURE__ */ new Date();
|
|
260
|
+
const result = db.run(
|
|
261
|
+
`INSERT INTO observations
|
|
262
|
+
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
|
263
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
264
|
+
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
|
|
265
|
+
);
|
|
266
|
+
return Number(result.lastInsertRowid);
|
|
267
|
+
}
|
|
268
|
+
function getObservationsBySession(db, memorySessionId) {
|
|
269
|
+
const query = db.query(
|
|
270
|
+
"SELECT * FROM observations WHERE memory_session_id = ? ORDER BY prompt_number ASC"
|
|
271
|
+
);
|
|
272
|
+
return query.all(memorySessionId);
|
|
273
|
+
}
|
|
274
|
+
function getObservationsByProject(db, project, limit = 100) {
|
|
275
|
+
const query = db.query(
|
|
276
|
+
"SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
277
|
+
);
|
|
278
|
+
return query.all(project, limit);
|
|
279
|
+
}
|
|
280
|
+
function searchObservations(db, searchTerm, project) {
|
|
281
|
+
const sql = project ? `SELECT * FROM observations
|
|
282
|
+
WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
|
|
283
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
284
|
+
WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
|
|
285
|
+
ORDER BY created_at_epoch DESC`;
|
|
286
|
+
const pattern = `%${escapeLikePattern(searchTerm)}%`;
|
|
287
|
+
const query = db.query(sql);
|
|
288
|
+
if (project) {
|
|
289
|
+
return query.all(project, pattern, pattern, pattern);
|
|
290
|
+
}
|
|
291
|
+
return query.all(pattern, pattern, pattern);
|
|
292
|
+
}
|
|
293
|
+
function deleteObservation(db, id) {
|
|
294
|
+
db.run("DELETE FROM observations WHERE id = ?", [id]);
|
|
295
|
+
}
|
|
296
|
+
function updateLastAccessed(db, ids) {
|
|
297
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
298
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
299
|
+
if (validIds.length === 0) return;
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
302
|
+
db.run(
|
|
303
|
+
`UPDATE observations SET last_accessed_epoch = ? WHERE id IN (${placeholders})`,
|
|
304
|
+
[now, ...validIds]
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
function consolidateObservations(db, project, options = {}) {
|
|
308
|
+
const minGroupSize = options.minGroupSize || 3;
|
|
309
|
+
const groups = db.query(`
|
|
310
|
+
SELECT type, files_modified, COUNT(*) as cnt, GROUP_CONCAT(id) as ids
|
|
311
|
+
FROM observations
|
|
312
|
+
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
313
|
+
GROUP BY type, files_modified
|
|
314
|
+
HAVING cnt >= ?
|
|
315
|
+
ORDER BY cnt DESC
|
|
316
|
+
`).all(project, minGroupSize);
|
|
317
|
+
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
318
|
+
let totalMerged = 0;
|
|
319
|
+
let totalRemoved = 0;
|
|
320
|
+
for (const group of groups) {
|
|
321
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
322
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
323
|
+
const observations = db.query(
|
|
324
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
325
|
+
).all(...obsIds);
|
|
326
|
+
if (observations.length < minGroupSize) continue;
|
|
327
|
+
if (options.dryRun) {
|
|
328
|
+
totalMerged += 1;
|
|
329
|
+
totalRemoved += observations.length - 1;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const keeper = observations[0];
|
|
333
|
+
const others = observations.slice(1);
|
|
334
|
+
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
335
|
+
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
336
|
+
for (const obs of others) {
|
|
337
|
+
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
338
|
+
uniqueTexts.add(obs.text);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
342
|
+
db.run(
|
|
343
|
+
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
344
|
+
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
345
|
+
);
|
|
346
|
+
const removeIds = others.map((o) => o.id);
|
|
347
|
+
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
348
|
+
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
349
|
+
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
350
|
+
totalMerged += 1;
|
|
351
|
+
totalRemoved += removeIds.length;
|
|
352
|
+
}
|
|
353
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
354
|
+
}
|
|
355
|
+
var init_Observations = __esm({
|
|
356
|
+
"src/services/sqlite/Observations.ts"() {
|
|
357
|
+
"use strict";
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// src/services/sqlite/Search.ts
|
|
362
|
+
var Search_exports = {};
|
|
363
|
+
__export(Search_exports, {
|
|
364
|
+
getObservationsByIds: () => getObservationsByIds,
|
|
365
|
+
getProjectStats: () => getProjectStats,
|
|
366
|
+
getStaleObservations: () => getStaleObservations,
|
|
367
|
+
getTimeline: () => getTimeline,
|
|
368
|
+
markObservationsStale: () => markObservationsStale,
|
|
369
|
+
searchObservationsFTS: () => searchObservationsFTS,
|
|
370
|
+
searchObservationsFTSWithRank: () => searchObservationsFTSWithRank,
|
|
371
|
+
searchObservationsLIKE: () => searchObservationsLIKE,
|
|
372
|
+
searchSummariesFiltered: () => searchSummariesFiltered
|
|
373
|
+
});
|
|
374
|
+
import { existsSync as existsSync3, statSync } from "fs";
|
|
375
|
+
function escapeLikePattern3(input) {
|
|
376
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
377
|
+
}
|
|
378
|
+
function sanitizeFTS5Query(query) {
|
|
379
|
+
const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
|
|
380
|
+
const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
381
|
+
return terms.join(" ");
|
|
382
|
+
}
|
|
383
|
+
function searchObservationsFTS(db, query, filters = {}) {
|
|
384
|
+
const limit = filters.limit || 50;
|
|
385
|
+
try {
|
|
386
|
+
const safeQuery = sanitizeFTS5Query(query);
|
|
387
|
+
if (!safeQuery) return searchObservationsLIKE(db, query, filters);
|
|
388
|
+
let sql = `
|
|
389
|
+
SELECT o.* FROM observations o
|
|
390
|
+
JOIN observations_fts fts ON o.id = fts.rowid
|
|
391
|
+
WHERE observations_fts MATCH ?
|
|
392
|
+
`;
|
|
393
|
+
const params = [safeQuery];
|
|
394
|
+
if (filters.project) {
|
|
395
|
+
sql += " AND o.project = ?";
|
|
396
|
+
params.push(filters.project);
|
|
397
|
+
}
|
|
398
|
+
if (filters.type) {
|
|
399
|
+
sql += " AND o.type = ?";
|
|
400
|
+
params.push(filters.type);
|
|
401
|
+
}
|
|
402
|
+
if (filters.dateStart) {
|
|
403
|
+
sql += " AND o.created_at_epoch >= ?";
|
|
404
|
+
params.push(filters.dateStart);
|
|
405
|
+
}
|
|
406
|
+
if (filters.dateEnd) {
|
|
407
|
+
sql += " AND o.created_at_epoch <= ?";
|
|
408
|
+
params.push(filters.dateEnd);
|
|
409
|
+
}
|
|
410
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
411
|
+
params.push(limit);
|
|
412
|
+
const stmt = db.query(sql);
|
|
413
|
+
return stmt.all(...params);
|
|
414
|
+
} catch {
|
|
415
|
+
return searchObservationsLIKE(db, query, filters);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
419
|
+
const limit = filters.limit || 50;
|
|
420
|
+
try {
|
|
421
|
+
const safeQuery = sanitizeFTS5Query(query);
|
|
422
|
+
if (!safeQuery) return [];
|
|
423
|
+
let sql = `
|
|
424
|
+
SELECT o.*, rank as fts5_rank FROM observations o
|
|
425
|
+
JOIN observations_fts fts ON o.id = fts.rowid
|
|
426
|
+
WHERE observations_fts MATCH ?
|
|
427
|
+
`;
|
|
428
|
+
const params = [safeQuery];
|
|
429
|
+
if (filters.project) {
|
|
430
|
+
sql += " AND o.project = ?";
|
|
431
|
+
params.push(filters.project);
|
|
432
|
+
}
|
|
433
|
+
if (filters.type) {
|
|
434
|
+
sql += " AND o.type = ?";
|
|
435
|
+
params.push(filters.type);
|
|
436
|
+
}
|
|
437
|
+
if (filters.dateStart) {
|
|
438
|
+
sql += " AND o.created_at_epoch >= ?";
|
|
439
|
+
params.push(filters.dateStart);
|
|
440
|
+
}
|
|
441
|
+
if (filters.dateEnd) {
|
|
442
|
+
sql += " AND o.created_at_epoch <= ?";
|
|
443
|
+
params.push(filters.dateEnd);
|
|
444
|
+
}
|
|
445
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
446
|
+
params.push(limit);
|
|
447
|
+
const stmt = db.query(sql);
|
|
448
|
+
return stmt.all(...params);
|
|
449
|
+
} catch {
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function searchObservationsLIKE(db, query, filters = {}) {
|
|
454
|
+
const limit = filters.limit || 50;
|
|
455
|
+
const pattern = `%${escapeLikePattern3(query)}%`;
|
|
456
|
+
let sql = `
|
|
457
|
+
SELECT * FROM observations
|
|
458
|
+
WHERE (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\' OR concepts LIKE ? ESCAPE '\\')
|
|
459
|
+
`;
|
|
460
|
+
const params = [pattern, pattern, pattern, pattern];
|
|
461
|
+
if (filters.project) {
|
|
462
|
+
sql += " AND project = ?";
|
|
463
|
+
params.push(filters.project);
|
|
464
|
+
}
|
|
465
|
+
if (filters.type) {
|
|
466
|
+
sql += " AND type = ?";
|
|
467
|
+
params.push(filters.type);
|
|
468
|
+
}
|
|
469
|
+
if (filters.dateStart) {
|
|
470
|
+
sql += " AND created_at_epoch >= ?";
|
|
471
|
+
params.push(filters.dateStart);
|
|
472
|
+
}
|
|
473
|
+
if (filters.dateEnd) {
|
|
474
|
+
sql += " AND created_at_epoch <= ?";
|
|
475
|
+
params.push(filters.dateEnd);
|
|
476
|
+
}
|
|
477
|
+
sql += " ORDER BY created_at_epoch DESC LIMIT ?";
|
|
478
|
+
params.push(limit);
|
|
479
|
+
const stmt = db.query(sql);
|
|
480
|
+
return stmt.all(...params);
|
|
481
|
+
}
|
|
482
|
+
function searchSummariesFiltered(db, query, filters = {}) {
|
|
483
|
+
const limit = filters.limit || 20;
|
|
484
|
+
const pattern = `%${escapeLikePattern3(query)}%`;
|
|
485
|
+
let sql = `
|
|
486
|
+
SELECT * FROM summaries
|
|
487
|
+
WHERE (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\' OR next_steps LIKE ? ESCAPE '\\')
|
|
488
|
+
`;
|
|
489
|
+
const params = [pattern, pattern, pattern, pattern, pattern];
|
|
490
|
+
if (filters.project) {
|
|
491
|
+
sql += " AND project = ?";
|
|
492
|
+
params.push(filters.project);
|
|
493
|
+
}
|
|
494
|
+
if (filters.dateStart) {
|
|
495
|
+
sql += " AND created_at_epoch >= ?";
|
|
496
|
+
params.push(filters.dateStart);
|
|
497
|
+
}
|
|
498
|
+
if (filters.dateEnd) {
|
|
499
|
+
sql += " AND created_at_epoch <= ?";
|
|
500
|
+
params.push(filters.dateEnd);
|
|
501
|
+
}
|
|
502
|
+
sql += " ORDER BY created_at_epoch DESC LIMIT ?";
|
|
503
|
+
params.push(limit);
|
|
504
|
+
const stmt = db.query(sql);
|
|
505
|
+
return stmt.all(...params);
|
|
506
|
+
}
|
|
507
|
+
function getObservationsByIds(db, ids) {
|
|
508
|
+
if (!Array.isArray(ids) || ids.length === 0) return [];
|
|
509
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
510
|
+
if (validIds.length === 0) return [];
|
|
511
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
512
|
+
const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
|
|
513
|
+
const stmt = db.query(sql);
|
|
514
|
+
return stmt.all(...validIds);
|
|
515
|
+
}
|
|
516
|
+
function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
517
|
+
const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
|
|
518
|
+
const anchor = anchorStmt.get(anchorId);
|
|
519
|
+
if (!anchor) return [];
|
|
520
|
+
const anchorEpoch = anchor.created_at_epoch;
|
|
521
|
+
const beforeStmt = db.query(`
|
|
522
|
+
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
523
|
+
FROM observations
|
|
524
|
+
WHERE created_at_epoch < ?
|
|
525
|
+
ORDER BY created_at_epoch DESC
|
|
526
|
+
LIMIT ?
|
|
527
|
+
`);
|
|
528
|
+
const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
|
|
529
|
+
const selfStmt = db.query(`
|
|
530
|
+
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
531
|
+
FROM observations WHERE id = ?
|
|
532
|
+
`);
|
|
533
|
+
const self = selfStmt.all(anchorId);
|
|
534
|
+
const afterStmt = db.query(`
|
|
535
|
+
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
536
|
+
FROM observations
|
|
537
|
+
WHERE created_at_epoch > ?
|
|
538
|
+
ORDER BY created_at_epoch ASC
|
|
539
|
+
LIMIT ?
|
|
540
|
+
`);
|
|
541
|
+
const after = afterStmt.all(anchorEpoch, depthAfter);
|
|
542
|
+
return [...before, ...self, ...after];
|
|
543
|
+
}
|
|
544
|
+
function getProjectStats(db, project) {
|
|
545
|
+
const obsStmt = db.query("SELECT COUNT(*) as count FROM observations WHERE project = ?");
|
|
546
|
+
const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
|
|
547
|
+
const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
|
|
548
|
+
const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
|
|
549
|
+
return {
|
|
550
|
+
observations: obsStmt.get(project)?.count || 0,
|
|
551
|
+
summaries: sumStmt.get(project)?.count || 0,
|
|
552
|
+
sessions: sesStmt.get(project)?.count || 0,
|
|
553
|
+
prompts: prmStmt.get(project)?.count || 0
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function getStaleObservations(db, project) {
|
|
557
|
+
const rows = db.query(`
|
|
558
|
+
SELECT * FROM observations
|
|
559
|
+
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
560
|
+
ORDER BY created_at_epoch DESC
|
|
561
|
+
LIMIT 500
|
|
562
|
+
`).all(project);
|
|
563
|
+
const staleObs = [];
|
|
564
|
+
for (const obs of rows) {
|
|
565
|
+
if (!obs.files_modified) continue;
|
|
566
|
+
const files = obs.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
|
|
567
|
+
let isStale = false;
|
|
568
|
+
for (const filepath of files) {
|
|
569
|
+
try {
|
|
570
|
+
if (!existsSync3(filepath)) continue;
|
|
571
|
+
const stat = statSync(filepath);
|
|
572
|
+
if (stat.mtimeMs > obs.created_at_epoch) {
|
|
573
|
+
isStale = true;
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (isStale) {
|
|
580
|
+
staleObs.push(obs);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return staleObs;
|
|
584
|
+
}
|
|
585
|
+
function markObservationsStale(db, ids, stale) {
|
|
586
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
587
|
+
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
588
|
+
if (validIds.length === 0) return;
|
|
589
|
+
const placeholders = validIds.map(() => "?").join(",");
|
|
590
|
+
db.run(
|
|
591
|
+
`UPDATE observations SET is_stale = ? WHERE id IN (${placeholders})`,
|
|
592
|
+
[stale ? 1 : 0, ...validIds]
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
var init_Search = __esm({
|
|
596
|
+
"src/services/sqlite/Search.ts"() {
|
|
597
|
+
"use strict";
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// src/services/search/EmbeddingService.ts
|
|
602
|
+
var EmbeddingService_exports = {};
|
|
603
|
+
__export(EmbeddingService_exports, {
|
|
604
|
+
EmbeddingService: () => EmbeddingService,
|
|
605
|
+
getEmbeddingService: () => getEmbeddingService
|
|
606
|
+
});
|
|
607
|
+
function getEmbeddingService() {
|
|
608
|
+
if (!embeddingService) {
|
|
609
|
+
embeddingService = new EmbeddingService();
|
|
610
|
+
}
|
|
611
|
+
return embeddingService;
|
|
612
|
+
}
|
|
613
|
+
var EmbeddingService, embeddingService;
|
|
614
|
+
var init_EmbeddingService = __esm({
|
|
615
|
+
"src/services/search/EmbeddingService.ts"() {
|
|
616
|
+
"use strict";
|
|
617
|
+
init_logger();
|
|
618
|
+
EmbeddingService = class {
|
|
619
|
+
provider = null;
|
|
620
|
+
model = null;
|
|
621
|
+
initialized = false;
|
|
622
|
+
initializing = null;
|
|
623
|
+
/**
|
|
624
|
+
* Inizializza il servizio di embedding.
|
|
625
|
+
* Tenta fastembed, poi @huggingface/transformers, poi fallback a null.
|
|
626
|
+
*/
|
|
627
|
+
async initialize() {
|
|
628
|
+
if (this.initialized) return this.provider !== null;
|
|
629
|
+
if (this.initializing) return this.initializing;
|
|
630
|
+
this.initializing = this._doInitialize();
|
|
631
|
+
const result = await this.initializing;
|
|
632
|
+
this.initializing = null;
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
async _doInitialize() {
|
|
636
|
+
try {
|
|
637
|
+
const fastembed = await import("fastembed");
|
|
638
|
+
const EmbeddingModel = fastembed.EmbeddingModel || fastembed.default?.EmbeddingModel;
|
|
639
|
+
const FlagEmbedding = fastembed.FlagEmbedding || fastembed.default?.FlagEmbedding;
|
|
640
|
+
if (FlagEmbedding && EmbeddingModel) {
|
|
641
|
+
this.model = await FlagEmbedding.init({
|
|
642
|
+
model: EmbeddingModel.BGESmallENV15
|
|
643
|
+
});
|
|
644
|
+
this.provider = "fastembed";
|
|
645
|
+
this.initialized = true;
|
|
646
|
+
logger.info("EMBEDDING", "Inizializzato con fastembed (BGE-small-en-v1.5)");
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
} catch (error) {
|
|
650
|
+
logger.debug("EMBEDDING", `fastembed non disponibile: ${error}`);
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
const transformers = await import("@huggingface/transformers");
|
|
654
|
+
const pipeline = transformers.pipeline || transformers.default?.pipeline;
|
|
655
|
+
if (pipeline) {
|
|
656
|
+
this.model = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2", {
|
|
657
|
+
quantized: true
|
|
658
|
+
});
|
|
659
|
+
this.provider = "transformers";
|
|
660
|
+
this.initialized = true;
|
|
661
|
+
logger.info("EMBEDDING", "Inizializzato con @huggingface/transformers (all-MiniLM-L6-v2)");
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
} catch (error) {
|
|
665
|
+
logger.debug("EMBEDDING", `@huggingface/transformers non disponibile: ${error}`);
|
|
666
|
+
}
|
|
667
|
+
this.provider = null;
|
|
668
|
+
this.initialized = true;
|
|
669
|
+
logger.warn("EMBEDDING", "Nessun provider embedding disponibile, ricerca semantica disabilitata");
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Genera embedding per un singolo testo.
|
|
674
|
+
* Ritorna Float32Array con 384 dimensioni, o null se non disponibile.
|
|
675
|
+
*/
|
|
676
|
+
async embed(text) {
|
|
677
|
+
if (!this.initialized) await this.initialize();
|
|
678
|
+
if (!this.provider || !this.model) return null;
|
|
679
|
+
try {
|
|
680
|
+
const truncated = text.substring(0, 2e3);
|
|
681
|
+
if (this.provider === "fastembed") {
|
|
682
|
+
return await this._embedFastembed(truncated);
|
|
683
|
+
} else if (this.provider === "transformers") {
|
|
684
|
+
return await this._embedTransformers(truncated);
|
|
685
|
+
}
|
|
686
|
+
} catch (error) {
|
|
687
|
+
logger.error("EMBEDDING", `Errore generazione embedding: ${error}`);
|
|
688
|
+
}
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Genera embeddings in batch.
|
|
693
|
+
*/
|
|
694
|
+
async embedBatch(texts) {
|
|
695
|
+
if (!this.initialized) await this.initialize();
|
|
696
|
+
if (!this.provider || !this.model) return texts.map(() => null);
|
|
697
|
+
const results = [];
|
|
698
|
+
for (const text of texts) {
|
|
699
|
+
try {
|
|
700
|
+
const embedding = await this.embed(text);
|
|
701
|
+
results.push(embedding);
|
|
702
|
+
} catch {
|
|
703
|
+
results.push(null);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return results;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Verifica se il servizio è disponibile.
|
|
710
|
+
*/
|
|
711
|
+
isAvailable() {
|
|
712
|
+
return this.initialized && this.provider !== null;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Nome del provider attivo.
|
|
716
|
+
*/
|
|
717
|
+
getProvider() {
|
|
718
|
+
return this.provider;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Dimensioni del vettore embedding.
|
|
722
|
+
*/
|
|
723
|
+
getDimensions() {
|
|
724
|
+
return 384;
|
|
725
|
+
}
|
|
726
|
+
// --- Provider specifici ---
|
|
727
|
+
async _embedFastembed(text) {
|
|
728
|
+
const embeddings = this.model.embed([text], 1);
|
|
729
|
+
for await (const batch of embeddings) {
|
|
730
|
+
if (batch && batch.length > 0) {
|
|
731
|
+
const vec = batch[0];
|
|
732
|
+
return vec instanceof Float32Array ? vec : new Float32Array(vec);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
async _embedTransformers(text) {
|
|
738
|
+
const output = await this.model(text, {
|
|
739
|
+
pooling: "mean",
|
|
740
|
+
normalize: true
|
|
741
|
+
});
|
|
742
|
+
if (output?.data) {
|
|
743
|
+
return output.data instanceof Float32Array ? output.data : new Float32Array(output.data);
|
|
744
|
+
}
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
embeddingService = null;
|
|
749
|
+
}
|
|
750
|
+
});
|
|
9
751
|
|
|
10
752
|
// src/shims/bun-sqlite.ts
|
|
11
753
|
import BetterSqlite3 from "better-sqlite3";
|
|
@@ -78,228 +820,8 @@ var BunQueryCompat = class {
|
|
|
78
820
|
import { join as join2, dirname, basename } from "path";
|
|
79
821
|
import { homedir as homedir2 } from "os";
|
|
80
822
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
823
|
+
init_logger();
|
|
81
824
|
import { fileURLToPath } from "url";
|
|
82
|
-
|
|
83
|
-
// src/utils/logger.ts
|
|
84
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
85
|
-
import { join } from "path";
|
|
86
|
-
import { homedir } from "os";
|
|
87
|
-
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
88
|
-
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
89
|
-
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
90
|
-
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
91
|
-
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
92
|
-
LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
|
|
93
|
-
return LogLevel2;
|
|
94
|
-
})(LogLevel || {});
|
|
95
|
-
var DEFAULT_DATA_DIR = join(homedir(), ".contextkit");
|
|
96
|
-
var Logger = class {
|
|
97
|
-
level = null;
|
|
98
|
-
useColor;
|
|
99
|
-
logFilePath = null;
|
|
100
|
-
logFileInitialized = false;
|
|
101
|
-
constructor() {
|
|
102
|
-
this.useColor = process.stdout.isTTY ?? false;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Initialize log file path and ensure directory exists (lazy initialization)
|
|
106
|
-
*/
|
|
107
|
-
ensureLogFileInitialized() {
|
|
108
|
-
if (this.logFileInitialized) return;
|
|
109
|
-
this.logFileInitialized = true;
|
|
110
|
-
try {
|
|
111
|
-
const logsDir = join(DEFAULT_DATA_DIR, "logs");
|
|
112
|
-
if (!existsSync(logsDir)) {
|
|
113
|
-
mkdirSync(logsDir, { recursive: true });
|
|
114
|
-
}
|
|
115
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
116
|
-
this.logFilePath = join(logsDir, `kiro-memory-${date}.log`);
|
|
117
|
-
} catch (error) {
|
|
118
|
-
console.error("[LOGGER] Failed to initialize log file:", error);
|
|
119
|
-
this.logFilePath = null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Lazy-load log level from settings file
|
|
124
|
-
*/
|
|
125
|
-
getLevel() {
|
|
126
|
-
if (this.level === null) {
|
|
127
|
-
try {
|
|
128
|
-
const settingsPath = join(DEFAULT_DATA_DIR, "settings.json");
|
|
129
|
-
if (existsSync(settingsPath)) {
|
|
130
|
-
const settingsData = readFileSync(settingsPath, "utf-8");
|
|
131
|
-
const settings = JSON.parse(settingsData);
|
|
132
|
-
const envLevel = (settings.KIRO_MEMORY_LOG_LEVEL || settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
|
|
133
|
-
this.level = LogLevel[envLevel] ?? 1 /* INFO */;
|
|
134
|
-
} else {
|
|
135
|
-
this.level = 1 /* INFO */;
|
|
136
|
-
}
|
|
137
|
-
} catch (error) {
|
|
138
|
-
this.level = 1 /* INFO */;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return this.level;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Create correlation ID for tracking an observation through the pipeline
|
|
145
|
-
*/
|
|
146
|
-
correlationId(sessionId, observationNum) {
|
|
147
|
-
return `obs-${sessionId}-${observationNum}`;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Create session correlation ID
|
|
151
|
-
*/
|
|
152
|
-
sessionId(sessionId) {
|
|
153
|
-
return `session-${sessionId}`;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Format data for logging - create compact summaries instead of full dumps
|
|
157
|
-
*/
|
|
158
|
-
formatData(data) {
|
|
159
|
-
if (data === null || data === void 0) return "";
|
|
160
|
-
if (typeof data === "string") return data;
|
|
161
|
-
if (typeof data === "number") return data.toString();
|
|
162
|
-
if (typeof data === "boolean") return data.toString();
|
|
163
|
-
if (typeof data === "object") {
|
|
164
|
-
if (data instanceof Error) {
|
|
165
|
-
return this.getLevel() === 0 /* DEBUG */ ? `${data.message}
|
|
166
|
-
${data.stack}` : data.message;
|
|
167
|
-
}
|
|
168
|
-
if (Array.isArray(data)) {
|
|
169
|
-
return `[${data.length} items]`;
|
|
170
|
-
}
|
|
171
|
-
const keys = Object.keys(data);
|
|
172
|
-
if (keys.length === 0) return "{}";
|
|
173
|
-
if (keys.length <= 3) {
|
|
174
|
-
return JSON.stringify(data);
|
|
175
|
-
}
|
|
176
|
-
return `{${keys.length} keys: ${keys.slice(0, 3).join(", ")}...}`;
|
|
177
|
-
}
|
|
178
|
-
return String(data);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Format timestamp in local timezone (YYYY-MM-DD HH:MM:SS.mmm)
|
|
182
|
-
*/
|
|
183
|
-
formatTimestamp(date) {
|
|
184
|
-
const year = date.getFullYear();
|
|
185
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
186
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
187
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
188
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
189
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
190
|
-
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
191
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Core logging method
|
|
195
|
-
*/
|
|
196
|
-
log(level, component, message, context, data) {
|
|
197
|
-
if (level < this.getLevel()) return;
|
|
198
|
-
this.ensureLogFileInitialized();
|
|
199
|
-
const timestamp = this.formatTimestamp(/* @__PURE__ */ new Date());
|
|
200
|
-
const levelStr = LogLevel[level].padEnd(5);
|
|
201
|
-
const componentStr = component.padEnd(6);
|
|
202
|
-
let correlationStr = "";
|
|
203
|
-
if (context?.correlationId) {
|
|
204
|
-
correlationStr = `[${context.correlationId}] `;
|
|
205
|
-
} else if (context?.sessionId) {
|
|
206
|
-
correlationStr = `[session-${context.sessionId}] `;
|
|
207
|
-
}
|
|
208
|
-
let dataStr = "";
|
|
209
|
-
if (data !== void 0 && data !== null) {
|
|
210
|
-
if (data instanceof Error) {
|
|
211
|
-
dataStr = this.getLevel() === 0 /* DEBUG */ ? `
|
|
212
|
-
${data.message}
|
|
213
|
-
${data.stack}` : ` ${data.message}`;
|
|
214
|
-
} else if (this.getLevel() === 0 /* DEBUG */ && typeof data === "object") {
|
|
215
|
-
dataStr = "\n" + JSON.stringify(data, null, 2);
|
|
216
|
-
} else {
|
|
217
|
-
dataStr = " " + this.formatData(data);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
let contextStr = "";
|
|
221
|
-
if (context) {
|
|
222
|
-
const { sessionId, memorySessionId, correlationId, ...rest } = context;
|
|
223
|
-
if (Object.keys(rest).length > 0) {
|
|
224
|
-
const pairs = Object.entries(rest).map(([k, v]) => `${k}=${v}`);
|
|
225
|
-
contextStr = ` {${pairs.join(", ")}}`;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
const logLine = `[${timestamp}] [${levelStr}] [${componentStr}] ${correlationStr}${message}${contextStr}${dataStr}`;
|
|
229
|
-
if (this.logFilePath) {
|
|
230
|
-
try {
|
|
231
|
-
appendFileSync(this.logFilePath, logLine + "\n", "utf8");
|
|
232
|
-
} catch (error) {
|
|
233
|
-
process.stderr.write(`[LOGGER] Failed to write to log file: ${error}
|
|
234
|
-
`);
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
process.stderr.write(logLine + "\n");
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
// Public logging methods
|
|
241
|
-
debug(component, message, context, data) {
|
|
242
|
-
this.log(0 /* DEBUG */, component, message, context, data);
|
|
243
|
-
}
|
|
244
|
-
info(component, message, context, data) {
|
|
245
|
-
this.log(1 /* INFO */, component, message, context, data);
|
|
246
|
-
}
|
|
247
|
-
warn(component, message, context, data) {
|
|
248
|
-
this.log(2 /* WARN */, component, message, context, data);
|
|
249
|
-
}
|
|
250
|
-
error(component, message, context, data) {
|
|
251
|
-
this.log(3 /* ERROR */, component, message, context, data);
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Log data flow: input → processing
|
|
255
|
-
*/
|
|
256
|
-
dataIn(component, message, context, data) {
|
|
257
|
-
this.info(component, `\u2192 ${message}`, context, data);
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Log data flow: processing → output
|
|
261
|
-
*/
|
|
262
|
-
dataOut(component, message, context, data) {
|
|
263
|
-
this.info(component, `\u2190 ${message}`, context, data);
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Log successful completion
|
|
267
|
-
*/
|
|
268
|
-
success(component, message, context, data) {
|
|
269
|
-
this.info(component, `\u2713 ${message}`, context, data);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Log failure
|
|
273
|
-
*/
|
|
274
|
-
failure(component, message, context, data) {
|
|
275
|
-
this.error(component, `\u2717 ${message}`, context, data);
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Log timing information
|
|
279
|
-
*/
|
|
280
|
-
timing(component, message, durationMs, context) {
|
|
281
|
-
this.info(component, `\u23F1 ${message}`, context, { duration: `${durationMs}ms` });
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Happy Path Error - logs when the expected "happy path" fails but we have a fallback
|
|
285
|
-
*/
|
|
286
|
-
happyPathError(component, message, context, data, fallback = "") {
|
|
287
|
-
const stack = new Error().stack || "";
|
|
288
|
-
const stackLines = stack.split("\n");
|
|
289
|
-
const callerLine = stackLines[2] || "";
|
|
290
|
-
const callerMatch = callerLine.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/);
|
|
291
|
-
const location = callerMatch ? `${callerMatch[1].split("/").pop()}:${callerMatch[2]}` : "unknown";
|
|
292
|
-
const enhancedContext = {
|
|
293
|
-
...context,
|
|
294
|
-
location
|
|
295
|
-
};
|
|
296
|
-
this.warn(component, `[HAPPY-PATH] ${message}`, enhancedContext, data);
|
|
297
|
-
return fallback;
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
var logger = new Logger();
|
|
301
|
-
|
|
302
|
-
// src/shared/paths.ts
|
|
303
825
|
function getDirname() {
|
|
304
826
|
if (typeof __dirname !== "undefined") {
|
|
305
827
|
return __dirname;
|
|
@@ -329,6 +851,7 @@ function ensureDir(dirPath) {
|
|
|
329
851
|
}
|
|
330
852
|
|
|
331
853
|
// src/services/sqlite/Database.ts
|
|
854
|
+
init_logger();
|
|
332
855
|
var SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024;
|
|
333
856
|
var SQLITE_CACHE_SIZE_PAGES = 1e4;
|
|
334
857
|
var KiroMemoryDatabase = class {
|
|
@@ -534,6 +1057,55 @@ var MigrationRunner = class {
|
|
|
534
1057
|
`);
|
|
535
1058
|
db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_aliases_name ON project_aliases(project_name)");
|
|
536
1059
|
}
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
version: 4,
|
|
1063
|
+
up: (db) => {
|
|
1064
|
+
db.run(`
|
|
1065
|
+
CREATE TABLE IF NOT EXISTS observation_embeddings (
|
|
1066
|
+
observation_id INTEGER PRIMARY KEY,
|
|
1067
|
+
embedding BLOB NOT NULL,
|
|
1068
|
+
model TEXT NOT NULL,
|
|
1069
|
+
dimensions INTEGER NOT NULL,
|
|
1070
|
+
created_at TEXT NOT NULL,
|
|
1071
|
+
FOREIGN KEY (observation_id) REFERENCES observations(id) ON DELETE CASCADE
|
|
1072
|
+
)
|
|
1073
|
+
`);
|
|
1074
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_embeddings_model ON observation_embeddings(model)");
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
version: 5,
|
|
1079
|
+
up: (db) => {
|
|
1080
|
+
db.run("ALTER TABLE observations ADD COLUMN last_accessed_epoch INTEGER");
|
|
1081
|
+
db.run("ALTER TABLE observations ADD COLUMN is_stale INTEGER DEFAULT 0");
|
|
1082
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_last_accessed ON observations(last_accessed_epoch)");
|
|
1083
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_stale ON observations(is_stale)");
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
version: 6,
|
|
1088
|
+
up: (db) => {
|
|
1089
|
+
db.run(`
|
|
1090
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
1091
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1092
|
+
session_id INTEGER NOT NULL,
|
|
1093
|
+
project TEXT NOT NULL,
|
|
1094
|
+
task TEXT NOT NULL,
|
|
1095
|
+
progress TEXT,
|
|
1096
|
+
next_steps TEXT,
|
|
1097
|
+
open_questions TEXT,
|
|
1098
|
+
relevant_files TEXT,
|
|
1099
|
+
context_snapshot TEXT,
|
|
1100
|
+
created_at TEXT NOT NULL,
|
|
1101
|
+
created_at_epoch INTEGER NOT NULL,
|
|
1102
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
1103
|
+
)
|
|
1104
|
+
`);
|
|
1105
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id)");
|
|
1106
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
|
|
1107
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
|
|
1108
|
+
}
|
|
537
1109
|
}
|
|
538
1110
|
];
|
|
539
1111
|
}
|
|
@@ -563,38 +1135,13 @@ function completeSession(db, id) {
|
|
|
563
1135
|
);
|
|
564
1136
|
}
|
|
565
1137
|
|
|
566
|
-
// src/services/sqlite/
|
|
567
|
-
|
|
568
|
-
const now = /* @__PURE__ */ new Date();
|
|
569
|
-
const result = db.run(
|
|
570
|
-
`INSERT INTO observations
|
|
571
|
-
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
|
572
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
573
|
-
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
|
|
574
|
-
);
|
|
575
|
-
return Number(result.lastInsertRowid);
|
|
576
|
-
}
|
|
577
|
-
function getObservationsByProject(db, project, limit = 100) {
|
|
578
|
-
const query = db.query(
|
|
579
|
-
"SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
580
|
-
);
|
|
581
|
-
return query.all(project, limit);
|
|
582
|
-
}
|
|
583
|
-
function searchObservations(db, searchTerm, project) {
|
|
584
|
-
const sql = project ? `SELECT * FROM observations
|
|
585
|
-
WHERE project = ? AND (title LIKE ? OR text LIKE ? OR narrative LIKE ?)
|
|
586
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
587
|
-
WHERE title LIKE ? OR text LIKE ? OR narrative LIKE ?
|
|
588
|
-
ORDER BY created_at_epoch DESC`;
|
|
589
|
-
const pattern = `%${searchTerm}%`;
|
|
590
|
-
const query = db.query(sql);
|
|
591
|
-
if (project) {
|
|
592
|
-
return query.all(project, pattern, pattern, pattern);
|
|
593
|
-
}
|
|
594
|
-
return query.all(pattern, pattern, pattern);
|
|
595
|
-
}
|
|
1138
|
+
// src/services/sqlite/index.ts
|
|
1139
|
+
init_Observations();
|
|
596
1140
|
|
|
597
1141
|
// src/services/sqlite/Summaries.ts
|
|
1142
|
+
function escapeLikePattern2(input) {
|
|
1143
|
+
return input.replace(/[%_\\]/g, "\\$&");
|
|
1144
|
+
}
|
|
598
1145
|
function createSummary(db, sessionId, project, request, investigated, learned, completed, nextSteps, notes) {
|
|
599
1146
|
const now = /* @__PURE__ */ new Date();
|
|
600
1147
|
const result = db.run(
|
|
@@ -612,12 +1159,12 @@ function getSummariesByProject(db, project, limit = 50) {
|
|
|
612
1159
|
return query.all(project, limit);
|
|
613
1160
|
}
|
|
614
1161
|
function searchSummaries(db, searchTerm, project) {
|
|
615
|
-
const sql = project ? `SELECT * FROM summaries
|
|
616
|
-
WHERE project = ? AND (request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?)
|
|
617
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
618
|
-
WHERE request LIKE ? OR learned LIKE ? OR completed LIKE ? OR notes LIKE ?
|
|
1162
|
+
const sql = project ? `SELECT * FROM summaries
|
|
1163
|
+
WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
|
|
1164
|
+
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
1165
|
+
WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
|
|
619
1166
|
ORDER BY created_at_epoch DESC`;
|
|
620
|
-
const pattern = `%${searchTerm}%`;
|
|
1167
|
+
const pattern = `%${escapeLikePattern2(searchTerm)}%`;
|
|
621
1168
|
const query = db.query(sql);
|
|
622
1169
|
if (project) {
|
|
623
1170
|
return query.all(project, pattern, pattern, pattern, pattern);
|
|
@@ -643,136 +1190,513 @@ function getPromptsByProject(db, project, limit = 100) {
|
|
|
643
1190
|
return query.all(project, limit);
|
|
644
1191
|
}
|
|
645
1192
|
|
|
646
|
-
// src/services/sqlite/
|
|
647
|
-
function
|
|
648
|
-
const
|
|
649
|
-
|
|
1193
|
+
// src/services/sqlite/Checkpoints.ts
|
|
1194
|
+
function createCheckpoint(db, sessionId, project, data) {
|
|
1195
|
+
const now = /* @__PURE__ */ new Date();
|
|
1196
|
+
const result = db.run(
|
|
1197
|
+
`INSERT INTO checkpoints (session_id, project, task, progress, next_steps, open_questions, relevant_files, context_snapshot, created_at, created_at_epoch)
|
|
1198
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1199
|
+
[
|
|
1200
|
+
sessionId,
|
|
1201
|
+
project,
|
|
1202
|
+
data.task,
|
|
1203
|
+
data.progress || null,
|
|
1204
|
+
data.nextSteps || null,
|
|
1205
|
+
data.openQuestions || null,
|
|
1206
|
+
data.relevantFiles || null,
|
|
1207
|
+
data.contextSnapshot || null,
|
|
1208
|
+
now.toISOString(),
|
|
1209
|
+
now.getTime()
|
|
1210
|
+
]
|
|
1211
|
+
);
|
|
1212
|
+
return Number(result.lastInsertRowid);
|
|
650
1213
|
}
|
|
651
|
-
function
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1214
|
+
function getLatestCheckpoint(db, sessionId) {
|
|
1215
|
+
const query = db.query(
|
|
1216
|
+
"SELECT * FROM checkpoints WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1"
|
|
1217
|
+
);
|
|
1218
|
+
return query.get(sessionId);
|
|
1219
|
+
}
|
|
1220
|
+
function getLatestCheckpointByProject(db, project) {
|
|
1221
|
+
const query = db.query(
|
|
1222
|
+
"SELECT * FROM checkpoints WHERE project = ? ORDER BY created_at_epoch DESC LIMIT 1"
|
|
1223
|
+
);
|
|
1224
|
+
return query.get(project);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// src/services/sqlite/Reports.ts
|
|
1228
|
+
function getReportData(db, project, startEpoch, endEpoch) {
|
|
1229
|
+
const startDate = new Date(startEpoch);
|
|
1230
|
+
const endDate = new Date(endEpoch);
|
|
1231
|
+
const days = Math.ceil((endEpoch - startEpoch) / (24 * 60 * 60 * 1e3));
|
|
1232
|
+
const label = days <= 7 ? "Weekly" : days <= 31 ? "Monthly" : "Custom";
|
|
1233
|
+
const countInRange = (table, epochCol = "created_at_epoch") => {
|
|
1234
|
+
const sql = project ? `SELECT COUNT(*) as count FROM ${table} WHERE project = ? AND ${epochCol} >= ? AND ${epochCol} <= ?` : `SELECT COUNT(*) as count FROM ${table} WHERE ${epochCol} >= ? AND ${epochCol} <= ?`;
|
|
1235
|
+
const stmt = db.query(sql);
|
|
1236
|
+
const row = project ? stmt.get(project, startEpoch, endEpoch) : stmt.get(startEpoch, endEpoch);
|
|
1237
|
+
return row?.count || 0;
|
|
1238
|
+
};
|
|
1239
|
+
const observations = countInRange("observations");
|
|
1240
|
+
const summaries = countInRange("summaries");
|
|
1241
|
+
const prompts = countInRange("prompts");
|
|
1242
|
+
const sessions = countInRange("sessions", "started_at_epoch");
|
|
1243
|
+
const timelineSql = project ? `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
|
|
1244
|
+
FROM observations
|
|
1245
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1246
|
+
GROUP BY day ORDER BY day ASC` : `SELECT DATE(datetime(created_at_epoch / 1000, 'unixepoch')) as day, COUNT(*) as count
|
|
1247
|
+
FROM observations
|
|
1248
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1249
|
+
GROUP BY day ORDER BY day ASC`;
|
|
1250
|
+
const timelineStmt = db.query(timelineSql);
|
|
1251
|
+
const timeline = project ? timelineStmt.all(project, startEpoch, endEpoch) : timelineStmt.all(startEpoch, endEpoch);
|
|
1252
|
+
const typeSql = project ? `SELECT type, COUNT(*) as count FROM observations
|
|
1253
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1254
|
+
GROUP BY type ORDER BY count DESC` : `SELECT type, COUNT(*) as count FROM observations
|
|
1255
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1256
|
+
GROUP BY type ORDER BY count DESC`;
|
|
1257
|
+
const typeStmt = db.query(typeSql);
|
|
1258
|
+
const typeDistribution = project ? typeStmt.all(project, startEpoch, endEpoch) : typeStmt.all(startEpoch, endEpoch);
|
|
1259
|
+
const sessionTotalSql = project ? `SELECT COUNT(*) as count FROM sessions WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ?` : `SELECT COUNT(*) as count FROM sessions WHERE started_at_epoch >= ? AND started_at_epoch <= ?`;
|
|
1260
|
+
const sessionTotal = (project ? db.query(sessionTotalSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionTotalSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1261
|
+
const sessionCompletedSql = project ? `SELECT COUNT(*) as count FROM sessions WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ? AND status = 'completed'` : `SELECT COUNT(*) as count FROM sessions WHERE started_at_epoch >= ? AND started_at_epoch <= ? AND status = 'completed'`;
|
|
1262
|
+
const sessionCompleted = (project ? db.query(sessionCompletedSql).get(project, startEpoch, endEpoch)?.count : db.query(sessionCompletedSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1263
|
+
const sessionAvgSql = project ? `SELECT AVG((completed_at_epoch - started_at_epoch) / 1000.0 / 60.0) as avg_min
|
|
1264
|
+
FROM sessions
|
|
1265
|
+
WHERE project = ? AND started_at_epoch >= ? AND started_at_epoch <= ?
|
|
1266
|
+
AND status = 'completed' AND completed_at_epoch IS NOT NULL AND completed_at_epoch > started_at_epoch` : `SELECT AVG((completed_at_epoch - started_at_epoch) / 1000.0 / 60.0) as avg_min
|
|
1267
|
+
FROM sessions
|
|
1268
|
+
WHERE started_at_epoch >= ? AND started_at_epoch <= ?
|
|
1269
|
+
AND status = 'completed' AND completed_at_epoch IS NOT NULL AND completed_at_epoch > started_at_epoch`;
|
|
1270
|
+
const avgRow = project ? db.query(sessionAvgSql).get(project, startEpoch, endEpoch) : db.query(sessionAvgSql).get(startEpoch, endEpoch);
|
|
1271
|
+
const avgDurationMinutes = Math.round((avgRow?.avg_min || 0) * 10) / 10;
|
|
1272
|
+
const knowledgeSql = project ? `SELECT COUNT(*) as count FROM observations
|
|
1273
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1274
|
+
AND type IN ('constraint', 'decision', 'heuristic', 'rejected')` : `SELECT COUNT(*) as count FROM observations
|
|
1275
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1276
|
+
AND type IN ('constraint', 'decision', 'heuristic', 'rejected')`;
|
|
1277
|
+
const knowledgeCount = (project ? db.query(knowledgeSql).get(project, startEpoch, endEpoch)?.count : db.query(knowledgeSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1278
|
+
const staleSql = project ? `SELECT COUNT(*) as count FROM observations
|
|
1279
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1` : `SELECT COUNT(*) as count FROM observations
|
|
1280
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ? AND is_stale = 1`;
|
|
1281
|
+
const staleCount = (project ? db.query(staleSql).get(project, startEpoch, endEpoch)?.count : db.query(staleSql).get(startEpoch, endEpoch)?.count) || 0;
|
|
1282
|
+
const summarySql = project ? `SELECT learned, completed, next_steps FROM summaries
|
|
1283
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1284
|
+
ORDER BY created_at_epoch DESC` : `SELECT learned, completed, next_steps FROM summaries
|
|
1285
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1286
|
+
ORDER BY created_at_epoch DESC`;
|
|
1287
|
+
const summaryRows = project ? db.query(summarySql).all(project, startEpoch, endEpoch) : db.query(summarySql).all(startEpoch, endEpoch);
|
|
1288
|
+
const topLearnings = [];
|
|
1289
|
+
const completedTasks = [];
|
|
1290
|
+
const nextStepsArr = [];
|
|
1291
|
+
for (const row of summaryRows) {
|
|
1292
|
+
if (row.learned) {
|
|
1293
|
+
const parts = row.learned.split("; ").filter(Boolean);
|
|
1294
|
+
topLearnings.push(...parts);
|
|
669
1295
|
}
|
|
670
|
-
if (
|
|
671
|
-
|
|
672
|
-
|
|
1296
|
+
if (row.completed) {
|
|
1297
|
+
const parts = row.completed.split("; ").filter(Boolean);
|
|
1298
|
+
completedTasks.push(...parts);
|
|
673
1299
|
}
|
|
674
|
-
if (
|
|
675
|
-
|
|
676
|
-
|
|
1300
|
+
if (row.next_steps) {
|
|
1301
|
+
const parts = row.next_steps.split("; ").filter(Boolean);
|
|
1302
|
+
nextStepsArr.push(...parts);
|
|
677
1303
|
}
|
|
678
|
-
sql += " ORDER BY rank LIMIT ?";
|
|
679
|
-
params.push(limit);
|
|
680
|
-
const stmt = db.query(sql);
|
|
681
|
-
return stmt.all(...params);
|
|
682
|
-
} catch {
|
|
683
|
-
return searchObservationsLIKE(db, query, filters);
|
|
684
1304
|
}
|
|
1305
|
+
const filesSql = project ? `SELECT files_modified FROM observations
|
|
1306
|
+
WHERE project = ? AND created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1307
|
+
AND files_modified IS NOT NULL AND files_modified != ''` : `SELECT files_modified FROM observations
|
|
1308
|
+
WHERE created_at_epoch >= ? AND created_at_epoch <= ?
|
|
1309
|
+
AND files_modified IS NOT NULL AND files_modified != ''`;
|
|
1310
|
+
const fileRows = project ? db.query(filesSql).all(project, startEpoch, endEpoch) : db.query(filesSql).all(startEpoch, endEpoch);
|
|
1311
|
+
const fileCounts = /* @__PURE__ */ new Map();
|
|
1312
|
+
for (const row of fileRows) {
|
|
1313
|
+
const files = row.files_modified.split(",").map((f) => f.trim()).filter(Boolean);
|
|
1314
|
+
for (const file of files) {
|
|
1315
|
+
fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
const fileHotspots = Array.from(fileCounts.entries()).map(([file, count]) => ({ file, count })).sort((a, b) => b.count - a.count).slice(0, 15);
|
|
1319
|
+
return {
|
|
1320
|
+
period: {
|
|
1321
|
+
start: startDate.toISOString().split("T")[0],
|
|
1322
|
+
end: endDate.toISOString().split("T")[0],
|
|
1323
|
+
days,
|
|
1324
|
+
label
|
|
1325
|
+
},
|
|
1326
|
+
overview: {
|
|
1327
|
+
observations,
|
|
1328
|
+
summaries,
|
|
1329
|
+
sessions,
|
|
1330
|
+
prompts,
|
|
1331
|
+
knowledgeCount,
|
|
1332
|
+
staleCount
|
|
1333
|
+
},
|
|
1334
|
+
timeline,
|
|
1335
|
+
typeDistribution,
|
|
1336
|
+
sessionStats: {
|
|
1337
|
+
total: sessionTotal,
|
|
1338
|
+
completed: sessionCompleted,
|
|
1339
|
+
avgDurationMinutes
|
|
1340
|
+
},
|
|
1341
|
+
topLearnings: [...new Set(topLearnings)].slice(0, 10),
|
|
1342
|
+
completedTasks: [...new Set(completedTasks)].slice(0, 10),
|
|
1343
|
+
nextSteps: [...new Set(nextStepsArr)].slice(0, 10),
|
|
1344
|
+
fileHotspots
|
|
1345
|
+
};
|
|
685
1346
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1347
|
+
|
|
1348
|
+
// src/services/sqlite/index.ts
|
|
1349
|
+
init_Search();
|
|
1350
|
+
|
|
1351
|
+
// src/sdk/index.ts
|
|
1352
|
+
init_Observations();
|
|
1353
|
+
init_Search();
|
|
1354
|
+
|
|
1355
|
+
// src/services/search/HybridSearch.ts
|
|
1356
|
+
init_EmbeddingService();
|
|
1357
|
+
|
|
1358
|
+
// src/services/search/VectorSearch.ts
|
|
1359
|
+
init_EmbeddingService();
|
|
1360
|
+
init_logger();
|
|
1361
|
+
function cosineSimilarity(a, b) {
|
|
1362
|
+
if (a.length !== b.length) return 0;
|
|
1363
|
+
let dotProduct = 0;
|
|
1364
|
+
let normA = 0;
|
|
1365
|
+
let normB = 0;
|
|
1366
|
+
for (let i = 0; i < a.length; i++) {
|
|
1367
|
+
dotProduct += a[i] * b[i];
|
|
1368
|
+
normA += a[i] * a[i];
|
|
1369
|
+
normB += b[i] * b[i];
|
|
1370
|
+
}
|
|
1371
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1372
|
+
if (denominator === 0) return 0;
|
|
1373
|
+
return dotProduct / denominator;
|
|
1374
|
+
}
|
|
1375
|
+
function float32ToBuffer(arr) {
|
|
1376
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1377
|
+
}
|
|
1378
|
+
function bufferToFloat32(buf) {
|
|
1379
|
+
const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
1380
|
+
return new Float32Array(arrayBuffer);
|
|
1381
|
+
}
|
|
1382
|
+
var VectorSearch = class {
|
|
1383
|
+
/**
|
|
1384
|
+
* Ricerca semantica: calcola cosine similarity tra query e tutti gli embeddings.
|
|
1385
|
+
*/
|
|
1386
|
+
async search(db, queryEmbedding, options = {}) {
|
|
1387
|
+
const limit = options.limit || 10;
|
|
1388
|
+
const threshold = options.threshold || 0.3;
|
|
1389
|
+
try {
|
|
1390
|
+
let sql = `
|
|
1391
|
+
SELECT e.observation_id, e.embedding,
|
|
1392
|
+
o.title, o.text, o.type, o.project, o.created_at, o.created_at_epoch
|
|
1393
|
+
FROM observation_embeddings e
|
|
1394
|
+
JOIN observations o ON o.id = e.observation_id
|
|
1395
|
+
`;
|
|
1396
|
+
const params = [];
|
|
1397
|
+
if (options.project) {
|
|
1398
|
+
sql += " WHERE o.project = ?";
|
|
1399
|
+
params.push(options.project);
|
|
1400
|
+
}
|
|
1401
|
+
const rows = db.query(sql).all(...params);
|
|
1402
|
+
const scored = [];
|
|
1403
|
+
for (const row of rows) {
|
|
1404
|
+
const embedding = bufferToFloat32(row.embedding);
|
|
1405
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding);
|
|
1406
|
+
if (similarity >= threshold) {
|
|
1407
|
+
scored.push({
|
|
1408
|
+
id: row.observation_id,
|
|
1409
|
+
observationId: row.observation_id,
|
|
1410
|
+
similarity,
|
|
1411
|
+
title: row.title,
|
|
1412
|
+
text: row.text,
|
|
1413
|
+
type: row.type,
|
|
1414
|
+
project: row.project,
|
|
1415
|
+
created_at: row.created_at,
|
|
1416
|
+
created_at_epoch: row.created_at_epoch
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
1421
|
+
return scored.slice(0, limit);
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
logger.error("VECTOR", `Errore ricerca vettoriale: ${error}`);
|
|
1424
|
+
return [];
|
|
1425
|
+
}
|
|
697
1426
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1427
|
+
/**
|
|
1428
|
+
* Salva embedding per un'osservazione.
|
|
1429
|
+
*/
|
|
1430
|
+
async storeEmbedding(db, observationId, embedding, model) {
|
|
1431
|
+
try {
|
|
1432
|
+
const blob = float32ToBuffer(embedding);
|
|
1433
|
+
db.query(`
|
|
1434
|
+
INSERT OR REPLACE INTO observation_embeddings
|
|
1435
|
+
(observation_id, embedding, model, dimensions, created_at)
|
|
1436
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1437
|
+
`).run(
|
|
1438
|
+
observationId,
|
|
1439
|
+
blob,
|
|
1440
|
+
model,
|
|
1441
|
+
embedding.length,
|
|
1442
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1443
|
+
);
|
|
1444
|
+
logger.debug("VECTOR", `Embedding salvato per osservazione ${observationId}`);
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
logger.error("VECTOR", `Errore salvataggio embedding: ${error}`);
|
|
1447
|
+
}
|
|
701
1448
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1449
|
+
/**
|
|
1450
|
+
* Genera embeddings per osservazioni che non li hanno ancora.
|
|
1451
|
+
*/
|
|
1452
|
+
async backfillEmbeddings(db, batchSize = 50) {
|
|
1453
|
+
const embeddingService2 = getEmbeddingService();
|
|
1454
|
+
if (!await embeddingService2.initialize()) {
|
|
1455
|
+
logger.warn("VECTOR", "Embedding service non disponibile, backfill saltato");
|
|
1456
|
+
return 0;
|
|
1457
|
+
}
|
|
1458
|
+
const rows = db.query(`
|
|
1459
|
+
SELECT o.id, o.title, o.text, o.narrative, o.concepts
|
|
1460
|
+
FROM observations o
|
|
1461
|
+
LEFT JOIN observation_embeddings e ON e.observation_id = o.id
|
|
1462
|
+
WHERE e.observation_id IS NULL
|
|
1463
|
+
ORDER BY o.created_at_epoch DESC
|
|
1464
|
+
LIMIT ?
|
|
1465
|
+
`).all(batchSize);
|
|
1466
|
+
if (rows.length === 0) return 0;
|
|
1467
|
+
let count = 0;
|
|
1468
|
+
const model = embeddingService2.getProvider() || "unknown";
|
|
1469
|
+
for (const row of rows) {
|
|
1470
|
+
const parts = [row.title];
|
|
1471
|
+
if (row.text) parts.push(row.text);
|
|
1472
|
+
if (row.narrative) parts.push(row.narrative);
|
|
1473
|
+
if (row.concepts) parts.push(row.concepts);
|
|
1474
|
+
const fullText = parts.join(" ").substring(0, 2e3);
|
|
1475
|
+
const embedding = await embeddingService2.embed(fullText);
|
|
1476
|
+
if (embedding) {
|
|
1477
|
+
await this.storeEmbedding(db, row.id, embedding, model);
|
|
1478
|
+
count++;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
logger.info("VECTOR", `Backfill completato: ${count}/${rows.length} embeddings generati`);
|
|
1482
|
+
return count;
|
|
705
1483
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1484
|
+
/**
|
|
1485
|
+
* Statistiche sugli embeddings.
|
|
1486
|
+
*/
|
|
1487
|
+
getStats(db) {
|
|
1488
|
+
try {
|
|
1489
|
+
const totalRow = db.query("SELECT COUNT(*) as count FROM observations").get();
|
|
1490
|
+
const embeddedRow = db.query("SELECT COUNT(*) as count FROM observation_embeddings").get();
|
|
1491
|
+
const total = totalRow?.count || 0;
|
|
1492
|
+
const embedded = embeddedRow?.count || 0;
|
|
1493
|
+
const percentage = total > 0 ? Math.round(embedded / total * 100) : 0;
|
|
1494
|
+
return { total, embedded, percentage };
|
|
1495
|
+
} catch {
|
|
1496
|
+
return { total: 0, embedded: 0, percentage: 0 };
|
|
1497
|
+
}
|
|
709
1498
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1499
|
+
};
|
|
1500
|
+
var vectorSearch = null;
|
|
1501
|
+
function getVectorSearch() {
|
|
1502
|
+
if (!vectorSearch) {
|
|
1503
|
+
vectorSearch = new VectorSearch();
|
|
1504
|
+
}
|
|
1505
|
+
return vectorSearch;
|
|
714
1506
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1507
|
+
|
|
1508
|
+
// src/services/search/ScoringEngine.ts
|
|
1509
|
+
var SEARCH_WEIGHTS = {
|
|
1510
|
+
semantic: 0.4,
|
|
1511
|
+
fts5: 0.3,
|
|
1512
|
+
recency: 0.2,
|
|
1513
|
+
projectMatch: 0.1
|
|
1514
|
+
};
|
|
1515
|
+
var CONTEXT_WEIGHTS = {
|
|
1516
|
+
semantic: 0,
|
|
1517
|
+
fts5: 0,
|
|
1518
|
+
recency: 0.7,
|
|
1519
|
+
projectMatch: 0.3
|
|
1520
|
+
};
|
|
1521
|
+
function recencyScore(createdAtEpoch, halfLifeHours = 168) {
|
|
1522
|
+
if (!createdAtEpoch || createdAtEpoch <= 0) return 0;
|
|
1523
|
+
const nowMs = Date.now();
|
|
1524
|
+
const ageMs = nowMs - createdAtEpoch;
|
|
1525
|
+
if (ageMs <= 0) return 1;
|
|
1526
|
+
const ageHours = ageMs / (1e3 * 60 * 60);
|
|
1527
|
+
return Math.exp(-ageHours * Math.LN2 / halfLifeHours);
|
|
1528
|
+
}
|
|
1529
|
+
function normalizeFTS5Rank(rank, allRanks) {
|
|
1530
|
+
if (allRanks.length === 0) return 0;
|
|
1531
|
+
if (allRanks.length === 1) return 1;
|
|
1532
|
+
const minRank = Math.min(...allRanks);
|
|
1533
|
+
const maxRank = Math.max(...allRanks);
|
|
1534
|
+
if (minRank === maxRank) return 1;
|
|
1535
|
+
return (maxRank - rank) / (maxRank - minRank);
|
|
1536
|
+
}
|
|
1537
|
+
function projectMatchScore(itemProject, targetProject) {
|
|
1538
|
+
if (!itemProject || !targetProject) return 0;
|
|
1539
|
+
return itemProject.toLowerCase() === targetProject.toLowerCase() ? 1 : 0;
|
|
1540
|
+
}
|
|
1541
|
+
function computeCompositeScore(signals, weights) {
|
|
1542
|
+
return signals.semantic * weights.semantic + signals.fts5 * weights.fts5 + signals.recency * weights.recency + signals.projectMatch * weights.projectMatch;
|
|
1543
|
+
}
|
|
1544
|
+
var KNOWLEDGE_TYPE_BOOST = {
|
|
1545
|
+
constraint: 1.3,
|
|
1546
|
+
decision: 1.25,
|
|
1547
|
+
heuristic: 1.15,
|
|
1548
|
+
rejected: 1.1
|
|
1549
|
+
};
|
|
1550
|
+
function knowledgeTypeBoost(type) {
|
|
1551
|
+
return KNOWLEDGE_TYPE_BOOST[type] ?? 1;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// src/services/search/HybridSearch.ts
|
|
1555
|
+
init_logger();
|
|
1556
|
+
var HybridSearch = class {
|
|
1557
|
+
embeddingInitialized = false;
|
|
1558
|
+
/**
|
|
1559
|
+
* Inizializza il servizio di embedding (lazy, non bloccante)
|
|
1560
|
+
*/
|
|
1561
|
+
async initialize() {
|
|
1562
|
+
try {
|
|
1563
|
+
const embeddingService2 = getEmbeddingService();
|
|
1564
|
+
await embeddingService2.initialize();
|
|
1565
|
+
this.embeddingInitialized = embeddingService2.isAvailable();
|
|
1566
|
+
logger.info("SEARCH", `HybridSearch inizializzato (embedding: ${this.embeddingInitialized ? "attivo" : "disattivato"})`);
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
logger.warn("SEARCH", "Inizializzazione embedding fallita, uso solo FTS5", {}, error);
|
|
1569
|
+
this.embeddingInitialized = false;
|
|
1570
|
+
}
|
|
726
1571
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1572
|
+
/**
|
|
1573
|
+
* Ricerca ibrida con scoring a 4 segnali
|
|
1574
|
+
*/
|
|
1575
|
+
async search(db, query, options = {}) {
|
|
1576
|
+
const limit = options.limit || 10;
|
|
1577
|
+
const weights = options.weights || SEARCH_WEIGHTS;
|
|
1578
|
+
const targetProject = options.project || "";
|
|
1579
|
+
const rawItems = /* @__PURE__ */ new Map();
|
|
1580
|
+
if (this.embeddingInitialized) {
|
|
1581
|
+
try {
|
|
1582
|
+
const embeddingService2 = getEmbeddingService();
|
|
1583
|
+
const queryEmbedding = await embeddingService2.embed(query);
|
|
1584
|
+
if (queryEmbedding) {
|
|
1585
|
+
const vectorSearch2 = getVectorSearch();
|
|
1586
|
+
const vectorResults = await vectorSearch2.search(db, queryEmbedding, {
|
|
1587
|
+
project: options.project,
|
|
1588
|
+
limit: limit * 2,
|
|
1589
|
+
// Prendiamo piu risultati per il ranking
|
|
1590
|
+
threshold: 0.3
|
|
1591
|
+
});
|
|
1592
|
+
for (const hit of vectorResults) {
|
|
1593
|
+
rawItems.set(String(hit.observationId), {
|
|
1594
|
+
id: String(hit.observationId),
|
|
1595
|
+
title: hit.title,
|
|
1596
|
+
content: hit.text || "",
|
|
1597
|
+
type: hit.type,
|
|
1598
|
+
project: hit.project,
|
|
1599
|
+
created_at: hit.created_at,
|
|
1600
|
+
created_at_epoch: hit.created_at_epoch,
|
|
1601
|
+
semanticScore: hit.similarity,
|
|
1602
|
+
fts5Rank: null,
|
|
1603
|
+
source: "vector"
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
logger.debug("SEARCH", `Vector search: ${vectorResults.length} risultati`);
|
|
1607
|
+
}
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
logger.warn("SEARCH", "Ricerca vettoriale fallita, uso solo keyword", {}, error);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
try {
|
|
1613
|
+
const { searchObservationsFTSWithRank: searchObservationsFTSWithRank2 } = await Promise.resolve().then(() => (init_Search(), Search_exports));
|
|
1614
|
+
const keywordResults = searchObservationsFTSWithRank2(db, query, {
|
|
1615
|
+
project: options.project,
|
|
1616
|
+
limit: limit * 2
|
|
1617
|
+
});
|
|
1618
|
+
for (const obs of keywordResults) {
|
|
1619
|
+
const id = String(obs.id);
|
|
1620
|
+
const existing = rawItems.get(id);
|
|
1621
|
+
if (existing) {
|
|
1622
|
+
existing.fts5Rank = obs.fts5_rank;
|
|
1623
|
+
existing.source = "vector";
|
|
1624
|
+
} else {
|
|
1625
|
+
rawItems.set(id, {
|
|
1626
|
+
id,
|
|
1627
|
+
title: obs.title,
|
|
1628
|
+
content: obs.text || obs.narrative || "",
|
|
1629
|
+
type: obs.type,
|
|
1630
|
+
project: obs.project,
|
|
1631
|
+
created_at: obs.created_at,
|
|
1632
|
+
created_at_epoch: obs.created_at_epoch,
|
|
1633
|
+
semanticScore: 0,
|
|
1634
|
+
fts5Rank: obs.fts5_rank,
|
|
1635
|
+
source: "keyword"
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
logger.debug("SEARCH", `Keyword search: ${keywordResults.length} risultati`);
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
logger.error("SEARCH", "Ricerca keyword fallita", {}, error);
|
|
1642
|
+
}
|
|
1643
|
+
if (rawItems.size === 0) return [];
|
|
1644
|
+
const allFTS5Ranks = Array.from(rawItems.values()).filter((item) => item.fts5Rank !== null).map((item) => item.fts5Rank);
|
|
1645
|
+
const scored = [];
|
|
1646
|
+
for (const item of rawItems.values()) {
|
|
1647
|
+
const signals = {
|
|
1648
|
+
semantic: item.semanticScore,
|
|
1649
|
+
fts5: item.fts5Rank !== null ? normalizeFTS5Rank(item.fts5Rank, allFTS5Ranks) : 0,
|
|
1650
|
+
recency: recencyScore(item.created_at_epoch),
|
|
1651
|
+
projectMatch: targetProject ? projectMatchScore(item.project, targetProject) : 0
|
|
1652
|
+
};
|
|
1653
|
+
const score = computeCompositeScore(signals, weights);
|
|
1654
|
+
const isHybrid = item.semanticScore > 0 && item.fts5Rank !== null;
|
|
1655
|
+
const hybridBoost = isHybrid ? 1.15 : 1;
|
|
1656
|
+
const finalScore = Math.min(1, score * hybridBoost * knowledgeTypeBoost(item.type));
|
|
1657
|
+
scored.push({
|
|
1658
|
+
id: item.id,
|
|
1659
|
+
title: item.title,
|
|
1660
|
+
content: item.content,
|
|
1661
|
+
type: item.type,
|
|
1662
|
+
project: item.project,
|
|
1663
|
+
created_at: item.created_at,
|
|
1664
|
+
created_at_epoch: item.created_at_epoch,
|
|
1665
|
+
score: finalScore,
|
|
1666
|
+
source: isHybrid ? "hybrid" : item.source,
|
|
1667
|
+
signals
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1671
|
+
const finalResults = scored.slice(0, limit);
|
|
1672
|
+
if (finalResults.length > 0) {
|
|
1673
|
+
try {
|
|
1674
|
+
const { updateLastAccessed: updateLastAccessed3 } = await Promise.resolve().then(() => (init_Observations(), Observations_exports));
|
|
1675
|
+
const ids = finalResults.map((r) => parseInt(r.id, 10)).filter((id) => id > 0);
|
|
1676
|
+
if (ids.length > 0) {
|
|
1677
|
+
updateLastAccessed3(db, ids);
|
|
1678
|
+
}
|
|
1679
|
+
} catch {
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
return finalResults;
|
|
730
1683
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1684
|
+
};
|
|
1685
|
+
var hybridSearch = null;
|
|
1686
|
+
function getHybridSearch() {
|
|
1687
|
+
if (!hybridSearch) {
|
|
1688
|
+
hybridSearch = new HybridSearch();
|
|
734
1689
|
}
|
|
735
|
-
|
|
736
|
-
params.push(limit);
|
|
737
|
-
const stmt = db.query(sql);
|
|
738
|
-
return stmt.all(...params);
|
|
739
|
-
}
|
|
740
|
-
function getObservationsByIds(db, ids) {
|
|
741
|
-
if (ids.length === 0) return [];
|
|
742
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
743
|
-
const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
|
|
744
|
-
const stmt = db.query(sql);
|
|
745
|
-
return stmt.all(...ids);
|
|
746
|
-
}
|
|
747
|
-
function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
748
|
-
const anchorStmt = db.query("SELECT created_at_epoch FROM observations WHERE id = ?");
|
|
749
|
-
const anchor = anchorStmt.get(anchorId);
|
|
750
|
-
if (!anchor) return [];
|
|
751
|
-
const anchorEpoch = anchor.created_at_epoch;
|
|
752
|
-
const beforeStmt = db.query(`
|
|
753
|
-
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
754
|
-
FROM observations
|
|
755
|
-
WHERE created_at_epoch < ?
|
|
756
|
-
ORDER BY created_at_epoch DESC
|
|
757
|
-
LIMIT ?
|
|
758
|
-
`);
|
|
759
|
-
const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
|
|
760
|
-
const selfStmt = db.query(`
|
|
761
|
-
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
762
|
-
FROM observations WHERE id = ?
|
|
763
|
-
`);
|
|
764
|
-
const self = selfStmt.all(anchorId);
|
|
765
|
-
const afterStmt = db.query(`
|
|
766
|
-
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
767
|
-
FROM observations
|
|
768
|
-
WHERE created_at_epoch > ?
|
|
769
|
-
ORDER BY created_at_epoch ASC
|
|
770
|
-
LIMIT ?
|
|
771
|
-
`);
|
|
772
|
-
const after = afterStmt.all(anchorEpoch, depthAfter);
|
|
773
|
-
return [...before, ...self, ...after];
|
|
1690
|
+
return hybridSearch;
|
|
774
1691
|
}
|
|
775
1692
|
|
|
1693
|
+
// src/sdk/index.ts
|
|
1694
|
+
init_EmbeddingService();
|
|
1695
|
+
init_logger();
|
|
1696
|
+
|
|
1697
|
+
// src/types/worker-types.ts
|
|
1698
|
+
var KNOWLEDGE_TYPES = ["constraint", "decision", "heuristic", "rejected"];
|
|
1699
|
+
|
|
776
1700
|
// src/sdk/index.ts
|
|
777
1701
|
var KiroMemorySDK = class {
|
|
778
1702
|
db;
|
|
@@ -805,11 +1729,62 @@ var KiroMemorySDK = class {
|
|
|
805
1729
|
recentPrompts: getPromptsByProject(this.db.db, this.project, 10)
|
|
806
1730
|
};
|
|
807
1731
|
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Valida input per storeObservation
|
|
1734
|
+
*/
|
|
1735
|
+
validateObservationInput(data) {
|
|
1736
|
+
if (!data.type || typeof data.type !== "string" || data.type.length > 100) {
|
|
1737
|
+
throw new Error("type \xE8 obbligatorio (stringa, max 100 caratteri)");
|
|
1738
|
+
}
|
|
1739
|
+
if (!data.title || typeof data.title !== "string" || data.title.length > 500) {
|
|
1740
|
+
throw new Error("title \xE8 obbligatorio (stringa, max 500 caratteri)");
|
|
1741
|
+
}
|
|
1742
|
+
if (!data.content || typeof data.content !== "string" || data.content.length > 1e5) {
|
|
1743
|
+
throw new Error("content \xE8 obbligatorio (stringa, max 100KB)");
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Valida input per storeSummary
|
|
1748
|
+
*/
|
|
1749
|
+
validateSummaryInput(data) {
|
|
1750
|
+
const MAX = 5e4;
|
|
1751
|
+
for (const [key, val] of Object.entries(data)) {
|
|
1752
|
+
if (val !== void 0 && val !== null) {
|
|
1753
|
+
if (typeof val !== "string") throw new Error(`${key} deve essere una stringa`);
|
|
1754
|
+
if (val.length > MAX) throw new Error(`${key} troppo grande (max 50KB)`);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Genera e salva embedding per un'osservazione (fire-and-forget, non blocca)
|
|
1760
|
+
*/
|
|
1761
|
+
async generateEmbeddingAsync(observationId, title, content, concepts) {
|
|
1762
|
+
try {
|
|
1763
|
+
const embeddingService2 = getEmbeddingService();
|
|
1764
|
+
if (!embeddingService2.isAvailable()) return;
|
|
1765
|
+
const parts = [title, content];
|
|
1766
|
+
if (concepts?.length) parts.push(concepts.join(", "));
|
|
1767
|
+
const fullText = parts.join(" ").substring(0, 2e3);
|
|
1768
|
+
const embedding = await embeddingService2.embed(fullText);
|
|
1769
|
+
if (embedding) {
|
|
1770
|
+
const vectorSearch2 = getVectorSearch();
|
|
1771
|
+
await vectorSearch2.storeEmbedding(
|
|
1772
|
+
this.db.db,
|
|
1773
|
+
observationId,
|
|
1774
|
+
embedding,
|
|
1775
|
+
embeddingService2.getProvider() || "unknown"
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
808
1782
|
/**
|
|
809
1783
|
* Store a new observation
|
|
810
1784
|
*/
|
|
811
1785
|
async storeObservation(data) {
|
|
812
|
-
|
|
1786
|
+
this.validateObservationInput(data);
|
|
1787
|
+
const observationId = createObservation(
|
|
813
1788
|
this.db.db,
|
|
814
1789
|
"sdk-" + Date.now(),
|
|
815
1790
|
this.project,
|
|
@@ -830,11 +1805,76 @@ var KiroMemorySDK = class {
|
|
|
830
1805
|
0
|
|
831
1806
|
// prompt_number
|
|
832
1807
|
);
|
|
1808
|
+
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1809
|
+
});
|
|
1810
|
+
return observationId;
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Salva conoscenza strutturata (constraint, decision, heuristic, rejected).
|
|
1814
|
+
* Usa il campo `type` per il knowledgeType e `facts` per i metadati JSON.
|
|
1815
|
+
*/
|
|
1816
|
+
async storeKnowledge(data) {
|
|
1817
|
+
if (!KNOWLEDGE_TYPES.includes(data.knowledgeType)) {
|
|
1818
|
+
throw new Error(`knowledgeType non valido: ${data.knowledgeType}. Valori ammessi: ${KNOWLEDGE_TYPES.join(", ")}`);
|
|
1819
|
+
}
|
|
1820
|
+
this.validateObservationInput({ type: data.knowledgeType, title: data.title, content: data.content });
|
|
1821
|
+
const metadata = (() => {
|
|
1822
|
+
switch (data.knowledgeType) {
|
|
1823
|
+
case "constraint":
|
|
1824
|
+
return {
|
|
1825
|
+
knowledgeType: "constraint",
|
|
1826
|
+
severity: data.metadata?.severity || "soft",
|
|
1827
|
+
reason: data.metadata?.reason
|
|
1828
|
+
};
|
|
1829
|
+
case "decision":
|
|
1830
|
+
return {
|
|
1831
|
+
knowledgeType: "decision",
|
|
1832
|
+
alternatives: data.metadata?.alternatives,
|
|
1833
|
+
reason: data.metadata?.reason
|
|
1834
|
+
};
|
|
1835
|
+
case "heuristic":
|
|
1836
|
+
return {
|
|
1837
|
+
knowledgeType: "heuristic",
|
|
1838
|
+
context: data.metadata?.context,
|
|
1839
|
+
confidence: data.metadata?.confidence
|
|
1840
|
+
};
|
|
1841
|
+
case "rejected":
|
|
1842
|
+
return {
|
|
1843
|
+
knowledgeType: "rejected",
|
|
1844
|
+
reason: data.metadata?.reason || "",
|
|
1845
|
+
alternatives: data.metadata?.alternatives
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
})();
|
|
1849
|
+
const observationId = createObservation(
|
|
1850
|
+
this.db.db,
|
|
1851
|
+
"sdk-" + Date.now(),
|
|
1852
|
+
data.project || this.project,
|
|
1853
|
+
data.knowledgeType,
|
|
1854
|
+
// type = knowledgeType
|
|
1855
|
+
data.title,
|
|
1856
|
+
null,
|
|
1857
|
+
// subtitle
|
|
1858
|
+
data.content,
|
|
1859
|
+
null,
|
|
1860
|
+
// narrative
|
|
1861
|
+
JSON.stringify(metadata),
|
|
1862
|
+
// facts = metadati JSON
|
|
1863
|
+
data.concepts?.join(", ") || null,
|
|
1864
|
+
data.files?.join(", ") || null,
|
|
1865
|
+
data.files?.join(", ") || null,
|
|
1866
|
+
0
|
|
1867
|
+
// prompt_number
|
|
1868
|
+
);
|
|
1869
|
+
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1870
|
+
});
|
|
1871
|
+
return observationId;
|
|
833
1872
|
}
|
|
834
1873
|
/**
|
|
835
1874
|
* Store a session summary
|
|
836
1875
|
*/
|
|
837
1876
|
async storeSummary(data) {
|
|
1877
|
+
this.validateSummaryInput(data);
|
|
838
1878
|
return createSummary(
|
|
839
1879
|
this.db.db,
|
|
840
1880
|
"sdk-" + Date.now(),
|
|
@@ -910,25 +1950,246 @@ var KiroMemorySDK = class {
|
|
|
910
1950
|
completed_at_epoch: null
|
|
911
1951
|
};
|
|
912
1952
|
}
|
|
913
|
-
return session;
|
|
1953
|
+
return session;
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Store a user prompt
|
|
1957
|
+
*/
|
|
1958
|
+
async storePrompt(contentSessionId, promptNumber, text) {
|
|
1959
|
+
return createPrompt(this.db.db, contentSessionId, this.project, promptNumber, text);
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Complete a session
|
|
1963
|
+
*/
|
|
1964
|
+
async completeSession(sessionId) {
|
|
1965
|
+
completeSession(this.db.db, sessionId);
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Getter for current project name
|
|
1969
|
+
*/
|
|
1970
|
+
getProject() {
|
|
1971
|
+
return this.project;
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Ricerca ibrida: vector search + keyword FTS5
|
|
1975
|
+
* Richiede inizializzazione HybridSearch (embedding service)
|
|
1976
|
+
*/
|
|
1977
|
+
async hybridSearch(query, options = {}) {
|
|
1978
|
+
const hybridSearch2 = getHybridSearch();
|
|
1979
|
+
return hybridSearch2.search(this.db.db, query, {
|
|
1980
|
+
project: this.project,
|
|
1981
|
+
limit: options.limit || 10
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Ricerca solo semantica (vector search)
|
|
1986
|
+
* Ritorna risultati basati su similarità coseno con gli embeddings
|
|
1987
|
+
*/
|
|
1988
|
+
async semanticSearch(query, options = {}) {
|
|
1989
|
+
const embeddingService2 = getEmbeddingService();
|
|
1990
|
+
if (!embeddingService2.isAvailable()) {
|
|
1991
|
+
await embeddingService2.initialize();
|
|
1992
|
+
}
|
|
1993
|
+
if (!embeddingService2.isAvailable()) return [];
|
|
1994
|
+
const queryEmbedding = await embeddingService2.embed(query);
|
|
1995
|
+
if (!queryEmbedding) return [];
|
|
1996
|
+
const vectorSearch2 = getVectorSearch();
|
|
1997
|
+
const results = await vectorSearch2.search(this.db.db, queryEmbedding, {
|
|
1998
|
+
project: this.project,
|
|
1999
|
+
limit: options.limit || 10,
|
|
2000
|
+
threshold: options.threshold || 0.3
|
|
2001
|
+
});
|
|
2002
|
+
return results.map((r) => ({
|
|
2003
|
+
id: String(r.observationId),
|
|
2004
|
+
title: r.title,
|
|
2005
|
+
content: r.text || "",
|
|
2006
|
+
type: r.type,
|
|
2007
|
+
project: r.project,
|
|
2008
|
+
created_at: r.created_at,
|
|
2009
|
+
created_at_epoch: r.created_at_epoch,
|
|
2010
|
+
score: r.similarity,
|
|
2011
|
+
source: "vector",
|
|
2012
|
+
signals: {
|
|
2013
|
+
semantic: r.similarity,
|
|
2014
|
+
fts5: 0,
|
|
2015
|
+
recency: recencyScore(r.created_at_epoch),
|
|
2016
|
+
projectMatch: projectMatchScore(r.project, this.project)
|
|
2017
|
+
}
|
|
2018
|
+
}));
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Genera embeddings per osservazioni che non li hanno ancora
|
|
2022
|
+
*/
|
|
2023
|
+
async backfillEmbeddings(batchSize = 50) {
|
|
2024
|
+
const vectorSearch2 = getVectorSearch();
|
|
2025
|
+
return vectorSearch2.backfillEmbeddings(this.db.db, batchSize);
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Statistiche sugli embeddings nel database
|
|
2029
|
+
*/
|
|
2030
|
+
getEmbeddingStats() {
|
|
2031
|
+
const vectorSearch2 = getVectorSearch();
|
|
2032
|
+
return vectorSearch2.getStats(this.db.db);
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Inizializza il servizio di embedding (lazy, chiamare prima di hybridSearch)
|
|
2036
|
+
*/
|
|
2037
|
+
async initializeEmbeddings() {
|
|
2038
|
+
const hybridSearch2 = getHybridSearch();
|
|
2039
|
+
await hybridSearch2.initialize();
|
|
2040
|
+
return getEmbeddingService().isAvailable();
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Contesto smart con ranking a 4 segnali e budget token.
|
|
2044
|
+
*
|
|
2045
|
+
* Se query presente: usa HybridSearch con SEARCH_WEIGHTS.
|
|
2046
|
+
* Se senza query: ranking per recency + project match (CONTEXT_WEIGHTS).
|
|
2047
|
+
*/
|
|
2048
|
+
async getSmartContext(options = {}) {
|
|
2049
|
+
const tokenBudget = options.tokenBudget || parseInt(process.env.KIRO_MEMORY_CONTEXT_TOKENS || "0", 10) || 2e3;
|
|
2050
|
+
const summaries = getSummariesByProject(this.db.db, this.project, 5);
|
|
2051
|
+
let items;
|
|
2052
|
+
if (options.query) {
|
|
2053
|
+
const hybridSearch2 = getHybridSearch();
|
|
2054
|
+
const results = await hybridSearch2.search(this.db.db, options.query, {
|
|
2055
|
+
project: this.project,
|
|
2056
|
+
limit: 30
|
|
2057
|
+
});
|
|
2058
|
+
items = results.map((r) => ({
|
|
2059
|
+
id: parseInt(r.id, 10) || 0,
|
|
2060
|
+
title: r.title,
|
|
2061
|
+
content: r.content,
|
|
2062
|
+
type: r.type,
|
|
2063
|
+
project: r.project,
|
|
2064
|
+
created_at: r.created_at,
|
|
2065
|
+
created_at_epoch: r.created_at_epoch,
|
|
2066
|
+
score: r.score,
|
|
2067
|
+
signals: r.signals
|
|
2068
|
+
}));
|
|
2069
|
+
} else {
|
|
2070
|
+
const observations = getObservationsByProject(this.db.db, this.project, 30);
|
|
2071
|
+
items = observations.map((obs) => {
|
|
2072
|
+
const signals = {
|
|
2073
|
+
semantic: 0,
|
|
2074
|
+
fts5: 0,
|
|
2075
|
+
recency: recencyScore(obs.created_at_epoch),
|
|
2076
|
+
projectMatch: projectMatchScore(obs.project, this.project)
|
|
2077
|
+
};
|
|
2078
|
+
const baseScore = computeCompositeScore(signals, CONTEXT_WEIGHTS);
|
|
2079
|
+
const boostedScore = Math.min(1, baseScore * knowledgeTypeBoost(obs.type));
|
|
2080
|
+
return {
|
|
2081
|
+
id: obs.id,
|
|
2082
|
+
title: obs.title,
|
|
2083
|
+
content: obs.text || obs.narrative || "",
|
|
2084
|
+
type: obs.type,
|
|
2085
|
+
project: obs.project,
|
|
2086
|
+
created_at: obs.created_at,
|
|
2087
|
+
created_at_epoch: obs.created_at_epoch,
|
|
2088
|
+
score: boostedScore,
|
|
2089
|
+
signals
|
|
2090
|
+
};
|
|
2091
|
+
});
|
|
2092
|
+
items.sort((a, b) => b.score - a.score);
|
|
2093
|
+
}
|
|
2094
|
+
let tokensUsed = 0;
|
|
2095
|
+
for (const item of items) {
|
|
2096
|
+
tokensUsed += Math.ceil((item.title.length + item.content.length) / 4);
|
|
2097
|
+
if (tokensUsed > tokenBudget) break;
|
|
2098
|
+
}
|
|
2099
|
+
return {
|
|
2100
|
+
project: this.project,
|
|
2101
|
+
items,
|
|
2102
|
+
summaries,
|
|
2103
|
+
tokenBudget,
|
|
2104
|
+
tokensUsed: Math.min(tokensUsed, tokenBudget)
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Rileva osservazioni stale (file modificati dopo la creazione) e le marca nel DB.
|
|
2109
|
+
* Ritorna il numero di osservazioni marcate come stale.
|
|
2110
|
+
*/
|
|
2111
|
+
async detectStaleObservations() {
|
|
2112
|
+
const staleObs = getStaleObservations(this.db.db, this.project);
|
|
2113
|
+
if (staleObs.length > 0) {
|
|
2114
|
+
const ids = staleObs.map((o) => o.id);
|
|
2115
|
+
markObservationsStale(this.db.db, ids, true);
|
|
2116
|
+
}
|
|
2117
|
+
return staleObs.length;
|
|
914
2118
|
}
|
|
915
2119
|
/**
|
|
916
|
-
*
|
|
2120
|
+
* Consolida osservazioni duplicate sullo stesso file e tipo.
|
|
2121
|
+
* Raggruppa per (project, type, files_modified), mantiene la piu recente.
|
|
917
2122
|
*/
|
|
918
|
-
async
|
|
919
|
-
return
|
|
2123
|
+
async consolidateObservations(options = {}) {
|
|
2124
|
+
return consolidateObservations(this.db.db, this.project, options);
|
|
920
2125
|
}
|
|
921
2126
|
/**
|
|
922
|
-
*
|
|
2127
|
+
* Statistiche decay: totale, stale, mai accedute, accedute di recente.
|
|
923
2128
|
*/
|
|
924
|
-
async
|
|
925
|
-
|
|
2129
|
+
async getDecayStats() {
|
|
2130
|
+
const total = this.db.db.query(
|
|
2131
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ?"
|
|
2132
|
+
).get(this.project)?.count || 0;
|
|
2133
|
+
const stale = this.db.db.query(
|
|
2134
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND is_stale = 1"
|
|
2135
|
+
).get(this.project)?.count || 0;
|
|
2136
|
+
const neverAccessed = this.db.db.query(
|
|
2137
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch IS NULL"
|
|
2138
|
+
).get(this.project)?.count || 0;
|
|
2139
|
+
const recentThreshold = Date.now() - 48 * 60 * 60 * 1e3;
|
|
2140
|
+
const recentlyAccessed = this.db.db.query(
|
|
2141
|
+
"SELECT COUNT(*) as count FROM observations WHERE project = ? AND last_accessed_epoch > ?"
|
|
2142
|
+
).get(this.project, recentThreshold)?.count || 0;
|
|
2143
|
+
return { total, stale, neverAccessed, recentlyAccessed };
|
|
926
2144
|
}
|
|
927
2145
|
/**
|
|
928
|
-
*
|
|
2146
|
+
* Crea un checkpoint strutturato per resume sessione.
|
|
2147
|
+
* Salva automaticamente un context_snapshot con le ultime 10 osservazioni.
|
|
929
2148
|
*/
|
|
930
|
-
|
|
931
|
-
|
|
2149
|
+
async createCheckpoint(sessionId, data) {
|
|
2150
|
+
const recentObs = getObservationsByProject(this.db.db, this.project, 10);
|
|
2151
|
+
const contextSnapshot = JSON.stringify(
|
|
2152
|
+
recentObs.map((o) => ({ id: o.id, type: o.type, title: o.title, text: o.text?.substring(0, 200) }))
|
|
2153
|
+
);
|
|
2154
|
+
return createCheckpoint(this.db.db, sessionId, this.project, {
|
|
2155
|
+
task: data.task,
|
|
2156
|
+
progress: data.progress,
|
|
2157
|
+
nextSteps: data.nextSteps,
|
|
2158
|
+
openQuestions: data.openQuestions,
|
|
2159
|
+
relevantFiles: data.relevantFiles?.join(", "),
|
|
2160
|
+
contextSnapshot
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Recupera l'ultimo checkpoint di una sessione specifica.
|
|
2165
|
+
*/
|
|
2166
|
+
async getCheckpoint(sessionId) {
|
|
2167
|
+
return getLatestCheckpoint(this.db.db, sessionId);
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Recupera l'ultimo checkpoint per il progetto corrente.
|
|
2171
|
+
* Utile per resume automatico senza specificare session ID.
|
|
2172
|
+
*/
|
|
2173
|
+
async getLatestProjectCheckpoint() {
|
|
2174
|
+
return getLatestCheckpointByProject(this.db.db, this.project);
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Genera un report di attività per il progetto corrente.
|
|
2178
|
+
* Aggrega osservazioni, sessioni, summaries e file per un periodo temporale.
|
|
2179
|
+
*/
|
|
2180
|
+
async generateReport(options) {
|
|
2181
|
+
const now = /* @__PURE__ */ new Date();
|
|
2182
|
+
let startEpoch;
|
|
2183
|
+
let endEpoch = now.getTime();
|
|
2184
|
+
if (options?.startDate && options?.endDate) {
|
|
2185
|
+
startEpoch = options.startDate.getTime();
|
|
2186
|
+
endEpoch = options.endDate.getTime();
|
|
2187
|
+
} else {
|
|
2188
|
+
const period = options?.period || "weekly";
|
|
2189
|
+
const daysBack = period === "monthly" ? 30 : 7;
|
|
2190
|
+
startEpoch = endEpoch - daysBack * 24 * 60 * 60 * 1e3;
|
|
2191
|
+
}
|
|
2192
|
+
return getReportData(this.db.db, this.project, startEpoch, endEpoch);
|
|
932
2193
|
}
|
|
933
2194
|
/**
|
|
934
2195
|
* Getter for direct database access (for API routes)
|
|
@@ -947,9 +2208,172 @@ function createKiroMemory(config) {
|
|
|
947
2208
|
return new KiroMemorySDK(config);
|
|
948
2209
|
}
|
|
949
2210
|
|
|
2211
|
+
// src/services/report-formatter.ts
|
|
2212
|
+
function formatReportText(data) {
|
|
2213
|
+
const lines = [];
|
|
2214
|
+
lines.push("");
|
|
2215
|
+
lines.push(` \x1B[36m\u2550\u2550\u2550 Kiro Memory Report \u2014 ${data.period.label} \u2550\u2550\u2550\x1B[0m`);
|
|
2216
|
+
lines.push(` \x1B[2m${data.period.start} \u2192 ${data.period.end} (${data.period.days} days)\x1B[0m`);
|
|
2217
|
+
lines.push("");
|
|
2218
|
+
lines.push(` \x1B[1mOverview\x1B[0m`);
|
|
2219
|
+
lines.push(` Observations: ${data.overview.observations}`);
|
|
2220
|
+
lines.push(` Summaries: ${data.overview.summaries}`);
|
|
2221
|
+
lines.push(` Sessions: ${data.overview.sessions}`);
|
|
2222
|
+
lines.push(` Prompts: ${data.overview.prompts}`);
|
|
2223
|
+
lines.push(` Knowledge: ${data.overview.knowledgeCount}`);
|
|
2224
|
+
if (data.overview.staleCount > 0) {
|
|
2225
|
+
lines.push(` Stale: ${data.overview.staleCount}`);
|
|
2226
|
+
}
|
|
2227
|
+
lines.push("");
|
|
2228
|
+
if (data.sessionStats.total > 0) {
|
|
2229
|
+
const completionPct = data.sessionStats.total > 0 ? Math.round(data.sessionStats.completed / data.sessionStats.total * 100) : 0;
|
|
2230
|
+
lines.push(` \x1B[1mSessions\x1B[0m`);
|
|
2231
|
+
lines.push(` Total: ${data.sessionStats.total} | Completed: ${data.sessionStats.completed} (${completionPct}%)`);
|
|
2232
|
+
if (data.sessionStats.avgDurationMinutes > 0) {
|
|
2233
|
+
lines.push(` Avg duration: ${data.sessionStats.avgDurationMinutes} min`);
|
|
2234
|
+
}
|
|
2235
|
+
lines.push("");
|
|
2236
|
+
}
|
|
2237
|
+
if (data.timeline.length > 0) {
|
|
2238
|
+
lines.push(` \x1B[1mTimeline\x1B[0m`);
|
|
2239
|
+
const maxCount = Math.max(...data.timeline.map((t) => t.count));
|
|
2240
|
+
const maxBarLen = 30;
|
|
2241
|
+
for (const entry of data.timeline) {
|
|
2242
|
+
const barLen = maxCount > 0 ? Math.round(entry.count / maxCount * maxBarLen) : 0;
|
|
2243
|
+
const bar = "\x1B[32m" + "\u2593".repeat(barLen) + "\x1B[0m";
|
|
2244
|
+
const dayShort = entry.day.substring(5);
|
|
2245
|
+
lines.push(` ${dayShort} ${bar} ${entry.count}`);
|
|
2246
|
+
}
|
|
2247
|
+
lines.push("");
|
|
2248
|
+
}
|
|
2249
|
+
if (data.typeDistribution.length > 0) {
|
|
2250
|
+
lines.push(` \x1B[1mBy Type\x1B[0m`);
|
|
2251
|
+
for (const entry of data.typeDistribution) {
|
|
2252
|
+
lines.push(` ${entry.type.padEnd(16)} ${entry.count}`);
|
|
2253
|
+
}
|
|
2254
|
+
lines.push("");
|
|
2255
|
+
}
|
|
2256
|
+
if (data.topLearnings.length > 0) {
|
|
2257
|
+
lines.push(` \x1B[1mKey Learnings\x1B[0m`);
|
|
2258
|
+
for (const learning of data.topLearnings) {
|
|
2259
|
+
lines.push(` - ${learning}`);
|
|
2260
|
+
}
|
|
2261
|
+
lines.push("");
|
|
2262
|
+
}
|
|
2263
|
+
if (data.completedTasks.length > 0) {
|
|
2264
|
+
lines.push(` \x1B[1mCompleted\x1B[0m`);
|
|
2265
|
+
for (const task of data.completedTasks) {
|
|
2266
|
+
lines.push(` - ${task}`);
|
|
2267
|
+
}
|
|
2268
|
+
lines.push("");
|
|
2269
|
+
}
|
|
2270
|
+
if (data.nextSteps.length > 0) {
|
|
2271
|
+
lines.push(` \x1B[1mNext Steps\x1B[0m`);
|
|
2272
|
+
for (const step of data.nextSteps) {
|
|
2273
|
+
lines.push(` - ${step}`);
|
|
2274
|
+
}
|
|
2275
|
+
lines.push("");
|
|
2276
|
+
}
|
|
2277
|
+
if (data.fileHotspots.length > 0) {
|
|
2278
|
+
lines.push(` \x1B[1mFile Hotspots\x1B[0m`);
|
|
2279
|
+
for (const entry of data.fileHotspots.slice(0, 10)) {
|
|
2280
|
+
lines.push(` ${entry.file} (${entry.count}x)`);
|
|
2281
|
+
}
|
|
2282
|
+
lines.push("");
|
|
2283
|
+
}
|
|
2284
|
+
return lines.join("\n");
|
|
2285
|
+
}
|
|
2286
|
+
function formatReportMarkdown(data) {
|
|
2287
|
+
const lines = [];
|
|
2288
|
+
lines.push(`# Kiro Memory Report \u2014 ${data.period.label}`);
|
|
2289
|
+
lines.push("");
|
|
2290
|
+
lines.push(`**Period**: ${data.period.start} \u2192 ${data.period.end} (${data.period.days} days)`);
|
|
2291
|
+
lines.push("");
|
|
2292
|
+
lines.push("## Overview");
|
|
2293
|
+
lines.push("");
|
|
2294
|
+
lines.push("| Metric | Count |");
|
|
2295
|
+
lines.push("|--------|------:|");
|
|
2296
|
+
lines.push(`| Observations | ${data.overview.observations} |`);
|
|
2297
|
+
lines.push(`| Summaries | ${data.overview.summaries} |`);
|
|
2298
|
+
lines.push(`| Sessions | ${data.overview.sessions} |`);
|
|
2299
|
+
lines.push(`| Prompts | ${data.overview.prompts} |`);
|
|
2300
|
+
lines.push(`| Knowledge items | ${data.overview.knowledgeCount} |`);
|
|
2301
|
+
if (data.overview.staleCount > 0) {
|
|
2302
|
+
lines.push(`| Stale observations | ${data.overview.staleCount} |`);
|
|
2303
|
+
}
|
|
2304
|
+
lines.push("");
|
|
2305
|
+
if (data.sessionStats.total > 0) {
|
|
2306
|
+
const completionPct = Math.round(data.sessionStats.completed / data.sessionStats.total * 100);
|
|
2307
|
+
lines.push("## Sessions");
|
|
2308
|
+
lines.push("");
|
|
2309
|
+
lines.push(`- **Total**: ${data.sessionStats.total}`);
|
|
2310
|
+
lines.push(`- **Completed**: ${data.sessionStats.completed} (${completionPct}%)`);
|
|
2311
|
+
if (data.sessionStats.avgDurationMinutes > 0) {
|
|
2312
|
+
lines.push(`- **Avg duration**: ${data.sessionStats.avgDurationMinutes} min`);
|
|
2313
|
+
}
|
|
2314
|
+
lines.push("");
|
|
2315
|
+
}
|
|
2316
|
+
if (data.timeline.length > 0) {
|
|
2317
|
+
lines.push("## Activity Timeline");
|
|
2318
|
+
lines.push("");
|
|
2319
|
+
lines.push("| Date | Observations |");
|
|
2320
|
+
lines.push("|------|------------:|");
|
|
2321
|
+
for (const entry of data.timeline) {
|
|
2322
|
+
lines.push(`| ${entry.day} | ${entry.count} |`);
|
|
2323
|
+
}
|
|
2324
|
+
lines.push("");
|
|
2325
|
+
}
|
|
2326
|
+
if (data.typeDistribution.length > 0) {
|
|
2327
|
+
lines.push("## Observation Types");
|
|
2328
|
+
lines.push("");
|
|
2329
|
+
for (const entry of data.typeDistribution) {
|
|
2330
|
+
lines.push(`- **${entry.type}**: ${entry.count}`);
|
|
2331
|
+
}
|
|
2332
|
+
lines.push("");
|
|
2333
|
+
}
|
|
2334
|
+
if (data.topLearnings.length > 0) {
|
|
2335
|
+
lines.push("## Key Learnings");
|
|
2336
|
+
lines.push("");
|
|
2337
|
+
for (const learning of data.topLearnings) {
|
|
2338
|
+
lines.push(`- ${learning}`);
|
|
2339
|
+
}
|
|
2340
|
+
lines.push("");
|
|
2341
|
+
}
|
|
2342
|
+
if (data.completedTasks.length > 0) {
|
|
2343
|
+
lines.push("## Completed");
|
|
2344
|
+
lines.push("");
|
|
2345
|
+
for (const task of data.completedTasks) {
|
|
2346
|
+
lines.push(`- ${task}`);
|
|
2347
|
+
}
|
|
2348
|
+
lines.push("");
|
|
2349
|
+
}
|
|
2350
|
+
if (data.nextSteps.length > 0) {
|
|
2351
|
+
lines.push("## Next Steps");
|
|
2352
|
+
lines.push("");
|
|
2353
|
+
for (const step of data.nextSteps) {
|
|
2354
|
+
lines.push(`- ${step}`);
|
|
2355
|
+
}
|
|
2356
|
+
lines.push("");
|
|
2357
|
+
}
|
|
2358
|
+
if (data.fileHotspots.length > 0) {
|
|
2359
|
+
lines.push("## File Hotspots");
|
|
2360
|
+
lines.push("");
|
|
2361
|
+
lines.push("| File | Modifications |");
|
|
2362
|
+
lines.push("|------|-------------:|");
|
|
2363
|
+
for (const entry of data.fileHotspots.slice(0, 10)) {
|
|
2364
|
+
lines.push(`| \`${entry.file}\` | ${entry.count} |`);
|
|
2365
|
+
}
|
|
2366
|
+
lines.push("");
|
|
2367
|
+
}
|
|
2368
|
+
return lines.join("\n");
|
|
2369
|
+
}
|
|
2370
|
+
function formatReportJson(data) {
|
|
2371
|
+
return JSON.stringify(data, null, 2);
|
|
2372
|
+
}
|
|
2373
|
+
|
|
950
2374
|
// src/cli/contextkit.ts
|
|
951
2375
|
import { execSync } from "child_process";
|
|
952
|
-
import { existsSync as
|
|
2376
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync, appendFileSync as appendFileSync2 } from "fs";
|
|
953
2377
|
import { join as join3, dirname as dirname2 } from "path";
|
|
954
2378
|
import { homedir as homedir3, platform, release } from "os";
|
|
955
2379
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -1010,7 +2434,7 @@ function isWSL() {
|
|
|
1010
2434
|
try {
|
|
1011
2435
|
const rel = release().toLowerCase();
|
|
1012
2436
|
if (rel.includes("microsoft") || rel.includes("wsl")) return true;
|
|
1013
|
-
if (
|
|
2437
|
+
if (existsSync4("/proc/version")) {
|
|
1014
2438
|
const proc = readFileSync2("/proc/version", "utf8").toLowerCase();
|
|
1015
2439
|
return proc.includes("microsoft") || proc.includes("wsl");
|
|
1016
2440
|
}
|
|
@@ -1184,10 +2608,11 @@ async function tryAutoFix(failedChecks) {
|
|
|
1184
2608
|
try {
|
|
1185
2609
|
const npmGlobalDir = join3(homedir3(), ".npm-global");
|
|
1186
2610
|
mkdirSync3(npmGlobalDir, { recursive: true });
|
|
1187
|
-
|
|
2611
|
+
const { spawnSync: spawnNpmConfig } = __require("child_process");
|
|
2612
|
+
spawnNpmConfig("npm", ["config", "set", "prefix", npmGlobalDir], { stdio: "ignore" });
|
|
1188
2613
|
const exportLine = 'export PATH="$HOME/.npm-global/bin:$PATH"';
|
|
1189
2614
|
let alreadyInRc = false;
|
|
1190
|
-
if (
|
|
2615
|
+
if (existsSync4(rcFile)) {
|
|
1191
2616
|
const content = readFileSync2(rcFile, "utf8");
|
|
1192
2617
|
alreadyInRc = content.includes(".npm-global/bin");
|
|
1193
2618
|
}
|
|
@@ -1210,7 +2635,7 @@ ${exportLine}
|
|
|
1210
2635
|
console.log("\n Fixing npm binary (installing nvm + Node.js 22)...");
|
|
1211
2636
|
const nvmDir = join3(homedir3(), ".nvm");
|
|
1212
2637
|
try {
|
|
1213
|
-
if (
|
|
2638
|
+
if (existsSync4(nvmDir)) {
|
|
1214
2639
|
console.log(` nvm already installed at ${nvmDir}`);
|
|
1215
2640
|
} else {
|
|
1216
2641
|
console.log(" Downloading nvm...");
|
|
@@ -1255,139 +2680,477 @@ ${exportLine}
|
|
|
1255
2680
|
if (sqliteCheck) {
|
|
1256
2681
|
console.log("\n Rebuilding better-sqlite3...");
|
|
1257
2682
|
try {
|
|
1258
|
-
const
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
console.log(` \x1B[
|
|
1272
|
-
|
|
2683
|
+
const { spawnSync: spawnRebuild } = __require("child_process");
|
|
2684
|
+
const globalDirResult = spawnRebuild("npm", ["prefix", "-g"], { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] });
|
|
2685
|
+
const globalDir = (globalDirResult.stdout || "").trim();
|
|
2686
|
+
const sqlitePkg = join3(globalDir, "lib", "node_modules", "kiro-memory");
|
|
2687
|
+
if (existsSync4(sqlitePkg)) {
|
|
2688
|
+
spawnRebuild("npm", ["rebuild", "better-sqlite3"], {
|
|
2689
|
+
cwd: sqlitePkg,
|
|
2690
|
+
stdio: "inherit",
|
|
2691
|
+
timeout: 6e4
|
|
2692
|
+
});
|
|
2693
|
+
} else {
|
|
2694
|
+
spawnRebuild("npm", ["rebuild", "better-sqlite3"], { stdio: "inherit", timeout: 6e4 });
|
|
2695
|
+
}
|
|
2696
|
+
console.log(` \x1B[32m\u2713\x1B[0m better-sqlite3 rebuilt`);
|
|
2697
|
+
anyFixed = true;
|
|
2698
|
+
} catch (err) {
|
|
2699
|
+
console.log(` \x1B[31m\u2717\x1B[0m Could not rebuild: ${err.message}`);
|
|
2700
|
+
console.log(" Try: npm install -g kiro-memory --build-from-source");
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
console.log("");
|
|
2704
|
+
return { fixed: anyFixed, needsRestart };
|
|
2705
|
+
}
|
|
2706
|
+
async function installKiro() {
|
|
2707
|
+
console.log("\n=== Kiro Memory - Installation ===\n");
|
|
2708
|
+
console.log("[1/4] Running environment checks...");
|
|
2709
|
+
let checks = runEnvironmentChecks();
|
|
2710
|
+
let { hasErrors } = printChecks(checks);
|
|
2711
|
+
if (hasErrors) {
|
|
2712
|
+
const { fixed, needsRestart } = await tryAutoFix(checks);
|
|
2713
|
+
if (needsRestart) {
|
|
2714
|
+
console.log(" \x1B[33m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m");
|
|
2715
|
+
console.log(" \x1B[33m\u2502\x1B[0m Node.js was installed via nvm. To activate it: \x1B[33m\u2502\x1B[0m");
|
|
2716
|
+
console.log(" \x1B[33m\u2502\x1B[0m \x1B[33m\u2502\x1B[0m");
|
|
2717
|
+
console.log(" \x1B[33m\u2502\x1B[0m 1. Close and reopen your terminal \x1B[33m\u2502\x1B[0m");
|
|
2718
|
+
console.log(" \x1B[33m\u2502\x1B[0m 2. Run: \x1B[1mnpm install -g kiro-memory\x1B[0m \x1B[33m\u2502\x1B[0m");
|
|
2719
|
+
console.log(" \x1B[33m\u2502\x1B[0m 3. Run: \x1B[1mkiro-memory install\x1B[0m \x1B[33m\u2502\x1B[0m");
|
|
2720
|
+
console.log(" \x1B[33m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m\n");
|
|
2721
|
+
process.exit(0);
|
|
2722
|
+
}
|
|
2723
|
+
if (fixed) {
|
|
2724
|
+
console.log(" Re-running checks...\n");
|
|
2725
|
+
checks = runEnvironmentChecks();
|
|
2726
|
+
({ hasErrors } = printChecks(checks));
|
|
2727
|
+
}
|
|
2728
|
+
if (hasErrors) {
|
|
2729
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.");
|
|
2730
|
+
console.log("After fixing, run: kiro-memory install\n");
|
|
2731
|
+
process.exit(1);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
const distDir = DIST_DIR;
|
|
2735
|
+
const kiroDir = process.env.KIRO_CONFIG_DIR || join3(homedir3(), ".kiro");
|
|
2736
|
+
const agentsDir = join3(kiroDir, "agents");
|
|
2737
|
+
const settingsDir = join3(kiroDir, "settings");
|
|
2738
|
+
const steeringDir = join3(kiroDir, "steering");
|
|
2739
|
+
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".contextkit");
|
|
2740
|
+
console.log("[2/4] Installing Kiro configuration...\n");
|
|
2741
|
+
for (const dir of [agentsDir, settingsDir, steeringDir, dataDir]) {
|
|
2742
|
+
mkdirSync3(dir, { recursive: true });
|
|
2743
|
+
}
|
|
2744
|
+
const agentConfig = AGENT_TEMPLATE.replace(/__DIST_DIR__/g, distDir);
|
|
2745
|
+
const agentDestPath = join3(agentsDir, "kiro-memory.json");
|
|
2746
|
+
writeFileSync(agentDestPath, agentConfig, "utf8");
|
|
2747
|
+
console.log(` \u2192 Agent config: ${agentDestPath}`);
|
|
2748
|
+
const mcpFilePath = join3(settingsDir, "mcp.json");
|
|
2749
|
+
let mcpConfig = { mcpServers: {} };
|
|
2750
|
+
if (existsSync4(mcpFilePath)) {
|
|
2751
|
+
try {
|
|
2752
|
+
mcpConfig = JSON.parse(readFileSync2(mcpFilePath, "utf8"));
|
|
2753
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
2754
|
+
} catch {
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
mcpConfig.mcpServers["kiro-memory"] = {
|
|
2758
|
+
command: "node",
|
|
2759
|
+
args: [join3(distDir, "servers", "mcp-server.js")]
|
|
2760
|
+
};
|
|
2761
|
+
writeFileSync(mcpFilePath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
2762
|
+
console.log(` \u2192 MCP config: ${mcpFilePath}`);
|
|
2763
|
+
const steeringDestPath = join3(steeringDir, "kiro-memory.md");
|
|
2764
|
+
writeFileSync(steeringDestPath, STEERING_CONTENT, "utf8");
|
|
2765
|
+
console.log(` \u2192 Steering: ${steeringDestPath}`);
|
|
2766
|
+
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
2767
|
+
console.log("\n[3/4] Shell alias setup\n");
|
|
2768
|
+
const { rcFile } = detectShellRc();
|
|
2769
|
+
const aliasLine = 'alias kiro="kiro-cli --agent kiro-memory"';
|
|
2770
|
+
let aliasAlreadySet = false;
|
|
2771
|
+
if (existsSync4(rcFile)) {
|
|
2772
|
+
const rcContent = readFileSync2(rcFile, "utf8");
|
|
2773
|
+
aliasAlreadySet = rcContent.includes("alias kiro=") && rcContent.includes("kiro-memory");
|
|
2774
|
+
}
|
|
2775
|
+
if (aliasAlreadySet) {
|
|
2776
|
+
console.log(` \x1B[32m\u2713\x1B[0m Alias already configured in ${rcFile}`);
|
|
2777
|
+
} else {
|
|
2778
|
+
console.log(" \x1B[36m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m");
|
|
2779
|
+
console.log(" \x1B[36m\u2502\x1B[0m Without an alias, you must type every time: \x1B[36m\u2502\x1B[0m");
|
|
2780
|
+
console.log(" \x1B[36m\u2502\x1B[0m \x1B[2mkiro-cli --agent kiro-memory\x1B[0m \x1B[36m\u2502\x1B[0m");
|
|
2781
|
+
console.log(" \x1B[36m\u2502\x1B[0m \x1B[36m\u2502\x1B[0m");
|
|
2782
|
+
console.log(" \x1B[36m\u2502\x1B[0m With the alias, just type: \x1B[36m\u2502\x1B[0m");
|
|
2783
|
+
console.log(" \x1B[36m\u2502\x1B[0m \x1B[1m\x1B[32mkiro\x1B[0m \x1B[36m\u2502\x1B[0m");
|
|
2784
|
+
console.log(" \x1B[36m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m");
|
|
2785
|
+
console.log("");
|
|
2786
|
+
const answer = await askUser(` Add alias to ${rcFile}? [Y/n] `);
|
|
2787
|
+
if (answer === "" || answer === "y" || answer === "yes") {
|
|
2788
|
+
try {
|
|
2789
|
+
appendFileSync2(rcFile, `
|
|
2790
|
+
# Kiro Memory \u2014 persistent memory alias
|
|
2791
|
+
${aliasLine}
|
|
2792
|
+
`);
|
|
2793
|
+
console.log(`
|
|
2794
|
+
\x1B[32m\u2713\x1B[0m Alias added to ${rcFile}`);
|
|
2795
|
+
console.log(` \x1B[33m\u2192\x1B[0m Run \x1B[1msource ${rcFile}\x1B[0m or open a new terminal to activate it.`);
|
|
2796
|
+
} catch (err) {
|
|
2797
|
+
console.log(`
|
|
2798
|
+
\x1B[31m\u2717\x1B[0m Could not write to ${rcFile}: ${err.message}`);
|
|
2799
|
+
console.log(` \x1B[33m\u2192\x1B[0m Add manually: ${aliasLine}`);
|
|
2800
|
+
}
|
|
2801
|
+
} else {
|
|
2802
|
+
console.log(`
|
|
2803
|
+
Skipped. You can add it manually later:`);
|
|
2804
|
+
console.log(` echo '${aliasLine}' >> ${rcFile}`);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
console.log("\n[4/4] Done!\n");
|
|
2808
|
+
console.log(" \x1B[32m\u2550\u2550\u2550 Installation complete! \u2550\u2550\u2550\x1B[0m\n");
|
|
2809
|
+
console.log(" Start Kiro with memory:");
|
|
2810
|
+
if (aliasAlreadySet) {
|
|
2811
|
+
console.log(" \x1B[1mkiro\x1B[0m");
|
|
2812
|
+
} else {
|
|
2813
|
+
console.log(" \x1B[1mkiro-cli --agent kiro-memory\x1B[0m");
|
|
2814
|
+
}
|
|
2815
|
+
console.log("");
|
|
2816
|
+
console.log(" The worker starts automatically when a Kiro session begins.");
|
|
2817
|
+
console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
|
|
2818
|
+
`);
|
|
2819
|
+
}
|
|
2820
|
+
var CLAUDE_CODE_STEERING = `# Kiro Memory - Persistent Cross-Session Memory
|
|
2821
|
+
|
|
2822
|
+
You have access to Kiro Memory, a persistent cross-session memory system that remembers context across sessions.
|
|
2823
|
+
|
|
2824
|
+
## Available MCP Tools
|
|
2825
|
+
|
|
2826
|
+
### kiro-memory/search
|
|
2827
|
+
Search previous session memory. Use when:
|
|
2828
|
+
- The user mentions past work or previous sessions
|
|
2829
|
+
- You need context on previous decisions
|
|
2830
|
+
- You want to check if a problem was already addressed
|
|
2831
|
+
|
|
2832
|
+
### kiro-memory/get_context
|
|
2833
|
+
Retrieve recent context for the current project. Use at the start of complex tasks.
|
|
2834
|
+
|
|
2835
|
+
### kiro-memory/timeline
|
|
2836
|
+
Show chronological context around an observation. Use to understand sequences of events.
|
|
2837
|
+
|
|
2838
|
+
### kiro-memory/get_observations
|
|
2839
|
+
Retrieve full details of specific observations by ID. Use after search to drill down.
|
|
2840
|
+
|
|
2841
|
+
## Behavior
|
|
2842
|
+
|
|
2843
|
+
- Previous session context is automatically injected at startup via hooks
|
|
2844
|
+
- Your actions (files written, commands run, searches) are tracked automatically
|
|
2845
|
+
- A summary is generated at the end of each session
|
|
2846
|
+
- No manual saving needed: the system is fully automatic
|
|
2847
|
+
`;
|
|
2848
|
+
async function installClaudeCode() {
|
|
2849
|
+
console.log("\n=== Kiro Memory - Claude Code Installation ===\n");
|
|
2850
|
+
console.log("[1/3] Running environment checks...");
|
|
2851
|
+
const checks = runEnvironmentChecks();
|
|
2852
|
+
const { hasErrors } = printChecks(checks);
|
|
2853
|
+
if (hasErrors) {
|
|
2854
|
+
const { fixed, needsRestart } = await tryAutoFix(checks);
|
|
2855
|
+
if (needsRestart) {
|
|
2856
|
+
console.log(" \x1B[33mRestart your terminal and re-run: kiro-memory install --claude-code\x1B[0m\n");
|
|
2857
|
+
process.exit(0);
|
|
2858
|
+
}
|
|
2859
|
+
if (fixed) {
|
|
2860
|
+
console.log(" Re-running checks...\n");
|
|
2861
|
+
const reChecks = runEnvironmentChecks();
|
|
2862
|
+
const reResult = printChecks(reChecks);
|
|
2863
|
+
if (reResult.hasErrors) {
|
|
2864
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.\n");
|
|
2865
|
+
process.exit(1);
|
|
2866
|
+
}
|
|
2867
|
+
} else if (hasErrors) {
|
|
2868
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the issues and retry.\n");
|
|
2869
|
+
process.exit(1);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
const distDir = DIST_DIR;
|
|
2873
|
+
const claudeDir = join3(homedir3(), ".claude");
|
|
2874
|
+
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
|
|
2875
|
+
console.log("[2/3] Installing Claude Code configuration...\n");
|
|
2876
|
+
mkdirSync3(claudeDir, { recursive: true });
|
|
2877
|
+
mkdirSync3(dataDir, { recursive: true });
|
|
2878
|
+
const settingsPath = join3(claudeDir, "settings.json");
|
|
2879
|
+
let settings = {};
|
|
2880
|
+
if (existsSync4(settingsPath)) {
|
|
2881
|
+
try {
|
|
2882
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf8"));
|
|
2883
|
+
} catch {
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
const hookMap = {
|
|
2887
|
+
"SessionStart": { script: "hooks/agentSpawn.js", timeout: 10 },
|
|
2888
|
+
"UserPromptSubmit": { script: "hooks/userPromptSubmit.js", timeout: 5 },
|
|
2889
|
+
"PostToolUse": { script: "hooks/postToolUse.js", timeout: 5 },
|
|
2890
|
+
"Stop": { script: "hooks/stop.js", timeout: 10 }
|
|
2891
|
+
};
|
|
2892
|
+
for (const [event, config] of Object.entries(hookMap)) {
|
|
2893
|
+
const hookEntry = {
|
|
2894
|
+
matcher: "",
|
|
2895
|
+
hooks: [{
|
|
2896
|
+
type: "command",
|
|
2897
|
+
command: `node ${join3(distDir, config.script)}`,
|
|
2898
|
+
timeout: config.timeout
|
|
2899
|
+
}]
|
|
2900
|
+
};
|
|
2901
|
+
if (!settings[event]) {
|
|
2902
|
+
settings[event] = [hookEntry];
|
|
2903
|
+
} else if (Array.isArray(settings[event])) {
|
|
2904
|
+
settings[event] = settings[event].filter(
|
|
2905
|
+
(h) => !h.hooks?.some(
|
|
2906
|
+
(hk) => hk.command?.includes("kiro-memory") || hk.command?.includes("contextkit")
|
|
2907
|
+
)
|
|
2908
|
+
);
|
|
2909
|
+
settings[event].push(hookEntry);
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
2913
|
+
console.log(` \u2192 Hooks config: ${settingsPath}`);
|
|
2914
|
+
const mcpPath = join3(homedir3(), ".mcp.json");
|
|
2915
|
+
let mcpConfig = {};
|
|
2916
|
+
if (existsSync4(mcpPath)) {
|
|
2917
|
+
try {
|
|
2918
|
+
mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
2919
|
+
} catch {
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
2923
|
+
mcpConfig.mcpServers["kiro-memory"] = {
|
|
2924
|
+
command: "node",
|
|
2925
|
+
args: [join3(distDir, "servers", "mcp-server.js")]
|
|
2926
|
+
};
|
|
2927
|
+
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
2928
|
+
console.log(` \u2192 MCP config: ${mcpPath}`);
|
|
2929
|
+
const steeringPath = join3(claudeDir, "CLAUDE.md");
|
|
2930
|
+
let existingSteering = "";
|
|
2931
|
+
if (existsSync4(steeringPath)) {
|
|
2932
|
+
existingSteering = readFileSync2(steeringPath, "utf8");
|
|
2933
|
+
}
|
|
2934
|
+
if (!existingSteering.includes("Kiro Memory")) {
|
|
2935
|
+
const separator = existingSteering.length > 0 ? "\n\n---\n\n" : "";
|
|
2936
|
+
writeFileSync(steeringPath, existingSteering + separator + CLAUDE_CODE_STEERING, "utf8");
|
|
2937
|
+
console.log(` \u2192 Steering: ${steeringPath}`);
|
|
2938
|
+
} else {
|
|
2939
|
+
console.log(` \u2192 Steering: ${steeringPath} (already configured)`);
|
|
2940
|
+
}
|
|
2941
|
+
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
2942
|
+
console.log("\n[3/3] Done!\n");
|
|
2943
|
+
console.log(" \x1B[32m\u2550\u2550\u2550 Claude Code integration complete! \u2550\u2550\u2550\x1B[0m\n");
|
|
2944
|
+
console.log(" Memory hooks are now active for Claude Code.");
|
|
2945
|
+
console.log(" Start a new Claude Code session to begin tracking context.\n");
|
|
2946
|
+
console.log(" The worker starts automatically on first session.");
|
|
2947
|
+
console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
|
|
2948
|
+
`);
|
|
2949
|
+
}
|
|
2950
|
+
async function installCursor() {
|
|
2951
|
+
console.log("\n=== Kiro Memory - Cursor Installation ===\n");
|
|
2952
|
+
console.log("[1/3] Running environment checks...");
|
|
2953
|
+
const checks = runEnvironmentChecks();
|
|
2954
|
+
const { hasErrors } = printChecks(checks);
|
|
2955
|
+
if (hasErrors) {
|
|
2956
|
+
const { fixed, needsRestart } = await tryAutoFix(checks);
|
|
2957
|
+
if (needsRestart) {
|
|
2958
|
+
console.log(" \x1B[33mRestart your terminal and re-run: kiro-memory install --cursor\x1B[0m\n");
|
|
2959
|
+
process.exit(0);
|
|
2960
|
+
}
|
|
2961
|
+
if (fixed) {
|
|
2962
|
+
console.log(" Re-running checks...\n");
|
|
2963
|
+
const reChecks = runEnvironmentChecks();
|
|
2964
|
+
const reResult = printChecks(reChecks);
|
|
2965
|
+
if (reResult.hasErrors) {
|
|
2966
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.\n");
|
|
2967
|
+
process.exit(1);
|
|
2968
|
+
}
|
|
2969
|
+
} else if (hasErrors) {
|
|
2970
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the issues and retry.\n");
|
|
2971
|
+
process.exit(1);
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
const distDir = DIST_DIR;
|
|
2975
|
+
const cursorDir = join3(homedir3(), ".cursor");
|
|
2976
|
+
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
|
|
2977
|
+
console.log("[2/3] Installing Cursor configuration...\n");
|
|
2978
|
+
mkdirSync3(cursorDir, { recursive: true });
|
|
2979
|
+
mkdirSync3(dataDir, { recursive: true });
|
|
2980
|
+
const hooksPath = join3(cursorDir, "hooks.json");
|
|
2981
|
+
let hooksConfig = { version: 1, hooks: {} };
|
|
2982
|
+
if (existsSync4(hooksPath)) {
|
|
2983
|
+
try {
|
|
2984
|
+
hooksConfig = JSON.parse(readFileSync2(hooksPath, "utf8"));
|
|
2985
|
+
if (!hooksConfig.hooks) hooksConfig.hooks = {};
|
|
2986
|
+
if (!hooksConfig.version) hooksConfig.version = 1;
|
|
2987
|
+
} catch {
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
const cursorHookMap = {
|
|
2991
|
+
"sessionStart": "hooks/agentSpawn.js",
|
|
2992
|
+
"beforeSubmitPrompt": "hooks/userPromptSubmit.js",
|
|
2993
|
+
"afterFileEdit": "hooks/postToolUse.js",
|
|
2994
|
+
"afterShellExecution": "hooks/postToolUse.js",
|
|
2995
|
+
"afterMCPExecution": "hooks/postToolUse.js",
|
|
2996
|
+
"stop": "hooks/stop.js"
|
|
2997
|
+
};
|
|
2998
|
+
for (const [event, script] of Object.entries(cursorHookMap)) {
|
|
2999
|
+
const hookEntry = {
|
|
3000
|
+
command: `node ${join3(distDir, script)}`
|
|
3001
|
+
};
|
|
3002
|
+
if (!hooksConfig.hooks[event]) {
|
|
3003
|
+
hooksConfig.hooks[event] = [hookEntry];
|
|
3004
|
+
} else if (Array.isArray(hooksConfig.hooks[event])) {
|
|
3005
|
+
hooksConfig.hooks[event] = hooksConfig.hooks[event].filter(
|
|
3006
|
+
(h) => !h.command?.includes("kiro-memory") && !h.command?.includes("contextkit")
|
|
3007
|
+
);
|
|
3008
|
+
hooksConfig.hooks[event].push(hookEntry);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
writeFileSync(hooksPath, JSON.stringify(hooksConfig, null, 2), "utf8");
|
|
3012
|
+
console.log(` \u2192 Hooks config: ${hooksPath}`);
|
|
3013
|
+
const mcpPath = join3(cursorDir, "mcp.json");
|
|
3014
|
+
let mcpConfig = {};
|
|
3015
|
+
if (existsSync4(mcpPath)) {
|
|
3016
|
+
try {
|
|
3017
|
+
mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
3018
|
+
} catch {
|
|
1273
3019
|
}
|
|
1274
3020
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
3021
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
3022
|
+
mcpConfig.mcpServers["kiro-memory"] = {
|
|
3023
|
+
command: "node",
|
|
3024
|
+
args: [join3(distDir, "servers", "mcp-server.js")]
|
|
3025
|
+
};
|
|
3026
|
+
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
3027
|
+
console.log(` \u2192 MCP config: ${mcpPath}`);
|
|
3028
|
+
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
3029
|
+
console.log("\n[3/3] Done!\n");
|
|
3030
|
+
console.log(" \x1B[32m\u2550\u2550\u2550 Cursor integration complete! \u2550\u2550\u2550\x1B[0m\n");
|
|
3031
|
+
console.log(" Memory hooks are now active for Cursor IDE.");
|
|
3032
|
+
console.log(" Start a new Cursor Agent session to begin tracking context.\n");
|
|
3033
|
+
console.log(" The worker starts automatically on first session.");
|
|
3034
|
+
console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
|
|
3035
|
+
`);
|
|
1277
3036
|
}
|
|
1278
|
-
async function
|
|
1279
|
-
console.log("\n=== Kiro Memory - Installation ===\n");
|
|
1280
|
-
console.log("[1/
|
|
1281
|
-
|
|
1282
|
-
|
|
3037
|
+
async function installWindsurf() {
|
|
3038
|
+
console.log("\n=== Kiro Memory - Windsurf Installation ===\n");
|
|
3039
|
+
console.log("[1/3] Running environment checks...");
|
|
3040
|
+
const checks = runEnvironmentChecks();
|
|
3041
|
+
const { hasErrors } = printChecks(checks);
|
|
1283
3042
|
if (hasErrors) {
|
|
1284
3043
|
const { fixed, needsRestart } = await tryAutoFix(checks);
|
|
1285
3044
|
if (needsRestart) {
|
|
1286
|
-
console.log(" \x1B[
|
|
1287
|
-
console.log(" \x1B[33m\u2502\x1B[0m Node.js was installed via nvm. To activate it: \x1B[33m\u2502\x1B[0m");
|
|
1288
|
-
console.log(" \x1B[33m\u2502\x1B[0m \x1B[33m\u2502\x1B[0m");
|
|
1289
|
-
console.log(" \x1B[33m\u2502\x1B[0m 1. Close and reopen your terminal \x1B[33m\u2502\x1B[0m");
|
|
1290
|
-
console.log(" \x1B[33m\u2502\x1B[0m 2. Run: \x1B[1mnpm install -g kiro-memory\x1B[0m \x1B[33m\u2502\x1B[0m");
|
|
1291
|
-
console.log(" \x1B[33m\u2502\x1B[0m 3. Run: \x1B[1mkiro-memory install\x1B[0m \x1B[33m\u2502\x1B[0m");
|
|
1292
|
-
console.log(" \x1B[33m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m\n");
|
|
3045
|
+
console.log(" \x1B[33mRestart your terminal and re-run: kiro-memory install --windsurf\x1B[0m\n");
|
|
1293
3046
|
process.exit(0);
|
|
1294
3047
|
}
|
|
1295
3048
|
if (fixed) {
|
|
1296
3049
|
console.log(" Re-running checks...\n");
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
3050
|
+
const reChecks = runEnvironmentChecks();
|
|
3051
|
+
const reResult = printChecks(reChecks);
|
|
3052
|
+
if (reResult.hasErrors) {
|
|
3053
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.\n");
|
|
3054
|
+
process.exit(1);
|
|
3055
|
+
}
|
|
3056
|
+
} else if (hasErrors) {
|
|
3057
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the issues and retry.\n");
|
|
1303
3058
|
process.exit(1);
|
|
1304
3059
|
}
|
|
1305
3060
|
}
|
|
1306
3061
|
const distDir = DIST_DIR;
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
}
|
|
1316
|
-
const agentConfig = AGENT_TEMPLATE.replace(/__DIST_DIR__/g, distDir);
|
|
1317
|
-
const agentDestPath = join3(agentsDir, "kiro-memory.json");
|
|
1318
|
-
writeFileSync(agentDestPath, agentConfig, "utf8");
|
|
1319
|
-
console.log(` \u2192 Agent config: ${agentDestPath}`);
|
|
1320
|
-
const mcpFilePath = join3(settingsDir, "mcp.json");
|
|
1321
|
-
let mcpConfig = { mcpServers: {} };
|
|
1322
|
-
if (existsSync3(mcpFilePath)) {
|
|
3062
|
+
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
|
|
3063
|
+
console.log("[2/3] Installing Windsurf configuration...\n");
|
|
3064
|
+
mkdirSync3(dataDir, { recursive: true });
|
|
3065
|
+
const windsurfDir = join3(homedir3(), ".codeium", "windsurf");
|
|
3066
|
+
mkdirSync3(windsurfDir, { recursive: true });
|
|
3067
|
+
const mcpPath = join3(windsurfDir, "mcp_config.json");
|
|
3068
|
+
let mcpConfig = {};
|
|
3069
|
+
if (existsSync4(mcpPath)) {
|
|
1323
3070
|
try {
|
|
1324
|
-
mcpConfig = JSON.parse(readFileSync2(
|
|
1325
|
-
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
3071
|
+
mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
1326
3072
|
} catch {
|
|
1327
3073
|
}
|
|
1328
3074
|
}
|
|
3075
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
1329
3076
|
mcpConfig.mcpServers["kiro-memory"] = {
|
|
1330
3077
|
command: "node",
|
|
1331
3078
|
args: [join3(distDir, "servers", "mcp-server.js")]
|
|
1332
3079
|
};
|
|
1333
|
-
writeFileSync(
|
|
1334
|
-
console.log(` \u2192 MCP config: ${
|
|
1335
|
-
const steeringDestPath = join3(steeringDir, "kiro-memory.md");
|
|
1336
|
-
writeFileSync(steeringDestPath, STEERING_CONTENT, "utf8");
|
|
1337
|
-
console.log(` \u2192 Steering: ${steeringDestPath}`);
|
|
3080
|
+
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
3081
|
+
console.log(` \u2192 MCP config: ${mcpPath}`);
|
|
1338
3082
|
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
1339
|
-
console.log("\n[3/
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
aliasAlreadySet = rcContent.includes("alias kiro=") && rcContent.includes("kiro-memory");
|
|
1346
|
-
}
|
|
1347
|
-
if (aliasAlreadySet) {
|
|
1348
|
-
console.log(` \x1B[32m\u2713\x1B[0m Alias already configured in ${rcFile}`);
|
|
1349
|
-
} else {
|
|
1350
|
-
console.log(" \x1B[36m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m");
|
|
1351
|
-
console.log(" \x1B[36m\u2502\x1B[0m Without an alias, you must type every time: \x1B[36m\u2502\x1B[0m");
|
|
1352
|
-
console.log(" \x1B[36m\u2502\x1B[0m \x1B[2mkiro-cli --agent kiro-memory\x1B[0m \x1B[36m\u2502\x1B[0m");
|
|
1353
|
-
console.log(" \x1B[36m\u2502\x1B[0m \x1B[36m\u2502\x1B[0m");
|
|
1354
|
-
console.log(" \x1B[36m\u2502\x1B[0m With the alias, just type: \x1B[36m\u2502\x1B[0m");
|
|
1355
|
-
console.log(" \x1B[36m\u2502\x1B[0m \x1B[1m\x1B[32mkiro\x1B[0m \x1B[36m\u2502\x1B[0m");
|
|
1356
|
-
console.log(" \x1B[36m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m");
|
|
1357
|
-
console.log("");
|
|
1358
|
-
const answer = await askUser(` Add alias to ${rcFile}? [Y/n] `);
|
|
1359
|
-
if (answer === "" || answer === "y" || answer === "yes") {
|
|
1360
|
-
try {
|
|
1361
|
-
appendFileSync2(rcFile, `
|
|
1362
|
-
# Kiro Memory \u2014 persistent memory alias
|
|
1363
|
-
${aliasLine}
|
|
3083
|
+
console.log("\n[3/3] Done!\n");
|
|
3084
|
+
console.log(" \x1B[32m\u2550\u2550\u2550 Windsurf integration complete! \u2550\u2550\u2550\x1B[0m\n");
|
|
3085
|
+
console.log(" Kiro Memory MCP server is now registered for Windsurf.");
|
|
3086
|
+
console.log(" Restart Windsurf to activate the MCP server.\n");
|
|
3087
|
+
console.log(" The worker starts automatically on first use.");
|
|
3088
|
+
console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
|
|
1364
3089
|
`);
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
3090
|
+
console.log(" \x1B[2mTip: Add a .windsurfrules file to your project with instructions");
|
|
3091
|
+
console.log(" to use the kiro-memory MCP tools for persistent context.\x1B[0m\n");
|
|
3092
|
+
}
|
|
3093
|
+
async function installCline() {
|
|
3094
|
+
console.log("\n=== Kiro Memory - Cline Installation ===\n");
|
|
3095
|
+
console.log("[1/3] Running environment checks...");
|
|
3096
|
+
const checks = runEnvironmentChecks();
|
|
3097
|
+
const { hasErrors } = printChecks(checks);
|
|
3098
|
+
if (hasErrors) {
|
|
3099
|
+
const { fixed, needsRestart } = await tryAutoFix(checks);
|
|
3100
|
+
if (needsRestart) {
|
|
3101
|
+
console.log(" \x1B[33mRestart your terminal and re-run: kiro-memory install --cline\x1B[0m\n");
|
|
3102
|
+
process.exit(0);
|
|
3103
|
+
}
|
|
3104
|
+
if (fixed) {
|
|
3105
|
+
console.log(" Re-running checks...\n");
|
|
3106
|
+
const reChecks = runEnvironmentChecks();
|
|
3107
|
+
const reResult = printChecks(reChecks);
|
|
3108
|
+
if (reResult.hasErrors) {
|
|
3109
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the remaining issues and retry.\n");
|
|
3110
|
+
process.exit(1);
|
|
1372
3111
|
}
|
|
1373
|
-
} else {
|
|
1374
|
-
console.log(
|
|
1375
|
-
|
|
1376
|
-
console.log(` echo '${aliasLine}' >> ${rcFile}`);
|
|
3112
|
+
} else if (hasErrors) {
|
|
3113
|
+
console.log("\x1B[31mInstallation aborted.\x1B[0m Fix the issues and retry.\n");
|
|
3114
|
+
process.exit(1);
|
|
1377
3115
|
}
|
|
1378
3116
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
console.log("
|
|
1382
|
-
|
|
1383
|
-
|
|
3117
|
+
const distDir = DIST_DIR;
|
|
3118
|
+
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".kiro-memory");
|
|
3119
|
+
console.log("[2/3] Installing Cline configuration...\n");
|
|
3120
|
+
mkdirSync3(dataDir, { recursive: true });
|
|
3121
|
+
const platform2 = process.platform;
|
|
3122
|
+
let clineSettingsDir;
|
|
3123
|
+
if (platform2 === "darwin") {
|
|
3124
|
+
clineSettingsDir = join3(homedir3(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
|
|
1384
3125
|
} else {
|
|
1385
|
-
|
|
3126
|
+
clineSettingsDir = join3(homedir3(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
|
|
1386
3127
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
3128
|
+
mkdirSync3(clineSettingsDir, { recursive: true });
|
|
3129
|
+
const mcpPath = join3(clineSettingsDir, "cline_mcp_settings.json");
|
|
3130
|
+
let mcpConfig = {};
|
|
3131
|
+
if (existsSync4(mcpPath)) {
|
|
3132
|
+
try {
|
|
3133
|
+
mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
3134
|
+
} catch {
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
3138
|
+
mcpConfig.mcpServers["kiro-memory"] = {
|
|
3139
|
+
command: "node",
|
|
3140
|
+
args: [join3(distDir, "servers", "mcp-server.js")]
|
|
3141
|
+
};
|
|
3142
|
+
writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
3143
|
+
console.log(` \u2192 MCP config: ${mcpPath}`);
|
|
3144
|
+
console.log(` \u2192 Data dir: ${dataDir}`);
|
|
3145
|
+
console.log("\n[3/3] Done!\n");
|
|
3146
|
+
console.log(" \x1B[32m\u2550\u2550\u2550 Cline integration complete! \u2550\u2550\u2550\x1B[0m\n");
|
|
3147
|
+
console.log(" Kiro Memory MCP server is now registered for Cline.");
|
|
3148
|
+
console.log(" Restart VS Code to activate the MCP server in Cline.\n");
|
|
3149
|
+
console.log(" The worker starts automatically on first use.");
|
|
1389
3150
|
console.log(` Web dashboard: \x1B[4mhttp://localhost:3001\x1B[0m
|
|
1390
3151
|
`);
|
|
3152
|
+
console.log(" \x1B[2mTip: Add a .clinerules file to your project with instructions");
|
|
3153
|
+
console.log(" to use the kiro-memory MCP tools for persistent context.\x1B[0m\n");
|
|
1391
3154
|
}
|
|
1392
3155
|
async function runDoctor() {
|
|
1393
3156
|
console.log("\n=== Kiro Memory - Diagnostics ===");
|
|
@@ -1398,12 +3161,12 @@ async function runDoctor() {
|
|
|
1398
3161
|
const dataDir = process.env.KIRO_MEMORY_DATA_DIR || process.env.CONTEXTKIT_DATA_DIR || join3(homedir3(), ".contextkit");
|
|
1399
3162
|
checks.push({
|
|
1400
3163
|
name: "Kiro agent config",
|
|
1401
|
-
ok:
|
|
1402
|
-
message:
|
|
1403
|
-
fix: !
|
|
3164
|
+
ok: existsSync4(agentPath),
|
|
3165
|
+
message: existsSync4(agentPath) ? agentPath : "Not found",
|
|
3166
|
+
fix: !existsSync4(agentPath) ? "Run: kiro-memory install" : void 0
|
|
1404
3167
|
});
|
|
1405
3168
|
let mcpOk = false;
|
|
1406
|
-
if (
|
|
3169
|
+
if (existsSync4(mcpPath)) {
|
|
1407
3170
|
try {
|
|
1408
3171
|
const mcp = JSON.parse(readFileSync2(mcpPath, "utf8"));
|
|
1409
3172
|
mcpOk = !!mcp.mcpServers?.["kiro-memory"] || !!mcp.mcpServers?.contextkit;
|
|
@@ -1418,8 +3181,115 @@ async function runDoctor() {
|
|
|
1418
3181
|
});
|
|
1419
3182
|
checks.push({
|
|
1420
3183
|
name: "Data directory",
|
|
1421
|
-
ok:
|
|
1422
|
-
message:
|
|
3184
|
+
ok: existsSync4(dataDir),
|
|
3185
|
+
message: existsSync4(dataDir) ? dataDir : "Not created (will be created on first use)"
|
|
3186
|
+
});
|
|
3187
|
+
const claudeDir = join3(homedir3(), ".claude");
|
|
3188
|
+
const claudeSettingsPath = join3(claudeDir, "settings.json");
|
|
3189
|
+
let claudeHooksOk = false;
|
|
3190
|
+
if (existsSync4(claudeSettingsPath)) {
|
|
3191
|
+
try {
|
|
3192
|
+
const claudeSettings = JSON.parse(readFileSync2(claudeSettingsPath, "utf8"));
|
|
3193
|
+
claudeHooksOk = !!(claudeSettings?.SessionStart || claudeSettings?.PostToolUse);
|
|
3194
|
+
if (claudeHooksOk) {
|
|
3195
|
+
const allSettings = JSON.stringify(claudeSettings);
|
|
3196
|
+
claudeHooksOk = allSettings.includes("kiro-memory") || allSettings.includes("agentSpawn");
|
|
3197
|
+
}
|
|
3198
|
+
} catch {
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
const claudeMcpPath = join3(homedir3(), ".mcp.json");
|
|
3202
|
+
let claudeMcpOk = false;
|
|
3203
|
+
if (existsSync4(claudeMcpPath)) {
|
|
3204
|
+
try {
|
|
3205
|
+
const claudeMcp = JSON.parse(readFileSync2(claudeMcpPath, "utf8"));
|
|
3206
|
+
claudeMcpOk = !!claudeMcp.mcpServers?.["kiro-memory"];
|
|
3207
|
+
} catch {
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
checks.push({
|
|
3211
|
+
name: "Claude Code hooks",
|
|
3212
|
+
ok: true,
|
|
3213
|
+
// Non-blocking: installazione opzionale
|
|
3214
|
+
message: claudeHooksOk ? "Configured in ~/.claude/settings.json" : "Not configured (optional: run kiro-memory install --claude-code)"
|
|
3215
|
+
});
|
|
3216
|
+
checks.push({
|
|
3217
|
+
name: "Claude Code MCP",
|
|
3218
|
+
ok: true,
|
|
3219
|
+
// Non-blocking: installazione opzionale
|
|
3220
|
+
message: claudeMcpOk ? "kiro-memory registered in ~/.mcp.json" : "Not configured (optional: run kiro-memory install --claude-code)"
|
|
3221
|
+
});
|
|
3222
|
+
const cursorDir = join3(homedir3(), ".cursor");
|
|
3223
|
+
const cursorHooksPath = join3(cursorDir, "hooks.json");
|
|
3224
|
+
let cursorHooksOk = false;
|
|
3225
|
+
if (existsSync4(cursorHooksPath)) {
|
|
3226
|
+
try {
|
|
3227
|
+
const cursorHooks = JSON.parse(readFileSync2(cursorHooksPath, "utf8"));
|
|
3228
|
+
cursorHooksOk = !!(cursorHooks.hooks?.sessionStart || cursorHooks.hooks?.afterFileEdit);
|
|
3229
|
+
if (cursorHooksOk) {
|
|
3230
|
+
const allHooks = JSON.stringify(cursorHooks.hooks);
|
|
3231
|
+
cursorHooksOk = allHooks.includes("kiro-memory") || allHooks.includes("agentSpawn");
|
|
3232
|
+
}
|
|
3233
|
+
} catch {
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
const cursorMcpPath = join3(cursorDir, "mcp.json");
|
|
3237
|
+
let cursorMcpOk = false;
|
|
3238
|
+
if (existsSync4(cursorMcpPath)) {
|
|
3239
|
+
try {
|
|
3240
|
+
const cursorMcp = JSON.parse(readFileSync2(cursorMcpPath, "utf8"));
|
|
3241
|
+
cursorMcpOk = !!cursorMcp.mcpServers?.["kiro-memory"];
|
|
3242
|
+
} catch {
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
checks.push({
|
|
3246
|
+
name: "Cursor hooks",
|
|
3247
|
+
ok: true,
|
|
3248
|
+
// Non-blocking: installazione opzionale
|
|
3249
|
+
message: cursorHooksOk ? "Configured in ~/.cursor/hooks.json" : "Not configured (optional: run kiro-memory install --cursor)"
|
|
3250
|
+
});
|
|
3251
|
+
checks.push({
|
|
3252
|
+
name: "Cursor MCP",
|
|
3253
|
+
ok: true,
|
|
3254
|
+
// Non-blocking: installazione opzionale
|
|
3255
|
+
message: cursorMcpOk ? "kiro-memory registered in ~/.cursor/mcp.json" : "Not configured (optional: run kiro-memory install --cursor)"
|
|
3256
|
+
});
|
|
3257
|
+
const windsurfMcpPath = join3(homedir3(), ".codeium", "windsurf", "mcp_config.json");
|
|
3258
|
+
let windsurfMcpOk = false;
|
|
3259
|
+
if (existsSync4(windsurfMcpPath)) {
|
|
3260
|
+
try {
|
|
3261
|
+
const windsurfMcp = JSON.parse(readFileSync2(windsurfMcpPath, "utf8"));
|
|
3262
|
+
windsurfMcpOk = !!windsurfMcp.mcpServers?.["kiro-memory"];
|
|
3263
|
+
} catch {
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
checks.push({
|
|
3267
|
+
name: "Windsurf MCP",
|
|
3268
|
+
ok: true,
|
|
3269
|
+
// Non-blocking: installazione opzionale
|
|
3270
|
+
message: windsurfMcpOk ? "kiro-memory registered in ~/.codeium/windsurf/mcp_config.json" : "Not configured (optional: run kiro-memory install --windsurf)"
|
|
3271
|
+
});
|
|
3272
|
+
const clinePlatform = process.platform;
|
|
3273
|
+
let clineSettingsBase;
|
|
3274
|
+
if (clinePlatform === "darwin") {
|
|
3275
|
+
clineSettingsBase = join3(homedir3(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
|
|
3276
|
+
} else {
|
|
3277
|
+
clineSettingsBase = join3(homedir3(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings");
|
|
3278
|
+
}
|
|
3279
|
+
const clineMcpPath = join3(clineSettingsBase, "cline_mcp_settings.json");
|
|
3280
|
+
let clineMcpOk = false;
|
|
3281
|
+
if (existsSync4(clineMcpPath)) {
|
|
3282
|
+
try {
|
|
3283
|
+
const clineMcp = JSON.parse(readFileSync2(clineMcpPath, "utf8"));
|
|
3284
|
+
clineMcpOk = !!clineMcp.mcpServers?.["kiro-memory"];
|
|
3285
|
+
} catch {
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
checks.push({
|
|
3289
|
+
name: "Cline MCP",
|
|
3290
|
+
ok: true,
|
|
3291
|
+
// Non-blocking: installazione opzionale
|
|
3292
|
+
message: clineMcpOk ? `kiro-memory registered in cline_mcp_settings.json` : "Not configured (optional: run kiro-memory install --cline)"
|
|
1423
3293
|
});
|
|
1424
3294
|
let workerOk = false;
|
|
1425
3295
|
try {
|
|
@@ -1447,7 +3317,17 @@ async function runDoctor() {
|
|
|
1447
3317
|
}
|
|
1448
3318
|
async function main() {
|
|
1449
3319
|
if (command === "install") {
|
|
1450
|
-
|
|
3320
|
+
if (args.includes("--claude-code")) {
|
|
3321
|
+
await installClaudeCode();
|
|
3322
|
+
} else if (args.includes("--cursor")) {
|
|
3323
|
+
await installCursor();
|
|
3324
|
+
} else if (args.includes("--windsurf")) {
|
|
3325
|
+
await installWindsurf();
|
|
3326
|
+
} else if (args.includes("--cline")) {
|
|
3327
|
+
await installCline();
|
|
3328
|
+
} else {
|
|
3329
|
+
await installKiro();
|
|
3330
|
+
}
|
|
1451
3331
|
return;
|
|
1452
3332
|
}
|
|
1453
3333
|
if (command === "doctor") {
|
|
@@ -1480,6 +3360,27 @@ async function main() {
|
|
|
1480
3360
|
case "add-sum":
|
|
1481
3361
|
await addSummary(sdk, args.slice(1).join(" "));
|
|
1482
3362
|
break;
|
|
3363
|
+
case "add-knowledge":
|
|
3364
|
+
case "add-k":
|
|
3365
|
+
await addKnowledge(sdk, args[1], args[2], args.slice(3).join(" "));
|
|
3366
|
+
break;
|
|
3367
|
+
case "decay":
|
|
3368
|
+
await handleDecay(sdk, args[1]);
|
|
3369
|
+
break;
|
|
3370
|
+
case "embeddings":
|
|
3371
|
+
case "emb":
|
|
3372
|
+
await handleEmbeddings(sdk, args[1]);
|
|
3373
|
+
break;
|
|
3374
|
+
case "semantic-search":
|
|
3375
|
+
case "sem":
|
|
3376
|
+
await semanticSearchCli(sdk, args[1]);
|
|
3377
|
+
break;
|
|
3378
|
+
case "resume":
|
|
3379
|
+
await resumeSession(sdk, args[1] ? parseInt(args[1]) : void 0);
|
|
3380
|
+
break;
|
|
3381
|
+
case "report":
|
|
3382
|
+
await generateReportCli(sdk, args.slice(1));
|
|
3383
|
+
break;
|
|
1483
3384
|
case "help":
|
|
1484
3385
|
case "--help":
|
|
1485
3386
|
case "-h":
|
|
@@ -1607,27 +3508,290 @@ async function addSummary(sdk, content) {
|
|
|
1607
3508
|
console.log(`\u2705 Summary stored with ID: ${id}
|
|
1608
3509
|
`);
|
|
1609
3510
|
}
|
|
3511
|
+
async function addKnowledge(sdk, knowledgeType, title, content) {
|
|
3512
|
+
const validTypes = ["constraint", "decision", "heuristic", "rejected"];
|
|
3513
|
+
if (!knowledgeType || !validTypes.includes(knowledgeType)) {
|
|
3514
|
+
console.error(`Error: knowledge type must be one of: ${validTypes.join(", ")}`);
|
|
3515
|
+
process.exit(1);
|
|
3516
|
+
}
|
|
3517
|
+
if (!title) {
|
|
3518
|
+
console.error("Error: title is required");
|
|
3519
|
+
process.exit(1);
|
|
3520
|
+
}
|
|
3521
|
+
if (!content) {
|
|
3522
|
+
console.error("Error: content is required");
|
|
3523
|
+
process.exit(1);
|
|
3524
|
+
}
|
|
3525
|
+
const severity = args.find((a) => a.startsWith("--severity="))?.split("=")[1];
|
|
3526
|
+
const alternativesRaw = args.find((a) => a.startsWith("--alternatives="))?.split("=")[1];
|
|
3527
|
+
const alternatives = alternativesRaw ? alternativesRaw.split(",").map((s) => s.trim()) : void 0;
|
|
3528
|
+
const reason = args.find((a) => a.startsWith("--reason="))?.split("=")[1];
|
|
3529
|
+
const context = args.find((a) => a.startsWith("--context="))?.split("=")[1];
|
|
3530
|
+
const confidence = args.find((a) => a.startsWith("--confidence="))?.split("=")[1];
|
|
3531
|
+
const conceptsRaw = args.find((a) => a.startsWith("--concepts="))?.split("=")[1];
|
|
3532
|
+
const concepts = conceptsRaw ? conceptsRaw.split(",").map((s) => s.trim()) : void 0;
|
|
3533
|
+
const filesRaw = args.find((a) => a.startsWith("--files="))?.split("=")[1];
|
|
3534
|
+
const files = filesRaw ? filesRaw.split(",").map((s) => s.trim()) : void 0;
|
|
3535
|
+
const cleanContent = content.split(" ").filter((w) => !w.startsWith("--")).join(" ");
|
|
3536
|
+
const id = await sdk.storeKnowledge({
|
|
3537
|
+
project: sdk.getProject(),
|
|
3538
|
+
knowledgeType,
|
|
3539
|
+
title,
|
|
3540
|
+
content: cleanContent || content,
|
|
3541
|
+
concepts,
|
|
3542
|
+
files,
|
|
3543
|
+
metadata: { severity, alternatives, reason, context, confidence }
|
|
3544
|
+
});
|
|
3545
|
+
console.log(`
|
|
3546
|
+
Knowledge stored successfully.`);
|
|
3547
|
+
console.log(` ID: ${id}`);
|
|
3548
|
+
console.log(` Type: ${knowledgeType}`);
|
|
3549
|
+
console.log(` Title: ${title}
|
|
3550
|
+
`);
|
|
3551
|
+
}
|
|
3552
|
+
async function handleEmbeddings(sdk, subcommand) {
|
|
3553
|
+
switch (subcommand) {
|
|
3554
|
+
case "stats": {
|
|
3555
|
+
const stats = sdk.getEmbeddingStats();
|
|
3556
|
+
console.log("\nEmbedding Statistics:\n");
|
|
3557
|
+
console.log(` Total observations: ${stats.total}`);
|
|
3558
|
+
console.log(` With embeddings: ${stats.embedded}`);
|
|
3559
|
+
console.log(` Coverage: ${stats.percentage}%`);
|
|
3560
|
+
await sdk.initializeEmbeddings();
|
|
3561
|
+
const { getEmbeddingService: getEmbeddingService2 } = await Promise.resolve().then(() => (init_EmbeddingService(), EmbeddingService_exports));
|
|
3562
|
+
const embService = getEmbeddingService2();
|
|
3563
|
+
console.log(` Provider: ${embService.getProvider() || "none"}`);
|
|
3564
|
+
console.log(` Dimensions: ${embService.getDimensions()}`);
|
|
3565
|
+
console.log(` Available: ${embService.isAvailable() ? "yes" : "no"}`);
|
|
3566
|
+
if (stats.percentage < 100 && stats.total > 0) {
|
|
3567
|
+
console.log(`
|
|
3568
|
+
Run 'kiro-memory embeddings backfill' to generate missing embeddings.`);
|
|
3569
|
+
}
|
|
3570
|
+
console.log("");
|
|
3571
|
+
break;
|
|
3572
|
+
}
|
|
3573
|
+
case "backfill": {
|
|
3574
|
+
const batchSize = parseInt(args[2]) || 50;
|
|
3575
|
+
console.log(`
|
|
3576
|
+
Generating embeddings (batch size: ${batchSize})...
|
|
3577
|
+
`);
|
|
3578
|
+
const available = await sdk.initializeEmbeddings();
|
|
3579
|
+
if (!available) {
|
|
3580
|
+
console.log(" No embedding provider available.");
|
|
3581
|
+
console.log(" Install fastembed or @huggingface/transformers:");
|
|
3582
|
+
console.log(" npm install fastembed");
|
|
3583
|
+
console.log(" npm install @huggingface/transformers\n");
|
|
3584
|
+
process.exit(1);
|
|
3585
|
+
}
|
|
3586
|
+
const count = await sdk.backfillEmbeddings(batchSize);
|
|
3587
|
+
console.log(` Generated ${count} embeddings.
|
|
3588
|
+
`);
|
|
3589
|
+
const stats = sdk.getEmbeddingStats();
|
|
3590
|
+
console.log(` Coverage: ${stats.embedded}/${stats.total} (${stats.percentage}%)
|
|
3591
|
+
`);
|
|
3592
|
+
break;
|
|
3593
|
+
}
|
|
3594
|
+
default:
|
|
3595
|
+
console.log("\nUsage: kiro-memory embeddings <subcommand>\n");
|
|
3596
|
+
console.log("Subcommands:");
|
|
3597
|
+
console.log(" stats Show embedding statistics");
|
|
3598
|
+
console.log(" backfill [size] Generate embeddings for observations without them (default: 50)\n");
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
async function semanticSearchCli(sdk, query) {
|
|
3602
|
+
if (!query) {
|
|
3603
|
+
console.error("Error: Please provide a search query");
|
|
3604
|
+
process.exit(1);
|
|
3605
|
+
}
|
|
3606
|
+
console.log(`
|
|
3607
|
+
Semantic search: "${query}"...
|
|
3608
|
+
`);
|
|
3609
|
+
await sdk.initializeEmbeddings();
|
|
3610
|
+
const results = await sdk.hybridSearch(query, { limit: 10 });
|
|
3611
|
+
if (results.length === 0) {
|
|
3612
|
+
console.log("No results found.\n");
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
console.log(`Found ${results.length} results:
|
|
3616
|
+
`);
|
|
3617
|
+
results.forEach((r, i) => {
|
|
3618
|
+
const scorePercent = Math.round(r.score * 100);
|
|
3619
|
+
console.log(` ${i + 1}. [${r.source}] ${r.title} (score: ${scorePercent}%)`);
|
|
3620
|
+
if (r.content) {
|
|
3621
|
+
console.log(` ${r.content.substring(0, 150)}${r.content.length > 150 ? "..." : ""}`);
|
|
3622
|
+
}
|
|
3623
|
+
console.log("");
|
|
3624
|
+
});
|
|
3625
|
+
}
|
|
3626
|
+
async function handleDecay(sdk, subcommand) {
|
|
3627
|
+
switch (subcommand) {
|
|
3628
|
+
case "stats": {
|
|
3629
|
+
const stats = await sdk.getDecayStats();
|
|
3630
|
+
console.log("\nDecay Statistics:\n");
|
|
3631
|
+
console.log(` Total observations: ${stats.total}`);
|
|
3632
|
+
console.log(` Stale (file changed): ${stats.stale}`);
|
|
3633
|
+
console.log(` Never accessed: ${stats.neverAccessed}`);
|
|
3634
|
+
console.log(` Recently accessed: ${stats.recentlyAccessed} (last 48h)`);
|
|
3635
|
+
if (stats.total > 0) {
|
|
3636
|
+
const freshPercent = Math.round((stats.total - stats.stale) / stats.total * 100);
|
|
3637
|
+
console.log(` Freshness: ${freshPercent}%`);
|
|
3638
|
+
}
|
|
3639
|
+
console.log("");
|
|
3640
|
+
break;
|
|
3641
|
+
}
|
|
3642
|
+
case "detect-stale": {
|
|
3643
|
+
console.log("\nDetecting stale observations...\n");
|
|
3644
|
+
const count = await sdk.detectStaleObservations();
|
|
3645
|
+
if (count > 0) {
|
|
3646
|
+
console.log(` Found and marked ${count} stale observation(s).`);
|
|
3647
|
+
console.log(` These observations reference files that have been modified since they were recorded.
|
|
3648
|
+
`);
|
|
3649
|
+
} else {
|
|
3650
|
+
console.log(" No stale observations found. All observations are fresh.\n");
|
|
3651
|
+
}
|
|
3652
|
+
break;
|
|
3653
|
+
}
|
|
3654
|
+
case "consolidate": {
|
|
3655
|
+
const dryRun = args.includes("--dry-run");
|
|
3656
|
+
console.log(`
|
|
3657
|
+
${dryRun ? "[DRY RUN] " : ""}Consolidating duplicate observations...
|
|
3658
|
+
`);
|
|
3659
|
+
const result = await sdk.consolidateObservations({ dryRun });
|
|
3660
|
+
if (result.merged > 0) {
|
|
3661
|
+
console.log(` Merged ${result.merged} group(s), removed ${result.removed} duplicate(s).`);
|
|
3662
|
+
if (dryRun) {
|
|
3663
|
+
console.log(` (Dry run: no changes were made. Remove --dry-run to apply.)`);
|
|
3664
|
+
}
|
|
3665
|
+
} else {
|
|
3666
|
+
console.log(" No duplicate observations found to consolidate.");
|
|
3667
|
+
}
|
|
3668
|
+
console.log("");
|
|
3669
|
+
break;
|
|
3670
|
+
}
|
|
3671
|
+
default:
|
|
3672
|
+
console.log("\nUsage: kiro-memory decay <subcommand>\n");
|
|
3673
|
+
console.log("Subcommands:");
|
|
3674
|
+
console.log(" stats Show decay statistics (stale, never accessed, etc.)");
|
|
3675
|
+
console.log(" detect-stale Detect and mark stale observations (files changed)");
|
|
3676
|
+
console.log(" consolidate [--dry-run] Consolidate duplicate observations\n");
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
async function generateReportCli(sdk, cliArgs) {
|
|
3680
|
+
const periodArg = cliArgs.find((a) => a.startsWith("--period="))?.split("=")[1];
|
|
3681
|
+
const formatArg = cliArgs.find((a) => a.startsWith("--format="))?.split("=")[1];
|
|
3682
|
+
const outputArg = cliArgs.find((a) => a.startsWith("--output="))?.split("=")[1];
|
|
3683
|
+
const period = periodArg === "monthly" ? "monthly" : "weekly";
|
|
3684
|
+
const format = formatArg === "md" || formatArg === "markdown" ? "markdown" : formatArg === "json" ? "json" : "text";
|
|
3685
|
+
const data = await sdk.generateReport({ period });
|
|
3686
|
+
let output;
|
|
3687
|
+
switch (format) {
|
|
3688
|
+
case "markdown":
|
|
3689
|
+
output = formatReportMarkdown(data);
|
|
3690
|
+
break;
|
|
3691
|
+
case "json":
|
|
3692
|
+
output = formatReportJson(data);
|
|
3693
|
+
break;
|
|
3694
|
+
default:
|
|
3695
|
+
output = formatReportText(data);
|
|
3696
|
+
}
|
|
3697
|
+
if (outputArg) {
|
|
3698
|
+
writeFileSync(outputArg, output, "utf8");
|
|
3699
|
+
console.log(`
|
|
3700
|
+
Report saved to: ${outputArg}
|
|
3701
|
+
`);
|
|
3702
|
+
} else {
|
|
3703
|
+
console.log(output);
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
async function resumeSession(sdk, sessionId) {
|
|
3707
|
+
const checkpoint = sessionId ? await sdk.getCheckpoint(sessionId) : await sdk.getLatestProjectCheckpoint();
|
|
3708
|
+
if (!checkpoint) {
|
|
3709
|
+
console.log("\n No checkpoint found.");
|
|
3710
|
+
if (sessionId) {
|
|
3711
|
+
console.log(` Session ${sessionId} has no checkpoint.`);
|
|
3712
|
+
} else {
|
|
3713
|
+
console.log(` No recent checkpoints for project "${sdk.getProject()}".`);
|
|
3714
|
+
}
|
|
3715
|
+
console.log(" Checkpoints are created automatically at the end of each session.\n");
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
console.log("");
|
|
3719
|
+
console.log(` \x1B[36m\u2550\u2550\u2550 Session Checkpoint \u2550\u2550\u2550\x1B[0m`);
|
|
3720
|
+
console.log(` \x1B[2mProject: ${checkpoint.project} | Session: ${checkpoint.session_id}\x1B[0m`);
|
|
3721
|
+
console.log(` \x1B[2m${new Date(checkpoint.created_at).toLocaleString()}\x1B[0m`);
|
|
3722
|
+
console.log("");
|
|
3723
|
+
console.log(` \x1B[1mTask:\x1B[0m ${checkpoint.task}`);
|
|
3724
|
+
if (checkpoint.progress) {
|
|
3725
|
+
console.log(` \x1B[1mProgress:\x1B[0m ${checkpoint.progress}`);
|
|
3726
|
+
}
|
|
3727
|
+
if (checkpoint.next_steps) {
|
|
3728
|
+
console.log(` \x1B[1mNext Steps:\x1B[0m ${checkpoint.next_steps}`);
|
|
3729
|
+
}
|
|
3730
|
+
if (checkpoint.open_questions) {
|
|
3731
|
+
console.log(` \x1B[1mOpen Questions:\x1B[0m ${checkpoint.open_questions}`);
|
|
3732
|
+
}
|
|
3733
|
+
if (checkpoint.relevant_files) {
|
|
3734
|
+
console.log(` \x1B[1mRelevant Files:\x1B[0m`);
|
|
3735
|
+
const files = checkpoint.relevant_files.split(",").map((f) => f.trim());
|
|
3736
|
+
files.forEach((f) => {
|
|
3737
|
+
console.log(` - ${f}`);
|
|
3738
|
+
});
|
|
3739
|
+
}
|
|
3740
|
+
console.log("");
|
|
3741
|
+
}
|
|
1610
3742
|
function showHelp() {
|
|
1611
3743
|
console.log(`Usage: kiro-memory <command> [options]
|
|
1612
3744
|
|
|
1613
3745
|
Setup:
|
|
1614
|
-
install Install
|
|
3746
|
+
install Install for Kiro CLI (default)
|
|
3747
|
+
install --claude-code Install hooks and MCP server for Claude Code
|
|
3748
|
+
install --cursor Install hooks and MCP server for Cursor IDE
|
|
3749
|
+
install --windsurf Install MCP server for Windsurf IDE
|
|
3750
|
+
install --cline Install MCP server for Cline (VS Code)
|
|
1615
3751
|
doctor Run environment diagnostics (checks Node, build tools, WSL, etc.)
|
|
1616
3752
|
|
|
1617
3753
|
Commands:
|
|
1618
3754
|
context, ctx Show current project context
|
|
1619
|
-
|
|
3755
|
+
resume [session-id] Resume previous session (shows checkpoint)
|
|
3756
|
+
report [options] Generate activity report
|
|
3757
|
+
--period=weekly|monthly Time period (default: weekly)
|
|
3758
|
+
--format=text|md|json Output format (default: text)
|
|
3759
|
+
--output=<file> Write to file instead of stdout
|
|
3760
|
+
search <query> Search across all context (keyword FTS5)
|
|
3761
|
+
semantic-search <query> Hybrid search: vector + keyword (semantic)
|
|
1620
3762
|
observations [limit] Show recent observations (default: 10)
|
|
1621
3763
|
summaries [limit] Show recent summaries (default: 5)
|
|
1622
3764
|
add-observation <title> <content> Add a new observation
|
|
1623
3765
|
add-summary <content> Add a new summary
|
|
3766
|
+
add-knowledge <type> <title> <content> Store structured knowledge
|
|
3767
|
+
Types: constraint, decision, heuristic, rejected
|
|
3768
|
+
Options: --severity=hard|soft --alternatives=a,b,c --reason=...
|
|
3769
|
+
--context=... --confidence=high|medium|low
|
|
3770
|
+
--concepts=a,b --files=path1,path2
|
|
3771
|
+
embeddings stats Show embedding statistics
|
|
3772
|
+
embeddings backfill [n] Generate embeddings for unprocessed observations
|
|
3773
|
+
decay stats Show decay statistics (stale, never accessed, etc.)
|
|
3774
|
+
decay detect-stale Detect and mark stale observations
|
|
3775
|
+
decay consolidate [--dry-run] Consolidate duplicate observations
|
|
1624
3776
|
help Show this help message
|
|
1625
3777
|
|
|
1626
3778
|
Examples:
|
|
1627
3779
|
kiro-memory install
|
|
1628
3780
|
kiro-memory doctor
|
|
1629
3781
|
kiro-memory context
|
|
3782
|
+
kiro-memory resume
|
|
3783
|
+
kiro-memory resume 42
|
|
3784
|
+
kiro-memory report
|
|
3785
|
+
kiro-memory report --period=monthly --format=md --output=report.md
|
|
1630
3786
|
kiro-memory search "authentication"
|
|
3787
|
+
kiro-memory semantic-search "how did I fix the auth bug"
|
|
3788
|
+
kiro-memory add-knowledge constraint "No any in TypeScript" "Never use any type" --severity=hard
|
|
3789
|
+
kiro-memory add-knowledge decision "PostgreSQL over MongoDB" "Chosen for ACID" --alternatives=MongoDB,DynamoDB
|
|
3790
|
+
kiro-memory embeddings stats
|
|
3791
|
+
kiro-memory embeddings backfill 100
|
|
3792
|
+
kiro-memory decay stats
|
|
3793
|
+
kiro-memory decay detect-stale
|
|
3794
|
+
kiro-memory decay consolidate --dry-run
|
|
1631
3795
|
kiro-memory observations 20
|
|
1632
3796
|
`);
|
|
1633
3797
|
}
|