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.
- package/LICENSE +21 -0
- package/README.md +349 -0
- package/dist/ConversationMemory.d.ts +231 -0
- package/dist/ConversationMemory.d.ts.map +1 -0
- package/dist/ConversationMemory.js +357 -0
- package/dist/ConversationMemory.js.map +1 -0
- package/dist/cache/QueryCache.d.ts +215 -0
- package/dist/cache/QueryCache.d.ts.map +1 -0
- package/dist/cache/QueryCache.js +294 -0
- package/dist/cache/QueryCache.js.map +1 -0
- package/dist/cli/commands.d.ts +9 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +954 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/help.d.ts +16 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +361 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/index.d.ts +30 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +111 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context/ContextInjector.d.ts +38 -0
- package/dist/context/ContextInjector.d.ts.map +1 -0
- package/dist/context/ContextInjector.js +235 -0
- package/dist/context/ContextInjector.js.map +1 -0
- package/dist/documentation/CodeAnalyzer.d.ts +29 -0
- package/dist/documentation/CodeAnalyzer.d.ts.map +1 -0
- package/dist/documentation/CodeAnalyzer.js +122 -0
- package/dist/documentation/CodeAnalyzer.js.map +1 -0
- package/dist/documentation/ConversationAnalyzer.d.ts +19 -0
- package/dist/documentation/ConversationAnalyzer.d.ts.map +1 -0
- package/dist/documentation/ConversationAnalyzer.js +157 -0
- package/dist/documentation/ConversationAnalyzer.js.map +1 -0
- package/dist/documentation/CrossReferencer.d.ts +67 -0
- package/dist/documentation/CrossReferencer.d.ts.map +1 -0
- package/dist/documentation/CrossReferencer.js +247 -0
- package/dist/documentation/CrossReferencer.js.map +1 -0
- package/dist/documentation/DocumentationGenerator.d.ts +22 -0
- package/dist/documentation/DocumentationGenerator.d.ts.map +1 -0
- package/dist/documentation/DocumentationGenerator.js +57 -0
- package/dist/documentation/DocumentationGenerator.js.map +1 -0
- package/dist/documentation/MarkdownFormatter.d.ts +26 -0
- package/dist/documentation/MarkdownFormatter.d.ts.map +1 -0
- package/dist/documentation/MarkdownFormatter.js +301 -0
- package/dist/documentation/MarkdownFormatter.js.map +1 -0
- package/dist/documentation/types.d.ts +176 -0
- package/dist/documentation/types.d.ts.map +1 -0
- package/dist/documentation/types.js +5 -0
- package/dist/documentation/types.js.map +1 -0
- package/dist/embeddings/ConfigManager.d.ts +46 -0
- package/dist/embeddings/ConfigManager.d.ts.map +1 -0
- package/dist/embeddings/ConfigManager.js +177 -0
- package/dist/embeddings/ConfigManager.js.map +1 -0
- package/dist/embeddings/EmbeddingConfig.d.ts +39 -0
- package/dist/embeddings/EmbeddingConfig.d.ts.map +1 -0
- package/dist/embeddings/EmbeddingConfig.js +132 -0
- package/dist/embeddings/EmbeddingConfig.js.map +1 -0
- package/dist/embeddings/EmbeddingGenerator.d.ts +51 -0
- package/dist/embeddings/EmbeddingGenerator.d.ts.map +1 -0
- package/dist/embeddings/EmbeddingGenerator.js +157 -0
- package/dist/embeddings/EmbeddingGenerator.js.map +1 -0
- package/dist/embeddings/EmbeddingProvider.d.ts +34 -0
- package/dist/embeddings/EmbeddingProvider.d.ts.map +1 -0
- package/dist/embeddings/EmbeddingProvider.js +6 -0
- package/dist/embeddings/EmbeddingProvider.js.map +1 -0
- package/dist/embeddings/ModelRegistry.d.ts +48 -0
- package/dist/embeddings/ModelRegistry.d.ts.map +1 -0
- package/dist/embeddings/ModelRegistry.js +170 -0
- package/dist/embeddings/ModelRegistry.js.map +1 -0
- package/dist/embeddings/VectorStore.d.ts +114 -0
- package/dist/embeddings/VectorStore.d.ts.map +1 -0
- package/dist/embeddings/VectorStore.js +393 -0
- package/dist/embeddings/VectorStore.js.map +1 -0
- package/dist/embeddings/providers/OllamaEmbeddings.d.ts +38 -0
- package/dist/embeddings/providers/OllamaEmbeddings.d.ts.map +1 -0
- package/dist/embeddings/providers/OllamaEmbeddings.js +125 -0
- package/dist/embeddings/providers/OllamaEmbeddings.js.map +1 -0
- package/dist/embeddings/providers/OpenAIEmbeddings.d.ts +40 -0
- package/dist/embeddings/providers/OpenAIEmbeddings.d.ts.map +1 -0
- package/dist/embeddings/providers/OpenAIEmbeddings.js +129 -0
- package/dist/embeddings/providers/OpenAIEmbeddings.js.map +1 -0
- package/dist/embeddings/providers/TransformersEmbeddings.d.ts +38 -0
- package/dist/embeddings/providers/TransformersEmbeddings.d.ts.map +1 -0
- package/dist/embeddings/providers/TransformersEmbeddings.js +115 -0
- package/dist/embeddings/providers/TransformersEmbeddings.js.map +1 -0
- package/dist/handoff/SessionHandoffStore.d.ts +80 -0
- package/dist/handoff/SessionHandoffStore.d.ts.map +1 -0
- package/dist/handoff/SessionHandoffStore.js +314 -0
- package/dist/handoff/SessionHandoffStore.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +115 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +27 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +157 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/memory/WorkingMemoryStore.d.ts +83 -0
- package/dist/memory/WorkingMemoryStore.d.ts.map +1 -0
- package/dist/memory/WorkingMemoryStore.js +318 -0
- package/dist/memory/WorkingMemoryStore.js.map +1 -0
- package/dist/memory/types.d.ts +192 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +8 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/parsers/CodexConversationParser.d.ts +51 -0
- package/dist/parsers/CodexConversationParser.d.ts.map +1 -0
- package/dist/parsers/CodexConversationParser.js +301 -0
- package/dist/parsers/CodexConversationParser.js.map +1 -0
- package/dist/parsers/ConversationParser.d.ts +286 -0
- package/dist/parsers/ConversationParser.d.ts.map +1 -0
- package/dist/parsers/ConversationParser.js +795 -0
- package/dist/parsers/ConversationParser.js.map +1 -0
- package/dist/parsers/DecisionExtractor.d.ts +144 -0
- package/dist/parsers/DecisionExtractor.d.ts.map +1 -0
- package/dist/parsers/DecisionExtractor.js +434 -0
- package/dist/parsers/DecisionExtractor.js.map +1 -0
- package/dist/parsers/GitIntegrator.d.ts +156 -0
- package/dist/parsers/GitIntegrator.d.ts.map +1 -0
- package/dist/parsers/GitIntegrator.js +348 -0
- package/dist/parsers/GitIntegrator.js.map +1 -0
- package/dist/parsers/MistakeExtractor.d.ts +151 -0
- package/dist/parsers/MistakeExtractor.d.ts.map +1 -0
- package/dist/parsers/MistakeExtractor.js +460 -0
- package/dist/parsers/MistakeExtractor.js.map +1 -0
- package/dist/parsers/RequirementsExtractor.d.ts +166 -0
- package/dist/parsers/RequirementsExtractor.d.ts.map +1 -0
- package/dist/parsers/RequirementsExtractor.js +338 -0
- package/dist/parsers/RequirementsExtractor.js.map +1 -0
- package/dist/realtime/ConversationWatcher.d.ts +87 -0
- package/dist/realtime/ConversationWatcher.d.ts.map +1 -0
- package/dist/realtime/ConversationWatcher.js +204 -0
- package/dist/realtime/ConversationWatcher.js.map +1 -0
- package/dist/realtime/IncrementalParser.d.ts +83 -0
- package/dist/realtime/IncrementalParser.d.ts.map +1 -0
- package/dist/realtime/IncrementalParser.js +232 -0
- package/dist/realtime/IncrementalParser.js.map +1 -0
- package/dist/realtime/LiveExtractor.d.ts +72 -0
- package/dist/realtime/LiveExtractor.d.ts.map +1 -0
- package/dist/realtime/LiveExtractor.js +288 -0
- package/dist/realtime/LiveExtractor.js.map +1 -0
- package/dist/search/SemanticSearch.d.ts +121 -0
- package/dist/search/SemanticSearch.d.ts.map +1 -0
- package/dist/search/SemanticSearch.js +823 -0
- package/dist/search/SemanticSearch.js.map +1 -0
- package/dist/storage/BackupManager.d.ts +58 -0
- package/dist/storage/BackupManager.d.ts.map +1 -0
- package/dist/storage/BackupManager.js +223 -0
- package/dist/storage/BackupManager.js.map +1 -0
- package/dist/storage/ConversationStorage.d.ts +341 -0
- package/dist/storage/ConversationStorage.d.ts.map +1 -0
- package/dist/storage/ConversationStorage.js +792 -0
- package/dist/storage/ConversationStorage.js.map +1 -0
- package/dist/storage/DeletionService.d.ts +70 -0
- package/dist/storage/DeletionService.d.ts.map +1 -0
- package/dist/storage/DeletionService.js +253 -0
- package/dist/storage/DeletionService.js.map +1 -0
- package/dist/storage/GlobalIndex.d.ts +133 -0
- package/dist/storage/GlobalIndex.d.ts.map +1 -0
- package/dist/storage/GlobalIndex.js +310 -0
- package/dist/storage/GlobalIndex.js.map +1 -0
- package/dist/storage/SQLiteManager.d.ts +114 -0
- package/dist/storage/SQLiteManager.d.ts.map +1 -0
- package/dist/storage/SQLiteManager.js +636 -0
- package/dist/storage/SQLiteManager.js.map +1 -0
- package/dist/storage/migrations.d.ts +54 -0
- package/dist/storage/migrations.d.ts.map +1 -0
- package/dist/storage/migrations.js +285 -0
- package/dist/storage/migrations.js.map +1 -0
- package/dist/storage/schema.sql +436 -0
- package/dist/tools/ToolDefinitions.d.ts +946 -0
- package/dist/tools/ToolDefinitions.d.ts.map +1 -0
- package/dist/tools/ToolDefinitions.js +937 -0
- package/dist/tools/ToolDefinitions.js.map +1 -0
- package/dist/tools/ToolHandlers.d.ts +791 -0
- package/dist/tools/ToolHandlers.d.ts.map +1 -0
- package/dist/tools/ToolHandlers.js +3262 -0
- package/dist/tools/ToolHandlers.js.map +1 -0
- package/dist/types/ToolTypes.d.ts +824 -0
- package/dist/types/ToolTypes.d.ts.map +1 -0
- package/dist/types/ToolTypes.js +6 -0
- package/dist/types/ToolTypes.js.map +1 -0
- package/dist/utils/Logger.d.ts +70 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +131 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/McpConfig.d.ts +54 -0
- package/dist/utils/McpConfig.d.ts.map +1 -0
- package/dist/utils/McpConfig.js +136 -0
- package/dist/utils/McpConfig.js.map +1 -0
- package/dist/utils/ProjectMigration.d.ts +82 -0
- package/dist/utils/ProjectMigration.d.ts.map +1 -0
- package/dist/utils/ProjectMigration.js +416 -0
- package/dist/utils/ProjectMigration.js.map +1 -0
- package/dist/utils/constants.d.ts +75 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +105 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/safeJson.d.ts +37 -0
- package/dist/utils/safeJson.d.ts.map +1 -0
- package/dist/utils/safeJson.js +48 -0
- package/dist/utils/safeJson.js.map +1 -0
- package/dist/utils/sanitization.d.ts +45 -0
- package/dist/utils/sanitization.d.ts.map +1 -0
- package/dist/utils/sanitization.js +153 -0
- package/dist/utils/sanitization.js.map +1 -0
- package/dist/utils/worktree.d.ts +15 -0
- package/dist/utils/worktree.d.ts.map +1 -0
- package/dist/utils/worktree.js +86 -0
- package/dist/utils/worktree.js.map +1 -0
- package/package.json +98 -0
- package/scripts/changelog-check.sh +62 -0
- package/scripts/check-node.js +17 -0
- package/scripts/dev-config.js +56 -0
- 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
|