kodingo-cli 1.3.0 → 1.3.2

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.
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ /**
3
+ * Real tests for C#/.NET support in tree-sitter-parser.ts / ast-parser.ts —
4
+ * build brief "Add real C#/.NET support to the CLI's parser" (2026-09-20).
5
+ *
6
+ * The real bar per the brief: not just "C# symbols get extracted", but
7
+ * genuine, stable deduplication — the same, unchanged function produces a
8
+ * byte-for-byte identical fingerprint across repeated runs, and a genuinely
9
+ * changed function's fingerprint correctly changes.
10
+ *
11
+ * Uses Node's built-in test runner (node:test) — this repo has no existing
12
+ * test framework to inherit (confirmed: package.json's own "test" script
13
+ * was a stub), and node:test needs zero new dependencies. Compiled and run
14
+ * via `npm test` (tsc then `node --test dist/__tests__`).
15
+ */
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ const node_test_1 = require("node:test");
21
+ const strict_1 = __importDefault(require("node:assert/strict"));
22
+ const tree_sitter_parser_1 = require("../utils/tree-sitter-parser");
23
+ const ast_parser_1 = require("../utils/ast-parser");
24
+ const REAL_CSHARP_SOURCE = `
25
+ using System;
26
+ using System.Threading.Tasks;
27
+
28
+ namespace Acme.Orders
29
+ {
30
+ public class OrderService : IOrderService
31
+ {
32
+ private readonly IRepository _repo;
33
+
34
+ public OrderService(IRepository repo)
35
+ {
36
+ _repo = repo;
37
+ }
38
+
39
+ public async Task<Order> GetOrderAsync(int id)
40
+ {
41
+ var order = await _repo.FindAsync(id);
42
+ LogAccess(id);
43
+ return order;
44
+ }
45
+
46
+ private void LogAccess(int id)
47
+ {
48
+ Console.WriteLine($"Accessed order {id}");
49
+ }
50
+
51
+ public static int Add(int a, int b)
52
+ {
53
+ if (a > 0)
54
+ {
55
+ return a + b;
56
+ }
57
+ return b;
58
+ }
59
+ }
60
+
61
+ public interface IOrderService
62
+ {
63
+ Task<Order> GetOrderAsync(int id);
64
+ }
65
+
66
+ public struct Point
67
+ {
68
+ public int Sum(int x, int y) => x + y;
69
+ }
70
+
71
+ public record OrderDto(int Id, string Name)
72
+ {
73
+ public string Describe() => $"{Id}: {Name}";
74
+ }
75
+
76
+ public class Order { }
77
+ }
78
+ `;
79
+ // A real, structural change to GetOrderAsync — a distinct code path added,
80
+ // not a cosmetic rename/whitespace/comment tweak (those are what
81
+ // computeFingerprint is specifically designed to SURVIVE).
82
+ const CHANGED_CSHARP_SOURCE = REAL_CSHARP_SOURCE.replace(`public async Task<Order> GetOrderAsync(int id)
83
+ {
84
+ var order = await _repo.FindAsync(id);
85
+ LogAccess(id);
86
+ return order;
87
+ }`, `public async Task<Order> GetOrderAsync(int id)
88
+ {
89
+ var order = await _repo.FindAsync(id);
90
+ LogAccess(id);
91
+ if (order == null) throw new InvalidOperationException("not found");
92
+ return order;
93
+ }`);
94
+ (0, node_test_1.describe)("parseWithTreeSitter — real C# extraction", () => {
95
+ (0, node_test_1.it)("extracts every real method, constructor, and local function with class-qualified names", async () => {
96
+ const symbols = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
97
+ const names = symbols.map(s => s.name).sort();
98
+ strict_1.default.deepEqual(names, [
99
+ "IOrderService.GetOrderAsync",
100
+ "OrderDto.Describe",
101
+ "OrderService.Add",
102
+ "OrderService.GetOrderAsync",
103
+ "OrderService.LogAccess",
104
+ "OrderService.OrderService",
105
+ "Point.Sum",
106
+ ].sort());
107
+ });
108
+ (0, node_test_1.it)("real, correct method vs constructor typing", async () => {
109
+ const symbols = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
110
+ const ctor = symbols.find(s => s.name === "OrderService.OrderService");
111
+ const method = symbols.find(s => s.name === "OrderService.GetOrderAsync");
112
+ strict_1.default.equal(ctor?.type, "method");
113
+ strict_1.default.equal(method?.type, "method");
114
+ });
115
+ (0, node_test_1.it)("real call extraction trims member-access callees to their trailing identifier", async () => {
116
+ const symbols = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
117
+ const method = symbols.find(s => s.name === "OrderService.GetOrderAsync");
118
+ strict_1.default.ok(method);
119
+ strict_1.default.ok(method.calls.includes("FindAsync"), `expected FindAsync in ${JSON.stringify(method.calls)}`);
120
+ strict_1.default.ok(method.calls.includes("LogAccess"), `expected LogAccess in ${JSON.stringify(method.calls)}`);
121
+ });
122
+ (0, node_test_1.it)("real using-directive extraction", async () => {
123
+ const symbols = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
124
+ strict_1.default.ok(symbols[0].imports.some(i => i.source.includes("using System;")));
125
+ strict_1.default.ok(symbols[0].imports.some(i => i.source.includes("using System.Threading.Tasks;")));
126
+ });
127
+ (0, node_test_1.it)("returns [] for an unrecognized extension rather than throwing", async () => {
128
+ const symbols = await (0, tree_sitter_parser_1.parseWithTreeSitter)("readme.md", "# not code");
129
+ strict_1.default.deepEqual(symbols, []);
130
+ });
131
+ });
132
+ (0, node_test_1.describe)("real deduplication — the actual bar this brief is judged against", () => {
133
+ (0, node_test_1.it)("the SAME, unchanged C# function produces a byte-for-byte identical fingerprint across repeated runs", async () => {
134
+ const run1 = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
135
+ const run2 = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
136
+ const run3 = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
137
+ const fp1 = run1.find(s => s.name === "OrderService.GetOrderAsync")?.fingerprint;
138
+ const fp2 = run2.find(s => s.name === "OrderService.GetOrderAsync")?.fingerprint;
139
+ const fp3 = run3.find(s => s.name === "OrderService.GetOrderAsync")?.fingerprint;
140
+ strict_1.default.ok(fp1);
141
+ strict_1.default.equal(fp1, fp2);
142
+ strict_1.default.equal(fp2, fp3);
143
+ // Every single real symbol in the file, not just one — genuine, stable
144
+ // dedup across the whole file, not a cherry-picked case.
145
+ for (let i = 0; i < run1.length; i++) {
146
+ strict_1.default.equal(run1[i].fingerprint, run2[i].fingerprint, `symbol ${run1[i].name} fingerprint drifted across runs`);
147
+ }
148
+ });
149
+ (0, node_test_1.it)("via the real, full parseFile dispatch (the actual path scan-repo.ts calls) — same stability", async () => {
150
+ const run1 = await (0, ast_parser_1.parseFile)("OrderService.cs", REAL_CSHARP_SOURCE);
151
+ const run2 = await (0, ast_parser_1.parseFile)("OrderService.cs", REAL_CSHARP_SOURCE);
152
+ strict_1.default.ok(run1.length > 0);
153
+ strict_1.default.deepEqual(run1.map(s => s.fingerprint), run2.map(s => s.fingerprint));
154
+ });
155
+ (0, node_test_1.it)("cosmetic-only changes (comments, whitespace) do NOT change the fingerprint — by design", async () => {
156
+ const cosmetic = REAL_CSHARP_SOURCE.replace(`public static int Add(int a, int b)`, `// adds two numbers\n public static int Add(int a, int b)`);
157
+ const before = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
158
+ const after = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", cosmetic);
159
+ const fpBefore = before.find(s => s.name === "OrderService.Add")?.fingerprint;
160
+ const fpAfter = after.find(s => s.name === "OrderService.Add")?.fingerprint;
161
+ strict_1.default.equal(fpBefore, fpAfter);
162
+ });
163
+ (0, node_test_1.it)("a genuinely CHANGED function's fingerprint correctly changes — an edit is not invisible", async () => {
164
+ const before = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", REAL_CSHARP_SOURCE);
165
+ const after = await (0, tree_sitter_parser_1.parseWithTreeSitter)("OrderService.cs", CHANGED_CSHARP_SOURCE);
166
+ const fpBefore = before.find(s => s.name === "OrderService.GetOrderAsync")?.fingerprint;
167
+ const fpAfter = after.find(s => s.name === "OrderService.GetOrderAsync")?.fingerprint;
168
+ strict_1.default.ok(fpBefore);
169
+ strict_1.default.ok(fpAfter);
170
+ strict_1.default.notEqual(fpBefore, fpAfter);
171
+ // Every OTHER real symbol in the file is untouched by the edit and must
172
+ // keep its own stable fingerprint — proving the change is properly
173
+ // scoped to the actual edited function, not a global reshuffle.
174
+ for (const name of ["OrderService.Add", "OrderService.LogAccess", "OrderService.OrderService", "Point.Sum"]) {
175
+ const b = before.find(s => s.name === name)?.fingerprint;
176
+ const a = after.find(s => s.name === name)?.fingerprint;
177
+ strict_1.default.equal(b, a, `unrelated symbol ${name} should not have changed`);
178
+ }
179
+ });
180
+ });
181
+ (0, node_test_1.describe)("computeFingerprint — sanity on real C# body text", () => {
182
+ (0, node_test_1.it)("normalizes C# string interpolation/comments deterministically", () => {
183
+ const a = (0, ast_parser_1.computeFingerprint)(`Console.WriteLine($"Accessed order {id}"); // log it`);
184
+ const b = (0, ast_parser_1.computeFingerprint)(`Console.WriteLine($"Accessed order {id}");`);
185
+ strict_1.default.equal(a, b);
186
+ });
187
+ });
@@ -119,6 +119,8 @@ function mapApiRecord(r) {
119
119
  record.fingerprint = r.fingerprint;
120
120
  if (r.deniedAt)
121
121
  record.deniedAt = new Date(r.deniedAt);
122
+ if (r.contentAsOf)
123
+ record.contentAsOf = new Date(r.contentAsOf);
122
124
  return record;
123
125
  }
124
126
  // ── Public API (mirrors memory-persistence.ts) ────────────────────────────────
@@ -161,6 +163,8 @@ async function saveMemory(input) {
161
163
  body.correctsId = input.correctsId;
162
164
  if (input.correctedById)
163
165
  body.correctedById = input.correctedById;
166
+ if (input.contentAsOf)
167
+ body.contentAsOf = input.contentAsOf.toISOString();
164
168
  // Attribution
165
169
  try {
166
170
  const config = (0, persistence_config_1.readConfig)();
@@ -61,6 +61,11 @@ async function initDb() {
61
61
  END IF;
62
62
  END $$;
63
63
 
64
+ -- Real, best-known age of the underlying decision/code, separate from
65
+ -- created_at (mirrors kodingo-api's schema.ts). NULL when genuinely
66
+ -- unknown — never defaulted to created_at.
67
+ ALTER TABLE kodingo_memory ADD COLUMN IF NOT EXISTS content_as_of timestamptz NULL;
68
+
64
69
  -- Indexes
65
70
  CREATE INDEX IF NOT EXISTS kodingo_memory_created_at_idx
66
71
  ON kodingo_memory (created_at);
@@ -128,7 +133,7 @@ async function saveMemory(input) {
128
133
  WITH inserted AS (
129
134
  INSERT INTO kodingo_memory (
130
135
  id, type, title, content, tags, repo_path,
131
- status, confidence, symbol, external_id, evidence_kind,
136
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
132
137
  corrects_id, corrected_by_id
133
138
  )
134
139
  VALUES (
@@ -143,12 +148,13 @@ async function saveMemory(input) {
143
148
  ${sqlNullableLiteral(input.symbol)},
144
149
  ${sqlNullableLiteral(externalId)},
145
150
  ${sqlLiteral(input.evidenceKind)},
151
+ ${sqlNullableTimestampLiteral(input.contentAsOf)},
146
152
  ${sqlNullableUuidLiteral(input.correctsId)},
147
153
  ${sqlNullableUuidLiteral(input.correctedById)}
148
154
  )
149
155
  RETURNING
150
156
  id, type, title, content, tags, repo_path,
151
- status, confidence, symbol, external_id, evidence_kind,
157
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
152
158
  corrects_id, corrected_by_id,
153
159
  created_at
154
160
  )
@@ -165,7 +171,7 @@ async function getMemoryById(id) {
165
171
  SELECT row_to_json(result) FROM (
166
172
  SELECT
167
173
  id, type, title, content, tags, repo_path,
168
- status, confidence, symbol, external_id, evidence_kind,
174
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
169
175
  corrects_id, corrected_by_id,
170
176
  created_at
171
177
  FROM kodingo_memory
@@ -193,7 +199,7 @@ async function updateMemoryLifecycle(params) {
193
199
  WHERE id = ${sqlLiteral(params.id)}
194
200
  RETURNING
195
201
  id, type, title, content, tags, repo_path,
196
- status, confidence, symbol, external_id, evidence_kind,
202
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
197
203
  corrects_id, corrected_by_id,
198
204
  created_at
199
205
  )
@@ -246,7 +252,7 @@ async function getMemoryByExternalId(externalId, repoPath) {
246
252
  SELECT row_to_json(result) FROM (
247
253
  SELECT
248
254
  id, type, title, content, tags, repo_path,
249
- status, confidence, symbol, external_id, evidence_kind,
255
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
250
256
  corrects_id, corrected_by_id,
251
257
  created_at
252
258
  FROM kodingo_memory
@@ -271,7 +277,7 @@ async function getProposedOrIgnoredRecordBySymbol(params) {
271
277
  SELECT row_to_json(result) FROM (
272
278
  SELECT
273
279
  id, type, title, content, tags, repo_path,
274
- status, confidence, symbol, external_id, evidence_kind,
280
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
275
281
  corrects_id, corrected_by_id,
276
282
  created_at
277
283
  FROM kodingo_memory
@@ -303,7 +309,7 @@ async function updateMemoryConfidence(params) {
303
309
  WHERE id = ${sqlLiteral(params.id)}
304
310
  RETURNING
305
311
  id, type, title, content, tags, repo_path,
306
- status, confidence, symbol, external_id, evidence_kind,
312
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
307
313
  corrects_id, corrected_by_id,
308
314
  created_at
309
315
  )
@@ -346,7 +352,7 @@ async function queryMemory(text, limit = 10, repoPath, opts = {}) {
346
352
  SELECT row_to_json(result) FROM (
347
353
  SELECT DISTINCT ON (coalesce(symbol, id::text))
348
354
  id, type, title, content, tags, repo_path,
349
- status, confidence, symbol, external_id, evidence_kind,
355
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
350
356
  corrects_id, corrected_by_id,
351
357
  created_at
352
358
  FROM kodingo_memory
@@ -377,7 +383,7 @@ async function queryMemory(text, limit = 10, repoPath, opts = {}) {
377
383
  SELECT row_to_json(result) FROM (
378
384
  SELECT
379
385
  id, type, title, content, tags, repo_path,
380
- status, confidence, symbol, external_id, evidence_kind,
386
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
381
387
  corrects_id, corrected_by_id,
382
388
  created_at,
383
389
  CASE
@@ -441,7 +447,7 @@ async function queryMemoryBySymbol(params) {
441
447
  SELECT row_to_json(result) FROM (
442
448
  SELECT
443
449
  id, type, title, content, tags, repo_path,
444
- status, confidence, symbol, external_id, evidence_kind,
450
+ status, confidence, symbol, external_id, evidence_kind, content_as_of,
445
451
  corrects_id, corrected_by_id,
446
452
  created_at
447
453
  FROM kodingo_memory
@@ -473,6 +479,7 @@ function mapRow(row) {
473
479
  symbol: row.symbol ?? null,
474
480
  externalId: row.external_id ?? null,
475
481
  evidenceKind: row.evidence_kind ?? "sourced",
482
+ contentAsOf: row.content_as_of ? new Date(row.content_as_of) : null,
476
483
  correctsId: row.corrects_id ?? null,
477
484
  correctedById: row.corrected_by_id ?? null,
478
485
  createdAt: new Date(row.created_at),
@@ -538,6 +545,11 @@ function sqlNullableUuidLiteral(value) {
538
545
  return "NULL";
539
546
  return `${sqlLiteral(value)}::uuid`;
540
547
  }
548
+ function sqlNullableTimestampLiteral(value) {
549
+ if (!value)
550
+ return "NULL";
551
+ return `${sqlLiteral(value.toISOString())}::timestamptz`;
552
+ }
541
553
  function sqlArrayLiteral(values) {
542
554
  if (!values || values.length === 0)
543
555
  return "NULL";
@@ -61,7 +61,7 @@ const API_BASE = "https://kodingo-api.onrender.com";
61
61
  const SUPPORTED_EXTENSIONS = [
62
62
  ".ts", ".tsx", ".js", ".jsx",
63
63
  ".py", ".go", ".rs", ".php",
64
- ".java", ".kt", ".rb", ".c", ".cpp",
64
+ ".java", ".kt", ".rb", ".c", ".cpp", ".cs",
65
65
  ];
66
66
  const IGNORED_DIRS = [
67
67
  "node_modules", ".git", ".kortex", "dist", "build",
@@ -112,6 +112,8 @@ async function extractSymbols(filePath) {
112
112
  code: n.body.slice(0, 2000), // cap to avoid huge payloads
113
113
  file: filePath,
114
114
  fingerprint: n.fingerprint,
115
+ startLine: n.startLine,
116
+ endLine: n.endLine,
115
117
  }));
116
118
  }
117
119
  catch {
@@ -201,6 +203,14 @@ async function inferAndCapture(token, symbol, repo, graph, repoRoot) {
201
203
  // — Kortex's own conclusion, not traceable to one real external
202
204
  // event, same class as kodingo-api's githubScanner.ts.
203
205
  evidenceKind: "derived",
206
+ // Real line range + repo-relative file path (Problem B) — lets
207
+ // kodingo-api resolve this symbol's real content age via git blame
208
+ // asynchronously after capture (it has the GitHub App token this
209
+ // CLI doesn't). Without these, content_as_of stays null — never
210
+ // guessed from scan timing.
211
+ startLine: symbol.startLine ?? undefined,
212
+ endLine: symbol.endLine ?? undefined,
213
+ filePath: path_1.default.relative(repoRoot, symbol.file).split(path_1.default.sep).join("/"),
204
214
  }),
205
215
  });
206
216
  return saveRes.ok;
@@ -104,6 +104,7 @@ class CloudEventPort {
104
104
  evidenceKind: "sourced",
105
105
  externalId,
106
106
  tags: ["signal:git", "kind:decision"],
107
+ contentAsOf: decision.proposedAt,
107
108
  };
108
109
  if (title)
109
110
  input.title = title;
@@ -77,7 +77,10 @@ class DbEventPort {
77
77
  }
78
78
  // No existing proposed/ignored record — insert new record as usual.
79
79
  // Git-signal-derived captures are genuinely sourced — externalId here is
80
- // a real git:<commitHash> reference (see the caller).
80
+ // a real git:<commitHash> reference (see the caller). contentAsOf is the
81
+ // real commit's own author date (decision.proposedAt) — trivially in
82
+ // hand here, unlike bulk/autonomous scans which have no equivalent
83
+ // single real event to anchor to (see contentAge.ts, kodingo-api).
81
84
  const input = {
82
85
  type: "decision",
83
86
  content,
@@ -86,6 +89,7 @@ class DbEventPort {
86
89
  confidence: 0.3,
87
90
  evidenceKind: "sourced",
88
91
  externalId,
92
+ contentAsOf: decision.proposedAt,
89
93
  };
90
94
  if (title)
91
95
  input.title = title;
@@ -417,7 +417,7 @@ const KOTLIN_PATTERNS = [
417
417
  /^(?:fun|override\s+fun|suspend\s+fun|private\s+fun|public\s+fun)\s+(\w+)/,
418
418
  /^(?:class|data class|object|interface)\s+(\w+)/,
419
419
  ];
420
- const TREE_SITTER_EXTENSIONS = new Set([".py", ".go", ".rs", ".java", ".kt", ".rb", ".php", ".c", ".cpp"]);
420
+ const TREE_SITTER_EXTENSIONS = new Set([".py", ".go", ".rs", ".java", ".kt", ".rb", ".php", ".c", ".cpp", ".cs"]);
421
421
  async function parseFile(filePath, source) {
422
422
  const ext = path.extname(filePath).toLowerCase();
423
423
  switch (ext) {
@@ -64,7 +64,7 @@ const ast_parser_1 = require("./ast-parser");
64
64
  const SUPPORTED_EXTENSIONS = [
65
65
  ".ts", ".tsx", ".js", ".jsx",
66
66
  ".py", ".go", ".rs", ".php",
67
- ".java", ".kt", ".rb", ".c", ".cpp",
67
+ ".java", ".kt", ".rb", ".c", ".cpp", ".cs",
68
68
  ];
69
69
  const TS_JS_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"]);
70
70
  const IGNORED_DIRS = [
@@ -104,6 +104,31 @@ const LANGUAGE_CONFIGS = {
104
104
  nameFieldCandidates: [], // handled via declarator fallback
105
105
  importTypes: ["preproc_include"],
106
106
  },
107
+ // C# — build brief "Add real C#/.NET support to the CLI's parser"
108
+ // (2026-09-20). Real node types confirmed directly against the bundled
109
+ // tree-sitter-c_sharp.wasm grammar (tree-sitter-wasms package, already a
110
+ // real dependency here — no new native package needed, see this brief's
111
+ // own final report for why that matters). method_declaration,
112
+ // constructor_declaration, and local_function_statement all carry a real
113
+ // `name` field; invocation_expression carries a real `function` field
114
+ // whose text (e.g. "_repo.FindAsync", "Console.WriteLine") is trimmed to
115
+ // its trailing identifier by the same extractTrailingIdentifier() already
116
+ // proven for PHP's "$this->service->method" shape — no C#-specific call
117
+ // handling needed. Deliberately excludes operator_declaration,
118
+ // destructor_declaration, and property accessors — real, distinct node
119
+ // shapes with no `name` field (or none at all) that no other language
120
+ // here gets special-cased treatment for either; kept proportionate to the
121
+ // existing five languages' own level of coverage rather than gold-plating
122
+ // C# beyond them.
123
+ csharp: {
124
+ wasmFile: "tree-sitter-c_sharp.wasm",
125
+ functionTypes: ["method_declaration", "constructor_declaration", "local_function_statement"],
126
+ classTypes: ["class_declaration", "interface_declaration", "struct_declaration", "record_declaration"],
127
+ callTypes: ["invocation_expression"],
128
+ calleeFieldCandidates: ["function"],
129
+ nameFieldCandidates: ["name"],
130
+ importTypes: ["using_directive"],
131
+ },
107
132
  };
108
133
  const EXT_TO_LANGUAGE = {
109
134
  ".py": "python",
@@ -115,6 +140,7 @@ const EXT_TO_LANGUAGE = {
115
140
  ".php": "php",
116
141
  ".c": "c",
117
142
  ".cpp": "cpp",
143
+ ".cs": "csharp",
118
144
  };
119
145
  let initialised = false;
120
146
  const languageCache = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodingo-cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Kodingo CLI",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -16,7 +16,7 @@
16
16
  "build": "tsc -p tsconfig.json",
17
17
  "dev": "tsc -p tsconfig.json --watch",
18
18
  "start": "node dist/cli.js",
19
- "test": "echo \"Error: no test specified\" && exit 1",
19
+ "test": "npm run build && node --test dist/__tests__/*.js",
20
20
  "prepare": "npm run build"
21
21
  },
22
22
  "dependencies": {
@@ -26,7 +26,7 @@
26
26
  "@typescript-eslint/typescript-estree": "^8.61.1",
27
27
  "commander": "^12.1.0",
28
28
  "kodingo-agent": "^0.2.0",
29
- "kodingo-core": "^0.3.0",
29
+ "kodingo-core": "^0.3.1",
30
30
  "open": "^11.0.0",
31
31
  "simple-git": "^3.30.0",
32
32
  "tree-sitter-wasms": "^0.1.13",