grepmax 0.17.24 → 0.18.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.
@@ -120,6 +120,7 @@ exports.dead = new commander_1.Command("dead")
120
120
  try {
121
121
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
122
122
  (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
123
+ (0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
123
124
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
124
125
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
125
126
  const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
@@ -242,6 +242,12 @@ exports.doctor = new commander_1.Command("doctor")
242
242
  return p.status === "indexed" &&
243
243
  ((_a = p.chunkerVersion) !== null && _a !== void 0 ? _a : 1) < config_1.CONFIG.CHUNKER_VERSION;
244
244
  });
245
+ // Projects whose stored embedding model/dim no longer matches the global
246
+ // config. Visibility only — recovery is a manual `gmax index --reset`
247
+ // (a dim change can't be auto-fixed in the shared fixed-dim table; that's
248
+ // the deferred Phase 1B re-embed work).
249
+ const staleEmbeddingProjects = projects.filter((p) => p.status === "indexed" &&
250
+ (0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, { modelTier: globalConfig.modelTier, vectorDim: globalConfig.vectorDim }) !== null);
245
251
  if (opts.agent) {
246
252
  const fields = [
247
253
  "index_health",
@@ -257,6 +263,7 @@ exports.doctor = new commander_1.Command("doctor")
257
263
  `daemon=${daemonUp ? "running" : "stopped"}`,
258
264
  `orphaned=${orphanedProjects.length}`,
259
265
  `stale_chunker=${staleChunkerProjects.length}`,
266
+ `stale_embedding=${staleEmbeddingProjects.length}`,
260
267
  ];
261
268
  console.log(fields.join("\t"));
262
269
  for (const p of staleChunkerProjects) {
@@ -273,6 +280,22 @@ exports.doctor = new commander_1.Command("doctor")
273
280
  `fix=gmax index --reset (in ${p.root})`,
274
281
  ].join("\t"));
275
282
  }
283
+ for (const p of staleEmbeddingProjects) {
284
+ const gap = (0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, { modelTier: globalConfig.modelTier, vectorDim: globalConfig.vectorDim });
285
+ if (!gap)
286
+ continue;
287
+ console.log([
288
+ "stale_embedding_project",
289
+ `name=${p.name || path.basename(p.root)}`,
290
+ `indexed_model=${gap.fromModel}`,
291
+ `current_model=${gap.toModel}`,
292
+ `indexed_dim=${gap.fromDim}`,
293
+ `current_dim=${gap.toDim}`,
294
+ `dim_changed=${gap.dimChanged}`,
295
+ `severity=${gap.severity}`,
296
+ `fix=gmax index --reset (in ${p.root})`,
297
+ ].join("\t"));
298
+ }
276
299
  }
277
300
  else {
278
301
  console.log("\nIndex Health\n");
@@ -331,6 +354,27 @@ exports.doctor = new commander_1.Command("doctor")
331
354
  console.log(` run 'gmax index --reset' in ${p.root}`);
332
355
  }
333
356
  }
357
+ // Index built with a different embedding model/dim than the current
358
+ // config. A dim change is breaking (search scores are invalid until a
359
+ // re-embed); a same-dim model swap is additive. Unlike the stale-chunker
360
+ // case this is NOT auto-fixed by `--fix`: a dim change can't coexist in
361
+ // the shared fixed-dim table (deferred Phase 1B), so we point at the
362
+ // manual reset instead.
363
+ if (staleEmbeddingProjects.length > 0) {
364
+ const gaps = staleEmbeddingProjects.map((p) => (0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, { modelTier: globalConfig.modelTier, vectorDim: globalConfig.vectorDim }));
365
+ const anyBreaking = gaps.some((g) => (g === null || g === void 0 ? void 0 : g.severity) === "breaking");
366
+ console.log(`${anyBreaking ? "WARN" : "INFO"} Stale embedding: ${staleEmbeddingProjects.length} project(s) indexed with a different embedding model/dim — run 'gmax index --reset' per project`);
367
+ staleEmbeddingProjects.forEach((p, i) => {
368
+ const gap = gaps[i];
369
+ if (!gap)
370
+ return;
371
+ const change = gap.dimChanged
372
+ ? `${gap.fromDim}d→${gap.toDim}d`
373
+ : `model ${gap.fromModel}→${gap.toModel}`;
374
+ console.log(` - ${p.name || path.basename(p.root)} (${change}, ${gap.severity})`);
375
+ console.log(` run 'gmax index --reset' in ${p.root}`);
376
+ });
377
+ }
334
378
  // Projects
335
379
  if (orphanedProjects.length > 0) {
336
380
  console.log(`WARN Orphaned projects: ${orphanedProjects.length} (directories no longer exist)`);
@@ -74,6 +74,7 @@ exports.impact = new commander_1.Command("impact")
74
74
  return;
75
75
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
76
76
  (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
77
+ (0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
77
78
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
78
79
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
79
80
  const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
@@ -131,6 +131,7 @@ exports.peek = new commander_1.Command("peek")
131
131
  try {
132
132
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
133
133
  (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
134
+ (0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
134
135
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
135
136
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
136
137
  const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
@@ -72,6 +72,7 @@ exports.related = new commander_1.Command("related")
72
72
  return;
73
73
  const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
74
74
  (0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
75
+ (0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
75
76
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
76
77
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
77
78
  const absPath = path.resolve(projectRoot, file);
@@ -0,0 +1,531 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.executeServerSearch = executeServerSearch;
46
+ exports.renderSearchOutput = renderSearchOutput;
47
+ const path = __importStar(require("node:path"));
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
+ const cross_project_1 = require("../lib/utils/cross-project");
52
+ const formatter_1 = require("../lib/utils/formatter");
53
+ const import_extractor_1 = require("../lib/utils/import-extractor");
54
+ const search_skeletons_1 = require("./search-skeletons");
55
+ /**
56
+ * Standalone HTTP-server search path. The per-project server answers the query
57
+ * over HTTP and returns JSON; we render it here with the same presentation
58
+ * modes as the local path (minus the modes the server can't precompute).
59
+ *
60
+ * Returns `true` when the server answered and rendering is complete (the caller
61
+ * should return without touching the local path); `false` when the request
62
+ * failed (`!response.ok` or a thrown error) and the caller should fall back to
63
+ * the in-process / daemon-mediated path.
64
+ *
65
+ * Mirrors the original action's structure: a handled server search returns
66
+ * BEFORE the command's main try/finally, so it intentionally skips query
67
+ * logging and gracefulExit.
68
+ */
69
+ function executeServerSearch(params) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ const { server, pattern, exec_path, projectRootForServer, options, minScore, } = params;
72
+ try {
73
+ const response = yield fetch(`http://localhost:${server.port}/search`, {
74
+ method: "POST",
75
+ headers: { "Content-Type": "application/json" },
76
+ body: JSON.stringify({
77
+ query: pattern,
78
+ limit: parseInt(options.m, 10),
79
+ path: exec_path
80
+ ? path.relative(projectRootForServer, path.resolve(exec_path))
81
+ : undefined,
82
+ }),
83
+ });
84
+ if (response.ok) {
85
+ const body = (yield response.json());
86
+ const searchResult = { data: body.results };
87
+ const filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
88
+ if (options.skeleton) {
89
+ yield (0, search_skeletons_1.outputSkeletons)(filteredData, projectRootForServer, parseInt(options.m, 10),
90
+ // Server doesn't easily expose DB instance here in HTTP client mode,
91
+ // but we are in client. Wait, this text implies "Server Search" block.
92
+ // Client talks to server. The server returns JSON.
93
+ // We don't have DB access here.
94
+ // So we pass null, and it will fallback to generating local skeleton (if file exists locally).
95
+ // This is acceptable for Phase 3.
96
+ null);
97
+ return true;
98
+ }
99
+ const compactHits = options.compact ? (0, compact_results_1.toCompactHits)(filteredData) : [];
100
+ if (options.compact) {
101
+ if (!compactHits.length) {
102
+ console.log("No matches found.");
103
+ console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
104
+ process.exitCode = 1;
105
+ }
106
+ else {
107
+ console.log((0, compact_results_1.formatCompactTable)(compactHits, projectRootForServer, pattern, {
108
+ isTTY: !!process.stdout.isTTY,
109
+ plain: !!options.plain,
110
+ }));
111
+ }
112
+ return true; // EXIT
113
+ }
114
+ if (!filteredData.length) {
115
+ console.log("No matches found.");
116
+ console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
117
+ process.exitCode = 1;
118
+ return true; // EXIT
119
+ }
120
+ if (options.agent) {
121
+ const importCache = new Map();
122
+ const getImportsForFile = (absPath) => {
123
+ var _a;
124
+ if (!options.imports || !absPath)
125
+ return "";
126
+ if (!importCache.has(absPath)) {
127
+ importCache.set(absPath, (0, import_extractor_1.extractImports)(absPath));
128
+ }
129
+ return (_a = importCache.get(absPath)) !== null && _a !== void 0 ? _a : "";
130
+ };
131
+ console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, projectRootForServer, {
132
+ includeImports: options.imports,
133
+ query: pattern,
134
+ getImportsForFile,
135
+ explain: options.explain,
136
+ }));
137
+ return true; // EXIT
138
+ }
139
+ const isTTY = process.stdout.isTTY;
140
+ const shouldBePlain = options.plain || !isTTY;
141
+ if (!options.agent && !options.compact) {
142
+ console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
143
+ console.log();
144
+ }
145
+ if (shouldBePlain) {
146
+ const mappedResults = (0, compact_results_1.toTextResults)(filteredData);
147
+ const output = (0, formatter_1.formatTextResults)(mappedResults, pattern, projectRootForServer, {
148
+ isPlain: true,
149
+ compact: options.compact,
150
+ content: options.content,
151
+ perFile: parseInt(options.perFile, 10),
152
+ showScores: options.scores,
153
+ });
154
+ console.log(output);
155
+ }
156
+ else {
157
+ const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
158
+ const output = formatResults(filteredData, projectRootForServer, {
159
+ content: options.content,
160
+ explain: options.explain,
161
+ });
162
+ console.log(output);
163
+ }
164
+ return true; // EXIT successful server search
165
+ }
166
+ }
167
+ catch (e) {
168
+ if (process.env.DEBUG) {
169
+ console.error("[search] server request failed, falling back to local:", e);
170
+ }
171
+ }
172
+ return false;
173
+ });
174
+ }
175
+ /**
176
+ * Presentation stage shared by the daemon-mediated and in-process search paths.
177
+ * Applies the min-score + name-regex post-filters, then dispatches to one of the
178
+ * seven presentation modes (cross-project, agent, skeleton, no-results, compact,
179
+ * context-for-llm, standard). Writes directly to stdout/stderr and may set
180
+ * `process.exitCode`. Returns the post-filter result count for query logging.
181
+ */
182
+ function renderSearchOutput(params) {
183
+ return __awaiter(this, void 0, void 0, function* () {
184
+ var _a, _b, _c, _d, _e, _f, _g;
185
+ const { searchResult, options, minScore, crossProject, pattern, effectiveRoot, projectRoot, vectorDb, precomputedSkeletons, precomputedGraph, indexState, } = params;
186
+ if (!options.agent && ((_a = searchResult.warnings) === null || _a === void 0 ? void 0 : _a.length)) {
187
+ for (const w of searchResult.warnings) {
188
+ console.warn(`Warning: ${w}`);
189
+ }
190
+ }
191
+ // Partial-index signal (Phase 6): when the index is mid-catchup, results
192
+ // may be incomplete. Non-agent renders it now as a warning; agent mode
193
+ // appends a machine-readable footer after the results below.
194
+ if (!options.agent) {
195
+ const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, { agent: false });
196
+ if (footer)
197
+ console.warn(footer);
198
+ }
199
+ let filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
200
+ // Post-filter by symbol name regex
201
+ if (options.name) {
202
+ try {
203
+ const regex = new RegExp(options.name, "i");
204
+ filteredData = filteredData.filter((r) => {
205
+ const defs = Array.isArray(r.defined_symbols) ? r.defined_symbols : [];
206
+ return defs.some((d) => regex.test(d));
207
+ });
208
+ }
209
+ catch (_h) {
210
+ // Invalid regex — skip
211
+ }
212
+ }
213
+ // Build import cache when --imports is requested
214
+ const importCache = new Map();
215
+ const getImportsForFile = (absPath) => {
216
+ var _a;
217
+ if (!options.imports || !absPath)
218
+ return "";
219
+ if (!importCache.has(absPath)) {
220
+ importCache.set(absPath, (0, import_extractor_1.extractImports)(absPath));
221
+ }
222
+ return (_a = importCache.get(absPath)) !== null && _a !== void 0 ? _a : "";
223
+ };
224
+ // Agent mode: ultra-compact one-line-per-result output
225
+ const resultCount = filteredData.length;
226
+ // Cross-project (Phase 6): render grouped by owning project so idioms
227
+ // from different stacks don't blur into one flat list. Only the
228
+ // string-formatter modes reach here — skeleton/context-for-llm/symbol
229
+ // were rejected up front.
230
+ if (crossProject.active) {
231
+ const emitFooter = () => {
232
+ const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, {
233
+ agent: !!options.agent,
234
+ });
235
+ if (footer) {
236
+ if (options.agent)
237
+ console.log(footer);
238
+ else
239
+ console.warn(footer);
240
+ }
241
+ };
242
+ if (!filteredData.length) {
243
+ console.log(options.agent ? "(none)" : "No matches found.");
244
+ process.exitCode = 1;
245
+ emitFooter();
246
+ return { resultCount };
247
+ }
248
+ const getPath = (r) => {
249
+ var _a, _b, _c;
250
+ 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 : "");
251
+ };
252
+ const groups = (0, cross_project_1.groupResultsByProject)(filteredData, crossProject.roots, getPath);
253
+ const isTTY = process.stdout.isTTY;
254
+ const shouldBePlain = options.plain || !isTTY;
255
+ const blocks = [];
256
+ for (const g of groups) {
257
+ let body;
258
+ if (options.agent) {
259
+ body = (0, agent_search_formatter_1.formatAgentSearchResults)(g.items, g.root, {
260
+ includeImports: options.imports,
261
+ query: pattern,
262
+ getImportsForFile,
263
+ explain: options.explain,
264
+ });
265
+ }
266
+ else if (options.compact) {
267
+ body = (0, compact_results_1.formatCompactTable)((0, compact_results_1.toCompactHits)(g.items), g.root, pattern, {
268
+ isTTY: !!isTTY,
269
+ plain: !!options.plain,
270
+ });
271
+ }
272
+ else if (shouldBePlain) {
273
+ body = (0, formatter_1.formatTextResults)((0, compact_results_1.toTextResults)(g.items), pattern, g.root, {
274
+ isPlain: true,
275
+ compact: options.compact,
276
+ content: options.content,
277
+ perFile: parseInt(options.perFile, 10),
278
+ showScores: options.scores,
279
+ });
280
+ }
281
+ else {
282
+ const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
283
+ body = formatResults(g.items, g.root, {
284
+ content: options.content,
285
+ explain: options.explain,
286
+ });
287
+ }
288
+ const header = options.agent
289
+ ? `## ${g.name} (${g.items.length})`
290
+ : `=== ${g.name} (${g.items.length}) ===`;
291
+ blocks.push(`${header}\n${body}`);
292
+ }
293
+ console.log(blocks.join("\n\n"));
294
+ emitFooter();
295
+ return { resultCount };
296
+ }
297
+ if (options.agent) {
298
+ if (!filteredData.length) {
299
+ console.log("(none)");
300
+ process.exitCode = 1;
301
+ }
302
+ else {
303
+ console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, effectiveRoot, {
304
+ includeImports: options.imports,
305
+ query: pattern,
306
+ getImportsForFile,
307
+ explain: options.explain,
308
+ }));
309
+ }
310
+ // Agent trace (compact)
311
+ if (options.symbol && filteredData.length > 0) {
312
+ try {
313
+ let graph = precomputedGraph;
314
+ if (!graph) {
315
+ if (!vectorDb)
316
+ throw new Error("no graph source");
317
+ const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
318
+ const builder = new GraphBuilder(vectorDb, effectiveRoot);
319
+ graph = yield builder.buildGraphMultiHop(pattern, 1);
320
+ }
321
+ if (graph === null || graph === void 0 ? void 0 : graph.center) {
322
+ console.log("---");
323
+ for (const t of graph.callerTree) {
324
+ const rel = t.node.file.startsWith(effectiveRoot)
325
+ ? t.node.file.slice(effectiveRoot.length + 1)
326
+ : t.node.file;
327
+ console.log(`<- ${t.node.symbol} ${rel}:${t.node.line + 1}`);
328
+ }
329
+ for (const c of graph.callees.slice(0, 10)) {
330
+ if (c.file) {
331
+ const rel = c.file.startsWith(effectiveRoot)
332
+ ? c.file.slice(effectiveRoot.length + 1)
333
+ : c.file;
334
+ console.log(`-> ${c.symbol} ${rel}:${c.line + 1}`);
335
+ }
336
+ }
337
+ }
338
+ }
339
+ catch (_j) { }
340
+ }
341
+ // Partial-index footer last, so it's the final line the agent reads —
342
+ // and emitted even on "(none)", where an empty result may just mean the
343
+ // relevant files aren't indexed yet.
344
+ const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, { agent: true });
345
+ if (footer)
346
+ console.log(footer);
347
+ return { resultCount };
348
+ }
349
+ if (options.skeleton) {
350
+ yield (0, search_skeletons_1.outputSkeletons)(filteredData, projectRoot, parseInt(options.m, 10), vectorDb, precomputedSkeletons);
351
+ return { resultCount };
352
+ }
353
+ if (!filteredData.length) {
354
+ console.log("No matches found.");
355
+ console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
356
+ process.exitCode = 1;
357
+ return { resultCount };
358
+ }
359
+ if (options.compact) {
360
+ const compactHits = (0, compact_results_1.toCompactHits)(filteredData);
361
+ console.log((0, compact_results_1.formatCompactTable)(compactHits, projectRoot, pattern, {
362
+ isTTY: !!process.stdout.isTTY,
363
+ plain: !!options.plain,
364
+ }));
365
+ return { resultCount };
366
+ }
367
+ // Context-for-LLM mode: full function body + imports per result
368
+ if (options.contextForLlm) {
369
+ const fs = yield Promise.resolve().then(() => __importStar(require("node:fs")));
370
+ const { extractImportsFromContent } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/import-extractor")));
371
+ const { packByBudget } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/budget-pack")));
372
+ const budget = parseInt(options.budget, 10) || 8000;
373
+ console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
374
+ // Build every candidate blob up front (token cost needs the rendered
375
+ // text), then pack to budget. Token-aware packing skips an oversized
376
+ // chunk and keeps filling with smaller, still-relevant ones rather than
377
+ // aborting the loop — recovering budget the old greedy `break` wasted.
378
+ const candidates = filteredData.map((r, idx) => {
379
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
380
+ 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 : "";
381
+ 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;
382
+ 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;
383
+ const relPath = absP.startsWith(projectRoot)
384
+ ? absP.slice(projectRoot.length + 1)
385
+ : absP;
386
+ const role = r.role || "IMPLEMENTATION";
387
+ const symbol = Array.isArray(r.defined_symbols) &&
388
+ r.defined_symbols.length > 0
389
+ ? r.defined_symbols[0]
390
+ : "";
391
+ let blobText;
392
+ try {
393
+ const content = fs.readFileSync(absP, "utf-8");
394
+ const allLines = content.split("\n");
395
+ const body = allLines
396
+ .slice(startLine, Math.min(endLine + 1, allLines.length))
397
+ .join("\n");
398
+ const imports = extractImportsFromContent(content);
399
+ const blob = [
400
+ `--- ${relPath}:${startLine + 1}${symbol ? ` ${symbol}` : ""} [${role}] ---`,
401
+ ];
402
+ if (imports)
403
+ blob.push("[imports]", imports, "");
404
+ blob.push("[body]", body);
405
+ blobText = blob.join("\n");
406
+ }
407
+ catch (_m) {
408
+ blobText = `--- ${relPath} (file not readable) ---`;
409
+ }
410
+ // Preserve relevance order when scores are absent (rank-derived
411
+ // fallback) so the density tiebreaker never reshuffles arbitrarily.
412
+ const score = typeof r.score === "number"
413
+ ? r.score
414
+ : (filteredData.length - idx) / filteredData.length;
415
+ return { blobText, tokens: Math.ceil(blobText.length / 4), score };
416
+ });
417
+ const pack = packByBudget(candidates.map((c) => ({ tokens: c.tokens, score: c.score })), budget);
418
+ for (const i of pack.selected) {
419
+ console.log(`\n${candidates[i].blobText}`);
420
+ }
421
+ if (pack.dropped > 0) {
422
+ console.log(`\n(budget: ~${pack.tokensUsed}/${budget} tokens, ${pack.dropped} lower-density result${pack.dropped > 1 ? "s" : ""} not shown)`);
423
+ }
424
+ return { resultCount };
425
+ }
426
+ const isTTY = process.stdout.isTTY;
427
+ const shouldBePlain = options.plain || !isTTY;
428
+ if (!options.agent && !options.compact) {
429
+ console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
430
+ console.log();
431
+ }
432
+ // Print imports per unique file before results when --imports is used
433
+ if (options.imports) {
434
+ const seenFiles = new Set();
435
+ for (const r of filteredData) {
436
+ const absP = (_d = (_b = r.path) !== null && _b !== void 0 ? _b : (_c = r.metadata) === null || _c === void 0 ? void 0 : _c.path) !== null && _d !== void 0 ? _d : "";
437
+ if (absP && !seenFiles.has(absP)) {
438
+ seenFiles.add(absP);
439
+ const imports = getImportsForFile(absP);
440
+ if (imports) {
441
+ const relP = absP.startsWith(effectiveRoot)
442
+ ? absP.slice(effectiveRoot.length + 1)
443
+ : absP;
444
+ console.log(`--- imports: ${relP} ---\n${imports}\n`);
445
+ }
446
+ }
447
+ }
448
+ }
449
+ if (shouldBePlain) {
450
+ const mappedResults = (0, compact_results_1.toTextResults)(filteredData);
451
+ const output = (0, formatter_1.formatTextResults)(mappedResults, pattern, projectRoot, {
452
+ isPlain: true,
453
+ compact: options.compact,
454
+ content: options.content,
455
+ perFile: parseInt(options.perFile, 10),
456
+ showScores: options.scores,
457
+ });
458
+ console.log(output);
459
+ if (options.explain) {
460
+ for (const r of filteredData) {
461
+ const b = r.scoreBreakdown;
462
+ if (b) {
463
+ const absP = (_g = (_e = r.path) !== null && _e !== void 0 ? _e : (_f = r.metadata) === null || _f === void 0 ? void 0 : _f.path) !== null && _g !== void 0 ? _g : "";
464
+ const relPath = absP.startsWith(projectRoot)
465
+ ? absP.slice(projectRoot.length + 1)
466
+ : absP;
467
+ 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)}`);
468
+ }
469
+ }
470
+ }
471
+ }
472
+ else {
473
+ // Use new holographic formatter for TTY
474
+ const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
475
+ const output = formatResults(filteredData, projectRoot, {
476
+ content: options.content,
477
+ explain: options.explain,
478
+ });
479
+ console.log(output);
480
+ }
481
+ // Symbol mode: append call graph
482
+ if (options.symbol) {
483
+ try {
484
+ let graph = precomputedGraph;
485
+ if (!graph) {
486
+ if (!vectorDb)
487
+ throw new Error("no graph source");
488
+ const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
489
+ const builder = new GraphBuilder(vectorDb, effectiveRoot);
490
+ graph = yield builder.buildGraphMultiHop(pattern, 1);
491
+ }
492
+ if (graph === null || graph === void 0 ? void 0 : graph.center) {
493
+ const lines = ["\n--- Call graph ---"];
494
+ const centerRel = path.relative(effectiveRoot, graph.center.file);
495
+ lines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
496
+ if (graph.importers.length > 0) {
497
+ const filtered = graph.importers.filter((p) => p !== graph.center.file);
498
+ if (filtered.length > 0) {
499
+ lines.push("Imported by:");
500
+ for (const imp of filtered.slice(0, 10)) {
501
+ lines.push(` ${path.relative(effectiveRoot, imp)}`);
502
+ }
503
+ }
504
+ }
505
+ if (graph.callerTree.length > 0) {
506
+ lines.push("Callers:");
507
+ for (const t of graph.callerTree) {
508
+ lines.push(` <- ${t.node.symbol} ${path.relative(effectiveRoot, t.node.file)}:${t.node.line + 1}`);
509
+ }
510
+ }
511
+ if (graph.callees.length > 0) {
512
+ lines.push("Calls:");
513
+ for (const c of graph.callees.slice(0, 15)) {
514
+ if (c.file) {
515
+ lines.push(` -> ${c.symbol} ${path.relative(effectiveRoot, c.file)}:${c.line + 1}`);
516
+ }
517
+ else {
518
+ lines.push(` -> ${c.symbol} (not indexed)`);
519
+ }
520
+ }
521
+ }
522
+ console.log(lines.join("\n"));
523
+ }
524
+ }
525
+ catch (_k) {
526
+ // Trace failed — skip silently
527
+ }
528
+ }
529
+ return { resultCount };
530
+ });
531
+ }