kodingo-cli 1.0.26 → 1.0.28

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.
@@ -4,6 +4,39 @@
4
4
  * Drop-in replacement for memory-persistence.ts — same function signatures,
5
5
  * same return types, but talks to kodingo-api over HTTP instead of psql.
6
6
  */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
7
40
  Object.defineProperty(exports, "__esModule", { value: true });
8
41
  exports.initDb = initDb;
9
42
  exports.checkDatabaseConnection = checkDatabaseConnection;
@@ -18,6 +51,8 @@ exports.getProposedOrIgnoredRecordBySymbol = getProposedOrIgnoredRecordBySymbol;
18
51
  exports.deleteMemoryById = deleteMemoryById;
19
52
  exports.queryMemory = queryMemory;
20
53
  exports.queryMemoryBySymbol = queryMemoryBySymbol;
54
+ const os = __importStar(require("os"));
55
+ const persistence_config_1 = require("../../utils/persistence-config");
21
56
  function getConfig() {
22
57
  const apiUrl = process.env.KODINGO_API_URL;
23
58
  const token = process.env.KODINGO_API_TOKEN;
@@ -124,6 +159,15 @@ async function saveMemory(input) {
124
159
  body.correctsId = input.correctsId;
125
160
  if (input.correctedById)
126
161
  body.correctedById = input.correctedById;
162
+ // Attribution
163
+ try {
164
+ const config = (0, persistence_config_1.readConfig)();
165
+ if (config.machineId) {
166
+ body.machineId = config.machineId;
167
+ body.displayName = os.hostname();
168
+ }
169
+ }
170
+ catch { }
127
171
  const record = await apiFetch("/memory", {
128
172
  method: "POST",
129
173
  body: JSON.stringify(body),
@@ -7,13 +7,81 @@ exports.scanGitCommand = scanGitCommand;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const os_1 = __importDefault(require("os"));
10
+ const ast_parser_1 = require("../utils/ast-parser");
11
+ const persistence_config_1 = require("../utils/persistence-config");
10
12
  const simple_git_1 = __importDefault(require("simple-git"));
11
13
  const git_listener_1 = require("../adapters/git-adapter/git-listener");
12
14
  const db_event_port_1 = require("../ports/db-event-port");
13
15
  const cloud_event_port_1 = require("../ports/cloud-event-port");
14
- const persistence_config_1 = require("../utils/persistence-config");
16
+ const persistence_config_2 = require("../utils/persistence-config");
17
+ const API_BASE_DEFAULT = "https://kodingo-api.onrender.com";
18
+ function getCredentials(repoRoot) {
19
+ const config = (0, persistence_config_1.readConfig)();
20
+ const normRoot = repoRoot.replace(/\/+$/, "");
21
+ let wsEntry = config.workspaces?.[normRoot] ?? config.workspaces?.[normRoot + "/"];
22
+ // Walk parent directories
23
+ let current = normRoot;
24
+ while (!wsEntry && current !== path_1.default.dirname(current)) {
25
+ current = path_1.default.dirname(current);
26
+ wsEntry = config.workspaces?.[current] ?? config.workspaces?.[current + "/"];
27
+ }
28
+ const token = wsEntry?.token ?? config.token ?? "";
29
+ const apiUrl = wsEntry?.apiUrl ?? config.apiUrl ?? API_BASE_DEFAULT;
30
+ return { token, apiUrl };
31
+ }
32
+ async function detectContextDecay(repoRoot, changedFiles) {
33
+ const { token, apiUrl } = getCredentials(repoRoot);
34
+ if (!token)
35
+ return;
36
+ const repoName = path_1.default.basename(repoRoot);
37
+ for (const filePath of changedFiles) {
38
+ if (!fs_1.default.existsSync(filePath))
39
+ continue;
40
+ try {
41
+ const source = fs_1.default.readFileSync(filePath, "utf-8");
42
+ const nodes = await (0, ast_parser_1.parseFile)(filePath, source);
43
+ for (const node of nodes) {
44
+ if (!node.fingerprint)
45
+ continue;
46
+ // Check if this fingerprint exists under a DIFFERENT symbol/repo
47
+ const res = await fetch(`${apiUrl}/memory?fingerprint=${encodeURIComponent(node.fingerprint)}&limit=5`, { headers: { "X-Kodingo-Token": token } });
48
+ if (!res.ok)
49
+ continue;
50
+ const data = await res.json();
51
+ const memories = data.data ?? [];
52
+ for (const m of memories) {
53
+ const sameSymbol = m.symbol === node.name;
54
+ const sameRepo = m.repo === repoName;
55
+ if (!sameSymbol || !sameRepo) {
56
+ // Fingerprint match but different identity — this is a rename/move
57
+ const migrateRes = await fetch(`${apiUrl}/memory/migrate`, {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json", "X-Kodingo-Token": token },
60
+ body: JSON.stringify({
61
+ fingerprint: node.fingerprint,
62
+ oldSymbol: m.symbol,
63
+ newSymbol: node.name,
64
+ oldRepo: m.repo ?? repoName,
65
+ newRepo: repoName,
66
+ }),
67
+ });
68
+ if (migrateRes.ok) {
69
+ const result = await migrateRes.json();
70
+ if (result.migrated > 0) {
71
+ console.log(` ↳ Kortex: ${result.migrated} memor${result.migrated === 1 ? "y" : "ies"} followed rename: ${m.symbol} → ${node.name}`);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ catch {
79
+ // silent — never block the commit flow
80
+ }
81
+ }
82
+ }
15
83
  async function scanGitCommand(options) {
16
- const { initDb, getMemoryByExternalId } = (0, persistence_config_1.getPersistence)();
84
+ const { initDb, getMemoryByExternalId } = (0, persistence_config_2.getPersistence)();
17
85
  const repoRoot = resolveRepoRoot(options);
18
86
  await initDb();
19
87
  const git = (0, simple_git_1.default)(repoRoot);
@@ -30,7 +98,7 @@ async function scanGitCommand(options) {
30
98
  console.log(`Git scan skipped (already stored) for repo: ${repoRoot} commit: ${commitHash.slice(0, 8)}`);
31
99
  return;
32
100
  }
33
- const eventPort = (0, persistence_config_1.isCloudMode)()
101
+ const eventPort = (0, persistence_config_2.isCloudMode)()
34
102
  ? new cloud_event_port_1.CloudEventPort(repoRoot)
35
103
  : new db_event_port_1.DbEventPort(repoRoot);
36
104
  const adapter = new git_listener_1.GitListenerAdapter(repoRoot, eventPort, 5000);
@@ -12,6 +12,39 @@
12
12
  * Caps at 50 symbols per run to avoid overwhelming the API.
13
13
  * Safe to run multiple times — skips symbols that already have memory.
14
14
  */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
15
48
  var __importDefault = (this && this.__importDefault) || function (mod) {
16
49
  return (mod && mod.__esModule) ? mod : { "default": mod };
17
50
  };
@@ -20,20 +53,14 @@ exports.scanRepoCommand = scanRepoCommand;
20
53
  const fs_1 = __importDefault(require("fs"));
21
54
  const path_1 = __importDefault(require("path"));
22
55
  const persistence_config_1 = require("../utils/persistence-config");
56
+ const os = __importStar(require("os"));
57
+ const ast_parser_1 = require("../utils/ast-parser");
23
58
  const API_BASE = "https://kodingo-api.onrender.com";
24
59
  const SUPPORTED_EXTENSIONS = [
25
60
  ".ts", ".tsx", ".js", ".jsx",
26
61
  ".py", ".go", ".rs", ".php",
27
62
  ".java", ".kt", ".rb", ".c", ".cpp",
28
63
  ];
29
- const SYMBOL_PATTERNS = [
30
- /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
31
- /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/,
32
- /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\(/,
33
- /^def\s+(\w+)\s*\(/,
34
- /^func\s+(\w+)\s*\(/,
35
- /^(?:pub\s+)?fn\s+(\w+)\s*\(/,
36
- ];
37
64
  const IGNORED_DIRS = [
38
65
  "node_modules", ".git", ".kortex", "dist", "build",
39
66
  "out", ".next", "coverage", "__pycache__", "vendor",
@@ -71,26 +98,16 @@ function walkFiles(dir) {
71
98
  catch { }
72
99
  return results;
73
100
  }
74
- function extractSymbols(filePath) {
101
+ async function extractSymbols(filePath) {
75
102
  try {
76
- const text = fs_1.default.readFileSync(filePath, "utf-8");
77
- const lines = text.split("\n");
78
- const entries = [];
79
- for (let i = 0; i < lines.length; i++) {
80
- const trimmed = lines[i].trim();
81
- for (const pat of SYMBOL_PATTERNS) {
82
- const m = trimmed.match(pat);
83
- if (m?.[1]) {
84
- entries.push({
85
- name: m[1],
86
- code: lines.slice(i, i + 40).join("\n"),
87
- file: filePath,
88
- });
89
- break;
90
- }
91
- }
92
- }
93
- return entries;
103
+ const source = fs_1.default.readFileSync(filePath, "utf-8");
104
+ const nodes = await (0, ast_parser_1.parseFile)(filePath, source);
105
+ return nodes.map(n => ({
106
+ name: n.name,
107
+ code: n.body.slice(0, 2000), // cap to avoid huge payloads
108
+ file: filePath,
109
+ fingerprint: n.fingerprint,
110
+ }));
94
111
  }
95
112
  catch {
96
113
  return [];
@@ -144,6 +161,9 @@ async function inferAndCapture(token, symbol, repo) {
144
161
  tags: [...(inferred.tags ?? []), "scan-repo"],
145
162
  status: "proposed",
146
163
  confidence: 0.35,
164
+ machineId: (0, persistence_config_1.readConfig)().machineId ?? undefined,
165
+ displayName: os.hostname(),
166
+ fingerprint: symbol.fingerprint ?? undefined,
147
167
  }),
148
168
  });
149
169
  return saveRes.ok;
@@ -174,7 +194,7 @@ async function scanRepoCommand(options = {}) {
174
194
  const files = walkFiles(repoRoot);
175
195
  const allSymbols = [];
176
196
  for (const file of files) {
177
- allSymbols.push(...extractSymbols(file));
197
+ allSymbols.push(...(await extractSymbols(file)));
178
198
  }
179
199
  if (allSymbols.length === 0) {
180
200
  if (!options.silent)
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ /**
3
+ * ast-parser.ts
4
+ *
5
+ * Extracts symbol nodes from source files using proper AST parsing for
6
+ * TypeScript/JavaScript and improved structural parsing for other languages.
7
+ *
8
+ * Returns SymbolNode objects with stable fingerprints that survive renames.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.computeFingerprint = computeFingerprint;
45
+ exports.parseFile = parseFile;
46
+ const path = __importStar(require("path"));
47
+ const crypto_1 = require("crypto");
48
+ // ── Fingerprint ───────────────────────────────────────────────────────────────
49
+ /**
50
+ * Normalise function body and hash it.
51
+ * Strips: whitespace, comments, string literals.
52
+ * Survives: renames, variable name changes, log additions, typo fixes.
53
+ * Breaks on: structural logic changes (intentional).
54
+ */
55
+ function computeFingerprint(body) {
56
+ const normalised = body
57
+ .replace(/\/\/.*$/gm, "") // remove single-line comments
58
+ .replace(/\/\*[\s\S]*?\*\//g, "") // remove multi-line comments
59
+ .replace(/#.*$/gm, "") // remove Python comments
60
+ .replace(/(["\`\'])(?:(?!\1)[\s\S])*?\1/g, "STR") // replace string literals
61
+ .replace(/\b\d+\b/g, "NUM") // replace numeric literals
62
+ .replace(/\s+/g, " ") // collapse whitespace
63
+ .trim();
64
+ return (0, crypto_1.createHash)("sha256").update(normalised).digest("hex").slice(0, 16);
65
+ }
66
+ // ── TypeScript / JavaScript parser ───────────────────────────────────────────
67
+ async function parseTypeScript(filePath, source) {
68
+ try {
69
+ const { parse } = await Promise.resolve().then(() => __importStar(require("@typescript-eslint/typescript-estree")));
70
+ const isTs = /\.(ts|tsx)$/.test(filePath);
71
+ const ast = parse(source, {
72
+ jsx: true,
73
+ loc: true,
74
+ range: true,
75
+ errorRecovery: true,
76
+ ...(isTs ? { filePath } : {}),
77
+ });
78
+ const symbols = [];
79
+ const lines = source.split("\n");
80
+ function getBody(start, end) {
81
+ return source.slice(start, end);
82
+ }
83
+ function visit(node, className) {
84
+ if (!node || typeof node !== "object")
85
+ return;
86
+ if (node.type === "FunctionDeclaration" && node.id?.name) {
87
+ const body = getBody(node.range[0], node.range[1]);
88
+ symbols.push({
89
+ name: node.id.name,
90
+ type: "function",
91
+ file: filePath,
92
+ startLine: node.loc.start.line,
93
+ endLine: node.loc.end.line,
94
+ body,
95
+ fingerprint: computeFingerprint(body),
96
+ });
97
+ }
98
+ if ((node.type === "VariableDeclarator") &&
99
+ node.id?.name &&
100
+ node.init &&
101
+ (node.init.type === "ArrowFunctionExpression" || node.init.type === "FunctionExpression")) {
102
+ const body = getBody(node.init.range[0], node.init.range[1]);
103
+ symbols.push({
104
+ name: node.id.name,
105
+ type: "arrow_function",
106
+ file: filePath,
107
+ startLine: node.loc.start.line,
108
+ endLine: node.loc.end.line,
109
+ body,
110
+ fingerprint: computeFingerprint(body),
111
+ });
112
+ }
113
+ if (node.type === "ClassDeclaration" ||
114
+ node.type === "ClassExpression") {
115
+ const name = node.id?.name;
116
+ if (name) {
117
+ const body = getBody(node.range[0], node.range[1]);
118
+ symbols.push({
119
+ name,
120
+ type: "class",
121
+ file: filePath,
122
+ startLine: node.loc.start.line,
123
+ endLine: node.loc.end.line,
124
+ body,
125
+ fingerprint: computeFingerprint(body),
126
+ });
127
+ // Visit methods inside the class
128
+ if (node.body?.body) {
129
+ for (const member of node.body.body) {
130
+ if ((member.type === "MethodDefinition" || member.type === "PropertyDefinition") &&
131
+ member.key?.name &&
132
+ member.value?.type === "FunctionExpression") {
133
+ const mBody = getBody(member.range[0], member.range[1]);
134
+ symbols.push({
135
+ name: `${name}.${member.key.name}`,
136
+ type: "method",
137
+ file: filePath,
138
+ startLine: member.loc.start.line,
139
+ endLine: member.loc.end.line,
140
+ body: mBody,
141
+ fingerprint: computeFingerprint(mBody),
142
+ });
143
+ }
144
+ }
145
+ }
146
+ }
147
+ return; // already handled children
148
+ }
149
+ // Recurse
150
+ for (const key of Object.keys(node)) {
151
+ if (key === "parent")
152
+ continue;
153
+ const child = node[key];
154
+ if (Array.isArray(child)) {
155
+ for (const c of child)
156
+ visit(c, className);
157
+ }
158
+ else if (child && typeof child === "object" && child.type) {
159
+ visit(child, className);
160
+ }
161
+ }
162
+ }
163
+ visit(ast);
164
+ return symbols;
165
+ }
166
+ catch {
167
+ return [];
168
+ }
169
+ }
170
+ // ── Structural parser for other languages ────────────────────────────────────
171
+ /**
172
+ * Extracts symbols using brace-depth tracking.
173
+ * Works for Go, Rust, Java, Kotlin, PHP, Ruby, C/C++.
174
+ */
175
+ function parseStructural(filePath, source, patterns) {
176
+ const symbols = [];
177
+ const lines = source.split("\n");
178
+ for (let i = 0; i < lines.length; i++) {
179
+ const line = lines[i].trim();
180
+ for (const pattern of patterns) {
181
+ const match = line.match(pattern);
182
+ if (!match?.[1])
183
+ continue;
184
+ const name = match[1];
185
+ const startLine = i + 1;
186
+ // Find the matching closing brace
187
+ let depth = 0;
188
+ let endLine = startLine;
189
+ let started = false;
190
+ for (let j = i; j < lines.length; j++) {
191
+ for (const ch of lines[j]) {
192
+ if (ch === "{") {
193
+ depth++;
194
+ started = true;
195
+ }
196
+ if (ch === "}") {
197
+ depth--;
198
+ }
199
+ }
200
+ if (started && depth === 0) {
201
+ endLine = j + 1;
202
+ break;
203
+ }
204
+ }
205
+ const body = lines.slice(i, endLine).join("\n");
206
+ symbols.push({
207
+ name,
208
+ type: line.match(/class\s/) ? "class" : "function",
209
+ file: filePath,
210
+ startLine,
211
+ endLine,
212
+ body,
213
+ fingerprint: computeFingerprint(body),
214
+ });
215
+ break;
216
+ }
217
+ }
218
+ return symbols;
219
+ }
220
+ /** Python uses indentation-based scoping */
221
+ function parsePython(filePath, source) {
222
+ const symbols = [];
223
+ const lines = source.split("\n");
224
+ const defPattern = /^(async\s+)?def\s+(\w+)\s*\(/;
225
+ const classPattern = /^class\s+(\w+)/;
226
+ for (let i = 0; i < lines.length; i++) {
227
+ const line = lines[i];
228
+ const defMatch = line.match(defPattern);
229
+ const classMatch = line.match(classPattern);
230
+ const match = defMatch || classMatch;
231
+ if (!match)
232
+ continue;
233
+ const name = defMatch ? match[2] : match[1];
234
+ const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
235
+ const startLine = i + 1;
236
+ let endLine = startLine;
237
+ for (let j = i + 1; j < lines.length; j++) {
238
+ const nextLine = lines[j];
239
+ if (nextLine.trim() === "")
240
+ continue;
241
+ const nextIndent = nextLine.match(/^(\s*)/)?.[1].length ?? 0;
242
+ if (nextIndent <= indent && nextLine.trim() !== "") {
243
+ endLine = j;
244
+ break;
245
+ }
246
+ endLine = j + 1;
247
+ }
248
+ const body = lines.slice(i, endLine).join("\n");
249
+ symbols.push({
250
+ name,
251
+ type: classMatch ? "class" : "function",
252
+ file: filePath,
253
+ startLine,
254
+ endLine,
255
+ body,
256
+ fingerprint: computeFingerprint(body),
257
+ });
258
+ }
259
+ return symbols;
260
+ }
261
+ // ── Language dispatch ─────────────────────────────────────────────────────────
262
+ const GO_PATTERNS = [
263
+ /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/,
264
+ ];
265
+ const RUST_PATTERNS = [
266
+ /^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/,
267
+ ];
268
+ const JAVA_PATTERNS = [
269
+ /^(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\(/,
270
+ /^(?:public|private|protected|abstract|\s)*class\s+(\w+)/,
271
+ ];
272
+ const PHP_PATTERNS = [
273
+ /^(?:public|private|protected|static|\s)*function\s+(\w+)\s*\(/,
274
+ /^(?:abstract\s+)?class\s+(\w+)/,
275
+ ];
276
+ const RUBY_PATTERNS = [
277
+ /^def\s+(\w+)/,
278
+ /^class\s+(\w+)/,
279
+ ];
280
+ const KOTLIN_PATTERNS = [
281
+ /^(?:fun|override\s+fun|suspend\s+fun|private\s+fun|public\s+fun)\s+(\w+)/,
282
+ /^(?:class|data class|object|interface)\s+(\w+)/,
283
+ ];
284
+ async function parseFile(filePath, source) {
285
+ const ext = path.extname(filePath).toLowerCase();
286
+ switch (ext) {
287
+ case ".ts":
288
+ case ".tsx":
289
+ case ".js":
290
+ case ".jsx":
291
+ return parseTypeScript(filePath, source);
292
+ case ".py":
293
+ return parsePython(filePath, source);
294
+ case ".go":
295
+ return parseStructural(filePath, source, GO_PATTERNS);
296
+ case ".rs":
297
+ return parseStructural(filePath, source, RUST_PATTERNS);
298
+ case ".java":
299
+ return parseStructural(filePath, source, JAVA_PATTERNS);
300
+ case ".php":
301
+ return parseStructural(filePath, source, PHP_PATTERNS);
302
+ case ".rb":
303
+ return parseStructural(filePath, source, RUBY_PATTERNS);
304
+ case ".kt":
305
+ return parseStructural(filePath, source, KOTLIN_PATTERNS);
306
+ default:
307
+ return [];
308
+ }
309
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodingo-cli",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Kodingo CLI",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -22,6 +22,8 @@
22
22
  "dependencies": {
23
23
  "@babel/parser": "^7.29.0",
24
24
  "@babel/traverse": "^7.29.0",
25
+ "@typescript-eslint/types": "^8.61.1",
26
+ "@typescript-eslint/typescript-estree": "^8.61.1",
25
27
  "commander": "^12.1.0",
26
28
  "kodingo-core": "^0.1.0",
27
29
  "open": "^11.0.0",