@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.
- package/dist/annotations.d.ts +16 -0
- package/dist/annotations.js +158 -0
- package/dist/context-enrichment.d.ts +2 -2
- package/dist/context-enrichment.js +37 -14
- package/dist/formatters.d.ts +2 -0
- package/dist/formatters.js +12 -0
- package/dist/index.js +64 -47
- package/dist/schemas.d.ts +97 -0
- package/dist/schemas.js +128 -0
- package/dist/tool-middleware.d.ts +40 -0
- package/dist/tool-middleware.js +216 -0
- package/dist/tool-registry.js +2 -1
- package/dist/tools/advanced.d.ts +2 -2
- package/dist/tools/advanced.js +200 -275
- package/dist/tools/agents.d.ts +2 -2
- package/dist/tools/agents.js +59 -78
- package/dist/tools/analytics.d.ts +2 -2
- package/dist/tools/analytics.js +170 -210
- package/dist/tools/architecture.d.ts +2 -2
- package/dist/tools/architecture.js +506 -661
- package/dist/tools/ask.d.ts +2 -2
- package/dist/tools/ask.js +164 -219
- package/dist/tools/cache.d.ts +2 -2
- package/dist/tools/cache.js +63 -82
- package/dist/tools/clustering.d.ts +2 -2
- package/dist/tools/clustering.js +154 -215
- package/dist/tools/confluence.d.ts +2 -2
- package/dist/tools/confluence.js +80 -116
- package/dist/tools/database.d.ts +2 -2
- package/dist/tools/database.js +303 -380
- package/dist/tools/feedback.d.ts +2 -2
- package/dist/tools/feedback.js +143 -184
- package/dist/tools/guidelines.d.ts +2 -2
- package/dist/tools/guidelines.js +123 -135
- package/dist/tools/indexing.d.ts +2 -2
- package/dist/tools/indexing.js +104 -100
- package/dist/tools/memory.d.ts +2 -2
- package/dist/tools/memory.js +299 -485
- package/dist/tools/pm.d.ts +2 -2
- package/dist/tools/pm.js +367 -615
- package/dist/tools/review.d.ts +2 -2
- package/dist/tools/review.js +142 -189
- package/dist/tools/search.d.ts +2 -2
- package/dist/tools/search.js +230 -305
- package/dist/tools/session.d.ts +2 -2
- package/dist/tools/session.js +288 -345
- package/dist/tools/suggestions.d.ts +2 -2
- package/dist/tools/suggestions.js +444 -255
- package/dist/types.d.ts +19 -2
- 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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
364
|
+
return result;
|
|
365
|
+
},
|
|
165
366
|
},
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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 (
|
|
221
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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
|
}
|