grepmax 0.17.21 → 0.17.23
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.
- package/dist/commands/add.js +3 -2
- package/dist/commands/audit.js +19 -2
- package/dist/commands/context.js +34 -2
- package/dist/commands/doctor.js +18 -0
- package/dist/commands/impact.js +24 -14
- package/dist/commands/index.js +16 -6
- package/dist/commands/mcp.js +2 -0
- package/dist/commands/peek.js +94 -17
- package/dist/commands/project.js +17 -4
- package/dist/commands/related.js +5 -3
- package/dist/commands/search-run.js +227 -0
- package/dist/commands/search-skeletons.js +133 -0
- package/dist/commands/search.js +46 -450
- package/dist/commands/test-find.js +8 -14
- package/dist/commands/trace.js +2 -29
- package/dist/config.js +5 -0
- package/dist/eval-like-limit-probe.js +62 -0
- package/dist/lib/daemon/daemon.js +15 -102
- package/dist/lib/daemon/search-handler.js +159 -0
- package/dist/lib/graph/callsites.js +148 -0
- package/dist/lib/graph/graph-builder.js +7 -3
- package/dist/lib/graph/impact.js +24 -11
- package/dist/lib/graph/test-hits.js +54 -0
- package/dist/lib/help/agent-cheatsheet.js +5 -0
- package/dist/lib/index/chunker.js +36 -2
- package/dist/lib/output/agent-search-formatter.js +59 -3
- package/dist/lib/output/compact-results.js +244 -0
- package/dist/lib/search/searcher.js +2 -1
- package/dist/lib/utils/query-timeout.js +52 -0
- package/package.json +2 -2
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
package/dist/lib/graph/impact.js
CHANGED
|
@@ -14,6 +14,7 @@ exports.resolveTargetSymbols = resolveTargetSymbols;
|
|
|
14
14
|
exports.findTests = findTests;
|
|
15
15
|
exports.findDependents = findDependents;
|
|
16
16
|
const filter_builder_1 = require("../utils/filter-builder");
|
|
17
|
+
const query_timeout_1 = require("../utils/query-timeout");
|
|
17
18
|
const graph_builder_1 = require("./graph-builder");
|
|
18
19
|
const TEST_DIR_RE = /(^|\/)(__tests__|tests?|specs?|benchmark)(\/|$)/i;
|
|
19
20
|
const TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
|
|
@@ -90,7 +91,11 @@ function expandFileSymbols(symbols, vectorDb, projectRoot, excludePrefixes) {
|
|
|
90
91
|
expanded.add(s);
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
+
// Cap the fan-out: a large class file can define 50+ symbols, and every
|
|
95
|
+
// expanded symbol costs one caller-scan (and more in the fallback). The
|
|
96
|
+
// original target stays first; co-defined symbols are best-effort extras.
|
|
97
|
+
const MAX_EXPANDED = 15;
|
|
98
|
+
return [...expanded].slice(0, MAX_EXPANDED);
|
|
94
99
|
});
|
|
95
100
|
}
|
|
96
101
|
/**
|
|
@@ -111,7 +116,7 @@ function findTests(symbols_1, vectorDb_1, projectRoot_1) {
|
|
|
111
116
|
yield walkCallers(symbol, graphBuilder, testHits, 0, depth, new Set());
|
|
112
117
|
}
|
|
113
118
|
if (testHits.size === 0) {
|
|
114
|
-
const importFiles = yield findImportFallbackTests(expanded, vectorDb, projectRoot, excludePrefixes);
|
|
119
|
+
const importFiles = yield findImportFallbackTests(expanded, symbols, vectorDb, projectRoot, excludePrefixes);
|
|
115
120
|
for (const file of importFiles) {
|
|
116
121
|
testHits.set(`${file}:(referenced)`, {
|
|
117
122
|
file,
|
|
@@ -124,12 +129,13 @@ function findTests(symbols_1, vectorDb_1, projectRoot_1) {
|
|
|
124
129
|
return [...testHits.values()].sort((a, b) => a.hops - b.hops || a.file.localeCompare(b.file));
|
|
125
130
|
});
|
|
126
131
|
}
|
|
127
|
-
function findImportFallbackTests(
|
|
132
|
+
function findImportFallbackTests(expandedSymbols, originalSymbols, vectorDb, projectRoot, excludePrefixes) {
|
|
128
133
|
return __awaiter(this, void 0, void 0, function* () {
|
|
129
134
|
const files = new Set();
|
|
130
135
|
// Signal 1: referenced_symbols match (precise; works when the chunker
|
|
131
|
-
// captured call references in test bodies).
|
|
132
|
-
|
|
136
|
+
// captured call references in test bodies). Uses the expanded set so tests
|
|
137
|
+
// that call a method of the target class still match.
|
|
138
|
+
const dependents = yield findDependents(expandedSymbols, vectorDb, projectRoot, undefined, 50, excludePrefixes);
|
|
133
139
|
for (const d of dependents) {
|
|
134
140
|
if (isTestPath(d.file))
|
|
135
141
|
files.add(d.file);
|
|
@@ -143,14 +149,18 @@ function findImportFallbackTests(symbols, vectorDb, projectRoot, excludePrefixes
|
|
|
143
149
|
const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
|
|
144
150
|
pathScope += ` AND path NOT LIKE '${(0, filter_builder_1.escapeSqlString)(exNorm)}%'`;
|
|
145
151
|
}
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
// Textual matching runs on the ORIGINAL targets only: matching co-defined
|
|
153
|
+
// file symbols (helpers like `log`) textually drags in every test file
|
|
154
|
+
// that mentions them, drowning the answer in false positives.
|
|
155
|
+
for (const sym of originalSymbols) {
|
|
156
|
+
// No .limit() here: LIKE + limit deadlocks in @lancedb 0.27.x when more
|
|
157
|
+
// rows match than the limit (verified). Unlimited scan is fast; cap in JS.
|
|
158
|
+
const rows = yield (0, query_timeout_1.withQueryTimeout)(table
|
|
148
159
|
.query()
|
|
149
160
|
.select(["path"])
|
|
150
161
|
.where(`content LIKE '%${(0, filter_builder_1.escapeSqlString)(sym)}%' AND ${pathScope}`)
|
|
151
|
-
.
|
|
152
|
-
|
|
153
|
-
for (const row of rows) {
|
|
162
|
+
.toArray(), `content LIKE %${sym}% (test fallback)`);
|
|
163
|
+
for (const row of rows.slice(0, 500)) {
|
|
154
164
|
const p = String(row.path || "");
|
|
155
165
|
if (isTestPath(p))
|
|
156
166
|
files.add(p);
|
|
@@ -198,11 +208,14 @@ function findDependents(symbols_1, vectorDb_1, projectRoot_1, excludePaths_1) {
|
|
|
198
208
|
}
|
|
199
209
|
const counts = new Map();
|
|
200
210
|
for (const sym of symbols) {
|
|
211
|
+
// 200, not 20: with per-chunk rows a popular symbol easily exceeds 20
|
|
212
|
+
// chunks, and truncation here silently drops whole dependent files.
|
|
213
|
+
// (array_contains + limit does not hit the LIKE+limit native hang.)
|
|
201
214
|
const rows = yield table
|
|
202
215
|
.query()
|
|
203
216
|
.select(["path"])
|
|
204
217
|
.where(`array_contains(referenced_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}') AND ${pathScope}`)
|
|
205
|
-
.limit(
|
|
218
|
+
.limit(200)
|
|
206
219
|
.toArray();
|
|
207
220
|
for (const row of rows) {
|
|
208
221
|
const p = String(row.path || "");
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.groupTestHitsByFile = groupTestHitsByFile;
|
|
4
|
+
exports.formatViaAgent = formatViaAgent;
|
|
5
|
+
exports.formatViaHuman = formatViaHuman;
|
|
6
|
+
exports.hopLabelAgent = hopLabelAgent;
|
|
7
|
+
exports.hopLabelHuman = hopLabelHuman;
|
|
8
|
+
function groupTestHitsByFile(hits) {
|
|
9
|
+
const byFile = new Map();
|
|
10
|
+
// Sort by hops so the first hit per file carries the best line/hops and
|
|
11
|
+
// `via` lists closest callers first ((referenced) fallback hits sort last).
|
|
12
|
+
const ordered = [...hits].sort((a, b) => (a.hops === -1 ? Number.MAX_SAFE_INTEGER : a.hops) -
|
|
13
|
+
(b.hops === -1 ? Number.MAX_SAFE_INTEGER : b.hops));
|
|
14
|
+
for (const h of ordered) {
|
|
15
|
+
let g = byFile.get(h.file);
|
|
16
|
+
if (!g) {
|
|
17
|
+
g = { file: h.file, line: h.line, hops: h.hops, via: [] };
|
|
18
|
+
byFile.set(h.file, g);
|
|
19
|
+
}
|
|
20
|
+
if (h.symbol && h.symbol !== "(referenced)" && !g.via.includes(h.symbol)) {
|
|
21
|
+
g.via.push(h.symbol);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [...byFile.values()].sort((a, b) => (a.hops === -1 ? Number.MAX_SAFE_INTEGER : a.hops) -
|
|
25
|
+
(b.hops === -1 ? Number.MAX_SAFE_INTEGER : b.hops) ||
|
|
26
|
+
a.file.localeCompare(b.file));
|
|
27
|
+
}
|
|
28
|
+
const MAX_VIA = 3;
|
|
29
|
+
/** `via=helperA,helperB(+2)` agent detail, or "" when there is none. */
|
|
30
|
+
function formatViaAgent(via) {
|
|
31
|
+
if (via.length === 0)
|
|
32
|
+
return "";
|
|
33
|
+
const shown = via.slice(0, MAX_VIA).join(",");
|
|
34
|
+
const more = via.length > MAX_VIA ? `(+${via.length - MAX_VIA})` : "";
|
|
35
|
+
return `\tvia=${shown}${more}`;
|
|
36
|
+
}
|
|
37
|
+
/** `, via helperA, helperB (+2 more)` human detail, or "" when none. */
|
|
38
|
+
function formatViaHuman(via) {
|
|
39
|
+
if (via.length === 0)
|
|
40
|
+
return "";
|
|
41
|
+
const shown = via.slice(0, MAX_VIA).join(", ");
|
|
42
|
+
const more = via.length > MAX_VIA ? ` (+${via.length - MAX_VIA} more)` : "";
|
|
43
|
+
return `, via ${shown}${more}`;
|
|
44
|
+
}
|
|
45
|
+
function hopLabelAgent(hops) {
|
|
46
|
+
return hops === -1 ? "via-import" : hops === 0 ? "direct" : `${hops}-hop`;
|
|
47
|
+
}
|
|
48
|
+
function hopLabelHuman(hops) {
|
|
49
|
+
return hops === -1
|
|
50
|
+
? "via import"
|
|
51
|
+
: hops === 0
|
|
52
|
+
? "calls directly"
|
|
53
|
+
: `${hops} hop${hops > 1 ? "s" : ""} away`;
|
|
54
|
+
}
|
|
@@ -37,6 +37,11 @@ Survey:
|
|
|
37
37
|
|
|
38
38
|
Scope flags: --root <name|path>, --in <subpath>, --exclude <subpath>.
|
|
39
39
|
Roles in results: [DEFI] [ORCH] [IMPL] [DOCS].
|
|
40
|
+
Notation: s=/d= score/distance (higher s / lower d = better) · C:n complexity ·
|
|
41
|
+
<- caller / -> callee · dep:/rev: outbound/inbound file deps (count = shared symbols) ·
|
|
42
|
+
test hops: direct = calls it, N-hop = N calls away, via-import = imports but call unseen ·
|
|
43
|
+
test via=X,Y: caller symbols inside the test file (often helpers, not the tests) ·
|
|
44
|
+
(not indexed) = external/builtin, no definition in the index.
|
|
40
45
|
Recovery: "not added yet" → gmax add; stale → gmax index; broken → gmax doctor --fix.`;
|
|
41
46
|
/** Full SessionStart context = prefix + cheatsheet. */
|
|
42
47
|
exports.SESSION_START_HINT = `${exports.SESSION_START_PREFIX}\n\n${exports.AGENT_CHEATSHEET}`;
|
|
@@ -811,6 +811,40 @@ class TreeSitterChunker {
|
|
|
811
811
|
return { chunks: combined, metadata };
|
|
812
812
|
});
|
|
813
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Restrict a sub-chunk's symbol lists to names that actually occur in its
|
|
816
|
+
* content slice. `{...chunk}` would otherwise copy the parent's full
|
|
817
|
+
* definedSymbols/referencedSymbols into every sub-chunk, fabricating graph
|
|
818
|
+
* edges: each sub-chunk of a large class "defines" the class and
|
|
819
|
+
* "references" everything the class references, so caller/dependent queries
|
|
820
|
+
* return one phantom hit per sub-chunk (verified: 3 real call sites → 66).
|
|
821
|
+
*/
|
|
822
|
+
static scopeSymbolsToContent(sub) {
|
|
823
|
+
const occurs = (s) => {
|
|
824
|
+
if (!s)
|
|
825
|
+
return false;
|
|
826
|
+
if (/^\w+$/.test(s)) {
|
|
827
|
+
return new RegExp(`\\b${s}\\b`).test(sub.content);
|
|
828
|
+
}
|
|
829
|
+
// Names with non-word chars ($foo, a.b) — substring match is the best
|
|
830
|
+
// we can do without a parser; over-keeping is safer than dropping edges.
|
|
831
|
+
return sub.content.includes(s);
|
|
832
|
+
};
|
|
833
|
+
if (sub.definedSymbols) {
|
|
834
|
+
const original = sub.definedSymbols;
|
|
835
|
+
sub.definedSymbols = original.filter(occurs);
|
|
836
|
+
// A mid-body slice defines nothing itself — keep the enclosing
|
|
837
|
+
// definition's name as parentSymbol so graph consumers can attribute
|
|
838
|
+
// the chunk ("Daemon" beats "unknown").
|
|
839
|
+
if (sub.definedSymbols.length === 0 && !sub.parentSymbol && original[0]) {
|
|
840
|
+
sub.parentSymbol = original[0];
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (sub.referencedSymbols) {
|
|
844
|
+
sub.referencedSymbols = sub.referencedSymbols.filter(occurs);
|
|
845
|
+
}
|
|
846
|
+
return sub;
|
|
847
|
+
}
|
|
814
848
|
splitIfTooBig(chunk) {
|
|
815
849
|
const charCount = chunk.content.length;
|
|
816
850
|
const lines = chunk.content.split("\n");
|
|
@@ -830,7 +864,7 @@ class TreeSitterChunker {
|
|
|
830
864
|
const subLines = lines.slice(i, end);
|
|
831
865
|
if (subLines.length < 3 && i > 0)
|
|
832
866
|
continue;
|
|
833
|
-
subChunks.push(Object.assign(Object.assign({}, chunk), { content: subLines.join("\n"), startLine: chunk.startLine + i, endLine: chunk.startLine + end - 1 }));
|
|
867
|
+
subChunks.push(TreeSitterChunker.scopeSymbolsToContent(Object.assign(Object.assign({}, chunk), { content: subLines.join("\n"), startLine: chunk.startLine + i, endLine: chunk.startLine + end - 1 })));
|
|
834
868
|
}
|
|
835
869
|
return subChunks.flatMap((sc) => sc.content.length > this.MAX_CHUNK_CHARS ? this.splitByChars(sc) : [sc]);
|
|
836
870
|
}
|
|
@@ -844,7 +878,7 @@ class TreeSitterChunker {
|
|
|
844
878
|
continue;
|
|
845
879
|
const prefixLines = chunk.content.slice(0, i).split("\n").length - 1;
|
|
846
880
|
const subLineCount = sub.split("\n").length;
|
|
847
|
-
res.push(Object.assign(Object.assign({}, chunk), { content: sub, startLine: chunk.startLine + prefixLines, endLine: chunk.startLine + prefixLines + subLineCount - 1 }));
|
|
881
|
+
res.push(TreeSitterChunker.scopeSymbolsToContent(Object.assign(Object.assign({}, chunk), { content: sub, startLine: chunk.startLine + prefixLines, endLine: chunk.startLine + prefixLines + subLineCount - 1 })));
|
|
848
882
|
}
|
|
849
883
|
return res;
|
|
850
884
|
}
|
|
@@ -101,11 +101,66 @@ function firstSignatureLine(chunk) {
|
|
|
101
101
|
}
|
|
102
102
|
return "";
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
const QUERY_STOPWORDS = new Set([
|
|
105
|
+
"the", "and", "for", "with", "that", "this", "from", "into", "where",
|
|
106
|
+
"when", "how", "what", "does", "not", "are", "was", "has", "have", "its",
|
|
107
|
+
"all", "can", "use", "uses", "using", "code",
|
|
108
|
+
]);
|
|
109
|
+
function extractQueryTerms(query) {
|
|
110
|
+
if (!query)
|
|
111
|
+
return [];
|
|
112
|
+
const terms = query
|
|
113
|
+
.toLowerCase()
|
|
114
|
+
.split(/[^a-z0-9_]+/)
|
|
115
|
+
.filter((t) => t.length >= 3 && !QUERY_STOPWORDS.has(t));
|
|
116
|
+
return [...new Set(terms)];
|
|
117
|
+
}
|
|
118
|
+
function truncateHint(line) {
|
|
119
|
+
return line.length > 120 ? `${line.slice(0, 117)}...` : line;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The chunk line that best matches the query terms — it shows *why* this
|
|
123
|
+
* chunk hit, where the first signature line only shows what the chunk is.
|
|
124
|
+
* Most distinct terms wins; code beats comments on a tie; earliest otherwise.
|
|
125
|
+
*/
|
|
126
|
+
function queryMatchingLine(chunk, terms) {
|
|
127
|
+
var _a, _b;
|
|
128
|
+
if (!terms.length)
|
|
129
|
+
return "";
|
|
130
|
+
const raw = String((_b = (_a = chunk.content) !== null && _a !== void 0 ? _a : chunk.text) !== null && _b !== void 0 ? _b : "");
|
|
131
|
+
let best = "";
|
|
132
|
+
let bestScore = 0;
|
|
133
|
+
let bestIsComment = true;
|
|
134
|
+
for (const line of raw.split("\n")) {
|
|
135
|
+
const trimmed = line.trim();
|
|
136
|
+
if (!trimmed || trimmed.length < 5)
|
|
137
|
+
continue;
|
|
138
|
+
if (trimmed.startsWith("import ") || trimmed.startsWith("File:"))
|
|
139
|
+
continue;
|
|
140
|
+
if (trimmed === "{" || trimmed === "}")
|
|
141
|
+
continue;
|
|
142
|
+
const lower = trimmed.toLowerCase();
|
|
143
|
+
let score = 0;
|
|
144
|
+
for (const t of terms)
|
|
145
|
+
if (lower.includes(t))
|
|
146
|
+
score++;
|
|
147
|
+
if (score === 0)
|
|
148
|
+
continue;
|
|
149
|
+
const isComment = /^(\/\/|\/\*|\*|#)/.test(trimmed);
|
|
150
|
+
if (score > bestScore ||
|
|
151
|
+
(score === bestScore && bestIsComment && !isComment)) {
|
|
152
|
+
best = trimmed;
|
|
153
|
+
bestScore = score;
|
|
154
|
+
bestIsComment = isComment;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return truncateHint(best);
|
|
158
|
+
}
|
|
159
|
+
function hintFor(chunk, queryTerms) {
|
|
105
160
|
if (typeof chunk.summary === "string" && chunk.summary) {
|
|
106
161
|
return chunk.summary;
|
|
107
162
|
}
|
|
108
|
-
return firstSignatureLine(chunk);
|
|
163
|
+
return queryMatchingLine(chunk, queryTerms) || firstSignatureLine(chunk);
|
|
109
164
|
}
|
|
110
165
|
function explainSuffix(chunk, enabled) {
|
|
111
166
|
if (!enabled || !chunk.scoreBreakdown)
|
|
@@ -117,6 +172,7 @@ function formatAgentSearchResults(results, projectRoot, options = {}) {
|
|
|
117
172
|
var _a, _b;
|
|
118
173
|
if (!results.length)
|
|
119
174
|
return "(none)";
|
|
175
|
+
const queryTerms = extractQueryTerms(options.query);
|
|
120
176
|
const groups = new Map();
|
|
121
177
|
for (const result of results) {
|
|
122
178
|
const absPath = chunkPath(result);
|
|
@@ -152,7 +208,7 @@ function formatAgentSearchResults(results, projectRoot, options = {}) {
|
|
|
152
208
|
const score = typeof result.score === "number"
|
|
153
209
|
? `\ts=${result.score.toFixed(3)}`
|
|
154
210
|
: "";
|
|
155
|
-
const hint = hintFor(result);
|
|
211
|
+
const hint = hintFor(result, queryTerms);
|
|
156
212
|
const locator = grouped
|
|
157
213
|
? ` :${chunkStartLine(result)}`
|
|
158
214
|
: `${rel}:${chunkStartLine(result)}`;
|
|
@@ -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
|
+
}
|
|
@@ -817,7 +817,7 @@ class Searcher {
|
|
|
817
817
|
const displayRows = yield table
|
|
818
818
|
.query()
|
|
819
819
|
.select([
|
|
820
|
-
"id", "defined_symbols", "imports", "exports",
|
|
820
|
+
"id", "defined_symbols", "parent_symbol", "imports", "exports",
|
|
821
821
|
"summary", "file_skeleton",
|
|
822
822
|
])
|
|
823
823
|
.where(`id IN (${finalIds.join(",")})`)
|
|
@@ -828,6 +828,7 @@ class Searcher {
|
|
|
828
828
|
const extra = displayMap.get(item.record.id);
|
|
829
829
|
if (extra) {
|
|
830
830
|
item.record.defined_symbols = extra.defined_symbols;
|
|
831
|
+
item.record.parent_symbol = extra.parent_symbol;
|
|
831
832
|
item.record.imports = extra.imports;
|
|
832
833
|
item.record.exports = extra.exports;
|
|
833
834
|
item.record.summary = extra.summary;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.QueryTimeoutError = void 0;
|
|
13
|
+
exports.withQueryTimeout = withQueryTimeout;
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = Number(process.env.GMAX_QUERY_TIMEOUT_MS || "") || 15000;
|
|
15
|
+
class QueryTimeoutError extends Error {
|
|
16
|
+
constructor(label, ms) {
|
|
17
|
+
super(`LanceDB query timed out after ${ms}ms (${label}). ` +
|
|
18
|
+
`The store may be busy or hitting a native scan bug — retry, or run: gmax doctor`);
|
|
19
|
+
this.name = "QueryTimeoutError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.QueryTimeoutError = QueryTimeoutError;
|
|
23
|
+
/**
|
|
24
|
+
* Race a LanceDB query against a wall-clock timeout so a native-layer deadlock
|
|
25
|
+
* surfaces as a loud error instead of hanging the process forever.
|
|
26
|
+
*
|
|
27
|
+
* Known trigger (@lancedb/lancedb 0.27.x): a `content LIKE` scan with
|
|
28
|
+
* `.limit(N)` where more than N rows match never resolves — the limit-pushdown
|
|
29
|
+
* cancellation loses the completion (see lancedb/lancedb#2189 for the same
|
|
30
|
+
* family of hangs). Callers should also avoid that query shape (scan without
|
|
31
|
+
* a limit and cap in JS); this wrapper is the backstop for shapes we missed.
|
|
32
|
+
*
|
|
33
|
+
* The timed-out native promise is NOT cancelled — its tokio task may stay
|
|
34
|
+
* parked. CLI commands exit via gracefulExit() (process.exit), so the leak is
|
|
35
|
+
* bounded to the command's lifetime. Long-lived callers (daemon) should treat
|
|
36
|
+
* a QueryTimeoutError as a signal that the connection may be wedged.
|
|
37
|
+
*/
|
|
38
|
+
function withQueryTimeout(promise_1, label_1) {
|
|
39
|
+
return __awaiter(this, arguments, void 0, function* (promise, label, ms = DEFAULT_TIMEOUT_MS) {
|
|
40
|
+
let timer;
|
|
41
|
+
const timeout = new Promise((_, reject) => {
|
|
42
|
+
timer = setTimeout(() => reject(new QueryTimeoutError(label, ms)), ms);
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
return yield Promise.race([promise, timeout]);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
if (timer)
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.23",
|
|
4
4
|
"author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
|
|
5
5
|
"homepage": "https://github.com/reowens/grepmax",
|
|
6
6
|
"bugs": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@clack/prompts": "^1.1.0",
|
|
61
61
|
"@huggingface/transformers": "^4.0.0",
|
|
62
|
-
"@lancedb/lancedb": "^0.
|
|
62
|
+
"@lancedb/lancedb": "^0.30.0",
|
|
63
63
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
64
64
|
"@parcel/watcher": "^2.5.6",
|
|
65
65
|
"apache-arrow": "^18.1.0",
|