codesearch-mcp 0.0.17-alpha.1 → 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.
- package/CHANGELOG.md +48 -0
- package/README.md +95 -6
- package/dist/app.js +70 -10
- package/dist/src/commands/ConfigCommand.d.ts +1 -0
- package/dist/src/commands/ConfigCommand.js +84 -4
- package/dist/src/commands/IndexCommand.js +35 -7
- package/dist/src/commands/McpCommand.js +9 -4
- package/dist/src/commands/{SetupCommand.d.ts → WebCommand.d.ts} +3 -2
- package/dist/src/commands/WebCommand.js +56 -0
- package/dist/src/config-store.d.ts +85 -4
- package/dist/src/config-store.js +157 -32
- package/dist/src/config.d.ts +32 -8
- package/dist/src/config.js +93 -30
- package/dist/src/constants.d.ts +35 -0
- package/dist/src/constants.js +57 -4
- package/dist/src/container.d.ts +10 -6
- package/dist/src/container.js +10 -5
- 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/graph.d.ts +60 -0
- package/dist/src/graph.js +239 -120
- package/dist/src/help.js +20 -5
- package/dist/src/interfaces/IAstParser.d.ts +20 -3
- package/dist/src/interfaces/IChunker.d.ts +24 -0
- package/dist/src/interfaces/IChunker.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +27 -0
- package/dist/src/interfaces/IDatabase.js +2 -0
- package/dist/src/interfaces/IEmbedder.d.ts +15 -2
- package/dist/src/interfaces/ILogger.d.ts +10 -0
- package/dist/src/interfaces/ILogger.js +2 -0
- package/dist/src/interfaces/IMcpToolHandler.d.ts +22 -0
- package/dist/src/interfaces/IMcpToolHandler.js +2 -0
- package/dist/src/interfaces/IMonorepoResolver.d.ts +33 -0
- package/dist/src/interfaces/IMonorepoResolver.js +2 -0
- package/dist/src/interfaces/IProjectDiscovery.d.ts +28 -0
- package/dist/src/interfaces/IProjectDiscovery.js +2 -0
- package/dist/src/interfaces/ISearcher.d.ts +37 -0
- package/dist/src/interfaces/ISearcher.js +2 -0
- package/dist/src/interfaces/index.d.ts +8 -0
- package/dist/src/interfaces/index.js +2 -0
- package/dist/src/owner-path.d.ts +10 -0
- package/dist/src/owner-path.js +27 -0
- package/dist/src/project-selector.d.ts +1 -1
- package/dist/src/services/AstChunker.d.ts +31 -0
- package/dist/src/services/AstChunker.js +114 -0
- package/dist/src/services/AstParserService.d.ts +10 -6
- package/dist/src/services/AstParserService.js +60 -15
- package/dist/src/services/DatabaseService.d.ts +14 -1
- package/dist/src/services/DatabaseService.js +70 -25
- package/dist/src/services/EmbedderFactory.d.ts +9 -0
- package/dist/src/services/EmbedderFactory.js +29 -0
- package/dist/src/services/IndexerService.d.ts +22 -12
- package/dist/src/services/IndexerService.js +106 -46
- package/dist/src/services/InfinityEmbedder.d.ts +79 -0
- package/dist/src/services/InfinityEmbedder.js +199 -0
- package/dist/src/services/LoggerService.d.ts +2 -1
- package/dist/src/services/McpServer.d.ts +16 -7
- package/dist/src/services/McpServer.js +54 -367
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +62 -0
- package/dist/src/services/MonorepoAwareDiscoveryService.js +125 -0
- package/dist/src/services/NxMonorepoResolver.d.ts +45 -0
- package/dist/src/services/NxMonorepoResolver.js +308 -0
- package/dist/src/services/OllamaEmbedder.d.ts +19 -5
- package/dist/src/services/OllamaEmbedder.js +91 -36
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +25 -0
- package/dist/src/services/OpenAICompatibleEmbedder.js +75 -0
- package/dist/src/services/ProjectDiscoveryService.d.ts +8 -0
- package/dist/src/{env-loader.js → services/ProjectDiscoveryService.js} +16 -5
- package/dist/src/services/SearcherService.d.ts +46 -7
- package/dist/src/services/SearcherService.js +183 -53
- package/dist/src/services/VoyageEmbedder.d.ts +22 -0
- package/dist/src/services/VoyageEmbedder.js +72 -0
- package/dist/src/services/WebServerService.d.ts +77 -0
- package/dist/src/services/WebServerService.js +267 -0
- package/dist/src/services/http-retry.d.ts +19 -0
- package/dist/src/services/http-retry.js +54 -0
- package/dist/src/services/reranker.d.ts +15 -0
- package/dist/src/services/reranker.js +44 -0
- package/dist/src/setup.d.ts +6 -0
- package/dist/src/setup.js +249 -31
- package/dist/src/tools/FindSymbolTool.d.ts +8 -0
- package/dist/src/tools/FindSymbolTool.js +45 -0
- package/dist/src/tools/GetCallersTool.d.ts +8 -0
- package/dist/src/tools/GetCallersTool.js +42 -0
- package/dist/src/tools/GetDependenciesTool.d.ts +8 -0
- package/dist/src/tools/GetDependenciesTool.js +38 -0
- package/dist/src/tools/GetDependentsTool.d.ts +8 -0
- package/dist/src/tools/GetDependentsTool.js +57 -0
- package/dist/src/tools/ListFilesTool.d.ts +8 -0
- package/dist/src/tools/ListFilesTool.js +39 -0
- package/dist/src/tools/ListProjectsTool.d.ts +8 -0
- package/dist/src/tools/ListProjectsTool.js +44 -0
- package/dist/src/tools/ReadFileRangeTool.d.ts +8 -0
- package/dist/src/tools/ReadFileRangeTool.js +44 -0
- package/dist/src/tools/ReadFileTool.d.ts +8 -0
- package/dist/src/tools/ReadFileTool.js +42 -0
- package/dist/src/tools/SearchCodebaseTool.d.ts +13 -0
- package/dist/src/tools/SearchCodebaseTool.js +81 -0
- package/dist/src/tools/format-helpers.d.ts +8 -0
- package/dist/src/tools/format-helpers.js +58 -0
- package/dist/src/tools/index.d.ts +9 -0
- package/dist/src/tools/index.js +21 -0
- package/dist/src/web/dashboard.d.ts +9 -0
- package/dist/src/web/dashboard.js +342 -0
- package/docker/docker-compose.infinity-gpu.yml +19 -0
- package/docker/docker-compose.yml +25 -0
- package/docs/web-dashboard.md +187 -0
- package/package.json +25 -3
- package/dist/src/ast.d.ts +0 -2
- package/dist/src/ast.js +0 -110
- 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/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/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a discovered project within a monorepo workspace.
|
|
3
|
+
*/
|
|
4
|
+
export interface MonorepoProject {
|
|
5
|
+
/** Project name (e.g. "my-app", "shared-utils") */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Absolute path to the project root */
|
|
8
|
+
path: string;
|
|
9
|
+
/** npm scope + package name (e.g. "@scope/my-app") — null if no package.json or no name */
|
|
10
|
+
packageName: string | null;
|
|
11
|
+
/** "app" | "lib" — derived from project.json or directory convention */
|
|
12
|
+
type: 'app' | 'lib';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Monorepo resolver — detects and resolves workspace structure.
|
|
16
|
+
* Implementations handle specific monorepo tooling (Nx, Turborepo, etc.)
|
|
17
|
+
*/
|
|
18
|
+
export interface IMonorepoResolver {
|
|
19
|
+
/**
|
|
20
|
+
* Detect whether the given root directory is a monorepo workspace.
|
|
21
|
+
*/
|
|
22
|
+
isMonorepo(rootDir: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Discover all projects (apps + libs) in the monorepo.
|
|
25
|
+
*/
|
|
26
|
+
discoverProjects(rootDir: string): MonorepoProject[];
|
|
27
|
+
/**
|
|
28
|
+
* Get the root tsconfig path that contains workspace-wide path aliases.
|
|
29
|
+
* For Nx this is typically tsconfig.base.json at the workspace root.
|
|
30
|
+
* The AST parser uses this to resolve cross-project imports (@scope/*).
|
|
31
|
+
*/
|
|
32
|
+
getRootTsConfigPath(rootDir: string): string | null;
|
|
33
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface DiscoveredProject {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
/** npm scope + package name (e.g. "@scope/my-app") — only set for monorepo projects */
|
|
5
|
+
packageName?: string | null;
|
|
6
|
+
/** "app" | "lib" — only set for monorepo projects */
|
|
7
|
+
type?: 'app' | 'lib';
|
|
8
|
+
/**
|
|
9
|
+
* Path to the workspace-wide tsconfig (e.g. tsconfig.base.json) that holds
|
|
10
|
+
* cross-project path aliases. Set for monorepo projects; undefined for flat
|
|
11
|
+
* workspaces (where each project uses its own local tsconfig.json).
|
|
12
|
+
*/
|
|
13
|
+
rootTsConfigPath?: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* When a top-level directory is itself a nested monorepo (e.g. an Nx repo
|
|
16
|
+
* under a flat workspaceRoot), this holds its expanded sub-projects. The
|
|
17
|
+
* repo stays a single selectable unit; `toLeafProjects` expands `children`
|
|
18
|
+
* into the actual projects to index. Absent/undefined for ordinary projects.
|
|
19
|
+
*/
|
|
20
|
+
children?: DiscoveredProject[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Project discovery abstraction — decouples project enumeration
|
|
24
|
+
* from the indexing service (Single Responsibility Principle).
|
|
25
|
+
*/
|
|
26
|
+
export interface IProjectDiscovery {
|
|
27
|
+
discover(rootDir: string): DiscoveredProject[];
|
|
28
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SearchResult, NeighborResult, GraphContext, DependencyRow, ProjectRow, GrepMatch, SymbolKind, SymbolHit, CallerRow, FileRangeResult } from '../services/SearcherService';
|
|
2
|
+
/**
|
|
3
|
+
* Searcher abstraction — McpServer depends on this interface
|
|
4
|
+
* rather than the concrete SearcherService.
|
|
5
|
+
*/
|
|
6
|
+
export interface ISearcher {
|
|
7
|
+
prepareQuery(raw: string): Promise<{
|
|
8
|
+
query: string;
|
|
9
|
+
translated: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
assertIndexMatches(): Promise<void>;
|
|
12
|
+
listProjects(): Promise<ProjectRow[]>;
|
|
13
|
+
getProjectEdges(): Promise<{
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
}[]>;
|
|
17
|
+
searchChunks(query: string, limit: number, project: string | null, threshold?: number): Promise<SearchResult[]>;
|
|
18
|
+
fetchNeighbors(results: SearchResult[]): Promise<NeighborResult[]>;
|
|
19
|
+
fetchGraphContext(results: SearchResult[]): Promise<Map<string, GraphContext>>;
|
|
20
|
+
getDependencies(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
|
|
21
|
+
getDependents(filePath: string, project: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
|
|
22
|
+
getCallers(symbol: string, project: string, calleeFile?: string | null): Promise<CallerRow[]>;
|
|
23
|
+
grepSymbol(symbol: string, project: string, excludeFile: string): Promise<{
|
|
24
|
+
matches: GrepMatch[];
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
findSymbol(name: string, project: string | null, kind: SymbolKind): Promise<{
|
|
28
|
+
hits: SymbolHit[];
|
|
29
|
+
truncated: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
listFiles(relativePath: string, project: string, depth: number, pattern: string | null): {
|
|
32
|
+
tree: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
};
|
|
35
|
+
readFileAbsolute(absPath: string, maxLines: number): Promise<FileRangeResult>;
|
|
36
|
+
readFileRange(filePath: string, startLine: number, endLine: number): Promise<FileRangeResult>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { IEmbedder, InputType } from './IEmbedder';
|
|
2
|
+
export type { IAstParser, DepEdge, SymbolEntry, CallEdge, FileAnalysis } from './IAstParser';
|
|
3
|
+
export type { IChunker, ChunkPiece, ChunkOpts } from './IChunker';
|
|
4
|
+
export type { ILogger } from './ILogger';
|
|
5
|
+
export type { IDatabase } from './IDatabase';
|
|
6
|
+
export type { ISearcher } from './ISearcher';
|
|
7
|
+
export type { IProjectDiscovery, DiscoveredProject } from './IProjectDiscovery';
|
|
8
|
+
export type { IMcpToolHandler, ToolDefinition, ToolResponse } from './IMcpToolHandler';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OwnerRange } from './interfaces/IAstParser';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the owning declaration for a chunk given its 1-based start line and a
|
|
4
|
+
* flat list of candidate ranges (top-level symbols + method ranges). The
|
|
5
|
+
* deepest range containing the start line wins (smallest `endLine - line` span),
|
|
6
|
+
* so a method beats its enclosing class. Returns `Container.name` for methods
|
|
7
|
+
* (via `container`) or `name` for top-level declarations, and `null` when the
|
|
8
|
+
* start line lies outside every range (imports / module-scope gaps).
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveOwnerPath(startLine: number, ranges: OwnerRange[]): string | null;
|
|
@@ -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
|
+
}
|
|
@@ -7,7 +7,7 @@ export declare class ProjectSelector {
|
|
|
7
7
|
private included;
|
|
8
8
|
private skipped;
|
|
9
9
|
constructor(workspaceDir: string);
|
|
10
|
-
select(allProjects:
|
|
10
|
+
select<T extends Project>(allProjects: T[], forceSelect: boolean): Promise<T[]>;
|
|
11
11
|
private printAlreadySelected;
|
|
12
12
|
private promptForSelection;
|
|
13
13
|
private promptForSkip;
|
|
@@ -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;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import type { IAstParser, DepEdge, FileAnalysis } from '../interfaces/IAstParser';
|
|
2
|
-
import {
|
|
1
|
+
import type { IAstParser, DepEdge, FileAnalysis, AnalyzeProjectOpts } from '../interfaces/IAstParser';
|
|
2
|
+
import type { ILogger } from '../interfaces';
|
|
3
3
|
export declare class AstParserService implements IAstParser {
|
|
4
4
|
private logger;
|
|
5
5
|
private morphProjects;
|
|
6
|
-
constructor(logger?:
|
|
7
|
-
analyzeProject(filePaths: string[], serviceName: string, projectRoot?: string, opts?:
|
|
8
|
-
skipDependencyResolution?: boolean;
|
|
9
|
-
}): Promise<Map<string, FileAnalysis>>;
|
|
6
|
+
constructor(logger?: ILogger);
|
|
7
|
+
analyzeProject(filePaths: string[], serviceName: string, projectRoot?: string, opts?: AnalyzeProjectOpts): Promise<Map<string, FileAnalysis>>;
|
|
10
8
|
clearCache(projectRoot?: string): void;
|
|
11
9
|
analyzeFile(filePath: string, serviceName: string, projectRoot?: string): Promise<FileAnalysis>;
|
|
12
10
|
extractImportEdges(filePath: string, serviceName: string, projectRoot?: string): Promise<DepEdge[]>;
|
|
@@ -16,6 +14,12 @@ export declare class AstParserService implements IAstParser {
|
|
|
16
14
|
private resolveFromSymbol;
|
|
17
15
|
private buildUsageMap;
|
|
18
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;
|
|
19
23
|
private extractCalls;
|
|
20
24
|
private getContainerName;
|
|
21
25
|
}
|
|
@@ -54,9 +54,10 @@ class AstParserService {
|
|
|
54
54
|
async analyzeProject(filePaths, serviceName, projectRoot = process.cwd(), opts = {}) {
|
|
55
55
|
const t0 = Date.now();
|
|
56
56
|
const skipDependencyResolution = opts.skipDependencyResolution ?? false;
|
|
57
|
-
|
|
57
|
+
const rootTsConfigPath = opts.rootTsConfigPath;
|
|
58
|
+
this.logger.debug('AstParser: analyzeProject start', { serviceName, projectRoot, files: filePaths.length, skipDependencyResolution, rootTsConfigPath });
|
|
58
59
|
const analysisMap = new Map();
|
|
59
|
-
const morphProject = this.createMorphProject(projectRoot, skipDependencyResolution);
|
|
60
|
+
const morphProject = this.createMorphProject(projectRoot, skipDependencyResolution, rootTsConfigPath);
|
|
60
61
|
for (const filePath of filePaths) {
|
|
61
62
|
if (!/\.(ts|tsx|js|jsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) {
|
|
62
63
|
analysisMap.set(filePath, { edges: [], symbols: [], calls: [] });
|
|
@@ -76,7 +77,8 @@ class AstParserService {
|
|
|
76
77
|
const edges = this.extractEdges(sourceFile, filePath, serviceName);
|
|
77
78
|
const symbols = this.extractSymbols(sourceFile, filePath, serviceName);
|
|
78
79
|
const calls = this.extractCalls(sourceFile, filePath, serviceName);
|
|
79
|
-
|
|
80
|
+
const ownerRanges = this.extractOwnerRanges(sourceFile);
|
|
81
|
+
analysisMap.set(filePath, { edges, symbols, calls, ownerRanges });
|
|
80
82
|
}
|
|
81
83
|
catch (err) {
|
|
82
84
|
this.logger.warn('AstParser: failed to analyze file', { filePath, reason: err.message });
|
|
@@ -125,9 +127,10 @@ class AstParserService {
|
|
|
125
127
|
const edges = this.extractEdges(sourceFile, filePath, serviceName);
|
|
126
128
|
const symbols = this.extractSymbols(sourceFile, filePath, serviceName);
|
|
127
129
|
const calls = this.extractCalls(sourceFile, filePath, serviceName);
|
|
130
|
+
const ownerRanges = this.extractOwnerRanges(sourceFile);
|
|
128
131
|
morphProject.removeSourceFile(sourceFile);
|
|
129
132
|
this.logger.debug('AstParser: analyzeFile done', { filePath, edges: edges.length, symbols: symbols.length, calls: calls.length, ms: Date.now() - t0 });
|
|
130
|
-
return { edges, symbols, calls };
|
|
133
|
+
return { edges, symbols, calls, ownerRanges };
|
|
131
134
|
}
|
|
132
135
|
// Kept for test compatibility (extract-import-edges.test.ts).
|
|
133
136
|
async extractImportEdges(filePath, serviceName, projectRoot) {
|
|
@@ -135,22 +138,37 @@ class AstParserService {
|
|
|
135
138
|
return edges;
|
|
136
139
|
}
|
|
137
140
|
// ── private: MorphProject cache (LRU, max MORPH_CACHE_MAX entries) ──────────
|
|
138
|
-
createMorphProject(projectRoot, skipFileDependencyResolution = false) {
|
|
141
|
+
createMorphProject(projectRoot, skipFileDependencyResolution = false, rootTsConfigPath) {
|
|
139
142
|
const t0 = Date.now();
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
// Determine which tsconfig to use:
|
|
144
|
+
// 1. Project-local tsconfig.json
|
|
145
|
+
// 2. Root tsconfig (e.g. tsconfig.base.json for Nx monorepos)
|
|
146
|
+
// 3. Fallback options
|
|
147
|
+
const localTsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
148
|
+
const hasLocalTsconfig = fs.existsSync(localTsconfigPath);
|
|
149
|
+
const hasRootTsconfig = rootTsConfigPath ? fs.existsSync(rootTsConfigPath) : false;
|
|
150
|
+
// For monorepos: prefer the root tsconfig.base.json which contains
|
|
151
|
+
// workspace-wide path aliases (e.g. @scope/* → libs/*/src)
|
|
152
|
+
const effectiveTsconfig = hasRootTsconfig ? rootTsConfigPath : (hasLocalTsconfig ? localTsconfigPath : null);
|
|
153
|
+
this.logger.debug('AstParser: tsconfig lookup', {
|
|
154
|
+
projectRoot,
|
|
155
|
+
localFound: hasLocalTsconfig,
|
|
156
|
+
rootTsConfigPath: rootTsConfigPath ?? null,
|
|
157
|
+
rootFound: hasRootTsconfig,
|
|
158
|
+
effective: effectiveTsconfig,
|
|
159
|
+
skipFileDependencyResolution,
|
|
160
|
+
});
|
|
143
161
|
let project;
|
|
144
162
|
try {
|
|
145
|
-
const opts =
|
|
146
|
-
? { tsConfigFilePath:
|
|
163
|
+
const opts = effectiveTsconfig
|
|
164
|
+
? { tsConfigFilePath: effectiveTsconfig, skipAddingFilesFromTsConfig: true, skipFileDependencyResolution }
|
|
147
165
|
: { ...FALLBACK_OPTS, skipFileDependencyResolution };
|
|
148
166
|
project = new ts_morph_1.Project(opts);
|
|
149
|
-
if (
|
|
167
|
+
if (effectiveTsconfig) {
|
|
150
168
|
const co = project.getCompilerOptions();
|
|
151
169
|
const pathsMap = co.paths ?? {};
|
|
152
170
|
const pathCount = Object.keys(pathsMap).length;
|
|
153
|
-
this.logger.debug('AstParser: tsconfig loaded', { pathCount, ms: Date.now() - t0 });
|
|
171
|
+
this.logger.debug('AstParser: tsconfig loaded', { tsconfig: effectiveTsconfig, pathCount, ms: Date.now() - t0 });
|
|
154
172
|
if (pathCount > 0) {
|
|
155
173
|
this.logger.debug('AstParser: path aliases', { aliases: Object.keys(pathsMap) });
|
|
156
174
|
}
|
|
@@ -161,7 +179,7 @@ class AstParserService {
|
|
|
161
179
|
}
|
|
162
180
|
catch (err) {
|
|
163
181
|
this.logger.warn('AstParser: tsconfig load failed, using fallback', {
|
|
164
|
-
tsconfigPath,
|
|
182
|
+
tsconfigPath: effectiveTsconfig ?? localTsconfigPath,
|
|
165
183
|
reason: err.message,
|
|
166
184
|
ms: Date.now() - t0,
|
|
167
185
|
});
|
|
@@ -353,6 +371,33 @@ class AstParserService {
|
|
|
353
371
|
}
|
|
354
372
|
return symbols;
|
|
355
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
|
+
}
|
|
356
401
|
// ── private: call graph extraction ────────────────────────────────────────
|
|
357
402
|
extractCalls(sourceFile, filePath, serviceName) {
|
|
358
403
|
const calls = [];
|
|
@@ -367,7 +412,7 @@ class AstParserService {
|
|
|
367
412
|
const sym = expr.getNameNode().getSymbol();
|
|
368
413
|
const decl = sym?.getDeclarations()?.[0];
|
|
369
414
|
const declPath = decl?.getSourceFile().getFilePath();
|
|
370
|
-
if (declPath && !declPath.includes('/node_modules/')
|
|
415
|
+
if (declPath && !declPath.includes('/node_modules/')) {
|
|
371
416
|
calleeFile = declPath;
|
|
372
417
|
calleeSymbol = calleeName;
|
|
373
418
|
}
|
|
@@ -383,7 +428,7 @@ class AstParserService {
|
|
|
383
428
|
: sym;
|
|
384
429
|
const decl = resolved.getDeclarations()?.[0];
|
|
385
430
|
const declPath = decl?.getSourceFile().getFilePath();
|
|
386
|
-
if (declPath && !declPath.includes('/node_modules/')
|
|
431
|
+
if (declPath && !declPath.includes('/node_modules/')) {
|
|
387
432
|
calleeFile = declPath;
|
|
388
433
|
calleeSymbol = calleeName;
|
|
389
434
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
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, IndexMeta } from '../interfaces/IDatabase';
|
|
4
5
|
import * as schema from '../db/schema';
|
|
5
6
|
type Schema = typeof schema;
|
|
6
7
|
export type DrizzleDb = NodePgDatabase<Schema>;
|
|
7
8
|
export type DrizzleTx = NodePgTransaction<Schema, ExtractTablesWithRelations<Schema>>;
|
|
8
|
-
export declare class DatabaseService {
|
|
9
|
+
export declare class DatabaseService implements IDatabase {
|
|
9
10
|
private config;
|
|
10
11
|
private pool;
|
|
11
12
|
private _db;
|
|
@@ -16,6 +17,18 @@ export declare class DatabaseService {
|
|
|
16
17
|
destroy(): Promise<void>;
|
|
17
18
|
initDb(): Promise<void>;
|
|
18
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;
|
|
19
32
|
private ensureDatabaseExists;
|
|
20
33
|
}
|
|
21
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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;
|