codesift-mcp 0.17.0 → 0.19.2

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 (149) hide show
  1. package/dist/cli/commands-daemon.d.ts.map +1 -1
  2. package/dist/cli/commands-daemon.js +32 -0
  3. package/dist/cli/commands-daemon.js.map +1 -1
  4. package/dist/cli/commands-maintenance.d.ts.map +1 -1
  5. package/dist/cli/commands-maintenance.js +173 -3
  6. package/dist/cli/commands-maintenance.js.map +1 -1
  7. package/dist/cli/hooks/pre-compact.d.ts.map +1 -1
  8. package/dist/cli/hooks/pre-compact.js +4 -0
  9. package/dist/cli/hooks/pre-compact.js.map +1 -1
  10. package/dist/cli/hooks/session.d.ts.map +1 -1
  11. package/dist/cli/hooks/session.js +5 -0
  12. package/dist/cli/hooks/session.js.map +1 -1
  13. package/dist/cli/hooks/wiki.d.ts +13 -0
  14. package/dist/cli/hooks/wiki.d.ts.map +1 -1
  15. package/dist/cli/hooks/wiki.js +19 -2
  16. package/dist/cli/hooks/wiki.js.map +1 -1
  17. package/dist/cli/owner-only-file.d.ts +9 -0
  18. package/dist/cli/owner-only-file.d.ts.map +1 -1
  19. package/dist/cli/owner-only-file.js +40 -8
  20. package/dist/cli/owner-only-file.js.map +1 -1
  21. package/dist/cli/service.d.ts.map +1 -1
  22. package/dist/cli/service.js +7 -1
  23. package/dist/cli/service.js.map +1 -1
  24. package/dist/cli/setup/claude.js +3 -3
  25. package/dist/cli/setup/claude.js.map +1 -1
  26. package/dist/cli/setup/codex.js +4 -4
  27. package/dist/cli/setup/codex.js.map +1 -1
  28. package/dist/cli/setup/fs.d.ts +2 -0
  29. package/dist/cli/setup/fs.d.ts.map +1 -1
  30. package/dist/cli/setup/fs.js +4 -1
  31. package/dist/cli/setup/fs.js.map +1 -1
  32. package/dist/cli/setup/gemini.js +4 -4
  33. package/dist/cli/setup/gemini.js.map +1 -1
  34. package/dist/cli/setup/hooks.d.ts +10 -0
  35. package/dist/cli/setup/hooks.d.ts.map +1 -1
  36. package/dist/cli/setup/hooks.js +25 -3
  37. package/dist/cli/setup/hooks.js.map +1 -1
  38. package/dist/instructions.d.ts +22 -1
  39. package/dist/instructions.d.ts.map +1 -1
  40. package/dist/instructions.js +61 -1
  41. package/dist/instructions.js.map +1 -1
  42. package/dist/lsp/lsp-client.d.ts +1 -1
  43. package/dist/lsp/lsp-client.d.ts.map +1 -1
  44. package/dist/lsp/lsp-client.js +2 -1
  45. package/dist/lsp/lsp-client.js.map +1 -1
  46. package/dist/lsp/lsp-manager.d.ts.map +1 -1
  47. package/dist/lsp/lsp-manager.js +10 -2
  48. package/dist/lsp/lsp-manager.js.map +1 -1
  49. package/dist/lsp/lsp-servers.d.ts +7 -0
  50. package/dist/lsp/lsp-servers.d.ts.map +1 -1
  51. package/dist/lsp/lsp-servers.js +78 -0
  52. package/dist/lsp/lsp-servers.js.map +1 -1
  53. package/dist/parser/parser-manager.d.ts.map +1 -1
  54. package/dist/parser/parser-manager.js +8 -2
  55. package/dist/parser/parser-manager.js.map +1 -1
  56. package/dist/register-tool-groups/core/symbols.d.ts.map +1 -1
  57. package/dist/register-tool-groups/core/symbols.js +71 -6
  58. package/dist/register-tool-groups/core/symbols.js.map +1 -1
  59. package/dist/register-tools/discovery.d.ts +7 -0
  60. package/dist/register-tools/discovery.d.ts.map +1 -1
  61. package/dist/register-tools/discovery.js +8 -1
  62. package/dist/register-tools/discovery.js.map +1 -1
  63. package/dist/search/bm25-store.d.ts +10 -0
  64. package/dist/search/bm25-store.d.ts.map +1 -1
  65. package/dist/search/bm25-store.js +140 -10
  66. package/dist/search/bm25-store.js.map +1 -1
  67. package/dist/search/bm25.d.ts +22 -0
  68. package/dist/search/bm25.d.ts.map +1 -1
  69. package/dist/search/bm25.js +43 -0
  70. package/dist/search/bm25.js.map +1 -1
  71. package/dist/server-helpers/cache-report.d.ts +36 -0
  72. package/dist/server-helpers/cache-report.d.ts.map +1 -0
  73. package/dist/server-helpers/cache-report.js +26 -0
  74. package/dist/server-helpers/cache-report.js.map +1 -0
  75. package/dist/server-helpers/health-vitals.d.ts.map +1 -1
  76. package/dist/server-helpers/health-vitals.js +19 -11
  77. package/dist/server-helpers/health-vitals.js.map +1 -1
  78. package/dist/server-helpers/response-budget.d.ts +24 -0
  79. package/dist/server-helpers/response-budget.d.ts.map +1 -0
  80. package/dist/server-helpers/response-budget.js +29 -0
  81. package/dist/server-helpers/response-budget.js.map +1 -0
  82. package/dist/server-helpers/response-hints.d.ts.map +1 -1
  83. package/dist/server-helpers/response-hints.js +43 -0
  84. package/dist/server-helpers/response-hints.js.map +1 -1
  85. package/dist/server-helpers/shown-source.d.ts +59 -0
  86. package/dist/server-helpers/shown-source.d.ts.map +1 -0
  87. package/dist/server-helpers/shown-source.js +175 -0
  88. package/dist/server-helpers/shown-source.js.map +1 -0
  89. package/dist/server-helpers/stdio-envelope.d.ts +31 -0
  90. package/dist/server-helpers/stdio-envelope.d.ts.map +1 -0
  91. package/dist/server-helpers/stdio-envelope.js +67 -0
  92. package/dist/server-helpers/stdio-envelope.js.map +1 -0
  93. package/dist/server-helpers.d.ts +7 -0
  94. package/dist/server-helpers.d.ts.map +1 -1
  95. package/dist/server-helpers.js +51 -10
  96. package/dist/server-helpers.js.map +1 -1
  97. package/dist/server.d.ts.map +1 -1
  98. package/dist/server.js +107 -45
  99. package/dist/server.js.map +1 -1
  100. package/dist/storage/_shared.d.ts +25 -0
  101. package/dist/storage/_shared.d.ts.map +1 -1
  102. package/dist/storage/_shared.js +39 -6
  103. package/dist/storage/_shared.js.map +1 -1
  104. package/dist/storage/session-state.d.ts +7 -0
  105. package/dist/storage/session-state.d.ts.map +1 -1
  106. package/dist/storage/session-state.js +3 -0
  107. package/dist/storage/session-state.js.map +1 -1
  108. package/dist/storage/shared-embedding-cache.d.ts.map +1 -1
  109. package/dist/storage/shared-embedding-cache.js +153 -2
  110. package/dist/storage/shared-embedding-cache.js.map +1 -1
  111. package/dist/storage/usage-tracker.d.ts +11 -0
  112. package/dist/storage/usage-tracker.d.ts.map +1 -1
  113. package/dist/storage/usage-tracker.js +18 -0
  114. package/dist/storage/usage-tracker.js.map +1 -1
  115. package/dist/tools/conversation-cache.d.ts +23 -3
  116. package/dist/tools/conversation-cache.d.ts.map +1 -1
  117. package/dist/tools/conversation-cache.js +141 -4
  118. package/dist/tools/conversation-cache.js.map +1 -1
  119. package/dist/tools/conversation-index-tools.d.ts +12 -0
  120. package/dist/tools/conversation-index-tools.d.ts.map +1 -1
  121. package/dist/tools/conversation-index-tools.js +315 -4
  122. package/dist/tools/conversation-index-tools.js.map +1 -1
  123. package/dist/tools/conversation-search-tools.d.ts +0 -5
  124. package/dist/tools/conversation-search-tools.d.ts.map +1 -1
  125. package/dist/tools/conversation-search-tools.js +77 -6
  126. package/dist/tools/conversation-search-tools.js.map +1 -1
  127. package/dist/tools/explore-tools.d.ts +9 -0
  128. package/dist/tools/explore-tools.d.ts.map +1 -0
  129. package/dist/tools/explore-tools.js +177 -0
  130. package/dist/tools/explore-tools.js.map +1 -0
  131. package/dist/tools/graph-tools.d.ts +7 -0
  132. package/dist/tools/graph-tools.d.ts.map +1 -1
  133. package/dist/tools/graph-tools.js +36 -0
  134. package/dist/tools/graph-tools.js.map +1 -1
  135. package/dist/tools/index-tools/state.d.ts +15 -1
  136. package/dist/tools/index-tools/state.d.ts.map +1 -1
  137. package/dist/tools/index-tools/state.js +25 -39
  138. package/dist/tools/index-tools/state.js.map +1 -1
  139. package/dist/tools/insights-tools.d.ts.map +1 -1
  140. package/dist/tools/insights-tools.js +6 -3
  141. package/dist/tools/insights-tools.js.map +1 -1
  142. package/dist/utils/import-graph/edge-cache.d.ts.map +1 -1
  143. package/dist/utils/import-graph/edge-cache.js +9 -1
  144. package/dist/utils/import-graph/edge-cache.js.map +1 -1
  145. package/package.json +3 -3
  146. package/rules/codesift.md +19 -0
  147. package/rules/codesift.mdc +19 -0
  148. package/rules/codex.md +19 -0
  149. package/rules/gemini.md +19 -0
@@ -1,6 +1,26 @@
1
- import type { BM25Index } from "../search/bm25.js";
2
- export declare function getConversationBM25Index(repoName: string): BM25Index | null;
3
- export declare function setConversationBM25Index(repoName: string, index: BM25Index): void;
1
+ import { type BM25Index } from "../search/bm25.js";
2
+ /**
3
+ * Newest index mtime this cache entry is known to cover, or 0 when unknown.
4
+ *
5
+ * Passed in by the caller because the cache does not know where a conversation repo's index lives;
6
+ * an entry built without one is never considered stale, which is the pre-existing behaviour and the
7
+ * conservative direction for a caller that cannot tell.
8
+ */
9
+ export declare function getConversationBM25Index(repoName: string, freshAsOfMs?: number): BM25Index | null;
10
+ /**
11
+ * Whether an entry exists at all, without touching recency or freshness.
12
+ *
13
+ * Lets the caller pay for a freshness read ONLY when there is something to invalidate. Asking
14
+ * unconditionally put a `loadIndexSummary` in front of every repo on the pass where all of them miss.
15
+ */
16
+ export declare function hasConversationBM25Index(repoName: string): boolean;
17
+ export declare function setConversationBM25Index(repoName: string, index: BM25Index, builtAtMs?: number): void;
18
+ /** Entry count and priced bytes, for the cache report on /health. */
19
+ export declare function conversationCacheStats(): {
20
+ bm25: number;
21
+ bm25_bytes: number;
22
+ embeddings: number;
23
+ };
4
24
  export declare function loadConversationEmbeddingsCached(embeddingPath: string): Promise<Map<string, Float32Array>>;
5
25
  export declare function clearConversationEmbeddingsCacheForTesting(): void;
6
26
  //# sourceMappingURL=conversation-cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-cache.d.ts","sourceRoot":"","sources":["../../src/tools/conversation-cache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAQnD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAE3E;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAEjF;AAED,wBAAsB,gCAAgC,CACpD,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAcpC;AAED,wBAAgB,0CAA0C,IAAI,IAAI,CAEjE"}
1
+ {"version":3,"file":"conversation-cache.d.ts","sourceRoot":"","sources":["../../src/tools/conversation-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAA4C,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAqD7F;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,SAAI,GAAG,SAAS,GAAG,IAAI,CAa5F;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,SAAa,GAAG,IAAI,CAKzG;AA2CD,qEAAqE;AACrE,wBAAgB,sBAAsB,IAAI;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAIA;AAED,wBAAsB,gCAAgC,CACpD,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAwBpC;AAED,wBAAgB,0CAA0C,IAAI,IAAI,CAIjE"}
@@ -1,11 +1,136 @@
1
1
  import { stat } from "node:fs/promises";
2
+ import { bm25CacheBudgetBytes, bm25FootprintBytes } from "../search/bm25.js";
3
+ /**
4
+ * BM25 indexes for conversation repos.
5
+ *
6
+ * This is the SECOND cache of BM25 indexes in the process, and it had none of what the first one
7
+ * got: no LRU, no budget, no entry cap, and — unlike its sibling — not even the accidental eviction
8
+ * that used to keep that one small. Nothing has ever removed an entry from it.
9
+ *
10
+ * Its size is not bounded by how many conversation indexes exist on disk either, because
11
+ * `loadConversationIndex` BUILDS the index from symbols rather than reading a persisted one: only 3
12
+ * of 1,258 conversation repos here have a `.bm25.ndjson` at all. What bounds it is how many repos get
13
+ * searched, and `searchAllConversations` searches every one of them in a single `Promise.all`.
14
+ *
15
+ * Measured 2026-09-27, one `search_all_conversations` call on this install:
16
+ *
17
+ * projects_searched 1,258 · 53.7 s · heapUsed 5 MB -> 3,262 MB
18
+ * RETAINED after an explicit GC: 2,784 MB
19
+ * a second identical call: 0.6 s — proving all of it is still referenced
20
+ *
21
+ * The fan-out comment in `searchAllConversations` says "with ~20+ conversation repos". That was true
22
+ * when it was written. Pricing and budget come from `search/bm25.js` so this cache and the code one
23
+ * cannot drift apart on what an index costs.
24
+ */
2
25
  const bm25Indexes = new Map();
26
+ /**
27
+ * Index freshness, so a long-lived process stops answering from a snapshot.
28
+ *
29
+ * The lookup was `if (!bm25) build`, with no revalidation anywhere — so the first search in a process
30
+ * fixed the answer for the life of that process. In the daemon, which had been up 27 hours when this
31
+ * was found, conversation search could not see a single conversation recorded since its first call.
32
+ * That is a correctness defect the memory bound would otherwise have hidden, since eviction also
33
+ * happens to refresh.
34
+ */
35
+ const indexedAt = new Map();
3
36
  const embeddingsCache = new Map();
4
- export function getConversationBM25Index(repoName) {
5
- return bm25Indexes.get(repoName) ?? null;
37
+ /**
38
+ * How many conversation embedding maps stay resident.
39
+ *
40
+ * Each load is individually capped by `embeddingMemBudgetBytes()`, which bounds one entry and says
41
+ * nothing about their number — the same "count is not size" mistake ADR-004 fixed for the index
42
+ * cache, inverted. Conversation embeddings are small here (0.03 GB on disk across 1,258 repos), so a
43
+ * modest entry cap is enough; it exists so the failure mode is bounded rather than proportional to
44
+ * how many projects the machine has ever had.
45
+ */
46
+ const MAX_EMBEDDING_ENTRIES = 64;
47
+ /**
48
+ * Newest index mtime this cache entry is known to cover, or 0 when unknown.
49
+ *
50
+ * Passed in by the caller because the cache does not know where a conversation repo's index lives;
51
+ * an entry built without one is never considered stale, which is the pre-existing behaviour and the
52
+ * conservative direction for a caller that cannot tell.
53
+ */
54
+ export function getConversationBM25Index(repoName, freshAsOfMs = 0) {
55
+ const index = bm25Indexes.get(repoName);
56
+ if (!index)
57
+ return null;
58
+ const builtAt = indexedAt.get(repoName) ?? 0;
59
+ if (freshAsOfMs > 0 && builtAt > 0 && freshAsOfMs > builtAt) {
60
+ bm25Indexes.delete(repoName);
61
+ indexedAt.delete(repoName);
62
+ return null;
63
+ }
64
+ // Re-insert so iteration order is least-recently-used first.
65
+ bm25Indexes.delete(repoName);
66
+ bm25Indexes.set(repoName, index);
67
+ return index;
68
+ }
69
+ /**
70
+ * Whether an entry exists at all, without touching recency or freshness.
71
+ *
72
+ * Lets the caller pay for a freshness read ONLY when there is something to invalidate. Asking
73
+ * unconditionally put a `loadIndexSummary` in front of every repo on the pass where all of them miss.
74
+ */
75
+ export function hasConversationBM25Index(repoName) {
76
+ return bm25Indexes.has(repoName);
6
77
  }
7
- export function setConversationBM25Index(repoName, index) {
78
+ export function setConversationBM25Index(repoName, index, builtAtMs = Date.now()) {
79
+ bm25Indexes.delete(repoName);
8
80
  bm25Indexes.set(repoName, index);
81
+ indexedAt.set(repoName, builtAtMs);
82
+ evictOverBudget(repoName);
83
+ }
84
+ /**
85
+ * Drop least-recently-used entries until the cache fits its budget.
86
+ *
87
+ * The repo being served is never evicted, even alone over budget: otherwise a single large
88
+ * conversation corpus would rebuild and discard its own index on every call.
89
+ */
90
+ /**
91
+ * Share of the BM25 budget this cache may hold.
92
+ *
93
+ * A quarter, not the whole tier. Conversation search is a secondary retrieval path, and giving it the
94
+ * same budget as the code BM25 cache would put four full-tier budgets in one process (indexes, code
95
+ * BM25, embeddings, conversations) — 4 GB of deliberate residency on a machine this size, which is
96
+ * how a process with correct individual bounds still walks into its heap ceiling.
97
+ *
98
+ * It costs little: a conversation index here averages ~2.2 MB (2,784 MB across 1,258 repos), so a
99
+ * quarter-tier still holds around a hundred of them — the recently-active projects — and a miss costs
100
+ * a rebuild, never an error. `CODESIFT_MAX_CONVERSATION_BM25_CACHE_MB` overrides directly.
101
+ */
102
+ function conversationBudgetBytes() {
103
+ const raw = process.env["CODESIFT_MAX_CONVERSATION_BM25_CACHE_MB"];
104
+ if (raw !== undefined) {
105
+ const parsed = Number.parseInt(raw, 10);
106
+ if (Number.isFinite(parsed) && parsed > 0)
107
+ return parsed * 1024 * 1024;
108
+ }
109
+ return Math.floor(bm25CacheBudgetBytes() / 4);
110
+ }
111
+ function evictOverBudget(pinned) {
112
+ const budget = conversationBudgetBytes();
113
+ let total = 0;
114
+ for (const index of bm25Indexes.values())
115
+ total += bm25FootprintBytes(index);
116
+ if (total <= budget)
117
+ return;
118
+ for (const [name, index] of bm25Indexes) {
119
+ if (total <= budget)
120
+ break;
121
+ if (name === pinned)
122
+ continue;
123
+ total -= bm25FootprintBytes(index);
124
+ bm25Indexes.delete(name);
125
+ indexedAt.delete(name);
126
+ }
127
+ }
128
+ /** Entry count and priced bytes, for the cache report on /health. */
129
+ export function conversationCacheStats() {
130
+ let bytes = 0;
131
+ for (const index of bm25Indexes.values())
132
+ bytes += bm25FootprintBytes(index);
133
+ return { bm25: bm25Indexes.size, bm25_bytes: bytes, embeddings: embeddingsCache.size };
9
134
  }
10
135
  export async function loadConversationEmbeddingsCached(embeddingPath) {
11
136
  let mtimeMs = -1;
@@ -16,15 +141,27 @@ export async function loadConversationEmbeddingsCached(embeddingPath) {
16
141
  // Missing files load as an empty embedding map.
17
142
  }
18
143
  const cached = embeddingsCache.get(embeddingPath);
19
- if (cached?.mtimeMs === mtimeMs)
144
+ if (cached?.mtimeMs === mtimeMs) {
145
+ embeddingsCache.delete(embeddingPath);
146
+ embeddingsCache.set(embeddingPath, cached);
20
147
  return cached.embeddings;
148
+ }
21
149
  const { loadEmbeddings } = await import("../storage/embedding-store.js");
22
150
  const { embeddingMemBudgetBytes } = await import("../config.js");
23
151
  const embeddings = await loadEmbeddings(embeddingPath, embeddingMemBudgetBytes());
152
+ embeddingsCache.delete(embeddingPath);
24
153
  embeddingsCache.set(embeddingPath, { mtimeMs, embeddings });
154
+ while (embeddingsCache.size > MAX_EMBEDDING_ENTRIES) {
155
+ const oldest = embeddingsCache.keys().next().value;
156
+ if (oldest === undefined)
157
+ break;
158
+ embeddingsCache.delete(oldest);
159
+ }
25
160
  return embeddings;
26
161
  }
27
162
  export function clearConversationEmbeddingsCacheForTesting() {
28
163
  embeddingsCache.clear();
164
+ bm25Indexes.clear();
165
+ indexedAt.clear();
29
166
  }
30
167
  //# sourceMappingURL=conversation-cache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-cache.js","sourceRoot":"","sources":["../../src/tools/conversation-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGxC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAqB,CAAC;AACjD,MAAM,eAAe,GAAG,IAAI,GAAG,EAG3B,CAAC;AAEL,MAAM,UAAU,wBAAwB,CAAC,QAAgB;IACvD,OAAO,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,QAAgB,EAAE,KAAgB;IACzE,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,aAAqB;IAErB,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC;IACjB,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;IACD,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAClD,IAAI,MAAM,EAAE,OAAO,KAAK,OAAO;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC;IAC1D,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;IACzE,MAAM,EAAE,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,uBAAuB,EAAE,CAAC,CAAC;IAClF,eAAe,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5D,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,0CAA0C;IACxD,eAAe,CAAC,KAAK,EAAE,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"conversation-cache.js","sourceRoot":"","sources":["../../src/tools/conversation-cache.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,18 @@ export interface IndexConversationsResult {
9
9
  compacted_sessions: number;
10
10
  /** Wall-clock time for the entire operation (ms). */
11
11
  elapsed_ms: number;
12
+ /**
13
+ * True when nothing was re-read because every `.jsonl` matched its recorded mtime.
14
+ *
15
+ * Additive and optional so a caller that does not know about it still reads the counts. It exists
16
+ * because a skip cannot report `compacted_sessions` — that is not stored — and a silent 0 there
17
+ * would be indistinguishable from "scanned, found none".
18
+ */
19
+ /** True when only the changed sessions were re-extracted rather than the whole directory. */
20
+ incremental?: boolean;
21
+ /** How many sessions were re-extracted or dropped on an incremental pass. */
22
+ changed_sessions?: number;
23
+ unchanged?: boolean;
12
24
  }
13
25
  /**
14
26
  * Index all JSONL conversation session files found in `projectPath`.
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-index-tools.d.ts","sourceRoot":"","sources":["../../src/tools/conversation-index-tools.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,wBAAwB;IACvC,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,6DAA6D;IAC7D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5B,OAAO,CAAC,wBAAwB,CAAC,CAoBnC;AA+FD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9E;AAED;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB1E"}
1
+ {"version":3,"file":"conversation-index-tools.d.ts","sourceRoot":"","sources":["../../src/tools/conversation-index-tools.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,wBAAwB;IACvC,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,6DAA6D;IAC7D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,6FAA6F;IAC7F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAGD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5B,OAAO,CAAC,wBAAwB,CAAC,CA4EnC;AAgWD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9E;AAED;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB1E"}
@@ -1,9 +1,10 @@
1
1
  import { readdir, stat, readFile } from "node:fs/promises";
2
2
  import { join, relative, basename } from "node:path";
3
3
  import { extractConversationSymbols } from "../parser/symbol-extractor.js";
4
- import { saveIndex, getIndexPath } from "../storage/index-store.js";
4
+ import { saveIndex, getIndexPath, loadIndex, loadIndexSummary, saveIncremental, removeFileFromIndex, } from "../storage/index-store.js";
5
5
  import { registerRepo } from "../storage/registry.js";
6
- import { buildBM25IndexYielding } from "../search/bm25.js";
6
+ import { buildBM25IndexYielding, updateBM25ForFile } from "../search/bm25.js";
7
+ import { loadBM25Index, saveBM25Index } from "../search/bm25-store.js";
7
8
  import { loadConfig } from "../config.js";
8
9
  import { embedSymbols } from "./index-tools.js";
9
10
  import { setConversationBM25Index } from "./conversation-cache.js";
@@ -24,9 +25,63 @@ export async function indexConversations(projectPath, options) {
24
25
  // Derive repo name: "conversations/<folder>"
25
26
  const repoName = `conversations/${basename(rootPath)}`;
26
27
  const indexPath = getIndexPath(config.dataDir, rootPath);
28
+ // Nothing detected change, so every call did everything: read every .jsonl, re-extract every turn,
29
+ // rebuild the BM25 index. `autoDiscoverConversations` runs this on server start, and the largest
30
+ // conversation directory here is 160,626 turns / 72 MB of source whose BM25 build alone measures
31
+ // 35.8 s — paid on every spawn, for a directory that usually gained one session or nothing.
32
+ //
33
+ // It is also what made persisting the index pointless: `persistConversationIndex` stamps
34
+ // `updated_at: Date.now()`, so an unconditional rescan invalidated its own cache every time.
35
+ // `embed` is the ONE thing a skip cannot stand in for. Embedding is opt-in and only an explicit
36
+ // `index_conversations` asks for it, so skipping on "the files did not change" silently drops the
37
+ // work the caller invoked the tool to get — the directory being unchanged says nothing about
38
+ // whether its vectors exist. Found by the cross-model review of this release; it is a regression
39
+ // the fast paths introduced, not pre-existing behaviour.
40
+ const wantsEmbed = options?.embed ?? true;
41
+ const unchanged = wantsEmbed ? null : await conversationsUnchanged(rootPath, indexPath);
42
+ if (unchanged) {
43
+ return {
44
+ sessions_found: unchanged.sessions,
45
+ turns_indexed: unchanged.turns,
46
+ skipped_noise_records: 0,
47
+ compacted_sessions: 0,
48
+ elapsed_ms: Date.now() - startTime,
49
+ unchanged: true,
50
+ };
51
+ }
52
+ // Re-extract only the sessions that moved.
53
+ //
54
+ // The skip above covers a directory nothing touched, which is every project except the one being
55
+ // worked in. The ACTIVE project's directory changes on every turn, so for it the skip can never
56
+ // fire — and a full pass over the largest one here measures 156 s (211 sessions, 164,500 turns).
57
+ // `autoDiscoverConversations` runs on every server spawn, so that was paid per session, on one
58
+ // thread, in the process whose event loop is the bottleneck.
59
+ //
60
+ // A conversation log is append-only and sessions are independent files, which is the case
61
+ // `updateBM25ForFile` already exists for: swap the changed file's symbols in place rather than
62
+ // rebuilding the vocabulary for all 164,500 turns.
63
+ // Same reason the skip is gated above: `incrementalConversationUpdate` already refuses when embedding
64
+ // was asked for (its first guard), and this makes that refusal visible at the call site rather than
65
+ // only inside the callee.
66
+ const incremental = wantsEmbed
67
+ ? null
68
+ : await incrementalConversationUpdate(rootPath, repoName, indexPath, options);
69
+ if (incremental) {
70
+ return {
71
+ sessions_found: incremental.sessions,
72
+ turns_indexed: incremental.turns,
73
+ skipped_noise_records: 0,
74
+ // Recomputed from the merged symbols rather than reported as 0. It is one pass over symbols this
75
+ // function already holds, and a silent 0 is indistinguishable from "scanned, found none".
76
+ compacted_sessions: incremental.compacted,
77
+ elapsed_ms: Date.now() - startTime,
78
+ incremental: true,
79
+ changed_sessions: incremental.changed,
80
+ };
81
+ }
27
82
  const scan = await scanConversationFiles(rootPath, repoName);
28
83
  // Explicit call embeds by default; auto-discovery passes embed:false (see below).
29
- await persistConversationIndex(rootPath, repoName, indexPath, scan, { embed: options?.embed ?? true });
84
+ await persistConversationIndex(rootPath, repoName, indexPath, scan, { embed: wantsEmbed });
30
85
  return {
31
86
  sessions_found: scan.sessions,
32
87
  turns_indexed: scan.turns,
@@ -35,6 +90,249 @@ export async function indexConversations(projectPath, options) {
35
90
  elapsed_ms: Date.now() - startTime,
36
91
  };
37
92
  }
93
+ /**
94
+ * Counts from the stored index when the directory is byte-for-byte the same, otherwise null.
95
+ *
96
+ * Compares the SET of `.jsonl` files and each one's mtime against what the index recorded. A
97
+ * conversation log is append-only, so an appended turn moves the mtime; a new session adds a path;
98
+ * a deleted one removes it. Any of those, or a summary that cannot be read, means rescan — the
99
+ * conservative direction, since the cost of rescanning is time and the cost of skipping wrongly is
100
+ * a search that cannot see a conversation.
101
+ *
102
+ * `compacted_sessions` is not stored and cannot be recovered here, so a skip reports 0 for it rather
103
+ * than a number it would have to invent; `unchanged: true` in the result says which kind of answer
104
+ * this is.
105
+ */
106
+ async function conversationsUnchanged(rootPath, indexPath) {
107
+ let summary;
108
+ try {
109
+ summary = await loadIndexSummary(indexPath);
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ if (!summary || summary.files.length === 0)
115
+ return null;
116
+ let entries;
117
+ try {
118
+ entries = (await readdir(rootPath)).filter((name) => name.endsWith(".jsonl"));
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ if (entries.length !== summary.files.length)
124
+ return null;
125
+ const recorded = new Map();
126
+ for (const file of summary.files) {
127
+ const mtime = file.mtime_ms ?? 0;
128
+ // An index written before mtimes were recorded cannot be compared — rescan rather than guess.
129
+ if (mtime === 0)
130
+ return null;
131
+ recorded.set(file.path, mtime);
132
+ }
133
+ for (const name of entries) {
134
+ const expected = recorded.get(name);
135
+ if (expected === undefined)
136
+ return null;
137
+ try {
138
+ if ((await stat(join(rootPath, name))).mtimeMs !== expected)
139
+ return null;
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ }
145
+ // Indexed SYMBOLS, which is turns plus one `conversation_summary` per compacted session. The split
146
+ // is not stored per file, and loading every symbol to recover it would cost more than this whole
147
+ // skip saves — so the number is named honestly on `unchanged` rather than silently differing from a
148
+ // scanned pass by the compacted count.
149
+ let symbols = 0;
150
+ for (const file of summary.files)
151
+ symbols += file.symbol_count;
152
+ return { sessions: summary.files.length, turns: symbols };
153
+ }
154
+ /**
155
+ * Swap the changed sessions into the stored index, or null when that cannot be done safely.
156
+ *
157
+ * Returns null — meaning "fall back to a full pass" — whenever anything is not certainly known: no
158
+ * stored index, an index written before mtimes were recorded, no persisted BM25 index to amend, or
159
+ * too much of the directory changed for amending to be cheaper than rebuilding. Falling back is
160
+ * always correct; amending on a wrong assumption produces an index that searches cleanly and is
161
+ * missing turns.
162
+ *
163
+ * Embeddings deliberately stay on the full path. They are opt-in, they are the expensive part when
164
+ * enabled, and an incremental embed needs its own per-symbol accounting rather than a file-level one.
165
+ */
166
+ /**
167
+ * Largest share of a conversation directory that is still worth amending file by file.
168
+ *
169
+ * Measured 2026-09-27 on a 15,838-symbol / 788-session index, amending against one clean
170
+ * `buildBM25Index` (4,211 ms):
171
+ *
172
+ * 5% 39 files 183 ms amend wins x22.96
173
+ * 25% 197 files 200 ms amend wins x21.00
174
+ * 50% 394 files 1,639 ms amend wins x2.57
175
+ * 75% 591 files 14,037 ms REBUILD wins x0.30
176
+ * 100% 788 files 22,888 ms REBUILD wins x0.18
177
+ *
178
+ * The crossover is between 50% and 75%, and the amend cost grows SUPERLINEARLY — each amended file is
179
+ * a remove-then-add over the postings maps every other file shares, so the churn compounds. That is
180
+ * why the bound is conservative rather than set at the crossover: past it the penalty is not a few
181
+ * percent, it is 5x.
182
+ *
183
+ * `centrality` is NOT a reason for this bound, though an earlier version of this comment said it was:
184
+ * a conversation index has **zero** centrality entries (measured on two of them), because there is no
185
+ * import graph in a chat log. `compacted_sessions` is not a reason either any more — it is recomputed
186
+ * from the merged symbols.
187
+ */
188
+ const CONVERSATION_AMEND_MAX_SHARE = 0.5;
189
+ async function incrementalConversationUpdate(rootPath, repoName, indexPath, options) {
190
+ if (options?.embed)
191
+ return null;
192
+ let stored;
193
+ try {
194
+ stored = await loadIndex(indexPath);
195
+ }
196
+ catch {
197
+ return null;
198
+ }
199
+ if (!stored || stored.files.length === 0 || stored.symbols.length === 0)
200
+ return null;
201
+ let entries;
202
+ try {
203
+ entries = (await readdir(rootPath)).filter((name) => name.endsWith(".jsonl"));
204
+ }
205
+ catch {
206
+ return null;
207
+ }
208
+ const recorded = new Map();
209
+ for (const file of stored.files) {
210
+ if ((file.mtime_ms ?? 0) === 0)
211
+ return null;
212
+ recorded.set(file.path, file);
213
+ }
214
+ const present = new Set(entries);
215
+ const removed = [...recorded.keys()].filter((path) => !present.has(path));
216
+ const changed = [];
217
+ for (const name of entries) {
218
+ let mtimeMs;
219
+ try {
220
+ mtimeMs = (await stat(join(rootPath, name))).mtimeMs;
221
+ }
222
+ catch {
223
+ return null;
224
+ }
225
+ if (recorded.get(name)?.mtime_ms !== mtimeMs)
226
+ changed.push({ path: name, mtimeMs });
227
+ }
228
+ if (changed.length === 0 && removed.length === 0)
229
+ return null; // the caller's skip already handled this
230
+ // Past this share of the directory, amending stops being cheaper than one clean pass: every amended
231
+ // file costs a remove-then-add over the shared postings maps, which churns the vocabulary, while a
232
+ // full build ingests each symbol once. The bound is measured, not guessed — see
233
+ // CONVERSATION_AMEND_MAX_SHARE.
234
+ if (changed.length + removed.length > Math.max(1, Math.floor(entries.length * CONVERSATION_AMEND_MAX_SHARE))) {
235
+ return null;
236
+ }
237
+ const bm25 = await loadBM25Index(indexPath, stored);
238
+ if (!bm25)
239
+ return null;
240
+ const symbolsByFile = new Map();
241
+ for (const symbol of stored.symbols) {
242
+ const list = symbolsByFile.get(symbol.file);
243
+ if (list)
244
+ list.push(symbol);
245
+ else
246
+ symbolsByFile.set(symbol.file, [symbol]);
247
+ }
248
+ const files = new Map(recorded);
249
+ for (const path of removed) {
250
+ updateBM25ForFile(bm25, path, []);
251
+ symbolsByFile.delete(path);
252
+ files.delete(path);
253
+ }
254
+ for (const { path, mtimeMs } of changed) {
255
+ let source;
256
+ try {
257
+ source = await readFile(join(rootPath, path), "utf-8");
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ const symbols = extractConversationSymbols(source, path, repoName);
263
+ updateBM25ForFile(bm25, path, symbols);
264
+ symbolsByFile.set(path, symbols);
265
+ files.set(path, {
266
+ path,
267
+ language: "conversation",
268
+ symbol_count: symbols.length,
269
+ last_modified: mtimeMs,
270
+ mtime_ms: mtimeMs,
271
+ });
272
+ }
273
+ const mergedSymbols = [];
274
+ let compacted = 0;
275
+ let turns = 0;
276
+ for (const list of symbolsByFile.values()) {
277
+ mergedSymbols.push(...list);
278
+ // `turns_indexed` counts TURNS, and a compacted session also carries one `conversation_summary`
279
+ // symbol. Reporting `mergedSymbols.length` over-counted by exactly the number of compacted
280
+ // sessions — 15,894 against the full path's 15,887 on this machine, with compacted = 7.
281
+ for (const symbol of list) {
282
+ if (symbol.kind === "conversation_turn")
283
+ turns++;
284
+ else if (symbol.kind === "conversation_summary")
285
+ compacted++;
286
+ }
287
+ }
288
+ const mergedFiles = [...files.values()];
289
+ // Write only the rows that moved. `saveIndex` rewrites every symbol of the repo: measured on a
290
+ // 15,838-symbol conversation index, 1,494 ms against **0-1 ms** for `saveIncremental` on one file —
291
+ // and the whole point of this path is that two sessions changed. Deletions go through
292
+ // `removeFileFromIndex`, which also drops the `files[]` row; `saveIncremental` would leave it.
293
+ for (const path of removed)
294
+ await removeFileFromIndex(indexPath, path);
295
+ for (const { path } of changed) {
296
+ await saveIncremental(indexPath, path, symbolsByFile.get(path) ?? [], files.get(path));
297
+ }
298
+ // Both writers stamp `updated_at` themselves, so the value has to be read back rather than assumed:
299
+ // the BM25 header must carry what the database now says, or every later search rejects the file and
300
+ // rebuilds — the exact state this path exists to leave behind.
301
+ let updatedAt = Date.now();
302
+ try {
303
+ updatedAt = (await loadIndexSummary(indexPath))?.updated_at ?? updatedAt;
304
+ }
305
+ catch {
306
+ return null; // cannot prove what the index says; a full pass will rewrite both consistently
307
+ }
308
+ const codeIndex = {
309
+ repo: repoName,
310
+ root: rootPath,
311
+ symbols: mergedSymbols,
312
+ files: mergedFiles,
313
+ created_at: stored.created_at,
314
+ updated_at: updatedAt,
315
+ symbol_count: mergedSymbols.length,
316
+ file_count: mergedFiles.length,
317
+ };
318
+ await saveBM25Index(indexPath, bm25, codeIndex);
319
+ setConversationBM25Index(repoName, bm25, codeIndex.updated_at);
320
+ const meta = {
321
+ name: repoName,
322
+ root: rootPath,
323
+ index_path: indexPath,
324
+ symbol_count: codeIndex.symbol_count,
325
+ file_count: codeIndex.file_count,
326
+ updated_at: codeIndex.updated_at,
327
+ };
328
+ await registerRepo(loadConfig().registryPath, meta);
329
+ return {
330
+ sessions: mergedFiles.length,
331
+ turns,
332
+ changed: changed.length + removed.length,
333
+ compacted,
334
+ };
335
+ }
38
336
  async function scanConversationFiles(rootPath, repoName) {
39
337
  let entries;
40
338
  try {
@@ -48,7 +346,9 @@ async function scanConversationFiles(rootPath, repoName) {
48
346
  const filePath = join(rootPath, fileName);
49
347
  // Read and extract
50
348
  let source;
349
+ let mtimeMs = 0;
51
350
  try {
351
+ mtimeMs = (await stat(filePath)).mtimeMs;
52
352
  source = await readFile(filePath, "utf-8");
53
353
  }
54
354
  catch {
@@ -67,7 +367,12 @@ async function scanConversationFiles(rootPath, repoName) {
67
367
  path: relPath,
68
368
  language: "conversation",
69
369
  symbol_count: symbols.length,
70
- last_modified: Date.now(),
370
+ // The FILE's mtime, not the scan's clock. This was `Date.now()`, which records when the
371
+ // scanner ran and therefore cannot be compared against anything on disk — so a stored
372
+ // conversation index could never be shown to be current, and every scan was a full rescan.
373
+ // `mtime_ms` is the field the type already documents as "for incremental skip".
374
+ last_modified: mtimeMs,
375
+ mtime_ms: mtimeMs,
71
376
  };
72
377
  scan.files.push(entry);
73
378
  }
@@ -88,6 +393,12 @@ async function persistConversationIndex(rootPath, repoName, indexPath, scan, opt
88
393
  file_count: scan.files.length,
89
394
  };
90
395
  await saveIndex(indexPath, codeIndex);
396
+ // Persist the BM25 index beside the code index, with the SAME `codeIndex` object that was just
397
+ // written — `saveBM25Index` stamps its header from it, and `loadBM25Index` refuses a header that
398
+ // disagrees. Writing it here rather than from the search path is what lets a search LOAD the index
399
+ // (4.3x to 20x faster than rebuilding, measured on this machine's three largest conversation
400
+ // directories) instead of tokenising the whole corpus again.
401
+ await saveBM25Index(indexPath, bm25, codeIndex);
91
402
  // Embedding conversations is OPT-IN. This used to fire unconditionally and
92
403
  // unawaited on every server start (autoDiscoverConversations → here), so each
93
404
  // of N concurrent MCP servers began embedding the whole ~/.claude/projects
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-index-tools.js","sourceRoot":"","sources":["../../src/tools/conversation-index-tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"conversation-index-tools.js","sourceRoot":"","sources":["../../src/tools/conversation-index-tools.ts"],"names":[],"mappings":"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"}
@@ -25,11 +25,6 @@ export declare function searchConversations(query: string, projectPath?: string,
25
25
  * query once instead of once per conversation repo. */
26
26
  queryVec?: Float32Array;
27
27
  }): Promise<SearchConversationsResult>;
28
- /**
29
- * Search ALL indexed conversation projects at once.
30
- * Iterates over all `conversations/*` repos in the registry,
31
- * searches each, merges and re-ranks results.
32
- */
33
28
  export declare function searchAllConversations(query: string, limit?: number): Promise<SearchConversationsResult & {
34
29
  projects_searched: number;
35
30
  }>;