@optave/codegraph 2.0.0 → 2.1.1-dev.00f091c

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/src/builder.js CHANGED
@@ -1,15 +1,48 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import path from 'node:path';
4
5
  import { loadConfig } from './config.js';
5
6
  import { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
6
7
  import { initSchema, openDb } from './db.js';
8
+ import { readJournal, writeJournalHeader } from './journal.js';
7
9
  import { debug, warn } from './logger.js';
8
10
  import { getActiveEngine, parseFilesAuto } from './parser.js';
9
11
  import { computeConfidence, resolveImportPath, resolveImportsBatch } from './resolve.js';
10
12
 
11
13
  export { resolveImportPath } from './resolve.js';
12
14
 
15
+ const BUILTIN_RECEIVERS = new Set([
16
+ 'console',
17
+ 'Math',
18
+ 'JSON',
19
+ 'Object',
20
+ 'Array',
21
+ 'String',
22
+ 'Number',
23
+ 'Boolean',
24
+ 'Date',
25
+ 'RegExp',
26
+ 'Map',
27
+ 'Set',
28
+ 'WeakMap',
29
+ 'WeakSet',
30
+ 'Promise',
31
+ 'Symbol',
32
+ 'Error',
33
+ 'TypeError',
34
+ 'RangeError',
35
+ 'Proxy',
36
+ 'Reflect',
37
+ 'Intl',
38
+ 'globalThis',
39
+ 'window',
40
+ 'document',
41
+ 'process',
42
+ 'Buffer',
43
+ 'require',
44
+ ]);
45
+
13
46
  export function collectFiles(dir, files = [], config = {}, directories = null) {
14
47
  const trackDirs = directories !== null;
15
48
  let entries;
@@ -80,8 +113,24 @@ function fileHash(content) {
80
113
  return createHash('md5').update(content).digest('hex');
81
114
  }
82
115
 
116
+ /**
117
+ * Stat a file, returning { mtimeMs, size } or null on error.
118
+ */
119
+ function fileStat(filePath) {
120
+ try {
121
+ const s = fs.statSync(filePath);
122
+ return { mtimeMs: s.mtimeMs, size: s.size };
123
+ } catch {
124
+ return null;
125
+ }
126
+ }
127
+
83
128
  /**
84
129
  * Determine which files have changed since last build.
130
+ * Three-tier cascade:
131
+ * Tier 0 — Journal: O(changed) when watcher was running
132
+ * Tier 1 — mtime+size: O(n) stats, O(changed) reads
133
+ * Tier 2 — Hash comparison: O(changed) reads (fallback from Tier 1)
85
134
  */
86
135
  function getChangedFiles(db, allFiles, rootDir) {
87
136
  // Check if file_hashes table exists
@@ -94,7 +143,6 @@ function getChangedFiles(db, allFiles, rootDir) {
94
143
  }
95
144
 
96
145
  if (!hasTable) {
97
- // No hash table = first build, everything is new
98
146
  return {
99
147
  changed: allFiles.map((f) => ({ file: f })),
100
148
  removed: [],
@@ -104,36 +152,140 @@ function getChangedFiles(db, allFiles, rootDir) {
104
152
 
105
153
  const existing = new Map(
106
154
  db
107
- .prepare('SELECT file, hash FROM file_hashes')
155
+ .prepare('SELECT file, hash, mtime, size FROM file_hashes')
108
156
  .all()
109
- .map((r) => [r.file, r.hash]),
157
+ .map((r) => [r.file, r]),
110
158
  );
111
159
 
112
- const changed = [];
160
+ // Build set of current files for removal detection
113
161
  const currentFiles = new Set();
162
+ for (const file of allFiles) {
163
+ currentFiles.add(normalizePath(path.relative(rootDir, file)));
164
+ }
165
+
166
+ const removed = [];
167
+ for (const existingFile of existing.keys()) {
168
+ if (!currentFiles.has(existingFile)) {
169
+ removed.push(existingFile);
170
+ }
171
+ }
172
+
173
+ // ── Tier 0: Journal ──────────────────────────────────────────────
174
+ const journal = readJournal(rootDir);
175
+ if (journal.valid) {
176
+ // Validate journal timestamp against DB — journal should be from after the last build
177
+ const dbMtimes = db.prepare('SELECT MAX(mtime) as latest FROM file_hashes').get();
178
+ const latestDbMtime = dbMtimes?.latest || 0;
179
+
180
+ // Empty journal = no watcher was running, fall to Tier 1 for safety
181
+ const hasJournalEntries = journal.changed.length > 0 || journal.removed.length > 0;
182
+
183
+ if (hasJournalEntries && journal.timestamp >= latestDbMtime) {
184
+ debug(
185
+ `Tier 0: journal valid, ${journal.changed.length} changed, ${journal.removed.length} removed`,
186
+ );
187
+ const changed = [];
188
+
189
+ for (const relPath of journal.changed) {
190
+ const absPath = path.join(rootDir, relPath);
191
+ const stat = fileStat(absPath);
192
+ if (!stat) continue;
193
+
194
+ let content;
195
+ try {
196
+ content = fs.readFileSync(absPath, 'utf-8');
197
+ } catch {
198
+ continue;
199
+ }
200
+ const hash = fileHash(content);
201
+ const record = existing.get(relPath);
202
+ if (!record || record.hash !== hash) {
203
+ changed.push({ file: absPath, content, hash, relPath, stat });
204
+ }
205
+ }
206
+
207
+ // Merge journal removals with filesystem removals (dedup)
208
+ const removedSet = new Set(removed);
209
+ for (const relPath of journal.removed) {
210
+ if (existing.has(relPath)) removedSet.add(relPath);
211
+ }
212
+
213
+ return { changed, removed: [...removedSet], isFullBuild: false };
214
+ }
215
+ debug(
216
+ `Tier 0: skipped (${hasJournalEntries ? 'timestamp stale' : 'no entries'}), falling to Tier 1`,
217
+ );
218
+ }
219
+
220
+ // ── Tier 1: mtime+size fast-path ─────────────────────────────────
221
+ const needsHash = []; // Files that failed mtime+size check
222
+ const skipped = []; // Files that passed mtime+size check
114
223
 
115
224
  for (const file of allFiles) {
116
225
  const relPath = normalizePath(path.relative(rootDir, file));
117
- currentFiles.add(relPath);
226
+ const record = existing.get(relPath);
227
+
228
+ if (!record) {
229
+ // New file — needs full read+hash
230
+ needsHash.push({ file, relPath });
231
+ continue;
232
+ }
233
+
234
+ const stat = fileStat(file);
235
+ if (!stat) continue;
236
+
237
+ const storedMtime = record.mtime || 0;
238
+ const storedSize = record.size || 0;
239
+
240
+ // size > 0 guard: pre-v4 rows have size=0, always fall through to hash
241
+ if (storedSize > 0 && Math.floor(stat.mtimeMs) === storedMtime && stat.size === storedSize) {
242
+ skipped.push(relPath);
243
+ continue;
244
+ }
245
+
246
+ needsHash.push({ file, relPath, stat });
247
+ }
248
+
249
+ if (needsHash.length > 0) {
250
+ debug(`Tier 1: ${skipped.length} skipped by mtime+size, ${needsHash.length} need hash check`);
251
+ }
252
+
253
+ // ── Tier 2: Hash comparison ──────────────────────────────────────
254
+ const changed = [];
118
255
 
256
+ for (const item of needsHash) {
119
257
  let content;
120
258
  try {
121
- content = fs.readFileSync(file, 'utf-8');
259
+ content = fs.readFileSync(item.file, 'utf-8');
122
260
  } catch {
123
261
  continue;
124
262
  }
125
263
  const hash = fileHash(content);
126
-
127
- if (existing.get(relPath) !== hash) {
128
- changed.push({ file, content, hash, relPath });
264
+ const stat = item.stat || fileStat(item.file);
265
+ const record = existing.get(item.relPath);
266
+
267
+ if (!record || record.hash !== hash) {
268
+ changed.push({ file: item.file, content, hash, relPath: item.relPath, stat });
269
+ } else if (stat) {
270
+ // Hash matches but mtime/size was stale — self-heal by updating stored metadata
271
+ changed.push({
272
+ file: item.file,
273
+ content,
274
+ hash,
275
+ relPath: item.relPath,
276
+ stat,
277
+ metadataOnly: true,
278
+ });
129
279
  }
130
280
  }
131
281
 
132
- const removed = [];
133
- for (const existingFile of existing.keys()) {
134
- if (!currentFiles.has(existingFile)) {
135
- removed.push(existingFile);
136
- }
282
+ // Filter out metadata-only updates from the "changed" list for parsing,
283
+ // but keep them so the caller can update file_hashes
284
+ const parseChanged = changed.filter((c) => !c.metadataOnly);
285
+ if (needsHash.length > 0) {
286
+ debug(
287
+ `Tier 2: ${parseChanged.length} actually changed, ${changed.length - parseChanged.length} metadata-only`,
288
+ );
137
289
  }
138
290
 
139
291
  return { changed, removed, isFullBuild: false };
@@ -179,9 +331,33 @@ export async function buildGraph(rootDir, opts = {}) {
179
331
  ? getChangedFiles(db, files, rootDir)
180
332
  : { changed: files.map((f) => ({ file: f })), removed: [], isFullBuild: true };
181
333
 
182
- if (!isFullBuild && changed.length === 0 && removed.length === 0) {
334
+ // Separate metadata-only updates (mtime/size self-heal) from real changes
335
+ const parseChanges = changed.filter((c) => !c.metadataOnly);
336
+ const metadataUpdates = changed.filter((c) => c.metadataOnly);
337
+
338
+ if (!isFullBuild && parseChanges.length === 0 && removed.length === 0) {
339
+ // Still update metadata for self-healing even when no real changes
340
+ if (metadataUpdates.length > 0) {
341
+ try {
342
+ const healHash = db.prepare(
343
+ 'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
344
+ );
345
+ const healTx = db.transaction(() => {
346
+ for (const item of metadataUpdates) {
347
+ const mtime = item.stat ? Math.floor(item.stat.mtimeMs) : 0;
348
+ const size = item.stat ? item.stat.size : 0;
349
+ healHash.run(item.relPath, item.hash, mtime, size);
350
+ }
351
+ });
352
+ healTx();
353
+ debug(`Self-healed mtime/size for ${metadataUpdates.length} files`);
354
+ } catch {
355
+ /* ignore heal errors */
356
+ }
357
+ }
183
358
  console.log('No changes detected. Graph is up to date.');
184
359
  db.close();
360
+ writeJournalHeader(rootDir, Date.now());
185
361
  return;
186
362
  }
187
363
 
@@ -190,7 +366,7 @@ export async function buildGraph(rootDir, opts = {}) {
190
366
  'PRAGMA foreign_keys = OFF; DELETE FROM node_metrics; DELETE FROM edges; DELETE FROM nodes; PRAGMA foreign_keys = ON;',
191
367
  );
192
368
  } else {
193
- console.log(`Incremental: ${changed.length} changed, ${removed.length} removed`);
369
+ console.log(`Incremental: ${parseChanges.length} changed, ${removed.length} removed`);
194
370
  // Remove metrics/edges/nodes for changed and removed files
195
371
  const deleteNodesForFile = db.prepare('DELETE FROM nodes WHERE file = ?');
196
372
  const deleteEdgesForFile = db.prepare(`
@@ -205,7 +381,7 @@ export async function buildGraph(rootDir, opts = {}) {
205
381
  deleteMetricsForFile.run(relPath);
206
382
  deleteNodesForFile.run(relPath);
207
383
  }
208
- for (const item of changed) {
384
+ for (const item of parseChanges) {
209
385
  const relPath = item.relPath || normalizePath(path.relative(rootDir, item.file));
210
386
  deleteEdgesForFile.run({ f: relPath });
211
387
  deleteMetricsForFile.run(relPath);
@@ -223,11 +399,11 @@ export async function buildGraph(rootDir, opts = {}) {
223
399
  'INSERT INTO edges (source_id, target_id, kind, confidence, dynamic) VALUES (?, ?, ?, ?, ?)',
224
400
  );
225
401
 
226
- // Prepare hash upsert
402
+ // Prepare hash upsert (with size column from migration v4)
227
403
  let upsertHash;
228
404
  try {
229
405
  upsertHash = db.prepare(
230
- 'INSERT OR REPLACE INTO file_hashes (file, hash, mtime) VALUES (?, ?, ?)',
406
+ 'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
231
407
  );
232
408
  } catch {
233
409
  upsertHash = null;
@@ -245,17 +421,17 @@ export async function buildGraph(rootDir, opts = {}) {
245
421
  // We'll fill these in during the parse pass + edge pass
246
422
  }
247
423
 
248
- const filesToParse = isFullBuild ? files.map((f) => ({ file: f })) : changed;
424
+ const filesToParse = isFullBuild ? files.map((f) => ({ file: f })) : parseChanges;
249
425
 
250
426
  // ── Unified parse via parseFilesAuto ───────────────────────────────
251
427
  const filePaths = filesToParse.map((item) => item.file);
252
428
  const allSymbols = await parseFilesAuto(filePaths, rootDir, engineOpts);
253
429
 
254
- // Build a hash lookup from incremental data (changed items may carry pre-computed hashes)
255
- const precomputedHashes = new Map();
430
+ // Build a lookup from incremental data (changed items may carry pre-computed hashes + stats)
431
+ const precomputedData = new Map();
256
432
  for (const item of filesToParse) {
257
- if (item.hash && item.relPath) {
258
- precomputedHashes.set(item.relPath, item.hash);
433
+ if (item.relPath) {
434
+ precomputedData.set(item.relPath, item);
259
435
  }
260
436
  }
261
437
 
@@ -271,11 +447,14 @@ export async function buildGraph(rootDir, opts = {}) {
271
447
  insertNode.run(exp.name, exp.kind, relPath, exp.line, null);
272
448
  }
273
449
 
274
- // Update file hash for incremental builds
450
+ // Update file hash with real mtime+size for incremental builds
275
451
  if (upsertHash) {
276
- const existingHash = precomputedHashes.get(relPath);
277
- if (existingHash) {
278
- upsertHash.run(relPath, existingHash, Date.now());
452
+ const precomputed = precomputedData.get(relPath);
453
+ if (precomputed?.hash) {
454
+ const stat = precomputed.stat || fileStat(path.join(rootDir, relPath));
455
+ const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
456
+ const size = stat ? stat.size : 0;
457
+ upsertHash.run(relPath, precomputed.hash, mtime, size);
279
458
  } else {
280
459
  const absPath = path.join(rootDir, relPath);
281
460
  let code;
@@ -285,11 +464,23 @@ export async function buildGraph(rootDir, opts = {}) {
285
464
  code = null;
286
465
  }
287
466
  if (code !== null) {
288
- upsertHash.run(relPath, fileHash(code), Date.now());
467
+ const stat = fileStat(absPath);
468
+ const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
469
+ const size = stat ? stat.size : 0;
470
+ upsertHash.run(relPath, fileHash(code), mtime, size);
289
471
  }
290
472
  }
291
473
  }
292
474
  }
475
+
476
+ // Also update metadata-only entries (self-heal mtime/size without re-parse)
477
+ if (upsertHash) {
478
+ for (const item of metadataUpdates) {
479
+ const mtime = item.stat ? Math.floor(item.stat.mtimeMs) : 0;
480
+ const size = item.stat ? item.stat.size : 0;
481
+ upsertHash.run(item.relPath, item.hash, mtime, size);
482
+ }
483
+ }
293
484
  });
294
485
  insertAll();
295
486
 
@@ -457,7 +648,9 @@ export async function buildGraph(rootDir, opts = {}) {
457
648
  }
458
649
 
459
650
  // Call edges with confidence scoring — using pre-loaded lookup maps (N+1 fix)
651
+ const seenCallEdges = new Set();
460
652
  for (const call of symbols.calls) {
653
+ if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
461
654
  let caller = null;
462
655
  for (const def of symbols.definitions) {
463
656
  if (def.line <= call.line) {
@@ -492,10 +685,18 @@ export async function buildGraph(rootDir, opts = {}) {
492
685
  );
493
686
  if (methodCandidates.length > 0) {
494
687
  targets = methodCandidates;
495
- } else {
496
- // Global fallback
497
- targets = nodesByName.get(call.name) || [];
688
+ } else if (
689
+ !call.receiver ||
690
+ call.receiver === 'this' ||
691
+ call.receiver === 'self' ||
692
+ call.receiver === 'super'
693
+ ) {
694
+ // Scoped fallback — same-dir or parent-dir only, not global
695
+ targets = (nodesByName.get(call.name) || []).filter(
696
+ (n) => computeConfidence(relPath, n.file, null) >= 0.5,
697
+ );
498
698
  }
699
+ // else: method call on a receiver — skip global fallback entirely
499
700
  }
500
701
  }
501
702
 
@@ -508,7 +709,9 @@ export async function buildGraph(rootDir, opts = {}) {
508
709
  }
509
710
 
510
711
  for (const t of targets) {
511
- if (t.id !== caller.id) {
712
+ const edgeKey = `${caller.id}|${t.id}`;
713
+ if (t.id !== caller.id && !seenCallEdges.has(edgeKey)) {
714
+ seenCallEdges.add(edgeKey);
512
715
  const confidence = computeConfidence(relPath, t.file, importedFrom);
513
716
  insertEdge.run(caller.id, t.id, 'calls', confidence, isDynamic);
514
717
  edgeCount++;
@@ -581,10 +784,21 @@ export async function buildGraph(rootDir, opts = {}) {
581
784
  console.log(`Stored in ${dbPath}`);
582
785
  db.close();
583
786
 
584
- try {
585
- const { registerRepo } = await import('./registry.js');
586
- registerRepo(rootDir);
587
- } catch (err) {
588
- debug(`Auto-registration failed: ${err.message}`);
787
+ // Write journal header after successful build
788
+ writeJournalHeader(rootDir, Date.now());
789
+
790
+ if (!opts.skipRegistry) {
791
+ const tmpDir = path.resolve(os.tmpdir());
792
+ const resolvedRoot = path.resolve(rootDir);
793
+ if (resolvedRoot.startsWith(tmpDir)) {
794
+ debug(`Skipping auto-registration for temp directory: ${resolvedRoot}`);
795
+ } else {
796
+ try {
797
+ const { registerRepo } = await import('./registry.js');
798
+ registerRepo(rootDir);
799
+ } catch (err) {
800
+ debug(`Auto-registration failed: ${err.message}`);
801
+ }
802
+ }
589
803
  }
590
804
  }
package/src/cli.js CHANGED
@@ -11,6 +11,7 @@ import { buildEmbeddings, MODELS, search } from './embedder.js';
11
11
  import { exportDOT, exportJSON, exportMermaid } from './export.js';
12
12
  import { setVerbose } from './logger.js';
13
13
  import {
14
+ context,
14
15
  diffImpact,
15
16
  fileDeps,
16
17
  fnDeps,
@@ -18,6 +19,7 @@ import {
18
19
  impactAnalysis,
19
20
  moduleMap,
20
21
  queryName,
22
+ stats,
21
23
  } from './queries.js';
22
24
  import {
23
25
  listRepos,
@@ -28,11 +30,14 @@ import {
28
30
  } from './registry.js';
29
31
  import { watchProject } from './watcher.js';
30
32
 
33
+ const __cliDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1'));
34
+ const pkg = JSON.parse(fs.readFileSync(path.join(__cliDir, '..', 'package.json'), 'utf-8'));
35
+
31
36
  const program = new Command();
32
37
  program
33
38
  .name('codegraph')
34
39
  .description('Local code dependency graph tool')
35
- .version('1.3.0')
40
+ .version(pkg.version)
36
41
  .option('-v, --verbose', 'Enable verbose/debug output')
37
42
  .option('--engine <engine>', 'Parser engine: native, wasm, or auto (default: auto)', 'auto')
38
43
  .hook('preAction', (thisCommand) => {
@@ -78,6 +83,15 @@ program
78
83
  moduleMap(opts.db, parseInt(opts.limit, 10), { json: opts.json });
79
84
  });
80
85
 
86
+ program
87
+ .command('stats')
88
+ .description('Show graph health overview: nodes, edges, languages, cycles, hotspots, embeddings')
89
+ .option('-d, --db <path>', 'Path to graph.db')
90
+ .option('-j, --json', 'Output as JSON')
91
+ .action((opts) => {
92
+ stats(opts.db, { json: opts.json });
93
+ });
94
+
81
95
  program
82
96
  .command('deps <file>')
83
97
  .description('Show what this file imports and what imports it')
@@ -117,6 +131,25 @@ program
117
131
  });
118
132
  });
119
133
 
134
+ program
135
+ .command('context <name>')
136
+ .description('Full context for a function: source, deps, callers, tests, signature')
137
+ .option('-d, --db <path>', 'Path to graph.db')
138
+ .option('--depth <n>', 'Include callee source up to N levels deep', '0')
139
+ .option('--no-source', 'Metadata only (skip source extraction)')
140
+ .option('--include-tests', 'Include test source code')
141
+ .option('-T, --no-tests', 'Exclude test files from callers')
142
+ .option('-j, --json', 'Output as JSON')
143
+ .action((name, opts) => {
144
+ context(name, opts.db, {
145
+ depth: parseInt(opts.depth, 10),
146
+ noSource: !opts.source,
147
+ noTests: !opts.tests,
148
+ includeTests: opts.includeTests,
149
+ json: opts.json,
150
+ });
151
+ });
152
+
120
153
  program
121
154
  .command('diff-impact [ref]')
122
155
  .description('Show impact of git changes (unstaged, staged, or vs a ref)')
@@ -214,6 +247,7 @@ registry
214
247
  .description('List all registered repositories')
215
248
  .option('-j, --json', 'Output as JSON')
216
249
  .action((opts) => {
250
+ pruneRegistry();
217
251
  const repos = listRepos();
218
252
  if (opts.json) {
219
253
  console.log(JSON.stringify(repos, null, 2));
@@ -257,14 +291,16 @@ registry
257
291
 
258
292
  registry
259
293
  .command('prune')
260
- .description('Remove registry entries whose directories no longer exist')
261
- .action(() => {
262
- const pruned = pruneRegistry();
294
+ .description('Remove stale registry entries (missing directories or idle beyond TTL)')
295
+ .option('--ttl <days>', 'Days of inactivity before pruning (default: 30)', '30')
296
+ .action((opts) => {
297
+ const pruned = pruneRegistry(undefined, parseInt(opts.ttl, 10));
263
298
  if (pruned.length === 0) {
264
299
  console.log('No stale entries found.');
265
300
  } else {
266
301
  for (const entry of pruned) {
267
- console.log(`Pruned "${entry.name}" (${entry.path})`);
302
+ const tag = entry.reason === 'expired' ? 'expired' : 'missing';
303
+ console.log(`Pruned "${entry.name}" (${entry.path}) [${tag}]`);
268
304
  }
269
305
  console.log(`\nRemoved ${pruned.length} stale ${pruned.length === 1 ? 'entry' : 'entries'}.`);
270
306
  }
@@ -278,7 +314,7 @@ program
278
314
  .action(() => {
279
315
  console.log('\nAvailable embedding models:\n');
280
316
  for (const [key, config] of Object.entries(MODELS)) {
281
- const def = key === 'minilm' ? ' (default)' : '';
317
+ const def = key === 'jina-code' ? ' (default)' : '';
282
318
  console.log(` ${key.padEnd(12)} ${String(config.dim).padStart(4)}d ${config.desc}${def}`);
283
319
  }
284
320
  console.log('\nUsage: codegraph embed --model <name>');
@@ -292,8 +328,8 @@ program
292
328
  )
293
329
  .option(
294
330
  '-m, --model <name>',
295
- 'Embedding model: minilm (default), jina-small, jina-base, jina-code, nomic, nomic-v1.5, bge-large. Run `codegraph models` for details',
296
- 'minilm',
331
+ 'Embedding model: minilm, jina-small, jina-base, jina-code (default), nomic, nomic-v1.5, bge-large. Run `codegraph models` for details',
332
+ 'jina-code',
297
333
  )
298
334
  .action(async (dir, opts) => {
299
335
  const root = path.resolve(dir || '.');
package/src/config.js CHANGED
@@ -19,7 +19,7 @@ export const DEFAULTS = {
19
19
  defaultDepth: 3,
20
20
  defaultLimit: 20,
21
21
  },
22
- embeddings: { model: 'minilm', llmProvider: null },
22
+ embeddings: { model: 'jina-code', llmProvider: null },
23
23
  llm: { provider: null, model: null, baseUrl: null, apiKey: null, apiKeyCommand: null },
24
24
  search: { defaultMinScore: 0.2, rrfK: 60, topK: 15 },
25
25
  ci: { failOnCycles: false, impactThreshold: null },
package/src/db.js CHANGED
@@ -67,6 +67,10 @@ export const MIGRATIONS = [
67
67
  );
68
68
  `,
69
69
  },
70
+ {
71
+ version: 4,
72
+ up: `ALTER TABLE file_hashes ADD COLUMN size INTEGER DEFAULT 0;`,
73
+ },
70
74
  ];
71
75
 
72
76
  export function openDb(dbPath) {
package/src/embedder.js CHANGED
@@ -55,7 +55,7 @@ export const MODELS = {
55
55
  },
56
56
  };
57
57
 
58
- export const DEFAULT_MODEL = 'minilm';
58
+ export const DEFAULT_MODEL = 'jina-code';
59
59
  const BATCH_SIZE_MAP = {
60
60
  minilm: 32,
61
61
  'jina-small': 16,
@@ -173,10 +173,10 @@ function initEmbeddingsSchema(db) {
173
173
  /**
174
174
  * Build embeddings for all functions/methods/classes in the graph.
175
175
  */
176
- export async function buildEmbeddings(rootDir, modelKey) {
176
+ export async function buildEmbeddings(rootDir, modelKey, customDbPath) {
177
177
  // path already imported at top
178
178
  // fs already imported at top
179
- const dbPath = findDbPath(null);
179
+ const dbPath = customDbPath || findDbPath(null);
180
180
 
181
181
  const db = new Database(dbPath);
182
182
  initEmbeddingsSchema(db);