grepmax 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,6 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.index = void 0;
46
+ const node_child_process_1 = require("node:child_process");
46
47
  const path = __importStar(require("node:path"));
47
48
  const commander_1 = require("commander");
48
49
  const grammar_loader_1 = require("../lib/index/grammar-loader");
@@ -52,6 +53,7 @@ const setup_helpers_1 = require("../lib/setup/setup-helpers");
52
53
  const vector_db_1 = require("../lib/store/vector-db");
53
54
  const exit_1 = require("../lib/utils/exit");
54
55
  const project_root_1 = require("../lib/utils/project-root");
56
+ const watcher_registry_1 = require("../lib/utils/watcher-registry");
55
57
  exports.index = new commander_1.Command("index")
56
58
  .description("Index the current directory and create searchable store")
57
59
  .option("-d, --dry-run", "Dry run the indexing process (no actual file syncing)", false)
@@ -77,6 +79,27 @@ exports.index = new commander_1.Command("index")
77
79
  }
78
80
  // Ensure grammars are present before indexing (silent if already exist)
79
81
  yield (0, grammar_loader_1.ensureGrammars)(console.log, { silent: true });
82
+ // Stop any watcher that covers this project — it holds the shared lock
83
+ const watcher = (0, watcher_registry_1.getWatcherCoveringPath)(projectRoot);
84
+ let restartWatcher = null;
85
+ if (watcher) {
86
+ console.log(`Stopping watcher (PID: ${watcher.pid}) for ${path.basename(watcher.projectRoot)}...`);
87
+ try {
88
+ process.kill(watcher.pid, "SIGTERM");
89
+ }
90
+ catch (_b) { }
91
+ // Wait for process to exit (up to 5s)
92
+ for (let i = 0; i < 50; i++) {
93
+ if (!(0, watcher_registry_1.isProcessRunning)(watcher.pid))
94
+ break;
95
+ yield new Promise((r) => setTimeout(r, 100));
96
+ }
97
+ (0, watcher_registry_1.unregisterWatcher)(watcher.pid);
98
+ restartWatcher = {
99
+ pid: watcher.pid,
100
+ projectRoot: watcher.projectRoot,
101
+ };
102
+ }
80
103
  const { spinner, onProgress } = (0, sync_helpers_1.createIndexingSpinner)(projectRoot, "Indexing...", { verbose: options.verbose });
81
104
  try {
82
105
  const result = yield (0, syncer_1.initialSync)({
@@ -100,6 +123,19 @@ exports.index = new commander_1.Command("index")
100
123
  spinner.fail("Indexing failed");
101
124
  throw e;
102
125
  }
126
+ finally {
127
+ // Restart the watcher if we stopped one
128
+ if (restartWatcher) {
129
+ try {
130
+ const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--path", restartWatcher.projectRoot], { detached: true, stdio: "ignore" });
131
+ child.unref();
132
+ console.log(`Restarted watcher for ${path.basename(restartWatcher.projectRoot)} (PID: ${child.pid})`);
133
+ }
134
+ catch (_c) {
135
+ console.log(`Note: could not restart watcher. Run: gmax watch --path ${restartWatcher.projectRoot} -b`);
136
+ }
137
+ }
138
+ }
103
139
  }
104
140
  catch (error) {
105
141
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -308,7 +308,7 @@ exports.mcp = new commander_1.Command("mcp")
308
308
  return;
309
309
  try {
310
310
  const db = getVectorDb();
311
- const hasIndex = yield db.hasAnyRows();
311
+ const hasIndex = yield db.hasRowsForPath(projectRoot);
312
312
  if (!hasIndex) {
313
313
  console.log("[MCP] No index found, running initial sync...");
314
314
  yield (0, syncer_1.initialSync)({ projectRoot });
@@ -318,7 +318,6 @@ exports.mcp = new commander_1.Command("mcp")
318
318
  console.log("[MCP] Index exists, ready.");
319
319
  }
320
320
  _indexReady = true;
321
- ensureWatcher();
322
321
  }
323
322
  catch (e) {
324
323
  console.error("[MCP] Index sync failed:", e);
@@ -326,33 +325,15 @@ exports.mcp = new commander_1.Command("mcp")
326
325
  });
327
326
  }
328
327
  // --- Background watcher ---
329
- function findIndexedParent(dir) {
330
- const resolved = path.resolve(dir);
331
- const projects = (0, project_registry_1.listProjects)();
332
- // Find indexed directories that are parents of `dir`, pick broadest
333
- let broadest;
334
- for (const p of projects) {
335
- if (resolved.startsWith(p.root) && p.root !== resolved) {
336
- if (!broadest || p.root.length < broadest.length) {
337
- broadest = p.root;
338
- }
339
- }
340
- }
341
- return broadest;
342
- }
343
328
  function ensureWatcher() {
344
- var _a;
345
329
  if ((0, watcher_registry_1.getWatcherCoveringPath)(projectRoot))
346
330
  return;
347
- const watchRoot = (_a = findIndexedParent(projectRoot)) !== null && _a !== void 0 ? _a : projectRoot;
348
- if ((0, watcher_registry_1.getWatcherCoveringPath)(watchRoot))
349
- return;
350
- const child = (0, node_child_process_1.spawn)("gmax", ["watch", "-b", "--path", watchRoot], {
331
+ const child = (0, node_child_process_1.spawn)("gmax", ["watch", "-b", "--path", projectRoot], {
351
332
  detached: true,
352
333
  stdio: "ignore",
353
334
  });
354
335
  child.unref();
355
- console.log(`[MCP] Started background watcher for ${watchRoot}`);
336
+ console.log(`[MCP] Started background watcher for ${projectRoot}`);
356
337
  }
357
338
  // --- Tool handlers ---
358
339
  function handleSemanticSearch(args_1) {
@@ -362,6 +343,7 @@ exports.mcp = new commander_1.Command("mcp")
362
343
  return err("Missing required parameter: query");
363
344
  const limit = Math.min(Math.max(Number(args.limit) || 3, 1), 50);
364
345
  yield ensureIndexReady();
346
+ ensureWatcher();
365
347
  try {
366
348
  const searcher = getSearcher();
367
349
  // Determine path prefix and display root for relative paths
@@ -609,7 +591,7 @@ exports.mcp = new commander_1.Command("mcp")
609
591
  }
610
592
  function handleIndexStatus() {
611
593
  return __awaiter(this, void 0, void 0, function* () {
612
- var _a, _b, _c;
594
+ var _a, _b, _c, _d, _e, _f;
613
595
  try {
614
596
  const config = (0, index_config_1.readIndexConfig)(config_1.PATHS.configPath);
615
597
  const globalConfig = (0, index_config_1.readGlobalConfig)();
@@ -633,7 +615,7 @@ exports.mcp = new commander_1.Command("mcp")
633
615
  }
634
616
  const lines = [
635
617
  `Index: ~/.gmax/lancedb (${stats.chunks} chunks, ${fileCount} files)`,
636
- `Model: ${(_b = config === null || config === void 0 ? void 0 : config.embedModel) !== null && _b !== void 0 ? _b : "unknown"} (${(_c = config === null || config === void 0 ? void 0 : config.vectorDim) !== null && _c !== void 0 ? _c : "?"}d, ${globalConfig.embedMode})`,
618
+ `Model: ${globalConfig.embedMode === "gpu" ? ((_d = (_c = (_b = config_1.MODEL_TIERS[globalConfig.modelTier]) === null || _b === void 0 ? void 0 : _b.mlxModel) !== null && _c !== void 0 ? _c : config === null || config === void 0 ? void 0 : config.embedModel) !== null && _d !== void 0 ? _d : "unknown") : ((_e = config === null || config === void 0 ? void 0 : config.embedModel) !== null && _e !== void 0 ? _e : "unknown")} (${(_f = config === null || config === void 0 ? void 0 : config.vectorDim) !== null && _f !== void 0 ? _f : "?"}d, ${globalConfig.embedMode})`,
637
619
  (config === null || config === void 0 ? void 0 : config.indexedAt)
638
620
  ? `Last indexed: ${config.indexedAt}`
639
621
  : "",
@@ -714,6 +696,7 @@ exports.mcp = new commander_1.Command("mcp")
714
696
  }
715
697
  }));
716
698
  yield server.connect(transport);
717
- // Kick off index readiness check in background
699
+ // Kick off index readiness check and watcher in background
718
700
  ensureIndexReady();
701
+ ensureWatcher();
719
702
  }));
@@ -63,15 +63,15 @@ exports.watch = new commander_1.Command("watch")
63
63
  .option("-p, --path <dir>", "Directory to watch (defaults to project root)")
64
64
  .option("--no-idle-timeout", "Disable the 30-minute idle shutdown")
65
65
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
66
- var _a;
66
+ var _a, _b;
67
67
  const projectRoot = options.path
68
68
  ? path.resolve(options.path)
69
69
  : (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
70
70
  const projectName = path.basename(projectRoot);
71
- // Check if watcher already running
72
- const existing = (0, watcher_registry_1.getWatcherForProject)(projectRoot);
71
+ // Check if watcher already running (exact match or parent covering this dir)
72
+ const existing = (_b = (0, watcher_registry_1.getWatcherForProject)(projectRoot)) !== null && _b !== void 0 ? _b : (0, watcher_registry_1.getWatcherCoveringPath)(projectRoot);
73
73
  if (existing && (0, watcher_registry_1.isProcessRunning)(existing.pid)) {
74
- console.log(`Watcher already running for ${projectName} (PID: ${existing.pid})`);
74
+ console.log(`Watcher already running for ${path.basename(existing.projectRoot)} (PID: ${existing.pid})`);
75
75
  return;
76
76
  }
77
77
  // Background spawn
@@ -285,6 +285,19 @@ class VectorDB {
285
285
  return rows.length > 0;
286
286
  });
287
287
  }
288
+ hasRowsForPath(pathPrefix) {
289
+ return __awaiter(this, void 0, void 0, function* () {
290
+ const table = yield this.ensureTable();
291
+ const prefix = pathPrefix.endsWith("/") ? pathPrefix : `${pathPrefix}/`;
292
+ const rows = yield table
293
+ .query()
294
+ .select(["id"])
295
+ .where(`path LIKE '${prefix.replace(/'/g, "''")}%'`)
296
+ .limit(1)
297
+ .toArray();
298
+ return rows.length > 0;
299
+ });
300
+ }
288
301
  getStats() {
289
302
  return __awaiter(this, void 0, void 0, function* () {
290
303
  const table = yield this.ensureTable();
@@ -108,7 +108,7 @@ function acquireWriterLock(lockDir) {
108
108
  const holderDesc = pid
109
109
  ? `${pid}${startedAt ? ` @ ${startedAt}` : ""}`
110
110
  : "unknown";
111
- throw new Error(`.gmax lock already held (${holderDesc}). Another indexing process is running or the lock must be cleared.`);
111
+ throw new Error(`.gmax lock already held (${holderDesc}). Another process is using the index. Try: gmax watch stop --all`);
112
112
  }
113
113
  }
114
114
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
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.5.4",
3
+ "version": "0.6.0",
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",
@@ -39,6 +39,15 @@ function startPythonServer(scriptName, logName) {
39
39
  child.unref();
40
40
  }
41
41
 
42
+ function startWatcher() {
43
+ try {
44
+ const { execFileSync } = require("node:child_process");
45
+ execFileSync("gmax", ["watch", "-b"], { timeout: 5000, stdio: "ignore" });
46
+ } catch {
47
+ // Watcher may already be running or gmax not in PATH — ignore
48
+ }
49
+ }
50
+
42
51
  async function main() {
43
52
  const embedMode =
44
53
  process.env.GMAX_EMBED_MODE || process.env.OSGREP_EMBED_MODE || "auto";
@@ -55,6 +64,9 @@ async function main() {
55
64
  }
56
65
  }
57
66
 
67
+ // Start a file watcher for the current project (30-min idle timeout)
68
+ startWatcher();
69
+
58
70
  const response = {
59
71
  hookSpecificOutput: {
60
72
  hookEventName: "SessionStart",
@@ -1,3 +1,6 @@
1
- // No-op: let gmax serve persist across sessions.
2
- // The start hook ensures exactly one is running.
3
- // Use `gmax serve stop` to explicitly shut it down.
1
+ try {
2
+ const { execFileSync } = require("node:child_process");
3
+ execFileSync("gmax", ["watch", "stop"], { timeout: 5000, stdio: "ignore" });
4
+ } catch {
5
+ // Watcher may not be running or gmax not in PATH — ignore
6
+ }