@aicodepulse/core 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.
package/dist/index.js ADDED
@@ -0,0 +1,1344 @@
1
+ // src/types.ts
2
+ var DEFAULT_CONFIG = {
3
+ exclude: ["node_modules", ".git", "dist", "build", ".next", "__pycache__", "*.min.js"],
4
+ maxFileSizeBytes: 512 * 1024,
5
+ defaultBudgetTokens: 4e3,
6
+ layerWeights: {
7
+ repo_overview: 0.1,
8
+ directory_map: 0.2,
9
+ symbol_table: 0.45,
10
+ import_graph: 0.15,
11
+ focus: 0.1
12
+ }
13
+ };
14
+
15
+ // src/storage/Database.ts
16
+ import BetterSqlite3 from "better-sqlite3";
17
+ var SCHEMA_VERSION = 1;
18
+ var SCHEMA_SQL = `
19
+ CREATE TABLE IF NOT EXISTS index_meta (
20
+ key TEXT PRIMARY KEY,
21
+ value TEXT NOT NULL
22
+ );
23
+
24
+ CREATE TABLE IF NOT EXISTS file_records (
25
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26
+ path TEXT NOT NULL UNIQUE,
27
+ language TEXT NOT NULL,
28
+ content_hash TEXT NOT NULL,
29
+ size_bytes INTEGER NOT NULL,
30
+ lines_total INTEGER NOT NULL,
31
+ indexed_at INTEGER NOT NULL,
32
+ is_deleted INTEGER NOT NULL DEFAULT 0
33
+ );
34
+ CREATE INDEX IF NOT EXISTS idx_file_language ON file_records(language);
35
+ CREATE INDEX IF NOT EXISTS idx_file_deleted ON file_records(is_deleted);
36
+
37
+ CREATE TABLE IF NOT EXISTS symbols (
38
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ file_id INTEGER NOT NULL REFERENCES file_records(id) ON DELETE CASCADE,
40
+ file_path TEXT NOT NULL,
41
+ name TEXT NOT NULL,
42
+ kind TEXT NOT NULL,
43
+ line INTEGER NOT NULL,
44
+ end_line INTEGER NOT NULL,
45
+ is_exported INTEGER NOT NULL DEFAULT 0,
46
+ signature TEXT,
47
+ doc_comment TEXT,
48
+ parent_name TEXT
49
+ );
50
+ CREATE INDEX IF NOT EXISTS idx_symbol_file ON symbols(file_id);
51
+ CREATE INDEX IF NOT EXISTS idx_symbol_name ON symbols(name);
52
+ CREATE INDEX IF NOT EXISTS idx_symbol_exported ON symbols(is_exported);
53
+
54
+ CREATE TABLE IF NOT EXISTS import_edges (
55
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ from_file_id INTEGER NOT NULL REFERENCES file_records(id) ON DELETE CASCADE,
57
+ from_path TEXT NOT NULL,
58
+ to_path TEXT,
59
+ to_package TEXT,
60
+ imported_names TEXT NOT NULL,
61
+ is_type_only INTEGER NOT NULL DEFAULT 0
62
+ );
63
+ CREATE INDEX IF NOT EXISTS idx_import_from ON import_edges(from_file_id);
64
+ CREATE INDEX IF NOT EXISTS idx_import_to ON import_edges(to_path);
65
+ `;
66
+ function openDatabase(dbPath) {
67
+ const db = new BetterSqlite3(dbPath);
68
+ db.pragma("journal_mode = WAL");
69
+ db.pragma("foreign_keys = ON");
70
+ runMigrations(db);
71
+ return db;
72
+ }
73
+ function runMigrations(db) {
74
+ db.exec(SCHEMA_SQL);
75
+ const existing = db.prepare("SELECT value FROM index_meta WHERE key = 'schema_version'").get();
76
+ if (!existing) {
77
+ db.prepare("INSERT INTO index_meta (key, value) VALUES ('schema_version', ?)").run(String(SCHEMA_VERSION));
78
+ }
79
+ }
80
+
81
+ // src/storage/FileRepository.ts
82
+ function rowToFile(row) {
83
+ return {
84
+ id: row.id,
85
+ path: row.path,
86
+ language: row.language,
87
+ contentHash: row.content_hash,
88
+ sizeBytes: row.size_bytes,
89
+ linesTotal: row.lines_total,
90
+ indexedAt: row.indexed_at,
91
+ isDeleted: Boolean(row.is_deleted)
92
+ };
93
+ }
94
+ var FileRepository = class {
95
+ constructor(db) {
96
+ this.db = db;
97
+ }
98
+ db;
99
+ upsert(file) {
100
+ const result = this.db.prepare(`
101
+ INSERT INTO file_records (path, language, content_hash, size_bytes, lines_total, indexed_at, is_deleted)
102
+ VALUES (@path, @language, @contentHash, @sizeBytes, @linesTotal, @indexedAt, @isDeleted)
103
+ ON CONFLICT(path) DO UPDATE SET
104
+ language = excluded.language,
105
+ content_hash = excluded.content_hash,
106
+ size_bytes = excluded.size_bytes,
107
+ lines_total = excluded.lines_total,
108
+ indexed_at = excluded.indexed_at,
109
+ is_deleted = excluded.is_deleted
110
+ `).run({
111
+ path: file.path,
112
+ language: file.language,
113
+ contentHash: file.contentHash,
114
+ sizeBytes: file.sizeBytes,
115
+ linesTotal: file.linesTotal,
116
+ indexedAt: file.indexedAt,
117
+ isDeleted: file.isDeleted ? 1 : 0
118
+ });
119
+ return result.lastInsertRowid;
120
+ }
121
+ getByPath(path) {
122
+ const row = this.db.prepare("SELECT * FROM file_records WHERE path = ?").get(path);
123
+ return row ? rowToFile(row) : null;
124
+ }
125
+ markDeleted(path) {
126
+ this.db.prepare("UPDATE file_records SET is_deleted = 1 WHERE path = ?").run(path);
127
+ }
128
+ getAllActive() {
129
+ const rows = this.db.prepare("SELECT * FROM file_records WHERE is_deleted = 0 ORDER BY path").all();
130
+ return rows.map(rowToFile);
131
+ }
132
+ getLanguageStats() {
133
+ return this.db.prepare(`
134
+ SELECT language, COUNT(*) as count
135
+ FROM file_records WHERE is_deleted = 0
136
+ GROUP BY language ORDER BY count DESC
137
+ `).all();
138
+ }
139
+ };
140
+
141
+ // src/storage/SymbolRepository.ts
142
+ function rowToSymbol(row) {
143
+ return {
144
+ id: row.id,
145
+ fileId: row.file_id,
146
+ filePath: row.file_path,
147
+ name: row.name,
148
+ kind: row.kind,
149
+ line: row.line,
150
+ endLine: row.end_line,
151
+ isExported: Boolean(row.is_exported),
152
+ signature: row.signature,
153
+ docComment: row.doc_comment,
154
+ parentName: row.parent_name
155
+ };
156
+ }
157
+ function rowToImport(row) {
158
+ return {
159
+ id: row.id,
160
+ fromFileId: row.from_file_id,
161
+ fromPath: row.from_path,
162
+ toPath: row.to_path,
163
+ toPackage: row.to_package,
164
+ importedNames: JSON.parse(row.imported_names),
165
+ isTypeOnly: Boolean(row.is_type_only)
166
+ };
167
+ }
168
+ var SymbolRepository = class {
169
+ constructor(db) {
170
+ this.db = db;
171
+ }
172
+ db;
173
+ insertSymbols(fileId, symbols) {
174
+ const stmt = this.db.prepare(`
175
+ INSERT INTO symbols (file_id, file_path, name, kind, line, end_line, is_exported, signature, doc_comment, parent_name)
176
+ VALUES (@fileId, @filePath, @name, @kind, @line, @endLine, @isExported, @signature, @docComment, @parentName)
177
+ `);
178
+ for (const s of symbols) {
179
+ stmt.run({
180
+ fileId: s.fileId,
181
+ filePath: s.filePath,
182
+ name: s.name,
183
+ kind: s.kind,
184
+ line: s.line,
185
+ endLine: s.endLine,
186
+ isExported: s.isExported ? 1 : 0,
187
+ signature: s.signature,
188
+ docComment: s.docComment,
189
+ parentName: s.parentName
190
+ });
191
+ }
192
+ }
193
+ insertImports(fileId, edges) {
194
+ const stmt = this.db.prepare(`
195
+ INSERT INTO import_edges (from_file_id, from_path, to_path, to_package, imported_names, is_type_only)
196
+ VALUES (@fromFileId, @fromPath, @toPath, @toPackage, @importedNames, @isTypeOnly)
197
+ `);
198
+ for (const e of edges) {
199
+ stmt.run({
200
+ fromFileId: e.fromFileId,
201
+ fromPath: e.fromPath,
202
+ toPath: e.toPath,
203
+ toPackage: e.toPackage,
204
+ importedNames: JSON.stringify(e.importedNames),
205
+ isTypeOnly: e.isTypeOnly ? 1 : 0
206
+ });
207
+ }
208
+ }
209
+ deleteByFileId(fileId) {
210
+ this.db.prepare("DELETE FROM symbols WHERE file_id = ?").run(fileId);
211
+ this.db.prepare("DELETE FROM import_edges WHERE from_file_id = ?").run(fileId);
212
+ }
213
+ getExportedByFile(filePath) {
214
+ const rows = this.db.prepare(
215
+ "SELECT * FROM symbols WHERE file_path = ? AND is_exported = 1 ORDER BY line"
216
+ ).all(filePath);
217
+ return rows.map(rowToSymbol);
218
+ }
219
+ getAllExported() {
220
+ const rows = this.db.prepare(
221
+ "SELECT * FROM symbols WHERE is_exported = 1 ORDER BY file_path, line"
222
+ ).all();
223
+ return rows.map(rowToSymbol);
224
+ }
225
+ search(query, limit = 20) {
226
+ const rows = this.db.prepare(
227
+ "SELECT * FROM symbols WHERE name LIKE ? AND is_exported = 1 LIMIT ?"
228
+ ).all(`%${query}%`, limit);
229
+ return rows.map(rowToSymbol);
230
+ }
231
+ getImportsFrom(filePath) {
232
+ const rows = this.db.prepare(
233
+ "SELECT * FROM import_edges WHERE from_path = ?"
234
+ ).all(filePath);
235
+ return rows.map(rowToImport);
236
+ }
237
+ getImportersOf(filePath) {
238
+ const rows = this.db.prepare(
239
+ "SELECT DISTINCT from_path FROM import_edges WHERE to_path = ?"
240
+ ).all(filePath);
241
+ return rows.map((r) => r.from_path);
242
+ }
243
+ getTopImportedFiles(limit = 20) {
244
+ return this.db.prepare(`
245
+ SELECT to_path as path, COUNT(*) as importerCount
246
+ FROM import_edges WHERE to_path IS NOT NULL
247
+ GROUP BY to_path ORDER BY importerCount DESC LIMIT ?
248
+ `).all(limit);
249
+ }
250
+ };
251
+
252
+ // src/storage/MetaRepository.ts
253
+ var MetaRepository = class {
254
+ constructor(db) {
255
+ this.db = db;
256
+ }
257
+ db;
258
+ get(key) {
259
+ const row = this.db.prepare("SELECT value FROM index_meta WHERE key = ?").get(key);
260
+ return row?.value ?? null;
261
+ }
262
+ set(key, value) {
263
+ this.db.prepare("INSERT OR REPLACE INTO index_meta (key, value) VALUES (?, ?)").run(key, value);
264
+ }
265
+ getIndexMeta() {
266
+ const fileCount = this.db.prepare("SELECT COUNT(*) as c FROM file_records WHERE is_deleted = 0").get().c;
267
+ const symbolCount = this.db.prepare("SELECT COUNT(*) as c FROM symbols").get().c;
268
+ return {
269
+ schemaVersion: Number(this.get("schema_version") ?? 1),
270
+ lastIndexedCommit: this.get("last_indexed_commit") ?? "",
271
+ lastIndexedAt: Number(this.get("last_indexed_at") ?? 0),
272
+ repoRoot: this.get("repo_root") ?? "",
273
+ totalFiles: fileCount,
274
+ totalSymbols: symbolCount
275
+ };
276
+ }
277
+ saveIndexMeta(commit, repoRoot) {
278
+ this.set("last_indexed_commit", commit);
279
+ this.set("last_indexed_at", String(Date.now()));
280
+ this.set("repo_root", repoRoot);
281
+ }
282
+ };
283
+
284
+ // src/indexer/Indexer.ts
285
+ import Parser from "tree-sitter";
286
+ import { readFileSync as readFileSync2, statSync } from "fs";
287
+ import { join as join2 } from "path";
288
+
289
+ // src/parser/ParserRegistry.ts
290
+ import { createRequire } from "module";
291
+ var req = createRequire(import.meta.url);
292
+ var registry = null;
293
+ function buildRegistry() {
294
+ const map = /* @__PURE__ */ new Map();
295
+ const entries = [
296
+ [[".js", ".mjs", ".cjs"], "javascript", "tree-sitter-javascript"],
297
+ [[".ts", ".tsx", ".mts", ".cts"], "typescript", "tree-sitter-typescript", (m) => m.typescript],
298
+ [[".py", ".pyw"], "python", "tree-sitter-python"],
299
+ [[".go"], "go", "tree-sitter-go"],
300
+ [[".rs"], "rust", "tree-sitter-rust"],
301
+ [[".java"], "java", "tree-sitter-java"],
302
+ [[".c", ".h"], "c", "tree-sitter-c"],
303
+ [[".cpp", ".cc", ".cxx", ".hpp", ".hxx"], "cpp", "tree-sitter-cpp"],
304
+ [[".cs"], "csharp", "tree-sitter-c-sharp"],
305
+ [[".rb"], "ruby", "tree-sitter-ruby"],
306
+ [[".php"], "php", "tree-sitter-php", (m) => m.php],
307
+ [[".sh", ".bash"], "bash", "tree-sitter-bash"],
308
+ [[".kt", ".kts"], "kotlin", "tree-sitter-kotlin"],
309
+ [[".swift"], "swift", "tree-sitter-swift"]
310
+ ];
311
+ for (const [exts, name, pkg, extract] of entries) {
312
+ try {
313
+ const mod = req(pkg);
314
+ const lang = extract ? extract(mod) : mod;
315
+ const config = { language: lang, extensions: exts, name };
316
+ for (const ext of exts) map.set(ext, config);
317
+ } catch {
318
+ }
319
+ }
320
+ return map;
321
+ }
322
+ function getRegistry() {
323
+ if (!registry) registry = buildRegistry();
324
+ return registry;
325
+ }
326
+ function getLanguageForExtension(ext) {
327
+ return getRegistry().get(ext) ?? null;
328
+ }
329
+ function extensionFromPath(filePath) {
330
+ const dot = filePath.lastIndexOf(".");
331
+ return dot === -1 ? "" : filePath.slice(dot).toLowerCase();
332
+ }
333
+
334
+ // src/parser/languages/typescript.ts
335
+ var typescriptQueries = {
336
+ symbols: `
337
+ (export_statement
338
+ (function_declaration name: (identifier) @name) @node
339
+ (#set! kind "function"))
340
+
341
+ (export_statement
342
+ (class_declaration name: (type_identifier) @name) @node
343
+ (#set! kind "class"))
344
+
345
+ (export_statement
346
+ (interface_declaration name: (type_identifier) @name) @node
347
+ (#set! kind "interface"))
348
+
349
+ (export_statement
350
+ (type_alias_declaration name: (type_identifier) @name) @node
351
+ (#set! kind "type"))
352
+
353
+ (export_statement
354
+ (enum_declaration name: (identifier) @name) @node
355
+ (#set! kind "enum"))
356
+
357
+ (export_statement
358
+ (lexical_declaration
359
+ (variable_declarator name: (identifier) @name)) @node
360
+ (#set! kind "variable"))
361
+
362
+ (export_statement
363
+ declaration: (function_declaration name: (identifier) @name) @node
364
+ (#set! kind "function"))
365
+
366
+ (export_default_declaration
367
+ (function_declaration name: (identifier) @name) @node
368
+ (#set! kind "function"))
369
+
370
+ (export_default_declaration
371
+ (class_declaration name: (type_identifier) @name) @node
372
+ (#set! kind "class"))
373
+ `,
374
+ imports: `
375
+ (import_statement
376
+ source: (string (string_fragment) @source)
377
+ (import_clause
378
+ (named_imports
379
+ (import_specifier name: (identifier) @specifier)))) @node
380
+
381
+ (import_statement
382
+ source: (string (string_fragment) @source)) @node
383
+ `
384
+ };
385
+ var javascriptQueries = typescriptQueries;
386
+
387
+ // src/parser/languages/python.ts
388
+ var pythonQueries = {
389
+ symbols: `
390
+ (function_definition name: (identifier) @name) @node
391
+
392
+ (class_definition name: (identifier) @name) @node
393
+
394
+ (expression_statement
395
+ (assignment
396
+ left: (identifier) @name)) @node
397
+ `,
398
+ imports: `
399
+ (import_statement
400
+ name: (dotted_name) @source) @node
401
+
402
+ (import_from_statement
403
+ module_name: (dotted_name) @source
404
+ name: (import_from_as_names
405
+ (dotted_name) @specifier)) @node
406
+
407
+ (import_from_statement
408
+ module_name: (dotted_name) @source) @node
409
+ `
410
+ };
411
+
412
+ // src/parser/languages/go.ts
413
+ var goQueries = {
414
+ symbols: `
415
+ (function_declaration name: (identifier) @name) @node
416
+
417
+ (method_declaration name: (field_identifier) @name) @node
418
+
419
+ (type_declaration
420
+ (type_spec name: (type_identifier) @name)) @node
421
+
422
+ (var_declaration
423
+ (var_spec name: (identifier) @name)) @node
424
+
425
+ (const_declaration
426
+ (const_spec name: (identifier) @name)) @node
427
+ `,
428
+ imports: `
429
+ (import_spec path: (interpreted_string_literal) @source) @node
430
+ `
431
+ };
432
+
433
+ // src/parser/languages/rust.ts
434
+ var rustQueries = {
435
+ symbols: `
436
+ (function_item name: (identifier) @name) @node
437
+
438
+ (struct_item name: (type_identifier) @name) @node
439
+
440
+ (enum_item name: (type_identifier) @name) @node
441
+
442
+ (trait_item name: (type_identifier) @name) @node
443
+
444
+ (impl_item type: (type_identifier) @name) @node
445
+
446
+ (type_item name: (type_identifier) @name) @node
447
+
448
+ (const_item name: (identifier) @name) @node
449
+ `,
450
+ imports: `
451
+ (use_declaration
452
+ argument: (scoped_identifier path: (identifier) @source)) @node
453
+
454
+ (extern_crate_declaration name: (identifier) @source) @node
455
+ `
456
+ };
457
+
458
+ // src/parser/languages/java.ts
459
+ var javaQueries = {
460
+ symbols: `
461
+ (class_declaration name: (identifier) @name) @node
462
+
463
+ (interface_declaration name: (identifier) @name) @node
464
+
465
+ (enum_declaration name: (identifier) @name) @node
466
+
467
+ (method_declaration name: (identifier) @name) @node
468
+
469
+ (field_declaration
470
+ declarator: (variable_declarator name: (identifier) @name)) @node
471
+ `,
472
+ imports: `
473
+ (import_declaration (scoped_identifier) @source) @node
474
+ `
475
+ };
476
+
477
+ // src/parser/languages/c.ts
478
+ var cQueries = {
479
+ symbols: `
480
+ (function_definition
481
+ declarator: (function_declarator
482
+ declarator: (identifier) @name)) @node
483
+
484
+ (declaration
485
+ declarator: (function_declarator
486
+ declarator: (identifier) @name)) @node
487
+
488
+ (struct_specifier name: (type_identifier) @name) @node
489
+
490
+ (enum_specifier name: (type_identifier) @name) @node
491
+
492
+ (type_definition
493
+ declarator: (type_identifier) @name) @node
494
+ `,
495
+ imports: `
496
+ (preproc_include path: (string_literal) @source) @node
497
+ (preproc_include path: (system_lib_string) @source) @node
498
+ `
499
+ };
500
+ var cppQueries = {
501
+ symbols: `
502
+ (function_definition
503
+ declarator: (function_declarator
504
+ declarator: (identifier) @name)) @node
505
+
506
+ (function_definition
507
+ declarator: (function_declarator
508
+ declarator: (qualified_identifier name: (identifier) @name))) @node
509
+
510
+ (class_specifier name: (type_identifier) @name) @node
511
+
512
+ (struct_specifier name: (type_identifier) @name) @node
513
+
514
+ (enum_specifier name: (type_identifier) @name) @node
515
+
516
+ (namespace_definition name: (identifier) @name) @node
517
+ `,
518
+ imports: `
519
+ (preproc_include path: (string_literal) @source) @node
520
+ (preproc_include path: (system_lib_string) @source) @node
521
+ `
522
+ };
523
+
524
+ // src/parser/languages/csharp.ts
525
+ var csharpQueries = {
526
+ symbols: `
527
+ (class_declaration name: (identifier) @name) @node
528
+
529
+ (interface_declaration name: (identifier) @name) @node
530
+
531
+ (enum_declaration name: (identifier) @name) @node
532
+
533
+ (struct_declaration name: (identifier) @name) @node
534
+
535
+ (method_declaration name: (identifier) @name) @node
536
+
537
+ (property_declaration name: (identifier) @name) @node
538
+
539
+ (record_declaration name: (identifier) @name) @node
540
+ `,
541
+ imports: `
542
+ (using_directive (qualified_name) @source) @node
543
+ (using_directive (identifier) @source) @node
544
+ `
545
+ };
546
+
547
+ // src/parser/languages/ruby.ts
548
+ var rubyQueries = {
549
+ symbols: `
550
+ (method name: (identifier) @name) @node
551
+
552
+ (singleton_method name: (identifier) @name) @node
553
+
554
+ (class name: (constant) @name) @node
555
+
556
+ (module name: (constant) @name) @node
557
+ `,
558
+ imports: `
559
+ (call
560
+ method: (identifier) @method
561
+ arguments: (argument_list (string (string_content) @source))
562
+ (#match? @method "^(require|require_relative|load)$")) @node
563
+ `
564
+ };
565
+
566
+ // src/parser/languages/php.ts
567
+ var phpQueries = {
568
+ symbols: `
569
+ (function_definition name: (name) @name) @node
570
+
571
+ (class_declaration name: (name) @name) @node
572
+
573
+ (interface_declaration name: (name) @name) @node
574
+
575
+ (trait_declaration name: (name) @name) @node
576
+
577
+ (enum_declaration name: (name) @name) @node
578
+
579
+ (method_declaration name: (name) @name) @node
580
+ `,
581
+ imports: `
582
+ (namespace_use_declaration
583
+ (namespace_use_clause (qualified_name) @source)) @node
584
+
585
+ (require_expression (string (string_value) @source)) @node
586
+ (include_expression (string (string_value) @source)) @node
587
+ `
588
+ };
589
+
590
+ // src/parser/languages/bash.ts
591
+ var bashQueries = {
592
+ symbols: `
593
+ (function_definition name: (word) @name) @node
594
+ `,
595
+ imports: `
596
+ (command
597
+ name: (command_name (word) @cmd)
598
+ argument: (word) @source
599
+ (#match? @cmd "^(source|\\.)$")) @node
600
+ `
601
+ };
602
+
603
+ // src/parser/languages/kotlin.ts
604
+ var kotlinQueries = {
605
+ symbols: `
606
+ (function_declaration (simple_identifier) @name) @node
607
+
608
+ (class_declaration (type_identifier) @name) @node
609
+
610
+ (object_declaration (type_identifier) @name) @node
611
+
612
+ (interface_declaration (type_identifier) @name) @node
613
+ `,
614
+ imports: `
615
+ (import_header (identifier) @source) @node
616
+ `
617
+ };
618
+
619
+ // src/parser/languages/swift.ts
620
+ var swiftQueries = {
621
+ symbols: `
622
+ (function_declaration name: (simple_identifier) @name) @node
623
+
624
+ (class_declaration name: (type_identifier) @name) @node
625
+
626
+ (struct_declaration name: (type_identifier) @name) @node
627
+
628
+ (enum_declaration name: (type_identifier) @name) @node
629
+
630
+ (protocol_declaration name: (type_identifier) @name) @node
631
+
632
+ (typealias_declaration name: (type_identifier) @name) @node
633
+ `,
634
+ imports: `
635
+ (import_declaration (identifier) @source) @node
636
+ `
637
+ };
638
+
639
+ // src/parser/SymbolExtractor.ts
640
+ var LANGUAGE_QUERIES = {
641
+ typescript: typescriptQueries,
642
+ javascript: javascriptQueries,
643
+ python: pythonQueries,
644
+ go: goQueries,
645
+ rust: rustQueries,
646
+ java: javaQueries,
647
+ c: cQueries,
648
+ cpp: cppQueries,
649
+ csharp: csharpQueries,
650
+ ruby: rubyQueries,
651
+ php: phpQueries,
652
+ bash: bashQueries,
653
+ kotlin: kotlinQueries,
654
+ swift: swiftQueries
655
+ };
656
+ var KIND_DEFAULTS = {
657
+ function_definition: "function",
658
+ function_declaration: "function",
659
+ function_item: "function",
660
+ method_declaration: "method",
661
+ method_definition: "method",
662
+ class_declaration: "class",
663
+ class_definition: "class",
664
+ class_specifier: "class",
665
+ class_item: "class",
666
+ interface_declaration: "interface",
667
+ interface_item: "interface",
668
+ type_alias_declaration: "type",
669
+ type_item: "type",
670
+ type_definition: "type",
671
+ enum_declaration: "enum",
672
+ enum_item: "enum",
673
+ struct_item: "class",
674
+ struct_specifier: "class",
675
+ trait_item: "interface",
676
+ trait_declaration: "interface",
677
+ export_statement: "variable",
678
+ lexical_declaration: "variable",
679
+ var_declaration: "variable",
680
+ const_item: "constant",
681
+ const_declaration: "constant"
682
+ };
683
+ function inferKind(node) {
684
+ const type = node.type;
685
+ if (KIND_DEFAULTS[type]) return KIND_DEFAULTS[type];
686
+ for (const child of node.children) {
687
+ if (KIND_DEFAULTS[child.type]) return KIND_DEFAULTS[child.type];
688
+ }
689
+ return "variable";
690
+ }
691
+ function extractDocComment(node) {
692
+ const prev = node.previousNamedSibling;
693
+ if (!prev) return null;
694
+ const text = prev.text;
695
+ if (text.startsWith("/**") || text.startsWith("///") || text.startsWith("#")) {
696
+ const line = text.split("\n")[0].replace(/^[/*#\s]+/, "").trim();
697
+ return line || null;
698
+ }
699
+ return null;
700
+ }
701
+ function extractSignature(node, name) {
702
+ const params = node.childForFieldName("parameters") ?? node.children.find((c) => c.type === "formal_parameters" || c.type === "parameters");
703
+ if (!params) return null;
704
+ const retType = node.childForFieldName("return_type") ?? node.childForFieldName("type");
705
+ const sig = retType ? `${params.text}: ${retType.text}` : params.text;
706
+ return sig.length > 80 ? sig.slice(0, 77) + "..." : sig;
707
+ }
708
+ function extractSymbols(tree, filePath, fileId, language, source) {
709
+ const queries = LANGUAGE_QUERIES[language];
710
+ if (!queries) return [];
711
+ const symbols = [];
712
+ const lines = source.split("\n");
713
+ const isExportedLang = ["typescript", "javascript"].includes(language);
714
+ function walk(node, depth = 0) {
715
+ const nodeType = node.type;
716
+ const nameNode = node.childForFieldName("name");
717
+ if (nameNode && KIND_DEFAULTS[nodeType]) {
718
+ const name = nameNode.text;
719
+ const isExported = isExportedLang ? node.parent?.type === "export_statement" || node.parent?.type === "export_default_declaration" : name.length > 0 && name[0] === name[0].toUpperCase();
720
+ if (name && name.length > 0) {
721
+ symbols.push({
722
+ fileId,
723
+ filePath,
724
+ name,
725
+ kind: inferKind(node),
726
+ line: node.startPosition.row + 1,
727
+ endLine: node.endPosition.row + 1,
728
+ isExported,
729
+ signature: extractSignature(node, name),
730
+ docComment: extractDocComment(node),
731
+ parentName: null
732
+ });
733
+ }
734
+ }
735
+ for (const child of node.children) {
736
+ if (depth < 6) walk(child, depth + 1);
737
+ }
738
+ }
739
+ walk(tree.rootNode);
740
+ return symbols;
741
+ }
742
+
743
+ // src/parser/ImportExtractor.ts
744
+ import { resolve, dirname, extname } from "path";
745
+ function resolveImportPath(source, fromFile) {
746
+ if (source.startsWith(".")) {
747
+ const dir = dirname(fromFile);
748
+ let resolved = resolve(dir, source);
749
+ if (!extname(resolved)) resolved += ".ts";
750
+ return { toPath: resolved, toPackage: null };
751
+ }
752
+ const parts = source.split("/");
753
+ const pkg = source.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
754
+ return { toPath: null, toPackage: pkg };
755
+ }
756
+ function walkForImports(node, filePath, fileId, language, edges, depth = 0) {
757
+ if (depth > 4) return;
758
+ const type = node.type;
759
+ if (type === "import_statement") {
760
+ const sourceNode = node.children.find((c) => c.type === "string");
761
+ if (sourceNode) {
762
+ const raw = sourceNode.text.replace(/['"]/g, "");
763
+ const { toPath, toPackage } = resolveImportPath(raw, filePath);
764
+ const isTypeOnly = node.children.some((c) => c.text === "type");
765
+ const names = [];
766
+ const namedImports = node.children.find((c) => c.type === "import_clause");
767
+ if (namedImports) {
768
+ const named = namedImports.children.find((c) => c.type === "named_imports");
769
+ if (named) {
770
+ for (const spec of named.children) {
771
+ if (spec.type === "import_specifier") {
772
+ const n = spec.childForFieldName("name");
773
+ if (n) names.push(n.text);
774
+ }
775
+ }
776
+ }
777
+ if (names.length === 0) names.push("*");
778
+ }
779
+ edges.push({ fromFileId: fileId, fromPath: filePath, toPath, toPackage, importedNames: names, isTypeOnly });
780
+ }
781
+ }
782
+ if (language === "python") {
783
+ if (type === "import_statement") {
784
+ const name = node.children.find((c) => c.type === "dotted_name");
785
+ if (name) {
786
+ const mod = name.text.replace(/\./g, "/");
787
+ edges.push({ fromFileId: fileId, fromPath: filePath, toPath: null, toPackage: mod, importedNames: ["*"], isTypeOnly: false });
788
+ }
789
+ }
790
+ if (type === "import_from_statement") {
791
+ const mod = node.childForFieldName("module_name")?.text ?? "";
792
+ const names = [];
793
+ const nameList = node.children.find((c) => c.type === "import_from_as_names");
794
+ if (nameList) {
795
+ for (const c of nameList.children) {
796
+ if (c.type === "dotted_name") names.push(c.text);
797
+ }
798
+ }
799
+ if (names.length === 0) names.push("*");
800
+ const isRelative = mod.startsWith(".");
801
+ const { toPath, toPackage } = isRelative ? resolveImportPath(mod, filePath) : { toPath: null, toPackage: mod };
802
+ edges.push({ fromFileId: fileId, fromPath: filePath, toPath, toPackage, importedNames: names, isTypeOnly: false });
803
+ }
804
+ }
805
+ if (language === "go" && type === "import_spec") {
806
+ const pathNode = node.childForFieldName("path");
807
+ if (pathNode) {
808
+ const raw = pathNode.text.replace(/"/g, "");
809
+ edges.push({ fromFileId: fileId, fromPath: filePath, toPath: null, toPackage: raw, importedNames: ["*"], isTypeOnly: false });
810
+ }
811
+ }
812
+ if (language === "rust" && type === "use_declaration") {
813
+ const arg = node.children.find((c) => c.type !== "use" && c.type !== ";");
814
+ if (arg) {
815
+ edges.push({ fromFileId: fileId, fromPath: filePath, toPath: null, toPackage: arg.text, importedNames: ["*"], isTypeOnly: false });
816
+ }
817
+ }
818
+ for (const child of node.children) {
819
+ walkForImports(child, filePath, fileId, language, edges, depth + 1);
820
+ }
821
+ }
822
+ function extractImports(tree, filePath, fileId, language) {
823
+ const edges = [];
824
+ walkForImports(tree.rootNode, filePath, fileId, language, edges);
825
+ return edges;
826
+ }
827
+
828
+ // src/indexer/HashUtil.ts
829
+ import { createHash } from "crypto";
830
+ function hashContent(content) {
831
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
832
+ }
833
+
834
+ // src/indexer/FileWalker.ts
835
+ import { glob } from "glob";
836
+ import Ignore from "ignore";
837
+ import { readFileSync, existsSync } from "fs";
838
+ import { join } from "path";
839
+ async function walkFiles(repoRoot, config) {
840
+ const ig = Ignore.default();
841
+ const gitignorePath = join(repoRoot, ".gitignore");
842
+ if (existsSync(gitignorePath)) {
843
+ ig.add(readFileSync(gitignorePath, "utf8"));
844
+ }
845
+ ig.add(config.exclude);
846
+ const pattern = "**/*";
847
+ const files = await glob(pattern, {
848
+ cwd: repoRoot,
849
+ nodir: true,
850
+ dot: false,
851
+ absolute: false
852
+ });
853
+ const result = [];
854
+ for (const rel of files) {
855
+ if (ig.ignores(rel)) continue;
856
+ result.push({
857
+ absolutePath: join(repoRoot, rel),
858
+ relativePath: rel.replace(/\\/g, "/")
859
+ });
860
+ }
861
+ return result;
862
+ }
863
+
864
+ // src/indexer/GitDiff.ts
865
+ import { execSync } from "child_process";
866
+ function getHeadCommit(repoRoot) {
867
+ try {
868
+ return execSync("git rev-parse HEAD", { cwd: repoRoot, encoding: "utf8" }).trim();
869
+ } catch {
870
+ return "";
871
+ }
872
+ }
873
+ function getChangedFiles(repoRoot, sinceCommit) {
874
+ if (!sinceCommit) return null;
875
+ try {
876
+ const output = execSync(
877
+ `git diff --name-status ${sinceCommit} HEAD`,
878
+ { cwd: repoRoot, encoding: "utf8" }
879
+ );
880
+ const changes = [];
881
+ for (const line of output.trim().split("\n")) {
882
+ if (!line) continue;
883
+ const parts = line.split(" ");
884
+ const rawStatus = parts[0].charAt(0);
885
+ const status = ["M", "A", "D", "R"].includes(rawStatus) ? rawStatus : "M";
886
+ if (status === "R") {
887
+ changes.push({ status: "D", path: parts[1] });
888
+ changes.push({ status: "A", path: parts[2] });
889
+ } else {
890
+ changes.push({ status, path: parts[1] });
891
+ }
892
+ }
893
+ return changes;
894
+ } catch {
895
+ return null;
896
+ }
897
+ }
898
+
899
+ // src/indexer/Indexer.ts
900
+ var Indexer = class {
901
+ constructor(db, repoRoot, config) {
902
+ this.db = db;
903
+ this.repoRoot = repoRoot;
904
+ this.config = config;
905
+ this.files = new FileRepository(db);
906
+ this.symbols = new SymbolRepository(db);
907
+ this.meta = new MetaRepository(db);
908
+ }
909
+ db;
910
+ repoRoot;
911
+ config;
912
+ parser = new Parser();
913
+ files;
914
+ symbols;
915
+ meta;
916
+ async runFull() {
917
+ const start = Date.now();
918
+ const allFiles = await walkFiles(this.repoRoot, this.config);
919
+ let added = 0, updated = 0, skipped = 0;
920
+ const transaction = this.db.transaction(() => {
921
+ for (const { absolutePath, relativePath } of allFiles) {
922
+ const result = this.indexFile(absolutePath, relativePath);
923
+ if (result === "added") added++;
924
+ else if (result === "updated") updated++;
925
+ else skipped++;
926
+ }
927
+ });
928
+ transaction();
929
+ const commit = getHeadCommit(this.repoRoot);
930
+ this.meta.saveIndexMeta(commit, this.repoRoot);
931
+ const metaInfo = this.meta.getIndexMeta();
932
+ return {
933
+ filesAdded: added,
934
+ filesUpdated: updated,
935
+ filesDeleted: 0,
936
+ filesSkipped: skipped,
937
+ symbolsTotal: metaInfo.totalSymbols,
938
+ durationMs: Date.now() - start,
939
+ mode: "full"
940
+ };
941
+ }
942
+ async runIncremental() {
943
+ const start = Date.now();
944
+ const lastCommit = this.meta.get("last_indexed_commit") ?? "";
945
+ const currentCommit = getHeadCommit(this.repoRoot);
946
+ if (!lastCommit || lastCommit === currentCommit) {
947
+ if (!lastCommit) return this.runFull();
948
+ return { filesAdded: 0, filesUpdated: 0, filesDeleted: 0, filesSkipped: 0, symbolsTotal: this.meta.getIndexMeta().totalSymbols, durationMs: 0, mode: "incremental" };
949
+ }
950
+ const changes = getChangedFiles(this.repoRoot, lastCommit);
951
+ if (!changes) return this.runFull();
952
+ let added = 0, updated = 0, deleted = 0, skipped = 0;
953
+ const transaction = this.db.transaction(() => {
954
+ for (const change of changes) {
955
+ const absPath = join2(this.repoRoot, change.path);
956
+ if (change.status === "D") {
957
+ const existing = this.files.getByPath(change.path);
958
+ if (existing) {
959
+ this.symbols.deleteByFileId(existing.id);
960
+ this.files.markDeleted(change.path);
961
+ deleted++;
962
+ }
963
+ } else {
964
+ const result = this.indexFile(absPath, change.path);
965
+ if (result === "added") added++;
966
+ else if (result === "updated") updated++;
967
+ else skipped++;
968
+ }
969
+ }
970
+ });
971
+ transaction();
972
+ this.meta.saveIndexMeta(currentCommit, this.repoRoot);
973
+ const metaInfo = this.meta.getIndexMeta();
974
+ return {
975
+ filesAdded: added,
976
+ filesUpdated: updated,
977
+ filesDeleted: deleted,
978
+ filesSkipped: skipped,
979
+ symbolsTotal: metaInfo.totalSymbols,
980
+ durationMs: Date.now() - start,
981
+ mode: "incremental"
982
+ };
983
+ }
984
+ indexFile(absolutePath, relativePath) {
985
+ let stat;
986
+ try {
987
+ stat = statSync(absolutePath);
988
+ } catch {
989
+ return "skipped";
990
+ }
991
+ if (stat.size > this.config.maxFileSizeBytes) return "skipped";
992
+ const ext = extensionFromPath(relativePath);
993
+ let content;
994
+ try {
995
+ content = readFileSync2(absolutePath, "utf8");
996
+ } catch {
997
+ return "skipped";
998
+ }
999
+ const contentHash = hashContent(content);
1000
+ const existing = this.files.getByPath(relativePath);
1001
+ if (existing && existing.contentHash === contentHash && !existing.isDeleted) {
1002
+ return "skipped";
1003
+ }
1004
+ const isNew = !existing;
1005
+ const lines = content.split("\n").length;
1006
+ const langConfig = null;
1007
+ const fileId = this.files.upsert({
1008
+ path: relativePath,
1009
+ language: "unknown",
1010
+ contentHash,
1011
+ sizeBytes: stat.size,
1012
+ linesTotal: lines,
1013
+ indexedAt: Date.now(),
1014
+ isDeleted: false
1015
+ });
1016
+ const record = this.files.getByPath(relativePath);
1017
+ if (!record) return "skipped";
1018
+ this.symbols.deleteByFileId(record.id);
1019
+ return isNew ? "added" : "updated";
1020
+ }
1021
+ async parseAndIndex(absolutePath, relativePath) {
1022
+ const langConfig = getLanguageForExtension(extensionFromPath(relativePath));
1023
+ if (!langConfig) return;
1024
+ let content;
1025
+ try {
1026
+ content = readFileSync2(absolutePath, "utf8");
1027
+ } catch {
1028
+ return;
1029
+ }
1030
+ this.parser.setLanguage(langConfig.language);
1031
+ const tree = this.parser.parse(content);
1032
+ const record = this.files.getByPath(relativePath);
1033
+ if (!record) return;
1034
+ this.files.upsert({ ...record, language: langConfig.name });
1035
+ const symbols = extractSymbols(tree, relativePath, record.id, langConfig.name, content);
1036
+ const imports = extractImports(tree, relativePath, record.id, langConfig.name);
1037
+ this.symbols.deleteByFileId(record.id);
1038
+ if (symbols.length > 0) this.symbols.insertSymbols(record.id, symbols);
1039
+ if (imports.length > 0) this.symbols.insertImports(record.id, imports);
1040
+ }
1041
+ async runFullWithParsing(onProgress) {
1042
+ const start = Date.now();
1043
+ const allFiles = await walkFiles(this.repoRoot, this.config);
1044
+ const transaction = this.db.transaction(() => {
1045
+ for (const { absolutePath, relativePath } of allFiles) {
1046
+ this.indexFile(absolutePath, relativePath);
1047
+ }
1048
+ });
1049
+ transaction();
1050
+ const parseable = allFiles.filter((f) => {
1051
+ const ext = extensionFromPath(f.relativePath);
1052
+ return ext !== "";
1053
+ });
1054
+ let done = 0;
1055
+ for (const { absolutePath, relativePath } of parseable) {
1056
+ await this.parseAndIndex(absolutePath, relativePath);
1057
+ done++;
1058
+ onProgress?.(done, parseable.length);
1059
+ }
1060
+ const commit = getHeadCommit(this.repoRoot);
1061
+ this.meta.saveIndexMeta(commit, this.repoRoot);
1062
+ const metaInfo = this.meta.getIndexMeta();
1063
+ return {
1064
+ filesAdded: parseable.length,
1065
+ filesUpdated: 0,
1066
+ filesDeleted: 0,
1067
+ filesSkipped: allFiles.length - parseable.length,
1068
+ symbolsTotal: metaInfo.totalSymbols,
1069
+ durationMs: Date.now() - start,
1070
+ mode: "full"
1071
+ };
1072
+ }
1073
+ async runIncrementalWithParsing(onProgress) {
1074
+ const start = Date.now();
1075
+ const lastCommit = this.meta.get("last_indexed_commit") ?? "";
1076
+ const currentCommit = getHeadCommit(this.repoRoot);
1077
+ if (!lastCommit) return this.runFullWithParsing(onProgress);
1078
+ if (lastCommit === currentCommit) {
1079
+ const metaInfo2 = this.meta.getIndexMeta();
1080
+ return { filesAdded: 0, filesUpdated: 0, filesDeleted: 0, filesSkipped: 0, symbolsTotal: metaInfo2.totalSymbols, durationMs: 0, mode: "incremental" };
1081
+ }
1082
+ const changes = getChangedFiles(this.repoRoot, lastCommit);
1083
+ if (!changes) return this.runFullWithParsing(onProgress);
1084
+ let added = 0, updated = 0, deleted = 0;
1085
+ const toReparse = [];
1086
+ for (const change of changes) {
1087
+ const absPath = join2(this.repoRoot, change.path);
1088
+ if (change.status === "D") {
1089
+ const existing = this.files.getByPath(change.path);
1090
+ if (existing) {
1091
+ this.symbols.deleteByFileId(existing.id);
1092
+ this.files.markDeleted(change.path);
1093
+ deleted++;
1094
+ }
1095
+ } else {
1096
+ const result = this.indexFile(absPath, change.path);
1097
+ if (result !== "skipped") {
1098
+ toReparse.push({ abs: absPath, rel: change.path });
1099
+ if (result === "added") added++;
1100
+ else updated++;
1101
+ }
1102
+ }
1103
+ }
1104
+ let done = 0;
1105
+ for (const { abs, rel } of toReparse) {
1106
+ await this.parseAndIndex(abs, rel);
1107
+ done++;
1108
+ onProgress?.(done, toReparse.length);
1109
+ }
1110
+ this.meta.saveIndexMeta(currentCommit, this.repoRoot);
1111
+ const metaInfo = this.meta.getIndexMeta();
1112
+ return {
1113
+ filesAdded: added,
1114
+ filesUpdated: updated,
1115
+ filesDeleted: deleted,
1116
+ filesSkipped: 0,
1117
+ symbolsTotal: metaInfo.totalSymbols,
1118
+ durationMs: Date.now() - start,
1119
+ mode: "incremental"
1120
+ };
1121
+ }
1122
+ };
1123
+
1124
+ // src/context/BudgetAllocator.ts
1125
+ function allocateBudget(totalTokens, config, hasFocusPath, layers) {
1126
+ const weights = { ...config.layerWeights };
1127
+ if (!hasFocusPath) {
1128
+ const focusWeight = weights.focus;
1129
+ delete weights.focus;
1130
+ const remaining = layers.filter((l) => l !== "focus");
1131
+ const total = remaining.reduce((s, l) => s + (weights[l] ?? 0), 0);
1132
+ for (const l of remaining) {
1133
+ weights[l] = (weights[l] ?? 0) / total * (1 - focusWeight / 1) + (l === "symbol_table" ? focusWeight : 0);
1134
+ }
1135
+ }
1136
+ const activeLayers = hasFocusPath ? layers : layers.filter((l) => l !== "focus");
1137
+ const activeTotal = activeLayers.reduce((s, l) => s + (weights[l] ?? 0), 0);
1138
+ return activeLayers.map((layer) => ({
1139
+ layer,
1140
+ tokens: Math.floor(totalTokens * (weights[layer] ?? 0) / activeTotal)
1141
+ }));
1142
+ }
1143
+
1144
+ // src/context/TokenCounter.ts
1145
+ import { encode } from "gpt-tokenizer";
1146
+ function countTokens(text) {
1147
+ return encode(text).length;
1148
+ }
1149
+
1150
+ // src/context/layers/RepoOverviewLayer.ts
1151
+ import { basename } from "path";
1152
+ function renderRepoOverview(db, budget) {
1153
+ const meta = new MetaRepository(db).getIndexMeta();
1154
+ const files = new FileRepository(db);
1155
+ const langStats = files.getLanguageStats();
1156
+ const repoName = basename(meta.repoRoot) || "unknown";
1157
+ const lastIndexed = meta.lastIndexedAt ? new Date(meta.lastIndexedAt).toISOString().slice(0, 10) : "never";
1158
+ const langSummary = langStats.slice(0, 8).map((l) => `${l.language}: ${l.count}`).join(", ");
1159
+ const content = [
1160
+ `# Repository: ${repoName}`,
1161
+ `Files: ${meta.totalFiles} | Symbols: ${meta.totalSymbols} | Last indexed: ${lastIndexed}`,
1162
+ `Languages: ${langSummary}`
1163
+ ].join("\n");
1164
+ return { content, truncated: countTokens(content) > budget };
1165
+ }
1166
+
1167
+ // src/context/layers/DirectoryMapLayer.ts
1168
+ function renderTree(paths, maxLines) {
1169
+ const dirs = /* @__PURE__ */ new Map();
1170
+ for (const p of paths) {
1171
+ const slash = p.lastIndexOf("/");
1172
+ const dir = slash === -1 ? "." : p.slice(0, slash);
1173
+ if (!dirs.has(dir)) dirs.set(dir, []);
1174
+ dirs.get(dir).push(slash === -1 ? p : p.slice(slash + 1));
1175
+ }
1176
+ const lines = [];
1177
+ const topLevel = /* @__PURE__ */ new Map();
1178
+ for (const p of paths) {
1179
+ const top = p.split("/")[0];
1180
+ topLevel.set(top, (topLevel.get(top) ?? 0) + 1);
1181
+ }
1182
+ for (const [dir, count] of [...topLevel.entries()].sort()) {
1183
+ if (lines.length >= maxLines) {
1184
+ lines.push("...");
1185
+ break;
1186
+ }
1187
+ lines.push(`${dir}/ (${count} files)`);
1188
+ }
1189
+ return lines.join("\n");
1190
+ }
1191
+ function renderDirectoryMap(db, budget) {
1192
+ const allFiles = new FileRepository(db).getAllActive();
1193
+ const paths = allFiles.map((f) => f.path);
1194
+ const charsPerToken = 4;
1195
+ const maxLines = Math.floor(budget * charsPerToken / 30);
1196
+ const content = renderTree(paths, maxLines);
1197
+ return { content, truncated: countTokens(content) > budget };
1198
+ }
1199
+
1200
+ // src/context/layers/SymbolTableLayer.ts
1201
+ function renderSymbolTable(db, budget) {
1202
+ const repo = new SymbolRepository(db);
1203
+ const symbols = repo.getAllExported();
1204
+ const byFile = /* @__PURE__ */ new Map();
1205
+ for (const s of symbols) {
1206
+ if (!byFile.has(s.filePath)) byFile.set(s.filePath, []);
1207
+ byFile.get(s.filePath).push(s);
1208
+ }
1209
+ const sorted = [...byFile.entries()].sort((a, b) => b[1].length - a[1].length);
1210
+ const lines = [];
1211
+ let usedTokens = 0;
1212
+ let truncated = false;
1213
+ for (const [file, syms] of sorted) {
1214
+ const header = `### ${file}`;
1215
+ const symLines = syms.map((s) => {
1216
+ const sig = s.signature ? `${s.name}${s.signature}` : s.name;
1217
+ const doc = s.docComment ? ` \u2014 ${s.docComment}` : "";
1218
+ return ` ${s.kind} ${sig}${doc}`;
1219
+ });
1220
+ const block = [header, ...symLines].join("\n");
1221
+ const blockTokens = countTokens(block);
1222
+ if (usedTokens + blockTokens > budget) {
1223
+ truncated = true;
1224
+ break;
1225
+ }
1226
+ lines.push(block);
1227
+ usedTokens += blockTokens;
1228
+ }
1229
+ return { content: lines.join("\n\n"), truncated };
1230
+ }
1231
+
1232
+ // src/context/layers/ImportGraphLayer.ts
1233
+ function renderImportGraph(db, budget) {
1234
+ const repo = new SymbolRepository(db);
1235
+ const hubs = repo.getTopImportedFiles(30);
1236
+ const lines = ["### Most-imported modules"];
1237
+ let usedTokens = countTokens(lines[0]);
1238
+ let truncated = false;
1239
+ for (const { path, importerCount } of hubs) {
1240
+ const line = ` ${path} \u2190 imported by ${importerCount} files`;
1241
+ const t = countTokens(line);
1242
+ if (usedTokens + t > budget) {
1243
+ truncated = true;
1244
+ break;
1245
+ }
1246
+ lines.push(line);
1247
+ usedTokens += t;
1248
+ }
1249
+ return { content: lines.join("\n"), truncated };
1250
+ }
1251
+
1252
+ // src/context/layers/FocusLayer.ts
1253
+ function renderFocusLayer(db, focusPath, budget) {
1254
+ const symRepo = new SymbolRepository(db);
1255
+ const fileRepo = new FileRepository(db);
1256
+ const allFiles = fileRepo.getAllActive().filter((f) => f.path.startsWith(focusPath));
1257
+ const lines = [`### Focus: ${focusPath}`];
1258
+ let usedTokens = countTokens(lines[0]);
1259
+ let truncated = false;
1260
+ for (const file of allFiles) {
1261
+ const syms = symRepo.getExportedByFile(file.path);
1262
+ if (syms.length === 0) continue;
1263
+ const importers = symRepo.getImportersOf(file.path);
1264
+ const fileLines = [` ${file.path}:`];
1265
+ for (const s of syms) {
1266
+ const sig = s.signature ? `${s.name}${s.signature}` : s.name;
1267
+ fileLines.push(` ${s.kind} ${sig}`);
1268
+ }
1269
+ if (importers.length > 0) {
1270
+ fileLines.push(` \u2190 imported by: ${importers.slice(0, 3).join(", ")}${importers.length > 3 ? ` +${importers.length - 3} more` : ""}`);
1271
+ }
1272
+ const block = fileLines.join("\n");
1273
+ const t = countTokens(block);
1274
+ if (usedTokens + t > budget) {
1275
+ truncated = true;
1276
+ break;
1277
+ }
1278
+ lines.push(block);
1279
+ usedTokens += t;
1280
+ }
1281
+ return { content: lines.join("\n"), truncated };
1282
+ }
1283
+
1284
+ // src/context/ContextGenerator.ts
1285
+ var DEFAULT_LAYERS = ["repo_overview", "directory_map", "symbol_table", "import_graph", "focus"];
1286
+ function renderLayer(db, layer, budget, focusPath) {
1287
+ switch (layer) {
1288
+ case "repo_overview":
1289
+ return renderRepoOverview(db, budget);
1290
+ case "directory_map":
1291
+ return renderDirectoryMap(db, budget);
1292
+ case "symbol_table":
1293
+ return renderSymbolTable(db, budget);
1294
+ case "import_graph":
1295
+ return renderImportGraph(db, budget);
1296
+ case "focus":
1297
+ return focusPath ? renderFocusLayer(db, focusPath, budget) : { content: "", truncated: false };
1298
+ default:
1299
+ return { content: "", truncated: false };
1300
+ }
1301
+ }
1302
+ function wrapMarkdown(layers) {
1303
+ return layers.map((l) => l.content).filter(Boolean).join("\n\n");
1304
+ }
1305
+ function wrapXML(layers) {
1306
+ const inner = layers.filter((l) => l.content).map((l) => ` <${l.layer}>
1307
+ ${l.content}
1308
+ </${l.layer}>`).join("\n");
1309
+ return `<codebase_context>
1310
+ ${inner}
1311
+ </codebase_context>`;
1312
+ }
1313
+ function generateContext(db, request, config) {
1314
+ const layers = request.layers ?? DEFAULT_LAYERS;
1315
+ const hasFocus = Boolean(request.focusPath);
1316
+ const budgets = allocateBudget(request.budgetTokens, config, hasFocus, layers);
1317
+ let remainingSurplus = 0;
1318
+ const results = [];
1319
+ for (const { layer, tokens } of budgets) {
1320
+ const layerBudget = tokens + remainingSurplus;
1321
+ const { content, truncated } = renderLayer(db, layer, layerBudget, request.focusPath);
1322
+ const used = countTokens(content);
1323
+ remainingSurplus = truncated ? 0 : layerBudget - used;
1324
+ results.push({ layer, tokensUsed: used, content, truncated });
1325
+ }
1326
+ const rendered = request.format === "xml" ? wrapXML(results) : wrapMarkdown(results);
1327
+ const totalTokens = countTokens(rendered);
1328
+ return {
1329
+ totalTokens,
1330
+ budgetTokens: request.budgetTokens,
1331
+ layers: results,
1332
+ rendered
1333
+ };
1334
+ }
1335
+ export {
1336
+ DEFAULT_CONFIG,
1337
+ FileRepository,
1338
+ Indexer,
1339
+ MetaRepository,
1340
+ SymbolRepository,
1341
+ generateContext,
1342
+ openDatabase
1343
+ };
1344
+ //# sourceMappingURL=index.js.map