codesearch-mcp 0.0.8-alpha → 0.0.10-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/README.md +26 -14
- package/dist/app.js +24 -144
- package/dist/src/commands/BackfillCommand.d.ts +7 -0
- package/dist/src/commands/BackfillCommand.js +27 -0
- package/dist/src/commands/ICommand.d.ts +3 -0
- package/dist/src/commands/ICommand.js +2 -0
- package/dist/src/commands/IndexCommand.d.ts +9 -0
- package/dist/src/commands/IndexCommand.js +75 -0
- package/dist/src/commands/McpCommand.d.ts +7 -0
- package/dist/src/commands/McpCommand.js +21 -0
- package/dist/src/commands/SetupCommand.d.ts +4 -0
- package/dist/src/commands/SetupCommand.js +10 -0
- package/dist/src/config.d.ts +1 -0
- package/dist/src/config.js +41 -2
- package/dist/src/container.d.ts +15 -0
- package/dist/src/container.js +21 -0
- package/dist/src/db/entities/code-chunk.entity.d.ts +1 -1
- package/dist/src/db/entities/code-chunk.entity.js +2 -2
- package/dist/src/db/entities/code-symbol.entity.d.ts +10 -0
- package/dist/src/db/entities/code-symbol.entity.js +59 -0
- package/dist/src/db/index.d.ts +1 -2
- package/dist/src/db/index.js +3 -6
- package/dist/src/file-utils.d.ts +2 -1
- package/dist/src/file-utils.js +10 -5
- package/dist/src/graph.js +5 -4
- package/dist/src/help.js +1 -1
- package/dist/src/indexer.js +1 -0
- package/dist/src/interfaces/IAstParser.d.ts +24 -0
- package/dist/src/interfaces/IAstParser.js +2 -0
- package/dist/src/interfaces/IEmbedder.d.ts +4 -0
- package/dist/src/interfaces/IEmbedder.js +2 -0
- package/dist/src/interrupt.d.ts +1 -3
- package/dist/src/interrupt.js +14 -16
- package/dist/src/mcp.js +101 -9
- package/dist/src/searcher.d.ts +18 -0
- package/dist/src/searcher.js +171 -1
- package/dist/src/services/AstParserService.d.ts +14 -0
- package/dist/src/services/AstParserService.js +256 -0
- package/dist/src/services/DatabaseService.d.ts +14 -0
- package/dist/src/services/DatabaseService.js +133 -0
- package/dist/src/services/IndexerService.d.ts +39 -0
- package/dist/src/services/IndexerService.js +286 -0
- package/dist/src/services/LoggerService.d.ts +12 -0
- package/dist/src/services/LoggerService.js +73 -0
- package/dist/src/services/McpServer.d.ts +13 -0
- package/dist/src/services/McpServer.js +414 -0
- package/dist/src/services/OllamaEmbedder.d.ts +9 -0
- package/dist/src/services/OllamaEmbedder.js +27 -0
- package/dist/src/services/SearcherService.d.ts +91 -0
- package/dist/src/services/SearcherService.js +453 -0
- package/dist/src/setup.js +23 -7
- package/package.json +11 -10
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IAstParser, DepEdge, FileAnalysis } from '../interfaces/IAstParser';
|
|
2
|
+
import { LoggerService } from './LoggerService';
|
|
3
|
+
export declare class AstParserService implements IAstParser {
|
|
4
|
+
private logger;
|
|
5
|
+
private morphProjects;
|
|
6
|
+
constructor(logger?: LoggerService);
|
|
7
|
+
analyzeFile(filePath: string, serviceName: string, projectRoot?: string): Promise<FileAnalysis>;
|
|
8
|
+
extractImportEdges(filePath: string, serviceName: string, projectRoot?: string): Promise<DepEdge[]>;
|
|
9
|
+
private getMorphProject;
|
|
10
|
+
private extractEdges;
|
|
11
|
+
private resolveFromSymbol;
|
|
12
|
+
private buildUsageMap;
|
|
13
|
+
private extractSymbols;
|
|
14
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AstParserService = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const ts_morph_1 = require("ts-morph");
|
|
40
|
+
const LoggerService_1 = require("./LoggerService");
|
|
41
|
+
const FALLBACK_OPTS = {
|
|
42
|
+
skipAddingFilesFromTsConfig: true,
|
|
43
|
+
skipFileDependencyResolution: false,
|
|
44
|
+
compilerOptions: { allowJs: true, noEmit: true },
|
|
45
|
+
};
|
|
46
|
+
class AstParserService {
|
|
47
|
+
logger;
|
|
48
|
+
morphProjects = new Map();
|
|
49
|
+
constructor(logger = LoggerService_1.LoggerService.silent()) {
|
|
50
|
+
this.logger = logger;
|
|
51
|
+
}
|
|
52
|
+
// ── public interface ────────────────────────────────────────────────────────
|
|
53
|
+
async analyzeFile(filePath, serviceName, projectRoot = path.dirname(filePath)) {
|
|
54
|
+
if (!/\.(ts|tsx|js|jsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) {
|
|
55
|
+
return { edges: [], symbols: [] };
|
|
56
|
+
}
|
|
57
|
+
const morphProject = this.getMorphProject(projectRoot);
|
|
58
|
+
const existing = morphProject.getSourceFile(filePath);
|
|
59
|
+
if (existing)
|
|
60
|
+
morphProject.removeSourceFile(existing);
|
|
61
|
+
let sourceFile;
|
|
62
|
+
try {
|
|
63
|
+
sourceFile = morphProject.addSourceFileAtPath(filePath);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { edges: [], symbols: [] };
|
|
67
|
+
}
|
|
68
|
+
const edges = this.extractEdges(sourceFile, filePath, serviceName);
|
|
69
|
+
const symbols = this.extractSymbols(sourceFile, filePath, serviceName);
|
|
70
|
+
morphProject.removeSourceFile(sourceFile);
|
|
71
|
+
return { edges, symbols };
|
|
72
|
+
}
|
|
73
|
+
// Kept for test compatibility (extract-import-edges.test.ts).
|
|
74
|
+
async extractImportEdges(filePath, serviceName, projectRoot) {
|
|
75
|
+
const { edges } = await this.analyzeFile(filePath, serviceName, projectRoot);
|
|
76
|
+
return edges;
|
|
77
|
+
}
|
|
78
|
+
// ── private: MorphProject cache ────────────────────────────────────────────
|
|
79
|
+
getMorphProject(projectRoot) {
|
|
80
|
+
if (!this.morphProjects.has(projectRoot)) {
|
|
81
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
82
|
+
const hasTsconfig = fs.existsSync(tsconfigPath);
|
|
83
|
+
this.logger.debug('AstParser: tsconfig lookup', { projectRoot, found: hasTsconfig });
|
|
84
|
+
let project;
|
|
85
|
+
try {
|
|
86
|
+
const opts = hasTsconfig
|
|
87
|
+
? { tsConfigFilePath: tsconfigPath, skipAddingFilesFromTsConfig: true, skipFileDependencyResolution: false }
|
|
88
|
+
: FALLBACK_OPTS;
|
|
89
|
+
project = new ts_morph_1.Project(opts);
|
|
90
|
+
if (hasTsconfig) {
|
|
91
|
+
const co = project.getCompilerOptions();
|
|
92
|
+
const pathsMap = co.paths ?? {};
|
|
93
|
+
const pathCount = Object.keys(pathsMap).length;
|
|
94
|
+
this.logger.debug('AstParser: tsconfig loaded', { pathCount });
|
|
95
|
+
if (pathCount > 0) {
|
|
96
|
+
this.logger.debug('AstParser: path aliases', { aliases: Object.keys(pathsMap) });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
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
|
+
}
|
|
109
|
+
return this.morphProjects.get(projectRoot);
|
|
110
|
+
}
|
|
111
|
+
// ── private: edge extraction (imports + re-exports) ────────────────────────
|
|
112
|
+
extractEdges(sourceFile, filePath, serviceName) {
|
|
113
|
+
const edges = [];
|
|
114
|
+
const usageMap = this.buildUsageMap(sourceFile);
|
|
115
|
+
for (const decl of sourceFile.getImportDeclarations()) {
|
|
116
|
+
const resolved = decl.getModuleSpecifierSourceFile();
|
|
117
|
+
if (!resolved) {
|
|
118
|
+
const specifier = decl.getModuleSpecifierValue();
|
|
119
|
+
if (specifier.startsWith('.')) {
|
|
120
|
+
this.logger.debug('AstParser: unresolved import', { specifier, file: filePath });
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const toFile = resolved.getFilePath();
|
|
125
|
+
if (toFile.includes('/node_modules/'))
|
|
126
|
+
continue;
|
|
127
|
+
const namedImports = decl.getNamedImports();
|
|
128
|
+
const defaultImport = decl.getDefaultImport();
|
|
129
|
+
const namespaceImport = decl.getNamespaceImport();
|
|
130
|
+
if (namedImports.length > 0) {
|
|
131
|
+
for (const named of namedImports) {
|
|
132
|
+
const localName = named.getAliasNode()?.getText() ?? named.getName();
|
|
133
|
+
const fromSymbol = this.resolveFromSymbol(localName, usageMap);
|
|
134
|
+
edges.push({
|
|
135
|
+
project: serviceName, fromFile: filePath, fromSymbol,
|
|
136
|
+
toFile, toSymbol: named.getName(), depType: 'import',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (defaultImport) {
|
|
141
|
+
edges.push({
|
|
142
|
+
project: serviceName, fromFile: filePath, fromSymbol: null,
|
|
143
|
+
toFile, toSymbol: null, depType: 'import-default',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
else if (namespaceImport) {
|
|
147
|
+
edges.push({
|
|
148
|
+
project: serviceName, fromFile: filePath, fromSymbol: null,
|
|
149
|
+
toFile, toSymbol: null, depType: 'import-namespace',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const decl of sourceFile.getExportDeclarations()) {
|
|
154
|
+
const resolved = decl.getModuleSpecifierSourceFile();
|
|
155
|
+
if (!resolved)
|
|
156
|
+
continue;
|
|
157
|
+
const toFile = resolved.getFilePath();
|
|
158
|
+
if (toFile.includes('/node_modules/'))
|
|
159
|
+
continue;
|
|
160
|
+
const namedExports = decl.getNamedExports();
|
|
161
|
+
if (namedExports.length > 0) {
|
|
162
|
+
for (const named of namedExports) {
|
|
163
|
+
edges.push({
|
|
164
|
+
project: serviceName, fromFile: filePath, fromSymbol: null,
|
|
165
|
+
toFile, toSymbol: named.getName(), depType: 'reexport',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
edges.push({
|
|
171
|
+
project: serviceName, fromFile: filePath, fromSymbol: null,
|
|
172
|
+
toFile, toSymbol: null, depType: 'reexport-namespace',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return edges;
|
|
177
|
+
}
|
|
178
|
+
resolveFromSymbol(localName, usageMap) {
|
|
179
|
+
const users = usageMap.get(localName);
|
|
180
|
+
return users?.length === 1 ? users[0] : null;
|
|
181
|
+
}
|
|
182
|
+
buildUsageMap(sourceFile) {
|
|
183
|
+
const map = new Map();
|
|
184
|
+
const containers = [
|
|
185
|
+
...sourceFile.getClasses().flatMap(n => n.getName() ? [{ name: n.getName(), node: n }] : []),
|
|
186
|
+
...sourceFile.getFunctions().flatMap(n => n.getName() ? [{ name: n.getName(), node: n }] : []),
|
|
187
|
+
// include top-level variable declarations so arrow function exports are tracked for fromSymbol
|
|
188
|
+
...sourceFile.getVariableStatements()
|
|
189
|
+
.flatMap(stmt => stmt.getDeclarations())
|
|
190
|
+
.flatMap(n => {
|
|
191
|
+
const name = n.getName();
|
|
192
|
+
return name ? [{ name, node: n }] : [];
|
|
193
|
+
}),
|
|
194
|
+
];
|
|
195
|
+
for (const { name, node } of containers) {
|
|
196
|
+
for (const id of node.getDescendantsOfKind(ts_morph_1.SyntaxKind.Identifier)) {
|
|
197
|
+
const text = id.getText();
|
|
198
|
+
const existing = map.get(text);
|
|
199
|
+
if (existing) {
|
|
200
|
+
if (!existing.includes(name))
|
|
201
|
+
existing.push(name);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
map.set(text, [name]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return map;
|
|
209
|
+
}
|
|
210
|
+
// ── private: symbol extraction ─────────────────────────────────────────────
|
|
211
|
+
extractSymbols(sourceFile, filePath, serviceName) {
|
|
212
|
+
const symbols = [];
|
|
213
|
+
// Find the first line matching the keyword — skips leading decorators.
|
|
214
|
+
const sig = (text, keyword) => {
|
|
215
|
+
const found = text.split('\n').find(l => keyword.test(l));
|
|
216
|
+
return found?.trim() ?? text.split('\n')[0].trim();
|
|
217
|
+
};
|
|
218
|
+
const getPos = (node) => {
|
|
219
|
+
const { line, character } = sourceFile.compilerNode
|
|
220
|
+
.getLineAndCharacterOfPosition(node.getStart());
|
|
221
|
+
return { line: line + 1, col: character + 1 };
|
|
222
|
+
};
|
|
223
|
+
for (const cls of sourceFile.getClasses()) {
|
|
224
|
+
const name = cls.getName();
|
|
225
|
+
if (!name)
|
|
226
|
+
continue;
|
|
227
|
+
const nameNode = cls.getNameNode();
|
|
228
|
+
const { line, col } = nameNode ? getPos(nameNode) : { line: cls.getStartLineNumber(), col: 1 };
|
|
229
|
+
symbols.push({ project: serviceName, filePath, name, kind: 'class', signature: sig(cls.getText(), /\bclass\s/), line, col });
|
|
230
|
+
}
|
|
231
|
+
for (const fn of sourceFile.getFunctions()) {
|
|
232
|
+
const name = fn.getName();
|
|
233
|
+
if (!name)
|
|
234
|
+
continue;
|
|
235
|
+
const nameNode = fn.getNameNode();
|
|
236
|
+
const { line, col } = nameNode ? getPos(nameNode) : { line: fn.getStartLineNumber(), col: 1 };
|
|
237
|
+
symbols.push({ project: serviceName, filePath, name, kind: 'function', signature: sig(fn.getText(), /\bfunction\s/), line, col });
|
|
238
|
+
}
|
|
239
|
+
for (const iface of sourceFile.getInterfaces()) {
|
|
240
|
+
const { line, col } = getPos(iface.getNameNode());
|
|
241
|
+
symbols.push({ project: serviceName, filePath, name: iface.getName(), kind: 'interface', signature: sig(iface.getText(), /\binterface\s/), line, col });
|
|
242
|
+
}
|
|
243
|
+
for (const ta of sourceFile.getTypeAliases()) {
|
|
244
|
+
const { line, col } = getPos(ta.getNameNode());
|
|
245
|
+
symbols.push({ project: serviceName, filePath, name: ta.getName(), kind: 'type', signature: sig(ta.getText(), /\btype\s/), line, col });
|
|
246
|
+
}
|
|
247
|
+
for (const stmt of sourceFile.getVariableStatements()) {
|
|
248
|
+
for (const decl of stmt.getDeclarations()) {
|
|
249
|
+
const { line, col } = getPos(decl.getNameNode());
|
|
250
|
+
symbols.push({ project: serviceName, filePath, name: decl.getName(), kind: 'variable', signature: sig(stmt.getText(), /\b(const|let|var)\s/), line, col });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return symbols;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
exports.AstParserService = AstParserService;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EntityManager, EntityTarget, ObjectLiteral, Repository } from 'typeorm';
|
|
2
|
+
import type { Config } from '../config';
|
|
3
|
+
export declare class DatabaseService {
|
|
4
|
+
private config;
|
|
5
|
+
private dataSource;
|
|
6
|
+
constructor(config: Config);
|
|
7
|
+
query<T = any>(sql: string, params?: any[]): Promise<T>;
|
|
8
|
+
transaction<T>(fn: (manager: EntityManager) => Promise<T>): Promise<T>;
|
|
9
|
+
getRepository<T extends ObjectLiteral>(entity: EntityTarget<T>): Repository<T>;
|
|
10
|
+
destroy(): Promise<void>;
|
|
11
|
+
initDb(): Promise<void>;
|
|
12
|
+
initDbWithSchema(): Promise<void>;
|
|
13
|
+
private ensureDatabaseExists;
|
|
14
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatabaseService = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
5
|
+
const pg_1 = require("pg");
|
|
6
|
+
const db_1 = require("../db");
|
|
7
|
+
class DatabaseService {
|
|
8
|
+
config;
|
|
9
|
+
dataSource;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.dataSource = new typeorm_1.DataSource({
|
|
13
|
+
type: 'postgres',
|
|
14
|
+
...config.db,
|
|
15
|
+
entities: [db_1.CodeChunk, db_1.CodeDependency, db_1.CodeSymbol],
|
|
16
|
+
synchronize: false,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
query(sql, params) {
|
|
20
|
+
return this.dataSource.query(sql, params);
|
|
21
|
+
}
|
|
22
|
+
transaction(fn) {
|
|
23
|
+
return this.dataSource.transaction(fn);
|
|
24
|
+
}
|
|
25
|
+
getRepository(entity) {
|
|
26
|
+
return this.dataSource.getRepository(entity);
|
|
27
|
+
}
|
|
28
|
+
destroy() {
|
|
29
|
+
if (!this.dataSource.isInitialized)
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
return this.dataSource.destroy();
|
|
32
|
+
}
|
|
33
|
+
async initDb() {
|
|
34
|
+
await this.dataSource.initialize();
|
|
35
|
+
}
|
|
36
|
+
async initDbWithSchema() {
|
|
37
|
+
await this.ensureDatabaseExists();
|
|
38
|
+
await this.dataSource.initialize();
|
|
39
|
+
// code_chunk table
|
|
40
|
+
const [{ exists }] = await this.dataSource.query(`
|
|
41
|
+
SELECT EXISTS (
|
|
42
|
+
SELECT 1 FROM information_schema.tables
|
|
43
|
+
WHERE table_schema = 'public' AND table_name = 'code_chunk'
|
|
44
|
+
)
|
|
45
|
+
`);
|
|
46
|
+
if (!exists) {
|
|
47
|
+
await this.dataSource.synchronize();
|
|
48
|
+
await this.dataSource.query(`
|
|
49
|
+
CREATE INDEX idx_code_chunk_embedding
|
|
50
|
+
ON code_chunk USING hnsw (embedding vector_cosine_ops)
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
await this.dataSource.query(`CREATE INDEX IF NOT EXISTS idx_code_chunk_service_file ON code_chunk ("serviceName", "filePath")`);
|
|
54
|
+
// code_dependency table
|
|
55
|
+
const [{ existsDep }] = await this.dataSource.query(`
|
|
56
|
+
SELECT EXISTS (
|
|
57
|
+
SELECT 1 FROM information_schema.tables
|
|
58
|
+
WHERE table_schema = 'public' AND table_name = 'code_dependency'
|
|
59
|
+
) AS "existsDep"
|
|
60
|
+
`);
|
|
61
|
+
if (!existsDep) {
|
|
62
|
+
await this.dataSource.query(`
|
|
63
|
+
CREATE TABLE code_dependency (
|
|
64
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
65
|
+
project TEXT NOT NULL,
|
|
66
|
+
"fromFile" TEXT NOT NULL,
|
|
67
|
+
"fromSymbol" TEXT,
|
|
68
|
+
"toFile" TEXT NOT NULL,
|
|
69
|
+
"toSymbol" TEXT,
|
|
70
|
+
"depType" TEXT NOT NULL
|
|
71
|
+
)
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
await this.dataSource.query(`CREATE INDEX IF NOT EXISTS idx_dep_project_from ON code_dependency (project, "fromFile")`);
|
|
75
|
+
await this.dataSource.query(`CREATE INDEX IF NOT EXISTS idx_dep_project_to ON code_dependency (project, "toFile")`);
|
|
76
|
+
await this.dataSource.query(`DROP INDEX IF EXISTS idx_dep_from`);
|
|
77
|
+
await this.dataSource.query(`DROP INDEX IF EXISTS idx_dep_to`);
|
|
78
|
+
await this.dataSource.query(`DROP INDEX IF EXISTS idx_dep_project`);
|
|
79
|
+
// code_symbol table
|
|
80
|
+
const [{ existsSym }] = await this.dataSource.query(`
|
|
81
|
+
SELECT EXISTS (
|
|
82
|
+
SELECT 1 FROM information_schema.tables
|
|
83
|
+
WHERE table_schema = 'public' AND table_name = 'code_symbol'
|
|
84
|
+
) AS "existsSym"
|
|
85
|
+
`);
|
|
86
|
+
if (!existsSym) {
|
|
87
|
+
await this.dataSource.query(`
|
|
88
|
+
CREATE TABLE code_symbol (
|
|
89
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
90
|
+
project TEXT NOT NULL,
|
|
91
|
+
"filePath" TEXT NOT NULL,
|
|
92
|
+
name TEXT NOT NULL,
|
|
93
|
+
kind TEXT NOT NULL,
|
|
94
|
+
signature TEXT,
|
|
95
|
+
line INT,
|
|
96
|
+
col INT
|
|
97
|
+
)
|
|
98
|
+
`);
|
|
99
|
+
}
|
|
100
|
+
await this.dataSource.query(`CREATE INDEX IF NOT EXISTS idx_symbol_name ON code_symbol (name, project)`);
|
|
101
|
+
await this.dataSource.query(`CREATE INDEX IF NOT EXISTS idx_symbol_file ON code_symbol (project, "filePath")`);
|
|
102
|
+
await this.dataSource.query(`CREATE UNIQUE INDEX IF NOT EXISTS idx_symbol_unique ON code_symbol (project, "filePath", name, kind)`);
|
|
103
|
+
}
|
|
104
|
+
async ensureDatabaseExists() {
|
|
105
|
+
const { host, port, username, password, database } = this.config.db;
|
|
106
|
+
const client = new pg_1.Client({ host, port, user: username, password, database: 'postgres' });
|
|
107
|
+
try {
|
|
108
|
+
await client.connect();
|
|
109
|
+
const { rows } = await client.query(`SELECT 1 FROM pg_database WHERE datname = $1`, [database]);
|
|
110
|
+
if (rows.length === 0) {
|
|
111
|
+
await client.query(`CREATE DATABASE "${database}"`);
|
|
112
|
+
await client.end();
|
|
113
|
+
const extClient = new pg_1.Client({ host, port, user: username, password, database });
|
|
114
|
+
try {
|
|
115
|
+
await extClient.connect();
|
|
116
|
+
await extClient.query(`CREATE EXTENSION IF NOT EXISTS vector`);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await extClient.end().catch(() => { });
|
|
120
|
+
}
|
|
121
|
+
console.log(`Created database "${database}"`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
await client.end();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
await client.end().catch(() => { });
|
|
129
|
+
throw new Error(`Failed to ensure database "${database}" exists: ${err.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.DatabaseService = DatabaseService;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Config } from '../config';
|
|
2
|
+
import type { IEmbedder } from '../interfaces/IEmbedder';
|
|
3
|
+
import type { IAstParser } from '../interfaces/IAstParser';
|
|
4
|
+
import type { DatabaseService } from './DatabaseService';
|
|
5
|
+
import { LoggerService } from './LoggerService';
|
|
6
|
+
export interface IndexStats {
|
|
7
|
+
serviceName: string;
|
|
8
|
+
indexed: number;
|
|
9
|
+
skipped: number;
|
|
10
|
+
failed: number;
|
|
11
|
+
chunks: number;
|
|
12
|
+
elapsedSec: number;
|
|
13
|
+
warnings: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare class IndexerService {
|
|
16
|
+
private db;
|
|
17
|
+
private embedder;
|
|
18
|
+
private ast;
|
|
19
|
+
private config;
|
|
20
|
+
private logger;
|
|
21
|
+
private readonly jsSplitter;
|
|
22
|
+
private readonly schemaSplitter;
|
|
23
|
+
constructor(db: DatabaseService, embedder: IEmbedder, ast: IAstParser, config: Config, logger?: LoggerService);
|
|
24
|
+
backfillSymbols(signal?: AbortSignal): Promise<{
|
|
25
|
+
total: number;
|
|
26
|
+
processed: number;
|
|
27
|
+
skipped: number;
|
|
28
|
+
failed: number;
|
|
29
|
+
}>;
|
|
30
|
+
discoverProjects(rootDir: string): {
|
|
31
|
+
name: string;
|
|
32
|
+
path: string;
|
|
33
|
+
}[];
|
|
34
|
+
private writeDepEdges;
|
|
35
|
+
scanAndIndex(dir: string, serviceName: string, force?: boolean, signal?: AbortSignal, preCollectedFiles?: string[]): Promise<IndexStats>;
|
|
36
|
+
private getSplitter;
|
|
37
|
+
private embedText;
|
|
38
|
+
private ingestFile;
|
|
39
|
+
}
|