cccmemory 1.8.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 (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -0
  3. package/dist/ConversationMemory.d.ts +231 -0
  4. package/dist/ConversationMemory.d.ts.map +1 -0
  5. package/dist/ConversationMemory.js +357 -0
  6. package/dist/ConversationMemory.js.map +1 -0
  7. package/dist/cache/QueryCache.d.ts +215 -0
  8. package/dist/cache/QueryCache.d.ts.map +1 -0
  9. package/dist/cache/QueryCache.js +294 -0
  10. package/dist/cache/QueryCache.js.map +1 -0
  11. package/dist/cli/commands.d.ts +9 -0
  12. package/dist/cli/commands.d.ts.map +1 -0
  13. package/dist/cli/commands.js +954 -0
  14. package/dist/cli/commands.js.map +1 -0
  15. package/dist/cli/help.d.ts +16 -0
  16. package/dist/cli/help.d.ts.map +1 -0
  17. package/dist/cli/help.js +361 -0
  18. package/dist/cli/help.js.map +1 -0
  19. package/dist/cli/index.d.ts +30 -0
  20. package/dist/cli/index.d.ts.map +1 -0
  21. package/dist/cli/index.js +111 -0
  22. package/dist/cli/index.js.map +1 -0
  23. package/dist/context/ContextInjector.d.ts +38 -0
  24. package/dist/context/ContextInjector.d.ts.map +1 -0
  25. package/dist/context/ContextInjector.js +235 -0
  26. package/dist/context/ContextInjector.js.map +1 -0
  27. package/dist/documentation/CodeAnalyzer.d.ts +29 -0
  28. package/dist/documentation/CodeAnalyzer.d.ts.map +1 -0
  29. package/dist/documentation/CodeAnalyzer.js +122 -0
  30. package/dist/documentation/CodeAnalyzer.js.map +1 -0
  31. package/dist/documentation/ConversationAnalyzer.d.ts +19 -0
  32. package/dist/documentation/ConversationAnalyzer.d.ts.map +1 -0
  33. package/dist/documentation/ConversationAnalyzer.js +157 -0
  34. package/dist/documentation/ConversationAnalyzer.js.map +1 -0
  35. package/dist/documentation/CrossReferencer.d.ts +67 -0
  36. package/dist/documentation/CrossReferencer.d.ts.map +1 -0
  37. package/dist/documentation/CrossReferencer.js +247 -0
  38. package/dist/documentation/CrossReferencer.js.map +1 -0
  39. package/dist/documentation/DocumentationGenerator.d.ts +22 -0
  40. package/dist/documentation/DocumentationGenerator.d.ts.map +1 -0
  41. package/dist/documentation/DocumentationGenerator.js +57 -0
  42. package/dist/documentation/DocumentationGenerator.js.map +1 -0
  43. package/dist/documentation/MarkdownFormatter.d.ts +26 -0
  44. package/dist/documentation/MarkdownFormatter.d.ts.map +1 -0
  45. package/dist/documentation/MarkdownFormatter.js +301 -0
  46. package/dist/documentation/MarkdownFormatter.js.map +1 -0
  47. package/dist/documentation/types.d.ts +176 -0
  48. package/dist/documentation/types.d.ts.map +1 -0
  49. package/dist/documentation/types.js +5 -0
  50. package/dist/documentation/types.js.map +1 -0
  51. package/dist/embeddings/ConfigManager.d.ts +46 -0
  52. package/dist/embeddings/ConfigManager.d.ts.map +1 -0
  53. package/dist/embeddings/ConfigManager.js +177 -0
  54. package/dist/embeddings/ConfigManager.js.map +1 -0
  55. package/dist/embeddings/EmbeddingConfig.d.ts +39 -0
  56. package/dist/embeddings/EmbeddingConfig.d.ts.map +1 -0
  57. package/dist/embeddings/EmbeddingConfig.js +132 -0
  58. package/dist/embeddings/EmbeddingConfig.js.map +1 -0
  59. package/dist/embeddings/EmbeddingGenerator.d.ts +51 -0
  60. package/dist/embeddings/EmbeddingGenerator.d.ts.map +1 -0
  61. package/dist/embeddings/EmbeddingGenerator.js +157 -0
  62. package/dist/embeddings/EmbeddingGenerator.js.map +1 -0
  63. package/dist/embeddings/EmbeddingProvider.d.ts +34 -0
  64. package/dist/embeddings/EmbeddingProvider.d.ts.map +1 -0
  65. package/dist/embeddings/EmbeddingProvider.js +6 -0
  66. package/dist/embeddings/EmbeddingProvider.js.map +1 -0
  67. package/dist/embeddings/ModelRegistry.d.ts +48 -0
  68. package/dist/embeddings/ModelRegistry.d.ts.map +1 -0
  69. package/dist/embeddings/ModelRegistry.js +170 -0
  70. package/dist/embeddings/ModelRegistry.js.map +1 -0
  71. package/dist/embeddings/VectorStore.d.ts +114 -0
  72. package/dist/embeddings/VectorStore.d.ts.map +1 -0
  73. package/dist/embeddings/VectorStore.js +393 -0
  74. package/dist/embeddings/VectorStore.js.map +1 -0
  75. package/dist/embeddings/providers/OllamaEmbeddings.d.ts +38 -0
  76. package/dist/embeddings/providers/OllamaEmbeddings.d.ts.map +1 -0
  77. package/dist/embeddings/providers/OllamaEmbeddings.js +125 -0
  78. package/dist/embeddings/providers/OllamaEmbeddings.js.map +1 -0
  79. package/dist/embeddings/providers/OpenAIEmbeddings.d.ts +40 -0
  80. package/dist/embeddings/providers/OpenAIEmbeddings.d.ts.map +1 -0
  81. package/dist/embeddings/providers/OpenAIEmbeddings.js +129 -0
  82. package/dist/embeddings/providers/OpenAIEmbeddings.js.map +1 -0
  83. package/dist/embeddings/providers/TransformersEmbeddings.d.ts +38 -0
  84. package/dist/embeddings/providers/TransformersEmbeddings.d.ts.map +1 -0
  85. package/dist/embeddings/providers/TransformersEmbeddings.js +115 -0
  86. package/dist/embeddings/providers/TransformersEmbeddings.js.map +1 -0
  87. package/dist/handoff/SessionHandoffStore.d.ts +80 -0
  88. package/dist/handoff/SessionHandoffStore.d.ts.map +1 -0
  89. package/dist/handoff/SessionHandoffStore.js +314 -0
  90. package/dist/handoff/SessionHandoffStore.js.map +1 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +115 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/mcp-server.d.ts +27 -0
  96. package/dist/mcp-server.d.ts.map +1 -0
  97. package/dist/mcp-server.js +157 -0
  98. package/dist/mcp-server.js.map +1 -0
  99. package/dist/memory/WorkingMemoryStore.d.ts +83 -0
  100. package/dist/memory/WorkingMemoryStore.d.ts.map +1 -0
  101. package/dist/memory/WorkingMemoryStore.js +318 -0
  102. package/dist/memory/WorkingMemoryStore.js.map +1 -0
  103. package/dist/memory/types.d.ts +192 -0
  104. package/dist/memory/types.d.ts.map +1 -0
  105. package/dist/memory/types.js +8 -0
  106. package/dist/memory/types.js.map +1 -0
  107. package/dist/parsers/CodexConversationParser.d.ts +51 -0
  108. package/dist/parsers/CodexConversationParser.d.ts.map +1 -0
  109. package/dist/parsers/CodexConversationParser.js +301 -0
  110. package/dist/parsers/CodexConversationParser.js.map +1 -0
  111. package/dist/parsers/ConversationParser.d.ts +286 -0
  112. package/dist/parsers/ConversationParser.d.ts.map +1 -0
  113. package/dist/parsers/ConversationParser.js +795 -0
  114. package/dist/parsers/ConversationParser.js.map +1 -0
  115. package/dist/parsers/DecisionExtractor.d.ts +144 -0
  116. package/dist/parsers/DecisionExtractor.d.ts.map +1 -0
  117. package/dist/parsers/DecisionExtractor.js +434 -0
  118. package/dist/parsers/DecisionExtractor.js.map +1 -0
  119. package/dist/parsers/GitIntegrator.d.ts +156 -0
  120. package/dist/parsers/GitIntegrator.d.ts.map +1 -0
  121. package/dist/parsers/GitIntegrator.js +348 -0
  122. package/dist/parsers/GitIntegrator.js.map +1 -0
  123. package/dist/parsers/MistakeExtractor.d.ts +151 -0
  124. package/dist/parsers/MistakeExtractor.d.ts.map +1 -0
  125. package/dist/parsers/MistakeExtractor.js +460 -0
  126. package/dist/parsers/MistakeExtractor.js.map +1 -0
  127. package/dist/parsers/RequirementsExtractor.d.ts +166 -0
  128. package/dist/parsers/RequirementsExtractor.d.ts.map +1 -0
  129. package/dist/parsers/RequirementsExtractor.js +338 -0
  130. package/dist/parsers/RequirementsExtractor.js.map +1 -0
  131. package/dist/realtime/ConversationWatcher.d.ts +87 -0
  132. package/dist/realtime/ConversationWatcher.d.ts.map +1 -0
  133. package/dist/realtime/ConversationWatcher.js +204 -0
  134. package/dist/realtime/ConversationWatcher.js.map +1 -0
  135. package/dist/realtime/IncrementalParser.d.ts +83 -0
  136. package/dist/realtime/IncrementalParser.d.ts.map +1 -0
  137. package/dist/realtime/IncrementalParser.js +232 -0
  138. package/dist/realtime/IncrementalParser.js.map +1 -0
  139. package/dist/realtime/LiveExtractor.d.ts +72 -0
  140. package/dist/realtime/LiveExtractor.d.ts.map +1 -0
  141. package/dist/realtime/LiveExtractor.js +288 -0
  142. package/dist/realtime/LiveExtractor.js.map +1 -0
  143. package/dist/search/SemanticSearch.d.ts +121 -0
  144. package/dist/search/SemanticSearch.d.ts.map +1 -0
  145. package/dist/search/SemanticSearch.js +823 -0
  146. package/dist/search/SemanticSearch.js.map +1 -0
  147. package/dist/storage/BackupManager.d.ts +58 -0
  148. package/dist/storage/BackupManager.d.ts.map +1 -0
  149. package/dist/storage/BackupManager.js +223 -0
  150. package/dist/storage/BackupManager.js.map +1 -0
  151. package/dist/storage/ConversationStorage.d.ts +341 -0
  152. package/dist/storage/ConversationStorage.d.ts.map +1 -0
  153. package/dist/storage/ConversationStorage.js +792 -0
  154. package/dist/storage/ConversationStorage.js.map +1 -0
  155. package/dist/storage/DeletionService.d.ts +70 -0
  156. package/dist/storage/DeletionService.d.ts.map +1 -0
  157. package/dist/storage/DeletionService.js +253 -0
  158. package/dist/storage/DeletionService.js.map +1 -0
  159. package/dist/storage/GlobalIndex.d.ts +133 -0
  160. package/dist/storage/GlobalIndex.d.ts.map +1 -0
  161. package/dist/storage/GlobalIndex.js +310 -0
  162. package/dist/storage/GlobalIndex.js.map +1 -0
  163. package/dist/storage/SQLiteManager.d.ts +114 -0
  164. package/dist/storage/SQLiteManager.d.ts.map +1 -0
  165. package/dist/storage/SQLiteManager.js +636 -0
  166. package/dist/storage/SQLiteManager.js.map +1 -0
  167. package/dist/storage/migrations.d.ts +54 -0
  168. package/dist/storage/migrations.d.ts.map +1 -0
  169. package/dist/storage/migrations.js +285 -0
  170. package/dist/storage/migrations.js.map +1 -0
  171. package/dist/storage/schema.sql +436 -0
  172. package/dist/tools/ToolDefinitions.d.ts +946 -0
  173. package/dist/tools/ToolDefinitions.d.ts.map +1 -0
  174. package/dist/tools/ToolDefinitions.js +937 -0
  175. package/dist/tools/ToolDefinitions.js.map +1 -0
  176. package/dist/tools/ToolHandlers.d.ts +791 -0
  177. package/dist/tools/ToolHandlers.d.ts.map +1 -0
  178. package/dist/tools/ToolHandlers.js +3262 -0
  179. package/dist/tools/ToolHandlers.js.map +1 -0
  180. package/dist/types/ToolTypes.d.ts +824 -0
  181. package/dist/types/ToolTypes.d.ts.map +1 -0
  182. package/dist/types/ToolTypes.js +6 -0
  183. package/dist/types/ToolTypes.js.map +1 -0
  184. package/dist/utils/Logger.d.ts +70 -0
  185. package/dist/utils/Logger.d.ts.map +1 -0
  186. package/dist/utils/Logger.js +131 -0
  187. package/dist/utils/Logger.js.map +1 -0
  188. package/dist/utils/McpConfig.d.ts +54 -0
  189. package/dist/utils/McpConfig.d.ts.map +1 -0
  190. package/dist/utils/McpConfig.js +136 -0
  191. package/dist/utils/McpConfig.js.map +1 -0
  192. package/dist/utils/ProjectMigration.d.ts +82 -0
  193. package/dist/utils/ProjectMigration.d.ts.map +1 -0
  194. package/dist/utils/ProjectMigration.js +416 -0
  195. package/dist/utils/ProjectMigration.js.map +1 -0
  196. package/dist/utils/constants.d.ts +75 -0
  197. package/dist/utils/constants.d.ts.map +1 -0
  198. package/dist/utils/constants.js +105 -0
  199. package/dist/utils/constants.js.map +1 -0
  200. package/dist/utils/safeJson.d.ts +37 -0
  201. package/dist/utils/safeJson.d.ts.map +1 -0
  202. package/dist/utils/safeJson.js +48 -0
  203. package/dist/utils/safeJson.js.map +1 -0
  204. package/dist/utils/sanitization.d.ts +45 -0
  205. package/dist/utils/sanitization.d.ts.map +1 -0
  206. package/dist/utils/sanitization.js +153 -0
  207. package/dist/utils/sanitization.js.map +1 -0
  208. package/dist/utils/worktree.d.ts +15 -0
  209. package/dist/utils/worktree.d.ts.map +1 -0
  210. package/dist/utils/worktree.js +86 -0
  211. package/dist/utils/worktree.js.map +1 -0
  212. package/package.json +98 -0
  213. package/scripts/changelog-check.sh +62 -0
  214. package/scripts/check-node.js +17 -0
  215. package/scripts/dev-config.js +56 -0
  216. package/scripts/postinstall.js +117 -0
@@ -0,0 +1,823 @@
1
+ /**
2
+ * Semantic Search Interface
3
+ * Combines vector store and embedding generation for conversation search
4
+ */
5
+ import { VectorStore } from "../embeddings/VectorStore.js";
6
+ import { getEmbeddingGenerator, EmbeddingGenerator } from "../embeddings/EmbeddingGenerator.js";
7
+ import { safeJsonParse } from "../utils/safeJson.js";
8
+ export class SemanticSearch {
9
+ vectorStore;
10
+ db;
11
+ constructor(sqliteManager) {
12
+ this.db = sqliteManager;
13
+ this.vectorStore = new VectorStore(sqliteManager);
14
+ }
15
+ /**
16
+ * Index all messages for semantic search
17
+ * @param messages - Messages to index
18
+ * @param incremental - If true, skip messages that already have embeddings (default: true for fast re-indexing)
19
+ */
20
+ async indexMessages(messages, incremental = true) {
21
+ console.error(`Indexing ${messages.length} messages...`);
22
+ const embedder = await getEmbeddingGenerator();
23
+ if (!embedder.isAvailable()) {
24
+ console.error("Embeddings not available - skipping indexing");
25
+ return;
26
+ }
27
+ // Filter messages with content
28
+ const messagesWithContent = messages.filter((m) => !!m.content && m.content.trim().length > 0);
29
+ // In incremental mode, skip messages that already have embeddings
30
+ let messagesToIndex = messagesWithContent;
31
+ if (incremental) {
32
+ const existingIds = this.vectorStore.getExistingMessageEmbeddingIds();
33
+ messagesToIndex = messagesWithContent.filter((m) => !existingIds.has(m.id));
34
+ if (messagesToIndex.length === 0) {
35
+ console.error(`⏭ All ${messagesWithContent.length} messages already have embeddings`);
36
+ return;
37
+ }
38
+ if (existingIds.size > 0) {
39
+ console.error(`⏭ Skipping ${messagesWithContent.length - messagesToIndex.length} already-embedded messages`);
40
+ }
41
+ }
42
+ console.error(`Generating embeddings for ${messagesToIndex.length} ${incremental ? "new " : ""}messages...`);
43
+ // Generate embeddings in batches
44
+ const texts = messagesToIndex.map((m) => m.content);
45
+ const embeddings = await embedder.embedBatch(texts, 32);
46
+ // Get model name from embedder info
47
+ const embedderInfo = EmbeddingGenerator.getInfo();
48
+ const modelName = embedderInfo?.model || "all-MiniLM-L6-v2";
49
+ // Store embeddings
50
+ for (let i = 0; i < messagesToIndex.length; i++) {
51
+ await this.vectorStore.storeMessageEmbedding(messagesToIndex[i].id, messagesToIndex[i].content, embeddings[i], modelName);
52
+ }
53
+ console.error("✓ Indexing complete");
54
+ }
55
+ /**
56
+ * Index decisions for semantic search
57
+ * @param decisions - Decisions to index
58
+ * @param incremental - If true, skip decisions that already have embeddings (default: true for fast re-indexing)
59
+ */
60
+ async indexDecisions(decisions, incremental = true) {
61
+ console.error(`Indexing ${decisions.length} decisions...`);
62
+ const embedder = await getEmbeddingGenerator();
63
+ if (!embedder.isAvailable()) {
64
+ console.error("Embeddings not available - skipping decision indexing");
65
+ return;
66
+ }
67
+ // In incremental mode, skip decisions that already have embeddings
68
+ let decisionsToIndex = decisions;
69
+ if (incremental) {
70
+ const existingIds = this.vectorStore.getExistingDecisionEmbeddingIds();
71
+ decisionsToIndex = decisions.filter((d) => !existingIds.has(d.id));
72
+ if (decisionsToIndex.length === 0) {
73
+ console.error(`⏭ All ${decisions.length} decisions already have embeddings`);
74
+ return;
75
+ }
76
+ if (existingIds.size > 0) {
77
+ console.error(`⏭ Skipping ${decisions.length - decisionsToIndex.length} already-embedded decisions`);
78
+ }
79
+ }
80
+ console.error(`Generating embeddings for ${decisionsToIndex.length} ${incremental ? "new " : ""}decisions...`);
81
+ // Generate embeddings for decision text + rationale
82
+ const texts = decisionsToIndex.map((d) => {
83
+ const parts = [d.decision_text];
84
+ if (d.rationale) {
85
+ parts.push(d.rationale);
86
+ }
87
+ if (d.context) {
88
+ parts.push(d.context);
89
+ }
90
+ return parts.join(" ");
91
+ });
92
+ const embeddings = await embedder.embedBatch(texts, 32);
93
+ // Store embeddings
94
+ for (let i = 0; i < decisionsToIndex.length; i++) {
95
+ await this.vectorStore.storeDecisionEmbedding(decisionsToIndex[i].id, embeddings[i]);
96
+ }
97
+ console.error("✓ Decision indexing complete");
98
+ }
99
+ /**
100
+ * Index all decisions in the database that don't have embeddings.
101
+ * This catches decisions that were stored before embeddings were available.
102
+ */
103
+ async indexMissingDecisionEmbeddings() {
104
+ const embedder = await getEmbeddingGenerator();
105
+ if (!embedder.isAvailable()) {
106
+ console.error("Embeddings not available - skipping missing decision indexing");
107
+ return 0;
108
+ }
109
+ // Get decisions without embeddings
110
+ const existingIds = this.vectorStore.getExistingDecisionEmbeddingIds();
111
+ const allDecisions = this.db
112
+ .prepare("SELECT id, decision_text, rationale, context FROM decisions")
113
+ .all();
114
+ const missingDecisions = allDecisions.filter((d) => !existingIds.has(d.id));
115
+ if (missingDecisions.length === 0) {
116
+ return 0;
117
+ }
118
+ console.error(`Generating embeddings for ${missingDecisions.length} decisions missing embeddings...`);
119
+ // Generate embeddings for decision text + rationale
120
+ const texts = missingDecisions.map((d) => {
121
+ const parts = [d.decision_text];
122
+ if (d.rationale) {
123
+ parts.push(d.rationale);
124
+ }
125
+ if (d.context) {
126
+ parts.push(d.context);
127
+ }
128
+ return parts.join(" ");
129
+ });
130
+ const embeddings = await embedder.embedBatch(texts, 32);
131
+ // Store embeddings
132
+ for (let i = 0; i < missingDecisions.length; i++) {
133
+ await this.vectorStore.storeDecisionEmbedding(missingDecisions[i].id, embeddings[i]);
134
+ }
135
+ console.error(`✓ Generated ${missingDecisions.length} missing decision embeddings`);
136
+ return missingDecisions.length;
137
+ }
138
+ /**
139
+ * Index mistakes for semantic search
140
+ * @param mistakes - Mistakes to index
141
+ * @param incremental - If true, skip mistakes that already have embeddings (default: true)
142
+ */
143
+ async indexMistakes(mistakes, incremental = true) {
144
+ console.error(`Indexing ${mistakes.length} mistakes...`);
145
+ const embedder = await getEmbeddingGenerator();
146
+ if (!embedder.isAvailable()) {
147
+ console.error("Embeddings not available - skipping mistake indexing");
148
+ return;
149
+ }
150
+ // In incremental mode, skip mistakes that already have embeddings
151
+ let mistakesToIndex = mistakes;
152
+ if (incremental) {
153
+ const existingIds = this.vectorStore.getExistingMistakeEmbeddingIds();
154
+ mistakesToIndex = mistakes.filter((m) => !existingIds.has(m.id));
155
+ if (mistakesToIndex.length === 0) {
156
+ console.error(`⏭ All ${mistakes.length} mistakes already have embeddings`);
157
+ return;
158
+ }
159
+ if (existingIds.size > 0) {
160
+ console.error(`⏭ Skipping ${mistakes.length - mistakesToIndex.length} already-embedded mistakes`);
161
+ }
162
+ }
163
+ console.error(`Generating embeddings for ${mistakesToIndex.length} ${incremental ? "new " : ""}mistakes...`);
164
+ // Generate embeddings for mistake text + correction
165
+ const texts = mistakesToIndex.map((m) => {
166
+ const parts = [m.what_went_wrong];
167
+ if (m.correction) {
168
+ parts.push(m.correction);
169
+ }
170
+ if (m.mistake_type) {
171
+ parts.push(m.mistake_type);
172
+ }
173
+ return parts.join(" ");
174
+ });
175
+ const embeddings = await embedder.embedBatch(texts, 32);
176
+ // Store embeddings
177
+ for (let i = 0; i < mistakesToIndex.length; i++) {
178
+ await this.vectorStore.storeMistakeEmbedding(mistakesToIndex[i].id, embeddings[i]);
179
+ }
180
+ console.error("✓ Mistake indexing complete");
181
+ }
182
+ /**
183
+ * Index all mistakes in the database that don't have embeddings.
184
+ * This catches mistakes that were stored before embeddings were available.
185
+ */
186
+ async indexMissingMistakeEmbeddings() {
187
+ const embedder = await getEmbeddingGenerator();
188
+ if (!embedder.isAvailable()) {
189
+ console.error("Embeddings not available - skipping missing mistake indexing");
190
+ return 0;
191
+ }
192
+ // Get mistakes without embeddings
193
+ const existingIds = this.vectorStore.getExistingMistakeEmbeddingIds();
194
+ const allMistakes = this.db
195
+ .prepare("SELECT id, what_went_wrong, correction, mistake_type FROM mistakes")
196
+ .all();
197
+ const missingMistakes = allMistakes.filter((m) => !existingIds.has(m.id));
198
+ if (missingMistakes.length === 0) {
199
+ return 0;
200
+ }
201
+ console.error(`Generating embeddings for ${missingMistakes.length} mistakes missing embeddings...`);
202
+ // Generate embeddings for mistake text + correction
203
+ const texts = missingMistakes.map((m) => {
204
+ const parts = [m.what_went_wrong];
205
+ if (m.correction) {
206
+ parts.push(m.correction);
207
+ }
208
+ if (m.mistake_type) {
209
+ parts.push(m.mistake_type);
210
+ }
211
+ return parts.join(" ");
212
+ });
213
+ const embeddings = await embedder.embedBatch(texts, 32);
214
+ // Store embeddings
215
+ for (let i = 0; i < missingMistakes.length; i++) {
216
+ await this.vectorStore.storeMistakeEmbedding(missingMistakes[i].id, embeddings[i]);
217
+ }
218
+ console.error(`✓ Generated ${missingMistakes.length} missing mistake embeddings`);
219
+ return missingMistakes.length;
220
+ }
221
+ /**
222
+ * Search for mistakes using semantic search
223
+ */
224
+ async searchMistakes(query, limit = 10) {
225
+ const embedder = await getEmbeddingGenerator();
226
+ if (!embedder.isAvailable()) {
227
+ console.error("Embeddings not available - using text search");
228
+ return this.fallbackMistakeSearch(query, limit);
229
+ }
230
+ // Generate query embedding
231
+ const queryEmbedding = await embedder.embed(query);
232
+ try {
233
+ // Use vec_distance_cosine for efficient ANN search with JOINs
234
+ // Note: Must include byteOffset/byteLength in case Float32Array is a view
235
+ const queryBuffer = Buffer.from(queryEmbedding.buffer, queryEmbedding.byteOffset, queryEmbedding.byteLength);
236
+ const rows = this.db
237
+ .prepare(`SELECT
238
+ vec.id as vec_id,
239
+ vec_distance_cosine(vec.embedding, ?) as distance,
240
+ m.id,
241
+ m.conversation_id,
242
+ m.message_id,
243
+ m.mistake_type,
244
+ m.what_went_wrong,
245
+ m.correction,
246
+ m.user_correction_message,
247
+ m.files_affected,
248
+ m.timestamp,
249
+ c.id as conv_id,
250
+ c.project_path,
251
+ c.first_message_at,
252
+ c.last_message_at,
253
+ c.message_count,
254
+ c.git_branch,
255
+ c.claude_version,
256
+ c.metadata as conv_metadata,
257
+ c.created_at as conv_created_at,
258
+ c.updated_at as conv_updated_at
259
+ FROM vec_mistake_embeddings vec
260
+ JOIN mistake_embeddings me ON vec.id = me.id
261
+ JOIN mistakes m ON me.mistake_id = m.id
262
+ JOIN conversations c ON m.conversation_id = c.id
263
+ ORDER BY distance
264
+ LIMIT ?`)
265
+ .all(queryBuffer, limit);
266
+ // Fall back to FTS if vector search returned no results
267
+ if (rows.length === 0) {
268
+ console.error("Vector search returned no mistake results - falling back to FTS");
269
+ return this.fallbackMistakeSearch(query, limit);
270
+ }
271
+ return rows.map((row) => ({
272
+ mistake: {
273
+ id: row.id,
274
+ conversation_id: row.conversation_id,
275
+ message_id: row.message_id,
276
+ mistake_type: row.mistake_type,
277
+ what_went_wrong: row.what_went_wrong,
278
+ correction: row.correction || undefined,
279
+ user_correction_message: row.user_correction_message || undefined,
280
+ files_affected: safeJsonParse(row.files_affected, []),
281
+ timestamp: row.timestamp,
282
+ },
283
+ conversation: {
284
+ id: row.conv_id,
285
+ project_path: row.project_path,
286
+ first_message_at: row.first_message_at,
287
+ last_message_at: row.last_message_at,
288
+ message_count: row.message_count,
289
+ git_branch: row.git_branch,
290
+ claude_version: row.claude_version,
291
+ metadata: safeJsonParse(row.conv_metadata, {}),
292
+ created_at: row.conv_created_at,
293
+ updated_at: row.conv_updated_at,
294
+ },
295
+ similarity: 1 - row.distance, // Convert distance to similarity
296
+ }));
297
+ }
298
+ catch (error) {
299
+ // Fallback to text search if vec search fails
300
+ console.error("Vec mistake search failed, falling back to text search:", error.message);
301
+ return this.fallbackMistakeSearch(query, limit);
302
+ }
303
+ }
304
+ /**
305
+ * Search conversations using natural language query
306
+ * @param query - The search query text
307
+ * @param limit - Maximum results to return
308
+ * @param filter - Optional filter criteria
309
+ * @param precomputedEmbedding - Optional pre-computed embedding to avoid re-embedding
310
+ */
311
+ async searchConversations(query, limit = 10, filter, precomputedEmbedding) {
312
+ const embedder = await getEmbeddingGenerator();
313
+ if (!embedder.isAvailable() && !precomputedEmbedding) {
314
+ console.error("Embeddings not available - falling back to full-text search");
315
+ return this.fallbackFullTextSearch(query, limit, filter);
316
+ }
317
+ try {
318
+ // Use pre-computed embedding if provided, otherwise generate
319
+ const queryEmbedding = precomputedEmbedding ?? await embedder.embed(query);
320
+ // Search vector store
321
+ const vectorResults = await this.vectorStore.searchMessages(queryEmbedding, limit * 2 // Get more results for filtering
322
+ );
323
+ // Enrich with message and conversation data
324
+ const enrichedResults = [];
325
+ for (const vecResult of vectorResults) {
326
+ const message = this.getMessage(vecResult.id);
327
+ if (!message) {
328
+ continue;
329
+ }
330
+ // Apply filters
331
+ if (filter) {
332
+ if (!this.applyFilter(message, filter)) {
333
+ continue;
334
+ }
335
+ }
336
+ const conversation = this.getConversation(message.conversation_id);
337
+ if (!conversation) {
338
+ continue;
339
+ }
340
+ enrichedResults.push({
341
+ message,
342
+ conversation,
343
+ similarity: vecResult.similarity,
344
+ snippet: this.generateSnippet(vecResult.content, query),
345
+ });
346
+ if (enrichedResults.length >= limit) {
347
+ break;
348
+ }
349
+ }
350
+ // Fall back to FTS if vector search returned no results
351
+ if (enrichedResults.length === 0) {
352
+ console.error("Vector search returned no results - falling back to FTS");
353
+ return this.fallbackFullTextSearch(query, limit, filter);
354
+ }
355
+ return enrichedResults;
356
+ }
357
+ catch (error) {
358
+ // If embedding fails, fall back to FTS
359
+ console.error("Embedding error, falling back to FTS:", error.message);
360
+ return this.fallbackFullTextSearch(query, limit, filter);
361
+ }
362
+ }
363
+ /**
364
+ * Search for decisions
365
+ */
366
+ async searchDecisions(query, limit = 10) {
367
+ const embedder = await getEmbeddingGenerator();
368
+ if (!embedder.isAvailable()) {
369
+ console.error("Embeddings not available - using text search");
370
+ return this.fallbackDecisionSearch(query, limit);
371
+ }
372
+ // Generate query embedding
373
+ const queryEmbedding = await embedder.embed(query);
374
+ try {
375
+ // Use vec_distance_cosine for efficient ANN search with JOINs to avoid N+1 queries
376
+ // Note: Must include byteOffset/byteLength in case Float32Array is a view
377
+ const queryBuffer = Buffer.from(queryEmbedding.buffer, queryEmbedding.byteOffset, queryEmbedding.byteLength);
378
+ const rows = this.db
379
+ .prepare(`SELECT
380
+ vec.id as vec_id,
381
+ vec_distance_cosine(vec.embedding, ?) as distance,
382
+ d.id,
383
+ d.conversation_id,
384
+ d.message_id,
385
+ d.decision_text,
386
+ d.rationale,
387
+ d.alternatives_considered,
388
+ d.rejected_reasons,
389
+ d.context,
390
+ d.related_files,
391
+ d.related_commits,
392
+ d.timestamp,
393
+ c.id as conv_id,
394
+ c.project_path,
395
+ c.first_message_at,
396
+ c.last_message_at,
397
+ c.message_count,
398
+ c.git_branch,
399
+ c.claude_version,
400
+ c.metadata as conv_metadata,
401
+ c.created_at as conv_created_at,
402
+ c.updated_at as conv_updated_at
403
+ FROM vec_decision_embeddings vec
404
+ JOIN decision_embeddings de ON vec.id = de.id
405
+ JOIN decisions d ON de.decision_id = d.id
406
+ JOIN conversations c ON d.conversation_id = c.id
407
+ ORDER BY distance
408
+ LIMIT ?`)
409
+ .all(queryBuffer, limit);
410
+ // Fall back to FTS if vector search returned no results
411
+ if (rows.length === 0) {
412
+ console.error("Vector search returned no decision results - falling back to FTS");
413
+ return this.fallbackDecisionSearch(query, limit);
414
+ }
415
+ return rows.map((row) => ({
416
+ decision: {
417
+ id: row.id,
418
+ conversation_id: row.conversation_id,
419
+ message_id: row.message_id,
420
+ decision_text: row.decision_text,
421
+ rationale: row.rationale,
422
+ alternatives_considered: safeJsonParse(row.alternatives_considered, []),
423
+ rejected_reasons: safeJsonParse(row.rejected_reasons, {}),
424
+ context: row.context,
425
+ related_files: safeJsonParse(row.related_files, []),
426
+ related_commits: safeJsonParse(row.related_commits, []),
427
+ timestamp: row.timestamp,
428
+ },
429
+ conversation: {
430
+ id: row.conv_id,
431
+ project_path: row.project_path,
432
+ first_message_at: row.first_message_at,
433
+ last_message_at: row.last_message_at,
434
+ message_count: row.message_count,
435
+ git_branch: row.git_branch,
436
+ claude_version: row.claude_version,
437
+ metadata: safeJsonParse(row.conv_metadata, {}),
438
+ created_at: row.conv_created_at,
439
+ updated_at: row.conv_updated_at,
440
+ },
441
+ similarity: 1 - row.distance, // Convert distance to similarity
442
+ }));
443
+ }
444
+ catch (error) {
445
+ // Fallback to text search if vec search fails (e.g., table doesn't exist)
446
+ console.error("Vec decision search failed, falling back to text search:", error.message);
447
+ return this.fallbackDecisionSearch(query, limit);
448
+ }
449
+ }
450
+ /**
451
+ * Sanitize query for FTS5 MATCH syntax.
452
+ * FTS5 has special characters that need escaping: . * " - + ( ) OR AND NOT
453
+ * We wrap each word in double quotes to treat them as literal strings.
454
+ */
455
+ sanitizeFtsQuery(query) {
456
+ // Split into words and wrap each in double quotes to escape special chars
457
+ // Also escape any existing double quotes within words
458
+ const words = query.trim().split(/\s+/).filter(w => w.length > 0);
459
+ if (words.length === 0) {
460
+ return '""'; // Empty query
461
+ }
462
+ // Escape double quotes and wrap each word
463
+ const escapedWords = words.map(word => {
464
+ // Escape internal double quotes by doubling them
465
+ const escaped = word.replace(/"/g, '""');
466
+ return `"${escaped}"`;
467
+ });
468
+ // Join with space (implicit AND in FTS5)
469
+ return escapedWords.join(' ');
470
+ }
471
+ /**
472
+ * Fallback to full-text search when embeddings unavailable
473
+ */
474
+ fallbackFullTextSearch(query, limit, filter) {
475
+ // Sanitize the query for FTS5 syntax
476
+ const ftsQuery = this.sanitizeFtsQuery(query);
477
+ const mapRowToResult = (row) => {
478
+ const conversation = {
479
+ id: row.conv_id,
480
+ project_path: row.project_path,
481
+ first_message_at: row.first_message_at,
482
+ last_message_at: row.last_message_at,
483
+ message_count: row.conv_message_count,
484
+ git_branch: row.git_branch,
485
+ claude_version: row.claude_version,
486
+ metadata: safeJsonParse(row.conv_metadata, {}),
487
+ created_at: row.conv_created_at,
488
+ updated_at: row.conv_updated_at,
489
+ };
490
+ return {
491
+ message: {
492
+ ...row,
493
+ metadata: safeJsonParse(row.metadata, {}),
494
+ is_sidechain: Boolean(row.is_sidechain),
495
+ },
496
+ conversation,
497
+ similarity: 0.5, // Default similarity for FTS/LIKE
498
+ snippet: this.generateSnippet(row.content || "", query),
499
+ };
500
+ };
501
+ // Try FTS first, fall back to LIKE if FTS fails
502
+ try {
503
+ let sql = `
504
+ SELECT m.*,
505
+ c.id as conv_id,
506
+ c.project_path,
507
+ c.first_message_at,
508
+ c.last_message_at,
509
+ c.message_count as conv_message_count,
510
+ c.git_branch,
511
+ c.claude_version,
512
+ c.metadata as conv_metadata,
513
+ c.created_at as conv_created_at,
514
+ c.updated_at as conv_updated_at
515
+ FROM messages m
516
+ JOIN conversations c ON m.conversation_id = c.id
517
+ WHERE m.id IN (
518
+ SELECT id FROM messages_fts WHERE messages_fts MATCH ?
519
+ )
520
+ `;
521
+ const params = [ftsQuery];
522
+ // Apply filters
523
+ if (filter) {
524
+ if (filter.date_range) {
525
+ sql += " AND m.timestamp BETWEEN ? AND ?";
526
+ params.push(filter.date_range[0], filter.date_range[1]);
527
+ }
528
+ if (filter.message_type && filter.message_type.length > 0) {
529
+ sql += ` AND m.message_type IN (${filter.message_type.map(() => "?").join(",")})`;
530
+ params.push(...filter.message_type);
531
+ }
532
+ if (filter.conversation_id) {
533
+ sql += " AND m.conversation_id = ?";
534
+ params.push(filter.conversation_id);
535
+ }
536
+ }
537
+ sql += " ORDER BY m.timestamp DESC LIMIT ?";
538
+ params.push(limit);
539
+ const rows = this.db.prepare(sql).all(...params);
540
+ return rows.map(mapRowToResult);
541
+ }
542
+ catch (_e) {
543
+ // FTS table may not exist or be corrupted, fall back to LIKE search
544
+ console.error("Messages FTS not available, using LIKE search");
545
+ let sql = `
546
+ SELECT m.*,
547
+ c.id as conv_id,
548
+ c.project_path,
549
+ c.first_message_at,
550
+ c.last_message_at,
551
+ c.message_count as conv_message_count,
552
+ c.git_branch,
553
+ c.claude_version,
554
+ c.metadata as conv_metadata,
555
+ c.created_at as conv_created_at,
556
+ c.updated_at as conv_updated_at
557
+ FROM messages m
558
+ JOIN conversations c ON m.conversation_id = c.id
559
+ WHERE m.content LIKE ?
560
+ `;
561
+ const likeQuery = `%${query}%`;
562
+ const params = [likeQuery];
563
+ // Apply filters
564
+ if (filter) {
565
+ if (filter.date_range) {
566
+ sql += " AND m.timestamp BETWEEN ? AND ?";
567
+ params.push(filter.date_range[0], filter.date_range[1]);
568
+ }
569
+ if (filter.message_type && filter.message_type.length > 0) {
570
+ sql += ` AND m.message_type IN (${filter.message_type.map(() => "?").join(",")})`;
571
+ params.push(...filter.message_type);
572
+ }
573
+ if (filter.conversation_id) {
574
+ sql += " AND m.conversation_id = ?";
575
+ params.push(filter.conversation_id);
576
+ }
577
+ }
578
+ sql += " ORDER BY m.timestamp DESC LIMIT ?";
579
+ params.push(limit);
580
+ const rows = this.db.prepare(sql).all(...params);
581
+ return rows.map(mapRowToResult);
582
+ }
583
+ }
584
+ /**
585
+ * Fallback decision search
586
+ */
587
+ fallbackDecisionSearch(query, limit) {
588
+ // Sanitize the query for FTS5 syntax
589
+ const ftsQuery = this.sanitizeFtsQuery(query);
590
+ const mapRowToResult = (row) => {
591
+ const conversation = this.getConversation(row.conversation_id);
592
+ if (!conversation) {
593
+ console.error(`Warning: Conversation ${row.conversation_id} not found for decision ${row.id}`);
594
+ throw new Error(`Data integrity error: Conversation ${row.conversation_id} not found`);
595
+ }
596
+ return {
597
+ decision: {
598
+ ...row,
599
+ alternatives_considered: safeJsonParse(row.alternatives_considered, []),
600
+ rejected_reasons: safeJsonParse(row.rejected_reasons, {}),
601
+ related_files: safeJsonParse(row.related_files, []),
602
+ related_commits: safeJsonParse(row.related_commits, []),
603
+ },
604
+ conversation,
605
+ similarity: 0.5,
606
+ };
607
+ };
608
+ // Try FTS first, fall back to LIKE if FTS fails
609
+ try {
610
+ const sql = `
611
+ SELECT d.*, c.project_path, c.git_branch
612
+ FROM decisions d
613
+ JOIN conversations c ON d.conversation_id = c.id
614
+ WHERE d.id IN (
615
+ SELECT id FROM decisions_fts WHERE decisions_fts MATCH ?
616
+ )
617
+ ORDER BY d.timestamp DESC
618
+ LIMIT ?
619
+ `;
620
+ const rows = this.db.prepare(sql).all(ftsQuery, limit);
621
+ return rows.map(mapRowToResult);
622
+ }
623
+ catch (_e) {
624
+ // FTS table may not exist or be corrupted, fall back to LIKE search
625
+ console.error("Decisions FTS not available, using LIKE search");
626
+ const sql = `
627
+ SELECT d.*, c.project_path, c.git_branch
628
+ FROM decisions d
629
+ JOIN conversations c ON d.conversation_id = c.id
630
+ WHERE d.decision_text LIKE ? OR d.rationale LIKE ? OR d.context LIKE ?
631
+ ORDER BY d.timestamp DESC
632
+ LIMIT ?
633
+ `;
634
+ const likeQuery = `%${query}%`;
635
+ const rows = this.db.prepare(sql).all(likeQuery, likeQuery, likeQuery, limit);
636
+ return rows.map(mapRowToResult);
637
+ }
638
+ }
639
+ /**
640
+ * Fallback mistake search using FTS
641
+ */
642
+ fallbackMistakeSearch(query, limit) {
643
+ // Sanitize the query for FTS5 syntax
644
+ const ftsQuery = this.sanitizeFtsQuery(query);
645
+ // Try FTS first, fall back to LIKE if FTS table doesn't exist
646
+ try {
647
+ const sql = `
648
+ SELECT m.*, c.project_path, c.git_branch,
649
+ c.id as conv_id, c.first_message_at, c.last_message_at,
650
+ c.message_count, c.claude_version, c.metadata as conv_metadata,
651
+ c.created_at as conv_created_at, c.updated_at as conv_updated_at
652
+ FROM mistakes m
653
+ JOIN conversations c ON m.conversation_id = c.id
654
+ WHERE m.id IN (
655
+ SELECT id FROM mistakes_fts WHERE mistakes_fts MATCH ?
656
+ )
657
+ ORDER BY m.timestamp DESC
658
+ LIMIT ?
659
+ `;
660
+ const rows = this.db.prepare(sql).all(ftsQuery, limit);
661
+ return rows.map((row) => ({
662
+ mistake: {
663
+ id: row.id,
664
+ conversation_id: row.conversation_id,
665
+ message_id: row.message_id,
666
+ mistake_type: row.mistake_type,
667
+ what_went_wrong: row.what_went_wrong,
668
+ correction: row.correction || undefined,
669
+ user_correction_message: row.user_correction_message || undefined,
670
+ files_affected: safeJsonParse(row.files_affected, []),
671
+ timestamp: row.timestamp,
672
+ },
673
+ conversation: {
674
+ id: row.conv_id,
675
+ project_path: row.project_path,
676
+ first_message_at: row.first_message_at,
677
+ last_message_at: row.last_message_at,
678
+ message_count: row.message_count,
679
+ git_branch: row.git_branch,
680
+ claude_version: row.claude_version,
681
+ metadata: safeJsonParse(row.conv_metadata, {}),
682
+ created_at: row.conv_created_at,
683
+ updated_at: row.conv_updated_at,
684
+ },
685
+ similarity: 0.5,
686
+ }));
687
+ }
688
+ catch (_e) {
689
+ // FTS table may not exist, fall back to LIKE search
690
+ console.error("Mistakes FTS not available, using LIKE search");
691
+ const sql = `
692
+ SELECT m.*, c.project_path, c.git_branch,
693
+ c.id as conv_id, c.first_message_at, c.last_message_at,
694
+ c.message_count, c.claude_version, c.metadata as conv_metadata,
695
+ c.created_at as conv_created_at, c.updated_at as conv_updated_at
696
+ FROM mistakes m
697
+ JOIN conversations c ON m.conversation_id = c.id
698
+ WHERE m.what_went_wrong LIKE ? OR m.correction LIKE ?
699
+ ORDER BY m.timestamp DESC
700
+ LIMIT ?
701
+ `;
702
+ const likeQuery = `%${query}%`;
703
+ const rows = this.db.prepare(sql).all(likeQuery, likeQuery, limit);
704
+ return rows.map((row) => ({
705
+ mistake: {
706
+ id: row.id,
707
+ conversation_id: row.conversation_id,
708
+ message_id: row.message_id,
709
+ mistake_type: row.mistake_type,
710
+ what_went_wrong: row.what_went_wrong,
711
+ correction: row.correction || undefined,
712
+ user_correction_message: row.user_correction_message || undefined,
713
+ files_affected: safeJsonParse(row.files_affected, []),
714
+ timestamp: row.timestamp,
715
+ },
716
+ conversation: {
717
+ id: row.conv_id,
718
+ project_path: row.project_path,
719
+ first_message_at: row.first_message_at,
720
+ last_message_at: row.last_message_at,
721
+ message_count: row.message_count,
722
+ git_branch: row.git_branch,
723
+ claude_version: row.claude_version,
724
+ metadata: safeJsonParse(row.conv_metadata, {}),
725
+ created_at: row.conv_created_at,
726
+ updated_at: row.conv_updated_at,
727
+ },
728
+ similarity: 0.5,
729
+ }));
730
+ }
731
+ }
732
+ /**
733
+ * Apply filter to message
734
+ */
735
+ applyFilter(message, filter) {
736
+ if (filter.date_range) {
737
+ if (message.timestamp < filter.date_range[0] ||
738
+ message.timestamp > filter.date_range[1]) {
739
+ return false;
740
+ }
741
+ }
742
+ if (filter.message_type) {
743
+ if (!filter.message_type.includes(message.message_type)) {
744
+ return false;
745
+ }
746
+ }
747
+ if (filter.conversation_id) {
748
+ if (message.conversation_id !== filter.conversation_id) {
749
+ return false;
750
+ }
751
+ }
752
+ return true;
753
+ }
754
+ /**
755
+ * Generate snippet from content
756
+ */
757
+ generateSnippet(content, query, length = 150) {
758
+ // Try to find query term in content
759
+ const lowerContent = content.toLowerCase();
760
+ const lowerQuery = query.toLowerCase();
761
+ const index = lowerContent.indexOf(lowerQuery);
762
+ if (index !== -1) {
763
+ // Extract around query term
764
+ const start = Math.max(0, index - 50);
765
+ const end = Math.min(content.length, index + query.length + 100);
766
+ let snippet = content.substring(start, end);
767
+ if (start > 0) {
768
+ snippet = "..." + snippet;
769
+ }
770
+ if (end < content.length) {
771
+ snippet = snippet + "...";
772
+ }
773
+ return snippet;
774
+ }
775
+ // Otherwise just return beginning
776
+ return content.substring(0, length) + (content.length > length ? "..." : "");
777
+ }
778
+ /**
779
+ * Get message by ID
780
+ */
781
+ getMessage(id) {
782
+ const row = this.db
783
+ .prepare("SELECT * FROM messages WHERE id = ?")
784
+ .get(id);
785
+ if (!row) {
786
+ return null;
787
+ }
788
+ return {
789
+ ...row,
790
+ metadata: safeJsonParse(row.metadata, {}),
791
+ is_sidechain: Boolean(row.is_sidechain),
792
+ };
793
+ }
794
+ /**
795
+ * Get conversation by ID
796
+ */
797
+ getConversation(id) {
798
+ const row = this.db
799
+ .prepare("SELECT * FROM conversations WHERE id = ?")
800
+ .get(id);
801
+ if (!row) {
802
+ return null;
803
+ }
804
+ return {
805
+ ...row,
806
+ metadata: safeJsonParse(row.metadata, {}),
807
+ };
808
+ }
809
+ /**
810
+ * Get search statistics
811
+ */
812
+ getStats() {
813
+ return {
814
+ total_embeddings: this.vectorStore.getEmbeddingCount(),
815
+ vec_enabled: this.vectorStore.isVecEnabled(),
816
+ model_info: {
817
+ model: "all-MiniLM-L6-v2",
818
+ dimensions: 384,
819
+ },
820
+ };
821
+ }
822
+ }
823
+ //# sourceMappingURL=SemanticSearch.js.map