kiro-memory 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +290 -0
- package/package.json +117 -0
- package/plugin/dist/cli/contextkit.js +1259 -0
- package/plugin/dist/hooks/agentSpawn.js +1187 -0
- package/plugin/dist/hooks/kiro-hooks.js +1184 -0
- package/plugin/dist/hooks/postToolUse.js +1219 -0
- package/plugin/dist/hooks/stop.js +1163 -0
- package/plugin/dist/hooks/userPromptSubmit.js +1152 -0
- package/plugin/dist/index.js +2103 -0
- package/plugin/dist/sdk/index.js +1083 -0
- package/plugin/dist/servers/mcp-server.js +266 -0
- package/plugin/dist/services/search/ChromaManager.js +357 -0
- package/plugin/dist/services/search/HybridSearch.js +502 -0
- package/plugin/dist/services/search/index.js +511 -0
- package/plugin/dist/services/sqlite/Database.js +625 -0
- package/plugin/dist/services/sqlite/Observations.js +46 -0
- package/plugin/dist/services/sqlite/Prompts.js +39 -0
- package/plugin/dist/services/sqlite/Search.js +143 -0
- package/plugin/dist/services/sqlite/Sessions.js +60 -0
- package/plugin/dist/services/sqlite/Summaries.js +44 -0
- package/plugin/dist/services/sqlite/index.js +951 -0
- package/plugin/dist/shared/paths.js +315 -0
- package/plugin/dist/types/worker-types.js +0 -0
- package/plugin/dist/utils/logger.js +222 -0
- package/plugin/dist/viewer.html +252 -0
- package/plugin/dist/viewer.js +23965 -0
- package/plugin/dist/worker-service.js +1782 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/servers/mcp-server.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
console.log = (...args) => console.error("[contextkit-mcp]", ...args);
|
|
11
|
+
var WORKER_HOST = process.env.CONTEXTKIT_WORKER_HOST || "127.0.0.1";
|
|
12
|
+
var WORKER_PORT = process.env.CONTEXTKIT_WORKER_PORT || "3001";
|
|
13
|
+
var WORKER_BASE = `http://${WORKER_HOST}:${WORKER_PORT}`;
|
|
14
|
+
async function callWorkerGET(endpoint, params = {}) {
|
|
15
|
+
const url = new URL(endpoint, WORKER_BASE);
|
|
16
|
+
Object.entries(params).forEach(([k, v]) => {
|
|
17
|
+
if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, v);
|
|
18
|
+
});
|
|
19
|
+
const resp = await fetch(url.toString(), { signal: AbortSignal.timeout(1e4) });
|
|
20
|
+
if (!resp.ok) throw new Error(`Worker ${resp.status}: ${await resp.text()}`);
|
|
21
|
+
return resp.json();
|
|
22
|
+
}
|
|
23
|
+
async function callWorkerPOST(endpoint, body) {
|
|
24
|
+
const url = new URL(endpoint, WORKER_BASE);
|
|
25
|
+
const resp = await fetch(url.toString(), {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/json" },
|
|
28
|
+
body: JSON.stringify(body),
|
|
29
|
+
signal: AbortSignal.timeout(1e4)
|
|
30
|
+
});
|
|
31
|
+
if (!resp.ok) throw new Error(`Worker ${resp.status}: ${await resp.text()}`);
|
|
32
|
+
return resp.json();
|
|
33
|
+
}
|
|
34
|
+
var TOOLS = [
|
|
35
|
+
{
|
|
36
|
+
name: "search",
|
|
37
|
+
description: "Cerca nella memoria di ContextKit. Restituisce osservazioni e sommari che corrispondono alla query. Usa questo tool per trovare contesto da sessioni precedenti.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
query: { type: "string", description: "Testo da cercare nelle osservazioni e sommari" },
|
|
42
|
+
project: { type: "string", description: "Filtra per nome progetto (opzionale)" },
|
|
43
|
+
type: { type: "string", description: "Filtra per tipo osservazione: file-write, command, research, tool-use (opzionale)" },
|
|
44
|
+
limit: { type: "number", description: "Numero massimo risultati (default: 20)" }
|
|
45
|
+
},
|
|
46
|
+
required: ["query"]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "timeline",
|
|
51
|
+
description: "Mostra il contesto cronologico attorno a un'osservazione specifica. Utile per capire cosa \xE8 successo prima e dopo un evento.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
anchor: { type: "number", description: "ID dell'osservazione come punto di riferimento" },
|
|
56
|
+
depth_before: { type: "number", description: "Numero di osservazioni prima (default: 5)" },
|
|
57
|
+
depth_after: { type: "number", description: "Numero di osservazioni dopo (default: 5)" }
|
|
58
|
+
},
|
|
59
|
+
required: ["anchor"]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "get_observations",
|
|
64
|
+
description: 'Recupera i dettagli completi di osservazioni specifiche per ID. Usa dopo "search" per ottenere il contenuto completo.',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
ids: {
|
|
69
|
+
type: "array",
|
|
70
|
+
items: { type: "number" },
|
|
71
|
+
description: "Array di ID osservazioni da recuperare"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: ["ids"]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "get_context",
|
|
79
|
+
description: "Recupera il contesto recente per un progetto: osservazioni, sommari e prompt recenti.",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
project: { type: "string", description: "Nome del progetto" }
|
|
84
|
+
},
|
|
85
|
+
required: ["project"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
var handlers = {
|
|
90
|
+
async search(args) {
|
|
91
|
+
const result = await callWorkerGET("/api/search", {
|
|
92
|
+
q: args.query,
|
|
93
|
+
project: args.project || "",
|
|
94
|
+
type: args.type || "",
|
|
95
|
+
limit: String(args.limit || 20)
|
|
96
|
+
});
|
|
97
|
+
const obs = result.observations || [];
|
|
98
|
+
const sums = result.summaries || [];
|
|
99
|
+
if (obs.length === 0 && sums.length === 0) {
|
|
100
|
+
return "Nessun risultato trovato per la query.";
|
|
101
|
+
}
|
|
102
|
+
let output = `## Risultati ricerca: "${args.query}"
|
|
103
|
+
|
|
104
|
+
`;
|
|
105
|
+
if (obs.length > 0) {
|
|
106
|
+
output += `### Osservazioni (${obs.length})
|
|
107
|
+
|
|
108
|
+
`;
|
|
109
|
+
output += "| ID | Tipo | Titolo | Data |\n|---|---|---|---|\n";
|
|
110
|
+
obs.forEach((o) => {
|
|
111
|
+
output += `| ${o.id} | ${o.type} | ${o.title} | ${o.created_at?.split("T")[0] || ""} |
|
|
112
|
+
`;
|
|
113
|
+
});
|
|
114
|
+
output += "\n";
|
|
115
|
+
}
|
|
116
|
+
if (sums.length > 0) {
|
|
117
|
+
output += `### Sommari (${sums.length})
|
|
118
|
+
|
|
119
|
+
`;
|
|
120
|
+
sums.forEach((s) => {
|
|
121
|
+
if (s.learned) output += `- **Appreso**: ${s.learned}
|
|
122
|
+
`;
|
|
123
|
+
if (s.completed) output += `- **Completato**: ${s.completed}
|
|
124
|
+
`;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return output;
|
|
128
|
+
},
|
|
129
|
+
async timeline(args) {
|
|
130
|
+
const result = await callWorkerGET("/api/timeline", {
|
|
131
|
+
anchor: String(args.anchor),
|
|
132
|
+
depth_before: String(args.depth_before || 5),
|
|
133
|
+
depth_after: String(args.depth_after || 5)
|
|
134
|
+
});
|
|
135
|
+
const entries = result.timeline || result || [];
|
|
136
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
137
|
+
return `Nessun contesto trovato attorno all'osservazione ${args.anchor}.`;
|
|
138
|
+
}
|
|
139
|
+
let output = `## Timeline attorno all'osservazione #${args.anchor}
|
|
140
|
+
|
|
141
|
+
`;
|
|
142
|
+
entries.forEach((e) => {
|
|
143
|
+
const marker = e.id === args.anchor ? "\u2192 " : " ";
|
|
144
|
+
output += `${marker}**#${e.id}** [${e.type}] ${e.title} (${e.created_at?.split("T")[0] || ""})
|
|
145
|
+
`;
|
|
146
|
+
if (e.content) output += ` ${e.content.substring(0, 200)}
|
|
147
|
+
`;
|
|
148
|
+
output += "\n";
|
|
149
|
+
});
|
|
150
|
+
return output;
|
|
151
|
+
},
|
|
152
|
+
async get_observations(args) {
|
|
153
|
+
const result = await callWorkerPOST("/api/observations/batch", { ids: args.ids });
|
|
154
|
+
const obs = result.observations || result || [];
|
|
155
|
+
if (!Array.isArray(obs) || obs.length === 0) {
|
|
156
|
+
return "Nessuna osservazione trovata per gli ID specificati.";
|
|
157
|
+
}
|
|
158
|
+
let output = `## Dettagli Osservazioni
|
|
159
|
+
|
|
160
|
+
`;
|
|
161
|
+
obs.forEach((o) => {
|
|
162
|
+
output += `### #${o.id}: ${o.title}
|
|
163
|
+
`;
|
|
164
|
+
output += `- **Tipo**: ${o.type}
|
|
165
|
+
`;
|
|
166
|
+
output += `- **Progetto**: ${o.project}
|
|
167
|
+
`;
|
|
168
|
+
output += `- **Data**: ${o.created_at}
|
|
169
|
+
`;
|
|
170
|
+
if (o.text) output += `- **Contenuto**: ${o.text}
|
|
171
|
+
`;
|
|
172
|
+
if (o.narrative) output += `- **Narrativa**: ${o.narrative}
|
|
173
|
+
`;
|
|
174
|
+
if (o.concepts) output += `- **Concetti**: ${o.concepts}
|
|
175
|
+
`;
|
|
176
|
+
if (o.files_read) output += `- **File letti**: ${o.files_read}
|
|
177
|
+
`;
|
|
178
|
+
if (o.files_modified) output += `- **File modificati**: ${o.files_modified}
|
|
179
|
+
`;
|
|
180
|
+
output += "\n";
|
|
181
|
+
});
|
|
182
|
+
return output;
|
|
183
|
+
},
|
|
184
|
+
async get_context(args) {
|
|
185
|
+
const result = await callWorkerGET(`/api/context/${encodeURIComponent(args.project)}`);
|
|
186
|
+
const obs = result.observations || [];
|
|
187
|
+
const sums = result.summaries || [];
|
|
188
|
+
let output = `## Contesto: ${args.project}
|
|
189
|
+
|
|
190
|
+
`;
|
|
191
|
+
if (sums.length > 0) {
|
|
192
|
+
output += `### Sommari Recenti
|
|
193
|
+
|
|
194
|
+
`;
|
|
195
|
+
sums.forEach((s) => {
|
|
196
|
+
if (s.request) output += `**Richiesta**: ${s.request}
|
|
197
|
+
`;
|
|
198
|
+
if (s.learned) output += `- Appreso: ${s.learned}
|
|
199
|
+
`;
|
|
200
|
+
if (s.completed) output += `- Completato: ${s.completed}
|
|
201
|
+
`;
|
|
202
|
+
if (s.next_steps) output += `- Prossimi passi: ${s.next_steps}
|
|
203
|
+
|
|
204
|
+
`;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (obs.length > 0) {
|
|
208
|
+
output += `### Osservazioni Recenti (${obs.length})
|
|
209
|
+
|
|
210
|
+
`;
|
|
211
|
+
obs.slice(0, 10).forEach((o) => {
|
|
212
|
+
output += `- **${o.title}** [${o.type}]: ${(o.text || "").substring(0, 100)}
|
|
213
|
+
`;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return output;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
async function main() {
|
|
220
|
+
const server = new Server(
|
|
221
|
+
{ name: "contextkit", version: "1.0.0" },
|
|
222
|
+
{ capabilities: { tools: {} } }
|
|
223
|
+
);
|
|
224
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
225
|
+
tools: TOOLS
|
|
226
|
+
}));
|
|
227
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
228
|
+
const { name, arguments: args } = request.params;
|
|
229
|
+
const handler = handlers[name];
|
|
230
|
+
if (!handler) {
|
|
231
|
+
return {
|
|
232
|
+
content: [{ type: "text", text: `Tool sconosciuto: ${name}` }],
|
|
233
|
+
isError: true
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const result = await handler(args || {});
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: "text", text: result }]
|
|
240
|
+
};
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const msg = error?.message || String(error);
|
|
243
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed")) {
|
|
244
|
+
return {
|
|
245
|
+
content: [{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: `Worker ContextKit non raggiungibile su ${WORKER_BASE}.
|
|
248
|
+
Avvia il worker con: cd <contextkit-dir> && npm run worker:start`
|
|
249
|
+
}],
|
|
250
|
+
isError: true
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
content: [{ type: "text", text: `Errore: ${msg}` }],
|
|
255
|
+
isError: true
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
const transport = new StdioServerTransport();
|
|
260
|
+
await server.connect(transport);
|
|
261
|
+
console.log("ContextKit MCP server avviato su stdio");
|
|
262
|
+
}
|
|
263
|
+
main().catch((err) => {
|
|
264
|
+
console.error("Errore avvio MCP server:", err);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
});
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// src/services/search/ChromaManager.ts
|
|
2
|
+
import { ChromaClient } from "chromadb";
|
|
3
|
+
import { join as join2 } from "path";
|
|
4
|
+
import { homedir as homedir2 } from "os";
|
|
5
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
6
|
+
|
|
7
|
+
// src/utils/logger.ts
|
|
8
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
12
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
13
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
14
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
15
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
16
|
+
LogLevel2[LogLevel2["SILENT"] = 4] = "SILENT";
|
|
17
|
+
return LogLevel2;
|
|
18
|
+
})(LogLevel || {});
|
|
19
|
+
var DEFAULT_DATA_DIR = join(homedir(), ".contextkit");
|
|
20
|
+
var Logger = class {
|
|
21
|
+
level = null;
|
|
22
|
+
useColor;
|
|
23
|
+
logFilePath = null;
|
|
24
|
+
logFileInitialized = false;
|
|
25
|
+
constructor() {
|
|
26
|
+
this.useColor = process.stdout.isTTY ?? false;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initialize log file path and ensure directory exists (lazy initialization)
|
|
30
|
+
*/
|
|
31
|
+
ensureLogFileInitialized() {
|
|
32
|
+
if (this.logFileInitialized) return;
|
|
33
|
+
this.logFileInitialized = true;
|
|
34
|
+
try {
|
|
35
|
+
const logsDir = join(DEFAULT_DATA_DIR, "logs");
|
|
36
|
+
if (!existsSync(logsDir)) {
|
|
37
|
+
mkdirSync(logsDir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
40
|
+
this.logFilePath = join(logsDir, `contextkit-${date}.log`);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("[LOGGER] Failed to initialize log file:", error);
|
|
43
|
+
this.logFilePath = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Lazy-load log level from settings file
|
|
48
|
+
*/
|
|
49
|
+
getLevel() {
|
|
50
|
+
if (this.level === null) {
|
|
51
|
+
try {
|
|
52
|
+
const settingsPath = join(DEFAULT_DATA_DIR, "settings.json");
|
|
53
|
+
if (existsSync(settingsPath)) {
|
|
54
|
+
const settingsData = readFileSync(settingsPath, "utf-8");
|
|
55
|
+
const settings = JSON.parse(settingsData);
|
|
56
|
+
const envLevel = (settings.CONTEXTKIT_LOG_LEVEL || "INFO").toUpperCase();
|
|
57
|
+
this.level = LogLevel[envLevel] ?? 1 /* INFO */;
|
|
58
|
+
} else {
|
|
59
|
+
this.level = 1 /* INFO */;
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.level = 1 /* INFO */;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return this.level;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create correlation ID for tracking an observation through the pipeline
|
|
69
|
+
*/
|
|
70
|
+
correlationId(sessionId, observationNum) {
|
|
71
|
+
return `obs-${sessionId}-${observationNum}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create session correlation ID
|
|
75
|
+
*/
|
|
76
|
+
sessionId(sessionId) {
|
|
77
|
+
return `session-${sessionId}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format data for logging - create compact summaries instead of full dumps
|
|
81
|
+
*/
|
|
82
|
+
formatData(data) {
|
|
83
|
+
if (data === null || data === void 0) return "";
|
|
84
|
+
if (typeof data === "string") return data;
|
|
85
|
+
if (typeof data === "number") return data.toString();
|
|
86
|
+
if (typeof data === "boolean") return data.toString();
|
|
87
|
+
if (typeof data === "object") {
|
|
88
|
+
if (data instanceof Error) {
|
|
89
|
+
return this.getLevel() === 0 /* DEBUG */ ? `${data.message}
|
|
90
|
+
${data.stack}` : data.message;
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(data)) {
|
|
93
|
+
return `[${data.length} items]`;
|
|
94
|
+
}
|
|
95
|
+
const keys = Object.keys(data);
|
|
96
|
+
if (keys.length === 0) return "{}";
|
|
97
|
+
if (keys.length <= 3) {
|
|
98
|
+
return JSON.stringify(data);
|
|
99
|
+
}
|
|
100
|
+
return `{${keys.length} keys: ${keys.slice(0, 3).join(", ")}...}`;
|
|
101
|
+
}
|
|
102
|
+
return String(data);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Format timestamp in local timezone (YYYY-MM-DD HH:MM:SS.mmm)
|
|
106
|
+
*/
|
|
107
|
+
formatTimestamp(date) {
|
|
108
|
+
const year = date.getFullYear();
|
|
109
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
110
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
111
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
112
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
113
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
114
|
+
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
115
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Core logging method
|
|
119
|
+
*/
|
|
120
|
+
log(level, component, message, context, data) {
|
|
121
|
+
if (level < this.getLevel()) return;
|
|
122
|
+
this.ensureLogFileInitialized();
|
|
123
|
+
const timestamp = this.formatTimestamp(/* @__PURE__ */ new Date());
|
|
124
|
+
const levelStr = LogLevel[level].padEnd(5);
|
|
125
|
+
const componentStr = component.padEnd(6);
|
|
126
|
+
let correlationStr = "";
|
|
127
|
+
if (context?.correlationId) {
|
|
128
|
+
correlationStr = `[${context.correlationId}] `;
|
|
129
|
+
} else if (context?.sessionId) {
|
|
130
|
+
correlationStr = `[session-${context.sessionId}] `;
|
|
131
|
+
}
|
|
132
|
+
let dataStr = "";
|
|
133
|
+
if (data !== void 0 && data !== null) {
|
|
134
|
+
if (data instanceof Error) {
|
|
135
|
+
dataStr = this.getLevel() === 0 /* DEBUG */ ? `
|
|
136
|
+
${data.message}
|
|
137
|
+
${data.stack}` : ` ${data.message}`;
|
|
138
|
+
} else if (this.getLevel() === 0 /* DEBUG */ && typeof data === "object") {
|
|
139
|
+
dataStr = "\n" + JSON.stringify(data, null, 2);
|
|
140
|
+
} else {
|
|
141
|
+
dataStr = " " + this.formatData(data);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let contextStr = "";
|
|
145
|
+
if (context) {
|
|
146
|
+
const { sessionId, memorySessionId, correlationId, ...rest } = context;
|
|
147
|
+
if (Object.keys(rest).length > 0) {
|
|
148
|
+
const pairs = Object.entries(rest).map(([k, v]) => `${k}=${v}`);
|
|
149
|
+
contextStr = ` {${pairs.join(", ")}}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const logLine = `[${timestamp}] [${levelStr}] [${componentStr}] ${correlationStr}${message}${contextStr}${dataStr}`;
|
|
153
|
+
if (this.logFilePath) {
|
|
154
|
+
try {
|
|
155
|
+
appendFileSync(this.logFilePath, logLine + "\n", "utf8");
|
|
156
|
+
} catch (error) {
|
|
157
|
+
process.stderr.write(`[LOGGER] Failed to write to log file: ${error}
|
|
158
|
+
`);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
process.stderr.write(logLine + "\n");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Public logging methods
|
|
165
|
+
debug(component, message, context, data) {
|
|
166
|
+
this.log(0 /* DEBUG */, component, message, context, data);
|
|
167
|
+
}
|
|
168
|
+
info(component, message, context, data) {
|
|
169
|
+
this.log(1 /* INFO */, component, message, context, data);
|
|
170
|
+
}
|
|
171
|
+
warn(component, message, context, data) {
|
|
172
|
+
this.log(2 /* WARN */, component, message, context, data);
|
|
173
|
+
}
|
|
174
|
+
error(component, message, context, data) {
|
|
175
|
+
this.log(3 /* ERROR */, component, message, context, data);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Log data flow: input → processing
|
|
179
|
+
*/
|
|
180
|
+
dataIn(component, message, context, data) {
|
|
181
|
+
this.info(component, `\u2192 ${message}`, context, data);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Log data flow: processing → output
|
|
185
|
+
*/
|
|
186
|
+
dataOut(component, message, context, data) {
|
|
187
|
+
this.info(component, `\u2190 ${message}`, context, data);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Log successful completion
|
|
191
|
+
*/
|
|
192
|
+
success(component, message, context, data) {
|
|
193
|
+
this.info(component, `\u2713 ${message}`, context, data);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Log failure
|
|
197
|
+
*/
|
|
198
|
+
failure(component, message, context, data) {
|
|
199
|
+
this.error(component, `\u2717 ${message}`, context, data);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Log timing information
|
|
203
|
+
*/
|
|
204
|
+
timing(component, message, durationMs, context) {
|
|
205
|
+
this.info(component, `\u23F1 ${message}`, context, { duration: `${durationMs}ms` });
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Happy Path Error - logs when the expected "happy path" fails but we have a fallback
|
|
209
|
+
*/
|
|
210
|
+
happyPathError(component, message, context, data, fallback = "") {
|
|
211
|
+
const stack = new Error().stack || "";
|
|
212
|
+
const stackLines = stack.split("\n");
|
|
213
|
+
const callerLine = stackLines[2] || "";
|
|
214
|
+
const callerMatch = callerLine.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/);
|
|
215
|
+
const location = callerMatch ? `${callerMatch[1].split("/").pop()}:${callerMatch[2]}` : "unknown";
|
|
216
|
+
const enhancedContext = {
|
|
217
|
+
...context,
|
|
218
|
+
location
|
|
219
|
+
};
|
|
220
|
+
this.warn(component, `[HAPPY-PATH] ${message}`, enhancedContext, data);
|
|
221
|
+
return fallback;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var logger = new Logger();
|
|
225
|
+
|
|
226
|
+
// src/services/search/ChromaManager.ts
|
|
227
|
+
var VECTOR_DB_DIR = join2(homedir2(), ".contextkit", "vector-db");
|
|
228
|
+
var ChromaManager = class {
|
|
229
|
+
client;
|
|
230
|
+
collection = null;
|
|
231
|
+
isAvailable = false;
|
|
232
|
+
constructor() {
|
|
233
|
+
if (!existsSync2(VECTOR_DB_DIR)) {
|
|
234
|
+
mkdirSync2(VECTOR_DB_DIR, { recursive: true });
|
|
235
|
+
}
|
|
236
|
+
this.client = new ChromaClient({
|
|
237
|
+
path: process.env.CHROMADB_URL || "http://localhost:8000"
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Initialize ChromaDB connection and collection
|
|
242
|
+
*/
|
|
243
|
+
async initialize() {
|
|
244
|
+
try {
|
|
245
|
+
await this.client.heartbeat();
|
|
246
|
+
this.collection = await this.client.getOrCreateCollection({
|
|
247
|
+
name: "contextkit-observations",
|
|
248
|
+
metadata: { description: "ContextKit observation embeddings" }
|
|
249
|
+
});
|
|
250
|
+
this.isAvailable = true;
|
|
251
|
+
logger.info("CHROMA", "ChromaDB initialized successfully");
|
|
252
|
+
return true;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
logger.warn("CHROMA", "ChromaDB not available, falling back to SQLite search", {}, error);
|
|
255
|
+
this.isAvailable = false;
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Add observation embedding to ChromaDB
|
|
261
|
+
*/
|
|
262
|
+
async addObservation(id, content, metadata) {
|
|
263
|
+
if (!this.isAvailable || !this.collection) {
|
|
264
|
+
logger.debug("CHROMA", "ChromaDB not available, skipping embedding");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
await this.collection.add({
|
|
269
|
+
ids: [id],
|
|
270
|
+
documents: [content],
|
|
271
|
+
metadatas: [metadata]
|
|
272
|
+
});
|
|
273
|
+
logger.debug("CHROMA", `Added observation ${id} to vector DB`);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
logger.error("CHROMA", `Failed to add observation ${id}`, {}, error);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Search observations by semantic similarity
|
|
280
|
+
*/
|
|
281
|
+
async search(query, options = {}) {
|
|
282
|
+
if (!this.isAvailable || !this.collection) {
|
|
283
|
+
logger.debug("CHROMA", "ChromaDB not available, returning empty results");
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const where = options.project ? { project: options.project } : void 0;
|
|
288
|
+
const results = await this.collection.query({
|
|
289
|
+
queryTexts: [query],
|
|
290
|
+
nResults: options.limit || 10,
|
|
291
|
+
where
|
|
292
|
+
});
|
|
293
|
+
const hits = [];
|
|
294
|
+
if (results.ids && results.ids[0]) {
|
|
295
|
+
for (let i = 0; i < results.ids[0].length; i++) {
|
|
296
|
+
hits.push({
|
|
297
|
+
id: results.ids[0][i],
|
|
298
|
+
content: results.documents?.[0]?.[i] || "",
|
|
299
|
+
metadata: results.metadatas?.[0]?.[i] || {},
|
|
300
|
+
distance: results.distances?.[0]?.[i] || 0
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
logger.debug("CHROMA", `Search returned ${hits.length} results`);
|
|
305
|
+
return hits;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
logger.error("CHROMA", "Search failed", {}, error);
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Delete observation from ChromaDB
|
|
313
|
+
*/
|
|
314
|
+
async deleteObservation(id) {
|
|
315
|
+
if (!this.isAvailable || !this.collection) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
await this.collection.delete({ ids: [id] });
|
|
320
|
+
logger.debug("CHROMA", `Deleted observation ${id}`);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
logger.error("CHROMA", `Failed to delete observation ${id}`, {}, error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Check if ChromaDB is available
|
|
327
|
+
*/
|
|
328
|
+
isChromaAvailable() {
|
|
329
|
+
return this.isAvailable;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Get collection stats
|
|
333
|
+
*/
|
|
334
|
+
async getStats() {
|
|
335
|
+
if (!this.isAvailable || !this.collection) {
|
|
336
|
+
return { count: 0 };
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const count = await this.collection.count();
|
|
340
|
+
return { count };
|
|
341
|
+
} catch (error) {
|
|
342
|
+
logger.error("CHROMA", "Failed to get stats", {}, error);
|
|
343
|
+
return { count: 0 };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
var chromaManager = null;
|
|
348
|
+
function getChromaManager() {
|
|
349
|
+
if (!chromaManager) {
|
|
350
|
+
chromaManager = new ChromaManager();
|
|
351
|
+
}
|
|
352
|
+
return chromaManager;
|
|
353
|
+
}
|
|
354
|
+
export {
|
|
355
|
+
ChromaManager,
|
|
356
|
+
getChromaManager
|
|
357
|
+
};
|