grepmax 0.2.5 → 0.3.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/README.md +85 -189
- package/dist/commands/mcp.js +59 -27
- package/dist/commands/watch.js +4 -1
- package/dist/config.js +1 -1
- package/dist/lib/index/index-config.js +1 -1
- package/dist/lib/utils/watcher-registry.js +10 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Natural-language search that works like `grep`. Fast, local, and built for codin
|
|
|
23
23
|
- **Call Graph Tracing:** Map dependencies with `trace` to see who calls what.
|
|
24
24
|
- **Role Detection:** Distinguishes `ORCHESTRATION` (high-level logic) from `DEFINITION` (types/classes).
|
|
25
25
|
- **Local & Private:** 100% local embeddings via ONNX (CPU) or MLX (Apple Silicon GPU).
|
|
26
|
-
- **
|
|
26
|
+
- **Centralized Index:** One database at `~/.gmax/` — index once, search from anywhere.
|
|
27
27
|
- **Agent-Ready:** Native output with symbols, roles, and call graphs.
|
|
28
28
|
|
|
29
29
|
## Quick Start
|
|
@@ -41,25 +41,30 @@ Natural-language search that works like `grep`. Fast, local, and built for codin
|
|
|
41
41
|
|
|
42
42
|
Downloads embedding models (~150MB) upfront and lets you choose between CPU (ONNX) and GPU (MLX) embedding modes. If you skip this, models download automatically on first use.
|
|
43
43
|
|
|
44
|
-
3. **
|
|
44
|
+
3. **Index**
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
cd my-repo
|
|
48
|
-
gmax
|
|
48
|
+
gmax index
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Indexes into a centralized store at `~/.gmax/lancedb/`. You can index any directory — a single repo, a monorepo, or an entire workspace.
|
|
52
|
+
|
|
53
|
+
4. **Search**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
gmax "where do we handle authentication?"
|
|
57
|
+
```
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
5. **Trace** (Call Graph)
|
|
54
60
|
|
|
55
61
|
```bash
|
|
56
62
|
gmax trace "function_name"
|
|
57
63
|
```
|
|
58
|
-
See who calls a function (upstream dependencies) and what it calls (downstream dependencies).
|
|
64
|
+
See who calls a function (upstream dependencies) and what it calls (downstream dependencies).
|
|
59
65
|
|
|
60
|
-
To find the symbols in your code base:
|
|
61
66
|
```bash
|
|
62
|
-
gmax symbols
|
|
67
|
+
gmax symbols # List all indexed symbols
|
|
63
68
|
```
|
|
64
69
|
|
|
65
70
|
In our public benchmarks, `grepmax` can save about 20% of your LLM tokens and deliver a 30% speedup.
|
|
@@ -68,53 +73,44 @@ In our public benchmarks, `grepmax` can save about 20% of your LLM tokens and de
|
|
|
68
73
|
<img src="public/bench.png" alt="gmax benchmark" width="100%" style="border-radius: 8px; margin: 20px 0;" />
|
|
69
74
|
</div>
|
|
70
75
|
|
|
76
|
+
## Agent Plugins
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
### Claude Code Plugin
|
|
78
|
+
### Claude Code
|
|
74
79
|
|
|
75
80
|
1. Run `gmax install-claude-code`
|
|
76
|
-
2. Open Claude Code
|
|
77
|
-
3.
|
|
78
|
-
4. The plugin's hooks auto-start `gmax serve` in the background and shut it down on session end. Claude will use `gmax` for semantic searches automatically but can be encouraged to do so.
|
|
81
|
+
2. Open Claude Code — the plugin auto-starts the MLX GPU server and a background file watcher.
|
|
82
|
+
3. Claude uses `gmax` for semantic searches automatically via MCP tools.
|
|
79
83
|
|
|
80
|
-
### Opencode
|
|
84
|
+
### Opencode
|
|
81
85
|
1. Run `gmax install-opencode`
|
|
82
|
-
2.
|
|
83
|
-
3. Highly recommend indexing your code base before using the plugin.
|
|
84
|
-
4. The plugin's hooks auto-start `gmax serve` in the background and shut it down on session end. OC will use `gmax` for semantic searches automatically but can be encouraged to do so.
|
|
86
|
+
2. OC uses `gmax` for semantic searches via MCP.
|
|
85
87
|
|
|
86
|
-
### Codex
|
|
88
|
+
### Codex
|
|
87
89
|
1. Run `gmax install-codex`
|
|
88
|
-
2. Codex
|
|
90
|
+
2. Codex uses `gmax` for semantic searches.
|
|
89
91
|
|
|
90
|
-
### Factory Droid
|
|
92
|
+
### Factory Droid
|
|
91
93
|
1. Run `gmax install-droid`
|
|
92
94
|
2. To remove: `gmax uninstall-droid`
|
|
93
95
|
|
|
94
96
|
### MCP Server
|
|
95
97
|
|
|
96
|
-
gmax
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
gmax mcp
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
This starts a stdio-based MCP server that auto-launches the `gmax serve` daemon. Available tools:
|
|
98
|
+
`gmax mcp` starts a stdio-based MCP server that searches the centralized index directly — no HTTP daemon needed.
|
|
103
99
|
|
|
104
100
|
| Tool | Description |
|
|
105
101
|
| --- | --- |
|
|
106
|
-
| `semantic_search` | Natural language code search
|
|
102
|
+
| `semantic_search` | Natural language code search. Use `root` to search a parent or sibling directory. |
|
|
103
|
+
| `search_all` | Search ALL indexed code across every directory. |
|
|
107
104
|
| `code_skeleton` | Collapsed file structure (~4x fewer tokens than reading the full file) |
|
|
108
|
-
| `trace_calls` | Call graph — who calls a symbol and what it calls |
|
|
105
|
+
| `trace_calls` | Call graph — who calls a symbol and what it calls (unscoped, crosses project boundaries) |
|
|
109
106
|
| `list_symbols` | List indexed functions, classes, and types with definition locations |
|
|
110
|
-
| `index_status` | Check
|
|
111
|
-
|
|
107
|
+
| `index_status` | Check index health: chunk counts, indexed directories, model info |
|
|
112
108
|
|
|
113
109
|
## Commands
|
|
114
110
|
|
|
115
111
|
### `gmax search`
|
|
116
112
|
|
|
117
|
-
The default command. Searches
|
|
113
|
+
The default command. Searches indexed code using semantic meaning.
|
|
118
114
|
|
|
119
115
|
```bash
|
|
120
116
|
gmax "how is the database connection pooled?"
|
|
@@ -133,147 +129,68 @@ gmax "how is the database connection pooled?"
|
|
|
133
129
|
| `--skeleton` | Show code skeleton for matching files instead of snippets. | `false` |
|
|
134
130
|
| `--plain` | Disable ANSI colors and use simpler formatting. | `false` |
|
|
135
131
|
| `-s`, `--sync` | Force re-index changed files before searching. | `false` |
|
|
136
|
-
| `-d`, `--dry-run` | Show what would be indexed without actually indexing. | `false` |
|
|
137
132
|
|
|
138
133
|
**Examples:**
|
|
139
134
|
|
|
140
135
|
```bash
|
|
141
|
-
# General concept search
|
|
142
136
|
gmax "API rate limiting logic"
|
|
143
|
-
|
|
144
|
-
# Deep dive (show more matches per file)
|
|
145
137
|
gmax "error handling" --per-file 5
|
|
146
|
-
|
|
147
|
-
# Just give me the files
|
|
148
138
|
gmax "user validation" --compact
|
|
149
|
-
|
|
150
|
-
# Show relevance scores and filter low-confidence matches
|
|
151
139
|
gmax "authentication" --scores --min-score 0.5
|
|
152
|
-
|
|
153
|
-
# Show skeletons of matching files
|
|
154
140
|
gmax "database connection" --skeleton
|
|
155
141
|
```
|
|
156
142
|
|
|
157
143
|
### `gmax index`
|
|
158
144
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
- Respects `.gitignore` and `.gmaxignore` (see [Configuration](#ignoring-files) section).
|
|
162
|
-
- **Smart Indexing:** Only embeds code and config files. Skips binaries, lockfiles, and minified assets.
|
|
163
|
-
- **Bounded Concurrency:** Uses a fixed thread pool to keep your system responsive.
|
|
164
|
-
- **Semantic Chunking:** Uses TreeSitter grammars for supported languages (TypeScript, JavaScript, Python, Go, Rust, C/C++, Java, C#, Ruby, PHP, Swift, Kotlin, JSON).
|
|
165
|
-
|
|
166
|
-
**Options:**
|
|
167
|
-
|
|
168
|
-
| Flag | Description | Default |
|
|
169
|
-
| --- | --- | --- |
|
|
170
|
-
| `-d`, `--dry-run` | See what would be indexed without making changes. | `false` |
|
|
171
|
-
| `-p`, `--path <dir>` | Path to index (defaults to current directory). | `.` |
|
|
172
|
-
| `-r`, `--reset` | Remove existing index and re-index from scratch. | `false` |
|
|
173
|
-
| `-v`, `--verbose` | Show detailed progress with file names. | `false` |
|
|
174
|
-
|
|
175
|
-
**Examples:**
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
gmax index # Index current dir
|
|
179
|
-
gmax index --dry-run # See what would be indexed
|
|
180
|
-
gmax index --verbose # Watch detailed progress (useful for debugging)
|
|
181
|
-
gmax index --reset # Full re-index from scratch
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### `gmax serve`
|
|
185
|
-
|
|
186
|
-
Runs a lightweight HTTP server with live file watching so searches stay hot in RAM.
|
|
187
|
-
|
|
188
|
-
- Keeps LanceDB and the embedding worker resident for <50ms responses.
|
|
189
|
-
- **Live reindexing:** Watches the repo (via chokidar) and incrementally re-indexes on file change.
|
|
190
|
-
- **Idle timeout:** Automatically shuts down after 30 minutes of inactivity (disable with `--no-idle-timeout`).
|
|
191
|
-
- Endpoints:
|
|
192
|
-
- `GET /health` — liveness check
|
|
193
|
-
- `GET /stats` — file count, chunk count, embed mode, index age, watcher status
|
|
194
|
-
- `POST /search` — `{ query, limit, path }`
|
|
145
|
+
Index a directory into the centralized store.
|
|
195
146
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
| `-p, --port <port>` | Port to listen on (default `4444`, retries up to 10 ports if taken) |
|
|
201
|
-
| `-b, --background` | Run server in background and exit immediately |
|
|
202
|
-
| `--cpu` | Use CPU-only embeddings (skip MLX GPU server) |
|
|
203
|
-
| `--no-idle-timeout` | Disable the 30-minute idle shutdown |
|
|
204
|
-
|
|
205
|
-
**Port Selection (priority order):**
|
|
206
|
-
1. Explicit `-p <port>` flag
|
|
207
|
-
2. `GMAX_PORT` environment variable
|
|
208
|
-
3. Default `4444` (auto-increments if in use)
|
|
209
|
-
|
|
210
|
-
**Usage:**
|
|
147
|
+
- Respects `.gitignore` and `.gmaxignore`.
|
|
148
|
+
- Only embeds code and config files. Skips binaries, lockfiles, and minified assets.
|
|
149
|
+
- Uses TreeSitter for semantic chunking (TypeScript, JavaScript, Python, Go, Rust, C/C++, Java, C#, Ruby, PHP, Swift, Kotlin, JSON).
|
|
150
|
+
- Files already indexed with matching content are skipped automatically.
|
|
211
151
|
|
|
212
152
|
```bash
|
|
213
|
-
gmax
|
|
214
|
-
gmax
|
|
215
|
-
gmax
|
|
153
|
+
gmax index # Index current dir
|
|
154
|
+
gmax index --path ~/workspace # Index a specific directory
|
|
155
|
+
gmax index --dry-run # See what would be indexed
|
|
156
|
+
gmax index --verbose # Watch detailed progress
|
|
157
|
+
gmax index --reset # Full re-index from scratch
|
|
216
158
|
```
|
|
217
159
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
gmax serve status # Show server status for current directory
|
|
222
|
-
gmax serve stop # Stop server in current directory
|
|
223
|
-
gmax serve stop --all # Stop all running gmax servers
|
|
224
|
-
```
|
|
160
|
+
### `gmax watch`
|
|
225
161
|
|
|
226
|
-
|
|
162
|
+
Background file watcher for live reindexing. Watches for file changes and incrementally updates the centralized index.
|
|
227
163
|
|
|
228
164
|
```bash
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
# Check status
|
|
234
|
-
gmax serve status
|
|
235
|
-
|
|
236
|
-
# Stop all when done
|
|
237
|
-
gmax serve stop --all
|
|
165
|
+
gmax watch -b # Background mode (auto-stops after 30min idle)
|
|
166
|
+
gmax watch --path ~/workspace # Watch a specific directory
|
|
167
|
+
gmax watch status # Show running watchers
|
|
168
|
+
gmax watch stop --all # Stop all watchers
|
|
238
169
|
```
|
|
239
170
|
|
|
240
|
-
|
|
171
|
+
The MCP server auto-starts a watcher on session start. You rarely need to run this manually.
|
|
241
172
|
|
|
242
|
-
### `gmax
|
|
173
|
+
### `gmax serve`
|
|
243
174
|
|
|
244
|
-
|
|
175
|
+
HTTP server with live file watching. Useful for non-MCP integrations.
|
|
245
176
|
|
|
246
177
|
```bash
|
|
247
|
-
gmax
|
|
178
|
+
gmax serve # Foreground, port 4444
|
|
179
|
+
gmax serve --background # Background mode
|
|
180
|
+
gmax serve --cpu # Force CPU-only embeddings
|
|
248
181
|
```
|
|
249
182
|
|
|
250
|
-
Shows store names, sizes, and last modified times. Useful for seeing what's indexed and cleaning up old stores.
|
|
251
|
-
|
|
252
183
|
### `gmax skeleton`
|
|
253
184
|
|
|
254
|
-
|
|
185
|
+
Compressed view of a file — signatures with bodies collapsed.
|
|
255
186
|
|
|
256
187
|
```bash
|
|
257
188
|
gmax skeleton src/lib/auth.ts
|
|
189
|
+
gmax skeleton AuthService # Find symbol, skeletonize its file
|
|
190
|
+
gmax skeleton "auth logic" # Search, skeletonize top matches
|
|
258
191
|
```
|
|
259
192
|
|
|
260
|
-
**
|
|
261
|
-
```typescript
|
|
262
|
-
class AuthService {
|
|
263
|
-
validate(token: string): boolean {
|
|
264
|
-
// → jwt.verify, checkScope, .. | C:5 | ORCH
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
**Modes:**
|
|
270
|
-
- `gmax skeleton <file>`: Skeletonize specific file.
|
|
271
|
-
- `gmax skeleton <Symbol>`: Find symbol in index and skeletonize its file.
|
|
272
|
-
- `gmax skeleton "query"`: Search for query and skeletonize top matches.
|
|
273
|
-
|
|
274
|
-
**Supported Languages:**
|
|
275
|
-
TypeScript, JavaScript, Python, Go, Rust, Java, C#, C++, C, Ruby, PHP, Swift, Kotlin.
|
|
276
|
-
|
|
193
|
+
**Supported Languages:** TypeScript, JavaScript, Python, Go, Rust, Java, C#, C++, C, Ruby, PHP, Swift, Kotlin.
|
|
277
194
|
|
|
278
195
|
### `gmax doctor`
|
|
279
196
|
|
|
@@ -283,88 +200,68 @@ Checks installation health, model paths, and database integrity.
|
|
|
283
200
|
gmax doctor
|
|
284
201
|
```
|
|
285
202
|
|
|
286
|
-
##
|
|
203
|
+
## Architecture
|
|
287
204
|
|
|
288
|
-
|
|
205
|
+
### Centralized Index
|
|
289
206
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
8. **Role Classification:** Detects `ORCHESTRATION` functions (high complexity, many calls) vs `DEFINITION` (types/classes) to help agents prioritize where to read.
|
|
207
|
+
All data lives in `~/.gmax/`:
|
|
208
|
+
- `~/.gmax/lancedb/` — LanceDB vector store (one database for all indexed directories)
|
|
209
|
+
- `~/.gmax/cache/meta.lmdb` — file metadata cache (content hashes, mtimes)
|
|
210
|
+
- `~/.gmax/config.json` — global config (model tier, embed mode)
|
|
211
|
+
- `~/.gmax/models/` — embedding models
|
|
212
|
+
- `~/.gmax/grammars/` — Tree-sitter grammars
|
|
213
|
+
- `~/.gmax/projects.json` — registry of indexed directories
|
|
298
214
|
|
|
299
|
-
|
|
215
|
+
All chunks store **absolute file paths**. Search scoping is done via path prefix filtering. There are no per-project index directories.
|
|
300
216
|
|
|
301
|
-
###
|
|
217
|
+
### Performance
|
|
302
218
|
|
|
303
|
-
|
|
219
|
+
- **Bounded Concurrency:** Worker threads scale to 50% of CPU cores (min 4). Override with `GMAX_WORKER_THREADS`.
|
|
220
|
+
- **Smart Chunking:** `tree-sitter` splits code by function/class boundaries for complete logical blocks.
|
|
221
|
+
- **Deduplication:** Identical code blocks are embedded once and cached.
|
|
222
|
+
- **Multi-stage Search:** Vector search + FTS + RRF fusion + ColBERT reranking + structural boosting.
|
|
223
|
+
- **Role Classification:** Detects `ORCHESTRATION` (high complexity, many calls) vs `DEFINITION` (types/classes).
|
|
304
224
|
|
|
305
|
-
|
|
306
|
-
2. **Git Repo without Remote** → directory name + hash (e.g., `utils-7f8a2b3c`)
|
|
307
|
-
3. **Non-Git Directory** → directory name + hash for collision safety
|
|
225
|
+
### GPU Embeddings (Apple Silicon)
|
|
308
226
|
|
|
309
|
-
|
|
310
|
-
```bash
|
|
311
|
-
cd ~/work/myproject # Auto-detected: owner-myproject
|
|
312
|
-
gmax "API handlers"
|
|
227
|
+
On Macs with Apple Silicon, gmax defaults to MLX for GPU-accelerated embeddings. The MLX embed server runs on port `8100` and is managed automatically by the Claude Code plugin hook.
|
|
313
228
|
|
|
314
|
-
|
|
315
|
-
gmax "helper functions"
|
|
316
|
-
```
|
|
229
|
+
To force CPU mode: `GMAX_EMBED_MODE=cpu gmax index`
|
|
317
230
|
|
|
318
|
-
|
|
231
|
+
## Configuration
|
|
319
232
|
|
|
320
233
|
### Ignoring Files
|
|
321
234
|
|
|
322
|
-
gmax respects
|
|
323
|
-
|
|
324
|
-
**`.gmaxignore` syntax:**
|
|
325
|
-
- Uses the same pattern syntax as `.gitignore`
|
|
326
|
-
- Patterns are relative to the repository root
|
|
327
|
-
- Supports glob patterns, negation (`!`), and directory patterns (`/`)
|
|
328
|
-
|
|
235
|
+
gmax respects `.gitignore` and `.gmaxignore` files. Create a `.gmaxignore` in your directory root to exclude additional patterns.
|
|
329
236
|
|
|
330
237
|
### Index Management
|
|
331
238
|
|
|
332
|
-
- **View indexed
|
|
333
|
-
- **Index location:**
|
|
334
|
-
- **Clean up
|
|
335
|
-
- **
|
|
336
|
-
|
|
337
|
-
### GPU Embeddings (Apple Silicon)
|
|
338
|
-
|
|
339
|
-
On Macs with Apple Silicon, gmax can use MLX for GPU-accelerated embeddings instead of ONNX on CPU.
|
|
340
|
-
|
|
341
|
-
1. Run `gmax setup` and select **GPU (MLX)** when prompted.
|
|
342
|
-
2. Start the server: `gmax serve` (automatically starts the MLX embed server).
|
|
343
|
-
3. To force CPU mode on a GPU-configured project: `gmax serve --cpu`.
|
|
344
|
-
|
|
345
|
-
The MLX embed server runs on port `8100` by default (configurable via `MLX_EMBED_PORT`). It is managed automatically by `gmax serve` — you don't need to start it manually.
|
|
239
|
+
- **View indexed directories:** `gmax list --all`
|
|
240
|
+
- **Index location:** `~/.gmax/lancedb/` (centralized)
|
|
241
|
+
- **Clean up:** `gmax index --reset` re-indexes the current directory from scratch
|
|
242
|
+
- **Full reset:** `rm -rf ~/.gmax/lancedb ~/.gmax/cache` to start completely fresh
|
|
346
243
|
|
|
347
244
|
## Development
|
|
348
245
|
|
|
349
246
|
```bash
|
|
350
247
|
pnpm install
|
|
351
|
-
pnpm build
|
|
248
|
+
pnpm build
|
|
352
249
|
pnpm test # vitest
|
|
353
250
|
pnpm format # biome check
|
|
251
|
+
just deploy # publish latest tag to npm
|
|
354
252
|
```
|
|
355
253
|
|
|
356
254
|
## Troubleshooting
|
|
357
255
|
|
|
358
|
-
- **Index feels stale?** Run `gmax index` to refresh, or use `gmax
|
|
256
|
+
- **Index feels stale?** Run `gmax index` to refresh, or use `gmax watch -b` for live reindexing.
|
|
359
257
|
- **Weird results?** Run `gmax doctor` to verify models.
|
|
360
258
|
- **Index getting stuck?** Run `gmax index --verbose` to see which file is being processed.
|
|
361
|
-
- **Need a fresh start?**
|
|
362
|
-
- **MLX server won't start?** Check `/tmp/mlx-embed-server.log` for errors. Use `
|
|
259
|
+
- **Need a fresh start?** `rm -rf ~/.gmax/lancedb ~/.gmax/cache` then `gmax index`.
|
|
260
|
+
- **MLX server won't start?** Check `/tmp/mlx-embed-server.log` for errors. Use `GMAX_EMBED_MODE=cpu` to fall back to CPU.
|
|
363
261
|
|
|
364
262
|
## Attribution
|
|
365
263
|
|
|
366
|
-
|
|
367
|
-
|
|
264
|
+
grepmax is built upon the foundation of [mgrep](https://github.com/mixedbread-ai/mgrep) by MixedBread. We acknowledge and appreciate the original architectural concepts and design decisions that informed this work.
|
|
368
265
|
|
|
369
266
|
See the [NOTICE](NOTICE) file for detailed attribution information.
|
|
370
267
|
|
|
@@ -372,4 +269,3 @@ See the [NOTICE](NOTICE) file for detailed attribution information.
|
|
|
372
269
|
|
|
373
270
|
Licensed under the Apache License, Version 2.0.
|
|
374
271
|
See [LICENSE](LICENSE) and [Apache-2.0](https://opensource.org/licenses/Apache-2.0) for details.
|
|
375
|
-
|
package/dist/commands/mcp.js
CHANGED
|
@@ -46,6 +46,7 @@ exports.mcp = void 0;
|
|
|
46
46
|
exports.toStringArray = toStringArray;
|
|
47
47
|
exports.ok = ok;
|
|
48
48
|
exports.err = err;
|
|
49
|
+
const node_child_process_1 = require("node:child_process");
|
|
49
50
|
const fs = __importStar(require("node:fs"));
|
|
50
51
|
const path = __importStar(require("node:path"));
|
|
51
52
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
@@ -63,6 +64,7 @@ const vector_db_1 = require("../lib/store/vector-db");
|
|
|
63
64
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
64
65
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
65
66
|
const project_root_1 = require("../lib/utils/project-root");
|
|
67
|
+
const watcher_registry_1 = require("../lib/utils/watcher-registry");
|
|
66
68
|
// ---------------------------------------------------------------------------
|
|
67
69
|
// Tool definitions
|
|
68
70
|
// ---------------------------------------------------------------------------
|
|
@@ -291,33 +293,63 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
291
293
|
console.log("[MCP] Index exists, ready.");
|
|
292
294
|
}
|
|
293
295
|
_indexReady = true;
|
|
296
|
+
ensureWatcher();
|
|
294
297
|
}
|
|
295
298
|
catch (e) {
|
|
296
299
|
console.error("[MCP] Index sync failed:", e);
|
|
297
300
|
}
|
|
298
301
|
});
|
|
299
302
|
}
|
|
303
|
+
// --- Background watcher ---
|
|
304
|
+
function findIndexedParent(dir) {
|
|
305
|
+
const resolved = path.resolve(dir);
|
|
306
|
+
const projects = (0, project_registry_1.listProjects)();
|
|
307
|
+
// Find indexed directories that are parents of `dir`, pick broadest
|
|
308
|
+
let broadest;
|
|
309
|
+
for (const p of projects) {
|
|
310
|
+
if (resolved.startsWith(p.root) && p.root !== resolved) {
|
|
311
|
+
if (!broadest || p.root.length < broadest.length) {
|
|
312
|
+
broadest = p.root;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return broadest;
|
|
317
|
+
}
|
|
318
|
+
function ensureWatcher() {
|
|
319
|
+
var _a;
|
|
320
|
+
if ((0, watcher_registry_1.getWatcherCoveringPath)(projectRoot))
|
|
321
|
+
return;
|
|
322
|
+
const watchRoot = (_a = findIndexedParent(projectRoot)) !== null && _a !== void 0 ? _a : projectRoot;
|
|
323
|
+
if ((0, watcher_registry_1.getWatcherCoveringPath)(watchRoot))
|
|
324
|
+
return;
|
|
325
|
+
const child = (0, node_child_process_1.spawn)("gmax", ["watch", "-b", "--path", watchRoot], {
|
|
326
|
+
detached: true,
|
|
327
|
+
stdio: "ignore",
|
|
328
|
+
});
|
|
329
|
+
child.unref();
|
|
330
|
+
console.log(`[MCP] Started background watcher for ${watchRoot}`);
|
|
331
|
+
}
|
|
300
332
|
// --- Tool handlers ---
|
|
301
333
|
function handleSemanticSearch(args_1) {
|
|
302
334
|
return __awaiter(this, arguments, void 0, function* (args, searchAll = false) {
|
|
303
335
|
const query = String(args.query || "");
|
|
304
336
|
if (!query)
|
|
305
337
|
return err("Missing required parameter: query");
|
|
306
|
-
const limit = Math.min(Math.max(Number(args.limit) ||
|
|
338
|
+
const limit = Math.min(Math.max(Number(args.limit) || 3, 1), 50);
|
|
307
339
|
yield ensureIndexReady();
|
|
308
340
|
try {
|
|
309
341
|
const searcher = getSearcher();
|
|
310
|
-
// Determine path prefix for
|
|
342
|
+
// Determine path prefix and display root for relative paths
|
|
311
343
|
let pathPrefix;
|
|
344
|
+
let displayRoot = projectRoot;
|
|
312
345
|
if (!searchAll) {
|
|
313
|
-
// Resolve search root — default to project root
|
|
314
346
|
const searchRoot = typeof args.root === "string"
|
|
315
347
|
? path.resolve(args.root)
|
|
316
348
|
: path.resolve(projectRoot);
|
|
349
|
+
displayRoot = searchRoot;
|
|
317
350
|
pathPrefix = searchRoot.endsWith("/")
|
|
318
351
|
? searchRoot
|
|
319
352
|
: `${searchRoot}/`;
|
|
320
|
-
// If a sub-path is specified, append it
|
|
321
353
|
if (typeof args.path === "string") {
|
|
322
354
|
pathPrefix = path.join(searchRoot, args.path);
|
|
323
355
|
if (!pathPrefix.endsWith("/"))
|
|
@@ -330,47 +362,47 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
330
362
|
}
|
|
331
363
|
const minScore = typeof args.min_score === "number" ? args.min_score : 0;
|
|
332
364
|
const maxPerFile = typeof args.max_per_file === "number" ? args.max_per_file : 0;
|
|
333
|
-
const MAX_SNIPPET_LINES =
|
|
334
|
-
let
|
|
335
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l
|
|
336
|
-
const
|
|
365
|
+
const MAX_SNIPPET_LINES = 4;
|
|
366
|
+
let results = result.data.map((r) => {
|
|
367
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
368
|
+
const absPath = (_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 : "";
|
|
369
|
+
const relPath = absPath.startsWith(displayRoot)
|
|
370
|
+
? absPath.slice(displayRoot.length + 1)
|
|
371
|
+
: absPath;
|
|
372
|
+
const startLine = (_f = (_d = r.startLine) !== null && _d !== void 0 ? _d : (_e = r.generated_metadata) === null || _e === void 0 ? void 0 : _e.start_line) !== null && _f !== void 0 ? _f : 0;
|
|
373
|
+
const endLine = (_j = (_g = r.endLine) !== null && _g !== void 0 ? _g : (_h = r.generated_metadata) === null || _h === void 0 ? void 0 : _h.end_line) !== null && _j !== void 0 ? _j : 0;
|
|
374
|
+
const score = typeof r.score === "number" ? r.score.toFixed(2) : "0";
|
|
375
|
+
const role = ((_k = r.role) !== null && _k !== void 0 ? _k : "IMPL").slice(0, 4).toUpperCase();
|
|
376
|
+
const defs = toStringArray((_l = r.definedSymbols) !== null && _l !== void 0 ? _l : r.defined_symbols).slice(0, 3);
|
|
337
377
|
const raw = typeof r.content === "string"
|
|
338
378
|
? r.content
|
|
339
379
|
: typeof r.text === "string"
|
|
340
380
|
? r.text
|
|
341
381
|
: "";
|
|
342
|
-
// Add line numbers and cap at MAX_SNIPPET_LINES
|
|
343
382
|
const lines = raw.split("\n");
|
|
344
383
|
const capped = lines.slice(0, MAX_SNIPPET_LINES);
|
|
345
384
|
const numbered = capped.map((line, i) => `${startLine + i + 1}│${line}`);
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
path: (_f = (_d = r.path) !== null && _d !== void 0 ? _d : (_e = r.metadata) === null || _e === void 0 ? void 0 : _e.path) !== null && _f !== void 0 ? _f : "",
|
|
351
|
-
startLine,
|
|
352
|
-
endLine: (_j = (_g = r.endLine) !== null && _g !== void 0 ? _g : (_h = r.generated_metadata) === null || _h === void 0 ? void 0 : _h.end_line) !== null && _j !== void 0 ? _j : 0,
|
|
353
|
-
score: typeof r.score === "number" ? +r.score.toFixed(3) : 0,
|
|
354
|
-
role: (_k = r.role) !== null && _k !== void 0 ? _k : "IMPLEMENTATION",
|
|
355
|
-
confidence: (_l = r.confidence) !== null && _l !== void 0 ? _l : "Unknown",
|
|
356
|
-
definedSymbols: toStringArray((_m = r.definedSymbols) !== null && _m !== void 0 ? _m : r.defined_symbols).slice(0, 5),
|
|
357
|
-
snippet,
|
|
358
|
-
};
|
|
385
|
+
const header = `${relPath}:${startLine + 1}-${endLine + 1} [${role}] score:${score}${defs.length ? ` defines:${defs.join(",")}` : ""}`;
|
|
386
|
+
const snippet = numbered.join("\n");
|
|
387
|
+
return { absPath, header, snippet, score: +score };
|
|
359
388
|
});
|
|
360
389
|
if (minScore > 0) {
|
|
361
|
-
|
|
390
|
+
results = results.filter((r) => r.score >= minScore);
|
|
362
391
|
}
|
|
363
392
|
if (maxPerFile > 0) {
|
|
364
393
|
const counts = new Map();
|
|
365
|
-
|
|
366
|
-
const count = counts.get(r.
|
|
394
|
+
results = results.filter((r) => {
|
|
395
|
+
const count = counts.get(r.absPath) || 0;
|
|
367
396
|
if (count >= maxPerFile)
|
|
368
397
|
return false;
|
|
369
|
-
counts.set(r.
|
|
398
|
+
counts.set(r.absPath, count + 1);
|
|
370
399
|
return true;
|
|
371
400
|
});
|
|
372
401
|
}
|
|
373
|
-
|
|
402
|
+
const output = results
|
|
403
|
+
.map((r) => `${r.header}\n${r.snippet}`)
|
|
404
|
+
.join("\n\n");
|
|
405
|
+
return ok(output);
|
|
374
406
|
}
|
|
375
407
|
catch (e) {
|
|
376
408
|
const msg = e instanceof Error ? e.message : String(e);
|
package/dist/commands/watch.js
CHANGED
|
@@ -60,10 +60,13 @@ const IDLE_CHECK_INTERVAL_MS = 60 * 1000; // check every minute
|
|
|
60
60
|
exports.watch = new commander_1.Command("watch")
|
|
61
61
|
.description("Start background file watcher for live reindexing")
|
|
62
62
|
.option("-b, --background", "Run watcher in background and exit")
|
|
63
|
+
.option("-p, --path <dir>", "Directory to watch (defaults to project root)")
|
|
63
64
|
.option("--no-idle-timeout", "Disable the 30-minute idle shutdown")
|
|
64
65
|
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
66
|
var _a;
|
|
66
|
-
const projectRoot =
|
|
67
|
+
const projectRoot = options.path
|
|
68
|
+
? path.resolve(options.path)
|
|
69
|
+
: (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
|
|
67
70
|
const projectName = path.basename(projectRoot);
|
|
68
71
|
// Check if watcher already running
|
|
69
72
|
const existing = (0, watcher_registry_1.getWatcherForProject)(projectRoot);
|
package/dist/config.js
CHANGED
|
@@ -65,7 +65,7 @@ const DEFAULT_WORKER_THREADS = (() => {
|
|
|
65
65
|
if (Number.isFinite(fromEnv) && fromEnv > 0)
|
|
66
66
|
return fromEnv;
|
|
67
67
|
const cores = os.cpus().length || 1;
|
|
68
|
-
const HARD_CAP = 4;
|
|
68
|
+
const HARD_CAP = Math.max(4, Math.floor(cores * 0.5));
|
|
69
69
|
return Math.max(1, Math.min(HARD_CAP, cores));
|
|
70
70
|
})();
|
|
71
71
|
exports.CONFIG = {
|
|
@@ -43,6 +43,7 @@ exports.isProcessRunning = isProcessRunning;
|
|
|
43
43
|
exports.registerWatcher = registerWatcher;
|
|
44
44
|
exports.unregisterWatcher = unregisterWatcher;
|
|
45
45
|
exports.getWatcherForProject = getWatcherForProject;
|
|
46
|
+
exports.getWatcherCoveringPath = getWatcherCoveringPath;
|
|
46
47
|
exports.listWatchers = listWatchers;
|
|
47
48
|
const fs = __importStar(require("node:fs"));
|
|
48
49
|
const path = __importStar(require("node:path"));
|
|
@@ -90,6 +91,15 @@ function getWatcherForProject(projectRoot) {
|
|
|
90
91
|
}
|
|
91
92
|
return undefined;
|
|
92
93
|
}
|
|
94
|
+
function getWatcherCoveringPath(dir) {
|
|
95
|
+
const resolved = path.resolve(dir);
|
|
96
|
+
const entries = loadRegistry();
|
|
97
|
+
for (const e of entries) {
|
|
98
|
+
if (resolved.startsWith(e.projectRoot) && isProcessRunning(e.pid))
|
|
99
|
+
return e;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
93
103
|
function listWatchers() {
|
|
94
104
|
const entries = loadRegistry();
|
|
95
105
|
const active = entries.filter((e) => isProcessRunning(e.pid));
|
package/package.json
CHANGED