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