devsmind-mcp 1.1.1 → 1.2.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.
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +223 -53
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/runner.d.ts +1 -1
- package/dist/cli/runner.js +674 -134
- package/dist/cli/runner.js.map +1 -1
- package/dist/db/database.js.map +1 -1
- package/dist/db/indexer.d.ts +3 -0
- package/dist/db/indexer.js +3 -0
- package/dist/db/indexer.js.map +1 -1
- package/dist/mcp/server.js +17 -36
- package/dist/mcp/server.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/runner.js
CHANGED
|
@@ -38,6 +38,7 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const http = __importStar(require("http"));
|
|
40
40
|
const https = __importStar(require("https"));
|
|
41
|
+
const crypto = __importStar(require("crypto"));
|
|
41
42
|
const database_1 = require("../db/database");
|
|
42
43
|
const indexer_1 = require("../db/indexer");
|
|
43
44
|
const scanner_1 = require("../utils/scanner");
|
|
@@ -81,6 +82,137 @@ function makeHttpRequest(urlStr, method, headers, body) {
|
|
|
81
82
|
function sleep(ms) {
|
|
82
83
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
83
84
|
}
|
|
85
|
+
// ── Progress Display ─────────────────────────────────────────────────────
|
|
86
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
87
|
+
const BAR_WIDTH = 28;
|
|
88
|
+
const IS_TTY = !!process.stdout.isTTY;
|
|
89
|
+
function fmtMs(ms) {
|
|
90
|
+
if (ms < 1000)
|
|
91
|
+
return '<1s';
|
|
92
|
+
const s = Math.round(ms / 1000);
|
|
93
|
+
if (s < 60)
|
|
94
|
+
return `${s}s`;
|
|
95
|
+
const m = Math.floor(s / 60);
|
|
96
|
+
const rs = s % 60;
|
|
97
|
+
return `${m}m ${rs.toString().padStart(2, '0')}s`;
|
|
98
|
+
}
|
|
99
|
+
function makeBar(done, total) {
|
|
100
|
+
const pct = total > 0 ? done / total : 0;
|
|
101
|
+
const filled = Math.round(BAR_WIDTH * pct);
|
|
102
|
+
return `[${'█'.repeat(filled)}${'░'.repeat(BAR_WIDTH - filled)}]`;
|
|
103
|
+
}
|
|
104
|
+
function truncate(s, max) {
|
|
105
|
+
return s.length > max ? '…' + s.slice(-(max - 1)) : s;
|
|
106
|
+
}
|
|
107
|
+
class ProgressDisplay {
|
|
108
|
+
isTTY = IS_TTY;
|
|
109
|
+
lineCount = 0;
|
|
110
|
+
spinIdx = 0;
|
|
111
|
+
// phase state
|
|
112
|
+
phaseLabel = '';
|
|
113
|
+
phaseNum = 0;
|
|
114
|
+
totalPhases = 2;
|
|
115
|
+
total = 0;
|
|
116
|
+
done = 0;
|
|
117
|
+
phaseStart = 0;
|
|
118
|
+
itemStart = 0;
|
|
119
|
+
times = [];
|
|
120
|
+
currentItem = '';
|
|
121
|
+
extraLine = '';
|
|
122
|
+
startPhase(phaseNum, label, total, alreadyDone = 0) {
|
|
123
|
+
this.phaseNum = phaseNum;
|
|
124
|
+
this.phaseLabel = label;
|
|
125
|
+
this.total = total;
|
|
126
|
+
this.done = alreadyDone; // resume offset — show true overall progress
|
|
127
|
+
this.times = [];
|
|
128
|
+
this.phaseStart = Date.now();
|
|
129
|
+
this.currentItem = alreadyDone > 0 ? `Resuming from item ${alreadyDone + 1}…` : 'Starting…';
|
|
130
|
+
this.extraLine = '';
|
|
131
|
+
this.lineCount = 0;
|
|
132
|
+
if (!this.isTTY) {
|
|
133
|
+
console.log(`\n${'═'.repeat(52)}`);
|
|
134
|
+
console.log(` Phase ${phaseNum}/${this.totalPhases}: ${label}`);
|
|
135
|
+
console.log(`${'═'.repeat(52)}`);
|
|
136
|
+
if (alreadyDone > 0) {
|
|
137
|
+
console.log(` Resuming: ${alreadyDone}/${total} already done`);
|
|
138
|
+
}
|
|
139
|
+
console.log(` Remaining: ${total - alreadyDone} item(s) to process`);
|
|
140
|
+
console.log(`${'─'.repeat(52)}\n`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const resumeTag = alreadyDone > 0 ? ` \x1B[90m(resuming from ${alreadyDone}/${total})\x1B[0m` : '';
|
|
144
|
+
process.stdout.write(`\n \x1B[1m\x1B[36mPhase ${phaseNum}/${this.totalPhases}: ${label}\x1B[0m${resumeTag}\n\n`);
|
|
145
|
+
this._render();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
beginItem(name) {
|
|
149
|
+
this.currentItem = name;
|
|
150
|
+
this.itemStart = Date.now();
|
|
151
|
+
this.spinIdx = (this.spinIdx + 1) % SPINNER_FRAMES.length;
|
|
152
|
+
if (this.isTTY)
|
|
153
|
+
this._render();
|
|
154
|
+
else
|
|
155
|
+
process.stdout.write(` [${this.done + 1}/${this.total}] ${name} … `);
|
|
156
|
+
}
|
|
157
|
+
completeItem(extra = '') {
|
|
158
|
+
const t = Date.now() - this.itemStart;
|
|
159
|
+
this.times.push(t);
|
|
160
|
+
this.done++;
|
|
161
|
+
this.extraLine = extra;
|
|
162
|
+
if (this.isTTY)
|
|
163
|
+
this._render();
|
|
164
|
+
else
|
|
165
|
+
console.log(`done (${fmtMs(t)}) ${extra}`);
|
|
166
|
+
}
|
|
167
|
+
skipItem(reason) {
|
|
168
|
+
this.done++;
|
|
169
|
+
this.extraLine = reason;
|
|
170
|
+
if (this.isTTY)
|
|
171
|
+
this._render();
|
|
172
|
+
else
|
|
173
|
+
console.log(`skip — ${reason}`);
|
|
174
|
+
}
|
|
175
|
+
_render() {
|
|
176
|
+
// Clear previously rendered block
|
|
177
|
+
if (this.lineCount > 0) {
|
|
178
|
+
process.stdout.write(`\x1B[${this.lineCount}A\x1B[0J`);
|
|
179
|
+
}
|
|
180
|
+
const pct = this.total > 0 ? (this.done / this.total) * 100 : 0;
|
|
181
|
+
const bar = makeBar(this.done, this.total);
|
|
182
|
+
const elapsed = Date.now() - this.phaseStart;
|
|
183
|
+
const avg = this.times.length > 0
|
|
184
|
+
? this.times.reduce((a, b) => a + b, 0) / this.times.length
|
|
185
|
+
: 0;
|
|
186
|
+
const remaining = this.total - this.done;
|
|
187
|
+
const eta = avg > 0 && remaining > 0 ? avg * remaining : 0;
|
|
188
|
+
const spin = SPINNER_FRAMES[this.spinIdx];
|
|
189
|
+
const itemShort = truncate(this.currentItem, 56);
|
|
190
|
+
const pctStr = `${Math.round(pct)}%`.padStart(4);
|
|
191
|
+
const doneStr = `${this.done}/${this.total}`;
|
|
192
|
+
const lines = [
|
|
193
|
+
` ${spin} ${bar} ${doneStr.padEnd(9)} ${pctStr}`,
|
|
194
|
+
` ⏱ Elapsed : \x1B[33m${fmtMs(elapsed)}\x1B[0m ETA : \x1B[32m${eta > 0 ? '~' + fmtMs(eta) : remaining > 0 ? 'calculating…' : 'done!'}\x1B[0m`,
|
|
195
|
+
` ⚡ Avg/item: \x1B[35m${avg > 0 ? fmtMs(avg) : '—'}\x1B[0m ${this.extraLine ? '\x1B[90m' + truncate(this.extraLine, 30) + '\x1B[0m' : ''}`,
|
|
196
|
+
` ▶ \x1B[90m${itemShort}\x1B[0m`,
|
|
197
|
+
``
|
|
198
|
+
];
|
|
199
|
+
process.stdout.write(lines.join('\n'));
|
|
200
|
+
this.lineCount = lines.length;
|
|
201
|
+
}
|
|
202
|
+
finishPhase(summary) {
|
|
203
|
+
if (this.isTTY && this.lineCount > 0) {
|
|
204
|
+
// Clear live block
|
|
205
|
+
process.stdout.write(`\x1B[${this.lineCount}A\x1B[0J`);
|
|
206
|
+
this.lineCount = 0;
|
|
207
|
+
}
|
|
208
|
+
const elapsed = Date.now() - this.phaseStart;
|
|
209
|
+
const avg = this.times.length > 0
|
|
210
|
+
? this.times.reduce((a, b) => a + b, 0) / this.times.length
|
|
211
|
+
: 0;
|
|
212
|
+
const checkmark = '\x1B[32m✔\x1B[0m';
|
|
213
|
+
console.log(` ${checkmark} ${summary} \x1B[90m(total: ${fmtMs(elapsed)}, avg: ${avg > 0 ? fmtMs(avg) : '—'}/item)\x1B[0m`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
84
216
|
// Build standard taxonomy prompt text
|
|
85
217
|
const TAXONOMY_PROMPT = `
|
|
86
218
|
Choose node types from this taxonomy:
|
|
@@ -97,23 +229,218 @@ Choose node types from this taxonomy:
|
|
|
97
229
|
- CLI: cli_command | cli_option
|
|
98
230
|
- UTILITY: util_function | helper | validator | formatter
|
|
99
231
|
`;
|
|
232
|
+
// ── Vertex AI Authentication & Helper Functions ───────────────────────────
|
|
233
|
+
function base64UrlEncode(obj) {
|
|
234
|
+
return Buffer.from(JSON.stringify(obj))
|
|
235
|
+
.toString('base64')
|
|
236
|
+
.replace(/\+/g, '-')
|
|
237
|
+
.replace(/\//g, '_')
|
|
238
|
+
.replace(/=/g, '');
|
|
239
|
+
}
|
|
240
|
+
function getAccessTokenFromServiceAccount(sa) {
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
242
|
+
try {
|
|
243
|
+
const header = { alg: 'RS256', typ: 'JWT' };
|
|
244
|
+
const now = Math.floor(Date.now() / 1000);
|
|
245
|
+
const payload = {
|
|
246
|
+
iss: sa.client_email,
|
|
247
|
+
scope: 'https://www.googleapis.com/auth/cloud-platform',
|
|
248
|
+
aud: sa.token_uri || 'https://oauth2.googleapis.com/token',
|
|
249
|
+
exp: now + 3600,
|
|
250
|
+
iat: now
|
|
251
|
+
};
|
|
252
|
+
const dataToSign = `${base64UrlEncode(header)}.${base64UrlEncode(payload)}`;
|
|
253
|
+
const signer = crypto.createSign('RSA-SHA256');
|
|
254
|
+
signer.update(dataToSign);
|
|
255
|
+
const signature = signer.sign(sa.private_key, 'base64')
|
|
256
|
+
.replace(/\+/g, '-')
|
|
257
|
+
.replace(/\//g, '_')
|
|
258
|
+
.replace(/=/g, '');
|
|
259
|
+
const jwt = `${dataToSign}.${signature}`;
|
|
260
|
+
const tokenUri = sa.token_uri || 'https://oauth2.googleapis.com/token';
|
|
261
|
+
const body = `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`;
|
|
262
|
+
const url = new URL(tokenUri);
|
|
263
|
+
const req = https.request({
|
|
264
|
+
hostname: url.hostname,
|
|
265
|
+
port: url.port || 443,
|
|
266
|
+
path: url.pathname + url.search,
|
|
267
|
+
method: 'POST',
|
|
268
|
+
headers: {
|
|
269
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
270
|
+
'Content-Length': Buffer.byteLength(body)
|
|
271
|
+
}
|
|
272
|
+
}, (res) => {
|
|
273
|
+
let chunks = '';
|
|
274
|
+
res.on('data', (chunk) => {
|
|
275
|
+
chunks += chunk;
|
|
276
|
+
});
|
|
277
|
+
res.on('end', () => {
|
|
278
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
279
|
+
try {
|
|
280
|
+
const parsed = JSON.parse(chunks);
|
|
281
|
+
if (parsed.access_token) {
|
|
282
|
+
resolve(parsed.access_token);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
reject(new Error(`No access token in response: ${chunks}`));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
reject(e);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
reject(new Error(`Token request failed with status ${res.statusCode}: ${chunks}`));
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
req.on('error', (err) => {
|
|
298
|
+
reject(err);
|
|
299
|
+
});
|
|
300
|
+
req.write(body);
|
|
301
|
+
req.end();
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
reject(err);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
let cachedVertexToken = null;
|
|
309
|
+
let vertexTokenExpiry = 0; // Epoch ms
|
|
310
|
+
async function getVertexTokenCached(saData) {
|
|
311
|
+
const now = Date.now();
|
|
312
|
+
if (cachedVertexToken && vertexTokenExpiry > now + 300000) {
|
|
313
|
+
return cachedVertexToken;
|
|
314
|
+
}
|
|
315
|
+
const token = await getAccessTokenFromServiceAccount(saData);
|
|
316
|
+
cachedVertexToken = token;
|
|
317
|
+
vertexTokenExpiry = Date.now() + 3600 * 1000;
|
|
318
|
+
return token;
|
|
319
|
+
}
|
|
320
|
+
async function extractWithVertex(model, token, projectId, location, filePath, code) {
|
|
321
|
+
const url = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${model}:generateContent`;
|
|
322
|
+
const systemPrompt = `You are a codebase indexing assistant. Your job is to analyze the source code file provided and extract all code structures (functions, methods, classes, controllers, services, interfaces, schema models, types) defined in the file.
|
|
323
|
+
Return ONLY a valid JSON object matching the schema:
|
|
324
|
+
{
|
|
325
|
+
"nodes": [
|
|
326
|
+
{
|
|
327
|
+
"node_id": "fully_qualified_identifier (e.g. Class.method or function)",
|
|
328
|
+
"name": "display_name",
|
|
329
|
+
"type": "type_from_taxonomy",
|
|
330
|
+
"signature": "param/return signature (optional)",
|
|
331
|
+
"code_snapshot": "the exact full source code block of this entity"
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
${TAXONOMY_PROMPT}
|
|
336
|
+
CRITICAL RULES:
|
|
337
|
+
1. ONLY extract code structures defined in the file. Do NOT extract imports or third-party libraries as nodes.
|
|
338
|
+
2. For each node, extract its exact code snippet as "code_snapshot".
|
|
339
|
+
3. DO NOT wrap JSON in markdown blocks (e.g. no \`\`\`json). Return raw JSON.
|
|
340
|
+
4. Be highly precise and return an empty JSON object if no code constructs are found.`;
|
|
341
|
+
const payload = {
|
|
342
|
+
contents: [
|
|
343
|
+
{
|
|
344
|
+
role: 'user',
|
|
345
|
+
parts: [
|
|
346
|
+
{
|
|
347
|
+
text: `File path: ${filePath}\n\nCode:\n${code}`
|
|
348
|
+
}
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
],
|
|
352
|
+
systemInstruction: {
|
|
353
|
+
parts: [
|
|
354
|
+
{
|
|
355
|
+
text: systemPrompt
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
},
|
|
359
|
+
generationConfig: {
|
|
360
|
+
responseMimeType: 'application/json'
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
const responseText = await makeHttpRequest(url, 'POST', {
|
|
364
|
+
'Content-Type': 'application/json',
|
|
365
|
+
'Authorization': `Bearer ${token}`
|
|
366
|
+
}, JSON.stringify(payload));
|
|
367
|
+
const parsed = JSON.parse(responseText);
|
|
368
|
+
const text = parsed.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
369
|
+
if (!text) {
|
|
370
|
+
return {};
|
|
371
|
+
}
|
|
372
|
+
return JSON.parse(text);
|
|
373
|
+
}
|
|
374
|
+
async function resolveConnectionsWithVertex(model, token, projectId, location, sourceNodeId, code, candidateNodeIds) {
|
|
375
|
+
const url = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${model}:generateContent`;
|
|
376
|
+
const systemPrompt = `You are a codebase indexing assistant. Your job is to analyze the source code of a specific code entity and identify which other known code entities from the provided candidate list it calls or references.
|
|
377
|
+
Return ONLY a valid JSON object matching the schema:
|
|
378
|
+
{
|
|
379
|
+
"connections": [
|
|
380
|
+
"target_node_id_1",
|
|
381
|
+
"target_node_id_2"
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
CRITICAL RULES:
|
|
385
|
+
1. ONLY return target node IDs that are present in the provided list of known candidates. Do NOT invent new node IDs.
|
|
386
|
+
2. DO NOT include connections to third-party libraries, language built-ins, or the source node itself.
|
|
387
|
+
3. DO NOT wrap JSON in markdown blocks (e.g. no \`\`\`json). Return raw JSON.
|
|
388
|
+
4. If no connections are found, return an empty array.`;
|
|
389
|
+
const payload = {
|
|
390
|
+
contents: [
|
|
391
|
+
{
|
|
392
|
+
role: 'user',
|
|
393
|
+
parts: [
|
|
394
|
+
{
|
|
395
|
+
text: `Source Node ID: ${sourceNodeId}\n\nSource Code:\n${code}\n\nCandidate Target Node IDs in the Codebase:\n${JSON.stringify(candidateNodeIds, null, 2)}`
|
|
396
|
+
}
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
],
|
|
400
|
+
systemInstruction: {
|
|
401
|
+
parts: [
|
|
402
|
+
{
|
|
403
|
+
text: systemPrompt
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
},
|
|
407
|
+
generationConfig: {
|
|
408
|
+
responseMimeType: 'application/json'
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const responseText = await makeHttpRequest(url, 'POST', {
|
|
412
|
+
'Content-Type': 'application/json',
|
|
413
|
+
'Authorization': `Bearer ${token}`
|
|
414
|
+
}, JSON.stringify(payload));
|
|
415
|
+
const parsed = JSON.parse(responseText);
|
|
416
|
+
const text = parsed.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
417
|
+
if (!text) {
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
420
|
+
const result = JSON.parse(text);
|
|
421
|
+
return result.connections || [];
|
|
422
|
+
}
|
|
100
423
|
async function extractWithGemini(model, key, filePath, code) {
|
|
101
424
|
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`;
|
|
102
|
-
const systemPrompt = `You are a codebase indexing assistant. Your job is to analyze the source code file provided and extract all code structures (functions, methods, classes, controllers, services, interfaces, schema models, types)
|
|
425
|
+
const systemPrompt = `You are a codebase indexing assistant. Your job is to analyze the source code file provided and extract all code structures (functions, methods, classes, controllers, services, interfaces, schema models, types) defined in the file.
|
|
103
426
|
Return ONLY a valid JSON object matching the schema:
|
|
104
427
|
{
|
|
105
428
|
"nodes": [
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
429
|
+
{
|
|
430
|
+
"node_id": "fully_qualified_identifier (e.g. Class.method or function)",
|
|
431
|
+
"name": "display_name",
|
|
432
|
+
"type": "type_from_taxonomy",
|
|
433
|
+
"signature": "param/return signature (optional)",
|
|
434
|
+
"code_snapshot": "the exact full source code block of this entity"
|
|
435
|
+
}
|
|
110
436
|
]
|
|
111
437
|
}
|
|
112
438
|
${TAXONOMY_PROMPT}
|
|
113
439
|
CRITICAL RULES:
|
|
114
440
|
1. ONLY extract code structures defined in the file. Do NOT extract imports or third-party libraries as nodes.
|
|
115
|
-
2.
|
|
116
|
-
3.
|
|
441
|
+
2. For each node, extract its exact code snippet as "code_snapshot".
|
|
442
|
+
3. DO NOT wrap JSON in markdown blocks (e.g. no \`\`\`json). Return raw JSON.
|
|
443
|
+
4. Be highly precise and return an empty JSON object if no code constructs are found.`;
|
|
117
444
|
const payload = {
|
|
118
445
|
contents: [
|
|
119
446
|
{
|
|
@@ -145,20 +472,24 @@ CRITICAL RULES:
|
|
|
145
472
|
}
|
|
146
473
|
async function extractWithOllama(url, model, filePath, code) {
|
|
147
474
|
const endpoint = `${url.replace(/\/$/, '')}/api/chat`;
|
|
148
|
-
const systemPrompt = `You are a codebase indexing assistant. Analyze this source code file and extract code structures (functions, classes, methods, endpoints)
|
|
475
|
+
const systemPrompt = `You are a codebase indexing assistant. Analyze this source code file and extract code structures (functions, classes, methods, endpoints).
|
|
149
476
|
Return ONLY a valid JSON object matching the schema:
|
|
150
477
|
{
|
|
151
478
|
"nodes": [
|
|
152
|
-
{
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
479
|
+
{
|
|
480
|
+
"node_id": "unique_string (e.g. Class.method or function)",
|
|
481
|
+
"name": "display_name",
|
|
482
|
+
"type": "type_from_taxonomy",
|
|
483
|
+
"signature": "param/return signature (optional)",
|
|
484
|
+
"code_snapshot": "the exact full source code block of this entity"
|
|
485
|
+
}
|
|
156
486
|
]
|
|
157
487
|
}
|
|
158
488
|
${TAXONOMY_PROMPT}
|
|
159
489
|
CRITICAL RULES:
|
|
160
490
|
1. ONLY extract constructs defined in this file. Do NOT extract third-party libraries or imports.
|
|
161
|
-
2.
|
|
491
|
+
2. For each node, extract its exact code snippet as "code_snapshot".
|
|
492
|
+
3. Return a clean, valid JSON object.`;
|
|
162
493
|
const userPrompt = `File path: ${filePath}\n\nCode:\n${code}`;
|
|
163
494
|
const payload = {
|
|
164
495
|
model,
|
|
@@ -172,10 +503,95 @@ CRITICAL RULES:
|
|
|
172
503
|
const responseText = await makeHttpRequest(endpoint, 'POST', { 'Content-Type': 'application/json' }, JSON.stringify(payload));
|
|
173
504
|
const parsed = JSON.parse(responseText);
|
|
174
505
|
const text = parsed.message?.content;
|
|
506
|
+
return JSON.parse(text);
|
|
507
|
+
}
|
|
508
|
+
async function resolveConnectionsWithGemini(model, key, sourceNodeId, code, candidateNodeIds) {
|
|
509
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`;
|
|
510
|
+
const systemPrompt = `You are a codebase indexing assistant. Your job is to analyze the source code of a specific code entity and identify which other known code entities from the provided candidate list it calls or references.
|
|
511
|
+
Return ONLY a valid JSON object matching the schema:
|
|
512
|
+
{
|
|
513
|
+
"connections": [
|
|
514
|
+
"target_node_id_1",
|
|
515
|
+
"target_node_id_2"
|
|
516
|
+
]
|
|
517
|
+
}
|
|
518
|
+
CRITICAL RULES:
|
|
519
|
+
1. ONLY return target node IDs that are present in the provided list of known candidates. Do NOT invent new node IDs.
|
|
520
|
+
2. DO NOT include connections to third-party libraries, language built-ins, or the source node itself.
|
|
521
|
+
3. DO NOT wrap JSON in markdown blocks (e.g. no \`\`\`json). Return raw JSON.
|
|
522
|
+
4. If no connections are found, return an empty array.`;
|
|
523
|
+
const payload = {
|
|
524
|
+
contents: [
|
|
525
|
+
{
|
|
526
|
+
parts: [
|
|
527
|
+
{
|
|
528
|
+
text: `Source Node ID: ${sourceNodeId}\n\nSource Code:\n${code}\n\nCandidate Target Node IDs in the Codebase:\n${JSON.stringify(candidateNodeIds, null, 2)}`
|
|
529
|
+
}
|
|
530
|
+
]
|
|
531
|
+
}
|
|
532
|
+
],
|
|
533
|
+
systemInstruction: {
|
|
534
|
+
parts: [
|
|
535
|
+
{
|
|
536
|
+
text: systemPrompt
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
},
|
|
540
|
+
generationConfig: {
|
|
541
|
+
responseMimeType: 'application/json'
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
const responseText = await makeHttpRequest(url, 'POST', { 'Content-Type': 'application/json' }, JSON.stringify(payload));
|
|
545
|
+
const parsed = JSON.parse(responseText);
|
|
546
|
+
const text = parsed.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
175
547
|
if (!text) {
|
|
176
|
-
return
|
|
548
|
+
return [];
|
|
177
549
|
}
|
|
178
|
-
|
|
550
|
+
const result = JSON.parse(text);
|
|
551
|
+
return result.connections || [];
|
|
552
|
+
}
|
|
553
|
+
async function resolveConnectionsWithOllama(url, model, sourceNodeId, code, candidateNodeIds) {
|
|
554
|
+
const endpoint = `${url.replace(/\/$/, '')}/api/chat`;
|
|
555
|
+
const systemPrompt = `You are a codebase indexing assistant. Analyze this source code of a code entity and identify which other known entities from the provided candidate list it calls or references.
|
|
556
|
+
Return ONLY a valid JSON object matching the schema:
|
|
557
|
+
{
|
|
558
|
+
"connections": [
|
|
559
|
+
"target_node_id_1",
|
|
560
|
+
"target_node_id_2"
|
|
561
|
+
]
|
|
562
|
+
}
|
|
563
|
+
CRITICAL RULES:
|
|
564
|
+
1. ONLY return target node IDs that are present in the provided list of known candidates. Do NOT invent new node IDs.
|
|
565
|
+
2. Return a clean, valid JSON object.`;
|
|
566
|
+
const userPrompt = `Source Node ID: ${sourceNodeId}\n\nSource Code:\n${code}\n\nCandidate Target Node IDs:\n${JSON.stringify(candidateNodeIds, null, 2)}`;
|
|
567
|
+
const payload = {
|
|
568
|
+
model,
|
|
569
|
+
messages: [
|
|
570
|
+
{ role: 'system', content: systemPrompt },
|
|
571
|
+
{ role: 'user', content: userPrompt }
|
|
572
|
+
],
|
|
573
|
+
stream: false,
|
|
574
|
+
format: 'json'
|
|
575
|
+
};
|
|
576
|
+
const responseText = await makeHttpRequest(endpoint, 'POST', { 'Content-Type': 'application/json' }, JSON.stringify(payload));
|
|
577
|
+
const parsed = JSON.parse(responseText);
|
|
578
|
+
const text = parsed.message?.content;
|
|
579
|
+
if (!text) {
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
const result = JSON.parse(text);
|
|
583
|
+
return result.connections || [];
|
|
584
|
+
}
|
|
585
|
+
function filterCandidates(codeSnapshot, allNodeIds) {
|
|
586
|
+
const lowerCode = codeSnapshot.toLowerCase();
|
|
587
|
+
return allNodeIds.filter(id => {
|
|
588
|
+
const shortName = id.includes('.') ? id.split('.').pop() : id;
|
|
589
|
+
if (!shortName || shortName.trim().length === 0)
|
|
590
|
+
return false;
|
|
591
|
+
if (shortName.length < 3)
|
|
592
|
+
return false;
|
|
593
|
+
return lowerCode.includes(shortName.toLowerCase());
|
|
594
|
+
});
|
|
179
595
|
}
|
|
180
596
|
async function runBackgroundIndexing(opts) {
|
|
181
597
|
const resolvedDevmind = path.resolve(opts.devmindPath);
|
|
@@ -183,6 +599,10 @@ async function runBackgroundIndexing(opts) {
|
|
|
183
599
|
console.log(` Brain directory : ${resolvedDevmind}`);
|
|
184
600
|
console.log(` Provider : ${opts.provider}`);
|
|
185
601
|
let modelName = opts.model || '';
|
|
602
|
+
let vertexSaData = null;
|
|
603
|
+
let vertexToken = null;
|
|
604
|
+
let vertexProjectId = '';
|
|
605
|
+
let vertexLocation = 'us-central1';
|
|
186
606
|
if (opts.provider === 'gemini') {
|
|
187
607
|
modelName = modelName || 'gemini-2.0-flash';
|
|
188
608
|
const apiKey = opts.key || process.env.GEMINI_API_KEY || '';
|
|
@@ -192,20 +612,61 @@ async function runBackgroundIndexing(opts) {
|
|
|
192
612
|
}
|
|
193
613
|
opts.key = apiKey;
|
|
194
614
|
}
|
|
615
|
+
else if (opts.provider === 'vertex') {
|
|
616
|
+
modelName = modelName || 'gemini-1.5-flash';
|
|
617
|
+
const inputKey = opts.key || process.env.GOOGLE_APPLICATION_CREDENTIALS || process.env.VERTEX_API_KEY || process.env.GEMINI_API_KEY || '';
|
|
618
|
+
if (!inputKey) {
|
|
619
|
+
console.error('❌ Error: Vertex AI requires a Service Account JSON path or Bearer Token. Pass --key or set GOOGLE_APPLICATION_CREDENTIALS / VERTEX_API_KEY environment variable.');
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
if (inputKey.trim().startsWith('{')) {
|
|
624
|
+
vertexSaData = JSON.parse(inputKey);
|
|
625
|
+
}
|
|
626
|
+
else if (fs.existsSync(inputKey)) {
|
|
627
|
+
vertexSaData = JSON.parse(fs.readFileSync(inputKey, 'utf-8'));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
catch (e) {
|
|
631
|
+
// Treat as raw token
|
|
632
|
+
}
|
|
633
|
+
vertexProjectId = vertexSaData?.project_id || process.env.GCP_PROJECT_ID || process.env.VERTEX_PROJECT_ID || '';
|
|
634
|
+
vertexLocation = process.env.GCP_LOCATION || process.env.VERTEX_LOCATION || 'us-central1';
|
|
635
|
+
if (!vertexSaData && !inputKey.startsWith('ya29.')) {
|
|
636
|
+
console.error('❌ Error: Vertex key must be a valid Service Account JSON file path, inline JSON, or raw OAuth access token starting with "ya29."');
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
if (!vertexProjectId) {
|
|
640
|
+
console.error('❌ Error: Vertex Project ID could not be determined. Please set GCP_PROJECT_ID environment variable or specify it in your service account JSON.');
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
if (!vertexSaData) {
|
|
644
|
+
vertexToken = inputKey; // Raw Bearer token
|
|
645
|
+
}
|
|
646
|
+
}
|
|
195
647
|
else {
|
|
196
648
|
modelName = modelName || 'qwen2.5-coder';
|
|
197
649
|
opts.url = opts.url || 'http://localhost:11434';
|
|
198
650
|
}
|
|
651
|
+
const getVertexToken = async () => {
|
|
652
|
+
if (vertexToken)
|
|
653
|
+
return vertexToken;
|
|
654
|
+
if (vertexSaData) {
|
|
655
|
+
return await getVertexTokenCached(vertexSaData);
|
|
656
|
+
}
|
|
657
|
+
throw new Error('No Vertex credentials available');
|
|
658
|
+
};
|
|
199
659
|
console.log(` Model : ${modelName}`);
|
|
200
|
-
// 1.
|
|
660
|
+
// 1. Open DB
|
|
661
|
+
const dbFile = path.join(resolvedDevmind, 'brain.db');
|
|
662
|
+
const db = new database_1.DevMindDatabase(dbFile);
|
|
663
|
+
// 2. Scan for repos & files
|
|
201
664
|
const { repos, total_files } = (0, scanner_1.scanRepoFiles)(resolvedDevmind);
|
|
202
665
|
if (total_files === 0) {
|
|
203
666
|
console.log('⚠️ No files found to index. Make sure config.json repositories are configured properly.');
|
|
667
|
+
db.close();
|
|
204
668
|
return;
|
|
205
669
|
}
|
|
206
|
-
// 2. Open DB
|
|
207
|
-
const dbFile = path.join(resolvedDevmind, 'brain.db');
|
|
208
|
-
const db = new database_1.DevMindDatabase(dbFile);
|
|
209
670
|
// 3. Read or create scratchpad
|
|
210
671
|
let pad = (0, indexer_1.readScratchpad)(resolvedDevmind);
|
|
211
672
|
if (!pad) {
|
|
@@ -216,137 +677,216 @@ async function runBackgroundIndexing(opts) {
|
|
|
216
677
|
db.close();
|
|
217
678
|
return;
|
|
218
679
|
}
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (idx !== -1) {
|
|
233
|
-
startIndex = idx + 1;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
console.log(` Progress : ${pad.files_done}/${pad.files_total} files (${Math.round((pad.files_done / pad.files_total) * 100)}%)`);
|
|
237
|
-
console.log(` Remaining Files : ${allFiles.length - startIndex} file(s)`);
|
|
238
|
-
console.log('──────────────────────────────────────────────────\n');
|
|
239
|
-
let fileIndex = startIndex;
|
|
240
|
-
let successCount = 0;
|
|
241
|
-
for (; fileIndex < allFiles.length; fileIndex++) {
|
|
242
|
-
const fileObj = allFiles[fileIndex];
|
|
243
|
-
const relPath = path.relative(process.cwd(), fileObj.absolutePath);
|
|
244
|
-
console.log(`[${pad.files_done + 1}/${pad.files_total}] Indexing: ${relPath}...`);
|
|
245
|
-
let code = '';
|
|
246
|
-
try {
|
|
247
|
-
code = fs.readFileSync(fileObj.absolutePath, 'utf-8');
|
|
680
|
+
// =========================================================================
|
|
681
|
+
// PHASE 1: NODE & CODE SNAPSHOT EXTRACTION
|
|
682
|
+
// =========================================================================
|
|
683
|
+
const progress = new ProgressDisplay();
|
|
684
|
+
if (pad.phase === 1) {
|
|
685
|
+
const reposDone = new Set(pad.repos_done);
|
|
686
|
+
const allFiles = [];
|
|
687
|
+
for (const repo of repos) {
|
|
688
|
+
if (reposDone.has(repo.repo_name))
|
|
689
|
+
continue;
|
|
690
|
+
for (const f of repo.files) {
|
|
691
|
+
allFiles.push({ repoName: repo.repo_name, absolutePath: f });
|
|
692
|
+
}
|
|
248
693
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
694
|
+
let startIndex = 0;
|
|
695
|
+
if (pad.last_file_indexed) {
|
|
696
|
+
const idx = allFiles.findIndex(f => f.absolutePath === pad.last_file_indexed);
|
|
697
|
+
if (idx !== -1)
|
|
698
|
+
startIndex = idx + 1;
|
|
252
699
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
let result = {};
|
|
264
|
-
let retries = 3;
|
|
265
|
-
while (retries > 0) {
|
|
700
|
+
// Use pad.files_total as true total so resume shows e.g. 14/1068, not 1/1055
|
|
701
|
+
progress.startPhase(1, 'Node & Code Extraction', pad.files_total, pad.files_done);
|
|
702
|
+
let fileIndex = startIndex;
|
|
703
|
+
for (; fileIndex < allFiles.length; fileIndex++) {
|
|
704
|
+
const fileObj = allFiles[fileIndex];
|
|
705
|
+
const relPath = path.relative(process.cwd(), fileObj.absolutePath);
|
|
706
|
+
progress.beginItem(relPath);
|
|
707
|
+
let code = '';
|
|
266
708
|
try {
|
|
267
|
-
|
|
268
|
-
|
|
709
|
+
code = fs.readFileSync(fileObj.absolutePath, 'utf-8');
|
|
710
|
+
}
|
|
711
|
+
catch (err) {
|
|
712
|
+
progress.skipItem(`read error: ${err.message}`);
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (code.trim().length === 0) {
|
|
716
|
+
pad.files_done++;
|
|
717
|
+
pad.last_file_indexed = fileObj.absolutePath;
|
|
718
|
+
(0, indexer_1.writeScratchpad)(resolvedDevmind, pad);
|
|
719
|
+
progress.skipItem('empty file');
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
let result = {};
|
|
723
|
+
let retries = 3;
|
|
724
|
+
while (retries > 0) {
|
|
725
|
+
try {
|
|
726
|
+
if (opts.provider === 'gemini') {
|
|
727
|
+
result = await extractWithGemini(modelName, opts.key, fileObj.absolutePath, code);
|
|
728
|
+
}
|
|
729
|
+
else if (opts.provider === 'vertex') {
|
|
730
|
+
const token = await getVertexToken();
|
|
731
|
+
result = await extractWithVertex(modelName, token, vertexProjectId, vertexLocation, fileObj.absolutePath, code);
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
result = await extractWithOllama(opts.url, modelName, fileObj.absolutePath, code);
|
|
735
|
+
}
|
|
736
|
+
break;
|
|
269
737
|
}
|
|
270
|
-
|
|
271
|
-
|
|
738
|
+
catch (err) {
|
|
739
|
+
retries--;
|
|
740
|
+
if (retries === 0) {
|
|
741
|
+
progress.finishPhase(`Paused — API error. Run again to resume.`);
|
|
742
|
+
console.error(`❌ ${err.message}`);
|
|
743
|
+
db.close();
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
await sleep(2000);
|
|
272
747
|
}
|
|
273
|
-
break;
|
|
274
748
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
749
|
+
let newNodesCount = 0;
|
|
750
|
+
if (result.nodes && Array.isArray(result.nodes)) {
|
|
751
|
+
for (const n of result.nodes) {
|
|
752
|
+
if (n.node_id && n.name && n.type) {
|
|
753
|
+
db.upsertNode({
|
|
754
|
+
id: n.node_id,
|
|
755
|
+
name: n.name,
|
|
756
|
+
type: n.type,
|
|
757
|
+
file_path: fileObj.absolutePath,
|
|
758
|
+
signature: n.signature || null
|
|
759
|
+
});
|
|
760
|
+
newNodesCount++;
|
|
761
|
+
if (n.code_snapshot) {
|
|
762
|
+
db.updateHistory({
|
|
763
|
+
node_id: n.node_id,
|
|
764
|
+
code_snapshot: n.code_snapshot,
|
|
765
|
+
reasoning: {
|
|
766
|
+
what_changed: 'Initial code extraction during background indexing',
|
|
767
|
+
why: 'Initial index setup',
|
|
768
|
+
goal: 'Establish baseline codebase knowledge graph',
|
|
769
|
+
developer: 'devsmind background indexer',
|
|
770
|
+
model: modelName
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
282
775
|
}
|
|
283
|
-
await sleep(2000);
|
|
284
776
|
}
|
|
777
|
+
pad.files_done++;
|
|
778
|
+
pad.nodes_created += newNodesCount;
|
|
779
|
+
pad.last_file_indexed = fileObj.absolutePath;
|
|
780
|
+
pad.current_repo = fileObj.repoName;
|
|
781
|
+
pad.updated_at = new Date().toISOString();
|
|
782
|
+
const currentRepoFiles = repos.find(r => r.repo_name === fileObj.repoName)?.files || [];
|
|
783
|
+
const isRepoDone = currentRepoFiles.length > 0 && currentRepoFiles[currentRepoFiles.length - 1] === fileObj.absolutePath;
|
|
784
|
+
if (isRepoDone && !pad.repos_done.includes(fileObj.repoName)) {
|
|
785
|
+
pad.repos_done.push(fileObj.repoName);
|
|
786
|
+
}
|
|
787
|
+
(0, indexer_1.writeScratchpad)(resolvedDevmind, pad);
|
|
788
|
+
progress.completeItem(`${pad.nodes_created} node(s) found so far`);
|
|
789
|
+
if (opts.provider === 'gemini' || opts.provider === 'vertex')
|
|
790
|
+
await sleep(2000);
|
|
791
|
+
else
|
|
792
|
+
await sleep(200);
|
|
285
793
|
}
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
794
|
+
// Transition to Phase 2
|
|
795
|
+
const activeNodes = db.listNodes();
|
|
796
|
+
pad.phase = 2;
|
|
797
|
+
pad.nodes_total = activeNodes.length;
|
|
798
|
+
pad.nodes_done = 0;
|
|
799
|
+
pad.updated_at = new Date().toISOString();
|
|
800
|
+
(0, indexer_1.writeScratchpad)(resolvedDevmind, pad);
|
|
801
|
+
progress.finishPhase(`Phase 1 done — ${activeNodes.length} node(s) extracted from ${pad.files_done} file(s)`);
|
|
802
|
+
}
|
|
803
|
+
// =========================================================================
|
|
804
|
+
// PHASE 2: AI CONNECTION RESOLUTION / LINKING
|
|
805
|
+
// =========================================================================
|
|
806
|
+
if (pad.phase === 2) {
|
|
807
|
+
const activeNodes = db.listNodes();
|
|
808
|
+
const allNodeIds = activeNodes.map(n => n.id);
|
|
809
|
+
const resumeIndex = pad.nodes_done || 0;
|
|
810
|
+
// Use total node count and resume offset so bar shows true progress
|
|
811
|
+
progress.startPhase(2, 'AI Connection Resolution', activeNodes.length, resumeIndex);
|
|
812
|
+
let nodeIndex = resumeIndex;
|
|
813
|
+
for (; nodeIndex < activeNodes.length; nodeIndex++) {
|
|
814
|
+
const node = activeNodes[nodeIndex];
|
|
815
|
+
progress.beginItem(node.id);
|
|
816
|
+
const latestCode = db.getLatestCode(node.id);
|
|
817
|
+
if (!latestCode || !latestCode.code_snapshot || latestCode.code_snapshot.trim().length === 0) {
|
|
818
|
+
pad.nodes_done = nodeIndex + 1;
|
|
819
|
+
pad.updated_at = new Date().toISOString();
|
|
820
|
+
(0, indexer_1.writeScratchpad)(resolvedDevmind, pad);
|
|
821
|
+
progress.skipItem('no code snapshot');
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
const candidates = filterCandidates(latestCode.code_snapshot, allNodeIds);
|
|
825
|
+
const filteredCandidates = candidates.filter(id => id !== node.id);
|
|
826
|
+
if (filteredCandidates.length === 0) {
|
|
827
|
+
pad.nodes_done = nodeIndex + 1;
|
|
828
|
+
pad.updated_at = new Date().toISOString();
|
|
829
|
+
(0, indexer_1.writeScratchpad)(resolvedDevmind, pad);
|
|
830
|
+
progress.skipItem('no matching candidates');
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
let connections = [];
|
|
834
|
+
let retries = 3;
|
|
835
|
+
while (retries > 0) {
|
|
836
|
+
try {
|
|
837
|
+
if (opts.provider === 'gemini') {
|
|
838
|
+
connections = await resolveConnectionsWithGemini(modelName, opts.key, node.id, latestCode.code_snapshot, filteredCandidates);
|
|
839
|
+
}
|
|
840
|
+
else if (opts.provider === 'vertex') {
|
|
841
|
+
const token = await getVertexToken();
|
|
842
|
+
connections = await resolveConnectionsWithVertex(modelName, token, vertexProjectId, vertexLocation, node.id, latestCode.code_snapshot, filteredCandidates);
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
connections = await resolveConnectionsWithOllama(opts.url, modelName, node.id, latestCode.code_snapshot, filteredCandidates);
|
|
846
|
+
}
|
|
847
|
+
break;
|
|
848
|
+
}
|
|
849
|
+
catch (err) {
|
|
850
|
+
retries--;
|
|
851
|
+
if (retries === 0) {
|
|
852
|
+
progress.finishPhase('Paused — API error. Run again to resume.');
|
|
853
|
+
console.error(`❌ ${err.message}`);
|
|
854
|
+
db.close();
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
await sleep(2000);
|
|
300
858
|
}
|
|
301
859
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
newConns++;
|
|
860
|
+
let addedCount = 0;
|
|
861
|
+
for (const targetId of connections) {
|
|
862
|
+
if (allNodeIds.includes(targetId)) {
|
|
863
|
+
db.addConnection(node.id, targetId);
|
|
864
|
+
addedCount++;
|
|
308
865
|
}
|
|
309
866
|
}
|
|
867
|
+
pad.nodes_done = nodeIndex + 1;
|
|
868
|
+
pad.connections_created += addedCount;
|
|
869
|
+
pad.updated_at = new Date().toISOString();
|
|
870
|
+
(0, indexer_1.writeScratchpad)(resolvedDevmind, pad);
|
|
871
|
+
progress.completeItem(`${pad.connections_created} connection(s) created so far`);
|
|
872
|
+
if (opts.provider === 'gemini' || opts.provider === 'vertex')
|
|
873
|
+
await sleep(2000);
|
|
874
|
+
else
|
|
875
|
+
await sleep(200);
|
|
310
876
|
}
|
|
311
|
-
|
|
312
|
-
pad.files_done++;
|
|
313
|
-
pad.nodes_created += newNodes;
|
|
314
|
-
pad.connections_created += newConns;
|
|
315
|
-
pad.last_file_indexed = fileObj.absolutePath;
|
|
316
|
-
pad.current_repo = fileObj.repoName;
|
|
317
|
-
// Check if the repository is fully indexed
|
|
318
|
-
const currentRepoFiles = repos.find(r => r.repo_name === fileObj.repoName)?.files || [];
|
|
319
|
-
const isRepoDone = currentRepoFiles.length > 0 && currentRepoFiles[currentRepoFiles.length - 1] === fileObj.absolutePath;
|
|
320
|
-
if (isRepoDone && !pad.repos_done.includes(fileObj.repoName)) {
|
|
321
|
-
pad.repos_done.push(fileObj.repoName);
|
|
322
|
-
}
|
|
323
|
-
(0, indexer_1.updateScratchpad)(resolvedDevmind, {
|
|
324
|
-
files_done: pad.files_done,
|
|
325
|
-
last_file_indexed: pad.last_file_indexed,
|
|
326
|
-
nodes_created: pad.nodes_created,
|
|
327
|
-
connections_created: pad.connections_created,
|
|
328
|
-
current_repo: pad.current_repo,
|
|
329
|
-
repos_done: pad.repos_done
|
|
330
|
-
});
|
|
331
|
-
console.log(` Success: Created ${newNodes} node(s), ${newConns} connection(s).`);
|
|
332
|
-
successCount++;
|
|
333
|
-
// Respect Gemini AI Studio Free Tier limit (15 Requests/Min -> 1 request every 4 seconds)
|
|
334
|
-
if (opts.provider === 'gemini') {
|
|
335
|
-
await sleep(4000);
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
// Short breather for local CPU/GPU to not cook
|
|
339
|
-
await sleep(200);
|
|
340
|
-
}
|
|
877
|
+
progress.finishPhase(`Phase 2 done — ${pad.connections_created} connection(s) linked across ${pad.nodes_total} node(s)`);
|
|
341
878
|
}
|
|
342
|
-
// Mark complete
|
|
343
|
-
|
|
879
|
+
// Mark indexing session as fully complete
|
|
880
|
+
pad.status = 'complete';
|
|
881
|
+
pad.updated_at = new Date().toISOString();
|
|
882
|
+
(0, indexer_1.writeScratchpad)(resolvedDevmind, pad);
|
|
344
883
|
db.vacuum();
|
|
345
884
|
db.close();
|
|
346
|
-
console.log('
|
|
347
|
-
console.log(
|
|
348
|
-
console.log(`
|
|
349
|
-
console.log(`
|
|
350
|
-
console.log(
|
|
885
|
+
console.log('');
|
|
886
|
+
console.log('\x1B[1m\x1B[32m ✔ Indexing complete!\x1B[0m');
|
|
887
|
+
console.log(` ├─ Files indexed : \x1B[33m${pad.files_done}\x1B[0m`);
|
|
888
|
+
console.log(` ├─ Nodes created : \x1B[33m${pad.nodes_created}\x1B[0m`);
|
|
889
|
+
console.log(` └─ Connections : \x1B[33m${pad.connections_created}\x1B[0m`);
|
|
890
|
+
console.log('');
|
|
351
891
|
}
|
|
352
892
|
//# sourceMappingURL=runner.js.map
|