codesearch-mcp 0.0.18-alpha → 0.1.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +75 -6
  3. package/dist/app.js +36 -0
  4. package/dist/src/commands/IndexCommand.js +14 -1
  5. package/dist/src/commands/McpCommand.js +6 -2
  6. package/dist/src/config-store.d.ts +49 -11
  7. package/dist/src/config-store.js +79 -19
  8. package/dist/src/config.d.ts +32 -8
  9. package/dist/src/config.js +84 -20
  10. package/dist/src/constants.d.ts +36 -2
  11. package/dist/src/constants.js +58 -7
  12. package/dist/src/container.js +5 -3
  13. package/dist/src/db/index.d.ts +1 -1
  14. package/dist/src/db/index.js +2 -1
  15. package/dist/src/db/schema.d.ts +111 -0
  16. package/dist/src/db/schema.js +15 -1
  17. package/dist/src/help.js +5 -4
  18. package/dist/src/interfaces/IAstParser.d.ts +14 -0
  19. package/dist/src/interfaces/IChunker.d.ts +24 -0
  20. package/dist/src/interfaces/IChunker.js +2 -0
  21. package/dist/src/interfaces/IDatabase.d.ts +14 -0
  22. package/dist/src/interfaces/IEmbedder.d.ts +14 -2
  23. package/dist/src/interfaces/ISearcher.d.ts +1 -0
  24. package/dist/src/interfaces/index.d.ts +2 -1
  25. package/dist/src/owner-path.d.ts +10 -0
  26. package/dist/src/owner-path.js +27 -0
  27. package/dist/src/services/AstChunker.d.ts +31 -0
  28. package/dist/src/services/AstChunker.js +114 -0
  29. package/dist/src/services/AstParserService.d.ts +6 -0
  30. package/dist/src/services/AstParserService.js +31 -2
  31. package/dist/src/services/DatabaseService.d.ts +13 -1
  32. package/dist/src/services/DatabaseService.js +70 -25
  33. package/dist/src/services/EmbedderFactory.d.ts +9 -0
  34. package/dist/src/services/EmbedderFactory.js +29 -0
  35. package/dist/src/services/IndexerService.d.ts +17 -5
  36. package/dist/src/services/IndexerService.js +101 -38
  37. package/dist/src/services/InfinityEmbedder.d.ts +79 -0
  38. package/dist/src/services/InfinityEmbedder.js +199 -0
  39. package/dist/src/services/OllamaEmbedder.d.ts +16 -5
  40. package/dist/src/services/OllamaEmbedder.js +84 -36
  41. package/dist/src/services/OpenAICompatibleEmbedder.d.ts +25 -0
  42. package/dist/src/services/OpenAICompatibleEmbedder.js +75 -0
  43. package/dist/src/services/SearcherService.d.ts +7 -1
  44. package/dist/src/services/SearcherService.js +35 -16
  45. package/dist/src/services/VoyageEmbedder.d.ts +22 -0
  46. package/dist/src/services/VoyageEmbedder.js +72 -0
  47. package/dist/src/services/WebServerService.d.ts +16 -11
  48. package/dist/src/services/WebServerService.js +44 -24
  49. package/dist/src/services/http-retry.d.ts +19 -0
  50. package/dist/src/services/http-retry.js +54 -0
  51. package/dist/src/services/reranker.d.ts +15 -0
  52. package/dist/src/services/reranker.js +44 -0
  53. package/dist/src/setup.d.ts +6 -0
  54. package/dist/src/setup.js +243 -25
  55. package/dist/src/tools/SearchCodebaseTool.js +1 -1
  56. package/dist/src/tools/format-helpers.js +2 -1
  57. package/dist/src/web/dashboard.js +5 -0
  58. package/docker/docker-compose.infinity-gpu.yml +19 -0
  59. package/docker/docker-compose.yml +25 -0
  60. package/package.json +21 -2
  61. package/dist/src/ast.d.ts +0 -2
  62. package/dist/src/ast.js +0 -110
  63. package/dist/src/commands/SetupCommand.d.ts +0 -7
  64. package/dist/src/commands/SetupCommand.js +0 -14
  65. package/dist/src/db/data-source.d.ts +0 -2
  66. package/dist/src/db/data-source.js +0 -13
  67. package/dist/src/db/entities/code-chunk.entity.d.ts +0 -9
  68. package/dist/src/db/entities/code-chunk.entity.js +0 -54
  69. package/dist/src/db/entities/code-dependency.entity.d.ts +0 -9
  70. package/dist/src/db/entities/code-dependency.entity.js +0 -54
  71. package/dist/src/db/entities/code-symbol.entity.d.ts +0 -10
  72. package/dist/src/db/entities/code-symbol.entity.js +0 -59
  73. package/dist/src/db/init.d.ts +0 -2
  74. package/dist/src/db/init.js +0 -62
  75. package/dist/src/env-loader.d.ts +0 -1
  76. package/dist/src/env-loader.js +0 -41
  77. package/dist/src/indexer.d.ts +0 -17
  78. package/dist/src/indexer.js +0 -242
  79. package/dist/src/mcp.d.ts +0 -1
  80. package/dist/src/mcp.js +0 -403
  81. package/dist/src/ollama.d.ts +0 -2
  82. package/dist/src/ollama.js +0 -6
  83. package/dist/src/searcher.d.ts +0 -74
  84. package/dist/src/searcher.js +0 -446
  85. package/dist/vitest.config.d.ts +0 -2
  86. package/dist/vitest.config.js +0 -9
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveOwnerPath = resolveOwnerPath;
4
+ /**
5
+ * Resolve the owning declaration for a chunk given its 1-based start line and a
6
+ * flat list of candidate ranges (top-level symbols + method ranges). The
7
+ * deepest range containing the start line wins (smallest `endLine - line` span),
8
+ * so a method beats its enclosing class. Returns `Container.name` for methods
9
+ * (via `container`) or `name` for top-level declarations, and `null` when the
10
+ * start line lies outside every range (imports / module-scope gaps).
11
+ */
12
+ function resolveOwnerPath(startLine, ranges) {
13
+ let best = null;
14
+ let bestSpan = Infinity;
15
+ for (const r of ranges) {
16
+ if (r.line <= startLine && startLine <= r.endLine) {
17
+ const span = r.endLine - r.line;
18
+ if (span < bestSpan) {
19
+ bestSpan = span;
20
+ best = r;
21
+ }
22
+ }
23
+ }
24
+ if (!best)
25
+ return null;
26
+ return best.container ? `${best.container}.${best.name}` : best.name;
27
+ }
@@ -0,0 +1,31 @@
1
+ import type { Config } from '../config';
2
+ import type { IChunker, ChunkPiece, ChunkOpts } from '../interfaces/IChunker';
3
+ /**
4
+ * Splits TS/JS source into embeddable pieces aligned to top-level declaration
5
+ * ranges (AST strategy). Falls back to the character-based recursive splitter
6
+ * for schema files, files without symbol analysis, or when strategy is
7
+ * 'character' — that path is byte-for-byte the previous behavior.
8
+ */
9
+ export declare class AstChunker implements IChunker {
10
+ private config;
11
+ private readonly jsSplitter;
12
+ private readonly schemaSplitter;
13
+ private readonly chunkSize;
14
+ constructor(config: Config);
15
+ chunk(code: string, opts: ChunkOpts): Promise<ChunkPiece[]>;
16
+ /** Character-splitter path for a whole file; line numbers from splitter metadata. */
17
+ private splitWholeFile;
18
+ private chunkByAst;
19
+ /**
20
+ * Build disjoint, sorted line intervals from symbol ranges. Overlapping /
21
+ * nested ranges merge into the outermost (a merged region covers any symbol
22
+ * inside it). Ranges are clamped to the file's line count.
23
+ */
24
+ private static buildIntervals;
25
+ /**
26
+ * Emit pieces for a 1-based inclusive line region. Whitespace-only gaps are
27
+ * dropped; regions longer than chunkSize are sub-split with the char splitter,
28
+ * with sub-piece line numbers offset back to absolute file lines.
29
+ */
30
+ private regionPieces;
31
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AstChunker = void 0;
4
+ const textsplitters_1 = require("@langchain/textsplitters");
5
+ /**
6
+ * Splits TS/JS source into embeddable pieces aligned to top-level declaration
7
+ * ranges (AST strategy). Falls back to the character-based recursive splitter
8
+ * for schema files, files without symbol analysis, or when strategy is
9
+ * 'character' — that path is byte-for-byte the previous behavior.
10
+ */
11
+ class AstChunker {
12
+ config;
13
+ jsSplitter;
14
+ schemaSplitter;
15
+ chunkSize;
16
+ constructor(config) {
17
+ this.config = config;
18
+ this.chunkSize = config.indexer.chunkSize;
19
+ this.jsSplitter = textsplitters_1.RecursiveCharacterTextSplitter.fromLanguage('js', {
20
+ chunkSize: config.indexer.chunkSize,
21
+ chunkOverlap: config.indexer.chunkOverlap,
22
+ });
23
+ this.schemaSplitter = new textsplitters_1.RecursiveCharacterTextSplitter({
24
+ chunkSize: config.indexer.chunkSize,
25
+ chunkOverlap: config.indexer.chunkOverlap,
26
+ separators: ['\n\n', '\n', ' '],
27
+ });
28
+ }
29
+ async chunk(code, opts) {
30
+ if (opts.isSchemaFile) {
31
+ return this.splitWholeFile(code, this.schemaSplitter);
32
+ }
33
+ if (opts.strategy === 'character' || opts.symbols.length === 0) {
34
+ return this.splitWholeFile(code, this.jsSplitter);
35
+ }
36
+ return this.chunkByAst(code, opts.symbols);
37
+ }
38
+ /** Character-splitter path for a whole file; line numbers from splitter metadata. */
39
+ async splitWholeFile(code, splitter) {
40
+ const docs = await splitter.createDocuments([code]);
41
+ return docs.map((d) => {
42
+ const from = (d.metadata?.loc?.lines?.from ?? 1);
43
+ const to = (d.metadata?.loc?.lines?.to ?? from);
44
+ return { content: d.pageContent, startLine: from, endLine: to };
45
+ });
46
+ }
47
+ async chunkByAst(code, symbols) {
48
+ const lines = code.split('\n');
49
+ const total = lines.length;
50
+ const intervals = AstChunker.buildIntervals(symbols, total);
51
+ const pieces = [];
52
+ let cursor = 1;
53
+ for (const iv of intervals) {
54
+ if (cursor < iv.start) {
55
+ pieces.push(...await this.regionPieces(lines, cursor, iv.start - 1, true));
56
+ }
57
+ pieces.push(...await this.regionPieces(lines, iv.start, iv.end, false));
58
+ cursor = iv.end + 1;
59
+ }
60
+ if (cursor <= total) {
61
+ pieces.push(...await this.regionPieces(lines, cursor, total, true));
62
+ }
63
+ return pieces;
64
+ }
65
+ /**
66
+ * Build disjoint, sorted line intervals from symbol ranges. Overlapping /
67
+ * nested ranges merge into the outermost (a merged region covers any symbol
68
+ * inside it). Ranges are clamped to the file's line count.
69
+ */
70
+ static buildIntervals(symbols, total) {
71
+ const raw = symbols
72
+ .filter(s => s.line != null && s.line >= 1 && s.line <= total)
73
+ .map(s => ({
74
+ start: s.line,
75
+ end: Math.min(Math.max(s.endLine ?? s.line, s.line), total),
76
+ }))
77
+ .sort((a, b) => a.start - b.start || b.end - a.end);
78
+ const merged = [];
79
+ for (const iv of raw) {
80
+ const last = merged[merged.length - 1];
81
+ if (last && iv.start <= last.end) {
82
+ last.end = Math.max(last.end, iv.end);
83
+ }
84
+ else {
85
+ merged.push({ ...iv });
86
+ }
87
+ }
88
+ return merged;
89
+ }
90
+ /**
91
+ * Emit pieces for a 1-based inclusive line region. Whitespace-only gaps are
92
+ * dropped; regions longer than chunkSize are sub-split with the char splitter,
93
+ * with sub-piece line numbers offset back to absolute file lines.
94
+ */
95
+ async regionPieces(lines, start, end, isGap) {
96
+ const text = lines.slice(start - 1, end).join('\n');
97
+ if (isGap && text.trim() === '')
98
+ return [];
99
+ if (text.length <= this.chunkSize) {
100
+ return [{ content: text, startLine: start, endLine: end }];
101
+ }
102
+ const docs = await this.jsSplitter.createDocuments([text]);
103
+ return docs.map((d) => {
104
+ const from = (d.metadata?.loc?.lines?.from ?? 1);
105
+ const to = (d.metadata?.loc?.lines?.to ?? from);
106
+ return {
107
+ content: d.pageContent,
108
+ startLine: start + from - 1,
109
+ endLine: start + to - 1,
110
+ };
111
+ });
112
+ }
113
+ }
114
+ exports.AstChunker = AstChunker;
@@ -14,6 +14,12 @@ export declare class AstParserService implements IAstParser {
14
14
  private resolveFromSymbol;
15
15
  private buildUsageMap;
16
16
  private extractSymbols;
17
+ /**
18
+ * Method / constructor / accessor line ranges with their enclosing class, for
19
+ * owner-path resolution only. Deliberately NOT fed into `code_symbol` (methods
20
+ * stay grep-based in find_symbol), so this is separate from extractSymbols.
21
+ */
22
+ private extractOwnerRanges;
17
23
  private extractCalls;
18
24
  private getContainerName;
19
25
  }
@@ -77,7 +77,8 @@ class AstParserService {
77
77
  const edges = this.extractEdges(sourceFile, filePath, serviceName);
78
78
  const symbols = this.extractSymbols(sourceFile, filePath, serviceName);
79
79
  const calls = this.extractCalls(sourceFile, filePath, serviceName);
80
- analysisMap.set(filePath, { edges, symbols, calls });
80
+ const ownerRanges = this.extractOwnerRanges(sourceFile);
81
+ analysisMap.set(filePath, { edges, symbols, calls, ownerRanges });
81
82
  }
82
83
  catch (err) {
83
84
  this.logger.warn('AstParser: failed to analyze file', { filePath, reason: err.message });
@@ -126,9 +127,10 @@ class AstParserService {
126
127
  const edges = this.extractEdges(sourceFile, filePath, serviceName);
127
128
  const symbols = this.extractSymbols(sourceFile, filePath, serviceName);
128
129
  const calls = this.extractCalls(sourceFile, filePath, serviceName);
130
+ const ownerRanges = this.extractOwnerRanges(sourceFile);
129
131
  morphProject.removeSourceFile(sourceFile);
130
132
  this.logger.debug('AstParser: analyzeFile done', { filePath, edges: edges.length, symbols: symbols.length, calls: calls.length, ms: Date.now() - t0 });
131
- return { edges, symbols, calls };
133
+ return { edges, symbols, calls, ownerRanges };
132
134
  }
133
135
  // Kept for test compatibility (extract-import-edges.test.ts).
134
136
  async extractImportEdges(filePath, serviceName, projectRoot) {
@@ -369,6 +371,33 @@ class AstParserService {
369
371
  }
370
372
  return symbols;
371
373
  }
374
+ /**
375
+ * Method / constructor / accessor line ranges with their enclosing class, for
376
+ * owner-path resolution only. Deliberately NOT fed into `code_symbol` (methods
377
+ * stay grep-based in find_symbol), so this is separate from extractSymbols.
378
+ */
379
+ extractOwnerRanges(sourceFile) {
380
+ const ranges = [];
381
+ for (const cls of sourceFile.getClasses()) {
382
+ const container = cls.getName();
383
+ if (!container)
384
+ continue;
385
+ const members = [
386
+ ...cls.getMethods().map(n => ({ name: n.getName(), node: n })),
387
+ ...cls.getGetAccessors().map(n => ({ name: n.getName(), node: n })),
388
+ ...cls.getSetAccessors().map(n => ({ name: n.getName(), node: n })),
389
+ ...cls.getConstructors().map(n => ({ name: 'constructor', node: n })),
390
+ ];
391
+ for (const { name, node } of members) {
392
+ ranges.push({
393
+ name, container,
394
+ line: node.getStartLineNumber(),
395
+ endLine: node.getEndLineNumber(),
396
+ });
397
+ }
398
+ }
399
+ return ranges;
400
+ }
372
401
  // ── private: call graph extraction ────────────────────────────────────────
373
402
  extractCalls(sourceFile, filePath, serviceName) {
374
403
  const calls = [];
@@ -1,7 +1,7 @@
1
1
  import type { NodePgDatabase, NodePgTransaction } from 'drizzle-orm/node-postgres';
2
2
  import type { ExtractTablesWithRelations } from 'drizzle-orm/relations';
3
3
  import type { Config } from '../config';
4
- import type { IDatabase } from '../interfaces/IDatabase';
4
+ import type { IDatabase, IndexMeta } from '../interfaces/IDatabase';
5
5
  import * as schema from '../db/schema';
6
6
  type Schema = typeof schema;
7
7
  export type DrizzleDb = NodePgDatabase<Schema>;
@@ -17,6 +17,18 @@ export declare class DatabaseService implements IDatabase {
17
17
  destroy(): Promise<void>;
18
18
  initDb(): Promise<void>;
19
19
  initDbWithSchema(): Promise<void>;
20
+ /**
21
+ * Create `code_chunk` at `dimensions` if it does not exist, plus its HNSW and
22
+ * (serviceName, filePath) indexes and the additive `contentHash`/`ownerPath`
23
+ * columns for pre-existing tables. Idempotent — safe to call repeatedly.
24
+ */
25
+ ensureChunkTable(dimensions: number): Promise<void>;
26
+ /** Drop and recreate `code_chunk` at `dimensions` — used on a dimension change. */
27
+ recreateChunkTable(dimensions: number): Promise<void>;
28
+ readIndexMeta(): Promise<IndexMeta | null>;
29
+ writeIndexMeta(meta: IndexMeta): Promise<void>;
30
+ /** Guard against SQL injection — the dimension is interpolated into DDL. */
31
+ private assertDim;
20
32
  private ensureDatabaseExists;
21
33
  }
22
34
  export {};
@@ -66,33 +66,20 @@ class DatabaseService {
66
66
  async initDbWithSchema() {
67
67
  await this.ensureDatabaseExists();
68
68
  await this.initDb();
69
- // code_chunk table
70
- const [{ exists }] = await this.query(`
71
- SELECT EXISTS (
72
- SELECT 1 FROM information_schema.tables
73
- WHERE table_schema = 'public' AND table_name = 'code_chunk'
69
+ // code_chunk table — created at the legacy default width (1024) so search
70
+ // tools always have a table. IndexerService recreates it at the resolved
71
+ // width before indexing when the active model's dimension differs.
72
+ await this.ensureChunkTable(1024);
73
+ // index_meta table — source of truth for the model that built the index.
74
+ await this.query(`
75
+ CREATE TABLE IF NOT EXISTS index_meta (
76
+ id BOOLEAN PRIMARY KEY DEFAULT true,
77
+ provider TEXT NOT NULL,
78
+ model TEXT NOT NULL,
79
+ dimensions INT NOT NULL,
80
+ "indexedAt" TIMESTAMPTZ NOT NULL DEFAULT now()
74
81
  )
75
82
  `);
76
- if (!exists) {
77
- await this.query(`
78
- CREATE TABLE code_chunk (
79
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
80
- "serviceName" TEXT NOT NULL,
81
- "filePath" TEXT NOT NULL,
82
- "startLine" INT,
83
- content TEXT NOT NULL,
84
- embedding vector(1024) NOT NULL,
85
- "lastModified" DOUBLE PRECISION,
86
- "contentHash" VARCHAR(64)
87
- )
88
- `);
89
- await this.query(`
90
- CREATE INDEX idx_code_chunk_embedding
91
- ON code_chunk USING hnsw (embedding vector_cosine_ops)
92
- `);
93
- }
94
- await this.query(`CREATE INDEX IF NOT EXISTS idx_code_chunk_service_file ON code_chunk ("serviceName", "filePath")`);
95
- await this.query(`ALTER TABLE code_chunk ADD COLUMN IF NOT EXISTS "contentHash" varchar(64) NULL`);
96
83
  // code_dependency table
97
84
  const [{ existsDep }] = await this.query(`
98
85
  SELECT EXISTS (
@@ -160,6 +147,64 @@ class DatabaseService {
160
147
  await this.query(`CREATE INDEX IF NOT EXISTS idx_call_callee_file ON code_call (project, "calleeFile")`);
161
148
  await this.query(`CREATE INDEX IF NOT EXISTS idx_call_from ON code_call (project, "fromFile")`);
162
149
  }
150
+ /**
151
+ * Create `code_chunk` at `dimensions` if it does not exist, plus its HNSW and
152
+ * (serviceName, filePath) indexes and the additive `contentHash`/`ownerPath`
153
+ * columns for pre-existing tables. Idempotent — safe to call repeatedly.
154
+ */
155
+ async ensureChunkTable(dimensions) {
156
+ const dim = this.assertDim(dimensions);
157
+ await this.query(`
158
+ CREATE TABLE IF NOT EXISTS code_chunk (
159
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
160
+ "serviceName" TEXT NOT NULL,
161
+ "filePath" TEXT NOT NULL,
162
+ "startLine" INT,
163
+ content TEXT NOT NULL,
164
+ embedding vector(${dim}) NOT NULL,
165
+ "lastModified" DOUBLE PRECISION,
166
+ "contentHash" VARCHAR(64),
167
+ "ownerPath" VARCHAR
168
+ )
169
+ `);
170
+ await this.query(`ALTER TABLE code_chunk ADD COLUMN IF NOT EXISTS "contentHash" varchar(64) NULL`);
171
+ await this.query(`ALTER TABLE code_chunk ADD COLUMN IF NOT EXISTS "ownerPath" varchar NULL`);
172
+ await this.query(`CREATE INDEX IF NOT EXISTS idx_code_chunk_embedding ON code_chunk USING hnsw (embedding vector_cosine_ops)`);
173
+ await this.query(`CREATE INDEX IF NOT EXISTS idx_code_chunk_service_file ON code_chunk ("serviceName", "filePath")`);
174
+ }
175
+ /** Drop and recreate `code_chunk` at `dimensions` — used on a dimension change. */
176
+ async recreateChunkTable(dimensions) {
177
+ const dim = this.assertDim(dimensions);
178
+ await this.query(`DROP TABLE IF EXISTS code_chunk`);
179
+ await this.ensureChunkTable(dim);
180
+ }
181
+ async readIndexMeta() {
182
+ try {
183
+ const rows = await this.query(`SELECT provider, model, dimensions FROM index_meta LIMIT 1`);
184
+ const row = rows[0];
185
+ return row ? { provider: row.provider, model: row.model, dimensions: Number(row.dimensions) } : null;
186
+ }
187
+ catch {
188
+ // Table absent (un-migrated DB) — treat as "no metadata".
189
+ return null;
190
+ }
191
+ }
192
+ async writeIndexMeta(meta) {
193
+ await this.query(`INSERT INTO index_meta (id, provider, model, dimensions, "indexedAt")
194
+ VALUES (true, $1, $2, $3, now())
195
+ ON CONFLICT (id) DO UPDATE
196
+ SET provider = EXCLUDED.provider,
197
+ model = EXCLUDED.model,
198
+ dimensions = EXCLUDED.dimensions,
199
+ "indexedAt" = now()`, [meta.provider, meta.model, meta.dimensions]);
200
+ }
201
+ /** Guard against SQL injection — the dimension is interpolated into DDL. */
202
+ assertDim(dimensions) {
203
+ if (!Number.isInteger(dimensions) || dimensions <= 0 || dimensions > 2000) {
204
+ throw new Error(`Invalid vector dimension: ${dimensions} (must be an integer 1–2000)`);
205
+ }
206
+ return dimensions;
207
+ }
163
208
  async ensureDatabaseExists() {
164
209
  const { host, port, username, password, database } = this.config.db;
165
210
  const client = new pg_1.Client({ host, port, user: username, password, database: 'postgres' });
@@ -0,0 +1,9 @@
1
+ import type { Config } from '../config';
2
+ import type { IEmbedder } from '../interfaces/IEmbedder';
3
+ import type { ILogger } from '../interfaces/ILogger';
4
+ import { type EmbedderDeps } from './OpenAICompatibleEmbedder';
5
+ /**
6
+ * Construct the `IEmbedder` implementation selected by `config.embedding.provider`.
7
+ * The default (`ollama`) keeps existing installs working unchanged.
8
+ */
9
+ export declare function createEmbedder(config: Config, logger?: ILogger, deps?: EmbedderDeps): IEmbedder;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEmbedder = createEmbedder;
4
+ const LoggerService_1 = require("./LoggerService");
5
+ const OllamaEmbedder_1 = require("./OllamaEmbedder");
6
+ const OpenAICompatibleEmbedder_1 = require("./OpenAICompatibleEmbedder");
7
+ const InfinityEmbedder_1 = require("./InfinityEmbedder");
8
+ const VoyageEmbedder_1 = require("./VoyageEmbedder");
9
+ /**
10
+ * Construct the `IEmbedder` implementation selected by `config.embedding.provider`.
11
+ * The default (`ollama`) keeps existing installs working unchanged.
12
+ */
13
+ function createEmbedder(config, logger = LoggerService_1.LoggerService.silent(), deps = {}) {
14
+ switch (config.embedding.provider) {
15
+ case 'openai-compatible':
16
+ return new OpenAICompatibleEmbedder_1.OpenAICompatibleEmbedder(config, logger, deps);
17
+ case 'infinity':
18
+ return new InfinityEmbedder_1.InfinityEmbedder(config, logger, deps);
19
+ case 'voyage':
20
+ return new VoyageEmbedder_1.VoyageEmbedder(config, logger, deps);
21
+ case 'ollama':
22
+ return new OllamaEmbedder_1.OllamaEmbedder(config, logger, deps);
23
+ default: {
24
+ // The Zod enum should already reject this; guard anyway for a clear message.
25
+ const p = config.embedding.provider;
26
+ throw new Error(`Unknown embedding provider: "${p}". Expected one of: ollama, openai-compatible, voyage, infinity.`);
27
+ }
28
+ }
29
+ }
@@ -1,8 +1,10 @@
1
1
  import type { Config } from '../config';
2
2
  import type { IEmbedder } from '../interfaces/IEmbedder';
3
3
  import type { IAstParser } from '../interfaces/IAstParser';
4
+ import type { IChunker } from '../interfaces/IChunker';
4
5
  import type { IDatabase } from '../interfaces/IDatabase';
5
6
  import type { ILogger } from '../interfaces/ILogger';
7
+ import type { IndexMeta } from '../interfaces/IDatabase';
6
8
  export interface IndexStats {
7
9
  serviceName: string;
8
10
  indexed: number;
@@ -16,11 +18,23 @@ export declare class IndexerService {
16
18
  private db;
17
19
  private embedder;
18
20
  private ast;
21
+ private chunker;
19
22
  private config;
20
23
  private logger;
21
- private readonly jsSplitter;
22
- private readonly schemaSplitter;
23
- constructor(db: IDatabase, embedder: IEmbedder, ast: IAstParser, config: Config, logger?: ILogger);
24
+ constructor(db: IDatabase, embedder: IEmbedder, ast: IAstParser, chunker: IChunker, config: Config, logger?: ILogger);
25
+ /**
26
+ * Resolve the active embedding dimension (config override → registry → probe),
27
+ * reconcile it against `index_meta`, and prepare `code_chunk` accordingly:
28
+ * a dimension change drops+recreates the table; a same-dimension model/provider
29
+ * change clears its rows (vector spaces are not comparable across models); an
30
+ * unchanged model keeps the rows for incremental indexing. Back-compat: an
31
+ * existing populated table with no `index_meta` row is treated as the legacy
32
+ * `ollama`/`bge-m3`/1024 index. Writes `index_meta` to reflect the active model.
33
+ * Call once per run, before the scan loop.
34
+ */
35
+ prepareEmbeddingSchema(): Promise<IndexMeta>;
36
+ private reconcileIndex;
37
+ private countChunks;
24
38
  backfillSymbols(signal?: AbortSignal): Promise<{
25
39
  total: number;
26
40
  processed: number;
@@ -37,10 +51,8 @@ export declare class IndexerService {
37
51
  scanAndIndex(dir: string, serviceName: string, force?: boolean, signal?: AbortSignal, preCollectedFiles?: string[], opts?: {
38
52
  rootTsConfigPath?: string;
39
53
  }): Promise<IndexStats>;
40
- private getSplitter;
41
54
  private static inferRole;
42
55
  private getEnrichedPrompt;
43
- private embedText;
44
56
  private static hashContent;
45
57
  private ingestFile;
46
58
  }