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 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
- - **Auto-Isolated:** Each repository gets its own index automatically.
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. **Search**
44
+ 3. **Index**
45
45
 
46
46
  ```bash
47
47
  cd my-repo
48
- gmax "where do we handle authentication?"
48
+ gmax index
49
49
  ```
50
50
 
51
- **Your first search will automatically index the repository.** Each repository is automatically isolated with its own index. Switching between repos "just works" no manual configuration needed. If the background server is running (`gmax serve`), search goes through the hot daemon; otherwise it falls back to on-demand indexing.
51
+ Indexes into a centralized store at `~/.gmax/lancedb/`. You can index any directorya 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
- 4. **Trace** (Call Graph)
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). Perfect for impact analysis and understanding code flow.
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 (`claude`) and ask it questions about your codebase.
77
- 3. Highly recommend indexing your code base before using the plugin.
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 Plugin
84
+ ### Opencode
81
85
  1. Run `gmax install-opencode`
82
- 2. Open OC (`opencode`) and ask it questions about your codebase.
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 Plugin
88
+ ### Codex
87
89
  1. Run `gmax install-codex`
88
- 2. Codex will use `gmax` for semantic searches.
90
+ 2. Codex uses `gmax` for semantic searches.
89
91
 
90
- ### Factory Droid Plugin
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 exposes tools via the [Model Context Protocol](https://modelcontextprotocol.io/) for any MCP-compatible AI agent or editor.
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 with score filtering and per-file caps |
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 daemon status, file count, embed mode, and index age |
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 the current directory using semantic meaning.
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
- Manually indexes the repository. Useful if you want to pre-warm the cache or if you've made massive changes outside of the editor.
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
- **Options:**
197
-
198
- | Flag | Description |
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 serve # Foreground, port 4444 (or next available)
214
- gmax serve --background # Background mode, auto port
215
- gmax serve -b -p 5000 # Background on specific port
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
- **Subcommands:**
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
- **Example workflow:**
162
+ Background file watcher for live reindexing. Watches for file changes and incrementally updates the centralized index.
227
163
 
228
164
  ```bash
229
- # Start servers in multiple projects
230
- cd ~/project-a && gmax serve -b # Starts on port 4444
231
- cd ~/project-b && gmax serve -b # Starts on port 4445 (auto-increment)
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
- Claude Code hooks start/stop this automatically; you rarely need to run it manually.
171
+ The MCP server auto-starts a watcher on session start. You rarely need to run this manually.
241
172
 
242
- ### `gmax list`
173
+ ### `gmax serve`
243
174
 
244
- Lists all indexed repositories (stores) and their metadata.
175
+ HTTP server with live file watching. Useful for non-MCP integrations.
245
176
 
246
177
  ```bash
247
- gmax list
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
- Generates a compressed "skeleton" of a file, showing only signatures, types, and class structures while eliding function bodies.
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
- **Output:**
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
- ## Performance & Architecture
203
+ ## Architecture
287
204
 
288
- gmax is designed to be a "good citizen" on your machine:
205
+ ### Centralized Index
289
206
 
290
- 1. **Bounded Concurrency:** Chunking/embedding stay within small thread pools (1–4) and capped batch sizes to keep laptops responsive.
291
- 2. **Smart Chunking:** Uses `tree-sitter` to split code by function/class boundaries, ensuring embeddings capture complete logical blocks.
292
- 3. **Deduplication:** Identical code blocks (boilerplate, license headers) are embedded once and cached, saving space and time.
293
- 4. **Semantic Split Search:** Queries both "Code" and "Docs" separately to ensure documentation doesn't drown out implementation details, then reranks with ColBERT.
294
- 5. **Global Batching:** A producer/consumer pipeline decouples chunking from embedding. Files are chunked concurrently, queued, embedded in fat batches, and written to LanceDB in bulk.
295
- 6. **Anchor-Only Scans & Batch Deletes:** File discovery and stale cleanup hit only anchor rows, and stale/changed paths are removed with a single `IN` delete to minimize I/O.
296
- 7. **Structural Boosting:** Function/class chunks get a small score boost; test/spec paths are slightly downweighted to bubble up primary definitions first.
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
- ## Configuration
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
- ### Automatic Repository Isolation
217
+ ### Performance
302
218
 
303
- gmax automatically creates a unique index for each repository based on:
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
- 1. **Git Remote URL** (e.g., `github.com/facebook/react` → `facebook-react`)
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
- **Examples:**
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
- cd ~/personal/utils # Auto-detected: utils-abc12345
315
- gmax "helper functions"
316
- ```
229
+ To force CPU mode: `GMAX_EMBED_MODE=cpu gmax index`
317
230
 
318
- Stores are isolated automatically — no manual `--store` flags needed!
231
+ ## Configuration
319
232
 
320
233
  ### Ignoring Files
321
234
 
322
- gmax respects both `.gitignore` and `.gmaxignore` files when indexing. Create a `.gmaxignore` file in your repository root to exclude additional files or patterns from indexing.
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 projects:** `gmax list`
333
- - **Index location:** `.gmax/` in each project root
334
- - **Clean up a project index:** `rm -rf .gmax/` in the project directory
335
- - **Global data (models, grammars):** `~/.gmax/`
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 # or pnpm dev
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 serve` for live reindexing.
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?** Delete `.gmax/` in your project root and run `gmax index`.
362
- - **MLX server won't start?** Check `/tmp/mlx-embed-server.log` for errors. Use `gmax serve --cpu` to fall back to CPU.
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
- gmax 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.
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
-
@@ -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) || 10, 1), 50);
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 scoping
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 = 8;
334
- let compact = result.data.map((r) => {
335
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
336
- const startLine = (_c = (_a = r.startLine) !== null && _a !== void 0 ? _a : (_b = r.generated_metadata) === null || _b === void 0 ? void 0 : _b.start_line) !== null && _c !== void 0 ? _c : 0;
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 snippet = lines.length > MAX_SNIPPET_LINES
347
- ? `${numbered.join("\n")}\n… (+${lines.length - MAX_SNIPPET_LINES} more lines)`
348
- : numbered.join("\n");
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
- compact = compact.filter((r) => r.score >= minScore);
390
+ results = results.filter((r) => r.score >= minScore);
362
391
  }
363
392
  if (maxPerFile > 0) {
364
393
  const counts = new Map();
365
- compact = compact.filter((r) => {
366
- const count = counts.get(r.path) || 0;
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.path, count + 1);
398
+ counts.set(r.absPath, count + 1);
370
399
  return true;
371
400
  });
372
401
  }
373
- return ok(JSON.stringify(compact));
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);
@@ -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 = (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
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 = {
@@ -60,7 +60,7 @@ function readGlobalConfig() {
60
60
  return {
61
61
  modelTier: config_1.DEFAULT_MODEL_TIER,
62
62
  vectorDim: tier.vectorDim,
63
- embedMode: "cpu",
63
+ embedMode: process.arch === "arm64" && process.platform === "darwin" ? "gpu" : "cpu",
64
64
  };
65
65
  }
66
66
  }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "author": "Robert Owens <robowens@me.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",