grepmax 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/NOTICE +2 -2
- package/README.md +72 -72
- package/dist/commands/claude-code.js +6 -6
- package/dist/commands/codex.js +17 -17
- package/dist/commands/doctor.js +6 -5
- package/dist/commands/droid.js +22 -22
- package/dist/commands/index.js +1 -1
- package/dist/commands/list.js +82 -19
- package/dist/commands/mcp.js +177 -158
- package/dist/commands/opencode.js +26 -26
- package/dist/commands/search.js +23 -13
- package/dist/commands/serve.js +30 -30
- package/dist/commands/setup.js +51 -40
- package/dist/commands/skeleton.js +19 -13
- package/dist/commands/symbols.js +40 -2
- package/dist/commands/verify.js +1 -1
- package/dist/commands/watch.js +206 -0
- package/dist/config.js +37 -7
- package/dist/eval.js +14 -14
- package/dist/index.js +11 -7
- package/dist/lib/core/languages.js +28 -0
- package/dist/lib/index/chunker.js +6 -3
- package/dist/lib/index/grammar-loader.js +2 -2
- package/dist/lib/index/ignore-patterns.js +1 -1
- package/dist/lib/index/index-config.js +50 -10
- package/dist/lib/index/sync-helpers.js +1 -1
- package/dist/lib/index/syncer.js +67 -45
- package/dist/lib/index/walker.js +3 -3
- package/dist/lib/index/watcher.js +4 -4
- package/dist/lib/output/formatter.js +1 -1
- package/dist/lib/search/searcher.js +9 -9
- package/dist/lib/setup/model-loader.js +3 -3
- package/dist/lib/setup/setup-helpers.js +2 -4
- package/dist/lib/skeleton/body-fields.js +20 -0
- package/dist/lib/skeleton/retriever.js +1 -1
- package/dist/lib/skeleton/skeletonizer.js +8 -2
- package/dist/lib/skeleton/summary-formatter.js +1 -4
- package/dist/lib/store/meta-cache.js +28 -3
- package/dist/lib/store/vector-db.js +17 -9
- package/dist/lib/utils/formatter.js +3 -3
- package/dist/lib/utils/lock.js +1 -1
- package/dist/lib/utils/project-registry.js +83 -0
- package/dist/lib/utils/project-root.js +32 -57
- package/dist/lib/utils/watcher-registry.js +100 -0
- package/dist/lib/workers/colbert-math.js +2 -2
- package/dist/lib/workers/download-worker.js +2 -2
- package/dist/lib/workers/embeddings/colbert.js +2 -2
- package/dist/lib/workers/embeddings/granite.js +4 -4
- package/dist/lib/workers/embeddings/mlx-client.js +1 -1
- package/dist/lib/workers/orchestrator.js +8 -8
- package/dist/lib/workers/pool.js +1 -1
- package/dist/lib/workers/worker.js +4 -1
- package/package.json +20 -21
- package/plugins/{osgrep → grepmax}/.claude-plugin/plugin.json +4 -4
- package/plugins/grepmax/hooks/start.js +63 -0
- package/plugins/grepmax/hooks/stop.js +3 -0
- package/plugins/{osgrep/skills/osgrep → grepmax/skills/gmax}/SKILL.md +11 -11
- package/plugins/osgrep/hooks/start.js +0 -90
- package/plugins/osgrep/hooks/stop.js +0 -3
- /package/plugins/{osgrep → grepmax}/hooks.json +0 -0
|
@@ -17,29 +17,29 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
17
17
|
const node_os_1 = __importDefault(require("node:os"));
|
|
18
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
19
|
const commander_1 = require("commander");
|
|
20
|
-
const TOOL_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "tool", "
|
|
21
|
-
const PLUGIN_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "plugin", "
|
|
20
|
+
const TOOL_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "tool", "gmax.ts");
|
|
21
|
+
const PLUGIN_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "plugin", "gmax.ts");
|
|
22
22
|
const CONFIG_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".config", "opencode", "opencode.json");
|
|
23
23
|
const SHIM_CONTENT = `
|
|
24
24
|
import { tool } from "@opencode-ai/plugin";
|
|
25
25
|
|
|
26
26
|
const SKILL = \`
|
|
27
27
|
---
|
|
28
|
-
name:
|
|
29
|
-
description: Semantic code search. Use alongside grep - grep for exact strings,
|
|
30
|
-
allowed-tools: "Bash(
|
|
28
|
+
name: gmax
|
|
29
|
+
description: Semantic code search. Use alongside grep - grep for exact strings, gmax for concepts.
|
|
30
|
+
allowed-tools: "Bash(gmax:*), Read"
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
-
## What
|
|
33
|
+
## What gmax does
|
|
34
34
|
|
|
35
|
-
Finds code by meaning. When you'd ask a colleague "where do we handle auth?", use
|
|
35
|
+
Finds code by meaning. When you'd ask a colleague "where do we handle auth?", use gmax.
|
|
36
36
|
|
|
37
37
|
- grep/ripgrep: exact string match, fast
|
|
38
|
-
-
|
|
38
|
+
- gmax: concept match, finds code you couldn't grep for
|
|
39
39
|
|
|
40
40
|
## Primary command
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
gmax "where do we validate user permissions"
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
Returns ~10 results with code snippets (15+ lines each). Usually enough to understand what's happening.
|
|
@@ -65,7 +65,7 @@ export async function handleAuth(req: Request) {
|
|
|
65
65
|
|
|
66
66
|
The snippet often has enough context. But if you need more:
|
|
67
67
|
|
|
68
|
-
#
|
|
68
|
+
# gmax found src/auth/handler.ts:45-90 as ORCH
|
|
69
69
|
Read src/auth/handler.ts:45-120
|
|
70
70
|
|
|
71
71
|
|
|
@@ -74,32 +74,32 @@ Read the specific line range, not the whole file.
|
|
|
74
74
|
## Other commands
|
|
75
75
|
|
|
76
76
|
# Trace call graph (who calls X, what X calls)
|
|
77
|
-
|
|
77
|
+
gmax trace handleAuth
|
|
78
78
|
|
|
79
79
|
# Skeleton of a huge file (to find which ranges to read)
|
|
80
|
-
|
|
80
|
+
gmax skeleton src/giant-2000-line-file.ts
|
|
81
81
|
|
|
82
82
|
# Just file paths when you only need locations
|
|
83
|
-
|
|
83
|
+
gmax "authentication" --compact
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
## Workflow: architecture questions
|
|
87
87
|
|
|
88
88
|
# 1. Find entry points
|
|
89
|
-
|
|
89
|
+
gmax "where do requests enter the server"
|
|
90
90
|
# Review the ORCH results - code is shown
|
|
91
91
|
|
|
92
92
|
# 2. If you need deeper context on a specific function
|
|
93
93
|
Read src/server/handler.ts:45-120
|
|
94
94
|
|
|
95
95
|
# 3. Trace to understand call flow
|
|
96
|
-
|
|
96
|
+
gmax trace handleRequest
|
|
97
97
|
|
|
98
98
|
## Tips
|
|
99
99
|
|
|
100
100
|
- More words = better results. "auth" is vague. "where does the server validate JWT tokens" is specific.
|
|
101
101
|
- ORCH results contain the logic - prioritize these
|
|
102
|
-
- Don't read entire files. Use the line ranges
|
|
102
|
+
- Don't read entire files. Use the line ranges gmax gives you.
|
|
103
103
|
- If results seem off, rephrase your query like you'd ask a teammate
|
|
104
104
|
|
|
105
105
|
\`;
|
|
@@ -108,16 +108,16 @@ export default tool({
|
|
|
108
108
|
description: SKILL,
|
|
109
109
|
args: {
|
|
110
110
|
argv: tool.schema.array(tool.schema.string())
|
|
111
|
-
.describe("Arguments for
|
|
111
|
+
.describe("Arguments for gmax, e.g. ['search', 'user auth']")
|
|
112
112
|
},
|
|
113
113
|
async execute({ argv }) {
|
|
114
114
|
try {
|
|
115
115
|
// @ts-ignore
|
|
116
|
-
const out = await Bun.spawn(["
|
|
116
|
+
const out = await Bun.spawn(["gmax", ...argv], { stdout: "pipe" }).stdout;
|
|
117
117
|
const text = await new Response(out).text();
|
|
118
118
|
return text.trim();
|
|
119
119
|
} catch (err) {
|
|
120
|
-
return \`Error running
|
|
120
|
+
return \`Error running gmax: \${err}\`;
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
})`;
|
|
@@ -148,14 +148,14 @@ function install() {
|
|
|
148
148
|
config.$schema = "https://opencode.ai/config.json";
|
|
149
149
|
if (!config.mcp)
|
|
150
150
|
config.mcp = {};
|
|
151
|
-
config.mcp.
|
|
151
|
+
config.mcp.gmax = {
|
|
152
152
|
type: "local",
|
|
153
|
-
command: ["
|
|
153
|
+
command: ["gmax", "mcp"],
|
|
154
154
|
enabled: true,
|
|
155
155
|
};
|
|
156
156
|
node_fs_1.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
157
157
|
console.log("✅ Registered MCP server in", CONFIG_PATH);
|
|
158
|
-
console.log(" Command: check proper path if '
|
|
158
|
+
console.log(" Command: check proper path if 'gmax' is not in PATH of OpenCode.");
|
|
159
159
|
}
|
|
160
160
|
catch (err) {
|
|
161
161
|
console.error("❌ Installation failed:", err);
|
|
@@ -174,8 +174,8 @@ function uninstall() {
|
|
|
174
174
|
// 2. Unregister MCP
|
|
175
175
|
if (node_fs_1.default.existsSync(CONFIG_PATH)) {
|
|
176
176
|
const config = JSON.parse(node_fs_1.default.readFileSync(CONFIG_PATH, "utf-8") || "{}");
|
|
177
|
-
if ((_a = config.mcp) === null || _a === void 0 ? void 0 : _a.
|
|
178
|
-
delete config.mcp.
|
|
177
|
+
if ((_a = config.mcp) === null || _a === void 0 ? void 0 : _a.gmax) {
|
|
178
|
+
delete config.mcp.gmax;
|
|
179
179
|
node_fs_1.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
180
180
|
console.log("✅ Unregistered MCP server.");
|
|
181
181
|
}
|
|
@@ -192,8 +192,8 @@ function uninstall() {
|
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
194
|
exports.installOpencode = new commander_1.Command("install-opencode")
|
|
195
|
-
.description("Install
|
|
195
|
+
.description("Install gmax as an OpenCode plugin (Daemon + Tool)")
|
|
196
196
|
.action(install);
|
|
197
197
|
exports.uninstallOpencode = new commander_1.Command("uninstall-opencode")
|
|
198
|
-
.description("Remove the
|
|
198
|
+
.description("Remove the gmax OpenCode plugin")
|
|
199
199
|
.action(uninstall);
|
package/dist/commands/search.js
CHANGED
|
@@ -178,7 +178,7 @@ function formatCompactTSV(hits, projectRoot, query) {
|
|
|
178
178
|
if (!hits.length)
|
|
179
179
|
return "No matches found.";
|
|
180
180
|
const lines = [];
|
|
181
|
-
lines.push(`
|
|
181
|
+
lines.push(`gmax hits\tquery=${query}\tcount=${hits.length}`);
|
|
182
182
|
lines.push("path\tlines\tscore\trole\tconf\tdefined");
|
|
183
183
|
for (const hit of hits) {
|
|
184
184
|
const relPath = path.isAbsolute(hit.path)
|
|
@@ -206,7 +206,7 @@ function formatCompactPretty(hits, projectRoot, query, termWidth, useAnsi) {
|
|
|
206
206
|
const gutters = 5;
|
|
207
207
|
const fixed = wLines + wScore + wRole + wConf + wDef + gutters;
|
|
208
208
|
const wPath = Math.max(24, Math.min(64, termWidth - fixed));
|
|
209
|
-
const header = `
|
|
209
|
+
const header = `gmax hits count=${hits.length} query="${query}"`;
|
|
210
210
|
const cols = [
|
|
211
211
|
padR("path", wPath),
|
|
212
212
|
padR("lines", wLines),
|
|
@@ -277,13 +277,17 @@ function outputSkeletons(results, projectRoot, limit, db) {
|
|
|
277
277
|
}
|
|
278
278
|
const skeletonOpts = { includeSummary: true };
|
|
279
279
|
const skeletonResults = [];
|
|
280
|
-
for (const
|
|
280
|
+
for (const filePath of filesToProcess) {
|
|
281
|
+
// Paths from search results are now absolute (centralized index)
|
|
282
|
+
const absPath = path.isAbsolute(filePath)
|
|
283
|
+
? filePath
|
|
284
|
+
: path.resolve(projectRoot, filePath);
|
|
281
285
|
// 1. Try DB cache
|
|
282
286
|
if (db) {
|
|
283
|
-
const cached = yield (0, retriever_1.getStoredSkeleton)(db,
|
|
287
|
+
const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
|
|
284
288
|
if (cached) {
|
|
285
289
|
skeletonResults.push({
|
|
286
|
-
file:
|
|
290
|
+
file: filePath,
|
|
287
291
|
skeleton: cached,
|
|
288
292
|
tokens: Math.ceil(cached.length / 4), // Rough estimate
|
|
289
293
|
});
|
|
@@ -292,20 +296,19 @@ function outputSkeletons(results, projectRoot, limit, db) {
|
|
|
292
296
|
}
|
|
293
297
|
// 2. Fallback to fresh generation
|
|
294
298
|
yield globalSkeletonizer.init();
|
|
295
|
-
const absPath = path.resolve(projectRoot, relPath);
|
|
296
299
|
if (!fs.existsSync(absPath)) {
|
|
297
300
|
skeletonResults.push({
|
|
298
|
-
file:
|
|
299
|
-
skeleton: `// File not found: ${
|
|
301
|
+
file: filePath,
|
|
302
|
+
skeleton: `// File not found: ${filePath}`,
|
|
300
303
|
tokens: 0,
|
|
301
304
|
error: "File not found",
|
|
302
305
|
});
|
|
303
306
|
continue;
|
|
304
307
|
}
|
|
305
308
|
const content = fs.readFileSync(absPath, "utf-8");
|
|
306
|
-
const res = yield globalSkeletonizer.skeletonizeFile(
|
|
309
|
+
const res = yield globalSkeletonizer.skeletonizeFile(absPath, content, skeletonOpts);
|
|
307
310
|
skeletonResults.push({
|
|
308
|
-
file:
|
|
311
|
+
file: filePath,
|
|
309
312
|
skeleton: res.skeleton,
|
|
310
313
|
tokens: res.tokenEstimate,
|
|
311
314
|
error: res.error,
|
|
@@ -425,11 +428,11 @@ exports.search = new commander_1.Command("search")
|
|
|
425
428
|
const projectRoot = (_b = (0, project_root_1.findProjectRoot)(searchRoot)) !== null && _b !== void 0 ? _b : searchRoot;
|
|
426
429
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
427
430
|
// Propagate project root to worker processes
|
|
428
|
-
process.env.
|
|
431
|
+
process.env.GMAX_PROJECT_ROOT = projectRoot;
|
|
429
432
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
430
433
|
// Check for active indexing lock and warn if present
|
|
431
434
|
// This allows agents (via shim) to know results might be partial.
|
|
432
|
-
if ((0, lock_1.isLocked)(paths.
|
|
435
|
+
if ((0, lock_1.isLocked)(paths.dataDir)) {
|
|
433
436
|
console.warn("⚠️ Warning: Indexing in progress... search results may be incomplete.");
|
|
434
437
|
}
|
|
435
438
|
const hasRows = yield vectorDb.hasAnyRows();
|
|
@@ -475,7 +478,14 @@ exports.search = new commander_1.Command("search")
|
|
|
475
478
|
}
|
|
476
479
|
}
|
|
477
480
|
const searcher = new searcher_1.Searcher(vectorDb);
|
|
478
|
-
|
|
481
|
+
// Use absolute path prefix for filtering
|
|
482
|
+
const searchPathPrefix = exec_path
|
|
483
|
+
? path.resolve(exec_path)
|
|
484
|
+
: projectRoot;
|
|
485
|
+
const pathFilter = searchPathPrefix.endsWith("/")
|
|
486
|
+
? searchPathPrefix
|
|
487
|
+
: `${searchPathPrefix}/`;
|
|
488
|
+
const searchResult = yield searcher.search(pattern, parseInt(options.m, 10), { rerank: true }, undefined, pathFilter);
|
|
479
489
|
const filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
|
|
480
490
|
if (options.skeleton) {
|
|
481
491
|
yield outputSkeletons(filteredData, projectRoot, parseInt(options.m, 10), vectorDb);
|
package/dist/commands/serve.js
CHANGED
|
@@ -49,8 +49,8 @@ const http = __importStar(require("node:http"));
|
|
|
49
49
|
const path = __importStar(require("node:path"));
|
|
50
50
|
const commander_1 = require("commander");
|
|
51
51
|
const config_1 = require("../config");
|
|
52
|
-
const index_config_1 = require("../lib/index/index-config");
|
|
53
52
|
const grammar_loader_1 = require("../lib/index/grammar-loader");
|
|
53
|
+
const index_config_1 = require("../lib/index/index-config");
|
|
54
54
|
const sync_helpers_1 = require("../lib/index/sync-helpers");
|
|
55
55
|
const syncer_1 = require("../lib/index/syncer");
|
|
56
56
|
const watcher_1 = require("../lib/index/watcher");
|
|
@@ -64,13 +64,19 @@ const server_registry_1 = require("../lib/utils/server-registry");
|
|
|
64
64
|
function isMlxServerUp() {
|
|
65
65
|
const port = parseInt(process.env.MLX_EMBED_PORT || "8100", 10);
|
|
66
66
|
return new Promise((resolve) => {
|
|
67
|
-
const req = http.get({ hostname: "127.0.0.1", port, path: "/health", timeout: 2000 }, (res) => {
|
|
67
|
+
const req = http.get({ hostname: "127.0.0.1", port, path: "/health", timeout: 2000 }, (res) => {
|
|
68
|
+
res.resume();
|
|
69
|
+
resolve(res.statusCode === 200);
|
|
70
|
+
});
|
|
68
71
|
req.on("error", () => resolve(false));
|
|
69
|
-
req.on("timeout", () => {
|
|
72
|
+
req.on("timeout", () => {
|
|
73
|
+
req.destroy();
|
|
74
|
+
resolve(false);
|
|
75
|
+
});
|
|
70
76
|
});
|
|
71
77
|
}
|
|
72
78
|
function startMlxServer(mlxModel) {
|
|
73
|
-
// Look for mlx-embed-server relative to the
|
|
79
|
+
// Look for mlx-embed-server relative to the grepmax package
|
|
74
80
|
const candidates = [
|
|
75
81
|
path.resolve(__dirname, "../../mlx-embed-server"),
|
|
76
82
|
path.resolve(__dirname, "../mlx-embed-server"),
|
|
@@ -94,8 +100,8 @@ function startMlxServer(mlxModel) {
|
|
|
94
100
|
return child;
|
|
95
101
|
}
|
|
96
102
|
exports.serve = new commander_1.Command("serve")
|
|
97
|
-
.description("Run
|
|
98
|
-
.option("-p, --port <port>", "Port to listen on", process.env.
|
|
103
|
+
.description("Run gmax as a background server with live indexing")
|
|
104
|
+
.option("-p, --port <port>", "Port to listen on", process.env.GMAX_PORT || "4444")
|
|
99
105
|
.option("-b, --background", "Run in background", false)
|
|
100
106
|
.option("--cpu", "Use CPU-only embeddings (skip MLX GPU server)", false)
|
|
101
107
|
.option("--no-idle-timeout", "Disable the 30-minute idle shutdown", false)
|
|
@@ -117,7 +123,9 @@ exports.serve = new commander_1.Command("serve")
|
|
|
117
123
|
.filter((arg) => arg !== "-b" && arg !== "--background");
|
|
118
124
|
const logDir = path.join(config_1.PATHS.globalRoot, "logs");
|
|
119
125
|
fs.mkdirSync(logDir, { recursive: true });
|
|
120
|
-
const safeName = path
|
|
126
|
+
const safeName = path
|
|
127
|
+
.basename(projectRoot)
|
|
128
|
+
.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
121
129
|
const logFile = path.join(logDir, `server-${safeName}.log`);
|
|
122
130
|
const out = fs.openSync(logFile, "a");
|
|
123
131
|
const err = fs.openSync(logFile, "a");
|
|
@@ -125,7 +133,7 @@ exports.serve = new commander_1.Command("serve")
|
|
|
125
133
|
detached: true,
|
|
126
134
|
stdio: ["ignore", out, err],
|
|
127
135
|
cwd: process.cwd(),
|
|
128
|
-
env: Object.assign(Object.assign({}, process.env), {
|
|
136
|
+
env: Object.assign(Object.assign({}, process.env), { GMAX_BACKGROUND: "true" }),
|
|
129
137
|
});
|
|
130
138
|
child.unref();
|
|
131
139
|
console.log(`Started background server (PID: ${child.pid})`);
|
|
@@ -134,7 +142,7 @@ exports.serve = new commander_1.Command("serve")
|
|
|
134
142
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
135
143
|
const projectName = path.basename(projectRoot);
|
|
136
144
|
// Propagate project root to worker processes
|
|
137
|
-
process.env.
|
|
145
|
+
process.env.GMAX_PROJECT_ROOT = projectRoot;
|
|
138
146
|
// Determine embed mode: --cpu flag overrides, then config, then default
|
|
139
147
|
// Default to GPU on Apple Silicon, CPU everywhere else
|
|
140
148
|
const isAppleSilicon = process.arch === "arm64" && process.platform === "darwin";
|
|
@@ -183,7 +191,7 @@ exports.serve = new commander_1.Command("serve")
|
|
|
183
191
|
yield (0, setup_helpers_1.ensureSetup)();
|
|
184
192
|
yield (0, grammar_loader_1.ensureGrammars)(console.log, { silent: true });
|
|
185
193
|
// Initial sync is self-contained (creates+closes its own VectorDB+MetaCache).
|
|
186
|
-
if (!process.env.
|
|
194
|
+
if (!process.env.GMAX_BACKGROUND) {
|
|
187
195
|
const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, "Indexing before starting server...");
|
|
188
196
|
try {
|
|
189
197
|
yield (0, syncer_1.initialSync)({ projectRoot, onProgress });
|
|
@@ -206,7 +214,7 @@ exports.serve = new commander_1.Command("serve")
|
|
|
206
214
|
projectRoot,
|
|
207
215
|
vectorDb,
|
|
208
216
|
metaCache,
|
|
209
|
-
|
|
217
|
+
dataDir: paths.dataDir,
|
|
210
218
|
onReindex: (files, durationMs) => {
|
|
211
219
|
console.log(`[watch:${projectName}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(durationMs / 1000).toFixed(1)}s)`);
|
|
212
220
|
},
|
|
@@ -242,7 +250,7 @@ exports.serve = new commander_1.Command("serve")
|
|
|
242
250
|
const cfg = (0, index_config_1.readIndexConfig)(paths.configPath);
|
|
243
251
|
const stats = {
|
|
244
252
|
files: dbStats.chunks > 0
|
|
245
|
-
?
|
|
253
|
+
? yield vectorDb.getDistinctFileCount()
|
|
246
254
|
: 0,
|
|
247
255
|
chunks: dbStats.chunks,
|
|
248
256
|
totalBytes: dbStats.totalBytes,
|
|
@@ -260,7 +268,9 @@ exports.serve = new commander_1.Command("serve")
|
|
|
260
268
|
catch (err) {
|
|
261
269
|
res.statusCode = 500;
|
|
262
270
|
res.setHeader("Content-Type", "application/json");
|
|
263
|
-
res.end(JSON.stringify({
|
|
271
|
+
res.end(JSON.stringify({
|
|
272
|
+
error: (err === null || err === void 0 ? void 0 : err.message) || "stats_failed",
|
|
273
|
+
}));
|
|
264
274
|
}
|
|
265
275
|
return;
|
|
266
276
|
}
|
|
@@ -292,23 +302,13 @@ exports.serve = new commander_1.Command("serve")
|
|
|
292
302
|
: {};
|
|
293
303
|
const query = typeof body.query === "string" ? body.query : "";
|
|
294
304
|
const limit = typeof body.limit === "number" ? body.limit : 10;
|
|
295
|
-
|
|
305
|
+
// Use absolute path prefix for search filtering
|
|
306
|
+
let searchPath = `${projectRoot}/`;
|
|
296
307
|
if (typeof body.path === "string") {
|
|
297
308
|
const resolvedPath = path.resolve(projectRoot, body.path);
|
|
298
|
-
|
|
299
|
-
?
|
|
300
|
-
: `${
|
|
301
|
-
// Normalize paths for consistency (Windows/Linux)
|
|
302
|
-
const normalizedRootPrefix = path.normalize(rootPrefix);
|
|
303
|
-
const normalizedResolvedPath = path.normalize(resolvedPath);
|
|
304
|
-
if (normalizedResolvedPath !== projectRoot &&
|
|
305
|
-
!normalizedResolvedPath.startsWith(normalizedRootPrefix)) {
|
|
306
|
-
res.statusCode = 400;
|
|
307
|
-
res.setHeader("Content-Type", "application/json");
|
|
308
|
-
res.end(JSON.stringify({ error: "invalid_path" }));
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
searchPath = path.relative(projectRoot, resolvedPath);
|
|
309
|
+
searchPath = resolvedPath.endsWith("/")
|
|
310
|
+
? resolvedPath
|
|
311
|
+
: `${resolvedPath}/`;
|
|
312
312
|
}
|
|
313
313
|
// Add AbortController for cancellation
|
|
314
314
|
const ac = new AbortController();
|
|
@@ -383,8 +383,8 @@ exports.serve = new commander_1.Command("serve")
|
|
|
383
383
|
server.listen(port, () => {
|
|
384
384
|
const address = server.address();
|
|
385
385
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
386
|
-
if (!process.env.
|
|
387
|
-
console.log(`
|
|
386
|
+
if (!process.env.GMAX_BACKGROUND) {
|
|
387
|
+
console.log(`gmax server listening on http://localhost:${actualPort} (${projectRoot})`);
|
|
388
388
|
}
|
|
389
389
|
(0, server_registry_1.registerServer)({
|
|
390
390
|
pid: process.pid,
|
package/dist/commands/setup.js
CHANGED
|
@@ -51,19 +51,13 @@ const config_1 = require("../config");
|
|
|
51
51
|
const grammar_loader_1 = require("../lib/index/grammar-loader");
|
|
52
52
|
const index_config_1 = require("../lib/index/index-config");
|
|
53
53
|
const setup_helpers_1 = require("../lib/setup/setup-helpers");
|
|
54
|
-
const project_root_1 = require("../lib/utils/project-root");
|
|
55
54
|
const exit_1 = require("../lib/utils/exit");
|
|
56
|
-
const
|
|
57
|
-
{
|
|
58
|
-
value: "ibm-granite/granite-embedding-small-english-r2",
|
|
59
|
-
label: "Granite Small (general purpose, 384-dim)",
|
|
60
|
-
},
|
|
61
|
-
];
|
|
55
|
+
const project_root_1 = require("../lib/utils/project-root");
|
|
62
56
|
exports.setup = new commander_1.Command("setup")
|
|
63
57
|
.description("Interactive setup: download models, choose embedding mode")
|
|
64
58
|
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
-
var _a, _b;
|
|
66
|
-
p.intro("
|
|
59
|
+
var _a, _b, _c, _d;
|
|
60
|
+
p.intro("gmax setup");
|
|
67
61
|
// Step 1: Download ONNX models + grammars (existing behavior)
|
|
68
62
|
try {
|
|
69
63
|
yield (0, setup_helpers_1.ensureSetup)();
|
|
@@ -84,9 +78,9 @@ exports.setup = new commander_1.Command("setup")
|
|
|
84
78
|
const modelPath = path.join(config_1.PATHS.models, ...id.split("/"));
|
|
85
79
|
return { id, exists: fs.existsSync(modelPath) };
|
|
86
80
|
});
|
|
87
|
-
|
|
81
|
+
for (const { id, exists } of modelStatuses) {
|
|
88
82
|
p.log.info(`${exists ? "✓" : "✗"} ${id}`);
|
|
89
|
-
}
|
|
83
|
+
}
|
|
90
84
|
// Check skiplist
|
|
91
85
|
const colbertPath = path.join(config_1.PATHS.models, ...config_1.MODEL_IDS.colbert.split("/"));
|
|
92
86
|
const skiplistPath = path.join(colbertPath, "skiplist.json");
|
|
@@ -100,13 +94,31 @@ exports.setup = new commander_1.Command("setup")
|
|
|
100
94
|
p.log.success("Skiplist downloaded");
|
|
101
95
|
}
|
|
102
96
|
}
|
|
103
|
-
catch (
|
|
97
|
+
catch (_e) {
|
|
104
98
|
p.log.warn("Skiplist download failed (will use fallback)");
|
|
105
99
|
}
|
|
106
100
|
}
|
|
107
|
-
// Step 3:
|
|
101
|
+
// Step 3: Read existing config
|
|
108
102
|
const paths = (0, project_root_1.ensureProjectPaths)(process.cwd());
|
|
109
103
|
const existingConfig = (0, index_config_1.readIndexConfig)(paths.configPath);
|
|
104
|
+
const globalConfig = (0, index_config_1.readGlobalConfig)();
|
|
105
|
+
// Step 4: Model tier selection
|
|
106
|
+
const modelTier = yield p.select({
|
|
107
|
+
message: "Model size",
|
|
108
|
+
options: Object.values(config_1.MODEL_TIERS).map((tier) => ({
|
|
109
|
+
value: tier.id,
|
|
110
|
+
label: tier.label,
|
|
111
|
+
hint: tier.id === "standard" ? "32GB+ RAM recommended" : "recommended",
|
|
112
|
+
})),
|
|
113
|
+
initialValue: (_b = (_a = existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.modelTier) !== null && _a !== void 0 ? _a : globalConfig.modelTier) !== null && _b !== void 0 ? _b : "small",
|
|
114
|
+
});
|
|
115
|
+
if (p.isCancel(modelTier)) {
|
|
116
|
+
p.cancel("Setup cancelled");
|
|
117
|
+
yield (0, exit_1.gracefulExit)();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const selectedTier = config_1.MODEL_TIERS[modelTier];
|
|
121
|
+
// Step 5: Embed mode selection
|
|
110
122
|
const embedMode = yield p.select({
|
|
111
123
|
message: "Embedding mode",
|
|
112
124
|
options: [
|
|
@@ -121,40 +133,39 @@ exports.setup = new commander_1.Command("setup")
|
|
|
121
133
|
hint: "Apple Silicon only, faster indexing + search",
|
|
122
134
|
},
|
|
123
135
|
],
|
|
124
|
-
initialValue: (
|
|
136
|
+
initialValue: (_c = existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.embedMode) !== null && _c !== void 0 ? _c : (process.arch === "arm64" && process.platform === "darwin"
|
|
137
|
+
? "gpu"
|
|
138
|
+
: "cpu"),
|
|
125
139
|
});
|
|
126
140
|
if (p.isCancel(embedMode)) {
|
|
127
141
|
p.cancel("Setup cancelled");
|
|
128
142
|
yield (0, exit_1.gracefulExit)();
|
|
129
143
|
return;
|
|
130
144
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
const mlxModel = embedMode === "gpu" ? selectedTier.mlxModel : undefined;
|
|
146
|
+
// Step 6: Write configs
|
|
147
|
+
(0, index_config_1.writeSetupConfig)(paths.configPath, {
|
|
148
|
+
embedMode,
|
|
149
|
+
mlxModel,
|
|
150
|
+
modelTier,
|
|
151
|
+
});
|
|
152
|
+
(0, index_config_1.writeGlobalConfig)({
|
|
153
|
+
modelTier,
|
|
154
|
+
vectorDim: selectedTier.vectorDim,
|
|
155
|
+
embedMode,
|
|
156
|
+
mlxModel,
|
|
157
|
+
});
|
|
158
|
+
// Step 7: Warn about reindex if tier/mode changed
|
|
159
|
+
if (existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.indexedAt) {
|
|
160
|
+
const tierChanged = existingConfig.modelTier !== modelTier;
|
|
161
|
+
const modeChanged = existingConfig.embedMode !== embedMode;
|
|
162
|
+
if (tierChanged) {
|
|
163
|
+
p.log.warn(`Model tier changed (${(_d = existingConfig.vectorDim) !== null && _d !== void 0 ? _d : 384}d → ${selectedTier.vectorDim}d). Existing indexes will be rebuilt on next use.`);
|
|
164
|
+
}
|
|
165
|
+
else if (modeChanged) {
|
|
166
|
+
p.log.warn("Embedding mode changed. Run `gmax serve` to apply the new settings.");
|
|
145
167
|
}
|
|
146
|
-
mlxModel = modelChoice;
|
|
147
|
-
}
|
|
148
|
-
// Step 4: Write config
|
|
149
|
-
(0, index_config_1.writeSetupConfig)(paths.configPath, { embedMode, mlxModel });
|
|
150
|
-
// Step 5: Warn about reindex if mode/model changed
|
|
151
|
-
if ((existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.indexedAt) &&
|
|
152
|
-
(existingConfig.embedMode !== embedMode ||
|
|
153
|
-
existingConfig.mlxModel !== mlxModel)) {
|
|
154
|
-
p.log.warn("Embedding mode changed. Run `osgrep serve` to reindex with the new settings.");
|
|
155
168
|
}
|
|
156
|
-
p.outro(embedMode === "gpu"
|
|
157
|
-
? `Ready — GPU mode with ${mlxModel}`
|
|
158
|
-
: "Ready — CPU mode");
|
|
169
|
+
p.outro(`Ready — ${selectedTier.label}, ${embedMode === "gpu" ? "GPU" : "CPU"} mode`);
|
|
159
170
|
yield (0, exit_1.gracefulExit)();
|
|
160
171
|
}));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* gmax skeleton - Show code skeleton (signatures without implementation)
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* gmax skeleton <file> # Skeleton of a file
|
|
7
|
+
* gmax skeleton <symbol> # Find symbol and skeleton its file
|
|
8
|
+
* gmax skeleton "query" # Search and skeleton top results
|
|
9
9
|
*/
|
|
10
10
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
11
|
if (k2 === undefined) k2 = k;
|
|
@@ -143,8 +143,8 @@ exports.skeleton = new commander_1.Command("skeleton")
|
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
if (vectorDb) {
|
|
146
|
-
|
|
147
|
-
const cached = yield (0, retriever_1.getStoredSkeleton)(vectorDb,
|
|
146
|
+
// Use absolute path for DB lookup (centralized index stores absolute paths)
|
|
147
|
+
const cached = yield (0, retriever_1.getStoredSkeleton)(vectorDb, filePath);
|
|
148
148
|
if (cached) {
|
|
149
149
|
outputResult({
|
|
150
150
|
success: true,
|
|
@@ -163,17 +163,20 @@ exports.skeleton = new commander_1.Command("skeleton")
|
|
|
163
163
|
const filePath = yield findFileBySymbol(target, vectorDb);
|
|
164
164
|
if (!filePath) {
|
|
165
165
|
console.error(`Symbol not found in index: ${target}`);
|
|
166
|
-
console.error("Try running '
|
|
166
|
+
console.error("Try running 'gmax index' first or use a search query.");
|
|
167
167
|
process.exitCode = 1;
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
|
-
|
|
170
|
+
// filePath from DB is absolute (centralized index)
|
|
171
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
172
|
+
? filePath
|
|
173
|
+
: path.resolve(projectRoot, filePath);
|
|
171
174
|
if (!fs.existsSync(absolutePath)) {
|
|
172
175
|
console.error(`File not found: ${absolutePath}`);
|
|
173
176
|
process.exitCode = 1;
|
|
174
177
|
return;
|
|
175
178
|
}
|
|
176
|
-
const cached = yield (0, retriever_1.getStoredSkeleton)(vectorDb,
|
|
179
|
+
const cached = yield (0, retriever_1.getStoredSkeleton)(vectorDb, absolutePath);
|
|
177
180
|
if (cached) {
|
|
178
181
|
outputResult({
|
|
179
182
|
success: true,
|
|
@@ -183,7 +186,7 @@ exports.skeleton = new commander_1.Command("skeleton")
|
|
|
183
186
|
return;
|
|
184
187
|
}
|
|
185
188
|
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
186
|
-
const result = yield skeletonizer.skeletonizeFile(
|
|
189
|
+
const result = yield skeletonizer.skeletonizeFile(absolutePath, content, skeletonOpts);
|
|
187
190
|
outputResult(result, options);
|
|
188
191
|
}
|
|
189
192
|
else {
|
|
@@ -211,7 +214,10 @@ exports.skeleton = new commander_1.Command("skeleton")
|
|
|
211
214
|
// Skeletonize each file
|
|
212
215
|
const results = [];
|
|
213
216
|
for (const filePath of filePaths) {
|
|
214
|
-
|
|
217
|
+
// Paths from search results are absolute (centralized index)
|
|
218
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
219
|
+
? filePath
|
|
220
|
+
: path.resolve(projectRoot, filePath);
|
|
215
221
|
if (!fs.existsSync(absolutePath)) {
|
|
216
222
|
results.push({
|
|
217
223
|
file: filePath,
|
|
@@ -222,7 +228,7 @@ exports.skeleton = new commander_1.Command("skeleton")
|
|
|
222
228
|
continue;
|
|
223
229
|
}
|
|
224
230
|
// Try cache first
|
|
225
|
-
const cached = yield (0, retriever_1.getStoredSkeleton)(vectorDb,
|
|
231
|
+
const cached = yield (0, retriever_1.getStoredSkeleton)(vectorDb, absolutePath);
|
|
226
232
|
if (cached) {
|
|
227
233
|
results.push({
|
|
228
234
|
file: filePath,
|
|
@@ -232,7 +238,7 @@ exports.skeleton = new commander_1.Command("skeleton")
|
|
|
232
238
|
continue;
|
|
233
239
|
}
|
|
234
240
|
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
235
|
-
const result = yield skeletonizer.skeletonizeFile(
|
|
241
|
+
const result = yield skeletonizer.skeletonizeFile(absolutePath, content, skeletonOpts);
|
|
236
242
|
results.push({
|
|
237
243
|
file: filePath,
|
|
238
244
|
skeleton: result.skeleton,
|