septum 0.1.0

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/bin/septum.ts +4 -0
  4. package/package.json +62 -0
  5. package/src/cli/commands/check.ts +161 -0
  6. package/src/cli/commands/hook.ts +188 -0
  7. package/src/cli/commands/ingest.ts +29 -0
  8. package/src/cli/commands/init.ts +211 -0
  9. package/src/cli/commands/locate.ts +61 -0
  10. package/src/cli/commands/query.ts +64 -0
  11. package/src/cli/commands/serve.ts +5 -0
  12. package/src/cli/commands/slice.ts +90 -0
  13. package/src/cli/commands/sync.ts +31 -0
  14. package/src/cli/index.ts +178 -0
  15. package/src/cli/templates/hooks.ts +75 -0
  16. package/src/cli/templates/rules.ts +38 -0
  17. package/src/cli/templates/skill.ts +66 -0
  18. package/src/core/boundary/evaluator.ts +210 -0
  19. package/src/core/config/loader.ts +84 -0
  20. package/src/core/config/schema.ts +39 -0
  21. package/src/core/database/client.ts +132 -0
  22. package/src/core/database/repositories/dependency.repository.ts +279 -0
  23. package/src/core/database/repositories/domain.repository.ts +198 -0
  24. package/src/core/database/repositories/file.repository.ts +99 -0
  25. package/src/core/database/repositories/meta.repository.ts +33 -0
  26. package/src/core/database/repositories/symbol.repository.ts +424 -0
  27. package/src/core/database/repository.ts +242 -0
  28. package/src/core/database/schema.sql +92 -0
  29. package/src/core/discovery/topology-detector.ts +418 -0
  30. package/src/core/ingestion/hasher.ts +5 -0
  31. package/src/core/ingestion/pipeline.ts +438 -0
  32. package/src/core/parser/boundary-tracker.ts +128 -0
  33. package/src/core/parser/extractors/base.ts +60 -0
  34. package/src/core/parser/extractors/frontend.ts +194 -0
  35. package/src/core/parser/extractors/go.ts +176 -0
  36. package/src/core/parser/extractors/laravel-semantic.ts +426 -0
  37. package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -0
  38. package/src/core/parser/extractors/nestjs-semantic.ts +119 -0
  39. package/src/core/parser/extractors/php.ts +149 -0
  40. package/src/core/parser/extractors/python.ts +226 -0
  41. package/src/core/parser/extractors/registry.ts +49 -0
  42. package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -0
  43. package/src/core/parser/extractors/typescript.ts +177 -0
  44. package/src/core/parser/tree-sitter.ts +39 -0
  45. package/src/core/resolver/call-graph-tracer.ts +668 -0
  46. package/src/core/resolver/module-resolver.ts +323 -0
  47. package/src/core/resolver/symbol-locator.ts +405 -0
  48. package/src/core/resolver/vertical-slice-tracer.ts +236 -0
  49. package/src/core/session/session-manager.ts +56 -0
  50. package/src/core/telemetry/telemetry.ts +86 -0
  51. package/src/index.ts +18 -0
  52. package/src/mcp/schemas.ts +68 -0
  53. package/src/mcp/server.ts +602 -0
  54. package/src/mcp/tools/check-boundary.ts +102 -0
  55. package/src/mcp/tools/get-domain-catalog.ts +91 -0
  56. package/src/mcp/tools/get-feature-context.ts +116 -0
  57. package/src/mcp/tools/get-symbol-hotspots.ts +49 -0
  58. package/src/mcp/tools/get-symbol-impact.ts +26 -0
  59. package/src/mcp/tools/get-symbol.ts +63 -0
  60. package/src/mcp/tools/locate-symbol.ts +74 -0
  61. package/src/mcp/tools/register-domain.ts +64 -0
  62. package/src/mcp/tools/trace-vertical-slice.ts +102 -0
  63. package/src/types/index.ts +296 -0
@@ -0,0 +1,424 @@
1
+ import * as fs from "node:fs";
2
+ import type { Database } from "bun:sqlite";
3
+ import type {
4
+ DomainRecord,
5
+ ExtractedSymbol,
6
+ FileRecord,
7
+ GetSymbolDetailsResponse,
8
+ SymbolKind,
9
+ SymbolRecord,
10
+ } from "../../../types/index.ts";
11
+
12
+ export class SymbolRepository {
13
+ constructor(private db: Database) {}
14
+
15
+ public replaceFileSymbols(fileId: number, symbols: ExtractedSymbol[]): void {
16
+ this.db.query("DELETE FROM symbols WHERE file_id = ?").run(fileId);
17
+ if (symbols.length === 0) return;
18
+
19
+ const stmt = this.db.query(
20
+ `INSERT INTO symbols (file_id, name, kind, signature, visibility, line_start, line_end, line_count)
21
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
22
+ );
23
+
24
+ for (const sym of symbols) {
25
+ const lineCount = sym.line_count ?? Math.max(1, sym.line_end - sym.line_start + 1);
26
+ stmt.run(
27
+ fileId,
28
+ sym.name,
29
+ sym.kind,
30
+ sym.signature,
31
+ sym.visibility,
32
+ sym.line_start,
33
+ sym.line_end,
34
+ lineCount
35
+ );
36
+ }
37
+ }
38
+
39
+ public getSymbolsByFileId(fileId: number): SymbolRecord[] {
40
+ return this.db
41
+ .query<SymbolRecord, [number]>(
42
+ "SELECT * FROM symbols WHERE file_id = ? ORDER BY line_start ASC"
43
+ )
44
+ .all(fileId);
45
+ }
46
+
47
+ public findSymbolsByName(name: string): Array<SymbolRecord & { file_path: string }> {
48
+ const query = `
49
+ SELECT s.*, f.path AS file_path
50
+ FROM symbols s
51
+ JOIN files f ON s.file_id = f.id
52
+ WHERE s.name = ? OR s.name LIKE ?
53
+ ORDER BY s.line_start ASC
54
+ `;
55
+ return this.db
56
+ .query<SymbolRecord & { file_path: string }, [string, string]>(query)
57
+ .all(name, `%::${name}`);
58
+ }
59
+
60
+ public findContainers(
61
+ containerName: string
62
+ ): Array<{ file: FileRecord; symbol?: SymbolRecord }> {
63
+ const symbolQuery = `
64
+ SELECT s.*, f.path AS file_path, f.id AS f_id, f.domain_id AS f_domain_id,
65
+ f.archetype AS f_archetype, f.content_hash AS f_content_hash,
66
+ f.line_count AS f_line_count, f.mtime_ms AS f_mtime_ms,
67
+ f.size_bytes AS f_size_bytes, f.last_scanned_at AS f_last_scanned_at
68
+ FROM symbols s
69
+ JOIN files f ON s.file_id = f.id
70
+ WHERE (s.name = ? OR s.name LIKE ? OR s.name LIKE ?)
71
+ AND s.kind IN ('class', 'interface', 'trait', 'enum')
72
+ LIMIT 10
73
+ `;
74
+ const rows = this.db
75
+ .query<any, [string, string, string]>(symbolQuery)
76
+ .all(containerName, `%\\${containerName}`, `%/${containerName}`);
77
+
78
+ if (rows.length > 0) {
79
+ return rows.map((r) => ({
80
+ file: {
81
+ id: r.f_id,
82
+ domain_id: r.f_domain_id,
83
+ path: r.file_path,
84
+ archetype: r.f_archetype,
85
+ content_hash: r.f_content_hash,
86
+ line_count: r.f_line_count,
87
+ mtime_ms: r.f_mtime_ms,
88
+ size_bytes: r.f_size_bytes,
89
+ last_scanned_at: r.f_last_scanned_at,
90
+ },
91
+ symbol: {
92
+ id: r.id,
93
+ file_id: r.file_id,
94
+ name: r.name,
95
+ kind: r.kind,
96
+ signature: r.signature,
97
+ visibility: r.visibility,
98
+ line_start: r.line_start,
99
+ line_end: r.line_end,
100
+ line_count: r.line_count ?? (r.line_end - r.line_start + 1),
101
+ },
102
+ }));
103
+ }
104
+
105
+ const fileQuery = `
106
+ SELECT * FROM files
107
+ WHERE path LIKE ? OR path LIKE ?
108
+ LIMIT 10
109
+ `;
110
+ const files = this.db
111
+ .query<FileRecord, [string, string]>(fileQuery)
112
+ .all(`%/${containerName}.%`, `%\\${containerName}.%`);
113
+
114
+ return files.map((file) => ({ file }));
115
+ }
116
+
117
+ public getAllSymbolsWithFiles(): Array<SymbolRecord & { file_path: string }> {
118
+ const query = `
119
+ SELECT s.*, f.path AS file_path
120
+ FROM symbols s
121
+ JOIN files f ON s.file_id = f.id
122
+ WHERE s.kind IN ('class', 'method', 'function')
123
+ ORDER BY s.name ASC
124
+ `;
125
+ return this.db.query<SymbolRecord & { file_path: string }, []>(query).all();
126
+ }
127
+
128
+ public findSymbolWithDomain(symbolName: string): {
129
+ symbol_name: string;
130
+ kind: string;
131
+ file_path: string;
132
+ domain_name: string;
133
+ } | null {
134
+ const query = `
135
+ SELECT
136
+ s.name AS symbol_name,
137
+ s.kind,
138
+ f.path AS file_path,
139
+ d.name AS domain_name
140
+ FROM symbols s
141
+ JOIN files f ON s.file_id = f.id
142
+ JOIN domains d ON f.domain_id = d.id
143
+ WHERE s.name = ?
144
+ LIMIT 1
145
+ `;
146
+ return this.db.query<any, [string]>(query).get(symbolName) ?? null;
147
+ }
148
+
149
+ public findSymbolWithMembers(symbolName: string): {
150
+ symbol_name: string;
151
+ kind: string;
152
+ signature: string;
153
+ file_path: string;
154
+ domain_name: string;
155
+ methods: string[];
156
+ properties: string[];
157
+ } | null {
158
+ const rootQuery = `
159
+ SELECT
160
+ s.id AS symbol_id,
161
+ s.file_id,
162
+ s.name AS symbol_name,
163
+ s.kind,
164
+ s.signature,
165
+ f.path AS file_path,
166
+ d.name AS domain_name
167
+ FROM symbols s
168
+ JOIN files f ON s.file_id = f.id
169
+ JOIN domains d ON f.domain_id = d.id
170
+ WHERE s.name = ?
171
+ LIMIT 1
172
+ `;
173
+ const root = this.db.query<any, [string]>(rootQuery).get(symbolName);
174
+ if (!root) return null;
175
+
176
+ const membersQuery = `
177
+ SELECT name, kind, signature
178
+ FROM symbols
179
+ WHERE file_id = ? AND name LIKE ?
180
+ ORDER BY line_start ASC
181
+ `;
182
+ const members = this.db
183
+ .query<any, [number, string]>(membersQuery)
184
+ .all(root.file_id, `${symbolName}::%`);
185
+
186
+ const methods: string[] = [];
187
+ const properties: string[] = [];
188
+
189
+ for (const m of members) {
190
+ if (m.kind === "method") {
191
+ methods.push(m.signature);
192
+ } else if (m.kind === "property") {
193
+ properties.push(m.signature);
194
+ }
195
+ }
196
+
197
+ return {
198
+ symbol_name: root.symbol_name,
199
+ kind: root.kind,
200
+ signature: root.signature,
201
+ file_path: root.file_path,
202
+ domain_name: root.domain_name,
203
+ methods,
204
+ properties,
205
+ };
206
+ }
207
+
208
+ public getFileWithDomain(filePath: string): {
209
+ file_id: number;
210
+ file_path: string;
211
+ domain_name: string;
212
+ } | null {
213
+ const query = `
214
+ SELECT
215
+ f.id AS file_id,
216
+ f.path AS file_path,
217
+ d.name AS domain_name
218
+ FROM files f
219
+ JOIN domains d ON f.domain_id = d.id
220
+ WHERE f.path = ?
221
+ LIMIT 1
222
+ `;
223
+ return this.db.query<any, [string]>(query).get(filePath) ?? null;
224
+ }
225
+
226
+ public getHotspotSymbols(options: {
227
+ minLines?: number;
228
+ kind?: SymbolKind;
229
+ domain?: string;
230
+ limit?: number;
231
+ } = {}): Array<SymbolRecord & { file_path: string; domain_name: string }> {
232
+ const minLines = options.minLines ?? 30;
233
+ const limit = options.limit ?? 50;
234
+
235
+ let sql = `
236
+ SELECT
237
+ s.id,
238
+ s.file_id,
239
+ s.name,
240
+ s.kind,
241
+ s.signature,
242
+ s.visibility,
243
+ s.line_start,
244
+ s.line_end,
245
+ s.line_count,
246
+ f.path AS file_path,
247
+ d.name AS domain_name
248
+ FROM symbols s
249
+ JOIN files f ON s.file_id = f.id
250
+ JOIN domains d ON f.domain_id = d.id
251
+ WHERE s.line_count >= ?
252
+ `;
253
+
254
+ const params: any[] = [minLines];
255
+
256
+ if (options.kind) {
257
+ sql += " AND s.kind = ?";
258
+ params.push(options.kind);
259
+ }
260
+
261
+ if (options.domain) {
262
+ sql += " AND d.name = ?";
263
+ params.push(options.domain);
264
+ }
265
+
266
+ sql += " ORDER BY s.line_count DESC, s.name ASC LIMIT ?";
267
+ params.push(limit);
268
+
269
+ return this.db.query<any, any[]>(sql).all(...params);
270
+ }
271
+
272
+ public getSymbolDetails(
273
+ symbolQuery: string,
274
+ options: { domain?: string; includeDependencies?: boolean } = {}
275
+ ): GetSymbolDetailsResponse | null {
276
+ const trimmed = symbolQuery.trim();
277
+ if (!trimmed) return null;
278
+
279
+ let sql = `
280
+ SELECT
281
+ s.id,
282
+ s.file_id,
283
+ s.name,
284
+ s.kind,
285
+ s.signature,
286
+ s.visibility,
287
+ s.line_start,
288
+ s.line_end,
289
+ s.line_count,
290
+ f.path AS file_path,
291
+ d.name AS domain_name
292
+ FROM symbols s
293
+ JOIN files f ON s.file_id = f.id
294
+ JOIN domains d ON f.domain_id = d.id
295
+ WHERE (s.name = ? OR s.name LIKE ? OR s.name LIKE ?)
296
+ `;
297
+
298
+ const params: any[] = [trimmed, `%::${trimmed}`, `${trimmed}::%`];
299
+
300
+ if (options.domain) {
301
+ sql += " AND d.name = ?";
302
+ params.push(options.domain);
303
+ }
304
+
305
+ sql += " ORDER BY CASE WHEN s.name = ? THEN 1 WHEN s.name LIKE ? THEN 2 ELSE 3 END, s.line_start ASC LIMIT 1";
306
+ params.push(trimmed, `%::${trimmed}`);
307
+
308
+ const row = this.db.query<any, any[]>(sql).get(...params);
309
+ if (!row) return null;
310
+
311
+ // 1. Sibling methods
312
+ let containerName = "";
313
+ if (row.name.includes("::")) {
314
+ containerName = row.name.split("::")[0];
315
+ }
316
+
317
+ let siblingMethods: string[] = [];
318
+ if (containerName) {
319
+ const siblings = this.db
320
+ .query<{ name: string }, [number, string, string]>(
321
+ `SELECT name FROM symbols
322
+ WHERE file_id = ? AND kind = 'method' AND name != ? AND name LIKE ?
323
+ ORDER BY line_start ASC LIMIT 30`
324
+ )
325
+ .all(row.file_id, row.name, `${containerName}::%`);
326
+ siblingMethods = siblings.map((s) => s.name.replace(`${containerName}::`, ""));
327
+ } else if (row.kind === "class" || row.kind === "interface" || row.kind === "trait") {
328
+ const classMethods = this.db
329
+ .query<{ name: string }, [number, string]>(
330
+ `SELECT name FROM symbols
331
+ WHERE file_id = ? AND kind = 'method' AND name LIKE ?
332
+ ORDER BY line_start ASC LIMIT 30`
333
+ )
334
+ .all(row.file_id, `${row.name}::%`);
335
+ siblingMethods = classMethods.map((s) => s.name.replace(`${row.name}::`, ""));
336
+ }
337
+
338
+ // 2. Dependencies Injected (from signature type-hints and constructor)
339
+ const dependenciesInjected = new Set<string>();
340
+ const extractTypeHints = (sig: string) => {
341
+ const paramMatch = sig.match(/\(([^)]*)\)/);
342
+ if (paramMatch && paramMatch[1]) {
343
+ const params = paramMatch[1].split(",");
344
+ const primitiveTypes = new Set([
345
+ "int", "string", "bool", "boolean", "float", "array", "iterable",
346
+ "callable", "void", "mixed", "object", "null", "never", "any", "number", "unknown"
347
+ ]);
348
+ for (const p of params) {
349
+ const parts = p.trim().replace(/^\?/, "").split(/\s+/);
350
+ if (parts.length >= 2) {
351
+ const rawType = parts[0].replace(/\[\]$/, "").replace(/^[\\$]/, "");
352
+ if (rawType && !primitiveTypes.has(rawType.toLowerCase()) && !rawType.startsWith("$")) {
353
+ dependenciesInjected.add(rawType);
354
+ }
355
+ }
356
+ }
357
+ }
358
+ };
359
+
360
+ extractTypeHints(row.signature);
361
+
362
+ // Also check constructor injection if this is a method
363
+ if (containerName) {
364
+ const ctor = this.db
365
+ .query<{ signature: string }, [number, string, string]>(
366
+ "SELECT signature FROM symbols WHERE file_id = ? AND (name = ? OR name LIKE ?) LIMIT 1"
367
+ )
368
+ .get(row.file_id, `${containerName}::__construct`, `%::__construct`);
369
+ if (ctor) {
370
+ extractTypeHints(ctor.signature);
371
+ }
372
+ }
373
+
374
+ // 3. Outbound Calls (inspected from physical source file lines if accessible)
375
+ const outboundCalls = new Set<string>();
376
+ try {
377
+ if (fs.existsSync(row.file_path)) {
378
+ const fileContent = fs.readFileSync(row.file_path, "utf-8");
379
+ const lines = fileContent.split(/\r?\n/);
380
+ const start = Math.max(0, row.line_start - 1);
381
+ const end = Math.min(lines.length, row.line_end);
382
+ const sliceLines = lines.slice(start, end).join("\n");
383
+
384
+ // Match Static invocations: ClassName::methodName(
385
+ const staticCalls = sliceLines.matchAll(/([A-Z][a-zA-Z0-9_]+)::([a-zA-Z0-9_]+)\s*\(/g);
386
+ for (const m of staticCalls) {
387
+ if (m[1] !== "self" && m[1] !== "parent" && m[1] !== "static") {
388
+ outboundCalls.add(`${m[1]}::${m[2]}`);
389
+ }
390
+ }
391
+
392
+ // Match property calls: $this->property->method(
393
+ const propCalls = sliceLines.matchAll(/\$this->([a-zA-Z0-9_]+)->([a-zA-Z0-9_]+)\s*\(/g);
394
+ for (const m of propCalls) {
395
+ // Capitalize property name for clean domain inference (e.g. orderService -> OrderService::cancelOrder)
396
+ const propName = m[1];
397
+ const methodName = m[2];
398
+ const inferredClass = propName.charAt(0).toUpperCase() + propName.slice(1);
399
+ outboundCalls.add(`${inferredClass}::${methodName}`);
400
+ }
401
+ }
402
+ } catch {
403
+ // Graceful fallback if file not physically accessible
404
+ }
405
+
406
+ return {
407
+ symbol: row.name,
408
+ kind: row.kind,
409
+ file_path: row.file_path,
410
+ domain: row.domain_name,
411
+ exact_range: {
412
+ start_line: row.line_start,
413
+ end_line: row.line_end,
414
+ total_lines: row.line_count,
415
+ },
416
+ signature: row.signature,
417
+ visibility: row.visibility,
418
+ dependencies_injected: Array.from(dependenciesInjected),
419
+ outbound_calls: Array.from(outboundCalls),
420
+ sibling_methods: siblingMethods.length > 0 ? siblingMethods : undefined,
421
+ };
422
+ }
423
+
424
+ }
@@ -0,0 +1,242 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type {
3
+ ArchetypeKind,
4
+ DomainCatalogResponse,
5
+ DomainConfig,
6
+ DomainRecord,
7
+ ExtractedDependency,
8
+ ExtractedSymbol,
9
+ FileRecord,
10
+ GetSymbolDetailsResponse,
11
+ GetSymbolImpactResponse,
12
+ InboundCaller,
13
+ SymbolKind,
14
+ SymbolRecord,
15
+ VerticalSliceRecord,
16
+ } from "../../types/index.ts";
17
+
18
+ export type {
19
+ ArchetypeKind,
20
+ DomainCatalogResponse,
21
+ DomainConfig,
22
+ DomainRecord,
23
+ ExtractedDependency,
24
+ ExtractedSymbol,
25
+ FileRecord,
26
+ GetSymbolDetailsResponse,
27
+ GetSymbolImpactResponse,
28
+ InboundCaller,
29
+ SymbolKind,
30
+ SymbolRecord,
31
+ VerticalSliceRecord,
32
+ } from "../../types/index.ts";
33
+ import { DependencyRepository } from "./repositories/dependency.repository.ts";
34
+ import { DomainRepository } from "./repositories/domain.repository.ts";
35
+ import { FileRepository } from "./repositories/file.repository.ts";
36
+ import { MetaRepository } from "./repositories/meta.repository.ts";
37
+ import { SymbolRepository } from "./repositories/symbol.repository.ts";
38
+
39
+ export type DatabaseRepository = SeptumRepository;
40
+
41
+ export class SeptumRepository {
42
+ public readonly domains: DomainRepository;
43
+ public readonly files: FileRepository;
44
+ public readonly symbols: SymbolRepository;
45
+ public readonly dependencies: DependencyRepository;
46
+ public readonly meta: MetaRepository;
47
+
48
+ constructor(private db: Database) {
49
+ this.domains = new DomainRepository(db);
50
+ this.files = new FileRepository(db);
51
+ this.symbols = new SymbolRepository(db);
52
+ this.dependencies = new DependencyRepository(db);
53
+ this.meta = new MetaRepository(db);
54
+ }
55
+
56
+ public runInTransaction<T>(fn: () => T): T {
57
+ return this.db.transaction(fn)();
58
+ }
59
+
60
+ // --- Domain Methods ---
61
+ public upsertDomain(name: string, config: DomainConfig): number {
62
+ return this.domains.upsertDomain(name, config);
63
+ }
64
+
65
+ public getDomainByName(name: string): DomainRecord | null {
66
+ return this.domains.getDomainByName(name);
67
+ }
68
+
69
+ public getAllDomains(): DomainRecord[] {
70
+ return this.domains.getAllDomains();
71
+ }
72
+
73
+ // --- File Methods ---
74
+ public getFileByPath(path: string): FileRecord | null {
75
+ return this.files.getFileByPath(path);
76
+ }
77
+
78
+ public getFilesByDomain(domainId: number): FileRecord[] {
79
+ return this.files.getFilesByDomain(domainId);
80
+ }
81
+
82
+ public upsertFile(
83
+ domainId: number,
84
+ path: string,
85
+ archetype: ArchetypeKind,
86
+ contentHash: string,
87
+ lineCount: number,
88
+ mtimeMs: number = 0,
89
+ sizeBytes: number = 0
90
+ ): number {
91
+ return this.files.upsertFile(
92
+ domainId,
93
+ path,
94
+ archetype,
95
+ contentHash,
96
+ lineCount,
97
+ mtimeMs,
98
+ sizeBytes
99
+ );
100
+ }
101
+
102
+ public updateFileMetadataOnly(id: number, mtimeMs: number, sizeBytes: number): void {
103
+ this.files.updateFileMetadataOnly(id, mtimeMs, sizeBytes);
104
+ }
105
+
106
+ public deleteFilesNotInPaths(domainId: number, validPaths: string[]): number {
107
+ return this.files.deleteFilesNotInPaths(domainId, validPaths);
108
+ }
109
+
110
+ public deleteFile(path: string): void {
111
+ this.files.deleteFile(path);
112
+ }
113
+
114
+ public getDomainCatalog(
115
+ domainName: string,
116
+ archetypeFilter?: string
117
+ ): DomainCatalogResponse | null {
118
+ return this.domains.getDomainCatalog(domainName, archetypeFilter);
119
+ }
120
+
121
+ // --- Symbol Methods ---
122
+ public replaceFileSymbols(fileId: number, symbols: ExtractedSymbol[]): void {
123
+ this.symbols.replaceFileSymbols(fileId, symbols);
124
+ }
125
+
126
+ public getSymbolsByFileId(fileId: number): SymbolRecord[] {
127
+ return this.symbols.getSymbolsByFileId(fileId);
128
+ }
129
+
130
+ public findSymbolsByName(name: string): Array<SymbolRecord & { file_path: string }> {
131
+ return this.symbols.findSymbolsByName(name);
132
+ }
133
+
134
+ public findContainers(
135
+ containerName: string
136
+ ): Array<{ file: FileRecord; symbol?: SymbolRecord }> {
137
+ return this.symbols.findContainers(containerName);
138
+ }
139
+
140
+ public getAllSymbolsWithFiles(): Array<SymbolRecord & { file_path: string }> {
141
+ return this.symbols.getAllSymbolsWithFiles();
142
+ }
143
+
144
+ // --- Dependency & Slice Methods ---
145
+ public replaceFileDependencies(fileId: number, deps: ExtractedDependency[]): void {
146
+ this.dependencies.replaceFileDependencies(fileId, deps);
147
+ }
148
+
149
+ public upsertVerticalSlice(
150
+ slice: Omit<VerticalSliceRecord, "id" | "created_at">
151
+ ): void {
152
+ this.dependencies.upsertVerticalSlice(slice);
153
+ }
154
+
155
+ public findVerticalSlices(searchTerm: string): VerticalSliceRecord[] {
156
+ return this.dependencies.findVerticalSlices(searchTerm);
157
+ }
158
+
159
+ public getAllVerticalSlices(): VerticalSliceRecord[] {
160
+ return this.dependencies.getAllVerticalSlices();
161
+ }
162
+
163
+ public clearVerticalSlices(domainId?: number): void {
164
+ this.dependencies.clearVerticalSlices(domainId);
165
+ }
166
+
167
+ public getAllDependenciesWithDomains(): Array<{
168
+ source_file: string;
169
+ source_domain: string;
170
+ line_number: number;
171
+ target: string;
172
+ statement: string;
173
+ is_external: boolean;
174
+ }> {
175
+ return this.dependencies.getAllDependenciesWithDomains();
176
+ }
177
+
178
+ public findSymbolWithDomain(
179
+ symbolName: string
180
+ ): {
181
+ symbol_name: string;
182
+ kind: string;
183
+ file_path: string;
184
+ domain_name: string;
185
+ } | null {
186
+ return this.symbols.findSymbolWithDomain(symbolName);
187
+ }
188
+
189
+ public findSymbolWithMembers(symbolName: string): {
190
+ symbol_name: string;
191
+ kind: string;
192
+ signature: string;
193
+ file_path: string;
194
+ domain_name: string;
195
+ methods: string[];
196
+ properties: string[];
197
+ } | null {
198
+ return this.symbols.findSymbolWithMembers(symbolName);
199
+ }
200
+
201
+ public getFileWithDomain(filePath: string): {
202
+ file_id: number;
203
+ file_path: string;
204
+ domain_name: string;
205
+ } | null {
206
+ return this.symbols.getFileWithDomain(filePath);
207
+ }
208
+
209
+ public getHotspotSymbols(options: {
210
+ minLines?: number;
211
+ kind?: SymbolKind;
212
+ domain?: string;
213
+ limit?: number;
214
+ } = {}): Array<SymbolRecord & { file_path: string; domain_name: string }> {
215
+ return this.symbols.getHotspotSymbols(options);
216
+ }
217
+
218
+ public getSymbolDetails(
219
+ symbolQuery: string,
220
+ options: { domain?: string; includeDependencies?: boolean } = {}
221
+ ): GetSymbolDetailsResponse | null {
222
+ return this.symbols.getSymbolDetails(symbolQuery, options);
223
+ }
224
+
225
+ public getSymbolImpact(symbolName: string): GetSymbolImpactResponse {
226
+ return this.dependencies.getSymbolImpact(symbolName);
227
+ }
228
+
229
+ // --- Metadata Methods ---
230
+ public getMeta(key: string): string | null {
231
+ return this.meta.getMeta(key);
232
+ }
233
+
234
+ public setMeta(key: string, value: string): void {
235
+ this.meta.setMeta(key, value);
236
+ }
237
+
238
+ public getAllMeta(): Record<string, string> {
239
+ return this.meta.getAllMeta();
240
+ }
241
+ }
242
+