ctxo-mcp 0.2.0 → 0.3.1

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.
@@ -2,16 +2,520 @@
2
2
  import {
3
3
  RevertDetector,
4
4
  SimpleGitAdapter,
5
- SqliteStorageAdapter
6
- } from "./chunk-P7JUSY3I.js";
5
+ SqliteStorageAdapter,
6
+ aggregateCoChanges
7
+ } from "./chunk-XSHNN6PU.js";
7
8
  import {
8
- JsonIndexReader
9
- } from "./chunk-54ETLIQX.js";
9
+ JsonIndexReader,
10
+ SYMBOL_KINDS
11
+ } from "./chunk-N6GPODUY.js";
12
+ import {
13
+ __esm,
14
+ __export,
15
+ __toCommonJS
16
+ } from "./chunk-JIDIH7DS.js";
17
+
18
+ // src/adapters/language/tree-sitter-adapter.ts
19
+ import Parser from "tree-sitter";
20
+ import { extname as extname3 } from "path";
21
+ var TreeSitterAdapter;
22
+ var init_tree_sitter_adapter = __esm({
23
+ "src/adapters/language/tree-sitter-adapter.ts"() {
24
+ "use strict";
25
+ TreeSitterAdapter = class {
26
+ tier = "syntax";
27
+ parser;
28
+ symbolRegistry = /* @__PURE__ */ new Map();
29
+ constructor(language) {
30
+ this.parser = new Parser();
31
+ this.parser.setLanguage(language);
32
+ }
33
+ isSupported(filePath) {
34
+ const ext = extname3(filePath).toLowerCase();
35
+ return this.extensions.includes(ext);
36
+ }
37
+ setSymbolRegistry(registry) {
38
+ this.symbolRegistry = registry;
39
+ }
40
+ parse(source) {
41
+ return this.parser.parse(source);
42
+ }
43
+ buildSymbolId(filePath, name, kind) {
44
+ return `${filePath}::${name}::${kind}`;
45
+ }
46
+ nodeToLineRange(node) {
47
+ return {
48
+ startLine: node.startPosition.row,
49
+ endLine: node.endPosition.row,
50
+ startOffset: node.startIndex,
51
+ endOffset: node.endIndex
52
+ };
53
+ }
54
+ countCyclomaticComplexity(node, branchTypes) {
55
+ let complexity = 1;
56
+ const visit = (n) => {
57
+ if (branchTypes.includes(n.type)) {
58
+ complexity++;
59
+ }
60
+ for (let i = 0; i < n.childCount; i++) {
61
+ visit(n.child(i));
62
+ }
63
+ };
64
+ visit(node);
65
+ return complexity;
66
+ }
67
+ };
68
+ }
69
+ });
70
+
71
+ // src/adapters/language/go-adapter.ts
72
+ var go_adapter_exports = {};
73
+ __export(go_adapter_exports, {
74
+ GoAdapter: () => GoAdapter
75
+ });
76
+ import GoLanguage from "tree-sitter-go";
77
+ var GO_BRANCH_TYPES, GoAdapter;
78
+ var init_go_adapter = __esm({
79
+ "src/adapters/language/go-adapter.ts"() {
80
+ "use strict";
81
+ init_tree_sitter_adapter();
82
+ GO_BRANCH_TYPES = [
83
+ "if_statement",
84
+ "for_statement",
85
+ "expression_switch_statement",
86
+ "type_switch_statement",
87
+ "expression_case",
88
+ "type_case",
89
+ "select_statement",
90
+ "communication_case"
91
+ ];
92
+ GoAdapter = class extends TreeSitterAdapter {
93
+ extensions = [".go"];
94
+ constructor() {
95
+ super(GoLanguage);
96
+ }
97
+ extractSymbols(filePath, source) {
98
+ try {
99
+ const tree = this.parse(source);
100
+ const symbols = [];
101
+ for (let i = 0; i < tree.rootNode.childCount; i++) {
102
+ const node = tree.rootNode.child(i);
103
+ if (node.type === "function_declaration") {
104
+ const name = node.childForFieldName("name")?.text;
105
+ if (!name || !this.isExported(name)) continue;
106
+ const range = this.nodeToLineRange(node);
107
+ symbols.push({
108
+ symbolId: this.buildSymbolId(filePath, name, "function"),
109
+ name,
110
+ kind: "function",
111
+ ...range
112
+ });
113
+ }
114
+ if (node.type === "method_declaration") {
115
+ const methodName = node.childForFieldName("name")?.text;
116
+ if (!methodName || !this.isExported(methodName)) continue;
117
+ const receiverType = this.extractReceiverType(node);
118
+ const qualifiedName = receiverType ? `${receiverType}.${methodName}` : methodName;
119
+ const range = this.nodeToLineRange(node);
120
+ symbols.push({
121
+ symbolId: this.buildSymbolId(filePath, qualifiedName, "method"),
122
+ name: qualifiedName,
123
+ kind: "method",
124
+ ...range
125
+ });
126
+ }
127
+ if (node.type === "type_declaration") {
128
+ this.extractTypeSymbols(node, filePath, symbols);
129
+ }
130
+ }
131
+ return symbols;
132
+ } catch (err) {
133
+ console.error(`[ctxo:go] Symbol extraction failed for ${filePath}: ${err.message}`);
134
+ return [];
135
+ }
136
+ }
137
+ extractEdges(filePath, source) {
138
+ try {
139
+ const tree = this.parse(source);
140
+ const edges = [];
141
+ const firstExportedSymbol = this.findFirstExportedSymbolId(tree.rootNode, filePath);
142
+ if (!firstExportedSymbol) return edges;
143
+ for (let i = 0; i < tree.rootNode.childCount; i++) {
144
+ const node = tree.rootNode.child(i);
145
+ if (node.type === "import_declaration") {
146
+ this.extractImportEdges(node, filePath, firstExportedSymbol, edges);
147
+ }
148
+ }
149
+ return edges;
150
+ } catch (err) {
151
+ console.error(`[ctxo:go] Edge extraction failed for ${filePath}: ${err.message}`);
152
+ return [];
153
+ }
154
+ }
155
+ extractComplexity(filePath, source) {
156
+ try {
157
+ const tree = this.parse(source);
158
+ const metrics = [];
159
+ for (let i = 0; i < tree.rootNode.childCount; i++) {
160
+ const node = tree.rootNode.child(i);
161
+ if (node.type === "function_declaration") {
162
+ const name = node.childForFieldName("name")?.text;
163
+ if (!name || !this.isExported(name)) continue;
164
+ metrics.push({
165
+ symbolId: this.buildSymbolId(filePath, name, "function"),
166
+ cyclomatic: this.countCyclomaticComplexity(node, GO_BRANCH_TYPES)
167
+ });
168
+ }
169
+ if (node.type === "method_declaration") {
170
+ const methodName = node.childForFieldName("name")?.text;
171
+ if (!methodName || !this.isExported(methodName)) continue;
172
+ const receiverType = this.extractReceiverType(node);
173
+ const qualifiedName = receiverType ? `${receiverType}.${methodName}` : methodName;
174
+ metrics.push({
175
+ symbolId: this.buildSymbolId(filePath, qualifiedName, "method"),
176
+ cyclomatic: this.countCyclomaticComplexity(node, GO_BRANCH_TYPES)
177
+ });
178
+ }
179
+ }
180
+ return metrics;
181
+ } catch (err) {
182
+ console.error(`[ctxo:go] Complexity extraction failed for ${filePath}: ${err.message}`);
183
+ return [];
184
+ }
185
+ }
186
+ // ── Private helpers ─────────────────────────────────────────
187
+ isExported(name) {
188
+ return name.length > 0 && name[0] === name[0].toUpperCase() && name[0] !== name[0].toLowerCase();
189
+ }
190
+ extractReceiverType(methodNode) {
191
+ const params = methodNode.child(1);
192
+ if (params?.type !== "parameter_list") return void 0;
193
+ for (let i = 0; i < params.childCount; i++) {
194
+ const param = params.child(i);
195
+ if (param.type === "parameter_declaration") {
196
+ const typeNode = param.childForFieldName("type");
197
+ if (typeNode) {
198
+ const text = typeNode.text;
199
+ return text.replace(/^\*/, "");
200
+ }
201
+ }
202
+ }
203
+ return void 0;
204
+ }
205
+ extractTypeSymbols(typeDecl, filePath, symbols) {
206
+ for (let i = 0; i < typeDecl.childCount; i++) {
207
+ const spec = typeDecl.child(i);
208
+ if (spec.type !== "type_spec") continue;
209
+ const name = spec.childForFieldName("name")?.text;
210
+ if (!name || !this.isExported(name)) continue;
211
+ const typeBody = spec.childForFieldName("type");
212
+ let kind = "type";
213
+ if (typeBody?.type === "struct_type") kind = "class";
214
+ else if (typeBody?.type === "interface_type") kind = "interface";
215
+ const range = this.nodeToLineRange(spec);
216
+ symbols.push({
217
+ symbolId: this.buildSymbolId(filePath, name, kind),
218
+ name,
219
+ kind,
220
+ ...range
221
+ });
222
+ }
223
+ }
224
+ extractImportEdges(importDecl, _filePath, fromSymbol, edges) {
225
+ const visit = (node) => {
226
+ if (node.type === "import_spec") {
227
+ const pathNode = node.childForFieldName("path") ?? node.child(0);
228
+ if (pathNode) {
229
+ const importPath = pathNode.text.replace(/"/g, "");
230
+ edges.push({
231
+ from: fromSymbol,
232
+ to: `${importPath}::${importPath.split("/").pop()}::variable`,
233
+ kind: "imports"
234
+ });
235
+ }
236
+ }
237
+ for (let i = 0; i < node.childCount; i++) {
238
+ visit(node.child(i));
239
+ }
240
+ };
241
+ visit(importDecl);
242
+ }
243
+ findFirstExportedSymbolId(rootNode, filePath) {
244
+ for (let i = 0; i < rootNode.childCount; i++) {
245
+ const node = rootNode.child(i);
246
+ if (node.type === "function_declaration") {
247
+ const name = node.childForFieldName("name")?.text;
248
+ if (name && this.isExported(name)) return this.buildSymbolId(filePath, name, "function");
249
+ }
250
+ if (node.type === "type_declaration") {
251
+ for (let j = 0; j < node.childCount; j++) {
252
+ const spec = node.child(j);
253
+ if (spec.type === "type_spec") {
254
+ const name = spec.childForFieldName("name")?.text;
255
+ if (name && this.isExported(name)) {
256
+ const typeBody = spec.childForFieldName("type");
257
+ const kind = typeBody?.type === "struct_type" ? "class" : typeBody?.type === "interface_type" ? "interface" : "type";
258
+ return this.buildSymbolId(filePath, name, kind);
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ return void 0;
265
+ }
266
+ };
267
+ }
268
+ });
269
+
270
+ // src/adapters/language/csharp-adapter.ts
271
+ var csharp_adapter_exports = {};
272
+ __export(csharp_adapter_exports, {
273
+ CSharpAdapter: () => CSharpAdapter
274
+ });
275
+ import CSharpLanguage from "tree-sitter-c-sharp";
276
+ var CSHARP_BRANCH_TYPES, CSharpAdapter;
277
+ var init_csharp_adapter = __esm({
278
+ "src/adapters/language/csharp-adapter.ts"() {
279
+ "use strict";
280
+ init_tree_sitter_adapter();
281
+ CSHARP_BRANCH_TYPES = [
282
+ "if_statement",
283
+ "for_statement",
284
+ "foreach_statement",
285
+ "while_statement",
286
+ "do_statement",
287
+ "switch_section",
288
+ "catch_clause",
289
+ "conditional_expression"
290
+ ];
291
+ CSharpAdapter = class extends TreeSitterAdapter {
292
+ extensions = [".cs"];
293
+ constructor() {
294
+ super(CSharpLanguage);
295
+ }
296
+ extractSymbols(filePath, source) {
297
+ try {
298
+ const tree = this.parse(source);
299
+ const symbols = [];
300
+ this.visitSymbols(tree.rootNode, filePath, "", symbols);
301
+ return symbols;
302
+ } catch (err) {
303
+ console.error(`[ctxo:csharp] Symbol extraction failed for ${filePath}: ${err.message}`);
304
+ return [];
305
+ }
306
+ }
307
+ extractEdges(filePath, source) {
308
+ try {
309
+ const tree = this.parse(source);
310
+ const edges = [];
311
+ const symbols = this.extractSymbols(filePath, source);
312
+ const firstSymbol = symbols.length > 0 ? symbols[0].symbolId : void 0;
313
+ if (!firstSymbol) return edges;
314
+ this.visitEdges(tree.rootNode, filePath, firstSymbol, "", edges);
315
+ return edges;
316
+ } catch (err) {
317
+ console.error(`[ctxo:csharp] Edge extraction failed for ${filePath}: ${err.message}`);
318
+ return [];
319
+ }
320
+ }
321
+ extractComplexity(filePath, source) {
322
+ try {
323
+ const tree = this.parse(source);
324
+ const metrics = [];
325
+ this.visitComplexity(tree.rootNode, filePath, "", metrics);
326
+ return metrics;
327
+ } catch (err) {
328
+ console.error(`[ctxo:csharp] Complexity extraction failed for ${filePath}: ${err.message}`);
329
+ return [];
330
+ }
331
+ }
332
+ // ── Symbol visitor ──────────────────────────────────────────
333
+ visitSymbols(node, filePath, namespace, symbols) {
334
+ if (node.type === "namespace_declaration") {
335
+ const name = node.childForFieldName("name")?.text ?? "";
336
+ const ns = namespace ? `${namespace}.${name}` : name;
337
+ for (let i = 0; i < node.childCount; i++) {
338
+ this.visitSymbols(node.child(i), filePath, ns, symbols);
339
+ }
340
+ return;
341
+ }
342
+ if (node.type === "declaration_list") {
343
+ for (let i = 0; i < node.childCount; i++) {
344
+ this.visitSymbols(node.child(i), filePath, namespace, symbols);
345
+ }
346
+ return;
347
+ }
348
+ const typeMapping = {
349
+ class_declaration: "class",
350
+ struct_declaration: "class",
351
+ record_declaration: "class",
352
+ interface_declaration: "interface",
353
+ enum_declaration: "type"
354
+ };
355
+ const kind = typeMapping[node.type];
356
+ if (kind) {
357
+ if (!this.isPublic(node)) return;
358
+ const name = node.childForFieldName("name")?.text;
359
+ if (!name) return;
360
+ const qualifiedName = namespace ? `${namespace}.${name}` : name;
361
+ const range = this.nodeToLineRange(node);
362
+ symbols.push({
363
+ symbolId: this.buildSymbolId(filePath, qualifiedName, kind),
364
+ name: qualifiedName,
365
+ kind,
366
+ ...range
367
+ });
368
+ if (kind === "class") {
369
+ this.extractMethodSymbols(node, filePath, qualifiedName, symbols);
370
+ }
371
+ return;
372
+ }
373
+ for (let i = 0; i < node.childCount; i++) {
374
+ this.visitSymbols(node.child(i), filePath, namespace, symbols);
375
+ }
376
+ }
377
+ extractMethodSymbols(classNode, filePath, className, symbols) {
378
+ const declList = classNode.children.find((c) => c.type === "declaration_list");
379
+ if (!declList) return;
380
+ for (let i = 0; i < declList.childCount; i++) {
381
+ const child = declList.child(i);
382
+ if (child.type !== "method_declaration" && child.type !== "constructor_declaration") continue;
383
+ if (!this.isPublic(child)) continue;
384
+ const name = child.childForFieldName("name")?.text;
385
+ if (!name) continue;
386
+ const paramCount = this.countParameters(child);
387
+ const qualifiedName = `${className}.${name}(${paramCount})`;
388
+ const range = this.nodeToLineRange(child);
389
+ symbols.push({
390
+ symbolId: this.buildSymbolId(filePath, qualifiedName, "method"),
391
+ name: qualifiedName,
392
+ kind: "method",
393
+ ...range
394
+ });
395
+ }
396
+ }
397
+ // ── Edge visitor ────────────────────────────────────────────
398
+ visitEdges(node, filePath, fromSymbol, namespace, edges) {
399
+ if (node.type === "using_directive") {
400
+ const nameNode = node.children.find((c) => c.type === "identifier" || c.type === "qualified_name");
401
+ if (nameNode) {
402
+ edges.push({
403
+ from: fromSymbol,
404
+ to: `${nameNode.text}::${nameNode.text.split(".").pop()}::variable`,
405
+ kind: "imports"
406
+ });
407
+ }
408
+ return;
409
+ }
410
+ if (node.type === "namespace_declaration") {
411
+ const name = node.childForFieldName("name")?.text ?? "";
412
+ const ns = namespace ? `${namespace}.${name}` : name;
413
+ for (let i = 0; i < node.childCount; i++) {
414
+ this.visitEdges(node.child(i), filePath, fromSymbol, ns, edges);
415
+ }
416
+ return;
417
+ }
418
+ if (node.type === "class_declaration" || node.type === "struct_declaration") {
419
+ if (!this.isPublic(node)) return;
420
+ const name = node.childForFieldName("name")?.text;
421
+ if (!name) return;
422
+ const qualifiedName = namespace ? `${namespace}.${name}` : name;
423
+ const classSymbolId = this.buildSymbolId(filePath, qualifiedName, "class");
424
+ const baseList = node.children.find((c) => c.type === "base_list");
425
+ if (baseList) {
426
+ for (let i = 0; i < baseList.childCount; i++) {
427
+ const child = baseList.child(i);
428
+ if (child.type === "identifier" || child.type === "qualified_name") {
429
+ const baseName = child.text;
430
+ const edgeKind = baseName.match(/^I[A-Z]/) ? "implements" : "extends";
431
+ const targetKind = edgeKind === "implements" ? "interface" : "class";
432
+ edges.push({
433
+ from: classSymbolId,
434
+ to: this.resolveBaseType(baseName, namespace, targetKind),
435
+ kind: edgeKind
436
+ });
437
+ }
438
+ }
439
+ }
440
+ }
441
+ for (let i = 0; i < node.childCount; i++) {
442
+ this.visitEdges(node.child(i), filePath, fromSymbol, namespace, edges);
443
+ }
444
+ }
445
+ // ── Complexity visitor ──────────────────────────────────────
446
+ visitComplexity(node, filePath, namespace, metrics) {
447
+ if (node.type === "namespace_declaration") {
448
+ const name = node.childForFieldName("name")?.text ?? "";
449
+ const ns = namespace ? `${namespace}.${name}` : name;
450
+ for (let i = 0; i < node.childCount; i++) {
451
+ this.visitComplexity(node.child(i), filePath, ns, metrics);
452
+ }
453
+ return;
454
+ }
455
+ const typeMapping = {
456
+ class_declaration: true,
457
+ struct_declaration: true,
458
+ record_declaration: true
459
+ };
460
+ if (typeMapping[node.type]) {
461
+ if (!this.isPublic(node)) return;
462
+ const className = node.childForFieldName("name")?.text;
463
+ if (!className) return;
464
+ const qualifiedClass = namespace ? `${namespace}.${className}` : className;
465
+ const declList = node.children.find((c) => c.type === "declaration_list");
466
+ if (!declList) return;
467
+ for (let i = 0; i < declList.childCount; i++) {
468
+ const child = declList.child(i);
469
+ if (child.type !== "method_declaration") continue;
470
+ if (!this.isPublic(child)) continue;
471
+ const methodName = child.childForFieldName("name")?.text;
472
+ if (!methodName) continue;
473
+ const paramCount = this.countParameters(child);
474
+ metrics.push({
475
+ symbolId: this.buildSymbolId(filePath, `${qualifiedClass}.${methodName}(${paramCount})`, "method"),
476
+ cyclomatic: this.countCyclomaticComplexity(child, CSHARP_BRANCH_TYPES)
477
+ });
478
+ }
479
+ return;
480
+ }
481
+ for (let i = 0; i < node.childCount; i++) {
482
+ this.visitComplexity(node.child(i), filePath, namespace, metrics);
483
+ }
484
+ }
485
+ // ── Helpers ─────────────────────────────────────────────────
486
+ countParameters(methodNode) {
487
+ const paramList = methodNode.childForFieldName("parameters");
488
+ if (!paramList) return 0;
489
+ let count = 0;
490
+ for (let i = 0; i < paramList.childCount; i++) {
491
+ if (paramList.child(i).type === "parameter") count++;
492
+ }
493
+ return count;
494
+ }
495
+ isPublic(node) {
496
+ for (let i = 0; i < node.childCount; i++) {
497
+ const child = node.child(i);
498
+ if (child.type === "modifier" && child.text === "public") return true;
499
+ }
500
+ return false;
501
+ }
502
+ resolveBaseType(baseName, namespace, defaultKind) {
503
+ const prefix = namespace ? `${namespace}.${baseName}` : baseName;
504
+ for (const [id] of this.symbolRegistry) {
505
+ if (id.includes(`::${prefix}::`)) return id;
506
+ if (id.includes(`::${baseName}::`)) return id;
507
+ }
508
+ const qualifiedName = namespace ? `${namespace}.${baseName}` : baseName;
509
+ return `${qualifiedName}::${baseName}::${defaultKind}`;
510
+ }
511
+ };
512
+ }
513
+ });
10
514
 
11
515
  // src/cli/index-command.ts
12
516
  import { execFileSync } from "child_process";
13
517
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync3, statSync, readdirSync } from "fs";
14
- import { join as join4, relative, extname as extname3 } from "path";
518
+ import { join as join4, relative, extname as extname4 } from "path";
15
519
 
16
520
  // src/core/staleness/content-hasher.ts
17
521
  import { createHash } from "crypto";
@@ -34,6 +538,8 @@ var TsMorphAdapter = class {
34
538
  extensions = SUPPORTED_EXTENSIONS;
35
539
  tier = "full";
36
540
  project;
541
+ symbolRegistry = /* @__PURE__ */ new Map();
542
+ projectPreloaded = false;
37
543
  constructor() {
38
544
  this.project = new Project({
39
545
  compilerOptions: {
@@ -50,6 +556,27 @@ var TsMorphAdapter = class {
50
556
  const ext = extname(filePath).toLowerCase();
51
557
  return SUPPORTED_EXTENSIONS.includes(ext);
52
558
  }
559
+ setSymbolRegistry(registry) {
560
+ this.symbolRegistry = registry;
561
+ }
562
+ loadProjectSources(files) {
563
+ for (const [filePath, source] of files) {
564
+ try {
565
+ const existing = this.project.getSourceFile(filePath);
566
+ if (existing) this.project.removeSourceFile(existing);
567
+ this.project.createSourceFile(filePath, source, { overwrite: true });
568
+ } catch (err) {
569
+ console.error(`[ctxo:ts-morph] Failed to preload ${filePath}: ${err.message}`);
570
+ }
571
+ }
572
+ this.projectPreloaded = true;
573
+ }
574
+ clearProjectSources() {
575
+ for (const sf of this.project.getSourceFiles()) {
576
+ this.project.removeSourceFile(sf);
577
+ }
578
+ this.projectPreloaded = false;
579
+ }
53
580
  extractSymbols(filePath, source) {
54
581
  const sourceFile = this.parseSource(filePath, source);
55
582
  if (!sourceFile) return [];
@@ -76,12 +603,15 @@ var TsMorphAdapter = class {
76
603
  this.extractImportEdges(sourceFile, filePath, edges);
77
604
  this.extractInheritanceEdges(sourceFile, filePath, edges);
78
605
  this.extractCallEdges(sourceFile, filePath, edges);
606
+ this.extractReferenceEdges(sourceFile, filePath, edges);
79
607
  return edges;
80
608
  } catch (err) {
81
609
  console.error(`[ctxo:ts-morph] Edge extraction failed for ${filePath}: ${err.message}`);
82
610
  return [];
83
611
  } finally {
84
- this.cleanupSourceFile(filePath);
612
+ if (!this.projectPreloaded) {
613
+ this.cleanupSourceFile(filePath);
614
+ }
85
615
  }
86
616
  }
87
617
  extractComplexity(filePath, source) {
@@ -124,7 +654,9 @@ var TsMorphAdapter = class {
124
654
  name,
125
655
  kind: "function",
126
656
  startLine: fn.getStartLineNumber() - 1,
127
- endLine: fn.getEndLineNumber() - 1
657
+ endLine: fn.getEndLineNumber() - 1,
658
+ startOffset: fn.getStart(),
659
+ endOffset: fn.getEnd()
128
660
  });
129
661
  }
130
662
  }
@@ -137,7 +669,9 @@ var TsMorphAdapter = class {
137
669
  name,
138
670
  kind: "class",
139
671
  startLine: cls.getStartLineNumber() - 1,
140
- endLine: cls.getEndLineNumber() - 1
672
+ endLine: cls.getEndLineNumber() - 1,
673
+ startOffset: cls.getStart(),
674
+ endOffset: cls.getEnd()
141
675
  });
142
676
  for (const method of cls.getMethods()) {
143
677
  const methodName = method.getName();
@@ -146,7 +680,9 @@ var TsMorphAdapter = class {
146
680
  name: `${name}.${methodName}`,
147
681
  kind: "method",
148
682
  startLine: method.getStartLineNumber() - 1,
149
- endLine: method.getEndLineNumber() - 1
683
+ endLine: method.getEndLineNumber() - 1,
684
+ startOffset: method.getStart(),
685
+ endOffset: method.getEnd()
150
686
  });
151
687
  }
152
688
  }
@@ -160,7 +696,9 @@ var TsMorphAdapter = class {
160
696
  name,
161
697
  kind: "interface",
162
698
  startLine: iface.getStartLineNumber() - 1,
163
- endLine: iface.getEndLineNumber() - 1
699
+ endLine: iface.getEndLineNumber() - 1,
700
+ startOffset: iface.getStart(),
701
+ endOffset: iface.getEnd()
164
702
  });
165
703
  }
166
704
  }
@@ -173,7 +711,9 @@ var TsMorphAdapter = class {
173
711
  name,
174
712
  kind: "type",
175
713
  startLine: typeAlias.getStartLineNumber() - 1,
176
- endLine: typeAlias.getEndLineNumber() - 1
714
+ endLine: typeAlias.getEndLineNumber() - 1,
715
+ startOffset: typeAlias.getStart(),
716
+ endOffset: typeAlias.getEnd()
177
717
  });
178
718
  }
179
719
  }
@@ -187,7 +727,9 @@ var TsMorphAdapter = class {
187
727
  name,
188
728
  kind: "variable",
189
729
  startLine: stmt.getStartLineNumber() - 1,
190
- endLine: stmt.getEndLineNumber() - 1
730
+ endLine: stmt.getEndLineNumber() - 1,
731
+ startOffset: decl.getStart(),
732
+ endOffset: decl.getEnd()
191
733
  });
192
734
  }
193
735
  }
@@ -195,7 +737,7 @@ var TsMorphAdapter = class {
195
737
  // ── Edge Extraction ─────────────────────────────────────────
196
738
  extractImportEdges(sourceFile, filePath, edges) {
197
739
  const fileSymbolId = this.buildSymbolId(filePath, sourceFile.getBaseName().replace(/\.[^.]+$/, ""), "variable");
198
- const fromSymbols = this.findExportedSymbolsInFile(filePath);
740
+ const fromSymbols = this.getExportedSymbolIds(sourceFile, filePath);
199
741
  const fromSymbol = fromSymbols.length > 0 ? fromSymbols[0] : fileSymbolId;
200
742
  for (const imp of sourceFile.getImportDeclarations()) {
201
743
  const moduleSpecifier = imp.getModuleSpecifierValue();
@@ -203,21 +745,36 @@ var TsMorphAdapter = class {
203
745
  continue;
204
746
  }
205
747
  const normalizedTarget = this.resolveRelativeImport(filePath, moduleSpecifier);
748
+ const isTypeOnly = imp.isTypeOnly();
206
749
  for (const named of imp.getNamedImports()) {
207
750
  const importedName = named.getName();
208
- edges.push({
751
+ const edge = {
209
752
  from: fromSymbol,
210
753
  to: this.resolveImportTarget(normalizedTarget, importedName),
211
754
  kind: "imports"
212
- });
755
+ };
756
+ if (isTypeOnly || named.isTypeOnly()) edge.typeOnly = true;
757
+ edges.push(edge);
213
758
  }
214
759
  const defaultImport = imp.getDefaultImport();
215
760
  if (defaultImport) {
216
- edges.push({
761
+ const edge = {
217
762
  from: fromSymbol,
218
763
  to: this.resolveImportTarget(normalizedTarget, defaultImport.getText()),
219
764
  kind: "imports"
220
- });
765
+ };
766
+ if (isTypeOnly) edge.typeOnly = true;
767
+ edges.push(edge);
768
+ }
769
+ const nsImport = imp.getNamespaceImport();
770
+ if (nsImport) {
771
+ const edge = {
772
+ from: fromSymbol,
773
+ to: this.buildSymbolId(normalizedTarget, nsImport.getText(), "variable"),
774
+ kind: "imports"
775
+ };
776
+ if (isTypeOnly) edge.typeOnly = true;
777
+ edges.push(edge);
221
778
  }
222
779
  }
223
780
  }
@@ -228,7 +785,7 @@ var TsMorphAdapter = class {
228
785
  const classSymbolId = this.buildSymbolId(filePath, className, "class");
229
786
  const baseClass = cls.getExtends();
230
787
  if (baseClass) {
231
- const baseName = baseClass.getExpression().getText();
788
+ const baseName = baseClass.getExpression().getText().replace(/<.*>$/, "");
232
789
  edges.push({
233
790
  from: classSymbolId,
234
791
  to: this.resolveSymbolReference(sourceFile, baseName, "class"),
@@ -236,7 +793,7 @@ var TsMorphAdapter = class {
236
793
  });
237
794
  }
238
795
  for (const impl of cls.getImplements()) {
239
- const ifaceName = impl.getExpression().getText();
796
+ const ifaceName = impl.getExpression().getText().replace(/<.*>$/, "");
240
797
  edges.push({
241
798
  from: classSymbolId,
242
799
  to: this.resolveSymbolReference(sourceFile, ifaceName, "interface"),
@@ -259,6 +816,14 @@ var TsMorphAdapter = class {
259
816
  edges.push({ from: fnSymbolId, to: resolved, kind: "calls" });
260
817
  }
261
818
  }
819
+ for (const newExpr of fn.getDescendantsOfKind(SyntaxKind.NewExpression)) {
820
+ const calledName = newExpr.getExpression().getText().split(".").pop();
821
+ if (!calledName) continue;
822
+ const resolved = this.resolveLocalCallTarget(sourceFile, filePath, calledName);
823
+ if (resolved) {
824
+ edges.push({ from: fnSymbolId, to: resolved, kind: "calls" });
825
+ }
826
+ }
262
827
  }
263
828
  for (const cls of sourceFile.getClasses()) {
264
829
  const className = cls.getName();
@@ -270,8 +835,26 @@ var TsMorphAdapter = class {
270
835
  const calledText = call.getExpression().getText();
271
836
  const calledName = calledText.split(".").pop();
272
837
  if (!calledName || calledName === methodName) continue;
273
- if (calledText.startsWith("this.")) continue;
274
- const resolved = this.resolveLocalCallTarget(sourceFile, filePath, calledName);
838
+ let resolved;
839
+ if (calledText.startsWith("this.")) {
840
+ resolved = this.resolveThisMethodCall(sourceFile, filePath, className, calledName);
841
+ } else {
842
+ resolved = this.resolveLocalCallTarget(sourceFile, filePath, calledName);
843
+ }
844
+ if (resolved) {
845
+ edges.push({ from: methodSymbolId, to: resolved, kind: "calls" });
846
+ }
847
+ }
848
+ for (const newExpr of method.getDescendantsOfKind(SyntaxKind.NewExpression)) {
849
+ const calledText = newExpr.getExpression().getText();
850
+ const calledName = calledText.split(".").pop();
851
+ if (!calledName) continue;
852
+ let resolved;
853
+ if (calledText.startsWith("this.")) {
854
+ resolved = this.resolveThisMethodCall(sourceFile, filePath, className, calledName);
855
+ } else {
856
+ resolved = this.resolveLocalCallTarget(sourceFile, filePath, calledName);
857
+ }
275
858
  if (resolved) {
276
859
  edges.push({ from: methodSymbolId, to: resolved, kind: "calls" });
277
860
  }
@@ -279,6 +862,48 @@ var TsMorphAdapter = class {
279
862
  }
280
863
  }
281
864
  }
865
+ extractReferenceEdges(sourceFile, filePath, edges) {
866
+ const importMap = /* @__PURE__ */ new Map();
867
+ for (const imp of sourceFile.getImportDeclarations()) {
868
+ const mod = imp.getModuleSpecifierValue();
869
+ if (!mod.startsWith(".") && !mod.startsWith("/")) continue;
870
+ const targetFile = this.resolveRelativeImport(filePath, mod);
871
+ for (const named of imp.getNamedImports()) {
872
+ importMap.set(named.getName(), this.resolveImportTarget(targetFile, named.getName()));
873
+ }
874
+ const defaultImport = imp.getDefaultImport();
875
+ if (defaultImport) {
876
+ importMap.set(defaultImport.getText(), this.resolveImportTarget(targetFile, defaultImport.getText()));
877
+ }
878
+ }
879
+ if (importMap.size === 0) return;
880
+ for (const fn of sourceFile.getFunctions()) {
881
+ if (!this.isExported(fn)) continue;
882
+ const fnName = fn.getName();
883
+ if (!fnName) continue;
884
+ const fromId = this.buildSymbolId(filePath, fnName, "function");
885
+ this.emitUsesEdges(fn, fromId, importMap, edges);
886
+ }
887
+ for (const cls of sourceFile.getClasses()) {
888
+ if (!this.isExported(cls)) continue;
889
+ const className = cls.getName();
890
+ if (!className) continue;
891
+ const classId = this.buildSymbolId(filePath, className, "class");
892
+ this.emitUsesEdges(cls, classId, importMap, edges);
893
+ }
894
+ }
895
+ emitUsesEdges(node, fromId, importMap, edges) {
896
+ const seen = /* @__PURE__ */ new Set();
897
+ for (const id of node.getDescendantsOfKind(SyntaxKind.Identifier)) {
898
+ const name = id.getText();
899
+ if (seen.has(name)) continue;
900
+ const target = importMap.get(name);
901
+ if (target) {
902
+ seen.add(name);
903
+ edges.push({ from: fromId, to: target, kind: "uses" });
904
+ }
905
+ }
906
+ }
282
907
  resolveLocalCallTarget(sourceFile, filePath, calledName) {
283
908
  for (const imp of sourceFile.getImportDeclarations()) {
284
909
  const moduleSpecifier = imp.getModuleSpecifierValue();
@@ -297,10 +922,24 @@ var TsMorphAdapter = class {
297
922
  }
298
923
  return void 0;
299
924
  }
925
+ resolveThisMethodCall(sourceFile, filePath, className, calledName) {
926
+ for (const cls of sourceFile.getClasses()) {
927
+ if (cls.getName() !== className) continue;
928
+ for (const method of cls.getMethods()) {
929
+ if (method.getName() === calledName) {
930
+ return this.buildSymbolId(filePath, `${className}.${calledName}`, "method");
931
+ }
932
+ }
933
+ }
934
+ return void 0;
935
+ }
300
936
  // ── Helpers ─────────────────────────────────────────────────
301
937
  parseSource(filePath, source) {
302
938
  try {
303
939
  const existing = this.project.getSourceFile(filePath);
940
+ if (existing && this.projectPreloaded) {
941
+ return existing;
942
+ }
304
943
  if (existing) {
305
944
  this.project.removeSourceFile(existing);
306
945
  }
@@ -332,6 +971,12 @@ var TsMorphAdapter = class {
332
971
  return resolved;
333
972
  }
334
973
  resolveImportTarget(targetFile, name) {
974
+ const prefix = `${targetFile}::${name}::`;
975
+ for (const kind2 of SYMBOL_KINDS) {
976
+ if (this.symbolRegistry.has(`${prefix}${kind2}`)) {
977
+ return `${prefix}${kind2}`;
978
+ }
979
+ }
335
980
  const targetSourceFile = this.project.getSourceFile(targetFile);
336
981
  if (targetSourceFile) {
337
982
  for (const fn of targetSourceFile.getFunctions()) {
@@ -364,8 +1009,13 @@ var TsMorphAdapter = class {
364
1009
  if (!moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) continue;
365
1010
  for (const named of imp.getNamedImports()) {
366
1011
  if (named.getName() === name) {
367
- const targetFile = imp.getModuleSpecifierSourceFile()?.getFilePath() ?? moduleSpecifier;
368
- return `${this.normalizeFilePath(targetFile)}::${name}::${defaultKind}`;
1012
+ const resolved = imp.getModuleSpecifierSourceFile()?.getFilePath();
1013
+ if (resolved) {
1014
+ return `${this.normalizeFilePath(resolved)}::${name}::${defaultKind}`;
1015
+ }
1016
+ const sourceDir = dirname(this.normalizeFilePath(sourceFile.getFilePath()));
1017
+ const resolvedPath = normalize(join(sourceDir, moduleSpecifier)).replace(/\\/g, "/").replace(/\.jsx$/, ".tsx").replace(/\.js$/, ".ts");
1018
+ return `${resolvedPath}::${name}::${defaultKind}`;
369
1019
  }
370
1020
  }
371
1021
  }
@@ -380,9 +1030,7 @@ var TsMorphAdapter = class {
380
1030
  normalizeFilePath(filePath) {
381
1031
  return filePath.replace(/^\//, "");
382
1032
  }
383
- findExportedSymbolsInFile(filePath) {
384
- const sourceFile = this.project.getSourceFile(filePath);
385
- if (!sourceFile) return [];
1033
+ getExportedSymbolIds(sourceFile, filePath) {
386
1034
  const ids = [];
387
1035
  for (const fn of sourceFile.getFunctions()) {
388
1036
  if (!this.isExported(fn)) continue;
@@ -455,6 +1103,9 @@ var LanguageAdapterRegistry = class {
455
1103
  this.adaptersByExtension.set(ext.toLowerCase(), adapter);
456
1104
  }
457
1105
  }
1106
+ getSupportedExtensions() {
1107
+ return new Set(this.adaptersByExtension.keys());
1108
+ }
458
1109
  getAdapter(filePath) {
459
1110
  if (!filePath) return void 0;
460
1111
  const ext = extname2(filePath).toLowerCase();
@@ -464,7 +1115,7 @@ var LanguageAdapterRegistry = class {
464
1115
  };
465
1116
 
466
1117
  // src/adapters/storage/json-index-writer.ts
467
- import { writeFileSync, mkdirSync, unlinkSync, existsSync } from "fs";
1118
+ import { writeFileSync, renameSync, mkdirSync, unlinkSync, existsSync } from "fs";
468
1119
  import { dirname as dirname2, join as join2, resolve, sep } from "path";
469
1120
  var JsonIndexWriter = class {
470
1121
  indexDir;
@@ -479,7 +1130,17 @@ var JsonIndexWriter = class {
479
1130
  mkdirSync(dirname2(targetPath), { recursive: true });
480
1131
  const sorted = this.sortKeys(fileIndex);
481
1132
  const json = JSON.stringify(sorted, null, 2);
482
- writeFileSync(targetPath, json, "utf-8");
1133
+ this.atomicWrite(targetPath, json);
1134
+ }
1135
+ writeCoChanges(matrix) {
1136
+ mkdirSync(this.indexDir, { recursive: true });
1137
+ const targetPath = join2(this.indexDir, "co-changes.json");
1138
+ this.atomicWrite(targetPath, JSON.stringify(matrix, null, 2));
1139
+ }
1140
+ atomicWrite(targetPath, content) {
1141
+ const tmpPath = `${targetPath}.${process.pid}.tmp`;
1142
+ writeFileSync(tmpPath, content, "utf-8");
1143
+ renameSync(tmpPath, targetPath);
483
1144
  }
484
1145
  delete(relativePath) {
485
1146
  const targetPath = this.resolveIndexPath(relativePath);
@@ -543,16 +1204,21 @@ var SchemaManager = class {
543
1204
  var IndexCommand = class {
544
1205
  projectRoot;
545
1206
  ctxoRoot;
1207
+ supportedExtensions;
546
1208
  constructor(projectRoot, ctxoRoot) {
547
1209
  this.projectRoot = projectRoot;
548
1210
  this.ctxoRoot = ctxoRoot ?? join4(projectRoot, ".ctxo");
1211
+ this.supportedExtensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".go", ".cs"]);
549
1212
  }
550
1213
  async run(options = {}) {
551
1214
  if (options.check) {
552
1215
  return this.runCheck();
553
1216
  }
554
1217
  const registry = new LanguageAdapterRegistry();
555
- registry.register(new TsMorphAdapter());
1218
+ const tsMorphAdapter = new TsMorphAdapter();
1219
+ registry.register(tsMorphAdapter);
1220
+ this.registerTreeSitterAdapters(registry);
1221
+ this.supportedExtensions = registry.getSupportedExtensions();
556
1222
  const writer = new JsonIndexWriter(this.ctxoRoot);
557
1223
  const schemaManager = new SchemaManager(this.ctxoRoot);
558
1224
  const hasher = new ContentHasher();
@@ -572,7 +1238,8 @@ var IndexCommand = class {
572
1238
  }
573
1239
  console.error(`[ctxo] Building codebase index... Found ${files.length} source files`);
574
1240
  }
575
- const indices = [];
1241
+ const symbolRegistry = /* @__PURE__ */ new Map();
1242
+ const pendingIndices = [];
576
1243
  let processed = 0;
577
1244
  for (const filePath of files) {
578
1245
  const adapter = registry.getAdapter(filePath);
@@ -582,40 +1249,73 @@ var IndexCommand = class {
582
1249
  const source = readFileSync2(filePath, "utf-8");
583
1250
  const lastModified = Math.floor(Date.now() / 1e3);
584
1251
  const symbols = adapter.extractSymbols(relativePath, source);
585
- const edges = adapter.extractEdges(relativePath, source);
586
1252
  const complexity = adapter.extractComplexity(relativePath, source);
587
- let intent = [];
588
- let antiPatterns = [];
589
- if (!options.skipHistory) {
590
- const commits = await gitAdapter.getCommitHistory(relativePath);
591
- intent = commits.map((c) => ({
592
- hash: c.hash,
593
- message: c.message,
594
- date: c.date,
595
- kind: "commit"
596
- }));
597
- antiPatterns = revertDetector.detect(commits);
1253
+ for (const sym of symbols) {
1254
+ symbolRegistry.set(sym.symbolId, sym.kind);
598
1255
  }
599
- const fileIndex = {
600
- file: relativePath,
601
- lastModified,
602
- contentHash: hasher.hash(source),
603
- symbols,
604
- edges,
605
- complexity,
606
- intent,
607
- antiPatterns
608
- };
609
- writer.write(fileIndex);
610
- indices.push(fileIndex);
1256
+ pendingIndices.push({
1257
+ relativePath,
1258
+ source,
1259
+ fileIndex: {
1260
+ file: relativePath,
1261
+ lastModified,
1262
+ contentHash: hasher.hash(source),
1263
+ symbols,
1264
+ edges: [],
1265
+ complexity,
1266
+ intent: [],
1267
+ antiPatterns: []
1268
+ }
1269
+ });
611
1270
  processed++;
612
1271
  if (processed % 50 === 0) {
613
- console.error(`[ctxo] Processed ${processed}/${files.length} files`);
1272
+ console.error(`[ctxo] Processed ${processed}/${files.length} files (symbols)`);
614
1273
  }
615
1274
  } catch (err) {
616
1275
  console.error(`[ctxo] Skipped ${relativePath}: ${err.message}`);
617
1276
  }
618
1277
  }
1278
+ const allSources = /* @__PURE__ */ new Map();
1279
+ for (const entry of pendingIndices) {
1280
+ allSources.set(entry.relativePath, entry.source);
1281
+ }
1282
+ tsMorphAdapter.loadProjectSources(allSources);
1283
+ for (const entry of pendingIndices) {
1284
+ const adapter = registry.getAdapter(entry.relativePath);
1285
+ if (!adapter) continue;
1286
+ try {
1287
+ adapter.setSymbolRegistry?.(symbolRegistry);
1288
+ entry.fileIndex.edges = adapter.extractEdges(entry.relativePath, entry.source);
1289
+ } catch (err) {
1290
+ console.error(`[ctxo] Edge extraction failed for ${entry.relativePath}: ${err.message}`);
1291
+ }
1292
+ }
1293
+ tsMorphAdapter.clearProjectSources();
1294
+ if (!options.skipHistory && pendingIndices.length > 0) {
1295
+ const maxHistory = options.maxHistory ?? 20;
1296
+ const batchHistory = await gitAdapter.getBatchHistory?.(maxHistory) ?? /* @__PURE__ */ new Map();
1297
+ for (const { relativePath, fileIndex } of pendingIndices) {
1298
+ const commits = batchHistory.get(relativePath) ?? [];
1299
+ fileIndex.intent = commits.map((c) => ({
1300
+ hash: c.hash,
1301
+ message: c.message,
1302
+ date: c.date,
1303
+ kind: "commit"
1304
+ }));
1305
+ fileIndex.antiPatterns = revertDetector.detect(commits);
1306
+ }
1307
+ }
1308
+ if (!options.skipHistory && pendingIndices.length > 0) {
1309
+ const fileIndices = pendingIndices.map((e) => e.fileIndex);
1310
+ const coChangeMatrix = aggregateCoChanges(fileIndices);
1311
+ writer.writeCoChanges(coChangeMatrix);
1312
+ console.error(`[ctxo] Co-change analysis: ${coChangeMatrix.entries.length} file pairs detected`);
1313
+ }
1314
+ const indices = [];
1315
+ for (const { fileIndex } of pendingIndices) {
1316
+ writer.write(fileIndex);
1317
+ indices.push(fileIndex);
1318
+ }
619
1319
  schemaManager.writeVersion();
620
1320
  const storage = new SqliteStorageAdapter(this.ctxoRoot);
621
1321
  try {
@@ -629,6 +1329,20 @@ var IndexCommand = class {
629
1329
  }
630
1330
  console.error(`[ctxo] Index complete: ${processed} files indexed`);
631
1331
  }
1332
+ registerTreeSitterAdapters(registry) {
1333
+ try {
1334
+ const { GoAdapter: GoAdapter2 } = (init_go_adapter(), __toCommonJS(go_adapter_exports));
1335
+ registry.register(new GoAdapter2());
1336
+ } catch {
1337
+ console.error("[ctxo] Go adapter unavailable (tree-sitter-go not installed)");
1338
+ }
1339
+ try {
1340
+ const { CSharpAdapter: CSharpAdapter2 } = (init_csharp_adapter(), __toCommonJS(csharp_adapter_exports));
1341
+ registry.register(new CSharpAdapter2());
1342
+ } catch {
1343
+ console.error("[ctxo] C# adapter unavailable (tree-sitter-c-sharp not installed)");
1344
+ }
1345
+ }
632
1346
  discoverFilesIn(root) {
633
1347
  try {
634
1348
  const output = execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], {
@@ -675,20 +1389,23 @@ var IndexCommand = class {
675
1389
  }
676
1390
  }
677
1391
  isSupportedExtension(filePath) {
678
- const ext = extname3(filePath).toLowerCase();
679
- return [".ts", ".tsx", ".js", ".jsx"].includes(ext);
1392
+ const ext = extname4(filePath).toLowerCase();
1393
+ return this.supportedExtensions.has(ext);
680
1394
  }
681
1395
  async runCheck() {
682
1396
  console.error("[ctxo] Checking index freshness...");
1397
+ const registry = new LanguageAdapterRegistry();
1398
+ registry.register(new TsMorphAdapter());
1399
+ this.registerTreeSitterAdapters(registry);
1400
+ this.supportedExtensions = registry.getSupportedExtensions();
683
1401
  const hasher = new ContentHasher();
684
1402
  const files = this.discoverFilesIn(this.projectRoot);
685
- const reader = new (await import("./json-index-reader-PNLPAS42.js")).JsonIndexReader(this.ctxoRoot);
1403
+ const reader = new (await import("./json-index-reader-FCKSKA6R.js")).JsonIndexReader(this.ctxoRoot);
686
1404
  const indices = reader.readAll();
687
1405
  const indexedMap = new Map(indices.map((i) => [i.file, i]));
688
1406
  let staleCount = 0;
689
1407
  for (const filePath of files) {
690
- const ext = extname3(filePath).toLowerCase();
691
- if (![".ts", ".tsx", ".js", ".jsx"].includes(ext)) continue;
1408
+ if (!this.isSupportedExtension(filePath)) continue;
692
1409
  const relativePath = relative(this.projectRoot, filePath).replace(/\\/g, "/");
693
1410
  const indexed = indexedMap.get(relativePath);
694
1411
  if (!indexed) {
@@ -911,7 +1628,7 @@ var InitCommand = class {
911
1628
  };
912
1629
 
913
1630
  // src/cli/watch-command.ts
914
- import { join as join9, extname as extname4, relative as relative2 } from "path";
1631
+ import { join as join9, extname as extname5, relative as relative2 } from "path";
915
1632
  import { readFileSync as readFileSync4 } from "fs";
916
1633
 
917
1634
  // src/adapters/watcher/chokidar-watcher-adapter.ts
@@ -953,7 +1670,6 @@ var ChokidarWatcherAdapter = class {
953
1670
  };
954
1671
 
955
1672
  // src/cli/watch-command.ts
956
- var SUPPORTED_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
957
1673
  var DEBOUNCE_MS = 300;
958
1674
  var WatchCommand = class {
959
1675
  projectRoot;
@@ -966,6 +1682,8 @@ var WatchCommand = class {
966
1682
  console.error("[ctxo] Starting file watcher...");
967
1683
  const registry = new LanguageAdapterRegistry();
968
1684
  registry.register(new TsMorphAdapter());
1685
+ this.registerTreeSitterAdapters(registry);
1686
+ const supportedExtensions = registry.getSupportedExtensions();
969
1687
  const writer = new JsonIndexWriter(this.ctxoRoot);
970
1688
  const storage = new SqliteStorageAdapter(this.ctxoRoot);
971
1689
  await storage.init();
@@ -975,8 +1693,6 @@ var WatchCommand = class {
975
1693
  const watcher = new ChokidarWatcherAdapter(this.projectRoot);
976
1694
  const pendingFiles = /* @__PURE__ */ new Map();
977
1695
  const reindexFile = async (filePath) => {
978
- const ext = extname4(filePath).toLowerCase();
979
- if (!SUPPORTED_EXTENSIONS2.has(ext)) return;
980
1696
  const adapter = registry.getAdapter(filePath);
981
1697
  if (!adapter) return;
982
1698
  const relativePath = relative2(this.projectRoot, filePath).replace(/\\/g, "/");
@@ -1024,8 +1740,8 @@ var WatchCommand = class {
1024
1740
  };
1025
1741
  watcher.start((event, filePath) => {
1026
1742
  if (event === "unlink") {
1027
- const ext = extname4(filePath).toLowerCase();
1028
- if (!SUPPORTED_EXTENSIONS2.has(ext)) return;
1743
+ const ext = extname5(filePath).toLowerCase();
1744
+ if (!supportedExtensions.has(ext)) return;
1029
1745
  const relativePath = relative2(this.projectRoot, filePath).replace(/\\/g, "/");
1030
1746
  writer.delete(relativePath);
1031
1747
  storage.deleteSymbolFile(relativePath);
@@ -1049,6 +1765,20 @@ var WatchCommand = class {
1049
1765
  process.on("SIGINT", cleanup);
1050
1766
  process.on("SIGTERM", cleanup);
1051
1767
  }
1768
+ registerTreeSitterAdapters(registry) {
1769
+ try {
1770
+ const { GoAdapter: GoAdapter2 } = (init_go_adapter(), __toCommonJS(go_adapter_exports));
1771
+ registry.register(new GoAdapter2());
1772
+ } catch {
1773
+ console.error("[ctxo] Go adapter unavailable (tree-sitter-go not installed)");
1774
+ }
1775
+ try {
1776
+ const { CSharpAdapter: CSharpAdapter2 } = (init_csharp_adapter(), __toCommonJS(csharp_adapter_exports));
1777
+ registry.register(new CSharpAdapter2());
1778
+ } catch {
1779
+ console.error("[ctxo] C# adapter unavailable (tree-sitter-c-sharp not installed)");
1780
+ }
1781
+ }
1052
1782
  };
1053
1783
 
1054
1784
  // src/cli/cli-router.ts
@@ -1070,10 +1800,18 @@ var CliRouter = class {
1070
1800
  if (fileIdx !== -1 && (!fileArg || fileArg.startsWith("--"))) {
1071
1801
  console.error("[ctxo] --file requires a path argument");
1072
1802
  process.exit(1);
1803
+ return;
1073
1804
  }
1074
1805
  const checkArg = args.includes("--check");
1075
1806
  const skipHistory = args.includes("--skip-history");
1076
- await new IndexCommand(this.projectRoot).run({ file: fileArg, check: checkArg, skipHistory });
1807
+ const maxHistoryIdx = args.indexOf("--max-history");
1808
+ const maxHistoryArg = maxHistoryIdx !== -1 ? Number(args[maxHistoryIdx + 1]) : void 0;
1809
+ if (maxHistoryIdx !== -1 && (!maxHistoryArg || isNaN(maxHistoryArg) || maxHistoryArg < 1)) {
1810
+ console.error("[ctxo] --max-history requires a positive integer");
1811
+ process.exit(1);
1812
+ return;
1813
+ }
1814
+ await new IndexCommand(this.projectRoot).run({ file: fileArg, check: checkArg, skipHistory, maxHistory: maxHistoryArg });
1077
1815
  break;
1078
1816
  }
1079
1817
  case "sync":
@@ -1094,6 +1832,7 @@ var CliRouter = class {
1094
1832
  default:
1095
1833
  console.error(`[ctxo] Unknown command: "${command}". Run "ctxo --help" for usage.`);
1096
1834
  process.exit(1);
1835
+ return;
1097
1836
  }
1098
1837
  }
1099
1838
  printHelp() {
@@ -1102,7 +1841,7 @@ ctxo \u2014 MCP server for dependency-aware codebase context
1102
1841
 
1103
1842
  Usage:
1104
1843
  ctxo Start MCP server (stdio transport)
1105
- ctxo index Build full codebase index
1844
+ ctxo index Build full codebase index (--max-history N, default 20)
1106
1845
  ctxo sync Rebuild SQLite cache from committed JSON index
1107
1846
  ctxo watch Start file watcher for incremental re-indexing
1108
1847
  ctxo verify-index CI gate: fail if index is stale
@@ -1115,4 +1854,4 @@ Usage:
1115
1854
  export {
1116
1855
  CliRouter
1117
1856
  };
1118
- //# sourceMappingURL=cli-router-PIWHLS5F.js.map
1857
+ //# sourceMappingURL=cli-router-NRUGPICL.js.map