grepmax 0.15.6 → 0.16.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.
@@ -50,6 +50,7 @@ const vector_db_1 = require("../lib/store/vector-db");
50
50
  const exit_1 = require("../lib/utils/exit");
51
51
  const filter_builder_1 = require("../lib/utils/filter-builder");
52
52
  const import_extractor_1 = require("../lib/utils/import-extractor");
53
+ const language_1 = require("../lib/utils/language");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
55
56
  const style = {
@@ -128,6 +129,25 @@ exports.extract = new commander_1.Command("extract")
128
129
  process.exitCode = 1;
129
130
  return;
130
131
  }
132
+ // Cross-language disambiguation: when the symbol is defined in 2+
133
+ // languages, refuse to silently pick one. Listing all matches with a
134
+ // recovery hint avoids the dogfooded failure mode where peek picked
135
+ // Swift but listed TS callers.
136
+ const byLang = (0, language_1.groupByLanguage)(chunks);
137
+ if (byLang.size >= 2) {
138
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
139
+ const lines = [
140
+ `Symbol '${symbol}' is defined in multiple languages:`,
141
+ ];
142
+ for (const [lang, group] of byLang) {
143
+ const c = group[0];
144
+ lines.push(` ${lang.padEnd(6)} ${rel(c.path)}:${c.startLine + 1}`);
145
+ }
146
+ lines.push(`Disambiguate with --root or pin to a path: gmax extract ${symbol} --root <project-root>`);
147
+ console.log(lines.join("\n"));
148
+ process.exitCode = 1;
149
+ return;
150
+ }
131
151
  const best = pickBestMatch(chunks, symbol);
132
152
  const content = fs.readFileSync(best.path, "utf-8");
133
153
  const allLines = content.split("\n");
@@ -50,6 +50,7 @@ const graph_builder_1 = require("../lib/graph/graph-builder");
50
50
  const vector_db_1 = require("../lib/store/vector-db");
51
51
  const exit_1 = require("../lib/utils/exit");
52
52
  const filter_builder_1 = require("../lib/utils/filter-builder");
53
+ const language_1 = require("../lib/utils/language");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
55
56
  const style = {
@@ -104,6 +105,41 @@ exports.peek = new commander_1.Command("peek")
104
105
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
105
106
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
106
107
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
108
+ // Cross-language disambiguation: when the symbol is defined in 2+
109
+ // languages, refuse to silently pick one. The graph builder otherwise
110
+ // picks one chunk arbitrarily and lists callers from a different
111
+ // language — verified failure mode.
112
+ {
113
+ const tableForCheck = yield vectorDb.ensureTable();
114
+ const prefixForCheck = projectRoot.endsWith("/")
115
+ ? projectRoot
116
+ : `${projectRoot}/`;
117
+ const allDefs = yield tableForCheck
118
+ .query()
119
+ .select(["path", "start_line"])
120
+ .where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefixForCheck)}%'`)
121
+ .limit(20)
122
+ .toArray();
123
+ const chunks = allDefs.map((row) => ({
124
+ path: String(row.path || ""),
125
+ startLine: Number(row.start_line || 0),
126
+ }));
127
+ const byLang = (0, language_1.groupByLanguage)(chunks);
128
+ if (byLang.size >= 2) {
129
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
130
+ const lines = [
131
+ `Symbol '${symbol}' is defined in multiple languages:`,
132
+ ];
133
+ for (const [lang, group] of byLang) {
134
+ const c = group[0];
135
+ lines.push(` ${lang.padEnd(6)} ${rel(c.path)}:${c.startLine + 1}`);
136
+ }
137
+ lines.push(`Disambiguate with --root or pin to a path: gmax peek ${symbol} --root <project-root>`);
138
+ console.log(lines.join("\n"));
139
+ process.exitCode = 1;
140
+ return;
141
+ }
142
+ }
107
143
  const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot);
108
144
  const graph = yield graphBuilder.buildGraph(symbol);
109
145
  if (!graph.center) {
@@ -43,6 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.remove = void 0;
46
+ const fs = __importStar(require("node:fs"));
46
47
  const path = __importStar(require("node:path"));
47
48
  const readline = __importStar(require("node:readline"));
48
49
  const commander_1 = require("commander");
@@ -68,12 +69,13 @@ function confirm(message) {
68
69
  }
69
70
  exports.remove = new commander_1.Command("remove")
70
71
  .description("Remove a project from the gmax index")
71
- .argument("[dir]", "Directory to remove (defaults to current directory)")
72
+ .argument("[dir-or-name]", "Directory or registered project name (defaults to current directory)")
72
73
  .option("-f, --force", "Skip confirmation prompt", false)
73
74
  .addHelpText("after", `
74
75
  Examples:
75
76
  gmax remove Remove the current project
76
- gmax remove ~/projects/app Remove a specific project
77
+ gmax remove ~/projects/app Remove a specific project by path
78
+ gmax remove my-app Remove a registered project by name
77
79
  gmax remove --force Skip confirmation
78
80
  `)
79
81
  .action((dir, opts) => __awaiter(void 0, void 0, void 0, function* () {
@@ -81,7 +83,41 @@ Examples:
81
83
  let vectorDb = null;
82
84
  let metaCache = null;
83
85
  try {
84
- const targetDir = dir ? path.resolve(dir) : process.cwd();
86
+ // Resolve name registered root when arg has no path separator and isn't a dir.
87
+ // Avoids the footgun where a typo'd name silently removed the cwd project.
88
+ let resolvedDir = dir;
89
+ if (dir &&
90
+ !dir.includes("/") &&
91
+ !dir.includes("\\") &&
92
+ !fs.existsSync(path.resolve(dir))) {
93
+ const matches = (0, project_registry_1.listProjects)().filter((p) => p.name === dir);
94
+ if (matches.length === 0) {
95
+ console.error(`No registered project named "${dir}".`);
96
+ const all = (0, project_registry_1.listProjects)();
97
+ if (all.length > 0) {
98
+ console.error("Available projects:");
99
+ for (const p of all) {
100
+ console.error(` ${p.name.padEnd(24)} ${p.root}`);
101
+ }
102
+ }
103
+ else {
104
+ console.error("No projects are currently registered.");
105
+ }
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+ if (matches.length > 1) {
110
+ console.error(`Multiple registered projects named "${dir}":`);
111
+ for (const p of matches) {
112
+ console.error(` ${p.root}`);
113
+ }
114
+ console.error("Pass an absolute path to disambiguate.");
115
+ process.exitCode = 1;
116
+ return;
117
+ }
118
+ resolvedDir = matches[0].root;
119
+ }
120
+ const targetDir = resolvedDir ? path.resolve(resolvedDir) : process.cwd();
85
121
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(targetDir)) !== null && _a !== void 0 ? _a : targetDir;
86
122
  const projectName = path.basename(projectRoot);
87
123
  const project = (0, project_registry_1.getProject)(projectRoot);
@@ -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();
@@ -685,19 +743,26 @@ Examples:
685
743
  }
686
744
  const sym = symbol ? ` ${symbol}` : "";
687
745
  const rl = role ? ` [${role}]` : "";
746
+ const score = r.score;
747
+ const scoreCol = typeof score === "number" ? `\ts=${score.toFixed(3)}` : "";
688
748
  const explainSuffix = options.explain && r.scoreBreakdown
689
749
  ? `\texplain:rerank=${r.scoreBreakdown.rerank.toFixed(3)},fused=${r.scoreBreakdown.fused.toFixed(3)},boost=${r.scoreBreakdown.boost.toFixed(2)}x,score=${r.scoreBreakdown.normalized.toFixed(3)}`
690
750
  : "";
691
- console.log(`${relPath}:${startLine}${sym}${rl}${hint}${explainSuffix}`);
751
+ console.log(`${relPath}:${startLine}${scoreCol}${sym}${rl}${hint}${explainSuffix}`);
692
752
  }
693
753
  }
694
754
  // Agent trace (compact)
695
- if (options.symbol && vectorDb && filteredData.length > 0) {
755
+ if (options.symbol && filteredData.length > 0) {
696
756
  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) {
757
+ let graph = precomputedGraph;
758
+ if (!graph) {
759
+ if (!vectorDb)
760
+ throw new Error("no graph source");
761
+ const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
762
+ const builder = new GraphBuilder(vectorDb, effectiveRoot);
763
+ graph = yield builder.buildGraphMultiHop(pattern, 1);
764
+ }
765
+ if (graph === null || graph === void 0 ? void 0 : graph.center) {
701
766
  console.log("---");
702
767
  for (const t of graph.callerTree) {
703
768
  const rel = t.node.file.startsWith(effectiveRoot)
@@ -715,12 +780,12 @@ Examples:
715
780
  }
716
781
  }
717
782
  }
718
- catch (_11) { }
783
+ catch (_12) { }
719
784
  }
720
785
  return;
721
786
  }
722
787
  if (options.skeleton) {
723
- yield outputSkeletons(filteredData, projectRoot, parseInt(options.m, 10), vectorDb);
788
+ yield outputSkeletons(filteredData, projectRoot, parseInt(options.m, 10), vectorDb, precomputedSkeletons);
724
789
  return;
725
790
  }
726
791
  if (!filteredData.length) {
@@ -747,9 +812,9 @@ Examples:
747
812
  let shown = 0;
748
813
  console.log(resultCountHeader(filteredData, parseInt(options.m, 10)));
749
814
  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;
815
+ 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 : "";
816
+ 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;
817
+ 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
818
  const relPath = absP.startsWith(projectRoot)
754
819
  ? absP.slice(projectRoot.length + 1)
755
820
  : absP;
@@ -782,7 +847,7 @@ Examples:
782
847
  tokensUsed += blobTokens;
783
848
  shown++;
784
849
  }
785
- catch (_12) {
850
+ catch (_13) {
786
851
  console.log(`\n--- ${relPath} (file not readable) ---`);
787
852
  shown++;
788
853
  }
@@ -799,7 +864,7 @@ Examples:
799
864
  if (options.imports) {
800
865
  const seenFiles = new Set();
801
866
  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 : "";
867
+ 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
868
  if (absP && !seenFiles.has(absP)) {
804
869
  seenFiles.add(absP);
805
870
  const imports = getImportsForFile(absP);
@@ -826,7 +891,7 @@ Examples:
826
891
  for (const r of filteredData) {
827
892
  const b = r.scoreBreakdown;
828
893
  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 : "";
894
+ 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
895
  const relPath = absP.startsWith(projectRoot)
831
896
  ? absP.slice(projectRoot.length + 1)
832
897
  : absP;
@@ -845,12 +910,17 @@ Examples:
845
910
  console.log(output);
846
911
  }
847
912
  // Symbol mode: append call graph
848
- if (options.symbol && vectorDb) {
913
+ if (options.symbol) {
849
914
  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) {
915
+ let graph = precomputedGraph;
916
+ if (!graph) {
917
+ if (!vectorDb)
918
+ throw new Error("no graph source");
919
+ const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
920
+ const builder = new GraphBuilder(vectorDb, effectiveRoot);
921
+ graph = yield builder.buildGraphMultiHop(pattern, 1);
922
+ }
923
+ if (graph === null || graph === void 0 ? void 0 : graph.center) {
854
924
  const lines = ["\n--- Call graph ---"];
855
925
  const centerRel = path.relative(effectiveRoot, graph.center.file);
856
926
  lines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
@@ -883,7 +953,7 @@ Examples:
883
953
  console.log(lines.join("\n"));
884
954
  }
885
955
  }
886
- catch (_13) {
956
+ catch (_14) {
887
957
  // Trace failed — skip silently
888
958
  }
889
959
  }
@@ -903,13 +973,13 @@ Examples:
903
973
  source: "cli",
904
974
  tool: "search",
905
975
  query: pattern,
906
- project: (_9 = (0, project_root_1.findProjectRoot)(root)) !== null && _9 !== void 0 ? _9 : root,
976
+ project: (_10 = (0, project_root_1.findProjectRoot)(root)) !== null && _10 !== void 0 ? _10 : root,
907
977
  results: _searchResultCount,
908
978
  ms: Date.now() - _searchStartMs,
909
979
  error: _searchError,
910
980
  });
911
981
  }
912
- catch (_14) { }
982
+ catch (_15) { }
913
983
  if (vectorDb) {
914
984
  try {
915
985
  yield vectorDb.close();
@@ -61,7 +61,6 @@ const setup_helpers_1 = require("../lib/setup/setup-helpers");
61
61
  const retriever_1 = require("../lib/skeleton/retriever");
62
62
  const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
63
63
  const vector_db_1 = require("../lib/store/vector-db");
64
- const file_utils_1 = require("../lib/utils/file-utils");
65
64
  const exit_1 = require("../lib/utils/exit");
66
65
  const project_registry_1 = require("../lib/utils/project-registry");
67
66
  const project_root_1 = require("../lib/utils/project-root");
@@ -156,27 +155,18 @@ Examples:
156
155
  };
157
156
  // Determine mode based on target
158
157
  const resolvedTarget = path.resolve(target);
159
- // Directory mode
158
+ // Directory mode is unsupported. Auto-picking files from a directory
159
+ // was confusingly magical (and on '.' it fell through to the resolver
160
+ // path and skeletonized .gitignore). Refuse and point at the file form.
160
161
  if (fs.existsSync(resolvedTarget) &&
161
162
  fs.statSync(resolvedTarget).isDirectory()) {
162
- const entries = fs.readdirSync(resolvedTarget, {
163
- withFileTypes: true,
164
- });
165
- const files = entries
166
- .filter((e) => e.isFile() &&
167
- (0, file_utils_1.isIndexableFile)(path.join(resolvedTarget, e.name)))
168
- .map((e) => path.join(resolvedTarget, e.name))
169
- .slice(0, Number.parseInt(options.limit, 10));
170
- if (files.length === 0) {
171
- console.error(`No indexable files in ${target}`);
172
- process.exitCode = 1;
173
- return;
174
- }
175
- for (const filePath of files) {
176
- const content = fs.readFileSync(filePath, "utf-8");
177
- const result = yield skeletonizer.skeletonizeFile(filePath, content, skeletonOpts);
178
- outputResult(result, options);
179
- }
163
+ console.error([
164
+ "skeleton expects a file or symbol, not a directory.",
165
+ "Try:",
166
+ " gmax skeleton src/foo.ts # one file's structure",
167
+ ' gmax search "<topic>" --agent # find relevant files first',
168
+ ].join("\n"));
169
+ process.exitCode = 1;
180
170
  return;
181
171
  }
182
172
  // Batch mode (comma-separated)