codesearch-mcp 0.0.9-alpha → 0.0.11-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 (48) hide show
  1. package/README.md +26 -14
  2. package/dist/app.js +24 -143
  3. package/dist/src/commands/BackfillCommand.d.ts +7 -0
  4. package/dist/src/commands/BackfillCommand.js +27 -0
  5. package/dist/src/commands/ICommand.d.ts +3 -0
  6. package/dist/src/commands/ICommand.js +2 -0
  7. package/dist/src/commands/IndexCommand.d.ts +9 -0
  8. package/dist/src/commands/IndexCommand.js +75 -0
  9. package/dist/src/commands/McpCommand.d.ts +7 -0
  10. package/dist/src/commands/McpCommand.js +21 -0
  11. package/dist/src/commands/SetupCommand.d.ts +4 -0
  12. package/dist/src/commands/SetupCommand.js +10 -0
  13. package/dist/src/config.d.ts +1 -0
  14. package/dist/src/config.js +41 -2
  15. package/dist/src/container.d.ts +15 -0
  16. package/dist/src/container.js +21 -0
  17. package/dist/src/db/entities/code-chunk.entity.d.ts +1 -1
  18. package/dist/src/db/entities/code-chunk.entity.js +2 -2
  19. package/dist/src/db/entities/code-symbol.entity.d.ts +10 -0
  20. package/dist/src/db/entities/code-symbol.entity.js +59 -0
  21. package/dist/src/db/index.d.ts +1 -2
  22. package/dist/src/db/index.js +3 -6
  23. package/dist/src/file-utils.d.ts +2 -1
  24. package/dist/src/file-utils.js +10 -5
  25. package/dist/src/graph.js +5 -4
  26. package/dist/src/help.js +1 -1
  27. package/dist/src/interfaces/IAstParser.d.ts +24 -0
  28. package/dist/src/interfaces/IAstParser.js +2 -0
  29. package/dist/src/interfaces/IEmbedder.d.ts +4 -0
  30. package/dist/src/interfaces/IEmbedder.js +2 -0
  31. package/dist/src/interrupt.d.ts +1 -3
  32. package/dist/src/interrupt.js +14 -16
  33. package/dist/src/services/AstParserService.d.ts +14 -0
  34. package/dist/src/services/AstParserService.js +256 -0
  35. package/dist/src/services/DatabaseService.d.ts +14 -0
  36. package/dist/src/services/DatabaseService.js +133 -0
  37. package/dist/src/services/IndexerService.d.ts +39 -0
  38. package/dist/src/services/IndexerService.js +286 -0
  39. package/dist/src/services/LoggerService.d.ts +12 -0
  40. package/dist/src/services/LoggerService.js +73 -0
  41. package/dist/src/services/McpServer.d.ts +13 -0
  42. package/dist/src/services/McpServer.js +411 -0
  43. package/dist/src/services/OllamaEmbedder.d.ts +9 -0
  44. package/dist/src/services/OllamaEmbedder.js +27 -0
  45. package/dist/src/services/SearcherService.d.ts +91 -0
  46. package/dist/src/services/SearcherService.js +453 -0
  47. package/dist/src/setup.js +23 -7
  48. package/package.json +11 -10
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CodeSymbol = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ let CodeSymbol = class CodeSymbol {
15
+ id;
16
+ project;
17
+ filePath;
18
+ name;
19
+ kind;
20
+ signature;
21
+ line;
22
+ col;
23
+ };
24
+ exports.CodeSymbol = CodeSymbol;
25
+ __decorate([
26
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
27
+ __metadata("design:type", String)
28
+ ], CodeSymbol.prototype, "id", void 0);
29
+ __decorate([
30
+ (0, typeorm_1.Column)(),
31
+ __metadata("design:type", String)
32
+ ], CodeSymbol.prototype, "project", void 0);
33
+ __decorate([
34
+ (0, typeorm_1.Column)(),
35
+ __metadata("design:type", String)
36
+ ], CodeSymbol.prototype, "filePath", void 0);
37
+ __decorate([
38
+ (0, typeorm_1.Column)(),
39
+ __metadata("design:type", String)
40
+ ], CodeSymbol.prototype, "name", void 0);
41
+ __decorate([
42
+ (0, typeorm_1.Column)(),
43
+ __metadata("design:type", String)
44
+ ], CodeSymbol.prototype, "kind", void 0);
45
+ __decorate([
46
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
47
+ __metadata("design:type", Object)
48
+ ], CodeSymbol.prototype, "signature", void 0);
49
+ __decorate([
50
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
51
+ __metadata("design:type", Object)
52
+ ], CodeSymbol.prototype, "line", void 0);
53
+ __decorate([
54
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
55
+ __metadata("design:type", Object)
56
+ ], CodeSymbol.prototype, "col", void 0);
57
+ exports.CodeSymbol = CodeSymbol = __decorate([
58
+ (0, typeorm_1.Entity)()
59
+ ], CodeSymbol);
@@ -1,4 +1,3 @@
1
1
  export { CodeChunk } from './entities/code-chunk.entity';
2
2
  export { CodeDependency } from './entities/code-dependency.entity';
3
- export { db } from './data-source';
4
- export { initDb, initDbWithSchema } from './init';
3
+ export { CodeSymbol } from './entities/code-symbol.entity';
@@ -1,12 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.initDbWithSchema = exports.initDb = exports.db = exports.CodeDependency = exports.CodeChunk = void 0;
3
+ exports.CodeSymbol = exports.CodeDependency = exports.CodeChunk = void 0;
4
4
  var code_chunk_entity_1 = require("./entities/code-chunk.entity");
5
5
  Object.defineProperty(exports, "CodeChunk", { enumerable: true, get: function () { return code_chunk_entity_1.CodeChunk; } });
6
6
  var code_dependency_entity_1 = require("./entities/code-dependency.entity");
7
7
  Object.defineProperty(exports, "CodeDependency", { enumerable: true, get: function () { return code_dependency_entity_1.CodeDependency; } });
8
- var data_source_1 = require("./data-source");
9
- Object.defineProperty(exports, "db", { enumerable: true, get: function () { return data_source_1.db; } });
10
- var init_1 = require("./init");
11
- Object.defineProperty(exports, "initDb", { enumerable: true, get: function () { return init_1.initDb; } });
12
- Object.defineProperty(exports, "initDbWithSchema", { enumerable: true, get: function () { return init_1.initDbWithSchema; } });
8
+ var code_symbol_entity_1 = require("./entities/code-symbol.entity");
9
+ Object.defineProperty(exports, "CodeSymbol", { enumerable: true, get: function () { return code_symbol_entity_1.CodeSymbol; } });
@@ -6,7 +6,8 @@ export interface ProjectMeta {
6
6
  notes: string | null;
7
7
  relations: Relations | null;
8
8
  }
9
- export declare function readRagMeta(projectDir: string): ProjectMeta | null;
9
+ export type RagMetaResult = ProjectMeta | 'missing' | 'invalid';
10
+ export declare function readRagMeta(projectDir: string): RagMetaResult;
10
11
  export declare const IGNORE_DIRS: Set<string>;
11
12
  export declare function loadIgnore(projectDir: string): ReturnType<typeof ignore>;
12
13
  export declare function collectFiles(dir: string, ig: ReturnType<typeof ignore>, rootDir: string): string[];
@@ -43,12 +43,10 @@ exports.collectFiles = collectFiles;
43
43
  const fs = __importStar(require("fs"));
44
44
  const path = __importStar(require("path"));
45
45
  const ignore_1 = __importDefault(require("ignore"));
46
- // Returns null if file is missing or contains invalid JSON.
47
- // Returns ProjectMeta (fields may be null) if file exists and parses correctly.
48
46
  function readRagMeta(projectDir) {
49
47
  const metaPath = path.join(projectDir, '.ragmeta.json');
50
48
  if (!fs.existsSync(metaPath))
51
- return null;
49
+ return 'missing';
52
50
  try {
53
51
  const raw = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
54
52
  return {
@@ -59,7 +57,7 @@ function readRagMeta(projectDir) {
59
57
  };
60
58
  }
61
59
  catch {
62
- return null;
60
+ return 'invalid';
63
61
  }
64
62
  }
65
63
  exports.IGNORE_DIRS = new Set([
@@ -79,7 +77,14 @@ function loadIgnore(projectDir) {
79
77
  }
80
78
  function collectFiles(dir, ig, rootDir) {
81
79
  const result = [];
82
- for (const file of fs.readdirSync(dir)) {
80
+ let entries;
81
+ try {
82
+ entries = fs.readdirSync(dir);
83
+ }
84
+ catch {
85
+ return result;
86
+ }
87
+ for (const file of entries) {
83
88
  const fullPath = path.join(dir, file);
84
89
  const relPath = path.relative(rootDir, fullPath);
85
90
  if (ig.ignores(relPath))
package/dist/src/graph.js CHANGED
@@ -243,10 +243,11 @@ function buildHtml(nodes, edges) {
243
243
  }
244
244
  // Single entry point — reads meta once, prints console summary and writes HTML
245
245
  function generateProjectGraph(projects, workspacePath) {
246
- const nodes = projects.map(p => ({
247
- name: p.name,
248
- ...((0, file_utils_1.readRagMeta)(p.path) ?? { description: null, stack: null, notes: null, relations: null }),
249
- }));
246
+ const EMPTY = { description: null, stack: null, notes: null, relations: null };
247
+ const nodes = projects.map(p => {
248
+ const meta = (0, file_utils_1.readRagMeta)(p.path);
249
+ return { name: p.name, ...(typeof meta === 'object' ? meta : EMPTY) };
250
+ });
250
251
  const edges = buildEdges(nodes);
251
252
  printConsole(nodes, edges, workspacePath);
252
253
  const htmlPath = path.join(workspacePath, 'project-graph.html');
package/dist/src/help.js CHANGED
@@ -19,7 +19,7 @@ function printHelp(command) {
19
19
  console.log(' -h, --help Show this help message');
20
20
  console.log('');
21
21
  console.log('Subcommands:');
22
- console.log(' backfill Populate dependency graph for already-indexed files');
22
+ console.log(' backfill Populate symbol index (code_symbol) for already-indexed files');
23
23
  break;
24
24
  case 'rag-mcp':
25
25
  case 'mcp':
@@ -0,0 +1,24 @@
1
+ export interface DepEdge {
2
+ project: string;
3
+ fromFile: string;
4
+ fromSymbol: string | null;
5
+ toFile: string;
6
+ toSymbol: string | null;
7
+ depType: string;
8
+ }
9
+ export interface SymbolEntry {
10
+ project: string;
11
+ filePath: string;
12
+ name: string;
13
+ kind: 'function' | 'class' | 'interface' | 'type' | 'variable';
14
+ signature: string | null;
15
+ line: number | null;
16
+ col: number | null;
17
+ }
18
+ export interface FileAnalysis {
19
+ edges: DepEdge[];
20
+ symbols: SymbolEntry[];
21
+ }
22
+ export interface IAstParser {
23
+ analyzeFile(filePath: string, serviceName: string, projectRoot?: string): Promise<FileAnalysis>;
24
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export interface IEmbedder {
2
+ embed(text: string): Promise<number[]>;
3
+ translate?(text: string, model: string): Promise<string>;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +1 @@
1
- export declare const signal: AbortSignal;
2
- export declare function setupInterrupt(): void;
3
- export declare function teardownInterrupt(): void;
1
+ export declare function setupInterrupt(controller: AbortController): () => void;
@@ -1,27 +1,25 @@
1
1
  "use strict";
2
- // Graceful interruption signal for long-running CLI operations.
3
- // Listens for ESC or Ctrl+C on stdin and aborts the shared controller.
4
2
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.signal = void 0;
6
3
  exports.setupInterrupt = setupInterrupt;
7
- exports.teardownInterrupt = teardownInterrupt;
8
- const controller = new AbortController();
9
- exports.signal = controller.signal;
10
- function setupInterrupt() {
4
+ // Graceful interruption for long-running CLI operations.
5
+ // Caller creates the AbortController; this function wires stdin and returns a cleanup function.
6
+ function setupInterrupt(controller) {
11
7
  if (!process.stdin.isTTY)
12
- return;
8
+ return () => { };
13
9
  process.stdin.setRawMode(true);
14
10
  process.stdin.resume();
15
11
  process.stdin.setEncoding('utf8');
16
- process.stdin.on('data', (key) => {
12
+ const handler = (key) => {
17
13
  if (key === '\x1b' || key === '\x03')
18
14
  controller.abort();
19
- });
15
+ };
16
+ process.stdin.on('data', handler);
20
17
  console.log('Press ESC or Ctrl+C to interrupt.\n');
21
- }
22
- function teardownInterrupt() {
23
- if (!process.stdin.isTTY)
24
- return;
25
- process.stdin.setRawMode(false);
26
- process.stdin.pause();
18
+ return () => {
19
+ process.stdin.off('data', handler);
20
+ if (process.stdin.isTTY) {
21
+ process.stdin.setRawMode(false);
22
+ process.stdin.pause();
23
+ }
24
+ };
27
25
  }
@@ -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;