claude-conversation-memory-mcp 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +230 -31
  2. package/dist/mcp-server.d.ts.map +1 -1
  3. package/dist/mcp-server.js +13 -1
  4. package/dist/mcp-server.js.map +1 -1
  5. package/dist/parsers/CodexConversationParser.d.ts +51 -0
  6. package/dist/parsers/CodexConversationParser.d.ts.map +1 -0
  7. package/dist/parsers/CodexConversationParser.js +279 -0
  8. package/dist/parsers/CodexConversationParser.js.map +1 -0
  9. package/dist/parsers/ConversationParser.d.ts +1 -0
  10. package/dist/parsers/ConversationParser.d.ts.map +1 -1
  11. package/dist/parsers/ConversationParser.js.map +1 -1
  12. package/dist/storage/ConversationStorage.d.ts.map +1 -1
  13. package/dist/storage/ConversationStorage.js +3 -3
  14. package/dist/storage/ConversationStorage.js.map +1 -1
  15. package/dist/storage/GlobalIndex.d.ts +125 -0
  16. package/dist/storage/GlobalIndex.d.ts.map +1 -0
  17. package/dist/storage/GlobalIndex.js +243 -0
  18. package/dist/storage/GlobalIndex.js.map +1 -0
  19. package/dist/storage/SQLiteManager.d.ts +4 -0
  20. package/dist/storage/SQLiteManager.d.ts.map +1 -1
  21. package/dist/storage/SQLiteManager.js +36 -2
  22. package/dist/storage/SQLiteManager.js.map +1 -1
  23. package/dist/storage/schema.sql +26 -0
  24. package/dist/tools/ToolDefinitions.d.ts +150 -0
  25. package/dist/tools/ToolDefinitions.d.ts.map +1 -1
  26. package/dist/tools/ToolDefinitions.js +152 -3
  27. package/dist/tools/ToolDefinitions.js.map +1 -1
  28. package/dist/tools/ToolHandlers.d.ts +58 -17
  29. package/dist/tools/ToolHandlers.d.ts.map +1 -1
  30. package/dist/tools/ToolHandlers.js +422 -40
  31. package/dist/tools/ToolHandlers.js.map +1 -1
  32. package/dist/types/ToolTypes.d.ts +95 -0
  33. package/dist/types/ToolTypes.d.ts.map +1 -1
  34. package/package.json +2 -1
  35. package/scripts/changelog-check.sh +62 -0
@@ -598,7 +598,7 @@ export class ToolHandlers {
598
598
  };
599
599
  }
600
600
  /**
601
- * Query history of tool uses (bash commands, file edits, reads, etc.).
601
+ * Query history of tool uses (bash commands, file edits, reads, etc.) with pagination and filtering.
602
602
  *
603
603
  * Shows what tools were used during conversations and their results. Useful
604
604
  * for understanding what commands were run, what files were edited, and
@@ -608,72 +608,152 @@ export class ToolHandlers {
608
608
  * - `tool_name`: Optional filter by tool name (Bash, Edit, Write, Read)
609
609
  * - `file_path`: Optional filter by file path
610
610
  * - `limit`: Maximum number of results (default: 20)
611
+ * - `offset`: Skip N results for pagination (default: 0)
612
+ * - `include_content`: Include tool content in response (default: true, false for metadata only)
613
+ * - `max_content_length`: Maximum characters per content field (default: 500)
614
+ * - `date_range`: Filter by timestamp range [start, end]
615
+ * - `conversation_id`: Filter by specific conversation
616
+ * - `errors_only`: Show only failed tool uses (default: false)
611
617
  *
612
618
  * @returns Tool history containing:
613
619
  * - `tool_name`: Tool filter if applied
614
620
  * - `file_path`: File filter if applied
615
- * - `tool_uses`: Array of tool uses with:
616
- * - `tool_use_id`: Tool use identifier
617
- * - `tool_name`: Name of the tool used
618
- * - `tool_input`: Input parameters to the tool
619
- * - `result`: Tool execution result with:
620
- * - `content`: Result content
621
- * - `is_error`: Whether the tool failed
622
- * - `stdout`: Standard output (for Bash)
623
- * - `stderr`: Standard error (for Bash)
624
- * - `timestamp`: When the tool was used
625
- * - `total_found`: Number of tool uses returned
621
+ * - `tool_uses`: Array of tool uses (may have truncated content)
622
+ * - `total_found`: Number of results returned in this page
623
+ * - `total_in_database`: Total matching records in database
624
+ * - `has_more`: Whether more results exist beyond current page
625
+ * - `offset`: Current offset position
626
626
  *
627
627
  * @example
628
628
  * ```typescript
629
- * const history = await handlers.getToolHistory({
629
+ * // Get first page of Bash commands
630
+ * const page1 = await handlers.getToolHistory({
630
631
  * tool_name: 'Bash',
631
- * limit: 10
632
+ * limit: 20,
633
+ * offset: 0
632
634
  * });
633
- * history.tool_uses.forEach(t => {
634
- * console.log(`${t.tool_name}: ${JSON.stringify(t.tool_input)}`);
635
- * console.log(`Success: ${!t.result.is_error}`);
635
+ *
636
+ * // Get metadata only (no content)
637
+ * const metadata = await handlers.getToolHistory({
638
+ * include_content: false,
639
+ * limit: 50
640
+ * });
641
+ *
642
+ * // Get errors from last 24 hours
643
+ * const errors = await handlers.getToolHistory({
644
+ * errors_only: true,
645
+ * date_range: [Date.now() - 86400000, Date.now()]
636
646
  * });
637
647
  * ```
638
648
  */
639
649
  async getToolHistory(args) {
640
650
  const typedArgs = args;
641
- const { tool_name, file_path, limit = 20 } = typedArgs;
642
- let sql = `
643
- SELECT tu.*, tr.content as result_content, tr.is_error, tr.stdout, tr.stderr
644
- FROM tool_uses tu
645
- LEFT JOIN tool_results tr ON tu.id = tr.tool_use_id
646
- WHERE 1=1
647
- `;
651
+ const { tool_name, file_path, limit = 20, offset = 0, include_content = true, max_content_length = 500, date_range, conversation_id, errors_only = false, } = typedArgs;
652
+ // Helper function to truncate text with indicator
653
+ const truncateText = (text, maxLength) => {
654
+ if (!text) {
655
+ return { value: undefined, truncated: false };
656
+ }
657
+ if (text.length <= maxLength) {
658
+ return { value: text, truncated: false };
659
+ }
660
+ return {
661
+ value: text.substring(0, maxLength) + '... (truncated)',
662
+ truncated: true,
663
+ };
664
+ };
665
+ // Build WHERE clause for filters
666
+ let whereClause = "WHERE 1=1";
648
667
  const params = [];
649
668
  if (tool_name) {
650
- sql += " AND tu.tool_name = ?";
669
+ whereClause += " AND tu.tool_name = ?";
651
670
  params.push(tool_name);
652
671
  }
653
672
  if (file_path) {
654
673
  const sanitized = sanitizeForLike(file_path);
655
- sql += " AND tu.tool_input LIKE ? ESCAPE '\\'";
674
+ whereClause += " AND tu.tool_input LIKE ? ESCAPE '\\'";
656
675
  params.push(`%${sanitized}%`);
657
676
  }
658
- sql += " ORDER BY tu.timestamp DESC LIMIT ?";
659
- params.push(limit);
660
- const toolUses = this.db.prepare(sql).all(...params);
677
+ if (date_range && date_range.length === 2) {
678
+ whereClause += " AND tu.timestamp BETWEEN ? AND ?";
679
+ params.push(date_range[0], date_range[1]);
680
+ }
681
+ if (conversation_id) {
682
+ whereClause += " AND tu.message_id IN (SELECT id FROM messages WHERE conversation_id = ?)";
683
+ params.push(conversation_id);
684
+ }
685
+ if (errors_only) {
686
+ whereClause += " AND tr.is_error = 1";
687
+ }
688
+ // Get total count of matching records
689
+ const countSql = `
690
+ SELECT COUNT(*) as total
691
+ FROM tool_uses tu
692
+ LEFT JOIN tool_results tr ON tu.id = tr.tool_use_id
693
+ ${whereClause}
694
+ `;
695
+ const countResult = this.db.prepare(countSql).get(...params);
696
+ const totalInDatabase = countResult.total;
697
+ // Get paginated results
698
+ const sql = `
699
+ SELECT tu.*, tr.content as result_content, tr.is_error, tr.stdout, tr.stderr
700
+ FROM tool_uses tu
701
+ LEFT JOIN tool_results tr ON tu.id = tr.tool_use_id
702
+ ${whereClause}
703
+ ORDER BY tu.timestamp DESC
704
+ LIMIT ? OFFSET ?
705
+ `;
706
+ const queryParams = [...params, limit, offset];
707
+ const toolUses = this.db.prepare(sql).all(...queryParams);
708
+ // Calculate pagination metadata
709
+ const hasMore = offset + toolUses.length < totalInDatabase;
661
710
  return {
662
711
  tool_name,
663
712
  file_path,
664
- tool_uses: toolUses.map((t) => ({
665
- tool_use_id: t.id,
666
- tool_name: t.tool_name,
667
- tool_input: JSON.parse(t.tool_input || "{}"),
668
- result: {
669
- content: t.result_content,
713
+ tool_uses: toolUses.map((t) => {
714
+ // Parse tool input
715
+ const toolInput = JSON.parse(t.tool_input || "{}");
716
+ // Build result object based on include_content setting
717
+ const result = {
670
718
  is_error: Boolean(t.is_error),
671
- stdout: t.stdout,
672
- stderr: t.stderr,
673
- },
674
- timestamp: new Date(t.timestamp).toISOString(),
675
- })),
719
+ };
720
+ if (include_content) {
721
+ // Truncate content fields if they exist
722
+ const contentTrunc = truncateText(t.result_content, max_content_length);
723
+ const stdoutTrunc = truncateText(t.stdout, max_content_length);
724
+ const stderrTrunc = truncateText(t.stderr, max_content_length);
725
+ if (contentTrunc.value !== undefined) {
726
+ result.content = contentTrunc.value;
727
+ if (contentTrunc.truncated) {
728
+ result.content_truncated = true;
729
+ }
730
+ }
731
+ if (stdoutTrunc.value !== undefined) {
732
+ result.stdout = stdoutTrunc.value;
733
+ if (stdoutTrunc.truncated) {
734
+ result.stdout_truncated = true;
735
+ }
736
+ }
737
+ if (stderrTrunc.value !== undefined) {
738
+ result.stderr = stderrTrunc.value;
739
+ if (stderrTrunc.truncated) {
740
+ result.stderr_truncated = true;
741
+ }
742
+ }
743
+ }
744
+ // If include_content=false, only return is_error (no content, stdout, stderr)
745
+ return {
746
+ tool_use_id: t.id,
747
+ tool_name: t.tool_name,
748
+ tool_input: toolInput,
749
+ result,
750
+ timestamp: new Date(t.timestamp).toISOString(),
751
+ };
752
+ }),
676
753
  total_found: toolUses.length,
754
+ total_in_database: totalInDatabase,
755
+ has_more: hasMore,
756
+ offset,
677
757
  };
678
758
  }
679
759
  /**
@@ -1329,5 +1409,307 @@ export class ToolHandlers {
1329
1409
  };
1330
1410
  }
1331
1411
  }
1412
+ // ==================== Global Cross-Project Tools ====================
1413
+ /**
1414
+ * Index all projects (Claude Code + Codex).
1415
+ *
1416
+ * Discovers and indexes all projects from both Claude Code and Codex,
1417
+ * registering them in a global index for cross-project search.
1418
+ *
1419
+ * @param args - Indexing arguments
1420
+ * @returns Summary of all indexed projects
1421
+ */
1422
+ async indexAllProjects(args) {
1423
+ const { GlobalIndex } = await import("../storage/GlobalIndex.js");
1424
+ const { homedir } = await import("os");
1425
+ const { join } = await import("path");
1426
+ const { existsSync, readdirSync } = await import("fs");
1427
+ 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;
1429
+ const globalIndex = new GlobalIndex();
1430
+ try {
1431
+ const projects = [];
1432
+ const errors = [];
1433
+ let totalMessages = 0;
1434
+ let totalConversations = 0;
1435
+ let totalDecisions = 0;
1436
+ let totalMistakes = 0;
1437
+ // Index Codex if requested
1438
+ if (include_codex && existsSync(codex_path)) {
1439
+ try {
1440
+ const { CodexConversationParser } = await import("../parsers/CodexConversationParser.js");
1441
+ const { SQLiteManager } = await import("../storage/SQLiteManager.js");
1442
+ const { ConversationStorage } = await import("../storage/ConversationStorage.js");
1443
+ // Create dedicated database for Codex
1444
+ const codexDbPath = join(codex_path, ".codex-conversations-memory.db");
1445
+ const codexDb = new SQLiteManager({ dbPath: codexDbPath });
1446
+ const codexStorage = new ConversationStorage(codexDb);
1447
+ // Parse Codex sessions
1448
+ const parser = new CodexConversationParser();
1449
+ const parseResult = parser.parseSession(codex_path);
1450
+ // Store all parsed data
1451
+ await codexStorage.storeConversations(parseResult.conversations);
1452
+ await codexStorage.storeMessages(parseResult.messages);
1453
+ await codexStorage.storeToolUses(parseResult.tool_uses);
1454
+ await codexStorage.storeToolResults(parseResult.tool_results);
1455
+ await codexStorage.storeFileEdits(parseResult.file_edits);
1456
+ await codexStorage.storeThinkingBlocks(parseResult.thinking_blocks);
1457
+ // Get stats from the database
1458
+ const stats = codexDb.getDatabase()
1459
+ .prepare("SELECT COUNT(*) as count FROM conversations")
1460
+ .get();
1461
+ const messageStats = codexDb.getDatabase()
1462
+ .prepare("SELECT COUNT(*) as count FROM messages")
1463
+ .get();
1464
+ const decisionStats = codexDb.getDatabase()
1465
+ .prepare("SELECT COUNT(*) as count FROM decisions")
1466
+ .get();
1467
+ const mistakeStats = codexDb.getDatabase()
1468
+ .prepare("SELECT COUNT(*) as count FROM mistakes")
1469
+ .get();
1470
+ // Register in global index
1471
+ globalIndex.registerProject({
1472
+ project_path: codex_path,
1473
+ source_type: "codex",
1474
+ db_path: codexDbPath,
1475
+ message_count: messageStats.count,
1476
+ conversation_count: stats.count,
1477
+ decision_count: decisionStats.count,
1478
+ mistake_count: mistakeStats.count,
1479
+ metadata: {
1480
+ indexed_folders: parseResult.indexed_folders || [],
1481
+ },
1482
+ });
1483
+ projects.push({
1484
+ project_path: codex_path,
1485
+ source_type: "codex",
1486
+ message_count: messageStats.count,
1487
+ conversation_count: stats.count,
1488
+ });
1489
+ totalMessages += messageStats.count;
1490
+ totalConversations += stats.count;
1491
+ totalDecisions += decisionStats.count;
1492
+ totalMistakes += mistakeStats.count;
1493
+ // Close the Codex database
1494
+ codexDb.close();
1495
+ }
1496
+ catch (error) {
1497
+ errors.push({
1498
+ project_path: codex_path,
1499
+ error: error.message,
1500
+ });
1501
+ }
1502
+ }
1503
+ // Index Claude Code projects if requested
1504
+ if (include_claude_code && existsSync(claude_projects_path)) {
1505
+ try {
1506
+ const projectFolders = readdirSync(claude_projects_path);
1507
+ for (const folder of projectFolders) {
1508
+ const folderPath = join(claude_projects_path, folder);
1509
+ try {
1510
+ // Try to determine the original project path from folder name
1511
+ const projectPath = folderPath; // Simplified for now
1512
+ // Index this project
1513
+ const indexResult = await this.indexConversations({
1514
+ project_path: projectPath,
1515
+ });
1516
+ if (indexResult.success) {
1517
+ // Register in global index
1518
+ globalIndex.registerProject({
1519
+ project_path: projectPath,
1520
+ source_type: "claude-code",
1521
+ db_path: this.db.getDbPath(),
1522
+ message_count: indexResult.stats.messages.count,
1523
+ conversation_count: indexResult.stats.conversations.count,
1524
+ decision_count: indexResult.stats.decisions.count,
1525
+ mistake_count: indexResult.stats.mistakes.count,
1526
+ });
1527
+ projects.push({
1528
+ project_path: projectPath,
1529
+ source_type: "claude-code",
1530
+ message_count: indexResult.stats.messages.count,
1531
+ conversation_count: indexResult.stats.conversations.count,
1532
+ });
1533
+ totalMessages += indexResult.stats.messages.count;
1534
+ totalConversations += indexResult.stats.conversations.count;
1535
+ totalDecisions += indexResult.stats.decisions.count;
1536
+ totalMistakes += indexResult.stats.mistakes.count;
1537
+ }
1538
+ }
1539
+ catch (error) {
1540
+ errors.push({
1541
+ project_path: folder,
1542
+ error: error.message,
1543
+ });
1544
+ }
1545
+ }
1546
+ }
1547
+ catch (error) {
1548
+ errors.push({
1549
+ project_path: claude_projects_path,
1550
+ error: error.message,
1551
+ });
1552
+ }
1553
+ }
1554
+ const stats = globalIndex.getGlobalStats();
1555
+ return {
1556
+ success: true,
1557
+ global_index_path: globalIndex.getDbPath(),
1558
+ projects_indexed: projects.length,
1559
+ claude_code_projects: stats.claude_code_projects,
1560
+ codex_projects: stats.codex_projects,
1561
+ total_messages: totalMessages,
1562
+ total_conversations: totalConversations,
1563
+ total_decisions: totalDecisions,
1564
+ total_mistakes: totalMistakes,
1565
+ projects,
1566
+ errors,
1567
+ message: `Indexed ${projects.length} project(s): ${stats.claude_code_projects} Claude Code + ${stats.codex_projects} Codex`,
1568
+ };
1569
+ }
1570
+ finally {
1571
+ // Ensure GlobalIndex is always closed
1572
+ globalIndex.close();
1573
+ }
1574
+ }
1575
+ /**
1576
+ * Search across all indexed projects.
1577
+ *
1578
+ * @param args - Search arguments
1579
+ * @returns Search results from all projects
1580
+ */
1581
+ async searchAllConversations(args) {
1582
+ const { GlobalIndex } = await import("../storage/GlobalIndex.js");
1583
+ const { SQLiteManager } = await import("../storage/SQLiteManager.js");
1584
+ const { SemanticSearch } = await import("../search/SemanticSearch.js");
1585
+ const typedArgs = args;
1586
+ const { query, limit = 20, date_range, source_type = "all" } = typedArgs;
1587
+ const globalIndex = new GlobalIndex();
1588
+ try {
1589
+ const projects = globalIndex.getAllProjects(source_type === "all" ? undefined : source_type);
1590
+ const allResults = [];
1591
+ let claudeCodeResults = 0;
1592
+ let codexResults = 0;
1593
+ for (const project of projects) {
1594
+ let projectDb = null;
1595
+ try {
1596
+ // Open this project's database
1597
+ projectDb = new SQLiteManager({ dbPath: project.db_path, readOnly: true });
1598
+ const semanticSearch = new SemanticSearch(projectDb);
1599
+ // Search this specific project's database
1600
+ const localResults = await semanticSearch.searchConversations(query, limit);
1601
+ // Filter by date range if specified
1602
+ const filteredResults = date_range
1603
+ ? localResults.filter((r) => {
1604
+ const timestamp = r.message.timestamp;
1605
+ return timestamp >= date_range[0] && timestamp <= date_range[1];
1606
+ })
1607
+ : localResults;
1608
+ // Enrich results with project info
1609
+ for (const result of filteredResults) {
1610
+ allResults.push({
1611
+ conversation_id: result.conversation.id,
1612
+ message_id: result.message.id,
1613
+ timestamp: new Date(result.message.timestamp).toISOString(),
1614
+ similarity: result.similarity,
1615
+ snippet: result.snippet,
1616
+ git_branch: result.conversation.git_branch,
1617
+ message_type: result.message.message_type,
1618
+ role: result.message.role,
1619
+ project_path: project.project_path,
1620
+ source_type: project.source_type,
1621
+ });
1622
+ if (project.source_type === "claude-code") {
1623
+ claudeCodeResults++;
1624
+ }
1625
+ else {
1626
+ codexResults++;
1627
+ }
1628
+ }
1629
+ }
1630
+ catch (_error) {
1631
+ // Skip projects that fail to search
1632
+ continue;
1633
+ }
1634
+ finally {
1635
+ // Close project database
1636
+ if (projectDb) {
1637
+ projectDb.close();
1638
+ }
1639
+ }
1640
+ }
1641
+ // Sort by similarity and limit
1642
+ const sortedResults = allResults.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
1643
+ return {
1644
+ query,
1645
+ results: sortedResults,
1646
+ total_found: sortedResults.length,
1647
+ projects_searched: projects.length,
1648
+ search_stats: {
1649
+ claude_code_results: claudeCodeResults,
1650
+ codex_results: codexResults,
1651
+ },
1652
+ message: `Found ${sortedResults.length} result(s) across ${projects.length} project(s)`,
1653
+ };
1654
+ }
1655
+ finally {
1656
+ // Ensure GlobalIndex is always closed
1657
+ globalIndex.close();
1658
+ }
1659
+ }
1660
+ /**
1661
+ * Get decisions from all indexed projects.
1662
+ *
1663
+ * @param args - Query arguments
1664
+ * @returns Decisions from all projects
1665
+ */
1666
+ async getAllDecisions(args) {
1667
+ const { GlobalIndex } = await import("../storage/GlobalIndex.js");
1668
+ const typedArgs = args;
1669
+ const { query, file_path: _file_path } = typedArgs;
1670
+ const globalIndex = new GlobalIndex();
1671
+ try {
1672
+ const projects = globalIndex.getAllProjects();
1673
+ // TODO: Implement cross-project decision search
1674
+ // For now, return empty results with proper structure
1675
+ return {
1676
+ query,
1677
+ decisions: [],
1678
+ total_found: 0,
1679
+ projects_searched: projects.length,
1680
+ message: `Cross-project decision search not yet implemented (would search ${projects.length} project(s))`,
1681
+ };
1682
+ }
1683
+ finally {
1684
+ globalIndex.close();
1685
+ }
1686
+ }
1687
+ /**
1688
+ * Search mistakes across all indexed projects.
1689
+ *
1690
+ * @param args - Search arguments
1691
+ * @returns Mistakes from all projects
1692
+ */
1693
+ async searchAllMistakes(args) {
1694
+ const { GlobalIndex } = await import("../storage/GlobalIndex.js");
1695
+ const typedArgs = args;
1696
+ const { query, mistake_type: _mistake_type } = typedArgs;
1697
+ const globalIndex = new GlobalIndex();
1698
+ try {
1699
+ const projects = globalIndex.getAllProjects();
1700
+ // TODO: Implement cross-project mistake search
1701
+ // For now, return empty results with proper structure
1702
+ return {
1703
+ query,
1704
+ mistakes: [],
1705
+ total_found: 0,
1706
+ projects_searched: projects.length,
1707
+ message: `Cross-project mistake search not yet implemented (would search ${projects.length} project(s))`,
1708
+ };
1709
+ }
1710
+ finally {
1711
+ globalIndex.close();
1712
+ }
1713
+ }
1332
1714
  }
1333
1715
  //# sourceMappingURL=ToolHandlers.js.map