unforgit 0.5.2 → 0.5.4
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/dist/chunk-CKUDYQYP.js +3652 -0
- package/dist/chunk-CKUDYQYP.js.map +1 -0
- package/dist/index.js +167 -238
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +26 -19
- package/dist/mcp.js.map +1 -1
- package/package.json +15 -8
- package/dist/chunk-7OCVIDC7.js +0 -12
- package/dist/chunk-7OCVIDC7.js.map +0 -1
|
@@ -0,0 +1,3652 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// ../../packages/core/dist/index.js
|
|
10
|
+
import OpenAI from "openai";
|
|
11
|
+
import OpenAI2 from "openai";
|
|
12
|
+
var DEFAULT_TTL_SECONDS_BY_TYPE = {
|
|
13
|
+
episodic: 30 * 24 * 60 * 60,
|
|
14
|
+
semantic: void 0,
|
|
15
|
+
procedural: void 0
|
|
16
|
+
};
|
|
17
|
+
var DEFAULT_USAGE_BOOST = {
|
|
18
|
+
enabled: true,
|
|
19
|
+
topKToRecord: 5,
|
|
20
|
+
minUsageCount: 2,
|
|
21
|
+
maxBoost: 0.15,
|
|
22
|
+
halfLifeDays: 30
|
|
23
|
+
};
|
|
24
|
+
var DEFAULT_MAINTENANCE = {
|
|
25
|
+
staleEpisodicDays: 30,
|
|
26
|
+
consolidationThreshold: 0.5,
|
|
27
|
+
consolidationMinGroupSize: 2,
|
|
28
|
+
consolidationMaxGroups: 5,
|
|
29
|
+
promoteRecallCount: 5,
|
|
30
|
+
pinRecallCount: 8,
|
|
31
|
+
dryRunDefault: true,
|
|
32
|
+
autoRunOnStore: true,
|
|
33
|
+
autoRunOnRecall: true,
|
|
34
|
+
debounceMs: 3e4
|
|
35
|
+
};
|
|
36
|
+
function resolveLifecycleConfig(config2) {
|
|
37
|
+
return {
|
|
38
|
+
ttlSecondsByType: {
|
|
39
|
+
...DEFAULT_TTL_SECONDS_BY_TYPE,
|
|
40
|
+
...config2?.ttlSecondsByType ?? {}
|
|
41
|
+
},
|
|
42
|
+
usageBoost: {
|
|
43
|
+
...DEFAULT_USAGE_BOOST,
|
|
44
|
+
...config2?.usageBoost ?? {}
|
|
45
|
+
},
|
|
46
|
+
maintenance: {
|
|
47
|
+
...DEFAULT_MAINTENANCE,
|
|
48
|
+
...config2?.maintenance ?? {}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function getDefaultTtlSeconds(memoryType, lifecycle) {
|
|
53
|
+
return resolveLifecycleConfig(lifecycle).ttlSecondsByType[memoryType];
|
|
54
|
+
}
|
|
55
|
+
function applyLifecycleDefaults(input, lifecycle) {
|
|
56
|
+
if (input.ttlSeconds !== void 0) {
|
|
57
|
+
return input;
|
|
58
|
+
}
|
|
59
|
+
const ttlSeconds = getDefaultTtlSeconds(input.memoryType, lifecycle);
|
|
60
|
+
if (ttlSeconds === void 0) {
|
|
61
|
+
return input;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...input,
|
|
65
|
+
ttlSeconds
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function isExpiredTtl(createdAt, ttlSeconds, now = /* @__PURE__ */ new Date()) {
|
|
69
|
+
if (!ttlSeconds || ttlSeconds <= 0) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return createdAt.getTime() + ttlSeconds * 1e3 <= now.getTime();
|
|
73
|
+
}
|
|
74
|
+
function isMemoryExpired(memory, now = /* @__PURE__ */ new Date()) {
|
|
75
|
+
if (memory.status === "deleted") {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return isExpiredTtl(memory.createdAt, memory.ttlSeconds, now);
|
|
79
|
+
}
|
|
80
|
+
function computeUsageBoost(usageCount, lastUsed, lifecycle, now = /* @__PURE__ */ new Date()) {
|
|
81
|
+
const { usageBoost } = resolveLifecycleConfig(lifecycle);
|
|
82
|
+
if (!usageBoost.enabled || usageCount < usageBoost.minUsageCount) {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
const effectiveCount = usageCount - usageBoost.minUsageCount + 1;
|
|
86
|
+
const usageFactor = 1 - Math.exp(-effectiveCount / usageBoost.minUsageCount);
|
|
87
|
+
const ageDays = lastUsed ? Math.max(0, (now.getTime() - lastUsed.getTime()) / (1e3 * 60 * 60 * 24)) : usageBoost.halfLifeDays;
|
|
88
|
+
const recencyFactor = Math.exp(-ageDays / usageBoost.halfLifeDays);
|
|
89
|
+
return Math.min(usageBoost.maxBoost, usageBoost.maxBoost * usageFactor * recencyFactor);
|
|
90
|
+
}
|
|
91
|
+
var LifecycleScheduler = class {
|
|
92
|
+
constructor(runner, options) {
|
|
93
|
+
this.runner = runner;
|
|
94
|
+
this.options = options;
|
|
95
|
+
}
|
|
96
|
+
states = /* @__PURE__ */ new Map();
|
|
97
|
+
schedule(orgId, repoId) {
|
|
98
|
+
const key = `${orgId}:${repoId}`;
|
|
99
|
+
const state = this.states.get(key) ?? {
|
|
100
|
+
running: false,
|
|
101
|
+
pending: false,
|
|
102
|
+
orgId,
|
|
103
|
+
repoId
|
|
104
|
+
};
|
|
105
|
+
state.orgId = orgId;
|
|
106
|
+
state.repoId = repoId;
|
|
107
|
+
if (state.running) {
|
|
108
|
+
state.pending = true;
|
|
109
|
+
this.states.set(key, state);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (state.timer) {
|
|
113
|
+
clearTimeout(state.timer);
|
|
114
|
+
}
|
|
115
|
+
state.timer = setTimeout(() => {
|
|
116
|
+
void this.run(key);
|
|
117
|
+
}, this.options.debounceMs);
|
|
118
|
+
this.states.set(key, state);
|
|
119
|
+
}
|
|
120
|
+
dispose() {
|
|
121
|
+
for (const state of this.states.values()) {
|
|
122
|
+
if (state.timer) {
|
|
123
|
+
clearTimeout(state.timer);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
this.states.clear();
|
|
127
|
+
}
|
|
128
|
+
async run(key) {
|
|
129
|
+
const state = this.states.get(key);
|
|
130
|
+
if (!state) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
state.timer = void 0;
|
|
134
|
+
state.running = true;
|
|
135
|
+
this.states.set(key, state);
|
|
136
|
+
try {
|
|
137
|
+
await this.runner(state.orgId, state.repoId);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
this.options.onError?.(error, {
|
|
140
|
+
orgId: state.orgId,
|
|
141
|
+
repoId: state.repoId
|
|
142
|
+
});
|
|
143
|
+
} finally {
|
|
144
|
+
state.running = false;
|
|
145
|
+
if (state.pending) {
|
|
146
|
+
state.pending = false;
|
|
147
|
+
this.states.set(key, state);
|
|
148
|
+
this.schedule(state.orgId, state.repoId);
|
|
149
|
+
} else {
|
|
150
|
+
this.states.delete(key);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
function recencyScore(createdAt) {
|
|
156
|
+
const ageMs = Date.now() - createdAt.getTime();
|
|
157
|
+
const ageDays = ageMs / (1e3 * 60 * 60 * 24);
|
|
158
|
+
return Math.max(0, 1 - ageDays / 365);
|
|
159
|
+
}
|
|
160
|
+
function rankResults(results) {
|
|
161
|
+
return results.sort((a, b) => b.score - a.score);
|
|
162
|
+
}
|
|
163
|
+
function computeCompositeScore(textScore, createdAt, confidence, usageBoost = 0) {
|
|
164
|
+
const recency = recencyScore(createdAt);
|
|
165
|
+
const conf = confidence ?? 0.5;
|
|
166
|
+
return Math.min(1, textScore * 0.55 + recency * 0.15 + conf * 0.15 + usageBoost);
|
|
167
|
+
}
|
|
168
|
+
function computeHybridScore(ftsScore, embeddingScore, createdAt, confidence, usageBoost = 0) {
|
|
169
|
+
const recency = recencyScore(createdAt);
|
|
170
|
+
const conf = confidence ?? 0.5;
|
|
171
|
+
return Math.min(
|
|
172
|
+
1,
|
|
173
|
+
embeddingScore * 0.45 + ftsScore * 0.15 + recency * 0.125 + conf * 0.125 + usageBoost
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
function deduplicateResults(results) {
|
|
177
|
+
const seen = /* @__PURE__ */ new Map();
|
|
178
|
+
for (const r of results) {
|
|
179
|
+
const existing = seen.get(r.id);
|
|
180
|
+
if (!existing || r.score > existing.score) {
|
|
181
|
+
seen.set(r.id, r);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return Array.from(seen.values());
|
|
185
|
+
}
|
|
186
|
+
function mergeAndRank(localResults, remoteResults, k) {
|
|
187
|
+
const all = [...localResults, ...remoteResults];
|
|
188
|
+
const deduped = deduplicateResults(all);
|
|
189
|
+
const ranked = rankResults(deduped);
|
|
190
|
+
return ranked.slice(0, k);
|
|
191
|
+
}
|
|
192
|
+
var SENSITIVE_PATTERNS = [
|
|
193
|
+
/password/i,
|
|
194
|
+
/secret/i,
|
|
195
|
+
/api[_-]?key/i,
|
|
196
|
+
/token/i,
|
|
197
|
+
/credential/i,
|
|
198
|
+
/private[_-]?key/i,
|
|
199
|
+
/-----BEGIN/
|
|
200
|
+
];
|
|
201
|
+
var RULE_LIKE_TAGS = /* @__PURE__ */ new Set([
|
|
202
|
+
"decision",
|
|
203
|
+
"adr",
|
|
204
|
+
"playbook",
|
|
205
|
+
"gotcha",
|
|
206
|
+
"convention",
|
|
207
|
+
"rule",
|
|
208
|
+
"standard",
|
|
209
|
+
"process",
|
|
210
|
+
"checklist"
|
|
211
|
+
]);
|
|
212
|
+
function containsSensitive(text) {
|
|
213
|
+
return SENSITIVE_PATTERNS.some((p) => p.test(text));
|
|
214
|
+
}
|
|
215
|
+
function hasRuleLikeTags(tags) {
|
|
216
|
+
return tags.some((t) => RULE_LIKE_TAGS.has(t.toLowerCase()));
|
|
217
|
+
}
|
|
218
|
+
function resolveVisibility(input) {
|
|
219
|
+
if (containsSensitive(input.text)) {
|
|
220
|
+
return { visibility: "private" };
|
|
221
|
+
}
|
|
222
|
+
if (input.memoryType === "episodic" && !input.sourceRefs) {
|
|
223
|
+
return { visibility: "private" };
|
|
224
|
+
}
|
|
225
|
+
const hasSource = input.sourceRefs && Object.keys(input.sourceRefs).length > 0;
|
|
226
|
+
const tags = input.tags ?? [];
|
|
227
|
+
if ((input.memoryType === "semantic" || input.memoryType === "procedural") && (hasSource || hasRuleLikeTags(tags))) {
|
|
228
|
+
return { visibility: "repo" };
|
|
229
|
+
}
|
|
230
|
+
return { visibility: "private", suggestion: "promote" };
|
|
231
|
+
}
|
|
232
|
+
var EMBEDDING_MODEL = "text-embedding-3-small";
|
|
233
|
+
var cachedClient = null;
|
|
234
|
+
function isOpenAIConfigured(apiKey) {
|
|
235
|
+
const key = apiKey ?? process.env.OPENAI_API_KEY;
|
|
236
|
+
return !!key && key !== "sk-your-api-key-here" && key.startsWith("sk-");
|
|
237
|
+
}
|
|
238
|
+
function getClient(apiKey) {
|
|
239
|
+
const key = apiKey ?? process.env.OPENAI_API_KEY;
|
|
240
|
+
if (!key) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
"OpenAI API key not configured. Set OPENAI_API_KEY environment variable or pass apiKey option. Semantic search features are disabled. Unforgit will use FTS-only search."
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (!cachedClient || apiKey) {
|
|
246
|
+
cachedClient = new OpenAI({ apiKey: key });
|
|
247
|
+
}
|
|
248
|
+
return cachedClient;
|
|
249
|
+
}
|
|
250
|
+
async function generateEmbedding(text, config2) {
|
|
251
|
+
const client = getClient(config2?.apiKey);
|
|
252
|
+
const model = config2?.model ?? EMBEDDING_MODEL;
|
|
253
|
+
const cleanText = text.trim().slice(0, 8e3);
|
|
254
|
+
const response = await client.embeddings.create({
|
|
255
|
+
model,
|
|
256
|
+
input: cleanText
|
|
257
|
+
});
|
|
258
|
+
const data = response.data[0];
|
|
259
|
+
if (!data?.embedding) {
|
|
260
|
+
throw new Error("OpenAI returned empty embedding");
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
embedding: data.embedding,
|
|
264
|
+
model,
|
|
265
|
+
tokensUsed: response.usage?.total_tokens ?? 0
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function cosineSimilarity(a, b) {
|
|
269
|
+
if (a.length !== b.length) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`Embedding dimensions mismatch: ${a.length} vs ${b.length}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
let dotProduct = 0;
|
|
275
|
+
let normA = 0;
|
|
276
|
+
let normB = 0;
|
|
277
|
+
for (let i = 0; i < a.length; i++) {
|
|
278
|
+
dotProduct += a[i] * b[i];
|
|
279
|
+
normA += a[i] * a[i];
|
|
280
|
+
normB += b[i] * b[i];
|
|
281
|
+
}
|
|
282
|
+
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
|
283
|
+
if (magnitude === 0) return 0;
|
|
284
|
+
return dotProduct / magnitude;
|
|
285
|
+
}
|
|
286
|
+
function serializeEmbedding(embedding) {
|
|
287
|
+
const buffer = Buffer.alloc(embedding.length * 4);
|
|
288
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
289
|
+
buffer.writeFloatLE(embedding[i], i * 4);
|
|
290
|
+
}
|
|
291
|
+
return buffer;
|
|
292
|
+
}
|
|
293
|
+
function deserializeEmbedding(buffer) {
|
|
294
|
+
const embedding = [];
|
|
295
|
+
const count = buffer.length / 4;
|
|
296
|
+
for (let i = 0; i < count; i++) {
|
|
297
|
+
embedding.push(buffer.readFloatLE(i * 4));
|
|
298
|
+
}
|
|
299
|
+
return embedding;
|
|
300
|
+
}
|
|
301
|
+
var CONSOLIDATION_PROMPT = `You are consolidating multiple related memories into a single unified memory.
|
|
302
|
+
|
|
303
|
+
Source memories:
|
|
304
|
+
{{MEMORIES}}
|
|
305
|
+
|
|
306
|
+
Instructions:
|
|
307
|
+
1. Identify the core knowledge/insight shared across these memories
|
|
308
|
+
2. Merge complementary information without losing important details
|
|
309
|
+
3. Remove redundancy while preserving unique facts
|
|
310
|
+
4. Keep the consolidated text concise (ideally under 200 words)
|
|
311
|
+
5. Maintain technical accuracy
|
|
312
|
+
6. Write in the same language as the source memories
|
|
313
|
+
|
|
314
|
+
Output format (JSON):
|
|
315
|
+
{
|
|
316
|
+
"text": "The consolidated memory text",
|
|
317
|
+
"suggestedTags": ["tag1", "tag2"],
|
|
318
|
+
"suggestedType": "semantic" | "procedural" | "episodic"
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
Output only valid JSON, nothing else.`;
|
|
322
|
+
function formatMemoriesForPrompt(memories) {
|
|
323
|
+
return memories.map(
|
|
324
|
+
(m, i) => `${i + 1}. [${m.type}] ${m.text}
|
|
325
|
+
Tags: ${m.tags.length > 0 ? m.tags.join(", ") : "none"}`
|
|
326
|
+
).join("\n\n");
|
|
327
|
+
}
|
|
328
|
+
function inferMemoryType(memories) {
|
|
329
|
+
const hasProcedural = memories.some((m) => m.type === "procedural");
|
|
330
|
+
const hasSemantic = memories.some((m) => m.type === "semantic");
|
|
331
|
+
if (hasProcedural) return "procedural";
|
|
332
|
+
if (hasSemantic) return "semantic";
|
|
333
|
+
return "episodic";
|
|
334
|
+
}
|
|
335
|
+
function mergeTags(memories) {
|
|
336
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
337
|
+
for (const m of memories) {
|
|
338
|
+
for (const tag of m.tags) {
|
|
339
|
+
tagSet.add(tag);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return Array.from(tagSet);
|
|
343
|
+
}
|
|
344
|
+
async function generateConsolidatedText(input, options) {
|
|
345
|
+
const apiKey = options?.apiKey ?? process.env.OPENAI_API_KEY;
|
|
346
|
+
if (!apiKey) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
"OpenAI API key not configured. Set OPENAI_API_KEY environment variable."
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const client = new OpenAI2({ apiKey });
|
|
352
|
+
const model = options?.model ?? "gpt-5.4";
|
|
353
|
+
const memoriesText = formatMemoriesForPrompt(input.memories);
|
|
354
|
+
const prompt = CONSOLIDATION_PROMPT.replace("{{MEMORIES}}", memoriesText);
|
|
355
|
+
const response = await client.chat.completions.create({
|
|
356
|
+
model,
|
|
357
|
+
messages: [
|
|
358
|
+
{
|
|
359
|
+
role: "user",
|
|
360
|
+
content: prompt
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
temperature: 0.3,
|
|
364
|
+
max_completion_tokens: 1e3
|
|
365
|
+
});
|
|
366
|
+
const content = response.choices[0]?.message?.content;
|
|
367
|
+
if (!content) {
|
|
368
|
+
throw new Error("OpenAI returned empty response");
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const parsed = JSON.parse(content);
|
|
372
|
+
if (!parsed.text || typeof parsed.text !== "string") {
|
|
373
|
+
throw new Error("Invalid response: missing text field");
|
|
374
|
+
}
|
|
375
|
+
const validTypes = ["episodic", "semantic", "procedural"];
|
|
376
|
+
const suggestedType = parsed.suggestedType && validTypes.includes(parsed.suggestedType) ? parsed.suggestedType : inferMemoryType(input.memories);
|
|
377
|
+
return {
|
|
378
|
+
text: parsed.text,
|
|
379
|
+
suggestedTags: Array.isArray(parsed.suggestedTags) ? parsed.suggestedTags.filter((t) => typeof t === "string") : mergeTags(input.memories),
|
|
380
|
+
suggestedType
|
|
381
|
+
};
|
|
382
|
+
} catch (_parseError) {
|
|
383
|
+
const textMatch = content.match(/"text"\s*:\s*"([^"]+)"/);
|
|
384
|
+
if (textMatch) {
|
|
385
|
+
return {
|
|
386
|
+
text: textMatch[1],
|
|
387
|
+
suggestedTags: mergeTags(input.memories),
|
|
388
|
+
suggestedType: inferMemoryType(input.memories)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
text: content.trim(),
|
|
393
|
+
suggestedTags: mergeTags(input.memories),
|
|
394
|
+
suggestedType: inferMemoryType(input.memories)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function memoriesToConsolidationInput(memories) {
|
|
399
|
+
return {
|
|
400
|
+
memories: memories.map((m) => ({
|
|
401
|
+
text: m.text,
|
|
402
|
+
type: m.memoryType,
|
|
403
|
+
tags: m.tags
|
|
404
|
+
}))
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
var QUALITY_WEIGHTS = {
|
|
408
|
+
textQuality: 0.2,
|
|
409
|
+
recallCount: 0.25,
|
|
410
|
+
consolidationStatus: 0.1,
|
|
411
|
+
age: 0.15,
|
|
412
|
+
hasLinks: 0.1,
|
|
413
|
+
hasTags: 0.1,
|
|
414
|
+
hasEmbedding: 0.1
|
|
415
|
+
};
|
|
416
|
+
function computeTextQuality(text) {
|
|
417
|
+
const length = text.trim().length;
|
|
418
|
+
if (length < 20) return 0.2;
|
|
419
|
+
if (length < 50) return 0.4;
|
|
420
|
+
if (length < 100) return 0.6;
|
|
421
|
+
if (length < 300) return 0.9;
|
|
422
|
+
if (length < 500) return 1;
|
|
423
|
+
if (length < 1e3) return 0.9;
|
|
424
|
+
return 0.7;
|
|
425
|
+
}
|
|
426
|
+
function computeRecallScore(recallCount) {
|
|
427
|
+
if (recallCount === 0) return 0;
|
|
428
|
+
if (recallCount < 3) return 0.3;
|
|
429
|
+
if (recallCount < 10) return 0.6;
|
|
430
|
+
if (recallCount < 25) return 0.8;
|
|
431
|
+
return 1;
|
|
432
|
+
}
|
|
433
|
+
function computeAgeScore(daysSinceCreation, daysSinceLastRecall) {
|
|
434
|
+
if (daysSinceLastRecall !== null && daysSinceLastRecall < 7) {
|
|
435
|
+
return 1;
|
|
436
|
+
}
|
|
437
|
+
if (daysSinceCreation < 7) return 1;
|
|
438
|
+
if (daysSinceCreation < 30) return 0.9;
|
|
439
|
+
if (daysSinceCreation < 90) return 0.7;
|
|
440
|
+
if (daysSinceCreation < 180) return 0.5;
|
|
441
|
+
if (daysSinceCreation < 365) return 0.3;
|
|
442
|
+
return 0.1;
|
|
443
|
+
}
|
|
444
|
+
function computeConsolidationScore(memory) {
|
|
445
|
+
if (memory.isConsolidation) return 1;
|
|
446
|
+
if (memory.status === "superseded") return 0.3;
|
|
447
|
+
if (memory.status === "deprecated") return 0.1;
|
|
448
|
+
return 0.7;
|
|
449
|
+
}
|
|
450
|
+
function computeQualityScore(memory, stats) {
|
|
451
|
+
const factors = {
|
|
452
|
+
textQuality: computeTextQuality(memory.text),
|
|
453
|
+
recallCount: computeRecallScore(stats.recallCount),
|
|
454
|
+
consolidationStatus: computeConsolidationScore(memory),
|
|
455
|
+
age: computeAgeScore(stats.daysSinceCreation, stats.daysSinceLastRecall),
|
|
456
|
+
hasLinks: stats.linkCount > 0 ? 1 : 0.3,
|
|
457
|
+
hasTags: memory.tags.length > 0 ? 1 : 0.3,
|
|
458
|
+
hasEmbedding: stats.hasEmbedding ? 1 : 0.5
|
|
459
|
+
};
|
|
460
|
+
const overall = factors.textQuality * QUALITY_WEIGHTS.textQuality + factors.recallCount * QUALITY_WEIGHTS.recallCount + factors.consolidationStatus * QUALITY_WEIGHTS.consolidationStatus + factors.age * QUALITY_WEIGHTS.age + factors.hasLinks * QUALITY_WEIGHTS.hasLinks + factors.hasTags * QUALITY_WEIGHTS.hasTags + factors.hasEmbedding * QUALITY_WEIGHTS.hasEmbedding;
|
|
461
|
+
const suggestions = [];
|
|
462
|
+
if (factors.textQuality < 0.5) {
|
|
463
|
+
suggestions.push("Consider expanding this memory with more detail");
|
|
464
|
+
}
|
|
465
|
+
if (factors.recallCount < 0.3 && stats.daysSinceCreation > 30) {
|
|
466
|
+
suggestions.push("This memory has never been recalled - consider deprecating if no longer relevant");
|
|
467
|
+
}
|
|
468
|
+
if (factors.hasLinks < 0.5) {
|
|
469
|
+
suggestions.push("Consider linking this memory to related memories");
|
|
470
|
+
}
|
|
471
|
+
if (factors.hasTags < 0.5) {
|
|
472
|
+
suggestions.push("Add tags to improve discoverability");
|
|
473
|
+
}
|
|
474
|
+
if (!stats.hasEmbedding) {
|
|
475
|
+
suggestions.push("Generate embedding for better semantic search");
|
|
476
|
+
}
|
|
477
|
+
if (stats.daysSinceCreation > 180 && stats.daysSinceLastRecall === null) {
|
|
478
|
+
suggestions.push("Old memory with no recalls - review for deprecation");
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
overall: Math.round(overall * 100) / 100,
|
|
482
|
+
factors,
|
|
483
|
+
suggestions
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function getHealthStatus(score) {
|
|
487
|
+
if (score >= 0.7) return "healthy";
|
|
488
|
+
if (score >= 0.4) return "needs_attention";
|
|
489
|
+
return "critical";
|
|
490
|
+
}
|
|
491
|
+
function computeRepositoryHealth(memories) {
|
|
492
|
+
if (memories.length === 0) {
|
|
493
|
+
return {
|
|
494
|
+
overallScore: 1,
|
|
495
|
+
status: "healthy",
|
|
496
|
+
memoryCounts: { total: 0, healthy: 0, needs_attention: 0, critical: 0 },
|
|
497
|
+
topIssues: []
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const scores = memories.map(
|
|
501
|
+
({ memory, stats }) => computeQualityScore(memory, stats)
|
|
502
|
+
);
|
|
503
|
+
const totalScore = scores.reduce((sum, s) => sum + s.overall, 0);
|
|
504
|
+
const overallScore = Math.round(totalScore / scores.length * 100) / 100;
|
|
505
|
+
const counts = { healthy: 0, needs_attention: 0, critical: 0 };
|
|
506
|
+
for (const score of scores) {
|
|
507
|
+
const status = getHealthStatus(score.overall);
|
|
508
|
+
counts[status]++;
|
|
509
|
+
}
|
|
510
|
+
const issueCount = {};
|
|
511
|
+
for (const score of scores) {
|
|
512
|
+
for (const suggestion of score.suggestions) {
|
|
513
|
+
issueCount[suggestion] = (issueCount[suggestion] || 0) + 1;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const topIssues = Object.entries(issueCount).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([description, count]) => ({
|
|
517
|
+
type: categorizeIssue(description),
|
|
518
|
+
count,
|
|
519
|
+
description
|
|
520
|
+
}));
|
|
521
|
+
return {
|
|
522
|
+
overallScore,
|
|
523
|
+
status: getHealthStatus(overallScore),
|
|
524
|
+
memoryCounts: {
|
|
525
|
+
total: memories.length,
|
|
526
|
+
...counts
|
|
527
|
+
},
|
|
528
|
+
topIssues
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function categorizeIssue(description) {
|
|
532
|
+
if (description.includes("tag")) return "tagging";
|
|
533
|
+
if (description.includes("link")) return "linking";
|
|
534
|
+
if (description.includes("embedding")) return "embedding";
|
|
535
|
+
if (description.includes("deprecat") || description.includes("recall")) return "maintenance";
|
|
536
|
+
if (description.includes("detail") || description.includes("expand")) return "content";
|
|
537
|
+
return "other";
|
|
538
|
+
}
|
|
539
|
+
function generateSuggestions(store, orgId, repoId, options) {
|
|
540
|
+
const maxSuggestions = options?.maxSuggestions ?? 20;
|
|
541
|
+
const suggestions = [];
|
|
542
|
+
const memories = store.list({
|
|
543
|
+
orgId,
|
|
544
|
+
repoId,
|
|
545
|
+
status: ["active"],
|
|
546
|
+
limit: 500
|
|
547
|
+
});
|
|
548
|
+
const usageStats = store.getUsageStats(orgId, repoId);
|
|
549
|
+
const usageMap = new Map(usageStats.map((s) => [s.memoryId, s]));
|
|
550
|
+
const memoryData = [];
|
|
551
|
+
for (const memory of memories) {
|
|
552
|
+
const usage = usageMap.get(memory.id);
|
|
553
|
+
const links = store.getLinks({ memoryId: memory.id });
|
|
554
|
+
const hasEmbedding = store.hasEmbedding(memory.id);
|
|
555
|
+
const stats = {
|
|
556
|
+
recallCount: usage?.count ?? 0,
|
|
557
|
+
linkCount: links.length,
|
|
558
|
+
hasEmbedding,
|
|
559
|
+
daysSinceCreation: Math.floor(
|
|
560
|
+
(Date.now() - memory.createdAt.getTime()) / (1e3 * 60 * 60 * 24)
|
|
561
|
+
),
|
|
562
|
+
daysSinceLastRecall: usage ? Math.floor(
|
|
563
|
+
(Date.now() - usage.lastUsed.getTime()) / (1e3 * 60 * 60 * 24)
|
|
564
|
+
) : null
|
|
565
|
+
};
|
|
566
|
+
const quality = computeQualityScore(memory, stats);
|
|
567
|
+
memoryData.push({ memory, stats, quality });
|
|
568
|
+
}
|
|
569
|
+
const similarPairs = findSimilarMemoryPairs(store, memoryData, orgId, repoId);
|
|
570
|
+
for (const pair of similarPairs.slice(0, 5)) {
|
|
571
|
+
suggestions.push({
|
|
572
|
+
id: `consolidate-${pair.id1.slice(0, 8)}-${pair.id2.slice(0, 8)}`,
|
|
573
|
+
type: "consolidate",
|
|
574
|
+
priority: pair.similarity > 0.8 ? "high" : "medium",
|
|
575
|
+
memoryIds: [pair.id1, pair.id2],
|
|
576
|
+
reason: `These memories are ${Math.round(pair.similarity * 100)}% similar and could be merged`,
|
|
577
|
+
confidence: pair.similarity,
|
|
578
|
+
action: {
|
|
579
|
+
command: `unforgit merge ${pair.id1} ${pair.id2}`,
|
|
580
|
+
description: "Merge these memories into one"
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
const staleMemories = memoryData.filter(
|
|
585
|
+
({ stats, quality }) => stats.daysSinceCreation > 90 && stats.recallCount === 0 && quality.overall < 0.5
|
|
586
|
+
);
|
|
587
|
+
for (const { memory } of staleMemories.slice(0, 5)) {
|
|
588
|
+
suggestions.push({
|
|
589
|
+
id: `deprecate-${memory.id.slice(0, 8)}`,
|
|
590
|
+
type: "deprecate",
|
|
591
|
+
priority: "medium",
|
|
592
|
+
memoryIds: [memory.id],
|
|
593
|
+
reason: "No recalls in 90+ days with low quality score",
|
|
594
|
+
confidence: 0.7,
|
|
595
|
+
action: {
|
|
596
|
+
command: `unforgit deprecate ${memory.id}`,
|
|
597
|
+
description: "Mark as deprecated"
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
const untagged = memoryData.filter(({ memory }) => memory.tags.length === 0);
|
|
602
|
+
if (untagged.length > 0) {
|
|
603
|
+
const ids = untagged.slice(0, 10).map(({ memory }) => memory.id);
|
|
604
|
+
suggestions.push({
|
|
605
|
+
id: "add-tags-batch",
|
|
606
|
+
type: "add_tags",
|
|
607
|
+
priority: "low",
|
|
608
|
+
memoryIds: ids,
|
|
609
|
+
reason: `${untagged.length} memories have no tags`,
|
|
610
|
+
confidence: 0.9,
|
|
611
|
+
action: {
|
|
612
|
+
command: "unforgit web",
|
|
613
|
+
description: "Open dashboard to add tags"
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
const unlinked = memoryData.filter(
|
|
618
|
+
({ stats, memory }) => stats.linkCount === 0 && !memory.isConsolidation && stats.daysSinceCreation > 7
|
|
619
|
+
);
|
|
620
|
+
if (unlinked.length > 5) {
|
|
621
|
+
suggestions.push({
|
|
622
|
+
id: "add-links-batch",
|
|
623
|
+
type: "add_links",
|
|
624
|
+
priority: "low",
|
|
625
|
+
memoryIds: unlinked.slice(0, 10).map(({ memory }) => memory.id),
|
|
626
|
+
reason: `${unlinked.length} memories are isolated (no links)`,
|
|
627
|
+
confidence: 0.8,
|
|
628
|
+
action: {
|
|
629
|
+
command: "unforgit web",
|
|
630
|
+
description: "Open graph view to create links"
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
const withoutEmbedding = memoryData.filter(({ stats }) => !stats.hasEmbedding);
|
|
635
|
+
if (withoutEmbedding.length > 0) {
|
|
636
|
+
suggestions.push({
|
|
637
|
+
id: "generate-embeddings",
|
|
638
|
+
type: "generate_embedding",
|
|
639
|
+
priority: withoutEmbedding.length > 10 ? "high" : "medium",
|
|
640
|
+
memoryIds: withoutEmbedding.map(({ memory }) => memory.id),
|
|
641
|
+
reason: `${withoutEmbedding.length} memories lack embeddings for semantic search`,
|
|
642
|
+
confidence: 1,
|
|
643
|
+
action: {
|
|
644
|
+
command: "unforgit embeddings backfill",
|
|
645
|
+
description: "Generate embeddings for all memories"
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const popularPrivate = memoryData.filter(
|
|
650
|
+
({ memory, stats }) => memory.visibility === "private" && stats.recallCount >= 5
|
|
651
|
+
);
|
|
652
|
+
for (const { memory, stats } of popularPrivate.slice(0, 3)) {
|
|
653
|
+
suggestions.push({
|
|
654
|
+
id: `promote-${memory.id.slice(0, 8)}`,
|
|
655
|
+
type: "promote",
|
|
656
|
+
priority: "medium",
|
|
657
|
+
memoryIds: [memory.id],
|
|
658
|
+
reason: `Private memory with ${stats.recallCount} recalls - consider sharing with team`,
|
|
659
|
+
confidence: 0.75,
|
|
660
|
+
action: {
|
|
661
|
+
command: `unforgit promote ${memory.id}`,
|
|
662
|
+
description: "Promote to shared visibility"
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
const sorted = suggestions.sort((a, b) => {
|
|
667
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
668
|
+
if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
|
|
669
|
+
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
670
|
+
}
|
|
671
|
+
return b.confidence - a.confidence;
|
|
672
|
+
}).slice(0, maxSuggestions);
|
|
673
|
+
return {
|
|
674
|
+
suggestions: sorted,
|
|
675
|
+
stats: {
|
|
676
|
+
totalMemories: memories.length,
|
|
677
|
+
memoriesAnalyzed: memoryData.length,
|
|
678
|
+
suggestionsGenerated: sorted.length
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function findSimilarMemoryPairs(store, memoryData, orgId, repoId) {
|
|
683
|
+
const pairs = [];
|
|
684
|
+
const seen = /* @__PURE__ */ new Set();
|
|
685
|
+
for (const { memory } of memoryData.slice(0, 50)) {
|
|
686
|
+
try {
|
|
687
|
+
const similar = store.findSimilar({
|
|
688
|
+
orgId,
|
|
689
|
+
repoId,
|
|
690
|
+
memoryId: memory.id,
|
|
691
|
+
threshold: 0.6,
|
|
692
|
+
k: 3
|
|
693
|
+
});
|
|
694
|
+
for (const match of similar) {
|
|
695
|
+
const pairKey = [memory.id, match.id].sort().join("-");
|
|
696
|
+
if (seen.has(pairKey)) continue;
|
|
697
|
+
seen.add(pairKey);
|
|
698
|
+
pairs.push({
|
|
699
|
+
id1: memory.id,
|
|
700
|
+
id2: match.id,
|
|
701
|
+
similarity: match.score
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
} catch {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return pairs.sort((a, b) => b.similarity - a.similarity);
|
|
709
|
+
}
|
|
710
|
+
function persistReviewableSuggestions(store, orgId, repoId, suggestions, options = {}) {
|
|
711
|
+
const existingPending = store.listCurationSuggestions({
|
|
712
|
+
orgId,
|
|
713
|
+
repoId,
|
|
714
|
+
status: ["pending"],
|
|
715
|
+
limit: 500
|
|
716
|
+
});
|
|
717
|
+
const existingKeys = new Set(
|
|
718
|
+
existingPending.map(
|
|
719
|
+
(suggestion) => reviewableSuggestionKey(suggestion.type, suggestion.memoryIds)
|
|
720
|
+
)
|
|
721
|
+
);
|
|
722
|
+
let created = 0;
|
|
723
|
+
let skippedExisting = 0;
|
|
724
|
+
for (const suggestion of suggestions) {
|
|
725
|
+
const key = reviewableSuggestionKey(suggestion.type, suggestion.memoryIds);
|
|
726
|
+
if (existingKeys.has(key)) {
|
|
727
|
+
skippedExisting++;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
store.createCurationSuggestion({
|
|
731
|
+
orgId,
|
|
732
|
+
repoId,
|
|
733
|
+
type: suggestion.type,
|
|
734
|
+
priority: suggestion.priority,
|
|
735
|
+
memoryIds: suggestion.memoryIds,
|
|
736
|
+
reason: suggestion.reason,
|
|
737
|
+
confidence: suggestion.confidence,
|
|
738
|
+
createdBy: options.createdBy,
|
|
739
|
+
payload: {
|
|
740
|
+
sourceSuggestionId: suggestion.id,
|
|
741
|
+
...suggestion.action ? { action: suggestion.action } : {}
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
existingKeys.add(key);
|
|
745
|
+
created++;
|
|
746
|
+
}
|
|
747
|
+
return { created, skippedExisting };
|
|
748
|
+
}
|
|
749
|
+
function reviewableSuggestionKey(type, memoryIds) {
|
|
750
|
+
return `${type}:${[...memoryIds].sort().join(",")}`;
|
|
751
|
+
}
|
|
752
|
+
function formatSuggestion(suggestion) {
|
|
753
|
+
const priorityEmoji = {
|
|
754
|
+
high: "\u{1F534}",
|
|
755
|
+
medium: "\u{1F7E1}",
|
|
756
|
+
low: "\u{1F7E2}"
|
|
757
|
+
};
|
|
758
|
+
const lines = [
|
|
759
|
+
`${priorityEmoji[suggestion.priority]} [${suggestion.type}] ${suggestion.reason}`,
|
|
760
|
+
` Confidence: ${Math.round(suggestion.confidence * 100)}%`,
|
|
761
|
+
` Memories: ${suggestion.memoryIds.map((id) => id.slice(0, 8)).join(", ")}`
|
|
762
|
+
];
|
|
763
|
+
if (suggestion.action) {
|
|
764
|
+
lines.push(` Action: ${suggestion.action.command}`);
|
|
765
|
+
}
|
|
766
|
+
return lines.join("\n");
|
|
767
|
+
}
|
|
768
|
+
function findConsolidationCandidates(store, orgId, repoId, options = {}) {
|
|
769
|
+
const {
|
|
770
|
+
threshold = 0.4,
|
|
771
|
+
minGroupSize = 2,
|
|
772
|
+
maxGroups = 10,
|
|
773
|
+
types,
|
|
774
|
+
excludeConsolidations = true
|
|
775
|
+
} = options;
|
|
776
|
+
const memories = store.list({
|
|
777
|
+
orgId,
|
|
778
|
+
repoId,
|
|
779
|
+
status: ["active"],
|
|
780
|
+
types,
|
|
781
|
+
limit: 1e3
|
|
782
|
+
});
|
|
783
|
+
const filteredMemories = excludeConsolidations ? memories.filter((m) => !m.isConsolidation) : memories;
|
|
784
|
+
if (filteredMemories.length < 2) {
|
|
785
|
+
return {
|
|
786
|
+
candidates: [],
|
|
787
|
+
totalMemoriesScanned: filteredMemories.length,
|
|
788
|
+
totalCandidateGroups: 0
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const similarityScores = /* @__PURE__ */ new Map();
|
|
792
|
+
const memoryMap = new Map(filteredMemories.map((m) => [m.id, m]));
|
|
793
|
+
for (const memory of filteredMemories) {
|
|
794
|
+
try {
|
|
795
|
+
const similar = store.findSimilar({
|
|
796
|
+
orgId,
|
|
797
|
+
repoId,
|
|
798
|
+
memoryId: memory.id,
|
|
799
|
+
threshold,
|
|
800
|
+
k: 10
|
|
801
|
+
});
|
|
802
|
+
for (const sim of similar) {
|
|
803
|
+
const targetMemory = memoryMap.get(sim.id);
|
|
804
|
+
if (!targetMemory) continue;
|
|
805
|
+
if (excludeConsolidations && targetMemory.isConsolidation) continue;
|
|
806
|
+
if (!similarityScores.has(memory.id)) {
|
|
807
|
+
similarityScores.set(memory.id, /* @__PURE__ */ new Map());
|
|
808
|
+
}
|
|
809
|
+
similarityScores.get(memory.id).set(sim.id, sim.score);
|
|
810
|
+
}
|
|
811
|
+
} catch {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const used = /* @__PURE__ */ new Set();
|
|
816
|
+
const groups = [];
|
|
817
|
+
const sortedMemories = [...filteredMemories].sort((a, b) => {
|
|
818
|
+
const aCount = similarityScores.get(a.id)?.size ?? 0;
|
|
819
|
+
const bCount = similarityScores.get(b.id)?.size ?? 0;
|
|
820
|
+
return bCount - aCount;
|
|
821
|
+
});
|
|
822
|
+
for (const seed of sortedMemories) {
|
|
823
|
+
if (used.has(seed.id)) continue;
|
|
824
|
+
const seedSimilar = similarityScores.get(seed.id);
|
|
825
|
+
if (!seedSimilar || seedSimilar.size === 0) continue;
|
|
826
|
+
const group = [seed.id];
|
|
827
|
+
let totalScore = 0;
|
|
828
|
+
let scoreCount = 0;
|
|
829
|
+
const candidates2 = Array.from(seedSimilar.entries()).filter(([id]) => !used.has(id)).sort((a, b) => b[1] - a[1]);
|
|
830
|
+
for (const [candidateId, score] of candidates2) {
|
|
831
|
+
if (group.length >= 5) break;
|
|
832
|
+
let isCompatible = true;
|
|
833
|
+
for (const memberId of group) {
|
|
834
|
+
if (memberId === seed.id) continue;
|
|
835
|
+
const memberSimilar = similarityScores.get(memberId);
|
|
836
|
+
const reverseScore = memberSimilar?.get(candidateId);
|
|
837
|
+
const candidateSimilar = similarityScores.get(candidateId);
|
|
838
|
+
const forwardScore = candidateSimilar?.get(memberId);
|
|
839
|
+
const pairScore = Math.max(reverseScore ?? 0, forwardScore ?? 0);
|
|
840
|
+
if (pairScore < threshold * 0.8) {
|
|
841
|
+
isCompatible = false;
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (isCompatible) {
|
|
846
|
+
group.push(candidateId);
|
|
847
|
+
totalScore += score;
|
|
848
|
+
scoreCount++;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (group.length >= minGroupSize) {
|
|
852
|
+
for (const id of group) {
|
|
853
|
+
used.add(id);
|
|
854
|
+
}
|
|
855
|
+
groups.push({
|
|
856
|
+
ids: group,
|
|
857
|
+
avgScore: scoreCount > 0 ? totalScore / scoreCount : threshold
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const candidates = [];
|
|
862
|
+
for (const group of groups) {
|
|
863
|
+
const groupMemories = group.ids.map((id) => memoryMap.get(id)).filter((m) => m !== void 0);
|
|
864
|
+
if (groupMemories.length < minGroupSize) continue;
|
|
865
|
+
const allTags = /* @__PURE__ */ new Set();
|
|
866
|
+
for (const m of groupMemories) {
|
|
867
|
+
for (const tag of m.tags) {
|
|
868
|
+
allTags.add(tag);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const typeCount = {};
|
|
872
|
+
for (const m of groupMemories) {
|
|
873
|
+
typeCount[m.memoryType] = (typeCount[m.memoryType] || 0) + 1;
|
|
874
|
+
}
|
|
875
|
+
const dominantType = Object.entries(typeCount).sort(
|
|
876
|
+
(a, b) => b[1] - a[1]
|
|
877
|
+
)[0]?.[0];
|
|
878
|
+
candidates.push({
|
|
879
|
+
memories: groupMemories.sort(
|
|
880
|
+
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
|
|
881
|
+
),
|
|
882
|
+
reason: `${groupMemories.length} similar ${dominantType ?? "mixed"} memories with avg similarity ${group.avgScore.toFixed(2)}`,
|
|
883
|
+
suggestedTags: Array.from(allTags),
|
|
884
|
+
averageScore: group.avgScore
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
candidates.sort((a, b) => {
|
|
888
|
+
if (b.memories.length !== a.memories.length) {
|
|
889
|
+
return b.memories.length - a.memories.length;
|
|
890
|
+
}
|
|
891
|
+
return b.averageScore - a.averageScore;
|
|
892
|
+
});
|
|
893
|
+
return {
|
|
894
|
+
candidates: candidates.slice(0, maxGroups),
|
|
895
|
+
totalMemoriesScanned: filteredMemories.length,
|
|
896
|
+
totalCandidateGroups: candidates.length
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
async function executeConsolidation(store, candidate, orgId, repoId, options = {}) {
|
|
900
|
+
const { apiKey, model, preserveOriginals = true } = options;
|
|
901
|
+
const input = memoriesToConsolidationInput(candidate.memories);
|
|
902
|
+
const llmResult = await generateConsolidatedText(input, { apiKey, model });
|
|
903
|
+
const sourceIds = candidate.memories.map((m) => m.id);
|
|
904
|
+
const result = store.consolidateMemories({
|
|
905
|
+
orgId,
|
|
906
|
+
repoId,
|
|
907
|
+
sourceIds,
|
|
908
|
+
consolidatedText: llmResult.text,
|
|
909
|
+
memoryType: llmResult.suggestedType,
|
|
910
|
+
tags: llmResult.suggestedTags,
|
|
911
|
+
preserveOriginals
|
|
912
|
+
});
|
|
913
|
+
return {
|
|
914
|
+
consolidatedId: result.consolidatedId,
|
|
915
|
+
sourceIds,
|
|
916
|
+
generatedText: llmResult.text,
|
|
917
|
+
suggestedTags: llmResult.suggestedTags,
|
|
918
|
+
memoryType: llmResult.suggestedType
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
function formatCandidatePreview(candidate) {
|
|
922
|
+
const lines = [];
|
|
923
|
+
lines.push(`Group: ${candidate.reason}`);
|
|
924
|
+
lines.push(`Tags: ${candidate.suggestedTags.join(", ") || "none"}`);
|
|
925
|
+
lines.push("Memories:");
|
|
926
|
+
for (const mem of candidate.memories) {
|
|
927
|
+
const preview2 = mem.text.length > 80 ? mem.text.slice(0, 80) + "..." : mem.text;
|
|
928
|
+
lines.push(` - [${mem.memoryType}] ${mem.id.slice(0, 8)}: ${preview2}`);
|
|
929
|
+
}
|
|
930
|
+
return lines.join("\n");
|
|
931
|
+
}
|
|
932
|
+
var AUTO_LINK_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
933
|
+
"the",
|
|
934
|
+
"a",
|
|
935
|
+
"an",
|
|
936
|
+
"is",
|
|
937
|
+
"are",
|
|
938
|
+
"was",
|
|
939
|
+
"were",
|
|
940
|
+
"be",
|
|
941
|
+
"been",
|
|
942
|
+
"being",
|
|
943
|
+
"have",
|
|
944
|
+
"has",
|
|
945
|
+
"had",
|
|
946
|
+
"do",
|
|
947
|
+
"does",
|
|
948
|
+
"did",
|
|
949
|
+
"will",
|
|
950
|
+
"would",
|
|
951
|
+
"could",
|
|
952
|
+
"should",
|
|
953
|
+
"may",
|
|
954
|
+
"might",
|
|
955
|
+
"must",
|
|
956
|
+
"shall",
|
|
957
|
+
"can",
|
|
958
|
+
"to",
|
|
959
|
+
"of",
|
|
960
|
+
"in",
|
|
961
|
+
"for",
|
|
962
|
+
"on",
|
|
963
|
+
"with",
|
|
964
|
+
"at",
|
|
965
|
+
"by",
|
|
966
|
+
"from",
|
|
967
|
+
"as",
|
|
968
|
+
"into",
|
|
969
|
+
"through",
|
|
970
|
+
"during",
|
|
971
|
+
"before",
|
|
972
|
+
"after",
|
|
973
|
+
"above",
|
|
974
|
+
"below",
|
|
975
|
+
"between",
|
|
976
|
+
"under",
|
|
977
|
+
"again",
|
|
978
|
+
"further",
|
|
979
|
+
"then",
|
|
980
|
+
"once",
|
|
981
|
+
"here",
|
|
982
|
+
"there",
|
|
983
|
+
"when",
|
|
984
|
+
"where",
|
|
985
|
+
"why",
|
|
986
|
+
"how",
|
|
987
|
+
"all",
|
|
988
|
+
"each",
|
|
989
|
+
"few",
|
|
990
|
+
"more",
|
|
991
|
+
"most",
|
|
992
|
+
"other",
|
|
993
|
+
"some",
|
|
994
|
+
"such",
|
|
995
|
+
"no",
|
|
996
|
+
"nor",
|
|
997
|
+
"not",
|
|
998
|
+
"only",
|
|
999
|
+
"own",
|
|
1000
|
+
"same",
|
|
1001
|
+
"so",
|
|
1002
|
+
"than",
|
|
1003
|
+
"too",
|
|
1004
|
+
"very",
|
|
1005
|
+
"just",
|
|
1006
|
+
"and",
|
|
1007
|
+
"but",
|
|
1008
|
+
"if",
|
|
1009
|
+
"or",
|
|
1010
|
+
"because",
|
|
1011
|
+
"until",
|
|
1012
|
+
"while",
|
|
1013
|
+
"this",
|
|
1014
|
+
"that",
|
|
1015
|
+
"these",
|
|
1016
|
+
"those",
|
|
1017
|
+
"it",
|
|
1018
|
+
"its"
|
|
1019
|
+
]);
|
|
1020
|
+
function buildAutoLinkQuery(text, maxTerms = 10) {
|
|
1021
|
+
const terms = text.toLowerCase().replace(/[_-]/g, " ").replace(/[^\w\s]/g, " ").split(/\s+/).filter((word) => word.length > 2).filter((word) => !AUTO_LINK_STOP_WORDS.has(word)).filter((word) => word !== "or" && word !== "and");
|
|
1022
|
+
const uniqueTerms = [...new Set(terms)].slice(0, maxTerms);
|
|
1023
|
+
if (uniqueTerms.length === 0) {
|
|
1024
|
+
return void 0;
|
|
1025
|
+
}
|
|
1026
|
+
return uniqueTerms.join(" ");
|
|
1027
|
+
}
|
|
1028
|
+
function getNotifications(store, orgId, repoId) {
|
|
1029
|
+
const notifications = [];
|
|
1030
|
+
const now = /* @__PURE__ */ new Date();
|
|
1031
|
+
const syncSummary = store.getSyncSummary(orgId, repoId);
|
|
1032
|
+
if (syncSummary.conflicts > 0) {
|
|
1033
|
+
notifications.push({
|
|
1034
|
+
id: "conflicts-pending",
|
|
1035
|
+
type: "conflicts_pending",
|
|
1036
|
+
priority: "high",
|
|
1037
|
+
title: "Sync Conflicts",
|
|
1038
|
+
message: `You have ${syncSummary.conflicts} unresolved sync conflict(s) that need attention.`,
|
|
1039
|
+
action: {
|
|
1040
|
+
command: "unforgit status",
|
|
1041
|
+
description: "View conflicts"
|
|
1042
|
+
},
|
|
1043
|
+
createdAt: now
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
const embeddingStats = store.getEmbeddingStats(orgId, repoId);
|
|
1047
|
+
if (embeddingStats.withoutEmbedding > 10) {
|
|
1048
|
+
notifications.push({
|
|
1049
|
+
id: "embeddings-missing",
|
|
1050
|
+
type: "embeddings_missing",
|
|
1051
|
+
priority: "medium",
|
|
1052
|
+
title: "Missing Embeddings",
|
|
1053
|
+
message: `${embeddingStats.withoutEmbedding} memories lack embeddings. Semantic search quality is reduced.`,
|
|
1054
|
+
action: {
|
|
1055
|
+
command: "unforgit embeddings backfill",
|
|
1056
|
+
description: "Generate missing embeddings"
|
|
1057
|
+
},
|
|
1058
|
+
createdAt: now
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
const suggestions = generateSuggestions(store, orgId, repoId, { maxSuggestions: 10 });
|
|
1062
|
+
const highPrioritySuggestions = suggestions.suggestions.filter(
|
|
1063
|
+
(s) => s.priority === "high"
|
|
1064
|
+
).length;
|
|
1065
|
+
if (highPrioritySuggestions > 0) {
|
|
1066
|
+
notifications.push({
|
|
1067
|
+
id: "suggestions-high-priority",
|
|
1068
|
+
type: "pending_suggestions",
|
|
1069
|
+
priority: "medium",
|
|
1070
|
+
title: "Curation Suggestions",
|
|
1071
|
+
message: `${highPrioritySuggestions} high-priority curation suggestion(s) available.`,
|
|
1072
|
+
action: {
|
|
1073
|
+
command: "unforgit web",
|
|
1074
|
+
description: "Open curation dashboard"
|
|
1075
|
+
},
|
|
1076
|
+
createdAt: now
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
if (syncSummary.pendingPush > 20) {
|
|
1080
|
+
notifications.push({
|
|
1081
|
+
id: "sync-stale",
|
|
1082
|
+
type: "sync_stale",
|
|
1083
|
+
priority: "low",
|
|
1084
|
+
title: "Pending Sync",
|
|
1085
|
+
message: `${syncSummary.pendingPush} memories waiting to be pushed to remote.`,
|
|
1086
|
+
action: {
|
|
1087
|
+
command: "unforgit push",
|
|
1088
|
+
description: "Push changes to remote"
|
|
1089
|
+
},
|
|
1090
|
+
createdAt: now
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
const unusedMemories = store.getUnusedMemories(orgId, repoId, 90);
|
|
1094
|
+
if (unusedMemories.length > 10) {
|
|
1095
|
+
notifications.push({
|
|
1096
|
+
id: "maintenance-unused",
|
|
1097
|
+
type: "maintenance_needed",
|
|
1098
|
+
priority: "low",
|
|
1099
|
+
title: "Maintenance Recommended",
|
|
1100
|
+
message: `${unusedMemories.length} memories haven't been recalled in 90+ days. Consider reviewing or deprecating.`,
|
|
1101
|
+
action: {
|
|
1102
|
+
command: "unforgit web",
|
|
1103
|
+
description: "Open curation dashboard"
|
|
1104
|
+
},
|
|
1105
|
+
createdAt: now
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
notifications.sort((a, b) => {
|
|
1109
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
1110
|
+
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
1111
|
+
});
|
|
1112
|
+
const summary = {
|
|
1113
|
+
total: notifications.length,
|
|
1114
|
+
high: notifications.filter((n) => n.priority === "high").length,
|
|
1115
|
+
medium: notifications.filter((n) => n.priority === "medium").length,
|
|
1116
|
+
low: notifications.filter((n) => n.priority === "low").length
|
|
1117
|
+
};
|
|
1118
|
+
return { notifications, summary };
|
|
1119
|
+
}
|
|
1120
|
+
function formatNotification(notification) {
|
|
1121
|
+
const priorityEmoji = {
|
|
1122
|
+
high: "\u{1F534}",
|
|
1123
|
+
medium: "\u{1F7E1}",
|
|
1124
|
+
low: "\u{1F7E2}"
|
|
1125
|
+
};
|
|
1126
|
+
const lines = [
|
|
1127
|
+
`${priorityEmoji[notification.priority]} ${notification.title}`,
|
|
1128
|
+
` ${notification.message}`
|
|
1129
|
+
];
|
|
1130
|
+
if (notification.action) {
|
|
1131
|
+
lines.push(` \u2192 ${notification.action.command}`);
|
|
1132
|
+
}
|
|
1133
|
+
return lines.join("\n");
|
|
1134
|
+
}
|
|
1135
|
+
function formatNotificationsSummary(result) {
|
|
1136
|
+
if (result.notifications.length === 0) {
|
|
1137
|
+
return "No notifications. Everything is up to date!";
|
|
1138
|
+
}
|
|
1139
|
+
const parts = [
|
|
1140
|
+
`${result.summary.total} notification(s):`,
|
|
1141
|
+
` High: ${result.summary.high}`,
|
|
1142
|
+
` Medium: ${result.summary.medium}`,
|
|
1143
|
+
` Low: ${result.summary.low}`,
|
|
1144
|
+
"",
|
|
1145
|
+
...result.notifications.map(formatNotification)
|
|
1146
|
+
];
|
|
1147
|
+
return parts.join("\n");
|
|
1148
|
+
}
|
|
1149
|
+
function preview(text) {
|
|
1150
|
+
return text.length > 120 ? `${text.slice(0, 120)}...` : text;
|
|
1151
|
+
}
|
|
1152
|
+
function getExpiringCandidates(memories) {
|
|
1153
|
+
return memories.filter(
|
|
1154
|
+
(memory) => memory.memoryType === "episodic" && memory.status === "active" && memory.ttlSeconds !== void 0 && isMemoryExpired(memory)
|
|
1155
|
+
).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).map((memory) => ({
|
|
1156
|
+
id: memory.id,
|
|
1157
|
+
ttlSeconds: memory.ttlSeconds,
|
|
1158
|
+
reason: `Expired after ${memory.ttlSeconds} seconds without consolidation`,
|
|
1159
|
+
textPreview: preview(memory.text)
|
|
1160
|
+
}));
|
|
1161
|
+
}
|
|
1162
|
+
function getStrengthenedCandidates(memories, usageStats, lifecycle) {
|
|
1163
|
+
const config2 = resolveLifecycleConfig(lifecycle);
|
|
1164
|
+
const usageMap = new Map(usageStats.map((stat) => [stat.memoryId, stat]));
|
|
1165
|
+
return memories.filter((memory) => memory.memoryType === "episodic" && memory.status === "active").map((memory) => {
|
|
1166
|
+
const usage = usageMap.get(memory.id);
|
|
1167
|
+
if (!usage || usage.count < config2.maintenance.promoteRecallCount) {
|
|
1168
|
+
return void 0;
|
|
1169
|
+
}
|
|
1170
|
+
const isPinned = memory.tags.includes("pinned");
|
|
1171
|
+
const recommendedAction = usage.count >= config2.maintenance.pinRecallCount && !isPinned ? "pin" : "promote";
|
|
1172
|
+
return {
|
|
1173
|
+
id: memory.id,
|
|
1174
|
+
usageCount: usage.count,
|
|
1175
|
+
lastUsed: usage.lastUsed,
|
|
1176
|
+
recommendedAction,
|
|
1177
|
+
reason: recommendedAction === "pin" ? `Frequently reused episodic memory (${usage.count} recalls); consider pinning it` : `Frequently reused episodic memory (${usage.count} recalls); consider promoting it`,
|
|
1178
|
+
textPreview: preview(memory.text)
|
|
1179
|
+
};
|
|
1180
|
+
}).filter((candidate) => candidate !== void 0).sort((a, b) => b.usageCount - a.usageCount);
|
|
1181
|
+
}
|
|
1182
|
+
async function runLocalLifecycleMaintenance(store, orgId, repoId, options = {}) {
|
|
1183
|
+
const lifecycle = resolveLifecycleConfig(options.lifecycle);
|
|
1184
|
+
const dryRun = options.dryRun ?? lifecycle.maintenance.dryRunDefault;
|
|
1185
|
+
const activeMemories = store.list({
|
|
1186
|
+
orgId,
|
|
1187
|
+
repoId,
|
|
1188
|
+
status: ["active"],
|
|
1189
|
+
includeExpired: true,
|
|
1190
|
+
limit: 1e3
|
|
1191
|
+
});
|
|
1192
|
+
const usageStats = store.getUsageStats(orgId, repoId);
|
|
1193
|
+
const expiredCandidates = getExpiringCandidates(activeMemories);
|
|
1194
|
+
const strengthenedCandidates = getStrengthenedCandidates(
|
|
1195
|
+
activeMemories.filter((memory) => !isMemoryExpired(memory)),
|
|
1196
|
+
usageStats,
|
|
1197
|
+
lifecycle
|
|
1198
|
+
);
|
|
1199
|
+
const consolidationPreview = findConsolidationCandidates(store, orgId, repoId, {
|
|
1200
|
+
threshold: lifecycle.maintenance.consolidationThreshold,
|
|
1201
|
+
minGroupSize: lifecycle.maintenance.consolidationMinGroupSize,
|
|
1202
|
+
maxGroups: lifecycle.maintenance.consolidationMaxGroups,
|
|
1203
|
+
types: ["episodic"],
|
|
1204
|
+
excludeConsolidations: true
|
|
1205
|
+
});
|
|
1206
|
+
const warnings = [];
|
|
1207
|
+
const errors = [];
|
|
1208
|
+
const executedConsolidations = [];
|
|
1209
|
+
if (!dryRun) {
|
|
1210
|
+
store.expireExpiredMemories(orgId, repoId);
|
|
1211
|
+
if (consolidationPreview.candidates.length > 0) {
|
|
1212
|
+
if (!isOpenAIConfigured()) {
|
|
1213
|
+
warnings.push(
|
|
1214
|
+
"Skipping consolidation execution because OpenAI is not configured."
|
|
1215
|
+
);
|
|
1216
|
+
} else {
|
|
1217
|
+
for (const candidate of consolidationPreview.candidates) {
|
|
1218
|
+
try {
|
|
1219
|
+
const result = await executeConsolidation(store, candidate, orgId, repoId, {
|
|
1220
|
+
model: options.model,
|
|
1221
|
+
preserveOriginals: options.preserveOriginals
|
|
1222
|
+
});
|
|
1223
|
+
executedConsolidations.push(result);
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
errors.push(
|
|
1226
|
+
error instanceof Error ? error.message : String(error)
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return {
|
|
1234
|
+
dryRun,
|
|
1235
|
+
totalActiveMemories: activeMemories.length,
|
|
1236
|
+
expiredCandidates,
|
|
1237
|
+
expiredCount: dryRun ? expiredCandidates.length : expiredCandidates.length,
|
|
1238
|
+
strengthenedCandidates,
|
|
1239
|
+
consolidationCandidates: consolidationPreview.candidates,
|
|
1240
|
+
executedConsolidations,
|
|
1241
|
+
warnings,
|
|
1242
|
+
errors
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
var MEMORY_TEMPLATES = {
|
|
1246
|
+
decision: {
|
|
1247
|
+
name: "Decision",
|
|
1248
|
+
description: "Technical or architectural decision",
|
|
1249
|
+
memoryType: "semantic",
|
|
1250
|
+
defaultTags: ["decision"],
|
|
1251
|
+
prefix: "Decision:",
|
|
1252
|
+
visibility: "repo"
|
|
1253
|
+
},
|
|
1254
|
+
adr: {
|
|
1255
|
+
name: "ADR",
|
|
1256
|
+
description: "Architecture Decision Record",
|
|
1257
|
+
memoryType: "semantic",
|
|
1258
|
+
defaultTags: ["adr", "architecture", "decision"],
|
|
1259
|
+
prefix: "ADR:",
|
|
1260
|
+
visibility: "repo"
|
|
1261
|
+
},
|
|
1262
|
+
gotcha: {
|
|
1263
|
+
name: "Gotcha",
|
|
1264
|
+
description: "Non-obvious issue or caveat discovered",
|
|
1265
|
+
memoryType: "episodic",
|
|
1266
|
+
defaultTags: ["gotcha", "warning"],
|
|
1267
|
+
prefix: "Gotcha:",
|
|
1268
|
+
visibility: "repo"
|
|
1269
|
+
},
|
|
1270
|
+
bug: {
|
|
1271
|
+
name: "Bug",
|
|
1272
|
+
description: "Bug found and fixed",
|
|
1273
|
+
memoryType: "episodic",
|
|
1274
|
+
defaultTags: ["bug", "fix"],
|
|
1275
|
+
prefix: "Bug:",
|
|
1276
|
+
visibility: "private"
|
|
1277
|
+
},
|
|
1278
|
+
playbook: {
|
|
1279
|
+
name: "Playbook",
|
|
1280
|
+
description: "Step-by-step procedure or workflow",
|
|
1281
|
+
memoryType: "procedural",
|
|
1282
|
+
defaultTags: ["playbook", "howto"],
|
|
1283
|
+
prefix: "Playbook:",
|
|
1284
|
+
visibility: "repo"
|
|
1285
|
+
},
|
|
1286
|
+
deploy: {
|
|
1287
|
+
name: "Deploy",
|
|
1288
|
+
description: "Deployment procedure or notes",
|
|
1289
|
+
memoryType: "procedural",
|
|
1290
|
+
defaultTags: ["deploy", "ops"],
|
|
1291
|
+
prefix: "Deploy:",
|
|
1292
|
+
visibility: "repo"
|
|
1293
|
+
},
|
|
1294
|
+
convention: {
|
|
1295
|
+
name: "Convention",
|
|
1296
|
+
description: "Coding convention or standard",
|
|
1297
|
+
memoryType: "semantic",
|
|
1298
|
+
defaultTags: ["convention", "standard"],
|
|
1299
|
+
prefix: "Convention:",
|
|
1300
|
+
visibility: "repo"
|
|
1301
|
+
},
|
|
1302
|
+
api: {
|
|
1303
|
+
name: "API",
|
|
1304
|
+
description: "API behavior or contract notes",
|
|
1305
|
+
memoryType: "semantic",
|
|
1306
|
+
defaultTags: ["api"],
|
|
1307
|
+
visibility: "repo"
|
|
1308
|
+
},
|
|
1309
|
+
workaround: {
|
|
1310
|
+
name: "Workaround",
|
|
1311
|
+
description: "Temporary workaround for an issue",
|
|
1312
|
+
memoryType: "episodic",
|
|
1313
|
+
defaultTags: ["workaround", "temporary"],
|
|
1314
|
+
prefix: "Workaround:",
|
|
1315
|
+
visibility: "private"
|
|
1316
|
+
},
|
|
1317
|
+
perf: {
|
|
1318
|
+
name: "Performance",
|
|
1319
|
+
description: "Performance finding or optimization",
|
|
1320
|
+
memoryType: "semantic",
|
|
1321
|
+
defaultTags: ["performance", "optimization"],
|
|
1322
|
+
prefix: "Perf:",
|
|
1323
|
+
visibility: "repo"
|
|
1324
|
+
},
|
|
1325
|
+
security: {
|
|
1326
|
+
name: "Security",
|
|
1327
|
+
description: "Security consideration or finding",
|
|
1328
|
+
memoryType: "semantic",
|
|
1329
|
+
defaultTags: ["security"],
|
|
1330
|
+
prefix: "Security:",
|
|
1331
|
+
visibility: "repo"
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
function getTemplate(name) {
|
|
1335
|
+
return MEMORY_TEMPLATES[name.toLowerCase()];
|
|
1336
|
+
}
|
|
1337
|
+
function applyTemplate(template, text, additionalTags = []) {
|
|
1338
|
+
const finalText = template.prefix && !text.toLowerCase().startsWith(template.prefix.toLowerCase()) ? `${template.prefix} ${text}` : text;
|
|
1339
|
+
const tags = [.../* @__PURE__ */ new Set([...template.defaultTags, ...additionalTags])];
|
|
1340
|
+
return {
|
|
1341
|
+
text: finalText,
|
|
1342
|
+
memoryType: template.memoryType,
|
|
1343
|
+
tags,
|
|
1344
|
+
visibility: template.visibility
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
function formatTemplateList() {
|
|
1348
|
+
const lines = ["Available templates:", ""];
|
|
1349
|
+
for (const [key, template] of Object.entries(MEMORY_TEMPLATES)) {
|
|
1350
|
+
lines.push(` ${key.padEnd(12)} - ${template.description}`);
|
|
1351
|
+
lines.push(` Type: ${template.memoryType}, Tags: ${template.defaultTags.join(", ")}`);
|
|
1352
|
+
}
|
|
1353
|
+
return lines.join("\n");
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// ../../packages/config/dist/index.js
|
|
1357
|
+
import fs from "fs";
|
|
1358
|
+
import path from "path";
|
|
1359
|
+
import { execSync } from "child_process";
|
|
1360
|
+
import { randomUUID } from "crypto";
|
|
1361
|
+
import YAML from "yaml";
|
|
1362
|
+
import { z } from "zod";
|
|
1363
|
+
var syncConfigSchema = z.object({
|
|
1364
|
+
enabled: z.boolean(),
|
|
1365
|
+
intervalMs: z.number().positive(),
|
|
1366
|
+
debounceMs: z.number().nonnegative(),
|
|
1367
|
+
autoResolveConflicts: z.enum([
|
|
1368
|
+
"last_write_wins",
|
|
1369
|
+
"local_wins",
|
|
1370
|
+
"remote_wins",
|
|
1371
|
+
"manual"
|
|
1372
|
+
])
|
|
1373
|
+
});
|
|
1374
|
+
var embeddingConfigSchema = z.object({
|
|
1375
|
+
enabled: z.boolean(),
|
|
1376
|
+
model: z.string(),
|
|
1377
|
+
autoGenerate: z.boolean()
|
|
1378
|
+
});
|
|
1379
|
+
var lifecycleTtlConfigSchema = z.object({
|
|
1380
|
+
episodic: z.number().int().positive().optional(),
|
|
1381
|
+
semantic: z.number().int().positive().optional(),
|
|
1382
|
+
procedural: z.number().int().positive().optional()
|
|
1383
|
+
});
|
|
1384
|
+
var lifecycleUsageBoostSchema = z.object({
|
|
1385
|
+
enabled: z.boolean(),
|
|
1386
|
+
topKToRecord: z.number().int().positive(),
|
|
1387
|
+
minUsageCount: z.number().int().positive(),
|
|
1388
|
+
maxBoost: z.number().min(0).max(1),
|
|
1389
|
+
halfLifeDays: z.number().positive()
|
|
1390
|
+
});
|
|
1391
|
+
var lifecycleMaintenanceSchema = z.object({
|
|
1392
|
+
staleEpisodicDays: z.number().int().positive(),
|
|
1393
|
+
consolidationThreshold: z.number().min(0).max(1),
|
|
1394
|
+
consolidationMinGroupSize: z.number().int().min(2),
|
|
1395
|
+
consolidationMaxGroups: z.number().int().positive(),
|
|
1396
|
+
promoteRecallCount: z.number().int().positive(),
|
|
1397
|
+
pinRecallCount: z.number().int().positive(),
|
|
1398
|
+
dryRunDefault: z.boolean(),
|
|
1399
|
+
autoRunOnStore: z.boolean(),
|
|
1400
|
+
autoRunOnRecall: z.boolean(),
|
|
1401
|
+
debounceMs: z.number().int().positive()
|
|
1402
|
+
});
|
|
1403
|
+
var lifecycleConfigSchema = z.object({
|
|
1404
|
+
ttlSecondsByType: lifecycleTtlConfigSchema.optional(),
|
|
1405
|
+
usageBoost: lifecycleUsageBoostSchema.partial().optional(),
|
|
1406
|
+
maintenance: lifecycleMaintenanceSchema.partial().optional()
|
|
1407
|
+
});
|
|
1408
|
+
var remoteConfigSchema = z.object({
|
|
1409
|
+
url: z.string(),
|
|
1410
|
+
orgId: z.string(),
|
|
1411
|
+
repoId: z.string()
|
|
1412
|
+
});
|
|
1413
|
+
var appConfigSchema = z.object({
|
|
1414
|
+
configVersion: z.number().optional(),
|
|
1415
|
+
remote: remoteConfigSchema,
|
|
1416
|
+
defaults: z.object({
|
|
1417
|
+
visibility: z.enum(["private", "repo", "auto"]),
|
|
1418
|
+
memoryType: z.enum(["episodic", "semantic", "procedural"])
|
|
1419
|
+
}),
|
|
1420
|
+
sync: syncConfigSchema.optional(),
|
|
1421
|
+
embeddings: embeddingConfigSchema.optional(),
|
|
1422
|
+
lifecycle: lifecycleConfigSchema.optional(),
|
|
1423
|
+
remotes: z.record(z.string(), remoteConfigSchema).optional()
|
|
1424
|
+
});
|
|
1425
|
+
var VALID_MEMORY_TYPES = ["episodic", "semantic", "procedural"];
|
|
1426
|
+
function validateMemoryType(value) {
|
|
1427
|
+
return VALID_MEMORY_TYPES.includes(value);
|
|
1428
|
+
}
|
|
1429
|
+
function parseConfidence(value) {
|
|
1430
|
+
const n = parseFloat(value);
|
|
1431
|
+
if (Number.isNaN(n) || n < 0 || n > 1) {
|
|
1432
|
+
throw new Error("--confidence must be a number between 0 and 1");
|
|
1433
|
+
}
|
|
1434
|
+
return n;
|
|
1435
|
+
}
|
|
1436
|
+
function parseThreshold(value) {
|
|
1437
|
+
const n = parseFloat(value);
|
|
1438
|
+
if (Number.isNaN(n) || n < 0 || n > 1) {
|
|
1439
|
+
throw new Error("--threshold must be a number between 0 and 1");
|
|
1440
|
+
}
|
|
1441
|
+
return n;
|
|
1442
|
+
}
|
|
1443
|
+
function parseTtl(value) {
|
|
1444
|
+
const n = parseInt(value, 10);
|
|
1445
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
1446
|
+
throw new Error("--ttl must be a positive integer (seconds)");
|
|
1447
|
+
}
|
|
1448
|
+
return n;
|
|
1449
|
+
}
|
|
1450
|
+
function parsePositiveInt(value, name) {
|
|
1451
|
+
const n = parseInt(value, 10);
|
|
1452
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
1453
|
+
throw new Error(`--${name} must be a positive integer`);
|
|
1454
|
+
}
|
|
1455
|
+
return n;
|
|
1456
|
+
}
|
|
1457
|
+
var DATA_DIR = ".unforgit";
|
|
1458
|
+
var CONFIG_FILE = "unforgit.yaml";
|
|
1459
|
+
var DB_FILE = "local.db";
|
|
1460
|
+
function writeConfigYaml(configPath, value) {
|
|
1461
|
+
const dir = path.dirname(configPath);
|
|
1462
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1463
|
+
const tmpPath = path.join(
|
|
1464
|
+
dir,
|
|
1465
|
+
`.${path.basename(configPath)}.${process.pid}.${randomUUID()}.tmp`
|
|
1466
|
+
);
|
|
1467
|
+
const fd = fs.openSync(
|
|
1468
|
+
tmpPath,
|
|
1469
|
+
fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY,
|
|
1470
|
+
384
|
|
1471
|
+
);
|
|
1472
|
+
try {
|
|
1473
|
+
fs.writeFileSync(fd, YAML.stringify(value), "utf-8");
|
|
1474
|
+
fs.fsyncSync(fd);
|
|
1475
|
+
} catch (err) {
|
|
1476
|
+
try {
|
|
1477
|
+
fs.closeSync(fd);
|
|
1478
|
+
} catch {
|
|
1479
|
+
}
|
|
1480
|
+
fs.rmSync(tmpPath, { force: true });
|
|
1481
|
+
throw err;
|
|
1482
|
+
}
|
|
1483
|
+
fs.closeSync(fd);
|
|
1484
|
+
fs.renameSync(tmpPath, configPath);
|
|
1485
|
+
fs.chmodSync(configPath, 384);
|
|
1486
|
+
}
|
|
1487
|
+
function detectGitInfo(cwd = process.cwd()) {
|
|
1488
|
+
try {
|
|
1489
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
1490
|
+
cwd,
|
|
1491
|
+
encoding: "utf-8",
|
|
1492
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1493
|
+
}).trim();
|
|
1494
|
+
const match = remoteUrl.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) ?? void 0;
|
|
1495
|
+
if (match) {
|
|
1496
|
+
return { orgId: match[1], repoId: match[2] };
|
|
1497
|
+
}
|
|
1498
|
+
} catch {
|
|
1499
|
+
}
|
|
1500
|
+
return { orgId: "", repoId: "" };
|
|
1501
|
+
}
|
|
1502
|
+
function getDataDir(cwd = process.cwd()) {
|
|
1503
|
+
return path.join(cwd, DATA_DIR);
|
|
1504
|
+
}
|
|
1505
|
+
function getDbPath(cwd = process.cwd()) {
|
|
1506
|
+
return path.join(getDataDir(cwd), DB_FILE);
|
|
1507
|
+
}
|
|
1508
|
+
function getConfigPath(cwd = process.cwd()) {
|
|
1509
|
+
return path.join(getDataDir(cwd), CONFIG_FILE);
|
|
1510
|
+
}
|
|
1511
|
+
function isInitialized(cwd = process.cwd()) {
|
|
1512
|
+
return fs.existsSync(getDataDir(cwd)) && fs.existsSync(getConfigPath(cwd));
|
|
1513
|
+
}
|
|
1514
|
+
function findRepoRoot(startDir = process.cwd()) {
|
|
1515
|
+
let dir = path.resolve(startDir);
|
|
1516
|
+
const root = path.parse(dir).root;
|
|
1517
|
+
while (dir !== root) {
|
|
1518
|
+
if (isInitialized(dir)) return dir;
|
|
1519
|
+
dir = path.dirname(dir);
|
|
1520
|
+
}
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
var CURRENT_CONFIG_VERSION = 2;
|
|
1524
|
+
function migrateConfig(parsed, configPath) {
|
|
1525
|
+
const version = parsed.configVersion ?? 0;
|
|
1526
|
+
if (version === CURRENT_CONFIG_VERSION) return parsed;
|
|
1527
|
+
if (version === 0) {
|
|
1528
|
+
parsed.configVersion = CURRENT_CONFIG_VERSION;
|
|
1529
|
+
writeConfigYaml(configPath, parsed);
|
|
1530
|
+
}
|
|
1531
|
+
if (version === 1) {
|
|
1532
|
+
parsed.configVersion = CURRENT_CONFIG_VERSION;
|
|
1533
|
+
writeConfigYaml(configPath, parsed);
|
|
1534
|
+
}
|
|
1535
|
+
return parsed;
|
|
1536
|
+
}
|
|
1537
|
+
function warnDeprecatedKeys(parsed) {
|
|
1538
|
+
const deprecated = [];
|
|
1539
|
+
if (parsed.remote?.apiKey) {
|
|
1540
|
+
deprecated.push("remote.apiKey \u2192 use UNFORGIT_API_KEY env var instead");
|
|
1541
|
+
}
|
|
1542
|
+
if (parsed.openaiApiKey) {
|
|
1543
|
+
deprecated.push("openaiApiKey \u2192 use OPENAI_API_KEY env var instead");
|
|
1544
|
+
}
|
|
1545
|
+
if (deprecated.length > 0) {
|
|
1546
|
+
console.error(
|
|
1547
|
+
`[unforgit] Deprecated keys found in unforgit.yaml (ignored):
|
|
1548
|
+
${deprecated.map((d) => ` - ${d}`).join("\n")}
|
|
1549
|
+
Remove them from your config. Secrets should be set via environment variables.
|
|
1550
|
+
`
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
function loadConfig(cwd = process.cwd()) {
|
|
1555
|
+
const configPath = getConfigPath(cwd);
|
|
1556
|
+
if (!fs.existsSync(configPath)) {
|
|
1557
|
+
throw new Error(
|
|
1558
|
+
"Unforgit not initialized. Run 'unforgit init' first."
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
1562
|
+
const parsed = YAML.parse(raw) ?? {};
|
|
1563
|
+
const migrated = migrateConfig(parsed, configPath);
|
|
1564
|
+
warnDeprecatedKeys(migrated);
|
|
1565
|
+
const result = appConfigSchema.safeParse(migrated);
|
|
1566
|
+
if (!result.success) {
|
|
1567
|
+
const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
1568
|
+
throw new Error(
|
|
1569
|
+
`Invalid unforgit.yaml configuration:
|
|
1570
|
+
${issues}
|
|
1571
|
+
|
|
1572
|
+
Fix the config at ${configPath} or re-run 'unforgit init'.`
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
const defaults = defaultConfig();
|
|
1576
|
+
const { openaiApiKey: _oai, ...cleanMigrated } = migrated;
|
|
1577
|
+
if (cleanMigrated.remote && typeof cleanMigrated.remote === "object") {
|
|
1578
|
+
const { apiKey: _ak, ...cleanRemote } = cleanMigrated.remote;
|
|
1579
|
+
cleanMigrated.remote = cleanRemote;
|
|
1580
|
+
}
|
|
1581
|
+
return {
|
|
1582
|
+
...defaults,
|
|
1583
|
+
...cleanMigrated,
|
|
1584
|
+
...result.data,
|
|
1585
|
+
remote: {
|
|
1586
|
+
...defaults.remote,
|
|
1587
|
+
...result.data.remote
|
|
1588
|
+
},
|
|
1589
|
+
defaults: {
|
|
1590
|
+
...defaults.defaults,
|
|
1591
|
+
...result.data.defaults
|
|
1592
|
+
},
|
|
1593
|
+
sync: {
|
|
1594
|
+
...defaults.sync,
|
|
1595
|
+
...result.data.sync ?? {}
|
|
1596
|
+
},
|
|
1597
|
+
embeddings: {
|
|
1598
|
+
...defaults.embeddings,
|
|
1599
|
+
...result.data.embeddings ?? {}
|
|
1600
|
+
},
|
|
1601
|
+
lifecycle: resolveLifecycleConfig(result.data.lifecycle)
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
function saveConfig(config2, cwd = process.cwd()) {
|
|
1605
|
+
const configPath = getConfigPath(cwd);
|
|
1606
|
+
writeConfigYaml(configPath, config2);
|
|
1607
|
+
}
|
|
1608
|
+
function defaultConfig() {
|
|
1609
|
+
return {
|
|
1610
|
+
configVersion: CURRENT_CONFIG_VERSION,
|
|
1611
|
+
remote: {
|
|
1612
|
+
url: "http://localhost:3737",
|
|
1613
|
+
orgId: "",
|
|
1614
|
+
repoId: ""
|
|
1615
|
+
},
|
|
1616
|
+
defaults: {
|
|
1617
|
+
visibility: "auto",
|
|
1618
|
+
memoryType: "episodic"
|
|
1619
|
+
},
|
|
1620
|
+
sync: {
|
|
1621
|
+
enabled: true,
|
|
1622
|
+
intervalMs: 6e4,
|
|
1623
|
+
debounceMs: 5e3,
|
|
1624
|
+
autoResolveConflicts: "last_write_wins"
|
|
1625
|
+
},
|
|
1626
|
+
embeddings: {
|
|
1627
|
+
enabled: true,
|
|
1628
|
+
model: "text-embedding-3-small",
|
|
1629
|
+
autoGenerate: true
|
|
1630
|
+
},
|
|
1631
|
+
lifecycle: resolveLifecycleConfig()
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1635
|
+
var MAX_RETRIES = 3;
|
|
1636
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
1637
|
+
function isTransientError(status) {
|
|
1638
|
+
return status >= 500 || status === 429;
|
|
1639
|
+
}
|
|
1640
|
+
var RemoteClient = class {
|
|
1641
|
+
constructor(baseUrl, apiKey, options) {
|
|
1642
|
+
this.baseUrl = baseUrl;
|
|
1643
|
+
this.apiKey = apiKey || process.env.UNFORGIT_API_KEY;
|
|
1644
|
+
this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1645
|
+
}
|
|
1646
|
+
apiKey;
|
|
1647
|
+
timeoutMs;
|
|
1648
|
+
getHeaders() {
|
|
1649
|
+
const headers = {
|
|
1650
|
+
"Content-Type": "application/json"
|
|
1651
|
+
};
|
|
1652
|
+
if (this.apiKey) {
|
|
1653
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
1654
|
+
}
|
|
1655
|
+
return headers;
|
|
1656
|
+
}
|
|
1657
|
+
handleError(res, operation, errorText) {
|
|
1658
|
+
if (res.status === 401) {
|
|
1659
|
+
throw new Error(
|
|
1660
|
+
`Authentication failed for ${operation}: Invalid or missing API key. Set the UNFORGIT_API_KEY environment variable.`
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
if (res.status === 404 && operation === "resetAll") {
|
|
1664
|
+
throw new Error(
|
|
1665
|
+
"Remote resetAll failed (404): the configured server does not support /v1/memories/reset. Rebuild or restart the remote API so it is running a version that includes the reset endpoint."
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
throw new Error(`Remote ${operation} failed (${res.status}): ${errorText}`);
|
|
1669
|
+
}
|
|
1670
|
+
async fetchWithTimeout(url, init) {
|
|
1671
|
+
const controller = new AbortController();
|
|
1672
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1673
|
+
try {
|
|
1674
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
1675
|
+
} catch (err) {
|
|
1676
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
1677
|
+
throw new Error(`Request timed out after ${this.timeoutMs}ms`);
|
|
1678
|
+
}
|
|
1679
|
+
throw err;
|
|
1680
|
+
} finally {
|
|
1681
|
+
clearTimeout(timer);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
async fetchWithRetry(url, init, operation) {
|
|
1685
|
+
let lastError;
|
|
1686
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
1687
|
+
try {
|
|
1688
|
+
const res = await this.fetchWithTimeout(url, init);
|
|
1689
|
+
if (res.ok || !isTransientError(res.status)) {
|
|
1690
|
+
return res;
|
|
1691
|
+
}
|
|
1692
|
+
lastError = new Error(
|
|
1693
|
+
`Remote ${operation} failed (${res.status}): ${await res.text()}`
|
|
1694
|
+
);
|
|
1695
|
+
} catch (err) {
|
|
1696
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1697
|
+
const isFatalAbort = lastError.message.includes("timed out") && attempt === MAX_RETRIES - 1;
|
|
1698
|
+
if (isFatalAbort) throw lastError;
|
|
1699
|
+
}
|
|
1700
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
1701
|
+
const backoff = INITIAL_BACKOFF_MS * Math.pow(2, attempt);
|
|
1702
|
+
await new Promise((resolve) => setTimeout(resolve, backoff));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
throw lastError ?? new Error(`Remote ${operation} failed after ${MAX_RETRIES} retries`);
|
|
1706
|
+
}
|
|
1707
|
+
async store(input) {
|
|
1708
|
+
const res = await this.fetchWithRetry(
|
|
1709
|
+
`${this.baseUrl}/v1/memory`,
|
|
1710
|
+
{ method: "POST", headers: this.getHeaders(), body: JSON.stringify(input) },
|
|
1711
|
+
"store"
|
|
1712
|
+
);
|
|
1713
|
+
if (!res.ok) {
|
|
1714
|
+
this.handleError(res, "store", await res.text());
|
|
1715
|
+
}
|
|
1716
|
+
return res.json();
|
|
1717
|
+
}
|
|
1718
|
+
async recall(query) {
|
|
1719
|
+
const res = await this.fetchWithRetry(
|
|
1720
|
+
`${this.baseUrl}/v1/recall`,
|
|
1721
|
+
{ method: "POST", headers: this.getHeaders(), body: JSON.stringify(query) },
|
|
1722
|
+
"recall"
|
|
1723
|
+
);
|
|
1724
|
+
if (!res.ok) {
|
|
1725
|
+
this.handleError(res, "recall", await res.text());
|
|
1726
|
+
}
|
|
1727
|
+
return res.json();
|
|
1728
|
+
}
|
|
1729
|
+
async deprecate(id, reason) {
|
|
1730
|
+
const res = await this.fetchWithRetry(
|
|
1731
|
+
`${this.baseUrl}/v1/memory/${id}/deprecate`,
|
|
1732
|
+
{ method: "POST", headers: this.getHeaders(), body: JSON.stringify({ reason }) },
|
|
1733
|
+
"deprecate"
|
|
1734
|
+
);
|
|
1735
|
+
if (!res.ok) {
|
|
1736
|
+
this.handleError(res, "deprecate", await res.text());
|
|
1737
|
+
}
|
|
1738
|
+
return res.json();
|
|
1739
|
+
}
|
|
1740
|
+
async supersede(oldId, newId) {
|
|
1741
|
+
const res = await this.fetchWithRetry(
|
|
1742
|
+
`${this.baseUrl}/v1/memory/${oldId}/supersede`,
|
|
1743
|
+
{ method: "POST", headers: this.getHeaders(), body: JSON.stringify({ newId }) },
|
|
1744
|
+
"supersede"
|
|
1745
|
+
);
|
|
1746
|
+
if (!res.ok) {
|
|
1747
|
+
this.handleError(res, "supersede", await res.text());
|
|
1748
|
+
}
|
|
1749
|
+
return res.json();
|
|
1750
|
+
}
|
|
1751
|
+
async link(sourceId, targetId, linkType, metadata) {
|
|
1752
|
+
const res = await this.fetchWithRetry(
|
|
1753
|
+
`${this.baseUrl}/v1/memory/${sourceId}/link`,
|
|
1754
|
+
{
|
|
1755
|
+
method: "POST",
|
|
1756
|
+
headers: this.getHeaders(),
|
|
1757
|
+
body: JSON.stringify({ targetId, linkType, metadata })
|
|
1758
|
+
},
|
|
1759
|
+
"link"
|
|
1760
|
+
);
|
|
1761
|
+
if (!res.ok) {
|
|
1762
|
+
this.handleError(res, "link", await res.text());
|
|
1763
|
+
}
|
|
1764
|
+
return res.json();
|
|
1765
|
+
}
|
|
1766
|
+
async unlink(sourceId, targetId, linkType) {
|
|
1767
|
+
const res = await this.fetchWithRetry(
|
|
1768
|
+
`${this.baseUrl}/v1/memory/${sourceId}/link`,
|
|
1769
|
+
{
|
|
1770
|
+
method: "DELETE",
|
|
1771
|
+
headers: this.getHeaders(),
|
|
1772
|
+
body: JSON.stringify({ targetId, linkType })
|
|
1773
|
+
},
|
|
1774
|
+
"unlink"
|
|
1775
|
+
);
|
|
1776
|
+
if (!res.ok) {
|
|
1777
|
+
this.handleError(res, "unlink", await res.text());
|
|
1778
|
+
}
|
|
1779
|
+
return res.json();
|
|
1780
|
+
}
|
|
1781
|
+
async getLinks(memoryId, linkType) {
|
|
1782
|
+
const params = new URLSearchParams();
|
|
1783
|
+
if (linkType) params.set("linkType", linkType);
|
|
1784
|
+
const qs = params.toString();
|
|
1785
|
+
const url = `${this.baseUrl}/v1/memory/${memoryId}/links${qs ? `?${qs}` : ""}`;
|
|
1786
|
+
const res = await this.fetchWithRetry(url, { headers: this.getHeaders() }, "getLinks");
|
|
1787
|
+
if (!res.ok) {
|
|
1788
|
+
this.handleError(res, "getLinks", await res.text());
|
|
1789
|
+
}
|
|
1790
|
+
return res.json();
|
|
1791
|
+
}
|
|
1792
|
+
async consolidate(body) {
|
|
1793
|
+
const res = await this.fetchWithRetry(
|
|
1794
|
+
`${this.baseUrl}/v1/consolidate`,
|
|
1795
|
+
{ method: "POST", headers: this.getHeaders(), body: JSON.stringify(body) },
|
|
1796
|
+
"consolidate"
|
|
1797
|
+
);
|
|
1798
|
+
if (!res.ok) {
|
|
1799
|
+
this.handleError(res, "consolidate", await res.text());
|
|
1800
|
+
}
|
|
1801
|
+
return res.json();
|
|
1802
|
+
}
|
|
1803
|
+
async delete(id, deletedBy, hardDelete) {
|
|
1804
|
+
const res = await this.fetchWithRetry(
|
|
1805
|
+
`${this.baseUrl}/v1/memory/${id}`,
|
|
1806
|
+
{
|
|
1807
|
+
method: "DELETE",
|
|
1808
|
+
headers: this.getHeaders(),
|
|
1809
|
+
body: JSON.stringify({ deletedBy, hardDelete })
|
|
1810
|
+
},
|
|
1811
|
+
"delete"
|
|
1812
|
+
);
|
|
1813
|
+
if (!res.ok) {
|
|
1814
|
+
this.handleError(res, "delete", await res.text());
|
|
1815
|
+
}
|
|
1816
|
+
return res.json();
|
|
1817
|
+
}
|
|
1818
|
+
async restore(id) {
|
|
1819
|
+
const res = await this.fetchWithRetry(
|
|
1820
|
+
`${this.baseUrl}/v1/memory/${id}/restore`,
|
|
1821
|
+
{ method: "POST", headers: this.getHeaders() },
|
|
1822
|
+
"restore"
|
|
1823
|
+
);
|
|
1824
|
+
if (!res.ok) {
|
|
1825
|
+
this.handleError(res, "restore", await res.text());
|
|
1826
|
+
}
|
|
1827
|
+
return res.json();
|
|
1828
|
+
}
|
|
1829
|
+
async resetAll(orgId, repoId) {
|
|
1830
|
+
const res = await this.fetchWithRetry(
|
|
1831
|
+
`${this.baseUrl}/v1/memories/reset`,
|
|
1832
|
+
{
|
|
1833
|
+
method: "POST",
|
|
1834
|
+
headers: this.getHeaders(),
|
|
1835
|
+
body: JSON.stringify({ orgId, repoId })
|
|
1836
|
+
},
|
|
1837
|
+
"resetAll"
|
|
1838
|
+
);
|
|
1839
|
+
if (!res.ok) {
|
|
1840
|
+
this.handleError(res, "resetAll", await res.text());
|
|
1841
|
+
}
|
|
1842
|
+
return res.json();
|
|
1843
|
+
}
|
|
1844
|
+
async runLifecycle(body) {
|
|
1845
|
+
const res = await this.fetchWithRetry(
|
|
1846
|
+
`${this.baseUrl}/v1/lifecycle/run`,
|
|
1847
|
+
{
|
|
1848
|
+
method: "POST",
|
|
1849
|
+
headers: this.getHeaders(),
|
|
1850
|
+
body: JSON.stringify(body)
|
|
1851
|
+
},
|
|
1852
|
+
"runLifecycle"
|
|
1853
|
+
);
|
|
1854
|
+
if (!res.ok) {
|
|
1855
|
+
this.handleError(res, "runLifecycle", await res.text());
|
|
1856
|
+
}
|
|
1857
|
+
return res.json();
|
|
1858
|
+
}
|
|
1859
|
+
async createApiKey(name, orgId) {
|
|
1860
|
+
const res = await this.fetchWithRetry(
|
|
1861
|
+
`${this.baseUrl}/v1/api-keys`,
|
|
1862
|
+
{
|
|
1863
|
+
method: "POST",
|
|
1864
|
+
headers: this.getHeaders(),
|
|
1865
|
+
body: JSON.stringify({ name, orgId })
|
|
1866
|
+
},
|
|
1867
|
+
"createApiKey"
|
|
1868
|
+
);
|
|
1869
|
+
if (!res.ok) {
|
|
1870
|
+
this.handleError(res, "createApiKey", await res.text());
|
|
1871
|
+
}
|
|
1872
|
+
return res.json();
|
|
1873
|
+
}
|
|
1874
|
+
async listApiKeys(orgId) {
|
|
1875
|
+
const params = new URLSearchParams();
|
|
1876
|
+
if (orgId) params.set("orgId", orgId);
|
|
1877
|
+
const qs = params.toString();
|
|
1878
|
+
const url = `${this.baseUrl}/v1/api-keys${qs ? `?${qs}` : ""}`;
|
|
1879
|
+
const res = await this.fetchWithRetry(url, { headers: this.getHeaders() }, "listApiKeys");
|
|
1880
|
+
if (!res.ok) {
|
|
1881
|
+
this.handleError(res, "listApiKeys", await res.text());
|
|
1882
|
+
}
|
|
1883
|
+
return res.json();
|
|
1884
|
+
}
|
|
1885
|
+
async revokeApiKey(id) {
|
|
1886
|
+
const res = await this.fetchWithRetry(
|
|
1887
|
+
`${this.baseUrl}/v1/api-keys/${id}`,
|
|
1888
|
+
{ method: "DELETE", headers: this.getHeaders() },
|
|
1889
|
+
"revokeApiKey"
|
|
1890
|
+
);
|
|
1891
|
+
if (!res.ok) {
|
|
1892
|
+
if (res.status === 404) {
|
|
1893
|
+
throw new Error(`API key '${id}' not found.`);
|
|
1894
|
+
}
|
|
1895
|
+
this.handleError(res, "revokeApiKey", await res.text());
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
|
|
1900
|
+
// ../../packages/db/dist/index.js
|
|
1901
|
+
import Database from "better-sqlite3";
|
|
1902
|
+
import { v4 as uuid } from "uuid";
|
|
1903
|
+
import path2 from "path";
|
|
1904
|
+
import fs2 from "fs";
|
|
1905
|
+
import * as path22 from "path";
|
|
1906
|
+
import { fileURLToPath } from "url";
|
|
1907
|
+
import * as runtime from "@prisma/client/runtime/client";
|
|
1908
|
+
import * as runtime2 from "@prisma/client/runtime/client";
|
|
1909
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
1910
|
+
var SCHEMA_SQL = `
|
|
1911
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
1912
|
+
id TEXT PRIMARY KEY,
|
|
1913
|
+
org_id TEXT NOT NULL,
|
|
1914
|
+
repo_id TEXT NOT NULL,
|
|
1915
|
+
scope_type TEXT NOT NULL DEFAULT 'repo',
|
|
1916
|
+
memory_type TEXT NOT NULL CHECK(memory_type IN ('episodic','semantic','procedural')),
|
|
1917
|
+
visibility TEXT NOT NULL DEFAULT 'private' CHECK(visibility IN ('private','repo')),
|
|
1918
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','deprecated','superseded','deleted')),
|
|
1919
|
+
text TEXT NOT NULL,
|
|
1920
|
+
summary TEXT,
|
|
1921
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
1922
|
+
source_refs TEXT,
|
|
1923
|
+
confidence REAL,
|
|
1924
|
+
ttl_seconds INTEGER,
|
|
1925
|
+
supersedes_id TEXT,
|
|
1926
|
+
is_consolidation INTEGER NOT NULL DEFAULT 0,
|
|
1927
|
+
consolidation_version INTEGER,
|
|
1928
|
+
author_id TEXT,
|
|
1929
|
+
author_name TEXT,
|
|
1930
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
1931
|
+
deleted_at TEXT,
|
|
1932
|
+
deleted_by TEXT,
|
|
1933
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1934
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1935
|
+
);
|
|
1936
|
+
|
|
1937
|
+
CREATE TABLE IF NOT EXISTS tombstones (
|
|
1938
|
+
id TEXT PRIMARY KEY,
|
|
1939
|
+
memory_id TEXT NOT NULL UNIQUE,
|
|
1940
|
+
org_id TEXT NOT NULL,
|
|
1941
|
+
repo_id TEXT NOT NULL,
|
|
1942
|
+
deleted_at TEXT NOT NULL,
|
|
1943
|
+
deleted_by TEXT,
|
|
1944
|
+
synced_at TEXT,
|
|
1945
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1946
|
+
);
|
|
1947
|
+
|
|
1948
|
+
CREATE INDEX IF NOT EXISTS idx_tombstones_sync ON tombstones(org_id, repo_id, synced_at);
|
|
1949
|
+
|
|
1950
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
1951
|
+
text, summary, content=memories, content_rowid=rowid
|
|
1952
|
+
);
|
|
1953
|
+
|
|
1954
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
1955
|
+
INSERT INTO memories_fts(rowid, text, summary)
|
|
1956
|
+
VALUES (new.rowid, new.text, new.summary);
|
|
1957
|
+
END;
|
|
1958
|
+
|
|
1959
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
1960
|
+
INSERT INTO memories_fts(memories_fts, rowid, text, summary)
|
|
1961
|
+
VALUES ('delete', old.rowid, old.text, old.summary);
|
|
1962
|
+
END;
|
|
1963
|
+
|
|
1964
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
1965
|
+
INSERT INTO memories_fts(memories_fts, rowid, text, summary)
|
|
1966
|
+
VALUES ('delete', old.rowid, old.text, old.summary);
|
|
1967
|
+
INSERT INTO memories_fts(rowid, text, summary)
|
|
1968
|
+
VALUES (new.rowid, new.text, new.summary);
|
|
1969
|
+
END;
|
|
1970
|
+
|
|
1971
|
+
CREATE TABLE IF NOT EXISTS memory_links (
|
|
1972
|
+
id TEXT PRIMARY KEY,
|
|
1973
|
+
source_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
1974
|
+
target_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
1975
|
+
link_type TEXT NOT NULL CHECK(link_type IN ('related_to','derived_from','contradicts','depends_on')),
|
|
1976
|
+
metadata TEXT,
|
|
1977
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1978
|
+
UNIQUE(source_id, target_id, link_type)
|
|
1979
|
+
);
|
|
1980
|
+
|
|
1981
|
+
CREATE TABLE IF NOT EXISTS sync_state (
|
|
1982
|
+
memory_id TEXT PRIMARY KEY,
|
|
1983
|
+
local_version INTEGER NOT NULL,
|
|
1984
|
+
remote_version INTEGER,
|
|
1985
|
+
last_pushed_at TEXT,
|
|
1986
|
+
last_pulled_at TEXT,
|
|
1987
|
+
sync_status TEXT NOT NULL DEFAULT 'pending_push' CHECK(sync_status IN ('synced','pending_push','pending_pull','conflict'))
|
|
1988
|
+
);
|
|
1989
|
+
|
|
1990
|
+
CREATE INDEX IF NOT EXISTS idx_sync_state_status ON sync_state(sync_status);
|
|
1991
|
+
|
|
1992
|
+
CREATE TABLE IF NOT EXISTS synced_links (
|
|
1993
|
+
link_id TEXT PRIMARY KEY,
|
|
1994
|
+
synced_at TEXT NOT NULL
|
|
1995
|
+
);
|
|
1996
|
+
|
|
1997
|
+
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
1998
|
+
memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
|
|
1999
|
+
embedding BLOB NOT NULL,
|
|
2000
|
+
model TEXT NOT NULL,
|
|
2001
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2002
|
+
);
|
|
2003
|
+
|
|
2004
|
+
CREATE TABLE IF NOT EXISTS memory_usage (
|
|
2005
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2006
|
+
memory_id TEXT NOT NULL,
|
|
2007
|
+
recalled_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2008
|
+
query TEXT,
|
|
2009
|
+
session_id TEXT
|
|
2010
|
+
);
|
|
2011
|
+
|
|
2012
|
+
CREATE INDEX IF NOT EXISTS idx_memory_usage_memory ON memory_usage(memory_id);
|
|
2013
|
+
CREATE INDEX IF NOT EXISTS idx_memory_usage_recalled ON memory_usage(recalled_at);
|
|
2014
|
+
|
|
2015
|
+
CREATE TABLE IF NOT EXISTS curation_suggestions (
|
|
2016
|
+
id TEXT PRIMARY KEY,
|
|
2017
|
+
org_id TEXT NOT NULL,
|
|
2018
|
+
repo_id TEXT NOT NULL,
|
|
2019
|
+
type TEXT NOT NULL CHECK(type IN ('consolidate','deprecate','delete','add_tags','add_links','review','promote','generate_embedding')),
|
|
2020
|
+
priority TEXT NOT NULL CHECK(priority IN ('high','medium','low')),
|
|
2021
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','approved','rejected','applied')),
|
|
2022
|
+
memory_ids TEXT NOT NULL DEFAULT '[]',
|
|
2023
|
+
reason TEXT NOT NULL,
|
|
2024
|
+
confidence REAL NOT NULL,
|
|
2025
|
+
payload TEXT,
|
|
2026
|
+
created_by TEXT,
|
|
2027
|
+
reviewed_by TEXT,
|
|
2028
|
+
review_note TEXT,
|
|
2029
|
+
reviewed_at TEXT,
|
|
2030
|
+
applied_at TEXT,
|
|
2031
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2032
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2033
|
+
);
|
|
2034
|
+
CREATE INDEX IF NOT EXISTS idx_curation_suggestions_repo_status ON curation_suggestions(org_id, repo_id, status, created_at);
|
|
2035
|
+
`;
|
|
2036
|
+
function rowToLink(row) {
|
|
2037
|
+
return {
|
|
2038
|
+
id: row.id,
|
|
2039
|
+
sourceId: row.source_id,
|
|
2040
|
+
targetId: row.target_id,
|
|
2041
|
+
linkType: row.link_type,
|
|
2042
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
2043
|
+
createdAt: new Date(row.created_at)
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
function rowToMemory(row) {
|
|
2047
|
+
return {
|
|
2048
|
+
id: row.id,
|
|
2049
|
+
orgId: row.org_id,
|
|
2050
|
+
repoId: row.repo_id,
|
|
2051
|
+
scopeType: row.scope_type ?? "repo",
|
|
2052
|
+
memoryType: row.memory_type,
|
|
2053
|
+
visibility: row.visibility,
|
|
2054
|
+
status: row.status,
|
|
2055
|
+
text: row.text,
|
|
2056
|
+
summary: row.summary ?? void 0,
|
|
2057
|
+
tags: JSON.parse(row.tags ?? "[]"),
|
|
2058
|
+
sourceRefs: row.source_refs ? JSON.parse(row.source_refs) : void 0,
|
|
2059
|
+
confidence: row.confidence ?? void 0,
|
|
2060
|
+
ttlSeconds: row.ttl_seconds ?? void 0,
|
|
2061
|
+
supersedesId: row.supersedes_id ?? void 0,
|
|
2062
|
+
isConsolidation: row.is_consolidation === 1,
|
|
2063
|
+
consolidationVersion: row.consolidation_version ?? void 0,
|
|
2064
|
+
authorId: row.author_id ?? void 0,
|
|
2065
|
+
authorName: row.author_name ?? void 0,
|
|
2066
|
+
version: row.version ?? 1,
|
|
2067
|
+
deletedAt: row.deleted_at ? new Date(row.deleted_at) : void 0,
|
|
2068
|
+
deletedBy: row.deleted_by ?? void 0,
|
|
2069
|
+
createdAt: new Date(row.created_at),
|
|
2070
|
+
updatedAt: new Date(row.updated_at)
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
function rowToCurationSuggestion(row) {
|
|
2074
|
+
return {
|
|
2075
|
+
id: row.id,
|
|
2076
|
+
orgId: row.org_id,
|
|
2077
|
+
repoId: row.repo_id,
|
|
2078
|
+
type: row.type,
|
|
2079
|
+
priority: row.priority,
|
|
2080
|
+
status: row.status,
|
|
2081
|
+
memoryIds: JSON.parse(row.memory_ids ?? "[]"),
|
|
2082
|
+
reason: row.reason,
|
|
2083
|
+
confidence: row.confidence,
|
|
2084
|
+
payload: row.payload ? JSON.parse(row.payload) : void 0,
|
|
2085
|
+
createdBy: row.created_by ?? void 0,
|
|
2086
|
+
reviewedBy: row.reviewed_by ?? void 0,
|
|
2087
|
+
reviewNote: row.review_note ?? void 0,
|
|
2088
|
+
reviewedAt: row.reviewed_at ? new Date(row.reviewed_at) : void 0,
|
|
2089
|
+
appliedAt: row.applied_at ? new Date(row.applied_at) : void 0,
|
|
2090
|
+
createdAt: new Date(row.created_at),
|
|
2091
|
+
updatedAt: new Date(row.updated_at)
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
function rowToTombstone(row) {
|
|
2095
|
+
return {
|
|
2096
|
+
id: row.id,
|
|
2097
|
+
memoryId: row.memory_id,
|
|
2098
|
+
orgId: row.org_id,
|
|
2099
|
+
repoId: row.repo_id,
|
|
2100
|
+
deletedAt: new Date(row.deleted_at),
|
|
2101
|
+
deletedBy: row.deleted_by ?? void 0,
|
|
2102
|
+
syncedAt: row.synced_at ? new Date(row.synced_at) : void 0
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
function nonExpiredMemoryClause(alias) {
|
|
2106
|
+
const prefix = alias ? `${alias}.` : "";
|
|
2107
|
+
return `(${prefix}status != 'active' OR ${prefix}ttl_seconds IS NULL OR datetime(${prefix}created_at, '+' || ${prefix}ttl_seconds || ' seconds') >= datetime('now'))`;
|
|
2108
|
+
}
|
|
2109
|
+
var LocalStore = class {
|
|
2110
|
+
db;
|
|
2111
|
+
constructor(dbPath) {
|
|
2112
|
+
const dir = path2.dirname(dbPath);
|
|
2113
|
+
if (!fs2.existsSync(dir)) {
|
|
2114
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
2115
|
+
}
|
|
2116
|
+
this.db = new Database(dbPath);
|
|
2117
|
+
this.db.pragma("journal_mode = WAL");
|
|
2118
|
+
this.db.pragma("foreign_keys = ON");
|
|
2119
|
+
this.db.exec(SCHEMA_SQL);
|
|
2120
|
+
this.migrateSchema();
|
|
2121
|
+
}
|
|
2122
|
+
migrateSchema() {
|
|
2123
|
+
const columns = this.db.prepare("PRAGMA table_info(memories)").all();
|
|
2124
|
+
const columnNames = columns.map((c) => c.name);
|
|
2125
|
+
if (!columnNames.includes("is_consolidation")) {
|
|
2126
|
+
this.db.exec(
|
|
2127
|
+
"ALTER TABLE memories ADD COLUMN is_consolidation INTEGER NOT NULL DEFAULT 0"
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
2130
|
+
if (!columnNames.includes("consolidation_version")) {
|
|
2131
|
+
this.db.exec(
|
|
2132
|
+
"ALTER TABLE memories ADD COLUMN consolidation_version INTEGER"
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
if (!columnNames.includes("author_id")) {
|
|
2136
|
+
this.db.exec("ALTER TABLE memories ADD COLUMN author_id TEXT");
|
|
2137
|
+
}
|
|
2138
|
+
if (!columnNames.includes("author_name")) {
|
|
2139
|
+
this.db.exec("ALTER TABLE memories ADD COLUMN author_name TEXT");
|
|
2140
|
+
}
|
|
2141
|
+
if (!columnNames.includes("version")) {
|
|
2142
|
+
this.db.exec("ALTER TABLE memories ADD COLUMN version INTEGER NOT NULL DEFAULT 1");
|
|
2143
|
+
}
|
|
2144
|
+
if (!columnNames.includes("deleted_at")) {
|
|
2145
|
+
this.db.exec("ALTER TABLE memories ADD COLUMN deleted_at TEXT");
|
|
2146
|
+
}
|
|
2147
|
+
if (!columnNames.includes("deleted_by")) {
|
|
2148
|
+
this.db.exec("ALTER TABLE memories ADD COLUMN deleted_by TEXT");
|
|
2149
|
+
}
|
|
2150
|
+
const tables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='tombstones'").all();
|
|
2151
|
+
if (tables.length === 0) {
|
|
2152
|
+
this.db.exec(`
|
|
2153
|
+
CREATE TABLE IF NOT EXISTS tombstones (
|
|
2154
|
+
id TEXT PRIMARY KEY,
|
|
2155
|
+
memory_id TEXT NOT NULL UNIQUE,
|
|
2156
|
+
org_id TEXT NOT NULL,
|
|
2157
|
+
repo_id TEXT NOT NULL,
|
|
2158
|
+
deleted_at TEXT NOT NULL,
|
|
2159
|
+
deleted_by TEXT,
|
|
2160
|
+
synced_at TEXT,
|
|
2161
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2162
|
+
);
|
|
2163
|
+
CREATE INDEX IF NOT EXISTS idx_tombstones_sync ON tombstones(org_id, repo_id, synced_at);
|
|
2164
|
+
`);
|
|
2165
|
+
}
|
|
2166
|
+
const syncTables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='sync_state'").all();
|
|
2167
|
+
if (syncTables.length === 0) {
|
|
2168
|
+
this.db.exec(`
|
|
2169
|
+
CREATE TABLE IF NOT EXISTS sync_state (
|
|
2170
|
+
memory_id TEXT PRIMARY KEY,
|
|
2171
|
+
local_version INTEGER NOT NULL,
|
|
2172
|
+
remote_version INTEGER,
|
|
2173
|
+
last_pushed_at TEXT,
|
|
2174
|
+
last_pulled_at TEXT,
|
|
2175
|
+
sync_status TEXT NOT NULL DEFAULT 'pending_push' CHECK(sync_status IN ('synced','pending_push','pending_pull','conflict'))
|
|
2176
|
+
);
|
|
2177
|
+
CREATE INDEX IF NOT EXISTS idx_sync_state_status ON sync_state(sync_status);
|
|
2178
|
+
`);
|
|
2179
|
+
}
|
|
2180
|
+
const embeddingTables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_embeddings'").all();
|
|
2181
|
+
if (embeddingTables.length === 0) {
|
|
2182
|
+
this.db.exec(`
|
|
2183
|
+
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
2184
|
+
memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
|
|
2185
|
+
embedding BLOB NOT NULL,
|
|
2186
|
+
model TEXT NOT NULL,
|
|
2187
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2188
|
+
);
|
|
2189
|
+
`);
|
|
2190
|
+
}
|
|
2191
|
+
const usageTables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_usage'").all();
|
|
2192
|
+
if (usageTables.length === 0) {
|
|
2193
|
+
this.db.exec(`
|
|
2194
|
+
CREATE TABLE IF NOT EXISTS memory_usage (
|
|
2195
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2196
|
+
memory_id TEXT NOT NULL,
|
|
2197
|
+
recalled_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2198
|
+
query TEXT,
|
|
2199
|
+
session_id TEXT
|
|
2200
|
+
);
|
|
2201
|
+
CREATE INDEX IF NOT EXISTS idx_memory_usage_memory ON memory_usage(memory_id);
|
|
2202
|
+
CREATE INDEX IF NOT EXISTS idx_memory_usage_recalled ON memory_usage(recalled_at);
|
|
2203
|
+
`);
|
|
2204
|
+
}
|
|
2205
|
+
const curationSuggestionTables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='curation_suggestions'").all();
|
|
2206
|
+
if (curationSuggestionTables.length === 0) {
|
|
2207
|
+
this.db.exec(`
|
|
2208
|
+
CREATE TABLE IF NOT EXISTS curation_suggestions (
|
|
2209
|
+
id TEXT PRIMARY KEY,
|
|
2210
|
+
org_id TEXT NOT NULL,
|
|
2211
|
+
repo_id TEXT NOT NULL,
|
|
2212
|
+
type TEXT NOT NULL CHECK(type IN ('consolidate','deprecate','delete','add_tags','add_links','review','promote','generate_embedding')),
|
|
2213
|
+
priority TEXT NOT NULL CHECK(priority IN ('high','medium','low')),
|
|
2214
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','approved','rejected','applied')),
|
|
2215
|
+
memory_ids TEXT NOT NULL DEFAULT '[]',
|
|
2216
|
+
reason TEXT NOT NULL,
|
|
2217
|
+
confidence REAL NOT NULL,
|
|
2218
|
+
payload TEXT,
|
|
2219
|
+
created_by TEXT,
|
|
2220
|
+
reviewed_by TEXT,
|
|
2221
|
+
review_note TEXT,
|
|
2222
|
+
reviewed_at TEXT,
|
|
2223
|
+
applied_at TEXT,
|
|
2224
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2225
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2226
|
+
);
|
|
2227
|
+
CREATE INDEX IF NOT EXISTS idx_curation_suggestions_repo_status ON curation_suggestions(org_id, repo_id, status, created_at);
|
|
2228
|
+
`);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
store(input) {
|
|
2232
|
+
const resolvedInput = applyLifecycleDefaults(input);
|
|
2233
|
+
const id = uuid();
|
|
2234
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2235
|
+
const normalizedOrgId = resolvedInput.orgId.toLowerCase();
|
|
2236
|
+
const normalizedRepoId = resolvedInput.repoId.toLowerCase();
|
|
2237
|
+
const visibility = resolvedInput.visibility === "auto" || !resolvedInput.visibility ? "private" : resolvedInput.visibility;
|
|
2238
|
+
this.db.prepare(
|
|
2239
|
+
`INSERT INTO memories
|
|
2240
|
+
(id, org_id, repo_id, scope_type, memory_type, visibility, status, text, summary, tags, source_refs, confidence, ttl_seconds, author_id, author_name, created_at, updated_at)
|
|
2241
|
+
VALUES (?, ?, ?, 'repo', ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2242
|
+
).run(
|
|
2243
|
+
id,
|
|
2244
|
+
normalizedOrgId,
|
|
2245
|
+
normalizedRepoId,
|
|
2246
|
+
resolvedInput.memoryType,
|
|
2247
|
+
visibility,
|
|
2248
|
+
resolvedInput.text,
|
|
2249
|
+
resolvedInput.summary ?? null,
|
|
2250
|
+
JSON.stringify(resolvedInput.tags ?? []),
|
|
2251
|
+
resolvedInput.sourceRefs ? JSON.stringify(resolvedInput.sourceRefs) : null,
|
|
2252
|
+
resolvedInput.confidence ?? null,
|
|
2253
|
+
resolvedInput.ttlSeconds ?? null,
|
|
2254
|
+
resolvedInput.authorId ?? null,
|
|
2255
|
+
resolvedInput.authorName ?? null,
|
|
2256
|
+
now,
|
|
2257
|
+
now
|
|
2258
|
+
);
|
|
2259
|
+
this.setSyncState({
|
|
2260
|
+
memoryId: id,
|
|
2261
|
+
localVersion: 1,
|
|
2262
|
+
syncStatus: "pending_push"
|
|
2263
|
+
});
|
|
2264
|
+
return this.getById(id);
|
|
2265
|
+
}
|
|
2266
|
+
getById(id) {
|
|
2267
|
+
const row = this.db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
|
|
2268
|
+
return row ? rowToMemory(row) : void 0;
|
|
2269
|
+
}
|
|
2270
|
+
recall(query) {
|
|
2271
|
+
const conditions = ["m.org_id = ?", "m.repo_id = ?"];
|
|
2272
|
+
const params = [query.orgId, query.repoId];
|
|
2273
|
+
if (!query.includeDeprecated) {
|
|
2274
|
+
conditions.push("m.status = 'active'");
|
|
2275
|
+
}
|
|
2276
|
+
if (!query.includeExpired) {
|
|
2277
|
+
conditions.push(nonExpiredMemoryClause("m"));
|
|
2278
|
+
}
|
|
2279
|
+
if (query.types && query.types.length > 0) {
|
|
2280
|
+
conditions.push(
|
|
2281
|
+
`m.memory_type IN (${query.types.map(() => "?").join(",")})`
|
|
2282
|
+
);
|
|
2283
|
+
params.push(...query.types);
|
|
2284
|
+
}
|
|
2285
|
+
if (query.timeRange?.from) {
|
|
2286
|
+
conditions.push("m.created_at >= ?");
|
|
2287
|
+
params.push(query.timeRange.from.toISOString());
|
|
2288
|
+
}
|
|
2289
|
+
if (query.timeRange?.to) {
|
|
2290
|
+
conditions.push("m.created_at <= ?");
|
|
2291
|
+
params.push(query.timeRange.to.toISOString());
|
|
2292
|
+
}
|
|
2293
|
+
const whereClause = conditions.join(" AND ");
|
|
2294
|
+
const k = query.k ?? 10;
|
|
2295
|
+
const rawQuery = query.query.replace(/[^\w\s]/g, " ").trim();
|
|
2296
|
+
const words = rawQuery.split(/\s+/).filter((w) => w.length > 0);
|
|
2297
|
+
const searchableWords = words.filter((w) => w.length >= 2);
|
|
2298
|
+
const ftsQuery = searchableWords.length > 0 ? `(${searchableWords.map((w) => `${w}*`).join(" OR ")})` : "";
|
|
2299
|
+
let sql;
|
|
2300
|
+
let finalParams;
|
|
2301
|
+
if (ftsQuery) {
|
|
2302
|
+
sql = `
|
|
2303
|
+
SELECT m.*, fts.rank AS fts_rank
|
|
2304
|
+
FROM memories_fts fts
|
|
2305
|
+
JOIN memories m ON m.rowid = fts.rowid
|
|
2306
|
+
WHERE fts.memories_fts MATCH ?
|
|
2307
|
+
AND ${whereClause}
|
|
2308
|
+
ORDER BY m.is_consolidation DESC, fts.rank
|
|
2309
|
+
LIMIT ?
|
|
2310
|
+
`;
|
|
2311
|
+
finalParams = [ftsQuery, ...params, k * 2];
|
|
2312
|
+
} else {
|
|
2313
|
+
sql = `
|
|
2314
|
+
SELECT m.*, 0 AS fts_rank
|
|
2315
|
+
FROM memories m
|
|
2316
|
+
WHERE ${whereClause}
|
|
2317
|
+
ORDER BY m.is_consolidation DESC, m.created_at DESC
|
|
2318
|
+
LIMIT ?
|
|
2319
|
+
`;
|
|
2320
|
+
finalParams = [...params, k * 2];
|
|
2321
|
+
}
|
|
2322
|
+
let rows = this.db.prepare(sql).all(...finalParams);
|
|
2323
|
+
if (rows.length === 0 && searchableWords.length > 0) {
|
|
2324
|
+
const likeConditions = searchableWords.slice(0, 5).map(() => "(m.text LIKE ? OR m.summary LIKE ?)").join(" OR ");
|
|
2325
|
+
const likeParams = [];
|
|
2326
|
+
for (const word of searchableWords.slice(0, 5)) {
|
|
2327
|
+
likeParams.push(`%${word}%`, `%${word}%`);
|
|
2328
|
+
}
|
|
2329
|
+
const fallbackSql = `
|
|
2330
|
+
SELECT m.*, 0 AS fts_rank
|
|
2331
|
+
FROM memories m
|
|
2332
|
+
WHERE ${whereClause}
|
|
2333
|
+
AND (${likeConditions})
|
|
2334
|
+
ORDER BY m.is_consolidation DESC, m.created_at DESC
|
|
2335
|
+
LIMIT ?
|
|
2336
|
+
`;
|
|
2337
|
+
rows = this.db.prepare(fallbackSql).all(...params, ...likeParams, k * 2);
|
|
2338
|
+
}
|
|
2339
|
+
const usageStats = this.getUsageStats(query.orgId, query.repoId);
|
|
2340
|
+
const usageMap = new Map(usageStats.map((stat) => [stat.memoryId, stat]));
|
|
2341
|
+
let results = rows.map((row) => {
|
|
2342
|
+
const memory = rowToMemory(row);
|
|
2343
|
+
const textScore = ftsQuery ? Math.min(1, Math.abs(row.fts_rank) / 10) : 0.5;
|
|
2344
|
+
const consolidationBoost = memory.isConsolidation ? 0.1 : 0;
|
|
2345
|
+
const usage = usageMap.get(memory.id);
|
|
2346
|
+
const usageBoost = computeUsageBoost(
|
|
2347
|
+
usage?.count ?? 0,
|
|
2348
|
+
usage?.lastUsed
|
|
2349
|
+
);
|
|
2350
|
+
const result = {
|
|
2351
|
+
id: memory.id,
|
|
2352
|
+
memoryType: memory.memoryType,
|
|
2353
|
+
text: memory.text,
|
|
2354
|
+
summary: memory.summary,
|
|
2355
|
+
tags: memory.tags,
|
|
2356
|
+
sourceRefs: memory.sourceRefs,
|
|
2357
|
+
score: computeCompositeScore(
|
|
2358
|
+
textScore + consolidationBoost,
|
|
2359
|
+
memory.createdAt,
|
|
2360
|
+
memory.confidence,
|
|
2361
|
+
usageBoost
|
|
2362
|
+
),
|
|
2363
|
+
source: "local",
|
|
2364
|
+
status: memory.status,
|
|
2365
|
+
supersedesId: memory.supersedesId,
|
|
2366
|
+
isConsolidation: memory.isConsolidation,
|
|
2367
|
+
consolidationVersion: memory.consolidationVersion
|
|
2368
|
+
};
|
|
2369
|
+
if (query.includeConsolidatedSources && memory.isConsolidation) {
|
|
2370
|
+
const sources = this.getConsolidatedSources(memory.id);
|
|
2371
|
+
result.sourceMemories = sources.map((src) => ({
|
|
2372
|
+
id: src.id,
|
|
2373
|
+
memoryType: src.memoryType,
|
|
2374
|
+
text: src.text,
|
|
2375
|
+
summary: src.summary,
|
|
2376
|
+
tags: src.tags,
|
|
2377
|
+
sourceRefs: src.sourceRefs,
|
|
2378
|
+
score: 0,
|
|
2379
|
+
source: "local"
|
|
2380
|
+
}));
|
|
2381
|
+
}
|
|
2382
|
+
return result;
|
|
2383
|
+
});
|
|
2384
|
+
if (query.tags && query.tags.length > 0) {
|
|
2385
|
+
results = results.filter((r) => {
|
|
2386
|
+
const memTags = r.tags;
|
|
2387
|
+
return query.tags.some((t) => memTags.includes(t));
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
return results.sort((a, b) => b.score - a.score).slice(0, k);
|
|
2391
|
+
}
|
|
2392
|
+
list(query) {
|
|
2393
|
+
const conditions = ["org_id = ?", "repo_id = ?"];
|
|
2394
|
+
const params = [query.orgId, query.repoId];
|
|
2395
|
+
if (!query.includeExpired) {
|
|
2396
|
+
conditions.push(nonExpiredMemoryClause());
|
|
2397
|
+
}
|
|
2398
|
+
if (query.types && query.types.length > 0) {
|
|
2399
|
+
conditions.push(
|
|
2400
|
+
`memory_type IN (${query.types.map(() => "?").join(",")})`
|
|
2401
|
+
);
|
|
2402
|
+
params.push(...query.types);
|
|
2403
|
+
}
|
|
2404
|
+
if (query.status && query.status.length > 0) {
|
|
2405
|
+
conditions.push(
|
|
2406
|
+
`status IN (${query.status.map(() => "?").join(",")})`
|
|
2407
|
+
);
|
|
2408
|
+
params.push(...query.status);
|
|
2409
|
+
}
|
|
2410
|
+
if (query.visibility && query.visibility.length > 0) {
|
|
2411
|
+
conditions.push(
|
|
2412
|
+
`visibility IN (${query.visibility.map(() => "?").join(",")})`
|
|
2413
|
+
);
|
|
2414
|
+
params.push(...query.visibility);
|
|
2415
|
+
}
|
|
2416
|
+
if (query.search) {
|
|
2417
|
+
const rawSearch = query.search.replace(/[^\w\s]/g, " ").trim();
|
|
2418
|
+
const searchWords = rawSearch.split(/\s+/).filter((w) => w.length >= 2);
|
|
2419
|
+
const ftsSearch = searchWords.length > 0 ? `(${searchWords.map((w) => `${w}*`).join(" OR ")})` : "";
|
|
2420
|
+
if (ftsSearch) {
|
|
2421
|
+
conditions.push(
|
|
2422
|
+
"rowid IN (SELECT rowid FROM memories_fts WHERE memories_fts MATCH ?)"
|
|
2423
|
+
);
|
|
2424
|
+
params.push(ftsSearch);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
const sortCol = query.sortBy === "updatedAt" ? "updated_at" : query.sortBy === "confidence" ? "confidence" : "created_at";
|
|
2428
|
+
const sortDir = query.sortOrder === "asc" ? "ASC" : "DESC";
|
|
2429
|
+
const limit = query.limit ?? 50;
|
|
2430
|
+
const offset = query.offset ?? 0;
|
|
2431
|
+
const sql = `
|
|
2432
|
+
SELECT * FROM memories
|
|
2433
|
+
WHERE ${conditions.join(" AND ")}
|
|
2434
|
+
ORDER BY ${sortCol} ${sortDir}
|
|
2435
|
+
LIMIT ? OFFSET ?
|
|
2436
|
+
`;
|
|
2437
|
+
params.push(limit, offset);
|
|
2438
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
2439
|
+
return rows.map(rowToMemory);
|
|
2440
|
+
}
|
|
2441
|
+
count(query) {
|
|
2442
|
+
const conditions = ["org_id = ?", "repo_id = ?"];
|
|
2443
|
+
const params = [query.orgId, query.repoId];
|
|
2444
|
+
if (!query.includeExpired) {
|
|
2445
|
+
conditions.push(nonExpiredMemoryClause());
|
|
2446
|
+
}
|
|
2447
|
+
if (query.types && query.types.length > 0) {
|
|
2448
|
+
conditions.push(
|
|
2449
|
+
`memory_type IN (${query.types.map(() => "?").join(",")})`
|
|
2450
|
+
);
|
|
2451
|
+
params.push(...query.types);
|
|
2452
|
+
}
|
|
2453
|
+
if (query.status && query.status.length > 0) {
|
|
2454
|
+
conditions.push(
|
|
2455
|
+
`status IN (${query.status.map(() => "?").join(",")})`
|
|
2456
|
+
);
|
|
2457
|
+
params.push(...query.status);
|
|
2458
|
+
}
|
|
2459
|
+
if (query.visibility && query.visibility.length > 0) {
|
|
2460
|
+
conditions.push(
|
|
2461
|
+
`visibility IN (${query.visibility.map(() => "?").join(",")})`
|
|
2462
|
+
);
|
|
2463
|
+
params.push(...query.visibility);
|
|
2464
|
+
}
|
|
2465
|
+
if (query.search) {
|
|
2466
|
+
const rawSearch = query.search.replace(/[^\w\s]/g, " ").trim();
|
|
2467
|
+
const searchWords = rawSearch.split(/\s+/).filter((w) => w.length >= 2);
|
|
2468
|
+
const ftsSearch = searchWords.length > 0 ? `(${searchWords.map((w) => `${w}*`).join(" OR ")})` : "";
|
|
2469
|
+
if (ftsSearch) {
|
|
2470
|
+
conditions.push(
|
|
2471
|
+
"rowid IN (SELECT rowid FROM memories_fts WHERE memories_fts MATCH ?)"
|
|
2472
|
+
);
|
|
2473
|
+
params.push(ftsSearch);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
const sql = `SELECT COUNT(*) as cnt FROM memories WHERE ${conditions.join(" AND ")}`;
|
|
2477
|
+
const row = this.db.prepare(sql).get(...params);
|
|
2478
|
+
return row.cnt;
|
|
2479
|
+
}
|
|
2480
|
+
stats(orgId, repoId) {
|
|
2481
|
+
const rows = this.db.prepare(
|
|
2482
|
+
`SELECT memory_type, status, visibility, COUNT(*) as cnt
|
|
2483
|
+
FROM memories WHERE org_id = ? AND repo_id = ?
|
|
2484
|
+
GROUP BY memory_type, status, visibility`
|
|
2485
|
+
).all(orgId, repoId);
|
|
2486
|
+
const stats = {
|
|
2487
|
+
total: 0,
|
|
2488
|
+
byType: { episodic: 0, semantic: 0, procedural: 0 },
|
|
2489
|
+
byStatus: { active: 0, deprecated: 0, superseded: 0, deleted: 0 },
|
|
2490
|
+
byVisibility: { private: 0, repo: 0 }
|
|
2491
|
+
};
|
|
2492
|
+
for (const row of rows) {
|
|
2493
|
+
stats.total += row.cnt;
|
|
2494
|
+
if (row.memory_type in stats.byType) {
|
|
2495
|
+
stats.byType[row.memory_type] += row.cnt;
|
|
2496
|
+
}
|
|
2497
|
+
if (row.status in stats.byStatus) {
|
|
2498
|
+
stats.byStatus[row.status] += row.cnt;
|
|
2499
|
+
}
|
|
2500
|
+
if (row.visibility in stats.byVisibility) {
|
|
2501
|
+
stats.byVisibility[row.visibility] += row.cnt;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return stats;
|
|
2505
|
+
}
|
|
2506
|
+
deprecate(id, reason) {
|
|
2507
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2508
|
+
const result = this.db.prepare(
|
|
2509
|
+
"UPDATE memories SET status = 'deprecated', updated_at = ? WHERE id = ?"
|
|
2510
|
+
).run(now, id);
|
|
2511
|
+
if (reason && result.changes > 0) {
|
|
2512
|
+
const mem = this.getById(id);
|
|
2513
|
+
if (mem) {
|
|
2514
|
+
const refs = mem.sourceRefs ?? {};
|
|
2515
|
+
refs.deprecation_reason = reason;
|
|
2516
|
+
this.db.prepare("UPDATE memories SET source_refs = ? WHERE id = ?").run(JSON.stringify(refs), id);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
return result.changes > 0;
|
|
2520
|
+
}
|
|
2521
|
+
supersede(oldId, newId) {
|
|
2522
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2523
|
+
const result = this.db.prepare(
|
|
2524
|
+
"UPDATE memories SET status = 'superseded', supersedes_id = ?, updated_at = ? WHERE id = ?"
|
|
2525
|
+
).run(newId, now, oldId);
|
|
2526
|
+
return result.changes > 0;
|
|
2527
|
+
}
|
|
2528
|
+
updateVisibility(id, visibility) {
|
|
2529
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2530
|
+
const result = this.db.prepare(
|
|
2531
|
+
"UPDATE memories SET visibility = ?, updated_at = ? WHERE id = ?"
|
|
2532
|
+
).run(visibility, now, id);
|
|
2533
|
+
return result.changes > 0;
|
|
2534
|
+
}
|
|
2535
|
+
expireExpiredMemories(orgId, repoId, deletedBy = "system:ttl-expiry") {
|
|
2536
|
+
const conditions = [
|
|
2537
|
+
"status = 'active'",
|
|
2538
|
+
"ttl_seconds IS NOT NULL",
|
|
2539
|
+
"datetime(created_at, '+' || ttl_seconds || ' seconds') < datetime('now')"
|
|
2540
|
+
];
|
|
2541
|
+
const params = [];
|
|
2542
|
+
if (orgId) {
|
|
2543
|
+
conditions.push("org_id = ?");
|
|
2544
|
+
params.push(orgId);
|
|
2545
|
+
}
|
|
2546
|
+
if (repoId) {
|
|
2547
|
+
conditions.push("repo_id = ?");
|
|
2548
|
+
params.push(repoId);
|
|
2549
|
+
}
|
|
2550
|
+
const rows = this.db.prepare(
|
|
2551
|
+
`SELECT id FROM memories WHERE ${conditions.join(" AND ")}`
|
|
2552
|
+
).all(...params);
|
|
2553
|
+
let expired = 0;
|
|
2554
|
+
for (const row of rows) {
|
|
2555
|
+
if (this.softDelete({ id: row.id, deletedBy })) {
|
|
2556
|
+
expired += 1;
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
return expired;
|
|
2560
|
+
}
|
|
2561
|
+
purgeExpired() {
|
|
2562
|
+
return this.expireExpiredMemories();
|
|
2563
|
+
}
|
|
2564
|
+
link(input) {
|
|
2565
|
+
const id = uuid();
|
|
2566
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2567
|
+
this.db.prepare(
|
|
2568
|
+
`INSERT INTO memory_links (id, source_id, target_id, link_type, metadata, created_at)
|
|
2569
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
2570
|
+
).run(
|
|
2571
|
+
id,
|
|
2572
|
+
input.sourceId,
|
|
2573
|
+
input.targetId,
|
|
2574
|
+
input.linkType,
|
|
2575
|
+
input.metadata ? JSON.stringify(input.metadata) : null,
|
|
2576
|
+
now
|
|
2577
|
+
);
|
|
2578
|
+
return this.getLinkById(id);
|
|
2579
|
+
}
|
|
2580
|
+
unlink(sourceId, targetId, linkType) {
|
|
2581
|
+
const result = this.db.prepare(
|
|
2582
|
+
"DELETE FROM memory_links WHERE source_id = ? AND target_id = ? AND link_type = ?"
|
|
2583
|
+
).run(sourceId, targetId, linkType);
|
|
2584
|
+
return result.changes > 0;
|
|
2585
|
+
}
|
|
2586
|
+
getLinks(query) {
|
|
2587
|
+
const conditions = [
|
|
2588
|
+
"(source_id = ? OR target_id = ?)"
|
|
2589
|
+
];
|
|
2590
|
+
const params = [query.memoryId, query.memoryId];
|
|
2591
|
+
if (query.linkType) {
|
|
2592
|
+
conditions.push("link_type = ?");
|
|
2593
|
+
params.push(query.linkType);
|
|
2594
|
+
}
|
|
2595
|
+
const sql = `SELECT * FROM memory_links WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC`;
|
|
2596
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
2597
|
+
return rows.map(rowToLink);
|
|
2598
|
+
}
|
|
2599
|
+
getLinkedMemories(memoryId, linkType) {
|
|
2600
|
+
const conditions = [
|
|
2601
|
+
"(l.source_id = ? OR l.target_id = ?)"
|
|
2602
|
+
];
|
|
2603
|
+
const params = [memoryId, memoryId];
|
|
2604
|
+
if (linkType) {
|
|
2605
|
+
conditions.push("l.link_type = ?");
|
|
2606
|
+
params.push(linkType);
|
|
2607
|
+
}
|
|
2608
|
+
const sql = `
|
|
2609
|
+
SELECT m.* FROM memories m
|
|
2610
|
+
JOIN memory_links l ON (
|
|
2611
|
+
(l.source_id = ? AND l.target_id = m.id) OR
|
|
2612
|
+
(l.target_id = ? AND l.source_id = m.id)
|
|
2613
|
+
)
|
|
2614
|
+
${linkType ? "WHERE l.link_type = ?" : ""}
|
|
2615
|
+
ORDER BY m.created_at DESC
|
|
2616
|
+
`;
|
|
2617
|
+
const linkedParams = linkType ? [memoryId, memoryId, linkType] : [memoryId, memoryId];
|
|
2618
|
+
const rows = this.db.prepare(sql).all(...linkedParams);
|
|
2619
|
+
return rows.map(rowToMemory);
|
|
2620
|
+
}
|
|
2621
|
+
getLinkById(id) {
|
|
2622
|
+
const row = this.db.prepare("SELECT * FROM memory_links WHERE id = ?").get(id);
|
|
2623
|
+
return row ? rowToLink(row) : void 0;
|
|
2624
|
+
}
|
|
2625
|
+
consolidateMemories(input) {
|
|
2626
|
+
const { sourceIds, consolidatedText, memoryType, tags, preserveOriginals = true } = input;
|
|
2627
|
+
const orgId = input.orgId.toLowerCase();
|
|
2628
|
+
const repoId = input.repoId.toLowerCase();
|
|
2629
|
+
if (sourceIds.length < 2) {
|
|
2630
|
+
throw new Error("At least 2 source memories are required for consolidation");
|
|
2631
|
+
}
|
|
2632
|
+
const sourceMemories = sourceIds.map((id2) => this.getById(id2)).filter((m) => m !== void 0);
|
|
2633
|
+
if (sourceMemories.length !== sourceIds.length) {
|
|
2634
|
+
const foundIds = sourceMemories.map((m) => m.id);
|
|
2635
|
+
const missingIds = sourceIds.filter((id2) => !foundIds.includes(id2));
|
|
2636
|
+
throw new Error(`Source memories not found: ${missingIds.join(", ")}`);
|
|
2637
|
+
}
|
|
2638
|
+
const inferredType = memoryType ?? this.inferMemoryType(sourceMemories);
|
|
2639
|
+
const mergedTags = tags ?? this.mergeTags(sourceMemories);
|
|
2640
|
+
const inheritedVisibility = sourceMemories.some((m) => m.visibility === "repo") ? "repo" : "private";
|
|
2641
|
+
const id = uuid();
|
|
2642
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2643
|
+
const version = 1;
|
|
2644
|
+
this.db.prepare(
|
|
2645
|
+
`INSERT INTO memories
|
|
2646
|
+
(id, org_id, repo_id, scope_type, memory_type, visibility, status, text, summary, tags, source_refs, confidence, is_consolidation, consolidation_version, created_at, updated_at)
|
|
2647
|
+
VALUES (?, ?, ?, 'repo', ?, ?, 'active', ?, ?, ?, ?, ?, 1, ?, ?, ?)`
|
|
2648
|
+
).run(
|
|
2649
|
+
id,
|
|
2650
|
+
orgId,
|
|
2651
|
+
repoId,
|
|
2652
|
+
inferredType,
|
|
2653
|
+
inheritedVisibility,
|
|
2654
|
+
consolidatedText,
|
|
2655
|
+
null,
|
|
2656
|
+
JSON.stringify(mergedTags),
|
|
2657
|
+
JSON.stringify({ consolidated_from: sourceIds }),
|
|
2658
|
+
null,
|
|
2659
|
+
version,
|
|
2660
|
+
now,
|
|
2661
|
+
now
|
|
2662
|
+
);
|
|
2663
|
+
for (const sourceId of sourceIds) {
|
|
2664
|
+
this.link({
|
|
2665
|
+
sourceId: id,
|
|
2666
|
+
targetId: sourceId,
|
|
2667
|
+
linkType: "derived_from",
|
|
2668
|
+
metadata: { consolidation: true }
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
if (preserveOriginals) {
|
|
2672
|
+
for (const sourceId of sourceIds) {
|
|
2673
|
+
this.supersede(sourceId, id);
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
if (inheritedVisibility === "repo") {
|
|
2677
|
+
this.setSyncState({
|
|
2678
|
+
memoryId: id,
|
|
2679
|
+
localVersion: version,
|
|
2680
|
+
syncStatus: "pending_push"
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
return {
|
|
2684
|
+
consolidatedId: id,
|
|
2685
|
+
version,
|
|
2686
|
+
sourcesPreserved: sourceIds.length,
|
|
2687
|
+
sourceIds
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
reconsolidate(input) {
|
|
2691
|
+
const { existingConsolidationId, additionalSourceIds = [], newText, tags } = input;
|
|
2692
|
+
const orgId = input.orgId.toLowerCase();
|
|
2693
|
+
const repoId = input.repoId.toLowerCase();
|
|
2694
|
+
const existing = this.getById(existingConsolidationId);
|
|
2695
|
+
if (!existing) {
|
|
2696
|
+
throw new Error(`Consolidation not found: ${existingConsolidationId}`);
|
|
2697
|
+
}
|
|
2698
|
+
if (!existing.isConsolidation) {
|
|
2699
|
+
throw new Error(`Memory ${existingConsolidationId} is not a consolidation`);
|
|
2700
|
+
}
|
|
2701
|
+
const existingLinks = this.getLinks({ memoryId: existingConsolidationId, linkType: "derived_from" });
|
|
2702
|
+
const existingSourceIds = existingLinks.filter((l) => l.sourceId === existingConsolidationId).map((l) => l.targetId);
|
|
2703
|
+
const allSourceIds = [.../* @__PURE__ */ new Set([...existingSourceIds, ...additionalSourceIds])];
|
|
2704
|
+
for (const id2 of additionalSourceIds) {
|
|
2705
|
+
const mem = this.getById(id2);
|
|
2706
|
+
if (!mem) {
|
|
2707
|
+
throw new Error(`Additional source memory not found: ${id2}`);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
const newVersion = (existing.consolidationVersion ?? 1) + 1;
|
|
2711
|
+
const mergedTags = tags ?? existing.tags;
|
|
2712
|
+
const inheritedVisibility = existing.visibility;
|
|
2713
|
+
const id = uuid();
|
|
2714
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2715
|
+
this.db.prepare(
|
|
2716
|
+
`INSERT INTO memories
|
|
2717
|
+
(id, org_id, repo_id, scope_type, memory_type, visibility, status, text, summary, tags, source_refs, confidence, is_consolidation, consolidation_version, created_at, updated_at)
|
|
2718
|
+
VALUES (?, ?, ?, 'repo', ?, ?, 'active', ?, ?, ?, ?, ?, 1, ?, ?, ?)`
|
|
2719
|
+
).run(
|
|
2720
|
+
id,
|
|
2721
|
+
orgId,
|
|
2722
|
+
repoId,
|
|
2723
|
+
existing.memoryType,
|
|
2724
|
+
inheritedVisibility,
|
|
2725
|
+
newText,
|
|
2726
|
+
null,
|
|
2727
|
+
JSON.stringify(mergedTags),
|
|
2728
|
+
JSON.stringify({
|
|
2729
|
+
consolidated_from: allSourceIds,
|
|
2730
|
+
previous_consolidation: existingConsolidationId
|
|
2731
|
+
}),
|
|
2732
|
+
null,
|
|
2733
|
+
newVersion,
|
|
2734
|
+
now,
|
|
2735
|
+
now
|
|
2736
|
+
);
|
|
2737
|
+
this.link({
|
|
2738
|
+
sourceId: id,
|
|
2739
|
+
targetId: existingConsolidationId,
|
|
2740
|
+
linkType: "derived_from",
|
|
2741
|
+
metadata: { reconsolidation: true, previous_version: existing.consolidationVersion ?? 1 }
|
|
2742
|
+
});
|
|
2743
|
+
for (const sourceId of additionalSourceIds) {
|
|
2744
|
+
this.link({
|
|
2745
|
+
sourceId: id,
|
|
2746
|
+
targetId: sourceId,
|
|
2747
|
+
linkType: "derived_from",
|
|
2748
|
+
metadata: { consolidation: true }
|
|
2749
|
+
});
|
|
2750
|
+
this.supersede(sourceId, id);
|
|
2751
|
+
}
|
|
2752
|
+
this.supersede(existingConsolidationId, id);
|
|
2753
|
+
if (inheritedVisibility === "repo") {
|
|
2754
|
+
this.setSyncState({
|
|
2755
|
+
memoryId: id,
|
|
2756
|
+
localVersion: newVersion,
|
|
2757
|
+
syncStatus: "pending_push"
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
return {
|
|
2761
|
+
consolidatedId: id,
|
|
2762
|
+
version: newVersion,
|
|
2763
|
+
sourcesPreserved: allSourceIds.length,
|
|
2764
|
+
sourceIds: allSourceIds
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
findSimilar(query) {
|
|
2768
|
+
const { orgId, repoId, memoryId, threshold = 0.3, k = 10 } = query;
|
|
2769
|
+
const memory = this.getById(memoryId);
|
|
2770
|
+
if (!memory) {
|
|
2771
|
+
throw new Error(`Memory not found: ${memoryId}`);
|
|
2772
|
+
}
|
|
2773
|
+
const results = this.recall({
|
|
2774
|
+
orgId,
|
|
2775
|
+
repoId,
|
|
2776
|
+
query: memory.text,
|
|
2777
|
+
k: k + 1
|
|
2778
|
+
});
|
|
2779
|
+
return results.filter((r) => r.id !== memoryId && r.score >= threshold).slice(0, k);
|
|
2780
|
+
}
|
|
2781
|
+
getConsolidationHistory(memoryId) {
|
|
2782
|
+
const memory = this.getById(memoryId);
|
|
2783
|
+
if (!memory) {
|
|
2784
|
+
return [];
|
|
2785
|
+
}
|
|
2786
|
+
const history = [];
|
|
2787
|
+
if (memory.isConsolidation) {
|
|
2788
|
+
const sourceLinks = this.getLinks({ memoryId, linkType: "derived_from" });
|
|
2789
|
+
for (const link of sourceLinks) {
|
|
2790
|
+
const targetId = link.sourceId === memoryId ? link.targetId : link.sourceId;
|
|
2791
|
+
const target = this.getById(targetId);
|
|
2792
|
+
if (target) {
|
|
2793
|
+
history.push(target);
|
|
2794
|
+
if (target.isConsolidation) {
|
|
2795
|
+
history.push(...this.getConsolidationHistory(targetId));
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
return history;
|
|
2801
|
+
}
|
|
2802
|
+
getConsolidatedSources(consolidationId) {
|
|
2803
|
+
const memory = this.getById(consolidationId);
|
|
2804
|
+
if (!memory || !memory.isConsolidation) {
|
|
2805
|
+
return [];
|
|
2806
|
+
}
|
|
2807
|
+
const sourceLinks = this.getLinks({ memoryId: consolidationId, linkType: "derived_from" });
|
|
2808
|
+
const sources = [];
|
|
2809
|
+
for (const link of sourceLinks) {
|
|
2810
|
+
if (link.sourceId === consolidationId) {
|
|
2811
|
+
const source = this.getById(link.targetId);
|
|
2812
|
+
if (source && !source.isConsolidation) {
|
|
2813
|
+
sources.push(source);
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
return sources;
|
|
2818
|
+
}
|
|
2819
|
+
inferMemoryType(memories) {
|
|
2820
|
+
const typeCounts = { episodic: 0, semantic: 0, procedural: 0 };
|
|
2821
|
+
for (const m of memories) {
|
|
2822
|
+
typeCounts[m.memoryType]++;
|
|
2823
|
+
}
|
|
2824
|
+
if (typeCounts.procedural > 0) return "procedural";
|
|
2825
|
+
if (typeCounts.semantic >= typeCounts.episodic) return "semantic";
|
|
2826
|
+
return "episodic";
|
|
2827
|
+
}
|
|
2828
|
+
mergeTags(memories) {
|
|
2829
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
2830
|
+
for (const m of memories) {
|
|
2831
|
+
for (const tag of m.tags) {
|
|
2832
|
+
tagSet.add(tag);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
return Array.from(tagSet);
|
|
2836
|
+
}
|
|
2837
|
+
softDelete(input) {
|
|
2838
|
+
const memory = this.getById(input.id);
|
|
2839
|
+
if (!memory) return false;
|
|
2840
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2841
|
+
const newVersion = (memory.version ?? 1) + 1;
|
|
2842
|
+
const result = this.db.transaction(() => {
|
|
2843
|
+
this.db.prepare(
|
|
2844
|
+
`UPDATE memories
|
|
2845
|
+
SET status = 'deleted', deleted_at = ?, deleted_by = ?, version = ?, updated_at = ?
|
|
2846
|
+
WHERE id = ?`
|
|
2847
|
+
).run(now, input.deletedBy ?? null, newVersion, now, input.id);
|
|
2848
|
+
this.db.prepare(
|
|
2849
|
+
`INSERT OR REPLACE INTO tombstones (id, memory_id, org_id, repo_id, deleted_at, deleted_by, created_at)
|
|
2850
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
2851
|
+
).run(
|
|
2852
|
+
uuid(),
|
|
2853
|
+
input.id,
|
|
2854
|
+
memory.orgId,
|
|
2855
|
+
memory.repoId,
|
|
2856
|
+
now,
|
|
2857
|
+
input.deletedBy ?? null,
|
|
2858
|
+
now
|
|
2859
|
+
);
|
|
2860
|
+
return true;
|
|
2861
|
+
})();
|
|
2862
|
+
return result;
|
|
2863
|
+
}
|
|
2864
|
+
hardDelete(id) {
|
|
2865
|
+
const result = this.db.prepare("DELETE FROM memories WHERE id = ?").run(id);
|
|
2866
|
+
return result.changes > 0;
|
|
2867
|
+
}
|
|
2868
|
+
restore(id) {
|
|
2869
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2870
|
+
const result = this.db.transaction(() => {
|
|
2871
|
+
const updateResult = this.db.prepare(
|
|
2872
|
+
`UPDATE memories
|
|
2873
|
+
SET status = 'active', deleted_at = NULL, deleted_by = NULL, version = version + 1, updated_at = ?
|
|
2874
|
+
WHERE id = ? AND status = 'deleted'`
|
|
2875
|
+
).run(now, id);
|
|
2876
|
+
if (updateResult.changes > 0) {
|
|
2877
|
+
this.db.prepare("DELETE FROM tombstones WHERE memory_id = ?").run(id);
|
|
2878
|
+
}
|
|
2879
|
+
return updateResult.changes > 0;
|
|
2880
|
+
})();
|
|
2881
|
+
return result;
|
|
2882
|
+
}
|
|
2883
|
+
getTombstones(orgId, repoId, sinceSyncedAt) {
|
|
2884
|
+
let sql = "SELECT * FROM tombstones WHERE org_id = ? AND repo_id = ?";
|
|
2885
|
+
const params = [orgId, repoId];
|
|
2886
|
+
if (sinceSyncedAt) {
|
|
2887
|
+
sql += " AND (synced_at IS NULL OR synced_at > ?)";
|
|
2888
|
+
params.push(sinceSyncedAt.toISOString());
|
|
2889
|
+
} else {
|
|
2890
|
+
sql += " AND synced_at IS NULL";
|
|
2891
|
+
}
|
|
2892
|
+
sql += " ORDER BY deleted_at ASC";
|
|
2893
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
2894
|
+
return rows.map(rowToTombstone);
|
|
2895
|
+
}
|
|
2896
|
+
getUnsyncedTombstones(orgId, repoId) {
|
|
2897
|
+
const rows = this.db.prepare(
|
|
2898
|
+
"SELECT * FROM tombstones WHERE org_id = ? AND repo_id = ? AND synced_at IS NULL ORDER BY deleted_at ASC"
|
|
2899
|
+
).all(orgId, repoId);
|
|
2900
|
+
return rows.map(rowToTombstone);
|
|
2901
|
+
}
|
|
2902
|
+
markTombstoneSynced(memoryId) {
|
|
2903
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2904
|
+
const result = this.db.prepare("UPDATE tombstones SET synced_at = ? WHERE memory_id = ?").run(now, memoryId);
|
|
2905
|
+
return result.changes > 0;
|
|
2906
|
+
}
|
|
2907
|
+
applyTombstone(tombstone) {
|
|
2908
|
+
const memory = this.getById(tombstone.memoryId);
|
|
2909
|
+
if (!memory) {
|
|
2910
|
+
this.db.prepare(
|
|
2911
|
+
`INSERT OR REPLACE INTO tombstones (id, memory_id, org_id, repo_id, deleted_at, deleted_by, synced_at, created_at)
|
|
2912
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2913
|
+
).run(
|
|
2914
|
+
tombstone.id,
|
|
2915
|
+
tombstone.memoryId,
|
|
2916
|
+
tombstone.orgId,
|
|
2917
|
+
tombstone.repoId,
|
|
2918
|
+
tombstone.deletedAt.toISOString(),
|
|
2919
|
+
tombstone.deletedBy ?? null,
|
|
2920
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2921
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
2922
|
+
);
|
|
2923
|
+
return true;
|
|
2924
|
+
}
|
|
2925
|
+
return this.softDelete({
|
|
2926
|
+
id: tombstone.memoryId,
|
|
2927
|
+
deletedBy: tombstone.deletedBy
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
incrementVersion(id) {
|
|
2931
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2932
|
+
this.db.prepare("UPDATE memories SET version = version + 1, updated_at = ? WHERE id = ?").run(now, id);
|
|
2933
|
+
const row = this.db.prepare("SELECT version FROM memories WHERE id = ?").get(id);
|
|
2934
|
+
return row?.version ?? 1;
|
|
2935
|
+
}
|
|
2936
|
+
getModifiedSince(orgId, repoId, since) {
|
|
2937
|
+
const rows = this.db.prepare(
|
|
2938
|
+
`SELECT * FROM memories
|
|
2939
|
+
WHERE org_id = ? AND repo_id = ? AND updated_at > ?
|
|
2940
|
+
ORDER BY updated_at ASC`
|
|
2941
|
+
).all(orgId, repoId, since.toISOString());
|
|
2942
|
+
return rows.map(rowToMemory);
|
|
2943
|
+
}
|
|
2944
|
+
upsertFromRemote(memory) {
|
|
2945
|
+
const existing = this.getById(memory.id);
|
|
2946
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2947
|
+
const normalizedOrgId = memory.orgId.toLowerCase();
|
|
2948
|
+
const normalizedRepoId = memory.repoId.toLowerCase();
|
|
2949
|
+
if (!existing) {
|
|
2950
|
+
this.db.prepare(
|
|
2951
|
+
`INSERT INTO memories
|
|
2952
|
+
(id, org_id, repo_id, scope_type, memory_type, visibility, status, text, summary, tags, source_refs, confidence, ttl_seconds, supersedes_id, is_consolidation, consolidation_version, author_id, author_name, version, deleted_at, deleted_by, created_at, updated_at)
|
|
2953
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2954
|
+
).run(
|
|
2955
|
+
memory.id,
|
|
2956
|
+
normalizedOrgId,
|
|
2957
|
+
normalizedRepoId,
|
|
2958
|
+
memory.scopeType ?? "repo",
|
|
2959
|
+
memory.memoryType,
|
|
2960
|
+
memory.visibility,
|
|
2961
|
+
memory.status,
|
|
2962
|
+
memory.text,
|
|
2963
|
+
memory.summary ?? null,
|
|
2964
|
+
JSON.stringify(memory.tags ?? []),
|
|
2965
|
+
memory.sourceRefs ? JSON.stringify(memory.sourceRefs) : null,
|
|
2966
|
+
memory.confidence ?? null,
|
|
2967
|
+
memory.ttlSeconds ?? null,
|
|
2968
|
+
memory.supersedesId ?? null,
|
|
2969
|
+
memory.isConsolidation ? 1 : 0,
|
|
2970
|
+
memory.consolidationVersion ?? null,
|
|
2971
|
+
memory.authorId ?? null,
|
|
2972
|
+
memory.authorName ?? null,
|
|
2973
|
+
memory.version ?? 1,
|
|
2974
|
+
memory.deletedAt?.toISOString() ?? null,
|
|
2975
|
+
memory.deletedBy ?? null,
|
|
2976
|
+
memory.createdAt.toISOString(),
|
|
2977
|
+
now
|
|
2978
|
+
);
|
|
2979
|
+
return { action: "created", conflict: false };
|
|
2980
|
+
}
|
|
2981
|
+
const remoteVersion = memory.version ?? 1;
|
|
2982
|
+
const localVersion = existing.version ?? 1;
|
|
2983
|
+
if (remoteVersion <= localVersion) {
|
|
2984
|
+
if (memory.updatedAt <= existing.updatedAt) {
|
|
2985
|
+
return { action: "skipped", conflict: false };
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
const hasConflict = localVersion !== remoteVersion && existing.updatedAt > memory.updatedAt;
|
|
2989
|
+
this.db.prepare(
|
|
2990
|
+
`UPDATE memories SET
|
|
2991
|
+
memory_type = ?, visibility = ?, status = ?, text = ?, summary = ?, tags = ?,
|
|
2992
|
+
source_refs = ?, confidence = ?, ttl_seconds = ?, supersedes_id = ?,
|
|
2993
|
+
is_consolidation = ?, consolidation_version = ?, author_id = ?, author_name = ?,
|
|
2994
|
+
version = ?, deleted_at = ?, deleted_by = ?, updated_at = ?
|
|
2995
|
+
WHERE id = ?`
|
|
2996
|
+
).run(
|
|
2997
|
+
memory.memoryType,
|
|
2998
|
+
memory.visibility,
|
|
2999
|
+
memory.status,
|
|
3000
|
+
memory.text,
|
|
3001
|
+
memory.summary ?? null,
|
|
3002
|
+
JSON.stringify(memory.tags ?? []),
|
|
3003
|
+
memory.sourceRefs ? JSON.stringify(memory.sourceRefs) : null,
|
|
3004
|
+
memory.confidence ?? null,
|
|
3005
|
+
memory.ttlSeconds ?? null,
|
|
3006
|
+
memory.supersedesId ?? null,
|
|
3007
|
+
memory.isConsolidation ? 1 : 0,
|
|
3008
|
+
memory.consolidationVersion ?? null,
|
|
3009
|
+
memory.authorId ?? null,
|
|
3010
|
+
memory.authorName ?? null,
|
|
3011
|
+
Math.max(remoteVersion, localVersion) + 1,
|
|
3012
|
+
memory.deletedAt?.toISOString() ?? null,
|
|
3013
|
+
memory.deletedBy ?? null,
|
|
3014
|
+
now,
|
|
3015
|
+
memory.id
|
|
3016
|
+
);
|
|
3017
|
+
return { action: "updated", conflict: hasConflict };
|
|
3018
|
+
}
|
|
3019
|
+
getSyncState(memoryId) {
|
|
3020
|
+
const row = this.db.prepare("SELECT * FROM sync_state WHERE memory_id = ?").get(memoryId);
|
|
3021
|
+
return row ? this.rowToSyncState(row) : void 0;
|
|
3022
|
+
}
|
|
3023
|
+
getAllSyncStates() {
|
|
3024
|
+
const rows = this.db.prepare("SELECT * FROM sync_state").all();
|
|
3025
|
+
return rows.map((row) => this.rowToSyncState(row));
|
|
3026
|
+
}
|
|
3027
|
+
getSyncStatesByStatus(status) {
|
|
3028
|
+
const rows = this.db.prepare("SELECT * FROM sync_state WHERE sync_status = ?").all(status);
|
|
3029
|
+
return rows.map((row) => this.rowToSyncState(row));
|
|
3030
|
+
}
|
|
3031
|
+
getPendingPush() {
|
|
3032
|
+
const rows = this.db.prepare(`
|
|
3033
|
+
SELECT m.*, s.local_version as sync_local_version, s.remote_version as sync_remote_version,
|
|
3034
|
+
s.last_pushed_at, s.last_pulled_at, s.sync_status
|
|
3035
|
+
FROM memories m
|
|
3036
|
+
JOIN sync_state s ON m.id = s.memory_id
|
|
3037
|
+
WHERE s.sync_status = 'pending_push'
|
|
3038
|
+
ORDER BY m.updated_at ASC
|
|
3039
|
+
`).all();
|
|
3040
|
+
return rows.map((row) => ({
|
|
3041
|
+
memory: rowToMemory(row),
|
|
3042
|
+
syncState: {
|
|
3043
|
+
memoryId: row.id,
|
|
3044
|
+
localVersion: row.sync_local_version,
|
|
3045
|
+
remoteVersion: row.sync_remote_version,
|
|
3046
|
+
lastPushedAt: row.last_pushed_at ? new Date(row.last_pushed_at) : void 0,
|
|
3047
|
+
lastPulledAt: row.last_pulled_at ? new Date(row.last_pulled_at) : void 0,
|
|
3048
|
+
syncStatus: row.sync_status
|
|
3049
|
+
}
|
|
3050
|
+
}));
|
|
3051
|
+
}
|
|
3052
|
+
getConflicts() {
|
|
3053
|
+
const rows = this.db.prepare(`
|
|
3054
|
+
SELECT m.*, s.local_version as sync_local_version, s.remote_version as sync_remote_version,
|
|
3055
|
+
s.last_pushed_at, s.last_pulled_at, s.sync_status
|
|
3056
|
+
FROM memories m
|
|
3057
|
+
JOIN sync_state s ON m.id = s.memory_id
|
|
3058
|
+
WHERE s.sync_status = 'conflict'
|
|
3059
|
+
ORDER BY m.updated_at ASC
|
|
3060
|
+
`).all();
|
|
3061
|
+
return rows.map((row) => ({
|
|
3062
|
+
memory: rowToMemory(row),
|
|
3063
|
+
syncState: {
|
|
3064
|
+
memoryId: row.id,
|
|
3065
|
+
localVersion: row.sync_local_version,
|
|
3066
|
+
remoteVersion: row.sync_remote_version,
|
|
3067
|
+
lastPushedAt: row.last_pushed_at ? new Date(row.last_pushed_at) : void 0,
|
|
3068
|
+
lastPulledAt: row.last_pulled_at ? new Date(row.last_pulled_at) : void 0,
|
|
3069
|
+
syncStatus: row.sync_status
|
|
3070
|
+
}
|
|
3071
|
+
}));
|
|
3072
|
+
}
|
|
3073
|
+
setSyncState(state) {
|
|
3074
|
+
this.db.prepare(`
|
|
3075
|
+
INSERT OR REPLACE INTO sync_state (memory_id, local_version, remote_version, last_pushed_at, last_pulled_at, sync_status)
|
|
3076
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
3077
|
+
`).run(
|
|
3078
|
+
state.memoryId,
|
|
3079
|
+
state.localVersion,
|
|
3080
|
+
state.remoteVersion ?? null,
|
|
3081
|
+
state.lastPushedAt?.toISOString() ?? null,
|
|
3082
|
+
state.lastPulledAt?.toISOString() ?? null,
|
|
3083
|
+
state.syncStatus
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
markAsPushed(memoryId, remoteVersion) {
|
|
3087
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3088
|
+
this.db.prepare(`
|
|
3089
|
+
UPDATE sync_state
|
|
3090
|
+
SET sync_status = 'synced', remote_version = ?, last_pushed_at = ?
|
|
3091
|
+
WHERE memory_id = ?
|
|
3092
|
+
`).run(remoteVersion, now, memoryId);
|
|
3093
|
+
}
|
|
3094
|
+
markAsPulled(memoryId, localVersion) {
|
|
3095
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3096
|
+
this.db.prepare(`
|
|
3097
|
+
UPDATE sync_state
|
|
3098
|
+
SET sync_status = 'synced', local_version = ?, last_pulled_at = ?
|
|
3099
|
+
WHERE memory_id = ?
|
|
3100
|
+
`).run(localVersion, now, memoryId);
|
|
3101
|
+
}
|
|
3102
|
+
markAsConflict(memoryId, remoteVersion) {
|
|
3103
|
+
this.db.prepare(`
|
|
3104
|
+
UPDATE sync_state
|
|
3105
|
+
SET sync_status = 'conflict', remote_version = ?
|
|
3106
|
+
WHERE memory_id = ?
|
|
3107
|
+
`).run(remoteVersion, memoryId);
|
|
3108
|
+
}
|
|
3109
|
+
getUntrackedMemories(orgId, repoId) {
|
|
3110
|
+
const rows = this.db.prepare(`
|
|
3111
|
+
SELECT m.* FROM memories m
|
|
3112
|
+
LEFT JOIN sync_state s ON m.id = s.memory_id
|
|
3113
|
+
WHERE m.org_id = ? AND m.repo_id = ? AND s.memory_id IS NULL
|
|
3114
|
+
ORDER BY m.created_at ASC
|
|
3115
|
+
`).all(orgId, repoId);
|
|
3116
|
+
return rows.map(rowToMemory);
|
|
3117
|
+
}
|
|
3118
|
+
initSyncStateForMemory(memoryId) {
|
|
3119
|
+
const memory = this.getById(memoryId);
|
|
3120
|
+
if (!memory) return;
|
|
3121
|
+
const existing = this.getSyncState(memoryId);
|
|
3122
|
+
if (existing) return;
|
|
3123
|
+
this.setSyncState({
|
|
3124
|
+
memoryId,
|
|
3125
|
+
localVersion: memory.version,
|
|
3126
|
+
syncStatus: "pending_push"
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
getSyncSummary(orgId, repoId) {
|
|
3130
|
+
const row = this.db.prepare(`
|
|
3131
|
+
SELECT
|
|
3132
|
+
SUM(CASE WHEN s.sync_status = 'synced' THEN 1 ELSE 0 END) as synced,
|
|
3133
|
+
SUM(CASE WHEN s.sync_status = 'pending_push' THEN 1 ELSE 0 END) as pending_push,
|
|
3134
|
+
SUM(CASE WHEN s.sync_status = 'pending_pull' THEN 1 ELSE 0 END) as pending_pull,
|
|
3135
|
+
SUM(CASE WHEN s.sync_status = 'conflict' THEN 1 ELSE 0 END) as conflicts
|
|
3136
|
+
FROM sync_state s
|
|
3137
|
+
JOIN memories m ON s.memory_id = m.id
|
|
3138
|
+
WHERE m.org_id = ? AND m.repo_id = ?
|
|
3139
|
+
`).get(orgId, repoId);
|
|
3140
|
+
return {
|
|
3141
|
+
synced: row.synced ?? 0,
|
|
3142
|
+
pendingPush: row.pending_push ?? 0,
|
|
3143
|
+
pendingPull: row.pending_pull ?? 0,
|
|
3144
|
+
conflicts: row.conflicts ?? 0
|
|
3145
|
+
};
|
|
3146
|
+
}
|
|
3147
|
+
rowToSyncState(row) {
|
|
3148
|
+
return {
|
|
3149
|
+
memoryId: row.memory_id,
|
|
3150
|
+
localVersion: row.local_version,
|
|
3151
|
+
remoteVersion: row.remote_version,
|
|
3152
|
+
lastPushedAt: row.last_pushed_at ? new Date(row.last_pushed_at) : void 0,
|
|
3153
|
+
lastPulledAt: row.last_pulled_at ? new Date(row.last_pulled_at) : void 0,
|
|
3154
|
+
syncStatus: row.sync_status
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
getSupersededMemoriesToSync(orgId, repoId) {
|
|
3158
|
+
const rows = this.db.prepare(`
|
|
3159
|
+
SELECT m.*
|
|
3160
|
+
FROM memories m
|
|
3161
|
+
LEFT JOIN sync_state s ON s.memory_id = m.id
|
|
3162
|
+
WHERE m.org_id = ?
|
|
3163
|
+
AND m.repo_id = ?
|
|
3164
|
+
AND m.status = 'superseded'
|
|
3165
|
+
AND m.supersedes_id IS NOT NULL
|
|
3166
|
+
AND (
|
|
3167
|
+
s.memory_id IS NULL
|
|
3168
|
+
OR s.sync_status = 'pending_push'
|
|
3169
|
+
OR (s.sync_status = 'synced' AND (s.last_pushed_at IS NULL OR s.last_pushed_at < m.updated_at))
|
|
3170
|
+
)
|
|
3171
|
+
`).all(orgId, repoId);
|
|
3172
|
+
return rows.map((row) => ({
|
|
3173
|
+
memory: rowToMemory(row),
|
|
3174
|
+
newId: row.supersedes_id
|
|
3175
|
+
}));
|
|
3176
|
+
}
|
|
3177
|
+
getLinksToSync(orgId, repoId) {
|
|
3178
|
+
const rows = this.db.prepare(`
|
|
3179
|
+
SELECT l.*, ms.id as source_mem_id, mt.id as target_mem_id
|
|
3180
|
+
FROM memory_links l
|
|
3181
|
+
JOIN memories ms ON ms.id = l.source_id
|
|
3182
|
+
JOIN memories mt ON mt.id = l.target_id
|
|
3183
|
+
WHERE ms.org_id = ?
|
|
3184
|
+
AND ms.repo_id = ?
|
|
3185
|
+
AND l.id NOT IN (SELECT link_id FROM synced_links)
|
|
3186
|
+
`).all(orgId, repoId);
|
|
3187
|
+
return rows.map((row) => ({
|
|
3188
|
+
link: rowToLink(row),
|
|
3189
|
+
sourceMemoryId: row.source_mem_id,
|
|
3190
|
+
targetMemoryId: row.target_mem_id
|
|
3191
|
+
}));
|
|
3192
|
+
}
|
|
3193
|
+
markLinkSynced(linkId) {
|
|
3194
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3195
|
+
this.db.prepare(`INSERT OR IGNORE INTO synced_links (link_id, synced_at) VALUES (?, ?)`).run(linkId, now);
|
|
3196
|
+
}
|
|
3197
|
+
markStatusSynced(memoryId) {
|
|
3198
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3199
|
+
this.db.prepare(`UPDATE sync_state SET last_pushed_at = ? WHERE memory_id = ?`).run(now, memoryId);
|
|
3200
|
+
}
|
|
3201
|
+
unconsolidate(consolidationId) {
|
|
3202
|
+
const memory = this.getById(consolidationId);
|
|
3203
|
+
if (!memory) {
|
|
3204
|
+
throw new Error(`Memory not found: ${consolidationId}`);
|
|
3205
|
+
}
|
|
3206
|
+
if (!memory.isConsolidation) {
|
|
3207
|
+
throw new Error(`Memory ${consolidationId} is not a consolidation`);
|
|
3208
|
+
}
|
|
3209
|
+
const sourceLinks = this.getLinks({ memoryId: consolidationId, linkType: "derived_from" });
|
|
3210
|
+
const sourceIds = sourceLinks.filter((l) => l.sourceId === consolidationId).map((l) => l.targetId);
|
|
3211
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3212
|
+
const restoredIds = [];
|
|
3213
|
+
let linksRemoved = 0;
|
|
3214
|
+
this.db.transaction(() => {
|
|
3215
|
+
for (const sourceId of sourceIds) {
|
|
3216
|
+
const source = this.getById(sourceId);
|
|
3217
|
+
if (source && source.status === "superseded" && source.supersedesId === consolidationId) {
|
|
3218
|
+
this.db.prepare(
|
|
3219
|
+
`UPDATE memories
|
|
3220
|
+
SET status = 'active', supersedes_id = NULL, version = version + 1, updated_at = ?
|
|
3221
|
+
WHERE id = ?`
|
|
3222
|
+
).run(now, sourceId);
|
|
3223
|
+
restoredIds.push(sourceId);
|
|
3224
|
+
this.setSyncState({
|
|
3225
|
+
memoryId: sourceId,
|
|
3226
|
+
localVersion: (source.version ?? 1) + 1,
|
|
3227
|
+
syncStatus: "pending_push"
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
const deleteLinksResult = this.db.prepare(
|
|
3232
|
+
`DELETE FROM memory_links
|
|
3233
|
+
WHERE source_id = ? AND link_type = 'derived_from'`
|
|
3234
|
+
).run(consolidationId);
|
|
3235
|
+
linksRemoved = deleteLinksResult.changes;
|
|
3236
|
+
const deleteTargetLinksResult = this.db.prepare(
|
|
3237
|
+
`DELETE FROM memory_links
|
|
3238
|
+
WHERE target_id = ?`
|
|
3239
|
+
).run(consolidationId);
|
|
3240
|
+
linksRemoved += deleteTargetLinksResult.changes;
|
|
3241
|
+
this.db.prepare(
|
|
3242
|
+
`DELETE FROM synced_links
|
|
3243
|
+
WHERE link_id IN (
|
|
3244
|
+
SELECT id FROM memory_links WHERE source_id = ? OR target_id = ?
|
|
3245
|
+
)`
|
|
3246
|
+
).run(consolidationId, consolidationId);
|
|
3247
|
+
this.softDelete({
|
|
3248
|
+
id: consolidationId,
|
|
3249
|
+
deletedBy: "unconsolidate"
|
|
3250
|
+
});
|
|
3251
|
+
})();
|
|
3252
|
+
return {
|
|
3253
|
+
restoredIds,
|
|
3254
|
+
consolidationDeleted: true,
|
|
3255
|
+
linksRemoved
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
cleanupOrphanLinks() {
|
|
3259
|
+
const result = this.db.prepare(
|
|
3260
|
+
`DELETE FROM memory_links
|
|
3261
|
+
WHERE id IN (
|
|
3262
|
+
SELECT ml.id FROM memory_links ml
|
|
3263
|
+
LEFT JOIN memories ms ON ms.id = ml.source_id
|
|
3264
|
+
LEFT JOIN memories mt ON mt.id = ml.target_id
|
|
3265
|
+
WHERE ms.id IS NULL
|
|
3266
|
+
OR mt.id IS NULL
|
|
3267
|
+
OR ms.status = 'deleted'
|
|
3268
|
+
OR mt.status = 'deleted'
|
|
3269
|
+
)`
|
|
3270
|
+
).run();
|
|
3271
|
+
this.db.prepare(
|
|
3272
|
+
`DELETE FROM synced_links
|
|
3273
|
+
WHERE link_id NOT IN (SELECT id FROM memory_links)`
|
|
3274
|
+
).run();
|
|
3275
|
+
return result.changes;
|
|
3276
|
+
}
|
|
3277
|
+
async storeEmbedding(memoryId, embedding, model) {
|
|
3278
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3279
|
+
const blob = serializeEmbedding(embedding);
|
|
3280
|
+
this.db.prepare(
|
|
3281
|
+
`INSERT OR REPLACE INTO memory_embeddings (memory_id, embedding, model, created_at)
|
|
3282
|
+
VALUES (?, ?, ?, ?)`
|
|
3283
|
+
).run(memoryId, blob, model, now);
|
|
3284
|
+
}
|
|
3285
|
+
async generateAndStoreEmbedding(memoryId, text, config2) {
|
|
3286
|
+
try {
|
|
3287
|
+
const result = await generateEmbedding(text, config2);
|
|
3288
|
+
await this.storeEmbedding(memoryId, result.embedding, result.model);
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
console.error(`Failed to generate embedding for ${memoryId}:`, error);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
getEmbedding(memoryId) {
|
|
3294
|
+
const row = this.db.prepare("SELECT embedding FROM memory_embeddings WHERE memory_id = ?").get(memoryId);
|
|
3295
|
+
if (!row) return void 0;
|
|
3296
|
+
return deserializeEmbedding(row.embedding);
|
|
3297
|
+
}
|
|
3298
|
+
hasEmbedding(memoryId) {
|
|
3299
|
+
const row = this.db.prepare("SELECT 1 FROM memory_embeddings WHERE memory_id = ?").get(memoryId);
|
|
3300
|
+
return !!row;
|
|
3301
|
+
}
|
|
3302
|
+
getAllEmbeddings(orgId, repoId) {
|
|
3303
|
+
const rows = this.db.prepare(
|
|
3304
|
+
`SELECT e.memory_id, e.embedding FROM memory_embeddings e
|
|
3305
|
+
JOIN memories m ON e.memory_id = m.id
|
|
3306
|
+
WHERE m.org_id = ? AND m.repo_id = ? AND m.status = 'active'`
|
|
3307
|
+
).all(orgId, repoId);
|
|
3308
|
+
return rows.map((row) => ({
|
|
3309
|
+
memoryId: row.memory_id,
|
|
3310
|
+
embedding: deserializeEmbedding(row.embedding)
|
|
3311
|
+
}));
|
|
3312
|
+
}
|
|
3313
|
+
getMemoriesWithoutEmbeddings(orgId, repoId) {
|
|
3314
|
+
const rows = this.db.prepare(
|
|
3315
|
+
`SELECT m.* FROM memories m
|
|
3316
|
+
LEFT JOIN memory_embeddings e ON m.id = e.memory_id
|
|
3317
|
+
WHERE m.org_id = ? AND m.repo_id = ? AND m.status = 'active' AND e.memory_id IS NULL`
|
|
3318
|
+
).all(orgId, repoId);
|
|
3319
|
+
return rows.map(rowToMemory);
|
|
3320
|
+
}
|
|
3321
|
+
async recallWithEmbeddings(query, queryEmbedding) {
|
|
3322
|
+
const ftsResults = this.recall(query);
|
|
3323
|
+
if (!queryEmbedding) {
|
|
3324
|
+
return ftsResults;
|
|
3325
|
+
}
|
|
3326
|
+
const allEmbeddings = this.getAllEmbeddings(query.orgId, query.repoId);
|
|
3327
|
+
if (allEmbeddings.length === 0) {
|
|
3328
|
+
return ftsResults;
|
|
3329
|
+
}
|
|
3330
|
+
const embeddingScores = /* @__PURE__ */ new Map();
|
|
3331
|
+
for (const { memoryId, embedding } of allEmbeddings) {
|
|
3332
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding);
|
|
3333
|
+
embeddingScores.set(memoryId, Math.max(0, similarity));
|
|
3334
|
+
}
|
|
3335
|
+
const ftsIds = new Set(ftsResults.map((r) => r.id));
|
|
3336
|
+
const k = query.k ?? 10;
|
|
3337
|
+
const usageStats = this.getUsageStats(query.orgId, query.repoId);
|
|
3338
|
+
const usageMap = new Map(usageStats.map((stat) => [stat.memoryId, stat]));
|
|
3339
|
+
const sortedByEmbedding = Array.from(embeddingScores.entries()).filter(([id]) => !ftsIds.has(id)).sort((a, b) => b[1] - a[1]).slice(0, k);
|
|
3340
|
+
const additionalMemories = [];
|
|
3341
|
+
for (const [memoryId, similarity] of sortedByEmbedding) {
|
|
3342
|
+
if (similarity < 0.3) continue;
|
|
3343
|
+
const memory = this.getById(memoryId);
|
|
3344
|
+
if (!memory || memory.status !== "active" || !query.includeExpired && memory.ttlSeconds && memory.createdAt.getTime() + memory.ttlSeconds * 1e3 <= Date.now()) {
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
3347
|
+
const usage = usageMap.get(memory.id);
|
|
3348
|
+
const usageBoost = computeUsageBoost(
|
|
3349
|
+
usage?.count ?? 0,
|
|
3350
|
+
usage?.lastUsed
|
|
3351
|
+
);
|
|
3352
|
+
additionalMemories.push({
|
|
3353
|
+
id: memory.id,
|
|
3354
|
+
memoryType: memory.memoryType,
|
|
3355
|
+
text: memory.text,
|
|
3356
|
+
summary: memory.summary,
|
|
3357
|
+
tags: memory.tags,
|
|
3358
|
+
sourceRefs: memory.sourceRefs,
|
|
3359
|
+
score: computeHybridScore(0, similarity, memory.createdAt, memory.confidence, usageBoost),
|
|
3360
|
+
source: "local",
|
|
3361
|
+
status: memory.status,
|
|
3362
|
+
supersedesId: memory.supersedesId,
|
|
3363
|
+
isConsolidation: memory.isConsolidation,
|
|
3364
|
+
consolidationVersion: memory.consolidationVersion
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
const hybridResults = ftsResults.map((r) => {
|
|
3368
|
+
const embScore = embeddingScores.get(r.id) ?? 0;
|
|
3369
|
+
const memory = this.getById(r.id);
|
|
3370
|
+
if (!memory) return r;
|
|
3371
|
+
const usage = usageMap.get(r.id);
|
|
3372
|
+
const usageBoost = computeUsageBoost(
|
|
3373
|
+
usage?.count ?? 0,
|
|
3374
|
+
usage?.lastUsed
|
|
3375
|
+
);
|
|
3376
|
+
return {
|
|
3377
|
+
...r,
|
|
3378
|
+
score: computeHybridScore(r.score, embScore, memory.createdAt, memory.confidence, usageBoost)
|
|
3379
|
+
};
|
|
3380
|
+
});
|
|
3381
|
+
const combined = [...hybridResults, ...additionalMemories];
|
|
3382
|
+
return combined.sort((a, b) => b.score - a.score).slice(0, k);
|
|
3383
|
+
}
|
|
3384
|
+
recordUsage(memoryId, query, sessionId) {
|
|
3385
|
+
this.db.prepare(
|
|
3386
|
+
`INSERT INTO memory_usage (memory_id, query, session_id) VALUES (?, ?, ?)`
|
|
3387
|
+
).run(memoryId, query ?? null, sessionId ?? null);
|
|
3388
|
+
}
|
|
3389
|
+
recordUsageBatch(memoryIds, query, sessionId) {
|
|
3390
|
+
const stmt = this.db.prepare(
|
|
3391
|
+
`INSERT INTO memory_usage (memory_id, query, session_id) VALUES (?, ?, ?)`
|
|
3392
|
+
);
|
|
3393
|
+
const insertMany = this.db.transaction((ids) => {
|
|
3394
|
+
for (const id of ids) {
|
|
3395
|
+
stmt.run(id, query ?? null, sessionId ?? null);
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
insertMany(memoryIds);
|
|
3399
|
+
}
|
|
3400
|
+
getUsageCount(memoryId) {
|
|
3401
|
+
const row = this.db.prepare("SELECT COUNT(*) as cnt FROM memory_usage WHERE memory_id = ?").get(memoryId);
|
|
3402
|
+
return row.cnt;
|
|
3403
|
+
}
|
|
3404
|
+
getUsageStats(orgId, repoId) {
|
|
3405
|
+
const rows = this.db.prepare(
|
|
3406
|
+
`SELECT u.memory_id, COUNT(*) as cnt, MAX(u.recalled_at) as last_used
|
|
3407
|
+
FROM memory_usage u
|
|
3408
|
+
JOIN memories m ON u.memory_id = m.id
|
|
3409
|
+
WHERE m.org_id = ? AND m.repo_id = ?
|
|
3410
|
+
GROUP BY u.memory_id
|
|
3411
|
+
ORDER BY cnt DESC`
|
|
3412
|
+
).all(orgId, repoId);
|
|
3413
|
+
return rows.map((row) => ({
|
|
3414
|
+
memoryId: row.memory_id,
|
|
3415
|
+
count: row.cnt,
|
|
3416
|
+
lastUsed: new Date(row.last_used)
|
|
3417
|
+
}));
|
|
3418
|
+
}
|
|
3419
|
+
getTopUsedMemories(orgId, repoId, limit = 10) {
|
|
3420
|
+
const rows = this.db.prepare(
|
|
3421
|
+
`SELECT m.*, COUNT(u.id) as usage_count
|
|
3422
|
+
FROM memories m
|
|
3423
|
+
LEFT JOIN memory_usage u ON m.id = u.memory_id
|
|
3424
|
+
WHERE m.org_id = ? AND m.repo_id = ? AND m.status = 'active'
|
|
3425
|
+
GROUP BY m.id
|
|
3426
|
+
ORDER BY usage_count DESC
|
|
3427
|
+
LIMIT ?`
|
|
3428
|
+
).all(orgId, repoId, limit);
|
|
3429
|
+
return rows.map((row) => ({
|
|
3430
|
+
memory: rowToMemory(row),
|
|
3431
|
+
usageCount: row.usage_count
|
|
3432
|
+
}));
|
|
3433
|
+
}
|
|
3434
|
+
getUnusedMemories(orgId, repoId, daysSinceCreation = 30) {
|
|
3435
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
3436
|
+
cutoff.setDate(cutoff.getDate() - daysSinceCreation);
|
|
3437
|
+
const rows = this.db.prepare(
|
|
3438
|
+
`SELECT m.* FROM memories m
|
|
3439
|
+
LEFT JOIN memory_usage u ON m.id = u.memory_id
|
|
3440
|
+
WHERE m.org_id = ?
|
|
3441
|
+
AND m.repo_id = ?
|
|
3442
|
+
AND m.status = 'active'
|
|
3443
|
+
AND m.created_at < ?
|
|
3444
|
+
AND u.id IS NULL`
|
|
3445
|
+
).all(orgId, repoId, cutoff.toISOString());
|
|
3446
|
+
return rows.map(rowToMemory);
|
|
3447
|
+
}
|
|
3448
|
+
getEmbeddingStats(orgId, repoId) {
|
|
3449
|
+
const row = this.db.prepare(
|
|
3450
|
+
`SELECT
|
|
3451
|
+
COUNT(m.id) as total,
|
|
3452
|
+
COUNT(e.memory_id) as with_embedding
|
|
3453
|
+
FROM memories m
|
|
3454
|
+
LEFT JOIN memory_embeddings e ON m.id = e.memory_id
|
|
3455
|
+
WHERE m.org_id = ? AND m.repo_id = ? AND m.status = 'active'`
|
|
3456
|
+
).get(orgId, repoId);
|
|
3457
|
+
return {
|
|
3458
|
+
total: row.total,
|
|
3459
|
+
withEmbedding: row.with_embedding,
|
|
3460
|
+
withoutEmbedding: row.total - row.with_embedding
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
createCurationSuggestion(input) {
|
|
3464
|
+
const id = uuid();
|
|
3465
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3466
|
+
const normalizedOrgId = input.orgId.toLowerCase();
|
|
3467
|
+
const normalizedRepoId = input.repoId.toLowerCase();
|
|
3468
|
+
this.db.prepare(
|
|
3469
|
+
`INSERT INTO curation_suggestions
|
|
3470
|
+
(id, org_id, repo_id, type, priority, status, memory_ids, reason, confidence, payload, created_by, created_at, updated_at)
|
|
3471
|
+
VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?, ?, ?)`
|
|
3472
|
+
).run(
|
|
3473
|
+
id,
|
|
3474
|
+
normalizedOrgId,
|
|
3475
|
+
normalizedRepoId,
|
|
3476
|
+
input.type,
|
|
3477
|
+
input.priority,
|
|
3478
|
+
JSON.stringify(input.memoryIds),
|
|
3479
|
+
input.reason,
|
|
3480
|
+
input.confidence,
|
|
3481
|
+
input.payload ? JSON.stringify(input.payload) : null,
|
|
3482
|
+
input.createdBy ?? null,
|
|
3483
|
+
now,
|
|
3484
|
+
now
|
|
3485
|
+
);
|
|
3486
|
+
return this.getCurationSuggestionById(id);
|
|
3487
|
+
}
|
|
3488
|
+
listCurationSuggestions(query) {
|
|
3489
|
+
const clauses = ["org_id = ?", "repo_id = ?"];
|
|
3490
|
+
const params = [query.orgId.toLowerCase(), query.repoId.toLowerCase()];
|
|
3491
|
+
if (query.status && query.status.length > 0) {
|
|
3492
|
+
clauses.push(`status IN (${query.status.map(() => "?").join(",")})`);
|
|
3493
|
+
params.push(...query.status);
|
|
3494
|
+
}
|
|
3495
|
+
if (query.types && query.types.length > 0) {
|
|
3496
|
+
clauses.push(`type IN (${query.types.map(() => "?").join(",")})`);
|
|
3497
|
+
params.push(...query.types);
|
|
3498
|
+
}
|
|
3499
|
+
const limit = Math.min(query.limit ?? 100, 500);
|
|
3500
|
+
const offset = query.offset ?? 0;
|
|
3501
|
+
params.push(limit, offset);
|
|
3502
|
+
const rows = this.db.prepare(
|
|
3503
|
+
`SELECT * FROM curation_suggestions
|
|
3504
|
+
WHERE ${clauses.join(" AND ")}
|
|
3505
|
+
ORDER BY created_at DESC
|
|
3506
|
+
LIMIT ? OFFSET ?`
|
|
3507
|
+
).all(...params);
|
|
3508
|
+
return rows.map(rowToCurationSuggestion);
|
|
3509
|
+
}
|
|
3510
|
+
reviewCurationSuggestion(input) {
|
|
3511
|
+
const existing = this.getCurationSuggestionById(input.id);
|
|
3512
|
+
if (!existing) {
|
|
3513
|
+
throw new Error(`Curation suggestion not found: ${input.id}`);
|
|
3514
|
+
}
|
|
3515
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3516
|
+
this.db.prepare(
|
|
3517
|
+
`UPDATE curation_suggestions
|
|
3518
|
+
SET status = ?, reviewed_by = ?, review_note = ?, reviewed_at = ?,
|
|
3519
|
+
applied_at = CASE WHEN ? = 'applied' THEN ? ELSE applied_at END,
|
|
3520
|
+
updated_at = ?
|
|
3521
|
+
WHERE id = ?`
|
|
3522
|
+
).run(
|
|
3523
|
+
input.status,
|
|
3524
|
+
input.reviewedBy ?? null,
|
|
3525
|
+
input.reviewNote ?? null,
|
|
3526
|
+
now,
|
|
3527
|
+
input.status,
|
|
3528
|
+
now,
|
|
3529
|
+
now,
|
|
3530
|
+
input.id
|
|
3531
|
+
);
|
|
3532
|
+
return this.getCurationSuggestionById(input.id);
|
|
3533
|
+
}
|
|
3534
|
+
getCurationSuggestionById(id) {
|
|
3535
|
+
const row = this.db.prepare("SELECT * FROM curation_suggestions WHERE id = ?").get(id);
|
|
3536
|
+
return row ? rowToCurationSuggestion(row) : void 0;
|
|
3537
|
+
}
|
|
3538
|
+
resetAll() {
|
|
3539
|
+
const result = this.db.transaction(() => {
|
|
3540
|
+
const embeddingsDeleted = this.db.prepare("DELETE FROM memory_embeddings").run().changes;
|
|
3541
|
+
const linksDeleted = this.db.prepare("DELETE FROM memory_links").run().changes;
|
|
3542
|
+
this.db.prepare("DELETE FROM synced_links").run();
|
|
3543
|
+
this.db.prepare("DELETE FROM sync_state").run();
|
|
3544
|
+
this.db.prepare("DELETE FROM tombstones").run();
|
|
3545
|
+
this.db.prepare("DELETE FROM memory_usage").run();
|
|
3546
|
+
const memoriesDeleted = this.db.prepare("DELETE FROM memories").run().changes;
|
|
3547
|
+
this.db.exec("INSERT INTO memories_fts(memories_fts) VALUES('rebuild')");
|
|
3548
|
+
return { memoriesDeleted, linksDeleted, embeddingsDeleted };
|
|
3549
|
+
})();
|
|
3550
|
+
return result;
|
|
3551
|
+
}
|
|
3552
|
+
clearEmbeddings() {
|
|
3553
|
+
return this.db.prepare("DELETE FROM memory_embeddings").run().changes;
|
|
3554
|
+
}
|
|
3555
|
+
close() {
|
|
3556
|
+
this.db.close();
|
|
3557
|
+
}
|
|
3558
|
+
};
|
|
3559
|
+
var config = {
|
|
3560
|
+
"previewFeatures": [],
|
|
3561
|
+
"clientVersion": "7.8.0",
|
|
3562
|
+
"engineVersion": "3c6e192761c0362d496ed980de936e2f3cebcd3a",
|
|
3563
|
+
"activeProvider": "postgresql",
|
|
3564
|
+
"inlineSchema": 'datasource db {\n provider = "postgresql"\n}\n\ngenerator client {\n provider = "prisma-client"\n output = "../packages/db/src/generated/prisma"\n}\n\nmodel Memory {\n id String @id @default(uuid()) @db.Uuid\n orgId String @map("org_id")\n repoId String @map("repo_id")\n scopeType String @default("repo") @map("scope_type")\n memoryType String @map("memory_type")\n visibility String @default("repo")\n status String @default("active")\n text String\n summary String?\n tags String[]\n sourceRefs Json? @map("source_refs")\n confidence Float?\n ttlSeconds Int? @map("ttl_seconds")\n supersedesId String? @map("supersedes_id") @db.Uuid\n version Int @default(1)\n deletedAt DateTime? @map("deleted_at")\n deletedBy String? @map("deleted_by")\n createdAt DateTime @default(now()) @map("created_at")\n updatedAt DateTime @updatedAt @map("updated_at")\n\n supersedes Memory? @relation("Supersedes", fields: [supersedesId], references: [id])\n supersededBy Memory[] @relation("Supersedes")\n\n sourceLinks MemoryLink[] @relation("SourceLinks")\n targetLinks MemoryLink[] @relation("TargetLinks")\n embedding MemoryEmbedding?\n usages MemoryUsage[]\n\n @@index([orgId, repoId, memoryType, status])\n @@index([orgId, repoId, updatedAt])\n @@index([deletedAt])\n @@map("memories")\n}\n\nmodel MemoryEmbedding {\n memoryId String @id @map("memory_id") @db.Uuid\n embedding Bytes\n model String\n createdAt DateTime @default(now()) @map("created_at")\n\n memory Memory @relation(fields: [memoryId], references: [id], onDelete: Cascade)\n\n @@map("memory_embeddings")\n}\n\nmodel MemoryUsage {\n id Int @id @default(autoincrement())\n memoryId String @map("memory_id") @db.Uuid\n recalledAt DateTime @default(now()) @map("recalled_at")\n query String?\n sessionId String? @map("session_id")\n\n memory Memory @relation(fields: [memoryId], references: [id], onDelete: Cascade)\n\n @@index([memoryId])\n @@index([recalledAt])\n @@map("memory_usage")\n}\n\nmodel MemoryLink {\n id String @id @default(uuid()) @db.Uuid\n sourceId String @map("source_id") @db.Uuid\n targetId String @map("target_id") @db.Uuid\n linkType String @map("link_type")\n metadata Json?\n createdAt DateTime @default(now()) @map("created_at")\n\n source Memory @relation("SourceLinks", fields: [sourceId], references: [id], onDelete: Cascade)\n target Memory @relation("TargetLinks", fields: [targetId], references: [id], onDelete: Cascade)\n\n @@unique([sourceId, targetId, linkType])\n @@map("memory_links")\n}\n\nmodel Tombstone {\n id String @id @default(uuid()) @db.Uuid\n memoryId String @map("memory_id") @db.Uuid\n orgId String @map("org_id")\n repoId String @map("repo_id")\n deletedAt DateTime @map("deleted_at")\n deletedBy String? @map("deleted_by")\n syncedAt DateTime? @map("synced_at")\n createdAt DateTime @default(now()) @map("created_at")\n\n @@unique([memoryId])\n @@index([orgId, repoId, syncedAt])\n @@map("tombstones")\n}\n\nmodel ApiKey {\n id String @id @default(uuid()) @db.Uuid\n key String @unique\n name String\n label String?\n orgId String @map("org_id")\n repoId String? @map("repo_id")\n userId String? @map("user_id") @db.Uuid\n createdBy String? @map("created_by") @db.Uuid\n isActive Boolean @default(true) @map("is_active")\n createdAt DateTime @default(now()) @map("created_at")\n lastUsedAt DateTime? @map("last_used_at")\n\n user User? @relation(fields: [userId], references: [id])\n logs ApiKeyLog[]\n\n @@index([key])\n @@index([orgId])\n @@index([userId])\n @@map("api_keys")\n}\n\nmodel ApiKeyLog {\n id String @id @default(uuid()) @db.Uuid\n apiKeyId String @map("api_key_id") @db.Uuid\n operation String\n memoryId String? @map("memory_id") @db.Uuid\n orgId String @map("org_id")\n repoId String @map("repo_id")\n query String?\n metadata Json?\n createdAt DateTime @default(now()) @map("created_at")\n\n apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)\n\n @@index([apiKeyId])\n @@index([createdAt])\n @@index([memoryId])\n @@index([orgId, repoId])\n @@map("api_key_logs")\n}\n\nmodel User {\n id String @id @default(uuid()) @db.Uuid\n githubId Int @unique @map("github_id")\n githubLogin String @map("github_login")\n name String?\n email String?\n avatarUrl String? @map("avatar_url")\n isAdmin Boolean @default(false) @map("is_admin")\n createdAt DateTime @default(now()) @map("created_at")\n updatedAt DateTime @updatedAt @map("updated_at")\n\n apiKeys ApiKey[]\n repoAccess UserRepoAccess[]\n\n @@map("users")\n}\n\nmodel UserRepoAccess {\n id String @id @default(uuid()) @db.Uuid\n userId String @map("user_id") @db.Uuid\n orgId String @map("org_id")\n repoId String @map("repo_id")\n permission String\n grantedAt DateTime @default(now()) @map("granted_at")\n grantedBy String? @map("granted_by") @db.Uuid\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([userId, orgId, repoId])\n @@index([orgId, repoId])\n @@map("user_repo_access")\n}\n',
|
|
3565
|
+
"runtimeDataModel": {
|
|
3566
|
+
"models": {},
|
|
3567
|
+
"enums": {},
|
|
3568
|
+
"types": {}
|
|
3569
|
+
},
|
|
3570
|
+
"parameterizationSchema": {
|
|
3571
|
+
"strings": [],
|
|
3572
|
+
"graph": ""
|
|
3573
|
+
}
|
|
3574
|
+
};
|
|
3575
|
+
config.runtimeDataModel = JSON.parse('{"models":{"Memory":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"orgId","kind":"scalar","type":"String","dbName":"org_id"},{"name":"repoId","kind":"scalar","type":"String","dbName":"repo_id"},{"name":"scopeType","kind":"scalar","type":"String","dbName":"scope_type"},{"name":"memoryType","kind":"scalar","type":"String","dbName":"memory_type"},{"name":"visibility","kind":"scalar","type":"String"},{"name":"status","kind":"scalar","type":"String"},{"name":"text","kind":"scalar","type":"String"},{"name":"summary","kind":"scalar","type":"String"},{"name":"tags","kind":"scalar","type":"String"},{"name":"sourceRefs","kind":"scalar","type":"Json","dbName":"source_refs"},{"name":"confidence","kind":"scalar","type":"Float"},{"name":"ttlSeconds","kind":"scalar","type":"Int","dbName":"ttl_seconds"},{"name":"supersedesId","kind":"scalar","type":"String","dbName":"supersedes_id"},{"name":"version","kind":"scalar","type":"Int"},{"name":"deletedAt","kind":"scalar","type":"DateTime","dbName":"deleted_at"},{"name":"deletedBy","kind":"scalar","type":"String","dbName":"deleted_by"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"updatedAt","kind":"scalar","type":"DateTime","dbName":"updated_at"},{"name":"supersedes","kind":"object","type":"Memory","relationName":"Supersedes"},{"name":"supersededBy","kind":"object","type":"Memory","relationName":"Supersedes"},{"name":"sourceLinks","kind":"object","type":"MemoryLink","relationName":"SourceLinks"},{"name":"targetLinks","kind":"object","type":"MemoryLink","relationName":"TargetLinks"},{"name":"embedding","kind":"object","type":"MemoryEmbedding","relationName":"MemoryToMemoryEmbedding"},{"name":"usages","kind":"object","type":"MemoryUsage","relationName":"MemoryToMemoryUsage"}],"dbName":"memories"},"MemoryEmbedding":{"fields":[{"name":"memoryId","kind":"scalar","type":"String","dbName":"memory_id"},{"name":"embedding","kind":"scalar","type":"Bytes"},{"name":"model","kind":"scalar","type":"String"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"memory","kind":"object","type":"Memory","relationName":"MemoryToMemoryEmbedding"}],"dbName":"memory_embeddings"},"MemoryUsage":{"fields":[{"name":"id","kind":"scalar","type":"Int"},{"name":"memoryId","kind":"scalar","type":"String","dbName":"memory_id"},{"name":"recalledAt","kind":"scalar","type":"DateTime","dbName":"recalled_at"},{"name":"query","kind":"scalar","type":"String"},{"name":"sessionId","kind":"scalar","type":"String","dbName":"session_id"},{"name":"memory","kind":"object","type":"Memory","relationName":"MemoryToMemoryUsage"}],"dbName":"memory_usage"},"MemoryLink":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"sourceId","kind":"scalar","type":"String","dbName":"source_id"},{"name":"targetId","kind":"scalar","type":"String","dbName":"target_id"},{"name":"linkType","kind":"scalar","type":"String","dbName":"link_type"},{"name":"metadata","kind":"scalar","type":"Json"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"source","kind":"object","type":"Memory","relationName":"SourceLinks"},{"name":"target","kind":"object","type":"Memory","relationName":"TargetLinks"}],"dbName":"memory_links"},"Tombstone":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"memoryId","kind":"scalar","type":"String","dbName":"memory_id"},{"name":"orgId","kind":"scalar","type":"String","dbName":"org_id"},{"name":"repoId","kind":"scalar","type":"String","dbName":"repo_id"},{"name":"deletedAt","kind":"scalar","type":"DateTime","dbName":"deleted_at"},{"name":"deletedBy","kind":"scalar","type":"String","dbName":"deleted_by"},{"name":"syncedAt","kind":"scalar","type":"DateTime","dbName":"synced_at"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"}],"dbName":"tombstones"},"ApiKey":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"key","kind":"scalar","type":"String"},{"name":"name","kind":"scalar","type":"String"},{"name":"label","kind":"scalar","type":"String"},{"name":"orgId","kind":"scalar","type":"String","dbName":"org_id"},{"name":"repoId","kind":"scalar","type":"String","dbName":"repo_id"},{"name":"userId","kind":"scalar","type":"String","dbName":"user_id"},{"name":"createdBy","kind":"scalar","type":"String","dbName":"created_by"},{"name":"isActive","kind":"scalar","type":"Boolean","dbName":"is_active"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"lastUsedAt","kind":"scalar","type":"DateTime","dbName":"last_used_at"},{"name":"user","kind":"object","type":"User","relationName":"ApiKeyToUser"},{"name":"logs","kind":"object","type":"ApiKeyLog","relationName":"ApiKeyToApiKeyLog"}],"dbName":"api_keys"},"ApiKeyLog":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"apiKeyId","kind":"scalar","type":"String","dbName":"api_key_id"},{"name":"operation","kind":"scalar","type":"String"},{"name":"memoryId","kind":"scalar","type":"String","dbName":"memory_id"},{"name":"orgId","kind":"scalar","type":"String","dbName":"org_id"},{"name":"repoId","kind":"scalar","type":"String","dbName":"repo_id"},{"name":"query","kind":"scalar","type":"String"},{"name":"metadata","kind":"scalar","type":"Json"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"apiKey","kind":"object","type":"ApiKey","relationName":"ApiKeyToApiKeyLog"}],"dbName":"api_key_logs"},"User":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"githubId","kind":"scalar","type":"Int","dbName":"github_id"},{"name":"githubLogin","kind":"scalar","type":"String","dbName":"github_login"},{"name":"name","kind":"scalar","type":"String"},{"name":"email","kind":"scalar","type":"String"},{"name":"avatarUrl","kind":"scalar","type":"String","dbName":"avatar_url"},{"name":"isAdmin","kind":"scalar","type":"Boolean","dbName":"is_admin"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"updatedAt","kind":"scalar","type":"DateTime","dbName":"updated_at"},{"name":"apiKeys","kind":"object","type":"ApiKey","relationName":"ApiKeyToUser"},{"name":"repoAccess","kind":"object","type":"UserRepoAccess","relationName":"UserToUserRepoAccess"}],"dbName":"users"},"UserRepoAccess":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"userId","kind":"scalar","type":"String","dbName":"user_id"},{"name":"orgId","kind":"scalar","type":"String","dbName":"org_id"},{"name":"repoId","kind":"scalar","type":"String","dbName":"repo_id"},{"name":"permission","kind":"scalar","type":"String"},{"name":"grantedAt","kind":"scalar","type":"DateTime","dbName":"granted_at"},{"name":"grantedBy","kind":"scalar","type":"String","dbName":"granted_by"},{"name":"user","kind":"object","type":"User","relationName":"UserToUserRepoAccess"}],"dbName":"user_repo_access"}},"enums":{},"types":{}}');
|
|
3576
|
+
config.parameterizationSchema = {
|
|
3577
|
+
strings: JSON.parse('["where","supersedes","orderBy","cursor","supersededBy","source","target","sourceLinks","targetLinks","memory","embedding","usages","_count","Memory.findUnique","Memory.findUniqueOrThrow","Memory.findFirst","Memory.findFirstOrThrow","Memory.findMany","data","Memory.createOne","Memory.createMany","Memory.createManyAndReturn","Memory.updateOne","Memory.updateMany","Memory.updateManyAndReturn","create","update","Memory.upsertOne","Memory.deleteOne","Memory.deleteMany","having","_avg","_sum","_min","_max","Memory.groupBy","Memory.aggregate","MemoryEmbedding.findUnique","MemoryEmbedding.findUniqueOrThrow","MemoryEmbedding.findFirst","MemoryEmbedding.findFirstOrThrow","MemoryEmbedding.findMany","MemoryEmbedding.createOne","MemoryEmbedding.createMany","MemoryEmbedding.createManyAndReturn","MemoryEmbedding.updateOne","MemoryEmbedding.updateMany","MemoryEmbedding.updateManyAndReturn","MemoryEmbedding.upsertOne","MemoryEmbedding.deleteOne","MemoryEmbedding.deleteMany","MemoryEmbedding.groupBy","MemoryEmbedding.aggregate","MemoryUsage.findUnique","MemoryUsage.findUniqueOrThrow","MemoryUsage.findFirst","MemoryUsage.findFirstOrThrow","MemoryUsage.findMany","MemoryUsage.createOne","MemoryUsage.createMany","MemoryUsage.createManyAndReturn","MemoryUsage.updateOne","MemoryUsage.updateMany","MemoryUsage.updateManyAndReturn","MemoryUsage.upsertOne","MemoryUsage.deleteOne","MemoryUsage.deleteMany","MemoryUsage.groupBy","MemoryUsage.aggregate","MemoryLink.findUnique","MemoryLink.findUniqueOrThrow","MemoryLink.findFirst","MemoryLink.findFirstOrThrow","MemoryLink.findMany","MemoryLink.createOne","MemoryLink.createMany","MemoryLink.createManyAndReturn","MemoryLink.updateOne","MemoryLink.updateMany","MemoryLink.updateManyAndReturn","MemoryLink.upsertOne","MemoryLink.deleteOne","MemoryLink.deleteMany","MemoryLink.groupBy","MemoryLink.aggregate","Tombstone.findUnique","Tombstone.findUniqueOrThrow","Tombstone.findFirst","Tombstone.findFirstOrThrow","Tombstone.findMany","Tombstone.createOne","Tombstone.createMany","Tombstone.createManyAndReturn","Tombstone.updateOne","Tombstone.updateMany","Tombstone.updateManyAndReturn","Tombstone.upsertOne","Tombstone.deleteOne","Tombstone.deleteMany","Tombstone.groupBy","Tombstone.aggregate","apiKeys","user","repoAccess","apiKey","logs","ApiKey.findUnique","ApiKey.findUniqueOrThrow","ApiKey.findFirst","ApiKey.findFirstOrThrow","ApiKey.findMany","ApiKey.createOne","ApiKey.createMany","ApiKey.createManyAndReturn","ApiKey.updateOne","ApiKey.updateMany","ApiKey.updateManyAndReturn","ApiKey.upsertOne","ApiKey.deleteOne","ApiKey.deleteMany","ApiKey.groupBy","ApiKey.aggregate","ApiKeyLog.findUnique","ApiKeyLog.findUniqueOrThrow","ApiKeyLog.findFirst","ApiKeyLog.findFirstOrThrow","ApiKeyLog.findMany","ApiKeyLog.createOne","ApiKeyLog.createMany","ApiKeyLog.createManyAndReturn","ApiKeyLog.updateOne","ApiKeyLog.updateMany","ApiKeyLog.updateManyAndReturn","ApiKeyLog.upsertOne","ApiKeyLog.deleteOne","ApiKeyLog.deleteMany","ApiKeyLog.groupBy","ApiKeyLog.aggregate","User.findUnique","User.findUniqueOrThrow","User.findFirst","User.findFirstOrThrow","User.findMany","User.createOne","User.createMany","User.createManyAndReturn","User.updateOne","User.updateMany","User.updateManyAndReturn","User.upsertOne","User.deleteOne","User.deleteMany","User.groupBy","User.aggregate","UserRepoAccess.findUnique","UserRepoAccess.findUniqueOrThrow","UserRepoAccess.findFirst","UserRepoAccess.findFirstOrThrow","UserRepoAccess.findMany","UserRepoAccess.createOne","UserRepoAccess.createMany","UserRepoAccess.createManyAndReturn","UserRepoAccess.updateOne","UserRepoAccess.updateMany","UserRepoAccess.updateManyAndReturn","UserRepoAccess.upsertOne","UserRepoAccess.deleteOne","UserRepoAccess.deleteMany","UserRepoAccess.groupBy","UserRepoAccess.aggregate","AND","OR","NOT","id","userId","orgId","repoId","permission","grantedAt","grantedBy","equals","in","notIn","lt","lte","gt","gte","not","contains","startsWith","endsWith","githubId","githubLogin","name","email","avatarUrl","isAdmin","createdAt","updatedAt","every","some","none","apiKeyId","operation","memoryId","query","metadata","string_contains","string_starts_with","string_ends_with","array_starts_with","array_ends_with","array_contains","key","label","createdBy","isActive","lastUsedAt","userId_orgId_repoId","deletedAt","deletedBy","syncedAt","sourceId","targetId","linkType","recalledAt","sessionId","model","scopeType","memoryType","visibility","status","text","summary","tags","sourceRefs","confidence","ttlSeconds","supersedesId","version","has","hasEvery","hasSome","sourceId_targetId_linkType","is","isNot","connectOrCreate","upsert","createMany","set","disconnect","delete","connect","updateMany","deleteMany","increment","decrement","multiply","divide","push"]'),
|
|
3578
|
+
graph: "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-QEAIeYBAQD5AQAh5wEBAPkBACHoAQEA-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-gEAIc4BAACWAgAg3gEBAPgBACHfAQEA-AEAIeABAQD5AQAhAwAAAAcAIAIAAG0AMB4AAG4AIAMAAAAHACACAAAIADADAAAJACALqgEAAKwCADCrAQAAdAAQrAEAAKwCADCtAQEAAAABrwEBAI8CACGwAQEAjwIAIcUBQACSAgAhzAEBAAAAAdsBQACSAgAh3AEBAJACACHdAUAApwIAIQEAAABxACABAAAAcQAgC6oBAACsAgAwqwEAAHQAEKwBAACsAgAwrQEBAJ0CACGvAQEAjwIAIbABAQCPAgAhxQFAAJICACHMAQEAnQIAIdsBQACSAgAh3AEBAJACACHdAUAApwIAIQLcAQAAyAIAIN0BAADIAgAgAwAAAHQAIAIAAHUAMAMAAHEAIAMAAAB0ACACAAB1ADADAABxACADAAAAdAAgAgAAdQAwAwAAcQAgCK0BAQAAAAGvAQEAAAABsAEBAAAAAcUBQAAAAAHMAQEAAAAB2wFAAAAAAdwBAQAAAAHdAUAAAAABARIAAHkAIAitAQEAAAABrwEBAAAAAbABAQAAAAHFAUAAAAABzAEBAAAAAdsBQAAAAAHcAQEAAAAB3QFAAAAAAQESAAB7ADABEgAAewAwCK0BAQDMAgAhrwEBAMwCACGwAQEAzAIAIcUBQADNAgAhzAEBAMwCACHbAUAAzQIAIdwBAQDOAgAh3QFAAPACACECAAAAcQAgEgAAfgAgCK0BAQDMAgAhrwEBAMwCACGwAQEAzAIAIcUBQADNAgAhzAEBAMwCACHbAUAAzQIAIdwBAQDOAgAh3QFAAPACACECAAAAdAAgEgAAgAEAIAIAAAB0ACASAACAAQAgAwAAAHEAIBkAAHkAIBoAAH4AIAEAAABxACABAAAAdAAgBQwAAJIDACAhAACUAwAgIgAAkwMAINwBAADIAgAg3QEAAMgCACALqgEAAKsCADCrAQAAhwEAEKwBAACrAgAwrQEBAPgBACGvAQEA-QEAIbABAQD5AQAhxQFAAPoBACHMAQEA-AEAIdsBQAD6AQAh3AEBAIcCACHdAUAAmQIAIQMAAAB0ACACAACGAQAwHgAAhwEAIAMAAAB0ACACAAB1ADADAABxACAQZgAAqAIAIGkAAKkCACCqAQAApgIAMKsBAACOAQAQrAEAAKYCADCtAQEAAAABrgEBAJ4CACGvAQEAjwIAIbABAQCQAgAhwQEBAI8CACHFAUAAkgIAIdUBAQAAAAHWAQEAkAIAIdcBAQCeAgAh2AEgAJECACHZAUAApwIAIQEAAACKAQAgDmUAAJMCACBnAACUAgAgqgEAAI4CADCrAQAAjAEAEKwBAACOAgAwrQEBAJ0CACG_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-AEAIa8BAQD5AQAhsAEBAPkBACHFAUAA-gEAIcoBAQD4AQAhywEBAPkBACHMAQEA-wEAIc0BAQCHAgAhzgEAAJYCACADAAAAlwEAIAIAAMUBADAeAADGAQAgAwAAAJcBACACAACYAQAwAwAAmQEAIA5lAACTAgAgZwAAlAIAIKoBAACOAgAwqwEAAIwBABCsAQAAjgIAMK0BAQAAAAG_AQIAAAABwAEBAI8CACHBAQEAkAIAIcIBAQCQAgAhwwEBAJACACHEASAAkQIAIcUBQACSAgAhxgFAAJICACEBAAAAyQEAIAEAAADJAQAgBWUAAIMDACBnAACEAwAgwQEAAMgCACDCAQAAyAIAIMMBAADIAgAgAwAAAIwBACACAADMAQAwAwAAyQEAIAMAAACMAQAgAgAAzAEAMAMAAMkBACADAAAAjAEAIAIAAMwBADADAADJAQAgC2UAAIEDACBnAACCAwAgrQEBAAAAAb8BAgAAAAHAAQEAAAABwQEBAAAAAcIBAQAAAAHDAQEAAAABxAEgAAAAAcUBQAAAAAHGAUAAAAABARIAANABACAJrQEBAAAAAb8BAgAAAAHAAQEAAAABwQEBAAAAAcIBAQAAAAHDAQEAAAABxAEgAAAAAcUBQAAAAAHGAUAAAAABARIAANIBADABEgAA0gEAMAtlAADYAgAgZwAA2QIAIK0BAQDMAgAhvwECANYCACHAAQEAzAIAIcEBAQDOAgAhwgEBAM4CACHDAQEAzgIAIcQBIADXAgAhxQFAAM0CACHGAUAAzQIAIQIAAADJAQAgEgAA1QEAIAmtAQEAzAIAIb8BAgDWAgAhwAEBAMwCACHBAQEAzgIAIcIBAQDOAgAhwwEBAM4CACHEASAA1wIAIcUBQADNAgAhxgFAAM0CACECAAAAjAEAIBIAANcBACACAAAAjAEAIBIAANcBACADAAAAyQEAIBkAANABACAaAADVAQAgAQAAAMkBACABAAAAjAEAIAgMAADRAgAgHwAA0gIAICAAANUCACAhAADUAgAgIgAA0wIAIMEBAADIAgAgwgEAAMgCACDDAQAAyAIAIAyqAQAAhQIAMKsBAADeAQAQrAEAAIUCADCtAQEA-AEAIb8BAgCGAgAhwAEBAPkBACHBAQEAhwIAIcIBAQCHAgAhwwEBAIcCACHEASAAiAIAIcUBQAD6AQAhxgFAAPoBACEDAAAAjAEAIAIAAN0BADAeAADeAQAgAwAAAIwBACACAADMAQAwAwAAyQEAIAEAAACTAQAgAQAAAJMBACADAAAAkQEAIAIAAJIBADADAACTAQAgAwAAAJEBACACAACSAQAwAwAAkwEAIAMAAACRAQAgAgAAkgEAMAMAAJMBACAIZgAA0AIAIK0BAQAAAAGuAQEAAAABrwEBAAAAAbABAQAAAAGxAQEAAAABsgFAAAAAAbMBAQAAAAEBEgAA5gEAIAetAQEAAAABrgEBAAAAAa8BAQAAAAGwAQEAAAABsQEBAAAAAbIBQAAAAAGzAQEAAAABARIAAOgBADABEgAA6AEAMAhmAADPAgAgrQEBAMwCACGuAQEAzAIAIa8BAQDMAgAhsAEBAMwCACGxAQEAzAIAIbIBQADNAgAhswEBAM4CACECAAAAkwEAIBIAAOsBACAHrQEBAMwCACGuAQEAzAIAIa8BAQDMAgAhsAEBAMwCACGxAQEAzAIAIbIBQADNAgAhswEBAM4CACECAAAAkQEAIBIAAO0BACACAAAAkQEAIBIAAO0BACADAAAAkwEAIBkAAOYBACAaAADrAQAgAQAAAJMBACABAAAAkQEAIAQMAADJAgAgIQAAywIAICIAAMoCACCzAQAAyAIAIAqqAQAA9wEAMKsBAAD0AQAQrAEAAPcBADCtAQEA-AEAIa4BAQD4AQAhrwEBAPkBACGwAQEA-QEAIbEBAQD5AQAhsgFAAPoBACGzAQEA-wEAIQMAAACRAQAgAgAA8wEAMB4AAPQBACADAAAAkQEAIAIAAJIBADADAACTAQAgCqoBAAD3AQAwqwEAAPQBABCsAQAA9wEAMK0BAQD4AQAhrgEBAPgBACGvAQEA-QEAIbABAQD5AQAhsQEBAPkBACGyAUAA-gEAIbMBAQD7AQAhCwwAAIACACAhAACDAgAgIgAAgwIAILQBAQAAAAG1AQEAAAAEtgEBAAAABLcBAQAAAAG4AQEAAAABuQEBAAAAAboBAQAAAAG7AQEAhAIAIQ4MAACAAgAgIQAAgwIAICIAAIMCACC0AQEAAAABtQEBAAAABLYBAQAAAAS3AQEAAAABuAEBAAAAAbkBAQAAAAG6AQEAAAABuwEBAIICACG8AQEAAAABvQEBAAAAAb4BAQAAAAELDAAAgAIAICEAAIECACAiAACBAgAgtAFAAAAAAbUBQAAAAAS2AUAAAAAEtwFAAAAAAbgBQAAAAAG5AUAAAAABugFAAAAAAbsBQAD_AQAhCwwAAP0BACAhAAD-AQAgIgAA_gEAILQBAQAAAAG1AQEAAAAFtgEBAAAABbcBAQAAAAG4AQEAAAABuQEBAAAAAboBAQAAAAG7AQEA_AEAIQsMAAD9AQAgIQAA_gEAICIAAP4BACC0AQEAAAABtQEBAAAABbYBAQAAAAW3AQEAAAABuAEBAAAAAbkBAQAAAAG6AQEAAAABuwEBAPwBACEItAECAAAAAbUBAgAAAAW2AQIAAAAFtwECAAAAAbgBAgAAAAG5AQIAAAABugECAAAAAbsBAgD9AQAhC7QBAQAAAAG1AQEAAAAFtgEBAAAABbcBAQAAAAG4AQEAAAABuQEBAAAAAboBAQAAAAG7AQEA_gEAIbwBAQAAAAG9AQEAAAABvgEBAAAAAQsMAACAAgAgIQAAgQIAICIAAIECACC0AUAAAAABtQFAAAAABLYBQAAAAAS3AUAAAAABuAFAAAAAAbkBQAAAAAG6AUAAAAABuwFAAP8BACEItAECAAAAAbUBAgAAAAS2AQIAAAAEtwECAAAAAbgBAgAAAAG5AQIAAAABugECAAAAAbsBAgCAAgAhCLQBQAAAAAG1AUAAAAAEtgFAAAAABLcBQAAAAAG4AUAAAAABuQFAAAAAAboBQAAAAAG7AUAAgQIAIQ4MAACAAgAgIQAAgwIAICIAAIMCACC0AQEAAAABtQEBAAAABLYBAQAAAAS3AQEAAAABuAEBAAAAAbkBAQAAAAG6AQEAAAABuwEBAIICACG8AQEAAAABvQEBAAAAAb4BAQAAAAELtAEBAAAAAbUBAQAAAAS2AQEAAAAEtwEBAAAAAbgBAQAAAAG5AQEAAAABugEBAAAAAbsBAQCDAgAhvAEBAAAAAb0BAQAAAAG-AQEAAAABCwwAAIACACAhAACDAgAgIgAAgwIAILQBAQAAAAG1AQEAAAAEtgEBAAAABLcBAQAAAAG4AQEAAAABuQEBAAAAAboBAQAAAAG7AQEAhAIAIQyqAQAAhQIAMKsBAADeAQAQrAEAAIUCADCtAQEA-AEAIb8BAgCGAgAhwAEBAPkBACHBAQEAhwIAIcIBAQCHAgAhwwEBAIcCACHEASAAiAIAIcUBQAD6AQAhxgFAAPoBACENDAAAgAIAIB8AAI0CACAgAACAAgAgIQAAgAIAICIAAIACACC0AQIAAAABtQECAAAABLYBAgAAAAS3AQIAAAABuAECAAAAAbkBAgAAAAG6AQIAAAABuwECAIwCACEODAAA_QEAICEAAP4BACAiAAD-AQAgtAEBAAAAAbUBAQAAAAW2AQEAAAAFtwEBAAAAAbgBAQAAAAG5AQEAAAABugEBAAAAAbsBAQCLAgAhvAEBAAAAAb0BAQAAAAG-AQEAAAABBQwAAIACACAhAACKAgAgIgAAigIAILQBIAAAAAG7ASAAiQIAIQUMAACAAgAgIQAAigIAICIAAIoCACC0ASAAAAABuwEgAIkCACECtAEgAAAAAbsBIACKAgAhDgwAAP0BACAhAAD-AQAgIgAA_gEAILQBAQAAAAG1AQEAAAAFtgEBAAAABbcBAQAAAAG4AQEAAAABuQEBAAAAAboBAQAAAAG7AQEAiwIAIbwBAQAAAAG9AQEAAAABvgEBAAAAAQ0MAACAAgAgHwAAjQIAICAAAIACACAhAACAAgAgIgAAgAIAILQBAgAAAAG1AQIAAAAEtgECAAAABLcBAgAAAAG4AQIAAAABuQECAAAAAboBAgAAAAG7AQIAjAIAIQi0AQgAAAABtQEIAAAABLYBCAAAAAS3AQgAAAABuAEIAAAAAbkBCAAAAAG6AQgAAAABuwEIAI0CACEOZQAAkwIAIGcAAJQCACCqAQAAjgIAMKsBAACMAQAQrAEAAI4CADCtAQEAnQIAIb8BAgCqAgAhwAEBAI8CACHBAQEAkAIAIcIBAQCQAgAhwwEBAJACACHEASAAkQIAIcUBQACSAgAhxgFAAJICACELtAEBAAAAAbUBAQAAAAS2AQEAAAAEtwEBAAAAAbgBAQAAAAG5AQEAAAABugEBAAAAAbsBAQCDAgAhvAEBAAAAAb0BAQAAAAG-AQEAAAABC7QBAQAAAAG1AQEAAAAFtgEBAAAABbcBAQAAAAG4AQEAAAABuQEBAAAAAboBAQAAAAG7AQEA_gEAIbwBAQAAAAG9AQEAAAABvgEBAAAAAQK0ASAAAAABuwEgAIoCACEItAFAAAAAAbUBQAAAAAS2AUAAAAAEtwFAAAAAAbgBQAAAAAG5AUAAAAABugFAAAAAAbsBQACBAgAhA8cBAACOAQAgyAEAAI4BACDJAQAAjgEAIAPHAQAAkQEAIMgBAACRAQAgyQEAAJEBACAMqgEAAJUCADCrAQAAxgEAEKwBAACVAgAwrQEBAPgBACGvAQEA-QEAIbABAQD5AQAhxQFAAPoBACHKAQEA-AEAIcsBAQD5AQAhzAEBAPsBACHNAQEAhwIAIc4BAACWAgAgDwwAAP0BACAhAACXAgAgIgAAlwIAILQBgAAAAAG3AYAAAAABuAGAAAAAAbkBgAAAAAG6AYAAAAABuwGAAAAAAc8BAQAAAAHQAQEAAAAB0QEBAAAAAdIBgAAAAAHTAYAAAAAB1AGAAAAAAQy0AYAAAAABtwGAAAAAAbgBgAAAAAG5AYAAAAABugGAAAAAAbsBgAAAAAHPAQEAAAAB0AEBAAAAAdEBAQAAAAHSAYAAAAAB0wGAAAAAAdQBgAAAAAEOqgEAAJgCADCrAQAAsAEAEKwBAACYAgAwrQEBAPgBACGuAQEA-wEAIa8BAQD5AQAhsAEBAIcCACHBAQEA-QEAIcUBQAD6AQAh1QEBAPkBACHWAQEAhwIAIdcBAQD7AQAh2AEgAIgCACHZAUAAmQIAIQsMAAD9AQAgIQAAmwIAICIAAJsCACC0AUAAAAABtQFAAAAABbYBQAAAAAW3AUAAAAABuAFAAAAAAbkBQAAAAAG6AUAAAAABuwFAAJoCACELDAAA_QEAICEAAJsCACAiAACbAgAgtAFAAAAAAbUBQAAAAAW2AUAAAAAFtwFAAAAAAbgBQAAAAAG5AUAAAAABugFAAAAAAbsBQACaAgAhCLQBQAAAAAG1AUAAAAAFtgFAAAAABbcBQAAAAAG4AUAAAAABuQFAAAAAAboBQAAAAAG7AUAAmwIAIQ1oAACgAgAgqgEAAJwCADCrAQAAlwEAEKwBAACcAgAwrQEBAJ0CACGvAQEAjwIAIbABAQCPAgAhxQFAAJICACHKAQEAnQIAIcsBAQCPAgAhzAEBAJ4CACHNAQEAkAIAIc4BAACfAgAgCLQBAQAAAAG1AQEAAAAEtgEBAAAABLcBAQAAAAG4AQEAAAABuQEBAAAAAboBAQAAAAG7AQEAogIAIQi0AQEAAAABtQEBAAAABbYBAQAAAAW3AQEAAAABuAEBAAAAAbkBAQAAAAG6AQEAAAABuwEBAKECACEMtAGAAAAAAbcBgAAAAAG4AYAAAAABuQGAAAAAAboBgAAAAAG7AYAAAAABzwEBAAAAAdABAQAAAAHRAQEAAAAB0gGAAAAAAdMBgAAAAAHUAYAAAAABEmYAAKgCACBpAACpAgAgqgEAAKYCADCrAQAAjgEAEKwBAACmAgAwrQEBAJ0CACGuAQEAngIAIa8BAQCPAgAhsAEBAJACACHBAQEAjwIAIcUBQACSAgAh1QEBAI8CACHWAQEAkAIAIdcBAQCeAgAh2AEgAJECACHZAUAApwIAIfQBAACOAQAg9QEAAI4BACAItAEBAAAAAbUBAQAAAAW2AQEAAAAFtwEBAAAAAbgBAQAAAAG5AQEAAAABugEBAAAAAbsBAQChAgAhCLQBAQAAAAG1AQEAAAAEtgEBAAAABLcBAQAAAAG4AQEAAAABuQEBAAAAAboBAQAAAAG7AQEAogIAIQOuAQEAAAABrwEBAAAAAbABAQAAAAELZgAApQIAIKoBAACkAgAwqwEAAJEBABCsAQAApAIAMK0BAQCdAgAhrgEBAJ0CACGvAQEAjwIAIbABAQCPAgAhsQEBAI8CACGyAUAAkgIAIbMBAQCeAgAhEGUAAJMCACBnAACUAgAgqgEAAI4CADCrAQAAjAEAEKwBAACOAgAwrQEBAJ0CACG_AQIAqgIAIcABAQCPAgAhwQEBAJACACHCAQEAkAIAIcMBAQCQAgAhxAEgAJECACHFAUAAkgIAIcYBQACSAgAh9AEAAIwBACD1AQAAjAEAIBBmAACoAgAgaQAAqQIAIKoBAACmAgAwqwEAAI4BABCsAQAApgIAMK0BAQCdAgAhrgEBAJ4CACGvAQEAjwIAIbABAQCQAgAhwQEBAI8CACHFAUAAkgIAIdUBAQCPAgAh1gEBAJACACHXAQEAngIAIdgBIACRAgAh2QFAAKcCACEItAFAAAAAAbUBQAAAAAW2AUAAAAAFtwFAAAAAAbgBQAAAAAG5AUAAAAABugFAAAAAAbsBQACbAgAhEGUAAJMCACBnAACUAgAgqgEAAI4CADCrAQAAjAEAEKwBAACOAgAwrQEBAJ0CACG_AQIAqgIAIcABAQCPAgAhwQEBAJACACHCAQEAkAIAIcMBAQCQAgAhxAEgAJECACHFAUAAkgIAIcYBQACSAgAh9AEAAIwBACD1AQAAjAEAIAPHAQAAlwEAIMgBAACXAQAgyQEAAJcBACAItAECAAAAAbUBAgAAAAS2AQIAAAAEtwECAAAAAbgBAgAAAAG5AQIAAAABugECAAAAAbsBAgCAAgAhC6oBAACrAgAwqwEAAIcBABCsAQAAqwIAMK0BAQD4AQAhrwEBAPkBACGwAQEA-QEAIcUBQAD6AQAhzAEBAPgBACHbAUAA-gEAIdwBAQCHAgAh3QFAAJkCACELqgEAAKwCADCrAQAAdAAQrAEAAKwCADCtAQEAnQIAIa8BAQCPAgAhsAEBAI8CACHFAUAAkgIAIcwBAQCdAgAh2wFAAJICACHcAQEAkAIAId0BQACnAgAhCaoBAACtAgAwqwEAAG4AEKwBAACtAgAwrQEBAPgBACHFAUAA-gEAIc4BAACWAgAg3gEBAPgBACHfAQEA-AEAIeABAQD5AQAhCKoBAACuAgAwqwEAAFgAEKwBAACuAgAwrQECAIYCACHMAQEA-AEAIc0BAQCHAgAh4QFAAPoBACHiAQEAhwIAIQcKAAGwAgAhqgEAAK8CADCrAQAAQgAQrAEAAK8CADDFAUAA-gEAIcwBAQD4AQAh4wEBAPkBACEHDAAAgAIAICEAALICACAiAACyAgAgtAEAAQAAAbUBAAEAAAS2AQABAAAEuwEAAbECACEHDAAAgAIAICEAALICACAiAACyAgAgtAEAAQAAAbUBAAEAAAS2AQABAAAEuwEAAbECACEEtAEAAQAAAbUBAAEAAAS2AQABAAAEuwEAAbICACEICQAAtQIAIAoAAbQCACGqAQAAswIAMKsBAAAMABCsAQAAswIAMMUBQACSAgAhzAEBAJ0CACHjAQEAjwIAIQS0AQABAAABtQEAAQAABLYBAAEAAAS7AQABsgIAIR4BAADDAgAgBAAAxAIAIAcAAMUCACAIAADFAgAgCgAAxgIAIAsAAMcCACCqAQAAwAIAMKsBAAADABCsAQAAwAIAMK0BAQCdAgAhrwEBAI8CACGwAQEAjwIAIcUBQACSAgAhxgFAAJICACHbAUAApwIAIdwBAQCQAgAh5AEBAI8CACHlAQEAjwIAIeYBAQCPAgAh5wEBAI8CACHoAQEAjwIAIekBAQCQAgAh6gEAALcCACDrAQAAnwIAIOwBCADBAgAh7QECAMICACHuAQEAngIAIe8BAgCqAgAh9AEAAAMAIPUBAAADACAWqgEAALYCADCrAQAAKgAQrAEAALYCADCtAQEA-AEAIa8BAQD5AQAhsAEBAPkBACHFAUAA-gEAIcYBQAD6AQAh2wFAAJkCACHcAQEAhwIAIeQBAQD5AQAh5QEBAPkBACHmAQEA-QEAIecBAQD5AQAh6AEBAPkBACHpAQEAhwIAIeoBAAC3AgAg6wEAAJYCACDsAQgAuAIAIe0BAgC5AgAh7gEBAPsBACHvAQIAhgIAIQS0AQEAAAAF8AEBAAAAAfEBAQAAAATyAQEAAAAEDQwAAP0BACAfAAC7AgAgIAAAuwIAICEAALsCACAiAAC7AgAgtAEIAAAAAbUBCAAAAAW2AQgAAAAFtwEIAAAAAbgBCAAAAAG5AQgAAAABugEIAAAAAbsBCAC8AgAhDQwAAP0BACAfAAC7AgAgIAAA_QEAICEAAP0BACAiAAD9AQAgtAECAAAAAbUBAgAAAAW2AQIAAAAFtwECAAAAAbgBAgAAAAG5AQIAAAABugECAAAAAbsBAgC6AgAhDQwAAP0BACAfAAC7AgAgIAAA_QEAICEAAP0BACAiAAD9AQAgtAECAAAAAbUBAgAAAAW2AQIAAAAFtwECAAAAAbgBAgAAAAG5AQIAAAABugECAAAAAbsBAgC6AgAhCLQBCAAAAAG1AQgAAAAFtgEIAAAABbcBCAAAAAG4AQgAAAABuQEIAAAAAboBCAAAAAG7AQgAuwIAIQ0MAAD9AQAgHwAAuwIAICAAALsCACAhAAC7AgAgIgAAuwIAILQBCAAAAAG1AQgAAAAFtgEIAAAABbcBCAAAAAG4AQgAAAABuQEIAAAAAboBCAAAAAG7AQgAvAIAIQkJAAC1AgAgqgEAAL0CADCrAQAADgAQrAEAAL0CADCtAQIAqgIAIcwBAQCdAgAhzQEBAJACACHhAUAAkgIAIeIBAQCQAgAhA94BAQAAAAHfAQEAAAAB4AEBAAAAAQsFAAC1AgAgBgAAtQIAIKoBAAC_AgAwqwEAAAcAEKwBAAC_AgAwrQEBAJ0CACHFAUAAkgIAIc4BAACfAgAg3gEBAJ0CACHfAQEAnQIAIeABAQCPAgAhHAEAAMMCACAEAADEAgAgBwAAxQIAIAgAAMUCACAKAADGAgAgCwAAxwIAIKoBAADAAgAwqwEAAAMAEKwBAADAAgAwrQEBAJ0CACGvAQEAjwIAIbABAQCPAgAhxQFAAJICACHGAUAAkgIAIdsBQACnAgAh3AEBAJACACHkAQEAjwIAIeUBAQCPAgAh5gEBAI8CACHnAQEAjwIAIegBAQCPAgAh6QEBAJACACHqAQAAtwIAIOsBAACfAgAg7AEIAMECACHtAQIAwgIAIe4BAQCeAgAh7wECAKoCACEItAEIAAAAAbUBCAAAAAW2AQgAAAAFtwEIAAAAAbgBCAAAAAG5AQgAAAABugEIAAAAAbsBCAC7AgAhCLQBAgAAAAG1AQIAAAAFtgECAAAABbcBAgAAAAG4AQIAAAABuQECAAAAAboBAgAAAAG7AQIA_QEAIR4BAADDAgAgBAAAxAIAIAcAAMUCACAIAADFAgAgCgAAxgIAIAsAAMcCACCqAQAAwAIAMKsBAAADABCsAQAAwAIAMK0BAQCdAgAhrwEBAI8CACGwAQEAjwIAIcUBQACSAgAhxgFAAJICACHbAUAApwIAIdwBAQCQAgAh5AEBAI8CACHlAQEAjwIAIeYBAQCPAgAh5wEBAI8CACHoAQEAjwIAIekBAQCQAgAh6gEAALcCACDrAQAAnwIAIOwBCADBAgAh7QECAMICACHuAQEAngIAIe8BAgCqAgAh9AEAAAMAIPUBAAADACADxwEAAAMAIMgBAAADACDJAQAAAwAgA8cBAAAHACDIAQAABwAgyQEAAAcAIAoJAAC1AgAgCgABtAIAIaoBAACzAgAwqwEAAAwAEKwBAACzAgAwxQFAAJICACHMAQEAnQIAIeMBAQCPAgAh9AEAAAwAIPUBAAAMACADxwEAAA4AIMgBAAAOACDJAQAADgAgAAAAAAH5AQEAAAABAfkBQAAAAAEB-QEBAAAAAQUZAACfBAAgGgAAogQAIPYBAACgBAAg9wEAAKEEACD8AQAAyQEAIAMZAACfBAAg9gEAAKAEACD8AQAAyQEAIAAAAAAABfkBAgAAAAH_AQIAAAABgAICAAAAAYECAgAAAAGCAgIAAAABAfkBIAAAAAELGQAA5gIAMBoAAOsCADD2AQAA5wIAMPcBAADoAgAw-AEAAOkCACD5AQAA6gIAMPoBAADqAgAw-wEAAOoCADD8AQAA6gIAMP0BAADsAgAw_gEAAO0CADALGQAA2gIAMBoAAN8CADD2AQAA2wIAMPcBAADcAgAw-AEAAN0CACD5AQAA3gIAMPoBAADeAgAw-wEAAN4CADD8AQAA3gIAMP0BAADgAgAw_gEAAOECADAGrQEBAAAAAa8BAQAAAAGwAQEAAAABsQEBAAAAAbIBQAAAAAGzAQEAAAABAgAAAJMBACAZAADlAgAgAwAAAJMBACAZAADlAgAgGgAA5AIAIAESAACeBAAwDGYAAKUCACCqAQAApAIAMKsBAACRAQAQrAEAAKQCADCtAQEAAAABrgEBAJ0CACGvAQEAjwIAIbABAQCPAgAhsQEBAI8CACGyAUAAkgIAIbMBAQCeAgAh2gEAAKMCACACAAAAkwEAIBIAAOQCACACAAAA4gIAIBIAAOMCACAKqgEAAOECADCrAQAA4gIAEKwBAADhAgAwrQEBAJ0CACGuAQEAnQIAIa8BAQCPAgAhsAEBAI8CACGxAQEAjwIAIbIBQACSAgAhswEBAJ4CACEKqgEAAOECADCrAQAA4gIAEKwBAADhAgAwrQEBAJ0CACGuAQEAnQIAIa8BAQCPAgAhsAEBAI8CACGxAQEAjwIAIbIBQACSAgAhswEBAJ4CACEGrQEBAMwCACGvAQEAzAIAIbABAQDMAgAhsQEBAMwCACGyAUAAzQIAIbMBAQDOAgAhBq0BAQDMAgAhrwEBAMwCACGwAQEAzAIAIbEBAQDMAgAhsgFAAM0CACGzAQEAzgIAIQatAQEAAAABrwEBAAAAAbABAQAAAAGxAQEAAAABsgFAAAAAAbMBAQAAAAELaQAAgAMAIK0BAQAAAAGvAQEAAAABsAEBAAAAAcEBAQAAAAHFAUAAAAAB1QEBAAAAAdYBAQAAAAHXAQEAAAAB2AEgAAAAAdkBQAAAAAECAAAAigEAIBkAAP8CACADAAAAigEAIBkAAP8CACAaAADxAgAgARIAAJ0EADAQZgAAqAIAIGkAAKkCACCqAQAApgIAMKsBAACOAQAQrAEAAKYCADCtAQEAAAABrgEBAJ4CACGvAQEAjwIAIbABAQCQAgAhwQEBAI8CACHFAUAAkgIAIdUBAQAAAAHWAQEAkAIAIdcBAQCeAgAh2AEgAJECACHZAUAApwIAIQIAAACKAQAgEgAA8QIAIAIAAADuAgAgEgAA7wIAIA6qAQAA7QIAMKsBAADuAgAQrAEAAO0CADCtAQEAnQIAIa4BAQCeAgAhrwEBAI8CACGwAQEAkAIAIcEBAQCPAgAhxQFAAJICACHVAQEAjwIAIdYBAQCQAgAh1wEBAJ4CACHYASAAkQIAIdkBQACnAgAhDqoBAADtAgAwqwEAAO4CABCsAQAA7QIAMK0BAQCdAgAhrgEBAJ4CACGvAQEAjwIAIbABAQCQAgAhwQEBAI8CACHFAUAAkgIAIdUBAQCPAgAh1gEBAJACACHXAQEAngIAIdgBIACRAgAh2QFAAKcCACEKrQEBAMwCACGvAQEAzAIAIbABAQDOAgAhwQEBAMwCACHFAUAAzQIAIdUBAQDMAgAh1gEBAM4CACHXAQEAzgIAIdgBIADXAgAh2QFAAPACACEB-QFAAAAAAQtpAADyAgAgrQEBAMwCACGvAQEAzAIAIbABAQDOAgAhwQEBAMwCACHFAUAAzQIAIdUBAQDMAgAh1gEBAM4CACHXAQEAzgIAIdgBIADXAgAh2QFAAPACACELGQAA8wIAMBoAAPgCADD2AQAA9AIAMPcBAAD1AgAw-AEAAPYCACD5AQAA9wIAMPoBAAD3AgAw-wEAAPcCADD8AQAA9wIAMP0BAAD5AgAw_gEAAPoCADAIrQEBAAAAAa8BAQAAAAGwAQEAAAABxQFAAAAAAcsBAQAAAAHMAQEAAAABzQEBAAAAAc4BgAAAAAECAAAAmQEAIBkAAP4CACADAAAAmQEAIBkAAP4CACAaAAD9AgAgARIAAJwEADANaAAAoAIAIKoBAACcAgAwqwEAAJcBABCsAQAAnAIAMK0BAQAAAAGvAQEAjwIAIbABAQCPAgAhxQFAAJICACHKAQEAnQIAIcsBAQCPAgAhzAEBAJ4CACHNAQEAkAIAIc4BAACfAgAgAgAAAJkBACASAAD9AgAgAgAAAPsCACASAAD8AgAgDKoBAAD6AgAwqwEAAPsCABCsAQAA-gIAMK0BAQCdAgAhrwEBAI8CACGwAQEAjwIAIcUBQACSAgAhygEBAJ0CACHLAQEAjwIAIcwBAQCeAgAhzQEBAJACACHOAQAAnwIAIAyqAQAA-gIAMKsBAAD7AgAQrAEAAPoCADCtAQEAnQIAIa8BAQCPAgAhsAEBAI8CACHFAUAAkgIAIcoBAQCdAgAhywEBAI8CACHMAQEAngIAIc0BAQCQAgAhzgEAAJ8CACAIrQEBAMwCACGvAQEAzAIAIbABAQDMAgAhxQFAAM0CACHLAQEAzAIAIcwBAQDOAgAhzQEBAM4CACHOAYAAAAABCK0BAQDMAgAhrwEBAMwCACGwAQEAzAIAIcUBQADNAgAhywEBAMwCACHMAQEAzgIAIc0BAQDOAgAhzgGAAAAAAQitAQEAAAABrwEBAAAAAbABAQAAAAHFAUAAAAABywEBAAAAAcwBAQAAAAHNAQEAAAABzgGAAAAAAQtpAACAAwAgrQEBAAAAAa8BAQAAAAGwAQEAAAABwQEBAAAAAcUBQAAAAAHVAQEAAAAB1gEBAAAAAdcBAQAAAAHYASAAAAAB2QFAAAAAAQQZAADzAgAw9gEAAPQCADD4AQAA9gIAIPwBAAD3AgAwBBkAAOYCADD2AQAA5wIAMPgBAADpAgAg_AEAAOoCADAEGQAA2gIAMPYBAADbAgAw-AEAAN0CACD8AQAA3gIAMAAAAAAABRkAAJcEACAaAACaBAAg9gEAAJgEACD3AQAAmQQAIPwBAACKAQAgAxkAAJcEACD2AQAAmAQAIPwBAACKAQAgAAAABxkAAJIEACAaAACVBAAg9gEAAJMEACD3AQAAlAQAIPoBAACMAQAg-wEAAIwBACD8AQAAyQEAIAMZAACSBAAg9gEAAJMEACD8AQAAyQEAIAdmAACQAwAgaQAAkQMAIK4BAADIAgAgsAEAAMgCACDWAQAAyAIAINcBAADIAgAg2QEAAMgCACAFZQAAgwMAIGcAAIQDACDBAQAAyAIAIMIBAADIAgAgwwEAAMgCACAAAAAAAAAABRkAAIoEACAaAACQBAAg9gEAAIsEACD3AQAAjwQAIPwBAAABACAFGQAAiAQAIBoAAI0EACD2AQAAiQQAIPcBAACMBAAg_AEAAAEAIAMZAACKBAAg9gEAAIsEACD8AQAAAQAgAxkAAIgEACD2AQAAiQQAIPwBAAABACAAAAAAAAUZAACDBAAgGgAAhgQAIPYBAACEBAAg9wEAAIUEACD8AQAAAQAgAxkAAIMEACD2AQAAhAQAIPwBAAABACAAAAAB-QEAAQAAAQUZAAD-AwAgGgAAgQQAIPYBAAD_AwAg9wEAAIAEACD8AQAAAQAgAxkAAP4DACD2AQAA_wMAIPwBAAABACANAQAAqQMAIAQAAPEDACAHAADyAwAgCAAA8gMAIAoAAPMDACALAAD0AwAg2wEAAMgCACDcAQAAyAIAIOkBAADIAgAg6wEAAMgCACDsAQAAyAIAIO0BAADIAgAg7gEAAMgCACAAAAAAAAL5AQEAAAAEgwIBAAAABQX5AQgAAAAB_wEIAAAAAYACCAAAAAGBAggAAAABggIIAAAAAQX5AQIAAAAB_wECAAAAAYACAgAAAAGBAgIAAAABggICAAAAAQcZAAD1AwAgGgAA_AMAIPYBAAD2AwAg9wEAAPsDACD6AQAAAwAg-wEAAAMAIPwBAAABACALGQAA3gMAMBoAAOMDADD2AQAA3wMAMPcBAADgAwAw-AEAAOEDACD5AQAA4gMAMPoBAADiAwAw-wEAAOIDADD8AQAA4gMAMP0BAADkAwAw_gEAAOUDADALGQAA1QMAMBoAANkDADD2AQAA1gMAMPcBAADXAwAw-AEAANgDACD5AQAAzQMAMPoBAADNAwAw-wEAAM0DADD8AQAAzQMAMP0BAADaAwAw_gEAANADADALGQAAyQMAMBoAAM4DADD2AQAAygMAMPcBAADLAwAw-AEAAMwDACD5AQAAzQMAMPoBAADNAwAw-wEAAM0DADD8AQAAzQMAMP0BAADPAwAw_gEAANADADAHGQAAxAMAIBoAAMcDACD2AQAAxQMAIPcBAADGAwAg-gEAAAwAIPsBAAAMACD8AQAALQAgCxkAALgDADAaAAC9AwAw9gEAALkDADD3AQAAugMAMPgBAAC7AwAg-QEAALwDADD6AQAAvAMAMPsBAAC8AwAw_AEAALwDADD9AQAAvgMAMP4BAAC_AwAwBK0BAgAAAAHNAQEAAAAB4QFAAAAAAeIBAQAAAAECAAAAEAAgGQAAwwMAIAMAAAAQACAZAADDAwAgGgAAwgMAIAESAAD6AwAwCQkAALUCACCqAQAAvQIAMKsBAAAOABCsAQAAvQIAMK0BAgAAAAHMAQEAnQIAIc0BAQCQAgAh4QFAAJICACHiAQEAkAIAIQIAAAAQACASAADCAwAgAgAAAMADACASAADBAwAgCKoBAAC_AwAwqwEAAMADABCsAQAAvwMAMK0BAgCqAgAhzAEBAJ0CACHNAQEAkAIAIeEBQACSAgAh4gEBAJACACEIqgEAAL8DADCrAQAAwAMAEKwBAAC_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_AEAAM0DADAEGQAAyQMAMPYBAADKAwAw-AEAAMwDACD8AQAAzQMAMAMZAADEAwAg9gEAAMUDACD8AQAALQAgBBkAALgDADD2AQAAuQMAMPgBAAC7AwAg_AEAALwDADADGQAA9QMAIPYBAAD2AwAg_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_AQIA1gIAIcABAQDMAgAhwQEBAM4CACHCAQEAzgIAIcMBAQDOAgAhxAEgANcCACHFAUAAzQIAIcYBQADNAgAhDGYAAI4DACCtAQEAAAABrgEBAAAAAa8BAQAAAAGwAQEAAAABwQEBAAAAAcUBQAAAAAHVAQEAAAAB1gEBAAAAAdcBAQAAAAHYASAAAAAB2QFAAAAAAQIAAACKAQAgGQAAlwQAIAMAAACOAQAgGQAAlwQAIBoAAJsEACAOAAAAjgEAIBIAAJsEACBmAACNAwAgrQEBAMwCACGuAQEAzgIAIa8BAQDMAgAhsAEBAM4CACHBAQEAzAIAIcUBQADNAgAh1QEBAMwCACHWAQEAzgIAIdcBAQDOAgAh2AEgANcCACHZAUAA8AIAIQxmAACNAwAgrQEBAMwCACGuAQEAzgIAIa8BAQDMAgAhsAEBAM4CACHBAQEAzAIAIcUBQADNAgAh1QEBAMwCACHWAQEAzgIAIdcBAQDOAgAh2AEgANcCACHZAUAA8AIAIQitAQEAAAABrwEBAAAAAbABAQAAAAHFAUAAAAABywEBAAAAAcwBAQAAAAHNAQEAAAABzgGAAAAAAQqtAQEAAAABrwEBAAAAAbABAQAAAAHBAQEAAAABxQFAAAAAAdUBAQAAAAHWAQEAAAAB1wEBAAAAAdgBIAAAAAHZAUAAAAABBq0BAQAAAAGvAQEAAAABsAEBAAAAAbEBAQAAAAGyAUAAAAABswEBAAAAAQplAACBAwAgrQEBAAAAAb8BAgAAAAHAAQEAAAABwQEBAAAAAcIBAQAAAAHDAQEAAAABxAEgAAAAAcUBQAAAAAHGAUAAAAABAgAAAMkBACAZAACfBAAgAwAAAIwBACAZAACfBAAgGgAAowQAIAwAAACMAQAgEgAAowQAIGUAANgCACCtAQEAzAIAIb8BAgDWAgAhwAEBAMwCACHBAQEAzgIAIcIBAQDOAgAhwwEBAM4CACHEASAA1wIAIcUBQADNAgAhxgFAAM0CACEKZQAA2AIAIK0BAQDMAgAhvwECANYCACHAAQEAzAIAIcEBAQDOAgAhwgEBAM4CACHDAQEAzgIAIcQBIADXAgAhxQFAAM0CACHGAUAAzQIAIQcBBAEEBgEHCgIICwIKDQMLEQQMAAUCBQABBgABAQkAAQEJAAEEBBIABxMACBQACxUAAAEBHwEBASUBBQwACh8ACyAADCEADSIADgAAAAAABQwACh8ACyAADCEADSIADgEJAAEBCQABAwwAEyEAFCIAFQAAAAMMABMhABQiABUBCQABAQkAAQUMABofABsgABwhAB0iAB4AAAAAAAUMABofABsgABwhAB0iAB4CBQABBgABAgUAAQYAAQMMACMhACQiACUAAAADDAAjIQAkIgAlAAAAAwwAKyEALCIALQAAAAMMACshACwiAC0DDAA0Zo0BMGmaATMDDAAyZZABL2eUATEBZgAwAmWVAQBnlgEAAWgALwFpmwEAAWalATABZqsBMAMMADghADkiADoAAAADDAA4IQA5IgA6AWgALwFoAC8DDAA_IQBAIgBBAAAAAwwAPyEAQCIAQQAABQwARh8ARyAASCEASSIASgAAAAAABQwARh8ARyAASCEASSIASgFmADABZgAwAwwATyEAUCIAUQAAAAMMAE8hAFAiAFENAgEOFgEPFwEQGAERGQETGwEUHQYVHgcWIQEXIwYYJAgbJgEcJwEdKAYjKwkkLA8lLgMmLwMnMQMoMgMpMwMqNQMrNwYsOBAtOgMuPAYvPREwPgMxPwMyQAYzQxI0RBY1RQQ2RgQ3RwQ4SAQ5SQQ6SwQ7TQY8Thc9UAQ-UgY_UxhAVARBVQRCVgZDWRlEWh9FWwJGXAJHXQJIXgJJXwJKYQJLYwZMZCBNZgJOaAZPaSFQagJRawJSbAZTbyJUcCZVcidWcydXdidYdydZeCdaeidbfAZcfShdfydegQEGX4IBKWCDASdhhAEnYoUBBmOIASpkiQEuaosBL2ucAS9snQEvbZ4BL26fAS9voQEvcKMBBnGkATVypwEvc6kBBnSqATZ1rAEvdq0BL3euAQZ4sQE3ebIBO3qzATN7tAEzfLUBM322ATN-twEzf7kBM4ABuwEGgQG8ATyCAb4BM4MBwAEGhAHBAT2FAcIBM4YBwwEzhwHEAQaIAccBPokByAFCigHKATCLAcsBMIwBzQEwjQHOATCOAc8BMI8B0QEwkAHTAQaRAdQBQ5IB1gEwkwHYAQaUAdkBRJUB2gEwlgHbATCXAdwBBpgB3wFFmQHgAUuaAeEBMZsB4gExnAHjATGdAeQBMZ4B5QExnwHnATGgAekBBqEB6gFMogHsATGjAe4BBqQB7wFNpQHwATGmAfEBMacB8gEGqAH1AU6pAfYBUg"
|
|
3579
|
+
};
|
|
3580
|
+
async function decodeBase64AsWasm(wasmBase64) {
|
|
3581
|
+
const { Buffer: Buffer2 } = await import("buffer");
|
|
3582
|
+
const wasmArray = Buffer2.from(wasmBase64, "base64");
|
|
3583
|
+
return new WebAssembly.Module(wasmArray);
|
|
3584
|
+
}
|
|
3585
|
+
config.compilerWasm = {
|
|
3586
|
+
getRuntime: async () => await import("@prisma/client/runtime/query_compiler_fast_bg.postgresql.mjs"),
|
|
3587
|
+
getQueryCompilerWasmModule: async () => {
|
|
3588
|
+
const { wasm } = await import("@prisma/client/runtime/query_compiler_fast_bg.postgresql.wasm-base64.mjs");
|
|
3589
|
+
return await decodeBase64AsWasm(wasm);
|
|
3590
|
+
},
|
|
3591
|
+
importName: "./query_compiler_fast_bg.js"
|
|
3592
|
+
};
|
|
3593
|
+
function getPrismaClientClass() {
|
|
3594
|
+
return runtime.getPrismaClient(config);
|
|
3595
|
+
}
|
|
3596
|
+
var getExtensionContext = runtime2.Extensions.getExtensionContext;
|
|
3597
|
+
var NullTypes2 = {
|
|
3598
|
+
DbNull: runtime2.NullTypes.DbNull,
|
|
3599
|
+
JsonNull: runtime2.NullTypes.JsonNull,
|
|
3600
|
+
AnyNull: runtime2.NullTypes.AnyNull
|
|
3601
|
+
};
|
|
3602
|
+
var TransactionIsolationLevel = runtime2.makeStrictEnum({
|
|
3603
|
+
ReadUncommitted: "ReadUncommitted",
|
|
3604
|
+
ReadCommitted: "ReadCommitted",
|
|
3605
|
+
RepeatableRead: "RepeatableRead",
|
|
3606
|
+
Serializable: "Serializable"
|
|
3607
|
+
});
|
|
3608
|
+
var defineExtension = runtime2.Extensions.defineExtension;
|
|
3609
|
+
globalThis["__dirname"] = path22.dirname(fileURLToPath(import.meta.url));
|
|
3610
|
+
var PrismaClient = getPrismaClientClass();
|
|
3611
|
+
|
|
3612
|
+
export {
|
|
3613
|
+
__require,
|
|
3614
|
+
resolveLifecycleConfig,
|
|
3615
|
+
applyLifecycleDefaults,
|
|
3616
|
+
LifecycleScheduler,
|
|
3617
|
+
mergeAndRank,
|
|
3618
|
+
resolveVisibility,
|
|
3619
|
+
generateEmbedding,
|
|
3620
|
+
computeRepositoryHealth,
|
|
3621
|
+
generateSuggestions,
|
|
3622
|
+
persistReviewableSuggestions,
|
|
3623
|
+
formatSuggestion,
|
|
3624
|
+
findConsolidationCandidates,
|
|
3625
|
+
executeConsolidation,
|
|
3626
|
+
formatCandidatePreview,
|
|
3627
|
+
buildAutoLinkQuery,
|
|
3628
|
+
getNotifications,
|
|
3629
|
+
formatNotificationsSummary,
|
|
3630
|
+
runLocalLifecycleMaintenance,
|
|
3631
|
+
MEMORY_TEMPLATES,
|
|
3632
|
+
getTemplate,
|
|
3633
|
+
applyTemplate,
|
|
3634
|
+
formatTemplateList,
|
|
3635
|
+
validateMemoryType,
|
|
3636
|
+
parseConfidence,
|
|
3637
|
+
parseThreshold,
|
|
3638
|
+
parseTtl,
|
|
3639
|
+
parsePositiveInt,
|
|
3640
|
+
detectGitInfo,
|
|
3641
|
+
getDataDir,
|
|
3642
|
+
getDbPath,
|
|
3643
|
+
getConfigPath,
|
|
3644
|
+
isInitialized,
|
|
3645
|
+
findRepoRoot,
|
|
3646
|
+
loadConfig,
|
|
3647
|
+
saveConfig,
|
|
3648
|
+
defaultConfig,
|
|
3649
|
+
RemoteClient,
|
|
3650
|
+
LocalStore
|
|
3651
|
+
};
|
|
3652
|
+
//# sourceMappingURL=chunk-CKUDYQYP.js.map
|