grepmax 0.8.1 → 0.8.2

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.
@@ -155,9 +155,12 @@ Examples:
155
155
  }
156
156
  // Start watcher
157
157
  const launched = (0, watcher_launcher_1.launchWatcher)(projectRoot);
158
- if (launched) {
158
+ if (launched.ok) {
159
159
  console.log(`Watcher started (PID: ${launched.pid})`);
160
160
  }
161
+ else if (launched.reason === "spawn-failed") {
162
+ console.warn(`[add] ${launched.message}`);
163
+ }
161
164
  }
162
165
  catch (error) {
163
166
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -130,7 +130,10 @@ function installPlugin() {
130
130
  const startScript = `
131
131
  const { spawn } = require("child_process");
132
132
  const fs = require("fs");
133
- const out = fs.openSync("/tmp/gmax.log", "a");
133
+ const path = require("path");
134
+ const logDir = path.join(require("os").homedir(), ".gmax", "logs");
135
+ fs.mkdirSync(logDir, { recursive: true });
136
+ const out = fs.openSync(path.join(logDir, "gmax.log"), "a");
134
137
  const child = spawn("gmax", ["serve"], { detached: true, stdio: ["ignore", out, out] });
135
138
  child.unref();
136
139
  `;
@@ -169,9 +169,12 @@ Examples:
169
169
  // Restart the watcher if we stopped one
170
170
  if (restartWatcher) {
171
171
  const launched = (0, watcher_launcher_1.launchWatcher)(restartWatcher.projectRoot);
172
- if (launched) {
172
+ if (launched.ok) {
173
173
  console.log(`Restarted watcher for ${path.basename(restartWatcher.projectRoot)} (PID: ${launched.pid})`);
174
174
  }
175
+ else if (launched.reason === "spawn-failed") {
176
+ console.warn(`[index] ${launched.message}`);
177
+ }
175
178
  }
176
179
  }
177
180
  }
@@ -77,6 +77,7 @@ const format_helpers_1 = require("../lib/utils/format-helpers");
77
77
  const import_extractor_1 = require("../lib/utils/import-extractor");
78
78
  const project_registry_1 = require("../lib/utils/project-registry");
79
79
  const project_root_1 = require("../lib/utils/project-root");
80
+ const watcher_launcher_1 = require("../lib/utils/watcher-launcher");
80
81
  const watcher_store_1 = require("../lib/utils/watcher-store");
81
82
  // ---------------------------------------------------------------------------
82
83
  // Tool definitions
@@ -352,17 +353,10 @@ exports.mcp = new commander_1.Command("mcp")
352
353
  }
353
354
  // --- Background watcher ---
354
355
  function ensureWatcher() {
355
- // Only start watcher for registered projects
356
- if (!(0, project_registry_1.getProject)(projectRoot))
357
- return;
358
- if ((0, watcher_store_1.getWatcherCoveringPath)(projectRoot))
359
- return;
360
- const child = (0, node_child_process_1.spawn)("gmax", ["watch", "-b", "--path", projectRoot], {
361
- detached: true,
362
- stdio: "ignore",
363
- });
364
- child.unref();
365
- console.log(`[MCP] Started background watcher for ${projectRoot}`);
356
+ const result = (0, watcher_launcher_1.launchWatcher)(projectRoot);
357
+ if (result.ok && !result.reused) {
358
+ console.log(`[MCP] Started background watcher for ${projectRoot} (PID: ${result.pid})`);
359
+ }
366
360
  }
367
361
  // --- Tool handlers ---
368
362
  function handleSemanticSearch(args_1) {
@@ -49,6 +49,7 @@ const commander_1 = require("commander");
49
49
  const meta_cache_1 = require("../lib/store/meta-cache");
50
50
  const vector_db_1 = require("../lib/store/vector-db");
51
51
  const exit_1 = require("../lib/utils/exit");
52
+ const process_1 = require("../lib/utils/process");
52
53
  const project_marker_1 = require("../lib/utils/project-marker");
53
54
  const project_registry_1 = require("../lib/utils/project-registry");
54
55
  const project_root_1 = require("../lib/utils/project-root");
@@ -102,15 +103,7 @@ Examples:
102
103
  const watcher = (0, watcher_store_1.getWatcherForProject)(projectRoot);
103
104
  if (watcher) {
104
105
  console.log(`Stopping watcher (PID: ${watcher.pid})...`);
105
- try {
106
- process.kill(watcher.pid, "SIGTERM");
107
- }
108
- catch (_b) { }
109
- for (let i = 0; i < 50; i++) {
110
- if (!(0, watcher_store_1.isProcessRunning)(watcher.pid))
111
- break;
112
- yield new Promise((r) => setTimeout(r, 100));
113
- }
106
+ yield (0, process_1.killProcess)(watcher.pid);
114
107
  (0, watcher_store_1.unregisterWatcher)(watcher.pid);
115
108
  }
116
109
  // Delete vectors from LanceDB
@@ -139,13 +132,13 @@ Examples:
139
132
  try {
140
133
  metaCache.close();
141
134
  }
142
- catch (_c) { }
135
+ catch (_b) { }
143
136
  }
144
137
  if (vectorDb) {
145
138
  try {
146
139
  yield vectorDb.close();
147
140
  }
148
- catch (_d) { }
141
+ catch (_c) { }
149
142
  }
150
143
  yield (0, exit_1.gracefulExit)();
151
144
  }
@@ -529,7 +529,10 @@ Examples:
529
529
  // Ensure a watcher is running for live reindexing
530
530
  if (!process.env.VITEST && !((_d = process.env.NODE_ENV) === null || _d === void 0 ? void 0 : _d.includes("test"))) {
531
531
  const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
532
- launchWatcher(projectRoot);
532
+ const launched = launchWatcher(projectRoot);
533
+ if (!launched.ok && launched.reason === "spawn-failed") {
534
+ console.warn(`[search] ${launched.message}`);
535
+ }
533
536
  }
534
537
  const searcher = new searcher_1.Searcher(vectorDb);
535
538
  // Use --root or fall back to project root
@@ -59,6 +59,7 @@ const setup_helpers_1 = require("../lib/setup/setup-helpers");
59
59
  const meta_cache_1 = require("../lib/store/meta-cache");
60
60
  const vector_db_1 = require("../lib/store/vector-db");
61
61
  const exit_1 = require("../lib/utils/exit");
62
+ const log_rotate_1 = require("../lib/utils/log-rotate");
62
63
  const project_root_1 = require("../lib/utils/project-root");
63
64
  const server_registry_1 = require("../lib/utils/server-registry");
64
65
  function isMlxServerUp() {
@@ -84,8 +85,7 @@ function startMlxServer(mlxModel) {
84
85
  const serverDir = candidates.find((d) => fs.existsSync(path.join(d, "server.py")));
85
86
  if (!serverDir)
86
87
  return null;
87
- const logPath = "/tmp/mlx-embed-server.log";
88
- const out = fs.openSync(logPath, "a");
88
+ const out = (0, log_rotate_1.openRotatedLog)(path.join(config_1.PATHS.logsDir, "mlx-embed-server.log"));
89
89
  const env = Object.assign({}, process.env);
90
90
  if (mlxModel) {
91
91
  env.MLX_EMBED_MODEL = mlxModel;
@@ -121,14 +121,12 @@ exports.serve = new commander_1.Command("serve")
121
121
  const args = process.argv
122
122
  .slice(2)
123
123
  .filter((arg) => arg !== "-b" && arg !== "--background");
124
- const logDir = path.join(config_1.PATHS.globalRoot, "logs");
125
- fs.mkdirSync(logDir, { recursive: true });
126
124
  const safeName = path
127
125
  .basename(projectRoot)
128
126
  .replace(/[^a-zA-Z0-9._-]/g, "_");
129
- const logFile = path.join(logDir, `server-${safeName}.log`);
130
- const out = fs.openSync(logFile, "a");
131
- const err = fs.openSync(logFile, "a");
127
+ const logFile = path.join(config_1.PATHS.logsDir, `server-${safeName}.log`);
128
+ const out = (0, log_rotate_1.openRotatedLog)(logFile);
129
+ const err = (0, log_rotate_1.openRotatedLog)(logFile);
132
130
  const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
133
131
  detached: true,
134
132
  stdio: ["ignore", out, err],
@@ -44,7 +44,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.watch = void 0;
46
46
  const node_child_process_1 = require("node:child_process");
47
- const fs = __importStar(require("node:fs"));
48
47
  const path = __importStar(require("node:path"));
49
48
  const commander_1 = require("commander");
50
49
  const config_1 = require("../config");
@@ -55,12 +54,13 @@ const watcher_1 = require("../lib/index/watcher");
55
54
  const meta_cache_1 = require("../lib/store/meta-cache");
56
55
  const vector_db_1 = require("../lib/store/vector-db");
57
56
  const exit_1 = require("../lib/utils/exit");
57
+ const log_rotate_1 = require("../lib/utils/log-rotate");
58
+ const process_1 = require("../lib/utils/process");
58
59
  const project_registry_1 = require("../lib/utils/project-registry");
59
60
  const project_root_1 = require("../lib/utils/project-root");
60
61
  const watcher_store_1 = require("../lib/utils/watcher-store");
61
62
  const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
62
63
  const IDLE_CHECK_INTERVAL_MS = 60 * 1000; // check every minute
63
- const MAX_LOG_BYTES = 5 * 1024 * 1024; // 5 MB — rotate log when exceeded
64
64
  exports.watch = new commander_1.Command("watch")
65
65
  .description("Start background file watcher for live reindexing")
66
66
  .option("-b, --background", "Run watcher in background and exit")
@@ -83,20 +83,9 @@ exports.watch = new commander_1.Command("watch")
83
83
  const args = process.argv
84
84
  .slice(2)
85
85
  .filter((arg) => arg !== "-b" && arg !== "--background");
86
- const logDir = path.join(config_1.PATHS.globalRoot, "logs");
87
- fs.mkdirSync(logDir, { recursive: true });
88
86
  const safeName = projectName.replace(/[^a-zA-Z0-9._-]/g, "_");
89
- const logFile = path.join(logDir, `watch-${safeName}.log`);
90
- // Rotate log if it exceeds MAX_LOG_BYTES
91
- try {
92
- const logStat = fs.statSync(logFile);
93
- if (logStat.size > MAX_LOG_BYTES) {
94
- const prev = `${logFile}.prev`;
95
- fs.renameSync(logFile, prev);
96
- }
97
- }
98
- catch (_c) { }
99
- const out = fs.openSync(logFile, "a");
87
+ const logFile = path.join(config_1.PATHS.logsDir, `watch-${safeName}.log`);
88
+ const out = (0, log_rotate_1.openRotatedLog)(logFile);
100
89
  const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], ...args], {
101
90
  detached: true,
102
91
  stdio: ["ignore", out, out],
@@ -228,11 +217,11 @@ exports.watch
228
217
  if (options.all) {
229
218
  const watchers = (0, watcher_store_1.listWatchers)();
230
219
  for (const w of watchers) {
231
- try {
232
- process.kill(w.pid, "SIGTERM");
233
- (0, watcher_store_1.unregisterWatcher)(w.pid);
220
+ const killed = yield (0, process_1.killProcess)(w.pid);
221
+ (0, watcher_store_1.unregisterWatcher)(w.pid);
222
+ if (!killed) {
223
+ console.warn(`Warning: PID ${w.pid} did not exit after SIGKILL`);
234
224
  }
235
- catch (_b) { }
236
225
  }
237
226
  console.log(`Stopped ${watchers.length} watcher(s).`);
238
227
  yield (0, exit_1.gracefulExit)();
@@ -245,14 +234,13 @@ exports.watch
245
234
  yield (0, exit_1.gracefulExit)();
246
235
  return;
247
236
  }
248
- try {
249
- process.kill(watcher.pid, "SIGTERM");
250
- (0, watcher_store_1.unregisterWatcher)(watcher.pid);
237
+ const killed = yield (0, process_1.killProcess)(watcher.pid);
238
+ (0, watcher_store_1.unregisterWatcher)(watcher.pid);
239
+ if (killed) {
251
240
  console.log(`Stopped watcher (PID: ${watcher.pid})`);
252
241
  }
253
- catch (_c) {
254
- console.log("Watcher process not found.");
255
- (0, watcher_store_1.unregisterWatcher)(watcher.pid);
242
+ else {
243
+ console.warn(`Warning: watcher PID ${watcher.pid} did not exit`);
256
244
  }
257
245
  yield (0, exit_1.gracefulExit)();
258
246
  }));
package/dist/config.js CHANGED
@@ -93,6 +93,7 @@ exports.PATHS = {
93
93
  globalRoot: GLOBAL_ROOT,
94
94
  models: path.join(GLOBAL_ROOT, "models"),
95
95
  grammars: path.join(GLOBAL_ROOT, "grammars"),
96
+ logsDir: path.join(GLOBAL_ROOT, "logs"),
96
97
  // Centralized index storage — one database for all indexed directories
97
98
  lancedbDir: path.join(GLOBAL_ROOT, "lancedb"),
98
99
  cacheDir: path.join(GLOBAL_ROOT, "cache"),
@@ -136,9 +136,11 @@ function startWatcher(opts) {
136
136
  const vectors = [];
137
137
  const metaUpdates = new Map();
138
138
  const metaDeletes = [];
139
+ const attempted = new Set();
139
140
  for (const [absPath, event] of batch) {
140
141
  if (batchAc.signal.aborted)
141
142
  break;
143
+ attempted.add(absPath);
142
144
  if (event === "unlink") {
143
145
  deletes.push(absPath);
144
146
  metaDeletes.push(absPath);
@@ -200,6 +202,12 @@ function startWatcher(opts) {
200
202
  }
201
203
  }
202
204
  }
205
+ // Requeue files that weren't attempted (aborted or pool unhealthy)
206
+ for (const [absPath, event] of batch) {
207
+ if (!attempted.has(absPath) && !pending.has(absPath)) {
208
+ pending.set(absPath, event);
209
+ }
210
+ }
203
211
  // Flush to VectorDB: insert first, then delete old (preserving new)
204
212
  const newIds = vectors.map((v) => v.id);
205
213
  if (vectors.length > 0) {
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.openRotatedLog = openRotatedLog;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const MAX_LOG_BYTES = 5 * 1024 * 1024; // 5 MB
40
+ /**
41
+ * Open a log file with rotation. Creates parent directories if needed.
42
+ * Rotates {name}.log -> {name}.log.prev when size exceeds maxBytes.
43
+ * Returns an fd suitable for stdio redirection.
44
+ */
45
+ function openRotatedLog(logPath, maxBytes = MAX_LOG_BYTES) {
46
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
47
+ try {
48
+ const stat = fs.statSync(logPath);
49
+ if (stat.size > maxBytes) {
50
+ fs.renameSync(logPath, `${logPath}.prev`);
51
+ }
52
+ }
53
+ catch (_a) { }
54
+ return fs.openSync(logPath, "a");
55
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.killProcess = killProcess;
13
+ const watcher_store_1 = require("./watcher-store");
14
+ /**
15
+ * Send SIGTERM, wait up to 3s, then SIGKILL if still alive.
16
+ * Returns true if process is confirmed dead.
17
+ */
18
+ function killProcess(pid) {
19
+ return __awaiter(this, void 0, void 0, function* () {
20
+ try {
21
+ process.kill(pid, "SIGTERM");
22
+ }
23
+ catch (_a) {
24
+ return false;
25
+ }
26
+ // Poll up to 3s for graceful exit
27
+ for (let i = 0; i < 30; i++) {
28
+ if (!(0, watcher_store_1.isProcessRunning)(pid))
29
+ return true;
30
+ yield new Promise((r) => setTimeout(r, 100));
31
+ }
32
+ // Force kill
33
+ try {
34
+ process.kill(pid, "SIGKILL");
35
+ }
36
+ catch (_b) { }
37
+ // Give SIGKILL a moment
38
+ for (let i = 0; i < 10; i++) {
39
+ if (!(0, watcher_store_1.isProcessRunning)(pid))
40
+ return true;
41
+ yield new Promise((r) => setTimeout(r, 100));
42
+ }
43
+ return !(0, watcher_store_1.isProcessRunning)(pid);
44
+ });
45
+ }
@@ -8,39 +8,41 @@ exports.launchWatcher = launchWatcher;
8
8
  const node_child_process_1 = require("node:child_process");
9
9
  const project_registry_1 = require("./project-registry");
10
10
  const watcher_store_1 = require("./watcher-store");
11
- /**
12
- * Launch a background watcher for a project.
13
- *
14
- * Returns { pid } on success, null if:
15
- * - Project is not registered
16
- * - Watcher is already running
17
- * - Spawn fails
18
- */
19
11
  function launchWatcher(projectRoot) {
20
12
  var _a;
21
13
  // 1. Project must be registered
22
14
  const project = (0, project_registry_1.getProject)(projectRoot);
23
15
  if (!project) {
24
- return null;
16
+ return {
17
+ ok: false,
18
+ reason: "not-registered",
19
+ message: `Project not registered. Run: gmax add ${projectRoot}`,
20
+ };
25
21
  }
26
22
  // 2. Check if watcher already running
27
23
  const existing = (_a = (0, watcher_store_1.getWatcherForProject)(projectRoot)) !== null && _a !== void 0 ? _a : (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
28
24
  if (existing && (0, watcher_store_1.isProcessRunning)(existing.pid)) {
29
- return { pid: existing.pid };
25
+ return { ok: true, pid: existing.pid, reused: true };
30
26
  }
31
27
  // 3. Spawn
32
28
  try {
33
29
  const child = (0, node_child_process_1.spawn)(process.argv[0], [process.argv[1], "watch", "--path", projectRoot, "-b"], { detached: true, stdio: "ignore" });
34
30
  child.unref();
35
31
  if (child.pid) {
36
- return { pid: child.pid };
32
+ return { ok: true, pid: child.pid, reused: false };
37
33
  }
38
- console.error(`[watcher-launcher] Spawn returned no PID for ${projectRoot}`);
39
- return null;
34
+ return {
35
+ ok: false,
36
+ reason: "spawn-failed",
37
+ message: `Spawn returned no PID for ${projectRoot}`,
38
+ };
40
39
  }
41
40
  catch (err) {
42
41
  const msg = err instanceof Error ? err.message : String(err);
43
- console.error(`[watcher-launcher] Failed to start watcher for ${projectRoot}: ${msg}`);
44
- return null;
42
+ return {
43
+ ok: false,
44
+ reason: "spawn-failed",
45
+ message: `Failed to start watcher for ${projectRoot}: ${msg}`,
46
+ };
45
47
  }
46
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
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.8.1",
3
+ "version": "0.8.2",
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",