grepmax 0.17.21 → 0.17.22

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.
@@ -62,8 +62,7 @@ const config_1 = require("../../config");
62
62
  const batch_processor_1 = require("../index/batch-processor");
63
63
  const syncer_1 = require("../index/syncer");
64
64
  const watcher_1 = require("../index/watcher");
65
- const searcher_1 = require("../search/searcher");
66
- const retriever_1 = require("../skeleton/retriever");
65
+ const search_handler_1 = require("./search-handler");
67
66
  const meta_cache_1 = require("../store/meta-cache");
68
67
  const vector_db_1 = require("../store/vector-db");
69
68
  const process_1 = require("../utils/process");
@@ -841,105 +840,19 @@ class Daemon {
841
840
  }
842
841
  search(payload, signal) {
843
842
  return __awaiter(this, void 0, void 0, function* () {
844
- var _a, _b, _c, _d;
845
- if (!this.vectorDb) {
846
- return { ok: false, error: "daemon not ready" };
847
- }
848
- const root = payload.projectRoot;
849
- if (!this.processors.has(root)) {
850
- // A full index (--reset) or the initial index removes/defers the
851
- // processor while (re)building. The partial index is still queryable, so
852
- // answer the search and flag it partial (below) rather than erroring —
853
- // only truly-unwatched, not-indexing projects get "not watched".
854
- const indexingNow = this.indexProgress.has(root) || ((_a = (0, project_registry_1.getProject)(root)) === null || _a === void 0 ? void 0 : _a.status) === "pending";
855
- if (!indexingNow) {
856
- return {
857
- ok: false,
858
- error: "project not watched",
859
- hint: `run: gmax add ${root}`,
860
- };
861
- }
862
- }
863
- let searcher = this.searchers.get(root);
864
- if (!searcher) {
865
- searcher = new searcher_1.Searcher(this.vectorDb);
866
- this.searchers.set(root, searcher);
867
- }
868
- this.lastActivity = Date.now();
869
- let result;
870
- try {
871
- result = yield searcher.search(payload.query, payload.limit, {
872
- rerank: payload.rerank === true,
873
- explain: payload.explain === true,
874
- seeds: payload.seeds,
875
- }, payload.filters, payload.pathPrefix, undefined, signal);
876
- }
877
- catch (err) {
878
- if ((err === null || err === void 0 ? void 0 : err.name) === "AbortError") {
879
- return { ok: false, error: "aborted" };
880
- }
881
- const msg = err instanceof Error ? err.message : String(err);
882
- return { ok: false, error: "search_failed", hint: msg };
883
- }
884
- const response = { ok: true, data: result.data };
885
- if ((_b = result.warnings) === null || _b === void 0 ? void 0 : _b.length)
886
- response.warnings = result.warnings;
887
- // Annotate partial results when the index is still catching up, so an
888
- // agent can caveat or retry. Only attached when actually indexing (the
889
- // formatter suppresses the settled case anyway).
890
- const idx = this.indexState(root);
891
- if (idx.indexing)
892
- response.indexState = idx;
893
- // --skeleton support: fetch per-file skeletons inline so the CLI doesn't
894
- // have to open its own VectorDB. getStoredSkeleton is a single LIMIT-1
895
- // lookup; cheap enough to call for the top N distinct paths.
896
- if (payload.includeSkeletons && result.data.length > 0) {
897
- const limit = payload.skeletonLimit && payload.skeletonLimit > 0 ? payload.skeletonLimit : 5;
898
- const seen = new Set();
899
- const skeletons = {};
900
- for (const chunk of result.data) {
901
- const p = (_c = chunk.path) !== null && _c !== void 0 ? _c : (_d = chunk.metadata) === null || _d === void 0 ? void 0 : _d.path;
902
- if (!p || seen.has(p))
903
- continue;
904
- seen.add(p);
905
- if (seen.size > limit)
906
- break;
907
- try {
908
- const sk = yield (0, retriever_1.getStoredSkeleton)(this.vectorDb, p);
909
- if (sk)
910
- skeletons[p] = sk;
911
- }
912
- catch (_e) {
913
- // best-effort — drop the entry, keep the search result
914
- }
915
- }
916
- if (Object.keys(skeletons).length > 0)
917
- response.skeletons = skeletons;
918
- }
919
- // --symbol support: build a 1-hop graph using the warm vectorDb. ~5
920
- // LanceDB queries; doesn't touch the worker pool.
921
- if (payload.includeGraph) {
922
- try {
923
- const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../graph/graph-builder")));
924
- const builder = new GraphBuilder(this.vectorDb, root);
925
- response.graph = yield builder.buildGraphMultiHop(payload.query, 1);
926
- }
927
- catch (_f) {
928
- // best-effort — drop graph, keep results
929
- }
930
- }
931
- // 2 MB cap on the JSON line. Lance can return huge chunks for unusual
932
- // queries (very long markdown blobs). Above this we fall back to the
933
- // in-process path which writes to stdout instead of a socket.
934
- const serialized = JSON.stringify(response);
935
- if (serialized.length > 2 * 1024 * 1024) {
936
- return {
937
- ok: false,
938
- error: "oversize",
939
- hint: `${serialized.length} bytes — falling back to in-process search`,
940
- };
941
- }
942
- return response;
843
+ // Search handling lives in search-handler.ts (Phase 12 split). The daemon
844
+ // supplies its warm VectorDB + watcher/index bookkeeping; the handler runs
845
+ // the query and assembles the response.
846
+ return (0, search_handler_1.handleDaemonSearch)({
847
+ vectorDb: this.vectorDb,
848
+ processors: this.processors,
849
+ indexProgress: this.indexProgress,
850
+ searchers: this.searchers,
851
+ getIndexState: (root) => this.indexState(root),
852
+ touchActivity: () => {
853
+ this.lastActivity = Date.now();
854
+ },
855
+ }, payload, signal);
943
856
  });
944
857
  }
945
858
  listProjects() {
@@ -0,0 +1,159 @@
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.handleDaemonSearch = handleDaemonSearch;
46
+ const searcher_1 = require("../search/searcher");
47
+ const retriever_1 = require("../skeleton/retriever");
48
+ const project_registry_1 = require("../utils/project-registry");
49
+ /**
50
+ * Daemon-side search: runs the hybrid+rerank against the already-warm VectorDB
51
+ * and optionally attaches inline skeletons / a 1-hop graph / a partial-index
52
+ * footer. Extracted from Daemon.search() (Phase 12) — behavior-preserving.
53
+ */
54
+ function handleDaemonSearch(deps, payload, signal) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ var _a, _b, _c, _d;
57
+ const { vectorDb } = deps;
58
+ if (!vectorDb) {
59
+ return { ok: false, error: "daemon not ready" };
60
+ }
61
+ const root = payload.projectRoot;
62
+ if (!deps.processors.has(root)) {
63
+ // A full index (--reset) or the initial index removes/defers the
64
+ // processor while (re)building. The partial index is still queryable, so
65
+ // answer the search and flag it partial (below) rather than erroring —
66
+ // only truly-unwatched, not-indexing projects get "not watched".
67
+ const indexingNow = deps.indexProgress.has(root) || ((_a = (0, project_registry_1.getProject)(root)) === null || _a === void 0 ? void 0 : _a.status) === "pending";
68
+ if (!indexingNow) {
69
+ return {
70
+ ok: false,
71
+ error: "project not watched",
72
+ hint: `run: gmax add ${root}`,
73
+ };
74
+ }
75
+ }
76
+ let searcher = deps.searchers.get(root);
77
+ if (!searcher) {
78
+ searcher = new searcher_1.Searcher(vectorDb);
79
+ deps.searchers.set(root, searcher);
80
+ }
81
+ deps.touchActivity();
82
+ let result;
83
+ try {
84
+ result = yield searcher.search(payload.query, payload.limit, {
85
+ rerank: payload.rerank === true,
86
+ explain: payload.explain === true,
87
+ seeds: payload.seeds,
88
+ }, payload.filters, payload.pathPrefix, undefined, signal);
89
+ }
90
+ catch (err) {
91
+ if ((err === null || err === void 0 ? void 0 : err.name) === "AbortError") {
92
+ return { ok: false, error: "aborted" };
93
+ }
94
+ const msg = err instanceof Error ? err.message : String(err);
95
+ return { ok: false, error: "search_failed", hint: msg };
96
+ }
97
+ const response = { ok: true, data: result.data };
98
+ if ((_b = result.warnings) === null || _b === void 0 ? void 0 : _b.length)
99
+ response.warnings = result.warnings;
100
+ // Annotate partial results when the index is still catching up, so an
101
+ // agent can caveat or retry. Only attached when actually indexing (the
102
+ // formatter suppresses the settled case anyway).
103
+ const idx = deps.getIndexState(root);
104
+ if (idx.indexing)
105
+ response.indexState = idx;
106
+ // --skeleton support: fetch per-file skeletons inline so the CLI doesn't
107
+ // have to open its own VectorDB. getStoredSkeleton is a single LIMIT-1
108
+ // lookup; cheap enough to call for the top N distinct paths.
109
+ if (payload.includeSkeletons && result.data.length > 0) {
110
+ const limit = payload.skeletonLimit && payload.skeletonLimit > 0
111
+ ? payload.skeletonLimit
112
+ : 5;
113
+ const seen = new Set();
114
+ const skeletons = {};
115
+ for (const chunk of result.data) {
116
+ const p = (_c = chunk.path) !== null && _c !== void 0 ? _c : (_d = chunk.metadata) === null || _d === void 0 ? void 0 : _d.path;
117
+ if (!p || seen.has(p))
118
+ continue;
119
+ seen.add(p);
120
+ if (seen.size > limit)
121
+ break;
122
+ try {
123
+ const sk = yield (0, retriever_1.getStoredSkeleton)(vectorDb, p);
124
+ if (sk)
125
+ skeletons[p] = sk;
126
+ }
127
+ catch (_e) {
128
+ // best-effort — drop the entry, keep the search result
129
+ }
130
+ }
131
+ if (Object.keys(skeletons).length > 0)
132
+ response.skeletons = skeletons;
133
+ }
134
+ // --symbol support: build a 1-hop graph using the warm vectorDb. ~5
135
+ // LanceDB queries; doesn't touch the worker pool.
136
+ if (payload.includeGraph) {
137
+ try {
138
+ const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../graph/graph-builder")));
139
+ const builder = new GraphBuilder(vectorDb, root);
140
+ response.graph = yield builder.buildGraphMultiHop(payload.query, 1);
141
+ }
142
+ catch (_f) {
143
+ // best-effort — drop graph, keep results
144
+ }
145
+ }
146
+ // 2 MB cap on the JSON line. Lance can return huge chunks for unusual
147
+ // queries (very long markdown blobs). Above this we fall back to the
148
+ // in-process path which writes to stdout instead of a socket.
149
+ const serialized = JSON.stringify(response);
150
+ if (serialized.length > 2 * 1024 * 1024) {
151
+ return {
152
+ ok: false,
153
+ error: "oversize",
154
+ hint: `${serialized.length} bytes — falling back to in-process search`,
155
+ };
156
+ }
157
+ return response;
158
+ });
159
+ }
@@ -0,0 +1,244 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.toTextResults = toTextResults;
37
+ exports.toCompactHits = toCompactHits;
38
+ exports.formatCompactTable = formatCompactTable;
39
+ exports.resultCountHeader = resultCountHeader;
40
+ const path = __importStar(require("node:path"));
41
+ function toTextResults(data) {
42
+ return data.map((r) => {
43
+ var _a, _b, _c, _d, _e, _f;
44
+ const rawPath = typeof ((_a = r.metadata) === null || _a === void 0 ? void 0 : _a.path) === "string"
45
+ ? r.metadata.path
46
+ : "Unknown path";
47
+ const start = typeof ((_b = r.generated_metadata) === null || _b === void 0 ? void 0 : _b.start_line) === "number"
48
+ ? r.generated_metadata.start_line
49
+ : 0;
50
+ const end = typeof ((_c = r.generated_metadata) === null || _c === void 0 ? void 0 : _c.end_line) === "number"
51
+ ? r.generated_metadata.end_line
52
+ : start + Math.max(0, ((_e = (_d = r.generated_metadata) === null || _d === void 0 ? void 0 : _d.num_lines) !== null && _e !== void 0 ? _e : 1) - 1);
53
+ return {
54
+ path: rawPath,
55
+ score: r.score,
56
+ content: r.text || "",
57
+ chunk_type: (_f = r.generated_metadata) === null || _f === void 0 ? void 0 : _f.type,
58
+ start_line: start,
59
+ end_line: end,
60
+ };
61
+ });
62
+ }
63
+ function getPreviewText(chunk) {
64
+ var _a, _b, _c, _d, _e;
65
+ const maxLen = 140;
66
+ const lines = (_b = (_a = chunk.text) === null || _a === void 0 ? void 0 : _a.split("\n").map((l) => l.trim()).filter(Boolean)) !== null && _b !== void 0 ? _b : [];
67
+ let preview = (_c = lines[0]) !== null && _c !== void 0 ? _c : "";
68
+ if (!preview && ((_d = chunk.defined_symbols) === null || _d === void 0 ? void 0 : _d.length)) {
69
+ preview = (_e = chunk.defined_symbols[0]) !== null && _e !== void 0 ? _e : "";
70
+ }
71
+ if (preview.length > maxLen) {
72
+ preview = `${preview.slice(0, maxLen)}...`;
73
+ }
74
+ return preview;
75
+ }
76
+ function toCompactHits(data) {
77
+ return data.map((chunk) => {
78
+ var _a, _b, _c, _d, _e, _f;
79
+ const rawPath = typeof ((_a = chunk.metadata) === null || _a === void 0 ? void 0 : _a.path) === "string"
80
+ ? chunk.metadata.path
81
+ : "Unknown path";
82
+ const start = typeof ((_b = chunk.generated_metadata) === null || _b === void 0 ? void 0 : _b.start_line) === "number"
83
+ ? chunk.generated_metadata.start_line
84
+ : 0;
85
+ const end = typeof ((_c = chunk.generated_metadata) === null || _c === void 0 ? void 0 : _c.end_line) === "number"
86
+ ? chunk.generated_metadata.end_line
87
+ : start + Math.max(0, ((_e = (_d = chunk.generated_metadata) === null || _d === void 0 ? void 0 : _d.num_lines) !== null && _e !== void 0 ? _e : 1) - 1);
88
+ return {
89
+ path: rawPath,
90
+ range: `${start + 1}-${end + 1}`,
91
+ start_line: start,
92
+ end_line: end,
93
+ role: chunk.role,
94
+ confidence: chunk.confidence,
95
+ score: chunk.score,
96
+ defined: Array.isArray(chunk.defined_symbols)
97
+ ? chunk.defined_symbols.slice(0, 3)
98
+ : typeof chunk.defined_symbols === "string"
99
+ ? [chunk.defined_symbols]
100
+ : typeof ((_f = chunk.defined_symbols) === null || _f === void 0 ? void 0 : _f.toArray) === "function"
101
+ ? chunk.defined_symbols.toArray().slice(0, 3)
102
+ : [],
103
+ preview: getPreviewText(chunk),
104
+ summary: typeof chunk.summary === "string" ? chunk.summary : undefined,
105
+ };
106
+ });
107
+ }
108
+ function compactRole(role) {
109
+ if (!role)
110
+ return "UNK";
111
+ if (role.startsWith("ORCH"))
112
+ return "ORCH";
113
+ if (role.startsWith("DEF"))
114
+ return "DEF";
115
+ if (role.startsWith("IMP"))
116
+ return "IMPL";
117
+ return role.slice(0, 4).toUpperCase();
118
+ }
119
+ function compactConf(conf) {
120
+ if (!conf)
121
+ return "U";
122
+ const c = conf.toUpperCase();
123
+ if (c.startsWith("H"))
124
+ return "H";
125
+ if (c.startsWith("M"))
126
+ return "M";
127
+ if (c.startsWith("L"))
128
+ return "L";
129
+ return "U";
130
+ }
131
+ function compactScore(score) {
132
+ if (typeof score !== "number")
133
+ return "";
134
+ const fixed = score.toFixed(3);
135
+ return fixed
136
+ .replace(/^0\./, ".")
137
+ .replace(/\.?0+$/, (m) => (m.startsWith(".") ? "" : m));
138
+ }
139
+ function truncateEnd(s, max) {
140
+ if (max <= 0)
141
+ return "";
142
+ if (s.length <= max)
143
+ return s;
144
+ if (max <= 3)
145
+ return s.slice(0, max);
146
+ return `${s.slice(0, max - 3)}...`;
147
+ }
148
+ function padR(s, w) {
149
+ const n = Math.max(0, w - s.length);
150
+ return s + " ".repeat(n);
151
+ }
152
+ function padL(s, w) {
153
+ const n = Math.max(0, w - s.length);
154
+ return " ".repeat(n) + s;
155
+ }
156
+ function formatCompactTSV(hits, projectRoot, query) {
157
+ var _a, _b;
158
+ if (!hits.length)
159
+ return "No matches found.";
160
+ const lines = [];
161
+ lines.push(`gmax hits\tquery=${query}\tcount=${hits.length}`);
162
+ lines.push("path\tlines\tscore\trole\tconf\tdefined\tsummary");
163
+ for (const hit of hits) {
164
+ const relPath = path.isAbsolute(hit.path)
165
+ ? path.relative(projectRoot, hit.path)
166
+ : hit.path;
167
+ const score = compactScore(hit.score);
168
+ const role = compactRole(hit.role);
169
+ const conf = compactConf(hit.confidence);
170
+ const defs = ((_a = hit.defined) !== null && _a !== void 0 ? _a : []).join(",");
171
+ const summary = (_b = hit.summary) !== null && _b !== void 0 ? _b : "";
172
+ lines.push([relPath, hit.range, score, role, conf, defs, summary].join("\t"));
173
+ }
174
+ return lines.join("\n");
175
+ }
176
+ function formatCompactPretty(hits, projectRoot, query, termWidth, useAnsi) {
177
+ var _a;
178
+ if (!hits.length)
179
+ return "No matches found.";
180
+ const dim = (s) => (useAnsi ? `\x1b[90m${s}\x1b[0m` : s);
181
+ const bold = (s) => (useAnsi ? `\x1b[1m${s}\x1b[0m` : s);
182
+ const wLines = 9;
183
+ const wScore = 6;
184
+ const wRole = 4;
185
+ const wConf = 1;
186
+ const wDef = 20;
187
+ const gutters = 5;
188
+ const fixed = wLines + wScore + wRole + wConf + wDef + gutters;
189
+ const wPath = Math.max(24, Math.min(64, termWidth - fixed));
190
+ const header = `gmax hits count=${hits.length} query="${query}"`;
191
+ const cols = [
192
+ padR("path", wPath),
193
+ padR("lines", wLines),
194
+ padL("score", wScore),
195
+ padR("role", wRole),
196
+ padR("c", wConf),
197
+ padR("defined", wDef),
198
+ ].join(" ");
199
+ const out = [];
200
+ out.push(bold(header));
201
+ out.push(dim(cols));
202
+ for (const hit of hits) {
203
+ const relPath = path.isAbsolute(hit.path)
204
+ ? path.relative(projectRoot, hit.path)
205
+ : hit.path;
206
+ const score = compactScore(hit.score);
207
+ const role = compactRole(hit.role);
208
+ const conf = compactConf(hit.confidence);
209
+ const defs = ((_a = hit.defined) !== null && _a !== void 0 ? _a : []).join(",") || "-";
210
+ const displayPath = `${relPath}:${hit.start_line + 1}`;
211
+ const paddedPath = padR(displayPath, wPath);
212
+ const row = [
213
+ paddedPath,
214
+ padR(hit.range, wLines),
215
+ padL(score || "", wScore),
216
+ padR(role, wRole),
217
+ padR(conf, wConf),
218
+ padR(truncateEnd(defs, wDef), wDef),
219
+ ].join(" ");
220
+ out.push(row);
221
+ }
222
+ return out.join("\n");
223
+ }
224
+ function formatCompactTable(hits, projectRoot, query, opts) {
225
+ var _a;
226
+ if (!hits.length)
227
+ return "No matches found.";
228
+ if (!opts.isTTY || opts.plain) {
229
+ return formatCompactTSV(hits, projectRoot, query);
230
+ }
231
+ const termWidth = Math.max(80, (_a = process.stdout.columns) !== null && _a !== void 0 ? _a : 120);
232
+ return formatCompactPretty(hits, projectRoot, query, termWidth, true);
233
+ }
234
+ function resultCountHeader(results, maxCount) {
235
+ var _a, _b, _c;
236
+ const files = new Set();
237
+ for (const r of results) {
238
+ const p = (_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 : "";
239
+ if (p)
240
+ files.add(p);
241
+ }
242
+ const showing = results.length < maxCount ? `${results.length}` : `top ${results.length}`;
243
+ return `Found ${results.length} match${results.length === 1 ? "" : "es"} (showing ${showing}) across ${files.size} file${files.size === 1 ? "" : "s"}`;
244
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.17.21",
3
+ "version": "0.17.22",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.17.21",
3
+ "version": "0.17.22",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",