@crowley/rag-mcp 1.0.5 → 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 (49) hide show
  1. package/dist/annotations.d.ts +16 -0
  2. package/dist/annotations.js +158 -0
  3. package/dist/context-enrichment.js +7 -0
  4. package/dist/formatters.d.ts +2 -0
  5. package/dist/formatters.js +12 -0
  6. package/dist/index.js +46 -47
  7. package/dist/schemas.d.ts +97 -0
  8. package/dist/schemas.js +128 -0
  9. package/dist/tool-middleware.d.ts +40 -0
  10. package/dist/tool-middleware.js +216 -0
  11. package/dist/tool-registry.js +2 -1
  12. package/dist/tools/advanced.d.ts +2 -2
  13. package/dist/tools/advanced.js +200 -275
  14. package/dist/tools/agents.d.ts +2 -2
  15. package/dist/tools/agents.js +59 -78
  16. package/dist/tools/analytics.d.ts +2 -2
  17. package/dist/tools/analytics.js +170 -210
  18. package/dist/tools/architecture.d.ts +2 -2
  19. package/dist/tools/architecture.js +506 -669
  20. package/dist/tools/ask.d.ts +2 -2
  21. package/dist/tools/ask.js +164 -219
  22. package/dist/tools/cache.d.ts +2 -2
  23. package/dist/tools/cache.js +63 -82
  24. package/dist/tools/clustering.d.ts +2 -2
  25. package/dist/tools/clustering.js +154 -215
  26. package/dist/tools/confluence.d.ts +2 -2
  27. package/dist/tools/confluence.js +80 -116
  28. package/dist/tools/database.d.ts +2 -2
  29. package/dist/tools/database.js +303 -380
  30. package/dist/tools/feedback.d.ts +2 -2
  31. package/dist/tools/feedback.js +143 -184
  32. package/dist/tools/guidelines.d.ts +2 -2
  33. package/dist/tools/guidelines.js +123 -135
  34. package/dist/tools/indexing.d.ts +2 -2
  35. package/dist/tools/indexing.js +100 -108
  36. package/dist/tools/memory.d.ts +2 -2
  37. package/dist/tools/memory.js +299 -485
  38. package/dist/tools/pm.d.ts +2 -2
  39. package/dist/tools/pm.js +367 -615
  40. package/dist/tools/review.d.ts +2 -2
  41. package/dist/tools/review.js +142 -189
  42. package/dist/tools/search.d.ts +2 -2
  43. package/dist/tools/search.js +230 -305
  44. package/dist/tools/session.d.ts +2 -2
  45. package/dist/tools/session.js +288 -345
  46. package/dist/tools/suggestions.d.ts +2 -2
  47. package/dist/tools/suggestions.js +425 -512
  48. package/dist/types.d.ts +19 -2
  49. package/package.json +4 -2
@@ -5,506 +5,419 @@
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import { truncate, pct, PREVIEW } from "../formatters.js";
8
+ import { z } from "zod";
9
+ import { TOOL_ANNOTATIONS } from "../annotations.js";
8
10
  /**
9
11
  * Create the suggestions tools module with project-specific descriptions.
10
12
  */
11
13
  export function createSuggestionTools(projectName) {
12
- const tools = [
14
+ return [
13
15
  {
14
16
  name: "context_briefing",
15
17
  description: `REQUIRED before code changes. Parallel lookup of recall + search + patterns + ADRs + graph for ${projectName}. One call replaces 5 separate RAG lookups.`,
16
- inputSchema: {
17
- type: "object",
18
- properties: {
19
- task: {
20
- type: "string",
21
- description: "What you will implement/change",
22
- },
23
- files: {
24
- type: "array",
25
- items: { type: "string" },
26
- description: "Files you plan to modify",
27
- },
28
- },
29
- required: ["task"],
18
+ schema: z.object({
19
+ task: z.string().describe("What you will implement/change"),
20
+ files: z.array(z.string()).optional().describe("Files you plan to modify"),
21
+ }),
22
+ annotations: TOOL_ANNOTATIONS["context_briefing"],
23
+ handler: async (args, ctx) => {
24
+ const { task, files } = args;
25
+ // 5 parallel lookups
26
+ const [memoriesRes, searchRes, patternsRes, adrsRes, graphRes] = await Promise.all([
27
+ // 1. Recall relevant memories
28
+ ctx.api
29
+ .post("/api/memory/recall", {
30
+ projectName: ctx.projectName,
31
+ query: task,
32
+ limit: 5,
33
+ type: "all",
34
+ })
35
+ .catch(() => null),
36
+ // 2. Hybrid search for related code
37
+ ctx.api
38
+ .post("/api/search-hybrid", {
39
+ projectName: ctx.projectName,
40
+ query: task,
41
+ limit: 5,
42
+ mode: "navigate",
43
+ })
44
+ .catch(() => null),
45
+ // 3. Architectural patterns
46
+ ctx.api
47
+ .post("/api/memory/recall", {
48
+ projectName: ctx.projectName,
49
+ query: task,
50
+ type: "context",
51
+ limit: 5,
52
+ tag: "pattern",
53
+ })
54
+ .catch(() => null),
55
+ // 4. ADRs
56
+ ctx.api
57
+ .post("/api/memory/recall", {
58
+ projectName: ctx.projectName,
59
+ query: task,
60
+ type: "decision",
61
+ limit: 3,
62
+ tag: "adr",
63
+ })
64
+ .catch(() => null),
65
+ // 5. Graph dependencies (if files specified)
66
+ files && files.length > 0
67
+ ? ctx.api
68
+ .post("/api/search-graph", {
69
+ projectName: ctx.projectName,
70
+ query: files[0],
71
+ expandHops: 1,
72
+ limit: 5,
73
+ })
74
+ .catch(() => null)
75
+ : Promise.resolve(null),
76
+ ]);
77
+ let result = `# Context Briefing: ${task}\n\n`;
78
+ // Memories
79
+ const memories = memoriesRes?.data?.results || memoriesRes?.data?.memories || [];
80
+ if (memories.length > 0) {
81
+ result += `## Memories (${memories.length})\n`;
82
+ for (const m of memories) {
83
+ const mem = m.memory || m;
84
+ result += `- [${mem.type || "note"}] ${truncate(mem.content || "", 150)}\n`;
85
+ }
86
+ result += "\n";
87
+ }
88
+ // Related code
89
+ const codeResults = searchRes?.data?.results || [];
90
+ if (codeResults.length > 0) {
91
+ result += `## Related Code (${codeResults.length})\n`;
92
+ for (const r of codeResults) {
93
+ result += `- \`${r.file}\``;
94
+ if (r.symbols?.length)
95
+ result += ` — ${r.symbols.join(", ")}`;
96
+ result += "\n";
97
+ }
98
+ result += "\n";
99
+ }
100
+ // Patterns
101
+ const patterns = (patternsRes?.data?.results || []).filter((r) => r.memory?.tags?.includes("pattern"));
102
+ if (patterns.length > 0) {
103
+ result += `## Patterns (${patterns.length})\n`;
104
+ for (const p of patterns) {
105
+ const name = p.memory?.metadata?.patternName || p.memory?.relatedTo || "Pattern";
106
+ result += `- **${name}**: ${truncate(p.memory?.content || "", 120)}\n`;
107
+ }
108
+ result += "\n";
109
+ }
110
+ // ADRs
111
+ const adrs = (adrsRes?.data?.results || []).filter((r) => r.memory?.tags?.includes("adr"));
112
+ if (adrs.length > 0) {
113
+ result += `## ADRs (${adrs.length})\n`;
114
+ for (const a of adrs) {
115
+ const title = a.memory?.metadata?.adrTitle || a.memory?.relatedTo || "ADR";
116
+ result += `- **${title}**: ${truncate(a.memory?.content || "", 120)}\n`;
117
+ }
118
+ result += "\n";
119
+ }
120
+ // Graph dependencies
121
+ const graphResults = graphRes?.data?.results || graphRes?.data?.directResults || [];
122
+ const connectedFiles = graphRes?.data?.connectedFiles || graphRes?.data?.expandedResults || [];
123
+ if (graphResults.length > 0 || connectedFiles.length > 0) {
124
+ result += `## Dependencies\n`;
125
+ for (const g of graphResults) {
126
+ result += `- \`${g.file}\`\n`;
127
+ }
128
+ for (const c of connectedFiles) {
129
+ result += `- \`${c.file}\` (connected)\n`;
130
+ }
131
+ result += "\n";
132
+ }
133
+ if (result.endsWith(`# Context Briefing: ${task}\n\n`)) {
134
+ result += "_No relevant context found. Proceed with implementation._\n";
135
+ }
136
+ return result;
30
137
  },
31
138
  },
32
139
  {
33
140
  name: "get_contextual_suggestions",
34
141
  description: `Get contextual suggestions based on current work context for ${projectName}. Returns relevant suggestions, triggers, and related memories.`,
35
- inputSchema: {
36
- type: "object",
37
- properties: {
38
- currentFile: {
39
- type: "string",
40
- description: "Currently active file path",
41
- },
42
- currentCode: {
43
- type: "string",
44
- description: "Currently selected or visible code",
45
- },
46
- recentFiles: {
47
- type: "array",
48
- items: { type: "string" },
49
- description: "Recently opened file paths",
50
- },
51
- task: {
52
- type: "string",
53
- description: "Current task description",
54
- },
55
- },
142
+ schema: z.object({
143
+ currentFile: z.string().optional().describe("Currently active file path"),
144
+ currentCode: z.string().optional().describe("Currently selected or visible code"),
145
+ recentFiles: z.array(z.string()).optional().describe("Recently opened file paths"),
146
+ task: z.string().optional().describe("Current task description"),
147
+ }),
148
+ annotations: TOOL_ANNOTATIONS["get_contextual_suggestions"],
149
+ handler: async (args, ctx) => {
150
+ const { currentFile, currentCode, recentFiles, task } = args;
151
+ const response = await ctx.api.post("/api/suggestions", {
152
+ projectName: ctx.projectName,
153
+ currentFile,
154
+ currentCode,
155
+ recentFiles,
156
+ task,
157
+ });
158
+ const data = response.data;
159
+ let result = `## Contextual Suggestions\n\n`;
160
+ if (data.relevanceScore !== undefined) {
161
+ result += `**Relevance Score:** ${pct(data.relevanceScore)}\n\n`;
162
+ }
163
+ if (data.triggers && data.triggers.length > 0) {
164
+ result += `### Triggers\n`;
165
+ for (const t of data.triggers) {
166
+ result += `- **${t.type}:** ${t.value}`;
167
+ if (t.confidence)
168
+ result += ` (${pct(t.confidence)})`;
169
+ result += "\n";
170
+ }
171
+ result += "\n";
172
+ }
173
+ if (data.suggestions && data.suggestions.length > 0) {
174
+ result += `### Suggestions\n`;
175
+ for (const s of data.suggestions) {
176
+ result += `- **${s.title}** [${s.type}]\n`;
177
+ if (s.description)
178
+ result += ` ${s.description}\n`;
179
+ if (s.reason)
180
+ result += ` *Reason: ${s.reason}*\n`;
181
+ if (s.relevance !== undefined)
182
+ result += ` Relevance: ${pct(s.relevance)}\n`;
183
+ }
184
+ result += "\n";
185
+ }
186
+ if (data.relatedMemories && data.relatedMemories.length > 0) {
187
+ result += `### Related Memories\n`;
188
+ for (const m of data.relatedMemories) {
189
+ result += `- ${m.content || m.title || JSON.stringify(m)}\n`;
190
+ }
191
+ }
192
+ return result;
56
193
  },
57
194
  },
58
195
  {
59
196
  name: "suggest_related_code",
60
197
  description: `Find code related to a given file or snippet in ${projectName}. Shows similar implementations and related modules.`,
61
- inputSchema: {
62
- type: "object",
63
- properties: {
64
- file: {
65
- type: "string",
66
- description: "File path to find related code for",
67
- },
68
- code: {
69
- type: "string",
70
- description: "Code snippet to find related code for",
71
- },
72
- limit: {
73
- type: "number",
74
- description: "Max results (default: 5)",
75
- default: 5,
76
- },
77
- },
198
+ schema: z.object({
199
+ file: z.string().optional().describe("File path to find related code for"),
200
+ code: z.string().optional().describe("Code snippet to find related code for"),
201
+ limit: z.number().optional().describe("Max results (default: 5)"),
202
+ }),
203
+ annotations: TOOL_ANNOTATIONS["suggest_related_code"],
204
+ handler: async (args, ctx) => {
205
+ const { file, code, limit = 5 } = args;
206
+ const response = await ctx.api.post("/api/code/related", {
207
+ projectName: ctx.projectName,
208
+ file,
209
+ code,
210
+ limit,
211
+ });
212
+ const results = response.data.results || response.data;
213
+ if (!results || results.length === 0) {
214
+ return "No related code found.";
215
+ }
216
+ let result = `## Related Code\n\n`;
217
+ for (const r of results) {
218
+ result += `### ${r.file}\n`;
219
+ result += `**Score:** ${pct(r.score)}`;
220
+ if (r.reason)
221
+ result += ` | **Reason:** ${r.reason}`;
222
+ if (r.line)
223
+ result += ` | Line ${r.line}`;
224
+ result += "\n";
225
+ if (r.content || r.code) {
226
+ result += "```\n" + truncate(r.content || r.code, PREVIEW.MEDIUM) + "\n```\n";
227
+ }
228
+ result += "\n";
229
+ }
230
+ return result;
78
231
  },
79
232
  },
80
233
  {
81
234
  name: "suggest_implementation",
82
235
  description: `Get implementation suggestions for a feature in ${projectName}. Shows similar patterns and adaptation hints.`,
83
- inputSchema: {
84
- type: "object",
85
- properties: {
86
- description: {
87
- type: "string",
88
- description: "Description of what to implement",
89
- },
90
- currentFile: {
91
- type: "string",
92
- description: "Current file for context",
93
- },
94
- language: {
95
- type: "string",
96
- description: "Target programming language",
97
- },
98
- },
99
- required: ["description"],
236
+ schema: z.object({
237
+ description: z.string().describe("Description of what to implement"),
238
+ currentFile: z.string().optional().describe("Current file for context"),
239
+ language: z.string().optional().describe("Target programming language"),
240
+ }),
241
+ annotations: TOOL_ANNOTATIONS["suggest_implementation"],
242
+ handler: async (args, ctx) => {
243
+ const { description, currentFile, language } = args;
244
+ const response = await ctx.api.post("/api/code/suggest-implementation", {
245
+ projectName: ctx.projectName,
246
+ description,
247
+ currentFile,
248
+ language,
249
+ });
250
+ const data = response.data;
251
+ const patterns = data.patterns || data.results || [];
252
+ if (!patterns || patterns.length === 0) {
253
+ return "No implementation suggestions found.";
254
+ }
255
+ const patternIcons = {
256
+ similar_structure: "\ud83d\udcd0",
257
+ same_domain: "\ud83c\udfaf",
258
+ related_import: "\ud83d\udce6",
259
+ test_pattern: "\ud83e\uddea",
260
+ };
261
+ let result = `## Implementation Suggestions\n\n`;
262
+ for (const p of patterns) {
263
+ const icon = patternIcons[p.pattern || p.type] || "\ud83d\udcd0";
264
+ result += `### ${icon} ${p.file || p.name || "Pattern"}\n`;
265
+ if (p.adaptationHints || p.hints) {
266
+ result += `**Adaptation:** ${p.adaptationHints || p.hints}\n`;
267
+ }
268
+ if (p.content || p.code) {
269
+ result += "```\n" + truncate(p.content || p.code, 400) + "\n```\n";
270
+ }
271
+ result += "\n";
272
+ }
273
+ return result;
100
274
  },
101
275
  },
102
276
  {
103
277
  name: "suggest_tests",
104
278
  description: `Get test suggestions for code in ${projectName}. Shows recommended test types, frameworks, and example patterns.`,
105
- inputSchema: {
106
- type: "object",
107
- properties: {
108
- file: {
109
- type: "string",
110
- description: "File to suggest tests for",
111
- },
112
- code: {
113
- type: "string",
114
- description: "Code to suggest tests for",
115
- },
116
- framework: {
117
- type: "string",
118
- description: "Test framework preference (jest, mocha, pytest, etc.)",
119
- },
120
- },
279
+ schema: z.object({
280
+ file: z.string().optional().describe("File to suggest tests for"),
281
+ code: z.string().optional().describe("Code to suggest tests for"),
282
+ framework: z.string().optional().describe("Test framework preference (jest, mocha, pytest, etc.)"),
283
+ }),
284
+ annotations: TOOL_ANNOTATIONS["suggest_tests"],
285
+ handler: async (args, ctx) => {
286
+ const { file, code, framework } = args;
287
+ const response = await ctx.api.post("/api/code/suggest-tests", {
288
+ projectName: ctx.projectName,
289
+ file,
290
+ code,
291
+ framework,
292
+ });
293
+ const data = response.data;
294
+ const tests = data.tests || data.suggestions || data.results || [];
295
+ if (!tests || tests.length === 0) {
296
+ return "No test suggestions found.";
297
+ }
298
+ const typeIcons = {
299
+ unit: "\ud83d\udd2c",
300
+ integration: "\ud83d\udd17",
301
+ e2e: "\ud83c\udf10",
302
+ };
303
+ let result = `## Test Suggestions\n\n`;
304
+ for (const t of tests) {
305
+ const icon = typeIcons[t.type] || "\ud83d\udd2c";
306
+ result += `### ${icon} ${t.name || t.title || t.type || "Test"}\n`;
307
+ if (t.framework)
308
+ result += `**Framework:** ${t.framework}\n`;
309
+ if (t.coverage)
310
+ result += `**Coverage:** ${t.coverage}\n`;
311
+ if (t.content || t.code) {
312
+ result += "```\n" + truncate(t.content || t.code, PREVIEW.LONG) + "\n```\n";
313
+ }
314
+ result += "\n";
315
+ }
316
+ return result;
121
317
  },
122
318
  },
123
319
  {
124
320
  name: "get_code_context",
125
321
  description: `Get full context for a code file in ${projectName}. Shows imports, related code, and test patterns.`,
126
- inputSchema: {
127
- type: "object",
128
- properties: {
129
- file: {
130
- type: "string",
131
- description: "File path to get context for",
132
- },
133
- code: {
134
- type: "string",
135
- description: "Code snippet for context",
136
- },
137
- },
138
- },
139
- },
140
- {
141
- name: "setup_project",
142
- description: "Configure Claude Code for RAG integration. Creates/updates .mcp.json, adds RAG instructions to CLAUDE.md, and configures permissions. Call after index_codebase on a new project.",
143
- inputSchema: {
144
- type: "object",
145
- properties: {
146
- projectPath: {
147
- type: "string",
148
- description: "Absolute path to project root",
149
- },
150
- projectName: {
151
- type: "string",
152
- description: "Project name in Qdrant (collection prefix)",
153
- },
154
- ragApiUrl: {
155
- type: "string",
156
- description: "RAG API URL (default: from MCP env)",
157
- },
158
- ragApiKey: {
159
- type: "string",
160
- description: "RAG API key (default: from MCP env)",
161
- },
162
- updateClaudeMd: {
163
- type: "boolean",
164
- description: "Add RAG section to CLAUDE.md (default: true)",
165
- },
166
- },
167
- required: ["projectPath", "projectName"],
168
- },
169
- },
170
- ];
171
- const handlers = {
172
- context_briefing: async (args, ctx) => {
173
- const { task, files } = args;
174
- // 5 parallel lookups
175
- const [memoriesRes, searchRes, patternsRes, adrsRes, graphRes] = await Promise.all([
176
- // 1. Recall relevant memories
177
- ctx.api
178
- .post("/api/memory/recall", {
179
- projectName: ctx.projectName,
180
- query: task,
181
- limit: 5,
182
- type: "all",
183
- })
184
- .catch(() => null),
185
- // 2. Hybrid search for related code
186
- ctx.api
187
- .post("/api/search-hybrid", {
188
- projectName: ctx.projectName,
189
- query: task,
190
- limit: 5,
191
- mode: "navigate",
192
- })
193
- .catch(() => null),
194
- // 3. Architectural patterns
195
- ctx.api
196
- .post("/api/memory/recall", {
197
- projectName: ctx.projectName,
198
- query: task,
199
- type: "context",
200
- limit: 5,
201
- tag: "pattern",
202
- })
203
- .catch(() => null),
204
- // 4. ADRs
205
- ctx.api
206
- .post("/api/memory/recall", {
322
+ schema: z.object({
323
+ file: z.string().optional().describe("File path to get context for"),
324
+ code: z.string().optional().describe("Code snippet for context"),
325
+ }),
326
+ annotations: TOOL_ANNOTATIONS["get_code_context"],
327
+ handler: async (args, ctx) => {
328
+ const { file, code } = args;
329
+ const response = await ctx.api.post("/api/code/context", {
207
330
  projectName: ctx.projectName,
208
- query: task,
209
- type: "decision",
210
- limit: 3,
211
- tag: "adr",
212
- })
213
- .catch(() => null),
214
- // 5. Graph dependencies (if files specified)
215
- files && files.length > 0
216
- ? ctx.api
217
- .post("/api/search-graph", {
218
- projectName: ctx.projectName,
219
- query: files[0],
220
- expandHops: 1,
221
- limit: 5,
222
- })
223
- .catch(() => null)
224
- : Promise.resolve(null),
225
- ]);
226
- let result = `# Context Briefing: ${task}\n\n`;
227
- // Memories
228
- const memories = memoriesRes?.data?.results || memoriesRes?.data?.memories || [];
229
- if (memories.length > 0) {
230
- result += `## Memories (${memories.length})\n`;
231
- for (const m of memories) {
232
- const mem = m.memory || m;
233
- result += `- [${mem.type || "note"}] ${truncate(mem.content || "", 150)}\n`;
234
- }
235
- result += "\n";
236
- }
237
- // Related code
238
- const codeResults = searchRes?.data?.results || [];
239
- if (codeResults.length > 0) {
240
- result += `## Related Code (${codeResults.length})\n`;
241
- for (const r of codeResults) {
242
- result += `- \`${r.file}\``;
243
- if (r.symbols?.length)
244
- result += ` — ${r.symbols.join(", ")}`;
331
+ file,
332
+ code,
333
+ });
334
+ const data = response.data;
335
+ let result = `## Code Context\n\n`;
336
+ if (data.imports && data.imports.length > 0) {
337
+ result += `### Imports\n`;
338
+ for (const imp of data.imports) {
339
+ result += `- ${imp}\n`;
340
+ }
245
341
  result += "\n";
246
342
  }
247
- result += "\n";
248
- }
249
- // Patterns
250
- const patterns = (patternsRes?.data?.results || []).filter((r) => r.memory?.tags?.includes("pattern"));
251
- if (patterns.length > 0) {
252
- result += `## Patterns (${patterns.length})\n`;
253
- for (const p of patterns) {
254
- const name = p.memory?.metadata?.patternName || p.memory?.relatedTo || "Pattern";
255
- result += `- **${name}**: ${truncate(p.memory?.content || "", 120)}\n`;
256
- }
257
- result += "\n";
258
- }
259
- // ADRs
260
- const adrs = (adrsRes?.data?.results || []).filter((r) => r.memory?.tags?.includes("adr"));
261
- if (adrs.length > 0) {
262
- result += `## ADRs (${adrs.length})\n`;
263
- for (const a of adrs) {
264
- const title = a.memory?.metadata?.adrTitle || a.memory?.relatedTo || "ADR";
265
- result += `- **${title}**: ${truncate(a.memory?.content || "", 120)}\n`;
266
- }
267
- result += "\n";
268
- }
269
- // Graph dependencies
270
- const graphResults = graphRes?.data?.results || graphRes?.data?.directResults || [];
271
- const connectedFiles = graphRes?.data?.connectedFiles || graphRes?.data?.expandedResults || [];
272
- if (graphResults.length > 0 || connectedFiles.length > 0) {
273
- result += `## Dependencies\n`;
274
- for (const g of graphResults) {
275
- result += `- \`${g.file}\`\n`;
276
- }
277
- for (const c of connectedFiles) {
278
- result += `- \`${c.file}\` (connected)\n`;
279
- }
280
- result += "\n";
281
- }
282
- if (result.endsWith(`# Context Briefing: ${task}\n\n`)) {
283
- result += "_No relevant context found. Proceed with implementation._\n";
284
- }
285
- return result;
286
- },
287
- get_contextual_suggestions: async (args, ctx) => {
288
- const { currentFile, currentCode, recentFiles, task } = args;
289
- const response = await ctx.api.post("/api/suggestions", {
290
- projectName: ctx.projectName,
291
- currentFile,
292
- currentCode,
293
- recentFiles,
294
- task,
295
- });
296
- const data = response.data;
297
- let result = `## Contextual Suggestions\n\n`;
298
- if (data.relevanceScore !== undefined) {
299
- result += `**Relevance Score:** ${pct(data.relevanceScore)}\n\n`;
300
- }
301
- if (data.triggers && data.triggers.length > 0) {
302
- result += `### Triggers\n`;
303
- for (const t of data.triggers) {
304
- result += `- **${t.type}:** ${t.value}`;
305
- if (t.confidence)
306
- result += ` (${pct(t.confidence)})`;
343
+ if (data.relatedCode && data.relatedCode.length > 0) {
344
+ result += `### Related Code\n`;
345
+ for (const r of data.relatedCode) {
346
+ result += `- **${r.file}** (${pct(r.score)})`;
347
+ if (r.reason)
348
+ result += ` - ${r.reason}`;
349
+ result += "\n";
350
+ }
307
351
  result += "\n";
308
352
  }
309
- result += "\n";
310
- }
311
- if (data.suggestions && data.suggestions.length > 0) {
312
- result += `### Suggestions\n`;
313
- for (const s of data.suggestions) {
314
- result += `- **${s.title}** [${s.type}]\n`;
315
- if (s.description)
316
- result += ` ${s.description}\n`;
317
- if (s.reason)
318
- result += ` *Reason: ${s.reason}*\n`;
319
- if (s.relevance !== undefined)
320
- result += ` Relevance: ${pct(s.relevance)}\n`;
321
- }
322
- result += "\n";
323
- }
324
- if (data.relatedMemories && data.relatedMemories.length > 0) {
325
- result += `### Related Memories\n`;
326
- for (const m of data.relatedMemories) {
327
- result += `- ${m.content || m.title || JSON.stringify(m)}\n`;
328
- }
329
- }
330
- return result;
331
- },
332
- suggest_related_code: async (args, ctx) => {
333
- const { file, code, limit = 5 } = args;
334
- const response = await ctx.api.post("/api/code/related", {
335
- projectName: ctx.projectName,
336
- file,
337
- code,
338
- limit,
339
- });
340
- const results = response.data.results || response.data;
341
- if (!results || results.length === 0) {
342
- return "No related code found.";
343
- }
344
- let result = `## Related Code\n\n`;
345
- for (const r of results) {
346
- result += `### ${r.file}\n`;
347
- result += `**Score:** ${pct(r.score)}`;
348
- if (r.reason)
349
- result += ` | **Reason:** ${r.reason}`;
350
- if (r.line)
351
- result += ` | Line ${r.line}`;
352
- result += "\n";
353
- if (r.content || r.code) {
354
- result += "```\n" + truncate(r.content || r.code, PREVIEW.MEDIUM) + "\n```\n";
355
- }
356
- result += "\n";
357
- }
358
- return result;
359
- },
360
- suggest_implementation: async (args, ctx) => {
361
- const { description, currentFile, language } = args;
362
- const response = await ctx.api.post("/api/code/suggest-implementation", {
363
- projectName: ctx.projectName,
364
- description,
365
- currentFile,
366
- language,
367
- });
368
- const data = response.data;
369
- const patterns = data.patterns || data.results || [];
370
- if (!patterns || patterns.length === 0) {
371
- return "No implementation suggestions found.";
372
- }
373
- const patternIcons = {
374
- similar_structure: "\ud83d\udcd0",
375
- same_domain: "\ud83c\udfaf",
376
- related_import: "\ud83d\udce6",
377
- test_pattern: "\ud83e\uddea",
378
- };
379
- let result = `## Implementation Suggestions\n\n`;
380
- for (const p of patterns) {
381
- const icon = patternIcons[p.pattern || p.type] || "\ud83d\udcd0";
382
- result += `### ${icon} ${p.file || p.name || "Pattern"}\n`;
383
- if (p.adaptationHints || p.hints) {
384
- result += `**Adaptation:** ${p.adaptationHints || p.hints}\n`;
385
- }
386
- if (p.content || p.code) {
387
- result += "```\n" + truncate(p.content || p.code, 400) + "\n```\n";
388
- }
389
- result += "\n";
390
- }
391
- return result;
392
- },
393
- suggest_tests: async (args, ctx) => {
394
- const { file, code, framework } = args;
395
- const response = await ctx.api.post("/api/code/suggest-tests", {
396
- projectName: ctx.projectName,
397
- file,
398
- code,
399
- framework,
400
- });
401
- const data = response.data;
402
- const tests = data.tests || data.suggestions || data.results || [];
403
- if (!tests || tests.length === 0) {
404
- return "No test suggestions found.";
405
- }
406
- const typeIcons = {
407
- unit: "\ud83d\udd2c",
408
- integration: "\ud83d\udd17",
409
- e2e: "\ud83c\udf10",
410
- };
411
- let result = `## Test Suggestions\n\n`;
412
- for (const t of tests) {
413
- const icon = typeIcons[t.type] || "\ud83d\udd2c";
414
- result += `### ${icon} ${t.name || t.title || t.type || "Test"}\n`;
415
- if (t.framework)
416
- result += `**Framework:** ${t.framework}\n`;
417
- if (t.coverage)
418
- result += `**Coverage:** ${t.coverage}\n`;
419
- if (t.content || t.code) {
420
- result += "```\n" + truncate(t.content || t.code, PREVIEW.LONG) + "\n```\n";
353
+ if (data.testPatterns && data.testPatterns.length > 0) {
354
+ result += `### Test Patterns\n`;
355
+ for (const t of data.testPatterns) {
356
+ result += `- **${t.file}**`;
357
+ if (t.type)
358
+ result += ` [${t.type}]`;
359
+ if (t.framework)
360
+ result += ` (${t.framework})`;
361
+ result += "\n";
362
+ }
421
363
  }
422
- result += "\n";
423
- }
424
- return result;
425
- },
426
- get_code_context: async (args, ctx) => {
427
- const { file, code } = args;
428
- const response = await ctx.api.post("/api/code/context", {
429
- projectName: ctx.projectName,
430
- file,
431
- code,
432
- });
433
- const data = response.data;
434
- let result = `## Code Context\n\n`;
435
- if (data.imports && data.imports.length > 0) {
436
- result += `### Imports\n`;
437
- for (const imp of data.imports) {
438
- result += `- ${imp}\n`;
439
- }
440
- result += "\n";
441
- }
442
- if (data.relatedCode && data.relatedCode.length > 0) {
443
- result += `### Related Code\n`;
444
- for (const r of data.relatedCode) {
445
- result += `- **${r.file}** (${pct(r.score)})`;
446
- if (r.reason)
447
- result += ` - ${r.reason}`;
448
- result += "\n";
449
- }
450
- result += "\n";
451
- }
452
- if (data.testPatterns && data.testPatterns.length > 0) {
453
- result += `### Test Patterns\n`;
454
- for (const t of data.testPatterns) {
455
- result += `- **${t.file}**`;
456
- if (t.type)
457
- result += ` [${t.type}]`;
458
- if (t.framework)
459
- result += ` (${t.framework})`;
460
- result += "\n";
461
- }
462
- }
463
- return result;
364
+ return result;
365
+ },
464
366
  },
465
- setup_project: async (args, ctx) => {
466
- const { projectPath, projectName: targetProject, ragApiUrl, ragApiKey, updateClaudeMd = true, } = args;
467
- const apiUrl = ragApiUrl || process.env.RAG_API_URL || "http://localhost:3100";
468
- const apiKey = ragApiKey || process.env.RAG_API_KEY;
469
- const serverName = `${targetProject}-rag`;
470
- const changes = [];
471
- // 1. Create/update .mcp.json
472
- const mcpJsonPath = path.join(projectPath, ".mcp.json");
473
- let mcpConfig = {};
474
- try {
475
- const existing = fs.readFileSync(mcpJsonPath, "utf-8");
476
- mcpConfig = JSON.parse(existing);
477
- }
478
- catch {
479
- // File doesn't exist or invalid JSON
480
- }
481
- if (!mcpConfig.mcpServers)
482
- mcpConfig.mcpServers = {};
483
- const serverEnv = {
484
- RAG_API_URL: apiUrl,
485
- PROJECT_NAME: targetProject,
486
- PROJECT_PATH: projectPath,
487
- };
488
- if (apiKey)
489
- serverEnv.RAG_API_KEY = apiKey;
490
- mcpConfig.mcpServers[serverName] = {
491
- command: "npx",
492
- args: ["-y", "@crowley/rag-mcp@latest"],
493
- env: serverEnv,
494
- };
495
- fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + "\n");
496
- changes.push(`.mcp.json — added \`${serverName}\` server`);
497
- // 2. Update CLAUDE.md with RAG section
498
- if (updateClaudeMd) {
499
- const claudeMdPath = path.join(projectPath, "CLAUDE.md");
500
- let claudeMd = "";
367
+ {
368
+ name: "setup_project",
369
+ description: "Configure Claude Code for RAG integration. Creates/updates .mcp.json, adds RAG instructions to CLAUDE.md, and configures permissions. Call after index_codebase on a new project.",
370
+ schema: z.object({
371
+ projectPath: z.string().describe("Absolute path to project root"),
372
+ projectName: z.string().describe("Project name in Qdrant (collection prefix)"),
373
+ ragApiUrl: z.string().optional().describe("RAG API URL (default: from MCP env)"),
374
+ ragApiKey: z.string().optional().describe("RAG API key (default: from MCP env)"),
375
+ updateClaudeMd: z.boolean().optional().describe("Add RAG section to CLAUDE.md (default: true)"),
376
+ }),
377
+ annotations: TOOL_ANNOTATIONS["setup_project"],
378
+ handler: async (args, ctx) => {
379
+ const { projectPath, projectName: targetProject, ragApiUrl, ragApiKey, updateClaudeMd = true, } = args;
380
+ const apiUrl = ragApiUrl || process.env.RAG_API_URL || "http://localhost:3100";
381
+ const apiKey = ragApiKey || process.env.RAG_API_KEY;
382
+ const serverName = `${targetProject}-rag`;
383
+ const changes = [];
384
+ // 1. Create/update .mcp.json
385
+ const mcpJsonPath = path.join(projectPath, ".mcp.json");
386
+ let mcpConfig = {};
501
387
  try {
502
- claudeMd = fs.readFileSync(claudeMdPath, "utf-8");
388
+ const existing = fs.readFileSync(mcpJsonPath, "utf-8");
389
+ mcpConfig = JSON.parse(existing);
503
390
  }
504
391
  catch {
505
- // File doesn't exist
392
+ // File doesn't exist or invalid JSON
506
393
  }
507
- const ragSection = `\n## RAG Integration
394
+ if (!mcpConfig.mcpServers)
395
+ mcpConfig.mcpServers = {};
396
+ const serverEnv = {
397
+ RAG_API_URL: apiUrl,
398
+ PROJECT_NAME: targetProject,
399
+ PROJECT_PATH: projectPath,
400
+ };
401
+ if (apiKey)
402
+ serverEnv.RAG_API_KEY = apiKey;
403
+ mcpConfig.mcpServers[serverName] = {
404
+ command: "npx",
405
+ args: ["-y", "@crowley/rag-mcp@latest"],
406
+ env: serverEnv,
407
+ };
408
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + "\n");
409
+ changes.push(`.mcp.json — added \`${serverName}\` server`);
410
+ // 2. Update CLAUDE.md with RAG section
411
+ if (updateClaudeMd) {
412
+ const claudeMdPath = path.join(projectPath, "CLAUDE.md");
413
+ let claudeMd = "";
414
+ try {
415
+ claudeMd = fs.readFileSync(claudeMdPath, "utf-8");
416
+ }
417
+ catch {
418
+ // File doesn't exist
419
+ }
420
+ const ragSection = `\n## RAG Integration
508
421
 
509
422
  You MUST call \`context_briefing\` before making any code changes.
510
423
  This single tool performs all RAG lookups in parallel (recall, search, patterns, ADRs, graph).
@@ -515,63 +428,63 @@ After completing significant changes:
515
428
  - \`remember\` — save important context for future sessions
516
429
  - \`record_adr\` — document architectural decisions
517
430
  `;
518
- if (claudeMd.includes("## RAG")) {
519
- changes.push("CLAUDE.md — RAG section already exists, skipped");
431
+ if (claudeMd.includes("## RAG")) {
432
+ changes.push("CLAUDE.md — RAG section already exists, skipped");
433
+ }
434
+ else {
435
+ claudeMd = claudeMd ? claudeMd.trimEnd() + "\n" + ragSection : `# CLAUDE.md\n${ragSection}`;
436
+ fs.writeFileSync(claudeMdPath, claudeMd);
437
+ changes.push("CLAUDE.md — added RAG Integration section");
438
+ }
439
+ }
440
+ // 3. Create/update .claude/settings.local.json permissions
441
+ const claudeDir = path.join(projectPath, ".claude");
442
+ const settingsPath = path.join(claudeDir, "settings.local.json");
443
+ let settings = {};
444
+ try {
445
+ const existing = fs.readFileSync(settingsPath, "utf-8");
446
+ settings = JSON.parse(existing);
447
+ }
448
+ catch {
449
+ // File doesn't exist or invalid JSON
450
+ }
451
+ if (!settings.permissions)
452
+ settings.permissions = {};
453
+ if (!settings.permissions.allow)
454
+ settings.permissions.allow = [];
455
+ const mcpPermission = `mcp__${serverName}__*`;
456
+ if (!settings.permissions.allow.includes(mcpPermission)) {
457
+ settings.permissions.allow.push(mcpPermission);
458
+ if (!fs.existsSync(claudeDir))
459
+ fs.mkdirSync(claudeDir, { recursive: true });
460
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
461
+ changes.push(`.claude/settings.local.json — added \`${mcpPermission}\` permission`);
520
462
  }
521
463
  else {
522
- claudeMd = claudeMd ? claudeMd.trimEnd() + "\n" + ragSection : `# CLAUDE.md\n${ragSection}`;
523
- fs.writeFileSync(claudeMdPath, claudeMd);
524
- changes.push("CLAUDE.md added RAG Integration section");
464
+ changes.push(".claude/settings.local.json permission already exists, skipped");
465
+ }
466
+ // 4. Check index status
467
+ let indexInfo = "";
468
+ try {
469
+ const statusRes = await ctx.api.get(`/api/index/status/${targetProject}_codebase`);
470
+ const data = statusRes.data;
471
+ indexInfo = `\n## Index Status\n- **Vectors:** ${data.vectorCount ?? "N/A"}\n- **Status:** ${data.status || "unknown"}\n`;
472
+ }
473
+ catch {
474
+ indexInfo = "\n## Index Status\n_Not indexed yet. Run `index_codebase` first._\n";
525
475
  }
526
- }
527
- // 3. Create/update .claude/settings.local.json permissions
528
- const claudeDir = path.join(projectPath, ".claude");
529
- const settingsPath = path.join(claudeDir, "settings.local.json");
530
- let settings = {};
531
- try {
532
- const existing = fs.readFileSync(settingsPath, "utf-8");
533
- settings = JSON.parse(existing);
534
- }
535
- catch {
536
- // File doesn't exist or invalid JSON
537
- }
538
- if (!settings.permissions)
539
- settings.permissions = {};
540
- if (!settings.permissions.allow)
541
- settings.permissions.allow = [];
542
- const mcpPermission = `mcp__${serverName}__*`;
543
- if (!settings.permissions.allow.includes(mcpPermission)) {
544
- settings.permissions.allow.push(mcpPermission);
545
- if (!fs.existsSync(claudeDir))
546
- fs.mkdirSync(claudeDir, { recursive: true });
547
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
548
- changes.push(`.claude/settings.local.json — added \`${mcpPermission}\` permission`);
549
- }
550
- else {
551
- changes.push(".claude/settings.local.json — permission already exists, skipped");
552
- }
553
- // 4. Check index status
554
- let indexInfo = "";
555
- try {
556
- const statusRes = await ctx.api.get(`/api/index/status/${targetProject}_codebase`);
557
- const data = statusRes.data;
558
- indexInfo = `\n## Index Status\n- **Vectors:** ${data.vectorCount ?? "N/A"}\n- **Status:** ${data.status || "unknown"}\n`;
559
- }
560
- catch {
561
- indexInfo = "\n## Index Status\n_Not indexed yet. Run `index_codebase` first._\n";
562
- }
563
- let result = `# Project Setup: ${targetProject}\n\n`;
564
- result += `## Files Updated\n`;
565
- for (const c of changes) {
566
- result += `- ${c}\n`;
567
- }
568
- result += indexInfo;
569
- result += `\n## Next Steps\n`;
570
- result += `1. Restart Claude Code to load the new MCP server\n`;
571
- result += `2. Run \`index_codebase\` if not indexed yet\n`;
572
- result += `3. Use \`context_briefing\` before code changes\n`;
573
- return result;
476
+ let result = `# Project Setup: ${targetProject}\n\n`;
477
+ result += `## Files Updated\n`;
478
+ for (const c of changes) {
479
+ result += `- ${c}\n`;
480
+ }
481
+ result += indexInfo;
482
+ result += `\n## Next Steps\n`;
483
+ result += `1. Restart Claude Code to load the new MCP server\n`;
484
+ result += `2. Run \`index_codebase\` if not indexed yet\n`;
485
+ result += `3. Use \`context_briefing\` before code changes\n`;
486
+ return result;
487
+ },
574
488
  },
575
- };
576
- return { tools, handlers };
489
+ ];
577
490
  }