kodingo-cli 1.0.27 → 1.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/scan-git.js +71 -3
- package/dist/commands/scan-repo.js +12 -28
- package/dist/utils/ast-parser.js +313 -0
- package/package.json +3 -1
|
@@ -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
|
|
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,
|
|
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,
|
|
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);
|
|
@@ -54,20 +54,13 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
54
54
|
const path_1 = __importDefault(require("path"));
|
|
55
55
|
const persistence_config_1 = require("../utils/persistence-config");
|
|
56
56
|
const os = __importStar(require("os"));
|
|
57
|
+
const ast_parser_1 = require("../utils/ast-parser");
|
|
57
58
|
const API_BASE = "https://kodingo-api.onrender.com";
|
|
58
59
|
const SUPPORTED_EXTENSIONS = [
|
|
59
60
|
".ts", ".tsx", ".js", ".jsx",
|
|
60
61
|
".py", ".go", ".rs", ".php",
|
|
61
62
|
".java", ".kt", ".rb", ".c", ".cpp",
|
|
62
63
|
];
|
|
63
|
-
const SYMBOL_PATTERNS = [
|
|
64
|
-
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
65
|
-
/^(?:export\s+)?(?:default\s+)?class\s+(\w+)/,
|
|
66
|
-
/^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\(/,
|
|
67
|
-
/^def\s+(\w+)\s*\(/,
|
|
68
|
-
/^func\s+(\w+)\s*\(/,
|
|
69
|
-
/^(?:pub\s+)?fn\s+(\w+)\s*\(/,
|
|
70
|
-
];
|
|
71
64
|
const IGNORED_DIRS = [
|
|
72
65
|
"node_modules", ".git", ".kortex", "dist", "build",
|
|
73
66
|
"out", ".next", "coverage", "__pycache__", "vendor",
|
|
@@ -105,26 +98,16 @@ function walkFiles(dir) {
|
|
|
105
98
|
catch { }
|
|
106
99
|
return results;
|
|
107
100
|
}
|
|
108
|
-
function extractSymbols(filePath) {
|
|
101
|
+
async function extractSymbols(filePath) {
|
|
109
102
|
try {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
entries.push({
|
|
119
|
-
name: m[1],
|
|
120
|
-
code: lines.slice(i, i + 40).join("\n"),
|
|
121
|
-
file: filePath,
|
|
122
|
-
});
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
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
|
+
}));
|
|
128
111
|
}
|
|
129
112
|
catch {
|
|
130
113
|
return [];
|
|
@@ -180,6 +163,7 @@ async function inferAndCapture(token, symbol, repo) {
|
|
|
180
163
|
confidence: 0.35,
|
|
181
164
|
machineId: (0, persistence_config_1.readConfig)().machineId ?? undefined,
|
|
182
165
|
displayName: os.hostname(),
|
|
166
|
+
fingerprint: symbol.fingerprint ?? undefined,
|
|
183
167
|
}),
|
|
184
168
|
});
|
|
185
169
|
return saveRes.ok;
|
|
@@ -210,7 +194,7 @@ async function scanRepoCommand(options = {}) {
|
|
|
210
194
|
const files = walkFiles(repoRoot);
|
|
211
195
|
const allSymbols = [];
|
|
212
196
|
for (const file of files) {
|
|
213
|
-
allSymbols.push(...extractSymbols(file));
|
|
197
|
+
allSymbols.push(...(await extractSymbols(file)));
|
|
214
198
|
}
|
|
215
199
|
if (allSymbols.length === 0) {
|
|
216
200
|
if (!options.silent)
|
|
@@ -0,0 +1,313 @@
|
|
|
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
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
68
|
+
const { parse: tsParse } = require("@typescript-eslint/typescript-estree");
|
|
69
|
+
async function parseTypeScript(filePath, source) {
|
|
70
|
+
try {
|
|
71
|
+
const parse = tsParse;
|
|
72
|
+
const isTs = /\.(ts|tsx)$/.test(filePath);
|
|
73
|
+
const ast = parse(source, {
|
|
74
|
+
jsx: true,
|
|
75
|
+
loc: true,
|
|
76
|
+
range: true,
|
|
77
|
+
errorRecovery: true,
|
|
78
|
+
...(isTs ? { filePath } : {}),
|
|
79
|
+
});
|
|
80
|
+
const symbols = [];
|
|
81
|
+
const lines = source.split("\n");
|
|
82
|
+
function getBody(start, end) {
|
|
83
|
+
return source.slice(start, end);
|
|
84
|
+
}
|
|
85
|
+
function visit(node, className) {
|
|
86
|
+
if (!node || typeof node !== "object")
|
|
87
|
+
return;
|
|
88
|
+
if (node.type === "FunctionDeclaration" && node.id?.name) {
|
|
89
|
+
const body = getBody(node.range[0], node.range[1]);
|
|
90
|
+
symbols.push({
|
|
91
|
+
name: node.id.name,
|
|
92
|
+
type: "function",
|
|
93
|
+
file: filePath,
|
|
94
|
+
startLine: node.loc.start.line,
|
|
95
|
+
endLine: node.loc.end.line,
|
|
96
|
+
body,
|
|
97
|
+
fingerprint: computeFingerprint(body),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if ((node.type === "VariableDeclarator") &&
|
|
101
|
+
node.id?.name &&
|
|
102
|
+
node.init &&
|
|
103
|
+
(node.init.type === "ArrowFunctionExpression" || node.init.type === "FunctionExpression")) {
|
|
104
|
+
const body = getBody(node.init.range[0], node.init.range[1]);
|
|
105
|
+
symbols.push({
|
|
106
|
+
name: node.id.name,
|
|
107
|
+
type: "arrow_function",
|
|
108
|
+
file: filePath,
|
|
109
|
+
startLine: node.loc.start.line,
|
|
110
|
+
endLine: node.loc.end.line,
|
|
111
|
+
body,
|
|
112
|
+
fingerprint: computeFingerprint(body),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (node.type === "ClassDeclaration" ||
|
|
116
|
+
node.type === "ClassExpression") {
|
|
117
|
+
const name = node.id?.name;
|
|
118
|
+
if (name) {
|
|
119
|
+
const body = getBody(node.range[0], node.range[1]);
|
|
120
|
+
symbols.push({
|
|
121
|
+
name,
|
|
122
|
+
type: "class",
|
|
123
|
+
file: filePath,
|
|
124
|
+
startLine: node.loc.start.line,
|
|
125
|
+
endLine: node.loc.end.line,
|
|
126
|
+
body,
|
|
127
|
+
fingerprint: computeFingerprint(body),
|
|
128
|
+
});
|
|
129
|
+
// Visit methods inside the class
|
|
130
|
+
if (node.body?.body) {
|
|
131
|
+
for (const member of node.body.body) {
|
|
132
|
+
if ((member.type === "MethodDefinition" || member.type === "PropertyDefinition") &&
|
|
133
|
+
member.key?.name &&
|
|
134
|
+
member.value?.type === "FunctionExpression") {
|
|
135
|
+
const mBody = getBody(member.range[0], member.range[1]);
|
|
136
|
+
symbols.push({
|
|
137
|
+
name: `${name}.${member.key.name}`,
|
|
138
|
+
type: "method",
|
|
139
|
+
file: filePath,
|
|
140
|
+
startLine: member.loc.start.line,
|
|
141
|
+
endLine: member.loc.end.line,
|
|
142
|
+
body: mBody,
|
|
143
|
+
fingerprint: computeFingerprint(mBody),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return; // already handled children
|
|
150
|
+
}
|
|
151
|
+
// Recurse
|
|
152
|
+
for (const key of Object.keys(node)) {
|
|
153
|
+
if (key === "parent")
|
|
154
|
+
continue;
|
|
155
|
+
const child = node[key];
|
|
156
|
+
if (Array.isArray(child)) {
|
|
157
|
+
for (const c of child)
|
|
158
|
+
visit(c, className);
|
|
159
|
+
}
|
|
160
|
+
else if (child && typeof child === "object" && child.type) {
|
|
161
|
+
visit(child, className);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
visit(ast);
|
|
166
|
+
return symbols;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// ── Structural parser for other languages ────────────────────────────────────
|
|
173
|
+
/**
|
|
174
|
+
* Extracts symbols using brace-depth tracking.
|
|
175
|
+
* Works for Go, Rust, Java, Kotlin, PHP, Ruby, C/C++.
|
|
176
|
+
*/
|
|
177
|
+
function parseStructural(filePath, source, patterns) {
|
|
178
|
+
const symbols = [];
|
|
179
|
+
const lines = source.split("\n");
|
|
180
|
+
for (let i = 0; i < lines.length; i++) {
|
|
181
|
+
const line = lines[i].trim();
|
|
182
|
+
for (const pattern of patterns) {
|
|
183
|
+
const match = line.match(pattern);
|
|
184
|
+
if (!match?.[1])
|
|
185
|
+
continue;
|
|
186
|
+
const name = match[1];
|
|
187
|
+
const startLine = i + 1;
|
|
188
|
+
// Find the matching closing brace
|
|
189
|
+
let depth = 0;
|
|
190
|
+
let endLine = startLine;
|
|
191
|
+
let started = false;
|
|
192
|
+
for (let j = i; j < lines.length; j++) {
|
|
193
|
+
for (const ch of lines[j]) {
|
|
194
|
+
if (ch === "{") {
|
|
195
|
+
depth++;
|
|
196
|
+
started = true;
|
|
197
|
+
}
|
|
198
|
+
if (ch === "}") {
|
|
199
|
+
depth--;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (started && depth === 0) {
|
|
203
|
+
endLine = j + 1;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const body = lines.slice(i, endLine).join("\n");
|
|
208
|
+
symbols.push({
|
|
209
|
+
name,
|
|
210
|
+
type: line.match(/class\s/) ? "class" : "function",
|
|
211
|
+
file: filePath,
|
|
212
|
+
startLine,
|
|
213
|
+
endLine,
|
|
214
|
+
body,
|
|
215
|
+
fingerprint: computeFingerprint(body),
|
|
216
|
+
});
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return symbols;
|
|
221
|
+
}
|
|
222
|
+
/** Python uses indentation-based scoping */
|
|
223
|
+
function parsePython(filePath, source) {
|
|
224
|
+
const symbols = [];
|
|
225
|
+
const lines = source.split("\n");
|
|
226
|
+
const defPattern = /^(async\s+)?def\s+(\w+)\s*\(/;
|
|
227
|
+
const classPattern = /^class\s+(\w+)/;
|
|
228
|
+
for (let i = 0; i < lines.length; i++) {
|
|
229
|
+
const line = lines[i];
|
|
230
|
+
const defMatch = line.match(defPattern);
|
|
231
|
+
const classMatch = line.match(classPattern);
|
|
232
|
+
const match = defMatch || classMatch;
|
|
233
|
+
if (!match)
|
|
234
|
+
continue;
|
|
235
|
+
const name = defMatch ? match[2] : match[1];
|
|
236
|
+
const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
|
|
237
|
+
const startLine = i + 1;
|
|
238
|
+
let endLine = startLine;
|
|
239
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
240
|
+
const nextLine = lines[j];
|
|
241
|
+
if (nextLine.trim() === "")
|
|
242
|
+
continue;
|
|
243
|
+
const nextIndent = nextLine.match(/^(\s*)/)?.[1].length ?? 0;
|
|
244
|
+
if (nextIndent <= indent && nextLine.trim() !== "") {
|
|
245
|
+
endLine = j;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
endLine = j + 1;
|
|
249
|
+
}
|
|
250
|
+
const body = lines.slice(i, endLine).join("\n");
|
|
251
|
+
symbols.push({
|
|
252
|
+
name,
|
|
253
|
+
type: classMatch ? "class" : "function",
|
|
254
|
+
file: filePath,
|
|
255
|
+
startLine,
|
|
256
|
+
endLine,
|
|
257
|
+
body,
|
|
258
|
+
fingerprint: computeFingerprint(body),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return symbols;
|
|
262
|
+
}
|
|
263
|
+
// ── Language dispatch ─────────────────────────────────────────────────────────
|
|
264
|
+
const GO_PATTERNS = [
|
|
265
|
+
/^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/,
|
|
266
|
+
];
|
|
267
|
+
const RUST_PATTERNS = [
|
|
268
|
+
/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/,
|
|
269
|
+
];
|
|
270
|
+
const JAVA_PATTERNS = [
|
|
271
|
+
/^(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\(/,
|
|
272
|
+
/^(?:public|private|protected|abstract|\s)*class\s+(\w+)/,
|
|
273
|
+
];
|
|
274
|
+
const PHP_PATTERNS = [
|
|
275
|
+
/^(?:public|private|protected|static|\s)*function\s+(\w+)\s*\(/,
|
|
276
|
+
/^(?:abstract\s+)?class\s+(\w+)/,
|
|
277
|
+
];
|
|
278
|
+
const RUBY_PATTERNS = [
|
|
279
|
+
/^def\s+(\w+)/,
|
|
280
|
+
/^class\s+(\w+)/,
|
|
281
|
+
];
|
|
282
|
+
const KOTLIN_PATTERNS = [
|
|
283
|
+
/^(?:fun|override\s+fun|suspend\s+fun|private\s+fun|public\s+fun)\s+(\w+)/,
|
|
284
|
+
/^(?:class|data class|object|interface)\s+(\w+)/,
|
|
285
|
+
];
|
|
286
|
+
async function parseFile(filePath, source) {
|
|
287
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
288
|
+
switch (ext) {
|
|
289
|
+
case ".ts":
|
|
290
|
+
case ".tsx":
|
|
291
|
+
case ".js":
|
|
292
|
+
case ".jsx":
|
|
293
|
+
case ".mjs":
|
|
294
|
+
case ".cjs":
|
|
295
|
+
return parseTypeScript(filePath, source);
|
|
296
|
+
case ".py":
|
|
297
|
+
return parsePython(filePath, source);
|
|
298
|
+
case ".go":
|
|
299
|
+
return parseStructural(filePath, source, GO_PATTERNS);
|
|
300
|
+
case ".rs":
|
|
301
|
+
return parseStructural(filePath, source, RUST_PATTERNS);
|
|
302
|
+
case ".java":
|
|
303
|
+
return parseStructural(filePath, source, JAVA_PATTERNS);
|
|
304
|
+
case ".php":
|
|
305
|
+
return parseStructural(filePath, source, PHP_PATTERNS);
|
|
306
|
+
case ".rb":
|
|
307
|
+
return parseStructural(filePath, source, RUBY_PATTERNS);
|
|
308
|
+
case ".kt":
|
|
309
|
+
return parseStructural(filePath, source, KOTLIN_PATTERNS);
|
|
310
|
+
default:
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodingo-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
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",
|