grepmax 0.18.0 → 0.19.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/README.md +27 -2
- 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/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 +587 -715
- package/dist/commands/peek.js +15 -9
- package/dist/commands/plugin.js +1 -1
- package/dist/commands/project.js +15 -5
- package/dist/commands/related.js +8 -9
- package/dist/commands/review.js +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/similar.js +4 -4
- package/dist/commands/status.js +5 -3
- package/dist/commands/summarize.js +6 -4
- package/dist/commands/test-find.js +2 -2
- package/dist/commands/trace.js +51 -9
- package/dist/commands/watch.js +9 -5
- 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 +22 -13
- package/dist/lib/daemon/ipc-handler.js +18 -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 +4 -4
- package/dist/lib/graph/callsites.js +151 -25
- package/dist/lib/help/agent-cheatsheet.js +1 -1
- package/dist/lib/index/batch-processor.js +6 -5
- package/dist/lib/index/chunker.js +2 -0
- package/dist/lib/index/syncer.js +7 -6
- package/dist/lib/llm/diff.js +56 -11
- package/dist/lib/llm/investigate.js +12 -5
- package/dist/lib/llm/review.js +21 -8
- package/dist/lib/llm/server.js +16 -7
- 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/searcher.js +37 -11
- package/dist/lib/search/seed-weight.js +4 -1
- package/dist/lib/skeleton/symbol-extractor.js +2 -1
- package/dist/lib/utils/cross-project.js +5 -1
- package/dist/lib/utils/daemon-client.js +5 -1
- package/dist/lib/utils/git.js +10 -2
- package/dist/lib/utils/project-registry.js +3 -2
- package/dist/lib/utils/scope-filter.js +3 -1
- package/dist/lib/utils/watcher-launcher.js +4 -1
- package/dist/lib/utils/watcher-store.js +2 -1
- package/dist/lib/workers/embeddings/mlx-client.js +7 -2
- package/dist/lib/workers/pool.js +9 -6
- package/dist/lib/workers/process-child.js +1 -1
- package/package.json +22 -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
|
@@ -29,7 +29,9 @@ function readSymbolArray(val) {
|
|
|
29
29
|
if (typeof maybe.toArray === "function") {
|
|
30
30
|
try {
|
|
31
31
|
const a = maybe.toArray();
|
|
32
|
-
return Array.isArray(a)
|
|
32
|
+
return Array.isArray(a)
|
|
33
|
+
? a.filter((v) => typeof v === "string")
|
|
34
|
+
: [];
|
|
33
35
|
}
|
|
34
36
|
catch (_a) {
|
|
35
37
|
return [];
|
|
@@ -441,9 +443,21 @@ class Searcher {
|
|
|
441
443
|
// loaded), so pull it into the lightweight path when symbols were seeded.
|
|
442
444
|
const needDefinedSymbols = pagerankEnabled || symbolQuery !== null || seedCtx.symbols.size > 0;
|
|
443
445
|
const LIGHTWEIGHT_COLUMNS = [
|
|
444
|
-
"id",
|
|
445
|
-
"
|
|
446
|
-
"
|
|
446
|
+
"id",
|
|
447
|
+
"path",
|
|
448
|
+
"hash",
|
|
449
|
+
"chunk_index",
|
|
450
|
+
"start_line",
|
|
451
|
+
"end_line",
|
|
452
|
+
"is_anchor",
|
|
453
|
+
"chunk_type",
|
|
454
|
+
"role",
|
|
455
|
+
"complexity",
|
|
456
|
+
"is_exported",
|
|
457
|
+
"content",
|
|
458
|
+
"parent_symbol",
|
|
459
|
+
"referenced_symbols",
|
|
460
|
+
"pooled_colbert_48d",
|
|
447
461
|
...(needDefinedSymbols ? ["defined_symbols"] : []),
|
|
448
462
|
];
|
|
449
463
|
// _distance is auto-added by vectorSearch, _score by FTS — include each
|
|
@@ -457,7 +471,7 @@ class Searcher {
|
|
|
457
471
|
if (whereClause) {
|
|
458
472
|
vectorQuery = vectorQuery.where(whereClause);
|
|
459
473
|
}
|
|
460
|
-
|
|
474
|
+
const vectorResults = (yield vectorQuery.toArray()).map((r) => (Object.assign({}, r)));
|
|
461
475
|
let ftsResults = [];
|
|
462
476
|
let ftsSearchFailed = false;
|
|
463
477
|
if (this.ftsAvailable) {
|
|
@@ -566,7 +580,7 @@ class Searcher {
|
|
|
566
580
|
var _a, _b;
|
|
567
581
|
const ka = a.id || `${a.path}:${a.chunk_index}`;
|
|
568
582
|
const kb = b.id || `${b.path}:${b.chunk_index}`;
|
|
569
|
-
return ((_a = candidateScores.get(kb)) !== null && _a !== void 0 ? _a : 0) - ((_b = candidateScores.get(ka)) !== null && _b !== void 0 ? _b : 0);
|
|
583
|
+
return (((_a = candidateScores.get(kb)) !== null && _a !== void 0 ? _a : 0) - ((_b = candidateScores.get(ka)) !== null && _b !== void 0 ? _b : 0));
|
|
570
584
|
});
|
|
571
585
|
}
|
|
572
586
|
}
|
|
@@ -751,7 +765,12 @@ class Searcher {
|
|
|
751
765
|
record: doc,
|
|
752
766
|
score: boosted,
|
|
753
767
|
breakdown: explain
|
|
754
|
-
? {
|
|
768
|
+
? {
|
|
769
|
+
rerank: base,
|
|
770
|
+
fused: fusedScore,
|
|
771
|
+
boost: blended > 0 ? boosted / blended : 1,
|
|
772
|
+
normalized: 0,
|
|
773
|
+
}
|
|
755
774
|
: undefined,
|
|
756
775
|
};
|
|
757
776
|
});
|
|
@@ -766,12 +785,14 @@ class Searcher {
|
|
|
766
785
|
const envWeight = Number.parseFloat((_o = process.env.GMAX_PR_WEIGHT) !== null && _o !== void 0 ? _o : "");
|
|
767
786
|
const PR_WEIGHT = Number.isFinite(envWeight) && envWeight >= 0 ? envWeight : 0.05;
|
|
768
787
|
for (const item of scored) {
|
|
769
|
-
const raw = item.record
|
|
788
|
+
const raw = item.record
|
|
789
|
+
.defined_symbols;
|
|
770
790
|
let defs = [];
|
|
771
791
|
if (Array.isArray(raw)) {
|
|
772
792
|
defs = raw.filter((v) => typeof v === "string");
|
|
773
793
|
}
|
|
774
|
-
else if (raw &&
|
|
794
|
+
else if (raw &&
|
|
795
|
+
typeof raw.toArray === "function") {
|
|
775
796
|
try {
|
|
776
797
|
const arr = raw.toArray();
|
|
777
798
|
if (Array.isArray(arr)) {
|
|
@@ -817,8 +838,13 @@ class Searcher {
|
|
|
817
838
|
const displayRows = yield table
|
|
818
839
|
.query()
|
|
819
840
|
.select([
|
|
820
|
-
"id",
|
|
821
|
-
"
|
|
841
|
+
"id",
|
|
842
|
+
"defined_symbols",
|
|
843
|
+
"parent_symbol",
|
|
844
|
+
"imports",
|
|
845
|
+
"exports",
|
|
846
|
+
"summary",
|
|
847
|
+
"file_skeleton",
|
|
822
848
|
])
|
|
823
849
|
.where(`id IN (${finalIds.join(",")})`)
|
|
824
850
|
.limit(finalIds.length)
|
|
@@ -64,7 +64,10 @@ function seedParamsFromEnv(env = process.env) {
|
|
|
64
64
|
function buildSeedContext(spec) {
|
|
65
65
|
var _a, _b;
|
|
66
66
|
const fileSuffixes = ((_a = spec === null || spec === void 0 ? void 0 : spec.files) !== null && _a !== void 0 ? _a : [])
|
|
67
|
-
.map((f) => f
|
|
67
|
+
.map((f) => f
|
|
68
|
+
.trim()
|
|
69
|
+
.toLowerCase()
|
|
70
|
+
.replace(/^\.?\//, ""))
|
|
68
71
|
.filter((f) => f.length > 0);
|
|
69
72
|
const symbols = new Set(((_b = spec === null || spec === void 0 ? void 0 : spec.symbols) !== null && _b !== void 0 ? _b : []).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
70
73
|
return {
|
|
@@ -13,7 +13,8 @@ function extractSymbolsFromSkeleton(annotatedSkeleton) {
|
|
|
13
13
|
const line = Number.parseInt(m[1], 10);
|
|
14
14
|
const sig = m[2].trim();
|
|
15
15
|
const exported = sig.includes("export ");
|
|
16
|
-
const type = ((_a = sig.match(/\b(class|interface|type|function|def|fn|func)\b/)) === null || _a === void 0 ? void 0 : _a[1]) ||
|
|
16
|
+
const type = ((_a = sig.match(/\b(class|interface|type|function|def|fn|func)\b/)) === null || _a === void 0 ? void 0 : _a[1]) ||
|
|
17
|
+
"other";
|
|
17
18
|
const name = ((_b = sig.match(/(?:function|class|interface|type|def|fn|func)\s+(\w+)/)) === null || _b === void 0 ? void 0 : _b[1]) ||
|
|
18
19
|
((_c = sig.match(/^(?:async\s+)?(\w+)\s*[(<]/)) === null || _c === void 0 ? void 0 : _c[1]) ||
|
|
19
20
|
"unknown";
|
|
@@ -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
|
}
|
|
@@ -95,7 +95,10 @@ function sendDaemonCommand(cmd, opts) {
|
|
|
95
95
|
socket.on("error", (err) => {
|
|
96
96
|
var _a;
|
|
97
97
|
clearTimeout(timer);
|
|
98
|
-
finish({
|
|
98
|
+
finish({
|
|
99
|
+
ok: false,
|
|
100
|
+
error: (_a = err.code) !== null && _a !== void 0 ? _a : err.message,
|
|
101
|
+
});
|
|
99
102
|
});
|
|
100
103
|
socket.on("close", () => {
|
|
101
104
|
clearTimeout(timer);
|
|
@@ -207,6 +210,7 @@ function sendStreamingCommand(cmd, onProgress, opts) {
|
|
|
207
210
|
socket.on("data", (chunk) => {
|
|
208
211
|
buf += chunk.toString();
|
|
209
212
|
let nl;
|
|
213
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: idiomatic buffer-split loop
|
|
210
214
|
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
211
215
|
const line = buf.slice(0, nl);
|
|
212
216
|
buf = buf.slice(nl + 1);
|
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))
|
|
@@ -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;
|
|
@@ -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
|
@@ -51,10 +51,10 @@ exports.isWorkerPoolInitialized = isWorkerPoolInitialized;
|
|
|
51
51
|
* to ensure the ONNX Runtime segfaults do not crash the main process.
|
|
52
52
|
*/
|
|
53
53
|
const childProcess = __importStar(require("node:child_process"));
|
|
54
|
-
const logger_1 = require("../utils/logger");
|
|
55
54
|
const fs = __importStar(require("node:fs"));
|
|
56
55
|
const path = __importStar(require("node:path"));
|
|
57
56
|
const config_1 = require("../../config");
|
|
57
|
+
const logger_1 = require("../utils/logger");
|
|
58
58
|
function reviveBufferLike(input) {
|
|
59
59
|
if (input &&
|
|
60
60
|
typeof input === "object" &&
|
|
@@ -126,9 +126,7 @@ class ProcessWorker {
|
|
|
126
126
|
// event). Guards against handleWorkerExit running twice when both events
|
|
127
127
|
// fire for the same crash.
|
|
128
128
|
this.cleanedUp = false;
|
|
129
|
-
const memArgs = maxMemoryMb
|
|
130
|
-
? [`--max-old-space-size=${maxMemoryMb}`]
|
|
131
|
-
: [];
|
|
129
|
+
const memArgs = maxMemoryMb ? [`--max-old-space-size=${maxMemoryMb}`] : [];
|
|
132
130
|
this.child = childProcess.fork(modulePath, {
|
|
133
131
|
execArgv: [...memArgs, ...execArgv],
|
|
134
132
|
env: Object.assign({}, process.env),
|
|
@@ -167,7 +165,10 @@ const WORKER_RSS_RECYCLE_MB = (() => {
|
|
|
167
165
|
// Methods that must skip the indexing backlog. encodeQuery is the search hot
|
|
168
166
|
// path: a single query is ~17ms but waits behind every queued processFile.
|
|
169
167
|
// rerank is similarly small and latency-sensitive.
|
|
170
|
-
const PRIORITY_METHODS = new Set([
|
|
168
|
+
const PRIORITY_METHODS = new Set([
|
|
169
|
+
"encodeQuery",
|
|
170
|
+
"rerank",
|
|
171
|
+
]);
|
|
171
172
|
class WorkerPool {
|
|
172
173
|
constructor() {
|
|
173
174
|
this.workers = [];
|
|
@@ -320,7 +321,9 @@ class WorkerPool {
|
|
|
320
321
|
if (task.method === "processFile") {
|
|
321
322
|
result = reviveProcessFileResult(result);
|
|
322
323
|
}
|
|
323
|
-
const elapsed = task.startTime
|
|
324
|
+
const elapsed = task.startTime
|
|
325
|
+
? `${Date.now() - task.startTime}ms`
|
|
326
|
+
: "?ms";
|
|
324
327
|
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
328
|
(0, logger_1.debug)("pool", `complete task=${task.id} method=${task.method} ${elapsed}${filePath ? ` file=${filePath}` : ""}`);
|
|
326
329
|
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.19.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",
|
|
@@ -71,38 +75,37 @@
|
|
|
71
75
|
"license": "Apache-2.0",
|
|
72
76
|
"description": "Semantic code search for coding agents. Local embeddings, LLM summaries, call graph tracing.",
|
|
73
77
|
"dependencies": {
|
|
74
|
-
"@clack/prompts": "^1.
|
|
75
|
-
"@huggingface/transformers": "^4.
|
|
78
|
+
"@clack/prompts": "^1.6.0",
|
|
79
|
+
"@huggingface/transformers": "^4.2.0",
|
|
76
80
|
"@lancedb/lancedb": "^0.30.0",
|
|
77
81
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
78
82
|
"@parcel/watcher": "^2.5.6",
|
|
79
83
|
"apache-arrow": "^18.1.0",
|
|
80
84
|
"chalk": "^5.6.2",
|
|
81
85
|
"cli-highlight": "^2.1.11",
|
|
82
|
-
"commander": "^
|
|
83
|
-
"dotenv": "^17.2
|
|
86
|
+
"commander": "^15.0.0",
|
|
87
|
+
"dotenv": "^17.4.2",
|
|
84
88
|
"fast-glob": "^3.3.3",
|
|
85
89
|
"ignore": "^7.0.5",
|
|
86
|
-
"lmdb": "^3.5.
|
|
90
|
+
"lmdb": "^3.5.6",
|
|
87
91
|
"onnxruntime-node": "1.24.3",
|
|
88
|
-
"openai": "^6.
|
|
89
|
-
"ora": "^9.
|
|
90
|
-
"piscina": "^5.1.4",
|
|
92
|
+
"openai": "^6.45.0",
|
|
93
|
+
"ora": "^9.4.1",
|
|
91
94
|
"proper-lockfile": "^4.1.2",
|
|
92
95
|
"simsimd": "^6.5.5",
|
|
93
|
-
"uuid": "^
|
|
94
|
-
"web-tree-sitter": "^0.26.
|
|
95
|
-
"zod": "^4.
|
|
96
|
+
"uuid": "^14.0.1",
|
|
97
|
+
"web-tree-sitter": "^0.26.9",
|
|
98
|
+
"zod": "^4.4.3"
|
|
96
99
|
},
|
|
97
100
|
"devDependencies": {
|
|
98
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.87",
|
|
99
101
|
"@biomejs/biome": "2.4.10",
|
|
100
|
-
"@types/node": "^
|
|
102
|
+
"@types/node": "^26.0.1",
|
|
101
103
|
"@types/proper-lockfile": "^4.1.4",
|
|
102
|
-
"node-gyp": "^
|
|
104
|
+
"node-gyp": "^13.0.0",
|
|
103
105
|
"ts-node": "^10.9.2",
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
106
|
+
"tsx": "^4.22.4",
|
|
107
|
+
"typescript": "^6.0.3",
|
|
108
|
+
"vite": "^8.1.0",
|
|
109
|
+
"vitest": "^4.1.9"
|
|
107
110
|
}
|
|
108
111
|
}
|
|
@@ -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
|
|
@@ -127,21 +127,35 @@ function startPythonServer(serverDir, scriptName, logName, processName) {
|
|
|
127
127
|
VIRTUAL_ENV: "",
|
|
128
128
|
CONDA_DEFAULT_ENV: "",
|
|
129
129
|
GMAX_PROCESS_NAME: processName || logName,
|
|
130
|
-
HF_TOKEN_PATH:
|
|
130
|
+
HF_TOKEN_PATH:
|
|
131
|
+
process.env.HF_TOKEN_PATH ||
|
|
132
|
+
_path.join(
|
|
133
|
+
require("node:os").homedir(),
|
|
134
|
+
".cache",
|
|
135
|
+
"huggingface",
|
|
136
|
+
"token",
|
|
137
|
+
),
|
|
131
138
|
},
|
|
132
139
|
});
|
|
133
140
|
child.unref();
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
// --- Crash counter (Item 14) ---
|
|
137
|
-
const CRASH_FILE = _path.join(
|
|
144
|
+
const CRASH_FILE = _path.join(
|
|
145
|
+
require("node:os").homedir(),
|
|
146
|
+
".gmax",
|
|
147
|
+
"mlx-embed-crashes.json",
|
|
148
|
+
);
|
|
138
149
|
const MAX_CRASHES = 3;
|
|
139
150
|
const CRASH_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
|
|
140
151
|
|
|
141
152
|
function readCrashCount() {
|
|
142
153
|
try {
|
|
143
154
|
const data = JSON.parse(fs.readFileSync(CRASH_FILE, "utf-8"));
|
|
144
|
-
if (
|
|
155
|
+
if (
|
|
156
|
+
data.lastCrash &&
|
|
157
|
+
Date.now() - new Date(data.lastCrash).getTime() > CRASH_WINDOW_MS
|
|
158
|
+
) {
|
|
145
159
|
return { count: 0, lastCrash: null }; // Window expired, reset
|
|
146
160
|
}
|
|
147
161
|
return { count: data.count || 0, lastCrash: data.lastCrash };
|
|
@@ -157,7 +171,9 @@ function writeCrashCount(count, lastCrash) {
|
|
|
157
171
|
}
|
|
158
172
|
|
|
159
173
|
function resetCrashCount() {
|
|
160
|
-
try {
|
|
174
|
+
try {
|
|
175
|
+
fs.unlinkSync(CRASH_FILE);
|
|
176
|
+
} catch {}
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
function isProjectRegistered() {
|
|
@@ -167,7 +183,9 @@ function isProjectRegistered() {
|
|
|
167
183
|
".gmax",
|
|
168
184
|
"projects.json",
|
|
169
185
|
);
|
|
170
|
-
const projects = JSON.parse(
|
|
186
|
+
const projects = JSON.parse(
|
|
187
|
+
require("node:fs").readFileSync(projectsPath, "utf-8"),
|
|
188
|
+
);
|
|
171
189
|
const cwd = process.cwd();
|
|
172
190
|
return projects.some((p) => cwd.startsWith(p.root));
|
|
173
191
|
} catch {
|
|
@@ -178,7 +196,10 @@ function isProjectRegistered() {
|
|
|
178
196
|
function startWatcher() {
|
|
179
197
|
if (!isProjectRegistered()) return;
|
|
180
198
|
try {
|
|
181
|
-
execFileSync("gmax", ["watch", "--daemon", "-b"], {
|
|
199
|
+
execFileSync("gmax", ["watch", "--daemon", "-b"], {
|
|
200
|
+
timeout: 5000,
|
|
201
|
+
stdio: "ignore",
|
|
202
|
+
});
|
|
182
203
|
} catch {
|
|
183
204
|
// Fallback to per-project mode (older gmax without --daemon)
|
|
184
205
|
try {
|
|
@@ -200,14 +221,19 @@ async function main() {
|
|
|
200
221
|
if (serverDir && !embedRunning) {
|
|
201
222
|
const crashes = readCrashCount();
|
|
202
223
|
if (crashes.count < MAX_CRASHES) {
|
|
203
|
-
startPythonServer(
|
|
224
|
+
startPythonServer(
|
|
225
|
+
serverDir,
|
|
226
|
+
"server.py",
|
|
227
|
+
"mlx-embed-server",
|
|
228
|
+
"gmax-embed",
|
|
229
|
+
);
|
|
204
230
|
|
|
205
231
|
// Fire-and-forget health verification (Item 13)
|
|
206
232
|
(async () => {
|
|
207
233
|
const maxAttempts = 5;
|
|
208
234
|
const delayMs = 2000;
|
|
209
235
|
for (let i = 0; i < maxAttempts; i++) {
|
|
210
|
-
await new Promise(r => setTimeout(r, delayMs));
|
|
236
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
211
237
|
if (await isServerRunning(8100)) {
|
|
212
238
|
resetCrashCount();
|
|
213
239
|
return;
|
|
@@ -223,8 +249,17 @@ async function main() {
|
|
|
223
249
|
}
|
|
224
250
|
|
|
225
251
|
// Start LLM summarizer server (port 8101) — opt-in only
|
|
226
|
-
if (
|
|
227
|
-
|
|
252
|
+
if (
|
|
253
|
+
process.env.GMAX_SUMMARIZER === "1" &&
|
|
254
|
+
serverDir &&
|
|
255
|
+
!(await isServerRunning(8101))
|
|
256
|
+
) {
|
|
257
|
+
startPythonServer(
|
|
258
|
+
serverDir,
|
|
259
|
+
"summarizer.py",
|
|
260
|
+
"mlx-summarizer",
|
|
261
|
+
"gmax-summarizer",
|
|
262
|
+
);
|
|
228
263
|
}
|
|
229
264
|
}
|
|
230
265
|
|
|
@@ -33,10 +33,7 @@ function isProjectRegistered() {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Agents that already know about gmax or can't use Bash
|
|
36
|
-
const SKIP_AGENT_TYPES = [
|
|
37
|
-
"grepmax:semantic-explore",
|
|
38
|
-
"statusline-setup",
|
|
39
|
-
];
|
|
36
|
+
const SKIP_AGENT_TYPES = ["grepmax:semantic-explore", "statusline-setup"];
|
|
40
37
|
|
|
41
38
|
async function main() {
|
|
42
39
|
const input = await readStdin();
|