@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
@@ -2,300 +2,489 @@
2
2
  * Suggestions tools module - contextual suggestions, related code,
3
3
  * implementation suggestions, test suggestions, and code context.
4
4
  */
5
+ import * as fs from "fs";
6
+ import * as path from "path";
5
7
  import { truncate, pct, PREVIEW } from "../formatters.js";
8
+ import { z } from "zod";
9
+ import { TOOL_ANNOTATIONS } from "../annotations.js";
6
10
  /**
7
11
  * Create the suggestions tools module with project-specific descriptions.
8
12
  */
9
13
  export function createSuggestionTools(projectName) {
10
- const tools = [
14
+ return [
15
+ {
16
+ name: "context_briefing",
17
+ description: `REQUIRED before code changes. Parallel lookup of recall + search + patterns + ADRs + graph for ${projectName}. One call replaces 5 separate RAG lookups.`,
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;
137
+ },
138
+ },
11
139
  {
12
140
  name: "get_contextual_suggestions",
13
141
  description: `Get contextual suggestions based on current work context for ${projectName}. Returns relevant suggestions, triggers, and related memories.`,
14
- inputSchema: {
15
- type: "object",
16
- properties: {
17
- currentFile: {
18
- type: "string",
19
- description: "Currently active file path",
20
- },
21
- currentCode: {
22
- type: "string",
23
- description: "Currently selected or visible code",
24
- },
25
- recentFiles: {
26
- type: "array",
27
- items: { type: "string" },
28
- description: "Recently opened file paths",
29
- },
30
- task: {
31
- type: "string",
32
- description: "Current task description",
33
- },
34
- },
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;
35
193
  },
36
194
  },
37
195
  {
38
196
  name: "suggest_related_code",
39
197
  description: `Find code related to a given file or snippet in ${projectName}. Shows similar implementations and related modules.`,
40
- inputSchema: {
41
- type: "object",
42
- properties: {
43
- file: {
44
- type: "string",
45
- description: "File path to find related code for",
46
- },
47
- code: {
48
- type: "string",
49
- description: "Code snippet to find related code for",
50
- },
51
- limit: {
52
- type: "number",
53
- description: "Max results (default: 5)",
54
- default: 5,
55
- },
56
- },
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;
57
231
  },
58
232
  },
59
233
  {
60
234
  name: "suggest_implementation",
61
235
  description: `Get implementation suggestions for a feature in ${projectName}. Shows similar patterns and adaptation hints.`,
62
- inputSchema: {
63
- type: "object",
64
- properties: {
65
- description: {
66
- type: "string",
67
- description: "Description of what to implement",
68
- },
69
- currentFile: {
70
- type: "string",
71
- description: "Current file for context",
72
- },
73
- language: {
74
- type: "string",
75
- description: "Target programming language",
76
- },
77
- },
78
- 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;
79
274
  },
80
275
  },
81
276
  {
82
277
  name: "suggest_tests",
83
278
  description: `Get test suggestions for code in ${projectName}. Shows recommended test types, frameworks, and example patterns.`,
84
- inputSchema: {
85
- type: "object",
86
- properties: {
87
- file: {
88
- type: "string",
89
- description: "File to suggest tests for",
90
- },
91
- code: {
92
- type: "string",
93
- description: "Code to suggest tests for",
94
- },
95
- framework: {
96
- type: "string",
97
- description: "Test framework preference (jest, mocha, pytest, etc.)",
98
- },
99
- },
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;
100
317
  },
101
318
  },
102
319
  {
103
320
  name: "get_code_context",
104
321
  description: `Get full context for a code file in ${projectName}. Shows imports, related code, and test patterns.`,
105
- inputSchema: {
106
- type: "object",
107
- properties: {
108
- file: {
109
- type: "string",
110
- description: "File path to get context for",
111
- },
112
- code: {
113
- type: "string",
114
- description: "Code snippet for context",
115
- },
116
- },
117
- },
118
- },
119
- ];
120
- const handlers = {
121
- get_contextual_suggestions: async (args, ctx) => {
122
- const { currentFile, currentCode, recentFiles, task } = args;
123
- const response = await ctx.api.post("/api/suggestions", {
124
- projectName: ctx.projectName,
125
- currentFile,
126
- currentCode,
127
- recentFiles,
128
- task,
129
- });
130
- const data = response.data;
131
- let result = `## Contextual Suggestions\n\n`;
132
- if (data.relevanceScore !== undefined) {
133
- result += `**Relevance Score:** ${pct(data.relevanceScore)}\n\n`;
134
- }
135
- if (data.triggers && data.triggers.length > 0) {
136
- result += `### Triggers\n`;
137
- for (const t of data.triggers) {
138
- result += `- **${t.type}:** ${t.value}`;
139
- if (t.confidence)
140
- result += ` (${pct(t.confidence)})`;
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", {
330
+ projectName: ctx.projectName,
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
+ }
141
341
  result += "\n";
142
342
  }
143
- result += "\n";
144
- }
145
- if (data.suggestions && data.suggestions.length > 0) {
146
- result += `### Suggestions\n`;
147
- for (const s of data.suggestions) {
148
- result += `- **${s.title}** [${s.type}]\n`;
149
- if (s.description)
150
- result += ` ${s.description}\n`;
151
- if (s.reason)
152
- result += ` *Reason: ${s.reason}*\n`;
153
- if (s.relevance !== undefined)
154
- result += ` Relevance: ${pct(s.relevance)}\n`;
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
+ }
351
+ result += "\n";
155
352
  }
156
- result += "\n";
157
- }
158
- if (data.relatedMemories && data.relatedMemories.length > 0) {
159
- result += `### Related Memories\n`;
160
- for (const m of data.relatedMemories) {
161
- result += `- ${m.content || m.title || JSON.stringify(m)}\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
+ }
162
363
  }
163
- }
164
- return result;
364
+ return result;
365
+ },
165
366
  },
166
- suggest_related_code: async (args, ctx) => {
167
- const { file, code, limit = 5 } = args;
168
- const response = await ctx.api.post("/api/code/related", {
169
- projectName: ctx.projectName,
170
- file,
171
- code,
172
- limit,
173
- });
174
- const results = response.data.results || response.data;
175
- if (!results || results.length === 0) {
176
- return "No related code found.";
177
- }
178
- let result = `## Related Code\n\n`;
179
- for (const r of results) {
180
- result += `### ${r.file}\n`;
181
- result += `**Score:** ${pct(r.score)}`;
182
- if (r.reason)
183
- result += ` | **Reason:** ${r.reason}`;
184
- if (r.line)
185
- result += ` | Line ${r.line}`;
186
- result += "\n";
187
- if (r.content || r.code) {
188
- result += "```\n" + truncate(r.content || r.code, PREVIEW.MEDIUM) + "\n```\n";
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 = {};
387
+ try {
388
+ const existing = fs.readFileSync(mcpJsonPath, "utf-8");
389
+ mcpConfig = JSON.parse(existing);
189
390
  }
190
- result += "\n";
191
- }
192
- return result;
193
- },
194
- suggest_implementation: async (args, ctx) => {
195
- const { description, currentFile, language } = args;
196
- const response = await ctx.api.post("/api/code/suggest-implementation", {
197
- projectName: ctx.projectName,
198
- description,
199
- currentFile,
200
- language,
201
- });
202
- const data = response.data;
203
- const patterns = data.patterns || data.results || [];
204
- if (!patterns || patterns.length === 0) {
205
- return "No implementation suggestions found.";
206
- }
207
- const patternIcons = {
208
- similar_structure: "\ud83d\udcd0",
209
- same_domain: "\ud83c\udfaf",
210
- related_import: "\ud83d\udce6",
211
- test_pattern: "\ud83e\uddea",
212
- };
213
- let result = `## Implementation Suggestions\n\n`;
214
- for (const p of patterns) {
215
- const icon = patternIcons[p.pattern || p.type] || "\ud83d\udcd0";
216
- result += `### ${icon} ${p.file || p.name || "Pattern"}\n`;
217
- if (p.adaptationHints || p.hints) {
218
- result += `**Adaptation:** ${p.adaptationHints || p.hints}\n`;
391
+ catch {
392
+ // File doesn't exist or invalid JSON
219
393
  }
220
- if (p.content || p.code) {
221
- result += "```\n" + truncate(p.content || p.code, 400) + "\n```\n";
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
421
+
422
+ You MUST call \`context_briefing\` before making any code changes.
423
+ This single tool performs all RAG lookups in parallel (recall, search, patterns, ADRs, graph).
424
+
425
+ Example: \`context_briefing(task: "describe your change", files: ["path/to/file.ts"])\`
426
+
427
+ After completing significant changes:
428
+ - \`remember\` — save important context for future sessions
429
+ - \`record_adr\` — document architectural decisions
430
+ `;
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
+ }
222
439
  }
223
- result += "\n";
224
- }
225
- return result;
226
- },
227
- suggest_tests: async (args, ctx) => {
228
- const { file, code, framework } = args;
229
- const response = await ctx.api.post("/api/code/suggest-tests", {
230
- projectName: ctx.projectName,
231
- file,
232
- code,
233
- framework,
234
- });
235
- const data = response.data;
236
- const tests = data.tests || data.suggestions || data.results || [];
237
- if (!tests || tests.length === 0) {
238
- return "No test suggestions found.";
239
- }
240
- const typeIcons = {
241
- unit: "\ud83d\udd2c",
242
- integration: "\ud83d\udd17",
243
- e2e: "\ud83c\udf10",
244
- };
245
- let result = `## Test Suggestions\n\n`;
246
- for (const t of tests) {
247
- const icon = typeIcons[t.type] || "\ud83d\udd2c";
248
- result += `### ${icon} ${t.name || t.title || t.type || "Test"}\n`;
249
- if (t.framework)
250
- result += `**Framework:** ${t.framework}\n`;
251
- if (t.coverage)
252
- result += `**Coverage:** ${t.coverage}\n`;
253
- if (t.content || t.code) {
254
- result += "```\n" + truncate(t.content || t.code, PREVIEW.LONG) + "\n```\n";
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);
255
447
  }
256
- result += "\n";
257
- }
258
- return result;
259
- },
260
- get_code_context: async (args, ctx) => {
261
- const { file, code } = args;
262
- const response = await ctx.api.post("/api/code/context", {
263
- projectName: ctx.projectName,
264
- file,
265
- code,
266
- });
267
- const data = response.data;
268
- let result = `## Code Context\n\n`;
269
- if (data.imports && data.imports.length > 0) {
270
- result += `### Imports\n`;
271
- for (const imp of data.imports) {
272
- result += `- ${imp}\n`;
448
+ catch {
449
+ // File doesn't exist or invalid JSON
273
450
  }
274
- result += "\n";
275
- }
276
- if (data.relatedCode && data.relatedCode.length > 0) {
277
- result += `### Related Code\n`;
278
- for (const r of data.relatedCode) {
279
- result += `- **${r.file}** (${pct(r.score)})`;
280
- if (r.reason)
281
- result += ` - ${r.reason}`;
282
- result += "\n";
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`);
283
462
  }
284
- result += "\n";
285
- }
286
- if (data.testPatterns && data.testPatterns.length > 0) {
287
- result += `### Test Patterns\n`;
288
- for (const t of data.testPatterns) {
289
- result += `- **${t.file}**`;
290
- if (t.type)
291
- result += ` [${t.type}]`;
292
- if (t.framework)
293
- result += ` (${t.framework})`;
294
- result += "\n";
463
+ else {
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";
295
475
  }
296
- }
297
- 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
+ },
298
488
  },
299
- };
300
- return { tools, handlers };
489
+ ];
301
490
  }