@grec0/memory-bank-mcp 0.0.2
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/.memoryignore.example +76 -0
- package/README.md +425 -0
- package/dist/common/chunker.js +407 -0
- package/dist/common/embeddingService.js +302 -0
- package/dist/common/errors.js +71 -0
- package/dist/common/fileScanner.js +261 -0
- package/dist/common/indexManager.js +332 -0
- package/dist/common/setup.js +49 -0
- package/dist/common/types.js +115 -0
- package/dist/common/utils.js +215 -0
- package/dist/common/vectorStore.js +332 -0
- package/dist/common/version.js +2 -0
- package/dist/index.js +274 -0
- package/dist/operations/boardMemberships.js +186 -0
- package/dist/operations/boards.js +268 -0
- package/dist/operations/cards.js +426 -0
- package/dist/operations/comments.js +249 -0
- package/dist/operations/labels.js +258 -0
- package/dist/operations/lists.js +157 -0
- package/dist/operations/projects.js +102 -0
- package/dist/operations/tasks.js +238 -0
- package/dist/tools/analyzeCoverage.js +316 -0
- package/dist/tools/board-summary.js +151 -0
- package/dist/tools/card-details.js +106 -0
- package/dist/tools/create-card-with-tasks.js +81 -0
- package/dist/tools/getStats.js +59 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/indexCode.js +53 -0
- package/dist/tools/readFile.js +69 -0
- package/dist/tools/searchMemory.js +60 -0
- package/dist/tools/workflow-actions.js +145 -0
- package/dist/tools/writeFile.js +66 -0
- package/package.json +58 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Import functions from operations directory
|
|
3
|
+
import { getBoard } from "../operations/boards.js";
|
|
4
|
+
import { getLists } from "../operations/lists.js";
|
|
5
|
+
import { getCards } from "../operations/cards.js";
|
|
6
|
+
import { getTasks } from "../operations/tasks.js";
|
|
7
|
+
import { getLabels } from "../operations/labels.js";
|
|
8
|
+
import { getComments } from "../operations/comments.js";
|
|
9
|
+
/**
|
|
10
|
+
* Zod schema for the getBoardSummary function parameters
|
|
11
|
+
* @property {string} boardId - The ID of the board to get a summary for
|
|
12
|
+
* @property {boolean} [includeTaskDetails=false] - Whether to include detailed task information for each card
|
|
13
|
+
* @property {boolean} [includeComments=false] - Whether to include comments for each card
|
|
14
|
+
*/
|
|
15
|
+
export const getBoardSummarySchema = z.object({
|
|
16
|
+
boardId: z.string().describe("The ID of the board to get a summary for"),
|
|
17
|
+
includeTaskDetails: z.boolean().optional().default(false).describe("Whether to include detailed task information for each card"),
|
|
18
|
+
includeComments: z.boolean().optional().default(false).describe("Whether to include comments for each card"),
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves a comprehensive summary of a board including lists, cards, tasks, and statistics
|
|
22
|
+
*
|
|
23
|
+
* This function aggregates data from multiple sources to provide a complete view of a board,
|
|
24
|
+
* including its lists, cards, tasks, and labels. It also calculates various statistics and
|
|
25
|
+
* provides workflow state analysis.
|
|
26
|
+
*
|
|
27
|
+
* @param {GetBoardSummaryParams} params - Parameters for retrieving board summary
|
|
28
|
+
* @param {string} params.boardId - The ID of the board to get a summary for
|
|
29
|
+
* @param {boolean} [params.includeTaskDetails=false] - Whether to include detailed task information for each card
|
|
30
|
+
* @param {boolean} [params.includeComments=false] - Whether to include comments for each card
|
|
31
|
+
* @returns {Promise<object>} Comprehensive board summary including lists, cards, tasks, statistics, and workflow state
|
|
32
|
+
* @throws {Error} If the board is not found
|
|
33
|
+
*/
|
|
34
|
+
export async function getBoardSummary(params) {
|
|
35
|
+
const { boardId, includeTaskDetails, includeComments } = params;
|
|
36
|
+
try {
|
|
37
|
+
// Get the board details
|
|
38
|
+
const board = await getBoard(boardId);
|
|
39
|
+
if (!board) {
|
|
40
|
+
throw new Error(`Board with ID ${boardId} not found`);
|
|
41
|
+
}
|
|
42
|
+
// Get all lists on the board
|
|
43
|
+
const allLists = await getLists(boardId);
|
|
44
|
+
// Get all cards for each list
|
|
45
|
+
const listsWithCards = await Promise.all(allLists.map(async (list) => {
|
|
46
|
+
const listCards = await getCards(list.id);
|
|
47
|
+
// Get tasks for each card if requested
|
|
48
|
+
const cardsWithDetails = await Promise.all(listCards.map(async (card) => {
|
|
49
|
+
let taskDetails = [];
|
|
50
|
+
if (includeTaskDetails) {
|
|
51
|
+
taskDetails = await getTasks(card.id);
|
|
52
|
+
}
|
|
53
|
+
// Get comments if requested
|
|
54
|
+
let cardComments = [];
|
|
55
|
+
if (includeComments) {
|
|
56
|
+
cardComments = await getComments(card.id);
|
|
57
|
+
}
|
|
58
|
+
// Calculate task completion percentage
|
|
59
|
+
const completedTasks = taskDetails.filter((task) => task.isCompleted).length;
|
|
60
|
+
const totalTasks = taskDetails.length;
|
|
61
|
+
const completionPercentage = totalTasks > 0
|
|
62
|
+
? Math.round((completedTasks / totalTasks) * 100)
|
|
63
|
+
: 0;
|
|
64
|
+
return {
|
|
65
|
+
...card,
|
|
66
|
+
tasks: includeTaskDetails
|
|
67
|
+
? {
|
|
68
|
+
items: taskDetails,
|
|
69
|
+
total: totalTasks,
|
|
70
|
+
completed: completedTasks,
|
|
71
|
+
completionPercentage,
|
|
72
|
+
}
|
|
73
|
+
: undefined,
|
|
74
|
+
comments: includeComments
|
|
75
|
+
? cardComments
|
|
76
|
+
: undefined,
|
|
77
|
+
};
|
|
78
|
+
}));
|
|
79
|
+
return {
|
|
80
|
+
...list,
|
|
81
|
+
cards: cardsWithDetails,
|
|
82
|
+
cardCount: cardsWithDetails.length,
|
|
83
|
+
};
|
|
84
|
+
}));
|
|
85
|
+
// Get all labels for the board
|
|
86
|
+
const boardLabels = await getLabels(boardId);
|
|
87
|
+
// Calculate overall statistics
|
|
88
|
+
const totalCards = listsWithCards.reduce((sum, list) => sum + list.cardCount, 0);
|
|
89
|
+
// Find specific lists by name
|
|
90
|
+
const backlogList = listsWithCards.find((list) => list.name.toLowerCase() === "backlog");
|
|
91
|
+
const inProgressList = listsWithCards.find((list) => list.name.toLowerCase() === "in progress");
|
|
92
|
+
const testingList = listsWithCards.find((list) => list.name.toLowerCase() === "testing");
|
|
93
|
+
const doneList = listsWithCards.find((list) => list.name.toLowerCase() === "done");
|
|
94
|
+
// Count cards with specific labels
|
|
95
|
+
const urgentCards = listsWithCards.flatMap((list) => list.cards)
|
|
96
|
+
.filter((card) => card.labelIds?.some((labelId) => boardLabels.find((label) => label.id === labelId &&
|
|
97
|
+
label.name.toLowerCase() === "urgent"))).length;
|
|
98
|
+
const bugCards = listsWithCards.flatMap((list) => list.cards)
|
|
99
|
+
.filter((card) => card.labelIds?.some((labelId) => boardLabels.find((label) => label.id === labelId &&
|
|
100
|
+
label.name.toLowerCase() === "bug"))).length;
|
|
101
|
+
return {
|
|
102
|
+
board,
|
|
103
|
+
lists: listsWithCards,
|
|
104
|
+
labels: boardLabels,
|
|
105
|
+
stats: {
|
|
106
|
+
totalCards,
|
|
107
|
+
backlogCount: backlogList?.cardCount || 0,
|
|
108
|
+
inProgressCount: inProgressList?.cardCount || 0,
|
|
109
|
+
testingCount: testingList?.cardCount || 0,
|
|
110
|
+
doneCount: doneList?.cardCount || 0,
|
|
111
|
+
urgentCount: urgentCards,
|
|
112
|
+
bugCount: bugCards,
|
|
113
|
+
completionPercentage: totalCards > 0
|
|
114
|
+
? Math.round((doneList?.cardCount || 0) / totalCards * 100)
|
|
115
|
+
: 0,
|
|
116
|
+
},
|
|
117
|
+
workflowState: {
|
|
118
|
+
hasCardsInBacklog: (backlogList?.cardCount || 0) > 0,
|
|
119
|
+
hasCardsInProgress: (inProgressList?.cardCount || 0) > 0,
|
|
120
|
+
hasCardsInTesting: (testingList?.cardCount || 0) > 0,
|
|
121
|
+
nextActionSuggestion: getNextActionSuggestion(backlogList?.cardCount || 0, inProgressList?.cardCount || 0, testingList?.cardCount || 0),
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
console.error("Error in getBoardSummary:", error);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Helper function to suggest the next action based on board state
|
|
132
|
+
*
|
|
133
|
+
* @param {number} backlogCount - Number of cards in the Backlog list
|
|
134
|
+
* @param {number} inProgressCount - Number of cards in the In Progress list
|
|
135
|
+
* @param {number} testingCount - Number of cards in the Testing list
|
|
136
|
+
* @returns {string} A suggestion for the next action to take
|
|
137
|
+
*/
|
|
138
|
+
function getNextActionSuggestion(backlogCount, inProgressCount, testingCount) {
|
|
139
|
+
if (testingCount > 0) {
|
|
140
|
+
return "Review cards in Testing that need feedback";
|
|
141
|
+
}
|
|
142
|
+
else if (inProgressCount > 0) {
|
|
143
|
+
return "Continue working on cards in In Progress";
|
|
144
|
+
}
|
|
145
|
+
else if (backlogCount > 0) {
|
|
146
|
+
return "Start working on a card from Backlog";
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
return "All tasks complete! Create new cards or projects";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getCard } from "../operations/cards.js";
|
|
3
|
+
import { getTasks } from "../operations/tasks.js";
|
|
4
|
+
import { getComments } from "../operations/comments.js";
|
|
5
|
+
import { getLabels } from "../operations/labels.js";
|
|
6
|
+
import { getProjects } from "../operations/projects.js";
|
|
7
|
+
import { getBoards } from "../operations/boards.js";
|
|
8
|
+
import { getLists } from "../operations/lists.js";
|
|
9
|
+
/**
|
|
10
|
+
* Zod schema for the getCardDetails function parameters
|
|
11
|
+
* @property {string} cardId - The ID of the card to get details for
|
|
12
|
+
*/
|
|
13
|
+
export const getCardDetailsSchema = z.object({
|
|
14
|
+
cardId: z.string().describe("The ID of the card to get details for"),
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves comprehensive details about a card including tasks, comments, labels, and analysis
|
|
18
|
+
*
|
|
19
|
+
* This function aggregates data from multiple sources to provide a complete view of a card,
|
|
20
|
+
* including its tasks, comments, and labels. It also calculates task completion percentage
|
|
21
|
+
* and performs analysis on the card's status.
|
|
22
|
+
*
|
|
23
|
+
* @param {GetCardDetailsParams} params - Parameters for retrieving card details
|
|
24
|
+
* @param {string} params.cardId - The ID of the card to get details for
|
|
25
|
+
* @returns {Promise<object>} Comprehensive card details including tasks, comments, labels, and analysis
|
|
26
|
+
* @throws {Error} If the card is not found or if the board ID cannot be determined
|
|
27
|
+
*/
|
|
28
|
+
export async function getCardDetails(params) {
|
|
29
|
+
const { cardId } = params;
|
|
30
|
+
try {
|
|
31
|
+
// Get the card details
|
|
32
|
+
const card = await getCard(cardId);
|
|
33
|
+
if (!card) {
|
|
34
|
+
throw new Error(`Card with ID ${cardId} not found`);
|
|
35
|
+
}
|
|
36
|
+
// Get tasks for the card
|
|
37
|
+
const tasks = await getTasks(card.id);
|
|
38
|
+
// Get comments for the card
|
|
39
|
+
const comments = await getComments(card.id);
|
|
40
|
+
// Find the board ID by searching through all projects and boards
|
|
41
|
+
let boardId = null;
|
|
42
|
+
// Get all projects
|
|
43
|
+
const projectsResponse = await getProjects(1, 100);
|
|
44
|
+
const projects = projectsResponse.items;
|
|
45
|
+
// For each project, get its boards
|
|
46
|
+
for (const project of projects) {
|
|
47
|
+
if (boardId)
|
|
48
|
+
break; // Stop if we already found the board ID
|
|
49
|
+
const boards = await getBoards(project.id);
|
|
50
|
+
// For each board, get its lists
|
|
51
|
+
for (const board of boards) {
|
|
52
|
+
if (boardId)
|
|
53
|
+
break; // Stop if we already found the board ID
|
|
54
|
+
const lists = await getLists(board.id);
|
|
55
|
+
// Check if the card's list ID is in this board
|
|
56
|
+
const matchingList = lists.find((list) => list.id === card.listId);
|
|
57
|
+
if (matchingList) {
|
|
58
|
+
boardId = board.id;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!boardId) {
|
|
64
|
+
throw new Error(`Could not determine board ID for card ${cardId}`);
|
|
65
|
+
}
|
|
66
|
+
const labels = await getLabels(boardId);
|
|
67
|
+
// Filter to just the labels assigned to this card
|
|
68
|
+
// Note: We need to get the labelIds from the card's data
|
|
69
|
+
// This might require additional API calls or data structure knowledge
|
|
70
|
+
// For now, we'll return all labels for the board
|
|
71
|
+
// Calculate task completion percentage
|
|
72
|
+
const completedTasks = tasks.filter((task) => task.isCompleted).length;
|
|
73
|
+
const totalTasks = tasks.length;
|
|
74
|
+
const completionPercentage = totalTasks > 0
|
|
75
|
+
? Math.round((completedTasks / totalTasks) * 100)
|
|
76
|
+
: 0;
|
|
77
|
+
// Sort comments by date (newest first)
|
|
78
|
+
const sortedComments = comments.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
79
|
+
// Check if the most recent comment is likely from a human (not the LLM)
|
|
80
|
+
// This is a heuristic and might need adjustment
|
|
81
|
+
const hasRecentHumanFeedback = sortedComments.length > 0 &&
|
|
82
|
+
sortedComments[0].data &&
|
|
83
|
+
!sortedComments[0].data.text.includes("Implemented feature") &&
|
|
84
|
+
!sortedComments[0].data.text.includes("Awaiting human review");
|
|
85
|
+
return {
|
|
86
|
+
card,
|
|
87
|
+
taskItems: tasks,
|
|
88
|
+
taskStats: {
|
|
89
|
+
total: totalTasks,
|
|
90
|
+
completed: completedTasks,
|
|
91
|
+
completionPercentage,
|
|
92
|
+
},
|
|
93
|
+
comments: sortedComments,
|
|
94
|
+
labels,
|
|
95
|
+
analysis: {
|
|
96
|
+
hasRecentHumanFeedback,
|
|
97
|
+
isComplete: completionPercentage === 100,
|
|
98
|
+
needsAttention: hasRecentHumanFeedback || completedTasks === 0,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error("Error in getCardDetails:", error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createCard } from "../operations/cards.js";
|
|
3
|
+
import { createTask } from "../operations/tasks.js";
|
|
4
|
+
import { createComment } from "../operations/comments.js";
|
|
5
|
+
/**
|
|
6
|
+
* Zod schema for the createCardWithTasks function parameters
|
|
7
|
+
* @property {string} listId - The ID of the list to create the card in
|
|
8
|
+
* @property {string} name - The name of the card
|
|
9
|
+
* @property {string} [description] - The description of the card
|
|
10
|
+
* @property {string[]} [tasks] - Array of task descriptions to create
|
|
11
|
+
* @property {string} [comment] - Optional comment to add to the card
|
|
12
|
+
* @property {number} [position] - Optional position for the card in the list
|
|
13
|
+
*/
|
|
14
|
+
export const createCardWithTasksSchema = z.object({
|
|
15
|
+
listId: z.string().describe("The ID of the list to create the card in"),
|
|
16
|
+
name: z.string().describe("The name of the card"),
|
|
17
|
+
description: z.string().optional().describe("The description of the card"),
|
|
18
|
+
tasks: z.array(z.string()).optional().describe("Array of task descriptions to create"),
|
|
19
|
+
comment: z.string().optional().describe("Optional comment to add to the card"),
|
|
20
|
+
position: z.number().optional().describe("Optional position for the card in the list"),
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new card with tasks, description, and optional comment in a single operation
|
|
24
|
+
*
|
|
25
|
+
* This function streamlines the process of creating a card with associated tasks and comments
|
|
26
|
+
* by handling all the necessary API calls in a single function.
|
|
27
|
+
*
|
|
28
|
+
* @param {CreateCardWithTasksParams} params - Parameters for creating a card with tasks
|
|
29
|
+
* @param {string} params.listId - The ID of the list to create the card in
|
|
30
|
+
* @param {string} params.name - The name of the card
|
|
31
|
+
* @param {string} [params.description] - The description of the card
|
|
32
|
+
* @param {string[]} [params.tasks] - Array of task descriptions to create
|
|
33
|
+
* @param {string} [params.comment] - Optional comment to add to the card
|
|
34
|
+
* @param {number} [params.position] - Optional position for the card in the list
|
|
35
|
+
* @returns {Promise<object>} The created card, tasks, and comment
|
|
36
|
+
* @throws {Error} If there's an error creating the card, tasks, or comment
|
|
37
|
+
*/
|
|
38
|
+
export async function createCardWithTasks(params) {
|
|
39
|
+
const { listId, name, description, tasks, comment, position = 65535 } = params;
|
|
40
|
+
try {
|
|
41
|
+
// Create the card
|
|
42
|
+
const card = await createCard({
|
|
43
|
+
listId,
|
|
44
|
+
name,
|
|
45
|
+
description: description || "",
|
|
46
|
+
position,
|
|
47
|
+
});
|
|
48
|
+
// Create tasks if provided
|
|
49
|
+
const createdTasks = [];
|
|
50
|
+
if (tasks && tasks.length > 0) {
|
|
51
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
52
|
+
const taskName = tasks[i];
|
|
53
|
+
// Calculate position for each task (65535, 131070, 196605, etc.)
|
|
54
|
+
const taskPosition = 65535 * (i + 1);
|
|
55
|
+
const task = await createTask({
|
|
56
|
+
cardId: card.id,
|
|
57
|
+
name: taskName,
|
|
58
|
+
position: taskPosition,
|
|
59
|
+
});
|
|
60
|
+
createdTasks.push(task);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Add a comment if provided
|
|
64
|
+
let createdComment = null;
|
|
65
|
+
if (comment) {
|
|
66
|
+
createdComment = await createComment({
|
|
67
|
+
cardId: card.id,
|
|
68
|
+
text: comment,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
card,
|
|
73
|
+
tasks: createdTasks,
|
|
74
|
+
comment: createdComment,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error("Error in createCardWithTasks:", error);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Get stats tool for Memory Bank
|
|
3
|
+
* Returns statistics about the indexed codebase
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Gets statistics about the Memory Bank
|
|
7
|
+
*/
|
|
8
|
+
export async function getStats(indexManager, embeddingService) {
|
|
9
|
+
try {
|
|
10
|
+
console.error("\nGetting Memory Bank statistics...");
|
|
11
|
+
// Get index stats
|
|
12
|
+
const indexStats = await indexManager.getStats();
|
|
13
|
+
// Get embedding cache stats
|
|
14
|
+
const cacheStats = embeddingService.getCacheStats();
|
|
15
|
+
const stats = {
|
|
16
|
+
totalFiles: indexStats.totalFiles,
|
|
17
|
+
totalChunks: indexStats.totalChunks,
|
|
18
|
+
lastIndexed: indexStats.lastIndexed?.toISOString(),
|
|
19
|
+
languages: indexStats.languages,
|
|
20
|
+
pendingFiles: indexStats.pendingFiles,
|
|
21
|
+
embeddingCache: cacheStats.size > 0 ? cacheStats : undefined,
|
|
22
|
+
};
|
|
23
|
+
// Format message
|
|
24
|
+
let message = `Memory Bank contains ${stats.totalChunks} chunk(s) from ${stats.totalFiles} file(s)`;
|
|
25
|
+
if (stats.lastIndexed) {
|
|
26
|
+
const lastIndexedDate = new Date(stats.lastIndexed);
|
|
27
|
+
const now = new Date();
|
|
28
|
+
const diffMs = now.getTime() - lastIndexedDate.getTime();
|
|
29
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
30
|
+
if (diffMins < 60) {
|
|
31
|
+
message += `, last indexed ${diffMins} minute(s) ago`;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
35
|
+
message += `, last indexed ${diffHours} hour(s) ago`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (stats.pendingFiles && stats.pendingFiles.length > 0) {
|
|
39
|
+
message += `. ${stats.pendingFiles.length} file(s) pending reindexing`;
|
|
40
|
+
}
|
|
41
|
+
console.error(message);
|
|
42
|
+
console.error(`Languages: ${Object.keys(stats.languages).join(", ")}`);
|
|
43
|
+
if (stats.embeddingCache) {
|
|
44
|
+
console.error(`Embedding cache: ${stats.embeddingCache.size} cached`);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
stats,
|
|
49
|
+
message,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error(`Error in getStats tool: ${error}`);
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
message: `Failed to get stats: ${error}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tools module for the Memory Bank MCP server
|
|
3
|
+
*
|
|
4
|
+
* This module exports tools for semantic code indexing and retrieval.
|
|
5
|
+
*/
|
|
6
|
+
// Export all Memory Bank tools
|
|
7
|
+
export * from "./indexCode.js";
|
|
8
|
+
export * from "./searchMemory.js";
|
|
9
|
+
export * from "./readFile.js";
|
|
10
|
+
export * from "./writeFile.js";
|
|
11
|
+
export * from "./getStats.js";
|
|
12
|
+
export * from "./analyzeCoverage.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Index code tool for Memory Bank
|
|
3
|
+
* Indexes code files semantically
|
|
4
|
+
*/
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
/**
|
|
7
|
+
* Indexes code from a directory or file
|
|
8
|
+
*/
|
|
9
|
+
export async function indexCode(params, indexManager, workspaceRoot) {
|
|
10
|
+
try {
|
|
11
|
+
// Determine path to index
|
|
12
|
+
const targetPath = params.path
|
|
13
|
+
? path.isAbsolute(params.path)
|
|
14
|
+
? params.path
|
|
15
|
+
: path.join(workspaceRoot, params.path)
|
|
16
|
+
: workspaceRoot;
|
|
17
|
+
console.error(`\nIndexing code at: ${targetPath}`);
|
|
18
|
+
console.error(`Workspace root: ${workspaceRoot}`);
|
|
19
|
+
console.error(`Recursive: ${params.recursive !== false}`);
|
|
20
|
+
console.error(`Force reindex: ${params.forceReindex || false}`);
|
|
21
|
+
// Run indexing
|
|
22
|
+
const result = await indexManager.indexFiles({
|
|
23
|
+
rootPath: targetPath,
|
|
24
|
+
recursive: params.recursive !== false,
|
|
25
|
+
forceReindex: params.forceReindex || false,
|
|
26
|
+
});
|
|
27
|
+
// Format result
|
|
28
|
+
const message = result.filesProcessed > 0
|
|
29
|
+
? `Successfully indexed ${result.filesProcessed} file(s), created ${result.chunksCreated} chunk(s) in ${(result.duration / 1000).toFixed(2)}s`
|
|
30
|
+
: result.errors.length > 0
|
|
31
|
+
? `Indexing completed with errors`
|
|
32
|
+
: `No files needed indexing (all up to date)`;
|
|
33
|
+
return {
|
|
34
|
+
success: result.errors.length === 0,
|
|
35
|
+
filesProcessed: result.filesProcessed,
|
|
36
|
+
chunksCreated: result.chunksCreated,
|
|
37
|
+
duration: result.duration,
|
|
38
|
+
errors: result.errors.length > 0 ? result.errors : undefined,
|
|
39
|
+
message,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(`Error in indexCode tool: ${error}`);
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
filesProcessed: 0,
|
|
47
|
+
chunksCreated: 0,
|
|
48
|
+
duration: 0,
|
|
49
|
+
errors: [String(error)],
|
|
50
|
+
message: `Failed to index code: ${error}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Read file tool for Memory Bank
|
|
3
|
+
* Reads files from the workspace
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
/**
|
|
8
|
+
* Reads a file from the workspace
|
|
9
|
+
*/
|
|
10
|
+
export async function readFile(params, workspaceRoot) {
|
|
11
|
+
try {
|
|
12
|
+
// Resolve file path
|
|
13
|
+
const filePath = path.isAbsolute(params.path)
|
|
14
|
+
? params.path
|
|
15
|
+
: path.join(workspaceRoot, params.path);
|
|
16
|
+
// Check if file exists
|
|
17
|
+
if (!fs.existsSync(filePath)) {
|
|
18
|
+
return {
|
|
19
|
+
success: false,
|
|
20
|
+
filePath: params.path,
|
|
21
|
+
message: `File not found: ${params.path}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Check if it's a file
|
|
25
|
+
const stats = fs.statSync(filePath);
|
|
26
|
+
if (!stats.isFile()) {
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
filePath: params.path,
|
|
30
|
+
message: `Path is not a file: ${params.path}`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Read file
|
|
34
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
35
|
+
const lines = content.split("\n");
|
|
36
|
+
const totalLines = lines.length;
|
|
37
|
+
// Apply line range if specified
|
|
38
|
+
let finalContent = content;
|
|
39
|
+
let linesRead;
|
|
40
|
+
if (params.startLine !== undefined || params.endLine !== undefined) {
|
|
41
|
+
const start = Math.max(0, (params.startLine || 1) - 1);
|
|
42
|
+
const end = Math.min(totalLines, params.endLine || totalLines);
|
|
43
|
+
finalContent = lines.slice(start, end).join("\n");
|
|
44
|
+
linesRead = { start: start + 1, end };
|
|
45
|
+
}
|
|
46
|
+
const message = linesRead
|
|
47
|
+
? `Read lines ${linesRead.start}-${linesRead.end} from ${params.path} (${totalLines} total lines)`
|
|
48
|
+
: `Read ${params.path} (${totalLines} lines, ${stats.size} bytes)`;
|
|
49
|
+
console.error(message);
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
content: finalContent,
|
|
53
|
+
filePath: params.path,
|
|
54
|
+
totalLines,
|
|
55
|
+
linesRead,
|
|
56
|
+
size: stats.size,
|
|
57
|
+
lastModified: stats.mtime,
|
|
58
|
+
message,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error(`Error in readFile tool: ${error}`);
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
filePath: params.path,
|
|
66
|
+
message: `Failed to read file: ${error}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Search memory tool for Memory Bank
|
|
3
|
+
* Searches code semantically using vector similarity
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Searches the Memory Bank for relevant code
|
|
7
|
+
*/
|
|
8
|
+
export async function searchMemory(params, indexManager) {
|
|
9
|
+
try {
|
|
10
|
+
if (!params.query || params.query.trim() === "") {
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
results: [],
|
|
14
|
+
count: 0,
|
|
15
|
+
query: params.query || "",
|
|
16
|
+
message: "Query cannot be empty",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
console.error(`\nSearching Memory Bank for: "${params.query}"`);
|
|
20
|
+
console.error(`Top K: ${params.topK || 10}`);
|
|
21
|
+
console.error(`Min score: ${params.minScore || 0.7}`);
|
|
22
|
+
if (params.filterByFile) {
|
|
23
|
+
console.error(`Filter by file: ${params.filterByFile}`);
|
|
24
|
+
}
|
|
25
|
+
if (params.filterByLanguage) {
|
|
26
|
+
console.error(`Filter by language: ${params.filterByLanguage}`);
|
|
27
|
+
}
|
|
28
|
+
// Search
|
|
29
|
+
const results = await indexManager.search(params.query, {
|
|
30
|
+
topK: params.topK || 10,
|
|
31
|
+
minScore: params.minScore !== undefined ? params.minScore : 0.7,
|
|
32
|
+
filterByFile: params.filterByFile,
|
|
33
|
+
filterByLanguage: params.filterByLanguage,
|
|
34
|
+
});
|
|
35
|
+
console.error(`Found ${results.length} result(s)`);
|
|
36
|
+
// Format message
|
|
37
|
+
let message = `Found ${results.length} result(s)`;
|
|
38
|
+
if (results.length > 0) {
|
|
39
|
+
const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;
|
|
40
|
+
message += ` (avg score: ${avgScore.toFixed(3)})`;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
results,
|
|
45
|
+
count: results.length,
|
|
46
|
+
query: params.query,
|
|
47
|
+
message,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.error(`Error in searchMemory tool: ${error}`);
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
results: [],
|
|
55
|
+
count: 0,
|
|
56
|
+
query: params.query || "",
|
|
57
|
+
message: `Search failed: ${error}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|