claude-conversation-memory-mcp 1.5.0 → 1.5.1
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/tools/ToolDefinitions.d.ts +101 -0
- package/dist/tools/ToolDefinitions.d.ts.map +1 -1
- package/dist/tools/ToolDefinitions.js +110 -9
- 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 +386 -88
- package/dist/tools/ToolHandlers.js.map +1 -1
- package/dist/types/ToolTypes.d.ts +41 -0
- package/dist/types/ToolTypes.d.ts.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,101 @@ 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
|
+
finally {
|
|
231
|
+
if (projectDb) {
|
|
232
|
+
projectDb.close();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
allResults.sort((a, b) => b.similarity - a.similarity);
|
|
237
|
+
const paginatedResults = allResults.slice(offset, offset + limit);
|
|
238
|
+
return {
|
|
239
|
+
query,
|
|
240
|
+
results: paginatedResults,
|
|
241
|
+
total_found: paginatedResults.length,
|
|
242
|
+
has_more: offset + limit < allResults.length,
|
|
243
|
+
offset,
|
|
244
|
+
scope: 'global',
|
|
245
|
+
};
|
|
174
246
|
}
|
|
175
|
-
|
|
247
|
+
// Handle current session scope
|
|
248
|
+
if (scope === 'current') {
|
|
249
|
+
if (!conversation_id) {
|
|
250
|
+
throw new Error("conversation_id is required when scope='current'");
|
|
251
|
+
}
|
|
252
|
+
const results = await this.memory.search(query, limit + offset);
|
|
253
|
+
const filteredResults = results.filter(r => r.conversation.id === conversation_id);
|
|
254
|
+
const dateFilteredResults = date_range
|
|
255
|
+
? filteredResults.filter(r => {
|
|
256
|
+
const timestamp = r.message.timestamp;
|
|
257
|
+
return timestamp >= date_range[0] && timestamp <= date_range[1];
|
|
258
|
+
})
|
|
259
|
+
: filteredResults;
|
|
260
|
+
const paginatedResults = dateFilteredResults.slice(offset, offset + limit);
|
|
261
|
+
return {
|
|
262
|
+
query,
|
|
263
|
+
results: paginatedResults.map((r) => ({
|
|
264
|
+
conversation_id: r.conversation.id,
|
|
265
|
+
message_id: r.message.id,
|
|
266
|
+
timestamp: new Date(r.message.timestamp).toISOString(),
|
|
267
|
+
similarity: r.similarity,
|
|
268
|
+
snippet: r.snippet,
|
|
269
|
+
git_branch: r.conversation.git_branch,
|
|
270
|
+
message_type: r.message.message_type,
|
|
271
|
+
role: r.message.role,
|
|
272
|
+
})),
|
|
273
|
+
total_found: paginatedResults.length,
|
|
274
|
+
has_more: offset + limit < dateFilteredResults.length,
|
|
275
|
+
offset,
|
|
276
|
+
scope: 'current',
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// Handle 'all' scope (default) - all sessions in current project
|
|
280
|
+
const results = await this.memory.search(query, limit + offset);
|
|
281
|
+
const filteredResults = date_range
|
|
282
|
+
? results.filter(r => {
|
|
283
|
+
const timestamp = r.message.timestamp;
|
|
284
|
+
return timestamp >= date_range[0] && timestamp <= date_range[1];
|
|
285
|
+
})
|
|
286
|
+
: results;
|
|
287
|
+
const paginatedResults = filteredResults.slice(offset, offset + limit);
|
|
176
288
|
return {
|
|
177
289
|
query,
|
|
178
|
-
results:
|
|
290
|
+
results: paginatedResults.map((r) => ({
|
|
179
291
|
conversation_id: r.conversation.id,
|
|
180
292
|
message_id: r.message.id,
|
|
181
293
|
timestamp: new Date(r.message.timestamp).toISOString(),
|
|
@@ -185,7 +297,10 @@ export class ToolHandlers {
|
|
|
185
297
|
message_type: r.message.message_type,
|
|
186
298
|
role: r.message.role,
|
|
187
299
|
})),
|
|
188
|
-
total_found:
|
|
300
|
+
total_found: paginatedResults.length,
|
|
301
|
+
has_more: offset + limit < filteredResults.length,
|
|
302
|
+
offset,
|
|
303
|
+
scope: 'all',
|
|
189
304
|
};
|
|
190
305
|
}
|
|
191
306
|
/**
|
|
@@ -231,17 +346,49 @@ export class ToolHandlers {
|
|
|
231
346
|
*/
|
|
232
347
|
async getDecisions(args) {
|
|
233
348
|
const typedArgs = args;
|
|
234
|
-
const { query, file_path, limit = 10 } = typedArgs;
|
|
235
|
-
|
|
349
|
+
const { query, file_path, limit = 10, offset = 0, scope = 'all', conversation_id } = typedArgs;
|
|
350
|
+
// Handle global scope
|
|
351
|
+
if (scope === 'global') {
|
|
352
|
+
const globalResponse = await this.getAllDecisions({ query, file_path, limit, offset, source_type: 'all' });
|
|
353
|
+
return {
|
|
354
|
+
query,
|
|
355
|
+
file_path,
|
|
356
|
+
decisions: globalResponse.decisions.map(d => ({
|
|
357
|
+
decision_id: d.decision_id,
|
|
358
|
+
decision_text: d.decision_text,
|
|
359
|
+
rationale: d.rationale,
|
|
360
|
+
alternatives_considered: d.alternatives_considered,
|
|
361
|
+
rejected_reasons: d.rejected_reasons,
|
|
362
|
+
context: d.context,
|
|
363
|
+
related_files: d.related_files,
|
|
364
|
+
related_commits: d.related_commits,
|
|
365
|
+
timestamp: d.timestamp,
|
|
366
|
+
similarity: d.similarity,
|
|
367
|
+
})),
|
|
368
|
+
total_found: globalResponse.total_found,
|
|
369
|
+
has_more: globalResponse.has_more,
|
|
370
|
+
offset: globalResponse.offset,
|
|
371
|
+
scope: 'global',
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
const results = await this.memory.searchDecisions(query, limit + offset);
|
|
236
375
|
// Filter by file if specified
|
|
237
376
|
let filteredResults = results;
|
|
238
377
|
if (file_path) {
|
|
239
378
|
filteredResults = results.filter((r) => r.decision.related_files.includes(file_path));
|
|
240
379
|
}
|
|
380
|
+
// Filter by conversation_id if scope is 'current'
|
|
381
|
+
if (scope === 'current') {
|
|
382
|
+
if (!conversation_id) {
|
|
383
|
+
throw new Error("conversation_id is required when scope='current'");
|
|
384
|
+
}
|
|
385
|
+
filteredResults = filteredResults.filter((r) => r.decision.conversation_id === conversation_id);
|
|
386
|
+
}
|
|
387
|
+
const paginatedResults = filteredResults.slice(offset, offset + limit);
|
|
241
388
|
return {
|
|
242
389
|
query,
|
|
243
390
|
file_path,
|
|
244
|
-
decisions:
|
|
391
|
+
decisions: paginatedResults.map((r) => ({
|
|
245
392
|
decision_id: r.decision.id,
|
|
246
393
|
decision_text: r.decision.decision_text,
|
|
247
394
|
rationale: r.decision.rationale,
|
|
@@ -253,7 +400,10 @@ export class ToolHandlers {
|
|
|
253
400
|
timestamp: new Date(r.decision.timestamp).toISOString(),
|
|
254
401
|
similarity: r.similarity,
|
|
255
402
|
})),
|
|
256
|
-
total_found:
|
|
403
|
+
total_found: paginatedResults.length,
|
|
404
|
+
has_more: offset + limit < filteredResults.length,
|
|
405
|
+
offset,
|
|
406
|
+
scope,
|
|
257
407
|
};
|
|
258
408
|
}
|
|
259
409
|
/**
|
|
@@ -442,24 +592,35 @@ export class ToolHandlers {
|
|
|
442
592
|
*/
|
|
443
593
|
async linkCommitsToConversations(args) {
|
|
444
594
|
const typedArgs = args;
|
|
445
|
-
const { query, conversation_id, limit = 20 } = typedArgs;
|
|
595
|
+
const { query, conversation_id, limit = 20, offset = 0, scope = 'all' } = typedArgs;
|
|
596
|
+
// Global scope not supported for git commits (project-specific)
|
|
597
|
+
if (scope === 'global') {
|
|
598
|
+
throw new Error("Global scope is not supported for linkCommitsToConversations (git commits are project-specific)");
|
|
599
|
+
}
|
|
446
600
|
let sql = "SELECT * FROM git_commits WHERE 1=1";
|
|
447
601
|
const params = [];
|
|
448
|
-
if (conversation_id) {
|
|
602
|
+
if (conversation_id || scope === 'current') {
|
|
603
|
+
const targetId = conversation_id || typedArgs.conversation_id;
|
|
604
|
+
if (!targetId) {
|
|
605
|
+
throw new Error("conversation_id is required when scope='current'");
|
|
606
|
+
}
|
|
449
607
|
sql += " AND conversation_id = ?";
|
|
450
|
-
params.push(
|
|
608
|
+
params.push(targetId);
|
|
451
609
|
}
|
|
452
610
|
if (query) {
|
|
453
611
|
sql += " AND message LIKE ?";
|
|
454
612
|
params.push(`%${query}%`);
|
|
455
613
|
}
|
|
456
|
-
sql +=
|
|
457
|
-
params.push(limit);
|
|
614
|
+
sql += ` ORDER BY timestamp DESC LIMIT ? OFFSET ?`;
|
|
615
|
+
params.push(limit + 1); // Fetch one extra to determine has_more
|
|
616
|
+
params.push(offset);
|
|
458
617
|
const commits = this.db.prepare(sql).all(...params);
|
|
618
|
+
const hasMore = commits.length > limit;
|
|
619
|
+
const results = hasMore ? commits.slice(0, limit) : commits;
|
|
459
620
|
return {
|
|
460
621
|
query,
|
|
461
622
|
conversation_id,
|
|
462
|
-
commits:
|
|
623
|
+
commits: results.map((c) => ({
|
|
463
624
|
hash: c.hash.substring(0, 7),
|
|
464
625
|
full_hash: c.hash,
|
|
465
626
|
message: c.message,
|
|
@@ -469,7 +630,10 @@ export class ToolHandlers {
|
|
|
469
630
|
files_changed: JSON.parse(c.files_changed || "[]"),
|
|
470
631
|
conversation_id: c.conversation_id,
|
|
471
632
|
})),
|
|
472
|
-
total_found:
|
|
633
|
+
total_found: results.length,
|
|
634
|
+
has_more: hasMore,
|
|
635
|
+
offset,
|
|
636
|
+
scope,
|
|
473
637
|
};
|
|
474
638
|
}
|
|
475
639
|
/**
|
|
@@ -511,7 +675,28 @@ export class ToolHandlers {
|
|
|
511
675
|
*/
|
|
512
676
|
async searchMistakes(args) {
|
|
513
677
|
const typedArgs = args;
|
|
514
|
-
const { query, mistake_type, limit = 10 } = typedArgs;
|
|
678
|
+
const { query, mistake_type, limit = 10, offset = 0, scope = 'all', conversation_id } = typedArgs;
|
|
679
|
+
// Handle global scope
|
|
680
|
+
if (scope === 'global') {
|
|
681
|
+
const globalResponse = await this.searchAllMistakes({ query, mistake_type, limit, offset, source_type: 'all' });
|
|
682
|
+
return {
|
|
683
|
+
query,
|
|
684
|
+
mistake_type,
|
|
685
|
+
mistakes: globalResponse.mistakes.map(m => ({
|
|
686
|
+
mistake_id: m.mistake_id,
|
|
687
|
+
mistake_type: m.mistake_type,
|
|
688
|
+
what_went_wrong: m.what_went_wrong,
|
|
689
|
+
correction: m.correction,
|
|
690
|
+
user_correction_message: m.user_correction_message,
|
|
691
|
+
files_affected: m.files_affected,
|
|
692
|
+
timestamp: m.timestamp,
|
|
693
|
+
})),
|
|
694
|
+
total_found: globalResponse.total_found,
|
|
695
|
+
has_more: globalResponse.has_more,
|
|
696
|
+
offset: globalResponse.offset,
|
|
697
|
+
scope: 'global',
|
|
698
|
+
};
|
|
699
|
+
}
|
|
515
700
|
const sanitized = sanitizeForLike(query);
|
|
516
701
|
let sql = "SELECT * FROM mistakes WHERE what_went_wrong LIKE ? ESCAPE '\\'";
|
|
517
702
|
const params = [`%${sanitized}%`];
|
|
@@ -519,13 +704,24 @@ export class ToolHandlers {
|
|
|
519
704
|
sql += " AND mistake_type = ?";
|
|
520
705
|
params.push(mistake_type);
|
|
521
706
|
}
|
|
522
|
-
|
|
523
|
-
|
|
707
|
+
// Filter by conversation_id if scope is 'current'
|
|
708
|
+
if (scope === 'current') {
|
|
709
|
+
if (!conversation_id) {
|
|
710
|
+
throw new Error("conversation_id is required when scope='current'");
|
|
711
|
+
}
|
|
712
|
+
sql += " AND conversation_id = ?";
|
|
713
|
+
params.push(conversation_id);
|
|
714
|
+
}
|
|
715
|
+
sql += ` ORDER BY timestamp DESC LIMIT ? OFFSET ?`;
|
|
716
|
+
params.push(limit + 1); // Fetch one extra to determine has_more
|
|
717
|
+
params.push(offset);
|
|
524
718
|
const mistakes = this.db.prepare(sql).all(...params);
|
|
719
|
+
const hasMore = mistakes.length > limit;
|
|
720
|
+
const results = hasMore ? mistakes.slice(0, limit) : mistakes;
|
|
525
721
|
return {
|
|
526
722
|
query,
|
|
527
723
|
mistake_type,
|
|
528
|
-
mistakes:
|
|
724
|
+
mistakes: results.map((m) => ({
|
|
529
725
|
mistake_id: m.id,
|
|
530
726
|
mistake_type: m.mistake_type,
|
|
531
727
|
what_went_wrong: m.what_went_wrong,
|
|
@@ -534,7 +730,10 @@ export class ToolHandlers {
|
|
|
534
730
|
files_affected: JSON.parse(m.files_affected || "[]"),
|
|
535
731
|
timestamp: new Date(m.timestamp).toISOString(),
|
|
536
732
|
})),
|
|
537
|
-
total_found:
|
|
733
|
+
total_found: results.length,
|
|
734
|
+
has_more: hasMore,
|
|
735
|
+
offset,
|
|
736
|
+
scope,
|
|
538
737
|
};
|
|
539
738
|
}
|
|
540
739
|
/**
|
|
@@ -793,8 +992,13 @@ export class ToolHandlers {
|
|
|
793
992
|
*/
|
|
794
993
|
async findSimilarSessions(args) {
|
|
795
994
|
const typedArgs = args;
|
|
796
|
-
const { query, limit = 5 } = typedArgs;
|
|
797
|
-
|
|
995
|
+
const { query, limit = 5, offset = 0, scope = 'all', conversation_id: _conversation_id } = typedArgs;
|
|
996
|
+
// Note: scope='global' and scope='current' have limited usefulness for finding similar SESSIONS
|
|
997
|
+
// but we implement them for API consistency
|
|
998
|
+
if (scope === 'current') {
|
|
999
|
+
throw new Error("scope='current' is not supported for findSimilarSessions (it finds sessions, not messages within a session)");
|
|
1000
|
+
}
|
|
1001
|
+
const results = await this.memory.search(query, (limit + offset) * 3); // Get more to group by conversation
|
|
798
1002
|
// Group by conversation
|
|
799
1003
|
const conversationMap = new Map();
|
|
800
1004
|
for (const result of results) {
|
|
@@ -819,13 +1023,16 @@ export class ToolHandlers {
|
|
|
819
1023
|
});
|
|
820
1024
|
}
|
|
821
1025
|
}
|
|
822
|
-
const
|
|
823
|
-
.sort((a, b) => b.relevance_score - a.relevance_score)
|
|
824
|
-
|
|
1026
|
+
const allSessions = Array.from(conversationMap.values())
|
|
1027
|
+
.sort((a, b) => b.relevance_score - a.relevance_score);
|
|
1028
|
+
const sessions = allSessions.slice(offset, offset + limit);
|
|
825
1029
|
return {
|
|
826
1030
|
query,
|
|
827
1031
|
sessions,
|
|
828
1032
|
total_found: sessions.length,
|
|
1033
|
+
has_more: offset + limit < allSessions.length,
|
|
1034
|
+
offset,
|
|
1035
|
+
scope,
|
|
829
1036
|
};
|
|
830
1037
|
}
|
|
831
1038
|
/**
|
|
@@ -869,20 +1076,24 @@ export class ToolHandlers {
|
|
|
869
1076
|
*/
|
|
870
1077
|
async recallAndApply(args) {
|
|
871
1078
|
const typedArgs = args;
|
|
872
|
-
const { query, context_types = ["conversations", "decisions", "mistakes", "file_changes", "commits"], file_path, date_range, limit = 5 } = typedArgs;
|
|
1079
|
+
const { query, context_types = ["conversations", "decisions", "mistakes", "file_changes", "commits"], file_path, date_range, limit = 5, offset = 0, scope = 'all', conversation_id } = typedArgs;
|
|
873
1080
|
const recalled = {};
|
|
874
1081
|
let totalItems = 0;
|
|
875
1082
|
const suggestions = [];
|
|
876
1083
|
// 1. Recall conversations if requested
|
|
877
1084
|
if (context_types.includes("conversations")) {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1085
|
+
// Use searchConversations with scope support
|
|
1086
|
+
const convResponse = await this.searchConversations({
|
|
1087
|
+
query,
|
|
1088
|
+
limit,
|
|
1089
|
+
offset,
|
|
1090
|
+
date_range,
|
|
1091
|
+
scope,
|
|
1092
|
+
conversation_id,
|
|
1093
|
+
});
|
|
1094
|
+
recalled.conversations = convResponse.results.map(result => ({
|
|
1095
|
+
session_id: result.conversation_id,
|
|
1096
|
+
timestamp: result.timestamp,
|
|
886
1097
|
snippet: result.snippet,
|
|
887
1098
|
relevance_score: result.similarity,
|
|
888
1099
|
}));
|
|
@@ -893,25 +1104,24 @@ export class ToolHandlers {
|
|
|
893
1104
|
}
|
|
894
1105
|
// 2. Recall decisions if requested
|
|
895
1106
|
if (context_types.includes("decisions")) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
type: d.context,
|
|
1107
|
+
// Use getDecisions with scope support
|
|
1108
|
+
const decisionsResponse = await this.getDecisions({
|
|
1109
|
+
query,
|
|
1110
|
+
file_path,
|
|
1111
|
+
limit,
|
|
1112
|
+
offset,
|
|
1113
|
+
scope,
|
|
1114
|
+
conversation_id,
|
|
1115
|
+
});
|
|
1116
|
+
recalled.decisions = decisionsResponse.decisions.map(d => ({
|
|
1117
|
+
decision_id: d.decision_id,
|
|
1118
|
+
type: d.context || 'unknown',
|
|
909
1119
|
description: d.decision_text,
|
|
910
1120
|
rationale: d.rationale || undefined,
|
|
911
|
-
alternatives: d.alternatives_considered
|
|
912
|
-
rejected_approaches:
|
|
913
|
-
affects_components:
|
|
914
|
-
timestamp:
|
|
1121
|
+
alternatives: d.alternatives_considered,
|
|
1122
|
+
rejected_approaches: Object.values(d.rejected_reasons),
|
|
1123
|
+
affects_components: d.related_files,
|
|
1124
|
+
timestamp: d.timestamp,
|
|
915
1125
|
}));
|
|
916
1126
|
totalItems += recalled.decisions.length;
|
|
917
1127
|
if (recalled.decisions.length > 0) {
|
|
@@ -920,25 +1130,23 @@ export class ToolHandlers {
|
|
|
920
1130
|
}
|
|
921
1131
|
// 3. Recall mistakes if requested
|
|
922
1132
|
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,
|
|
1133
|
+
// Use searchMistakes with scope support
|
|
1134
|
+
const mistakesResponse = await this.searchMistakes({
|
|
1135
|
+
query,
|
|
1136
|
+
limit,
|
|
1137
|
+
offset,
|
|
1138
|
+
scope,
|
|
1139
|
+
conversation_id,
|
|
1140
|
+
});
|
|
1141
|
+
recalled.mistakes = mistakesResponse.mistakes.map(m => ({
|
|
1142
|
+
mistake_id: m.mistake_id,
|
|
935
1143
|
type: m.mistake_type,
|
|
936
1144
|
description: m.what_went_wrong,
|
|
937
1145
|
what_happened: m.what_went_wrong,
|
|
938
1146
|
how_fixed: m.correction || undefined,
|
|
939
1147
|
lesson_learned: m.user_correction_message || undefined,
|
|
940
|
-
files_affected:
|
|
941
|
-
timestamp:
|
|
1148
|
+
files_affected: m.files_affected,
|
|
1149
|
+
timestamp: m.timestamp,
|
|
942
1150
|
}));
|
|
943
1151
|
totalItems += recalled.mistakes.length;
|
|
944
1152
|
if (recalled.mistakes.length > 0) {
|
|
@@ -1583,7 +1791,7 @@ export class ToolHandlers {
|
|
|
1583
1791
|
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
1584
1792
|
const { SemanticSearch } = await import("../search/SemanticSearch.js");
|
|
1585
1793
|
const typedArgs = args;
|
|
1586
|
-
const { query, limit = 20, date_range, source_type = "all" } = typedArgs;
|
|
1794
|
+
const { query, limit = 20, offset = 0, date_range, source_type = "all" } = typedArgs;
|
|
1587
1795
|
const globalIndex = new GlobalIndex();
|
|
1588
1796
|
try {
|
|
1589
1797
|
const projects = globalIndex.getAllProjects(source_type === "all" ? undefined : source_type);
|
|
@@ -1638,18 +1846,21 @@ export class ToolHandlers {
|
|
|
1638
1846
|
}
|
|
1639
1847
|
}
|
|
1640
1848
|
}
|
|
1641
|
-
// Sort by similarity and
|
|
1642
|
-
const sortedResults = allResults.sort((a, b) => b.similarity - a.similarity)
|
|
1849
|
+
// Sort by similarity and paginate
|
|
1850
|
+
const sortedResults = allResults.sort((a, b) => b.similarity - a.similarity);
|
|
1851
|
+
const paginatedResults = sortedResults.slice(offset, offset + limit);
|
|
1643
1852
|
return {
|
|
1644
1853
|
query,
|
|
1645
|
-
results:
|
|
1646
|
-
total_found:
|
|
1854
|
+
results: paginatedResults,
|
|
1855
|
+
total_found: paginatedResults.length,
|
|
1856
|
+
has_more: offset + limit < sortedResults.length,
|
|
1857
|
+
offset,
|
|
1647
1858
|
projects_searched: projects.length,
|
|
1648
1859
|
search_stats: {
|
|
1649
1860
|
claude_code_results: claudeCodeResults,
|
|
1650
1861
|
codex_results: codexResults,
|
|
1651
1862
|
},
|
|
1652
|
-
message: `Found ${
|
|
1863
|
+
message: `Found ${paginatedResults.length} result(s) across ${projects.length} project(s)`,
|
|
1653
1864
|
};
|
|
1654
1865
|
}
|
|
1655
1866
|
finally {
|
|
@@ -1665,19 +1876,64 @@ export class ToolHandlers {
|
|
|
1665
1876
|
*/
|
|
1666
1877
|
async getAllDecisions(args) {
|
|
1667
1878
|
const { GlobalIndex } = await import("../storage/GlobalIndex.js");
|
|
1879
|
+
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
1668
1880
|
const typedArgs = args;
|
|
1669
|
-
const { query, file_path
|
|
1881
|
+
const { query, file_path, limit = 20, offset = 0, source_type = 'all' } = typedArgs;
|
|
1670
1882
|
const globalIndex = new GlobalIndex();
|
|
1671
1883
|
try {
|
|
1672
|
-
const projects = globalIndex.getAllProjects();
|
|
1673
|
-
|
|
1674
|
-
|
|
1884
|
+
const projects = globalIndex.getAllProjects(source_type === "all" ? undefined : source_type);
|
|
1885
|
+
const allDecisions = [];
|
|
1886
|
+
for (const project of projects) {
|
|
1887
|
+
let projectDb = null;
|
|
1888
|
+
try {
|
|
1889
|
+
projectDb = new SQLiteManager({ dbPath: project.db_path, readOnly: true });
|
|
1890
|
+
const sanitized = sanitizeForLike(query);
|
|
1891
|
+
let sql = "SELECT * FROM decisions WHERE decision_text LIKE ? ESCAPE '\\\\'";
|
|
1892
|
+
const params = [`%${sanitized}%`];
|
|
1893
|
+
if (file_path) {
|
|
1894
|
+
sql += " AND related_files LIKE ?";
|
|
1895
|
+
params.push(`%${file_path}%`);
|
|
1896
|
+
}
|
|
1897
|
+
sql += " ORDER BY timestamp DESC LIMIT ?";
|
|
1898
|
+
params.push(limit + offset + 1);
|
|
1899
|
+
const decisions = projectDb.prepare(sql).all(...params);
|
|
1900
|
+
for (const d of decisions) {
|
|
1901
|
+
allDecisions.push({
|
|
1902
|
+
decision_id: d.id,
|
|
1903
|
+
decision_text: d.decision_text,
|
|
1904
|
+
rationale: d.rationale,
|
|
1905
|
+
alternatives_considered: JSON.parse(d.alternatives_considered || "[]"),
|
|
1906
|
+
rejected_reasons: JSON.parse(d.rejected_reasons || "{}"),
|
|
1907
|
+
context: d.context,
|
|
1908
|
+
related_files: JSON.parse(d.related_files || "[]"),
|
|
1909
|
+
related_commits: JSON.parse(d.related_commits || "[]"),
|
|
1910
|
+
timestamp: new Date(d.timestamp).toISOString(),
|
|
1911
|
+
similarity: DEFAULT_SIMILARITY_SCORE,
|
|
1912
|
+
project_path: project.project_path,
|
|
1913
|
+
source_type: project.source_type,
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
catch (_error) {
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
finally {
|
|
1921
|
+
if (projectDb) {
|
|
1922
|
+
projectDb.close();
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
// Sort by timestamp and paginate
|
|
1927
|
+
const sortedDecisions = allDecisions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
1928
|
+
const paginatedDecisions = sortedDecisions.slice(offset, offset + limit);
|
|
1675
1929
|
return {
|
|
1676
1930
|
query,
|
|
1677
|
-
decisions:
|
|
1678
|
-
total_found:
|
|
1931
|
+
decisions: paginatedDecisions,
|
|
1932
|
+
total_found: paginatedDecisions.length,
|
|
1933
|
+
has_more: offset + limit < sortedDecisions.length,
|
|
1934
|
+
offset,
|
|
1679
1935
|
projects_searched: projects.length,
|
|
1680
|
-
message: `
|
|
1936
|
+
message: `Found ${paginatedDecisions.length} decision(s) across ${projects.length} project(s)`,
|
|
1681
1937
|
};
|
|
1682
1938
|
}
|
|
1683
1939
|
finally {
|
|
@@ -1692,19 +1948,61 @@ export class ToolHandlers {
|
|
|
1692
1948
|
*/
|
|
1693
1949
|
async searchAllMistakes(args) {
|
|
1694
1950
|
const { GlobalIndex } = await import("../storage/GlobalIndex.js");
|
|
1951
|
+
const { SQLiteManager } = await import("../storage/SQLiteManager.js");
|
|
1695
1952
|
const typedArgs = args;
|
|
1696
|
-
const { query, mistake_type
|
|
1953
|
+
const { query, mistake_type, limit = 20, offset = 0, source_type = 'all' } = typedArgs;
|
|
1697
1954
|
const globalIndex = new GlobalIndex();
|
|
1698
1955
|
try {
|
|
1699
|
-
const projects = globalIndex.getAllProjects();
|
|
1700
|
-
|
|
1701
|
-
|
|
1956
|
+
const projects = globalIndex.getAllProjects(source_type === "all" ? undefined : source_type);
|
|
1957
|
+
const allMistakes = [];
|
|
1958
|
+
for (const project of projects) {
|
|
1959
|
+
let projectDb = null;
|
|
1960
|
+
try {
|
|
1961
|
+
projectDb = new SQLiteManager({ dbPath: project.db_path, readOnly: true });
|
|
1962
|
+
const sanitized = sanitizeForLike(query);
|
|
1963
|
+
let sql = "SELECT * FROM mistakes WHERE what_went_wrong LIKE ? ESCAPE '\\\\'";
|
|
1964
|
+
const params = [`%${sanitized}%`];
|
|
1965
|
+
if (mistake_type) {
|
|
1966
|
+
sql += " AND mistake_type = ?";
|
|
1967
|
+
params.push(mistake_type);
|
|
1968
|
+
}
|
|
1969
|
+
sql += " ORDER BY timestamp DESC LIMIT ?";
|
|
1970
|
+
params.push(limit + offset + 1);
|
|
1971
|
+
const mistakes = projectDb.prepare(sql).all(...params);
|
|
1972
|
+
for (const m of mistakes) {
|
|
1973
|
+
allMistakes.push({
|
|
1974
|
+
mistake_id: m.id,
|
|
1975
|
+
mistake_type: m.mistake_type,
|
|
1976
|
+
what_went_wrong: m.what_went_wrong,
|
|
1977
|
+
correction: m.correction,
|
|
1978
|
+
user_correction_message: m.user_correction_message,
|
|
1979
|
+
files_affected: JSON.parse(m.files_affected || "[]"),
|
|
1980
|
+
timestamp: new Date(m.timestamp).toISOString(),
|
|
1981
|
+
project_path: project.project_path,
|
|
1982
|
+
source_type: project.source_type,
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
catch (_error) {
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
finally {
|
|
1990
|
+
if (projectDb) {
|
|
1991
|
+
projectDb.close();
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
// Sort by timestamp and paginate
|
|
1996
|
+
const sortedMistakes = allMistakes.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
1997
|
+
const paginatedMistakes = sortedMistakes.slice(offset, offset + limit);
|
|
1702
1998
|
return {
|
|
1703
1999
|
query,
|
|
1704
|
-
mistakes:
|
|
1705
|
-
total_found:
|
|
2000
|
+
mistakes: paginatedMistakes,
|
|
2001
|
+
total_found: paginatedMistakes.length,
|
|
2002
|
+
has_more: offset + limit < sortedMistakes.length,
|
|
2003
|
+
offset,
|
|
1706
2004
|
projects_searched: projects.length,
|
|
1707
|
-
message: `
|
|
2005
|
+
message: `Found ${paginatedMistakes.length} mistake(s) across ${projects.length} project(s)`,
|
|
1708
2006
|
};
|
|
1709
2007
|
}
|
|
1710
2008
|
finally {
|