grepmax 0.15.5 → 0.16.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.
@@ -53,7 +53,6 @@ exports.mcp = void 0;
53
53
  exports.toStringArray = toStringArray;
54
54
  exports.ok = ok;
55
55
  exports.err = err;
56
- const node_child_process_1 = require("node:child_process");
57
56
  const fs = __importStar(require("node:fs"));
58
57
  const path = __importStar(require("node:path"));
59
58
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
@@ -363,8 +362,6 @@ exports.mcp = new commander_1.Command("mcp")
363
362
  let _searcher = null;
364
363
  let _skeletonizer = null;
365
364
  let _indexReady = false;
366
- let _indexing = false;
367
- let _indexProgress = "";
368
365
  const cleanup = () => __awaiter(void 0, void 0, void 0, function* () {
369
366
  if (_vectorDb) {
370
367
  try {
@@ -421,70 +418,15 @@ exports.mcp = new commander_1.Command("mcp")
421
418
  });
422
419
  }
423
420
  // --- Index sync ---
424
- let _indexChildPid = null;
425
- function isIndexProcessRunning() {
426
- if (!_indexChildPid)
427
- return false;
428
- try {
429
- process.kill(_indexChildPid, 0);
430
- return true;
431
- }
432
- catch (_a) {
433
- return false;
434
- }
435
- }
436
421
  function ensureIndexReady() {
437
422
  return __awaiter(this, void 0, void 0, function* () {
438
- var _a;
439
423
  if (_indexReady)
440
424
  return;
441
- // Check if a previously spawned index process finished
442
- if (_indexing && !isIndexProcessRunning()) {
443
- _indexing = false;
444
- _indexProgress = "";
445
- _indexChildPid = null;
446
- }
447
- // Check project registry — more reliable than querying the DB.
448
- // Avoids false negatives from lock contention and cascade re-indexing.
449
425
  const projects = (0, project_registry_1.listProjects)();
450
426
  const isRegistered = projects.some((p) => p.root === projectRoot);
451
427
  if (isRegistered) {
452
428
  _indexReady = true;
453
- return;
454
429
  }
455
- // Truly first-time: no registry entry at all
456
- if (_indexing)
457
- return;
458
- _indexing = true;
459
- _indexProgress = "starting...";
460
- console.log("[MCP] First-time setup for this project...");
461
- const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "add", projectRoot], { detached: true, stdio: "ignore" });
462
- _indexChildPid = (_a = child.pid) !== null && _a !== void 0 ? _a : null;
463
- child.unref();
464
- _indexProgress = `PID ${_indexChildPid}`;
465
- const indexTimeout = setTimeout(() => {
466
- try {
467
- child.kill("SIGKILL");
468
- }
469
- catch (_a) { }
470
- _indexing = false;
471
- _indexProgress = "";
472
- _indexChildPid = null;
473
- console.error("[MCP] Background indexing timed out after 30 minutes");
474
- }, 30 * 60 * 1000);
475
- child.on("exit", (code) => {
476
- clearTimeout(indexTimeout);
477
- _indexing = false;
478
- _indexProgress = "";
479
- _indexChildPid = null;
480
- if (code === 0) {
481
- _indexReady = true;
482
- console.log("[MCP] First-time setup complete.");
483
- }
484
- else {
485
- console.error(`[MCP] Indexing failed (exit code: ${code})`);
486
- }
487
- });
488
430
  });
489
431
  }
490
432
  // --- Background watcher ---
@@ -511,12 +453,11 @@ exports.mcp = new commander_1.Command("mcp")
511
453
  const searchAll = isSearchAll || args.scope === "all";
512
454
  const limit = Math.min(Math.max(Number(args.limit) || 3, 1), 50);
513
455
  ensureWatcher();
514
- if (_indexing) {
515
- return ok(`Indexing in progress (${_indexProgress}). Results may be incomplete or empty — try again shortly.`);
516
- }
517
- // Check if project is pending or has no chunks
518
456
  const proj = (0, project_registry_1.getProject)(projectRoot);
519
- if ((proj === null || proj === void 0 ? void 0 : proj.status) === "pending" || (proj && proj.chunkCount === 0)) {
457
+ if (!proj) {
458
+ return err("Project not added to gmax yet. Run `gmax add` to index it first.");
459
+ }
460
+ if (proj.status === "pending" || proj.chunkCount === 0) {
520
461
  return err("Project not indexed yet. Run `gmax add` to index it first.");
521
462
  }
522
463
  try {
@@ -875,8 +816,9 @@ exports.mcp = new commander_1.Command("mcp")
875
816
  const symbol = String(args.symbol || "");
876
817
  if (!symbol)
877
818
  return err("Missing required parameter: symbol");
878
- if (_indexing) {
879
- return ok(`Indexing in progress (${_indexProgress}). trace_calls requires a complete index — try again shortly.`);
819
+ const proj = (0, project_registry_1.getProject)(projectRoot);
820
+ if (!proj) {
821
+ return err("Project not added to gmax yet. Run `gmax add` to index it first.");
880
822
  }
881
823
  try {
882
824
  const db = getVectorDb();
@@ -1177,8 +1119,9 @@ exports.mcp = new commander_1.Command("mcp")
1177
1119
  const pattern = typeof args.pattern === "string" ? args.pattern : undefined;
1178
1120
  const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 100);
1179
1121
  const pathPrefix = typeof args.path === "string" ? args.path : undefined;
1180
- if (_indexing) {
1181
- return ok(`Indexing in progress (${_indexProgress}). list_symbols requires a complete index — try again shortly.`);
1122
+ const proj = (0, project_registry_1.getProject)(projectRoot);
1123
+ if (!proj) {
1124
+ return err("Project not added to gmax yet. Run `gmax add` to index it first.");
1182
1125
  }
1183
1126
  try {
1184
1127
  const db = getVectorDb();
@@ -1273,9 +1216,6 @@ exports.mcp = new commander_1.Command("mcp")
1273
1216
  watcherLine += " — search results may be incomplete";
1274
1217
  }
1275
1218
  }
1276
- const indexingLine = _indexing
1277
- ? `Indexing: in progress (${_indexProgress})`
1278
- : "";
1279
1219
  const lines = [
1280
1220
  `Index: ~/.gmax/lancedb (${stats.chunks} chunks, ${fileCount} files)`,
1281
1221
  `Model: ${globalConfig.embedMode === "gpu" ? ((_d = (_c = (_b = config_1.MODEL_TIERS[globalConfig.modelTier]) === null || _b === void 0 ? void 0 : _b.mlxModel) !== null && _c !== void 0 ? _c : config === null || config === void 0 ? void 0 : config.embedModel) !== null && _d !== void 0 ? _d : "unknown") : ((_e = config === null || config === void 0 ? void 0 : config.embedModel) !== null && _e !== void 0 ? _e : "unknown")} (${(_f = config === null || config === void 0 ? void 0 : config.vectorDim) !== null && _f !== void 0 ? _f : "?"}d, ${globalConfig.embedMode})`,
@@ -1283,7 +1223,6 @@ exports.mcp = new commander_1.Command("mcp")
1283
1223
  ? `Last indexed: ${config.indexedAt}`
1284
1224
  : "",
1285
1225
  watcherLine,
1286
- indexingLine,
1287
1226
  "",
1288
1227
  "Indexed directories:",
1289
1228
  ...(yield Promise.all(projects.map((p) => __awaiter(this, void 0, void 0, function* () {
@@ -256,9 +256,9 @@ function formatCompactTable(hits, projectRoot, query, opts) {
256
256
  }
257
257
  // Reuse Skeletonizer instance
258
258
  let globalSkeletonizer = null;
259
- function outputSkeletons(results, projectRoot, limit, db) {
259
+ function outputSkeletons(results, projectRoot, limit, db, precomputed) {
260
260
  return __awaiter(this, void 0, void 0, function* () {
261
- var _a;
261
+ var _a, _b;
262
262
  const seenPaths = new Set();
263
263
  const filesToProcess = [];
264
264
  for (const result of results) {
@@ -288,6 +288,16 @@ function outputSkeletons(results, projectRoot, limit, db) {
288
288
  const absPath = path.isAbsolute(filePath)
289
289
  ? filePath
290
290
  : path.resolve(projectRoot, filePath);
291
+ // 0. Daemon-supplied (preferred — already-warm DB lookup, no cold open)
292
+ const fromDaemon = (_b = precomputed === null || precomputed === void 0 ? void 0 : precomputed[absPath]) !== null && _b !== void 0 ? _b : precomputed === null || precomputed === void 0 ? void 0 : precomputed[filePath];
293
+ if (fromDaemon) {
294
+ skeletonResults.push({
295
+ file: filePath,
296
+ skeleton: fromDaemon,
297
+ tokens: Math.ceil(fromDaemon.length / 4),
298
+ });
299
+ continue;
300
+ }
291
301
  // 1. Try DB cache
292
302
  if (db) {
293
303
  const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
@@ -378,7 +388,7 @@ Examples:
378
388
  gmax "handler" --name "handle.*" --exclude tests/
379
389
  `)
380
390
  .action((pattern, exec_path, _options, cmd) => __awaiter(void 0, void 0, void 0, function* () {
381
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9;
391
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10;
382
392
  const options = cmd.optsWithGlobals();
383
393
  const root = process.cwd();
384
394
  const minScore = Number.isFinite(Number.parseFloat(options.minScore))
@@ -498,79 +508,10 @@ Examples:
498
508
  if (project.status === "pending") {
499
509
  console.warn("This project is still being indexed. Results may be incomplete.\n");
500
510
  }
501
- vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
502
- // Check for active indexing lock and warn if present
503
- if (!options.agent && (0, lock_1.isLocked)(paths.dataDir)) {
504
- console.warn("⚠️ Warning: Indexing in progress... search results may be incomplete.");
505
- }
506
- const hasRows = yield vectorDb.hasAnyRows();
507
- const needsSync = options.sync || !hasRows;
508
- if (needsSync) {
509
- const isTTY = process.stdout.isTTY;
510
- let abortController;
511
- let signal;
512
- if (!isTTY) {
513
- abortController = new AbortController();
514
- signal = abortController.signal;
515
- setTimeout(() => {
516
- abortController === null || abortController === void 0 ? void 0 : abortController.abort();
517
- }, 60000); // 60 seconds timeout for non-TTY auto-indexing
518
- }
519
- const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, options.sync ? "Indexing..." : "Indexing repository (first run)...");
520
- try {
521
- yield (0, grammar_loader_1.ensureGrammars)(console.log, { silent: true });
522
- const result = yield (0, syncer_1.initialSync)({
523
- projectRoot,
524
- dryRun: options.dryRun,
525
- onProgress,
526
- signal,
527
- });
528
- if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
529
- spinner.warn(`Indexing timed out (${result.processed}/${result.total}). Results may be partial.`);
530
- }
531
- if (options.dryRun) {
532
- spinner.succeed(`Dry run complete (${result.processed}/${result.total}) • would have indexed ${result.indexed}`);
533
- console.log((0, sync_helpers_1.formatDryRunSummary)(result, {
534
- actionDescription: "would have indexed",
535
- includeTotal: true,
536
- }));
537
- return;
538
- }
539
- yield vectorDb.createFTSIndex();
540
- // Update registry after sync
541
- const { readGlobalConfig } = yield Promise.resolve().then(() => __importStar(require("../lib/index/index-config")));
542
- const { registerProject } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/project-registry")));
543
- const gc = readGlobalConfig();
544
- registerProject({
545
- root: projectRoot,
546
- name: path.basename(projectRoot),
547
- vectorDim: gc.vectorDim,
548
- modelTier: gc.modelTier,
549
- embedMode: gc.embedMode,
550
- lastIndexed: new Date().toISOString(),
551
- chunkCount: result.indexed,
552
- status: "indexed",
553
- });
554
- const failedSuffix = result.failedFiles > 0 ? ` • ${result.failedFiles} failed` : "";
555
- spinner.succeed(`${options.sync ? "Indexing" : "Initial indexing"} complete (${result.processed}/${result.total}) • indexed ${result.indexed}${failedSuffix}`);
556
- }
557
- catch (e) {
558
- spinner.fail("Indexing failed");
559
- throw e;
560
- }
561
- }
562
- // Ensure a watcher is running for live reindexing
563
- if (!process.env.VITEST && !((_d = process.env.NODE_ENV) === null || _d === void 0 ? void 0 : _d.includes("test"))) {
564
- const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
565
- const launched = yield launchWatcher(projectRoot);
566
- if (!launched.ok && launched.reason === "spawn-failed") {
567
- console.warn(`[search] ${launched.message}`);
568
- }
569
- }
570
- const searcher = new searcher_1.Searcher(vectorDb);
571
- // Use --root or fall back to project root
511
+ // Compute effective paths + filters early — both the daemon-mediated
512
+ // and in-process search paths need them.
572
513
  const effectiveRoot = options.root
573
- ? (_e = (0, project_root_1.findProjectRoot)(path.resolve(options.root))) !== null && _e !== void 0 ? _e : path.resolve(options.root)
514
+ ? (_d = (0, project_root_1.findProjectRoot)(path.resolve(options.root))) !== null && _d !== void 0 ? _d : path.resolve(options.root)
574
515
  : projectRoot;
575
516
  const searchPathPrefix = exec_path
576
517
  ? path.resolve(exec_path)
@@ -578,7 +519,6 @@ Examples:
578
519
  const pathFilter = searchPathPrefix.endsWith("/")
579
520
  ? searchPathPrefix
580
521
  : `${searchPathPrefix}/`;
581
- // Build filters from CLI options
582
522
  const searchFilters = {};
583
523
  if (options.file)
584
524
  searchFilters.file = options.file;
@@ -588,8 +528,126 @@ Examples:
588
528
  searchFilters.language = options.lang;
589
529
  if (options.role)
590
530
  searchFilters.role = options.role;
591
- const searchResult = yield searcher.search(pattern, parseInt(options.m, 10), { rerank: true, explain: options.explain }, Object.keys(searchFilters).length > 0 ? searchFilters : undefined, pathFilter);
592
- if (!options.agent && ((_f = searchResult.warnings) === null || _f === void 0 ? void 0 : _f.length)) {
531
+ // Daemon-mediated search: ships query+args over IPC, daemon runs the
532
+ // hybrid+rerank against its already-warm VectorDB and worker pool.
533
+ // Drops cold-start cost (~17s wall, 6GB RAM in the CLI) to <1s. Falls
534
+ // back to in-process on any failure.
535
+ let searchResult = null;
536
+ let precomputedSkeletons;
537
+ let precomputedGraph;
538
+ if (!options.sync && !options.dryRun) {
539
+ try {
540
+ const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
541
+ if (yield isDaemonRunning()) {
542
+ const resp = yield sendDaemonCommand({
543
+ cmd: "search",
544
+ projectRoot: effectiveRoot,
545
+ query: pattern,
546
+ limit: parseInt(options.m, 10),
547
+ filters: Object.keys(searchFilters).length > 0
548
+ ? searchFilters
549
+ : undefined,
550
+ pathPrefix: pathFilter,
551
+ rerank: true,
552
+ explain: options.explain,
553
+ includeSkeletons: options.skeleton,
554
+ includeGraph: options.symbol,
555
+ }, { timeoutMs: 60000 });
556
+ if (resp.ok) {
557
+ searchResult = {
558
+ data: resp.data,
559
+ warnings: resp.warnings,
560
+ };
561
+ precomputedSkeletons = resp.skeletons;
562
+ precomputedGraph = resp.graph;
563
+ }
564
+ else if (process.env.GMAX_DEBUG === "1") {
565
+ console.error(`[search] daemon path unavailable: ${(_e = resp.error) !== null && _e !== void 0 ? _e : "unknown"}`);
566
+ }
567
+ }
568
+ }
569
+ catch (err) {
570
+ if (process.env.GMAX_DEBUG === "1") {
571
+ console.error("[search] daemon attempt threw:", err);
572
+ }
573
+ }
574
+ }
575
+ // In-process fallback: open VectorDB, ensure index, run Searcher.
576
+ // Only entered when the daemon path didn't produce results.
577
+ if (!searchResult) {
578
+ vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
579
+ // Check for active indexing lock and warn if present
580
+ if (!options.agent && (0, lock_1.isLocked)(paths.dataDir)) {
581
+ console.warn("⚠️ Warning: Indexing in progress... search results may be incomplete.");
582
+ }
583
+ const hasRows = yield vectorDb.hasAnyRows();
584
+ const needsSync = options.sync || !hasRows;
585
+ if (needsSync) {
586
+ const isTTY = process.stdout.isTTY;
587
+ let abortController;
588
+ let signal;
589
+ if (!isTTY) {
590
+ abortController = new AbortController();
591
+ signal = abortController.signal;
592
+ setTimeout(() => {
593
+ abortController === null || abortController === void 0 ? void 0 : abortController.abort();
594
+ }, 60000); // 60 seconds timeout for non-TTY auto-indexing
595
+ }
596
+ const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, options.sync ? "Indexing..." : "Indexing repository (first run)...");
597
+ try {
598
+ yield (0, grammar_loader_1.ensureGrammars)(console.log, { silent: true });
599
+ const result = yield (0, syncer_1.initialSync)({
600
+ projectRoot,
601
+ dryRun: options.dryRun,
602
+ onProgress,
603
+ signal,
604
+ });
605
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
606
+ spinner.warn(`Indexing timed out (${result.processed}/${result.total}). Results may be partial.`);
607
+ }
608
+ if (options.dryRun) {
609
+ spinner.succeed(`Dry run complete (${result.processed}/${result.total}) • would have indexed ${result.indexed}`);
610
+ console.log((0, sync_helpers_1.formatDryRunSummary)(result, {
611
+ actionDescription: "would have indexed",
612
+ includeTotal: true,
613
+ }));
614
+ return;
615
+ }
616
+ yield vectorDb.createFTSIndex();
617
+ // Update registry after sync
618
+ const { readGlobalConfig } = yield Promise.resolve().then(() => __importStar(require("../lib/index/index-config")));
619
+ const { registerProject } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/project-registry")));
620
+ const gc = readGlobalConfig();
621
+ registerProject({
622
+ root: projectRoot,
623
+ name: path.basename(projectRoot),
624
+ vectorDim: gc.vectorDim,
625
+ modelTier: gc.modelTier,
626
+ embedMode: gc.embedMode,
627
+ lastIndexed: new Date().toISOString(),
628
+ chunkCount: result.indexed,
629
+ status: "indexed",
630
+ });
631
+ const failedSuffix = result.failedFiles > 0 ? ` • ${result.failedFiles} failed` : "";
632
+ spinner.succeed(`${options.sync ? "Indexing" : "Initial indexing"} complete (${result.processed}/${result.total}) • indexed ${result.indexed}${failedSuffix}`);
633
+ }
634
+ catch (e) {
635
+ spinner.fail("Indexing failed");
636
+ throw e;
637
+ }
638
+ }
639
+ // Ensure a watcher is running for live reindexing
640
+ if (!process.env.VITEST && !((_f = process.env.NODE_ENV) === null || _f === void 0 ? void 0 : _f.includes("test"))) {
641
+ const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
642
+ const launched = yield launchWatcher(projectRoot);
643
+ if (!launched.ok && launched.reason === "spawn-failed") {
644
+ console.warn(`[search] ${launched.message}`);
645
+ }
646
+ }
647
+ const searcher = new searcher_1.Searcher(vectorDb);
648
+ searchResult = yield searcher.search(pattern, parseInt(options.m, 10), { rerank: true, explain: options.explain }, Object.keys(searchFilters).length > 0 ? searchFilters : undefined, pathFilter);
649
+ } // end if (!searchResult) — in-process fallback
650
+ if (!options.agent && ((_g = searchResult.warnings) === null || _g === void 0 ? void 0 : _g.length)) {
593
651
  for (const w of searchResult.warnings) {
594
652
  console.warn(`Warning: ${w}`);
595
653
  }
@@ -606,7 +664,7 @@ Examples:
606
664
  return defs.some((d) => regex.test(d));
607
665
  });
608
666
  }
609
- catch (_10) {
667
+ catch (_11) {
610
668
  // Invalid regex — skip
611
669
  }
612
670
  }
@@ -632,16 +690,16 @@ Examples:
632
690
  // In agent mode, print imports header per file
633
691
  const seenImportFiles = new Set();
634
692
  for (const r of filteredData) {
635
- const absP = (_j = (_g = r.path) !== null && _g !== void 0 ? _g : (_h = r.metadata) === null || _h === void 0 ? void 0 : _h.path) !== null && _j !== void 0 ? _j : "";
693
+ const absP = (_k = (_h = r.path) !== null && _h !== void 0 ? _h : (_j = r.metadata) === null || _j === void 0 ? void 0 : _j.path) !== null && _k !== void 0 ? _k : "";
636
694
  const relPath = absP.startsWith(effectiveRoot)
637
695
  ? absP.slice(effectiveRoot.length + 1)
638
696
  : absP;
639
- const startLine = Math.max(1, ((_o = (_l = (_k = r.startLine) !== null && _k !== void 0 ? _k : r.start_line) !== null && _l !== void 0 ? _l : (_m = r.generated_metadata) === null || _m === void 0 ? void 0 : _m.start_line) !== null && _o !== void 0 ? _o : 0) + 1);
697
+ const startLine = Math.max(1, ((_p = (_m = (_l = r.startLine) !== null && _l !== void 0 ? _l : r.start_line) !== null && _m !== void 0 ? _m : (_o = r.generated_metadata) === null || _o === void 0 ? void 0 : _o.start_line) !== null && _p !== void 0 ? _p : 0) + 1);
640
698
  const defs = Array.isArray(r.defined_symbols)
641
699
  ? r.defined_symbols
642
700
  : [];
643
701
  const symbol = defs[0] || "";
644
- const role = ((_p = r.role) !== null && _p !== void 0 ? _p : "")
702
+ const role = ((_q = r.role) !== null && _q !== void 0 ? _q : "")
645
703
  .slice(0, 4)
646
704
  .toUpperCase();
647
705
  let hint = "";
@@ -650,7 +708,7 @@ Examples:
650
708
  }
651
709
  else {
652
710
  // Extract first meaningful signature line from content
653
- const raw = (_r = (_q = r.content) !== null && _q !== void 0 ? _q : r.text) !== null && _r !== void 0 ? _r : "";
711
+ const raw = (_s = (_r = r.content) !== null && _r !== void 0 ? _r : r.text) !== null && _s !== void 0 ? _s : "";
654
712
  const lines = raw.split("\n");
655
713
  for (const line of lines) {
656
714
  const trimmed = line.trim();
@@ -692,12 +750,17 @@ Examples:
692
750
  }
693
751
  }
694
752
  // Agent trace (compact)
695
- if (options.symbol && vectorDb && filteredData.length > 0) {
753
+ if (options.symbol && filteredData.length > 0) {
696
754
  try {
697
- const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
698
- const builder = new GraphBuilder(vectorDb, effectiveRoot);
699
- const graph = yield builder.buildGraphMultiHop(pattern, 1);
700
- if (graph.center) {
755
+ let graph = precomputedGraph;
756
+ if (!graph) {
757
+ if (!vectorDb)
758
+ throw new Error("no graph source");
759
+ const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
760
+ const builder = new GraphBuilder(vectorDb, effectiveRoot);
761
+ graph = yield builder.buildGraphMultiHop(pattern, 1);
762
+ }
763
+ if (graph === null || graph === void 0 ? void 0 : graph.center) {
701
764
  console.log("---");
702
765
  for (const t of graph.callerTree) {
703
766
  const rel = t.node.file.startsWith(effectiveRoot)
@@ -715,12 +778,12 @@ Examples:
715
778
  }
716
779
  }
717
780
  }
718
- catch (_11) { }
781
+ catch (_12) { }
719
782
  }
720
783
  return;
721
784
  }
722
785
  if (options.skeleton) {
723
- yield outputSkeletons(filteredData, projectRoot, parseInt(options.m, 10), vectorDb);
786
+ yield outputSkeletons(filteredData, projectRoot, parseInt(options.m, 10), vectorDb, precomputedSkeletons);
724
787
  return;
725
788
  }
726
789
  if (!filteredData.length) {
@@ -747,9 +810,9 @@ Examples:
747
810
  let shown = 0;
748
811
  console.log(resultCountHeader(filteredData, parseInt(options.m, 10)));
749
812
  for (const r of filteredData) {
750
- const absP = (_u = (_s = r.path) !== null && _s !== void 0 ? _s : (_t = r.metadata) === null || _t === void 0 ? void 0 : _t.path) !== null && _u !== void 0 ? _u : "";
751
- const startLine = (_y = (_w = (_v = r.startLine) !== null && _v !== void 0 ? _v : r.start_line) !== null && _w !== void 0 ? _w : (_x = r.generated_metadata) === null || _x === void 0 ? void 0 : _x.start_line) !== null && _y !== void 0 ? _y : 0;
752
- const endLine = (_2 = (_0 = (_z = r.endLine) !== null && _z !== void 0 ? _z : r.end_line) !== null && _0 !== void 0 ? _0 : (_1 = r.generated_metadata) === null || _1 === void 0 ? void 0 : _1.end_line) !== null && _2 !== void 0 ? _2 : startLine;
813
+ const absP = (_v = (_t = r.path) !== null && _t !== void 0 ? _t : (_u = r.metadata) === null || _u === void 0 ? void 0 : _u.path) !== null && _v !== void 0 ? _v : "";
814
+ const startLine = (_z = (_x = (_w = r.startLine) !== null && _w !== void 0 ? _w : r.start_line) !== null && _x !== void 0 ? _x : (_y = r.generated_metadata) === null || _y === void 0 ? void 0 : _y.start_line) !== null && _z !== void 0 ? _z : 0;
815
+ const endLine = (_3 = (_1 = (_0 = r.endLine) !== null && _0 !== void 0 ? _0 : r.end_line) !== null && _1 !== void 0 ? _1 : (_2 = r.generated_metadata) === null || _2 === void 0 ? void 0 : _2.end_line) !== null && _3 !== void 0 ? _3 : startLine;
753
816
  const relPath = absP.startsWith(projectRoot)
754
817
  ? absP.slice(projectRoot.length + 1)
755
818
  : absP;
@@ -782,7 +845,7 @@ Examples:
782
845
  tokensUsed += blobTokens;
783
846
  shown++;
784
847
  }
785
- catch (_12) {
848
+ catch (_13) {
786
849
  console.log(`\n--- ${relPath} (file not readable) ---`);
787
850
  shown++;
788
851
  }
@@ -799,7 +862,7 @@ Examples:
799
862
  if (options.imports) {
800
863
  const seenFiles = new Set();
801
864
  for (const r of filteredData) {
802
- const absP = (_5 = (_3 = r.path) !== null && _3 !== void 0 ? _3 : (_4 = r.metadata) === null || _4 === void 0 ? void 0 : _4.path) !== null && _5 !== void 0 ? _5 : "";
865
+ const absP = (_6 = (_4 = r.path) !== null && _4 !== void 0 ? _4 : (_5 = r.metadata) === null || _5 === void 0 ? void 0 : _5.path) !== null && _6 !== void 0 ? _6 : "";
803
866
  if (absP && !seenFiles.has(absP)) {
804
867
  seenFiles.add(absP);
805
868
  const imports = getImportsForFile(absP);
@@ -826,7 +889,7 @@ Examples:
826
889
  for (const r of filteredData) {
827
890
  const b = r.scoreBreakdown;
828
891
  if (b) {
829
- const absP = (_8 = (_6 = r.path) !== null && _6 !== void 0 ? _6 : (_7 = r.metadata) === null || _7 === void 0 ? void 0 : _7.path) !== null && _8 !== void 0 ? _8 : "";
892
+ const absP = (_9 = (_7 = r.path) !== null && _7 !== void 0 ? _7 : (_8 = r.metadata) === null || _8 === void 0 ? void 0 : _8.path) !== null && _9 !== void 0 ? _9 : "";
830
893
  const relPath = absP.startsWith(projectRoot)
831
894
  ? absP.slice(projectRoot.length + 1)
832
895
  : absP;
@@ -845,12 +908,17 @@ Examples:
845
908
  console.log(output);
846
909
  }
847
910
  // Symbol mode: append call graph
848
- if (options.symbol && vectorDb) {
911
+ if (options.symbol) {
849
912
  try {
850
- const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
851
- const builder = new GraphBuilder(vectorDb, effectiveRoot);
852
- const graph = yield builder.buildGraphMultiHop(pattern, 1);
853
- if (graph.center) {
913
+ let graph = precomputedGraph;
914
+ if (!graph) {
915
+ if (!vectorDb)
916
+ throw new Error("no graph source");
917
+ const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
918
+ const builder = new GraphBuilder(vectorDb, effectiveRoot);
919
+ graph = yield builder.buildGraphMultiHop(pattern, 1);
920
+ }
921
+ if (graph === null || graph === void 0 ? void 0 : graph.center) {
854
922
  const lines = ["\n--- Call graph ---"];
855
923
  const centerRel = path.relative(effectiveRoot, graph.center.file);
856
924
  lines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
@@ -883,7 +951,7 @@ Examples:
883
951
  console.log(lines.join("\n"));
884
952
  }
885
953
  }
886
- catch (_13) {
954
+ catch (_14) {
887
955
  // Trace failed — skip silently
888
956
  }
889
957
  }
@@ -903,13 +971,13 @@ Examples:
903
971
  source: "cli",
904
972
  tool: "search",
905
973
  query: pattern,
906
- project: (_9 = (0, project_root_1.findProjectRoot)(root)) !== null && _9 !== void 0 ? _9 : root,
974
+ project: (_10 = (0, project_root_1.findProjectRoot)(root)) !== null && _10 !== void 0 ? _10 : root,
907
975
  results: _searchResultCount,
908
976
  ms: Date.now() - _searchStartMs,
909
977
  error: _searchError,
910
978
  });
911
979
  }
912
- catch (_14) { }
980
+ catch (_15) { }
913
981
  if (vectorDb) {
914
982
  try {
915
983
  yield vectorDb.close();