lynkr 9.9.1 → 9.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -9
- package/bin/cli.js +2 -0
- package/bin/lynkr-init.js +4 -12
- package/bin/lynkr-reset.js +71 -0
- package/config/model-tiers.json +199 -52
- package/package.json +2 -2
- package/scripts/validate-difficulty-classifier.js +27 -6
- package/src/api/providers-handler.js +0 -1
- package/src/api/router.js +275 -160
- package/src/clients/databricks.js +95 -6
- package/src/config/index.js +3 -43
- package/src/orchestrator/index.js +117 -1235
- package/src/orchestrator/passthrough-stream.js +382 -0
- package/src/orchestrator/sse-transformer.js +408 -0
- package/src/routing/affinity-store.js +17 -3
- package/src/routing/difficulty-classifier.js +52 -10
- package/src/routing/index.js +35 -3
- package/src/routing/intent-score.js +20 -2
- package/src/routing/session-affinity.js +8 -1
- package/src/routing/side-channel-detector.js +103 -0
- package/src/server.js +0 -46
- package/src/agents/context-manager.js +0 -236
- package/src/agents/decomposition/dispatcher.js +0 -185
- package/src/agents/decomposition/gate.js +0 -136
- package/src/agents/decomposition/index.js +0 -183
- package/src/agents/decomposition/model-call.js +0 -75
- package/src/agents/decomposition/planner.js +0 -223
- package/src/agents/decomposition/synthesizer.js +0 -89
- package/src/agents/decomposition/telemetry.js +0 -55
- package/src/agents/definitions/loader.js +0 -653
- package/src/agents/executor.js +0 -457
- package/src/agents/index.js +0 -165
- package/src/agents/parallel-coordinator.js +0 -68
- package/src/agents/reflector.js +0 -331
- package/src/agents/skillbook.js +0 -331
- package/src/agents/store.js +0 -259
- package/src/edits/index.js +0 -171
- package/src/indexer/babel-parser.js +0 -213
- package/src/indexer/index.js +0 -1629
- package/src/indexer/navigation/index.js +0 -32
- package/src/indexer/navigation/providers/treeSitter.js +0 -36
- package/src/indexer/parser.js +0 -443
- package/src/tasks/store.js +0 -349
- package/src/tests/coverage.js +0 -173
- package/src/tests/index.js +0 -171
- package/src/tests/store.js +0 -213
- package/src/tools/agent-task.js +0 -145
- package/src/tools/code-mode.js +0 -304
- package/src/tools/decompose.js +0 -91
- package/src/tools/edits.js +0 -94
- package/src/tools/execution.js +0 -171
- package/src/tools/git.js +0 -1346
- package/src/tools/index.js +0 -306
- package/src/tools/indexer.js +0 -360
- package/src/tools/lazy-loader.js +0 -366
- package/src/tools/mcp-remote.js +0 -88
- package/src/tools/mcp.js +0 -116
- package/src/tools/process.js +0 -167
- package/src/tools/smart-selection.js +0 -180
- package/src/tools/stubs.js +0 -55
- package/src/tools/tasks.js +0 -260
- package/src/tools/tests.js +0 -132
- package/src/tools/tinyfish.js +0 -358
- package/src/tools/truncate.js +0 -106
- package/src/tools/web-client.js +0 -71
- package/src/tools/web.js +0 -415
- package/src/tools/workspace.js +0 -204
package/src/agents/store.js
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
let Database;
|
|
2
|
-
try {
|
|
3
|
-
Database = require("better-sqlite3");
|
|
4
|
-
} catch {
|
|
5
|
-
Database = null;
|
|
6
|
-
}
|
|
7
|
-
const path = require("path");
|
|
8
|
-
const fs = require("fs");
|
|
9
|
-
const logger = require("../logger");
|
|
10
|
-
|
|
11
|
-
class AgentStore {
|
|
12
|
-
constructor() {
|
|
13
|
-
if (!Database) {
|
|
14
|
-
this.db = null;
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
// Use same database location as main app
|
|
20
|
-
const dbDir = path.join(process.cwd(), "data");
|
|
21
|
-
if (!fs.existsSync(dbDir)) {
|
|
22
|
-
fs.mkdirSync(dbDir, { recursive: true });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const dbPath = path.join(dbDir, "lynkr.db");
|
|
26
|
-
this.db = new Database(dbPath, {
|
|
27
|
-
verbose: process.env.DEBUG_SQL ? console.log : null,
|
|
28
|
-
fileMustExist: false
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
this.initTables();
|
|
32
|
-
this.prepareStatements();
|
|
33
|
-
} catch (err) {
|
|
34
|
-
logger.warn({ err: err.message }, "AgentStore: better-sqlite3 not available");
|
|
35
|
-
this.db = null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
initTables() {
|
|
40
|
-
this.db.exec(`
|
|
41
|
-
CREATE TABLE IF NOT EXISTS agent_executions (
|
|
42
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
-
session_id TEXT,
|
|
44
|
-
agent_type TEXT NOT NULL,
|
|
45
|
-
prompt TEXT NOT NULL,
|
|
46
|
-
model TEXT NOT NULL,
|
|
47
|
-
status TEXT NOT NULL, -- 'pending', 'running', 'completed', 'failed'
|
|
48
|
-
result TEXT,
|
|
49
|
-
error TEXT,
|
|
50
|
-
steps INTEGER DEFAULT 0,
|
|
51
|
-
duration_ms INTEGER,
|
|
52
|
-
input_tokens INTEGER DEFAULT 0,
|
|
53
|
-
output_tokens INTEGER DEFAULT 0,
|
|
54
|
-
created_at INTEGER NOT NULL,
|
|
55
|
-
completed_at INTEGER
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
CREATE INDEX IF NOT EXISTS idx_agent_executions_session_id
|
|
59
|
-
ON agent_executions(session_id);
|
|
60
|
-
|
|
61
|
-
CREATE INDEX IF NOT EXISTS idx_agent_executions_agent_type
|
|
62
|
-
ON agent_executions(agent_type);
|
|
63
|
-
|
|
64
|
-
CREATE INDEX IF NOT EXISTS idx_agent_executions_status
|
|
65
|
-
ON agent_executions(status);
|
|
66
|
-
|
|
67
|
-
CREATE INDEX IF NOT EXISTS idx_agent_executions_created_at
|
|
68
|
-
ON agent_executions(created_at DESC);
|
|
69
|
-
`);
|
|
70
|
-
|
|
71
|
-
logger.info("Agent store tables initialized");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
prepareStatements() {
|
|
75
|
-
this.stmts = {
|
|
76
|
-
create: this.db.prepare(`
|
|
77
|
-
INSERT INTO agent_executions (
|
|
78
|
-
session_id, agent_type, prompt, model, status, created_at
|
|
79
|
-
) VALUES (?, ?, ?, ?, ?, ?)
|
|
80
|
-
`),
|
|
81
|
-
|
|
82
|
-
updateStatus: this.db.prepare(`
|
|
83
|
-
UPDATE agent_executions
|
|
84
|
-
SET status = ?, completed_at = ?
|
|
85
|
-
WHERE id = ?
|
|
86
|
-
`),
|
|
87
|
-
|
|
88
|
-
complete: this.db.prepare(`
|
|
89
|
-
UPDATE agent_executions
|
|
90
|
-
SET status = 'completed',
|
|
91
|
-
result = ?,
|
|
92
|
-
steps = ?,
|
|
93
|
-
duration_ms = ?,
|
|
94
|
-
input_tokens = ?,
|
|
95
|
-
output_tokens = ?,
|
|
96
|
-
completed_at = ?
|
|
97
|
-
WHERE id = ?
|
|
98
|
-
`),
|
|
99
|
-
|
|
100
|
-
fail: this.db.prepare(`
|
|
101
|
-
UPDATE agent_executions
|
|
102
|
-
SET status = 'failed',
|
|
103
|
-
error = ?,
|
|
104
|
-
steps = ?,
|
|
105
|
-
duration_ms = ?,
|
|
106
|
-
completed_at = ?
|
|
107
|
-
WHERE id = ?
|
|
108
|
-
`),
|
|
109
|
-
|
|
110
|
-
get: this.db.prepare(`
|
|
111
|
-
SELECT * FROM agent_executions WHERE id = ?
|
|
112
|
-
`),
|
|
113
|
-
|
|
114
|
-
getBySession: this.db.prepare(`
|
|
115
|
-
SELECT * FROM agent_executions
|
|
116
|
-
WHERE session_id = ?
|
|
117
|
-
ORDER BY created_at DESC
|
|
118
|
-
`),
|
|
119
|
-
|
|
120
|
-
getRecent: this.db.prepare(`
|
|
121
|
-
SELECT * FROM agent_executions
|
|
122
|
-
ORDER BY created_at DESC
|
|
123
|
-
LIMIT ?
|
|
124
|
-
`),
|
|
125
|
-
|
|
126
|
-
stats: this.db.prepare(`
|
|
127
|
-
SELECT
|
|
128
|
-
agent_type,
|
|
129
|
-
COUNT(*) as total_executions,
|
|
130
|
-
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
|
|
131
|
-
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,
|
|
132
|
-
AVG(CASE WHEN status = 'completed' THEN duration_ms ELSE NULL END) as avg_duration_ms,
|
|
133
|
-
SUM(input_tokens) as total_input_tokens,
|
|
134
|
-
SUM(output_tokens) as total_output_tokens
|
|
135
|
-
FROM agent_executions
|
|
136
|
-
GROUP BY agent_type
|
|
137
|
-
`)
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Create new agent execution
|
|
143
|
-
*/
|
|
144
|
-
createExecution({ sessionId, agentType, prompt, model }) {
|
|
145
|
-
const now = Date.now();
|
|
146
|
-
const result = this.stmts.create.run(
|
|
147
|
-
sessionId || null,
|
|
148
|
-
agentType,
|
|
149
|
-
prompt,
|
|
150
|
-
model,
|
|
151
|
-
'pending',
|
|
152
|
-
now
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
logger.info({
|
|
156
|
-
executionId: result.lastInsertRowid,
|
|
157
|
-
agentType,
|
|
158
|
-
sessionId
|
|
159
|
-
}, "Created agent execution");
|
|
160
|
-
|
|
161
|
-
return result.lastInsertRowid;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Update execution status
|
|
166
|
-
*/
|
|
167
|
-
updateStatus(executionId, status) {
|
|
168
|
-
const now = Date.now();
|
|
169
|
-
this.stmts.updateStatus.run(status, now, executionId);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Mark execution as completed
|
|
174
|
-
*/
|
|
175
|
-
completeExecution(executionId, result, stats = {}) {
|
|
176
|
-
const now = Date.now();
|
|
177
|
-
this.stmts.complete.run(
|
|
178
|
-
result,
|
|
179
|
-
stats.steps || 0,
|
|
180
|
-
stats.durationMs || 0,
|
|
181
|
-
stats.inputTokens || 0,
|
|
182
|
-
stats.outputTokens || 0,
|
|
183
|
-
now,
|
|
184
|
-
executionId
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
logger.info({
|
|
188
|
-
executionId,
|
|
189
|
-
steps: stats.steps,
|
|
190
|
-
durationMs: stats.durationMs
|
|
191
|
-
}, "Agent execution completed");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Mark execution as failed
|
|
196
|
-
*/
|
|
197
|
-
failExecution(executionId, error, stats = {}) {
|
|
198
|
-
const now = Date.now();
|
|
199
|
-
this.stmts.fail.run(
|
|
200
|
-
error.message || String(error),
|
|
201
|
-
stats.steps || 0,
|
|
202
|
-
stats.durationMs || 0,
|
|
203
|
-
now,
|
|
204
|
-
executionId
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
logger.warn({
|
|
208
|
-
executionId,
|
|
209
|
-
error: error.message
|
|
210
|
-
}, "Agent execution failed");
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Get execution by ID
|
|
215
|
-
*/
|
|
216
|
-
getExecution(executionId) {
|
|
217
|
-
return this.stmts.get.get(executionId);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Get executions by session
|
|
222
|
-
*/
|
|
223
|
-
getSessionExecutions(sessionId) {
|
|
224
|
-
return this.stmts.getBySession.all(sessionId);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Get recent executions
|
|
229
|
-
*/
|
|
230
|
-
getRecentExecutions(limit = 100) {
|
|
231
|
-
return this.stmts.getRecent.all(limit);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Get aggregate statistics
|
|
236
|
-
*/
|
|
237
|
-
getStats() {
|
|
238
|
-
return this.stmts.stats.all();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Close database connection
|
|
243
|
-
*/
|
|
244
|
-
close() {
|
|
245
|
-
this.db.close();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Singleton instance
|
|
250
|
-
let instance = null;
|
|
251
|
-
|
|
252
|
-
function getInstance() {
|
|
253
|
-
if (!instance) {
|
|
254
|
-
instance = new AgentStore();
|
|
255
|
-
}
|
|
256
|
-
return instance;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
module.exports = getInstance();
|
package/src/edits/index.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
const fs = require("fs/promises");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const { createTwoFilesPatch } = require("diff");
|
|
4
|
-
const db = require("../db");
|
|
5
|
-
const { resolveWorkspacePath } = require("../workspace");
|
|
6
|
-
const logger = require("../logger");
|
|
7
|
-
|
|
8
|
-
const insertEditStmt = db.prepare(
|
|
9
|
-
`INSERT INTO edits (
|
|
10
|
-
session_id,
|
|
11
|
-
file_path,
|
|
12
|
-
created_at,
|
|
13
|
-
source,
|
|
14
|
-
before_content,
|
|
15
|
-
after_content,
|
|
16
|
-
diff,
|
|
17
|
-
metadata
|
|
18
|
-
) VALUES (
|
|
19
|
-
@session_id,
|
|
20
|
-
@file_path,
|
|
21
|
-
@created_at,
|
|
22
|
-
@source,
|
|
23
|
-
@before_content,
|
|
24
|
-
@after_content,
|
|
25
|
-
@diff,
|
|
26
|
-
@metadata
|
|
27
|
-
)`,
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const selectEditByIdStmt = db.prepare(
|
|
31
|
-
`SELECT id, session_id, file_path, created_at, source,
|
|
32
|
-
before_content, after_content, diff, metadata
|
|
33
|
-
FROM edits WHERE id = ?`,
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
function buildHistoryQuery({ filePath, sessionId, limit }) {
|
|
37
|
-
let query = `SELECT id, session_id, file_path, created_at, source,
|
|
38
|
-
before_content, after_content, diff, metadata
|
|
39
|
-
FROM edits`;
|
|
40
|
-
const conditions = [];
|
|
41
|
-
const params = [];
|
|
42
|
-
if (filePath) {
|
|
43
|
-
conditions.push("file_path = ?");
|
|
44
|
-
params.push(filePath);
|
|
45
|
-
}
|
|
46
|
-
if (sessionId) {
|
|
47
|
-
conditions.push("session_id = ?");
|
|
48
|
-
params.push(sessionId);
|
|
49
|
-
}
|
|
50
|
-
if (conditions.length) {
|
|
51
|
-
query += ` WHERE ${conditions.join(" AND ")}`;
|
|
52
|
-
}
|
|
53
|
-
query += " ORDER BY created_at DESC";
|
|
54
|
-
if (limit) {
|
|
55
|
-
query += ` LIMIT ${limit}`;
|
|
56
|
-
}
|
|
57
|
-
return { query, params };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function computeDiff(filePath, beforeContent, afterContent) {
|
|
61
|
-
const before = beforeContent ?? "";
|
|
62
|
-
const after = afterContent ?? "";
|
|
63
|
-
if (before === after) return null;
|
|
64
|
-
return createTwoFilesPatch(
|
|
65
|
-
path.join("before", filePath),
|
|
66
|
-
path.join("after", filePath),
|
|
67
|
-
before,
|
|
68
|
-
after,
|
|
69
|
-
undefined,
|
|
70
|
-
undefined,
|
|
71
|
-
{ context: 3 },
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function recordEdit({
|
|
76
|
-
sessionId,
|
|
77
|
-
filePath,
|
|
78
|
-
source = "tool",
|
|
79
|
-
beforeContent,
|
|
80
|
-
afterContent,
|
|
81
|
-
metadata,
|
|
82
|
-
}) {
|
|
83
|
-
if ((beforeContent ?? "") === (afterContent ?? "")) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
const diff = computeDiff(filePath, beforeContent, afterContent);
|
|
87
|
-
const createdAt = Date.now();
|
|
88
|
-
insertEditStmt.run({
|
|
89
|
-
session_id: sessionId ?? null,
|
|
90
|
-
file_path: filePath,
|
|
91
|
-
created_at: createdAt,
|
|
92
|
-
source,
|
|
93
|
-
before_content: beforeContent ?? null,
|
|
94
|
-
after_content: afterContent ?? null,
|
|
95
|
-
diff,
|
|
96
|
-
metadata: metadata ? JSON.stringify(metadata) : null,
|
|
97
|
-
});
|
|
98
|
-
logger.info(
|
|
99
|
-
{
|
|
100
|
-
sessionId,
|
|
101
|
-
filePath,
|
|
102
|
-
source,
|
|
103
|
-
},
|
|
104
|
-
"Recorded workspace edit",
|
|
105
|
-
);
|
|
106
|
-
return {
|
|
107
|
-
sessionId,
|
|
108
|
-
filePath,
|
|
109
|
-
createdAt,
|
|
110
|
-
diff,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function getEditHistory({ filePath, sessionId, limit }) {
|
|
115
|
-
const { query, params } = buildHistoryQuery({
|
|
116
|
-
filePath,
|
|
117
|
-
sessionId,
|
|
118
|
-
limit,
|
|
119
|
-
});
|
|
120
|
-
const rows = db.prepare(query).all(...params);
|
|
121
|
-
return rows.map((row) => ({
|
|
122
|
-
id: row.id,
|
|
123
|
-
sessionId: row.session_id,
|
|
124
|
-
filePath: row.file_path,
|
|
125
|
-
createdAt: row.created_at,
|
|
126
|
-
source: row.source,
|
|
127
|
-
diff: row.diff,
|
|
128
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
129
|
-
}));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function revertEdit({ editId, sessionId }) {
|
|
133
|
-
const row = selectEditByIdStmt.get(editId);
|
|
134
|
-
if (!row) {
|
|
135
|
-
throw new Error(`Edit ${editId} not found.`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const absolute = resolveWorkspacePath(row.file_path);
|
|
139
|
-
if (row.before_content === null || row.before_content === undefined) {
|
|
140
|
-
// Original file did not exist; delete if present.
|
|
141
|
-
try {
|
|
142
|
-
await fs.unlink(absolute);
|
|
143
|
-
} catch (err) {
|
|
144
|
-
if (err.code !== "ENOENT") {
|
|
145
|
-
throw err;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
await fs.writeFile(absolute, row.before_content, "utf8");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
recordEdit({
|
|
153
|
-
sessionId,
|
|
154
|
-
filePath: row.file_path,
|
|
155
|
-
source: "revert_edit",
|
|
156
|
-
beforeContent: row.after_content,
|
|
157
|
-
afterContent: row.before_content,
|
|
158
|
-
metadata: { revertedEditId: row.id },
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
revertedEditId: row.id,
|
|
163
|
-
filePath: row.file_path,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
module.exports = {
|
|
168
|
-
recordEdit,
|
|
169
|
-
getEditHistory,
|
|
170
|
-
revertEdit,
|
|
171
|
-
};
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Babel-based Parser (Pure JS Fallback)
|
|
3
|
-
*
|
|
4
|
-
* Used when tree-sitter is unavailable (e.g., Node 25 without prebuilt binaries).
|
|
5
|
-
* Supports JavaScript, TypeScript, JSX, and TSX.
|
|
6
|
-
*
|
|
7
|
-
* @module indexer/babel-parser
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const logger = require("../logger");
|
|
11
|
-
|
|
12
|
-
let babelParser = null;
|
|
13
|
-
let babelTraverse = null;
|
|
14
|
-
|
|
15
|
-
function loadBabel() {
|
|
16
|
-
if (babelParser) return true;
|
|
17
|
-
try {
|
|
18
|
-
babelParser = require("@babel/parser");
|
|
19
|
-
babelTraverse = require("@babel/traverse").default;
|
|
20
|
-
return true;
|
|
21
|
-
} catch (err) {
|
|
22
|
-
logger.warn({ err: err.message }, "[BabelParser] Failed to load @babel/parser");
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const LANGUAGE_TO_PLUGINS = {
|
|
28
|
-
javascript: ["jsx"],
|
|
29
|
-
"javascript-react": ["jsx"],
|
|
30
|
-
typescript: ["typescript", "jsx"],
|
|
31
|
-
"typescript-react": ["typescript", "jsx"],
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Parse JavaScript/TypeScript file using Babel
|
|
36
|
-
*/
|
|
37
|
-
function parseFile(relativePath, content, language) {
|
|
38
|
-
if (!loadBabel()) return null;
|
|
39
|
-
|
|
40
|
-
const plugins = LANGUAGE_TO_PLUGINS[language];
|
|
41
|
-
if (!plugins) {
|
|
42
|
-
logger.debug({ language }, "[BabelParser] Unsupported language");
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const ast = babelParser.parse(content, {
|
|
48
|
-
sourceType: "unambiguous",
|
|
49
|
-
plugins: [...plugins, "decorators-legacy", "classProperties", "dynamicImport"],
|
|
50
|
-
errorRecovery: true,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const symbols = [];
|
|
54
|
-
const dependencies = [];
|
|
55
|
-
const imports = [];
|
|
56
|
-
const exports = [];
|
|
57
|
-
const references = [];
|
|
58
|
-
|
|
59
|
-
babelTraverse(ast, {
|
|
60
|
-
// Functions
|
|
61
|
-
FunctionDeclaration(path) {
|
|
62
|
-
if (path.node.id) {
|
|
63
|
-
symbols.push({
|
|
64
|
-
name: path.node.id.name,
|
|
65
|
-
kind: "function",
|
|
66
|
-
line: path.node.loc?.start.line || 1,
|
|
67
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
// Arrow functions assigned to variables
|
|
73
|
-
VariableDeclarator(path) {
|
|
74
|
-
if (
|
|
75
|
-
path.node.init &&
|
|
76
|
-
(path.node.init.type === "ArrowFunctionExpression" ||
|
|
77
|
-
path.node.init.type === "FunctionExpression") &&
|
|
78
|
-
path.node.id?.type === "Identifier"
|
|
79
|
-
) {
|
|
80
|
-
symbols.push({
|
|
81
|
-
name: path.node.id.name,
|
|
82
|
-
kind: "function",
|
|
83
|
-
line: path.node.loc?.start.line || 1,
|
|
84
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
// Classes
|
|
90
|
-
ClassDeclaration(path) {
|
|
91
|
-
if (path.node.id) {
|
|
92
|
-
symbols.push({
|
|
93
|
-
name: path.node.id.name,
|
|
94
|
-
kind: "class",
|
|
95
|
-
line: path.node.loc?.start.line || 1,
|
|
96
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
// Class methods
|
|
102
|
-
ClassMethod(path) {
|
|
103
|
-
if (path.node.key?.type === "Identifier") {
|
|
104
|
-
symbols.push({
|
|
105
|
-
name: path.node.key.name,
|
|
106
|
-
kind: "method",
|
|
107
|
-
line: path.node.loc?.start.line || 1,
|
|
108
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
// Import statements
|
|
114
|
-
ImportDeclaration(path) {
|
|
115
|
-
const importPath = path.node.source.value;
|
|
116
|
-
dependencies.push({
|
|
117
|
-
kind: "import",
|
|
118
|
-
path: importPath,
|
|
119
|
-
metadata: { clause: content.slice(path.node.start, path.node.end) },
|
|
120
|
-
});
|
|
121
|
-
imports.push({
|
|
122
|
-
path: importPath,
|
|
123
|
-
clause: content.slice(path.node.start, path.node.end),
|
|
124
|
-
line: path.node.loc?.start.line || 1,
|
|
125
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
// require() calls
|
|
130
|
-
CallExpression(path) {
|
|
131
|
-
if (
|
|
132
|
-
path.node.callee?.type === "Identifier" &&
|
|
133
|
-
path.node.callee.name === "require" &&
|
|
134
|
-
path.node.arguments[0]?.type === "StringLiteral"
|
|
135
|
-
) {
|
|
136
|
-
const reqPath = path.node.arguments[0].value;
|
|
137
|
-
dependencies.push({
|
|
138
|
-
kind: "require",
|
|
139
|
-
path: reqPath,
|
|
140
|
-
metadata: { clause: content.slice(path.node.start, path.node.end) },
|
|
141
|
-
});
|
|
142
|
-
imports.push({
|
|
143
|
-
path: reqPath,
|
|
144
|
-
clause: content.slice(path.node.start, path.node.end),
|
|
145
|
-
line: path.node.loc?.start.line || 1,
|
|
146
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
// Exports
|
|
152
|
-
ExportNamedDeclaration(path) {
|
|
153
|
-
exports.push({
|
|
154
|
-
clause: content.slice(path.node.start, path.node.end).substring(0, 200),
|
|
155
|
-
line: path.node.loc?.start.line || 1,
|
|
156
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
157
|
-
});
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
ExportDefaultDeclaration(path) {
|
|
161
|
-
exports.push({
|
|
162
|
-
clause: content.slice(path.node.start, path.node.end).substring(0, 200),
|
|
163
|
-
line: path.node.loc?.start.line || 1,
|
|
164
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
165
|
-
});
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
// Track identifiers for references (limited to avoid noise)
|
|
169
|
-
Identifier(path) {
|
|
170
|
-
// Only track identifiers in call expressions or member expressions
|
|
171
|
-
if (
|
|
172
|
-
path.parent?.type === "CallExpression" &&
|
|
173
|
-
path.parent.callee === path.node
|
|
174
|
-
) {
|
|
175
|
-
references.push({
|
|
176
|
-
name: path.node.name,
|
|
177
|
-
line: path.node.loc?.start.line || 1,
|
|
178
|
-
column: (path.node.loc?.start.column || 0) + 1,
|
|
179
|
-
snippet: path.node.name,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const langType = language.includes("typescript") ? "typescript" : "javascript";
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
symbols,
|
|
189
|
-
dependencies,
|
|
190
|
-
references,
|
|
191
|
-
imports,
|
|
192
|
-
exports,
|
|
193
|
-
language: langType,
|
|
194
|
-
definitions: symbols,
|
|
195
|
-
parser: "babel",
|
|
196
|
-
};
|
|
197
|
-
} catch (err) {
|
|
198
|
-
logger.warn({ err: err.message, file: relativePath }, "[BabelParser] Parse failed");
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Check if Babel parser is available
|
|
205
|
-
*/
|
|
206
|
-
function isBabelAvailable() {
|
|
207
|
-
return loadBabel();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
module.exports = {
|
|
211
|
-
parseFile,
|
|
212
|
-
isBabelAvailable,
|
|
213
|
-
};
|