@esankhan3/code-search-mcp 0.0.2 → 0.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/README.md +203 -263
- package/dist/resources/resources.js +3 -3
- package/dist/server.js +2 -2
- package/dist/tools/graph.js +1 -1
- package/dist/tools/index-tools.js +1 -1
- package/dist/tools/profile.js +2 -2
- package/dist/tools/search.js +1 -1
- package/package.json +16 -9
- package/dist/core/ast-graph-builder.js +0 -800
- package/dist/core/chunker.js +0 -368
- package/dist/core/claude-runner.js +0 -173
- package/dist/core/config.js +0 -59
- package/dist/core/cross-repo-detector.js +0 -887
- package/dist/core/embedder.js +0 -334
- package/dist/core/file-walker.js +0 -186
- package/dist/core/git-diff.js +0 -160
- package/dist/core/graph-metrics.js +0 -233
- package/dist/core/indexer.js +0 -552
- package/dist/core/project-graph-builder.js +0 -202
- package/dist/core/query-classifier.js +0 -144
- package/dist/core/query-router.js +0 -210
- package/dist/core/rag-evaluator.js +0 -213
- package/dist/core/repo-profiler.js +0 -383
- package/dist/core/reranker.js +0 -218
- package/dist/core/retriever.js +0 -242
- package/dist/core/semantic-edge-detector.js +0 -147
- package/dist/core/service-mesh-inferrer.js +0 -241
- package/dist/core/structural-hasher.js +0 -316
- package/dist/core/tree-sitter-parser.js +0 -539
- package/dist/core/types.js +0 -0
- package/dist/core/vector-store.js +0 -259
- package/dist/core/workspace-detector.js +0 -481
package/dist/core/embedder.js
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import { execSync as execSyncCmd } from "node:child_process";
|
|
2
|
-
import { existsSync as fsExistsSync, readFileSync as fsReadFileSync } from "node:fs";
|
|
3
|
-
import { join as pathJoin } from "node:path";
|
|
4
|
-
import { homedir as osHomedir } from "node:os";
|
|
5
|
-
class CodestralEmbedder {
|
|
6
|
-
name = "codestral";
|
|
7
|
-
dimensions;
|
|
8
|
-
model;
|
|
9
|
-
constructor(options) {
|
|
10
|
-
this.model = options?.model ?? "codestral-embed-2505";
|
|
11
|
-
this.dimensions = options?.dimensions ?? 1024;
|
|
12
|
-
}
|
|
13
|
-
async embed(texts) {
|
|
14
|
-
const apiKey = process.env.MISTRAL_API_KEY;
|
|
15
|
-
if (!apiKey) {
|
|
16
|
-
throw new Error("MISTRAL_API_KEY environment variable is not set");
|
|
17
|
-
}
|
|
18
|
-
const response = await fetch("https://api.mistral.ai/v1/embeddings", {
|
|
19
|
-
method: "POST",
|
|
20
|
-
headers: {
|
|
21
|
-
"Content-Type": "application/json",
|
|
22
|
-
Authorization: `Bearer ${apiKey}`
|
|
23
|
-
},
|
|
24
|
-
body: JSON.stringify({
|
|
25
|
-
model: this.model,
|
|
26
|
-
input: texts,
|
|
27
|
-
encoding_format: "float"
|
|
28
|
-
})
|
|
29
|
-
});
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
const body = await response.text();
|
|
32
|
-
throw new Error(`Codestral embedding request failed (${response.status}): ${body}`);
|
|
33
|
-
}
|
|
34
|
-
const json = await response.json();
|
|
35
|
-
return json.data.map((d) => d.embedding);
|
|
36
|
-
}
|
|
37
|
-
async embedSingle(text) {
|
|
38
|
-
const [result] = await this.embed([text]);
|
|
39
|
-
return result;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
class VoyageEmbedder {
|
|
43
|
-
name = "voyage";
|
|
44
|
-
dimensions;
|
|
45
|
-
model;
|
|
46
|
-
constructor(options) {
|
|
47
|
-
this.model = options?.model ?? "voyage-code-3";
|
|
48
|
-
this.dimensions = options?.dimensions ?? 1024;
|
|
49
|
-
}
|
|
50
|
-
async embed(texts) {
|
|
51
|
-
const apiKey = process.env.VOYAGE_API_KEY;
|
|
52
|
-
if (!apiKey) {
|
|
53
|
-
throw new Error("VOYAGE_API_KEY environment variable is not set");
|
|
54
|
-
}
|
|
55
|
-
const response = await fetch("https://api.voyageai.com/v1/embeddings", {
|
|
56
|
-
method: "POST",
|
|
57
|
-
headers: {
|
|
58
|
-
"Content-Type": "application/json",
|
|
59
|
-
Authorization: `Bearer ${apiKey}`
|
|
60
|
-
},
|
|
61
|
-
body: JSON.stringify({
|
|
62
|
-
model: this.model,
|
|
63
|
-
input: texts,
|
|
64
|
-
input_type: "document"
|
|
65
|
-
})
|
|
66
|
-
});
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
const body = await response.text();
|
|
69
|
-
throw new Error(`Voyage embedding request failed (${response.status}): ${body}`);
|
|
70
|
-
}
|
|
71
|
-
const json = await response.json();
|
|
72
|
-
return json.data.map((d) => d.embedding);
|
|
73
|
-
}
|
|
74
|
-
async embedSingle(text) {
|
|
75
|
-
const [result] = await this.embed([text]);
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
class OpenAIEmbedder {
|
|
80
|
-
name = "openai";
|
|
81
|
-
dimensions;
|
|
82
|
-
model;
|
|
83
|
-
constructor(options) {
|
|
84
|
-
this.model = options?.model ?? "text-embedding-3-large";
|
|
85
|
-
this.dimensions = options?.dimensions ?? 1024;
|
|
86
|
-
}
|
|
87
|
-
async embed(texts) {
|
|
88
|
-
const apiKey = process.env.OPENAI_API_KEY;
|
|
89
|
-
if (!apiKey) {
|
|
90
|
-
throw new Error("OPENAI_API_KEY environment variable is not set");
|
|
91
|
-
}
|
|
92
|
-
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
93
|
-
method: "POST",
|
|
94
|
-
headers: {
|
|
95
|
-
"Content-Type": "application/json",
|
|
96
|
-
Authorization: `Bearer ${apiKey}`
|
|
97
|
-
},
|
|
98
|
-
body: JSON.stringify({
|
|
99
|
-
model: this.model,
|
|
100
|
-
input: texts,
|
|
101
|
-
dimensions: this.dimensions
|
|
102
|
-
})
|
|
103
|
-
});
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
const body = await response.text();
|
|
106
|
-
throw new Error(`OpenAI embedding request failed (${response.status}): ${body}`);
|
|
107
|
-
}
|
|
108
|
-
const json = await response.json();
|
|
109
|
-
return json.data.map((d) => d.embedding);
|
|
110
|
-
}
|
|
111
|
-
async embedSingle(text) {
|
|
112
|
-
const [result] = await this.embed([text]);
|
|
113
|
-
return result;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const OLLAMA_PREFIX_MODELS = {
|
|
117
|
-
"nomic-embed-text": { document: "search_document: ", query: "search_query: " }
|
|
118
|
-
};
|
|
119
|
-
const OLLAMA_MODEL_DIMS = {
|
|
120
|
-
"bge-m3": 1024,
|
|
121
|
-
"nomic-embed-text": 768,
|
|
122
|
-
"snowflake-arctic-embed:l": 1024,
|
|
123
|
-
"mxbai-embed-large": 1024,
|
|
124
|
-
"all-minilm": 384
|
|
125
|
-
};
|
|
126
|
-
class OllamaEmbedder {
|
|
127
|
-
name = "ollama";
|
|
128
|
-
dimensions;
|
|
129
|
-
model;
|
|
130
|
-
baseUrl;
|
|
131
|
-
prefixes;
|
|
132
|
-
constructor(options) {
|
|
133
|
-
this.model = options?.model ?? "bge-m3";
|
|
134
|
-
this.dimensions = options?.dimensions ?? OLLAMA_MODEL_DIMS[this.model] ?? 1024;
|
|
135
|
-
this.baseUrl = process.env.OLLAMA_HOST ?? "http://localhost:11434";
|
|
136
|
-
this.prefixes = OLLAMA_PREFIX_MODELS[this.model] ?? null;
|
|
137
|
-
}
|
|
138
|
-
/** Embed texts as documents (for indexing) */
|
|
139
|
-
async embed(texts) {
|
|
140
|
-
const prefixed = this.prefixes ? texts.map((t) => `${this.prefixes.document}${t}`) : texts;
|
|
141
|
-
return this._rawEmbed(prefixed);
|
|
142
|
-
}
|
|
143
|
-
/** Embed a single text as a query (for search) */
|
|
144
|
-
async embedSingle(text) {
|
|
145
|
-
const prefixed = this.prefixes ? `${this.prefixes.query}${text}` : text;
|
|
146
|
-
const [result] = await this._rawEmbed([prefixed]);
|
|
147
|
-
return result;
|
|
148
|
-
}
|
|
149
|
-
async _rawEmbed(texts) {
|
|
150
|
-
const response = await fetch(`${this.baseUrl}/api/embed`, {
|
|
151
|
-
method: "POST",
|
|
152
|
-
headers: { "Content-Type": "application/json" },
|
|
153
|
-
body: JSON.stringify({ model: this.model, input: texts })
|
|
154
|
-
});
|
|
155
|
-
if (!response.ok) {
|
|
156
|
-
const body = await response.text();
|
|
157
|
-
throw new Error(`Ollama embedding request failed (${response.status}): ${body}`);
|
|
158
|
-
}
|
|
159
|
-
const json = await response.json();
|
|
160
|
-
return json.embeddings;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
class GeminiOAuthEmbedder {
|
|
164
|
-
name = "gemini-oauth";
|
|
165
|
-
dimensions;
|
|
166
|
-
model;
|
|
167
|
-
constructor(options) {
|
|
168
|
-
this.model = options?.model ?? "text-embedding-004";
|
|
169
|
-
this.dimensions = options?.dimensions ?? 768;
|
|
170
|
-
}
|
|
171
|
-
getAccessToken() {
|
|
172
|
-
const oauthPath = pathJoin(osHomedir(), ".gemini", "oauth_creds.json");
|
|
173
|
-
if (!fsExistsSync(oauthPath)) {
|
|
174
|
-
throw new Error("Gemini CLI not authenticated. Run: gemini auth login");
|
|
175
|
-
}
|
|
176
|
-
const creds = JSON.parse(fsReadFileSync(oauthPath, "utf-8"));
|
|
177
|
-
if (!creds.access_token) {
|
|
178
|
-
throw new Error("Gemini OAuth token not found. Run: gemini auth login");
|
|
179
|
-
}
|
|
180
|
-
return creds.access_token;
|
|
181
|
-
}
|
|
182
|
-
async embed(texts) {
|
|
183
|
-
const token = this.getAccessToken();
|
|
184
|
-
const results = [];
|
|
185
|
-
const response = await fetch(
|
|
186
|
-
`https://generativelanguage.googleapis.com/v1beta/models/${this.model}:batchEmbedContents`,
|
|
187
|
-
{
|
|
188
|
-
method: "POST",
|
|
189
|
-
headers: {
|
|
190
|
-
"Content-Type": "application/json",
|
|
191
|
-
Authorization: `Bearer ${token}`
|
|
192
|
-
},
|
|
193
|
-
body: JSON.stringify({
|
|
194
|
-
requests: texts.map((text) => ({
|
|
195
|
-
model: `models/${this.model}`,
|
|
196
|
-
content: { parts: [{ text }] },
|
|
197
|
-
taskType: "RETRIEVAL_DOCUMENT"
|
|
198
|
-
}))
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
|
-
);
|
|
202
|
-
if (!response.ok) {
|
|
203
|
-
const body = await response.text();
|
|
204
|
-
throw new Error(`Gemini embedding request failed (${response.status}): ${body.slice(0, 300)}`);
|
|
205
|
-
}
|
|
206
|
-
const json = await response.json();
|
|
207
|
-
for (const emb of json.embeddings) {
|
|
208
|
-
results.push(emb.values);
|
|
209
|
-
}
|
|
210
|
-
return results;
|
|
211
|
-
}
|
|
212
|
-
async embedSingle(text) {
|
|
213
|
-
const [result] = await this.embed([text]);
|
|
214
|
-
return result;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
class OpenAICompatibleEmbedder {
|
|
218
|
-
name = "openai-compatible";
|
|
219
|
-
dimensions;
|
|
220
|
-
model;
|
|
221
|
-
baseUrl;
|
|
222
|
-
apiKey;
|
|
223
|
-
constructor(options) {
|
|
224
|
-
this.baseUrl = options?.baseUrl || process.env.CODE_SEARCH_EMBEDDING_BASE_URL || "";
|
|
225
|
-
this.model = options?.model || process.env.CODE_SEARCH_EMBEDDING_MODEL || "";
|
|
226
|
-
this.apiKey = options?.apiKey || process.env.CODE_SEARCH_EMBEDDING_API_KEY;
|
|
227
|
-
this.dimensions = options?.dimensions ?? 1024;
|
|
228
|
-
if (!this.baseUrl) throw new Error("Embedding base URL required. Set CODE_SEARCH_EMBEDDING_BASE_URL");
|
|
229
|
-
if (!this.model) throw new Error("Embedding model required. Set CODE_SEARCH_EMBEDDING_MODEL");
|
|
230
|
-
}
|
|
231
|
-
async embed(texts) {
|
|
232
|
-
const headers = { "Content-Type": "application/json" };
|
|
233
|
-
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
234
|
-
const response = await fetch(`${this.baseUrl.replace(/\/$/, "")}/v1/embeddings`, {
|
|
235
|
-
method: "POST",
|
|
236
|
-
headers,
|
|
237
|
-
body: JSON.stringify({
|
|
238
|
-
model: this.model,
|
|
239
|
-
input: texts,
|
|
240
|
-
dimensions: this.dimensions,
|
|
241
|
-
encoding_format: "float"
|
|
242
|
-
})
|
|
243
|
-
});
|
|
244
|
-
if (!response.ok) {
|
|
245
|
-
const body = await response.text();
|
|
246
|
-
throw new Error(`Embedding request failed (${response.status}): ${body}`);
|
|
247
|
-
}
|
|
248
|
-
const json = await response.json();
|
|
249
|
-
return json.data.map((d) => d.embedding);
|
|
250
|
-
}
|
|
251
|
-
async embedSingle(text) {
|
|
252
|
-
const [result] = await this.embed([text]);
|
|
253
|
-
return result;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
function isOllamaRunning() {
|
|
257
|
-
try {
|
|
258
|
-
const host = process.env.OLLAMA_HOST ?? "http://localhost:11434";
|
|
259
|
-
execSyncCmd(`curl -s --max-time 2 ${host}/api/tags`, { stdio: "pipe", timeout: 3e3 });
|
|
260
|
-
return true;
|
|
261
|
-
} catch {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
function isGeminiCliAuthenticated() {
|
|
266
|
-
try {
|
|
267
|
-
const oauthPath = pathJoin(osHomedir(), ".gemini", "oauth_creds.json");
|
|
268
|
-
if (!fsExistsSync(oauthPath)) return false;
|
|
269
|
-
const creds = JSON.parse(fsReadFileSync(oauthPath, "utf-8"));
|
|
270
|
-
return !!creds.access_token;
|
|
271
|
-
} catch {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
function createEmbeddingProvider(config) {
|
|
276
|
-
const opts = { model: config.model, dimensions: config.dimensions };
|
|
277
|
-
switch (config.provider) {
|
|
278
|
-
case "codestral":
|
|
279
|
-
case "mistral":
|
|
280
|
-
return new CodestralEmbedder(opts);
|
|
281
|
-
case "voyage":
|
|
282
|
-
return new VoyageEmbedder(opts);
|
|
283
|
-
case "openai":
|
|
284
|
-
return new OpenAIEmbedder(opts);
|
|
285
|
-
case "ollama":
|
|
286
|
-
return new OllamaEmbedder(opts);
|
|
287
|
-
case "gemini-oauth":
|
|
288
|
-
case "gemini":
|
|
289
|
-
return new GeminiOAuthEmbedder(opts);
|
|
290
|
-
case "openai-compatible":
|
|
291
|
-
case "custom":
|
|
292
|
-
return new OpenAICompatibleEmbedder(opts);
|
|
293
|
-
case "auto": {
|
|
294
|
-
if (process.env.CODE_SEARCH_EMBEDDING_BASE_URL) return new OpenAICompatibleEmbedder(opts);
|
|
295
|
-
if (isOllamaRunning()) return new OllamaEmbedder(opts);
|
|
296
|
-
if (process.env.MISTRAL_API_KEY) return new CodestralEmbedder(opts);
|
|
297
|
-
if (process.env.OPENAI_API_KEY) return new OpenAIEmbedder(opts);
|
|
298
|
-
if (process.env.VOYAGE_API_KEY) return new VoyageEmbedder(opts);
|
|
299
|
-
if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
|
|
300
|
-
return new GeminiOAuthEmbedder(opts);
|
|
301
|
-
}
|
|
302
|
-
if (isGeminiCliAuthenticated()) return new GeminiOAuthEmbedder(opts);
|
|
303
|
-
throw new Error(
|
|
304
|
-
"No embedding provider available. Install Ollama (brew install ollama && ollama pull nomic-embed-text), or set an API key (MISTRAL_API_KEY, OPENAI_API_KEY), or set CODE_SEARCH_EMBEDDING_BASE_URL for a custom provider."
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
default:
|
|
308
|
-
throw new Error(
|
|
309
|
-
`Unknown embedding provider "${config.provider}". Supported: codestral, mistral, voyage, openai, ollama, gemini, openai-compatible, custom, auto`
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
async function batchEmbed(provider, texts, batchSize = 50, delayMs = 100) {
|
|
314
|
-
const results = [];
|
|
315
|
-
for (let i = 0; i < texts.length; i += batchSize) {
|
|
316
|
-
const batch = texts.slice(i, i + batchSize);
|
|
317
|
-
const embeddings = await provider.embed(batch);
|
|
318
|
-
results.push(...embeddings);
|
|
319
|
-
if (i + batchSize < texts.length && delayMs > 0) {
|
|
320
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return results;
|
|
324
|
-
}
|
|
325
|
-
export {
|
|
326
|
-
CodestralEmbedder,
|
|
327
|
-
GeminiOAuthEmbedder,
|
|
328
|
-
OllamaEmbedder,
|
|
329
|
-
OpenAICompatibleEmbedder,
|
|
330
|
-
OpenAIEmbedder,
|
|
331
|
-
VoyageEmbedder,
|
|
332
|
-
batchEmbed,
|
|
333
|
-
createEmbeddingProvider
|
|
334
|
-
};
|
package/dist/core/file-walker.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { join, extname } from "node:path";
|
|
3
|
-
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4
|
-
".ts",
|
|
5
|
-
".tsx",
|
|
6
|
-
".js",
|
|
7
|
-
".jsx",
|
|
8
|
-
".py",
|
|
9
|
-
".go",
|
|
10
|
-
".rs",
|
|
11
|
-
".java",
|
|
12
|
-
".php"
|
|
13
|
-
]);
|
|
14
|
-
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
15
|
-
"node_modules",
|
|
16
|
-
"dist",
|
|
17
|
-
"build",
|
|
18
|
-
".git",
|
|
19
|
-
"vendor",
|
|
20
|
-
"__pycache__",
|
|
21
|
-
".next"
|
|
22
|
-
]);
|
|
23
|
-
function langFromExt(ext) {
|
|
24
|
-
switch (ext) {
|
|
25
|
-
case ".ts":
|
|
26
|
-
case ".tsx":
|
|
27
|
-
return "typescript";
|
|
28
|
-
case ".js":
|
|
29
|
-
case ".jsx":
|
|
30
|
-
return "javascript";
|
|
31
|
-
case ".py":
|
|
32
|
-
return "python";
|
|
33
|
-
case ".go":
|
|
34
|
-
return "go";
|
|
35
|
-
case ".rs":
|
|
36
|
-
return "rust";
|
|
37
|
-
case ".java":
|
|
38
|
-
return "java";
|
|
39
|
-
case ".php":
|
|
40
|
-
return "php";
|
|
41
|
-
default:
|
|
42
|
-
return "unknown";
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function walkDir(dir, collected) {
|
|
46
|
-
let entries;
|
|
47
|
-
try {
|
|
48
|
-
entries = readdirSync(dir);
|
|
49
|
-
} catch {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
for (const entry of entries) {
|
|
53
|
-
if (SKIP_DIRS.has(entry)) continue;
|
|
54
|
-
const full = join(dir, entry);
|
|
55
|
-
let stat;
|
|
56
|
-
try {
|
|
57
|
-
stat = statSync(full);
|
|
58
|
-
} catch {
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (stat.isDirectory()) {
|
|
62
|
-
walkDir(full, collected);
|
|
63
|
-
} else if (stat.isFile() && SOURCE_EXTENSIONS.has(extname(entry))) {
|
|
64
|
-
collected.push(full);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function extractImports(lines, lang) {
|
|
69
|
-
const imports = [];
|
|
70
|
-
for (const line of lines) {
|
|
71
|
-
const trimmed = line.trim();
|
|
72
|
-
if (lang === "typescript" || lang === "javascript") {
|
|
73
|
-
const m = trimmed.match(/^import\s+.*from\s+['"]([@\w/.-]+)['"]/);
|
|
74
|
-
if (m) imports.push(m[1]);
|
|
75
|
-
} else if (lang === "python") {
|
|
76
|
-
const m = trimmed.match(/^(?:from\s+([\w.]+)\s+)?import\s+([\w., ]+)/);
|
|
77
|
-
if (m) imports.push(m[1] ?? m[2]);
|
|
78
|
-
} else if (lang === "go") {
|
|
79
|
-
const m = trimmed.match(/^\s*"([^"]+)"/);
|
|
80
|
-
if (m) imports.push(m[1]);
|
|
81
|
-
} else if (lang === "java") {
|
|
82
|
-
const m = trimmed.match(/^import\s+([\w.]+);/);
|
|
83
|
-
if (m) imports.push(m[1]);
|
|
84
|
-
} else if (lang === "rust") {
|
|
85
|
-
const m = trimmed.match(/^use\s+([\w:]+)/);
|
|
86
|
-
if (m) imports.push(m[1]);
|
|
87
|
-
} else if (lang === "php") {
|
|
88
|
-
const m = trimmed.match(/^use\s+([\w\\]+)/);
|
|
89
|
-
if (m) imports.push(m[1]);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return imports;
|
|
93
|
-
}
|
|
94
|
-
function extractNamedImports(lines, lang) {
|
|
95
|
-
const results = [];
|
|
96
|
-
const fullText = lines.join("\n");
|
|
97
|
-
if (lang === "typescript" || lang === "javascript") {
|
|
98
|
-
const re = /^import\s+\{([^}]+)\}\s+from\s+['"]([@\w/.\-]+)['"]/gm;
|
|
99
|
-
let m;
|
|
100
|
-
while ((m = re.exec(fullText)) !== null) {
|
|
101
|
-
const names = m[1].split(",").map((s) => {
|
|
102
|
-
const parts = s.trim().split(/\s+as\s+/);
|
|
103
|
-
return parts[0].trim();
|
|
104
|
-
}).filter(Boolean);
|
|
105
|
-
if (names.length > 0) results.push({ specifier: m[2], names });
|
|
106
|
-
}
|
|
107
|
-
const defaultRe = /^import\s+([A-Z]\w*)\s+from\s+['"]([@\w/.\-]+)['"]/gm;
|
|
108
|
-
while ((m = defaultRe.exec(fullText)) !== null) {
|
|
109
|
-
results.push({ specifier: m[2], names: [m[1]] });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (lang === "python") {
|
|
113
|
-
const re = /^from\s+([\w.]+)\s+import\s+(.+)$/gm;
|
|
114
|
-
let m;
|
|
115
|
-
while ((m = re.exec(fullText)) !== null) {
|
|
116
|
-
const names = m[2].split(",").map((s) => {
|
|
117
|
-
const parts = s.trim().split(/\s+as\s+/);
|
|
118
|
-
return parts[0].trim();
|
|
119
|
-
}).filter((s) => s && !s.startsWith("("));
|
|
120
|
-
if (names.length > 0) results.push({ specifier: m[1], names });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (lang === "go") {
|
|
124
|
-
for (const line of lines) {
|
|
125
|
-
const trimmed = line.trim();
|
|
126
|
-
const aliased = trimmed.match(/^\s*(\w+)\s+"([^"]+)"/);
|
|
127
|
-
if (aliased) {
|
|
128
|
-
results.push({ specifier: aliased[2], names: [aliased[1]] });
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
const regular = trimmed.match(/^\s*"([^"]+)"/);
|
|
132
|
-
if (regular) {
|
|
133
|
-
const lastSeg = regular[1].split("/").pop();
|
|
134
|
-
if (lastSeg) results.push({ specifier: regular[1], names: [lastSeg] });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (lang === "php") {
|
|
139
|
-
for (const line of lines) {
|
|
140
|
-
const trimmed = line.trim();
|
|
141
|
-
const m = trimmed.match(/^use\s+([\w\\]+)(?:\s+as\s+(\w+))?\s*;/);
|
|
142
|
-
if (m) {
|
|
143
|
-
const parts = m[1].split("\\");
|
|
144
|
-
const name = m[2] || parts[parts.length - 1];
|
|
145
|
-
results.push({ specifier: m[1], names: [name] });
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (lang === "rust") {
|
|
150
|
-
for (const line of lines) {
|
|
151
|
-
const trimmed = line.trim();
|
|
152
|
-
const grouped = trimmed.match(/^use\s+([\w:]+)::\{([^}]+)\}\s*;/);
|
|
153
|
-
if (grouped) {
|
|
154
|
-
const names = grouped[2].split(",").map((s) => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
|
|
155
|
-
if (names.length > 0) results.push({ specifier: grouped[1], names });
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
const single = trimmed.match(/^use\s+([\w:]+)(?:\s+as\s+(\w+))?\s*;/);
|
|
159
|
-
if (single) {
|
|
160
|
-
const segments = single[1].split("::");
|
|
161
|
-
const name = single[2] || segments[segments.length - 1];
|
|
162
|
-
results.push({ specifier: single[1], names: [name] });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (lang === "java") {
|
|
167
|
-
for (const line of lines) {
|
|
168
|
-
const trimmed = line.trim();
|
|
169
|
-
const m = trimmed.match(/^import\s+([\w.]+)\s*;/);
|
|
170
|
-
if (m) {
|
|
171
|
-
const parts = m[1].split(".");
|
|
172
|
-
const name = parts[parts.length - 1];
|
|
173
|
-
if (name !== "*") results.push({ specifier: m[1], names: [name] });
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return results;
|
|
178
|
-
}
|
|
179
|
-
export {
|
|
180
|
-
SKIP_DIRS,
|
|
181
|
-
SOURCE_EXTENSIONS,
|
|
182
|
-
extractImports,
|
|
183
|
-
extractNamedImports,
|
|
184
|
-
langFromExt,
|
|
185
|
-
walkDir
|
|
186
|
-
};
|
package/dist/core/git-diff.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { extname } from "node:path";
|
|
3
|
-
const SOURCE_EXTS = /* @__PURE__ */ new Set([
|
|
4
|
-
".ts",
|
|
5
|
-
".tsx",
|
|
6
|
-
".js",
|
|
7
|
-
".jsx",
|
|
8
|
-
".mjs",
|
|
9
|
-
".cjs",
|
|
10
|
-
".go",
|
|
11
|
-
".py",
|
|
12
|
-
".rs",
|
|
13
|
-
".java",
|
|
14
|
-
".php",
|
|
15
|
-
".rb",
|
|
16
|
-
".swift",
|
|
17
|
-
".kt",
|
|
18
|
-
".cs",
|
|
19
|
-
".scala",
|
|
20
|
-
".c",
|
|
21
|
-
".cpp",
|
|
22
|
-
".h",
|
|
23
|
-
".hpp",
|
|
24
|
-
".yaml",
|
|
25
|
-
".yml",
|
|
26
|
-
".json",
|
|
27
|
-
".toml",
|
|
28
|
-
".xml",
|
|
29
|
-
".sql",
|
|
30
|
-
".graphql",
|
|
31
|
-
".proto",
|
|
32
|
-
".md",
|
|
33
|
-
".txt"
|
|
34
|
-
]);
|
|
35
|
-
function isSourceFile(filePath) {
|
|
36
|
-
return SOURCE_EXTS.has(extname(filePath).toLowerCase());
|
|
37
|
-
}
|
|
38
|
-
function parseNameStatus(output) {
|
|
39
|
-
const added = [];
|
|
40
|
-
const modified = [];
|
|
41
|
-
const deleted = [];
|
|
42
|
-
const renamed = [];
|
|
43
|
-
if (!output) return { added, modified, deleted, renamed, fallbackToFull: false };
|
|
44
|
-
for (const line of output.split("\n")) {
|
|
45
|
-
if (!line.trim()) continue;
|
|
46
|
-
const parts = line.split(" ");
|
|
47
|
-
const status = parts[0];
|
|
48
|
-
const filePath = parts[1];
|
|
49
|
-
if (!filePath || !status) continue;
|
|
50
|
-
if (!isSourceFile(filePath) && status[0] !== "R" && status[0] !== "C") continue;
|
|
51
|
-
switch (status[0]) {
|
|
52
|
-
case "A":
|
|
53
|
-
added.push(filePath);
|
|
54
|
-
break;
|
|
55
|
-
case "M":
|
|
56
|
-
modified.push(filePath);
|
|
57
|
-
break;
|
|
58
|
-
case "D":
|
|
59
|
-
deleted.push(filePath);
|
|
60
|
-
break;
|
|
61
|
-
case "R": {
|
|
62
|
-
const newPath = parts[2];
|
|
63
|
-
if (newPath) {
|
|
64
|
-
renamed.push({ from: filePath, to: newPath });
|
|
65
|
-
if (isSourceFile(filePath)) deleted.push(filePath);
|
|
66
|
-
if (isSourceFile(newPath)) added.push(newPath);
|
|
67
|
-
}
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
case "C": {
|
|
71
|
-
const copyPath = parts[2];
|
|
72
|
-
if (copyPath && isSourceFile(copyPath)) added.push(copyPath);
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return { added, modified, deleted, renamed, fallbackToFull: false };
|
|
78
|
-
}
|
|
79
|
-
function mergeDiffs(a, b, extraAdded) {
|
|
80
|
-
const allAdded = /* @__PURE__ */ new Set([...a.added, ...b.added, ...extraAdded]);
|
|
81
|
-
const allModified = /* @__PURE__ */ new Set([...a.modified, ...b.modified]);
|
|
82
|
-
const allDeleted = /* @__PURE__ */ new Set([...a.deleted, ...b.deleted]);
|
|
83
|
-
const allRenamed = [...a.renamed, ...b.renamed];
|
|
84
|
-
for (const f of allAdded) allModified.delete(f);
|
|
85
|
-
for (const f of [...allAdded]) {
|
|
86
|
-
if (allDeleted.has(f)) {
|
|
87
|
-
allAdded.delete(f);
|
|
88
|
-
allDeleted.delete(f);
|
|
89
|
-
allModified.add(f);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
added: [...allAdded],
|
|
94
|
-
modified: [...allModified],
|
|
95
|
-
deleted: [...allDeleted],
|
|
96
|
-
renamed: allRenamed,
|
|
97
|
-
fallbackToFull: a.fallbackToFull || b.fallbackToFull
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
function getCommittedChanges(repoPath, sinceCommit) {
|
|
101
|
-
try {
|
|
102
|
-
const output = execSync(
|
|
103
|
-
`git diff --name-status ${sinceCommit}..HEAD`,
|
|
104
|
-
{ cwd: repoPath, encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
|
|
105
|
-
).trim();
|
|
106
|
-
return parseNameStatus(output);
|
|
107
|
-
} catch {
|
|
108
|
-
return { added: [], modified: [], deleted: [], renamed: [], fallbackToFull: true };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function getUncommittedChanges(repoPath) {
|
|
112
|
-
try {
|
|
113
|
-
const staged = execSync("git diff --name-status --cached", {
|
|
114
|
-
cwd: repoPath,
|
|
115
|
-
encoding: "utf-8",
|
|
116
|
-
timeout: 5e3,
|
|
117
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
118
|
-
}).trim();
|
|
119
|
-
const unstaged = execSync("git diff --name-status", {
|
|
120
|
-
cwd: repoPath,
|
|
121
|
-
encoding: "utf-8",
|
|
122
|
-
timeout: 5e3,
|
|
123
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
124
|
-
}).trim();
|
|
125
|
-
const untracked = execSync("git ls-files --others --exclude-standard", {
|
|
126
|
-
cwd: repoPath,
|
|
127
|
-
encoding: "utf-8",
|
|
128
|
-
timeout: 5e3,
|
|
129
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
130
|
-
}).trim();
|
|
131
|
-
const stagedDiff = parseNameStatus(staged);
|
|
132
|
-
const unstagedDiff = parseNameStatus(unstaged);
|
|
133
|
-
const untrackedFiles = untracked ? untracked.split("\n").filter((f) => f && isSourceFile(f)) : [];
|
|
134
|
-
return mergeDiffs(stagedDiff, unstagedDiff, untrackedFiles);
|
|
135
|
-
} catch {
|
|
136
|
-
return { added: [], modified: [], deleted: [], renamed: [], fallbackToFull: false };
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
function getAllChanges(repoPath, lastIndexedSha) {
|
|
140
|
-
if (!lastIndexedSha) {
|
|
141
|
-
return { added: [], modified: [], deleted: [], renamed: [], fallbackToFull: true };
|
|
142
|
-
}
|
|
143
|
-
const committed = getCommittedChanges(repoPath, lastIndexedSha);
|
|
144
|
-
if (committed.fallbackToFull) return committed;
|
|
145
|
-
const uncommitted = getUncommittedChanges(repoPath);
|
|
146
|
-
return mergeDiffs(committed, uncommitted, []);
|
|
147
|
-
}
|
|
148
|
-
function getChangedFilesList(diff) {
|
|
149
|
-
return [...diff.added, ...diff.modified];
|
|
150
|
-
}
|
|
151
|
-
function getDeletedFilesList(diff) {
|
|
152
|
-
return [...diff.deleted];
|
|
153
|
-
}
|
|
154
|
-
export {
|
|
155
|
-
getAllChanges,
|
|
156
|
-
getChangedFilesList,
|
|
157
|
-
getCommittedChanges,
|
|
158
|
-
getDeletedFilesList,
|
|
159
|
-
getUncommittedChanges
|
|
160
|
-
};
|