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,792 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Storage Layer - CRUD operations for all conversation-related data.
|
|
3
|
+
*
|
|
4
|
+
* This class provides the data access layer for the cccmemory system.
|
|
5
|
+
* It handles storing and retrieving conversations, messages, tool uses, decisions,
|
|
6
|
+
* mistakes, requirements, and git commits.
|
|
7
|
+
*
|
|
8
|
+
* All store operations use transactions for atomicity and performance.
|
|
9
|
+
* All JSON fields are automatically serialized/deserialized.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const storage = new ConversationStorage(sqliteManager);
|
|
14
|
+
* await storage.storeConversations(conversations);
|
|
15
|
+
* const conv = storage.getConversation('conv-123');
|
|
16
|
+
* const timeline = storage.getFileTimeline('src/index.ts');
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { sanitizeForLike } from "../utils/sanitization.js";
|
|
20
|
+
import { QueryCache } from "../cache/QueryCache.js";
|
|
21
|
+
import { safeJsonParse } from "../utils/safeJson.js";
|
|
22
|
+
/**
|
|
23
|
+
* Data access layer for conversation memory storage.
|
|
24
|
+
*
|
|
25
|
+
* Provides CRUD operations for all conversation-related entities using SQLite.
|
|
26
|
+
* Supports optional caching for frequently accessed queries.
|
|
27
|
+
*/
|
|
28
|
+
export class ConversationStorage {
|
|
29
|
+
db;
|
|
30
|
+
cache = null;
|
|
31
|
+
/**
|
|
32
|
+
* Create a new ConversationStorage instance.
|
|
33
|
+
*
|
|
34
|
+
* @param db - SQLiteManager instance for database access
|
|
35
|
+
*/
|
|
36
|
+
constructor(db) {
|
|
37
|
+
this.db = db;
|
|
38
|
+
}
|
|
39
|
+
// ==================== Cache Management ====================
|
|
40
|
+
/**
|
|
41
|
+
* Enable query result caching.
|
|
42
|
+
*
|
|
43
|
+
* Caching improves performance for frequently accessed queries by storing
|
|
44
|
+
* results in memory. Cache is automatically invalidated when data changes.
|
|
45
|
+
*
|
|
46
|
+
* @param config - Cache configuration (maxSize and ttlMs)
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* storage.enableCache({ maxSize: 100, ttlMs: 300000 });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
enableCache(config) {
|
|
54
|
+
this.cache = new QueryCache(config);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Disable query result caching.
|
|
58
|
+
*
|
|
59
|
+
* Clears all cached data and stops caching new queries.
|
|
60
|
+
*/
|
|
61
|
+
disableCache() {
|
|
62
|
+
this.cache = null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if caching is enabled.
|
|
66
|
+
*
|
|
67
|
+
* @returns True if caching is enabled
|
|
68
|
+
*/
|
|
69
|
+
isCacheEnabled() {
|
|
70
|
+
return this.cache !== null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Clear all cached query results.
|
|
74
|
+
*
|
|
75
|
+
* Clears the cache but keeps caching enabled.
|
|
76
|
+
*/
|
|
77
|
+
clearCache() {
|
|
78
|
+
if (this.cache) {
|
|
79
|
+
this.cache.clear();
|
|
80
|
+
this.cache.resetStats();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get cache statistics.
|
|
85
|
+
*
|
|
86
|
+
* Returns performance metrics including hits, misses, hit rate, and evictions.
|
|
87
|
+
*
|
|
88
|
+
* @returns Cache statistics or null if caching is disabled
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const stats = storage.getCacheStats();
|
|
93
|
+
* if (stats) {
|
|
94
|
+
* console.error(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
getCacheStats() {
|
|
99
|
+
return this.cache ? this.cache.getStats() : null;
|
|
100
|
+
}
|
|
101
|
+
// ==================== Conversations ====================
|
|
102
|
+
/**
|
|
103
|
+
* Store conversations in the database.
|
|
104
|
+
*
|
|
105
|
+
* Uses UPSERT (INSERT ON CONFLICT UPDATE) to handle both new and updated conversations.
|
|
106
|
+
* All operations are performed in a single transaction for atomicity.
|
|
107
|
+
*
|
|
108
|
+
* @param conversations - Array of conversation objects to store
|
|
109
|
+
* @returns Promise that resolves when all conversations are stored
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* await storage.storeConversations([
|
|
114
|
+
* {
|
|
115
|
+
* id: 'conv-123',
|
|
116
|
+
* project_path: '/path/to/project',
|
|
117
|
+
* first_message_at: Date.now(),
|
|
118
|
+
* last_message_at: Date.now(),
|
|
119
|
+
* message_count: 42,
|
|
120
|
+
* git_branch: 'main',
|
|
121
|
+
* claude_version: '3.5',
|
|
122
|
+
* metadata: {},
|
|
123
|
+
* created_at: Date.now(),
|
|
124
|
+
* updated_at: Date.now()
|
|
125
|
+
* }
|
|
126
|
+
* ]);
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
async storeConversations(conversations) {
|
|
130
|
+
const stmt = this.db.prepare(`
|
|
131
|
+
INSERT INTO conversations
|
|
132
|
+
(id, project_path, source_type, first_message_at, last_message_at, message_count,
|
|
133
|
+
git_branch, claude_version, metadata, created_at, updated_at)
|
|
134
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
135
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
136
|
+
project_path = excluded.project_path,
|
|
137
|
+
source_type = excluded.source_type,
|
|
138
|
+
first_message_at = excluded.first_message_at,
|
|
139
|
+
last_message_at = excluded.last_message_at,
|
|
140
|
+
message_count = excluded.message_count,
|
|
141
|
+
git_branch = excluded.git_branch,
|
|
142
|
+
claude_version = excluded.claude_version,
|
|
143
|
+
metadata = excluded.metadata,
|
|
144
|
+
updated_at = excluded.updated_at
|
|
145
|
+
`);
|
|
146
|
+
this.db.transaction(() => {
|
|
147
|
+
for (const conv of conversations) {
|
|
148
|
+
stmt.run(conv.id, conv.project_path, conv.source_type || 'claude-code', conv.first_message_at, conv.last_message_at, conv.message_count, conv.git_branch, conv.claude_version, JSON.stringify(conv.metadata), conv.created_at, conv.updated_at);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
// Invalidate cache once after batch (not per-item)
|
|
152
|
+
if (this.cache) {
|
|
153
|
+
this.cache.clear();
|
|
154
|
+
}
|
|
155
|
+
console.error(`✓ Stored ${conversations.length} conversations`);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Retrieve a single conversation by ID.
|
|
159
|
+
*
|
|
160
|
+
* @param id - Conversation ID to retrieve
|
|
161
|
+
* @returns Conversation object if found, null otherwise
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* const conv = storage.getConversation('conv-123');
|
|
166
|
+
* if (conv) {
|
|
167
|
+
* console.error(`${conv.message_count} messages on ${conv.git_branch}`);
|
|
168
|
+
* }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
getConversation(id) {
|
|
172
|
+
const cacheKey = `conversation:${id}`;
|
|
173
|
+
// Check cache first
|
|
174
|
+
if (this.cache) {
|
|
175
|
+
const cached = this.cache.get(cacheKey);
|
|
176
|
+
if (cached !== undefined) {
|
|
177
|
+
return cached;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const row = this.db
|
|
181
|
+
.prepare("SELECT * FROM conversations WHERE id = ?")
|
|
182
|
+
.get(id);
|
|
183
|
+
if (!row) {
|
|
184
|
+
// Cache null result to avoid repeated queries
|
|
185
|
+
this.cache?.set(cacheKey, null);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const result = {
|
|
189
|
+
...row,
|
|
190
|
+
metadata: safeJsonParse(row.metadata, {}),
|
|
191
|
+
};
|
|
192
|
+
// Cache the result
|
|
193
|
+
this.cache?.set(cacheKey, result);
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
// ==================== Messages ====================
|
|
197
|
+
/**
|
|
198
|
+
* Store messages in the database.
|
|
199
|
+
*
|
|
200
|
+
* Stores all messages from conversations including content, metadata, and relationships.
|
|
201
|
+
* Uses UPSERT (INSERT ON CONFLICT UPDATE) for idempotent storage.
|
|
202
|
+
*
|
|
203
|
+
* @param messages - Array of message objects to store
|
|
204
|
+
* @param skipFtsRebuild - Skip FTS rebuild (for batch operations, call rebuildAllFts() at end)
|
|
205
|
+
* @returns Promise that resolves when all messages are stored
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* await storage.storeMessages([
|
|
210
|
+
* {
|
|
211
|
+
* id: 'msg-123',
|
|
212
|
+
* conversation_id: 'conv-123',
|
|
213
|
+
* message_type: 'text',
|
|
214
|
+
* role: 'user',
|
|
215
|
+
* content: 'Hello',
|
|
216
|
+
* timestamp: Date.now(),
|
|
217
|
+
* is_sidechain: false,
|
|
218
|
+
* metadata: {}
|
|
219
|
+
* }
|
|
220
|
+
* ]);
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
async storeMessages(messages, skipFtsRebuild = false) {
|
|
224
|
+
const stmt = this.db.prepare(`
|
|
225
|
+
INSERT INTO messages
|
|
226
|
+
(id, conversation_id, parent_id, message_type, role, content,
|
|
227
|
+
timestamp, is_sidechain, agent_id, request_id, git_branch, cwd, metadata)
|
|
228
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
229
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
230
|
+
conversation_id = excluded.conversation_id,
|
|
231
|
+
parent_id = excluded.parent_id,
|
|
232
|
+
message_type = excluded.message_type,
|
|
233
|
+
role = excluded.role,
|
|
234
|
+
content = excluded.content,
|
|
235
|
+
timestamp = excluded.timestamp,
|
|
236
|
+
is_sidechain = excluded.is_sidechain,
|
|
237
|
+
agent_id = excluded.agent_id,
|
|
238
|
+
request_id = excluded.request_id,
|
|
239
|
+
git_branch = excluded.git_branch,
|
|
240
|
+
cwd = excluded.cwd,
|
|
241
|
+
metadata = excluded.metadata
|
|
242
|
+
`);
|
|
243
|
+
// Build a set of message IDs for FK validation (from current batch)
|
|
244
|
+
const messageIds = new Set(messages.map(m => m.id));
|
|
245
|
+
// Collect parent_ids that need DB lookup (not in current batch)
|
|
246
|
+
const parentIdsToCheck = new Set();
|
|
247
|
+
for (const msg of messages) {
|
|
248
|
+
if (msg.parent_id && !messageIds.has(msg.parent_id)) {
|
|
249
|
+
parentIdsToCheck.add(msg.parent_id);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Only query DB for specific parent_ids we need (O(k) where k = unique parent refs)
|
|
253
|
+
// Chunk to avoid SQLite's max variables limit (999 by default)
|
|
254
|
+
if (parentIdsToCheck.size > 0) {
|
|
255
|
+
const CHUNK_SIZE = 500; // Stay well under SQLite's limit
|
|
256
|
+
const parentIdsArray = Array.from(parentIdsToCheck);
|
|
257
|
+
for (let i = 0; i < parentIdsArray.length; i += CHUNK_SIZE) {
|
|
258
|
+
const chunk = parentIdsArray.slice(i, i + CHUNK_SIZE);
|
|
259
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
260
|
+
const existingIds = this.db
|
|
261
|
+
.prepare(`SELECT id FROM messages WHERE id IN (${placeholders})`)
|
|
262
|
+
.all(...chunk);
|
|
263
|
+
for (const row of existingIds) {
|
|
264
|
+
messageIds.add(row.id);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
this.db.transaction(() => {
|
|
269
|
+
for (const msg of messages) {
|
|
270
|
+
// Nullify parent_id if it references a non-existent message to avoid FK violation
|
|
271
|
+
const safeParentId = msg.parent_id && messageIds.has(msg.parent_id) ? msg.parent_id : null;
|
|
272
|
+
stmt.run(msg.id, msg.conversation_id, safeParentId, msg.message_type, msg.role || null, msg.content || null, msg.timestamp, msg.is_sidechain ? 1 : 0, msg.agent_id || null, msg.request_id || null, msg.git_branch || null, msg.cwd || null, JSON.stringify(msg.metadata));
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
// Rebuild FTS index for full-text search
|
|
276
|
+
// FTS5 with external content requires explicit rebuild after inserts
|
|
277
|
+
// Skip during batch operations for performance (call rebuildAllFts() at end)
|
|
278
|
+
if (!skipFtsRebuild) {
|
|
279
|
+
this.rebuildMessagesFts();
|
|
280
|
+
}
|
|
281
|
+
console.error(`✓ Stored ${messages.length} messages`);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Rebuild the messages FTS index.
|
|
285
|
+
* Required for FTS5 external content tables after inserting data.
|
|
286
|
+
* Call this after batch operations that used skipFtsRebuild=true.
|
|
287
|
+
*/
|
|
288
|
+
rebuildMessagesFts() {
|
|
289
|
+
try {
|
|
290
|
+
this.db.getDatabase().exec("INSERT INTO messages_fts(messages_fts) VALUES('rebuild')");
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
// FTS rebuild may fail if table doesn't exist or schema mismatch
|
|
294
|
+
// Log but don't throw - FTS is optional fallback
|
|
295
|
+
console.error("FTS rebuild warning:", error.message);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// ==================== Tool Uses ====================
|
|
299
|
+
/**
|
|
300
|
+
* Store tool use records in the database.
|
|
301
|
+
*
|
|
302
|
+
* Records all tool invocations from assistant messages.
|
|
303
|
+
*
|
|
304
|
+
* @param toolUses - Array of tool use objects
|
|
305
|
+
* @returns Promise that resolves when stored
|
|
306
|
+
*/
|
|
307
|
+
async storeToolUses(toolUses) {
|
|
308
|
+
const stmt = this.db.prepare(`
|
|
309
|
+
INSERT INTO tool_uses
|
|
310
|
+
(id, message_id, tool_name, tool_input, timestamp)
|
|
311
|
+
VALUES (?, ?, ?, ?, ?)
|
|
312
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
313
|
+
message_id = excluded.message_id,
|
|
314
|
+
tool_name = excluded.tool_name,
|
|
315
|
+
tool_input = excluded.tool_input,
|
|
316
|
+
timestamp = excluded.timestamp
|
|
317
|
+
`);
|
|
318
|
+
this.db.transaction(() => {
|
|
319
|
+
for (const tool of toolUses) {
|
|
320
|
+
stmt.run(tool.id, tool.message_id, tool.tool_name, JSON.stringify(tool.tool_input), tool.timestamp);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
console.error(`✓ Stored ${toolUses.length} tool uses`);
|
|
324
|
+
}
|
|
325
|
+
// ==================== Tool Results ====================
|
|
326
|
+
/**
|
|
327
|
+
* Store tool execution results in the database.
|
|
328
|
+
*
|
|
329
|
+
* Records the output/results from tool invocations.
|
|
330
|
+
*
|
|
331
|
+
* @param toolResults - Array of tool result objects
|
|
332
|
+
* @returns Promise that resolves when stored
|
|
333
|
+
*/
|
|
334
|
+
async storeToolResults(toolResults) {
|
|
335
|
+
const stmt = this.db.prepare(`
|
|
336
|
+
INSERT INTO tool_results
|
|
337
|
+
(id, tool_use_id, message_id, content, is_error, stdout, stderr, is_image, timestamp)
|
|
338
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
339
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
340
|
+
tool_use_id = excluded.tool_use_id,
|
|
341
|
+
message_id = excluded.message_id,
|
|
342
|
+
content = excluded.content,
|
|
343
|
+
is_error = excluded.is_error,
|
|
344
|
+
stdout = excluded.stdout,
|
|
345
|
+
stderr = excluded.stderr,
|
|
346
|
+
is_image = excluded.is_image,
|
|
347
|
+
timestamp = excluded.timestamp
|
|
348
|
+
`);
|
|
349
|
+
this.db.transaction(() => {
|
|
350
|
+
for (const result of toolResults) {
|
|
351
|
+
stmt.run(result.id, result.tool_use_id, result.message_id, result.content || null, result.is_error ? 1 : 0, result.stdout || null, result.stderr || null, result.is_image ? 1 : 0, result.timestamp);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
console.error(`✓ Stored ${toolResults.length} tool results`);
|
|
355
|
+
}
|
|
356
|
+
// ==================== File Edits ====================
|
|
357
|
+
/**
|
|
358
|
+
* Store file edit records in the database.
|
|
359
|
+
*
|
|
360
|
+
* Records all file modifications made during conversations.
|
|
361
|
+
*
|
|
362
|
+
* @param fileEdits - Array of file edit objects
|
|
363
|
+
* @returns Promise that resolves when stored
|
|
364
|
+
*/
|
|
365
|
+
async storeFileEdits(fileEdits) {
|
|
366
|
+
const stmt = this.db.prepare(`
|
|
367
|
+
INSERT INTO file_edits
|
|
368
|
+
(id, conversation_id, file_path, message_id, backup_version,
|
|
369
|
+
backup_time, snapshot_timestamp, metadata)
|
|
370
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
371
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
372
|
+
conversation_id = excluded.conversation_id,
|
|
373
|
+
file_path = excluded.file_path,
|
|
374
|
+
message_id = excluded.message_id,
|
|
375
|
+
backup_version = excluded.backup_version,
|
|
376
|
+
backup_time = excluded.backup_time,
|
|
377
|
+
snapshot_timestamp = excluded.snapshot_timestamp,
|
|
378
|
+
metadata = excluded.metadata
|
|
379
|
+
`);
|
|
380
|
+
this.db.transaction(() => {
|
|
381
|
+
for (const edit of fileEdits) {
|
|
382
|
+
stmt.run(edit.id, edit.conversation_id, edit.file_path, edit.message_id, edit.backup_version || null, edit.backup_time || null, edit.snapshot_timestamp, JSON.stringify(edit.metadata));
|
|
383
|
+
// Invalidate all caches for this file
|
|
384
|
+
if (this.cache) {
|
|
385
|
+
this.cache.delete(`edits:${edit.file_path}`);
|
|
386
|
+
this.cache.delete(`timeline:${edit.file_path}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
console.error(`✓ Stored ${fileEdits.length} file edits`);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Retrieve all edits for a specific file.
|
|
394
|
+
*
|
|
395
|
+
* @param filePath - Path to the file
|
|
396
|
+
* @returns Array of file edits, ordered by timestamp (most recent first)
|
|
397
|
+
*/
|
|
398
|
+
getFileEdits(filePath) {
|
|
399
|
+
const cacheKey = `edits:${filePath}`;
|
|
400
|
+
// Check cache first
|
|
401
|
+
if (this.cache) {
|
|
402
|
+
const cached = this.cache.get(cacheKey);
|
|
403
|
+
if (cached !== undefined) {
|
|
404
|
+
return cached;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const rows = this.db
|
|
408
|
+
.prepare("SELECT * FROM file_edits WHERE file_path = ? ORDER BY snapshot_timestamp DESC")
|
|
409
|
+
.all(filePath);
|
|
410
|
+
// Parse metadata JSON for each row
|
|
411
|
+
const result = rows.map(row => ({
|
|
412
|
+
...row,
|
|
413
|
+
metadata: safeJsonParse(row.metadata, {}),
|
|
414
|
+
}));
|
|
415
|
+
// Cache the result
|
|
416
|
+
this.cache?.set(cacheKey, result);
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
// ==================== Thinking Blocks ====================
|
|
420
|
+
/**
|
|
421
|
+
* Store thinking blocks in the database.
|
|
422
|
+
*
|
|
423
|
+
* Thinking blocks contain Claude's internal reasoning. They can be large and
|
|
424
|
+
* are optionally indexed based on the includeThinking flag.
|
|
425
|
+
*
|
|
426
|
+
* @param blocks - Array of thinking block objects
|
|
427
|
+
* @returns Promise that resolves when stored
|
|
428
|
+
*/
|
|
429
|
+
async storeThinkingBlocks(blocks) {
|
|
430
|
+
const stmt = this.db.prepare(`
|
|
431
|
+
INSERT INTO thinking_blocks
|
|
432
|
+
(id, message_id, thinking_content, signature, timestamp)
|
|
433
|
+
VALUES (?, ?, ?, ?, ?)
|
|
434
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
435
|
+
message_id = excluded.message_id,
|
|
436
|
+
thinking_content = excluded.thinking_content,
|
|
437
|
+
signature = excluded.signature,
|
|
438
|
+
timestamp = excluded.timestamp
|
|
439
|
+
`);
|
|
440
|
+
this.db.transaction(() => {
|
|
441
|
+
for (const block of blocks) {
|
|
442
|
+
stmt.run(block.id, block.message_id, block.thinking_content, block.signature || null, block.timestamp);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
console.error(`✓ Stored ${blocks.length} thinking blocks`);
|
|
446
|
+
}
|
|
447
|
+
// ==================== Decisions ====================
|
|
448
|
+
/**
|
|
449
|
+
* Store extracted decisions in the database.
|
|
450
|
+
*
|
|
451
|
+
* Decisions include architectural choices, technical decisions, and their rationale.
|
|
452
|
+
*
|
|
453
|
+
* @param decisions - Array of decision objects
|
|
454
|
+
* @param skipFtsRebuild - Skip FTS rebuild (for batch operations, call rebuildAllFts() at end)
|
|
455
|
+
* @returns Promise that resolves when stored
|
|
456
|
+
*/
|
|
457
|
+
async storeDecisions(decisions, skipFtsRebuild = false) {
|
|
458
|
+
const stmt = this.db.prepare(`
|
|
459
|
+
INSERT INTO decisions
|
|
460
|
+
(id, conversation_id, message_id, decision_text, rationale,
|
|
461
|
+
alternatives_considered, rejected_reasons, context, related_files,
|
|
462
|
+
related_commits, timestamp)
|
|
463
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
464
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
465
|
+
conversation_id = excluded.conversation_id,
|
|
466
|
+
message_id = excluded.message_id,
|
|
467
|
+
decision_text = excluded.decision_text,
|
|
468
|
+
rationale = excluded.rationale,
|
|
469
|
+
alternatives_considered = excluded.alternatives_considered,
|
|
470
|
+
rejected_reasons = excluded.rejected_reasons,
|
|
471
|
+
context = excluded.context,
|
|
472
|
+
related_files = excluded.related_files,
|
|
473
|
+
related_commits = excluded.related_commits,
|
|
474
|
+
timestamp = excluded.timestamp
|
|
475
|
+
`);
|
|
476
|
+
this.db.transaction(() => {
|
|
477
|
+
for (const decision of decisions) {
|
|
478
|
+
stmt.run(decision.id, decision.conversation_id, decision.message_id, decision.decision_text, decision.rationale || null, JSON.stringify(decision.alternatives_considered), JSON.stringify(decision.rejected_reasons), decision.context || null, JSON.stringify(decision.related_files), JSON.stringify(decision.related_commits), decision.timestamp);
|
|
479
|
+
// Invalidate caches for related files
|
|
480
|
+
if (this.cache && decision.related_files) {
|
|
481
|
+
for (const filePath of decision.related_files) {
|
|
482
|
+
this.cache.delete(`decisions:${filePath}`);
|
|
483
|
+
this.cache.delete(`timeline:${filePath}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
// Rebuild FTS index for full-text search
|
|
489
|
+
// FTS5 with external content requires explicit rebuild after inserts
|
|
490
|
+
// Skip during batch operations for performance (call rebuildAllFts() at end)
|
|
491
|
+
if (!skipFtsRebuild) {
|
|
492
|
+
this.rebuildDecisionsFts();
|
|
493
|
+
}
|
|
494
|
+
console.error(`✓ Stored ${decisions.length} decisions`);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Rebuild the decisions FTS index.
|
|
498
|
+
* Required for FTS5 external content tables after inserting data.
|
|
499
|
+
* Call this after batch operations that used skipFtsRebuild=true.
|
|
500
|
+
*/
|
|
501
|
+
rebuildDecisionsFts() {
|
|
502
|
+
try {
|
|
503
|
+
this.db.getDatabase().exec("INSERT INTO decisions_fts(decisions_fts) VALUES('rebuild')");
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
// FTS rebuild may fail if table doesn't exist or schema mismatch
|
|
507
|
+
// Log but don't throw - FTS is optional fallback
|
|
508
|
+
console.error("FTS decisions rebuild warning:", error.message);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Rebuild all FTS indexes.
|
|
513
|
+
* Call this once after batch operations that used skipFtsRebuild=true.
|
|
514
|
+
*/
|
|
515
|
+
rebuildAllFts() {
|
|
516
|
+
this.rebuildMessagesFts();
|
|
517
|
+
this.rebuildDecisionsFts();
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Retrieve all decisions related to a specific file.
|
|
521
|
+
*
|
|
522
|
+
* @param filePath - Path to the file
|
|
523
|
+
* @returns Array of decisions that reference this file
|
|
524
|
+
* @internal
|
|
525
|
+
*/
|
|
526
|
+
getDecisionsForFile(filePath) {
|
|
527
|
+
const cacheKey = `decisions:${filePath}`;
|
|
528
|
+
// Check cache first
|
|
529
|
+
if (this.cache) {
|
|
530
|
+
const cached = this.cache.get(cacheKey);
|
|
531
|
+
if (cached !== undefined) {
|
|
532
|
+
return cached;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const sanitized = sanitizeForLike(filePath);
|
|
536
|
+
const rows = this.db
|
|
537
|
+
.prepare("SELECT * FROM decisions WHERE related_files LIKE ? ESCAPE '\\' ORDER BY timestamp DESC")
|
|
538
|
+
.all(`%"${sanitized}"%`);
|
|
539
|
+
const result = rows.map((row) => ({
|
|
540
|
+
...row,
|
|
541
|
+
alternatives_considered: safeJsonParse(row.alternatives_considered, []),
|
|
542
|
+
rejected_reasons: safeJsonParse(row.rejected_reasons, {}),
|
|
543
|
+
related_files: safeJsonParse(row.related_files, []),
|
|
544
|
+
related_commits: safeJsonParse(row.related_commits, []),
|
|
545
|
+
}));
|
|
546
|
+
// Cache the result
|
|
547
|
+
this.cache?.set(cacheKey, result);
|
|
548
|
+
return result;
|
|
549
|
+
}
|
|
550
|
+
// ==================== Git Commits ====================
|
|
551
|
+
/**
|
|
552
|
+
* Store git commit records linked to conversations.
|
|
553
|
+
*
|
|
554
|
+
* Links git commits to the conversations where they were made or discussed.
|
|
555
|
+
*
|
|
556
|
+
* @param commits - Array of git commit objects
|
|
557
|
+
* @returns Promise that resolves when stored
|
|
558
|
+
*/
|
|
559
|
+
async storeGitCommits(commits) {
|
|
560
|
+
const stmt = this.db.prepare(`
|
|
561
|
+
INSERT INTO git_commits
|
|
562
|
+
(hash, message, author, timestamp, branch, files_changed,
|
|
563
|
+
conversation_id, related_message_id, metadata)
|
|
564
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
565
|
+
ON CONFLICT(hash) DO UPDATE SET
|
|
566
|
+
message = excluded.message,
|
|
567
|
+
author = excluded.author,
|
|
568
|
+
timestamp = excluded.timestamp,
|
|
569
|
+
branch = excluded.branch,
|
|
570
|
+
files_changed = excluded.files_changed,
|
|
571
|
+
conversation_id = excluded.conversation_id,
|
|
572
|
+
related_message_id = excluded.related_message_id,
|
|
573
|
+
metadata = excluded.metadata
|
|
574
|
+
`);
|
|
575
|
+
this.db.transaction(() => {
|
|
576
|
+
for (const commit of commits) {
|
|
577
|
+
stmt.run(commit.hash, commit.message, commit.author || null, commit.timestamp, commit.branch || null, JSON.stringify(commit.files_changed), commit.conversation_id || null, commit.related_message_id || null, JSON.stringify(commit.metadata));
|
|
578
|
+
// Invalidate caches for affected files
|
|
579
|
+
if (this.cache && commit.files_changed) {
|
|
580
|
+
for (const filePath of commit.files_changed) {
|
|
581
|
+
this.cache.delete(`commits:${filePath}`);
|
|
582
|
+
this.cache.delete(`timeline:${filePath}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
console.error(`✓ Stored ${commits.length} git commits`);
|
|
588
|
+
}
|
|
589
|
+
getCommitsForFile(filePath) {
|
|
590
|
+
const cacheKey = `commits:${filePath}`;
|
|
591
|
+
// Check cache first
|
|
592
|
+
if (this.cache) {
|
|
593
|
+
const cached = this.cache.get(cacheKey);
|
|
594
|
+
if (cached !== undefined) {
|
|
595
|
+
return cached;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const sanitized = sanitizeForLike(filePath);
|
|
599
|
+
const rows = this.db
|
|
600
|
+
.prepare("SELECT * FROM git_commits WHERE files_changed LIKE ? ESCAPE '\\' ORDER BY timestamp DESC")
|
|
601
|
+
.all(`%"${sanitized}"%`);
|
|
602
|
+
const result = rows.map((row) => ({
|
|
603
|
+
...row,
|
|
604
|
+
files_changed: safeJsonParse(row.files_changed, []),
|
|
605
|
+
metadata: safeJsonParse(row.metadata, {}),
|
|
606
|
+
}));
|
|
607
|
+
// Cache the result
|
|
608
|
+
this.cache?.set(cacheKey, result);
|
|
609
|
+
return result;
|
|
610
|
+
}
|
|
611
|
+
// ==================== Mistakes ====================
|
|
612
|
+
/**
|
|
613
|
+
* Store extracted mistakes in the database.
|
|
614
|
+
*
|
|
615
|
+
* Mistakes include errors, bugs, and wrong approaches that were later corrected.
|
|
616
|
+
*
|
|
617
|
+
* @param mistakes - Array of mistake objects
|
|
618
|
+
* @returns Promise that resolves when stored
|
|
619
|
+
*/
|
|
620
|
+
async storeMistakes(mistakes) {
|
|
621
|
+
const stmt = this.db.prepare(`
|
|
622
|
+
INSERT INTO mistakes
|
|
623
|
+
(id, conversation_id, message_id, mistake_type, what_went_wrong,
|
|
624
|
+
correction, user_correction_message, files_affected, timestamp)
|
|
625
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
626
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
627
|
+
conversation_id = excluded.conversation_id,
|
|
628
|
+
message_id = excluded.message_id,
|
|
629
|
+
mistake_type = excluded.mistake_type,
|
|
630
|
+
what_went_wrong = excluded.what_went_wrong,
|
|
631
|
+
correction = excluded.correction,
|
|
632
|
+
user_correction_message = excluded.user_correction_message,
|
|
633
|
+
files_affected = excluded.files_affected,
|
|
634
|
+
timestamp = excluded.timestamp
|
|
635
|
+
`);
|
|
636
|
+
this.db.transaction(() => {
|
|
637
|
+
for (const mistake of mistakes) {
|
|
638
|
+
stmt.run(mistake.id, mistake.conversation_id, mistake.message_id, mistake.mistake_type, mistake.what_went_wrong, mistake.correction || null, mistake.user_correction_message || null, JSON.stringify(mistake.files_affected), mistake.timestamp);
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
console.error(`✓ Stored ${mistakes.length} mistakes`);
|
|
642
|
+
}
|
|
643
|
+
// ==================== Requirements ====================
|
|
644
|
+
/**
|
|
645
|
+
* Store extracted requirements in the database.
|
|
646
|
+
*
|
|
647
|
+
* Requirements include dependencies, constraints, and specifications for components.
|
|
648
|
+
*
|
|
649
|
+
* @param requirements - Array of requirement objects
|
|
650
|
+
* @returns Promise that resolves when stored
|
|
651
|
+
*/
|
|
652
|
+
async storeRequirements(requirements) {
|
|
653
|
+
const stmt = this.db.prepare(`
|
|
654
|
+
INSERT INTO requirements
|
|
655
|
+
(id, type, description, rationale, affects_components,
|
|
656
|
+
conversation_id, message_id, timestamp)
|
|
657
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
658
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
659
|
+
type = excluded.type,
|
|
660
|
+
description = excluded.description,
|
|
661
|
+
rationale = excluded.rationale,
|
|
662
|
+
affects_components = excluded.affects_components,
|
|
663
|
+
conversation_id = excluded.conversation_id,
|
|
664
|
+
message_id = excluded.message_id,
|
|
665
|
+
timestamp = excluded.timestamp
|
|
666
|
+
`);
|
|
667
|
+
this.db.transaction(() => {
|
|
668
|
+
for (const req of requirements) {
|
|
669
|
+
stmt.run(req.id, req.type, req.description, req.rationale || null, JSON.stringify(req.affects_components), req.conversation_id, req.message_id, req.timestamp);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
console.error(`✓ Stored ${requirements.length} requirements`);
|
|
673
|
+
}
|
|
674
|
+
// ==================== Validations ====================
|
|
675
|
+
/**
|
|
676
|
+
* Store validation records in the database.
|
|
677
|
+
*
|
|
678
|
+
* Validations capture test results and performance data from conversations.
|
|
679
|
+
*
|
|
680
|
+
* @param validations - Array of validation objects
|
|
681
|
+
* @returns Promise that resolves when stored
|
|
682
|
+
*/
|
|
683
|
+
async storeValidations(validations) {
|
|
684
|
+
const stmt = this.db.prepare(`
|
|
685
|
+
INSERT INTO validations
|
|
686
|
+
(id, conversation_id, what_was_tested, test_command, result,
|
|
687
|
+
performance_data, files_tested, timestamp)
|
|
688
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
689
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
690
|
+
conversation_id = excluded.conversation_id,
|
|
691
|
+
what_was_tested = excluded.what_was_tested,
|
|
692
|
+
test_command = excluded.test_command,
|
|
693
|
+
result = excluded.result,
|
|
694
|
+
performance_data = excluded.performance_data,
|
|
695
|
+
files_tested = excluded.files_tested,
|
|
696
|
+
timestamp = excluded.timestamp
|
|
697
|
+
`);
|
|
698
|
+
this.db.transaction(() => {
|
|
699
|
+
for (const val of validations) {
|
|
700
|
+
stmt.run(val.id, val.conversation_id, val.what_was_tested, val.test_command || null, val.result, val.performance_data ? JSON.stringify(val.performance_data) : null, JSON.stringify(val.files_tested), val.timestamp);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
console.error(`✓ Stored ${validations.length} validations`);
|
|
704
|
+
}
|
|
705
|
+
// ==================== Queries ====================
|
|
706
|
+
/**
|
|
707
|
+
* Get the complete timeline of changes to a file.
|
|
708
|
+
*
|
|
709
|
+
* Combines file edits, git commits, and related decisions into a single timeline.
|
|
710
|
+
* This is a key method used by tools like checkBeforeModify and getFileEvolution.
|
|
711
|
+
*
|
|
712
|
+
* @param filePath - Path to the file
|
|
713
|
+
* @returns Object containing:
|
|
714
|
+
* - `file_path`: The file path queried
|
|
715
|
+
* - `edits`: All file edit records
|
|
716
|
+
* - `commits`: All git commits affecting this file
|
|
717
|
+
* - `decisions`: All decisions related to this file
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* ```typescript
|
|
721
|
+
* const timeline = storage.getFileTimeline('src/index.ts');
|
|
722
|
+
* console.error(`${timeline.edits.length} edits`);
|
|
723
|
+
* console.error(`${timeline.commits.length} commits`);
|
|
724
|
+
* console.error(`${timeline.decisions.length} decisions`);
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
getFileTimeline(filePath) {
|
|
728
|
+
const cacheKey = `timeline:${filePath}`;
|
|
729
|
+
// Check cache first
|
|
730
|
+
if (this.cache) {
|
|
731
|
+
const cached = this.cache.get(cacheKey);
|
|
732
|
+
if (cached !== undefined) {
|
|
733
|
+
return cached;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// Combine file edits, commits, and decisions
|
|
737
|
+
const edits = this.getFileEdits(filePath);
|
|
738
|
+
const commits = this.getCommitsForFile(filePath);
|
|
739
|
+
const decisions = this.getDecisionsForFile(filePath);
|
|
740
|
+
const result = {
|
|
741
|
+
file_path: filePath,
|
|
742
|
+
edits,
|
|
743
|
+
commits,
|
|
744
|
+
decisions,
|
|
745
|
+
};
|
|
746
|
+
// Cache the result
|
|
747
|
+
this.cache?.set(cacheKey, result);
|
|
748
|
+
return result;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Get statistics about the indexed conversation data.
|
|
752
|
+
*
|
|
753
|
+
* Returns counts of all major entity types stored in the database.
|
|
754
|
+
* Used for displaying indexing results and system health checks.
|
|
755
|
+
*
|
|
756
|
+
* @returns Object containing counts for:
|
|
757
|
+
* - `conversations`: Total conversations indexed
|
|
758
|
+
* - `messages`: Total messages stored
|
|
759
|
+
* - `decisions`: Total decisions extracted
|
|
760
|
+
* - `mistakes`: Total mistakes documented
|
|
761
|
+
* - `git_commits`: Total git commits linked
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* ```typescript
|
|
765
|
+
* const stats = storage.getStats();
|
|
766
|
+
* console.error(`Indexed ${stats.conversations.count} conversations`);
|
|
767
|
+
* console.error(`Extracted ${stats.decisions.count} decisions`);
|
|
768
|
+
* console.error(`Linked ${stats.git_commits.count} commits`);
|
|
769
|
+
* ```
|
|
770
|
+
*/
|
|
771
|
+
getStats() {
|
|
772
|
+
const stats = {
|
|
773
|
+
conversations: this.db
|
|
774
|
+
.prepare("SELECT COUNT(*) as count FROM conversations")
|
|
775
|
+
.get(),
|
|
776
|
+
messages: this.db
|
|
777
|
+
.prepare("SELECT COUNT(*) as count FROM messages")
|
|
778
|
+
.get(),
|
|
779
|
+
decisions: this.db
|
|
780
|
+
.prepare("SELECT COUNT(*) as count FROM decisions")
|
|
781
|
+
.get(),
|
|
782
|
+
mistakes: this.db
|
|
783
|
+
.prepare("SELECT COUNT(*) as count FROM mistakes")
|
|
784
|
+
.get(),
|
|
785
|
+
git_commits: this.db
|
|
786
|
+
.prepare("SELECT COUNT(*) as count FROM git_commits")
|
|
787
|
+
.get(),
|
|
788
|
+
};
|
|
789
|
+
return stats;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
//# sourceMappingURL=ConversationStorage.js.map
|