@esankhan3/code-search-mcp 0.0.1
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/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/core/ast-graph-builder.js +800 -0
- package/dist/core/chunker.js +368 -0
- package/dist/core/claude-runner.js +173 -0
- package/dist/core/config.js +59 -0
- package/dist/core/cross-repo-detector.js +887 -0
- package/dist/core/embedder.js +334 -0
- package/dist/core/env-config.js +86 -0
- package/dist/core/file-walker.js +186 -0
- package/dist/core/git-diff.js +160 -0
- package/dist/core/graph-metrics.js +233 -0
- package/dist/core/indexer.js +552 -0
- package/dist/core/project-graph-builder.js +202 -0
- package/dist/core/query-classifier.js +144 -0
- package/dist/core/query-router.js +210 -0
- package/dist/core/rag-evaluator.js +213 -0
- package/dist/core/repo-profiler.js +383 -0
- package/dist/core/reranker.js +218 -0
- package/dist/core/retriever.js +242 -0
- package/dist/core/semantic-edge-detector.js +147 -0
- package/dist/core/service-mesh-inferrer.js +241 -0
- package/dist/core/structural-hasher.js +316 -0
- package/dist/core/tree-sitter-parser.js +539 -0
- package/dist/core/types.js +0 -0
- package/dist/core/vector-store.js +259 -0
- package/dist/core/workspace-detector.js +481 -0
- package/dist/index.js +177 -0
- package/dist/middleware/auth.js +109 -0
- package/dist/resources/resources.js +79 -0
- package/dist/server.js +242 -0
- package/dist/sources/github-org.js +109 -0
- package/dist/sources/local-path.js +44 -0
- package/dist/tools/graph.js +193 -0
- package/dist/tools/index-tools.js +43 -0
- package/dist/tools/profile.js +88 -0
- package/dist/tools/search.js +84 -0
- package/dist/transports/http-transport.js +162 -0
- package/dist/transports/remote-proxy.js +152 -0
- package/package.json +33 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
|
+
function safeRead(filePath) {
|
|
4
|
+
try {
|
|
5
|
+
return readFileSync(filePath, "utf-8");
|
|
6
|
+
} catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function safeJsonParse(content) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(content);
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function safeDirEntries(dir) {
|
|
18
|
+
try {
|
|
19
|
+
return readdirSync(dir);
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function isDir(p) {
|
|
25
|
+
try {
|
|
26
|
+
return statSync(p).isDirectory();
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const MANIFEST_REGISTRY = [
|
|
32
|
+
// ---- Node.js (npm / yarn / pnpm) ----
|
|
33
|
+
{
|
|
34
|
+
filename: "package.json",
|
|
35
|
+
ecosystem: "node",
|
|
36
|
+
extractName(content) {
|
|
37
|
+
const pkg = safeJsonParse(content);
|
|
38
|
+
return pkg?.name ?? null;
|
|
39
|
+
},
|
|
40
|
+
extractDeps(content) {
|
|
41
|
+
const pkg = safeJsonParse(content);
|
|
42
|
+
if (!pkg) return [];
|
|
43
|
+
return [
|
|
44
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
45
|
+
...Object.keys(pkg.devDependencies ?? {}),
|
|
46
|
+
...Object.keys(pkg.peerDependencies ?? {})
|
|
47
|
+
];
|
|
48
|
+
},
|
|
49
|
+
extractWorkspaceGlobs(content) {
|
|
50
|
+
const pkg = safeJsonParse(content);
|
|
51
|
+
if (!pkg) return null;
|
|
52
|
+
if (Array.isArray(pkg.workspaces)) return pkg.workspaces;
|
|
53
|
+
if (Array.isArray(pkg.workspaces?.packages)) return pkg.workspaces.packages;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
// ---- Rust (Cargo) ----
|
|
58
|
+
{
|
|
59
|
+
filename: "Cargo.toml",
|
|
60
|
+
ecosystem: "rust",
|
|
61
|
+
extractName(content) {
|
|
62
|
+
const m = content.match(/\[package\][^[]*?name\s*=\s*"([^"]+)"/s);
|
|
63
|
+
return m?.[1] ?? null;
|
|
64
|
+
},
|
|
65
|
+
extractDeps(content) {
|
|
66
|
+
const deps = [];
|
|
67
|
+
const depSection = content.match(/\[dependencies\]([\s\S]*?)(?=\n\[|$)/);
|
|
68
|
+
if (depSection) {
|
|
69
|
+
const entries = depSection[1].matchAll(/^(\w[\w-]*)\s*=/gm);
|
|
70
|
+
for (const m of entries) deps.push(m[1]);
|
|
71
|
+
}
|
|
72
|
+
const devSection = content.match(/\[dev-dependencies\]([\s\S]*?)(?=\n\[|$)/);
|
|
73
|
+
if (devSection) {
|
|
74
|
+
const entries = devSection[1].matchAll(/^(\w[\w-]*)\s*=/gm);
|
|
75
|
+
for (const m of entries) deps.push(m[1]);
|
|
76
|
+
}
|
|
77
|
+
return deps;
|
|
78
|
+
},
|
|
79
|
+
extractWorkspaceGlobs(content) {
|
|
80
|
+
const ws = content.match(/\[workspace\][^[]*?members\s*=\s*\[([\s\S]*?)\]/);
|
|
81
|
+
if (!ws) return null;
|
|
82
|
+
const globs = [...ws[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
|
|
83
|
+
return globs.length > 0 ? globs : null;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
// ---- Go ----
|
|
87
|
+
{
|
|
88
|
+
filename: "go.mod",
|
|
89
|
+
ecosystem: "go",
|
|
90
|
+
extractName(content) {
|
|
91
|
+
const m = content.match(/^module\s+(\S+)/m);
|
|
92
|
+
return m?.[1] ?? null;
|
|
93
|
+
},
|
|
94
|
+
extractDeps(content) {
|
|
95
|
+
const deps = [];
|
|
96
|
+
const singles = content.matchAll(/^require\s+(\S+)\s+/gm);
|
|
97
|
+
for (const m of singles) deps.push(m[1]);
|
|
98
|
+
const blocks = content.matchAll(/require\s*\(([\s\S]*?)\)/g);
|
|
99
|
+
for (const block of blocks) {
|
|
100
|
+
const entries = block[1].matchAll(/^\s*(\S+)\s+/gm);
|
|
101
|
+
for (const m of entries) deps.push(m[1]);
|
|
102
|
+
}
|
|
103
|
+
return deps;
|
|
104
|
+
}
|
|
105
|
+
// go.mod doesn't declare workspaces — go.work does (handled separately)
|
|
106
|
+
},
|
|
107
|
+
// ---- Python (pyproject.toml) ----
|
|
108
|
+
{
|
|
109
|
+
filename: "pyproject.toml",
|
|
110
|
+
ecosystem: "python",
|
|
111
|
+
extractName(content) {
|
|
112
|
+
const m = content.match(/\[project\][^[]*?name\s*=\s*"([^"]+)"/s);
|
|
113
|
+
return m?.[1] ?? null;
|
|
114
|
+
},
|
|
115
|
+
extractDeps(content) {
|
|
116
|
+
const deps = [];
|
|
117
|
+
const depSection = content.match(/\[project\][^[]*?dependencies\s*=\s*\[([\s\S]*?)\]/s);
|
|
118
|
+
if (depSection) {
|
|
119
|
+
const entries = depSection[1].matchAll(/"([a-zA-Z0-9_-]+)[^"]*"/g);
|
|
120
|
+
for (const m of entries) deps.push(m[1]);
|
|
121
|
+
}
|
|
122
|
+
return deps;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
// ---- Python (setup.py) ----
|
|
126
|
+
{
|
|
127
|
+
filename: "setup.py",
|
|
128
|
+
ecosystem: "python",
|
|
129
|
+
extractName(content) {
|
|
130
|
+
const m = content.match(/name\s*=\s*['"]([^'"]+)['"]/);
|
|
131
|
+
return m?.[1] ?? null;
|
|
132
|
+
},
|
|
133
|
+
extractDeps(content) {
|
|
134
|
+
const deps = [];
|
|
135
|
+
const m = content.match(/install_requires\s*=\s*\[([\s\S]*?)\]/);
|
|
136
|
+
if (m) {
|
|
137
|
+
const entries = m[1].matchAll(/['"]([a-zA-Z0-9_-]+)[^'"]*['"]/g);
|
|
138
|
+
for (const e of entries) deps.push(e[1]);
|
|
139
|
+
}
|
|
140
|
+
return deps;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
// ---- Java (Maven) ----
|
|
144
|
+
{
|
|
145
|
+
filename: "pom.xml",
|
|
146
|
+
ecosystem: "java",
|
|
147
|
+
extractName(content) {
|
|
148
|
+
const group = content.match(/<groupId>([^<]+)<\/groupId>/);
|
|
149
|
+
const artifact = content.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
150
|
+
if (artifact) return group ? `${group[1]}:${artifact[1]}` : artifact[1];
|
|
151
|
+
return null;
|
|
152
|
+
},
|
|
153
|
+
extractDeps(content) {
|
|
154
|
+
const deps = [];
|
|
155
|
+
const depBlocks = content.matchAll(/<dependency>\s*([\s\S]*?)<\/dependency>/g);
|
|
156
|
+
for (const block of depBlocks) {
|
|
157
|
+
const g = block[1].match(/<groupId>([^<]+)<\/groupId>/);
|
|
158
|
+
const a = block[1].match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
159
|
+
if (a) deps.push(g ? `${g[1]}:${a[1]}` : a[1]);
|
|
160
|
+
}
|
|
161
|
+
return deps;
|
|
162
|
+
},
|
|
163
|
+
extractWorkspaceGlobs(content) {
|
|
164
|
+
const modules = [...content.matchAll(/<module>([^<]+)<\/module>/g)].map((m) => m[1]);
|
|
165
|
+
return modules.length > 0 ? modules : null;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
// ---- Java (Gradle) ----
|
|
169
|
+
{
|
|
170
|
+
filename: "build.gradle",
|
|
171
|
+
ecosystem: "java",
|
|
172
|
+
extractName(_content, dirName) {
|
|
173
|
+
return dirName || null;
|
|
174
|
+
},
|
|
175
|
+
extractDeps(content) {
|
|
176
|
+
const deps = [];
|
|
177
|
+
const entries = content.matchAll(/(?:implementation|api|compileOnly|runtimeOnly)\s+['"]([^'"]+)['"]/g);
|
|
178
|
+
for (const m of entries) deps.push(m[1].replace(/:[^:]+$/, ""));
|
|
179
|
+
return deps;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
// ---- .NET (C#/F#) ----
|
|
183
|
+
{
|
|
184
|
+
filename: "*.csproj",
|
|
185
|
+
ecosystem: "dotnet",
|
|
186
|
+
extractName(content, dirName) {
|
|
187
|
+
const m = content.match(/<AssemblyName>([^<]+)<\/AssemblyName>/);
|
|
188
|
+
return m?.[1] ?? dirName ?? null;
|
|
189
|
+
},
|
|
190
|
+
extractDeps(content) {
|
|
191
|
+
const deps = [];
|
|
192
|
+
const refs = content.matchAll(/<PackageReference\s+Include="([^"]+)"/g);
|
|
193
|
+
for (const m of refs) deps.push(m[1]);
|
|
194
|
+
const projRefs = content.matchAll(/<ProjectReference\s+Include="([^"]+)"/g);
|
|
195
|
+
for (const m of projRefs) deps.push(m[1]);
|
|
196
|
+
return deps;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
// ---- Elixir (Mix) ----
|
|
200
|
+
{
|
|
201
|
+
filename: "mix.exs",
|
|
202
|
+
ecosystem: "elixir",
|
|
203
|
+
extractName(content) {
|
|
204
|
+
const m = content.match(/app:\s*:(\w+)/);
|
|
205
|
+
return m?.[1] ?? null;
|
|
206
|
+
},
|
|
207
|
+
extractDeps(content) {
|
|
208
|
+
const deps = [];
|
|
209
|
+
const entries = content.matchAll(/\{:(\w+),/g);
|
|
210
|
+
for (const m of entries) deps.push(m[1]);
|
|
211
|
+
return deps;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
// ---- PHP (Composer) ----
|
|
215
|
+
{
|
|
216
|
+
filename: "composer.json",
|
|
217
|
+
ecosystem: "php",
|
|
218
|
+
extractName(content) {
|
|
219
|
+
const pkg = safeJsonParse(content);
|
|
220
|
+
return pkg?.name ?? null;
|
|
221
|
+
},
|
|
222
|
+
extractDeps(content) {
|
|
223
|
+
const pkg = safeJsonParse(content);
|
|
224
|
+
if (!pkg) return [];
|
|
225
|
+
return [
|
|
226
|
+
...Object.keys(pkg.require ?? {}),
|
|
227
|
+
...Object.keys(pkg["require-dev"] ?? {})
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
// ---- Swift (Package.swift) ----
|
|
232
|
+
{
|
|
233
|
+
filename: "Package.swift",
|
|
234
|
+
ecosystem: "swift",
|
|
235
|
+
extractName(content) {
|
|
236
|
+
const m = content.match(/name:\s*"([^"]+)"/);
|
|
237
|
+
return m?.[1] ?? null;
|
|
238
|
+
},
|
|
239
|
+
extractDeps(content) {
|
|
240
|
+
const deps = [];
|
|
241
|
+
const entries = content.matchAll(/\.package\s*\([^)]*url:\s*"([^"]+)"/g);
|
|
242
|
+
for (const m of entries) {
|
|
243
|
+
const name = m[1].replace(/\.git$/, "").split("/").pop();
|
|
244
|
+
if (name) deps.push(name);
|
|
245
|
+
}
|
|
246
|
+
return deps;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
];
|
|
250
|
+
const WORKSPACE_ROOT_CONFIGS = [
|
|
251
|
+
// pnpm-workspace.yaml
|
|
252
|
+
{
|
|
253
|
+
filename: "pnpm-workspace.yaml",
|
|
254
|
+
extractGlobs(content) {
|
|
255
|
+
const lines = content.split("\n");
|
|
256
|
+
const globs = [];
|
|
257
|
+
let inPackages = false;
|
|
258
|
+
for (const line of lines) {
|
|
259
|
+
if (/^packages\s*:/.test(line)) {
|
|
260
|
+
inPackages = true;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (inPackages) {
|
|
264
|
+
const m = line.match(/^\s+-\s+['"]?([^'"#\s]+)['"]?/);
|
|
265
|
+
if (m) {
|
|
266
|
+
globs.push(m[1]);
|
|
267
|
+
} else if (/^\S/.test(line)) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return globs.length > 0 ? globs : null;
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
// go.work
|
|
276
|
+
{
|
|
277
|
+
filename: "go.work",
|
|
278
|
+
extractGlobs(content) {
|
|
279
|
+
const globs = [];
|
|
280
|
+
const singles = content.matchAll(/^use\s+(\.\S+)/gm);
|
|
281
|
+
for (const m of singles) globs.push(m[1]);
|
|
282
|
+
const blocks = content.matchAll(/use\s*\(([\s\S]*?)\)/g);
|
|
283
|
+
for (const block of blocks) {
|
|
284
|
+
const entries = block[1].matchAll(/^\s*(\.\S+)/gm);
|
|
285
|
+
for (const m of entries) globs.push(m[1]);
|
|
286
|
+
}
|
|
287
|
+
return globs.length > 0 ? globs : null;
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
// lerna.json
|
|
291
|
+
{
|
|
292
|
+
filename: "lerna.json",
|
|
293
|
+
extractGlobs(content) {
|
|
294
|
+
const parsed = safeJsonParse(content);
|
|
295
|
+
if (Array.isArray(parsed?.packages)) return parsed.packages;
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
// settings.gradle (Gradle multi-project)
|
|
300
|
+
{
|
|
301
|
+
filename: "settings.gradle",
|
|
302
|
+
extractGlobs(content) {
|
|
303
|
+
const includes = [...content.matchAll(/include\s+['"]([^'"]+)['"]/g)].map((m) => m[1]);
|
|
304
|
+
return includes.length > 0 ? includes.map((i) => i.replace(/^:/, "").replace(/:/g, "/")) : null;
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
// settings.gradle.kts
|
|
308
|
+
{
|
|
309
|
+
filename: "settings.gradle.kts",
|
|
310
|
+
extractGlobs(content) {
|
|
311
|
+
const includes = [...content.matchAll(/include\s*\(\s*"([^"]+)"\s*\)/g)].map((m) => m[1]);
|
|
312
|
+
return includes.length > 0 ? includes.map((i) => i.replace(/^:/, "").replace(/:/g, "/")) : null;
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
// nx.json (Nx workspace — projects are auto-detected from package.json but can be explicit)
|
|
316
|
+
{
|
|
317
|
+
filename: "nx.json",
|
|
318
|
+
extractGlobs(content) {
|
|
319
|
+
const parsed = safeJsonParse(content);
|
|
320
|
+
if (!parsed) return null;
|
|
321
|
+
const layout = parsed.workspaceLayout;
|
|
322
|
+
const globs = [];
|
|
323
|
+
if (layout?.appsDir) globs.push(`${layout.appsDir}/*`);
|
|
324
|
+
if (layout?.libsDir) globs.push(`${layout.libsDir}/*`);
|
|
325
|
+
return globs.length > 0 ? globs : null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
];
|
|
329
|
+
function expandGlobs(rootPath, globs) {
|
|
330
|
+
const dirs = [];
|
|
331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
332
|
+
for (const glob of globs) {
|
|
333
|
+
const cleaned = glob.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
334
|
+
if (cleaned.endsWith("/**")) {
|
|
335
|
+
const base = cleaned.slice(0, -3);
|
|
336
|
+
const searchDir = join(rootPath, base);
|
|
337
|
+
collectDirsRecursive(searchDir, dirs, seen, 3);
|
|
338
|
+
} else if (cleaned.endsWith("/*")) {
|
|
339
|
+
const base = cleaned.slice(0, -2);
|
|
340
|
+
const searchDir = join(rootPath, base);
|
|
341
|
+
for (const entry of safeDirEntries(searchDir)) {
|
|
342
|
+
const full = join(searchDir, entry);
|
|
343
|
+
if (isDir(full) && !seen.has(full)) {
|
|
344
|
+
seen.add(full);
|
|
345
|
+
dirs.push(full);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
const full = join(rootPath, cleaned);
|
|
350
|
+
if (isDir(full) && !seen.has(full)) {
|
|
351
|
+
seen.add(full);
|
|
352
|
+
dirs.push(full);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return dirs;
|
|
357
|
+
}
|
|
358
|
+
function collectDirsRecursive(dir, result, seen, maxDepth) {
|
|
359
|
+
if (maxDepth <= 0) return;
|
|
360
|
+
for (const entry of safeDirEntries(dir)) {
|
|
361
|
+
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist" || entry === "build") continue;
|
|
362
|
+
const full = join(dir, entry);
|
|
363
|
+
if (isDir(full) && !seen.has(full)) {
|
|
364
|
+
seen.add(full);
|
|
365
|
+
result.push(full);
|
|
366
|
+
collectDirsRecursive(full, result, seen, maxDepth - 1);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function findManifestInDir(dir) {
|
|
371
|
+
const dirName = dir.split("/").pop() ?? "";
|
|
372
|
+
const entries = new Set(safeDirEntries(dir));
|
|
373
|
+
for (const desc of MANIFEST_REGISTRY) {
|
|
374
|
+
if (desc.filename.startsWith("*")) {
|
|
375
|
+
const ext = desc.filename.slice(1);
|
|
376
|
+
const match = [...entries].find((e) => e.endsWith(ext));
|
|
377
|
+
if (match) {
|
|
378
|
+
const content = safeRead(join(dir, match));
|
|
379
|
+
if (content) return { descriptor: desc, content, filename: match };
|
|
380
|
+
}
|
|
381
|
+
} else if (entries.has(desc.filename)) {
|
|
382
|
+
const content = safeRead(join(dir, desc.filename));
|
|
383
|
+
if (content) {
|
|
384
|
+
const name = desc.extractName(content, dirName);
|
|
385
|
+
if (name) return { descriptor: desc, content, filename: desc.filename };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
function detectTsconfigAliases(repoPath) {
|
|
392
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
393
|
+
for (const filename of ["tsconfig.json", "tsconfig.base.json"]) {
|
|
394
|
+
const content = safeRead(join(repoPath, filename));
|
|
395
|
+
if (!content) continue;
|
|
396
|
+
const config = safeJsonParse(content);
|
|
397
|
+
if (!config?.compilerOptions?.paths) continue;
|
|
398
|
+
const baseUrl = config.compilerOptions.baseUrl ?? ".";
|
|
399
|
+
const paths = config.compilerOptions.paths;
|
|
400
|
+
for (const [alias, targets] of Object.entries(paths)) {
|
|
401
|
+
if (!Array.isArray(targets) || targets.length === 0) continue;
|
|
402
|
+
const cleanAlias = alias.replace(/\/\*$/, "");
|
|
403
|
+
const target = targets[0].replace(/\/\*$/, "");
|
|
404
|
+
const resolved = relative(repoPath, resolve(repoPath, baseUrl, target));
|
|
405
|
+
aliases.set(cleanAlias, resolved);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return aliases;
|
|
409
|
+
}
|
|
410
|
+
function detectWorkspace(repoPath) {
|
|
411
|
+
const packages = [];
|
|
412
|
+
const nameToPackage = /* @__PURE__ */ new Map();
|
|
413
|
+
const discoveredDirs = /* @__PURE__ */ new Set();
|
|
414
|
+
const allGlobs = [];
|
|
415
|
+
for (const rootConfig of WORKSPACE_ROOT_CONFIGS) {
|
|
416
|
+
const content = safeRead(join(repoPath, rootConfig.filename));
|
|
417
|
+
if (!content) continue;
|
|
418
|
+
const globs = rootConfig.extractGlobs(content);
|
|
419
|
+
if (globs) allGlobs.push(...globs);
|
|
420
|
+
}
|
|
421
|
+
for (const desc of MANIFEST_REGISTRY) {
|
|
422
|
+
if (!desc.extractWorkspaceGlobs) continue;
|
|
423
|
+
if (desc.filename.startsWith("*")) continue;
|
|
424
|
+
const content = safeRead(join(repoPath, desc.filename));
|
|
425
|
+
if (!content) continue;
|
|
426
|
+
const globs = desc.extractWorkspaceGlobs(content);
|
|
427
|
+
if (globs) allGlobs.push(...globs);
|
|
428
|
+
}
|
|
429
|
+
if (allGlobs.length > 0) {
|
|
430
|
+
const expanded = expandGlobs(repoPath, allGlobs);
|
|
431
|
+
for (const dir of expanded) discoveredDirs.add(dir);
|
|
432
|
+
}
|
|
433
|
+
if (discoveredDirs.size === 0) {
|
|
434
|
+
const fallbackDirs = [];
|
|
435
|
+
collectDirsRecursive(repoPath, fallbackDirs, /* @__PURE__ */ new Set(), 3);
|
|
436
|
+
for (const dir of fallbackDirs) {
|
|
437
|
+
if (findManifestInDir(dir)) discoveredDirs.add(dir);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
for (const dir of discoveredDirs) {
|
|
441
|
+
const found = findManifestInDir(dir);
|
|
442
|
+
if (!found) continue;
|
|
443
|
+
const { descriptor, content, filename } = found;
|
|
444
|
+
const dirName = dir.split("/").pop() ?? "";
|
|
445
|
+
const name = descriptor.extractName(content, dirName);
|
|
446
|
+
if (!name) continue;
|
|
447
|
+
const pkg = {
|
|
448
|
+
name,
|
|
449
|
+
path: dir,
|
|
450
|
+
relativePath: relative(repoPath, dir),
|
|
451
|
+
ecosystem: descriptor.ecosystem,
|
|
452
|
+
manifestFile: filename,
|
|
453
|
+
dependencies: descriptor.extractDeps(content)
|
|
454
|
+
};
|
|
455
|
+
packages.push(pkg);
|
|
456
|
+
nameToPackage.set(name, pkg);
|
|
457
|
+
}
|
|
458
|
+
const rootManifest = findManifestInDir(repoPath);
|
|
459
|
+
if (rootManifest) {
|
|
460
|
+
const { descriptor, content, filename } = rootManifest;
|
|
461
|
+
const name = descriptor.extractName(content, repoPath.split("/").pop() ?? "");
|
|
462
|
+
if (name && !nameToPackage.has(name)) {
|
|
463
|
+
const pkg = {
|
|
464
|
+
name,
|
|
465
|
+
path: repoPath,
|
|
466
|
+
relativePath: ".",
|
|
467
|
+
ecosystem: descriptor.ecosystem,
|
|
468
|
+
manifestFile: filename,
|
|
469
|
+
dependencies: descriptor.extractDeps(content)
|
|
470
|
+
};
|
|
471
|
+
packages.push(pkg);
|
|
472
|
+
nameToPackage.set(name, pkg);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const pathAliases = detectTsconfigAliases(repoPath);
|
|
476
|
+
return { repoPath, packages, nameToPackage, pathAliases };
|
|
477
|
+
}
|
|
478
|
+
export {
|
|
479
|
+
detectTsconfigAliases,
|
|
480
|
+
detectWorkspace
|
|
481
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
let mode = "remote";
|
|
6
|
+
let remoteUrl = null;
|
|
7
|
+
let apiKey;
|
|
8
|
+
let source = null;
|
|
9
|
+
let projectName = null;
|
|
10
|
+
let githubToken;
|
|
11
|
+
let force = false;
|
|
12
|
+
let port;
|
|
13
|
+
let transport;
|
|
14
|
+
let authMode;
|
|
15
|
+
for (let i = 0; i < args.length; i++) {
|
|
16
|
+
if (args[i] === "--remote" && args[i + 1]) {
|
|
17
|
+
mode = "remote";
|
|
18
|
+
remoteUrl = args[++i];
|
|
19
|
+
} else if (args[i] === "--local") {
|
|
20
|
+
mode = "local";
|
|
21
|
+
if (args[i + 1] && !args[i + 1].startsWith("--")) {
|
|
22
|
+
source = args[++i];
|
|
23
|
+
}
|
|
24
|
+
} else if (args[i] === "--serve") {
|
|
25
|
+
mode = "serve";
|
|
26
|
+
} else if (args[i] === "--api-key" && args[i + 1]) {
|
|
27
|
+
apiKey = args[++i];
|
|
28
|
+
} else if (args[i] === "--project" && args[i + 1]) {
|
|
29
|
+
projectName = args[++i];
|
|
30
|
+
} else if (args[i] === "--token" && args[i + 1]) {
|
|
31
|
+
githubToken = args[++i];
|
|
32
|
+
} else if (args[i] === "--force") {
|
|
33
|
+
force = true;
|
|
34
|
+
} else if (args[i] === "--port" && args[i + 1]) {
|
|
35
|
+
port = args[++i];
|
|
36
|
+
} else if (args[i] === "--transport" && args[i + 1]) {
|
|
37
|
+
transport = args[++i];
|
|
38
|
+
} else if (args[i] === "--auth" && args[i + 1]) {
|
|
39
|
+
authMode = args[++i];
|
|
40
|
+
} else if (args[i] === "--help" || args[i] === "-h") {
|
|
41
|
+
console.error(`
|
|
42
|
+
Code Search MCP \u2014 multi-repo code intelligence via MCP
|
|
43
|
+
|
|
44
|
+
REMOTE MODE (default \u2014 connects to a deployed server):
|
|
45
|
+
code-search-mcp Uses CODE_SEARCH_SERVER env
|
|
46
|
+
code-search-mcp --remote https://server:3100 Explicit server URL
|
|
47
|
+
code-search-mcp --remote https://server:3100 --api-key sk-xxx
|
|
48
|
+
|
|
49
|
+
Environment:
|
|
50
|
+
CODE_SEARCH_SERVER Remote server URL
|
|
51
|
+
CODE_SEARCH_API_KEY API key for authentication
|
|
52
|
+
|
|
53
|
+
Claude Desktop config:
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"code-search": {
|
|
57
|
+
"command": "code-search-mcp",
|
|
58
|
+
"env": {
|
|
59
|
+
"CODE_SEARCH_SERVER": "https://your-server:3100",
|
|
60
|
+
"CODE_SEARCH_API_KEY": "your-api-key"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
LOCAL MODE (self-hosted \u2014 indexes repos locally):
|
|
67
|
+
code-search-mcp --local /path/to/repos Index local directory
|
|
68
|
+
code-search-mcp --local github:org-name Clone from GitHub org
|
|
69
|
+
code-search-mcp --local --project my-project Serve existing index
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
--project <name> Project name (default: derived from source)
|
|
73
|
+
--token <token> GitHub token (or GITHUB_TOKEN env)
|
|
74
|
+
--force Force full re-index
|
|
75
|
+
|
|
76
|
+
SERVER MODE (deploy as HTTP service):
|
|
77
|
+
code-search-mcp --serve Start HTTP server
|
|
78
|
+
code-search-mcp --serve --port 3100 --auth api-key
|
|
79
|
+
|
|
80
|
+
Options:
|
|
81
|
+
--port <port> HTTP port (default: 3100)
|
|
82
|
+
--auth <mode> none | api-key | jwt
|
|
83
|
+
--transport <mode> streamable-http | sse
|
|
84
|
+
|
|
85
|
+
Environment:
|
|
86
|
+
CODE_SEARCH_TRANSPORT streamable-http | sse
|
|
87
|
+
CODE_SEARCH_PORT HTTP port (default: 3100)
|
|
88
|
+
CODE_SEARCH_AUTH_MODE none | api-key | jwt
|
|
89
|
+
CODE_SEARCH_AUTH_API_KEYS Comma-separated API keys
|
|
90
|
+
CODE_SEARCH_EMBEDDING_PROVIDER auto | codestral | openai | voyage | ollama | custom
|
|
91
|
+
CODE_SEARCH_EMBEDDING_API_KEY Unified embedding API key
|
|
92
|
+
CODE_SEARCH_DATA_DIR Data directory override
|
|
93
|
+
`);
|
|
94
|
+
process.exit(0);
|
|
95
|
+
} else if (!args[i].startsWith("-")) {
|
|
96
|
+
const arg = args[i];
|
|
97
|
+
if (arg.startsWith("http://") || arg.startsWith("https://")) {
|
|
98
|
+
mode = "remote";
|
|
99
|
+
remoteUrl = arg;
|
|
100
|
+
} else {
|
|
101
|
+
mode = "local";
|
|
102
|
+
source = arg;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
(async () => {
|
|
107
|
+
try {
|
|
108
|
+
if (mode === "remote") {
|
|
109
|
+
const serverUrl = remoteUrl ?? process.env.CODE_SEARCH_SERVER ?? process.env.CODE_SEARCH_REMOTE_URL;
|
|
110
|
+
const key = apiKey ?? process.env.CODE_SEARCH_API_KEY ?? process.env.CODE_SEARCH_AUTH_API_KEY;
|
|
111
|
+
if (!serverUrl) {
|
|
112
|
+
console.error(`[code-search-mcp] No remote server configured.
|
|
113
|
+
|
|
114
|
+
Set CODE_SEARCH_SERVER environment variable:
|
|
115
|
+
export CODE_SEARCH_SERVER=https://your-server:3100
|
|
116
|
+
|
|
117
|
+
Or pass it directly:
|
|
118
|
+
code-search-mcp --remote https://your-server:3100
|
|
119
|
+
|
|
120
|
+
For local use (self-hosted with repos on this machine):
|
|
121
|
+
code-search-mcp --local /path/to/repos
|
|
122
|
+
|
|
123
|
+
For help:
|
|
124
|
+
code-search-mcp --help
|
|
125
|
+
`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const { startRemoteProxy } = await import("./transports/remote-proxy.js");
|
|
129
|
+
await startRemoteProxy({ serverUrl, apiKey: key });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (mode === "serve") {
|
|
133
|
+
if (port) process.env.CODE_SEARCH_PORT = port;
|
|
134
|
+
if (authMode) process.env.CODE_SEARCH_AUTH_MODE = authMode;
|
|
135
|
+
process.env.CODE_SEARCH_TRANSPORT = transport === "sse" ? "sse" : "streamable-http";
|
|
136
|
+
const { startServer: startServer2 } = await import("./server.js");
|
|
137
|
+
await startServer2(projectName ?? "default", source ? resolve(source) : null);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
let directoryPath = null;
|
|
141
|
+
if (source?.startsWith("github:")) {
|
|
142
|
+
const { cloneOrUpdateOrg } = await import("./sources/github-org.js");
|
|
143
|
+
const spec = source.replace("github:", "");
|
|
144
|
+
const slashIdx = spec.indexOf("/");
|
|
145
|
+
const org = slashIdx > 0 ? spec.slice(0, slashIdx) : spec;
|
|
146
|
+
const pattern = slashIdx > 0 ? spec.slice(slashIdx + 1) : void 0;
|
|
147
|
+
if (!projectName) projectName = org;
|
|
148
|
+
console.error(`[code-search-mcp] Cloning repos from github:${org}${pattern ? `/${pattern}` : ""}...`);
|
|
149
|
+
const repos = await cloneOrUpdateOrg(org, {
|
|
150
|
+
pattern,
|
|
151
|
+
token: githubToken,
|
|
152
|
+
onProgress: (m) => console.error(m)
|
|
153
|
+
});
|
|
154
|
+
console.error(`[code-search-mcp] ${repos.length} repos ready`);
|
|
155
|
+
const { homedir } = await import("node:os");
|
|
156
|
+
const { join } = await import("node:path");
|
|
157
|
+
directoryPath = join(homedir(), ".code-search", org);
|
|
158
|
+
} else if (source) {
|
|
159
|
+
directoryPath = resolve(source);
|
|
160
|
+
if (!existsSync(directoryPath)) {
|
|
161
|
+
console.error(`[code-search-mcp] Directory not found: ${directoryPath}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
if (!projectName) {
|
|
165
|
+
projectName = directoryPath.split("/").filter(Boolean).pop() || "project";
|
|
166
|
+
}
|
|
167
|
+
} else if (!projectName) {
|
|
168
|
+
directoryPath = process.cwd();
|
|
169
|
+
projectName = directoryPath.split("/").filter(Boolean).pop() || "project";
|
|
170
|
+
}
|
|
171
|
+
const { startServer } = await import("./server.js");
|
|
172
|
+
await startServer(projectName, directoryPath);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error(`[code-search-mcp] Fatal:`, err);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
})();
|