codesearch-mcp 0.0.18-alpha → 0.2.0-alpha
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/CHANGELOG.md +47 -1
- package/README.md +194 -23
- package/dist/app.js +38 -2
- package/dist/src/commands/IndexCommand.js +55 -1
- package/dist/src/commands/McpCommand.js +6 -2
- package/dist/src/config-error.d.ts +7 -0
- package/dist/src/config-error.js +14 -0
- package/dist/src/config-store.d.ts +73 -17
- package/dist/src/config-store.js +142 -27
- package/dist/src/config.d.ts +36 -11
- package/dist/src/config.js +89 -27
- package/dist/src/constants.d.ts +42 -2
- package/dist/src/constants.js +61 -7
- package/dist/src/container.js +5 -3
- package/dist/src/db/index.d.ts +1 -1
- package/dist/src/db/index.js +2 -1
- package/dist/src/db/schema.d.ts +111 -0
- package/dist/src/db/schema.js +15 -1
- package/dist/src/help.js +6 -5
- package/dist/src/interfaces/IAstParser.d.ts +14 -0
- package/dist/src/interfaces/IChunker.d.ts +24 -0
- package/dist/src/interfaces/IChunker.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +31 -0
- package/dist/src/interfaces/IEmbedder.d.ts +14 -2
- package/dist/src/interfaces/ILogger.d.ts +7 -0
- package/dist/src/interfaces/ISearcher.d.ts +1 -0
- package/dist/src/interfaces/index.d.ts +2 -1
- package/dist/src/owner-path.d.ts +10 -0
- package/dist/src/owner-path.js +27 -0
- package/dist/src/services/AstChunker.d.ts +42 -0
- package/dist/src/services/AstChunker.js +158 -0
- package/dist/src/services/AstParserService.d.ts +6 -0
- package/dist/src/services/AstParserService.js +31 -2
- package/dist/src/services/DatabaseService.d.ts +17 -1
- package/dist/src/services/DatabaseService.js +116 -25
- package/dist/src/services/EmbedderFactory.d.ts +9 -0
- package/dist/src/services/EmbedderFactory.js +26 -0
- package/dist/src/services/IndexerService.d.ts +21 -5
- package/dist/src/services/IndexerService.js +127 -44
- package/dist/src/services/InfinityEmbedder.d.ts +38 -0
- package/dist/src/services/InfinityEmbedder.js +88 -0
- package/dist/src/services/LoggerService.d.ts +3 -1
- package/dist/src/services/LoggerService.js +17 -3
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +25 -0
- package/dist/src/services/OpenAICompatibleEmbedder.js +75 -0
- package/dist/src/services/SearcherService.d.ts +21 -1
- package/dist/src/services/SearcherService.js +129 -28
- package/dist/src/services/VoyageEmbedder.d.ts +22 -0
- package/dist/src/services/VoyageEmbedder.js +72 -0
- package/dist/src/services/WebServerService.d.ts +57 -14
- package/dist/src/services/WebServerService.js +134 -26
- package/dist/src/services/http-retry.d.ts +19 -0
- package/dist/src/services/http-retry.js +54 -0
- package/dist/src/services/infinity-provisioning.d.ts +115 -0
- package/dist/src/services/infinity-provisioning.js +516 -0
- package/dist/src/services/legacy-ollama-index.d.ts +24 -0
- package/dist/src/services/legacy-ollama-index.js +61 -0
- package/dist/src/services/reranker.d.ts +15 -0
- package/dist/src/services/reranker.js +44 -0
- package/dist/src/setup.d.ts +43 -0
- package/dist/src/setup.js +227 -55
- package/dist/src/tools/SearchCodebaseTool.js +1 -1
- package/dist/src/tools/format-helpers.js +2 -1
- package/dist/src/web/dashboard.js +5 -0
- package/docker/docker-compose.infinity-gpu.yml +19 -0
- package/docker/docker-compose.yml +20 -5
- package/docs/web-dashboard.md +36 -17
- package/package.json +18 -3
- package/dist/src/ast.d.ts +0 -2
- package/dist/src/ast.js +0 -110
- package/dist/src/commands/SetupCommand.d.ts +0 -7
- package/dist/src/commands/SetupCommand.js +0 -14
- package/dist/src/db/data-source.d.ts +0 -2
- package/dist/src/db/data-source.js +0 -13
- package/dist/src/db/entities/code-chunk.entity.d.ts +0 -9
- package/dist/src/db/entities/code-chunk.entity.js +0 -54
- package/dist/src/db/entities/code-dependency.entity.d.ts +0 -9
- package/dist/src/db/entities/code-dependency.entity.js +0 -54
- package/dist/src/db/entities/code-symbol.entity.d.ts +0 -10
- package/dist/src/db/entities/code-symbol.entity.js +0 -59
- package/dist/src/db/init.d.ts +0 -2
- package/dist/src/db/init.js +0 -62
- package/dist/src/env-loader.d.ts +0 -1
- package/dist/src/env-loader.js +0 -41
- package/dist/src/indexer.d.ts +0 -17
- package/dist/src/indexer.js +0 -242
- package/dist/src/mcp.d.ts +0 -1
- package/dist/src/mcp.js +0 -403
- package/dist/src/ollama.d.ts +0 -2
- package/dist/src/ollama.js +0 -6
- package/dist/src/searcher.d.ts +0 -74
- package/dist/src/searcher.js +0 -446
- package/dist/src/services/OllamaEmbedder.d.ts +0 -14
- package/dist/src/services/OllamaEmbedder.js +0 -63
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
package/dist/src/indexer.js
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.discoverProjects = discoverProjects;
|
|
37
|
-
exports.collectDepEdges = collectDepEdges;
|
|
38
|
-
exports.writeDepEdges = writeDepEdges;
|
|
39
|
-
exports.scanAndIndex = scanAndIndex;
|
|
40
|
-
const fs = __importStar(require("fs"));
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
|
-
const textsplitters_1 = require("@langchain/textsplitters");
|
|
43
|
-
const cli_progress_1 = require("cli-progress");
|
|
44
|
-
const db_1 = require("./db");
|
|
45
|
-
const ast_1 = require("./ast");
|
|
46
|
-
const config_1 = require("./config");
|
|
47
|
-
const ollama_1 = require("./ollama");
|
|
48
|
-
const file_utils_1 = require("./file-utils");
|
|
49
|
-
// JS/TS splitter uses language-aware separators (functions, classes, blocks)
|
|
50
|
-
const jsSplitter = textsplitters_1.RecursiveCharacterTextSplitter.fromLanguage('js', {
|
|
51
|
-
chunkSize: config_1.config.indexer.chunkSize,
|
|
52
|
-
chunkOverlap: config_1.config.indexer.chunkOverlap,
|
|
53
|
-
});
|
|
54
|
-
// Schema splitter uses generic separators — better for Prisma/GraphQL block structure
|
|
55
|
-
const schemaSplitter = new textsplitters_1.RecursiveCharacterTextSplitter({
|
|
56
|
-
chunkSize: config_1.config.indexer.chunkSize,
|
|
57
|
-
chunkOverlap: config_1.config.indexer.chunkOverlap,
|
|
58
|
-
separators: ['\n\n', '\n', ' '],
|
|
59
|
-
});
|
|
60
|
-
const SCHEMA_EXTENSIONS = /\.(prisma|graphql|gql)$/;
|
|
61
|
-
function getSplitter(filePath) {
|
|
62
|
-
return SCHEMA_EXTENSIONS.test(filePath) ? schemaSplitter : jsSplitter;
|
|
63
|
-
}
|
|
64
|
-
// Detect JS/TS projects by the presence of package.json in their root
|
|
65
|
-
function discoverProjects(rootDir) {
|
|
66
|
-
return fs.readdirSync(rootDir, { withFileTypes: true })
|
|
67
|
-
.filter(d => d.isDirectory() && !file_utils_1.IGNORE_DIRS.has(d.name))
|
|
68
|
-
.map(d => ({ name: d.name, path: path.join(rootDir, d.name) }))
|
|
69
|
-
.filter(p => fs.existsSync(path.join(p.path, 'package.json')));
|
|
70
|
-
}
|
|
71
|
-
// Embed a pre-formatted prompt (caller is responsible for prefix and context)
|
|
72
|
-
async function embedText(prompt) {
|
|
73
|
-
const { embedding } = await ollama_1.ollama.embeddings({
|
|
74
|
-
model: config_1.config.ollama.model,
|
|
75
|
-
prompt,
|
|
76
|
-
});
|
|
77
|
-
return embedding;
|
|
78
|
-
}
|
|
79
|
-
// Shared helpers for dependency edge writes — used by ingestFile (incremental) and backfill CLI.
|
|
80
|
-
// Extract edges via ts-morph (disk I/O) and normalize to workspace-relative paths.
|
|
81
|
-
// Call this OUTSIDE any DB transaction — disk/AST work must not hold a DB connection open.
|
|
82
|
-
async function collectDepEdges(absFilePath, serviceName, projectRoot) {
|
|
83
|
-
const rawEdges = await (0, ast_1.extractImportEdges)(absFilePath, serviceName, projectRoot);
|
|
84
|
-
return rawEdges
|
|
85
|
-
.filter(e => e.toFile.startsWith(config_1.config.workspaceRoot))
|
|
86
|
-
.map(e => ({
|
|
87
|
-
...e,
|
|
88
|
-
fromFile: path.relative(config_1.config.workspaceRoot, e.fromFile),
|
|
89
|
-
toFile: path.relative(config_1.config.workspaceRoot, e.toFile),
|
|
90
|
-
}));
|
|
91
|
-
}
|
|
92
|
-
// Replace edge rows for a file. When `manager` is provided, runs inside that transaction;
|
|
93
|
-
// otherwise opens its own. Keeps delete + insert atomic either way.
|
|
94
|
-
async function writeDepEdges(serviceName, relFilePath, edges, manager) {
|
|
95
|
-
const run = async (mgr) => {
|
|
96
|
-
await mgr.delete(db_1.CodeDependency, { project: serviceName, fromFile: relFilePath });
|
|
97
|
-
if (edges.length > 0) {
|
|
98
|
-
await mgr.createQueryBuilder().insert().into(db_1.CodeDependency).values(edges).execute();
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
if (manager)
|
|
102
|
-
await run(manager);
|
|
103
|
-
else
|
|
104
|
-
await db_1.db.transaction(run);
|
|
105
|
-
}
|
|
106
|
-
// Ingest a single file: skip if unchanged, otherwise delete old chunks and re-index.
|
|
107
|
-
// Returns status and chunk count. Never throws — failures are caught and reported.
|
|
108
|
-
async function ingestFile(filePath, serviceName, mtimeMs, projectRoot) {
|
|
109
|
-
try {
|
|
110
|
-
const repo = db_1.db.getRepository(db_1.CodeChunk);
|
|
111
|
-
const relFilePath = path.relative(config_1.config.workspaceRoot, filePath);
|
|
112
|
-
// Incremental check: if mtime matches what's stored, the file hasn't changed
|
|
113
|
-
const existing = await repo.findOne({
|
|
114
|
-
select: ['id', 'lastModified'],
|
|
115
|
-
where: { serviceName, filePath: relFilePath },
|
|
116
|
-
});
|
|
117
|
-
if (existing?.lastModified === mtimeMs) {
|
|
118
|
-
return { status: 'skipped', chunks: 0 };
|
|
119
|
-
}
|
|
120
|
-
// Extract import edges before the transaction — disk I/O must not hold a DB connection open
|
|
121
|
-
const edges = await collectDepEdges(filePath, serviceName, projectRoot);
|
|
122
|
-
// Read and embed outside the transaction — Ollama calls must not hold a DB transaction open
|
|
123
|
-
const code = fs.readFileSync(filePath, 'utf-8');
|
|
124
|
-
const chunks = await getSplitter(filePath).createDocuments([code]);
|
|
125
|
-
const relPath = path.relative(projectRoot, filePath); // project-relative for embedding header
|
|
126
|
-
const records = [];
|
|
127
|
-
for (let i = 0; i < chunks.length; i += config_1.config.indexer.batchSize) {
|
|
128
|
-
const batch = chunks.slice(i, i + config_1.config.indexer.batchSize);
|
|
129
|
-
const embedded = await Promise.all(batch.map(async (chunk) => {
|
|
130
|
-
const startLine = (chunk.metadata?.loc?.lines?.from ?? 1);
|
|
131
|
-
const prompt = `Project: ${serviceName} | Path: ${relPath}\n${chunk.pageContent}`;
|
|
132
|
-
const embedding = await embedText(prompt);
|
|
133
|
-
return repo.create({
|
|
134
|
-
serviceName, filePath: relFilePath, startLine,
|
|
135
|
-
content: chunk.pageContent,
|
|
136
|
-
embedding: `[${embedding}]`,
|
|
137
|
-
lastModified: mtimeMs,
|
|
138
|
-
});
|
|
139
|
-
}));
|
|
140
|
-
records.push(...embedded);
|
|
141
|
-
}
|
|
142
|
-
// Atomic replace: delete stale chunks + dependency edges, insert new ones in a single transaction.
|
|
143
|
-
// Prevents partial index if the process crashes mid-write.
|
|
144
|
-
await db_1.db.transaction(async (manager) => {
|
|
145
|
-
await manager.delete(db_1.CodeChunk, { serviceName, filePath: relFilePath });
|
|
146
|
-
for (let i = 0; i < records.length; i += config_1.config.indexer.batchSize) {
|
|
147
|
-
await manager.createQueryBuilder()
|
|
148
|
-
.insert()
|
|
149
|
-
.into(db_1.CodeChunk)
|
|
150
|
-
.values(records.slice(i, i + config_1.config.indexer.batchSize))
|
|
151
|
-
.execute();
|
|
152
|
-
}
|
|
153
|
-
await writeDepEdges(serviceName, relFilePath, edges, manager);
|
|
154
|
-
});
|
|
155
|
-
return { status: 'indexed', chunks: chunks.length };
|
|
156
|
-
}
|
|
157
|
-
catch (err) {
|
|
158
|
-
console.error(`\n[indexer] Failed: ${filePath} — ${err.message}`);
|
|
159
|
-
return { status: 'failed', chunks: 0 };
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// Index a project directory incrementally.
|
|
163
|
-
// Removes chunks for deleted files, skips unchanged files, re-indexes modified ones.
|
|
164
|
-
// Pass force=true to invalidate all cached mtimes and force full re-index (e.g. after embedding strategy change).
|
|
165
|
-
// Pass signal to support graceful interruption — stops between files, never mid-transaction.
|
|
166
|
-
async function scanAndIndex(dir, serviceName, force = false, signal, preCollectedFiles) {
|
|
167
|
-
const startTime = Date.now();
|
|
168
|
-
const repo = db_1.db.getRepository(db_1.CodeChunk);
|
|
169
|
-
// Force mode: wipe lastModified so all files are treated as changed
|
|
170
|
-
if (force) {
|
|
171
|
-
await db_1.db.query(`UPDATE code_chunk SET "lastModified" = NULL WHERE "serviceName" = $1`, [serviceName]);
|
|
172
|
-
}
|
|
173
|
-
const files = preCollectedFiles ?? (0, file_utils_1.collectFiles)(dir, (0, file_utils_1.loadIgnore)(dir), dir);
|
|
174
|
-
// Clean up chunks for files no longer in the collected set — covers both deleted files and
|
|
175
|
-
// files newly excluded by .ragignore (they exist on disk but should no longer be indexed).
|
|
176
|
-
const collectedRelPaths = new Set(files.map(f => path.relative(config_1.config.workspaceRoot, f)));
|
|
177
|
-
const dbPaths = await db_1.db.query(`SELECT DISTINCT "filePath" FROM code_chunk WHERE "serviceName" = $1`, [serviceName]);
|
|
178
|
-
for (const { filePath } of dbPaths) {
|
|
179
|
-
if (!collectedRelPaths.has(filePath)) {
|
|
180
|
-
await repo.delete({ serviceName, filePath });
|
|
181
|
-
await db_1.db.query(`DELETE FROM code_dependency WHERE project = $1 AND "fromFile" = $2`, [serviceName, filePath]);
|
|
182
|
-
await db_1.db.query(`DELETE FROM code_dependency WHERE project = $1 AND "toFile" = $2`, [serviceName, filePath]);
|
|
183
|
-
console.log(` Removed stale: ${path.basename(filePath)}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
const bar = new cli_progress_1.SingleBar({
|
|
187
|
-
format: `{serviceName} {bar} {percentage}% | {value}/{total} | ETA {eta_formatted} | {filename}`,
|
|
188
|
-
barCompleteChar: '█',
|
|
189
|
-
barIncompleteChar: '░',
|
|
190
|
-
hideCursor: true,
|
|
191
|
-
clearOnComplete: false,
|
|
192
|
-
}, cli_progress_1.Presets.shades_grey);
|
|
193
|
-
const warnings = [];
|
|
194
|
-
console.log(`\nIndexing ${serviceName}: ${files.length} files`);
|
|
195
|
-
const meta = (0, file_utils_1.readRagMeta)(dir);
|
|
196
|
-
if (meta === null && fs.existsSync(path.join(dir, '.ragmeta.json'))) {
|
|
197
|
-
const msg = `.ragmeta.json found but invalid JSON`;
|
|
198
|
-
console.log(` ⚠ ${msg}`);
|
|
199
|
-
warnings.push(msg);
|
|
200
|
-
}
|
|
201
|
-
else if (meta) {
|
|
202
|
-
const parts = [meta.description, meta.stack && `[${meta.stack}]`].filter(Boolean).join(' ');
|
|
203
|
-
console.log(` ✓ .ragmeta.json: ${parts || '(no fields)'}`);
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
const msg = `No .ragmeta.json — add {"description":"...","stack":"...","notes":"..."} so list_projects can describe this project`;
|
|
207
|
-
console.log(` ⚠ ${msg}`);
|
|
208
|
-
warnings.push(msg);
|
|
209
|
-
}
|
|
210
|
-
if (!fs.existsSync(path.join(dir, '.ragignore'))) {
|
|
211
|
-
const msg = `No .ragignore — create one to exclude generated/codegen files (e.g. src/gql/graphql.ts, **/__snapshots__/**)`;
|
|
212
|
-
console.log(` ⚠ ${msg}`);
|
|
213
|
-
warnings.push(msg);
|
|
214
|
-
}
|
|
215
|
-
bar.start(files.length, 0, { serviceName, filename: '' });
|
|
216
|
-
const stats = {
|
|
217
|
-
serviceName, indexed: 0, skipped: 0, failed: 0, chunks: 0, elapsedSec: 0, warnings,
|
|
218
|
-
};
|
|
219
|
-
for (const filePath of files) {
|
|
220
|
-
if (signal?.aborted) {
|
|
221
|
-
bar.stop();
|
|
222
|
-
console.log(`\n Interrupted — ${serviceName}`);
|
|
223
|
-
stats.elapsedSec = Math.round((Date.now() - startTime) / 100) / 10;
|
|
224
|
-
return stats;
|
|
225
|
-
}
|
|
226
|
-
bar.update({ filename: path.basename(filePath) });
|
|
227
|
-
const mtimeMs = fs.statSync(filePath).mtimeMs;
|
|
228
|
-
const result = await ingestFile(filePath, serviceName, mtimeMs, dir);
|
|
229
|
-
if (result.status === 'indexed')
|
|
230
|
-
stats.indexed++;
|
|
231
|
-
else if (result.status === 'skipped')
|
|
232
|
-
stats.skipped++;
|
|
233
|
-
else
|
|
234
|
-
stats.failed++;
|
|
235
|
-
stats.chunks += result.chunks;
|
|
236
|
-
bar.increment();
|
|
237
|
-
}
|
|
238
|
-
bar.stop();
|
|
239
|
-
process.stdout.write('\x1B[J'); // erase any blank lines cli-progress left below the bar
|
|
240
|
-
stats.elapsedSec = Math.round((Date.now() - startTime) / 100) / 10;
|
|
241
|
-
return stats;
|
|
242
|
-
}
|
package/dist/src/mcp.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function startMCP(): Promise<void>;
|
package/dist/src/mcp.js
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.startMCP = startMCP;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
|
-
const path = __importStar(require("path"));
|
|
39
|
-
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
40
|
-
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
41
|
-
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
42
|
-
const config_1 = require("./config");
|
|
43
|
-
const searcher_1 = require("./searcher");
|
|
44
|
-
const PKG_VERSION = (() => {
|
|
45
|
-
try {
|
|
46
|
-
return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8')).version ?? '0.0.0';
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return '0.0.0';
|
|
50
|
-
}
|
|
51
|
-
})();
|
|
52
|
-
const SERVER_INSTRUCTIONS = `
|
|
53
|
-
You have access to a local semantic code search server. Use these tools to navigate unfamiliar codebases.
|
|
54
|
-
|
|
55
|
-
## Tools
|
|
56
|
-
|
|
57
|
-
- **list_projects** — call this first to see what projects are indexed and their exact names.
|
|
58
|
-
- **search_codebase** — semantic search across indexed source files. Returns file path, line number, and a code chunk.
|
|
59
|
-
- **read_file_range** — read a specific line range from any indexed file. Use this to expand context around a search result.
|
|
60
|
-
- **get_dependencies** — show what files a given file imports (direct or transitive). Use to understand what a file depends on.
|
|
61
|
-
- **get_dependents** — show which files import a given file (reverse graph). Use before refactoring to find all callers.
|
|
62
|
-
|
|
63
|
-
## Recommended workflow
|
|
64
|
-
|
|
65
|
-
1. Use \`list_projects\` to discover available projects before filtering by \`project\`.
|
|
66
|
-
2. Use \`search_codebase\` to find relevant code by meaning.
|
|
67
|
-
3. If a result looks **truncated or incomplete** (e.g. you see only the middle of a function), call \`read_file_range\` with the returned \`file_path\` and \`start_line\` set to the **Line** number shown in the result. Example: if the result shows **Line:** 150, pass \`start_line: 150\`.
|
|
68
|
-
4. For exploratory searches where you expect results to span chunk boundaries, pass \`include_neighbors: true\` to automatically include the chunk before and after each match.
|
|
69
|
-
5. Use \`find_symbol\` when you know the exact name of a function or class and want its definition location immediately — faster than semantic search.
|
|
70
|
-
6. Use \`list_files\` to explore project structure (src/, components/, etc.) before diving into files.
|
|
71
|
-
7. Use \`read_file\` to read a complete file when you have its absolute path from \`search_codebase\` results.
|
|
72
|
-
|
|
73
|
-
## When to use read_file_range vs include_neighbors
|
|
74
|
-
|
|
75
|
-
- **include_neighbors: true** — good when you already know the context will be fragmented (e.g. searching for a class definition that's likely split across chunks).
|
|
76
|
-
- **read_file_range** — better when you want to read a precise section, or when a result looks cut off and you want to expand just that one file.
|
|
77
|
-
|
|
78
|
-
## Handling Code Structure
|
|
79
|
-
- **Class/Function limits**: Chunks often end abruptly. Always use \`read_file_range\` to find the closing brace or the beginning of a method if you suspect you're seeing only a fragment.
|
|
80
|
-
- **Cross-file references**: If you see a method call like \`this.validator.validate(data)\`, use \`search_codebase\` to find the definition of that \`validator\` service.
|
|
81
|
-
|
|
82
|
-
## Relevance scores
|
|
83
|
-
|
|
84
|
-
Lower score = better match (cosine distance). Results above the configured threshold are filtered out automatically.
|
|
85
|
-
|
|
86
|
-
## Dependency graph tools
|
|
87
|
-
|
|
88
|
-
- **get_dependencies** — show what files a given file imports (direct or transitive). Use when you need to understand what a file depends on.
|
|
89
|
-
- **get_dependents** — show which files import a given file (reverse graph). Use before refactoring to find all callers and avoid breaking changes.
|
|
90
|
-
- **find_symbol** — find where a symbol (function, class, type, interface, variable) is defined by exact name. Faster than semantic search when you know the name.
|
|
91
|
-
- **list_files** — browse the directory tree of a project path. Use to understand project structure before reading files.
|
|
92
|
-
- **read_file** — read a complete file by absolute path with line numbers. Truncates at max_lines with a hint to continue.
|
|
93
|
-
|
|
94
|
-
Both tools require \`file_path\` (as returned by \`search_codebase\`) and \`project\` (from \`list_projects\`). Pass \`transitive: true\` for the full dependency chain.
|
|
95
|
-
`.trim();
|
|
96
|
-
function formatDependencies(filePath, rows, transitive) {
|
|
97
|
-
const direct = rows.filter(r => !transitive || (r.depth ?? 1) === 1);
|
|
98
|
-
const indirect = transitive ? rows.filter(r => (r.depth ?? 1) > 1) : [];
|
|
99
|
-
const lines = [`**Dependencies of** \`${filePath}\`\n`, '**Direct:**'];
|
|
100
|
-
for (const r of direct) {
|
|
101
|
-
const sym = r.toSymbol ? ` (${r.depType} \`${r.toSymbol}\`)` : ` (${r.depType})`;
|
|
102
|
-
lines.push(` → \`${r.toFile}\`${sym}`);
|
|
103
|
-
}
|
|
104
|
-
if (indirect.length > 0) {
|
|
105
|
-
lines.push('\n**Transitive:**');
|
|
106
|
-
for (const r of indirect) {
|
|
107
|
-
const sym = r.toSymbol ? ` (\`${r.toSymbol}\`)` : '';
|
|
108
|
-
lines.push(` → \`${r.toFile}\`${sym} *(via ${r.via}, depth ${r.depth})*`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return lines.join('\n');
|
|
112
|
-
}
|
|
113
|
-
function formatDependents(filePath, rows, transitive) {
|
|
114
|
-
const direct = rows.filter(r => !transitive || (r.depth ?? 1) === 1);
|
|
115
|
-
const indirect = transitive ? rows.filter(r => (r.depth ?? 1) > 1) : [];
|
|
116
|
-
const lines = [`**Dependents of** \`${filePath}\`\n`, '**Direct:**'];
|
|
117
|
-
for (const r of direct) {
|
|
118
|
-
const sym = r.toSymbol ? ` (imports \`${r.toSymbol}\`)` : '';
|
|
119
|
-
lines.push(` ← \`${r.fromFile}\`${sym}`);
|
|
120
|
-
}
|
|
121
|
-
if (indirect.length > 0) {
|
|
122
|
-
lines.push('\n**Transitive:**');
|
|
123
|
-
for (const r of indirect) {
|
|
124
|
-
lines.push(` ← \`${r.fromFile}\` *(via ${r.via}, depth ${r.depth})*`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return lines.join('\n');
|
|
128
|
-
}
|
|
129
|
-
function formatChunk(r, label) {
|
|
130
|
-
return `${label ? `*(${label})*\n` : ''}**File:** ${r.filePath}\n**Project:** ${r.serviceName}\n**Line:** ${r.startLine}\n\`\`\`\n${r.content}\n\`\`\``;
|
|
131
|
-
}
|
|
132
|
-
async function startMCP() {
|
|
133
|
-
const server = new index_js_1.Server({ name: 'local-rag', version: PKG_VERSION }, { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS });
|
|
134
|
-
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
135
|
-
tools: [
|
|
136
|
-
{
|
|
137
|
-
name: 'search_codebase',
|
|
138
|
-
description: 'Search local project codebase by semantic similarity',
|
|
139
|
-
inputSchema: {
|
|
140
|
-
type: 'object',
|
|
141
|
-
properties: {
|
|
142
|
-
query: { type: 'string' },
|
|
143
|
-
limit: { type: 'number', description: `Max results (default ${config_1.config.search.defaultLimit}, max ${config_1.config.search.maxLimit})` },
|
|
144
|
-
project: { type: 'string', description: 'Filter by project name (optional). Use list_projects to see available names.' },
|
|
145
|
-
include_neighbors: { type: 'boolean', description: 'Also include the chunk before and after each result from the same file (Small-to-Big retrieval). Useful when results look truncated.' },
|
|
146
|
-
},
|
|
147
|
-
required: ['query'],
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: 'list_projects',
|
|
152
|
-
description: 'List all indexed projects with their chunk counts',
|
|
153
|
-
inputSchema: { type: 'object', properties: {} },
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
name: 'read_file_range',
|
|
157
|
-
description: 'Read a specific line range from an indexed source file. Use this when a search result looks incomplete — read the surrounding lines to get full context.',
|
|
158
|
-
inputSchema: {
|
|
159
|
-
type: 'object',
|
|
160
|
-
properties: {
|
|
161
|
-
file_path: { type: 'string', description: 'File path as returned by search_codebase' },
|
|
162
|
-
start_line: { type: 'number', description: 'First line to read (1-based). Use the **Line** number from search_codebase results. Default: 1.' },
|
|
163
|
-
end_line: { type: 'number', description: 'Last line to read (1-based, inclusive). Default: start_line + 99.' },
|
|
164
|
-
},
|
|
165
|
-
required: ['file_path'],
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: 'get_dependencies',
|
|
170
|
-
description: 'Show what files a given file imports (direct or transitive). Use to understand what a file depends on before modifying it.',
|
|
171
|
-
inputSchema: {
|
|
172
|
-
type: 'object',
|
|
173
|
-
properties: {
|
|
174
|
-
file_path: { type: 'string', description: 'File path as returned by search_codebase' },
|
|
175
|
-
project: { type: 'string', description: 'Project name (use list_projects to find it)' },
|
|
176
|
-
symbol: { type: 'string', description: 'Filter to a specific imported symbol name (optional)' },
|
|
177
|
-
transitive: { type: 'boolean', description: 'Include transitive (indirect) dependencies. Default: false.' },
|
|
178
|
-
},
|
|
179
|
-
required: ['file_path', 'project'],
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
name: 'get_dependents',
|
|
184
|
-
description: 'Show which files import a given file (reverse graph). Use before refactoring to find all callers and avoid breaking changes.',
|
|
185
|
-
inputSchema: {
|
|
186
|
-
type: 'object',
|
|
187
|
-
properties: {
|
|
188
|
-
file_path: { type: 'string', description: 'File path as returned by search_codebase' },
|
|
189
|
-
project: { type: 'string', description: 'Project name (use list_projects to find it)' },
|
|
190
|
-
symbol: { type: 'string', description: 'Exported symbol name. Two behaviors: (1) if graph dependents exist, narrows them to imports of this symbol; (2) if the import graph returns zero results, triggers a whole-word text-search fallback (grep -F -w) across all .ts/.tsx/.js/.jsx files in the project to find runtime usages — DI containers, singletons, dynamic lookups, string references. Capped at 200 matches.' },
|
|
191
|
-
transitive: { type: 'boolean', description: 'Include transitive (indirect) dependents. Default: false.' },
|
|
192
|
-
},
|
|
193
|
-
required: ['file_path', 'project'],
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
name: 'find_symbol',
|
|
198
|
-
description: 'Find TypeScript/JavaScript symbol definitions (functions, classes, types, interfaces, variables) by exact name. Use when you know the symbol name and want its definition location fast.',
|
|
199
|
-
inputSchema: {
|
|
200
|
-
type: 'object',
|
|
201
|
-
properties: {
|
|
202
|
-
name: { type: 'string', description: 'Exact symbol name (case-sensitive, whole-word match)' },
|
|
203
|
-
project: { type: 'string', description: 'Limit search to this project (optional). Use list_projects to find names.' },
|
|
204
|
-
kind: { type: 'string', enum: ['function', 'class', 'type', 'interface', 'variable', 'all'], description: 'Symbol kind filter. Default: all.' },
|
|
205
|
-
},
|
|
206
|
-
required: ['name'],
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
name: 'list_files',
|
|
211
|
-
description: 'List files and directories in a project path as an indented tree. Use to understand project structure before reading files.',
|
|
212
|
-
inputSchema: {
|
|
213
|
-
type: 'object',
|
|
214
|
-
properties: {
|
|
215
|
-
path: { type: 'string', description: 'Directory path relative to the project root (e.g. "src/components" or "." for root)' },
|
|
216
|
-
project: { type: 'string', description: 'Project name (from list_projects)' },
|
|
217
|
-
depth: { type: 'number', description: 'Tree depth: 1 = immediate children only, up to 5. Default: 2.' },
|
|
218
|
-
pattern: { type: 'string', description: 'Optional filename filter (substring or extension like ".ts"). Only matching files are shown.' },
|
|
219
|
-
},
|
|
220
|
-
required: ['path', 'project'],
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
name: 'read_file',
|
|
225
|
-
description: 'Read a file from the workspace by absolute path (as returned by search_codebase). Returns contents with line numbers. Truncates at max_lines with a hint to continue with read_file_range.',
|
|
226
|
-
inputSchema: {
|
|
227
|
-
type: 'object',
|
|
228
|
-
properties: {
|
|
229
|
-
file_path: { type: 'string', description: 'Absolute path to the file (as returned in filePath by search_codebase)' },
|
|
230
|
-
max_lines: { type: 'number', description: 'Maximum lines to return (default 300, max 1000)' },
|
|
231
|
-
},
|
|
232
|
-
required: ['file_path'],
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
}));
|
|
237
|
-
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
|
|
238
|
-
try {
|
|
239
|
-
if (req.params.name === 'list_projects') {
|
|
240
|
-
const rows = await (0, searcher_1.listProjects)();
|
|
241
|
-
if (rows.length === 0) {
|
|
242
|
-
return { content: [{ type: 'text', text: 'No indexed projects found.' }] };
|
|
243
|
-
}
|
|
244
|
-
return { content: [{ type: 'text', text: rows.map(r => {
|
|
245
|
-
const lines = [`${r.serviceName}: ${r.chunks} chunks`];
|
|
246
|
-
if (r.description)
|
|
247
|
-
lines.push(` description: ${r.description}`);
|
|
248
|
-
if (r.stack)
|
|
249
|
-
lines.push(` stack: ${r.stack}`);
|
|
250
|
-
if (r.notes)
|
|
251
|
-
lines.push(` notes: ${r.notes}`);
|
|
252
|
-
if (r.relations) {
|
|
253
|
-
for (const [key, val] of Object.entries(r.relations)) {
|
|
254
|
-
const list = Array.isArray(val) ? val.join(', ') : val;
|
|
255
|
-
lines.push(` ${key}: ${list}`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return lines.join('\n');
|
|
259
|
-
}).join('\n\n') }] };
|
|
260
|
-
}
|
|
261
|
-
if (req.params.name === 'search_codebase') {
|
|
262
|
-
const raw = String(req.params.arguments?.query);
|
|
263
|
-
const limit = Math.min(Math.max(1, Number(req.params.arguments?.limit) || config_1.config.search.defaultLimit), config_1.config.search.maxLimit);
|
|
264
|
-
const project = req.params.arguments?.project ? String(req.params.arguments.project) : null;
|
|
265
|
-
const includeNeighbors = Boolean(req.params.arguments?.include_neighbors);
|
|
266
|
-
const { query, translated } = await (0, searcher_1.prepareQuery)(raw);
|
|
267
|
-
const results = await (0, searcher_1.searchChunks)(query, limit, project);
|
|
268
|
-
if (results.length === 0) {
|
|
269
|
-
return { content: [{ type: 'text', text: 'No relevant results found.' }] };
|
|
270
|
-
}
|
|
271
|
-
const header = translated ? `> Query translated: "${raw}" → "${query}"\n\n` : '';
|
|
272
|
-
if (includeNeighbors) {
|
|
273
|
-
const withNeighbors = await (0, searcher_1.fetchNeighbors)(results);
|
|
274
|
-
const blocks = withNeighbors.map(({ result, prevChunk, nextChunk }) => {
|
|
275
|
-
const parts = [];
|
|
276
|
-
if (prevChunk)
|
|
277
|
-
parts.push(formatChunk(prevChunk, 'prev chunk'));
|
|
278
|
-
parts.push(`${formatChunk(result)}\n**Relevance:** ${result.score}`);
|
|
279
|
-
if (nextChunk)
|
|
280
|
-
parts.push(formatChunk(nextChunk, 'next chunk'));
|
|
281
|
-
return parts.join('\n\n');
|
|
282
|
-
});
|
|
283
|
-
return { content: [{ type: 'text', text: header + blocks.join('\n\n---\n\n') }] };
|
|
284
|
-
}
|
|
285
|
-
const text = results
|
|
286
|
-
.map(r => `${formatChunk(r)}\n**Relevance:** ${r.score}`)
|
|
287
|
-
.join('\n\n---\n\n');
|
|
288
|
-
return { content: [{ type: 'text', text: header + text }] };
|
|
289
|
-
}
|
|
290
|
-
if (req.params.name === 'read_file_range') {
|
|
291
|
-
const filePath = String(req.params.arguments?.file_path);
|
|
292
|
-
const startLine = Math.max(1, Number(req.params.arguments?.start_line) || 1);
|
|
293
|
-
const endLine = Number(req.params.arguments?.end_line) || startLine + 99;
|
|
294
|
-
const range = await (0, searcher_1.readFileRange)(filePath, startLine, endLine);
|
|
295
|
-
if (!range.ok) {
|
|
296
|
-
const message = range.reason === 'invalid_path' ? `Invalid file path (outside workspace): ${filePath}` :
|
|
297
|
-
range.reason === 'not_indexed' ? `File is not indexed: ${filePath}. Run indexing first.` :
|
|
298
|
-
`File was deleted from disk after indexing: ${filePath}. Re-run indexing to update.`;
|
|
299
|
-
return { content: [{ type: 'text', text: message }] };
|
|
300
|
-
}
|
|
301
|
-
const numbered = range.lines.map((line, i) => `${startLine + i}: ${line}`).join('\n');
|
|
302
|
-
let text = `**File:** ${range.filePath}\n**Lines:** ${range.startLine}–${range.endLine}\n\`\`\`\n${numbered}\n\`\`\``;
|
|
303
|
-
if (range.hasMore) {
|
|
304
|
-
text += `\n\n[file has more lines — call read_file_range again with start_line: ${range.endLine + 1}]`;
|
|
305
|
-
}
|
|
306
|
-
return { content: [{ type: 'text', text }] };
|
|
307
|
-
}
|
|
308
|
-
if (req.params.name === 'get_dependencies') {
|
|
309
|
-
const filePath = String(req.params.arguments?.file_path);
|
|
310
|
-
const project = String(req.params.arguments?.project);
|
|
311
|
-
const symbol = req.params.arguments?.symbol ? String(req.params.arguments.symbol) : null;
|
|
312
|
-
const transitive = Boolean(req.params.arguments?.transitive);
|
|
313
|
-
const rows = await (0, searcher_1.getDependencies)(filePath, project, symbol, transitive);
|
|
314
|
-
if (rows.length === 0) {
|
|
315
|
-
return { content: [{ type: 'text', text: `No dependencies found for: ${filePath}` }] };
|
|
316
|
-
}
|
|
317
|
-
return { content: [{ type: 'text', text: formatDependencies(filePath, rows, transitive) }] };
|
|
318
|
-
}
|
|
319
|
-
if (req.params.name === 'get_dependents') {
|
|
320
|
-
const filePath = String(req.params.arguments?.file_path);
|
|
321
|
-
const project = String(req.params.arguments?.project);
|
|
322
|
-
const symbol = req.params.arguments?.symbol ? String(req.params.arguments.symbol) : null;
|
|
323
|
-
const transitive = Boolean(req.params.arguments?.transitive);
|
|
324
|
-
const rows = await (0, searcher_1.getDependents)(filePath, project, symbol, transitive);
|
|
325
|
-
if (rows.length > 0) {
|
|
326
|
-
return { content: [{ type: 'text', text: formatDependents(filePath, rows, transitive) }] };
|
|
327
|
-
}
|
|
328
|
-
const header = `**Dependents of** \`${filePath}\`\n\n**Direct:** No direct import dependents found.`;
|
|
329
|
-
if (!symbol) {
|
|
330
|
-
return { content: [{ type: 'text', text: `${header}\n\nTip: pass \`symbol\` to trigger a text-search fallback for runtime usages (e.g. via singleton or container).` }] };
|
|
331
|
-
}
|
|
332
|
-
const { matches, truncated } = (0, searcher_1.grepSymbol)(symbol, project, filePath);
|
|
333
|
-
if (matches.length === 0) {
|
|
334
|
-
return { content: [{ type: 'text', text: `${header}\n\n**Fallback \`[text search]\` for \`${symbol}\`:** No results.` }] };
|
|
335
|
-
}
|
|
336
|
-
const grouped = new Map();
|
|
337
|
-
for (const m of matches) {
|
|
338
|
-
if (!grouped.has(m.filePath))
|
|
339
|
-
grouped.set(m.filePath, []);
|
|
340
|
-
grouped.get(m.filePath).push(m.line);
|
|
341
|
-
}
|
|
342
|
-
const fallbackLines = [`\n**Fallback \`[text search]\` for \`${symbol}\`:**${truncated ? ' *(truncated to 200 results)*' : ''}`];
|
|
343
|
-
for (const [fp, lines] of grouped) {
|
|
344
|
-
fallbackLines.push(` ~ \`${fp}\`:${lines.join(', ')}`);
|
|
345
|
-
}
|
|
346
|
-
return { content: [{ type: 'text', text: header + fallbackLines.join('\n') }] };
|
|
347
|
-
}
|
|
348
|
-
if (req.params.name === 'find_symbol') {
|
|
349
|
-
const name = String(req.params.arguments?.name ?? '');
|
|
350
|
-
const project = req.params.arguments?.project ? String(req.params.arguments.project) : null;
|
|
351
|
-
const rawKind = req.params.arguments?.kind;
|
|
352
|
-
const validKinds = ['function', 'class', 'type', 'interface', 'variable', 'all'];
|
|
353
|
-
const kind = validKinds.includes(rawKind) ? rawKind : 'all';
|
|
354
|
-
const { hits, truncated } = (0, searcher_1.findSymbol)(name, project, kind);
|
|
355
|
-
if (hits.length === 0) {
|
|
356
|
-
return { content: [{ type: 'text', text: `No definitions found for symbol: ${name}` }] };
|
|
357
|
-
}
|
|
358
|
-
const lines = [];
|
|
359
|
-
if (truncated)
|
|
360
|
-
lines.push(`*(truncated to ${hits.length} results)*\n`);
|
|
361
|
-
for (const h of hits) {
|
|
362
|
-
lines.push(`**${h.kind}** \`${h.filePath}\` line ${h.line}, col ${h.column}`);
|
|
363
|
-
lines.push(`\`\`\`\n${h.signature}\n\`\`\``);
|
|
364
|
-
}
|
|
365
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
366
|
-
}
|
|
367
|
-
if (req.params.name === 'list_files') {
|
|
368
|
-
const relPath = String(req.params.arguments?.path ?? '.');
|
|
369
|
-
const project = String(req.params.arguments?.project ?? '');
|
|
370
|
-
const rawDepth = Number(req.params.arguments?.depth);
|
|
371
|
-
const depth = isNaN(rawDepth) || rawDepth < 1 ? 2 : Math.min(5, rawDepth);
|
|
372
|
-
const pattern = req.params.arguments?.pattern ? String(req.params.arguments.pattern) : null;
|
|
373
|
-
const { tree, error } = (0, searcher_1.listFiles)(relPath, project, depth, pattern);
|
|
374
|
-
if (error) {
|
|
375
|
-
return { content: [{ type: 'text', text: `list_files error: ${error}` }] };
|
|
376
|
-
}
|
|
377
|
-
return { content: [{ type: 'text', text: `**${project}/${relPath}**\n\`\`\`\n${tree}\n\`\`\`` }] };
|
|
378
|
-
}
|
|
379
|
-
if (req.params.name === 'read_file') {
|
|
380
|
-
const filePath = String(req.params.arguments?.file_path ?? '');
|
|
381
|
-
const rawMax = Number(req.params.arguments?.max_lines);
|
|
382
|
-
const maxLines = isNaN(rawMax) || rawMax < 1 ? 300 : Math.min(1000, rawMax);
|
|
383
|
-
const result = await (0, searcher_1.readFileAbsolute)(filePath, maxLines);
|
|
384
|
-
if (!result.ok) {
|
|
385
|
-
const message = result.reason === 'invalid_path' ? `Invalid path (outside workspace): ${filePath}` :
|
|
386
|
-
`File not found on disk: ${filePath}`;
|
|
387
|
-
return { content: [{ type: 'text', text: message }] };
|
|
388
|
-
}
|
|
389
|
-
const numbered = result.lines.map((line, i) => `${i + 1}: ${line}`).join('\n');
|
|
390
|
-
let text = `**File:** ${result.filePath}\n**Lines:** 1–${result.endLine}\n\`\`\`\n${numbered}\n\`\`\``;
|
|
391
|
-
if (result.hasMore) {
|
|
392
|
-
text += `\n\n[truncated: file has more lines — use read_file_range with file_path: "${result.filePath}", start_line: ${result.endLine + 1} to continue]`;
|
|
393
|
-
}
|
|
394
|
-
return { content: [{ type: 'text', text }] };
|
|
395
|
-
}
|
|
396
|
-
throw new Error(`Unknown tool: ${req.params.name}`);
|
|
397
|
-
}
|
|
398
|
-
catch (err) {
|
|
399
|
-
return { content: [{ type: 'text', text: `Tool error: ${err.message}` }] };
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
await server.connect(new stdio_js_1.StdioServerTransport());
|
|
403
|
-
}
|
package/dist/src/ollama.d.ts
DELETED
package/dist/src/ollama.js
DELETED