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