grepmax 0.17.24 → 0.18.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.
@@ -45,20 +45,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.search = void 0;
46
46
  const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
- const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
49
- const compact_results_1 = require("../lib/output/compact-results");
50
- const index_state_footer_1 = require("../lib/output/index-state-footer");
51
48
  const setup_helpers_1 = require("../lib/setup/setup-helpers");
52
49
  const cross_project_1 = require("../lib/utils/cross-project");
53
50
  const exit_1 = require("../lib/utils/exit");
54
- const formatter_1 = require("../lib/utils/formatter");
55
- const import_extractor_1 = require("../lib/utils/import-extractor");
56
51
  const project_registry_1 = require("../lib/utils/project-registry");
57
52
  const project_root_1 = require("../lib/utils/project-root");
58
53
  const server_registry_1 = require("../lib/utils/server-registry");
59
54
  const stale_hint_1 = require("../lib/utils/stale-hint");
55
+ const search_output_1 = require("./search-output");
60
56
  const search_run_1 = require("./search-run");
61
- const search_skeletons_1 = require("./search-skeletons");
62
57
  exports.search = new commander_1.Command("search")
63
58
  .description("Search code by meaning (default command)")
64
59
  .option("-m <max_count>, --max-count <max_count>", "The maximum number of results to return (total)", "5")
@@ -88,8 +83,8 @@ exports.search = new commander_1.Command("search")
88
83
  .option("--name <regex>", "Filter results by symbol name regex")
89
84
  .option("-C, --context <n>", "Include N lines before/after each result")
90
85
  .option("--agent", "Ultra-compact output for AI agents (one line per result)", false)
91
- .option("--seed-file <path>", "Bias results toward your working context (repeatable; comma-separated also accepted)", (value, prev) => (prev ? [...prev, value] : [value]))
92
- .option("--seed-symbol <name>", "Bias results toward an identifier you're working with (repeatable; comma-separated also accepted)", (value, prev) => (prev ? [...prev, value] : [value]))
86
+ .option("--seed-file <path>", "Bias results toward your working context (repeatable; comma-separated also accepted)", (value, prev) => prev ? [...prev, value] : [value])
87
+ .option("--seed-symbol <name>", "Bias results toward an identifier you're working with (repeatable; comma-separated also accepted)", (value, prev) => prev ? [...prev, value] : [value])
93
88
  .argument("<pattern>", 'Natural language query (e.g. "where do we handle auth?")')
94
89
  .argument("[path]", "Restrict search to this path prefix")
95
90
  .addHelpText("after", `
@@ -104,7 +99,7 @@ Examples:
104
99
  gmax "auth middleware" --projects api,gateway --plain
105
100
  `)
106
101
  .action((pattern, exec_path, _options, cmd) => __awaiter(void 0, void 0, void 0, function* () {
107
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
102
+ var _a, _b, _c, _d, _e;
108
103
  const options = cmd.optsWithGlobals();
109
104
  const root = process.cwd();
110
105
  const minScore = Number.isFinite(Number.parseFloat(options.minScore))
@@ -154,110 +149,20 @@ Examples:
154
149
  const server = crossProject.active
155
150
  ? null
156
151
  : (0, server_registry_1.getServerForProject)(projectRootForServer);
152
+ // The standalone HTTP-server path lives OUTSIDE the main try/finally below:
153
+ // a handled server search returns here, intentionally skipping query
154
+ // logging and gracefulExit (unchanged from before the extraction).
157
155
  if (server) {
158
- try {
159
- const response = yield fetch(`http://localhost:${server.port}/search`, {
160
- method: "POST",
161
- headers: { "Content-Type": "application/json" },
162
- body: JSON.stringify({
163
- query: pattern,
164
- limit: parseInt(options.m, 10),
165
- path: exec_path
166
- ? path.relative(projectRootForServer, path.resolve(exec_path))
167
- : undefined,
168
- }),
169
- });
170
- if (response.ok) {
171
- const body = (yield response.json());
172
- const searchResult = { data: body.results };
173
- const filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
174
- if (options.skeleton) {
175
- yield (0, search_skeletons_1.outputSkeletons)(filteredData, projectRootForServer, parseInt(options.m, 10),
176
- // Server doesn't easily expose DB instance here in HTTP client mode,
177
- // but we are in client. Wait, this text implies "Server Search" block.
178
- // Client talks to server. The server returns JSON.
179
- // We don't have DB access here.
180
- // So we pass null, and it will fallback to generating local skeleton (if file exists locally).
181
- // This is acceptable for Phase 3.
182
- null);
183
- return;
184
- }
185
- const compactHits = options.compact
186
- ? (0, compact_results_1.toCompactHits)(filteredData)
187
- : [];
188
- if (options.compact) {
189
- if (!compactHits.length) {
190
- console.log("No matches found.");
191
- console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
192
- process.exitCode = 1;
193
- }
194
- else {
195
- console.log((0, compact_results_1.formatCompactTable)(compactHits, projectRootForServer, pattern, {
196
- isTTY: !!process.stdout.isTTY,
197
- plain: !!options.plain,
198
- }));
199
- }
200
- return; // EXIT
201
- }
202
- if (!filteredData.length) {
203
- console.log("No matches found.");
204
- console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
205
- process.exitCode = 1;
206
- return; // EXIT
207
- }
208
- if (options.agent) {
209
- const importCache = new Map();
210
- const getImportsForFile = (absPath) => {
211
- var _a;
212
- if (!options.imports || !absPath)
213
- return "";
214
- if (!importCache.has(absPath)) {
215
- importCache.set(absPath, (0, import_extractor_1.extractImports)(absPath));
216
- }
217
- return (_a = importCache.get(absPath)) !== null && _a !== void 0 ? _a : "";
218
- };
219
- console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, projectRootForServer, {
220
- includeImports: options.imports,
221
- query: pattern,
222
- getImportsForFile,
223
- explain: options.explain,
224
- }));
225
- return; // EXIT
226
- }
227
- const isTTY = process.stdout.isTTY;
228
- const shouldBePlain = options.plain || !isTTY;
229
- _searchResultCount = filteredData.length;
230
- if (!options.agent && !options.compact) {
231
- console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
232
- console.log();
233
- }
234
- if (shouldBePlain) {
235
- const mappedResults = (0, compact_results_1.toTextResults)(filteredData);
236
- const output = (0, formatter_1.formatTextResults)(mappedResults, pattern, projectRootForServer, {
237
- isPlain: true,
238
- compact: options.compact,
239
- content: options.content,
240
- perFile: parseInt(options.perFile, 10),
241
- showScores: options.scores,
242
- });
243
- console.log(output);
244
- }
245
- else {
246
- const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
247
- const output = formatResults(filteredData, projectRootForServer, {
248
- content: options.content,
249
- explain: options.explain,
250
- });
251
- console.log(output);
252
- }
253
- return; // EXIT successful server search
254
- }
255
- }
256
- catch (e) {
257
- if (process.env.DEBUG) {
258
- console.error("[search] server request failed, falling back to local:", e);
259
- }
260
- }
156
+ const handled = yield (0, search_output_1.executeServerSearch)({
157
+ server,
158
+ pattern,
159
+ exec_path,
160
+ projectRootForServer,
161
+ options,
162
+ minScore,
163
+ });
164
+ if (handled)
165
+ return;
261
166
  }
262
167
  try {
263
168
  yield (0, setup_helpers_1.ensureSetup)();
@@ -287,6 +192,10 @@ Examples:
287
192
  console.warn("This project is still being indexed. Results may be incomplete.\n");
288
193
  }
289
194
  (0, stale_hint_1.maybeWarnStaleChunker)(checkRoot, { agent: options.agent });
195
+ (0, stale_hint_1.maybeWarnStaleEmbedding)(checkRoot, { agent: options.agent });
196
+ if (crossProject.active) {
197
+ (0, stale_hint_1.maybeWarnCrossProjectDim)(crossProject.roots, { agent: options.agent });
198
+ }
290
199
  // Compute effective paths + filters early — both the daemon-mediated
291
200
  // and in-process search paths need them. Reuse the resolved checkRoot
292
201
  // so --root <name> only resolves once per invocation.
@@ -351,7 +260,9 @@ Examples:
351
260
  };
352
261
  const seedFiles = splitSeeds(options.seedFile);
353
262
  const seedSymbols = splitSeeds(options.seedSymbol);
354
- const seeds = seedFiles || seedSymbols ? { files: seedFiles, symbols: seedSymbols } : undefined;
263
+ const seeds = seedFiles || seedSymbols
264
+ ? { files: seedFiles, symbols: seedSymbols }
265
+ : undefined;
355
266
  // Acquire results: daemon-mediated first, in-process fallback otherwise.
356
267
  // The render stage below is shared across both. `runSearch` reports the
357
268
  // VectorDB it opens via the callback so the `finally` can close it.
@@ -369,353 +280,24 @@ Examples:
369
280
  vectorDb = acquired.vectorDb;
370
281
  if (acquired.kind === "dry-run")
371
282
  return;
372
- const { searchResult, precomputedSkeletons, precomputedGraph, indexState } = acquired;
373
- if (!options.agent && ((_e = searchResult.warnings) === null || _e === void 0 ? void 0 : _e.length)) {
374
- for (const w of searchResult.warnings) {
375
- console.warn(`Warning: ${w}`);
376
- }
377
- }
378
- // Partial-index signal (Phase 6): when the index is mid-catchup, results
379
- // may be incomplete. Non-agent renders it now as a warning; agent mode
380
- // appends a machine-readable footer after the results below.
381
- if (!options.agent) {
382
- const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, { agent: false });
383
- if (footer)
384
- console.warn(footer);
385
- }
386
- let filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
387
- // Post-filter by symbol name regex
388
- if (options.name) {
389
- try {
390
- const regex = new RegExp(options.name, "i");
391
- filteredData = filteredData.filter((r) => {
392
- const defs = Array.isArray(r.defined_symbols)
393
- ? r.defined_symbols
394
- : [];
395
- return defs.some((d) => regex.test(d));
396
- });
397
- }
398
- catch (_o) {
399
- // Invalid regex — skip
400
- }
401
- }
402
- // Build import cache when --imports is requested
403
- const importCache = new Map();
404
- const getImportsForFile = (absPath) => {
405
- var _a;
406
- if (!options.imports || !absPath)
407
- return "";
408
- if (!importCache.has(absPath)) {
409
- importCache.set(absPath, (0, import_extractor_1.extractImports)(absPath));
410
- }
411
- return (_a = importCache.get(absPath)) !== null && _a !== void 0 ? _a : "";
412
- };
413
- // Agent mode: ultra-compact one-line-per-result output
414
- _searchResultCount = filteredData.length;
415
- // Cross-project (Phase 6): render grouped by owning project so idioms
416
- // from different stacks don't blur into one flat list. Only the
417
- // string-formatter modes reach here — skeleton/context-for-llm/symbol
418
- // were rejected up front.
419
- if (crossProject.active) {
420
- const emitFooter = () => {
421
- const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, {
422
- agent: !!options.agent,
423
- });
424
- if (footer) {
425
- if (options.agent)
426
- console.log(footer);
427
- else
428
- console.warn(footer);
429
- }
430
- };
431
- if (!filteredData.length) {
432
- console.log(options.agent ? "(none)" : "No matches found.");
433
- process.exitCode = 1;
434
- emitFooter();
435
- return;
436
- }
437
- const getPath = (r) => {
438
- var _a, _b, _c;
439
- return String((_c = (_a = r.path) !== null && _a !== void 0 ? _a : (_b = r.metadata) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : "");
440
- };
441
- const groups = (0, cross_project_1.groupResultsByProject)(filteredData, crossProject.roots, getPath);
442
- const isTTY = process.stdout.isTTY;
443
- const shouldBePlain = options.plain || !isTTY;
444
- const blocks = [];
445
- for (const g of groups) {
446
- let body;
447
- if (options.agent) {
448
- body = (0, agent_search_formatter_1.formatAgentSearchResults)(g.items, g.root, {
449
- includeImports: options.imports,
450
- query: pattern,
451
- getImportsForFile,
452
- explain: options.explain,
453
- });
454
- }
455
- else if (options.compact) {
456
- body = (0, compact_results_1.formatCompactTable)((0, compact_results_1.toCompactHits)(g.items), g.root, pattern, {
457
- isTTY: !!isTTY,
458
- plain: !!options.plain,
459
- });
460
- }
461
- else if (shouldBePlain) {
462
- body = (0, formatter_1.formatTextResults)((0, compact_results_1.toTextResults)(g.items), pattern, g.root, {
463
- isPlain: true,
464
- compact: options.compact,
465
- content: options.content,
466
- perFile: parseInt(options.perFile, 10),
467
- showScores: options.scores,
468
- });
469
- }
470
- else {
471
- const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
472
- body = formatResults(g.items, g.root, {
473
- content: options.content,
474
- explain: options.explain,
475
- });
476
- }
477
- const header = options.agent
478
- ? `## ${g.name} (${g.items.length})`
479
- : `=== ${g.name} (${g.items.length}) ===`;
480
- blocks.push(`${header}\n${body}`);
481
- }
482
- console.log(blocks.join("\n\n"));
483
- emitFooter();
484
- return;
485
- }
486
- if (options.agent) {
487
- if (!filteredData.length) {
488
- console.log("(none)");
489
- process.exitCode = 1;
490
- }
491
- else {
492
- console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, effectiveRoot, {
493
- includeImports: options.imports,
494
- query: pattern,
495
- getImportsForFile,
496
- explain: options.explain,
497
- }));
498
- }
499
- // Agent trace (compact)
500
- if (options.symbol && filteredData.length > 0) {
501
- try {
502
- let graph = precomputedGraph;
503
- if (!graph) {
504
- if (!vectorDb)
505
- throw new Error("no graph source");
506
- const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
507
- const builder = new GraphBuilder(vectorDb, effectiveRoot);
508
- graph = yield builder.buildGraphMultiHop(pattern, 1);
509
- }
510
- if (graph === null || graph === void 0 ? void 0 : graph.center) {
511
- console.log("---");
512
- for (const t of graph.callerTree) {
513
- const rel = t.node.file.startsWith(effectiveRoot)
514
- ? t.node.file.slice(effectiveRoot.length + 1)
515
- : t.node.file;
516
- console.log(`<- ${t.node.symbol} ${rel}:${t.node.line + 1}`);
517
- }
518
- for (const c of graph.callees.slice(0, 10)) {
519
- if (c.file) {
520
- const rel = c.file.startsWith(effectiveRoot)
521
- ? c.file.slice(effectiveRoot.length + 1)
522
- : c.file;
523
- console.log(`-> ${c.symbol} ${rel}:${c.line + 1}`);
524
- }
525
- }
526
- }
527
- }
528
- catch (_p) { }
529
- }
530
- // Partial-index footer last, so it's the final line the agent reads —
531
- // and emitted even on "(none)", where an empty result may just mean the
532
- // relevant files aren't indexed yet.
533
- const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, { agent: true });
534
- if (footer)
535
- console.log(footer);
536
- return;
537
- }
538
- if (options.skeleton) {
539
- yield (0, search_skeletons_1.outputSkeletons)(filteredData, projectRoot, parseInt(options.m, 10), vectorDb, precomputedSkeletons);
540
- return;
541
- }
542
- if (!filteredData.length) {
543
- console.log("No matches found.");
544
- console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
545
- process.exitCode = 1;
546
- return;
547
- }
548
- if (options.compact) {
549
- const compactHits = (0, compact_results_1.toCompactHits)(filteredData);
550
- console.log((0, compact_results_1.formatCompactTable)(compactHits, projectRoot, pattern, {
551
- isTTY: !!process.stdout.isTTY,
552
- plain: !!options.plain,
553
- }));
554
- return;
555
- }
556
- _searchResultCount = filteredData.length;
557
- // Context-for-LLM mode: full function body + imports per result
558
- if (options.contextForLlm) {
559
- const fs = yield Promise.resolve().then(() => __importStar(require("node:fs")));
560
- const { extractImportsFromContent } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/import-extractor")));
561
- const { packByBudget } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/budget-pack")));
562
- const budget = parseInt(options.budget, 10) || 8000;
563
- console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
564
- // Build every candidate blob up front (token cost needs the rendered
565
- // text), then pack to budget. Token-aware packing skips an oversized
566
- // chunk and keeps filling with smaller, still-relevant ones rather than
567
- // aborting the loop — recovering budget the old greedy `break` wasted.
568
- const candidates = filteredData.map((r, idx) => {
569
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
570
- const absP = (_c = (_a = r.path) !== null && _a !== void 0 ? _a : (_b = r.metadata) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : "";
571
- const startLine = (_g = (_e = (_d = r.startLine) !== null && _d !== void 0 ? _d : r.start_line) !== null && _e !== void 0 ? _e : (_f = r.generated_metadata) === null || _f === void 0 ? void 0 : _f.start_line) !== null && _g !== void 0 ? _g : 0;
572
- const endLine = (_l = (_j = (_h = r.endLine) !== null && _h !== void 0 ? _h : r.end_line) !== null && _j !== void 0 ? _j : (_k = r.generated_metadata) === null || _k === void 0 ? void 0 : _k.end_line) !== null && _l !== void 0 ? _l : startLine;
573
- const relPath = absP.startsWith(projectRoot)
574
- ? absP.slice(projectRoot.length + 1)
575
- : absP;
576
- const role = r.role || "IMPLEMENTATION";
577
- const symbol = Array.isArray(r.defined_symbols) &&
578
- r.defined_symbols.length > 0
579
- ? r.defined_symbols[0]
580
- : "";
581
- let blobText;
582
- try {
583
- const content = fs.readFileSync(absP, "utf-8");
584
- const allLines = content.split("\n");
585
- const body = allLines
586
- .slice(startLine, Math.min(endLine + 1, allLines.length))
587
- .join("\n");
588
- const imports = extractImportsFromContent(content);
589
- const blob = [
590
- `--- ${relPath}:${startLine + 1}${symbol ? ` ${symbol}` : ""} [${role}] ---`,
591
- ];
592
- if (imports)
593
- blob.push("[imports]", imports, "");
594
- blob.push("[body]", body);
595
- blobText = blob.join("\n");
596
- }
597
- catch (_m) {
598
- blobText = `--- ${relPath} (file not readable) ---`;
599
- }
600
- // Preserve relevance order when scores are absent (rank-derived
601
- // fallback) so the density tiebreaker never reshuffles arbitrarily.
602
- const score = typeof r.score === "number"
603
- ? r.score
604
- : (filteredData.length - idx) / filteredData.length;
605
- return { blobText, tokens: Math.ceil(blobText.length / 4), score };
606
- });
607
- const pack = packByBudget(candidates.map((c) => ({ tokens: c.tokens, score: c.score })), budget);
608
- for (const i of pack.selected) {
609
- console.log(`\n${candidates[i].blobText}`);
610
- }
611
- if (pack.dropped > 0) {
612
- console.log(`\n(budget: ~${pack.tokensUsed}/${budget} tokens, ${pack.dropped} lower-density result${pack.dropped > 1 ? "s" : ""} not shown)`);
613
- }
614
- return;
615
- }
616
- const isTTY = process.stdout.isTTY;
617
- const shouldBePlain = options.plain || !isTTY;
618
- if (!options.agent && !options.compact) {
619
- console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
620
- console.log();
621
- }
622
- // Print imports per unique file before results when --imports is used
623
- if (options.imports) {
624
- const seenFiles = new Set();
625
- for (const r of filteredData) {
626
- const absP = (_h = (_f = r.path) !== null && _f !== void 0 ? _f : (_g = r.metadata) === null || _g === void 0 ? void 0 : _g.path) !== null && _h !== void 0 ? _h : "";
627
- if (absP && !seenFiles.has(absP)) {
628
- seenFiles.add(absP);
629
- const imports = getImportsForFile(absP);
630
- if (imports) {
631
- const relP = absP.startsWith(effectiveRoot)
632
- ? absP.slice(effectiveRoot.length + 1)
633
- : absP;
634
- console.log(`--- imports: ${relP} ---\n${imports}\n`);
635
- }
636
- }
637
- }
638
- }
639
- if (shouldBePlain) {
640
- const mappedResults = (0, compact_results_1.toTextResults)(filteredData);
641
- const output = (0, formatter_1.formatTextResults)(mappedResults, pattern, projectRoot, {
642
- isPlain: true,
643
- compact: options.compact,
644
- content: options.content,
645
- perFile: parseInt(options.perFile, 10),
646
- showScores: options.scores,
647
- });
648
- console.log(output);
649
- if (options.explain) {
650
- for (const r of filteredData) {
651
- const b = r.scoreBreakdown;
652
- if (b) {
653
- const absP = (_l = (_j = r.path) !== null && _j !== void 0 ? _j : (_k = r.metadata) === null || _k === void 0 ? void 0 : _k.path) !== null && _l !== void 0 ? _l : "";
654
- const relPath = absP.startsWith(projectRoot)
655
- ? absP.slice(projectRoot.length + 1)
656
- : absP;
657
- console.log(` [explain ${relPath}] rerank=${b.rerank.toFixed(3)} fused=${b.fused.toFixed(3)} boost=${b.boost.toFixed(2)}x final=${b.normalized.toFixed(3)}`);
658
- }
659
- }
660
- }
661
- }
662
- else {
663
- // Use new holographic formatter for TTY
664
- const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
665
- const output = formatResults(filteredData, projectRoot, {
666
- content: options.content,
667
- explain: options.explain,
668
- });
669
- console.log(output);
670
- }
671
- // Symbol mode: append call graph
672
- if (options.symbol) {
673
- try {
674
- let graph = precomputedGraph;
675
- if (!graph) {
676
- if (!vectorDb)
677
- throw new Error("no graph source");
678
- const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
679
- const builder = new GraphBuilder(vectorDb, effectiveRoot);
680
- graph = yield builder.buildGraphMultiHop(pattern, 1);
681
- }
682
- if (graph === null || graph === void 0 ? void 0 : graph.center) {
683
- const lines = ["\n--- Call graph ---"];
684
- const centerRel = path.relative(effectiveRoot, graph.center.file);
685
- lines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
686
- if (graph.importers.length > 0) {
687
- const filtered = graph.importers.filter((p) => p !== graph.center.file);
688
- if (filtered.length > 0) {
689
- lines.push("Imported by:");
690
- for (const imp of filtered.slice(0, 10)) {
691
- lines.push(` ${path.relative(effectiveRoot, imp)}`);
692
- }
693
- }
694
- }
695
- if (graph.callerTree.length > 0) {
696
- lines.push("Callers:");
697
- for (const t of graph.callerTree) {
698
- lines.push(` <- ${t.node.symbol} ${path.relative(effectiveRoot, t.node.file)}:${t.node.line + 1}`);
699
- }
700
- }
701
- if (graph.callees.length > 0) {
702
- lines.push("Calls:");
703
- for (const c of graph.callees.slice(0, 15)) {
704
- if (c.file) {
705
- lines.push(` -> ${c.symbol} ${path.relative(effectiveRoot, c.file)}:${c.line + 1}`);
706
- }
707
- else {
708
- lines.push(` -> ${c.symbol} (not indexed)`);
709
- }
710
- }
711
- }
712
- console.log(lines.join("\n"));
713
- }
714
- }
715
- catch (_q) {
716
- // Trace failed — skip silently
717
- }
718
- }
283
+ const { searchResult, precomputedSkeletons, precomputedGraph, indexState, } = acquired;
284
+ // Presentation stage (shared by daemon-mediated + in-process paths):
285
+ // min-score/name post-filters + the 7-mode render switch live in
286
+ // renderSearchOutput; it returns the post-filter count for query logging.
287
+ const { resultCount } = yield (0, search_output_1.renderSearchOutput)({
288
+ searchResult,
289
+ options,
290
+ minScore,
291
+ crossProject,
292
+ pattern,
293
+ effectiveRoot,
294
+ projectRoot,
295
+ vectorDb,
296
+ precomputedSkeletons,
297
+ precomputedGraph,
298
+ indexState,
299
+ });
300
+ _searchResultCount = resultCount;
719
301
  }
720
302
  catch (error) {
721
303
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -732,13 +314,13 @@ Examples:
732
314
  source: "cli",
733
315
  tool: "search",
734
316
  query: pattern,
735
- project: (_m = (0, project_root_1.findProjectRoot)(root)) !== null && _m !== void 0 ? _m : root,
317
+ project: (_e = (0, project_root_1.findProjectRoot)(root)) !== null && _e !== void 0 ? _e : root,
736
318
  results: _searchResultCount,
737
319
  ms: Date.now() - _searchStartMs,
738
320
  error: _searchError,
739
321
  });
740
322
  }
741
- catch (_r) { }
323
+ catch (_f) { }
742
324
  if (vectorDb) {
743
325
  try {
744
326
  yield vectorDb.close();
@@ -73,6 +73,7 @@ exports.similar = new commander_1.Command("similar")
73
73
  return;
74
74
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
75
75
  (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
76
+ (0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
76
77
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
77
78
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
78
79
  const table = yield vectorDb.ensureTable();
@@ -69,6 +69,7 @@ exports.testFind = new commander_1.Command("test")
69
69
  return;
70
70
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
71
71
  (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
72
+ (0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
72
73
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
73
74
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
74
75
  const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
@@ -185,6 +185,7 @@ exports.trace = new commander_1.Command("trace")
185
185
  try {
186
186
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
187
187
  (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
188
+ (0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
188
189
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
189
190
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
190
191
  const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
package/dist/config.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.INDEXABLE_EXTENSIONS = exports.FRAGMENT_COMPACT_THRESHOLD = exports.DISK_LOW_BYTES = exports.DISK_CRITICAL_BYTES = exports.MAX_FILE_SIZE_BYTES = exports.PATHS = exports.MAX_WORKER_MEMORY_MB = exports.WORKER_BOOT_TIMEOUT_MS = exports.WORKER_TIMEOUT_MS = exports.CHUNKER_VERSION_HISTORY = exports.CONFIG = exports.MODEL_IDS = exports.DEFAULT_MODEL_TIER = exports.MODEL_TIERS = void 0;
37
37
  exports.describeChunkerGap = describeChunkerGap;
38
+ exports.describeEmbeddingGap = describeEmbeddingGap;
38
39
  const os = __importStar(require("node:os"));
39
40
  const path = __importStar(require("node:path"));
40
41
  exports.MODEL_TIERS = {
@@ -123,6 +124,39 @@ function describeChunkerGap(indexedVersion) {
123
124
  notes: missed.map((h) => h.note),
124
125
  };
125
126
  }
127
+ /**
128
+ * Describe the gap between an index's stored embedding identity (model tier +
129
+ * vector dim) and the current global config, or null when they already agree.
130
+ *
131
+ * A dimension change is `breaking`: vectors of differing widths cannot be
132
+ * compared, and the single fixed-dim `chunks` table silently pads/truncates a
133
+ * mismatched query embedding, yielding meaningless scores. A same-width model
134
+ * swap is `additive`: the stored vectors are a different model's output but the
135
+ * same width, so search keeps working with mixed-model quality until a re-embed.
136
+ *
137
+ * Mirrors describeChunkerGap so `gmax doctor` and the query-time hint render the
138
+ * same severity. Pure over MODEL_TIERS — callers pass the current identity (read
139
+ * from the global config) so this module stays free of config I/O.
140
+ */
141
+ function describeEmbeddingGap(stored, current) {
142
+ var _a, _b, _c, _d, _e, _f, _g, _h;
143
+ const fromModel = (_a = stored.modelTier) !== null && _a !== void 0 ? _a : exports.DEFAULT_MODEL_TIER;
144
+ const toModel = (_b = current.modelTier) !== null && _b !== void 0 ? _b : exports.DEFAULT_MODEL_TIER;
145
+ const fromDim = (_e = (_c = stored.vectorDim) !== null && _c !== void 0 ? _c : (_d = exports.MODEL_TIERS[fromModel]) === null || _d === void 0 ? void 0 : _d.vectorDim) !== null && _e !== void 0 ? _e : exports.CONFIG.VECTOR_DIM;
146
+ const toDim = (_h = (_f = current.vectorDim) !== null && _f !== void 0 ? _f : (_g = exports.MODEL_TIERS[toModel]) === null || _g === void 0 ? void 0 : _g.vectorDim) !== null && _h !== void 0 ? _h : exports.CONFIG.VECTOR_DIM;
147
+ const dimChanged = fromDim !== toDim;
148
+ const modelChanged = fromModel !== toModel;
149
+ if (!dimChanged && !modelChanged)
150
+ return null;
151
+ return {
152
+ fromModel,
153
+ toModel,
154
+ fromDim,
155
+ toDim,
156
+ dimChanged,
157
+ severity: dimChanged ? "breaking" : "additive",
158
+ };
159
+ }
126
160
  exports.WORKER_TIMEOUT_MS = Number.parseInt(process.env.GMAX_WORKER_TIMEOUT_MS || "60000", 10);
127
161
  exports.WORKER_BOOT_TIMEOUT_MS = Number.parseInt(process.env.GMAX_WORKER_BOOT_TIMEOUT_MS || "300000", 10);
128
162
  exports.MAX_WORKER_MEMORY_MB = (() => {