@crowley/rag-mcp 1.0.4 → 1.0.6

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 (50) hide show
  1. package/dist/annotations.d.ts +16 -0
  2. package/dist/annotations.js +158 -0
  3. package/dist/context-enrichment.d.ts +2 -2
  4. package/dist/context-enrichment.js +37 -14
  5. package/dist/formatters.d.ts +2 -0
  6. package/dist/formatters.js +12 -0
  7. package/dist/index.js +64 -47
  8. package/dist/schemas.d.ts +97 -0
  9. package/dist/schemas.js +128 -0
  10. package/dist/tool-middleware.d.ts +40 -0
  11. package/dist/tool-middleware.js +216 -0
  12. package/dist/tool-registry.js +2 -1
  13. package/dist/tools/advanced.d.ts +2 -2
  14. package/dist/tools/advanced.js +200 -275
  15. package/dist/tools/agents.d.ts +2 -2
  16. package/dist/tools/agents.js +59 -78
  17. package/dist/tools/analytics.d.ts +2 -2
  18. package/dist/tools/analytics.js +170 -210
  19. package/dist/tools/architecture.d.ts +2 -2
  20. package/dist/tools/architecture.js +506 -661
  21. package/dist/tools/ask.d.ts +2 -2
  22. package/dist/tools/ask.js +164 -219
  23. package/dist/tools/cache.d.ts +2 -2
  24. package/dist/tools/cache.js +63 -82
  25. package/dist/tools/clustering.d.ts +2 -2
  26. package/dist/tools/clustering.js +154 -215
  27. package/dist/tools/confluence.d.ts +2 -2
  28. package/dist/tools/confluence.js +80 -116
  29. package/dist/tools/database.d.ts +2 -2
  30. package/dist/tools/database.js +303 -380
  31. package/dist/tools/feedback.d.ts +2 -2
  32. package/dist/tools/feedback.js +143 -184
  33. package/dist/tools/guidelines.d.ts +2 -2
  34. package/dist/tools/guidelines.js +123 -135
  35. package/dist/tools/indexing.d.ts +2 -2
  36. package/dist/tools/indexing.js +104 -100
  37. package/dist/tools/memory.d.ts +2 -2
  38. package/dist/tools/memory.js +299 -485
  39. package/dist/tools/pm.d.ts +2 -2
  40. package/dist/tools/pm.js +367 -615
  41. package/dist/tools/review.d.ts +2 -2
  42. package/dist/tools/review.js +142 -189
  43. package/dist/tools/search.d.ts +2 -2
  44. package/dist/tools/search.js +230 -305
  45. package/dist/tools/session.d.ts +2 -2
  46. package/dist/tools/session.js +288 -345
  47. package/dist/tools/suggestions.d.ts +2 -2
  48. package/dist/tools/suggestions.js +444 -255
  49. package/dist/types.d.ts +19 -2
  50. package/package.json +4 -2
@@ -5,7 +5,9 @@
5
5
  * batch_remember, validate_memory, review_memories,
6
6
  * promote_memory, run_quality_gates, memory_maintenance
7
7
  */
8
- import { formatMemoryResults, truncate, PREVIEW } from "../formatters.js";
8
+ import { formatMemoryResults, truncate, paginationFooter, PREVIEW } from "../formatters.js";
9
+ import { z } from "zod";
10
+ import { TOOL_ANNOTATIONS } from "../annotations.js";
9
11
  const typeEmojis = {
10
12
  decision: "\u{1F3AF}",
11
13
  insight: "\u{1F4A1}",
@@ -21,545 +23,357 @@ const statusEmojis = {
21
23
  cancelled: "\u274C",
22
24
  };
23
25
  export function createMemoryTools(projectName) {
24
- const tools = [
26
+ return [
25
27
  {
26
28
  name: "remember",
27
29
  description: "Store important information in agent memory. Use this to save decisions, insights, context, todos, or important conversations for future reference.",
28
- inputSchema: {
29
- type: "object",
30
- properties: {
31
- content: {
32
- type: "string",
33
- description: "Information to remember",
34
- },
35
- type: {
36
- type: "string",
37
- enum: [
38
- "decision",
39
- "insight",
40
- "context",
41
- "todo",
42
- "conversation",
43
- "note",
44
- ],
45
- description: "Type of memory (default: note)",
46
- default: "note",
47
- },
48
- tags: {
49
- type: "array",
50
- items: { type: "string" },
51
- description: "Tags for categorization (e.g., ['feature-x', 'important'])",
52
- },
53
- relatedTo: {
54
- type: "string",
55
- description: "Related feature or topic",
56
- },
57
- },
58
- required: ["content"],
30
+ schema: z.object({
31
+ content: z.string().describe("Information to remember"),
32
+ type: z.enum(["decision", "insight", "context", "todo", "conversation", "note"]).optional().describe("Type of memory (default: note)"),
33
+ tags: z.array(z.string()).optional().describe("Tags for categorization (e.g., ['feature-x', 'important'])"),
34
+ relatedTo: z.string().optional().describe("Related feature or topic"),
35
+ }),
36
+ annotations: TOOL_ANNOTATIONS["remember"],
37
+ handler: async (args, ctx) => {
38
+ const content = args.content;
39
+ const type = args.type || "note";
40
+ const tags = args.tags || [];
41
+ const relatedTo = args.relatedTo;
42
+ const response = await ctx.api.post("/api/memory", {
43
+ projectName: ctx.projectName,
44
+ content,
45
+ type,
46
+ tags,
47
+ relatedTo,
48
+ });
49
+ const memory = response.data.memory;
50
+ return (`\u2705 **Memory stored**\n\n` +
51
+ `- **ID:** ${memory.id}\n` +
52
+ `- **Type:** ${memory.type}\n` +
53
+ `- **Content:** ${truncate(content, 200)}\n` +
54
+ (tags.length > 0 ? `- **Tags:** ${tags.join(", ")}\n` : "") +
55
+ (relatedTo ? `- **Related to:** ${relatedTo}\n` : "") +
56
+ `- **Created:** ${new Date(memory.createdAt).toLocaleString()}`);
59
57
  },
60
58
  },
61
59
  {
62
60
  name: "recall",
63
61
  description: "Retrieve relevant memories based on context. Searches agent memory for past decisions, insights, and notes related to the query.",
64
- inputSchema: {
65
- type: "object",
66
- properties: {
67
- query: {
68
- type: "string",
69
- description: "What to recall (semantic search)",
70
- },
71
- type: {
72
- type: "string",
73
- enum: [
74
- "decision",
75
- "insight",
76
- "context",
77
- "todo",
78
- "conversation",
79
- "note",
80
- "all",
81
- ],
82
- description: "Filter by memory type (default: all)",
83
- default: "all",
84
- },
85
- limit: {
86
- type: "number",
87
- description: "Max memories to retrieve (default: 5)",
88
- default: 5,
89
- },
90
- },
91
- required: ["query"],
62
+ schema: z.object({
63
+ query: z.string().describe("What to recall (semantic search)"),
64
+ type: z.enum(["decision", "insight", "context", "todo", "conversation", "note", "all"]).optional().describe("Filter by memory type (default: all)"),
65
+ limit: z.number().optional().describe("Max memories to retrieve (default: 5)"),
66
+ }),
67
+ annotations: TOOL_ANNOTATIONS["recall"],
68
+ handler: async (args, ctx) => {
69
+ const query = args.query;
70
+ const type = args.type || "all";
71
+ const limit = args.limit || 5;
72
+ const response = await ctx.api.post("/api/memory/recall", {
73
+ projectName: ctx.projectName,
74
+ query,
75
+ type,
76
+ limit,
77
+ });
78
+ const results = response.data.results || [];
79
+ if (results.length === 0) {
80
+ return `\u{1F50D} No memories found for: "${query}"`;
81
+ }
82
+ const header = `\u{1F9E0} **Recalled Memories** (${results.length} found)\n\n`;
83
+ return header + formatMemoryResults(results);
92
84
  },
93
85
  },
94
86
  {
95
87
  name: "list_memories",
96
88
  description: "List recent memories or filter by type/tags. Shows what the agent has remembered.",
97
- inputSchema: {
98
- type: "object",
99
- properties: {
100
- type: {
101
- type: "string",
102
- enum: [
103
- "decision",
104
- "insight",
105
- "context",
106
- "todo",
107
- "conversation",
108
- "note",
109
- "all",
110
- ],
111
- description: "Filter by type",
112
- default: "all",
113
- },
114
- tag: {
115
- type: "string",
116
- description: "Filter by tag",
117
- },
118
- limit: {
119
- type: "number",
120
- description: "Max results (default: 10)",
121
- default: 10,
122
- },
123
- },
89
+ schema: z.object({
90
+ type: z.enum(["decision", "insight", "context", "todo", "conversation", "note", "all"]).optional().describe("Filter by type"),
91
+ tag: z.string().optional().describe("Filter by tag"),
92
+ limit: z.number().optional().describe("Max results (default: 10)"),
93
+ offset: z.number().optional().describe("Pagination offset (default: 0)"),
94
+ }),
95
+ annotations: TOOL_ANNOTATIONS["list_memories"],
96
+ handler: async (args, ctx) => {
97
+ const type = args.type || "all";
98
+ const tag = args.tag;
99
+ const limit = args.limit || 10;
100
+ const offset = args.offset || 0;
101
+ const params = new URLSearchParams({
102
+ projectName: ctx.projectName,
103
+ limit: limit.toString(),
104
+ offset: offset.toString(),
105
+ });
106
+ if (type && type !== "all")
107
+ params.append("type", type);
108
+ if (tag)
109
+ params.append("tag", tag);
110
+ const response = await ctx.api.get(`/api/memory/list?${params}`);
111
+ const memories = response.data.memories || [];
112
+ if (memories.length === 0) {
113
+ return `\u{1F4ED} No memories found${type !== "all" ? ` of type "${type}"` : ""}`;
114
+ }
115
+ let result = `\u{1F4DA} **Agent Memories** (${memories.length})\n\n`;
116
+ memories.forEach((m, i) => {
117
+ const emoji = typeEmojis[m.type] || "\u{1F4DD}";
118
+ const statusStr = m.status ? ` [${m.status}]` : "";
119
+ result += `${offset + i + 1}. ${emoji} **${m.type}**${statusStr}: ${truncate(m.content, PREVIEW.SHORT)}\n`;
120
+ result += ` ID: \`${m.id}\` | ${new Date(m.createdAt).toLocaleDateString()}\n\n`;
121
+ });
122
+ result += paginationFooter(memories.length, limit, offset);
123
+ return result;
124
124
  },
125
125
  },
126
126
  {
127
127
  name: "forget",
128
128
  description: "Delete a specific memory by ID or clear memories by type.",
129
- inputSchema: {
130
- type: "object",
131
- properties: {
132
- memoryId: {
133
- type: "string",
134
- description: "Specific memory ID to delete",
135
- },
136
- type: {
137
- type: "string",
138
- enum: [
139
- "decision",
140
- "insight",
141
- "context",
142
- "todo",
143
- "conversation",
144
- "note",
145
- ],
146
- description: "Delete all memories of this type",
147
- },
148
- olderThanDays: {
149
- type: "number",
150
- description: "Delete memories older than N days",
151
- },
152
- },
129
+ schema: z.object({
130
+ memoryId: z.string().optional().describe("Specific memory ID to delete"),
131
+ type: z.enum(["decision", "insight", "context", "todo", "conversation", "note"]).optional().describe("Delete all memories of this type"),
132
+ olderThanDays: z.number().optional().describe("Delete memories older than N days"),
133
+ }),
134
+ annotations: TOOL_ANNOTATIONS["forget"],
135
+ handler: async (args, ctx) => {
136
+ const memoryId = args.memoryId;
137
+ const type = args.type;
138
+ if (memoryId) {
139
+ const response = await ctx.api.delete(`/api/memory/${memoryId}?projectName=${ctx.projectName}`);
140
+ return response.data.success
141
+ ? `\u{1F5D1}\uFE0F Memory deleted: ${memoryId}`
142
+ : `\u274C Failed to delete memory: ${memoryId}`;
143
+ }
144
+ if (type) {
145
+ await ctx.api.delete(`/api/memory/type/${type}?projectName=${ctx.projectName}`);
146
+ return `\u{1F5D1}\uFE0F Deleted all memories of type: ${type}`;
147
+ }
148
+ return "Please specify memoryId or type to delete.";
153
149
  },
154
150
  },
155
151
  {
156
152
  name: "update_todo",
157
153
  description: "Update status of a todo/task in memory.",
158
- inputSchema: {
159
- type: "object",
160
- properties: {
161
- todoId: {
162
- type: "string",
163
- description: "Todo memory ID",
164
- },
165
- status: {
166
- type: "string",
167
- enum: ["pending", "in_progress", "done", "cancelled"],
168
- description: "New status",
169
- },
170
- note: {
171
- type: "string",
172
- description: "Optional note about the update",
173
- },
174
- },
175
- required: ["todoId", "status"],
154
+ schema: z.object({
155
+ todoId: z.string().describe("Todo memory ID"),
156
+ status: z.enum(["pending", "in_progress", "done", "cancelled"]).describe("New status"),
157
+ note: z.string().optional().describe("Optional note about the update"),
158
+ }),
159
+ annotations: TOOL_ANNOTATIONS["update_todo"],
160
+ handler: async (args, ctx) => {
161
+ const todoId = args.todoId;
162
+ const status = args.status;
163
+ const note = args.note;
164
+ const response = await ctx.api.patch(`/api/memory/todo/${todoId}`, {
165
+ projectName: ctx.projectName,
166
+ status,
167
+ note,
168
+ });
169
+ if (!response.data.memory) {
170
+ return `\u274C Todo not found: ${todoId}`;
171
+ }
172
+ return (`${statusEmojis[status] || "\u{1F4CB}"} **Todo updated**\n\n` +
173
+ `- **ID:** ${todoId}\n` +
174
+ `- **Status:** ${status}\n` +
175
+ (note ? `- **Note:** ${note}\n` : "") +
176
+ `- **Content:** ${response.data.memory.content}`);
176
177
  },
177
178
  },
178
179
  {
179
180
  name: "batch_remember",
180
181
  description: `Efficiently store multiple memories at once in ${projectName}. Faster than individual remember calls.`,
181
- inputSchema: {
182
- type: "object",
183
- properties: {
184
- items: {
185
- type: "array",
186
- items: {
187
- type: "object",
188
- properties: {
189
- content: {
190
- type: "string",
191
- description: "Content to remember",
192
- },
193
- type: {
194
- type: "string",
195
- enum: [
196
- "decision",
197
- "insight",
198
- "context",
199
- "todo",
200
- "conversation",
201
- "note",
202
- ],
203
- description: "Memory type (default: note)",
204
- },
205
- tags: {
206
- type: "array",
207
- items: { type: "string" },
208
- description: "Tags for categorization",
209
- },
210
- relatedTo: {
211
- type: "string",
212
- description: "Related feature or topic",
213
- },
214
- },
215
- required: ["content"],
216
- },
217
- description: "Array of memories to store",
218
- },
219
- },
220
- required: ["items"],
182
+ schema: z.object({
183
+ items: z.array(z.object({
184
+ content: z.string().describe("Content to remember"),
185
+ type: z.enum(["decision", "insight", "context", "todo", "conversation", "note"]).optional().describe("Memory type (default: note)"),
186
+ tags: z.array(z.string()).optional().describe("Tags for categorization"),
187
+ relatedTo: z.string().optional().describe("Related feature or topic"),
188
+ })).describe("Array of memories to store"),
189
+ }),
190
+ annotations: TOOL_ANNOTATIONS["batch_remember"],
191
+ handler: async (args, ctx) => {
192
+ const items = args.items;
193
+ const response = await ctx.api.post("/api/memory/batch", {
194
+ items,
195
+ });
196
+ const { savedCount, errors, memories } = response.data;
197
+ let result = `# \u{1F4E6} Batch Memory Result\n\n`;
198
+ result += `**Saved**: ${savedCount} memories\n\n`;
199
+ if (memories && memories.length > 0) {
200
+ result += `## Stored Memories\n`;
201
+ memories.forEach((m) => {
202
+ result += `- [${m.type}] ${truncate(m.content, 80)}\n`;
203
+ result += ` ID: \`${m.id}\`\n`;
204
+ });
205
+ }
206
+ if (errors && errors.length > 0) {
207
+ result += `\n## \u26A0\uFE0F Errors\n`;
208
+ errors.forEach((e) => {
209
+ result += `- ${e}\n`;
210
+ });
211
+ }
212
+ return result;
221
213
  },
222
214
  },
223
215
  {
224
216
  name: "validate_memory",
225
217
  description: `Validate or reject an auto-extracted memory in ${projectName}. Helps improve future extraction accuracy.`,
226
- inputSchema: {
227
- type: "object",
228
- properties: {
229
- memoryId: {
230
- type: "string",
231
- description: "ID of the memory to validate",
232
- },
233
- validated: {
234
- type: "boolean",
235
- description: "true to confirm the memory is valuable, false to reject it",
236
- },
237
- },
238
- required: ["memoryId", "validated"],
218
+ schema: z.object({
219
+ memoryId: z.string().describe("ID of the memory to validate"),
220
+ validated: z.boolean().describe("true to confirm the memory is valuable, false to reject it"),
221
+ }),
222
+ annotations: TOOL_ANNOTATIONS["validate_memory"],
223
+ handler: async (args, ctx) => {
224
+ const memoryId = args.memoryId;
225
+ const validated = args.validated;
226
+ const response = await ctx.api.patch(`/api/memory/${memoryId}/validate`, {
227
+ validated,
228
+ });
229
+ const { memory } = response.data;
230
+ return (`\u2705 Memory ${validated ? "validated" : "rejected"}\n\n` +
231
+ `- **ID**: ${memory.id}\n` +
232
+ `- **Type**: ${memory.type}\n` +
233
+ `- **Content**: ${truncate(memory.content, PREVIEW.SHORT)}\n` +
234
+ `- **Validated**: ${memory.validated}`);
239
235
  },
240
236
  },
241
237
  {
242
238
  name: "review_memories",
243
239
  description: `Get auto-extracted memories pending review in ${projectName}. Shows unvalidated learnings that need human confirmation.`,
244
- inputSchema: {
245
- type: "object",
246
- properties: {
247
- limit: {
248
- type: "number",
249
- description: "Max memories to return (default: 20)",
250
- default: 20,
251
- },
252
- },
240
+ schema: z.object({
241
+ limit: z.number().optional().describe("Max memories to return (default: 20)"),
242
+ offset: z.number().optional().describe("Pagination offset (default: 0)"),
243
+ }),
244
+ annotations: TOOL_ANNOTATIONS["review_memories"],
245
+ handler: async (args, ctx) => {
246
+ const limit = args.limit || 20;
247
+ const offset = args.offset || 0;
248
+ const response = await ctx.api.get(`/api/memory/quarantine?limit=${limit}&offset=${offset}`);
249
+ const { memories, count } = response.data;
250
+ if (count === 0) {
251
+ return "No unvalidated memories to review. All auto-extracted learnings have been reviewed.";
252
+ }
253
+ let result = `# \u{1F4CB} Memories Pending Review (${count} total)\n\n`;
254
+ result += `These are auto-extracted learnings that need validation.\n\n`;
255
+ memories.forEach((m, i) => {
256
+ result += `## ${offset + i + 1}. ${m.type.toUpperCase()}\n`;
257
+ result += `**ID**: \`${m.id}\`\n`;
258
+ result += `**Confidence**: ${((m.confidence || 0) * 100).toFixed(0)}%\n`;
259
+ result += `**Source**: ${m.source || "unknown"}\n`;
260
+ result += `**Content**: ${m.content}\n`;
261
+ if (m.tags && m.tags.length > 0) {
262
+ result += `**Tags**: ${m.tags.join(", ")}\n`;
263
+ }
264
+ result += `\nTo validate: \`validate_memory(memoryId="${m.id}", validated=true)\`\n`;
265
+ result += `To reject: \`validate_memory(memoryId="${m.id}", validated=false)\`\n\n`;
266
+ });
267
+ result += paginationFooter(memories.length, limit, offset);
268
+ return result;
253
269
  },
254
270
  },
255
271
  {
256
272
  name: "promote_memory",
257
273
  description: `Promote a quarantine memory to durable storage in ${projectName}. Requires a reason for promotion. Optionally runs quality gates before promotion.`,
258
- inputSchema: {
259
- type: "object",
260
- properties: {
261
- memoryId: {
262
- type: "string",
263
- description: "ID of the memory to promote",
264
- },
265
- reason: {
266
- type: "string",
267
- enum: ["human_validated", "pr_merged", "tests_passed"],
268
- description: "Reason for promotion",
269
- },
270
- evidence: {
271
- type: "string",
272
- description: "Optional evidence supporting the promotion",
273
- },
274
- runGates: {
275
- type: "boolean",
276
- description: "Run quality gates before promotion (default: false)",
277
- },
278
- affectedFiles: {
279
- type: "array",
280
- items: { type: "string" },
281
- description: "Files affected by this memory (for quality gate checking)",
282
- },
283
- },
284
- required: ["memoryId", "reason"],
274
+ schema: z.object({
275
+ memoryId: z.string().describe("ID of the memory to promote"),
276
+ reason: z.enum(["human_validated", "pr_merged", "tests_passed"]).describe("Reason for promotion"),
277
+ evidence: z.string().optional().describe("Optional evidence supporting the promotion"),
278
+ runGates: z.boolean().optional().describe("Run quality gates before promotion (default: false)"),
279
+ affectedFiles: z.array(z.string()).optional().describe("Files affected by this memory (for quality gate checking)"),
280
+ }),
281
+ annotations: TOOL_ANNOTATIONS["promote_memory"],
282
+ handler: async (args, ctx) => {
283
+ const memoryId = args.memoryId;
284
+ const reason = args.reason;
285
+ const evidence = args.evidence;
286
+ const runGates = args.runGates;
287
+ const affectedFiles = args.affectedFiles;
288
+ const response = await ctx.api.post("/api/memory/promote", {
289
+ projectName: ctx.projectName,
290
+ memoryId,
291
+ reason,
292
+ evidence,
293
+ runGates: runGates || false,
294
+ projectPath: runGates ? ctx.projectPath : undefined,
295
+ affectedFiles: runGates ? affectedFiles : undefined,
296
+ });
297
+ const { memory } = response.data;
298
+ return (`\u2705 **Memory promoted to durable storage**\n\n` +
299
+ `- **ID:** ${memory.id}\n` +
300
+ `- **Type:** ${memory.type}\n` +
301
+ `- **Reason:** ${reason}\n` +
302
+ (evidence ? `- **Evidence:** ${evidence}\n` : "") +
303
+ (runGates ? `- **Quality Gates:** passed\n` : "") +
304
+ `- **Content:** ${truncate(memory.content, 200)}`);
285
305
  },
286
306
  },
287
307
  {
288
308
  name: "run_quality_gates",
289
309
  description: `Run quality gates (typecheck, tests, blast radius) for ${projectName}.`,
290
- inputSchema: {
291
- type: "object",
292
- properties: {
293
- affectedFiles: {
294
- type: "array",
295
- items: { type: "string" },
296
- description: "Files to check (for related tests and blast radius)",
297
- },
298
- skipGates: {
299
- type: "array",
300
- items: { type: "string" },
301
- description: "Gates to skip (typecheck, test, blast_radius)",
302
- },
303
- },
310
+ schema: z.object({
311
+ affectedFiles: z.array(z.string()).optional().describe("Files to check (for related tests and blast radius)"),
312
+ skipGates: z.array(z.string()).optional().describe("Gates to skip (typecheck, test, blast_radius)"),
313
+ }),
314
+ annotations: TOOL_ANNOTATIONS["run_quality_gates"],
315
+ handler: async (args, ctx) => {
316
+ const affectedFiles = args.affectedFiles;
317
+ const skipGates = args.skipGates;
318
+ const response = await ctx.api.post("/api/quality/run", {
319
+ projectName: ctx.projectName,
320
+ projectPath: ctx.projectPath,
321
+ affectedFiles,
322
+ skipGates,
323
+ });
324
+ const report = response.data;
325
+ let result = `**Quality Report**: ${report.passed ? "\u2705 All gates passed" : "\u274C Some gates failed"}\n\n`;
326
+ for (const gate of report.gates) {
327
+ const icon = gate.passed ? "\u2705" : "\u274C";
328
+ result += `${icon} **${gate.gate}** (${(gate.duration / 1000).toFixed(1)}s)\n`;
329
+ result += ` ${gate.details.slice(0, 500)}\n\n`;
330
+ }
331
+ if (report.blastRadius) {
332
+ result += `\n**Blast Radius**: ${report.blastRadius.affectedFiles.length} files, depth ${report.blastRadius.depth}\n`;
333
+ if (report.blastRadius.affectedFiles.length > 0) {
334
+ result += report.blastRadius.affectedFiles
335
+ .slice(0, 10)
336
+ .map((f) => ` - ${f}`)
337
+ .join("\n");
338
+ if (report.blastRadius.affectedFiles.length > 10) {
339
+ result += `\n ... and ${report.blastRadius.affectedFiles.length - 10} more`;
340
+ }
341
+ }
342
+ }
343
+ return result;
304
344
  },
305
345
  },
306
346
  {
307
347
  name: "memory_maintenance",
308
348
  description: `Run feedback-driven memory maintenance for ${projectName}: auto-promote memories with 3+ positive feedback, auto-prune memories with 2+ incorrect feedback.`,
309
- inputSchema: {
310
- type: "object",
311
- properties: {},
312
- },
313
- },
314
- ];
315
- const handlers = {
316
- // ----- remember -----
317
- async remember(args, ctx) {
318
- const content = args.content;
319
- const type = args.type || "note";
320
- const tags = args.tags || [];
321
- const relatedTo = args.relatedTo;
322
- const response = await ctx.api.post("/api/memory", {
323
- projectName: ctx.projectName,
324
- content,
325
- type,
326
- tags,
327
- relatedTo,
328
- });
329
- const memory = response.data.memory;
330
- return (`\u2705 **Memory stored**\n\n` +
331
- `- **ID:** ${memory.id}\n` +
332
- `- **Type:** ${memory.type}\n` +
333
- `- **Content:** ${truncate(content, 200)}\n` +
334
- (tags.length > 0 ? `- **Tags:** ${tags.join(", ")}\n` : "") +
335
- (relatedTo ? `- **Related to:** ${relatedTo}\n` : "") +
336
- `- **Created:** ${new Date(memory.createdAt).toLocaleString()}`);
337
- },
338
- // ----- recall -----
339
- async recall(args, ctx) {
340
- const query = args.query;
341
- const type = args.type || "all";
342
- const limit = args.limit || 5;
343
- const response = await ctx.api.post("/api/memory/recall", {
344
- projectName: ctx.projectName,
345
- query,
346
- type,
347
- limit,
348
- });
349
- const results = response.data.results || [];
350
- if (results.length === 0) {
351
- return `\u{1F50D} No memories found for: "${query}"`;
352
- }
353
- const header = `\u{1F9E0} **Recalled Memories** (${results.length} found)\n\n`;
354
- return header + formatMemoryResults(results);
355
- },
356
- // ----- list_memories -----
357
- async list_memories(args, ctx) {
358
- const type = args.type || "all";
359
- const tag = args.tag;
360
- const limit = args.limit || 10;
361
- const params = new URLSearchParams({
362
- projectName: ctx.projectName,
363
- limit: limit.toString(),
364
- });
365
- if (type && type !== "all")
366
- params.append("type", type);
367
- if (tag)
368
- params.append("tag", tag);
369
- const response = await ctx.api.get(`/api/memory/list?${params}`);
370
- const memories = response.data.memories || [];
371
- if (memories.length === 0) {
372
- return `\u{1F4ED} No memories found${type !== "all" ? ` of type "${type}"` : ""}`;
373
- }
374
- let result = `\u{1F4DA} **Agent Memories** (${memories.length})\n\n`;
375
- memories.forEach((m, i) => {
376
- const emoji = typeEmojis[m.type] || "\u{1F4DD}";
377
- const statusStr = m.status ? ` [${m.status}]` : "";
378
- result += `${i + 1}. ${emoji} **${m.type}**${statusStr}: ${truncate(m.content, PREVIEW.SHORT)}\n`;
379
- result += ` ID: \`${m.id}\` | ${new Date(m.createdAt).toLocaleDateString()}\n\n`;
380
- });
381
- return result;
382
- },
383
- // ----- forget -----
384
- async forget(args, ctx) {
385
- const memoryId = args.memoryId;
386
- const type = args.type;
387
- if (memoryId) {
388
- const response = await ctx.api.delete(`/api/memory/${memoryId}?projectName=${ctx.projectName}`);
389
- return response.data.success
390
- ? `\u{1F5D1}\uFE0F Memory deleted: ${memoryId}`
391
- : `\u274C Failed to delete memory: ${memoryId}`;
392
- }
393
- if (type) {
394
- await ctx.api.delete(`/api/memory/type/${type}?projectName=${ctx.projectName}`);
395
- return `\u{1F5D1}\uFE0F Deleted all memories of type: ${type}`;
396
- }
397
- return "Please specify memoryId or type to delete.";
398
- },
399
- // ----- update_todo -----
400
- async update_todo(args, ctx) {
401
- const todoId = args.todoId;
402
- const status = args.status;
403
- const note = args.note;
404
- const response = await ctx.api.patch(`/api/memory/todo/${todoId}`, {
405
- projectName: ctx.projectName,
406
- status,
407
- note,
408
- });
409
- if (!response.data.memory) {
410
- return `\u274C Todo not found: ${todoId}`;
411
- }
412
- return (`${statusEmojis[status] || "\u{1F4CB}"} **Todo updated**\n\n` +
413
- `- **ID:** ${todoId}\n` +
414
- `- **Status:** ${status}\n` +
415
- (note ? `- **Note:** ${note}\n` : "") +
416
- `- **Content:** ${response.data.memory.content}`);
417
- },
418
- // ----- batch_remember -----
419
- async batch_remember(args, ctx) {
420
- const items = args.items;
421
- const response = await ctx.api.post("/api/memory/batch", {
422
- items,
423
- });
424
- const { savedCount, errors, memories } = response.data;
425
- let result = `# \u{1F4E6} Batch Memory Result\n\n`;
426
- result += `**Saved**: ${savedCount} memories\n\n`;
427
- if (memories && memories.length > 0) {
428
- result += `## Stored Memories\n`;
429
- memories.forEach((m) => {
430
- result += `- [${m.type}] ${truncate(m.content, 80)}\n`;
431
- result += ` ID: \`${m.id}\`\n`;
432
- });
433
- }
434
- if (errors && errors.length > 0) {
435
- result += `\n## \u26A0\uFE0F Errors\n`;
436
- errors.forEach((e) => {
437
- result += `- ${e}\n`;
349
+ schema: z.object({}),
350
+ annotations: TOOL_ANNOTATIONS["memory_maintenance"],
351
+ handler: async (_args, ctx) => {
352
+ const response = await ctx.api.post("/api/memory/maintenance", {
353
+ projectName: ctx.projectName,
438
354
  });
439
- }
440
- return result;
441
- },
442
- // ----- validate_memory -----
443
- async validate_memory(args, ctx) {
444
- const memoryId = args.memoryId;
445
- const validated = args.validated;
446
- const response = await ctx.api.patch(`/api/memory/${memoryId}/validate`, {
447
- validated,
448
- });
449
- const { memory } = response.data;
450
- return (`\u2705 Memory ${validated ? "validated" : "rejected"}\n\n` +
451
- `- **ID**: ${memory.id}\n` +
452
- `- **Type**: ${memory.type}\n` +
453
- `- **Content**: ${truncate(memory.content, PREVIEW.SHORT)}\n` +
454
- `- **Validated**: ${memory.validated}`);
455
- },
456
- // ----- promote_memory -----
457
- async promote_memory(args, ctx) {
458
- const memoryId = args.memoryId;
459
- const reason = args.reason;
460
- const evidence = args.evidence;
461
- const runGates = args.runGates;
462
- const affectedFiles = args.affectedFiles;
463
- const response = await ctx.api.post("/api/memory/promote", {
464
- projectName: ctx.projectName,
465
- memoryId,
466
- reason,
467
- evidence,
468
- runGates: runGates || false,
469
- projectPath: runGates ? ctx.projectPath : undefined,
470
- affectedFiles: runGates ? affectedFiles : undefined,
471
- });
472
- const { memory } = response.data;
473
- return (`\u2705 **Memory promoted to durable storage**\n\n` +
474
- `- **ID:** ${memory.id}\n` +
475
- `- **Type:** ${memory.type}\n` +
476
- `- **Reason:** ${reason}\n` +
477
- (evidence ? `- **Evidence:** ${evidence}\n` : "") +
478
- (runGates ? `- **Quality Gates:** passed\n` : "") +
479
- `- **Content:** ${truncate(memory.content, 200)}`);
480
- },
481
- // ----- run_quality_gates -----
482
- async run_quality_gates(args, ctx) {
483
- const affectedFiles = args.affectedFiles;
484
- const skipGates = args.skipGates;
485
- const response = await ctx.api.post("/api/quality/run", {
486
- projectName: ctx.projectName,
487
- projectPath: ctx.projectPath,
488
- affectedFiles,
489
- skipGates,
490
- });
491
- const report = response.data;
492
- let result = `**Quality Report**: ${report.passed ? "\u2705 All gates passed" : "\u274C Some gates failed"}\n\n`;
493
- for (const gate of report.gates) {
494
- const icon = gate.passed ? "\u2705" : "\u274C";
495
- result += `${icon} **${gate.gate}** (${(gate.duration / 1000).toFixed(1)}s)\n`;
496
- result += ` ${gate.details.slice(0, 500)}\n\n`;
497
- }
498
- if (report.blastRadius) {
499
- result += `\n**Blast Radius**: ${report.blastRadius.affectedFiles.length} files, depth ${report.blastRadius.depth}\n`;
500
- if (report.blastRadius.affectedFiles.length > 0) {
501
- result += report.blastRadius.affectedFiles
502
- .slice(0, 10)
503
- .map((f) => ` - ${f}`)
504
- .join("\n");
505
- if (report.blastRadius.affectedFiles.length > 10) {
506
- result += `\n ... and ${report.blastRadius.affectedFiles.length - 10} more`;
507
- }
355
+ const { promoted, pruned, errors } = response.data;
356
+ let result = `# \u{1F9F9} Memory Maintenance Results\n\n`;
357
+ if (promoted.length > 0) {
358
+ result += `**Promoted** (${promoted.length}): memories with 3+ positive feedback moved to durable\n`;
359
+ promoted.forEach((id) => { result += ` \u2705 ${id}\n`; });
360
+ result += `\n`;
508
361
  }
509
- }
510
- return result;
511
- },
512
- // ----- memory_maintenance -----
513
- async memory_maintenance(args, ctx) {
514
- const response = await ctx.api.post("/api/memory/maintenance", {
515
- projectName: ctx.projectName,
516
- });
517
- const { promoted, pruned, errors } = response.data;
518
- let result = `# \u{1F9F9} Memory Maintenance Results\n\n`;
519
- if (promoted.length > 0) {
520
- result += `**Promoted** (${promoted.length}): memories with 3+ positive feedback moved to durable\n`;
521
- promoted.forEach((id) => { result += ` \u2705 ${id}\n`; });
522
- result += `\n`;
523
- }
524
- if (pruned.length > 0) {
525
- result += `**Pruned** (${pruned.length}): memories with 2+ incorrect feedback removed\n`;
526
- pruned.forEach((id) => { result += ` \u{1F5D1}\u{FE0F} ${id}\n`; });
527
- result += `\n`;
528
- }
529
- if (errors.length > 0) {
530
- result += `**Errors** (${errors.length}):\n`;
531
- errors.forEach((e) => { result += ` \u26A0\u{FE0F} ${e}\n`; });
532
- result += `\n`;
533
- }
534
- if (promoted.length === 0 && pruned.length === 0) {
535
- result += `No memories needed maintenance. All feedback thresholds are below auto-action levels.\n`;
536
- }
537
- return result;
538
- },
539
- // ----- review_memories -----
540
- async review_memories(args, ctx) {
541
- const limit = args.limit || 20;
542
- const response = await ctx.api.get(`/api/memory/quarantine?limit=${limit}`);
543
- const { memories, count } = response.data;
544
- if (count === 0) {
545
- return "No unvalidated memories to review. All auto-extracted learnings have been reviewed.";
546
- }
547
- let result = `# \u{1F4CB} Memories Pending Review (${count})\n\n`;
548
- result += `These are auto-extracted learnings that need validation.\n\n`;
549
- memories.forEach((m, i) => {
550
- result += `## ${i + 1}. ${m.type.toUpperCase()}\n`;
551
- result += `**ID**: \`${m.id}\`\n`;
552
- result += `**Confidence**: ${((m.confidence || 0) * 100).toFixed(0)}%\n`;
553
- result += `**Source**: ${m.source || "unknown"}\n`;
554
- result += `**Content**: ${m.content}\n`;
555
- if (m.tags && m.tags.length > 0) {
556
- result += `**Tags**: ${m.tags.join(", ")}\n`;
362
+ if (pruned.length > 0) {
363
+ result += `**Pruned** (${pruned.length}): memories with 2+ incorrect feedback removed\n`;
364
+ pruned.forEach((id) => { result += ` \u{1F5D1}\u{FE0F} ${id}\n`; });
365
+ result += `\n`;
366
+ }
367
+ if (errors.length > 0) {
368
+ result += `**Errors** (${errors.length}):\n`;
369
+ errors.forEach((e) => { result += ` \u26A0\u{FE0F} ${e}\n`; });
370
+ result += `\n`;
371
+ }
372
+ if (promoted.length === 0 && pruned.length === 0) {
373
+ result += `No memories needed maintenance. All feedback thresholds are below auto-action levels.\n`;
557
374
  }
558
- result += `\nTo validate: \`validate_memory(memoryId="${m.id}", validated=true)\`\n`;
559
- result += `To reject: \`validate_memory(memoryId="${m.id}", validated=false)\`\n\n`;
560
- });
561
- return result;
375
+ return result;
376
+ },
562
377
  },
563
- };
564
- return { tools, handlers };
378
+ ];
565
379
  }