codesearch-mcp 0.0.13-alpha → 0.0.15-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 +13 -0
- package/README.md +6 -6
- package/dist/app.js +3 -2
- package/dist/src/commands/McpCommand.js +16 -3
- package/dist/src/commands/SetupCommand.d.ts +3 -0
- package/dist/src/commands/SetupCommand.js +5 -1
- package/dist/src/container.d.ts +1 -1
- package/dist/src/container.js +2 -2
- package/dist/src/project-selector.js +32 -20
- package/dist/src/services/AstParserService.js +40 -28
- package/dist/src/services/DatabaseService.js +4 -2
- package/dist/src/services/LoggerService.d.ts +1 -1
- package/dist/src/services/LoggerService.js +11 -5
- package/dist/src/services/McpServer.d.ts +1 -1
- package/dist/src/services/McpServer.js +62 -25
- package/dist/src/setup.d.ts +2 -1
- package/dist/src/setup.js +103 -43
- package/docker/docker-compose.yml +1 -1
- package/package.json +4 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
5
|
+
|
|
6
|
+
## [0.0.15-alpha] - 2026-05-02
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- **MCP server instructions restructured** — `Step 0` (query classification table) moved before the `Tools` list so it arrives before the truncation threshold and is always visible to the model
|
|
11
|
+
- **Lookup workflow fixed** — corrected order to `find_symbol` first (faster, exact match), `search_codebase` as fallback
|
|
12
|
+
- **Path distinction clarified** — `read_file_range` (relative path) and `read_file` (absolute path) now labelled directly in the Tools list, reducing wrong-path errors
|
|
13
|
+
- **Threshold contradiction resolved** — removed conflicting `0.35` example from Lookup/Conceptual workflow; canonical threshold guidance kept only in Relevance scores section
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ rag-setup
|
|
|
34
34
|
Walks you through:
|
|
35
35
|
- **Local Docker** — copies `docker-compose.yml`, starts PostgreSQL + Ollama, pulls `bge-m3` automatically
|
|
36
36
|
- **Remote DB** — enter connection details for Supabase, Railway, or self-hosted; connection is verified before saving
|
|
37
|
-
- **Logging** — choose log level and
|
|
37
|
+
- **Logging** — choose log level and directory (defaults: `error` level, `~/.config/codesearch-mcp/logs/`)
|
|
38
38
|
|
|
39
39
|
Config is saved to `~/.config/codesearch-mcp/.env`.
|
|
40
40
|
|
|
@@ -102,18 +102,18 @@ src/generated/
|
|
|
102
102
|
|
|
103
103
|
## Logging
|
|
104
104
|
|
|
105
|
-
Logs are written to `~/.config/codesearch-mcp/
|
|
105
|
+
Logs are written to `~/.config/codesearch-mcp/logs/` by default (separate subdirectories for indexer and MCP). Only errors are logged unless configured otherwise.
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
# Read logs
|
|
109
|
-
tail -f ~/.config/codesearch-mcp/
|
|
110
|
-
npx pino-pretty < ~/.config/codesearch-mcp/
|
|
109
|
+
tail -f ~/.config/codesearch-mcp/logs/indexer/current.log
|
|
110
|
+
npx pino-pretty < ~/.config/codesearch-mcp/logs/indexer/current.log
|
|
111
111
|
|
|
112
112
|
# Detailed indexing diagnostics (timing per file: deps / embed / db)
|
|
113
113
|
LOG_LEVEL=debug rag-index /path/to/workspace
|
|
114
114
|
|
|
115
115
|
# Disable file logging
|
|
116
|
-
|
|
116
|
+
LOG_DIR=false rag-index /path/to/workspace
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
## Environment variables
|
|
@@ -134,7 +134,7 @@ All set by `rag-setup`. Override any of them in `~/.config/codesearch-mcp/.env`.
|
|
|
134
134
|
| `AUTO_TRANSLATE` | `false` | Translate non-English queries to English before embedding |
|
|
135
135
|
| `TRANSLATE_MODEL` | `llama3.2` | Ollama model for translation (requires `ollama pull llama3.2`) |
|
|
136
136
|
| `LOG_LEVEL` | `error` | Log level written to file: `error` \| `warn` \| `info` \| `debug` |
|
|
137
|
-
| `
|
|
137
|
+
| `LOG_DIR` | `~/.config/codesearch-mcp/logs` | Base log directory, or `false` to disable |
|
|
138
138
|
|
|
139
139
|
## License
|
|
140
140
|
|
package/dist/app.js
CHANGED
|
@@ -53,7 +53,8 @@ async function main() {
|
|
|
53
53
|
(0, help_1.printHelp)(argv1 !== 'app.js' ? argv1 : mode);
|
|
54
54
|
process.exit(0);
|
|
55
55
|
}
|
|
56
|
-
const
|
|
56
|
+
const loggerName = mode === 'index' || mode === 'backfill' ? 'indexer' : mode === 'mcp' ? 'mcp' : mode === 'setup' ? 'setup' : undefined;
|
|
57
|
+
const container = (0, container_1.createContainer)(loggerName);
|
|
57
58
|
if (!container.config.workspaceRoot && mode !== 'setup') {
|
|
58
59
|
const hint = argv1 === 'rag-mcp'
|
|
59
60
|
? 'Set WORKSPACE_ROOT in ~/.config/codesearch-mcp/.env or run `rag-setup`.'
|
|
@@ -64,7 +65,7 @@ async function main() {
|
|
|
64
65
|
const commands = {
|
|
65
66
|
index: new IndexCommand_1.IndexCommand(container, argv1),
|
|
66
67
|
mcp: new McpCommand_1.McpCommand(container),
|
|
67
|
-
setup: new SetupCommand_1.SetupCommand(),
|
|
68
|
+
setup: new SetupCommand_1.SetupCommand(container),
|
|
68
69
|
backfill: new BackfillCommand_1.BackfillCommand(container),
|
|
69
70
|
};
|
|
70
71
|
const cmd = commands[mode];
|
|
@@ -7,9 +7,22 @@ class McpCommand {
|
|
|
7
7
|
this.container = container;
|
|
8
8
|
}
|
|
9
9
|
async execute() {
|
|
10
|
-
const { db, mcp } = this.container;
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const { db, mcp, logger } = this.container;
|
|
11
|
+
let startupError;
|
|
12
|
+
try {
|
|
13
|
+
await db.initDb();
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
startupError = err;
|
|
17
|
+
logger.error('McpCommand: database connection failed', { reason: err.message });
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
await mcp.start(startupError);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
logger.error('McpCommand: MCP server failed to start', { reason: err.message });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
13
26
|
// server.connect() resolves immediately after setup; keep the process alive
|
|
14
27
|
// until the MCP client disconnects (stdin closes)
|
|
15
28
|
await new Promise(resolve => {
|
|
@@ -3,8 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SetupCommand = void 0;
|
|
4
4
|
const setup_1 = require("../setup");
|
|
5
5
|
class SetupCommand {
|
|
6
|
+
container;
|
|
7
|
+
constructor(container) {
|
|
8
|
+
this.container = container;
|
|
9
|
+
}
|
|
6
10
|
async execute() {
|
|
7
|
-
await (0, setup_1.runSetup)();
|
|
11
|
+
await (0, setup_1.runSetup)(this.container.logger);
|
|
8
12
|
}
|
|
9
13
|
}
|
|
10
14
|
exports.SetupCommand = SetupCommand;
|
package/dist/src/container.d.ts
CHANGED
package/dist/src/container.js
CHANGED
|
@@ -9,8 +9,8 @@ const IndexerService_1 = require("./services/IndexerService");
|
|
|
9
9
|
const SearcherService_1 = require("./services/SearcherService");
|
|
10
10
|
const McpServer_1 = require("./services/McpServer");
|
|
11
11
|
const LoggerService_1 = require("./services/LoggerService");
|
|
12
|
-
function createContainer() {
|
|
13
|
-
const logger = LoggerService_1.LoggerService.fromEnv();
|
|
12
|
+
function createContainer(loggerName) {
|
|
13
|
+
const logger = LoggerService_1.LoggerService.fromEnv(loggerName);
|
|
14
14
|
const db = new DatabaseService_1.DatabaseService(config_1.config);
|
|
15
15
|
const embedder = new OllamaEmbedder_1.OllamaEmbedder(config_1.config);
|
|
16
16
|
const ast = new AstParserService_1.AstParserService(logger);
|
|
@@ -37,37 +37,49 @@ exports.selectProjects = selectProjects;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const prompts_1 = require("@inquirer/prompts");
|
|
40
|
-
function loadLines(filePath) {
|
|
41
|
-
|
|
40
|
+
async function loadLines(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
43
|
+
return new Set(content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#')));
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err.code !== 'ENOENT')
|
|
47
|
+
throw err;
|
|
42
48
|
return new Set();
|
|
43
|
-
|
|
44
|
-
.split('\n')
|
|
45
|
-
.map(l => l.trim())
|
|
46
|
-
.filter(l => l && !l.startsWith('#')));
|
|
49
|
+
}
|
|
47
50
|
}
|
|
48
51
|
// Write the set if non-empty, otherwise delete the file so state is deterministic.
|
|
49
52
|
// Previously .ragskip could be left stale (last entry removed but file not rewritten);
|
|
50
53
|
// treating both files the same way avoids that drift.
|
|
51
|
-
function saveLines(filePath, names) {
|
|
54
|
+
async function saveLines(filePath, names) {
|
|
52
55
|
if (names.size > 0) {
|
|
53
|
-
fs.
|
|
56
|
+
await fs.promises.writeFile(filePath, [...names].sort().join('\n') + '\n');
|
|
54
57
|
}
|
|
55
|
-
else
|
|
56
|
-
|
|
58
|
+
else {
|
|
59
|
+
try {
|
|
60
|
+
await fs.promises.unlink(filePath);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err.code !== 'ENOENT')
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
|
-
function loadConfig(workspaceDir) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
async function loadConfig(workspaceDir) {
|
|
69
|
+
const [included, skipped] = await Promise.all([
|
|
70
|
+
loadLines(path.join(workspaceDir, '.ragprojects')),
|
|
71
|
+
loadLines(path.join(workspaceDir, '.ragskip')),
|
|
72
|
+
]);
|
|
73
|
+
return { included, skipped };
|
|
64
74
|
}
|
|
65
|
-
function saveConfig(workspaceDir, config) {
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
async function saveConfig(workspaceDir, config) {
|
|
76
|
+
await Promise.all([
|
|
77
|
+
saveLines(path.join(workspaceDir, '.ragprojects'), config.included),
|
|
78
|
+
saveLines(path.join(workspaceDir, '.ragskip'), config.skipped),
|
|
79
|
+
]);
|
|
68
80
|
}
|
|
69
81
|
async function selectProjects(allProjects, workspaceDir, forceSelect) {
|
|
70
|
-
const config = loadConfig(workspaceDir);
|
|
82
|
+
const config = await loadConfig(workspaceDir);
|
|
71
83
|
const newProjects = allProjects.filter(p => !config.included.has(p.name) && !config.skipped.has(p.name));
|
|
72
84
|
// Nothing to ask — return previously saved selection silently
|
|
73
85
|
if (newProjects.length === 0 && !forceSelect) {
|
|
@@ -121,7 +133,7 @@ async function selectProjects(allProjects, workspaceDir, forceSelect) {
|
|
|
121
133
|
for (const name of toSkip)
|
|
122
134
|
config.skipped.add(name);
|
|
123
135
|
}
|
|
124
|
-
saveConfig(workspaceDir, config);
|
|
136
|
+
await saveConfig(workspaceDir, config);
|
|
125
137
|
const selected = allProjects.filter(p => config.included.has(p.name));
|
|
126
138
|
console.log(`\nSelected: ${selected.map(p => p.name).join(', ') || '(none)'}`);
|
|
127
139
|
return selected;
|
|
@@ -43,6 +43,7 @@ const FALLBACK_OPTS = {
|
|
|
43
43
|
skipFileDependencyResolution: false,
|
|
44
44
|
compilerOptions: { allowJs: true, noEmit: true },
|
|
45
45
|
};
|
|
46
|
+
const MORPH_CACHE_MAX = 10;
|
|
46
47
|
class AstParserService {
|
|
47
48
|
logger;
|
|
48
49
|
morphProjects = new Map();
|
|
@@ -75,38 +76,49 @@ class AstParserService {
|
|
|
75
76
|
const { edges } = await this.analyzeFile(filePath, serviceName, projectRoot);
|
|
76
77
|
return edges;
|
|
77
78
|
}
|
|
78
|
-
// ── private: MorphProject cache
|
|
79
|
+
// ── private: MorphProject cache (LRU, max MORPH_CACHE_MAX entries) ──────────
|
|
79
80
|
getMorphProject(projectRoot) {
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
this.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
if (this.morphProjects.has(projectRoot)) {
|
|
82
|
+
// Move to end = most recently used
|
|
83
|
+
const cached = this.morphProjects.get(projectRoot);
|
|
84
|
+
this.morphProjects.delete(projectRoot);
|
|
85
|
+
this.morphProjects.set(projectRoot, cached);
|
|
86
|
+
return cached;
|
|
87
|
+
}
|
|
88
|
+
if (this.morphProjects.size >= MORPH_CACHE_MAX) {
|
|
89
|
+
// Evict least recently used (first entry in insertion-order Map)
|
|
90
|
+
const lruKey = this.morphProjects.keys().next().value;
|
|
91
|
+
this.morphProjects.delete(lruKey);
|
|
92
|
+
this.logger.debug('AstParser: evicted LRU project', { lruKey });
|
|
93
|
+
}
|
|
94
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
95
|
+
const hasTsconfig = fs.existsSync(tsconfigPath);
|
|
96
|
+
this.logger.debug('AstParser: tsconfig lookup', { projectRoot, found: hasTsconfig });
|
|
97
|
+
let project;
|
|
98
|
+
try {
|
|
99
|
+
const opts = hasTsconfig
|
|
100
|
+
? { tsConfigFilePath: tsconfigPath, skipAddingFilesFromTsConfig: true, skipFileDependencyResolution: false }
|
|
101
|
+
: FALLBACK_OPTS;
|
|
102
|
+
project = new ts_morph_1.Project(opts);
|
|
103
|
+
if (hasTsconfig) {
|
|
104
|
+
const co = project.getCompilerOptions();
|
|
105
|
+
const pathsMap = co.paths ?? {};
|
|
106
|
+
const pathCount = Object.keys(pathsMap).length;
|
|
107
|
+
this.logger.debug('AstParser: tsconfig loaded', { pathCount });
|
|
108
|
+
if (pathCount > 0) {
|
|
109
|
+
this.logger.debug('AstParser: path aliases', { aliases: Object.keys(pathsMap) });
|
|
98
110
|
}
|
|
99
111
|
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
this.logger.warn('AstParser: tsconfig load failed, using fallback', {
|
|
102
|
-
tsconfigPath,
|
|
103
|
-
reason: err.message,
|
|
104
|
-
});
|
|
105
|
-
project = new ts_morph_1.Project(FALLBACK_OPTS);
|
|
106
|
-
}
|
|
107
|
-
this.morphProjects.set(projectRoot, project);
|
|
108
112
|
}
|
|
109
|
-
|
|
113
|
+
catch (err) {
|
|
114
|
+
this.logger.warn('AstParser: tsconfig load failed, using fallback', {
|
|
115
|
+
tsconfigPath,
|
|
116
|
+
reason: err.message,
|
|
117
|
+
});
|
|
118
|
+
project = new ts_morph_1.Project(FALLBACK_OPTS);
|
|
119
|
+
}
|
|
120
|
+
this.morphProjects.set(projectRoot, project);
|
|
121
|
+
return project;
|
|
110
122
|
}
|
|
111
123
|
// ── private: edge extraction (imports + re-exports) ────────────────────────
|
|
112
124
|
extractEdges(sourceFile, filePath, serviceName) {
|
|
@@ -108,7 +108,8 @@ class DatabaseService {
|
|
|
108
108
|
await client.connect();
|
|
109
109
|
const { rows } = await client.query(`SELECT 1 FROM pg_database WHERE datname = $1`, [database]);
|
|
110
110
|
if (rows.length === 0) {
|
|
111
|
-
|
|
111
|
+
const safeName = database.replace(/"/g, '""');
|
|
112
|
+
await client.query(`CREATE DATABASE "${safeName}"`);
|
|
112
113
|
await client.end();
|
|
113
114
|
const extClient = new pg_1.Client({ host, port, user: username, password, database });
|
|
114
115
|
try {
|
|
@@ -126,7 +127,8 @@ class DatabaseService {
|
|
|
126
127
|
}
|
|
127
128
|
catch (err) {
|
|
128
129
|
await client.end().catch(() => { });
|
|
129
|
-
|
|
130
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
131
|
+
throw new Error(`Failed to ensure database "${database}" exists: ${msg}`);
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
}
|
|
@@ -4,7 +4,7 @@ export declare class LoggerService {
|
|
|
4
4
|
private readonly logger;
|
|
5
5
|
constructor(logger: pino.Logger);
|
|
6
6
|
static silent(): LoggerService;
|
|
7
|
-
static fromEnv(): LoggerService;
|
|
7
|
+
static fromEnv(name?: string): LoggerService;
|
|
8
8
|
debug(msg: string, data?: Record<string, unknown>): void;
|
|
9
9
|
info(msg: string, data?: Record<string, unknown>): void;
|
|
10
10
|
warn(msg: string, data?: Record<string, unknown>): void;
|
|
@@ -41,7 +41,7 @@ const pino_1 = __importDefault(require("pino"));
|
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
|
-
const
|
|
44
|
+
const DEFAULT_LOG_DIR = path.join(os.homedir(), '.config', 'codesearch-mcp', 'logs');
|
|
45
45
|
class LoggerService {
|
|
46
46
|
logger;
|
|
47
47
|
constructor(logger) {
|
|
@@ -50,11 +50,17 @@ class LoggerService {
|
|
|
50
50
|
static silent() {
|
|
51
51
|
return new LoggerService((0, pino_1.default)({ level: 'silent' }));
|
|
52
52
|
}
|
|
53
|
-
static fromEnv() {
|
|
53
|
+
static fromEnv(name) {
|
|
54
54
|
const level = (process.env.LOG_LEVEL ?? 'error');
|
|
55
|
-
const
|
|
56
|
-
const enabled =
|
|
57
|
-
|
|
55
|
+
const logDirEnv = process.env.LOG_DIR;
|
|
56
|
+
const enabled = logDirEnv !== 'false' && logDirEnv !== '0';
|
|
57
|
+
let filePath = null;
|
|
58
|
+
if (enabled) {
|
|
59
|
+
const logDir = logDirEnv || DEFAULT_LOG_DIR;
|
|
60
|
+
filePath = name
|
|
61
|
+
? path.join(logDir, name, `${name}.log`)
|
|
62
|
+
: path.join(logDir, 'rag.log');
|
|
63
|
+
}
|
|
58
64
|
if (filePath)
|
|
59
65
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
60
66
|
const transport = filePath
|
|
@@ -6,7 +6,7 @@ export declare class McpServer {
|
|
|
6
6
|
private config;
|
|
7
7
|
private logger;
|
|
8
8
|
constructor(searcher: SearcherService, config: Config, logger?: LoggerService);
|
|
9
|
-
start(): Promise<void>;
|
|
9
|
+
start(startupError?: Error): Promise<void>;
|
|
10
10
|
private formatChunk;
|
|
11
11
|
private formatDependencies;
|
|
12
12
|
private formatDependents;
|
|
@@ -48,48 +48,77 @@ const PKG_VERSION = (() => {
|
|
|
48
48
|
return '0.0.0';
|
|
49
49
|
}
|
|
50
50
|
})();
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
function buildInstructions(t) {
|
|
52
|
+
return `
|
|
53
|
+
You have access to a local code intelligence server. Use these tools to search codebases by meaning, navigate import graphs, look up symbol definitions, and read source files.
|
|
54
|
+
|
|
55
|
+
## Step 0 — Classify your query before starting
|
|
56
|
+
|
|
57
|
+
Pick one workflow based on what you need:
|
|
58
|
+
|
|
59
|
+
| Type | Signals | Workflow |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| **Lookup** | "where is X defined", "find Y" | find_symbol (if name known) → search_codebase (if not found) |
|
|
62
|
+
| **Conceptual** | "how does X work", "what is Y" | search_codebase → read_file_range |
|
|
63
|
+
| **Flow audit** | "how does X connect to Y", "audit this flow", "trace the path" | search_codebase → **get_dependencies + get_dependents** → Grep |
|
|
64
|
+
| **Impact** | "what would break if I change X", "what uses Z" | get_dependents → search_codebase for context |
|
|
53
65
|
|
|
54
66
|
## Tools
|
|
55
67
|
|
|
56
68
|
- **list_projects** — call this first to see what projects are indexed and their exact names.
|
|
57
69
|
- **search_codebase** — semantic search across indexed source files. Returns file path, line number, and a code chunk.
|
|
58
|
-
- **read_file_range** — read a specific line range from any indexed file.
|
|
70
|
+
- **read_file_range** — read a specific line range from any indexed file. Expects a **relative path** as returned by search_codebase.
|
|
59
71
|
- **get_dependencies** — show what files a given file imports (direct or transitive). Use to understand what a file depends on.
|
|
60
72
|
- **get_dependents** — show which files import a given file (reverse graph). Use before refactoring to find all callers.
|
|
61
73
|
- **find_symbol** — find where a symbol (function, class, type, interface, variable, method) is defined by exact name. Faster than semantic search when you know the name.
|
|
62
74
|
- **list_files** — browse the directory tree of a project path. Use to understand project structure before reading files.
|
|
63
|
-
- **read_file** — read a complete file by absolute path with line numbers. Truncates at max_lines with a hint to continue.
|
|
75
|
+
- **read_file** — read a complete file by **absolute path** with line numbers. Truncates at max_lines with a hint to continue.
|
|
64
76
|
|
|
65
77
|
## Recommended workflow
|
|
66
78
|
|
|
79
|
+
### Lookup / Conceptual
|
|
67
80
|
1. Use \`list_projects\` to discover available projects before filtering by \`project\`.
|
|
68
|
-
2. Use \`search_codebase\` to find relevant code by meaning.
|
|
69
|
-
3. If a result looks **truncated or incomplete
|
|
70
|
-
4. For exploratory searches where
|
|
71
|
-
5. Use \`find_symbol\` when you know the exact name
|
|
72
|
-
6. Use \`list_files\` to explore project structure
|
|
73
|
-
7. Use \`read_file\` to read a complete file when you have its absolute path from \`search_codebase\` results.
|
|
74
|
-
8. If \`search_codebase\` returns results that seem off-topic, pass a lower \`threshold\` (e.g. \`0.35\`) to filter out weak semantic matches.
|
|
81
|
+
2. Use \`search_codebase\` to find relevant code by meaning. If your query uses an ambiguous term (e.g. "subscription" could mean pub/sub or billing), pass a lower \`threshold\` (e.g. \`0.40–0.45\`) to filter out weak matches.
|
|
82
|
+
3. If a result looks **truncated or incomplete**, call \`read_file_range\` with the returned \`file_path\` and \`start_line\`.
|
|
83
|
+
4. For exploratory searches where results span chunk boundaries, pass \`include_neighbors: true\`.
|
|
84
|
+
5. Use \`find_symbol\` when you know the exact name — faster than semantic search. Use \`kind: "method"\` for class methods — grep matches lines where \`public\`, \`async\`, \`static\`, or \`override\` appears before the name.
|
|
85
|
+
6. Use \`list_files\` to explore project structure before diving into files. Use \`read_file\` to read a complete file by absolute path.
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
### Flow audit (mandatory steps — do not skip 3 and 4)
|
|
88
|
+
1. Use \`list_projects\` to confirm project names.
|
|
89
|
+
2. Use \`search_codebase\` to find the **central file** (entry point of the flow).
|
|
90
|
+
3. **\`get_dependencies(central_file)\`** — what does this file import? Catches configs, loaders, Redis clients, types.
|
|
91
|
+
4. **\`get_dependents(central_file)\`** — who calls this file? Catches resolvers, routes, socket handlers.
|
|
92
|
+
5. For each peripheral file found in steps 3–4: use \`read_file_range\` or \`find_symbol\` to inspect it.
|
|
93
|
+
6. Only after steps 3–4 draw architectural conclusions — \`search_codebase\` alone misses peripheral files because they are semantically thin (configs, types, loaders contain no domain vocabulary).
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
- **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.
|
|
95
|
+
> **Note:** If \`get_dependencies\` / \`get_dependents\` return empty, the file likely uses path aliases (e.g. \`@core/\`, \`@/\`, \`@db/\`). Fall back to \`Grep\` on the symbol name across the project.
|
|
80
96
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
### Impact analysis
|
|
98
|
+
1. \`get_dependents(target_file)\` — direct callers.
|
|
99
|
+
2. \`get_dependents(target_file, transitive: true)\` — full reverse graph.
|
|
100
|
+
3. \`search_codebase\` for context on unfamiliar dependents.
|
|
84
101
|
|
|
85
|
-
##
|
|
102
|
+
## When to use read_file_range vs include_neighbors
|
|
86
103
|
|
|
87
|
-
|
|
104
|
+
- **include_neighbors: true** — good when context is fragmented across chunks (e.g. a class definition split across chunks).
|
|
105
|
+
- **read_file_range** — better for a precise section, or when one result is cut off.
|
|
106
|
+
|
|
107
|
+
## Handling Code Structure
|
|
108
|
+
- **Class/Function limits**: Chunks often end abruptly. Use \`read_file_range\` to find the closing brace or method start.
|
|
109
|
+
- **Cross-file references**: If you see \`this.validator.validate(data)\`, use \`search_codebase\` to find \`validator\`.
|
|
88
110
|
|
|
89
111
|
## Dependency graph
|
|
90
112
|
|
|
91
113
|
\`get_dependencies\` and \`get_dependents\` require \`file_path\` (as returned by \`search_codebase\`) and \`project\` (from \`list_projects\`). Pass \`transitive: true\` for the full dependency chain. depType values: \`import\` | \`import-default\` | \`import-namespace\` | \`reexport\` | \`reexport-namespace\`. \`fromSymbol\` — the top-level class or function in the importing file that uses this import (null if ambiguous or not tracked).
|
|
114
|
+
|
|
115
|
+
If \`get_dependents\` returns 0 results, add \`symbol\` (the exported name you care about) — this triggers a grep fallback that finds runtime usages in DI containers, string references, and dynamic lookups that the import graph misses.
|
|
116
|
+
|
|
117
|
+
## Relevance scores
|
|
118
|
+
|
|
119
|
+
Lower score = better match (cosine distance). Results above the configured threshold are filtered out automatically. Treat scores above 0.50 as weak hints — verify with \`read_file_range\` or \`Grep\`. Pass \`threshold\` to \`search_codebase\` to override per-query: \`0.40–0.45\` for precise technical queries (known term, exact concept); \`${t}\` default (broad but reliable). Avoid \`0.35\` — too strict for DSL files (GraphQL, Prisma) and infrastructure code.
|
|
92
120
|
`.trim();
|
|
121
|
+
}
|
|
93
122
|
class McpServer {
|
|
94
123
|
searcher;
|
|
95
124
|
config;
|
|
@@ -99,8 +128,8 @@ class McpServer {
|
|
|
99
128
|
this.config = config;
|
|
100
129
|
this.logger = logger;
|
|
101
130
|
}
|
|
102
|
-
async start() {
|
|
103
|
-
const server = new index_js_1.Server({ name: 'local-rag', version: PKG_VERSION }, { capabilities: { tools: {} }, instructions:
|
|
131
|
+
async start(startupError) {
|
|
132
|
+
const server = new index_js_1.Server({ name: 'local-rag', version: PKG_VERSION }, { capabilities: { tools: {} }, instructions: buildInstructions(this.config.search.similarityThreshold) });
|
|
104
133
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
105
134
|
tools: [
|
|
106
135
|
{
|
|
@@ -113,14 +142,14 @@ class McpServer {
|
|
|
113
142
|
limit: { type: 'number', description: `Max results (default ${this.config.search.defaultLimit}, max ${this.config.search.maxLimit})` },
|
|
114
143
|
project: { type: 'string', description: 'Filter by project name (optional). Use list_projects to see available names.' },
|
|
115
144
|
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.' },
|
|
116
|
-
threshold: { type: 'number', description: `Max cosine distance (0–1). Lower = stricter. Default: ${this.config.search.similarityThreshold}. Use
|
|
145
|
+
threshold: { type: 'number', description: `Max cosine distance (0–1). Lower = stricter. Default: ${this.config.search.similarityThreshold}. Use 0.40–0.45 for precise technical queries, 0.55–0.60 for broad exploration.` },
|
|
117
146
|
},
|
|
118
147
|
required: ['query'],
|
|
119
148
|
},
|
|
120
149
|
},
|
|
121
150
|
{
|
|
122
151
|
name: 'list_projects',
|
|
123
|
-
description: 'List all indexed projects with their chunk counts',
|
|
152
|
+
description: 'List all indexed projects with their chunk counts and metadata (description, stack, notes, relations from .ragmeta.json).',
|
|
124
153
|
inputSchema: { type: 'object', properties: {} },
|
|
125
154
|
},
|
|
126
155
|
{
|
|
@@ -193,11 +222,11 @@ class McpServer {
|
|
|
193
222
|
},
|
|
194
223
|
{
|
|
195
224
|
name: 'read_file',
|
|
196
|
-
description: 'Read a file
|
|
225
|
+
description: 'Read a file by absolute path. Returns contents with line numbers. Truncates at max_lines with a hint to continue with read_file_range.',
|
|
197
226
|
inputSchema: {
|
|
198
227
|
type: 'object',
|
|
199
228
|
properties: {
|
|
200
|
-
file_path: { type: 'string', description: 'Absolute path to the file
|
|
229
|
+
file_path: { type: 'string', description: 'Absolute path to the file. Note: search_codebase returns relative paths — construct the absolute path from your workspace root.' },
|
|
201
230
|
max_lines: { type: 'number', description: 'Maximum lines to return (default 300, max 1000)' },
|
|
202
231
|
},
|
|
203
232
|
required: ['file_path'],
|
|
@@ -206,6 +235,14 @@ class McpServer {
|
|
|
206
235
|
],
|
|
207
236
|
}));
|
|
208
237
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
|
|
238
|
+
if (startupError) {
|
|
239
|
+
return {
|
|
240
|
+
content: [{
|
|
241
|
+
type: 'text',
|
|
242
|
+
text: `MCP server is unavailable: ${startupError.message}\n\nCheck the log file and fix the issue, then restart the MCP server.`,
|
|
243
|
+
}],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
209
246
|
try {
|
|
210
247
|
if (req.params.name === 'list_projects') {
|
|
211
248
|
const rows = await this.searcher.listProjects();
|
package/dist/src/setup.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { LoggerService } from './services/LoggerService';
|
|
2
|
+
export declare function runSetup(logger?: LoggerService): Promise<void>;
|
package/dist/src/setup.js
CHANGED
|
@@ -41,6 +41,7 @@ const os = __importStar(require("os"));
|
|
|
41
41
|
const child_process_1 = require("child_process");
|
|
42
42
|
const constants_1 = require("./constants");
|
|
43
43
|
const CONFIG_DIR = path.join(os.homedir(), '.config', 'codesearch-mcp');
|
|
44
|
+
const DEFAULT_LOG_DIR = path.join(CONFIG_DIR, 'logs');
|
|
44
45
|
const CONFIG_FILE = path.join(CONFIG_DIR, '.env');
|
|
45
46
|
const DOCKER_COMPOSE_SRC = path.join(__dirname, '..', '..', 'docker', 'docker-compose.yml');
|
|
46
47
|
const DOCKER_COMPOSE_DST = path.join(CONFIG_DIR, 'docker-compose.yml');
|
|
@@ -64,7 +65,7 @@ function writeEnv(vars) {
|
|
|
64
65
|
fs.writeFileSync(CONFIG_FILE, content, { mode: 0o600 });
|
|
65
66
|
console.log(`\nConfig saved to ${CONFIG_FILE}`);
|
|
66
67
|
}
|
|
67
|
-
async function waitForDb(host, port, user, pass, db, maxWaitSec = 60) {
|
|
68
|
+
async function waitForDb(host, port, user, pass, db, maxWaitSec = 60, logger, steps) {
|
|
68
69
|
const { Client } = await Promise.resolve().then(() => __importStar(require('pg')));
|
|
69
70
|
const deadline = Date.now() + maxWaitSec * 1000;
|
|
70
71
|
process.stdout.write('Waiting for PostgreSQL to be ready');
|
|
@@ -81,10 +82,12 @@ async function waitForDb(host, port, user, pass, db, maxWaitSec = 60) {
|
|
|
81
82
|
await new Promise(r => setTimeout(r, 2000));
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
+
const reason = 'PostgreSQL did not become ready in time.';
|
|
86
|
+
console.error(`\n${reason}`);
|
|
87
|
+
logger?.error('waitForDb: timeout', { reason, steps });
|
|
85
88
|
process.exit(1);
|
|
86
89
|
}
|
|
87
|
-
async function waitForOllama(host, maxWaitSec = 60) {
|
|
90
|
+
async function waitForOllama(host, maxWaitSec = 60, logger, steps) {
|
|
88
91
|
const deadline = Date.now() + maxWaitSec * 1000;
|
|
89
92
|
process.stdout.write('Waiting for Ollama to be ready');
|
|
90
93
|
while (Date.now() < deadline) {
|
|
@@ -106,10 +109,12 @@ async function waitForOllama(host, maxWaitSec = 60) {
|
|
|
106
109
|
await new Promise(r => setTimeout(r, 2000));
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
|
-
|
|
112
|
+
const reason = 'Ollama did not become ready in time.';
|
|
113
|
+
console.error(`\n${reason}`);
|
|
114
|
+
logger?.error('waitForOllama: timeout', { reason, steps });
|
|
110
115
|
process.exit(1);
|
|
111
116
|
}
|
|
112
|
-
async function checkRemoteDb(host, port, user, pass, db) {
|
|
117
|
+
async function checkRemoteDb(host, port, user, pass, db, logger, steps) {
|
|
113
118
|
try {
|
|
114
119
|
const { Client } = await Promise.resolve().then(() => __importStar(require('pg')));
|
|
115
120
|
const client = new Client({ host, port: parseInt(port), user, password: pass, database: db, connectionTimeoutMillis: 5000 });
|
|
@@ -118,50 +123,62 @@ async function checkRemoteDb(host, port, user, pass, db) {
|
|
|
118
123
|
return true;
|
|
119
124
|
}
|
|
120
125
|
catch (err) {
|
|
121
|
-
|
|
126
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
127
|
+
console.error(` Connection failed: ${msg}`);
|
|
128
|
+
logger?.error('checkRemoteDb: connection failed', { reason: msg, steps });
|
|
122
129
|
return false;
|
|
123
130
|
}
|
|
124
131
|
}
|
|
125
|
-
async function setupLocalDocker(ollamaHost) {
|
|
126
|
-
console.log('\
|
|
132
|
+
async function setupLocalDocker(ollamaHost, dbPort, dbUser, dbPass, logger, steps) {
|
|
133
|
+
console.log('\nWriting docker-compose.yml to', DOCKER_COMPOSE_DST);
|
|
134
|
+
const yamlQuote = (s) => `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
135
|
+
let composeContent = fs.readFileSync(DOCKER_COMPOSE_SRC, 'utf-8');
|
|
136
|
+
composeContent = composeContent
|
|
137
|
+
.replace('POSTGRES_USER: dev', `POSTGRES_USER: ${yamlQuote(dbUser)}`)
|
|
138
|
+
.replace('POSTGRES_PASSWORD: dev', `POSTGRES_PASSWORD: ${yamlQuote(dbPass)}`)
|
|
139
|
+
.replace('"5433:5432"', `"${dbPort}:5432"`);
|
|
127
140
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
128
|
-
fs.
|
|
141
|
+
fs.writeFileSync(DOCKER_COMPOSE_DST, composeContent, 'utf-8');
|
|
129
142
|
console.log('\nStarting Docker containers...');
|
|
130
143
|
const upResult = (0, child_process_1.spawnSync)('docker', ['compose', '-f', DOCKER_COMPOSE_DST, 'up', '-d'], { stdio: 'inherit' });
|
|
131
144
|
if (upResult.status !== 0) {
|
|
132
|
-
|
|
145
|
+
const reason = 'Failed to start Docker containers. Make sure Docker is running.';
|
|
146
|
+
console.error(reason);
|
|
147
|
+
logger?.error('setupLocalDocker: docker startup failed', { reason, steps });
|
|
133
148
|
process.exit(1);
|
|
134
149
|
}
|
|
135
|
-
await waitForDb('localhost',
|
|
136
|
-
await waitForOllama(ollamaHost);
|
|
150
|
+
await waitForDb('localhost', parseInt(dbPort), dbUser, dbPass, 'postgres', 60, logger, steps);
|
|
151
|
+
await waitForOllama(ollamaHost, 60, logger, steps);
|
|
137
152
|
// Resolve the ollama container ID via compose — compose project name (and thus the
|
|
138
153
|
// default container name) depends on the parent directory, so hardcoding it breaks when
|
|
139
154
|
// users install via npm -g or move the compose file around.
|
|
140
155
|
const ollamaId = (0, child_process_1.spawnSync)('docker', ['compose', '-f', DOCKER_COMPOSE_DST, 'ps', '-q', 'ollama'], { encoding: 'utf-8' })
|
|
141
156
|
.stdout?.trim();
|
|
142
157
|
if (!ollamaId) {
|
|
143
|
-
|
|
158
|
+
const reason = 'Could not resolve ollama container ID from compose. Pull the model manually:';
|
|
159
|
+
console.error(reason);
|
|
144
160
|
console.error(` docker exec <ollama-container> ollama pull ${constants_1.DEFAULT_EMBED_MODEL}`);
|
|
161
|
+
logger?.error('setupLocalDocker: ollama container not found', { reason, steps });
|
|
145
162
|
process.exit(1);
|
|
146
163
|
}
|
|
147
164
|
console.log(`Pulling ${constants_1.DEFAULT_EMBED_MODEL} model (this may take a while)...`);
|
|
148
165
|
(0, child_process_1.spawnSync)('docker', ['exec', ollamaId, 'ollama', 'pull', constants_1.DEFAULT_EMBED_MODEL], { stdio: 'inherit' });
|
|
149
|
-
return {
|
|
150
|
-
DB_HOST: 'localhost',
|
|
151
|
-
DB_PORT: '5433',
|
|
152
|
-
DB_USER: 'dev',
|
|
153
|
-
DB_PASS: 'dev',
|
|
154
|
-
};
|
|
166
|
+
return { DB_HOST: 'localhost', DB_PORT: dbPort, DB_USER: dbUser, DB_PASS: dbPass };
|
|
155
167
|
}
|
|
156
|
-
async function setupRemoteDb(rl) {
|
|
168
|
+
async function setupRemoteDb(rl, logger, steps, logStep) {
|
|
169
|
+
const lp = async (label, def) => {
|
|
170
|
+
const answer = await prompt(rl, label, def);
|
|
171
|
+
logStep?.(label, answer);
|
|
172
|
+
return answer;
|
|
173
|
+
};
|
|
157
174
|
let vars = {};
|
|
158
175
|
while (true) {
|
|
159
|
-
vars.DB_HOST = await
|
|
160
|
-
vars.DB_PORT = await
|
|
161
|
-
vars.DB_USER = await
|
|
162
|
-
vars.DB_PASS = await
|
|
176
|
+
vars.DB_HOST = await lp('DB host', 'localhost');
|
|
177
|
+
vars.DB_PORT = await lp('DB port', '5432');
|
|
178
|
+
vars.DB_USER = await lp('DB user', 'postgres');
|
|
179
|
+
vars.DB_PASS = await lp('DB password', '');
|
|
163
180
|
console.log('\nTesting connection...');
|
|
164
|
-
const ok = await checkRemoteDb(vars.DB_HOST, vars.DB_PORT, vars.DB_USER, vars.DB_PASS, 'postgres');
|
|
181
|
+
const ok = await checkRemoteDb(vars.DB_HOST, vars.DB_PORT, vars.DB_USER, vars.DB_PASS, 'postgres', logger, steps);
|
|
165
182
|
if (ok) {
|
|
166
183
|
console.log('Connection successful.');
|
|
167
184
|
break;
|
|
@@ -172,18 +189,47 @@ async function setupRemoteDb(rl) {
|
|
|
172
189
|
}
|
|
173
190
|
return vars;
|
|
174
191
|
}
|
|
175
|
-
async function runSetup() {
|
|
192
|
+
async function runSetup(logger) {
|
|
176
193
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
177
|
-
|
|
178
|
-
|
|
194
|
+
const steps = [];
|
|
195
|
+
function logStep(q, raw) {
|
|
196
|
+
const a = /password/i.test(q) ? '***' : raw.trim();
|
|
197
|
+
logger?.debug('setup: prompt', { q, a });
|
|
198
|
+
steps.push({ q, a });
|
|
199
|
+
}
|
|
200
|
+
const loggedAsk = async (question) => {
|
|
201
|
+
const answer = await ask(rl, question);
|
|
202
|
+
const key = question.split('\n').find(l => l.trim())?.trim() ?? question.trim();
|
|
203
|
+
logStep(key, answer);
|
|
204
|
+
return answer;
|
|
205
|
+
};
|
|
206
|
+
const loggedPrompt = async (label, def) => {
|
|
207
|
+
const answer = await prompt(rl, label, def);
|
|
208
|
+
logStep(label, answer || def);
|
|
209
|
+
return answer;
|
|
210
|
+
};
|
|
211
|
+
console.log('=== codesearch-mcp setup ===');
|
|
212
|
+
console.log('Press Enter to keep the default value shown in parentheses.\n');
|
|
213
|
+
const dbChoice = await loggedAsk('Infrastructure:\n 1. Local Docker (auto-start PostgreSQL + Ollama)\n 2. Remote (Supabase, Railway, self-hosted DB + local Ollama)\n\nEnter choice (1): ');
|
|
179
214
|
const useDocker = (dbChoice.trim() || '1') === '1';
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
215
|
+
const workspaceExample = (() => {
|
|
216
|
+
const candidates = ['Projects', 'projects', 'workspace', 'code', 'Code', 'dev', 'Development'];
|
|
217
|
+
const found = candidates.find(d => fs.existsSync(path.join(os.homedir(), d)));
|
|
218
|
+
return path.join(os.homedir(), found ?? 'projects');
|
|
219
|
+
})();
|
|
220
|
+
console.log(`\nWorkspace root — full path to the directory that contains all your projects`);
|
|
221
|
+
console.log(` (e.g. ${workspaceExample})`);
|
|
222
|
+
let workspaceInput;
|
|
223
|
+
do {
|
|
224
|
+
workspaceInput = await loggedAsk('Path: ');
|
|
225
|
+
if (!workspaceInput.trim())
|
|
226
|
+
console.log(' Path is required.');
|
|
227
|
+
} while (!workspaceInput.trim());
|
|
228
|
+
const workspaceRoot = expandHome(workspaceInput.trim());
|
|
229
|
+
const ollamaHost = await loggedPrompt('Ollama API URL', 'http://localhost:11434');
|
|
184
230
|
const dbVars = useDocker
|
|
185
|
-
? await setupLocalDocker(ollamaHost)
|
|
186
|
-
: await setupRemoteDb(rl);
|
|
231
|
+
? await setupLocalDocker(ollamaHost, await loggedPrompt('DB port', '5433'), await loggedPrompt('DB user', 'dev'), await loggedPrompt('DB password', 'dev'), logger, steps)
|
|
232
|
+
: await setupRemoteDb(rl, logger, steps, logStep);
|
|
187
233
|
console.log('\n--- Logging ---');
|
|
188
234
|
console.log(' error — failures only (recommended)');
|
|
189
235
|
console.log(' warn — + recoverable issues');
|
|
@@ -191,20 +237,21 @@ async function runSetup() {
|
|
|
191
237
|
const validLevels = ['error', 'warn', 'info', 'debug'];
|
|
192
238
|
let logLevel;
|
|
193
239
|
do {
|
|
194
|
-
logLevel = await
|
|
240
|
+
logLevel = await loggedPrompt('Log level (error/warn/info/debug)', 'error');
|
|
195
241
|
if (!validLevels.includes(logLevel))
|
|
196
242
|
console.log(` Invalid level. Choose: ${validLevels.join(', ')}`);
|
|
197
243
|
} while (!validLevels.includes(logLevel));
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
writeEnv({
|
|
244
|
+
const logDirInput = await loggedAsk(`Log dir (leave blank for default: ${DEFAULT_LOG_DIR}, or "false" to disable): `);
|
|
245
|
+
const logDir = logDirInput.trim();
|
|
246
|
+
const envVars = {
|
|
202
247
|
...dbVars,
|
|
203
248
|
WORKSPACE_ROOT: workspaceRoot,
|
|
204
249
|
OLLAMA_HOST: ollamaHost,
|
|
205
250
|
LOG_LEVEL: logLevel,
|
|
206
|
-
|
|
207
|
-
|
|
251
|
+
};
|
|
252
|
+
if (logDir)
|
|
253
|
+
envVars['LOG_DIR'] = logDir;
|
|
254
|
+
writeEnv(envVars);
|
|
208
255
|
rl.close();
|
|
209
256
|
console.log('\nInitializing database schema...');
|
|
210
257
|
const dotenv = await Promise.resolve().then(() => __importStar(require('dotenv')));
|
|
@@ -212,8 +259,21 @@ async function runSetup() {
|
|
|
212
259
|
const { config } = await Promise.resolve().then(() => __importStar(require('./config')));
|
|
213
260
|
const { DatabaseService } = await Promise.resolve().then(() => __importStar(require('./services/DatabaseService')));
|
|
214
261
|
const dbService = new DatabaseService(config);
|
|
215
|
-
|
|
216
|
-
|
|
262
|
+
let initError = null;
|
|
263
|
+
try {
|
|
264
|
+
await dbService.initDbWithSchema();
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
initError = err instanceof Error ? err : new Error(String(err));
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
await dbService.destroy();
|
|
271
|
+
}
|
|
272
|
+
if (initError) {
|
|
273
|
+
console.error(`Database schema init failed: ${initError.message}`);
|
|
274
|
+
logger?.error('runSetup: database schema init failed', { reason: initError.message, steps });
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
217
277
|
console.log('\nSetup complete!');
|
|
218
278
|
console.log('\nNext steps:');
|
|
219
279
|
console.log(` rag-index ${workspaceRoot}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codesearch-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15-alpha",
|
|
4
4
|
"description": "Local semantic codebase search via MCP — indexes projects into pgvector, exposes search to Claude",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/app.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist/",
|
|
14
14
|
"docker/",
|
|
15
|
-
"README.md"
|
|
15
|
+
"README.md",
|
|
16
|
+
"CHANGELOG.md"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build": "tsc",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@inquirer/prompts": "^8.4.1",
|
|
32
|
-
"@langchain/textsplitters": "^0.1
|
|
33
|
+
"@langchain/textsplitters": "^1.0.1",
|
|
33
34
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
34
35
|
"cli-progress": "^3.12.0",
|
|
35
36
|
"dotenv": "^17.4.2",
|