@cortexkit/pi-magic-context 0.47.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/auto-search-worker.js +27 -22
  2. package/dist/embedding-activity-ew6dshfd.js +10 -0
  3. package/dist/embedding-backfill-b71dvfs8.js +20 -0
  4. package/dist/embedding-cache-7rp4gqzr.js +16 -0
  5. package/dist/embedding-worker.js +11 -8
  6. package/dist/harness-9872e0ym.js +10 -0
  7. package/dist/historian-calibration-extension.js +30 -4
  8. package/dist/index-041ex0cj.js +519 -0
  9. package/dist/index-0h2mkab2.js +17 -0
  10. package/dist/{index-2p1e8706.js → index-0vrkp1jq.js} +3 -3
  11. package/dist/index-135e8hvg.js +43 -0
  12. package/dist/{index-vgcemg0n.js → index-1evzceyw.js} +151 -2452
  13. package/dist/index-21v1kfs7.js +2309 -0
  14. package/dist/{index-n3g5mwa8.js → index-290tzwp1.js} +3 -3
  15. package/dist/{index-0qejsd7f.js → index-3cgbv47m.js} +289 -45
  16. package/dist/{index-xpbh1ypk.js → index-3mgf68pn.js} +38 -741
  17. package/dist/index-3rea6zd9.js +2396 -0
  18. package/dist/index-674f6qs2.js +16 -0
  19. package/dist/index-8976mezv.js +209 -0
  20. package/dist/index-904reqtg.js +152 -0
  21. package/dist/index-9p40dgkm.js +22 -0
  22. package/dist/index-9x1f96r8.js +11 -0
  23. package/dist/index-bejpsj10.js +1060 -0
  24. package/dist/{index-spk5vrhy.js → index-bgmv2kaf.js} +1 -16
  25. package/dist/{index-k3ezsjng.js → index-ehybv5nr.js} +14657 -13951
  26. package/dist/index-fapenpch.js +13 -0
  27. package/dist/index-hwqb3s7f.js +3804 -0
  28. package/dist/index-kjd1fj55.js +3106 -0
  29. package/dist/{index-td885aah.js → index-mcgehvgw.js} +181 -230
  30. package/dist/index-r024r8da.js +171 -0
  31. package/dist/{index-266qrgss.js → index-r56xgha8.js} +301 -27
  32. package/dist/{index-kztsbkqq.js → index-t7xfqtg6.js} +171 -168
  33. package/dist/index-vakawmbc.js +1262 -0
  34. package/dist/{index-7n8gb6j3.js → index-x3dcnxh0.js} +34 -33
  35. package/dist/index-xjqheg7a.js +931 -0
  36. package/dist/index-yarjn32j.js +3026 -0
  37. package/dist/index-yhzhdp3m.js +143 -0
  38. package/dist/index-yzcjxnt2.js +787 -0
  39. package/dist/{index-w76x3af7.js → index-zh1jw8qp.js} +107 -4
  40. package/dist/index.js +4926 -2437
  41. package/dist/logger-j6tecd1h.js +19 -0
  42. package/dist/message-index-bounded-qz5dz2se.js +120 -0
  43. package/dist/{message-time-backfill-d3db2etr.js → message-time-backfill-05ppypkz.js} +7 -6
  44. package/dist/migration-worker.js +20 -14
  45. package/dist/project-embedding-registry-ky7wrkr1.js +85 -0
  46. package/dist/raw-ordinal-warmup-worker.js +60 -0
  47. package/dist/{read-session-chunk-atxsyfqv.js → read-session-chunk-24sd89tf.js} +14 -6
  48. package/dist/read-session-raw-npx3920c.js +57 -0
  49. package/dist/{safe-notification-target-6cn3z99t.js → safe-notification-target-vn9kpmv4.js} +3 -2
  50. package/dist/search-jbh28nwy.js +40 -0
  51. package/dist/sqlite-3yye76ht.js +44 -0
  52. package/dist/sqlite-context-pragmas-gcgch6qz.js +6 -0
  53. package/dist/storage-memory-1z20y55x.js +71 -0
  54. package/dist/storage-tags-kjnsqbdb.js +127 -0
  55. package/dist/subagent-entry.js +35 -17
  56. package/dist/transformers-node-wasm.js +18415 -18415
  57. package/package.json +3 -3
  58. package/dist/index-7s8vw2y0.js +0 -5207
  59. package/dist/index-qaa5j277.js +0 -8544
  60. /package/dist/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
@@ -0,0 +1,3804 @@
1
+ import {
2
+ getHarness2
3
+ } from "./index-0h2mkab2.js";
4
+ import {
5
+ isTransientSqliteError2
6
+ } from "./index-3cgbv47m.js";
7
+ import {
8
+ PROMOTABLE_CATEGORIES,
9
+ CATEGORY_DEFAULT_TTL
10
+ } from "./index-135e8hvg.js";
11
+ import {
12
+ getDataDir,
13
+ sanitizeDiagnosticText,
14
+ log2,
15
+ sessionLog2
16
+ } from "./index-x3dcnxh0.js";
17
+ import {
18
+ resolveWorkspaceShareCategories,
19
+ resolveWorkspaceIdentitySet,
20
+ expandWorkspaceIdentitySetWithAliases,
21
+ resolveStoredPathWorkspaceIdentity,
22
+ sourceNameForMemory,
23
+ managedAuthorityNoteRow
24
+ } from "./index-3mgf68pn.js";
25
+ import {
26
+ isKnownAutocommit
27
+ } from "./index-mcgehvgw.js";
28
+ import {
29
+ SubcClient
30
+ } from "./index-21v1kfs7.js";
31
+ import {
32
+ buildCanonicalChunkTextFromFts,
33
+ loadCompartmentChunkEmbeddingsForSearch
34
+ } from "./index-bejpsj10.js";
35
+ import {
36
+ recordEmbeddingMeasurement,
37
+ saveCommitEmbedding,
38
+ loadProjectCommitEmbeddings,
39
+ loadUnembeddedCommits,
40
+ countEmbeddedCommits,
41
+ upsertCommits,
42
+ getLatestIndexedCommitSha,
43
+ enforceProjectCap,
44
+ contentSha2562,
45
+ getShadowEmbeddingMeasurementCohort2,
46
+ getPrimaryEmbeddingMeasurementCohort2,
47
+ embedShadowTextForProject2,
48
+ enqueueShadowEmbeddingItems2,
49
+ getProjectEmbeddingSnapshot2,
50
+ embedTextForProject2,
51
+ embedItemsForProject2
52
+ } from "./index-yarjn32j.js";
53
+ import {
54
+ saveEmbeddingIfHashMatches,
55
+ getProjectEmbeddings2
56
+ } from "./index-yhzhdp3m.js";
57
+ import {
58
+ cosineSimilarity,
59
+ isEmbeddingEnabled,
60
+ embedText,
61
+ ensureMemoryEmbeddings2
62
+ } from "./index-904reqtg.js";
63
+ import {
64
+ getMemorySelectColumns2,
65
+ isMemoryRow2,
66
+ toMemory2,
67
+ ModuleMemoryAuthorityError2,
68
+ insertMemory2,
69
+ getMemoryByHash2,
70
+ getMemoriesByProject2,
71
+ buildWorkspaceMemorySqlFilter2,
72
+ getMemoriesByProjects2,
73
+ getMemoryById2,
74
+ updateMemorySeenCount2,
75
+ updateMemoryRetrievalCount2
76
+ } from "./index-xjqheg7a.js";
77
+ import {
78
+ getIndexedMessageCorpusSize
79
+ } from "./index-yzcjxnt2.js";
80
+ import {
81
+ computeNormalizedHash
82
+ } from "./index-9x1f96r8.js";
83
+
84
+ // ../plugin/src/features/magic-context/storage-notes.ts
85
+ var NOTE_TYPES = new Set(["session", "smart"]);
86
+ var NOTE_STATUSES = new Set(["active", "pending", "ready", "dismissed"]);
87
+ var NOTE_CHECK_STATUSES = new Set([
88
+ "uncompiled",
89
+ "compiled",
90
+ "failing",
91
+ "fallback",
92
+ "parked"
93
+ ]);
94
+ var CONDITION_COMPILE_STATUSES = new Set([
95
+ "compiled",
96
+ "plain",
97
+ "refused"
98
+ ]);
99
+ var DEFAULT_SMART_STATUSES = ["pending", "ready"];
100
+ function toNullableString(value) {
101
+ return typeof value === "string" && value.length > 0 ? value : null;
102
+ }
103
+ function toNullableNumber(value) {
104
+ return typeof value === "number" ? value : null;
105
+ }
106
+ function isNoteRow(row) {
107
+ if (row === null || typeof row !== "object")
108
+ return false;
109
+ const candidate = row;
110
+ return typeof candidate.id === "number" && typeof candidate.type === "string" && NOTE_TYPES.has(candidate.type) && typeof candidate.status === "string" && NOTE_STATUSES.has(candidate.status) && typeof candidate.content === "string" && (candidate.session_id === null || typeof candidate.session_id === "string") && (candidate.project_path === null || typeof candidate.project_path === "string") && (candidate.surface_condition === null || typeof candidate.surface_condition === "string") && typeof candidate.created_at === "number" && typeof candidate.updated_at === "number" && (candidate.last_checked_at === null || typeof candidate.last_checked_at === "number") && (candidate.ready_at === null || typeof candidate.ready_at === "number") && (candidate.ready_reason === null || typeof candidate.ready_reason === "string");
111
+ }
112
+ function toNote(row) {
113
+ return {
114
+ id: row.id,
115
+ type: row.type,
116
+ status: row.status,
117
+ content: row.content,
118
+ sessionId: toNullableString(row.session_id),
119
+ projectPath: toNullableString(row.project_path),
120
+ surfaceCondition: toNullableString(row.surface_condition),
121
+ compiledProvider: toNullableString(row.compiled_provider),
122
+ compiledConfig: toNullableString(row.compiled_config),
123
+ compiledAt: toNullableNumber(row.compiled_at),
124
+ compileStatus: typeof row.compile_status === "string" && CONDITION_COMPILE_STATUSES.has(row.compile_status) ? row.compile_status : null,
125
+ createdAt: row.created_at,
126
+ updatedAt: row.updated_at,
127
+ lastCheckedAt: toNullableNumber(row.last_checked_at),
128
+ readyAt: toNullableNumber(row.ready_at),
129
+ readyReason: toNullableString(row.ready_reason),
130
+ anchorOrdinal: toNullableNumber(row.anchor_ordinal),
131
+ compiledCheck: toNullableString(row.compiled_check),
132
+ manifestJson: toNullableString(row.manifest_json),
133
+ checkHash: toNullableString(row.check_hash),
134
+ checkCron: toNullableString(row.check_cron),
135
+ checkVersion: toNullableNumber(row.check_version),
136
+ checkStatus: typeof row.check_status === "string" && NOTE_CHECK_STATUSES.has(row.check_status) ? row.check_status : null,
137
+ checkFailureCount: toNullableNumber(row.check_failure_count) ?? 0,
138
+ checkNetworkFailureCount: toNullableNumber(row.check_network_failure_count) ?? 0,
139
+ checkQuarantinedUntil: toNullableNumber(row.check_quarantined_until),
140
+ checkNextDueAt: toNullableNumber(row.check_next_due_at),
141
+ checkCompiledAt: toNullableNumber(row.check_compiled_at),
142
+ checkFalseSinceAt: toNullableNumber(row.check_false_since_at),
143
+ checkLastLivenessAt: toNullableNumber(row.check_last_liveness_at),
144
+ policyVersion: toNullableNumber(row.policy_version)
145
+ };
146
+ }
147
+ function noteCheckColumnsExist(db) {
148
+ try {
149
+ const rows = db.prepare("PRAGMA table_info(notes)").all();
150
+ return rows.some((row) => row.name === "compiled_check");
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
155
+ var PENDING_SESSION_NOTE_PREDICATE = "type = 'session' AND status = 'pending' AND surface_condition IS NOT NULL";
156
+ function healPendingSessionNotes(db) {
157
+ let predicate = `${PENDING_SESSION_NOTE_PREDICATE} AND NOT ${managedAuthorityNoteRow("notes")}`;
158
+ let healable;
159
+ try {
160
+ healable = db.prepare(`SELECT 1 FROM notes WHERE ${predicate} LIMIT 1`).get();
161
+ } catch (error) {
162
+ if (!(error instanceof Error) || !error.message.includes("no such table"))
163
+ throw error;
164
+ predicate = PENDING_SESSION_NOTE_PREDICATE;
165
+ healable = db.prepare(`SELECT 1 FROM notes WHERE ${predicate} LIMIT 1`).get();
166
+ }
167
+ if (healable == null)
168
+ return;
169
+ try {
170
+ db.prepare(`UPDATE notes SET status = 'active', surface_condition = NULL WHERE ${predicate}`).run();
171
+ } catch (error) {
172
+ if (!isTransientSqliteError2(error))
173
+ throw error;
174
+ }
175
+ }
176
+ function getNoteById(db, noteId) {
177
+ healPendingSessionNotes(db);
178
+ const row = db.prepare("SELECT * FROM notes WHERE id = ?").get(noteId);
179
+ return isNoteRow(row) ? toNote(row) : null;
180
+ }
181
+ function getNoteByIdInScope(db, noteId, scope) {
182
+ const note = getNoteById(db, noteId);
183
+ return note && noteBelongsToScope(note, scope) ? note : null;
184
+ }
185
+ function noteBelongsToScope(note, scope) {
186
+ if (note.type === "session") {
187
+ return note.sessionId === scope.sessionId;
188
+ }
189
+ return note.projectPath === scope.projectPath;
190
+ }
191
+ function buildStatusClause(status) {
192
+ if (status === undefined) {
193
+ return null;
194
+ }
195
+ const statuses = Array.isArray(status) ? status : [status];
196
+ if (statuses.length === 0) {
197
+ return null;
198
+ }
199
+ const placeholders = statuses.map(() => "?").join(", ");
200
+ return {
201
+ sql: `status IN (${placeholders})`,
202
+ params: statuses
203
+ };
204
+ }
205
+ function getNotes(db, options = {}) {
206
+ healPendingSessionNotes(db);
207
+ const clauses = [];
208
+ const params = [];
209
+ if (options.sessionId !== undefined) {
210
+ clauses.push("session_id = ?");
211
+ params.push(options.sessionId);
212
+ }
213
+ if (options.projectPath !== undefined) {
214
+ clauses.push("project_path = ?");
215
+ params.push(options.projectPath);
216
+ }
217
+ if (options.type !== undefined) {
218
+ clauses.push("type = ?");
219
+ params.push(options.type);
220
+ }
221
+ const statusClause = buildStatusClause(options.status);
222
+ if (statusClause) {
223
+ clauses.push(statusClause.sql);
224
+ params.push(...statusClause.params);
225
+ }
226
+ const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
227
+ return db.prepare(`SELECT * FROM notes${where} ORDER BY created_at ASC, id ASC`).all(...params).filter(isNoteRow).map(toNote);
228
+ }
229
+ function addNote(db, type, options) {
230
+ const now = Date.now();
231
+ const result = type === "session" ? db.prepare("INSERT INTO notes (type, status, content, session_id, created_at, updated_at, harness, anchor_ordinal) VALUES ('session', 'active', ?, ?, ?, ?, ?, ?) RETURNING *").get(options.content, options.sessionId, now, now, getHarness2(), options.anchorOrdinal ?? null) : db.prepare("INSERT INTO notes (type, status, content, session_id, project_path, surface_condition, compiled_provider, compiled_config, compiled_at, compile_status, created_at, updated_at, harness, anchor_ordinal) VALUES ('smart', 'pending', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *").get(options.content, options.sessionId ?? null, options.projectPath, options.surfaceCondition, options.compiledProvider ?? null, options.compiledConfig ?? null, options.compiledAt ?? null, options.compileStatus ?? null, now, now, getHarness2(), options.anchorOrdinal ?? null);
232
+ if (!isNoteRow(result)) {
233
+ throw new Error("[notes] failed to insert note");
234
+ }
235
+ return toNote(result);
236
+ }
237
+ function getSessionNotes(db, sessionId) {
238
+ return getNotes(db, { sessionId, type: "session", status: "active" });
239
+ }
240
+ function getSmartNotes(db, projectPath, status) {
241
+ return getNotes(db, {
242
+ projectPath,
243
+ type: "smart",
244
+ status: status ?? DEFAULT_SMART_STATUSES
245
+ });
246
+ }
247
+ function getPendingSmartNotes(db, projectPath) {
248
+ return getSmartNotes(db, projectPath, "pending");
249
+ }
250
+ function getReadySmartNotes(db, projectPath) {
251
+ return getSmartNotes(db, projectPath, "ready");
252
+ }
253
+ function updateNote(db, noteId, updates, scope) {
254
+ const existing = getNoteByIdInScope(db, noteId, scope);
255
+ if (!existing) {
256
+ return null;
257
+ }
258
+ if (updates.surfaceCondition !== undefined && existing.type !== "smart") {
259
+ return null;
260
+ }
261
+ const now = Date.now();
262
+ const sets = ["updated_at = ?"];
263
+ const params = [now];
264
+ if (updates.content !== undefined) {
265
+ sets.push("content = ?");
266
+ params.push(updates.content);
267
+ }
268
+ if (updates.sessionId !== undefined) {
269
+ sets.push("session_id = ?");
270
+ params.push(updates.sessionId);
271
+ }
272
+ const smartConditionChanged = existing.type === "smart" && updates.surfaceCondition !== undefined && updates.surfaceCondition !== existing.surfaceCondition;
273
+ if (updates.status !== undefined && !smartConditionChanged) {
274
+ sets.push("status = ?");
275
+ params.push(updates.status);
276
+ }
277
+ if (existing.type === "smart") {
278
+ if (updates.projectPath !== undefined) {
279
+ sets.push("project_path = ?");
280
+ params.push(updates.projectPath);
281
+ }
282
+ if (updates.surfaceCondition !== undefined) {
283
+ sets.push("surface_condition = ?");
284
+ params.push(updates.surfaceCondition);
285
+ }
286
+ if (smartConditionChanged) {
287
+ sets.push("status = 'pending'", "last_checked_at = NULL", "ready_at = NULL", "ready_reason = NULL");
288
+ sets.push("compiled_provider = ?", "compiled_config = ?", "compiled_at = ?", "compile_status = ?");
289
+ params.push(updates.compiledProvider ?? null, updates.compiledConfig ?? null, updates.compiledAt ?? null, updates.compileStatus ?? null);
290
+ if (noteCheckColumnsExist(db)) {
291
+ sets.push("compiled_check = NULL", "manifest_json = NULL", "check_hash = NULL", "check_cron = NULL", "check_version = 0", "check_status = 'uncompiled'", "check_failure_count = 0", "check_network_failure_count = 0", "check_quarantined_until = NULL", "check_next_due_at = NULL", "check_compiled_at = NULL", "check_false_since_at = NULL", "check_last_liveness_at = NULL");
292
+ }
293
+ } else {
294
+ if (updates.lastCheckedAt !== undefined) {
295
+ sets.push("last_checked_at = ?");
296
+ params.push(updates.lastCheckedAt);
297
+ }
298
+ if (updates.readyAt !== undefined) {
299
+ sets.push("ready_at = ?");
300
+ params.push(updates.readyAt);
301
+ }
302
+ if (updates.readyReason !== undefined) {
303
+ sets.push("ready_reason = ?");
304
+ params.push(updates.readyReason);
305
+ }
306
+ }
307
+ }
308
+ if (sets.length === 1) {
309
+ return null;
310
+ }
311
+ params.push(noteId);
312
+ const result = db.prepare(`UPDATE notes SET ${sets.join(", ")} WHERE id = ? RETURNING *`).get(...params);
313
+ return isNoteRow(result) ? toNote(result) : null;
314
+ }
315
+ function dismissNotes(db, noteIds, scope) {
316
+ const now = Date.now();
317
+ return db.transaction(() => noteIds.map((noteId) => {
318
+ const existing = getNoteById(db, noteId);
319
+ if (!existing)
320
+ return { noteId, outcome: "not_found" };
321
+ if (!noteBelongsToScope(existing, scope)) {
322
+ return { noteId, outcome: "not_owned" };
323
+ }
324
+ if (existing.status === "dismissed") {
325
+ return { noteId, outcome: "already_dismissed" };
326
+ }
327
+ const result = db.prepare("UPDATE notes SET status = 'dismissed', updated_at = ? WHERE id = ? AND status != 'dismissed'").run(now, noteId);
328
+ return {
329
+ noteId,
330
+ outcome: result.changes > 0 ? "dismissed" : "already_dismissed"
331
+ };
332
+ })).immediate();
333
+ }
334
+ function dismissNote(db, noteId, scope) {
335
+ return dismissNotes(db, [noteId], scope)[0]?.outcome === "dismissed";
336
+ }
337
+ function markNoteReady(db, noteId, reason) {
338
+ const now = Date.now();
339
+ db.prepare("UPDATE notes SET status = 'ready', ready_at = ?, ready_reason = ?, updated_at = ?, last_checked_at = ? WHERE id = ? AND type = 'smart'").run(now, reason ?? null, now, now, noteId);
340
+ }
341
+ function markNoteChecked(db, noteId) {
342
+ const now = Date.now();
343
+ db.prepare("UPDATE notes SET last_checked_at = ?, updated_at = ? WHERE id = ? AND type = 'smart'").run(now, now, noteId);
344
+ }
345
+
346
+ // ../plugin/src/features/magic-context/storage-primers.ts
347
+ var PRIMER_CANDIDATE_TTL_MS = 90 * 24 * 60 * 60 * 1000;
348
+ var PRIMER_CANDIDATE_MAX_AGE_MS = 180 * 24 * 60 * 60 * 1000;
349
+ function normalizePrimerQuestion(question) {
350
+ return question.trim().toLowerCase().replace(/[“”]/g, '"').replace(/[’]/g, "'").replace(/\s+/g, " ").replace(/[?.!]+$/g, "").trim();
351
+ }
352
+ function primerOccurrenceKey(candidate) {
353
+ return [
354
+ candidate.projectPath,
355
+ candidate.harness,
356
+ candidate.sessionId,
357
+ candidate.sourceStartMessageId,
358
+ candidate.sourceEndMessageId
359
+ ].join("\x1F");
360
+ }
361
+ function primerOccurrenceUtcDay(sourceMessageTime) {
362
+ return new Date(sourceMessageTime).toISOString().slice(0, 10);
363
+ }
364
+ function vectorBlob(vector) {
365
+ if (!vector)
366
+ return null;
367
+ return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
368
+ }
369
+ function blobToFloat32Array(value) {
370
+ if (!value)
371
+ return null;
372
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
373
+ return new Float32Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
374
+ }
375
+ function parseCandidateIds(raw) {
376
+ if (!raw)
377
+ return [];
378
+ try {
379
+ const parsed = JSON.parse(raw);
380
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "number" && Number.isFinite(id)) : [];
381
+ } catch {
382
+ return [];
383
+ }
384
+ }
385
+ function parsePrimerSourceProvenance(raw) {
386
+ if (raw === null)
387
+ return null;
388
+ try {
389
+ const parsed = JSON.parse(raw);
390
+ if (!Array.isArray(parsed))
391
+ return null;
392
+ const provenance = [];
393
+ for (const value of parsed) {
394
+ if (!value || typeof value !== "object")
395
+ return null;
396
+ const source = value;
397
+ if (typeof source.candidate_id !== "number" || !Number.isFinite(source.candidate_id) || typeof source.project_path !== "string" || typeof source.harness !== "string" || typeof source.session_id !== "string" || source.source_compartment_start !== null && typeof source.source_compartment_start !== "number" || source.source_compartment_end !== null && typeof source.source_compartment_end !== "number" || typeof source.source_start_message_id !== "string" || typeof source.source_end_message_id !== "string") {
398
+ return null;
399
+ }
400
+ provenance.push({
401
+ candidateId: source.candidate_id,
402
+ projectPath: source.project_path,
403
+ harness: source.harness,
404
+ sessionId: source.session_id,
405
+ sourceCompartmentStart: source.source_compartment_start,
406
+ sourceCompartmentEnd: source.source_compartment_end,
407
+ sourceStartMessageId: source.source_start_message_id,
408
+ sourceEndMessageId: source.source_end_message_id
409
+ });
410
+ }
411
+ return provenance;
412
+ } catch {
413
+ return null;
414
+ }
415
+ }
416
+ function loadPrimerSourceProvenance(db, candidateIds) {
417
+ const ids = [...new Set(candidateIds)].sort((a, b) => a - b);
418
+ if (ids.length === 0)
419
+ return [];
420
+ const placeholders = ids.map(() => "?").join(",");
421
+ const rows = db.prepare(`SELECT id, project_path, harness, session_id,
422
+ source_compartment_start, source_compartment_end,
423
+ source_start_message_id, source_end_message_id
424
+ FROM primer_candidates
425
+ WHERE id IN (${placeholders})
426
+ ORDER BY id ASC`).all(...ids);
427
+ return rows.map((row) => ({
428
+ candidateId: row.id,
429
+ projectPath: row.project_path,
430
+ harness: row.harness,
431
+ sessionId: row.session_id,
432
+ sourceCompartmentStart: row.source_compartment_start,
433
+ sourceCompartmentEnd: row.source_compartment_end,
434
+ sourceStartMessageId: row.source_start_message_id,
435
+ sourceEndMessageId: row.source_end_message_id
436
+ }));
437
+ }
438
+ function serializePrimerSourceProvenance(provenance, sourceCandidateIds) {
439
+ if (provenance.length === 0 && sourceCandidateIds.length > 0)
440
+ return null;
441
+ return JSON.stringify(provenance.map((source) => ({
442
+ candidate_id: source.candidateId,
443
+ project_path: source.projectPath,
444
+ harness: source.harness,
445
+ session_id: source.sessionId,
446
+ source_compartment_start: source.sourceCompartmentStart,
447
+ source_compartment_end: source.sourceCompartmentEnd,
448
+ source_start_message_id: source.sourceStartMessageId,
449
+ source_end_message_id: source.sourceEndMessageId
450
+ })));
451
+ }
452
+ function normalizedCandidateIds(candidateIds) {
453
+ return [...new Set(candidateIds)].sort((a, b) => a - b);
454
+ }
455
+ function toCandidate(row) {
456
+ return {
457
+ id: row.id,
458
+ projectPath: row.project_path,
459
+ harness: row.harness,
460
+ sessionId: row.session_id,
461
+ question: row.question,
462
+ normalizedQuestion: row.normalized_question,
463
+ sourceCompartmentStart: row.source_compartment_start,
464
+ sourceCompartmentEnd: row.source_compartment_end,
465
+ sourceStartMessageId: row.source_start_message_id,
466
+ sourceEndMessageId: row.source_end_message_id,
467
+ sourceMessageTime: row.source_message_time,
468
+ questionEmbedding: blobToFloat32Array(row.question_embedding),
469
+ questionEmbeddingModelId: row.question_embedding_model_id,
470
+ createdAt: row.created_at
471
+ };
472
+ }
473
+ function toPrimer(row) {
474
+ const status = row.status === "archived" ? "archived" : "active";
475
+ return {
476
+ id: row.id,
477
+ projectPath: row.project_path,
478
+ question: row.question,
479
+ questionEmbedding: blobToFloat32Array(row.question_embedding),
480
+ questionEmbeddingModelId: row.question_embedding_model_id,
481
+ answer: row.answer,
482
+ status,
483
+ totalSupport: row.total_support,
484
+ lastObservedAt: row.last_observed_at,
485
+ answerRefreshedAt: row.answer_refreshed_at,
486
+ sourceCandidateIds: parseCandidateIds(row.source_candidate_ids),
487
+ sourceProvenance: parsePrimerSourceProvenance(row.source_candidate_provenance),
488
+ createdAt: row.created_at,
489
+ updatedAt: row.updated_at
490
+ };
491
+ }
492
+ function insertPrimerCandidates(db, candidates) {
493
+ const ids = [];
494
+ const stmt = db.prepare(`
495
+ INSERT INTO primer_candidates (
496
+ project_path, harness, session_id, question, normalized_question,
497
+ source_compartment_start, source_compartment_end,
498
+ source_start_message_id, source_end_message_id, source_message_time,
499
+ question_embedding, question_embedding_model_id, created_at
500
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
501
+ ON CONFLICT(project_path, harness, session_id, source_start_message_id, source_end_message_id)
502
+ DO UPDATE SET
503
+ question = excluded.question,
504
+ normalized_question = excluded.normalized_question,
505
+ source_compartment_start = excluded.source_compartment_start,
506
+ source_compartment_end = excluded.source_compartment_end,
507
+ source_message_time = excluded.source_message_time,
508
+ question_embedding = COALESCE(excluded.question_embedding, primer_candidates.question_embedding),
509
+ question_embedding_model_id = COALESCE(excluded.question_embedding_model_id, primer_candidates.question_embedding_model_id),
510
+ created_at = MIN(primer_candidates.created_at, excluded.created_at)
511
+ `);
512
+ const select = db.prepare(`
513
+ SELECT id FROM primer_candidates
514
+ WHERE project_path = ? AND harness = ? AND session_id = ?
515
+ AND source_start_message_id = ? AND source_end_message_id = ?
516
+ `);
517
+ db.transaction(() => {
518
+ for (const candidate of candidates) {
519
+ const question = candidate.question.trim();
520
+ if (!question)
521
+ continue;
522
+ const normalized = candidate.normalizedQuestion ?? normalizePrimerQuestion(question);
523
+ stmt.run(candidate.projectPath, candidate.harness || "opencode", candidate.sessionId, question, normalized, candidate.sourceCompartmentStart ?? null, candidate.sourceCompartmentEnd ?? null, candidate.sourceStartMessageId, candidate.sourceEndMessageId, candidate.sourceMessageTime, vectorBlob(candidate.questionEmbedding), candidate.questionEmbeddingModelId ?? null, candidate.createdAt ?? Date.now());
524
+ const row = select.get(candidate.projectPath, candidate.harness || "opencode", candidate.sessionId, candidate.sourceStartMessageId, candidate.sourceEndMessageId);
525
+ if (typeof row?.id === "number")
526
+ ids.push(row.id);
527
+ }
528
+ }).immediate();
529
+ return ids;
530
+ }
531
+ function updatePrimerCandidateEmbedding(db, candidateId, vector, modelId) {
532
+ db.prepare("UPDATE primer_candidates SET question_embedding = ?, question_embedding_model_id = ? WHERE id = ?").run(vectorBlob(vector), modelId, candidateId);
533
+ }
534
+ function getPrimerCandidatesByIds(db, ids) {
535
+ if (ids.length === 0)
536
+ return [];
537
+ const placeholders = ids.map(() => "?").join(",");
538
+ const rows = db.prepare(`SELECT * FROM primer_candidates WHERE id IN (${placeholders})`).all(...ids);
539
+ return rows.map(toCandidate);
540
+ }
541
+ function getPrimerCandidatesForPromotion(db, projectPath, now = Date.now(), ttlMs = PRIMER_CANDIDATE_TTL_MS) {
542
+ const cutoff = now - ttlMs;
543
+ const rows = db.prepare(`SELECT * FROM primer_candidates
544
+ WHERE project_path = ? AND source_message_time >= ?
545
+ ORDER BY project_path ASC, harness ASC, session_id ASC, source_start_message_id ASC, source_end_message_id ASC, id ASC`).all(projectPath, cutoff);
546
+ return rows.map(toCandidate);
547
+ }
548
+ function countPrimerCandidatesForProject(db, projectPath) {
549
+ const row = db.prepare("SELECT COUNT(*) AS count FROM primer_candidates WHERE project_path = ?").get(projectPath);
550
+ return row?.count ?? 0;
551
+ }
552
+ function getActivePrimers(db, projectPath) {
553
+ const rows = db.prepare(`SELECT * FROM primers
554
+ WHERE project_path = ? AND status = 'active'
555
+ ORDER BY COALESCE(last_observed_at, created_at) DESC, id ASC`).all(projectPath);
556
+ return rows.map(toPrimer);
557
+ }
558
+ function createPrimer(db, input) {
559
+ return db.transaction(() => {
560
+ const now = input.now ?? Date.now();
561
+ const sourceCandidateIds = normalizedCandidateIds(input.sourceCandidateIds);
562
+ const sourceProvenance = loadPrimerSourceProvenance(db, sourceCandidateIds);
563
+ const info = db.prepare(`INSERT INTO primers (
564
+ project_path, question, question_embedding, question_embedding_model_id, answer,
565
+ status, total_support, last_observed_at, answer_refreshed_at,
566
+ source_candidate_ids, source_candidate_provenance, created_at, updated_at
567
+ ) VALUES (?, ?, ?, ?, ?, 'active', ?, ?, NULL, ?, ?, ?, ?)`).run(input.projectPath, input.question, vectorBlob(input.questionEmbedding), input.questionEmbeddingModelId ?? null, input.answer ?? "", input.totalSupport, input.lastObservedAt, JSON.stringify(sourceCandidateIds), serializePrimerSourceProvenance(sourceProvenance, sourceCandidateIds), now, now);
568
+ return Number(info.lastInsertRowid);
569
+ }).immediate();
570
+ }
571
+ function updatePrimerSupport(db, input) {
572
+ db.transaction(() => {
573
+ const sourceCandidateIds = normalizedCandidateIds(input.sourceCandidateIds);
574
+ const allowedIds = new Set(sourceCandidateIds);
575
+ const existingRow = db.prepare("SELECT source_candidate_provenance FROM primers WHERE id = ?").get(input.primerId);
576
+ const provenanceByCandidateId = new Map;
577
+ for (const source of parsePrimerSourceProvenance(existingRow?.source_candidate_provenance ?? null) ?? []) {
578
+ if (allowedIds.has(source.candidateId)) {
579
+ provenanceByCandidateId.set(source.candidateId, source);
580
+ }
581
+ }
582
+ for (const source of loadPrimerSourceProvenance(db, sourceCandidateIds)) {
583
+ provenanceByCandidateId.set(source.candidateId, source);
584
+ }
585
+ const sourceProvenance = [...provenanceByCandidateId.values()].sort((a, b) => a.candidateId - b.candidateId);
586
+ db.prepare(`UPDATE primers
587
+ SET question_embedding = COALESCE(?, question_embedding),
588
+ question_embedding_model_id = COALESCE(?, question_embedding_model_id),
589
+ total_support = ?,
590
+ last_observed_at = ?,
591
+ source_candidate_ids = ?,
592
+ source_candidate_provenance = ?,
593
+ updated_at = ?
594
+ WHERE id = ?`).run(vectorBlob(input.questionEmbedding), input.questionEmbeddingModelId ?? null, input.totalSupport, input.lastObservedAt, JSON.stringify(sourceCandidateIds), serializePrimerSourceProvenance(sourceProvenance, sourceCandidateIds), input.now ?? Date.now(), input.primerId);
595
+ }).immediate();
596
+ }
597
+ function updatePrimerAnswer(db, primerId, answer, refreshedAt = Date.now()) {
598
+ db.prepare("UPDATE primers SET answer = ?, answer_refreshed_at = ?, updated_at = ? WHERE id = ?").run(answer, refreshedAt, refreshedAt, primerId);
599
+ }
600
+
601
+ // ../plugin/src/shared/user-facing-codes.ts
602
+ var USER_FACING_FAILURES = {
603
+ historian_unavailable: {
604
+ code: "MC-H01",
605
+ sentence: "History compression could not finish this turn.",
606
+ action: "It will retry automatically."
607
+ },
608
+ historian_saved_history_misaligned: {
609
+ code: "MC-H03",
610
+ sentence: "History compression is paused because this session's saved summaries no longer line up with its messages.",
611
+ action: "Run /ctx-recomp to rebuild them."
612
+ },
613
+ history_boundary_unresolved: {
614
+ code: "MC-H04",
615
+ sentence: "This request was not sent: the message that marks where this session's history summary ends is missing from the OpenCode store, and without it the request is larger than the model's context window.",
616
+ action: "Run /ctx-recomp to rebuild the history summary."
617
+ },
618
+ history_over_window_unmanaged: {
619
+ code: "MC-H06",
620
+ sentence: "This request was not sent: this session's history is larger than the model's context window, and Magic Context does not have a summary of it yet to send in its place.",
621
+ action: "Send your message again once history compression has caught up, or switch to a model with a larger context window."
622
+ },
623
+ frozen_history_over_window: {
624
+ code: "MC-H07",
625
+ sentence: "This request was not sent: the conversation is larger than the context window the provider reported, and the compressed history Magic Context has ready does not fit either.",
626
+ action: "Run /ctx-flush to compress it now, then send your message again."
627
+ },
628
+ historian_window_too_small: {
629
+ code: "MC-H05",
630
+ sentence: "History compression is paused because the history model's context window is too small for its instructions.",
631
+ action: "Set historian.model in magic-context.jsonc to a model with a larger context window."
632
+ },
633
+ recomp_unavailable: {
634
+ code: "MC-R01",
635
+ sentence: "History compression could not be rebuilt.",
636
+ action: "Run /ctx-recomp again."
637
+ },
638
+ dream_provider_timeout: {
639
+ code: "MC-D01",
640
+ sentence: "Memory maintenance took too long to respond.",
641
+ action: "Run /ctx-dream again."
642
+ },
643
+ dream_provider_error: {
644
+ code: "MC-D02",
645
+ sentence: "Memory maintenance could not reach its model.",
646
+ action: "Check the model connection, then run /ctx-dream again."
647
+ },
648
+ dream_local_refusal: {
649
+ code: "MC-D12",
650
+ sentence: "Memory maintenance was refused before reaching the model.",
651
+ action: "Check the hidden-request diagnostic in the Magic Context log before retrying."
652
+ },
653
+ dream_step_limit: {
654
+ code: "MC-D10",
655
+ sentence: "Memory maintenance stopped at its hidden agent step limit.",
656
+ action: "This task needs less work per run; changing the model connection will not help."
657
+ },
658
+ dream_token_budget: {
659
+ code: "MC-D11",
660
+ sentence: "Memory maintenance reached its prompt-token budget.",
661
+ action: "The unfinished items will be retried on the next run."
662
+ },
663
+ dream_empty_completion: {
664
+ code: "MC-D03",
665
+ sentence: "Memory maintenance received no usable response.",
666
+ action: "Run /ctx-dream again."
667
+ },
668
+ dream_no_models: {
669
+ code: "MC-D04",
670
+ sentence: "Memory maintenance has no model available.",
671
+ action: "Check the model settings, then run /ctx-dream again."
672
+ },
673
+ dream_child_aborted: {
674
+ code: "MC-D05",
675
+ sentence: "Memory maintenance was interrupted.",
676
+ action: "Run /ctx-dream again."
677
+ },
678
+ dream_parse_failed: {
679
+ code: "MC-D06",
680
+ sentence: "Memory maintenance could not use the model response.",
681
+ action: "Run /ctx-dream again."
682
+ },
683
+ dream_unknown: {
684
+ code: "MC-D07",
685
+ sentence: "Memory maintenance could not finish.",
686
+ action: "Run /ctx-dream again."
687
+ },
688
+ dreamer_tick_blocked: {
689
+ code: "MC-D09",
690
+ sentence: "Background maintenance is not running: its last pass stopped before it reached the scheduled tasks.",
691
+ action: "It is retried automatically; if it keeps happening, check the Magic Context log for the stage that stopped and run `npx @cortexkit/magic-context doctor`."
692
+ },
693
+ dream_task_needs_tool_loop: {
694
+ code: "MC-D08",
695
+ sentence: "Some memory maintenance tasks need a tool loop this host does not provide.",
696
+ action: "The remaining tasks still run; the listed ones are skipped on this host."
697
+ },
698
+ embedding_substitution_rejected: {
699
+ code: "MC-E01",
700
+ sentence: "Search indexing could not use the selected model.",
701
+ action: "Check the embedding model setting, then run /ctx-embed start again."
702
+ },
703
+ embedding_http_error: {
704
+ code: "MC-E02",
705
+ sentence: "The search indexing provider refused the request.",
706
+ action: "Check the provider connection and credentials, then run /ctx-embed start again."
707
+ },
708
+ embedding_transport_error: {
709
+ code: "MC-E03",
710
+ sentence: "Search indexing could not reach its provider.",
711
+ action: "Check the connection, then run /ctx-embed start again."
712
+ },
713
+ embedding_invalid_envelope: {
714
+ code: "MC-E04",
715
+ sentence: "Search indexing received an unsupported response.",
716
+ action: "Check the embedding endpoint, then run /ctx-embed start again."
717
+ },
718
+ embedding_empty_result: {
719
+ code: "MC-E05",
720
+ sentence: "Search indexing received no usable result.",
721
+ action: "Run /ctx-embed start again."
722
+ },
723
+ embedding_certification_refusal: {
724
+ code: "MC-E06",
725
+ sentence: "Search indexing is not ready for this provider.",
726
+ action: "Finish the provider setup, or set a fallback provider in the embedding settings, then run /ctx-embed start again."
727
+ },
728
+ embedding_credential_required: {
729
+ code: "MC-E07",
730
+ sentence: "Search indexing needs provider credentials.",
731
+ action: "Sign in to the provider, then run /ctx-embed start again."
732
+ },
733
+ embedding_local_binding_missing: {
734
+ code: "MC-E08",
735
+ sentence: "Local search indexing is unavailable on this system.",
736
+ action: "Run `npx @cortexkit/magic-context doctor`, then retry."
737
+ },
738
+ embedding_local_fs_unavailable: {
739
+ code: "MC-E09",
740
+ sentence: "Local search indexing cannot save its model files.",
741
+ action: "Update or reinstall Magic Context, then retry."
742
+ },
743
+ embedding_local_download_failure: {
744
+ code: "MC-E10",
745
+ sentence: "Local search indexing could not download its model.",
746
+ action: "Check the network connection, then retry."
747
+ },
748
+ embedding_local_runtime_error: {
749
+ code: "MC-E11",
750
+ sentence: "Local search indexing could not start.",
751
+ action: "Run `npx @cortexkit/magic-context doctor`, then retry."
752
+ },
753
+ embedding_unavailable: {
754
+ code: "MC-E12",
755
+ sentence: "Search indexing could not finish.",
756
+ action: "Run /ctx-embed start again."
757
+ },
758
+ status_unavailable: {
759
+ code: "MC-S01",
760
+ sentence: "Magic Context status is temporarily unavailable.",
761
+ action: "Retry /ctx-status in a moment."
762
+ },
763
+ transform_update_failed: {
764
+ code: "MC-S02",
765
+ sentence: "The last context update did not finish.",
766
+ action: "Send another message to retry."
767
+ },
768
+ transform_pass_degraded: {
769
+ code: "MC-S06",
770
+ sentence: "This request was not sent: Magic Context could not finish preparing it, and without that preparation it could be far larger than the previous request.",
771
+ action: "Send your message again."
772
+ },
773
+ configuration_warning: {
774
+ code: "MC-S03",
775
+ sentence: "Some configuration settings could not be applied.",
776
+ action: "Fix the configuration warning shown in /ctx-status, then restart."
777
+ },
778
+ status_log_unavailable: {
779
+ code: "MC-S04",
780
+ sentence: "Some diagnostic details could not be saved.",
781
+ action: "Retry /ctx-status in a moment."
782
+ },
783
+ memory_writes_paused: {
784
+ code: "MC-C01",
785
+ sentence: "Memory writes are paused while the engine syncs.",
786
+ action: "Retry in a moment."
787
+ },
788
+ memory_access_unavailable: {
789
+ code: "MC-C02",
790
+ sentence: "Memory access is temporarily unavailable.",
791
+ action: "Retry in a moment."
792
+ },
793
+ note_changes_paused: {
794
+ code: "MC-C03",
795
+ sentence: "Note changes are paused while the engine syncs.",
796
+ action: "Retry in a moment."
797
+ },
798
+ note_access_unavailable: {
799
+ code: "MC-C04",
800
+ sentence: "Notes are temporarily unavailable.",
801
+ action: "Retry in a moment."
802
+ },
803
+ context_cleanup_paused: {
804
+ code: "MC-C05",
805
+ sentence: "Context cleanup is paused while the engine syncs.",
806
+ action: "Retry in a moment."
807
+ },
808
+ partial_history_unavailable: {
809
+ code: "MC-C06",
810
+ sentence: "Partial history compression is not available in the current mode.",
811
+ action: "Run /ctx-recomp without a range."
812
+ },
813
+ smart_note_conditions_unavailable: {
814
+ code: "MC-C08",
815
+ sentence: "Conditional notes are not available in the current mode.",
816
+ action: "Save a regular note without a condition."
817
+ },
818
+ history_compression_paused: {
819
+ code: "MC-C09",
820
+ sentence: "History compression is paused while the engine syncs.",
821
+ action: "Retry in a moment."
822
+ },
823
+ history_compression_needs_message: {
824
+ code: "MC-C12",
825
+ sentence: "History compression has not seen this session since Magic Context reconnected.",
826
+ action: "Send a message in this session first, then run /ctx-wrapup again."
827
+ },
828
+ context_service_unavailable: {
829
+ code: "MC-C10",
830
+ sentence: "Magic Context is temporarily unavailable.",
831
+ action: "Retry in a moment."
832
+ },
833
+ context_db_missing: {
834
+ code: "MC-C15",
835
+ sentence: "Magic Context has no context.db.",
836
+ action: "Run `npx @cortexkit/magic-context doctor store init`, then restart ck-mc."
837
+ },
838
+ single_store_migration_required: {
839
+ code: "MC-C14",
840
+ sentence: "Magic Context's Rust mode needs a one-time migration of its store.",
841
+ action: "Quit OpenCode and every ck-mc process, then run `magic-context doctor single-store migrate`."
842
+ },
843
+ store_ahead_of_binary: {
844
+ code: "MC-C13",
845
+ sentence: "Magic Context refused to start: its store (store.db) was migrated by a newer ck-mc build than the one running.",
846
+ action: "Update ck-mc, or roll back by restoring ck-mc together with context.db and store.db from the same backup."
847
+ },
848
+ compaction_marker_missing: {
849
+ code: "MC-C11",
850
+ sentence: "The history boundary marker is missing from the OpenCode store, so requests carry the full session.",
851
+ action: "It is retried on every message; if this persists, run `/ctx-flush`."
852
+ },
853
+ memory_mirror_stalled: {
854
+ code: "MC-M01",
855
+ sentence: "Memory synchronization stopped before the host mirror caught up.",
856
+ action: "Send another message to resume it, or run `ck doctor drain-authority`."
857
+ },
858
+ memory_authority_mismatch: {
859
+ code: "MC-M02",
860
+ sentence: "Memory authority is inconsistent between the host and module.",
861
+ action: "Run `ck doctor drain-authority` before changing Rust mode."
862
+ },
863
+ dreamer_task_failing: {
864
+ code: "MC-S05",
865
+ sentence: "A background maintenance task keeps failing on its schedule.",
866
+ action: "Check the Magic Context log for the failing task and its error."
867
+ }
868
+ };
869
+ function renderUserFacingFailure(key, style = "markdown") {
870
+ const failure = USER_FACING_FAILURES[key];
871
+ const action = style === "plain" ? failure.action.replaceAll("`", "") : failure.action;
872
+ return `${failure.sentence} ${action} (${failure.code})`;
873
+ }
874
+ function userFacingFailureCode(key) {
875
+ return USER_FACING_FAILURES[key].code;
876
+ }
877
+ var CAPABILITY_FAILURES = {
878
+ memory_write: "memory_writes_paused",
879
+ memory_access: "memory_access_unavailable",
880
+ note_change: "note_changes_paused",
881
+ note_access: "note_access_unavailable",
882
+ context_cleanup: "context_cleanup_paused",
883
+ partial_history: "partial_history_unavailable",
884
+ smart_note_condition: "smart_note_conditions_unavailable",
885
+ history_compression: "history_compression_paused",
886
+ context_service: "context_service_unavailable"
887
+ };
888
+ function renderCapabilityRefusal(capability) {
889
+ return renderUserFacingFailure(CAPABILITY_FAILURES[capability]);
890
+ }
891
+ var DREAM_FAILURE_KEYS = {
892
+ provider_timeout: "dream_provider_timeout",
893
+ provider_error: "dream_provider_error",
894
+ local_refusal: "dream_local_refusal",
895
+ step_limit: "dream_step_limit",
896
+ token_budget: "dream_token_budget",
897
+ empty_completion: "dream_empty_completion",
898
+ no_models: "dream_no_models",
899
+ child_aborted: "dream_child_aborted",
900
+ parse_failed: "dream_parse_failed",
901
+ unknown: "dream_unknown"
902
+ };
903
+ var EMBEDDING_FAILURE_KEYS = {
904
+ substitution_rejected: "embedding_substitution_rejected",
905
+ http_error: "embedding_http_error",
906
+ transport_error: "embedding_transport_error",
907
+ invalid_envelope: "embedding_invalid_envelope",
908
+ empty_result: "embedding_empty_result",
909
+ certification_refusal: "embedding_certification_refusal",
910
+ credential_required: "embedding_credential_required",
911
+ local_binding_missing: "embedding_local_binding_missing",
912
+ local_fs_unavailable: "embedding_local_fs_unavailable",
913
+ local_download_failure: "embedding_local_download_failure",
914
+ local_runtime_error: "embedding_local_runtime_error"
915
+ };
916
+ function renderDreamFailure(failureClass, style = "markdown", refusalReason) {
917
+ const rendered = renderUserFacingFailure(DREAM_FAILURE_KEYS[failureClass], style);
918
+ return failureClass === "local_refusal" && refusalReason ? `${rendered} Reason: ${sanitizeDiagnosticText(refusalReason).slice(0, 500)}` : rendered;
919
+ }
920
+ function dreamFailureCode(failureClass) {
921
+ return userFacingFailureCode(DREAM_FAILURE_KEYS[failureClass]);
922
+ }
923
+ function renderEmbeddingFailure(failureClass, style = "markdown") {
924
+ return renderUserFacingFailure(EMBEDDING_FAILURE_KEYS[failureClass], style);
925
+ }
926
+
927
+ // ../plugin/src/hooks/magic-context/single-store-refusal.ts
928
+ var SINGLE_STORE_MIGRATION_REQUIRED_CODE = "single_store_migration_required";
929
+ function renderSingleStoreMigrationRequiredRefusal() {
930
+ const failure = USER_FACING_FAILURES.single_store_migration_required;
931
+ return `${failure.sentence} ${failure.action} (${failure.code})`;
932
+ }
933
+
934
+ class SingleStoreMigrationRequiredError extends Error {
935
+ code = SINGLE_STORE_MIGRATION_REQUIRED_CODE;
936
+ constructor(options) {
937
+ super(renderSingleStoreMigrationRequiredRefusal(), options);
938
+ this.name = "SingleStoreMigrationRequiredError";
939
+ }
940
+ }
941
+ function projectNeedsSingleStoreMigration(db, projectPath) {
942
+ const table = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'authority_managed'").get();
943
+ return Boolean(table && db.prepare("SELECT 1 FROM authority_managed WHERE project_path = ?").get(projectPath));
944
+ }
945
+ // ../plugin/src/features/magic-context/memory/promotion.ts
946
+ function isPromotableCategory(category) {
947
+ return PROMOTABLE_CATEGORIES.some((promotableCategory) => promotableCategory === category);
948
+ }
949
+ function resolveExpiresAt(category) {
950
+ const ttl = CATEGORY_DEFAULT_TTL[category];
951
+ return ttl === undefined ? null : Date.now() + ttl;
952
+ }
953
+ function promoteSessionFactsDurable(db, sessionId, projectPath, facts) {
954
+ const newMemoryRefs = [];
955
+ let factsPromoted = 0;
956
+ for (const fact of facts) {
957
+ if (!fact || typeof fact.category !== "string" || typeof fact.content !== "string" || fact.content.trim().length === 0) {
958
+ continue;
959
+ }
960
+ if (!isPromotableCategory(fact.category)) {
961
+ continue;
962
+ }
963
+ const normalizedHash = computeNormalizedHash(fact.content);
964
+ const existingMemory = getMemoryByHash2(db, projectPath, fact.category, normalizedHash);
965
+ if (existingMemory) {
966
+ updateMemorySeenCount2(db, existingMemory.id);
967
+ factsPromoted += 1;
968
+ continue;
969
+ }
970
+ const memoryInput = {
971
+ projectPath,
972
+ category: fact.category,
973
+ content: fact.content,
974
+ sourceSessionId: sessionId,
975
+ sourceType: "historian",
976
+ expiresAt: resolveExpiresAt(fact.category)
977
+ };
978
+ const memory = insertMemory2(db, memoryInput);
979
+ newMemoryRefs.push({ memoryId: memory.id, content: memory.content });
980
+ factsPromoted += 1;
981
+ }
982
+ return { newMemoryRefs, factsPromoted };
983
+ }
984
+ async function embedPromotedFacts(db, sessionId, projectPath, refs) {
985
+ for (const ref of refs) {
986
+ await embedAndStoreMemory(db, sessionId, projectPath, ref.memoryId, ref.content);
987
+ }
988
+ }
989
+ async function embedAndStoreMemory(db, sessionId, projectPath, memoryId, content) {
990
+ try {
991
+ const hashBeforeEmbed = getMemoryById2(db, memoryId)?.normalizedHash;
992
+ if (!hashBeforeEmbed) {
993
+ return;
994
+ }
995
+ const result = await embedTextForProject2(projectPath, content);
996
+ if (result) {
997
+ db.transaction(() => {
998
+ saveEmbeddingIfHashMatches(db, memoryId, result.vector, result.modelId, hashBeforeEmbed);
999
+ }).immediate();
1000
+ }
1001
+ } catch (error) {
1002
+ sessionLog2(sessionId, `memory embedding failed for memory ${memoryId}:`, error);
1003
+ }
1004
+ }
1005
+ // ../plugin/src/features/magic-context/memory/storage-memory-fts.ts
1006
+ var DEFAULT_SEARCH_LIMIT = 10;
1007
+ var searchStatements = new WeakMap;
1008
+ var datedSearchStatements = new WeakMap;
1009
+ var unionSearchStatements = new Map;
1010
+ var datedUnionSearchStatements = new Map;
1011
+ function getSearchStatement(db, dated = false) {
1012
+ const statements = dated ? datedSearchStatements : searchStatements;
1013
+ let stmt = statements.get(db);
1014
+ if (!stmt) {
1015
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns2(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path = ? AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?)${dated ? " AND memories.created_at BETWEEN ? AND ?" : ""} AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
1016
+ statements.set(db, stmt);
1017
+ }
1018
+ return stmt;
1019
+ }
1020
+ function getUnionSearchStatement(db, arity, dated = false) {
1021
+ const statementRegistry = dated ? datedUnionSearchStatements : unionSearchStatements;
1022
+ let statements = statementRegistry.get(arity);
1023
+ if (!statements) {
1024
+ statements = new WeakMap;
1025
+ statementRegistry.set(arity, statements);
1026
+ }
1027
+ let stmt = statements.get(db);
1028
+ if (!stmt) {
1029
+ const placeholders = Array.from({ length: arity }, () => "?").join(", ");
1030
+ stmt = db.prepare(`SELECT ${getMemorySelectColumns2(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${placeholders}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?)${dated ? " AND memories.created_at BETWEEN ? AND ?" : ""} AND memories_fts MATCH ? ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`);
1031
+ statements.set(db, stmt);
1032
+ }
1033
+ return stmt;
1034
+ }
1035
+ function uniqueProjectPaths(projectPaths) {
1036
+ return [...new Set(projectPaths.filter((path) => path.length > 0))];
1037
+ }
1038
+ function relaxedFtsQuery(query) {
1039
+ const tokens = [...new Set(query.match(/[\p{L}\p{N}_]+/gu) ?? [])].filter((token) => token.length > 2 || /\d/.test(token)).slice(0, 16);
1040
+ return tokens.map((token) => `"${token.replace(/"/g, '""')}"`).join(" OR ");
1041
+ }
1042
+ function sanitizeFtsQuery(query) {
1043
+ const tokens = query.replaceAll("\x00", " ").split(/\s+/).filter((token) => token.length > 0);
1044
+ if (tokens.length === 0)
1045
+ return "";
1046
+ return tokens.map((token) => `"${token.replace(/"/g, '""')}"`).join(" ");
1047
+ }
1048
+ function searchMemoriesFTS(db, projectPath, query, limit = DEFAULT_SEARCH_LIMIT, dateRange = null) {
1049
+ const trimmedQuery = query.trim();
1050
+ if (trimmedQuery.length === 0 || limit <= 0) {
1051
+ return [];
1052
+ }
1053
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
1054
+ if (sanitized.length === 0) {
1055
+ return [];
1056
+ }
1057
+ let rows = getSearchStatement(db, dateRange !== null).all(projectPath, Date.now(), ...dateRange === null ? [] : [dateRange.from, dateRange.to], sanitized, limit).filter(isMemoryRow2);
1058
+ if (rows.length === 0) {
1059
+ const relaxed = relaxedFtsQuery(trimmedQuery);
1060
+ if (relaxed)
1061
+ rows = getSearchStatement(db, dateRange !== null).all(projectPath, Date.now(), ...dateRange === null ? [] : [dateRange.from, dateRange.to], relaxed, limit).filter(isMemoryRow2);
1062
+ }
1063
+ return rows.map(toMemory2);
1064
+ }
1065
+ function searchMemoriesFTSUnion(db, projectPaths, query, limit = DEFAULT_SEARCH_LIMIT, ownIdentities, shareCategories, dateRange = null) {
1066
+ const identities = uniqueProjectPaths(projectPaths);
1067
+ if (identities.length === 0)
1068
+ return [];
1069
+ const sharingFilter = buildWorkspaceMemorySqlFilter2({
1070
+ identities,
1071
+ ownIdentities,
1072
+ shareCategories,
1073
+ tableName: "memories",
1074
+ includeClassificationFields: (() => {
1075
+ const columns = db.prepare("PRAGMA table_info(memories)").all();
1076
+ return columns.some((row) => row.name === "shareable") && columns.some((row) => row.name === "scope");
1077
+ })()
1078
+ });
1079
+ if (identities.length === 1 && !sharingFilter.active) {
1080
+ return searchMemoriesFTS(db, identities[0], query, limit, dateRange);
1081
+ }
1082
+ const trimmedQuery = query.trim();
1083
+ if (trimmedQuery.length === 0 || limit <= 0)
1084
+ return [];
1085
+ const sanitized = sanitizeFtsQuery(trimmedQuery);
1086
+ if (sanitized.length === 0)
1087
+ return [];
1088
+ let rows = sharingFilter.active ? db.prepare(`SELECT ${getMemorySelectColumns2(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${identities.map(() => "?").join(", ")}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?)${dateRange === null ? "" : " AND memories.created_at BETWEEN ? AND ?"} AND memories_fts MATCH ?${sharingFilter.clause} ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`).all(...identities, Date.now(), ...dateRange === null ? [] : [dateRange.from, dateRange.to], sanitized, ...sharingFilter.params, limit).filter(isMemoryRow2) : getUnionSearchStatement(db, identities.length, dateRange !== null).all(...identities, Date.now(), ...dateRange === null ? [] : [dateRange.from, dateRange.to], sanitized, limit).filter(isMemoryRow2);
1089
+ if (rows.length === 0) {
1090
+ const relaxed = relaxedFtsQuery(trimmedQuery);
1091
+ if (relaxed) {
1092
+ rows = (sharingFilter.active ? db.prepare(`SELECT ${getMemorySelectColumns2(db)} FROM memories_fts INNER JOIN memories ON memories.id = memories_fts.rowid WHERE memories.project_path IN (${identities.map(() => "?").join(", ")}) AND memories.status IN ('active', 'permanent') AND (memories.expires_at IS NULL OR memories.expires_at > ?)${dateRange === null ? "" : " AND memories.created_at BETWEEN ? AND ?"} AND memories_fts MATCH ?${sharingFilter.clause} ORDER BY bm25(memories_fts), memories.updated_at DESC, memories.id ASC LIMIT ?`) : getUnionSearchStatement(db, identities.length, dateRange !== null)).all(...identities, Date.now(), ...dateRange === null ? [] : [dateRange.from, dateRange.to], relaxed, ...sharingFilter.params, limit).filter(isMemoryRow2);
1093
+ }
1094
+ }
1095
+ return rows.map(toMemory2);
1096
+ }
1097
+ // ../plugin/src/features/magic-context/memory/storage-memory-verifications.ts
1098
+ var MEMORY_VERIFICATION_SENTINEL = "";
1099
+ function uniqueSortedFiles(files) {
1100
+ return Array.from(new Set(files.filter((file) => file !== MEMORY_VERIFICATION_SENTINEL))).sort();
1101
+ }
1102
+ function recordMemoryMapping(db, memoryId, normalizedFiles, now, mappingOrigin = "mapper") {
1103
+ const realFiles = uniqueSortedFiles(normalizedFiles);
1104
+ const filesToWrite = realFiles.length > 0 ? realFiles : [MEMORY_VERIFICATION_SENTINEL];
1105
+ db.prepare("DELETE FROM memory_verifications WHERE memory_id = ?").run(memoryId);
1106
+ const insert = db.prepare("INSERT INTO memory_verifications (memory_id, file_path, verified_at, mapped_at, mapping_origin) VALUES (?, ?, 0, ?, ?)");
1107
+ for (const file of filesToWrite) {
1108
+ insert.run(memoryId, file, now, mappingOrigin);
1109
+ }
1110
+ return filesToWrite.length;
1111
+ }
1112
+ function recordMemoryVerifications(db, memoryId, normalizedFiles, now) {
1113
+ const realFiles = uniqueSortedFiles(normalizedFiles);
1114
+ const filesToWrite = realFiles.length > 0 ? realFiles : [MEMORY_VERIFICATION_SENTINEL];
1115
+ db.prepare("DELETE FROM memory_verifications WHERE memory_id = ?").run(memoryId);
1116
+ const insert = db.prepare("INSERT INTO memory_verifications (memory_id, file_path, verified_at, mapped_at, mapping_origin) VALUES (?, ?, ?, ?, 'mapper')");
1117
+ for (const file of filesToWrite) {
1118
+ insert.run(memoryId, file, now, now);
1119
+ }
1120
+ return filesToWrite.length;
1121
+ }
1122
+ function getUnmappedMemoryIds(db, memoryIds) {
1123
+ const ids = Array.from(new Set(memoryIds.filter(Number.isInteger)));
1124
+ if (ids.length === 0)
1125
+ return [];
1126
+ const rows = db.prepare("SELECT DISTINCT memory_id FROM memory_verifications WHERE memory_id IN (SELECT value FROM json_each(?))").all(JSON.stringify(ids));
1127
+ const mapped = new Set(rows.map((r) => r.memory_id));
1128
+ return ids.filter((id) => !mapped.has(id));
1129
+ }
1130
+ function clearMemoryVerifications(db, memoryId) {
1131
+ db.prepare("DELETE FROM memory_verifications WHERE memory_id = ?").run(memoryId);
1132
+ }
1133
+ function getMemoryVerifications(db, memoryIds) {
1134
+ const ids = Array.from(new Set(memoryIds.filter(Number.isInteger)));
1135
+ const result = new Map;
1136
+ if (ids.length === 0)
1137
+ return result;
1138
+ const rows = db.prepare(`SELECT memory_id, file_path, verified_at, mapped_at, mapping_origin
1139
+ FROM memory_verifications
1140
+ WHERE memory_id IN (SELECT value FROM json_each(?))
1141
+ ORDER BY memory_id, file_path`).all(JSON.stringify(ids));
1142
+ for (const row of rows) {
1143
+ const existing = result.get(row.memory_id) ?? {
1144
+ files: [],
1145
+ hasSentinel: false,
1146
+ mappingOrigin: "mapper",
1147
+ verifiedAt: 0,
1148
+ mappedAt: 0
1149
+ };
1150
+ if (row.file_path === MEMORY_VERIFICATION_SENTINEL) {
1151
+ existing.hasSentinel = true;
1152
+ } else if (!existing.files.includes(row.file_path)) {
1153
+ existing.files.push(row.file_path);
1154
+ }
1155
+ if (row.mapping_origin === "host_rejected_fallback") {
1156
+ existing.mappingOrigin = "host_rejected_fallback";
1157
+ }
1158
+ existing.verifiedAt = Math.max(existing.verifiedAt, row.verified_at);
1159
+ existing.mappedAt = Math.max(existing.mappedAt, row.mapped_at ?? 0);
1160
+ result.set(row.memory_id, existing);
1161
+ }
1162
+ for (const state of result.values()) {
1163
+ state.files.sort();
1164
+ }
1165
+ return result;
1166
+ }
1167
+ // ../plugin/src/features/magic-context/memory/verification-paths.ts
1168
+ import { execFile } from "node:child_process";
1169
+ import { existsSync, realpathSync, statSync } from "node:fs";
1170
+ import path from "node:path";
1171
+ import { promisify } from "node:util";
1172
+ var execFileAsync = promisify(execFile);
1173
+ var GIT_TIMEOUT_MS = 1e4;
1174
+ var defaultExecFileForVerificationPaths = async (file, args, options) => await execFileAsync(file, [...args], {
1175
+ ...options,
1176
+ windowsHide: true
1177
+ });
1178
+ var execFileForVerificationPaths = defaultExecFileForVerificationPaths;
1179
+ async function runGit(cwd, args) {
1180
+ try {
1181
+ const result = await execFileForVerificationPaths("git", args, {
1182
+ cwd,
1183
+ timeout: GIT_TIMEOUT_MS,
1184
+ maxBuffer: 16 * 1024 * 1024,
1185
+ encoding: "utf8",
1186
+ windowsHide: true
1187
+ });
1188
+ return String(result.stdout);
1189
+ } catch (error) {
1190
+ if (error.killed || error.code === "ETIMEDOUT") {
1191
+ throw new Error(`Git verification command git ${args.join(" ")} timed out after ${GIT_TIMEOUT_MS}ms`, {
1192
+ cause: error
1193
+ });
1194
+ }
1195
+ return null;
1196
+ }
1197
+ }
1198
+ function toPosixPath(value) {
1199
+ return value.split(path.sep).join("/");
1200
+ }
1201
+ function isWithin(root, candidate) {
1202
+ const rel = path.relative(root, candidate);
1203
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
1204
+ }
1205
+ function safeRealpath(value) {
1206
+ try {
1207
+ return realpathSync.native(value);
1208
+ } catch {
1209
+ return null;
1210
+ }
1211
+ }
1212
+ async function resolveGitTopLevel(cwd) {
1213
+ const stdout = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
1214
+ const root = stdout?.trim();
1215
+ return root ? safeRealpath(root) ?? path.resolve(root) : null;
1216
+ }
1217
+ async function readGitHead(cwd) {
1218
+ const stdout = await runGit(cwd, ["rev-parse", "HEAD"]);
1219
+ const head = stdout?.trim();
1220
+ return head && /^[0-9a-f]{40}$/i.test(head) ? head : null;
1221
+ }
1222
+ async function readGitChangedFilesSince(cwd, revision, knownGitRoot) {
1223
+ if (!/^[0-9a-f]{7,40}$/i.test(revision))
1224
+ return null;
1225
+ const gitRoot = knownGitRoot ?? await resolveGitTopLevel(cwd);
1226
+ if (!gitRoot)
1227
+ return null;
1228
+ const stdout = await runGit(gitRoot, ["diff", "--name-only", "-z", revision]);
1229
+ if (stdout === null)
1230
+ return null;
1231
+ return new Set(stdout.split("\x00").filter(Boolean));
1232
+ }
1233
+ async function readGitFileChangeTimesSince(cwd, sinceMs, knownGitRoot) {
1234
+ const gitRoot = knownGitRoot ?? await resolveGitTopLevel(cwd);
1235
+ if (!gitRoot)
1236
+ return null;
1237
+ const sinceSec = Math.max(0, Math.floor(sinceMs / 1000));
1238
+ const stdout = await runGit(gitRoot, [
1239
+ "log",
1240
+ `--since=@${sinceSec}`,
1241
+ "--name-only",
1242
+ "--format=%ct"
1243
+ ]);
1244
+ if (stdout === null)
1245
+ return null;
1246
+ const times = new Map;
1247
+ let currentMs = 0;
1248
+ for (const rawLine of stdout.split(`
1249
+ `)) {
1250
+ const line = rawLine.trimEnd();
1251
+ if (line === "")
1252
+ continue;
1253
+ if (/^\d+$/.test(line)) {
1254
+ currentMs = Number.parseInt(line, 10) * 1000;
1255
+ continue;
1256
+ }
1257
+ if (currentMs > 0 && !times.has(line)) {
1258
+ times.set(line, currentMs);
1259
+ }
1260
+ }
1261
+ return times;
1262
+ }
1263
+ async function gitTrackedPath(gitRoot, repoRelativePath) {
1264
+ const stdout = await runGit(gitRoot, [
1265
+ "ls-files",
1266
+ "-z",
1267
+ "--full-name",
1268
+ "--error-unmatch",
1269
+ "--",
1270
+ repoRelativePath
1271
+ ]);
1272
+ const fallbackStdout = stdout === null ? await runGit(gitRoot, ["ls-files", "-z", "--full-name"]) : null;
1273
+ if (stdout === null && fallbackStdout === null)
1274
+ return null;
1275
+ const matches = (stdout ?? fallbackStdout ?? "").split("\x00").filter(Boolean);
1276
+ if (matches.length === 0)
1277
+ return null;
1278
+ return matches.find((match) => match === repoRelativePath) ?? matches.find((match) => match.toLowerCase() === repoRelativePath.toLowerCase()) ?? (matches.length === 1 ? matches[0] : null);
1279
+ }
1280
+ function verificationFileExists(baseRoot, filePath) {
1281
+ if (!filePath || filePath === ".")
1282
+ return false;
1283
+ const root = path.resolve(baseRoot);
1284
+ const candidate = path.resolve(root, filePath);
1285
+ return isWithin(root, candidate) && existsSync(candidate);
1286
+ }
1287
+ async function normalizeVerificationFiles(args) {
1288
+ const cwd = path.resolve(args.cwd);
1289
+ const gitRoot = await resolveGitTopLevel(cwd);
1290
+ const root = gitRoot ?? cwd;
1291
+ const rootReal = safeRealpath(root) ?? root;
1292
+ const warnings = [];
1293
+ const normalized = [];
1294
+ let trackedPaths;
1295
+ for (const raw of args.files) {
1296
+ const value = typeof raw === "string" ? raw.trim() : "";
1297
+ if (!value) {
1298
+ warnings.push("Skipped blank verification path.");
1299
+ continue;
1300
+ }
1301
+ if (value === ".") {
1302
+ warnings.push('Skipped verification path "." (repo/project root is not a file).');
1303
+ continue;
1304
+ }
1305
+ const candidate = path.resolve(cwd, value);
1306
+ const candidateReal = safeRealpath(candidate);
1307
+ if (candidateReal && !isWithin(rootReal, candidateReal)) {
1308
+ warnings.push(`Skipped verification path "${value}" because it resolves outside the project.`);
1309
+ continue;
1310
+ }
1311
+ if (!candidateReal && !isWithin(path.resolve(root), candidate)) {
1312
+ warnings.push(`Skipped verification path "${value}" because it escapes the project.`);
1313
+ continue;
1314
+ }
1315
+ if (path.resolve(root) === candidate) {
1316
+ warnings.push(`Skipped verification path "${value}" because it is the repo/project root.`);
1317
+ continue;
1318
+ }
1319
+ if (existsSync(candidate)) {
1320
+ try {
1321
+ if (statSync(candidate).isDirectory()) {
1322
+ warnings.push(`Skipped verification path "${value}" because it is a directory.`);
1323
+ continue;
1324
+ }
1325
+ } catch {
1326
+ warnings.push(`Skipped verification path "${value}" because it could not be inspected.`);
1327
+ continue;
1328
+ }
1329
+ }
1330
+ if (gitRoot) {
1331
+ const repoRelative = toPosixPath(path.relative(gitRoot, candidateReal ?? candidate));
1332
+ if (!repoRelative || repoRelative === "." || repoRelative.startsWith("../")) {
1333
+ warnings.push(`Skipped verification path "${value}" because it is not inside the git repo.`);
1334
+ continue;
1335
+ }
1336
+ if (!trackedPaths) {
1337
+ let trackedOutput = null;
1338
+ try {
1339
+ trackedOutput = await runGit(gitRoot, ["ls-files", "-z", "--full-name"]);
1340
+ } catch {}
1341
+ trackedPaths = new Set(trackedOutput?.split("\x00").filter(Boolean) ?? []);
1342
+ }
1343
+ const tracked = trackedPaths.has(repoRelative) ? repoRelative : await gitTrackedPath(gitRoot, repoRelative);
1344
+ if (!tracked) {
1345
+ warnings.push(`Skipped verification path "${value}" because it is not a tracked git file.`);
1346
+ continue;
1347
+ }
1348
+ if (candidateReal && tracked !== repoRelative) {
1349
+ const realRelative = toPosixPath(path.relative(gitRoot, candidateReal));
1350
+ if (realRelative !== tracked) {
1351
+ warnings.push(`Skipped verification path "${value}" because it is not a tracked git file.`);
1352
+ continue;
1353
+ }
1354
+ }
1355
+ normalized.push(tracked);
1356
+ } else {
1357
+ if (!existsSync(candidate)) {
1358
+ warnings.push(`Skipped verification path "${value}" because it does not exist.`);
1359
+ continue;
1360
+ }
1361
+ const projectRelative = toPosixPath(path.relative(cwd, candidate));
1362
+ if (!projectRelative || projectRelative === "." || projectRelative.startsWith("../")) {
1363
+ warnings.push(`Skipped verification path "${value}" because it is not inside the project.`);
1364
+ continue;
1365
+ }
1366
+ normalized.push(projectRelative);
1367
+ }
1368
+ }
1369
+ return {
1370
+ files: Array.from(new Set(normalized)).sort(),
1371
+ warnings,
1372
+ gitRoot
1373
+ };
1374
+ }
1375
+ // ../plugin/src/features/magic-context/git-commits/git-log-reader.ts
1376
+ import { execFile as execFile2 } from "node:child_process";
1377
+ import { promisify as promisify2 } from "node:util";
1378
+ var execFileAsync2 = promisify2(execFile2);
1379
+ var GIT_TIMEOUT_MS2 = 1e4;
1380
+ var DEFAULT_MAX_COMMITS = 5000;
1381
+ var RECORD_SEPARATOR = "\x1E";
1382
+ var FIELD_SEPARATOR = "\x1F";
1383
+ var FULL_OBJECT_NAME = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
1384
+ function classifyGitLogFailure(message) {
1385
+ if (message.includes("not a git repository"))
1386
+ return "not_a_repo";
1387
+ if (message.includes("unknown revision or path not in the working tree") || message.includes("does not have any commits yet") || message.includes("bad revision")) {
1388
+ return "no_head";
1389
+ }
1390
+ return "transient";
1391
+ }
1392
+ async function readGitCommitsResult(directory, options = {}) {
1393
+ const revision = options.branch ?? "HEAD";
1394
+ if (revision.startsWith("-")) {
1395
+ throw new Error(`readGitCommits: refusing revision that looks like an option: "${revision}"`);
1396
+ }
1397
+ const projectLabel = options.projectIdentity ?? "<project>";
1398
+ const exclusion = options.excludeReachableFrom && FULL_OBJECT_NAME.test(options.excludeReachableFrom) ? options.excludeReachableFrom : null;
1399
+ const args = [
1400
+ "log",
1401
+ revision,
1402
+ ...exclusion ? [`^${exclusion}`] : [],
1403
+ "--no-merges",
1404
+ `--max-count=${options.maxCommits ?? DEFAULT_MAX_COMMITS}`,
1405
+ `--format=%H${FIELD_SEPARATOR}%s${FIELD_SEPARATOR}%ae${FIELD_SEPARATOR}%ct${FIELD_SEPARATOR}%b${RECORD_SEPARATOR}`
1406
+ ];
1407
+ if (options.sinceMs !== undefined && options.sinceMs > 0) {
1408
+ const iso = new Date(options.sinceMs).toISOString();
1409
+ args.push(`--since=${iso}`);
1410
+ }
1411
+ args.push("--");
1412
+ let stdout;
1413
+ try {
1414
+ const result = await execFileAsync2("git", args, {
1415
+ windowsHide: true,
1416
+ cwd: directory,
1417
+ timeout: GIT_TIMEOUT_MS2,
1418
+ maxBuffer: 32 * 1024 * 1024,
1419
+ encoding: "utf8"
1420
+ });
1421
+ stdout = result.stdout;
1422
+ } catch (error) {
1423
+ const message = error instanceof Error ? error.message : String(error);
1424
+ if (exclusion && message.includes("bad object")) {
1425
+ log2(`[git-commits] indexed tip ${exclusion.slice(0, 7)} is missing for ${projectLabel}; reading the full window`);
1426
+ return readGitCommitsResult(directory, { ...options, excludeReachableFrom: undefined });
1427
+ }
1428
+ const failure = classifyGitLogFailure(message);
1429
+ if (failure === "transient") {
1430
+ log2(`[git-commits] readGitCommits failed for ${projectLabel}: ${message.slice(0, 500)}`);
1431
+ } else {
1432
+ log2(`[git-commits] ${projectLabel} is not indexable (${failure})`);
1433
+ }
1434
+ return { commits: [], failure };
1435
+ }
1436
+ if (stdout.trim().length === 0) {
1437
+ log2(`[git-commits] readGitCommits returned empty stdout for ${projectLabel} (sinceMs=${options.sinceMs ?? "none"} args=${args.slice(0, 4).join(" ")})`);
1438
+ }
1439
+ return { commits: parseGitLogOutput(stdout), failure: null };
1440
+ }
1441
+ function parseGitLogOutput(stdout) {
1442
+ const commits = [];
1443
+ const records = stdout.split(RECORD_SEPARATOR);
1444
+ for (const rawRecord of records) {
1445
+ const record = rawRecord.replace(/^\s+/, "");
1446
+ if (!record)
1447
+ continue;
1448
+ const fields = [];
1449
+ let remaining = record;
1450
+ for (let i = 0;i < 4; i++) {
1451
+ const idx = remaining.indexOf(FIELD_SEPARATOR);
1452
+ if (idx < 0)
1453
+ break;
1454
+ fields.push(remaining.slice(0, idx));
1455
+ remaining = remaining.slice(idx + FIELD_SEPARATOR.length);
1456
+ }
1457
+ fields.push(remaining);
1458
+ if (fields.length < 5)
1459
+ continue;
1460
+ const sha = fields[0].trim();
1461
+ const subject = fields[1].trim();
1462
+ const author = fields[2].trim();
1463
+ const timeSec = Number.parseInt(fields[3].trim(), 10);
1464
+ const body = fields[4].trim();
1465
+ if (!FULL_OBJECT_NAME.test(sha) || !Number.isFinite(timeSec) || timeSec <= 0) {
1466
+ continue;
1467
+ }
1468
+ const message = body.length > 0 ? `${subject}
1469
+
1470
+ ${body}` : subject;
1471
+ commits.push({
1472
+ sha,
1473
+ shortSha: sha.slice(0, 7),
1474
+ message,
1475
+ author: author.length > 0 ? author : null,
1476
+ committedAtMs: timeSec * 1000
1477
+ });
1478
+ }
1479
+ return commits;
1480
+ }
1481
+ // ../plugin/src/features/magic-context/git-commits/indexer.ts
1482
+ var MS_PER_DAY = 24 * 60 * 60 * 1000;
1483
+ var EMBED_BATCH_SIZE = 16;
1484
+ var EMBED_MAX_PER_SWEEP = 500;
1485
+ var EMBED_SWEEP_MAX_WALL_CLOCK_MS = 5 * 60 * 1000;
1486
+ var indexInProgress = new Set;
1487
+ var embedInProgress = new Set;
1488
+ async function indexCommitsForProject(db, projectPath, directory, options) {
1489
+ const result = {
1490
+ scanned: 0,
1491
+ inserted: 0,
1492
+ updated: 0,
1493
+ evicted: 0,
1494
+ embedded: 0,
1495
+ nonIndexable: false
1496
+ };
1497
+ if (indexInProgress.has(projectPath)) {
1498
+ log2(`[git-commits] index already in progress for ${projectPath}, skipping`);
1499
+ return result;
1500
+ }
1501
+ indexInProgress.add(projectPath);
1502
+ try {
1503
+ const latestIndexed = getLatestIndexedCommitSha(db, projectPath);
1504
+ const sinceMs = Date.now() - options.sinceDays * MS_PER_DAY;
1505
+ const read = await readGitCommitsResult(directory, {
1506
+ sinceMs,
1507
+ maxCommits: options.maxCommits,
1508
+ projectIdentity: projectPath,
1509
+ excludeReachableFrom: latestIndexed ?? undefined
1510
+ });
1511
+ const commits = read.commits;
1512
+ result.scanned = commits.length;
1513
+ if (read.failure === "not_a_repo" || read.failure === "no_head") {
1514
+ result.nonIndexable = true;
1515
+ return result;
1516
+ }
1517
+ if (commits.length === 0) {
1518
+ result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
1519
+ log2(`[git-commits] no new commits for ${projectPath} (sinceMs=${sinceMs} latestIndexed=${latestIndexed?.slice(0, 7) ?? "none"} evicted=${result.evicted})`);
1520
+ return result;
1521
+ }
1522
+ log2(`[git-commits] read ${commits.length} commits for ${projectPath} (sinceMs=${sinceMs} latestIndexed=${latestIndexed?.slice(0, 7) ?? "none"})`);
1523
+ const upsert = upsertCommits(db, projectPath, commits);
1524
+ result.inserted = upsert.inserted;
1525
+ result.updated = upsert.updated;
1526
+ result.evicted = enforceProjectCap(db, projectPath, options.maxCommits);
1527
+ const snapshot = getProjectEmbeddingSnapshot2(projectPath);
1528
+ if (options.skipEmbed || !snapshot?.gitCommitEnabled) {
1529
+ log2(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=0 (embedding skipped: skipEmbed=${options.skipEmbed === true} gitCommitEnabled=${snapshot?.gitCommitEnabled === true})`);
1530
+ return result;
1531
+ }
1532
+ result.embedded = await embedUnembeddedCommits(db, projectPath);
1533
+ log2(`[git-commits] indexed ${projectPath}: scanned=${result.scanned} inserted=${result.inserted} updated=${result.updated} evicted=${result.evicted} embedded=${result.embedded}`);
1534
+ return result;
1535
+ } finally {
1536
+ indexInProgress.delete(projectPath);
1537
+ }
1538
+ }
1539
+ async function embedUnembeddedCommits(db, projectPath) {
1540
+ if (embedInProgress.has(projectPath)) {
1541
+ return 0;
1542
+ }
1543
+ const snapshot = getProjectEmbeddingSnapshot2(projectPath);
1544
+ if (!snapshot?.gitCommitEnabled) {
1545
+ return 0;
1546
+ }
1547
+ embedInProgress.add(projectPath);
1548
+ const startedAt = Date.now();
1549
+ const deadline = startedAt + EMBED_SWEEP_MAX_WALL_CLOCK_MS;
1550
+ let total = 0;
1551
+ try {
1552
+ while (Date.now() < deadline && total < EMBED_MAX_PER_SWEEP) {
1553
+ const rows = loadUnembeddedCommits(db, projectPath, snapshot.modelId, EMBED_BATCH_SIZE);
1554
+ if (rows.length === 0)
1555
+ break;
1556
+ let embeddedThisBatch = 0;
1557
+ try {
1558
+ const result = await embedItemsForProject2(projectPath, rows.map((row) => ({
1559
+ id: `commit:${row.sha}`,
1560
+ text: row.message,
1561
+ contentSha256: contentSha2562(row.message)
1562
+ })), undefined, db, projectPath);
1563
+ if (!result)
1564
+ break;
1565
+ db.transaction(() => {
1566
+ for (const row of rows) {
1567
+ const embedding = result.vectors.get(`commit:${row.sha}`);
1568
+ if (!embedding)
1569
+ continue;
1570
+ saveCommitEmbedding(db, row.sha, embedding, result.modelId);
1571
+ embeddedThisBatch += 1;
1572
+ }
1573
+ }).immediate();
1574
+ enqueueShadowEmbeddingItems2(projectPath, "commit", rows.filter((row) => result.vectors.has(`commit:${row.sha}`)).map((row) => row.sha));
1575
+ } catch (error) {
1576
+ log2(`[git-commits] embed batch failed for ${projectPath}: ${error instanceof Error ? error.message : String(error)}`);
1577
+ break;
1578
+ }
1579
+ if (embeddedThisBatch === 0)
1580
+ break;
1581
+ total += embeddedThisBatch;
1582
+ if (embeddedThisBatch < rows.length)
1583
+ break;
1584
+ }
1585
+ if (total > 0) {
1586
+ const totalEmbedded = countEmbeddedCommits(db, projectPath, snapshot.modelId);
1587
+ log2(`[git-commits] embedded ${total} commits for ${projectPath} (total embedded: ${totalEmbedded})`);
1588
+ }
1589
+ return total;
1590
+ } finally {
1591
+ embedInProgress.delete(projectPath);
1592
+ }
1593
+ }
1594
+ // ../plugin/src/features/magic-context/git-commits/search-git-commits.ts
1595
+ var ftsStatements = new WeakMap;
1596
+ var datedFtsStatements = new WeakMap;
1597
+ var ftsPlainStatements = new WeakMap;
1598
+ var datedFtsPlainStatements = new WeakMap;
1599
+ var getBySHAsStatements = new WeakMap;
1600
+ var datedGetBySHAsStatements = new WeakMap;
1601
+ var embeddingCache = new WeakMap;
1602
+ function getSearchEmbeddings(db, projectPath, modelId) {
1603
+ if (!isKnownAutocommit(db)) {
1604
+ embeddingCache.delete(db);
1605
+ return loadProjectCommitEmbeddings(db, projectPath, modelId);
1606
+ }
1607
+ let cache = embeddingCache.get(db);
1608
+ if (!cache) {
1609
+ cache = {
1610
+ revisionStatement: db.prepare(`SELECT total_changes() AS writes,
1611
+ (SELECT data_version FROM pragma_data_version) AS dataVersion,
1612
+ (SELECT schema_version FROM pragma_schema_version) AS schemaVersion`),
1613
+ revision: "",
1614
+ entries: new Map
1615
+ };
1616
+ embeddingCache.set(db, cache);
1617
+ }
1618
+ const revision = JSON.stringify(cache.revisionStatement.get());
1619
+ if (revision !== cache.revision) {
1620
+ cache.entries.clear();
1621
+ cache.revision = revision;
1622
+ }
1623
+ const key = JSON.stringify([projectPath, modelId]);
1624
+ const cached = cache.entries.get(key);
1625
+ if (cached) {
1626
+ cache.entries.delete(key);
1627
+ cache.entries.set(key, cached);
1628
+ return cached;
1629
+ }
1630
+ const vectors = loadProjectCommitEmbeddings(db, projectPath, modelId);
1631
+ const size = (entries) => [...entries.values()].reduce((sum, vector) => sum + vector.byteLength, 0);
1632
+ if (size(vectors) <= 32 * 1024 * 1024)
1633
+ cache.entries.set(key, vectors);
1634
+ while (cache.entries.size > 16 || [...cache.entries.values()].reduce((sum, entry) => sum + size(entry), 0) > 32 * 1024 * 1024) {
1635
+ const oldest = cache.entries.keys().next().value;
1636
+ if (oldest === undefined)
1637
+ break;
1638
+ cache.entries.delete(oldest);
1639
+ }
1640
+ return vectors;
1641
+ }
1642
+ function rowToCommit(row) {
1643
+ return {
1644
+ sha: row.sha,
1645
+ shortSha: row.short_sha,
1646
+ projectPath: row.project_path,
1647
+ message: row.message,
1648
+ author: row.author,
1649
+ committedAtMs: row.committed_at,
1650
+ indexedAtMs: row.indexed_at
1651
+ };
1652
+ }
1653
+ function getFtsStatement(db, dated = false) {
1654
+ const statements = dated ? datedFtsStatements : ftsStatements;
1655
+ let stmt = statements.get(db);
1656
+ if (!stmt) {
1657
+ stmt = db.prepare(`SELECT c.sha AS sha, c.project_path AS project_path, c.short_sha AS short_sha,
1658
+ c.message AS message, c.author AS author,
1659
+ c.committed_at AS committed_at, c.indexed_at AS indexed_at
1660
+ FROM git_commits_fts
1661
+ INNER JOIN git_commits c ON c.sha = git_commits_fts.sha
1662
+ WHERE c.project_path = ?
1663
+ ${dated ? "AND c.committed_at BETWEEN ? AND ?" : ""}
1664
+ AND git_commits_fts MATCH ?
1665
+ ORDER BY bm25(git_commits_fts) LIMIT ?`);
1666
+ statements.set(db, stmt);
1667
+ }
1668
+ return stmt;
1669
+ }
1670
+ function escapeLikePattern(text) {
1671
+ return text.replace(/[\\%_]/g, (char) => `\\${char}`);
1672
+ }
1673
+ function getLikeFallbackStatement(db, dated = false) {
1674
+ const statements = dated ? datedFtsPlainStatements : ftsPlainStatements;
1675
+ let stmt = statements.get(db);
1676
+ if (!stmt) {
1677
+ stmt = db.prepare(`SELECT sha, project_path, short_sha, message, author, committed_at, indexed_at
1678
+ FROM git_commits
1679
+ WHERE project_path = ?
1680
+ ${dated ? "AND committed_at BETWEEN ? AND ?" : ""}
1681
+ AND lower(message) LIKE '%' || lower(?) || '%' ESCAPE '\\'
1682
+ ORDER BY committed_at DESC LIMIT ?`);
1683
+ statements.set(db, stmt);
1684
+ }
1685
+ return stmt;
1686
+ }
1687
+ function getBySHAsStatement(db, dated = false) {
1688
+ const statements = dated ? datedGetBySHAsStatements : getBySHAsStatements;
1689
+ let stmt = statements.get(db);
1690
+ if (!stmt) {
1691
+ stmt = db.prepare(`SELECT sha, project_path, short_sha, message, author, committed_at, indexed_at
1692
+ FROM git_commits
1693
+ WHERE project_path = ?
1694
+ ${dated ? "AND committed_at BETWEEN ? AND ?" : ""}
1695
+ AND sha IN (SELECT value FROM json_each(?))`);
1696
+ statements.set(db, stmt);
1697
+ }
1698
+ return stmt;
1699
+ }
1700
+ function clamp01(value) {
1701
+ if (!Number.isFinite(value))
1702
+ return 0;
1703
+ return Math.min(1, Math.max(0, value));
1704
+ }
1705
+ function searchGitCommitsSync(db, projectPath, query, options) {
1706
+ const trimmed = query.trim();
1707
+ if (trimmed.length === 0 || options.limit <= 0)
1708
+ return [];
1709
+ const semanticWeight = options.semanticWeight ?? 0.7;
1710
+ const ftsWeight = options.ftsWeight ?? 0.3;
1711
+ const singleSourcePenalty = options.singleSourcePenalty ?? 0.8;
1712
+ const fetchLimit = Math.max(options.limit * 3, 30);
1713
+ const dated = options.from !== undefined || options.to !== undefined;
1714
+ const from = options.from ?? Number.MIN_SAFE_INTEGER;
1715
+ const to = options.to ?? Number.MAX_SAFE_INTEGER;
1716
+ const ftsCandidates = [];
1717
+ const sanitized = sanitizeFtsQuery(trimmed);
1718
+ if (sanitized.length > 0) {
1719
+ try {
1720
+ for (const row of getFtsStatement(db, dated).all(projectPath, ...dated ? [from, to] : [], sanitized, fetchLimit)) {
1721
+ ftsCandidates.push(rowToCommit(row));
1722
+ }
1723
+ } catch (error) {
1724
+ log2(`[git-commits] FTS query failed for "${trimmed}": ${error instanceof Error ? error.message : String(error)}`);
1725
+ }
1726
+ }
1727
+ if (ftsCandidates.length === 0) {
1728
+ for (const row of getLikeFallbackStatement(db, dated).all(projectPath, ...dated ? [from, to] : [], escapeLikePattern(trimmed), fetchLimit)) {
1729
+ ftsCandidates.push(rowToCommit(row));
1730
+ }
1731
+ }
1732
+ const ftsScores = new Map;
1733
+ ftsCandidates.forEach((commit, rank) => {
1734
+ ftsScores.set(commit.sha, 1 / (rank + 1));
1735
+ });
1736
+ const semanticScores = new Map;
1737
+ if (options.queryEmbedding && options.queryModelId && options.queryModelId !== "off") {
1738
+ const embeddings = getSearchEmbeddings(db, projectPath, options.queryModelId);
1739
+ for (const [sha, embedding] of embeddings.entries()) {
1740
+ const similarity = clamp01(cosineSimilarity(options.queryEmbedding, embedding));
1741
+ if (similarity > 0) {
1742
+ semanticScores.set(sha, similarity);
1743
+ }
1744
+ }
1745
+ }
1746
+ const bySha = new Map;
1747
+ for (const commit of ftsCandidates)
1748
+ bySha.set(commit.sha, commit);
1749
+ const semanticOnlyShas = [...semanticScores.keys()].filter((sha) => !bySha.has(sha));
1750
+ if (semanticOnlyShas.length > 0) {
1751
+ const rows = getBySHAsStatement(db, dated).all(projectPath, ...dated ? [from, to] : [], JSON.stringify(semanticOnlyShas));
1752
+ for (const row of rows)
1753
+ bySha.set(row.sha, rowToCommit(row));
1754
+ }
1755
+ if (options.captureLanes) {
1756
+ const fts = [...bySha.values()].filter((commit) => ftsScores.has(commit.sha)).map((commit) => ({
1757
+ commit,
1758
+ matchType: "fts",
1759
+ score: (ftsScores.get(commit.sha) ?? 0) * singleSourcePenalty
1760
+ })).sort((a, b) => b.score - a.score || b.commit.committedAtMs - a.commit.committedAtMs).slice(0, 25);
1761
+ const vector = [...bySha.values()].filter((commit) => (semanticScores.get(commit.sha) ?? 0) > 0).map((commit) => ({
1762
+ commit,
1763
+ matchType: "semantic",
1764
+ score: semanticScores.get(commit.sha) ?? 0
1765
+ })).sort((a, b) => b.score - a.score || b.commit.committedAtMs - a.commit.committedAtMs).slice(0, 25);
1766
+ options.captureLanes(fts, vector);
1767
+ }
1768
+ const results = [];
1769
+ for (const [sha, commit] of bySha.entries()) {
1770
+ const sem = semanticScores.get(sha);
1771
+ const fts = ftsScores.get(sha);
1772
+ let score = 0;
1773
+ let matchType = "fts";
1774
+ if (sem !== undefined && fts !== undefined) {
1775
+ score = semanticWeight * sem + ftsWeight * fts;
1776
+ matchType = "hybrid";
1777
+ } else if (sem !== undefined) {
1778
+ score = sem * singleSourcePenalty;
1779
+ matchType = "semantic";
1780
+ } else if (fts !== undefined) {
1781
+ score = fts * singleSourcePenalty;
1782
+ matchType = "fts";
1783
+ }
1784
+ if (score <= 0)
1785
+ continue;
1786
+ results.push({ commit, score, matchType });
1787
+ }
1788
+ results.sort((left, right) => {
1789
+ if (right.score !== left.score)
1790
+ return right.score - left.score;
1791
+ return right.commit.committedAtMs - left.commit.committedAtMs;
1792
+ });
1793
+ return results.slice(0, options.limit);
1794
+ }
1795
+ // ../plugin/src/features/magic-context/literal-probes.ts
1796
+ var MAX_PROBES = 5;
1797
+ var MIN_PROBE_LENGTH = 3;
1798
+ var SLASH_COMMAND_RE = /\/[a-z][a-z0-9]*(?:-[a-z0-9]+)+/gi;
1799
+ var KEBAB_SNAKE_RE = /[a-z][a-z0-9]*(?:[-_][a-z0-9]+)+/gi;
1800
+ var DOTTED_RE = /[a-z0-9][a-z0-9_-]*(?:\.[a-z0-9_-]+)+/gi;
1801
+ var CAMEL_RE = /\b[a-zA-Z][a-z0-9]*(?:[A-Z][a-z0-9]*)+\b/g;
1802
+ var SHA_RE = /\b[0-9a-f]{7,40}\b/gi;
1803
+ var ERROR_CODE_RE = /\b(?:TS\d{4,}|ERR_[A-Z][A-Z0-9_]*)\b/g;
1804
+ var QUOTED_RE = /["`]([^"`]{3,80})["`]/g;
1805
+ function looksLikeSha(token) {
1806
+ return /[0-9]/.test(token) && /^[0-9a-f]{7,40}$/i.test(token);
1807
+ }
1808
+ function extractLiteralProbes(query) {
1809
+ const trimmed = query.trim();
1810
+ if (trimmed.length === 0)
1811
+ return [];
1812
+ const ordered = [];
1813
+ const seen = new Set;
1814
+ const add = (raw) => {
1815
+ if (!raw)
1816
+ return;
1817
+ const probe = raw.trim();
1818
+ if (probe.length < MIN_PROBE_LENGTH)
1819
+ return;
1820
+ const key = probe.toLowerCase();
1821
+ if (seen.has(key))
1822
+ return;
1823
+ seen.add(key);
1824
+ ordered.push(probe);
1825
+ };
1826
+ for (const m of trimmed.matchAll(QUOTED_RE))
1827
+ add(m[1]);
1828
+ for (const m of trimmed.matchAll(SLASH_COMMAND_RE))
1829
+ add(m[0]);
1830
+ for (const m of trimmed.matchAll(ERROR_CODE_RE))
1831
+ add(m[0]);
1832
+ for (const m of trimmed.matchAll(DOTTED_RE))
1833
+ add(m[0]);
1834
+ for (const m of trimmed.matchAll(KEBAB_SNAKE_RE))
1835
+ add(m[0]);
1836
+ for (const m of trimmed.matchAll(CAMEL_RE))
1837
+ add(m[0]);
1838
+ for (const m of trimmed.matchAll(SHA_RE)) {
1839
+ if (looksLikeSha(m[0]))
1840
+ add(m[0]);
1841
+ }
1842
+ return ordered.slice(0, MAX_PROBES);
1843
+ }
1844
+ function containsProbeVerbatim(text, probes) {
1845
+ if (probes.length === 0)
1846
+ return false;
1847
+ const haystack = text.toLowerCase();
1848
+ return probes.some((probe) => haystack.includes(probe.toLowerCase()));
1849
+ }
1850
+
1851
+ // ../plugin/src/features/magic-context/message-fts-session-filter.ts
1852
+ var MESSAGE_FTS_SESSION_FILTER_SQL = `+message_history_fts.rowid IN (
1853
+ SELECT fts_rowid FROM message_fts_rowid_map WHERE session_id = ?1
1854
+ ) AND `;
1855
+ var coverageCaches = new WeakMap;
1856
+ function getCoverageCache(db) {
1857
+ let cache = coverageCaches.get(db);
1858
+ if (!cache) {
1859
+ cache = {
1860
+ pin: db.prepare("SELECT fts_rowid FROM message_fts_rowid_map LIMIT 1"),
1861
+ stamp: db.prepare("SELECT total_changes() AS changes, data_version AS version FROM pragma_data_version"),
1862
+ missing: db.prepare(`SELECT DISTINCT session_id AS sessionId
1863
+ FROM message_history_fts WHERE rowid IN (
1864
+ SELECT id FROM message_history_fts_docsize
1865
+ EXCEPT SELECT fts_rowid FROM message_fts_rowid_map
1866
+ )`)
1867
+ };
1868
+ coverageCaches.set(db, cache);
1869
+ }
1870
+ return cache;
1871
+ }
1872
+ function withMessageFtsSessionFilter(db, sessionId, read) {
1873
+ let cacheable = false;
1874
+ try {
1875
+ const transaction = db;
1876
+ cacheable = transaction.inTransaction === false || transaction.isTransaction === false;
1877
+ if (!cacheable)
1878
+ coverageCaches.delete(db);
1879
+ } catch {
1880
+ coverageCaches.delete(db);
1881
+ }
1882
+ return db.transaction(() => {
1883
+ let sessionFirst = false;
1884
+ try {
1885
+ const cache = getCoverageCache(db);
1886
+ cache.pin.get();
1887
+ const stamp = cache.stamp.get();
1888
+ let incomplete = cache.incompleteSessions;
1889
+ if (!cacheable || !incomplete || cache.version !== stamp.version || cache.changes !== stamp.changes) {
1890
+ const rows = cache.missing.all();
1891
+ incomplete = new Set(rows.map((row) => row.sessionId));
1892
+ if (cacheable) {
1893
+ cache.version = stamp.version;
1894
+ cache.changes = stamp.changes;
1895
+ cache.incompleteSessions = incomplete;
1896
+ }
1897
+ }
1898
+ sessionFirst = !incomplete.has(sessionId);
1899
+ } catch {
1900
+ coverageCaches.delete(db);
1901
+ }
1902
+ return read(sessionFirst);
1903
+ }).deferred();
1904
+ }
1905
+
1906
+ // ../plugin/src/features/magic-context/search-measurement.ts
1907
+ import { createHash } from "node:crypto";
1908
+ function resultId(result) {
1909
+ switch (result.source) {
1910
+ case "memory":
1911
+ return `memory:${result.memoryId}`;
1912
+ case "message":
1913
+ return `message:${result.messageId}`;
1914
+ case "compartment":
1915
+ return `chunk:${result.compartmentId}`;
1916
+ case "git_commit":
1917
+ return `commit:${result.sha}`;
1918
+ case "primer":
1919
+ return `primer:${result.primerId}`;
1920
+ case "note":
1921
+ return `note:${result.noteId}`;
1922
+ }
1923
+ }
1924
+ async function recordShadowMeasurement(args) {
1925
+ try {
1926
+ const shadowCohort = getShadowEmbeddingMeasurementCohort2(args.projectPath);
1927
+ if (!shadowCohort)
1928
+ return;
1929
+ const primaryCohort = getPrimaryEmbeddingMeasurementCohort2(args.projectPath);
1930
+ const shadowStartedAt = Date.now();
1931
+ let shadowFailed = false;
1932
+ let shadowResults = [];
1933
+ try {
1934
+ const vector = await embedShadowTextForProject2(args.projectPath, args.query);
1935
+ if (!vector) {
1936
+ shadowFailed = true;
1937
+ } else {
1938
+ shadowResults = await args.search(args.db, args.sessionId, args.projectPath, args.query, {
1939
+ ...args.options,
1940
+ embedQuery: async () => vector,
1941
+ isEmbeddingRuntimeEnabled: () => true,
1942
+ embeddingEnabled: true,
1943
+ embeddingModelIdOverride: shadowCohort.modelId,
1944
+ chunkModelIdOverride: shadowCohort.chunkModelId,
1945
+ measurementDisabled: true,
1946
+ countRetrievals: false
1947
+ });
1948
+ }
1949
+ } catch {
1950
+ shadowFailed = true;
1951
+ }
1952
+ const primaryModelId = args.primaryQuery?.modelId ?? primaryCohort?.modelId ?? "";
1953
+ const primaryFingerprint = primaryCohort?.fingerprint ?? "";
1954
+ const primaryEpoch = primaryCohort?.epoch ?? 0;
1955
+ const cohortKey = JSON.stringify({
1956
+ primaryFingerprint,
1957
+ primaryEpoch,
1958
+ shadowFingerprint: shadowCohort.fingerprint,
1959
+ shadowEpoch: shadowCohort.epoch
1960
+ });
1961
+ const primaryIds = args.primaryResults.map(resultId);
1962
+ const shadowIds = shadowResults.map(resultId);
1963
+ const corpusHash = sha256(JSON.stringify({ query: args.query, primaryIds, shadowIds }));
1964
+ recordEmbeddingMeasurement(args.db, {
1965
+ sessionId: args.sessionId,
1966
+ projectPath: args.projectPath,
1967
+ queryText: args.query,
1968
+ cohortKey,
1969
+ primaryResultIds: primaryIds,
1970
+ shadowResultIds: shadowIds,
1971
+ primaryLatencyMs: args.primaryLatencyMs,
1972
+ shadowLatencyMs: Date.now() - shadowStartedAt,
1973
+ primaryFailed: args.primaryQuery === null,
1974
+ shadowFailed,
1975
+ primaryModelId,
1976
+ shadowModelId: shadowCohort.modelId,
1977
+ primaryFingerprint,
1978
+ shadowFingerprint: shadowCohort.fingerprint,
1979
+ primaryEpoch,
1980
+ shadowEpoch: shadowCohort.epoch,
1981
+ corpusHash,
1982
+ coverage: {
1983
+ primaryResultCount: primaryIds.length,
1984
+ shadowResultCount: shadowIds.length,
1985
+ primaryAnswered: args.primaryQuery !== null,
1986
+ shadowAnswered: !shadowFailed
1987
+ }
1988
+ });
1989
+ } catch (error) {
1990
+ log2("[magic-context] shadow embedding measurement failed:", error);
1991
+ }
1992
+ }
1993
+ function sha256(value) {
1994
+ return createHash("sha256").update(value).digest("hex");
1995
+ }
1996
+
1997
+ // ../plugin/src/hooks/magic-context/module-transport.ts
1998
+ import { join } from "node:path";
1999
+
2000
+ // ../plugin/src/hooks/magic-context/reply-pages.ts
2001
+ var MAX_REPLY_BYTES = 128 * 1024 * 1024;
2002
+ var MAX_PAGE_BYTES = 512 * 1024;
2003
+
2004
+ // ../plugin/src/hooks/magic-context/module-transport.ts
2005
+ function getDefaultSubcConnectionFile() {
2006
+ return join(getDataDir(), "cortexkit", "run", "subc-connection.json");
2007
+ }
2008
+
2009
+ // ../plugin/src/features/magic-context/search-rerank.ts
2010
+ var RERANK_MODEL = "gte-reranker-modernbert-base-f32";
2011
+ var RERANK_LANE_LIMIT = 25;
2012
+ var DEADLINE_MS = 1500;
2013
+ function isProseQuery(query) {
2014
+ const text = query.trim();
2015
+ if (!text || /[\n;]|^["'`]|["'`]$/.test(text))
2016
+ return false;
2017
+ if (/^(?:error\b|cannot\b|failed\b|panic\b|traceback\b|assertion\b|SQLITE_|TypeError\b|ReferenceError\b|SyntaxError\b)/i.test(text))
2018
+ return false;
2019
+ if (/\b(?:[A-Z][A-Z_]+:|\w*(?:Error|Exception):)|\bat\s+\S+\s*\(/.test(text))
2020
+ return false;
2021
+ const words = text.replace(/[?!.]$/, "").split(/\s+/);
2022
+ if (words.length < 3)
2023
+ return false;
2024
+ const plain = words.filter((word) => /^[a-zA-Z]+(?:['’-][a-zA-Z]+)*$/.test(word));
2025
+ if (/^(?:how|why|what|where|when|which|who)\b/i.test(text))
2026
+ return plain.length >= 3;
2027
+ return words.length >= 4 && plain.length >= 3 && !/[\\/]|::|=/.test(text);
2028
+ }
2029
+ function emptySearchLanes() {
2030
+ return {
2031
+ memory_fts: [],
2032
+ memory_vector: [],
2033
+ primer_fts: [],
2034
+ primer_vector: [],
2035
+ message_fts: [],
2036
+ chunk_vector: [],
2037
+ git_fts: [],
2038
+ git_vector: [],
2039
+ notes: []
2040
+ };
2041
+ }
2042
+ function searchResultKey(hit) {
2043
+ switch (hit.source) {
2044
+ case "memory":
2045
+ return `memory:${hit.memoryId}`;
2046
+ case "primer":
2047
+ return `primer:${hit.primerId}`;
2048
+ case "message":
2049
+ return `message:${hit.messageId}`;
2050
+ case "compartment":
2051
+ return `compartment:${hit.compartmentId}`;
2052
+ case "git_commit":
2053
+ return `git_commit:${hit.sha}`;
2054
+ case "note":
2055
+ return `note:${hit.noteId}`;
2056
+ }
2057
+ }
2058
+ function fuseSearchLanes(lanes) {
2059
+ const compartments = lanes.chunk_vector.filter((hit) => hit.source === "compartment");
2060
+ const fused = new Map;
2061
+ for (const [name, lane] of Object.entries(lanes)) {
2062
+ const seen = new Set;
2063
+ lane.slice(0, RERANK_LANE_LIMIT).forEach((original, index) => {
2064
+ const hit = name === "message_fts" && original.source === "message" ? compartments.find((chunk) => original.messageOrdinal >= chunk.startOrdinal && original.messageOrdinal <= chunk.endOrdinal) ?? original : original;
2065
+ const key = searchResultKey(hit);
2066
+ if (seen.has(key))
2067
+ return;
2068
+ seen.add(key);
2069
+ const prior = fused.get(key);
2070
+ const score = 1 / (60 + index + 1);
2071
+ if (prior)
2072
+ prior.score += score;
2073
+ else
2074
+ fused.set(key, { hit, score, order: fused.size });
2075
+ });
2076
+ }
2077
+ return [...fused.values()].sort((a, b) => b.score - a.score || a.order - b.order).slice(0, 20).map((row) => row.hit);
2078
+ }
2079
+ function truncateUtf8(text, bytes) {
2080
+ const buffer = Buffer.from(text);
2081
+ let end = Math.min(buffer.length, bytes);
2082
+ while (end > 0 && end < buffer.length && (buffer[end] & 192) === 128)
2083
+ end--;
2084
+ return buffer.subarray(0, end).toString("utf8");
2085
+ }
2086
+ function candidateText(location, title, body) {
2087
+ const header = `${truncateUtf8(location.replaceAll(`
2088
+ `, " "), 200)}
2089
+ ${truncateUtf8(title.replaceAll(`
2090
+ `, " "), 120)}
2091
+ `;
2092
+ return header + truncateUtf8(body, 1024 - Buffer.byteLength(header));
2093
+ }
2094
+
2095
+ class RerankFailure extends Error {
2096
+ reason;
2097
+ constructor(reason) {
2098
+ super(reason);
2099
+ this.reason = reason;
2100
+ }
2101
+ }
2102
+ function unwrap(value) {
2103
+ if (!value || typeof value !== "object")
2104
+ throw new RerankFailure("validation");
2105
+ const outer = value;
2106
+ const body = outer.result ?? outer;
2107
+ if (!body || typeof body !== "object")
2108
+ throw new RerankFailure("validation");
2109
+ if (body.error) {
2110
+ const error = body.error;
2111
+ throw new RerankFailure(error.code ?? "synapse_error");
2112
+ }
2113
+ return body;
2114
+ }
2115
+ function pinReranker(value) {
2116
+ const body = unwrap(value);
2117
+ const models = body.models;
2118
+ const model = Array.isArray(models) ? models.find((row) => row?.model_id === RERANK_MODEL) : null;
2119
+ if (model?.certified !== true || model.fingerprints?.length !== 1 || typeof model.fingerprints[0] !== "string" || !/^[a-f0-9]{64}$/.test(model.fingerprints[0]) || !Number.isSafeInteger(body.table_epoch))
2120
+ throw new RerankFailure("catalog_validation");
2121
+ return { fingerprint: model.fingerprints[0], tableEpoch: body.table_epoch };
2122
+ }
2123
+ var loggedReasons = new Set;
2124
+ function logRerankFallback(reason) {
2125
+ if (loggedReasons.has(reason))
2126
+ return;
2127
+ loggedReasons.add(reason);
2128
+ log2(`[ctx_search] rerank fallback (${reason}); retaining original ranking`);
2129
+ }
2130
+ function reasonOf(error) {
2131
+ if (error instanceof RerankFailure)
2132
+ return error.reason;
2133
+ if (error && typeof error === "object" && "code" in error) {
2134
+ const code = String(error.code);
2135
+ return ["ENOENT", "ECONNREFUSED"].includes(code) ? "missing_synapse" : code;
2136
+ }
2137
+ return "synapse_error";
2138
+ }
2139
+ async function bounded(promise) {
2140
+ let timer;
2141
+ try {
2142
+ return await Promise.race([
2143
+ promise,
2144
+ new Promise((_, reject) => {
2145
+ timer = setTimeout(() => reject(new RerankFailure("deadline_exceeded")), DEADLINE_MS);
2146
+ })
2147
+ ]);
2148
+ } finally {
2149
+ clearTimeout(timer);
2150
+ }
2151
+ }
2152
+ var connections = new Map;
2153
+ async function connect(connectionFile) {
2154
+ let connection = connections.get(connectionFile);
2155
+ if (!connection) {
2156
+ connection = SubcClient.connect({ connectionFile });
2157
+ connections.set(connectionFile, connection);
2158
+ connection.catch(() => connections.delete(connectionFile));
2159
+ }
2160
+ return bounded(connection);
2161
+ }
2162
+ async function prepareReranker(projectRoot, sessionId, connectionFile, injectedClient) {
2163
+ try {
2164
+ if (injectedClient === null)
2165
+ throw new RerankFailure("missing_synapse");
2166
+ const client = injectedClient ?? await connect(connectionFile ?? getDefaultSubcConnectionFile());
2167
+ const identity = {
2168
+ project_root: projectRoot,
2169
+ harness: "magic-context",
2170
+ session: sessionId
2171
+ };
2172
+ const lane = pinReranker(await bounded(client.call("synapse", "models.list", {}, { timeoutMs: DEADLINE_MS, targetKind: "management_surface", identity })));
2173
+ return { client, lane, identity };
2174
+ } catch (error) {
2175
+ logRerankFallback(reasonOf(error));
2176
+ return null;
2177
+ }
2178
+ }
2179
+ async function rerankSearchResults(session, query, baseline, pool, texts, limit) {
2180
+ if (!pool.length)
2181
+ return baseline;
2182
+ try {
2183
+ if (pool.length > 20 || texts.length !== pool.length || texts.some((text) => Buffer.byteLength(text) > 1024))
2184
+ throw new RerankFailure("validation");
2185
+ const body = unwrap(await bounded(session.client.call("synapse", "rerank.score", {
2186
+ model: RERANK_MODEL,
2187
+ query,
2188
+ candidates: texts,
2189
+ required_fingerprint: session.lane.fingerprint,
2190
+ allow_equivalent: false,
2191
+ accept_declared: false,
2192
+ max_queue_ms: 100,
2193
+ deadline_ms: DEADLINE_MS
2194
+ }, {
2195
+ timeoutMs: DEADLINE_MS,
2196
+ targetKind: "management_surface",
2197
+ identity: session.identity
2198
+ })));
2199
+ if (body.fingerprint !== session.lane.fingerprint)
2200
+ throw new RerankFailure("fingerprint_mismatch");
2201
+ if (body.table_epoch !== session.lane.tableEpoch)
2202
+ throw new RerankFailure("epoch_mismatch");
2203
+ if (!Array.isArray(body.scores) || body.scores.length !== pool.length || body.scores.some((score) => typeof score !== "number" || !Number.isFinite(score)))
2204
+ throw new RerankFailure("validation");
2205
+ const scores = body.scores;
2206
+ return pool.map((hit, index) => ({ hit, index, score: scores[index] })).sort((a, b) => b.score - a.score || a.index - b.index).slice(0, limit).map((row) => row.hit);
2207
+ } catch (error) {
2208
+ logRerankFallback(reasonOf(error));
2209
+ return baseline;
2210
+ }
2211
+ }
2212
+ function candidateTexts(db, sessionId, projectPath, pool) {
2213
+ return pool.map((hit) => {
2214
+ const id = searchResultKey(hit).split(":").slice(1).join(":");
2215
+ let location = `${hit.source} ${id} ${projectPath}`;
2216
+ let title = hit.content.split(`
2217
+ `)[0];
2218
+ let body = hit.content;
2219
+ switch (hit.source) {
2220
+ case "memory": {
2221
+ const row = db.prepare("SELECT content FROM memories WHERE id=?").get(hit.memoryId);
2222
+ body = row.content;
2223
+ title = body.split(`
2224
+ `)[0];
2225
+ break;
2226
+ }
2227
+ case "primer": {
2228
+ const row = db.prepare("SELECT question, answer FROM primers WHERE id=?").get(hit.primerId);
2229
+ title = row.question;
2230
+ body = `Q: ${row.question}
2231
+ A: ${row.answer}`;
2232
+ break;
2233
+ }
2234
+ case "git_commit": {
2235
+ const row = db.prepare("SELECT message FROM git_commits WHERE sha=?").get(hit.sha);
2236
+ body = row.message;
2237
+ title = `${hit.shortSha} ${body.split(`
2238
+ `)[0]}`;
2239
+ break;
2240
+ }
2241
+ case "note": {
2242
+ const row = db.prepare("SELECT content, ready_reason AS reason FROM notes WHERE id=?").get(hit.noteId);
2243
+ body = `${row.content}${row.reason ? `
2244
+ Reason: ${row.reason}` : ""}`;
2245
+ title = body.split(`
2246
+ `)[0];
2247
+ break;
2248
+ }
2249
+ case "message": {
2250
+ const mapped = db.prepare(`SELECT f.content FROM message_fts_rowid_map m
2251
+ JOIN message_history_fts f ON f.rowid=m.fts_rowid
2252
+ WHERE m.session_id=? AND m.message_ordinal=? AND f.message_id=?`).get(sessionId, hit.messageOrdinal, hit.messageId);
2253
+ const row = mapped ?? db.prepare("SELECT content FROM message_history_fts WHERE session_id=? AND message_id=? ORDER BY CAST(message_ordinal AS INTEGER) LIMIT 1").get(sessionId, hit.messageId);
2254
+ body = row.content;
2255
+ title = `${hit.role}: ${body.split(`
2256
+ `)[0]}`;
2257
+ location = `message ${hit.messageId} ${sessionId} @${hit.messageOrdinal}`;
2258
+ break;
2259
+ }
2260
+ case "compartment": {
2261
+ title = hit.title;
2262
+ location = `compartment ${hit.compartmentId} ${hit.sessionId} @${hit.startOrdinal}-${hit.endOrdinal}`;
2263
+ body = buildCanonicalChunkTextFromFts(db, hit.sessionId, hit.startOrdinal, hit.endOrdinal) ?? "";
2264
+ break;
2265
+ }
2266
+ }
2267
+ return candidateText(location, title, body);
2268
+ });
2269
+ }
2270
+
2271
+ // ../plugin/src/features/magic-context/search.ts
2272
+ var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
2273
+ var MAX_UNIFIED_SEARCH_LIMIT = 25;
2274
+ var FTS_SEMANTIC_CANDIDATE_LIMIT = 50;
2275
+ var SEMANTIC_WEIGHT = 0.7;
2276
+ var FTS_WEIGHT = 0.3;
2277
+ var SINGLE_SOURCE_PENALTY = 0.8;
2278
+ var RESULT_PREVIEW_LIMIT = 220;
2279
+ var MEMORY_SOURCE_BOOST = 1.3;
2280
+ var MESSAGE_SOURCE_BOOST = 1.275;
2281
+ var GIT_COMMIT_SOURCE_BOOST = 1.2;
2282
+ var PRIMER_SOURCE_BOOST = 1.25;
2283
+ var messageSearchStatements = new WeakMap;
2284
+ var sessionFirstMessageSearchStatements = new WeakMap;
2285
+ var messageSearchStatementsWithCutoff = new WeakMap;
2286
+ var sessionFirstMessageSearchStatementsWithCutoff = new WeakMap;
2287
+ var messageSearchStatementsWithDateRange = new WeakMap;
2288
+ var messageSearchDiagnosticStatements = new WeakMap;
2289
+ var sessionFirstMessageSearchDiagnosticStatements = new WeakMap;
2290
+ var messageSearchDiagnosticStatementsWithDateRange = new WeakMap;
2291
+ var batchedMessageSearchStatements = new WeakMap;
2292
+ var batchedFtsCountStatements = new WeakMap;
2293
+ function createUnifiedSearchDiagnostics2() {
2294
+ return {
2295
+ suppressedVisibleMemoryIds: [],
2296
+ suppressedLiveMessageMatches: 0,
2297
+ gitCommitUnavailable: null
2298
+ };
2299
+ }
2300
+ function searchMessageHistory2(db, request) {
2301
+ const diagnostics = request.collectDiagnostics ? createUnifiedSearchDiagnostics2() : undefined;
2302
+ const capture = request.captureLimit ? { limit: request.captureLimit, results: [] } : undefined;
2303
+ const results = searchMessages({
2304
+ db,
2305
+ sessionId: request.sessionId,
2306
+ query: request.query,
2307
+ limit: request.limit,
2308
+ maxOrdinal: request.maxOrdinal,
2309
+ probes: request.probes,
2310
+ relaxedRecall: request.relaxedRecall,
2311
+ diagnostics,
2312
+ dateRange: request.dateRange,
2313
+ capture
2314
+ });
2315
+ return {
2316
+ results,
2317
+ laneResults: capture?.results,
2318
+ suppressedLiveMessageMatches: diagnostics?.suppressedLiveMessageMatches ?? 0
2319
+ };
2320
+ }
2321
+ function normalizeLimit(limit) {
2322
+ if (typeof limit !== "number" || !Number.isFinite(limit)) {
2323
+ return DEFAULT_UNIFIED_SEARCH_LIMIT;
2324
+ }
2325
+ return Math.min(MAX_UNIFIED_SEARCH_LIMIT, Math.max(1, Math.floor(limit)));
2326
+ }
2327
+ function normalizeDateRange(from, to) {
2328
+ if (from === undefined && to === undefined)
2329
+ return null;
2330
+ return {
2331
+ from: typeof from === "number" && Number.isSafeInteger(from) ? from : Number.MIN_SAFE_INTEGER,
2332
+ to: typeof to === "number" && Number.isSafeInteger(to) ? to : Number.MAX_SAFE_INTEGER
2333
+ };
2334
+ }
2335
+ function timestampIsInRange(timestamp, range) {
2336
+ return range === null || timestamp >= range.from && timestamp <= range.to;
2337
+ }
2338
+ var ID_SHAPED_QUERY_MAX_TOKENS = 5;
2339
+ var ID_SHAPED_TOKEN = /^#?\d+$/;
2340
+ function parseIdShapedQuery2(query) {
2341
+ const trimmed = query.trim();
2342
+ if (trimmed.length === 0) {
2343
+ return null;
2344
+ }
2345
+ const tokens = trimmed.split(/[\s,]+/).filter((token) => token.length > 0);
2346
+ if (tokens.length === 0 || tokens.length > ID_SHAPED_QUERY_MAX_TOKENS) {
2347
+ return null;
2348
+ }
2349
+ const ids = [];
2350
+ for (const token of tokens) {
2351
+ if (!ID_SHAPED_TOKEN.test(token)) {
2352
+ return null;
2353
+ }
2354
+ const parsed = Number.parseInt(token.replace(/^#/, ""), 10);
2355
+ if (!Number.isFinite(parsed) || parsed <= 0) {
2356
+ return null;
2357
+ }
2358
+ ids.push(parsed);
2359
+ }
2360
+ return ids;
2361
+ }
2362
+ function normalizeCosineScore(score) {
2363
+ if (!Number.isFinite(score)) {
2364
+ return 0;
2365
+ }
2366
+ return Math.min(1, Math.max(0, score));
2367
+ }
2368
+ function previewText(text) {
2369
+ const normalized = text.replace(/\s+/g, " ").trim();
2370
+ if (normalized.length <= RESULT_PREVIEW_LIMIT) {
2371
+ return normalized;
2372
+ }
2373
+ return `${normalized.slice(0, RESULT_PREVIEW_LIMIT - 1).trimEnd()}…`;
2374
+ }
2375
+ function resolveSearchWorkspaceContext(db, projectPath, identitySet) {
2376
+ const resolved = identitySet ?? resolveWorkspaceIdentitySet(db, projectPath);
2377
+ const isWorkspaced = resolved.identities.length > 1;
2378
+ const expanded = expandWorkspaceIdentitySetWithAliases(db, resolved.identities);
2379
+ const expandedIdentities = isWorkspaced ? expanded.expandedIdentities : resolved.identities;
2380
+ const canonicalIdentityByStoredPath = isWorkspaced ? expanded.canonicalIdentityByStoredPath : new Map(resolved.identities.map((identity) => [identity, identity]));
2381
+ const ownIdentities = expandedIdentities.filter((identity) => canonicalIdentityByStoredPath.get(identity) === projectPath);
2382
+ return {
2383
+ identities: resolved.identities,
2384
+ expandedIdentities,
2385
+ ownIdentities,
2386
+ shareCategories: isWorkspaced ? resolveWorkspaceShareCategories(db, projectPath) : null,
2387
+ namesByIdentity: resolved.namesByIdentity,
2388
+ canonicalIdentityByStoredPath,
2389
+ isWorkspaced
2390
+ };
2391
+ }
2392
+ function memoryWorkspaceIdentity(memory, workspace) {
2393
+ return resolveStoredPathWorkspaceIdentity(memory.projectPath, workspace.identities, workspace.canonicalIdentityByStoredPath);
2394
+ }
2395
+ function sourceNamesForSearchMemories(args) {
2396
+ if (!args.workspace.isWorkspaced)
2397
+ return;
2398
+ const sourceNames = new Map;
2399
+ for (const memory of args.memories) {
2400
+ const source = sourceNameForMemory(memory.projectPath, args.projectPath, args.workspace.identities, args.workspace.namesByIdentity, args.workspace.canonicalIdentityByStoredPath);
2401
+ if (source)
2402
+ sourceNames.set(memory.id, source);
2403
+ }
2404
+ return sourceNames.size > 0 ? sourceNames : undefined;
2405
+ }
2406
+ function getMessageSearchStatement(db, sessionFirst) {
2407
+ const statements = sessionFirst ? sessionFirstMessageSearchStatements : messageSearchStatements;
2408
+ let stmt = statements.get(db);
2409
+ if (!stmt) {
2410
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, message_id AS messageId, role, content FROM message_history_fts WHERE ${sessionFirst ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}session_id = ?1 AND message_history_fts MATCH ? ORDER BY bm25(message_history_fts), CAST(message_ordinal AS INTEGER) ASC LIMIT ?`);
2411
+ statements.set(db, stmt);
2412
+ }
2413
+ return stmt;
2414
+ }
2415
+ function getMessageSearchStatementWithCutoff(db, sessionFirst) {
2416
+ const statements = sessionFirst ? sessionFirstMessageSearchStatementsWithCutoff : messageSearchStatementsWithCutoff;
2417
+ let stmt = statements.get(db);
2418
+ if (!stmt) {
2419
+ stmt = db.prepare(`SELECT message_ordinal AS messageOrdinal, message_id AS messageId, role, content FROM message_history_fts WHERE ${sessionFirst ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}session_id = ?1 AND message_history_fts MATCH ? AND CAST(message_ordinal AS INTEGER) <= ? ORDER BY bm25(message_history_fts), CAST(message_ordinal AS INTEGER) ASC LIMIT ?`);
2420
+ statements.set(db, stmt);
2421
+ }
2422
+ return stmt;
2423
+ }
2424
+ function getMessageSearchStatementWithDateRange(db, withCutoff) {
2425
+ let statements = messageSearchStatementsWithDateRange.get(db);
2426
+ if (!statements) {
2427
+ statements = new Map;
2428
+ messageSearchStatementsWithDateRange.set(db, statements);
2429
+ }
2430
+ const key = withCutoff ? "cutoff" : "all";
2431
+ let statement = statements.get(key);
2432
+ if (!statement) {
2433
+ statement = db.prepare(`SELECT message_history_fts.message_ordinal AS messageOrdinal,
2434
+ message_history_fts.message_id AS messageId,
2435
+ message_history_fts.role AS role,
2436
+ message_history_fts.content AS content
2437
+ FROM message_history_fts
2438
+ JOIN message_fts_rowid_map AS map
2439
+ ON map.session_id = message_history_fts.session_id
2440
+ AND map.fts_rowid = message_history_fts.rowid
2441
+ WHERE ${MESSAGE_FTS_SESSION_FILTER_SQL}message_history_fts.session_id = ?1
2442
+ AND message_history_fts MATCH ?
2443
+ AND map.message_time_ms BETWEEN ? AND ?
2444
+ ${withCutoff ? "AND CAST(message_history_fts.message_ordinal AS INTEGER) <= ?" : ""}
2445
+ ORDER BY bm25(message_history_fts),
2446
+ CAST(message_history_fts.message_ordinal AS INTEGER) ASC
2447
+ LIMIT ?`);
2448
+ statements.set(key, statement);
2449
+ }
2450
+ return statement;
2451
+ }
2452
+ function getMessageSearchDiagnosticStatement(db, sessionFirst) {
2453
+ const statements = sessionFirst ? sessionFirstMessageSearchDiagnosticStatements : messageSearchDiagnosticStatements;
2454
+ let stmt = statements.get(db);
2455
+ if (!stmt) {
2456
+ stmt = db.prepare(`
2457
+ WITH matches AS MATERIALIZED (
2458
+ SELECT
2459
+ message_ordinal AS messageOrdinal,
2460
+ message_id AS messageId,
2461
+ role,
2462
+ content,
2463
+ CAST(message_ordinal AS INTEGER) AS ordinalValue,
2464
+ bm25(message_history_fts) AS ftsRank
2465
+ FROM message_history_fts
2466
+ WHERE ${sessionFirst ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}session_id = ?1 AND message_history_fts MATCH ?
2467
+ ),
2468
+ eligible AS (
2469
+ SELECT * FROM matches
2470
+ WHERE ordinalValue <= ?
2471
+ ORDER BY ftsRank, ordinalValue ASC
2472
+ LIMIT ?
2473
+ ),
2474
+ summary AS (
2475
+ SELECT COUNT(*) AS suppressedCount
2476
+ FROM matches
2477
+ WHERE ordinalValue > ?
2478
+ )
2479
+ SELECT
2480
+ eligible.messageOrdinal,
2481
+ eligible.messageId,
2482
+ eligible.role,
2483
+ eligible.content,
2484
+ eligible.ftsRank,
2485
+ summary.suppressedCount,
2486
+ 0 AS summaryOnly
2487
+ FROM eligible CROSS JOIN summary
2488
+ UNION ALL
2489
+ SELECT NULL, NULL, NULL, NULL, NULL, summary.suppressedCount, 1
2490
+ FROM summary
2491
+ WHERE NOT EXISTS (SELECT 1 FROM eligible)
2492
+ ORDER BY summaryOnly ASC, ftsRank ASC, messageOrdinal ASC
2493
+ `);
2494
+ statements.set(db, stmt);
2495
+ }
2496
+ return stmt;
2497
+ }
2498
+ function getMessageSearchDiagnosticStatementWithDateRange(db) {
2499
+ let statement = messageSearchDiagnosticStatementsWithDateRange.get(db);
2500
+ if (!statement) {
2501
+ statement = db.prepare(`
2502
+ WITH matches AS MATERIALIZED (
2503
+ SELECT
2504
+ message_history_fts.message_ordinal AS messageOrdinal,
2505
+ message_history_fts.message_id AS messageId,
2506
+ message_history_fts.role AS role,
2507
+ message_history_fts.content AS content,
2508
+ CAST(message_history_fts.message_ordinal AS INTEGER) AS ordinalValue,
2509
+ bm25(message_history_fts) AS ftsRank
2510
+ FROM message_history_fts
2511
+ JOIN message_fts_rowid_map AS map
2512
+ ON map.session_id = message_history_fts.session_id
2513
+ AND map.fts_rowid = message_history_fts.rowid
2514
+ WHERE ${MESSAGE_FTS_SESSION_FILTER_SQL}message_history_fts.session_id = ?1
2515
+ AND message_history_fts MATCH ?
2516
+ AND map.message_time_ms BETWEEN ? AND ?
2517
+ ),
2518
+ eligible AS (
2519
+ SELECT * FROM matches
2520
+ WHERE ordinalValue <= ?
2521
+ ORDER BY ftsRank, ordinalValue ASC
2522
+ LIMIT ?
2523
+ ),
2524
+ summary AS (
2525
+ SELECT COUNT(*) AS suppressedCount
2526
+ FROM matches
2527
+ WHERE ordinalValue > ?
2528
+ )
2529
+ SELECT
2530
+ eligible.messageOrdinal,
2531
+ eligible.messageId,
2532
+ eligible.role,
2533
+ eligible.content,
2534
+ eligible.ftsRank,
2535
+ summary.suppressedCount,
2536
+ 0 AS summaryOnly
2537
+ FROM eligible CROSS JOIN summary
2538
+ UNION ALL
2539
+ SELECT NULL, NULL, NULL, NULL, NULL, summary.suppressedCount, 1
2540
+ FROM summary
2541
+ WHERE NOT EXISTS (SELECT 1 FROM eligible)
2542
+ ORDER BY summaryOnly ASC, ftsRank ASC, messageOrdinal ASC
2543
+ `);
2544
+ messageSearchDiagnosticStatementsWithDateRange.set(db, statement);
2545
+ }
2546
+ return statement;
2547
+ }
2548
+ function getBatchedFtsCountStatement(db, queryCount, cutoff, dateRange, sessionFirst) {
2549
+ let statements = batchedFtsCountStatements.get(db);
2550
+ if (!statements) {
2551
+ statements = new Map;
2552
+ batchedFtsCountStatements.set(db, statements);
2553
+ }
2554
+ const key = `${queryCount}:${cutoff === null ? "all" : "cutoff"}:${dateRange === null ? "all-dates" : "dated"}:${sessionFirst}`;
2555
+ let statement = statements.get(key);
2556
+ if (!statement) {
2557
+ const cutoffSql = cutoff === null ? "" : " AND CAST(message_history_fts.message_ordinal AS INTEGER) <= ?";
2558
+ const joinSql = dateRange === null ? "" : ` JOIN message_fts_rowid_map AS map
2559
+ ON map.session_id = message_history_fts.session_id
2560
+ AND map.fts_rowid = message_history_fts.rowid`;
2561
+ const dateSql = dateRange === null ? "" : " AND map.message_time_ms BETWEEN ? AND ?";
2562
+ statement = db.prepare(Array.from({ length: queryCount }, (_, index) => `SELECT ${index} AS queryIndex, COUNT(*) AS count
2563
+ FROM message_history_fts${joinSql}
2564
+ WHERE ${sessionFirst || dateRange !== null ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}message_history_fts.session_id = ${index === 0 ? "?1" : "?"}
2565
+ AND message_history_fts MATCH ?${dateSql}${cutoffSql}`).join(`
2566
+ UNION ALL
2567
+ `));
2568
+ statements.set(key, statement);
2569
+ }
2570
+ return statement;
2571
+ }
2572
+ function contentOnlyMessageQuery(ftsQuery) {
2573
+ return ftsQuery.length === 0 ? "" : `content : (${ftsQuery})`;
2574
+ }
2575
+ function countSessionFtsMatchesBatch(db, sessionId, ftsQueries, cutoff, dateRange, sessionFirst) {
2576
+ if (ftsQueries.length === 0)
2577
+ return [];
2578
+ const bindings = [];
2579
+ for (const query of ftsQueries) {
2580
+ bindings.push(sessionId, contentOnlyMessageQuery(query));
2581
+ if (dateRange !== null)
2582
+ bindings.push(dateRange.from, dateRange.to);
2583
+ if (cutoff !== null)
2584
+ bindings.push(cutoff);
2585
+ }
2586
+ try {
2587
+ const rows = getBatchedFtsCountStatement(db, ftsQueries.length, cutoff, dateRange, sessionFirst).all(...bindings);
2588
+ const counts = Array.from({ length: ftsQueries.length }, () => 0);
2589
+ for (const row of rows) {
2590
+ if (typeof row.queryIndex === "number" && row.queryIndex >= 0 && row.queryIndex < counts.length && typeof row.count === "number") {
2591
+ counts[row.queryIndex] = row.count;
2592
+ }
2593
+ }
2594
+ return counts;
2595
+ } catch {
2596
+ return Array.from({ length: ftsQueries.length }, () => 0);
2597
+ }
2598
+ }
2599
+ function getMessageOrdinal(value) {
2600
+ if (typeof value === "number" && Number.isFinite(value)) {
2601
+ return value;
2602
+ }
2603
+ if (typeof value === "string" && value.trim().length > 0) {
2604
+ const parsed = Number.parseInt(value, 10);
2605
+ return Number.isFinite(parsed) ? parsed : null;
2606
+ }
2607
+ return null;
2608
+ }
2609
+ async function getSemanticScores(args) {
2610
+ const semanticScores = new Map;
2611
+ if (!args.queryEmbedding || args.memories.length === 0 || !args.queryModelId || args.queryModelId === "off") {
2612
+ return semanticScores;
2613
+ }
2614
+ if (!args.workspace?.isWorkspaced) {
2615
+ const cachedEmbeddings = getProjectEmbeddings2(args.db, args.projectPath, args.queryModelId);
2616
+ const embeddings = args.backfillMemoryEmbeddings === false ? cachedEmbeddings : await ensureMemoryEmbeddings2({
2617
+ db: args.db,
2618
+ projectIdentity: args.projectPath,
2619
+ memories: args.memories,
2620
+ existingEmbeddings: cachedEmbeddings
2621
+ });
2622
+ for (const memory of args.memories) {
2623
+ const memoryEmbedding = embeddings.get(memory.id);
2624
+ if (!memoryEmbedding) {
2625
+ continue;
2626
+ }
2627
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
2628
+ }
2629
+ return semanticScores;
2630
+ }
2631
+ const workspace = args.workspace;
2632
+ const memoriesByIdentity = new Map;
2633
+ for (const memory of args.memories) {
2634
+ const identity = memoryWorkspaceIdentity(memory, workspace);
2635
+ if (!identity)
2636
+ continue;
2637
+ const list = memoriesByIdentity.get(identity) ?? [];
2638
+ list.push(memory);
2639
+ memoriesByIdentity.set(identity, list);
2640
+ }
2641
+ const ownMemories = memoriesByIdentity.get(args.projectPath) ?? [];
2642
+ if (ownMemories.length > 0 && args.backfillMemoryEmbeddings !== false) {
2643
+ const ownEmbeddings = getProjectEmbeddings2(args.db, args.projectPath, args.queryModelId);
2644
+ await ensureMemoryEmbeddings2({
2645
+ db: args.db,
2646
+ projectIdentity: args.projectPath,
2647
+ memories: ownMemories,
2648
+ existingEmbeddings: ownEmbeddings
2649
+ });
2650
+ }
2651
+ for (const identity of workspace.identities) {
2652
+ const memberMemories = memoriesByIdentity.get(identity) ?? [];
2653
+ if (memberMemories.length === 0)
2654
+ continue;
2655
+ const cachedEmbeddings = getProjectEmbeddings2(args.db, identity, args.queryModelId);
2656
+ for (const memory of memberMemories) {
2657
+ const memoryEmbedding = cachedEmbeddings.get(memory.id);
2658
+ if (!memoryEmbedding || memoryEmbedding.modelId !== args.queryModelId)
2659
+ continue;
2660
+ semanticScores.set(memory.id, normalizeCosineScore(cosineSimilarity(args.queryEmbedding, memoryEmbedding.embedding)));
2661
+ }
2662
+ }
2663
+ return semanticScores;
2664
+ }
2665
+ function getFtsMatches(args) {
2666
+ try {
2667
+ return args.workspace?.isWorkspaced ? searchMemoriesFTSUnion(args.db, args.workspace.expandedIdentities, args.query, args.limit, args.workspace.ownIdentities, args.workspace.shareCategories, args.dateRange) : searchMemoriesFTS(args.db, args.projectPath, args.query, args.limit, args.dateRange);
2668
+ } catch (error) {
2669
+ log2(`[search] FTS query failed for "${args.query}": ${error instanceof Error ? error.message : String(error)}`);
2670
+ return [];
2671
+ }
2672
+ }
2673
+ function getFtsScores(matches) {
2674
+ return new Map(matches.map((memory, rank) => [memory.id, 1 / (rank + 1)]));
2675
+ }
2676
+ function selectSemanticCandidates(args) {
2677
+ if (args.ftsMatches.length === 0) {
2678
+ return args.memories;
2679
+ }
2680
+ const candidateIds = new Set(args.ftsMatches.map((memory) => memory.id));
2681
+ if (args.queryModelId && args.queryModelId !== "off") {
2682
+ const embeddingProjects = args.workspace?.isWorkspaced ? args.workspace.identities : [args.projectPath];
2683
+ for (const projectPath of embeddingProjects) {
2684
+ const storedEmbeddings = getProjectEmbeddings2(args.db, projectPath, args.queryModelId);
2685
+ for (const memoryId of storedEmbeddings.keys()) {
2686
+ candidateIds.add(memoryId);
2687
+ }
2688
+ }
2689
+ }
2690
+ return args.memories.filter((memory) => candidateIds.has(memory.id));
2691
+ }
2692
+ function mergeMemoryResults(args) {
2693
+ const memoryById = new Map(args.memories.map((memory) => [memory.id, memory]));
2694
+ const candidateIds = new Set([...args.semanticScores.keys(), ...args.ftsScores.keys()]);
2695
+ const results = [];
2696
+ const suppressedVisibleIds = [];
2697
+ for (const id of candidateIds) {
2698
+ const memory = memoryById.get(id);
2699
+ if (!memory) {
2700
+ continue;
2701
+ }
2702
+ const semanticScore = args.semanticScores.get(id);
2703
+ const ftsScore = args.ftsScores.get(id);
2704
+ let score = 0;
2705
+ let matchType = "fts";
2706
+ if (semanticScore !== undefined && ftsScore !== undefined) {
2707
+ score = SEMANTIC_WEIGHT * semanticScore + FTS_WEIGHT * ftsScore;
2708
+ matchType = "hybrid";
2709
+ } else if (semanticScore !== undefined) {
2710
+ score = semanticScore * (args.singleSourcePenalty ?? SINGLE_SOURCE_PENALTY);
2711
+ matchType = "semantic";
2712
+ } else if (ftsScore !== undefined) {
2713
+ score = ftsScore * SINGLE_SOURCE_PENALTY;
2714
+ matchType = "fts";
2715
+ }
2716
+ if (score <= 0) {
2717
+ continue;
2718
+ }
2719
+ if (args.visibleMemoryIds?.has(id)) {
2720
+ suppressedVisibleIds.push(id);
2721
+ continue;
2722
+ }
2723
+ results.push({
2724
+ source: "memory",
2725
+ content: previewText(memory.content),
2726
+ score,
2727
+ memoryId: memory.id,
2728
+ category: memory.category,
2729
+ matchType,
2730
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
2731
+ });
2732
+ }
2733
+ return {
2734
+ results: results.sort((left, right) => {
2735
+ if (right.score !== left.score) {
2736
+ return right.score - left.score;
2737
+ }
2738
+ return left.memoryId - right.memoryId;
2739
+ }).slice(0, args.limit),
2740
+ suppressedVisibleIds: suppressedVisibleIds.sort((left, right) => left - right)
2741
+ };
2742
+ }
2743
+ async function searchMemories(args) {
2744
+ if (!args.memoryEnabled) {
2745
+ return { results: [], suppressedVisibleIds: [] };
2746
+ }
2747
+ const unfilteredMemories = args.workspace?.isWorkspaced ? getMemoriesByProjects2(args.db, args.workspace.expandedIdentities, ["active", "permanent"], Date.now(), args.workspace.ownIdentities, args.workspace.shareCategories) : getMemoriesByProject2(args.db, args.projectPath);
2748
+ const memories = unfilteredMemories.filter((memory) => timestampIsInRange(memory.createdAt, args.dateRange));
2749
+ if (memories.length === 0) {
2750
+ return { results: [], suppressedVisibleIds: [] };
2751
+ }
2752
+ const ftsMatches = getFtsMatches({
2753
+ db: args.db,
2754
+ projectPath: args.projectPath,
2755
+ query: args.query,
2756
+ limit: FTS_SEMANTIC_CANDIDATE_LIMIT,
2757
+ workspace: args.workspace,
2758
+ dateRange: args.dateRange
2759
+ });
2760
+ const ftsScores = getFtsScores(ftsMatches);
2761
+ const semanticCandidates = selectSemanticCandidates({
2762
+ db: args.db,
2763
+ memories,
2764
+ projectPath: args.projectPath,
2765
+ ftsMatches,
2766
+ queryModelId: args.queryModelId,
2767
+ workspace: args.workspace
2768
+ });
2769
+ const semanticScores = await getSemanticScores({
2770
+ db: args.db,
2771
+ projectPath: args.projectPath,
2772
+ memories: semanticCandidates,
2773
+ backfillMemoryEmbeddings: args.backfillMemoryEmbeddings,
2774
+ queryEmbedding: args.queryEmbedding,
2775
+ queryModelId: args.queryModelId,
2776
+ workspace: args.workspace
2777
+ });
2778
+ const sourceNameByMemoryId = sourceNamesForSearchMemories({
2779
+ memories,
2780
+ projectPath: args.projectPath,
2781
+ workspace: args.workspace ?? {
2782
+ identities: [args.projectPath],
2783
+ expandedIdentities: [args.projectPath],
2784
+ namesByIdentity: new Map,
2785
+ canonicalIdentityByStoredPath: new Map([[args.projectPath, args.projectPath]]),
2786
+ ownIdentities: [args.projectPath],
2787
+ shareCategories: null,
2788
+ isWorkspaced: false
2789
+ }
2790
+ });
2791
+ if (args.lanes) {
2792
+ args.lanes.memory_fts = mergeMemoryResults({
2793
+ memories,
2794
+ semanticScores: new Map,
2795
+ ftsScores,
2796
+ sourceNameByMemoryId,
2797
+ limit: 25,
2798
+ visibleMemoryIds: args.visibleMemoryIds
2799
+ }).results;
2800
+ args.lanes.memory_vector = mergeMemoryResults({
2801
+ memories,
2802
+ semanticScores,
2803
+ ftsScores: new Map,
2804
+ sourceNameByMemoryId,
2805
+ singleSourcePenalty: 1,
2806
+ limit: 25,
2807
+ visibleMemoryIds: args.visibleMemoryIds
2808
+ }).results;
2809
+ }
2810
+ return mergeMemoryResults({
2811
+ memories,
2812
+ semanticScores,
2813
+ ftsScores,
2814
+ limit: args.limit,
2815
+ visibleMemoryIds: args.visibleMemoryIds,
2816
+ sourceNameByMemoryId
2817
+ });
2818
+ }
2819
+ function linearDecayScore(rank, total) {
2820
+ if (total <= 0)
2821
+ return 0;
2822
+ return Math.max(0, 1 - rank / total);
2823
+ }
2824
+ function normalizeMessageSearchRow(row, cutoff) {
2825
+ const messageOrdinal = getMessageOrdinal(row.messageOrdinal);
2826
+ if (messageOrdinal === null || typeof row.messageId !== "string" || typeof row.role !== "string" || typeof row.content !== "string") {
2827
+ return null;
2828
+ }
2829
+ if (cutoff !== null && messageOrdinal > cutoff)
2830
+ return null;
2831
+ return {
2832
+ messageOrdinal,
2833
+ messageId: row.messageId,
2834
+ role: row.role,
2835
+ content: row.content
2836
+ };
2837
+ }
2838
+ function runMessageFtsQuery(db, sessionId, ftsQuery, fetchLimit, cutoff, dateRange, sessionFirst) {
2839
+ if (ftsQuery.length === 0)
2840
+ return [];
2841
+ let rawRows;
2842
+ const matchQuery = contentOnlyMessageQuery(ftsQuery);
2843
+ if (dateRange !== null) {
2844
+ const bindings = [sessionId, matchQuery, dateRange.from, dateRange.to];
2845
+ if (cutoff !== null)
2846
+ bindings.push(cutoff);
2847
+ bindings.push(fetchLimit);
2848
+ rawRows = getMessageSearchStatementWithDateRange(db, cutoff !== null).all(...bindings);
2849
+ } else {
2850
+ rawRows = cutoff !== null ? getMessageSearchStatementWithCutoff(db, sessionFirst).all(sessionId, matchQuery, cutoff, fetchLimit) : getMessageSearchStatement(db, sessionFirst).all(sessionId, matchQuery, fetchLimit);
2851
+ }
2852
+ const rows = rawRows.map((row) => row);
2853
+ const result = [];
2854
+ for (const row of rows) {
2855
+ const normalized = normalizeMessageSearchRow(row, cutoff);
2856
+ if (normalized)
2857
+ result.push(normalized);
2858
+ }
2859
+ return result;
2860
+ }
2861
+ function runMessageFtsQueryWithDiagnostics(args) {
2862
+ if (args.ftsQuery.length === 0)
2863
+ return { rows: [], suppressedCount: 0 };
2864
+ const matchQuery = contentOnlyMessageQuery(args.ftsQuery);
2865
+ const rawRows = (args.dateRange === null ? getMessageSearchDiagnosticStatement(args.db, args.sessionFirst).all(args.sessionId, matchQuery, args.cutoff, args.fetchLimit, args.cutoff) : getMessageSearchDiagnosticStatementWithDateRange(args.db).all(args.sessionId, matchQuery, args.dateRange.from, args.dateRange.to, args.cutoff, args.fetchLimit, args.cutoff)).map((row) => row);
2866
+ const suppressedCount = rawRows[0]?.suppressedCount ?? 0;
2867
+ const rows = [];
2868
+ for (const row of rawRows) {
2869
+ if (row.summaryOnly === 1)
2870
+ continue;
2871
+ const normalized = normalizeMessageSearchRow(row, args.cutoff);
2872
+ if (normalized)
2873
+ rows.push(normalized);
2874
+ }
2875
+ return { rows, suppressedCount };
2876
+ }
2877
+ function getBatchedMessageSearchStatement(db, queryCount, cutoff, dateRange, sessionFirst) {
2878
+ let statements = batchedMessageSearchStatements.get(db);
2879
+ if (!statements) {
2880
+ statements = new Map;
2881
+ batchedMessageSearchStatements.set(db, statements);
2882
+ }
2883
+ const key = `${queryCount}:${cutoff === null ? "all" : "cutoff"}:${dateRange === null ? "all-dates" : "dated"}:${sessionFirst}`;
2884
+ let statement = statements.get(key);
2885
+ if (!statement) {
2886
+ const cutoffSql = cutoff === null ? "" : " AND CAST(message_history_fts.message_ordinal AS INTEGER) <= ?";
2887
+ const joinSql = dateRange === null ? "" : ` JOIN message_fts_rowid_map AS map
2888
+ ON map.session_id = message_history_fts.session_id
2889
+ AND map.fts_rowid = message_history_fts.rowid`;
2890
+ const dateSql = dateRange === null ? "" : " AND map.message_time_ms BETWEEN ? AND ?";
2891
+ const branches = Array.from({ length: queryCount }, (_, index) => `SELECT * FROM (
2892
+ SELECT ${index} AS queryIndex,
2893
+ message_history_fts.message_ordinal AS messageOrdinal,
2894
+ message_history_fts.message_id AS messageId,
2895
+ message_history_fts.role AS role,
2896
+ message_history_fts.content AS content,
2897
+ bm25(message_history_fts) AS ftsRank
2898
+ FROM message_history_fts${joinSql}
2899
+ WHERE ${sessionFirst || dateRange !== null ? MESSAGE_FTS_SESSION_FILTER_SQL : ""}message_history_fts.session_id = ${index === 0 ? "?1" : "?"}
2900
+ AND message_history_fts MATCH ?${dateSql}${cutoffSql}
2901
+ ORDER BY ftsRank
2902
+ LIMIT ?
2903
+ )`);
2904
+ statement = db.prepare(`${branches.join(`
2905
+ UNION ALL
2906
+ `)}
2907
+ ORDER BY queryIndex ASC, ftsRank ASC`);
2908
+ statements.set(key, statement);
2909
+ }
2910
+ return statement;
2911
+ }
2912
+ function runMessageFtsQueriesBatch(db, sessionId, ftsQueries, fetchLimit, cutoff, dateRange, sessionFirst) {
2913
+ if (ftsQueries.length === 0)
2914
+ return [];
2915
+ const bindings = [];
2916
+ for (const query of ftsQueries) {
2917
+ bindings.push(sessionId, contentOnlyMessageQuery(query));
2918
+ if (dateRange !== null)
2919
+ bindings.push(dateRange.from, dateRange.to);
2920
+ if (cutoff !== null)
2921
+ bindings.push(cutoff);
2922
+ bindings.push(fetchLimit);
2923
+ }
2924
+ const rows = getBatchedMessageSearchStatement(db, ftsQueries.length, cutoff, dateRange, sessionFirst).all(...bindings);
2925
+ const result = Array.from({ length: ftsQueries.length }, () => []);
2926
+ for (const row of rows) {
2927
+ if (typeof row.queryIndex !== "number" || row.queryIndex < 0 || row.queryIndex >= result.length) {
2928
+ continue;
2929
+ }
2930
+ const normalized = normalizeMessageSearchRow(row, cutoff);
2931
+ if (normalized)
2932
+ result[row.queryIndex].push(normalized);
2933
+ }
2934
+ return result;
2935
+ }
2936
+ var RRF_K = 60;
2937
+ var VERBATIM_RANK_BONUS = 1 / RRF_K;
2938
+ var IDF_FALLOFF = 100;
2939
+ function probeDiscriminationWeight(df, corpusSize) {
2940
+ if (corpusSize <= 0 || df <= 0)
2941
+ return 1;
2942
+ return 1 / (1 + IDF_FALLOFF * df / corpusSize);
2943
+ }
2944
+ function searchMessages(args) {
2945
+ return withMessageFtsSessionFilter(args.db, args.sessionId, (sessionFirst) => searchMessagesInSnapshot(args, sessionFirst));
2946
+ }
2947
+ function searchMessagesInSnapshot(args, sessionFirst) {
2948
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
2949
+ const baselineFetchLimit = cutoff !== null ? args.limit * 3 : args.limit;
2950
+ const captureFetchLimit = args.capture ? cutoff !== null ? args.capture.limit * 3 : args.capture.limit : 0;
2951
+ const fetchLimit = Math.max(baselineFetchLimit, captureFetchLimit);
2952
+ const baseQuery = sanitizeFtsQuery(args.query.trim());
2953
+ const probes = args.probes ?? [];
2954
+ if (probes.length === 0) {
2955
+ const outcome = args.diagnostics && cutoff !== null ? runMessageFtsQueryWithDiagnostics({
2956
+ db: args.db,
2957
+ sessionId: args.sessionId,
2958
+ ftsQuery: baseQuery,
2959
+ fetchLimit,
2960
+ cutoff,
2961
+ dateRange: args.dateRange,
2962
+ sessionFirst
2963
+ }) : {
2964
+ rows: runMessageFtsQuery(args.db, args.sessionId, baseQuery, fetchLimit, cutoff, args.dateRange, sessionFirst),
2965
+ suppressedCount: 0
2966
+ };
2967
+ if (args.diagnostics) {
2968
+ args.diagnostics.suppressedLiveMessageMatches = outcome.suppressedCount;
2969
+ }
2970
+ const rows = outcome.rows.length > 0 || !args.relaxedRecall ? outcome.rows : runMessageFtsQuery(args.db, args.sessionId, relaxedFtsQuery(args.query), fetchLimit, cutoff, args.dateRange, sessionFirst);
2971
+ const renderRows = (limit) => {
2972
+ const filtered = rows.slice(0, limit);
2973
+ return filtered.map((row, rank) => ({
2974
+ source: "message",
2975
+ content: previewText(row.content),
2976
+ score: linearDecayScore(rank, filtered.length),
2977
+ messageOrdinal: row.messageOrdinal,
2978
+ messageId: row.messageId,
2979
+ role: row.role
2980
+ }));
2981
+ };
2982
+ if (args.capture)
2983
+ args.capture.results = renderRows(args.capture.limit);
2984
+ return renderRows(args.limit);
2985
+ }
2986
+ const sanitizedProbes = probes.map((probe) => ({ probe, query: sanitizeFtsQuery(probe) })).filter((entry) => entry.query.length > 0);
2987
+ const corpusSize = getIndexedMessageCorpusSize(args.db, args.sessionId, cutoff);
2988
+ const probeCounts = countSessionFtsMatchesBatch(args.db, args.sessionId, sanitizedProbes.map((entry) => entry.query), cutoff, args.dateRange, sessionFirst);
2989
+ const collectBaseDiagnostics = args.diagnostics !== undefined && cutoff !== null;
2990
+ const baseOutcome = collectBaseDiagnostics && baseQuery.length > 0 ? runMessageFtsQueryWithDiagnostics({
2991
+ db: args.db,
2992
+ sessionId: args.sessionId,
2993
+ ftsQuery: baseQuery,
2994
+ fetchLimit,
2995
+ cutoff,
2996
+ dateRange: args.dateRange,
2997
+ sessionFirst
2998
+ }) : null;
2999
+ if (args.diagnostics) {
3000
+ args.diagnostics.suppressedLiveMessageMatches = baseOutcome?.suppressedCount ?? 0;
3001
+ }
3002
+ const searchQueries = [
3003
+ ...!collectBaseDiagnostics && baseQuery.length > 0 ? [baseQuery] : [],
3004
+ ...sanitizedProbes.map((entry) => entry.query)
3005
+ ];
3006
+ const rowsByQuery = runMessageFtsQueriesBatch(args.db, args.sessionId, searchQueries, fetchLimit, cutoff, args.dateRange, sessionFirst);
3007
+ const queryLists = [];
3008
+ let queryIndex = 0;
3009
+ if (baseQuery.length > 0) {
3010
+ queryLists.push({
3011
+ rows: baseOutcome?.rows ?? rowsByQuery[queryIndex] ?? [],
3012
+ weight: 1
3013
+ });
3014
+ if (!collectBaseDiagnostics)
3015
+ queryIndex += 1;
3016
+ }
3017
+ const probeWeights = new Map;
3018
+ sanitizedProbes.forEach((entry, probeIndex) => {
3019
+ const weight = probeDiscriminationWeight(probeCounts[probeIndex] ?? 0, corpusSize);
3020
+ probeWeights.set(entry.probe, weight);
3021
+ queryLists.push({ rows: rowsByQuery[queryIndex] ?? [], weight });
3022
+ queryIndex += 1;
3023
+ });
3024
+ const rankProbeLists = (rowLimit, resultLimit) => {
3025
+ const fused = new Map;
3026
+ for (const list of queryLists) {
3027
+ list.rows.slice(0, rowLimit).forEach((row, rank) => {
3028
+ const rrf = list.weight / (RRF_K + rank);
3029
+ const existing = fused.get(row.messageId);
3030
+ if (existing) {
3031
+ existing.score += rrf;
3032
+ } else {
3033
+ fused.set(row.messageId, { row, score: rrf });
3034
+ }
3035
+ });
3036
+ }
3037
+ for (const entry of fused.values()) {
3038
+ let best = 0;
3039
+ for (const probe of probes) {
3040
+ const weight = probeWeights.get(probe) ?? 0;
3041
+ if (weight > best && containsProbeVerbatim(entry.row.content, [probe])) {
3042
+ best = weight;
3043
+ }
3044
+ }
3045
+ if (best > 0) {
3046
+ entry.score += best * VERBATIM_RANK_BONUS;
3047
+ }
3048
+ }
3049
+ const ranked = [...fused.values()].sort((a, b) => b.score !== a.score ? b.score - a.score : a.row.messageOrdinal - b.row.messageOrdinal).slice(0, resultLimit);
3050
+ return ranked.map((entry, rank) => ({
3051
+ source: "message",
3052
+ content: previewText(entry.row.content),
3053
+ score: linearDecayScore(rank, ranked.length),
3054
+ messageOrdinal: entry.row.messageOrdinal,
3055
+ messageId: entry.row.messageId,
3056
+ role: entry.row.role
3057
+ }));
3058
+ };
3059
+ if (args.capture)
3060
+ args.capture.results = rankProbeLists(captureFetchLimit, args.capture.limit);
3061
+ return rankProbeLists(baselineFetchLimit, args.limit);
3062
+ }
3063
+ var NOTE_SEARCHABLE_STATUSES = ["active", "pending", "ready"];
3064
+ var MAX_NOTE_KEYWORD_SCORE = 3.5;
3065
+ function noteSearchText(note) {
3066
+ const reason = note.readyReason?.trim();
3067
+ return reason ? `${note.content}
3068
+ Reason: ${reason}` : note.content;
3069
+ }
3070
+ function tokenizeKeywordNeedle(text) {
3071
+ const matches = text.toLowerCase().match(/[a-z0-9/._:-]+/g) ?? [];
3072
+ const seen = new Set;
3073
+ const tokens = [];
3074
+ for (const match of matches) {
3075
+ if (match.length <= 1 || !/[a-z0-9]/.test(match) || seen.has(match)) {
3076
+ continue;
3077
+ }
3078
+ seen.add(match);
3079
+ tokens.push(match);
3080
+ }
3081
+ return tokens;
3082
+ }
3083
+ function normalizeNoteKeywordScore(score) {
3084
+ return normalizeCosineScore(score / MAX_NOTE_KEYWORD_SCORE) * SINGLE_SOURCE_PENALTY;
3085
+ }
3086
+ function rankNotesForNeedle(notes, needle) {
3087
+ const normalizedNeedle = needle.trim().toLowerCase();
3088
+ if (normalizedNeedle.length === 0) {
3089
+ return [];
3090
+ }
3091
+ const needleTokens = tokenizeKeywordNeedle(normalizedNeedle);
3092
+ const ranked = [];
3093
+ for (const note of notes) {
3094
+ const text = noteSearchText(note);
3095
+ const normalizedText = text.toLowerCase();
3096
+ const noteTokens = new Set(tokenizeKeywordNeedle(normalizedText));
3097
+ const exact = normalizedText.includes(normalizedNeedle);
3098
+ const matchedTokens = needleTokens.filter((token) => noteTokens.has(token)).length;
3099
+ if (!exact && matchedTokens === 0) {
3100
+ continue;
3101
+ }
3102
+ const matchedUniqueTokens = new Set(needleTokens.filter((token) => noteTokens.has(token))).size;
3103
+ const coverage = needleTokens.length > 0 ? matchedTokens / needleTokens.length : 0;
3104
+ const density = noteTokens.size > 0 ? matchedUniqueTokens / noteTokens.size : 0;
3105
+ const exactPhrase = exact && (needleTokens.length > 1 || normalizedText.trim() === normalizedNeedle);
3106
+ const allTokens = needleTokens.length > 1 && matchedTokens === needleTokens.length;
3107
+ const score = (exactPhrase ? 2 : 0) + coverage * density + (allTokens ? 0.5 * density : 0);
3108
+ ranked.push({ note, score, text });
3109
+ }
3110
+ return ranked.sort((left, right) => {
3111
+ if (right.score !== left.score) {
3112
+ return right.score - left.score;
3113
+ }
3114
+ if (right.note.createdAt !== left.note.createdAt) {
3115
+ return right.note.createdAt - left.note.createdAt;
3116
+ }
3117
+ return left.note.id - right.note.id;
3118
+ });
3119
+ }
3120
+ function searchNotes(args) {
3121
+ if (args.limit <= 0) {
3122
+ return [];
3123
+ }
3124
+ const notes = [
3125
+ ...getNotes(args.db, {
3126
+ sessionId: args.sessionId,
3127
+ type: "session",
3128
+ status: NOTE_SEARCHABLE_STATUSES
3129
+ }),
3130
+ ...getNotes(args.db, {
3131
+ projectPath: args.projectPath,
3132
+ type: "smart",
3133
+ status: NOTE_SEARCHABLE_STATUSES
3134
+ })
3135
+ ].filter((note) => timestampIsInRange(note.createdAt, args.dateRange));
3136
+ if (notes.length === 0) {
3137
+ return [];
3138
+ }
3139
+ const baseList = rankNotesForNeedle(notes, args.query);
3140
+ const probes = args.probes ?? [];
3141
+ if (probes.length === 0) {
3142
+ const ranked = baseList.slice(0, args.limit);
3143
+ return ranked.map((entry) => ({
3144
+ source: "note",
3145
+ content: previewText(entry.text),
3146
+ score: normalizeNoteKeywordScore(entry.score),
3147
+ noteId: entry.note.id,
3148
+ status: entry.note.status,
3149
+ createdAt: entry.note.createdAt,
3150
+ anchorOrdinal: entry.note.anchorOrdinal,
3151
+ sourceSessionId: entry.note.sessionId
3152
+ }));
3153
+ }
3154
+ const queryLists = [];
3155
+ if (baseList.length > 0) {
3156
+ queryLists.push({ rows: baseList, weight: 1 });
3157
+ }
3158
+ for (const probe of probes) {
3159
+ const rows = rankNotesForNeedle(notes, probe);
3160
+ if (rows.length === 0) {
3161
+ continue;
3162
+ }
3163
+ const weight = probeDiscriminationWeight(rows.length, notes.length);
3164
+ queryLists.push({ rows, weight });
3165
+ }
3166
+ const fused = new Map;
3167
+ for (const list of queryLists) {
3168
+ for (const row of list.rows) {
3169
+ const relevance = row.score * list.weight;
3170
+ const existing = fused.get(row.note.id);
3171
+ if (existing) {
3172
+ if (relevance > existing.score) {
3173
+ existing.entry = row;
3174
+ existing.score = relevance;
3175
+ }
3176
+ } else {
3177
+ fused.set(row.note.id, { entry: row, score: relevance });
3178
+ }
3179
+ }
3180
+ }
3181
+ const ranked = [...fused.values()].sort((left, right) => {
3182
+ if (right.score !== left.score) {
3183
+ return right.score - left.score;
3184
+ }
3185
+ if (right.entry.note.createdAt !== left.entry.note.createdAt) {
3186
+ return right.entry.note.createdAt - left.entry.note.createdAt;
3187
+ }
3188
+ return left.entry.note.id - right.entry.note.id;
3189
+ }).slice(0, args.limit);
3190
+ return ranked.map((entry) => ({
3191
+ source: "note",
3192
+ content: previewText(entry.entry.text),
3193
+ score: normalizeNoteKeywordScore(entry.score),
3194
+ noteId: entry.entry.note.id,
3195
+ status: entry.entry.note.status,
3196
+ createdAt: entry.entry.note.createdAt,
3197
+ anchorOrdinal: entry.entry.note.anchorOrdinal,
3198
+ sourceSessionId: entry.entry.note.sessionId
3199
+ }));
3200
+ }
3201
+ function searchCompartmentChunks(args) {
3202
+ if (!args.queryEmbedding || args.limit <= 0 || !args.modelId || args.modelId === "off")
3203
+ return [];
3204
+ const cutoff = args.maxOrdinal != null && args.maxOrdinal >= 0 ? args.maxOrdinal : null;
3205
+ const rows = loadCompartmentChunkEmbeddingsForSearch(args.db, args.sessionId, args.projectPath, args.modelId, args.dateRange);
3206
+ if (rows.length === 0)
3207
+ return [];
3208
+ const byCompartment = new Map;
3209
+ for (const row of rows) {
3210
+ if (cutoff !== null && row.endOrdinal > cutoff) {
3211
+ continue;
3212
+ }
3213
+ const score = normalizeCosineScore(cosineSimilarity(args.queryEmbedding, row.vector));
3214
+ if (score <= 0)
3215
+ continue;
3216
+ const existing = byCompartment.get(row.compartmentId);
3217
+ if (!existing || score > existing.score) {
3218
+ byCompartment.set(row.compartmentId, { row, score });
3219
+ }
3220
+ }
3221
+ return [...byCompartment.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.row.startOrdinal - right.row.startOrdinal).slice(0, args.limit).map(({ row, score }) => ({
3222
+ source: "compartment",
3223
+ content: previewText(row.title),
3224
+ score: score * SINGLE_SOURCE_PENALTY,
3225
+ compartmentId: row.compartmentId,
3226
+ sessionId: row.sessionId,
3227
+ title: row.title,
3228
+ startOrdinal: row.startOrdinal,
3229
+ endOrdinal: row.endOrdinal,
3230
+ matchType: "semantic"
3231
+ }));
3232
+ }
3233
+ function mergeMessageAndCompartmentResults(args) {
3234
+ if (args.compartments.length === 0)
3235
+ return args.messages;
3236
+ if (args.messages.length === 0)
3237
+ return args.compartments;
3238
+ const fused = new Map;
3239
+ const add = (key, result, score, tieOrdinal) => {
3240
+ const existing = fused.get(key);
3241
+ if (existing) {
3242
+ existing.score += score;
3243
+ return existing;
3244
+ }
3245
+ const entry = { result, score, tieOrdinal, snippetScore: -1 };
3246
+ fused.set(key, entry);
3247
+ return entry;
3248
+ };
3249
+ args.compartments.forEach((compartment, rank) => {
3250
+ add(`compartment:${compartment.compartmentId}`, compartment, 1 / (RRF_K + rank), compartment.startOrdinal);
3251
+ });
3252
+ for (const [rank, message] of args.messages.entries()) {
3253
+ const containing = args.compartments.find((compartment) => message.messageOrdinal >= compartment.startOrdinal && message.messageOrdinal <= compartment.endOrdinal);
3254
+ const contribution = 1 / (RRF_K + rank);
3255
+ if (!containing) {
3256
+ add(`message:${message.messageId}`, message, contribution, message.messageOrdinal);
3257
+ continue;
3258
+ }
3259
+ const entry = add(`compartment:${containing.compartmentId}`, containing, contribution, containing.startOrdinal);
3260
+ if (message.score > entry.snippetScore && entry.result.source === "compartment") {
3261
+ entry.snippetScore = message.score;
3262
+ entry.result = {
3263
+ ...entry.result,
3264
+ matchType: "hybrid",
3265
+ snippet: message.content
3266
+ };
3267
+ }
3268
+ }
3269
+ const ranked = [...fused.values()].sort((left, right) => right.score !== left.score ? right.score - left.score : left.tieOrdinal - right.tieOrdinal).slice(0, args.limit);
3270
+ return ranked.map((entry, rank) => ({
3271
+ ...entry.result,
3272
+ score: linearDecayScore(rank, ranked.length)
3273
+ }));
3274
+ }
3275
+ function getSourceBoost(result) {
3276
+ switch (result.source) {
3277
+ case "memory":
3278
+ return MEMORY_SOURCE_BOOST;
3279
+ case "message":
3280
+ case "compartment":
3281
+ return MESSAGE_SOURCE_BOOST;
3282
+ case "git_commit":
3283
+ return GIT_COMMIT_SOURCE_BOOST;
3284
+ case "primer":
3285
+ return PRIMER_SOURCE_BOOST;
3286
+ case "note":
3287
+ return 1;
3288
+ }
3289
+ }
3290
+ function compareUnifiedResults(left, right) {
3291
+ const leftEffective = left.score * getSourceBoost(left);
3292
+ const rightEffective = right.score * getSourceBoost(right);
3293
+ if (rightEffective !== leftEffective) {
3294
+ return rightEffective - leftEffective;
3295
+ }
3296
+ if (left.source === "memory" && right.source === "memory") {
3297
+ return left.memoryId - right.memoryId;
3298
+ }
3299
+ if (left.source === "message" && right.source === "message") {
3300
+ return left.messageOrdinal - right.messageOrdinal;
3301
+ }
3302
+ if (left.source === "compartment" && right.source === "compartment") {
3303
+ return left.startOrdinal - right.startOrdinal;
3304
+ }
3305
+ if (left.source === "git_commit" && right.source === "git_commit") {
3306
+ return right.committedAtMs - left.committedAtMs;
3307
+ }
3308
+ if (left.source === "primer" && right.source === "primer") {
3309
+ return right.support - left.support || left.primerId - right.primerId;
3310
+ }
3311
+ if (left.source === "note" && right.source === "note") {
3312
+ return right.createdAt - left.createdAt || left.noteId - right.noteId;
3313
+ }
3314
+ return 0;
3315
+ }
3316
+ function toGitCommitResult(hit) {
3317
+ return {
3318
+ source: "git_commit",
3319
+ content: previewText(hit.commit.message),
3320
+ score: hit.score,
3321
+ sha: hit.commit.sha,
3322
+ shortSha: hit.commit.shortSha,
3323
+ author: hit.commit.author,
3324
+ committedAtMs: hit.commit.committedAtMs,
3325
+ matchType: hit.matchType
3326
+ };
3327
+ }
3328
+ function searchGitCommits(args) {
3329
+ if (args.limit <= 0)
3330
+ return [];
3331
+ const lanes = args.lanes;
3332
+ const hits = searchGitCommitsSync(args.db, args.projectPath, args.query, {
3333
+ limit: args.limit,
3334
+ queryEmbedding: args.queryEmbedding,
3335
+ queryModelId: args.queryModelId,
3336
+ from: args.dateRange?.from,
3337
+ to: args.dateRange?.to,
3338
+ captureLanes: lanes ? (fts, vector) => {
3339
+ lanes.git_fts = fts.map(toGitCommitResult);
3340
+ lanes.git_vector = vector.map(toGitCommitResult);
3341
+ } : undefined
3342
+ });
3343
+ return hits.map(toGitCommitResult);
3344
+ }
3345
+ function primerText(primer) {
3346
+ const answer = primer.answer.trim();
3347
+ return answer ? `Q: ${primer.question}
3348
+ A: ${answer}` : `Q: ${primer.question}`;
3349
+ }
3350
+ function searchPrimers(args) {
3351
+ const primers = getActivePrimers(args.db, args.projectPath).filter((primer) => timestampIsInRange(primer.createdAt, args.dateRange));
3352
+ if (primers.length === 0 || args.limit <= 0)
3353
+ return [];
3354
+ const ftsQuery = sanitizeFtsQuery(args.query);
3355
+ const ftsRanks = new Map;
3356
+ if (ftsQuery) {
3357
+ const rows = args.db.prepare(`SELECT p.id AS id, bm25(primers_fts) AS rank
3358
+ FROM primers_fts
3359
+ JOIN primers p ON p.id = primers_fts.rowid
3360
+ WHERE primers_fts MATCH ? AND p.project_path = ? AND p.status = 'active'
3361
+ ${args.dateRange === null ? "" : "AND p.created_at BETWEEN ? AND ?"}
3362
+ ORDER BY rank ASC
3363
+ LIMIT ?`).all(ftsQuery, args.projectPath, ...args.dateRange === null ? [] : [args.dateRange.from, args.dateRange.to], args.lanes ? Math.max(args.limit * 3, 225) : args.limit * 3);
3364
+ const baselineRows = rows.slice(0, args.limit * 3);
3365
+ baselineRows.forEach((row, index) => {
3366
+ ftsRanks.set(row.id, linearDecayScore(index, baselineRows.length));
3367
+ });
3368
+ if (args.lanes) {
3369
+ const byId = new Map(primers.map((primer) => [primer.id, primer]));
3370
+ args.lanes.primer_fts = rows.flatMap((row, index) => {
3371
+ const primer = byId.get(row.id);
3372
+ return primer ? [
3373
+ {
3374
+ source: "primer",
3375
+ primerId: primer.id,
3376
+ content: previewText(primerText(primer)),
3377
+ question: primer.question,
3378
+ support: primer.totalSupport,
3379
+ lastObservedAt: primer.lastObservedAt,
3380
+ matchType: "fts",
3381
+ score: linearDecayScore(index, rows.length)
3382
+ }
3383
+ ] : [];
3384
+ }).slice(0, 25);
3385
+ }
3386
+ }
3387
+ const vectorResults = [];
3388
+ const scored = primers.map((primer) => {
3389
+ const semantic = args.queryEmbedding && primer.questionEmbedding && primer.questionEmbeddingModelId === args.queryModelId ? normalizeCosineScore(cosineSimilarity(args.queryEmbedding, primer.questionEmbedding)) : 0;
3390
+ if (args.lanes && semantic > 0)
3391
+ vectorResults.push({
3392
+ source: "primer",
3393
+ primerId: primer.id,
3394
+ content: previewText(primerText(primer)),
3395
+ question: primer.question,
3396
+ support: primer.totalSupport,
3397
+ lastObservedAt: primer.lastObservedAt,
3398
+ matchType: "semantic",
3399
+ score: semantic
3400
+ });
3401
+ const fts = ftsRanks.get(primer.id) ?? 0;
3402
+ if (semantic <= 0 && fts <= 0)
3403
+ return null;
3404
+ const score = semantic > 0 && fts > 0 ? semantic * SEMANTIC_WEIGHT + fts * FTS_WEIGHT : Math.max(semantic, fts);
3405
+ return {
3406
+ source: "primer",
3407
+ content: previewText(primerText(primer)),
3408
+ score,
3409
+ primerId: primer.id,
3410
+ question: primer.question,
3411
+ support: primer.totalSupport,
3412
+ lastObservedAt: primer.lastObservedAt,
3413
+ matchType: semantic > 0 && fts > 0 ? "hybrid" : semantic > 0 ? "semantic" : "fts"
3414
+ };
3415
+ }).filter((result) => result !== null).sort((a, b) => b.score - a.score || b.support - a.support || a.primerId - b.primerId).slice(0, args.limit);
3416
+ if (args.lanes)
3417
+ args.lanes.primer_vector = vectorResults.sort((a, b) => b.score - a.score || b.support - a.support || a.primerId - b.primerId).slice(0, 25);
3418
+ return scored;
3419
+ }
3420
+ function resolveSources(sources) {
3421
+ if (sources === undefined) {
3422
+ return new Set(["memory", "message", "git_commit", "primer", "note"]);
3423
+ }
3424
+ const set = new Set;
3425
+ for (const source of sources) {
3426
+ if (source === "memory" || source === "message" || source === "git_commit" || source === "primer" || source === "note") {
3427
+ set.add(source);
3428
+ }
3429
+ }
3430
+ return set;
3431
+ }
3432
+ function memoriesToIdLookupResults(args) {
3433
+ const ordered = args.memories.slice(0, args.limit);
3434
+ return ordered.map((memory, rank) => ({
3435
+ source: "memory",
3436
+ content: previewText(memory.content),
3437
+ score: 1 - rank * 0.01,
3438
+ memoryId: memory.id,
3439
+ category: memory.category,
3440
+ matchType: "fts",
3441
+ sourceName: args.sourceNameByMemoryId?.get(memory.id)
3442
+ }));
3443
+ }
3444
+ function resolveMemoriesByIdsForSearch2(args) {
3445
+ if (args.diagnostics) {
3446
+ args.diagnostics.suppressedVisibleMemoryIds = [];
3447
+ }
3448
+ if (args.ids.length === 0) {
3449
+ return null;
3450
+ }
3451
+ const workspace = resolveSearchWorkspaceContext(args.db, args.projectPath);
3452
+ const fetched = workspace.isWorkspaced ? getMemoriesByProjects2(args.db, workspace.expandedIdentities, ["active", "permanent", "archived"], Date.now(), workspace.ownIdentities, workspace.shareCategories) : getMemoriesByProject2(args.db, args.projectPath, ["active", "permanent", "archived"]);
3453
+ if (fetched.length === 0) {
3454
+ return null;
3455
+ }
3456
+ const dateRange = normalizeDateRange(args.from, args.to);
3457
+ const memoriesById = new Map(fetched.filter((memory) => timestampIsInRange(memory.createdAt, dateRange)).map((memory) => [memory.id, memory]));
3458
+ const ordered = [];
3459
+ const suppressedVisibleIds = new Set;
3460
+ for (const id of args.ids) {
3461
+ const memory = memoriesById.get(id);
3462
+ if (!memory)
3463
+ continue;
3464
+ if (args.visibleMemoryIds?.has(id)) {
3465
+ suppressedVisibleIds.add(id);
3466
+ continue;
3467
+ }
3468
+ ordered.push(memory);
3469
+ if (ordered.length >= args.limit)
3470
+ break;
3471
+ }
3472
+ if (args.diagnostics) {
3473
+ args.diagnostics.suppressedVisibleMemoryIds = [...suppressedVisibleIds].sort((left, right) => left - right);
3474
+ }
3475
+ if (ordered.length === 0) {
3476
+ return null;
3477
+ }
3478
+ return memoriesToIdLookupResults({
3479
+ memories: ordered,
3480
+ limit: args.limit,
3481
+ sourceNameByMemoryId: sourceNamesForSearchMemories({
3482
+ memories: ordered,
3483
+ projectPath: args.projectPath,
3484
+ workspace
3485
+ })
3486
+ });
3487
+ }
3488
+ async function unifiedSearch2(db, sessionId, projectPath, query, options = {}) {
3489
+ const trimmedQuery = query.trim();
3490
+ const measurementStartedAt = Date.now();
3491
+ if (trimmedQuery.length === 0 || options.signal?.aborted) {
3492
+ return [];
3493
+ }
3494
+ const limit = normalizeLimit(options.limit);
3495
+ const reranker = options.explicitSearch && options.rerank !== false && isProseQuery(trimmedQuery) ? await prepareReranker(projectPath, sessionId, options.rerankConnectionFile, options.rerankClient) : null;
3496
+ const lanes = reranker || options.captureRerankLanes ? emptySearchLanes() : undefined;
3497
+ const dateRange = normalizeDateRange(options.from, options.to);
3498
+ const tierLimit = Math.max(limit * 3, DEFAULT_UNIFIED_SEARCH_LIMIT);
3499
+ if (options.diagnostics) {
3500
+ options.diagnostics.suppressedVisibleMemoryIds = [];
3501
+ options.diagnostics.suppressedLiveMessageMatches = 0;
3502
+ options.diagnostics.gitCommitUnavailable = null;
3503
+ }
3504
+ const embeddingEnabled = options.embeddingEnabled ?? true;
3505
+ const embedQuery = options.embedQuery ?? embedText;
3506
+ const isEmbeddingRuntimeEnabled = options.isEmbeddingRuntimeEnabled ?? isEmbeddingEnabled;
3507
+ const gitCommitsEnabled = options.gitCommitsEnabled ?? false;
3508
+ const activeSources = resolveSources(options.sources);
3509
+ const memoryFeatureEnabled = options.memoryEnabled ?? true;
3510
+ const runMemory = activeSources.has("memory") && memoryFeatureEnabled;
3511
+ const runMessages = activeSources.has("message");
3512
+ const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
3513
+ if (options.diagnostics && activeSources.has("git_commit") && options.gitRepositoryAvailable === false) {
3514
+ options.diagnostics.gitCommitUnavailable = "no_git_repository";
3515
+ }
3516
+ const runPrimers = activeSources.has("primer") && memoryFeatureEnabled;
3517
+ const runNotes = activeSources.has("note");
3518
+ const runCompartmentChunks = runMessages && embeddingEnabled;
3519
+ const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks || runPrimers) && embeddingEnabled && isEmbeddingRuntimeEnabled();
3520
+ const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error) => {
3521
+ log2(`[search] query embedding failed: ${error instanceof Error ? error.message : String(error)}`);
3522
+ return null;
3523
+ }) : Promise.resolve(null);
3524
+ await Promise.resolve();
3525
+ if (options.signal?.aborted)
3526
+ return [];
3527
+ const messageProbes = options.explicitSearch ? extractLiteralProbes(trimmedQuery) : [];
3528
+ let messageResults = [];
3529
+ if (runMessages) {
3530
+ const messageRequest = {
3531
+ sessionId,
3532
+ query: trimmedQuery,
3533
+ limit: tierLimit,
3534
+ captureLimit: lanes ? 75 : undefined,
3535
+ maxOrdinal: options.maxMessageOrdinal,
3536
+ probes: messageProbes,
3537
+ relaxedRecall: options.explicitSearch,
3538
+ collectDiagnostics: options.diagnostics !== undefined,
3539
+ dateRange
3540
+ };
3541
+ const delegated = options.searchMessageHistory ? await options.searchMessageHistory(messageRequest).catch((error) => {
3542
+ log2(`[search] off-thread message search failed: ${error instanceof Error ? error.message : String(error)}`);
3543
+ return null;
3544
+ }) : null;
3545
+ if (delegated && options.signal?.aborted)
3546
+ return [];
3547
+ const outcome = delegated ?? searchMessageHistory2(db, messageRequest);
3548
+ if (options.diagnostics) {
3549
+ options.diagnostics.suppressedLiveMessageMatches = outcome.suppressedLiveMessageMatches;
3550
+ }
3551
+ messageResults = outcome.results;
3552
+ if (lanes)
3553
+ lanes.message_fts = (outcome.laneResults ?? outcome.results).slice(0, 25);
3554
+ }
3555
+ const capturedQuery = await queryEmbeddingPromise;
3556
+ if (options.signal?.aborted)
3557
+ return [];
3558
+ const embeddingSnapshot = getProjectEmbeddingSnapshot2(projectPath);
3559
+ const queryContract = capturedQuery instanceof Float32Array || capturedQuery === null ? null : capturedQuery;
3560
+ const generationIsCurrent = queryContract === null || embeddingSnapshot !== null && embeddingSnapshot.generation === queryContract.generation;
3561
+ const queryEmbedding = generationIsCurrent ? queryContract?.vector ?? (capturedQuery instanceof Float32Array ? capturedQuery : null) : null;
3562
+ const workspace = resolveSearchWorkspaceContext(db, projectPath);
3563
+ const embeddingModelId = queryContract?.modelId ?? options.embeddingModelIdOverride ?? embeddingSnapshot?.modelId;
3564
+ const chunkModelId = queryContract?.chunkModelId ?? options.chunkModelIdOverride ?? embeddingSnapshot?.chunkModelId;
3565
+ const compartmentResults = runCompartmentChunks ? searchCompartmentChunks({
3566
+ db,
3567
+ sessionId,
3568
+ projectPath,
3569
+ queryEmbedding,
3570
+ limit: lanes ? Math.max(tierLimit, 25) : tierLimit,
3571
+ maxOrdinal: options.maxMessageOrdinal,
3572
+ modelId: chunkModelId && chunkModelId !== "off" ? chunkModelId : null,
3573
+ dateRange
3574
+ }) : [];
3575
+ if (lanes)
3576
+ lanes.chunk_vector = compartmentResults.slice(0, 25);
3577
+ const messageLikeResults = mergeMessageAndCompartmentResults({
3578
+ messages: messageResults,
3579
+ compartments: compartmentResults.slice(0, tierLimit),
3580
+ limit: tierLimit
3581
+ });
3582
+ const [memoryOutcome, gitCommitResults, primerResults, noteResults] = await Promise.all([
3583
+ runMemory ? searchMemories({
3584
+ db,
3585
+ projectPath,
3586
+ query: trimmedQuery,
3587
+ limit: tierLimit,
3588
+ memoryEnabled: true,
3589
+ queryEmbedding,
3590
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
3591
+ workspace,
3592
+ visibleMemoryIds: options.visibleMemoryIds,
3593
+ backfillMemoryEmbeddings: options.backfillMemoryEmbeddings,
3594
+ lanes,
3595
+ dateRange
3596
+ }) : Promise.resolve({
3597
+ results: [],
3598
+ suppressedVisibleIds: []
3599
+ }),
3600
+ runGitCommits ? Promise.resolve(searchGitCommits({
3601
+ db,
3602
+ projectPath,
3603
+ query: trimmedQuery,
3604
+ limit: tierLimit,
3605
+ queryEmbedding,
3606
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
3607
+ dateRange,
3608
+ lanes
3609
+ })) : Promise.resolve([]),
3610
+ runPrimers ? Promise.resolve(searchPrimers({
3611
+ db,
3612
+ projectPath,
3613
+ query: trimmedQuery,
3614
+ limit: tierLimit,
3615
+ queryEmbedding,
3616
+ queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null,
3617
+ dateRange,
3618
+ lanes
3619
+ })) : Promise.resolve([]),
3620
+ runNotes ? Promise.resolve(searchNotes({
3621
+ db,
3622
+ sessionId,
3623
+ projectPath,
3624
+ query: trimmedQuery,
3625
+ limit: lanes ? Math.max(tierLimit, 25) : tierLimit,
3626
+ probes: messageProbes,
3627
+ dateRange
3628
+ })) : Promise.resolve([])
3629
+ ]);
3630
+ if (options.diagnostics) {
3631
+ options.diagnostics.suppressedVisibleMemoryIds = memoryOutcome.suppressedVisibleIds;
3632
+ }
3633
+ let results = [
3634
+ ...memoryOutcome.results,
3635
+ ...primerResults,
3636
+ ...messageLikeResults,
3637
+ ...gitCommitResults,
3638
+ ...noteResults
3639
+ ].sort(compareUnifiedResults).slice(0, limit);
3640
+ const originalResults = results;
3641
+ const originalLatencyMs = Date.now() - measurementStartedAt;
3642
+ if (lanes) {
3643
+ lanes.notes = noteResults.slice(0, 25);
3644
+ options.captureRerankLanes?.(lanes);
3645
+ }
3646
+ if (reranker && lanes) {
3647
+ const pool = fuseSearchLanes(lanes);
3648
+ try {
3649
+ results = await rerankSearchResults(reranker, trimmedQuery, results, pool, candidateTexts(db, sessionId, projectPath, pool), limit);
3650
+ } catch {
3651
+ logRerankFallback("candidate_validation");
3652
+ }
3653
+ }
3654
+ if (!options.measurementDisabled) {
3655
+ recordShadowMeasurement({
3656
+ db,
3657
+ sessionId,
3658
+ projectPath,
3659
+ query: trimmedQuery,
3660
+ options: { ...options, rerank: false, captureRerankLanes: undefined },
3661
+ primaryResults: originalResults,
3662
+ primaryQuery: queryContract,
3663
+ primaryLatencyMs: originalLatencyMs,
3664
+ search: unifiedSearch2
3665
+ });
3666
+ }
3667
+ const countRetrievals = options.countRetrievals ?? true;
3668
+ if (countRetrievals) {
3669
+ const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
3670
+ if (memoryIds.length > 0) {
3671
+ db.transaction(() => {
3672
+ for (const memoryId of memoryIds) {
3673
+ try {
3674
+ updateMemoryRetrievalCount2(db, memoryId);
3675
+ } catch (error) {
3676
+ if (error instanceof ModuleMemoryAuthorityError2)
3677
+ continue;
3678
+ throw error;
3679
+ }
3680
+ }
3681
+ }).immediate();
3682
+ }
3683
+ }
3684
+ return results;
3685
+ }
3686
+ var SEARCH_NOTE_EXPAND_HINT = "Use ctx_expand(start=N-10, end=N) around any note @msg anchor above to read the surrounding conversation context.";
3687
+ function formatSearchAge(timestampMs) {
3688
+ const ageMs = Date.now() - timestampMs;
3689
+ if (ageMs < 0)
3690
+ return "future";
3691
+ const days = Math.floor(ageMs / (24 * 60 * 60 * 1000));
3692
+ if (days <= 0)
3693
+ return "today";
3694
+ if (days === 1)
3695
+ return "1d ago";
3696
+ if (days < 30)
3697
+ return `${days}d ago`;
3698
+ const months = Math.floor(days / 30);
3699
+ if (months === 1)
3700
+ return "1mo ago";
3701
+ if (months < 12)
3702
+ return `${months}mo ago`;
3703
+ const years = Math.floor(days / 365);
3704
+ return years === 1 ? "1y ago" : `${years}y ago`;
3705
+ }
3706
+ function formatUnifiedSearchResult(result, index, currentSessionId) {
3707
+ if (result.source === "memory") {
3708
+ const source = result.sourceName ? ` source=${result.sourceName}` : "";
3709
+ return [
3710
+ `[${index}] [memory] score=${result.score.toFixed(2)} id=${result.memoryId} category=${result.category}${source} match=${result.matchType}`,
3711
+ result.content
3712
+ ].join(`
3713
+ `);
3714
+ }
3715
+ if (result.source === "git_commit") {
3716
+ return [
3717
+ `[${index}] [git_commit] score=${result.score.toFixed(2)} sha=${result.shortSha} ${formatSearchAge(result.committedAtMs)} match=${result.matchType}`,
3718
+ result.content
3719
+ ].join(`
3720
+ `);
3721
+ }
3722
+ if (result.source === "primer") {
3723
+ return [
3724
+ `[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
3725
+ result.content
3726
+ ].join(`
3727
+ `);
3728
+ }
3729
+ if (result.source === "note") {
3730
+ const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
3731
+ return [
3732
+ `[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatSearchAge(result.createdAt)}${anchor}`,
3733
+ result.content
3734
+ ].join(`
3735
+ `);
3736
+ }
3737
+ if (result.source === "compartment") {
3738
+ return [
3739
+ `[${index}] [message] score=${result.score.toFixed(2)} compartment_id=${result.compartmentId} range=${result.startOrdinal}-${result.endOrdinal} match=${result.matchType} title=${result.title}`,
3740
+ result.snippet ? `Snippet: ${result.snippet}` : result.content
3741
+ ].join(`
3742
+ `);
3743
+ }
3744
+ const expandStart = Math.max(1, result.messageOrdinal - 3);
3745
+ const expandEnd = result.messageOrdinal + 3;
3746
+ return [
3747
+ `[${index}] [message] score=${result.score.toFixed(2)} ordinal=${result.messageOrdinal} range=${expandStart}-${expandEnd} role=${result.role}`,
3748
+ result.content
3749
+ ].join(`
3750
+ `);
3751
+ }
3752
+ function formatSearchDiagnosticLines(results, diagnostics) {
3753
+ if (!diagnostics)
3754
+ return [];
3755
+ const lines = [];
3756
+ const visibleIds = [...diagnostics.suppressedVisibleMemoryIds].sort((left, right) => left - right);
3757
+ if (visibleIds.length > 0) {
3758
+ const count = visibleIds.length;
3759
+ const noun = count === 1 ? "match" : "matches";
3760
+ const ids = visibleIds.join(", ");
3761
+ if (results.some((result) => result.source === "memory")) {
3762
+ lines.push(`Memories: ${count} additional ${noun} suppressed because ${count === 1 ? "it is" : "they are"} already visible in your project-memory block (ids ${ids}).`);
3763
+ } else {
3764
+ lines.push(`Memories: ${count} ${noun} found, all already visible in your project-memory block (ids ${ids}).`);
3765
+ }
3766
+ }
3767
+ if (diagnostics.suppressedLiveMessageMatches > 0) {
3768
+ const count = diagnostics.suppressedLiveMessageMatches;
3769
+ lines.push(`Message history: ${count} raw-message ${count === 1 ? "match is" : "matches are"} newer than the last compartment boundary (already in your context).`);
3770
+ }
3771
+ if (diagnostics.gitCommitUnavailable === "no_git_repository") {
3772
+ lines.push("Git commits: no git repository — commit search unavailable for this project.");
3773
+ }
3774
+ return lines;
3775
+ }
3776
+ function formatSearchResults2(query, results, currentSessionId, diagnostics) {
3777
+ const diagnosticLines = formatSearchDiagnosticLines(results, diagnostics);
3778
+ if (results.length === 0) {
3779
+ if (diagnosticLines.length > 0) {
3780
+ return `No hidden results found for "${query}".
3781
+
3782
+ ${diagnosticLines.join(`
3783
+ `)}`;
3784
+ }
3785
+ return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
3786
+ }
3787
+ const bodyParts = results.map((result, index) => formatUnifiedSearchResult(result, index + 1, currentSessionId));
3788
+ if (diagnosticLines.length > 0)
3789
+ bodyParts.push(diagnosticLines.join(`
3790
+ `));
3791
+ if (results.some((result) => result.source === "message" || result.source === "compartment")) {
3792
+ bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
3793
+ }
3794
+ if (results.some((result) => result.source === "note" && result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId)) {
3795
+ bodyParts.push(SEARCH_NOTE_EXPAND_HINT);
3796
+ }
3797
+ return `Found ${results.length} result${results.length === 1 ? "" : "s"} for "${query}":
3798
+
3799
+ ${bodyParts.join(`
3800
+
3801
+ `)}`;
3802
+ }
3803
+
3804
+ export { getNoteByIdInScope, getNotes, addNote, getSessionNotes, getPendingSmartNotes, getReadySmartNotes, updateNote, dismissNotes, dismissNote, markNoteReady, markNoteChecked, PRIMER_CANDIDATE_TTL_MS, PRIMER_CANDIDATE_MAX_AGE_MS, primerOccurrenceKey, primerOccurrenceUtcDay, insertPrimerCandidates, updatePrimerCandidateEmbedding, getPrimerCandidatesByIds, getPrimerCandidatesForPromotion, countPrimerCandidatesForProject, getActivePrimers, createPrimer, updatePrimerSupport, updatePrimerAnswer, USER_FACING_FAILURES, renderUserFacingFailure, userFacingFailureCode, renderCapabilityRefusal, renderDreamFailure, dreamFailureCode, renderEmbeddingFailure, renderSingleStoreMigrationRequiredRefusal, SingleStoreMigrationRequiredError, projectNeedsSingleStoreMigration, promoteSessionFactsDurable, embedPromotedFacts, recordMemoryMapping, recordMemoryVerifications, getUnmappedMemoryIds, clearMemoryVerifications, getMemoryVerifications, resolveGitTopLevel, readGitHead, readGitChangedFilesSince, readGitFileChangeTimesSince, verificationFileExists, normalizeVerificationFiles, indexCommitsForProject, embedUnembeddedCommits, createUnifiedSearchDiagnostics2, searchMessageHistory2, parseIdShapedQuery2, resolveMemoriesByIdsForSearch2, unifiedSearch2, formatSearchResults2 };