grepmax 0.26.18 → 0.26.20

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.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.18",
13
+ "version": "0.26.20",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
@@ -54,6 +54,7 @@ const sync_helpers_1 = require("../lib/index/sync-helpers");
54
54
  const syncer_1 = require("../lib/index/syncer");
55
55
  const setup_helpers_1 = require("../lib/setup/setup-helpers");
56
56
  const vector_db_1 = require("../lib/store/vector-db");
57
+ const autostart_1 = require("../lib/utils/autostart");
57
58
  const blocked_roots_1 = require("../lib/utils/blocked-roots");
58
59
  const exit_1 = require("../lib/utils/exit");
59
60
  const project_marker_1 = require("../lib/utils/project-marker");
@@ -226,6 +227,12 @@ Examples:
226
227
  }
227
228
  else {
228
229
  // Fallback: direct mode with lock
230
+ const notice = (0, autostart_1.autostartDisabledNotice)();
231
+ if (notice) {
232
+ spinner.stop();
233
+ console.log(notice);
234
+ spinner.start();
235
+ }
229
236
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
230
237
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
231
238
  try {
@@ -51,6 +51,7 @@ const sync_helpers_1 = require("../lib/index/sync-helpers");
51
51
  const syncer_1 = require("../lib/index/syncer");
52
52
  const setup_helpers_1 = require("../lib/setup/setup-helpers");
53
53
  const vector_db_1 = require("../lib/store/vector-db");
54
+ const autostart_1 = require("../lib/utils/autostart");
54
55
  const exit_1 = require("../lib/utils/exit");
55
56
  const project_registry_1 = require("../lib/utils/project-registry");
56
57
  const project_root_1 = require("../lib/utils/project-root");
@@ -149,6 +150,9 @@ Examples:
149
150
  }
150
151
  else {
151
152
  // Fallback: direct mode with lock — stop any watcher first
153
+ const notice = (0, autostart_1.autostartDisabledNotice)();
154
+ if (notice)
155
+ console.log(notice);
152
156
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
153
157
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
154
158
  let restartWatcher = false;
@@ -93,7 +93,11 @@ exports.llm
93
93
  try {
94
94
  const { ensureDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
95
95
  if (!(yield ensureDaemonRunning())) {
96
- console.error("Failed to start daemon");
96
+ const { autostartDisabledUndo } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/autostart")));
97
+ const undo = autostartDisabledUndo();
98
+ console.error(undo
99
+ ? `Daemon autostart is disabled and the LLM server needs the daemon. Re-enable with: ${undo}`
100
+ : "Failed to start daemon");
97
101
  process.exitCode = 1;
98
102
  return;
99
103
  }
@@ -14,6 +14,7 @@ const commander_1 = require("commander");
14
14
  const config_1 = require("../config");
15
15
  const index_config_1 = require("../lib/index/index-config");
16
16
  const vector_db_1 = require("../lib/store/vector-db");
17
+ const autostart_1 = require("../lib/utils/autostart");
17
18
  const daemon_client_1 = require("../lib/utils/daemon-client");
18
19
  const exit_1 = require("../lib/utils/exit");
19
20
  const project_registry_1 = require("../lib/utils/project-registry");
@@ -38,8 +39,12 @@ Examples:
38
39
  try {
39
40
  if (opts.rebuild) {
40
41
  const running = yield (0, daemon_client_1.ensureDaemonRunning)();
41
- if (!running)
42
- throw new Error("failed to start the gmax daemon");
42
+ if (!running) {
43
+ const undo = (0, autostart_1.autostartDisabledUndo)();
44
+ throw new Error(undo
45
+ ? `daemon autostart is disabled and --rebuild needs the daemon; re-enable with: ${undo}`
46
+ : "failed to start the gmax daemon");
47
+ }
43
48
  const ping = yield (0, daemon_client_1.sendDaemonCommand)({ cmd: "ping" }, { timeoutMs: 10000 });
44
49
  const capabilities = ping.capabilities && typeof ping.capabilities === "object"
45
50
  ? ping.capabilities
package/dist/config.js CHANGED
@@ -229,6 +229,10 @@ exports.PATHS = {
229
229
  // never SIGKILLs a peer mid-cleanup once the peer has already dropped its
230
230
  // socket/PID/lock liveness markers.
231
231
  daemonDrainingFile: path.join(GLOBAL_ROOT, "daemon.draining"),
232
+ // Kill switch for implicit daemon auto-start (see lib/utils/autostart.ts).
233
+ // Its presence stops hooks and CLI commands from reviving a quarantined
234
+ // daemon; explicit `gmax watch --daemon` still works.
235
+ autostartDisabledFile: path.join(GLOBAL_ROOT, "autostart-disabled"),
232
236
  // Centralized index storage — one database for all indexed directories
233
237
  lancedbDir: path.join(GLOBAL_ROOT, "lancedb"),
234
238
  cacheDir: path.join(GLOBAL_ROOT, "cache"),
@@ -47,6 +47,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
48
  exports.StoreLease = exports.StoreLeaseTimeoutError = void 0;
49
49
  exports.storeLeasePaths = storeLeasePaths;
50
+ exports.defaultProbeOwner = defaultProbeOwner;
50
51
  exports.hasStoreExclusiveIntent = hasStoreExclusiveIntent;
51
52
  const node_child_process_1 = require("node:child_process");
52
53
  const node_crypto_1 = require("node:crypto");
@@ -97,19 +98,37 @@ function currentProcessStart(pid) {
97
98
  return fallback;
98
99
  }
99
100
  }
100
- function defaultProbeOwner(owner) {
101
+ const defaultProbeDeps = {
102
+ signal: (pid) => {
103
+ process.kill(pid, 0);
104
+ },
105
+ processStartOf: (pid) => (0, node_child_process_1.execFileSync)("ps", ["-p", String(pid), "-o", "lstart="], {
106
+ encoding: "utf8",
107
+ timeout: 1000,
108
+ }).trim(),
109
+ };
110
+ function defaultProbeOwner(owner, deps = defaultProbeDeps) {
101
111
  try {
102
- process.kill(owner.pid, 0);
112
+ deps.signal(owner.pid);
103
113
  }
104
114
  catch (error) {
105
- return error.code === "ESRCH"
106
- ? "dead"
107
- : "unknown";
115
+ const code = error.code;
116
+ if (code === "ESRCH")
117
+ return "dead";
118
+ // EPERM means the PID exists but is not signalable by us -- the signature
119
+ // of a recycled PID that now belongs to another user. That is not evidence
120
+ // the lease owner is alive, so fall through to the processStart comparison
121
+ // below, which `ps` answers across user boundaries. Returning "unknown"
122
+ // here instead would make the reader marker immortal and hang every
123
+ // exclusive operation for the life of the store.
124
+ if (code !== "EPERM")
125
+ return "unknown";
108
126
  }
109
127
  if (owner.processStart.startsWith(`pid:${owner.pid}:started:`))
110
128
  return "same";
111
129
  try {
112
- const start = (0, node_child_process_1.execFileSync)("ps", ["-p", String(owner.pid), "-o", "lstart="], { encoding: "utf8", timeout: 1000 }).trim();
130
+ const start = deps.processStartOf(owner.pid);
131
+ // Reserve "unknown" for a start-time lookup that could not answer.
113
132
  if (!start)
114
133
  return "unknown";
115
134
  return start === owner.processStart ? "same" : "reused";
@@ -0,0 +1,88 @@
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.autostartDisabledReason = autostartDisabledReason;
37
+ exports.isAutostartDisabled = isAutostartDisabled;
38
+ exports.autostartDisabledUndo = autostartDisabledUndo;
39
+ exports.autostartDisabledNotice = autostartDisabledNotice;
40
+ const fs = __importStar(require("node:fs"));
41
+ const config_1 = require("../../config");
42
+ /**
43
+ * Kill switch for *implicit* daemon auto-start. Create
44
+ * ~/.gmax/autostart-disabled (or export GMAX_NO_AUTOSTART=1) to keep session
45
+ * hooks and ordinary commands (`gmax add`, `gmax index`, search, MCP) from
46
+ * reviving the daemon — needed when the host is quarantined and the daemon's
47
+ * write volume is the thing under investigation.
48
+ *
49
+ * Explicit `gmax watch --daemon` is never gated: typing it is the user asking
50
+ * for a daemon in that moment.
51
+ *
52
+ * plugins/grepmax/hooks/start.js carries a plain-JS copy of these semantics
53
+ * (env var checked first, then the file) because a SessionStart hook cannot
54
+ * import from dist. Keep the two in sync.
55
+ */
56
+ function autostartDisabledReason() {
57
+ if (process.env.GMAX_NO_AUTOSTART === "1")
58
+ return "env";
59
+ try {
60
+ return fs.existsSync(config_1.PATHS.autostartDisabledFile) ? "file" : null;
61
+ }
62
+ catch (_a) {
63
+ return null;
64
+ }
65
+ }
66
+ function isAutostartDisabled() {
67
+ return autostartDisabledReason() !== null;
68
+ }
69
+ /**
70
+ * One-line notice for commands that fell back to in-process work because the
71
+ * kill switch is on, or null when it isn't. Names the specific undo step so
72
+ * the message is actionable whichever way autostart was disabled.
73
+ */
74
+ /** The shell command that re-enables autostart, or null when it is not disabled. */
75
+ function autostartDisabledUndo() {
76
+ const reason = autostartDisabledReason();
77
+ if (!reason)
78
+ return null;
79
+ return reason === "env"
80
+ ? "unset GMAX_NO_AUTOSTART"
81
+ : `rm ${config_1.PATHS.autostartDisabledFile}`;
82
+ }
83
+ function autostartDisabledNotice() {
84
+ const undo = autostartDisabledUndo();
85
+ if (!undo)
86
+ return null;
87
+ return `Daemon autostart is disabled — running in-process. Re-enable with: ${undo}`;
88
+ }
@@ -269,12 +269,19 @@ function isDaemonDraining(pid) {
269
269
  /**
270
270
  * Ensure the daemon is running — start it if needed, then wait for resources.
271
271
  * Returns true if daemon is ready, false if it couldn't be started.
272
+ *
273
+ * This is the implicit spawn path, so it honors the autostart kill switch: with
274
+ * it on, an already-running daemon is still used, but a dead one is not
275
+ * revived and callers fall back to in-process work.
272
276
  */
273
277
  function ensureDaemonRunning() {
274
278
  return __awaiter(this, void 0, void 0, function* () {
275
279
  if (yield isDaemonReady())
276
280
  return true;
277
281
  if (!(yield isDaemonRunning())) {
282
+ const { isAutostartDisabled } = yield Promise.resolve().then(() => __importStar(require("./autostart")));
283
+ if (isAutostartDisabled())
284
+ return false;
278
285
  const { spawnDaemon } = yield Promise.resolve().then(() => __importStar(require("./daemon-launcher")));
279
286
  const pid = yield spawnDaemon();
280
287
  if (!pid)
@@ -16,6 +16,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.launchWatcher = launchWatcher;
18
18
  const node_child_process_1 = require("node:child_process");
19
+ const autostart_1 = require("./autostart");
19
20
  const daemon_client_1 = require("./daemon-client");
20
21
  const daemon_launcher_1 = require("./daemon-launcher");
21
22
  const project_registry_1 = require("./project-registry");
@@ -48,7 +49,13 @@ function launchWatcher(projectRoot) {
48
49
  if (resp.ok && typeof resp.pid === "number") {
49
50
  return { ok: true, pid: resp.pid, reused: true };
50
51
  }
51
- // 4. Daemon not running — try to start it, poll until ready
52
+ // 4. Daemon not running — try to start it, poll until ready.
53
+ // Both remaining steps spawn a background process, so stop here when the
54
+ // autostart kill switch is on: a quarantined daemon must stay down.
55
+ const notice = (0, autostart_1.autostartDisabledNotice)();
56
+ if (notice) {
57
+ return { ok: false, reason: "autostart-disabled", message: notice };
58
+ }
52
59
  const error = resp.error;
53
60
  if (error === "ENOENT" || error === "ECONNREFUSED") {
54
61
  const daemonPid = yield (0, daemon_launcher_1.spawnDaemon)();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.18",
3
+ "version": "0.26.20",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.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.26.18",
3
+ "version": "0.26.20",
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",
@@ -82,6 +82,9 @@ function isProjectRegistered() {
82
82
  // GMAX_NO_AUTOSTART=1) to keep sessions from reviving the daemon — needed when the
83
83
  // host is quarantined after a kernel-zone incident and the daemon's write volume is
84
84
  // the thing under investigation. Delete the file to restore normal behavior.
85
+ //
86
+ // The CLI honors the same switch via src/lib/utils/autostart.ts (PATHS.autostartDisabledFile).
87
+ // This copy exists because a hook cannot import from dist — keep the two in sync.
85
88
  function isAutostartDisabled() {
86
89
  if (process.env.GMAX_NO_AUTOSTART === "1") return true;
87
90
  try {