grepmax 0.18.1 → 0.20.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.
- package/.claude-plugin/marketplace.json +23 -0
- package/README.md +28 -3
- package/dist/commands/add.js +4 -2
- package/dist/commands/claude-code.js +1 -1
- package/dist/commands/codex.js +3 -1
- package/dist/commands/context.js +1 -1
- package/dist/commands/dead.js +2 -6
- package/dist/commands/doctor.js +26 -7
- package/dist/commands/extract.js +3 -5
- package/dist/commands/impact.js +6 -4
- package/dist/commands/index.js +7 -2
- package/dist/commands/log.js +4 -2
- package/dist/commands/mcp.js +594 -722
- package/dist/commands/peek.js +15 -9
- package/dist/commands/plugin.js +36 -22
- package/dist/commands/project.js +16 -6
- package/dist/commands/related.js +8 -9
- package/dist/commands/remove.js +9 -2
- package/dist/commands/review.js +1 -1
- package/dist/commands/setup.js +4 -2
- package/dist/commands/similar.js +4 -4
- package/dist/commands/status.js +6 -4
- package/dist/commands/summarize.js +6 -4
- package/dist/commands/symbols.js +1 -1
- package/dist/commands/test-find.js +2 -2
- package/dist/commands/trace.js +12 -5
- package/dist/commands/watch.js +33 -9
- package/dist/eval-graph-nav.js +4 -1
- package/dist/eval-graph-recovery-probe.js +56 -14
- package/dist/eval-graph-spotcheck.js +10 -2
- package/dist/eval-graph-totals.js +5 -2
- package/dist/eval-oss.js +212 -37
- package/dist/eval-seed.js +13 -4
- package/dist/index.js +9 -9
- package/dist/lib/daemon/daemon.js +32 -13
- package/dist/lib/daemon/ipc-handler.js +24 -5
- package/dist/lib/daemon/mlx-server-manager.js +11 -3
- package/dist/lib/daemon/process-manager.js +1 -2
- package/dist/lib/daemon/watcher-manager.js +22 -6
- package/dist/lib/graph/callsites.js +151 -25
- package/dist/lib/graph/graph-builder.js +2 -2
- package/dist/lib/graph/impact.js +6 -6
- package/dist/lib/index/batch-processor.js +16 -6
- package/dist/lib/index/chunker.js +2 -0
- package/dist/lib/index/syncer.js +22 -10
- package/dist/lib/index/watcher-batch.js +10 -1
- package/dist/lib/llm/config.js +2 -1
- package/dist/lib/llm/diff.js +56 -11
- package/dist/lib/llm/investigate.js +52 -11
- package/dist/lib/llm/review.js +21 -8
- package/dist/lib/llm/server.js +25 -9
- package/dist/lib/llm/tools.js +1 -1
- package/dist/lib/output/agent-search-formatter.js +25 -3
- package/dist/lib/output/index-state-footer.js +1 -1
- package/dist/lib/review/risk.js +2 -4
- package/dist/lib/search/pagerank.js +1 -1
- package/dist/lib/search/searcher.js +43 -17
- package/dist/lib/search/seed-weight.js +4 -1
- package/dist/lib/skeleton/symbol-extractor.js +2 -1
- package/dist/lib/store/vector-db.js +21 -10
- package/dist/lib/utils/cross-project.js +5 -1
- package/dist/lib/utils/daemon-client.js +39 -1
- package/dist/lib/utils/filter-builder.js +22 -0
- package/dist/lib/utils/git.js +10 -2
- package/dist/lib/utils/project-registry.js +3 -2
- package/dist/lib/utils/scope-filter.js +6 -6
- package/dist/lib/utils/watcher-launcher.js +4 -1
- package/dist/lib/utils/watcher-store.js +2 -1
- package/dist/lib/workers/embeddings/granite.js +4 -1
- package/dist/lib/workers/embeddings/mlx-client.js +7 -2
- package/dist/lib/workers/pool.js +30 -7
- package/dist/lib/workers/process-child.js +1 -1
- package/package.json +23 -19
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/cwd-changed.js +4 -1
- package/plugins/grepmax/hooks/pre-grep.js +2 -1
- package/plugins/grepmax/hooks/start.js +45 -10
- package/plugins/grepmax/hooks/subagent-start.js +1 -4
|
@@ -48,6 +48,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
48
48
|
const lancedb = __importStar(require("@lancedb/lancedb"));
|
|
49
49
|
const apache_arrow_1 = require("apache-arrow");
|
|
50
50
|
const config_1 = require("../../config");
|
|
51
|
+
const index_config_1 = require("../index/index-config");
|
|
51
52
|
const cleanup_1 = require("../utils/cleanup");
|
|
52
53
|
const filter_builder_1 = require("../utils/filter-builder");
|
|
53
54
|
const logger_1 = require("../utils/logger");
|
|
@@ -86,7 +87,10 @@ class VectorDB {
|
|
|
86
87
|
this.activeWrites = 0;
|
|
87
88
|
this.writeDrainResolve = null;
|
|
88
89
|
this.compactingPromise = null;
|
|
89
|
-
|
|
90
|
+
// Default to the configured tier's dim (not the hard-wired small-tier 384)
|
|
91
|
+
// so a `standard`-tier index actually stores 768d vectors. An explicit
|
|
92
|
+
// arg still wins (eval scripts, tests).
|
|
93
|
+
this.vectorDim = vectorDim !== null && vectorDim !== void 0 ? vectorDim : (0, index_config_1.readGlobalConfig)().vectorDim;
|
|
90
94
|
this.unregisterCleanup = (0, cleanup_1.registerCleanup)(() => this.close());
|
|
91
95
|
}
|
|
92
96
|
/**
|
|
@@ -411,11 +415,13 @@ class VectorDB {
|
|
|
411
415
|
// Callers (syncer flushBatch) splice records before passing — they're never reused.
|
|
412
416
|
for (const rec of records) {
|
|
413
417
|
const vec = toNumberArray(rec.vector);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
418
|
+
// Never silently pad/truncate: a width mismatch means the embedding tier
|
|
419
|
+
// and this table disagree (wrong model wired, or a stale index after a
|
|
420
|
+
// tier change). Reshaping would store garbage that scores meaninglessly.
|
|
421
|
+
// Fail loudly and point at the fix instead.
|
|
422
|
+
if (vec.length !== this.vectorDim) {
|
|
423
|
+
throw new Error(`Vector dimension mismatch: got ${vec.length}d, expected ${this.vectorDim}d. ` +
|
|
424
|
+
"The embedding model tier likely changed without a rebuild — run `gmax index --reset`.");
|
|
419
425
|
}
|
|
420
426
|
rec.vector = vec;
|
|
421
427
|
rec.colbert = toBuffer(rec.colbert);
|
|
@@ -676,7 +682,7 @@ class VectorDB {
|
|
|
676
682
|
const rows = yield table
|
|
677
683
|
.query()
|
|
678
684
|
.select(["id"])
|
|
679
|
-
.where(
|
|
685
|
+
.where((0, filter_builder_1.pathStartsWith)(prefix))
|
|
680
686
|
.limit(1)
|
|
681
687
|
.toArray();
|
|
682
688
|
return rows.length > 0;
|
|
@@ -686,7 +692,7 @@ class VectorDB {
|
|
|
686
692
|
return __awaiter(this, void 0, void 0, function* () {
|
|
687
693
|
const table = yield this.ensureTable();
|
|
688
694
|
const prefix = pathPrefix.endsWith("/") ? pathPrefix : `${pathPrefix}/`;
|
|
689
|
-
return table.countRows(
|
|
695
|
+
return table.countRows((0, filter_builder_1.pathStartsWith)(prefix));
|
|
690
696
|
});
|
|
691
697
|
}
|
|
692
698
|
countDistinctFilesForPath(pathPrefix) {
|
|
@@ -701,7 +707,7 @@ class VectorDB {
|
|
|
701
707
|
const rows = yield table
|
|
702
708
|
.query()
|
|
703
709
|
.select(["path"])
|
|
704
|
-
.where(
|
|
710
|
+
.where((0, filter_builder_1.pathStartsWith)(prefix))
|
|
705
711
|
.toArray();
|
|
706
712
|
const unique = new Set();
|
|
707
713
|
for (const r of rows) {
|
|
@@ -804,7 +810,12 @@ class VectorDB {
|
|
|
804
810
|
return __awaiter(this, void 0, void 0, function* () {
|
|
805
811
|
this.ensureDiskOk();
|
|
806
812
|
const table = yield this.ensureTable();
|
|
807
|
-
|
|
813
|
+
// Slash-terminate so a project root can't bleed into a sibling
|
|
814
|
+
// (`/repo/app` must not delete `/repo/app2`), and use starts_with so `_`/`%`
|
|
815
|
+
// in the path are literal, not LIKE wildcards. Destructive path — keep this
|
|
816
|
+
// self-protective even if a caller forgets to normalize.
|
|
817
|
+
const dirPrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
|
|
818
|
+
yield this.withWriteGate(() => table.delete((0, filter_builder_1.pathStartsWith)(dirPrefix)));
|
|
808
819
|
});
|
|
809
820
|
}
|
|
810
821
|
drop() {
|
|
@@ -105,7 +105,11 @@ function groupResultsByProject(results, roots, getPath) {
|
|
|
105
105
|
const key = (_a = owner === null || owner === void 0 ? void 0 : owner.root) !== null && _a !== void 0 ? _a : "(unknown)";
|
|
106
106
|
let bucket = buckets.get(key);
|
|
107
107
|
if (!bucket) {
|
|
108
|
-
bucket = {
|
|
108
|
+
bucket = {
|
|
109
|
+
name: (_b = owner === null || owner === void 0 ? void 0 : owner.name) !== null && _b !== void 0 ? _b : "(unknown)",
|
|
110
|
+
root: (_c = owner === null || owner === void 0 ? void 0 : owner.root) !== null && _c !== void 0 ? _c : "",
|
|
111
|
+
items: [],
|
|
112
|
+
};
|
|
109
113
|
buckets.set(key, bucket);
|
|
110
114
|
order.push(key);
|
|
111
115
|
}
|
|
@@ -45,6 +45,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.sendDaemonCommand = sendDaemonCommand;
|
|
46
46
|
exports.isDaemonRunning = isDaemonRunning;
|
|
47
47
|
exports.isDaemonHeartbeatFresh = isDaemonHeartbeatFresh;
|
|
48
|
+
exports.readDaemonPid = readDaemonPid;
|
|
49
|
+
exports.waitForProcessExit = waitForProcessExit;
|
|
48
50
|
exports.ensureDaemonRunning = ensureDaemonRunning;
|
|
49
51
|
exports.sendStreamingCommand = sendStreamingCommand;
|
|
50
52
|
const fs = __importStar(require("node:fs"));
|
|
@@ -95,7 +97,10 @@ function sendDaemonCommand(cmd, opts) {
|
|
|
95
97
|
socket.on("error", (err) => {
|
|
96
98
|
var _a;
|
|
97
99
|
clearTimeout(timer);
|
|
98
|
-
finish({
|
|
100
|
+
finish({
|
|
101
|
+
ok: false,
|
|
102
|
+
error: (_a = err.code) !== null && _a !== void 0 ? _a : err.message,
|
|
103
|
+
});
|
|
99
104
|
});
|
|
100
105
|
socket.on("close", () => {
|
|
101
106
|
clearTimeout(timer);
|
|
@@ -149,6 +154,38 @@ function isDaemonHeartbeatFresh() {
|
|
|
149
154
|
return false;
|
|
150
155
|
}
|
|
151
156
|
}
|
|
157
|
+
/** Read the daemon's PID from the PID file, or null if absent/invalid. */
|
|
158
|
+
function readDaemonPid() {
|
|
159
|
+
try {
|
|
160
|
+
const pid = parseInt(fs.readFileSync(config_1.PATHS.daemonPidFile, "utf-8").trim(), 10);
|
|
161
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
162
|
+
}
|
|
163
|
+
catch (_a) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Poll until a process has fully exited, or the timeout elapses. Used before
|
|
169
|
+
* spawning a successor daemon: shutdown() drops the socket/lock immediately but
|
|
170
|
+
* keeps draining in-flight work, and a successor that starts in that window
|
|
171
|
+
* classifies the still-draining predecessor as stale and SIGKILLs it. Waiting
|
|
172
|
+
* for the actual process exit closes that race. Returns true if it exited.
|
|
173
|
+
*/
|
|
174
|
+
function waitForProcessExit(pid, timeoutMs) {
|
|
175
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
176
|
+
const deadline = Date.now() + timeoutMs;
|
|
177
|
+
while (Date.now() < deadline) {
|
|
178
|
+
try {
|
|
179
|
+
process.kill(pid, 0);
|
|
180
|
+
}
|
|
181
|
+
catch (_a) {
|
|
182
|
+
return true; // ESRCH — process is gone
|
|
183
|
+
}
|
|
184
|
+
yield new Promise((r) => setTimeout(r, 100));
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
152
189
|
/**
|
|
153
190
|
* Ensure the daemon is running — start it if needed, poll up to 5s.
|
|
154
191
|
* Returns true if daemon is ready, false if it couldn't be started.
|
|
@@ -207,6 +244,7 @@ function sendStreamingCommand(cmd, onProgress, opts) {
|
|
|
207
244
|
socket.on("data", (chunk) => {
|
|
208
245
|
buf += chunk.toString();
|
|
209
246
|
let nl;
|
|
247
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: idiomatic buffer-split loop
|
|
210
248
|
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
211
249
|
const line = buf.slice(0, nl);
|
|
212
250
|
buf = buf.slice(nl + 1);
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.escapeSqlString = escapeSqlString;
|
|
4
|
+
exports.pathStartsWith = pathStartsWith;
|
|
5
|
+
exports.pathNotStartsWith = pathNotStartsWith;
|
|
4
6
|
exports.normalizePath = normalizePath;
|
|
5
7
|
function escapeSqlString(str) {
|
|
6
8
|
// LanceDB (via DataFusion) treats backslashes literally in standard strings.
|
|
7
9
|
// We only need to escape single quotes by doubling them.
|
|
8
10
|
return str.replace(/'/g, "''");
|
|
9
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* SQL predicate matching rows whose `path` begins with `prefix`.
|
|
14
|
+
*
|
|
15
|
+
* Use this instead of `path LIKE '<prefix>%'` for any path-prefix scope.
|
|
16
|
+
* `escapeSqlString` only neutralizes quotes, not LIKE metacharacters, so a
|
|
17
|
+
* prefix containing `_` (matches any single char) or `%` (matches anything)
|
|
18
|
+
* would silently match — and delete — across sibling projects
|
|
19
|
+
* (`/repo/my_app/` would match `/repo/myXapp/`). `starts_with()` has no
|
|
20
|
+
* wildcard semantics, so the prefix is matched literally.
|
|
21
|
+
*
|
|
22
|
+
* Callers are responsible for trailing-slash boundary correctness: pass
|
|
23
|
+
* `/repo/app/` (not `/repo/app`) so the scope can't bleed into `/repo/app2/`.
|
|
24
|
+
*/
|
|
25
|
+
function pathStartsWith(prefix) {
|
|
26
|
+
return `starts_with(path, '${escapeSqlString(prefix)}')`;
|
|
27
|
+
}
|
|
28
|
+
/** Negation of {@link pathStartsWith} — excludes paths under `prefix`. */
|
|
29
|
+
function pathNotStartsWith(prefix) {
|
|
30
|
+
return `NOT ${pathStartsWith(prefix)}`;
|
|
31
|
+
}
|
|
10
32
|
/**
|
|
11
33
|
* Normalizes a path to use forward slashes, ensuring consistency across platforms.
|
|
12
34
|
* @param p The path to normalize
|
package/dist/lib/utils/git.js
CHANGED
|
@@ -90,7 +90,11 @@ function getMainRepoRoot(worktreeRoot) {
|
|
|
90
90
|
* Returns absolute paths. Includes both staged and unstaged changes.
|
|
91
91
|
*/
|
|
92
92
|
function getChangedFiles(ref, cwd) {
|
|
93
|
-
const opts = {
|
|
93
|
+
const opts = {
|
|
94
|
+
cwd: cwd !== null && cwd !== void 0 ? cwd : process.cwd(),
|
|
95
|
+
encoding: "utf-8",
|
|
96
|
+
timeout: 10000,
|
|
97
|
+
};
|
|
94
98
|
try {
|
|
95
99
|
let output;
|
|
96
100
|
if (ref) {
|
|
@@ -210,7 +214,11 @@ function getCommitHistory(opts) {
|
|
|
210
214
|
* Returns absolute paths.
|
|
211
215
|
*/
|
|
212
216
|
function getUntrackedFiles(cwd) {
|
|
213
|
-
const opts = {
|
|
217
|
+
const opts = {
|
|
218
|
+
cwd: cwd !== null && cwd !== void 0 ? cwd : process.cwd(),
|
|
219
|
+
encoding: "utf-8",
|
|
220
|
+
timeout: 10000,
|
|
221
|
+
};
|
|
214
222
|
try {
|
|
215
223
|
const output = (0, node_child_process_1.execFileSync)("git", ["ls-files", "--others", "--exclude-standard"], opts);
|
|
216
224
|
const root = (0, node_child_process_1.execFileSync)("git", ["rev-parse", "--show-toplevel"], opts).trim();
|
|
@@ -66,7 +66,7 @@ function loadRegistry() {
|
|
|
66
66
|
}
|
|
67
67
|
function saveRegistry(entries) {
|
|
68
68
|
fs.mkdirSync(path.dirname(REGISTRY_PATH), { recursive: true });
|
|
69
|
-
const tmp = REGISTRY_PATH
|
|
69
|
+
const tmp = `${REGISTRY_PATH}.tmp`;
|
|
70
70
|
fs.writeFileSync(tmp, `${JSON.stringify(entries, null, 2)}\n`);
|
|
71
71
|
fs.renameSync(tmp, REGISTRY_PATH);
|
|
72
72
|
}
|
|
@@ -119,7 +119,8 @@ function removeProject(root) {
|
|
|
119
119
|
*/
|
|
120
120
|
function getParentProject(root) {
|
|
121
121
|
const resolved = root.endsWith("/") ? root : `${root}/`;
|
|
122
|
-
return loadRegistry().find((e) => e.root !== root &&
|
|
122
|
+
return loadRegistry().find((e) => e.root !== root &&
|
|
123
|
+
resolved.startsWith(e.root.endsWith("/") ? e.root : `${e.root}/`));
|
|
123
124
|
}
|
|
124
125
|
/**
|
|
125
126
|
* Find registered projects that are children of this path.
|
|
@@ -49,7 +49,9 @@ function toArray(value) {
|
|
|
49
49
|
.filter(Boolean);
|
|
50
50
|
}
|
|
51
51
|
function joinSubpath(projectRoot, sub) {
|
|
52
|
-
const rootWithSlash = projectRoot.endsWith("/")
|
|
52
|
+
const rootWithSlash = projectRoot.endsWith("/")
|
|
53
|
+
? projectRoot
|
|
54
|
+
: `${projectRoot}/`;
|
|
53
55
|
if (path.isAbsolute(sub))
|
|
54
56
|
return sub.endsWith("/") ? sub : `${sub}/`;
|
|
55
57
|
if (sub.startsWith(rootWithSlash))
|
|
@@ -90,14 +92,12 @@ function buildScopeWhere(scope, condition) {
|
|
|
90
92
|
const parts = [];
|
|
91
93
|
if (condition)
|
|
92
94
|
parts.push(condition);
|
|
93
|
-
parts.push(
|
|
95
|
+
parts.push((0, filter_builder_1.pathStartsWith)(scope.pathPrefix));
|
|
94
96
|
for (const ex of scope.excludePrefixes) {
|
|
95
|
-
parts.push(
|
|
97
|
+
parts.push((0, filter_builder_1.pathNotStartsWith)(ex));
|
|
96
98
|
}
|
|
97
99
|
if (scope.inPrefixes.length > 0) {
|
|
98
|
-
const ors = scope.inPrefixes
|
|
99
|
-
.map((p) => `path LIKE '${(0, filter_builder_1.escapeSqlString)(p)}%'`)
|
|
100
|
-
.join(" OR ");
|
|
100
|
+
const ors = scope.inPrefixes.map((p) => (0, filter_builder_1.pathStartsWith)(p)).join(" OR ");
|
|
101
101
|
parts.push(`(${ors})`);
|
|
102
102
|
}
|
|
103
103
|
return parts.join(" AND ");
|
|
@@ -56,7 +56,10 @@ function launchWatcher(projectRoot) {
|
|
|
56
56
|
for (let i = 0; i < 25; i++) {
|
|
57
57
|
yield new Promise((r) => setTimeout(r, 200));
|
|
58
58
|
try {
|
|
59
|
-
const retry = yield (0, daemon_client_1.sendDaemonCommand)({
|
|
59
|
+
const retry = yield (0, daemon_client_1.sendDaemonCommand)({
|
|
60
|
+
cmd: "watch",
|
|
61
|
+
root: projectRoot,
|
|
62
|
+
});
|
|
60
63
|
if (retry.ok && typeof retry.pid === "number") {
|
|
61
64
|
return { ok: true, pid: retry.pid, reused: false };
|
|
62
65
|
}
|
|
@@ -84,7 +84,8 @@ function isAlive(info) {
|
|
|
84
84
|
if (!isProcessRunning(info.pid))
|
|
85
85
|
return false;
|
|
86
86
|
// If heartbeat exists and is stale, treat as dead (possibly deadlocked)
|
|
87
|
-
if (info.lastHeartbeat &&
|
|
87
|
+
if (info.lastHeartbeat &&
|
|
88
|
+
Date.now() - info.lastHeartbeat > HEARTBEAT_STALE_MS) {
|
|
88
89
|
return false;
|
|
89
90
|
}
|
|
90
91
|
return true;
|
|
@@ -63,7 +63,10 @@ class GraniteModel {
|
|
|
63
63
|
this.vectorDimensions = vectorDim !== null && vectorDim !== void 0 ? vectorDim : config_1.CONFIG.VECTOR_DIM;
|
|
64
64
|
}
|
|
65
65
|
resolvePaths() {
|
|
66
|
-
|
|
66
|
+
// Honor the active tier's ONNX model (propagated from the pool); fall back
|
|
67
|
+
// to the small-tier default when unset (standalone/test invocations).
|
|
68
|
+
const modelId = process.env.GMAX_EMBED_ONNX_MODEL || config_1.MODEL_IDS.embed;
|
|
69
|
+
const basePath = path.join(CACHE_DIR, modelId);
|
|
67
70
|
const onnxDir = path.join(basePath, "onnx");
|
|
68
71
|
const candidates = ["model_q4.onnx", "model.onnx"];
|
|
69
72
|
for (const candidate of candidates) {
|
|
@@ -182,8 +182,13 @@ function mlxEmbed(texts) {
|
|
|
182
182
|
const wasPreviouslyAvailable = mlxAvailable !== false;
|
|
183
183
|
mlxAvailable = false;
|
|
184
184
|
const now = Date.now();
|
|
185
|
-
if (wasPreviouslyAvailable ||
|
|
186
|
-
|
|
185
|
+
if (wasPreviouslyAvailable ||
|
|
186
|
+
now - lastMlxWarning >= MLX_WARNING_INTERVAL_MS) {
|
|
187
|
+
console.error("[mlx] Embed server failed: bad response (ok=" +
|
|
188
|
+
ok +
|
|
189
|
+
", hasVectors=" +
|
|
190
|
+
!!(data === null || data === void 0 ? void 0 : data.vectors) +
|
|
191
|
+
")");
|
|
187
192
|
lastMlxWarning = now;
|
|
188
193
|
}
|
|
189
194
|
return null;
|
package/dist/lib/workers/pool.js
CHANGED
|
@@ -43,6 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.WorkerPool = void 0;
|
|
46
|
+
exports.embeddingEnv = embeddingEnv;
|
|
46
47
|
exports.getWorkerPool = getWorkerPool;
|
|
47
48
|
exports.destroyWorkerPool = destroyWorkerPool;
|
|
48
49
|
exports.isWorkerPoolInitialized = isWorkerPoolInitialized;
|
|
@@ -51,10 +52,11 @@ exports.isWorkerPoolInitialized = isWorkerPoolInitialized;
|
|
|
51
52
|
* to ensure the ONNX Runtime segfaults do not crash the main process.
|
|
52
53
|
*/
|
|
53
54
|
const childProcess = __importStar(require("node:child_process"));
|
|
54
|
-
const logger_1 = require("../utils/logger");
|
|
55
55
|
const fs = __importStar(require("node:fs"));
|
|
56
56
|
const path = __importStar(require("node:path"));
|
|
57
57
|
const config_1 = require("../../config");
|
|
58
|
+
const index_config_1 = require("../index/index-config");
|
|
59
|
+
const logger_1 = require("../utils/logger");
|
|
58
60
|
function reviveBufferLike(input) {
|
|
59
61
|
if (input &&
|
|
60
62
|
typeof input === "object" &&
|
|
@@ -110,6 +112,22 @@ const FORCE_KILL_GRACE_MS = 200;
|
|
|
110
112
|
// escalate to SIGKILL. ~5s is well above ONNX teardown time but short
|
|
111
113
|
// enough that the reap loop self-heals within a minute.
|
|
112
114
|
const REAP_FORCE_KILL_GRACE_MS = 5000;
|
|
115
|
+
/**
|
|
116
|
+
* Embedding env derived from the active model tier, injected into every spawned
|
|
117
|
+
* worker so the worker's VectorDB dim (GMAX_VECTOR_DIM) and CPU ONNX model
|
|
118
|
+
* (GMAX_EMBED_ONNX_MODEL) match the configured tier. Without this, workers fall
|
|
119
|
+
* back to the hard-wired small tier (384d) regardless of config. One spot covers
|
|
120
|
+
* every worker user (index, add, search, daemon, mcp). Merged so process.env can
|
|
121
|
+
* override — see the spread order at fork().
|
|
122
|
+
*/
|
|
123
|
+
function embeddingEnv() {
|
|
124
|
+
const { modelTier } = (0, index_config_1.readGlobalConfig)();
|
|
125
|
+
const ids = (0, index_config_1.getModelIdsForTier)(modelTier);
|
|
126
|
+
return {
|
|
127
|
+
GMAX_VECTOR_DIM: String(ids.vectorDim),
|
|
128
|
+
GMAX_EMBED_ONNX_MODEL: ids.embed,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
113
131
|
class ProcessWorker {
|
|
114
132
|
constructor(modulePath, execArgv, maxMemoryMb) {
|
|
115
133
|
this.modulePath = modulePath;
|
|
@@ -126,12 +144,12 @@ class ProcessWorker {
|
|
|
126
144
|
// event). Guards against handleWorkerExit running twice when both events
|
|
127
145
|
// fire for the same crash.
|
|
128
146
|
this.cleanedUp = false;
|
|
129
|
-
const memArgs = maxMemoryMb
|
|
130
|
-
|
|
131
|
-
|
|
147
|
+
const memArgs = maxMemoryMb ? [`--max-old-space-size=${maxMemoryMb}`] : [];
|
|
148
|
+
// embeddingEnv() first so a manually-exported GMAX_VECTOR_DIM /
|
|
149
|
+
// GMAX_EMBED_ONNX_MODEL in process.env still wins (spread last).
|
|
132
150
|
this.child = childProcess.fork(modulePath, {
|
|
133
151
|
execArgv: [...memArgs, ...execArgv],
|
|
134
|
-
env: Object.assign({}, process.env),
|
|
152
|
+
env: Object.assign(Object.assign({}, embeddingEnv()), process.env),
|
|
135
153
|
});
|
|
136
154
|
}
|
|
137
155
|
}
|
|
@@ -167,7 +185,10 @@ const WORKER_RSS_RECYCLE_MB = (() => {
|
|
|
167
185
|
// Methods that must skip the indexing backlog. encodeQuery is the search hot
|
|
168
186
|
// path: a single query is ~17ms but waits behind every queued processFile.
|
|
169
187
|
// rerank is similarly small and latency-sensitive.
|
|
170
|
-
const PRIORITY_METHODS = new Set([
|
|
188
|
+
const PRIORITY_METHODS = new Set([
|
|
189
|
+
"encodeQuery",
|
|
190
|
+
"rerank",
|
|
191
|
+
]);
|
|
171
192
|
class WorkerPool {
|
|
172
193
|
constructor() {
|
|
173
194
|
this.workers = [];
|
|
@@ -320,7 +341,9 @@ class WorkerPool {
|
|
|
320
341
|
if (task.method === "processFile") {
|
|
321
342
|
result = reviveProcessFileResult(result);
|
|
322
343
|
}
|
|
323
|
-
const elapsed = task.startTime
|
|
344
|
+
const elapsed = task.startTime
|
|
345
|
+
? `${Date.now() - task.startTime}ms`
|
|
346
|
+
: "?ms";
|
|
324
347
|
const filePath = (_h = (_f = (_e = task.payload) === null || _e === void 0 ? void 0 : _e.path) !== null && _f !== void 0 ? _f : (_g = task.payload) === null || _g === void 0 ? void 0 : _g.absolutePath) !== null && _h !== void 0 ? _h : "";
|
|
325
348
|
(0, logger_1.debug)("pool", `complete task=${task.id} method=${task.method} ${elapsed}${filePath ? ` file=${filePath}` : ""}`);
|
|
326
349
|
task.resolve(result);
|
|
@@ -47,8 +47,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
48
|
const node_process_1 = __importDefault(require("node:process"));
|
|
49
49
|
node_process_1.default.title = "gmax-worker";
|
|
50
|
-
const worker_1 = __importStar(require("./worker"));
|
|
51
50
|
const logger_1 = require("../utils/logger");
|
|
51
|
+
const worker_1 = __importStar(require("./worker"));
|
|
52
52
|
// Every outgoing message also carries `rss` (see send()).
|
|
53
53
|
const send = (msg) => {
|
|
54
54
|
if (node_process_1.default.send) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
|
|
5
5
|
"homepage": "https://github.com/reowens/grepmax",
|
|
6
6
|
"bugs": {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"bin": {
|
|
15
15
|
"gmax": "dist/index.js"
|
|
16
16
|
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.12.0"
|
|
19
|
+
},
|
|
17
20
|
"scripts": {
|
|
18
21
|
"postinstall": "node scripts/postinstall.js",
|
|
19
22
|
"prebuild": "mkdir -p dist",
|
|
@@ -30,9 +33,10 @@
|
|
|
30
33
|
"bench:recall:json": "GMAX_EVAL_JSON=1 npx tsx src/eval.ts",
|
|
31
34
|
"bench:oss": "npx tsx src/eval-oss.ts all",
|
|
32
35
|
"bench:oss:json": "GMAX_EVAL_JSON=1 npx tsx src/eval-oss.ts all",
|
|
33
|
-
"format": "biome
|
|
36
|
+
"format": "biome format --write .",
|
|
34
37
|
"format:check": "biome check .",
|
|
35
38
|
"lint": "biome lint .",
|
|
39
|
+
"lint:fix": "biome check --write .",
|
|
36
40
|
"typecheck": "tsc --noEmit",
|
|
37
41
|
"prepublishOnly": "pnpm build",
|
|
38
42
|
"preversion": "pnpm test && pnpm typecheck",
|
|
@@ -61,6 +65,7 @@
|
|
|
61
65
|
],
|
|
62
66
|
"files": [
|
|
63
67
|
"dist",
|
|
68
|
+
".claude-plugin",
|
|
64
69
|
"plugins",
|
|
65
70
|
"scripts/postinstall.js",
|
|
66
71
|
"mlx-embed-server",
|
|
@@ -71,38 +76,37 @@
|
|
|
71
76
|
"license": "Apache-2.0",
|
|
72
77
|
"description": "Semantic code search for coding agents. Local embeddings, LLM summaries, call graph tracing.",
|
|
73
78
|
"dependencies": {
|
|
74
|
-
"@clack/prompts": "^1.
|
|
75
|
-
"@huggingface/transformers": "^4.
|
|
79
|
+
"@clack/prompts": "^1.6.0",
|
|
80
|
+
"@huggingface/transformers": "^4.2.0",
|
|
76
81
|
"@lancedb/lancedb": "^0.30.0",
|
|
77
82
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
78
83
|
"@parcel/watcher": "^2.5.6",
|
|
79
84
|
"apache-arrow": "^18.1.0",
|
|
80
85
|
"chalk": "^5.6.2",
|
|
81
86
|
"cli-highlight": "^2.1.11",
|
|
82
|
-
"commander": "^
|
|
83
|
-
"dotenv": "^17.2
|
|
87
|
+
"commander": "^15.0.0",
|
|
88
|
+
"dotenv": "^17.4.2",
|
|
84
89
|
"fast-glob": "^3.3.3",
|
|
85
90
|
"ignore": "^7.0.5",
|
|
86
|
-
"lmdb": "^3.5.
|
|
91
|
+
"lmdb": "^3.5.6",
|
|
87
92
|
"onnxruntime-node": "1.24.3",
|
|
88
|
-
"openai": "^6.
|
|
89
|
-
"ora": "^9.
|
|
90
|
-
"piscina": "^5.1.4",
|
|
93
|
+
"openai": "^6.45.0",
|
|
94
|
+
"ora": "^9.4.1",
|
|
91
95
|
"proper-lockfile": "^4.1.2",
|
|
92
96
|
"simsimd": "^6.5.5",
|
|
93
|
-
"uuid": "^
|
|
94
|
-
"web-tree-sitter": "^0.26.
|
|
95
|
-
"zod": "^4.
|
|
97
|
+
"uuid": "^14.0.1",
|
|
98
|
+
"web-tree-sitter": "^0.26.9",
|
|
99
|
+
"zod": "^4.4.3"
|
|
96
100
|
},
|
|
97
101
|
"devDependencies": {
|
|
98
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.87",
|
|
99
102
|
"@biomejs/biome": "2.4.10",
|
|
100
|
-
"@types/node": "^
|
|
103
|
+
"@types/node": "^26.0.1",
|
|
101
104
|
"@types/proper-lockfile": "^4.1.4",
|
|
102
|
-
"node-gyp": "^
|
|
105
|
+
"node-gyp": "^13.0.0",
|
|
103
106
|
"ts-node": "^10.9.2",
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
+
"tsx": "^4.22.4",
|
|
108
|
+
"typescript": "^6.0.3",
|
|
109
|
+
"vite": "^8.1.0",
|
|
110
|
+
"vitest": "^4.1.9"
|
|
107
111
|
}
|
|
108
112
|
}
|
|
@@ -41,7 +41,10 @@ async function main() {
|
|
|
41
41
|
if (!isProjectRegistered(newCwd)) return;
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
|
-
execFileSync("gmax", ["watch", "--daemon", "-b"], {
|
|
44
|
+
execFileSync("gmax", ["watch", "--daemon", "-b"], {
|
|
45
|
+
timeout: 5000,
|
|
46
|
+
stdio: "ignore",
|
|
47
|
+
});
|
|
45
48
|
} catch {
|
|
46
49
|
try {
|
|
47
50
|
execFileSync("gmax", ["watch", "-b"], { timeout: 5000, stdio: "ignore" });
|
|
@@ -28,7 +28,8 @@ const SNAKE_CASE = /_[a-z]/;
|
|
|
28
28
|
const ALL_CAPS = /^[A-Z_]+$/;
|
|
29
29
|
const HAS_DOT = /\./;
|
|
30
30
|
const HAS_QUOTE = /['"]/;
|
|
31
|
-
const CODE_KEYWORDS =
|
|
31
|
+
const CODE_KEYWORDS =
|
|
32
|
+
/\b(class|function|const|let|var|import|export|return|extends|implements|interface|type|enum|struct|def|async|await)\b/;
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Conservative heuristic: returns true only when the pattern is very
|