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