claude-conversation-memory-mcp 1.5.0 → 1.6.4
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/dist/embeddings/VectorStore.d.ts +10 -0
- package/dist/embeddings/VectorStore.d.ts.map +1 -1
- package/dist/embeddings/VectorStore.js +76 -0
- package/dist/embeddings/VectorStore.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/parsers/CodexConversationParser.d.ts +1 -1
- package/dist/parsers/CodexConversationParser.d.ts.map +1 -1
- package/dist/parsers/CodexConversationParser.js +27 -5
- package/dist/parsers/CodexConversationParser.js.map +1 -1
- package/dist/parsers/ConversationParser.d.ts +18 -0
- package/dist/parsers/ConversationParser.d.ts.map +1 -1
- package/dist/parsers/ConversationParser.js +62 -1
- package/dist/parsers/ConversationParser.js.map +1 -1
- package/dist/search/SemanticSearch.d.ts +6 -2
- package/dist/search/SemanticSearch.d.ts.map +1 -1
- package/dist/search/SemanticSearch.js +78 -35
- package/dist/search/SemanticSearch.js.map +1 -1
- package/dist/storage/ConversationStorage.d.ts +10 -0
- package/dist/storage/ConversationStorage.d.ts.map +1 -1
- package/dist/storage/ConversationStorage.js +34 -0
- package/dist/storage/ConversationStorage.js.map +1 -1
- package/dist/storage/SQLiteManager.d.ts.map +1 -1
- package/dist/storage/SQLiteManager.js +87 -17
- package/dist/storage/SQLiteManager.js.map +1 -1
- package/dist/storage/migrations.d.ts.map +1 -1
- package/dist/storage/migrations.js +41 -8
- package/dist/storage/migrations.js.map +1 -1
- package/dist/storage/schema.sql +1 -1
- package/dist/tools/ToolDefinitions.d.ts +116 -0
- package/dist/tools/ToolDefinitions.d.ts.map +1 -1
- package/dist/tools/ToolDefinitions.js +126 -10
- package/dist/tools/ToolDefinitions.js.map +1 -1
- package/dist/tools/ToolHandlers.d.ts +21 -0
- package/dist/tools/ToolHandlers.d.ts.map +1 -1
- package/dist/tools/ToolHandlers.js +519 -119
- package/dist/tools/ToolHandlers.js.map +1 -1
- package/dist/types/ToolTypes.d.ts +46 -0
- package/dist/types/ToolTypes.d.ts.map +1 -1
- package/dist/utils/ProjectMigration.d.ts.map +1 -1
- package/dist/utils/ProjectMigration.js +4 -3
- package/dist/utils/ProjectMigration.js.map +1 -1
- package/dist/utils/sanitization.d.ts +10 -0
- package/dist/utils/sanitization.d.ts.map +1 -1
- package/dist/utils/sanitization.js +33 -0
- package/dist/utils/sanitization.js.map +1 -1
- package/package.json +1 -1
|
@@ -29,6 +29,32 @@ import { pathToProjectFolderName } from "../utils/sanitization.js";
|
|
|
29
29
|
import { DeletionService } from "../storage/DeletionService.js";
|
|
30
30
|
import { readdirSync } from "fs";
|
|
31
31
|
import { join } from "path";
|
|
32
|
+
/**
|
|
33
|
+
* Default similarity score used when semantic search is not available.
|
|
34
|
+
* This applies to SQL-based searches where semantic embeddings are not used.
|
|
35
|
+
*/
|
|
36
|
+
const DEFAULT_SIMILARITY_SCORE = 1.0;
|
|
37
|
+
/**
|
|
38
|
+
* Pagination Patterns:
|
|
39
|
+
*
|
|
40
|
+
* This codebase uses two different pagination patterns based on data source:
|
|
41
|
+
*
|
|
42
|
+
* 1. SQL-based pagination (fetch+1):
|
|
43
|
+
* - Fetch limit+1 records from database
|
|
44
|
+
* - hasMore = results.length > limit
|
|
45
|
+
* - Slice to limit if hasMore is true
|
|
46
|
+
* - Use case: Single-database SQL queries (searchMistakes, linkCommitsToConversations)
|
|
47
|
+
* - Advantage: Efficient, minimal data transfer
|
|
48
|
+
*
|
|
49
|
+
* 2. In-memory pagination (slice):
|
|
50
|
+
* - Fetch all needed results (or limit+offset)
|
|
51
|
+
* - Slice to get paginated subset: results.slice(offset, offset + limit)
|
|
52
|
+
* - has_more = offset + limit < results.length
|
|
53
|
+
* - Use case: Semantic search, cross-project aggregation
|
|
54
|
+
* - Advantage: Allows sorting/filtering before pagination
|
|
55
|
+
*
|
|
56
|
+
* Both patterns are correct and optimized for their respective use cases.
|
|
57
|
+
*/
|
|
32
58
|
/**
|
|
33
59
|
* Tool handlers for the conversation-memory MCP server.
|
|
34
60
|
*
|
|
@@ -167,15 +193,106 @@ export class ToolHandlers {
|
|
|
167
193
|
*/
|
|
168
194
|
async searchConversations(args) {
|
|
169
195
|
const typedArgs = args;
|
|
170
|
-
const { query, limit = 10, date_range } = typedArgs;
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
|
|
196
|
+
const { query, limit = 10, offset = 0, date_range, scope = 'all', conversation_id } = typedArgs;
|
|
197
|
+
// Handle global scope by delegating to searchAllConversations
|
|
198
|
+
if (scope === 'global') {
|
|
199
|
+
const { GlobalIndex } = await import("../storage/GlobalIndex.js");
|
|
200
|
+
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
201
|
+
const { SemanticSearch } = await import("../search/SemanticSearch.js");
|
|
202
|
+
const globalIndex = new GlobalIndex();
|
|
203
|
+
const projects = globalIndex.getAllProjects();
|
|
204
|
+
const allResults = [];
|
|
205
|
+
for (const project of projects) {
|
|
206
|
+
let projectDb = null;
|
|
207
|
+
try {
|
|
208
|
+
projectDb = new SQLiteManager({ dbPath: project.db_path, readOnly: true });
|
|
209
|
+
const semanticSearch = new SemanticSearch(projectDb);
|
|
210
|
+
const localResults = await semanticSearch.searchConversations(query, limit + offset);
|
|
211
|
+
const filteredResults = date_range
|
|
212
|
+
? localResults.filter((r) => {
|
|
213
|
+
const timestamp = r.message.timestamp;
|
|
214
|
+
return timestamp >= date_range[0] && timestamp <= date_range[1];
|
|
215
|
+
})
|
|
216
|
+
: localResults;
|
|
217
|
+
for (const result of filteredResults) {
|
|
218
|
+
allResults.push({
|
|
219
|
+
conversation_id: result.conversation.id,
|
|
220
|
+
message_id: result.message.id,
|
|
221
|
+
timestamp: new Date(result.message.timestamp).toISOString(),
|
|
222
|
+
similarity: result.similarity,
|
|
223
|
+
snippet: result.snippet,
|
|
224
|
+
git_branch: result.conversation.git_branch,
|
|
225
|
+
message_type: result.message.message_type,
|
|
226
|
+
role: result.message.role,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (_error) {
|
|
231
|
+
// Skip projects that fail to search (embedding errors, etc.)
|
|
232
|
+
// Continue to next project
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
if (projectDb) {
|
|
237
|
+
projectDb.close();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
allResults.sort((a, b) => b.similarity - a.similarity);
|
|
242
|
+
const paginatedResults = allResults.slice(offset, offset + limit);
|
|
243
|
+
return {
|
|
244
|
+
query,
|
|
245
|
+
results: paginatedResults,
|
|
246
|
+
total_found: paginatedResults.length,
|
|
247
|
+
has_more: offset + limit < allResults.length,
|
|
248
|
+
offset,
|
|
249
|
+
scope: 'global',
|
|
250
|
+
};
|
|
174
251
|
}
|
|
175
|
-
|
|
252
|
+
// Handle current session scope
|
|
253
|
+
if (scope === 'current') {
|
|
254
|
+
if (!conversation_id) {
|
|
255
|
+
throw new Error("conversation_id is required when scope='current'");
|
|
256
|
+
}
|
|
257
|
+
const results = await this.memory.search(query, limit + offset);
|
|
258
|
+
const filteredResults = results.filter(r => r.conversation.id === conversation_id);
|
|
259
|
+
const dateFilteredResults = date_range
|
|
260
|
+
? filteredResults.filter(r => {
|
|
261
|
+
const timestamp = r.message.timestamp;
|
|
262
|
+
return timestamp >= date_range[0] && timestamp <= date_range[1];
|
|
263
|
+
})
|
|
264
|
+
: filteredResults;
|
|
265
|
+
const paginatedResults = dateFilteredResults.slice(offset, offset + limit);
|
|
266
|
+
return {
|
|
267
|
+
query,
|
|
268
|
+
results: paginatedResults.map((r) => ({
|
|
269
|
+
conversation_id: r.conversation.id,
|
|
270
|
+
message_id: r.message.id,
|
|
271
|
+
timestamp: new Date(r.message.timestamp).toISOString(),
|
|
272
|
+
similarity: r.similarity,
|
|
273
|
+
snippet: r.snippet,
|
|
274
|
+
git_branch: r.conversation.git_branch,
|
|
275
|
+
message_type: r.message.message_type,
|
|
276
|
+
role: r.message.role,
|
|
277
|
+
})),
|
|
278
|
+
total_found: paginatedResults.length,
|
|
279
|
+
has_more: offset + limit < dateFilteredResults.length,
|
|
280
|
+
offset,
|
|
281
|
+
scope: 'current',
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
// Handle 'all' scope (default) - all sessions in current project
|
|
285
|
+
const results = await this.memory.search(query, limit + offset);
|
|
286
|
+
const filteredResults = date_range
|
|
287
|
+
? results.filter(r => {
|
|
288
|
+
const timestamp = r.message.timestamp;
|
|
289
|
+
return timestamp >= date_range[0] && timestamp <= date_range[1];
|
|
290
|
+
})
|
|
291
|
+
: results;
|
|
292
|
+
const paginatedResults = filteredResults.slice(offset, offset + limit);
|
|
176
293
|
return {
|
|
177
294
|
query,
|
|
178
|
-
results:
|
|
295
|
+
results: paginatedResults.map((r) => ({
|
|
179
296
|
conversation_id: r.conversation.id,
|
|
180
297
|
message_id: r.message.id,
|
|
181
298
|
timestamp: new Date(r.message.timestamp).toISOString(),
|
|
@@ -185,7 +302,10 @@ export class ToolHandlers {
|
|
|
185
302
|
message_type: r.message.message_type,
|
|
186
303
|
role: r.message.role,
|
|
187
304
|
})),
|
|
188
|
-
total_found:
|
|
305
|
+
total_found: paginatedResults.length,
|
|
306
|
+
has_more: offset + limit < filteredResults.length,
|
|
307
|
+
offset,
|
|
308
|
+
scope: 'all',
|
|
189
309
|
};
|
|
190
310
|
}
|
|
191
311
|
/**
|
|
@@ -231,17 +351,49 @@ export class ToolHandlers {
|
|
|
231
351
|
*/
|
|
232
352
|
async getDecisions(args) {
|
|
233
353
|
const typedArgs = args;
|
|
234
|
-
const { query, file_path, limit = 10 } = typedArgs;
|
|
235
|
-
|
|
354
|
+
const { query, file_path, limit = 10, offset = 0, scope = 'all', conversation_id } = typedArgs;
|
|
355
|
+
// Handle global scope
|
|
356
|
+
if (scope === 'global') {
|
|
357
|
+
const globalResponse = await this.getAllDecisions({ query, file_path, limit, offset, source_type: 'all' });
|
|
358
|
+
return {
|
|
359
|
+
query,
|
|
360
|
+
file_path,
|
|
361
|
+
decisions: globalResponse.decisions.map(d => ({
|
|
362
|
+
decision_id: d.decision_id,
|
|
363
|
+
decision_text: d.decision_text,
|
|
364
|
+
rationale: d.rationale,
|
|
365
|
+
alternatives_considered: d.alternatives_considered,
|
|
366
|
+
rejected_reasons: d.rejected_reasons,
|
|
367
|
+
context: d.context,
|
|
368
|
+
related_files: d.related_files,
|
|
369
|
+
related_commits: d.related_commits,
|
|
370
|
+
timestamp: d.timestamp,
|
|
371
|
+
similarity: d.similarity,
|
|
372
|
+
})),
|
|
373
|
+
total_found: globalResponse.total_found,
|
|
374
|
+
has_more: globalResponse.has_more,
|
|
375
|
+
offset: globalResponse.offset,
|
|
376
|
+
scope: 'global',
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const results = await this.memory.searchDecisions(query, limit + offset);
|
|
236
380
|
// Filter by file if specified
|
|
237
381
|
let filteredResults = results;
|
|
238
382
|
if (file_path) {
|
|
239
383
|
filteredResults = results.filter((r) => r.decision.related_files.includes(file_path));
|
|
240
384
|
}
|
|
385
|
+
// Filter by conversation_id if scope is 'current'
|
|
386
|
+
if (scope === 'current') {
|
|
387
|
+
if (!conversation_id) {
|
|
388
|
+
throw new Error("conversation_id is required when scope='current'");
|
|
389
|
+
}
|
|
390
|
+
filteredResults = filteredResults.filter((r) => r.decision.conversation_id === conversation_id);
|
|
391
|
+
}
|
|
392
|
+
const paginatedResults = filteredResults.slice(offset, offset + limit);
|
|
241
393
|
return {
|
|
242
394
|
query,
|
|
243
395
|
file_path,
|
|
244
|
-
decisions:
|
|
396
|
+
decisions: paginatedResults.map((r) => ({
|
|
245
397
|
decision_id: r.decision.id,
|
|
246
398
|
decision_text: r.decision.decision_text,
|
|
247
399
|
rationale: r.decision.rationale,
|
|
@@ -253,7 +405,10 @@ export class ToolHandlers {
|
|
|
253
405
|
timestamp: new Date(r.decision.timestamp).toISOString(),
|
|
254
406
|
similarity: r.similarity,
|
|
255
407
|
})),
|
|
256
|
-
total_found:
|
|
408
|
+
total_found: paginatedResults.length,
|
|
409
|
+
has_more: offset + limit < filteredResults.length,
|
|
410
|
+
offset,
|
|
411
|
+
scope,
|
|
257
412
|
};
|
|
258
413
|
}
|
|
259
414
|
/**
|
|
@@ -356,7 +511,7 @@ export class ToolHandlers {
|
|
|
356
511
|
*/
|
|
357
512
|
async getFileEvolution(args) {
|
|
358
513
|
const typedArgs = args;
|
|
359
|
-
const { file_path, include_decisions = true, include_commits = true } = typedArgs;
|
|
514
|
+
const { file_path, include_decisions = true, include_commits = true, limit = 50, offset = 0 } = typedArgs;
|
|
360
515
|
const timeline = this.memory.getFileTimeline(file_path);
|
|
361
516
|
const events = [];
|
|
362
517
|
timeline.edits.forEach((edit) => {
|
|
@@ -396,10 +551,13 @@ export class ToolHandlers {
|
|
|
396
551
|
}
|
|
397
552
|
// Sort by timestamp (descending - most recent first)
|
|
398
553
|
events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
554
|
+
// Apply pagination
|
|
555
|
+
const paginatedEvents = events.slice(offset, offset + limit);
|
|
399
556
|
return {
|
|
400
557
|
file_path,
|
|
401
558
|
total_edits: timeline.edits.length,
|
|
402
|
-
timeline:
|
|
559
|
+
timeline: paginatedEvents,
|
|
560
|
+
has_more: offset + limit < events.length,
|
|
403
561
|
};
|
|
404
562
|
}
|
|
405
563
|
/**
|
|
@@ -442,24 +600,35 @@ export class ToolHandlers {
|
|
|
442
600
|
*/
|
|
443
601
|
async linkCommitsToConversations(args) {
|
|
444
602
|
const typedArgs = args;
|
|
445
|
-
const { query, conversation_id, limit = 20 } = typedArgs;
|
|
603
|
+
const { query, conversation_id, limit = 20, offset = 0, scope = 'all' } = typedArgs;
|
|
604
|
+
// Global scope not supported for git commits (project-specific)
|
|
605
|
+
if (scope === 'global') {
|
|
606
|
+
throw new Error("Global scope is not supported for linkCommitsToConversations (git commits are project-specific)");
|
|
607
|
+
}
|
|
446
608
|
let sql = "SELECT * FROM git_commits WHERE 1=1";
|
|
447
609
|
const params = [];
|
|
448
|
-
if (conversation_id) {
|
|
610
|
+
if (conversation_id || scope === 'current') {
|
|
611
|
+
const targetId = conversation_id || typedArgs.conversation_id;
|
|
612
|
+
if (!targetId) {
|
|
613
|
+
throw new Error("conversation_id is required when scope='current'");
|
|
614
|
+
}
|
|
449
615
|
sql += " AND conversation_id = ?";
|
|
450
|
-
params.push(
|
|
616
|
+
params.push(targetId);
|
|
451
617
|
}
|
|
452
618
|
if (query) {
|
|
453
619
|
sql += " AND message LIKE ?";
|
|
454
620
|
params.push(`%${query}%`);
|
|
455
621
|
}
|
|
456
|
-
sql +=
|
|
457
|
-
params.push(limit);
|
|
622
|
+
sql += ` ORDER BY timestamp DESC LIMIT ? OFFSET ?`;
|
|
623
|
+
params.push(limit + 1); // Fetch one extra to determine has_more
|
|
624
|
+
params.push(offset);
|
|
458
625
|
const commits = this.db.prepare(sql).all(...params);
|
|
626
|
+
const hasMore = commits.length > limit;
|
|
627
|
+
const results = hasMore ? commits.slice(0, limit) : commits;
|
|
459
628
|
return {
|
|
460
629
|
query,
|
|
461
630
|
conversation_id,
|
|
462
|
-
commits:
|
|
631
|
+
commits: results.map((c) => ({
|
|
463
632
|
hash: c.hash.substring(0, 7),
|
|
464
633
|
full_hash: c.hash,
|
|
465
634
|
message: c.message,
|
|
@@ -469,7 +638,10 @@ export class ToolHandlers {
|
|
|
469
638
|
files_changed: JSON.parse(c.files_changed || "[]"),
|
|
470
639
|
conversation_id: c.conversation_id,
|
|
471
640
|
})),
|
|
472
|
-
total_found:
|
|
641
|
+
total_found: results.length,
|
|
642
|
+
has_more: hasMore,
|
|
643
|
+
offset,
|
|
644
|
+
scope,
|
|
473
645
|
};
|
|
474
646
|
}
|
|
475
647
|
/**
|
|
@@ -511,7 +683,28 @@ export class ToolHandlers {
|
|
|
511
683
|
*/
|
|
512
684
|
async searchMistakes(args) {
|
|
513
685
|
const typedArgs = args;
|
|
514
|
-
const { query, mistake_type, limit = 10 } = typedArgs;
|
|
686
|
+
const { query, mistake_type, limit = 10, offset = 0, scope = 'all', conversation_id } = typedArgs;
|
|
687
|
+
// Handle global scope
|
|
688
|
+
if (scope === 'global') {
|
|
689
|
+
const globalResponse = await this.searchAllMistakes({ query, mistake_type, limit, offset, source_type: 'all' });
|
|
690
|
+
return {
|
|
691
|
+
query,
|
|
692
|
+
mistake_type,
|
|
693
|
+
mistakes: globalResponse.mistakes.map(m => ({
|
|
694
|
+
mistake_id: m.mistake_id,
|
|
695
|
+
mistake_type: m.mistake_type,
|
|
696
|
+
what_went_wrong: m.what_went_wrong,
|
|
697
|
+
correction: m.correction,
|
|
698
|
+
user_correction_message: m.user_correction_message,
|
|
699
|
+
files_affected: m.files_affected,
|
|
700
|
+
timestamp: m.timestamp,
|
|
701
|
+
})),
|
|
702
|
+
total_found: globalResponse.total_found,
|
|
703
|
+
has_more: globalResponse.has_more,
|
|
704
|
+
offset: globalResponse.offset,
|
|
705
|
+
scope: 'global',
|
|
706
|
+
};
|
|
707
|
+
}
|
|
515
708
|
const sanitized = sanitizeForLike(query);
|
|
516
709
|
let sql = "SELECT * FROM mistakes WHERE what_went_wrong LIKE ? ESCAPE '\\'";
|
|
517
710
|
const params = [`%${sanitized}%`];
|
|
@@ -519,13 +712,24 @@ export class ToolHandlers {
|
|
|
519
712
|
sql += " AND mistake_type = ?";
|
|
520
713
|
params.push(mistake_type);
|
|
521
714
|
}
|
|
522
|
-
|
|
523
|
-
|
|
715
|
+
// Filter by conversation_id if scope is 'current'
|
|
716
|
+
if (scope === 'current') {
|
|
717
|
+
if (!conversation_id) {
|
|
718
|
+
throw new Error("conversation_id is required when scope='current'");
|
|
719
|
+
}
|
|
720
|
+
sql += " AND conversation_id = ?";
|
|
721
|
+
params.push(conversation_id);
|
|
722
|
+
}
|
|
723
|
+
sql += ` ORDER BY timestamp DESC LIMIT ? OFFSET ?`;
|
|
724
|
+
params.push(limit + 1); // Fetch one extra to determine has_more
|
|
725
|
+
params.push(offset);
|
|
524
726
|
const mistakes = this.db.prepare(sql).all(...params);
|
|
727
|
+
const hasMore = mistakes.length > limit;
|
|
728
|
+
const results = hasMore ? mistakes.slice(0, limit) : mistakes;
|
|
525
729
|
return {
|
|
526
730
|
query,
|
|
527
731
|
mistake_type,
|
|
528
|
-
mistakes:
|
|
732
|
+
mistakes: results.map((m) => ({
|
|
529
733
|
mistake_id: m.id,
|
|
530
734
|
mistake_type: m.mistake_type,
|
|
531
735
|
what_went_wrong: m.what_went_wrong,
|
|
@@ -534,7 +738,10 @@ export class ToolHandlers {
|
|
|
534
738
|
files_affected: JSON.parse(m.files_affected || "[]"),
|
|
535
739
|
timestamp: new Date(m.timestamp).toISOString(),
|
|
536
740
|
})),
|
|
537
|
-
total_found:
|
|
741
|
+
total_found: results.length,
|
|
742
|
+
has_more: hasMore,
|
|
743
|
+
offset,
|
|
744
|
+
scope,
|
|
538
745
|
};
|
|
539
746
|
}
|
|
540
747
|
/**
|
|
@@ -793,8 +1000,13 @@ export class ToolHandlers {
|
|
|
793
1000
|
*/
|
|
794
1001
|
async findSimilarSessions(args) {
|
|
795
1002
|
const typedArgs = args;
|
|
796
|
-
const { query, limit = 5 } = typedArgs;
|
|
797
|
-
|
|
1003
|
+
const { query, limit = 5, offset = 0, scope = 'all', conversation_id: _conversation_id } = typedArgs;
|
|
1004
|
+
// Note: scope='global' and scope='current' have limited usefulness for finding similar SESSIONS
|
|
1005
|
+
// but we implement them for API consistency
|
|
1006
|
+
if (scope === 'current') {
|
|
1007
|
+
throw new Error("scope='current' is not supported for findSimilarSessions (it finds sessions, not messages within a session)");
|
|
1008
|
+
}
|
|
1009
|
+
const results = await this.memory.search(query, (limit + offset) * 3); // Get more to group by conversation
|
|
798
1010
|
// Group by conversation
|
|
799
1011
|
const conversationMap = new Map();
|
|
800
1012
|
for (const result of results) {
|
|
@@ -819,13 +1031,16 @@ export class ToolHandlers {
|
|
|
819
1031
|
});
|
|
820
1032
|
}
|
|
821
1033
|
}
|
|
822
|
-
const
|
|
823
|
-
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
824
|
-
|
|
1034
|
+
const allSessions = Array.from(conversationMap.values())
|
|
1035
|
+
.sort((a, b) => b.relevance_score - a.relevance_score);
|
|
1036
|
+
const sessions = allSessions.slice(offset, offset + limit);
|
|
825
1037
|
return {
|
|
826
1038
|
query,
|
|
827
1039
|
sessions,
|
|
828
1040
|
total_found: sessions.length,
|
|
1041
|
+
has_more: offset + limit < allSessions.length,
|
|
1042
|
+
offset,
|
|
1043
|
+
scope,
|
|
829
1044
|
};
|
|
830
1045
|
}
|
|
831
1046
|
/**
|
|
@@ -869,20 +1084,24 @@ export class ToolHandlers {
|
|
|
869
1084
|
*/
|
|
870
1085
|
async recallAndApply(args) {
|
|
871
1086
|
const typedArgs = args;
|
|
872
|
-
const { query, context_types = ["conversations", "decisions", "mistakes", "file_changes", "commits"], file_path, date_range, limit = 5 } = typedArgs;
|
|
1087
|
+
const { query, context_types = ["conversations", "decisions", "mistakes", "file_changes", "commits"], file_path, date_range, limit = 5, offset = 0, scope = 'all', conversation_id } = typedArgs;
|
|
873
1088
|
const recalled = {};
|
|
874
1089
|
let totalItems = 0;
|
|
875
1090
|
const suggestions = [];
|
|
876
1091
|
// 1. Recall conversations if requested
|
|
877
1092
|
if (context_types.includes("conversations")) {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1093
|
+
// Use searchConversations with scope support
|
|
1094
|
+
const convResponse = await this.searchConversations({
|
|
1095
|
+
query,
|
|
1096
|
+
limit,
|
|
1097
|
+
offset,
|
|
1098
|
+
date_range,
|
|
1099
|
+
scope,
|
|
1100
|
+
conversation_id,
|
|
1101
|
+
});
|
|
1102
|
+
recalled.conversations = convResponse.results.map(result => ({
|
|
1103
|
+
session_id: result.conversation_id,
|
|
1104
|
+
timestamp: result.timestamp,
|
|
886
1105
|
snippet: result.snippet,
|
|
887
1106
|
relevance_score: result.similarity,
|
|
888
1107
|
}));
|
|
@@ -893,25 +1112,24 @@ export class ToolHandlers {
|
|
|
893
1112
|
}
|
|
894
1113
|
// 2. Recall decisions if requested
|
|
895
1114
|
if (context_types.includes("decisions")) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
type: d.context,
|
|
1115
|
+
// Use getDecisions with scope support
|
|
1116
|
+
const decisionsResponse = await this.getDecisions({
|
|
1117
|
+
query,
|
|
1118
|
+
file_path,
|
|
1119
|
+
limit,
|
|
1120
|
+
offset,
|
|
1121
|
+
scope,
|
|
1122
|
+
conversation_id,
|
|
1123
|
+
});
|
|
1124
|
+
recalled.decisions = decisionsResponse.decisions.map(d => ({
|
|
1125
|
+
decision_id: d.decision_id,
|
|
1126
|
+
type: d.context || 'unknown',
|
|
909
1127
|
description: d.decision_text,
|
|
910
1128
|
rationale: d.rationale || undefined,
|
|
911
|
-
alternatives: d.alternatives_considered
|
|
912
|
-
rejected_approaches:
|
|
913
|
-
affects_components:
|
|
914
|
-
timestamp:
|
|
1129
|
+
alternatives: d.alternatives_considered,
|
|
1130
|
+
rejected_approaches: Object.values(d.rejected_reasons),
|
|
1131
|
+
affects_components: d.related_files,
|
|
1132
|
+
timestamp: d.timestamp,
|
|
915
1133
|
}));
|
|
916
1134
|
totalItems += recalled.decisions.length;
|
|
917
1135
|
if (recalled.decisions.length > 0) {
|
|
@@ -920,25 +1138,23 @@ export class ToolHandlers {
|
|
|
920
1138
|
}
|
|
921
1139
|
// 3. Recall mistakes if requested
|
|
922
1140
|
if (context_types.includes("mistakes")) {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
recalled.mistakes = mistakes.map(m => ({
|
|
934
|
-
mistake_id: m.id,
|
|
1141
|
+
// Use searchMistakes with scope support
|
|
1142
|
+
const mistakesResponse = await this.searchMistakes({
|
|
1143
|
+
query,
|
|
1144
|
+
limit,
|
|
1145
|
+
offset,
|
|
1146
|
+
scope,
|
|
1147
|
+
conversation_id,
|
|
1148
|
+
});
|
|
1149
|
+
recalled.mistakes = mistakesResponse.mistakes.map(m => ({
|
|
1150
|
+
mistake_id: m.mistake_id,
|
|
935
1151
|
type: m.mistake_type,
|
|
936
1152
|
description: m.what_went_wrong,
|
|
937
1153
|
what_happened: m.what_went_wrong,
|
|
938
1154
|
how_fixed: m.correction || undefined,
|
|
939
1155
|
lesson_learned: m.user_correction_message || undefined,
|
|
940
|
-
files_affected:
|
|
941
|
-
timestamp:
|
|
1156
|
+
files_affected: m.files_affected,
|
|
1157
|
+
timestamp: m.timestamp,
|
|
942
1158
|
}));
|
|
943
1159
|
totalItems += recalled.mistakes.length;
|
|
944
1160
|
if (recalled.mistakes.length > 0) {
|
|
@@ -1425,7 +1641,7 @@ export class ToolHandlers {
|
|
|
1425
1641
|
const { join } = await import("path");
|
|
1426
1642
|
const { existsSync, readdirSync } = await import("fs");
|
|
1427
1643
|
const typedArgs = args;
|
|
1428
|
-
const { include_codex = true, include_claude_code = true, codex_path = join(homedir(), ".codex"), claude_projects_path = join(homedir(), ".claude", "projects"), } = typedArgs;
|
|
1644
|
+
const { include_codex = true, include_claude_code = true, codex_path = join(homedir(), ".codex"), claude_projects_path = join(homedir(), ".claude", "projects"), incremental = true, } = typedArgs;
|
|
1429
1645
|
const globalIndex = new GlobalIndex();
|
|
1430
1646
|
try {
|
|
1431
1647
|
const projects = [];
|
|
@@ -1440,13 +1656,24 @@ export class ToolHandlers {
|
|
|
1440
1656
|
const { CodexConversationParser } = await import("../parsers/CodexConversationParser.js");
|
|
1441
1657
|
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
1442
1658
|
const { ConversationStorage } = await import("../storage/ConversationStorage.js");
|
|
1659
|
+
const { SemanticSearch } = await import("../search/SemanticSearch.js");
|
|
1660
|
+
const { DecisionExtractor } = await import("../parsers/DecisionExtractor.js");
|
|
1661
|
+
const { MistakeExtractor } = await import("../parsers/MistakeExtractor.js");
|
|
1443
1662
|
// Create dedicated database for Codex
|
|
1444
1663
|
const codexDbPath = join(codex_path, ".codex-conversations-memory.db");
|
|
1445
1664
|
const codexDb = new SQLiteManager({ dbPath: codexDbPath });
|
|
1446
1665
|
const codexStorage = new ConversationStorage(codexDb);
|
|
1666
|
+
// Get last indexed time for incremental mode
|
|
1667
|
+
let codexLastIndexedMs;
|
|
1668
|
+
if (incremental) {
|
|
1669
|
+
const existingProject = globalIndex.getProject(codex_path);
|
|
1670
|
+
if (existingProject) {
|
|
1671
|
+
codexLastIndexedMs = existingProject.last_indexed;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1447
1674
|
// Parse Codex sessions
|
|
1448
1675
|
const parser = new CodexConversationParser();
|
|
1449
|
-
const parseResult = parser.parseSession(codex_path);
|
|
1676
|
+
const parseResult = parser.parseSession(codex_path, undefined, codexLastIndexedMs);
|
|
1450
1677
|
// Store all parsed data
|
|
1451
1678
|
await codexStorage.storeConversations(parseResult.conversations);
|
|
1452
1679
|
await codexStorage.storeMessages(parseResult.messages);
|
|
@@ -1454,6 +1681,25 @@ export class ToolHandlers {
|
|
|
1454
1681
|
await codexStorage.storeToolResults(parseResult.tool_results);
|
|
1455
1682
|
await codexStorage.storeFileEdits(parseResult.file_edits);
|
|
1456
1683
|
await codexStorage.storeThinkingBlocks(parseResult.thinking_blocks);
|
|
1684
|
+
// Extract and store decisions
|
|
1685
|
+
const decisionExtractor = new DecisionExtractor();
|
|
1686
|
+
const decisions = decisionExtractor.extractDecisions(parseResult.messages, parseResult.thinking_blocks);
|
|
1687
|
+
await codexStorage.storeDecisions(decisions);
|
|
1688
|
+
// Extract and store mistakes
|
|
1689
|
+
const mistakeExtractor = new MistakeExtractor();
|
|
1690
|
+
const mistakes = mistakeExtractor.extractMistakes(parseResult.messages, parseResult.tool_results);
|
|
1691
|
+
await codexStorage.storeMistakes(mistakes);
|
|
1692
|
+
// Generate embeddings for semantic search
|
|
1693
|
+
try {
|
|
1694
|
+
const semanticSearch = new SemanticSearch(codexDb);
|
|
1695
|
+
await semanticSearch.indexMessages(parseResult.messages, incremental);
|
|
1696
|
+
await semanticSearch.indexDecisions(decisions, incremental);
|
|
1697
|
+
console.log(`✓ Generated embeddings for Codex project`);
|
|
1698
|
+
}
|
|
1699
|
+
catch (embedError) {
|
|
1700
|
+
console.warn("⚠️ Embedding generation failed for Codex:", embedError.message);
|
|
1701
|
+
console.warn(" FTS fallback will be used for search");
|
|
1702
|
+
}
|
|
1457
1703
|
// Get stats from the database
|
|
1458
1704
|
const stats = codexDb.getDatabase()
|
|
1459
1705
|
.prepare("SELECT COUNT(*) as count FROM conversations")
|
|
@@ -1503,38 +1749,102 @@ export class ToolHandlers {
|
|
|
1503
1749
|
// Index Claude Code projects if requested
|
|
1504
1750
|
if (include_claude_code && existsSync(claude_projects_path)) {
|
|
1505
1751
|
try {
|
|
1752
|
+
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
1753
|
+
const { ConversationStorage } = await import("../storage/ConversationStorage.js");
|
|
1754
|
+
const { ConversationParser } = await import("../parsers/ConversationParser.js");
|
|
1755
|
+
const { DecisionExtractor } = await import("../parsers/DecisionExtractor.js");
|
|
1756
|
+
const { MistakeExtractor } = await import("../parsers/MistakeExtractor.js");
|
|
1757
|
+
const { statSync } = await import("fs");
|
|
1506
1758
|
const projectFolders = readdirSync(claude_projects_path);
|
|
1507
1759
|
for (const folder of projectFolders) {
|
|
1508
1760
|
const folderPath = join(claude_projects_path, folder);
|
|
1509
1761
|
try {
|
|
1510
|
-
//
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1762
|
+
// Skip if not a directory
|
|
1763
|
+
if (!statSync(folderPath).isDirectory()) {
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
// Create dedicated database for this Claude Code project
|
|
1767
|
+
const projectDbPath = join(folderPath, ".claude-conversations-memory.db");
|
|
1768
|
+
const projectDb = new SQLiteManager({ dbPath: projectDbPath });
|
|
1769
|
+
const projectStorage = new ConversationStorage(projectDb);
|
|
1770
|
+
// Get last indexed time for incremental mode
|
|
1771
|
+
let lastIndexedMs;
|
|
1772
|
+
if (incremental) {
|
|
1773
|
+
const existingProject = globalIndex.getProject(folderPath);
|
|
1774
|
+
if (existingProject) {
|
|
1775
|
+
lastIndexedMs = existingProject.last_indexed;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
// Parse Claude Code conversations directly from this folder
|
|
1779
|
+
const parser = new ConversationParser();
|
|
1780
|
+
const parseResult = parser.parseFromFolder(folderPath, undefined, lastIndexedMs);
|
|
1781
|
+
// Skip empty projects
|
|
1782
|
+
if (parseResult.messages.length === 0) {
|
|
1783
|
+
projectDb.close();
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1786
|
+
// Store all parsed data
|
|
1787
|
+
await projectStorage.storeConversations(parseResult.conversations);
|
|
1788
|
+
await projectStorage.storeMessages(parseResult.messages);
|
|
1789
|
+
await projectStorage.storeToolUses(parseResult.tool_uses);
|
|
1790
|
+
await projectStorage.storeToolResults(parseResult.tool_results);
|
|
1791
|
+
await projectStorage.storeFileEdits(parseResult.file_edits);
|
|
1792
|
+
await projectStorage.storeThinkingBlocks(parseResult.thinking_blocks);
|
|
1793
|
+
// Extract and store decisions
|
|
1794
|
+
const decisionExtractor = new DecisionExtractor();
|
|
1795
|
+
const decisions = decisionExtractor.extractDecisions(parseResult.messages, parseResult.thinking_blocks);
|
|
1796
|
+
await projectStorage.storeDecisions(decisions);
|
|
1797
|
+
// Extract and store mistakes
|
|
1798
|
+
const mistakeExtractor = new MistakeExtractor();
|
|
1799
|
+
const mistakes = mistakeExtractor.extractMistakes(parseResult.messages, parseResult.tool_results);
|
|
1800
|
+
await projectStorage.storeMistakes(mistakes);
|
|
1801
|
+
// Generate embeddings for semantic search
|
|
1802
|
+
try {
|
|
1803
|
+
const { SemanticSearch } = await import("../search/SemanticSearch.js");
|
|
1804
|
+
const semanticSearch = new SemanticSearch(projectDb);
|
|
1805
|
+
await semanticSearch.indexMessages(parseResult.messages, incremental);
|
|
1806
|
+
await semanticSearch.indexDecisions(decisions, incremental);
|
|
1807
|
+
console.log(`✓ Generated embeddings for project: ${folder}`);
|
|
1808
|
+
}
|
|
1809
|
+
catch (embedError) {
|
|
1810
|
+
console.warn(`⚠️ Embedding generation failed for ${folder}:`, embedError.message);
|
|
1811
|
+
console.warn(" FTS fallback will be used for search");
|
|
1537
1812
|
}
|
|
1813
|
+
// Get stats from the database
|
|
1814
|
+
const stats = projectDb.getDatabase()
|
|
1815
|
+
.prepare("SELECT COUNT(*) as count FROM conversations")
|
|
1816
|
+
.get();
|
|
1817
|
+
const messageStats = projectDb.getDatabase()
|
|
1818
|
+
.prepare("SELECT COUNT(*) as count FROM messages")
|
|
1819
|
+
.get();
|
|
1820
|
+
const decisionStats = projectDb.getDatabase()
|
|
1821
|
+
.prepare("SELECT COUNT(*) as count FROM decisions")
|
|
1822
|
+
.get();
|
|
1823
|
+
const mistakeStats = projectDb.getDatabase()
|
|
1824
|
+
.prepare("SELECT COUNT(*) as count FROM mistakes")
|
|
1825
|
+
.get();
|
|
1826
|
+
// Register in global index with the project-specific database path
|
|
1827
|
+
globalIndex.registerProject({
|
|
1828
|
+
project_path: folderPath,
|
|
1829
|
+
source_type: "claude-code",
|
|
1830
|
+
db_path: projectDbPath,
|
|
1831
|
+
message_count: messageStats.count,
|
|
1832
|
+
conversation_count: stats.count,
|
|
1833
|
+
decision_count: decisionStats.count,
|
|
1834
|
+
mistake_count: mistakeStats.count,
|
|
1835
|
+
});
|
|
1836
|
+
projects.push({
|
|
1837
|
+
project_path: folderPath,
|
|
1838
|
+
source_type: "claude-code",
|
|
1839
|
+
message_count: messageStats.count,
|
|
1840
|
+
conversation_count: stats.count,
|
|
1841
|
+
});
|
|
1842
|
+
totalMessages += messageStats.count;
|
|
1843
|
+
totalConversations += stats.count;
|
|
1844
|
+
totalDecisions += decisionStats.count;
|
|
1845
|
+
totalMistakes += mistakeStats.count;
|
|
1846
|
+
// Close the project database
|
|
1847
|
+
projectDb.close();
|
|
1538
1848
|
}
|
|
1539
1849
|
catch (error) {
|
|
1540
1850
|
errors.push({
|
|
@@ -1583,7 +1893,7 @@ export class ToolHandlers {
|
|
|
1583
1893
|
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
1584
1894
|
const { SemanticSearch } = await import("../search/SemanticSearch.js");
|
|
1585
1895
|
const typedArgs = args;
|
|
1586
|
-
const { query, limit = 20, date_range, source_type = "all" } = typedArgs;
|
|
1896
|
+
const { query, limit = 20, offset = 0, date_range, source_type = "all" } = typedArgs;
|
|
1587
1897
|
const globalIndex = new GlobalIndex();
|
|
1588
1898
|
try {
|
|
1589
1899
|
const projects = globalIndex.getAllProjects(source_type === "all" ? undefined : source_type);
|
|
@@ -1638,18 +1948,21 @@ export class ToolHandlers {
|
|
|
1638
1948
|
}
|
|
1639
1949
|
}
|
|
1640
1950
|
}
|
|
1641
|
-
// Sort by similarity and
|
|
1642
|
-
const sortedResults = allResults.sort((a, b) => b.similarity - a.similarity)
|
|
1951
|
+
// Sort by similarity and paginate
|
|
1952
|
+
const sortedResults = allResults.sort((a, b) => b.similarity - a.similarity);
|
|
1953
|
+
const paginatedResults = sortedResults.slice(offset, offset + limit);
|
|
1643
1954
|
return {
|
|
1644
1955
|
query,
|
|
1645
|
-
results:
|
|
1646
|
-
total_found:
|
|
1956
|
+
results: paginatedResults,
|
|
1957
|
+
total_found: paginatedResults.length,
|
|
1958
|
+
has_more: offset + limit < sortedResults.length,
|
|
1959
|
+
offset,
|
|
1647
1960
|
projects_searched: projects.length,
|
|
1648
1961
|
search_stats: {
|
|
1649
1962
|
claude_code_results: claudeCodeResults,
|
|
1650
1963
|
codex_results: codexResults,
|
|
1651
1964
|
},
|
|
1652
|
-
message: `Found ${
|
|
1965
|
+
message: `Found ${paginatedResults.length} result(s) across ${projects.length} project(s)`,
|
|
1653
1966
|
};
|
|
1654
1967
|
}
|
|
1655
1968
|
finally {
|
|
@@ -1665,19 +1978,64 @@ export class ToolHandlers {
|
|
|
1665
1978
|
*/
|
|
1666
1979
|
async getAllDecisions(args) {
|
|
1667
1980
|
const { GlobalIndex } = await import("../storage/GlobalIndex.js");
|
|
1981
|
+
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
1668
1982
|
const typedArgs = args;
|
|
1669
|
-
const { query, file_path
|
|
1983
|
+
const { query, file_path, limit = 20, offset = 0, source_type = 'all' } = typedArgs;
|
|
1670
1984
|
const globalIndex = new GlobalIndex();
|
|
1671
1985
|
try {
|
|
1672
|
-
const projects = globalIndex.getAllProjects();
|
|
1673
|
-
|
|
1674
|
-
|
|
1986
|
+
const projects = globalIndex.getAllProjects(source_type === "all" ? undefined : source_type);
|
|
1987
|
+
const allDecisions = [];
|
|
1988
|
+
for (const project of projects) {
|
|
1989
|
+
let projectDb = null;
|
|
1990
|
+
try {
|
|
1991
|
+
projectDb = new SQLiteManager({ dbPath: project.db_path, readOnly: true });
|
|
1992
|
+
const sanitized = sanitizeForLike(query);
|
|
1993
|
+
let sql = "SELECT * FROM decisions WHERE decision_text LIKE ? ESCAPE '\\\\'";
|
|
1994
|
+
const params = [`%${sanitized}%`];
|
|
1995
|
+
if (file_path) {
|
|
1996
|
+
sql += " AND related_files LIKE ?";
|
|
1997
|
+
params.push(`%${file_path}%`);
|
|
1998
|
+
}
|
|
1999
|
+
sql += " ORDER BY timestamp DESC LIMIT ?";
|
|
2000
|
+
params.push(limit + offset + 1);
|
|
2001
|
+
const decisions = projectDb.prepare(sql).all(...params);
|
|
2002
|
+
for (const d of decisions) {
|
|
2003
|
+
allDecisions.push({
|
|
2004
|
+
decision_id: d.id,
|
|
2005
|
+
decision_text: d.decision_text,
|
|
2006
|
+
rationale: d.rationale,
|
|
2007
|
+
alternatives_considered: JSON.parse(d.alternatives_considered || "[]"),
|
|
2008
|
+
rejected_reasons: JSON.parse(d.rejected_reasons || "{}"),
|
|
2009
|
+
context: d.context,
|
|
2010
|
+
related_files: JSON.parse(d.related_files || "[]"),
|
|
2011
|
+
related_commits: JSON.parse(d.related_commits || "[]"),
|
|
2012
|
+
timestamp: new Date(d.timestamp).toISOString(),
|
|
2013
|
+
similarity: DEFAULT_SIMILARITY_SCORE,
|
|
2014
|
+
project_path: project.project_path,
|
|
2015
|
+
source_type: project.source_type,
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
catch (_error) {
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
finally {
|
|
2023
|
+
if (projectDb) {
|
|
2024
|
+
projectDb.close();
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
// Sort by timestamp and paginate
|
|
2029
|
+
const sortedDecisions = allDecisions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
2030
|
+
const paginatedDecisions = sortedDecisions.slice(offset, offset + limit);
|
|
1675
2031
|
return {
|
|
1676
2032
|
query,
|
|
1677
|
-
decisions:
|
|
1678
|
-
total_found:
|
|
2033
|
+
decisions: paginatedDecisions,
|
|
2034
|
+
total_found: paginatedDecisions.length,
|
|
2035
|
+
has_more: offset + limit < sortedDecisions.length,
|
|
2036
|
+
offset,
|
|
1679
2037
|
projects_searched: projects.length,
|
|
1680
|
-
message: `
|
|
2038
|
+
message: `Found ${paginatedDecisions.length} decision(s) across ${projects.length} project(s)`,
|
|
1681
2039
|
};
|
|
1682
2040
|
}
|
|
1683
2041
|
finally {
|
|
@@ -1692,19 +2050,61 @@ export class ToolHandlers {
|
|
|
1692
2050
|
*/
|
|
1693
2051
|
async searchAllMistakes(args) {
|
|
1694
2052
|
const { GlobalIndex } = await import("../storage/GlobalIndex.js");
|
|
2053
|
+
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
1695
2054
|
const typedArgs = args;
|
|
1696
|
-
const { query, mistake_type
|
|
2055
|
+
const { query, mistake_type, limit = 20, offset = 0, source_type = 'all' } = typedArgs;
|
|
1697
2056
|
const globalIndex = new GlobalIndex();
|
|
1698
2057
|
try {
|
|
1699
|
-
const projects = globalIndex.getAllProjects();
|
|
1700
|
-
|
|
1701
|
-
|
|
2058
|
+
const projects = globalIndex.getAllProjects(source_type === "all" ? undefined : source_type);
|
|
2059
|
+
const allMistakes = [];
|
|
2060
|
+
for (const project of projects) {
|
|
2061
|
+
let projectDb = null;
|
|
2062
|
+
try {
|
|
2063
|
+
projectDb = new SQLiteManager({ dbPath: project.db_path, readOnly: true });
|
|
2064
|
+
const sanitized = sanitizeForLike(query);
|
|
2065
|
+
let sql = "SELECT * FROM mistakes WHERE what_went_wrong LIKE ? ESCAPE '\\\\'";
|
|
2066
|
+
const params = [`%${sanitized}%`];
|
|
2067
|
+
if (mistake_type) {
|
|
2068
|
+
sql += " AND mistake_type = ?";
|
|
2069
|
+
params.push(mistake_type);
|
|
2070
|
+
}
|
|
2071
|
+
sql += " ORDER BY timestamp DESC LIMIT ?";
|
|
2072
|
+
params.push(limit + offset + 1);
|
|
2073
|
+
const mistakes = projectDb.prepare(sql).all(...params);
|
|
2074
|
+
for (const m of mistakes) {
|
|
2075
|
+
allMistakes.push({
|
|
2076
|
+
mistake_id: m.id,
|
|
2077
|
+
mistake_type: m.mistake_type,
|
|
2078
|
+
what_went_wrong: m.what_went_wrong,
|
|
2079
|
+
correction: m.correction,
|
|
2080
|
+
user_correction_message: m.user_correction_message,
|
|
2081
|
+
files_affected: JSON.parse(m.files_affected || "[]"),
|
|
2082
|
+
timestamp: new Date(m.timestamp).toISOString(),
|
|
2083
|
+
project_path: project.project_path,
|
|
2084
|
+
source_type: project.source_type,
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
catch (_error) {
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
finally {
|
|
2092
|
+
if (projectDb) {
|
|
2093
|
+
projectDb.close();
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
// Sort by timestamp and paginate
|
|
2098
|
+
const sortedMistakes = allMistakes.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
2099
|
+
const paginatedMistakes = sortedMistakes.slice(offset, offset + limit);
|
|
1702
2100
|
return {
|
|
1703
2101
|
query,
|
|
1704
|
-
mistakes:
|
|
1705
|
-
total_found:
|
|
2102
|
+
mistakes: paginatedMistakes,
|
|
2103
|
+
total_found: paginatedMistakes.length,
|
|
2104
|
+
has_more: offset + limit < sortedMistakes.length,
|
|
2105
|
+
offset,
|
|
1706
2106
|
projects_searched: projects.length,
|
|
1707
|
-
message: `
|
|
2107
|
+
message: `Found ${paginatedMistakes.length} mistake(s) across ${projects.length} project(s)`,
|
|
1708
2108
|
};
|
|
1709
2109
|
}
|
|
1710
2110
|
finally {
|