grepmax 0.26.16 → 0.26.19
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/.claude-plugin/marketplace.json +1 -1
- package/dist/commands/add.js +13 -0
- package/dist/commands/index.js +4 -0
- package/dist/config.js +4 -0
- package/dist/lib/daemon/daemon.js +9 -0
- package/dist/lib/store/store-lease.js +25 -6
- package/dist/lib/utils/autostart.js +81 -0
- package/dist/lib/utils/blocked-roots.js +27 -1
- package/dist/lib/utils/daemon-client.js +7 -0
- package/dist/lib/utils/project-registry.js +5 -0
- package/dist/lib/utils/watcher-launcher.js +8 -1
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/start.js +3 -0
|
@@ -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.
|
|
13
|
+
"version": "0.26.19",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/dist/commands/add.js
CHANGED
|
@@ -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");
|
|
@@ -98,6 +99,12 @@ Examples:
|
|
|
98
99
|
process.exitCode = 1;
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
102
|
+
if ((0, blocked_roots_1.isGitWorktreeRoot)(projectRoot)) {
|
|
103
|
+
logBlockedAttempt("git_worktree", projectRoot);
|
|
104
|
+
console.error(`Refusing to add ${projectRoot}: ${blocked_roots_1.WORKTREE_REFUSAL}`);
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
101
108
|
// Check if already registered
|
|
102
109
|
const existing = (0, project_registry_1.getProject)(projectRoot);
|
|
103
110
|
if (existing) {
|
|
@@ -220,6 +227,12 @@ Examples:
|
|
|
220
227
|
}
|
|
221
228
|
else {
|
|
222
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
|
+
}
|
|
223
236
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
224
237
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
225
238
|
try {
|
package/dist/commands/index.js
CHANGED
|
@@ -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;
|
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"),
|
|
@@ -59,6 +59,7 @@ const server_1 = require("../llm/server");
|
|
|
59
59
|
const meta_cache_1 = require("../store/meta-cache");
|
|
60
60
|
const store_lease_1 = require("../store/store-lease");
|
|
61
61
|
const vector_db_1 = require("../store/vector-db");
|
|
62
|
+
const blocked_roots_1 = require("../utils/blocked-roots");
|
|
62
63
|
const daemon_client_1 = require("../utils/daemon-client");
|
|
63
64
|
const daemon_launcher_1 = require("../utils/daemon-launcher");
|
|
64
65
|
const kernel_zone_1 = require("../utils/kernel-zone");
|
|
@@ -789,6 +790,10 @@ class Daemon {
|
|
|
789
790
|
return;
|
|
790
791
|
}
|
|
791
792
|
const project = (0, project_registry_1.getProject)(root);
|
|
793
|
+
if (!project && (0, blocked_roots_1.isGitWorktreeRoot)(root)) {
|
|
794
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: blocked_roots_1.WORKTREE_REFUSAL });
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
792
797
|
if ((project === null || project === void 0 ? void 0 : project.status) === "indexed") {
|
|
793
798
|
if (!this.activeGeneration ||
|
|
794
799
|
(0, embedding_generation_1.compareEmbeddingGeneration)(project, this.activeGeneration)
|
|
@@ -854,6 +859,10 @@ class Daemon {
|
|
|
854
859
|
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: "daemon resources not ready" });
|
|
855
860
|
return;
|
|
856
861
|
}
|
|
862
|
+
if (!(0, project_registry_1.getProject)(root) && (0, blocked_roots_1.isGitWorktreeRoot)(root)) {
|
|
863
|
+
(0, ipc_handler_1.writeDone)(conn, { ok: false, error: blocked_roots_1.WORKTREE_REFUSAL });
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
857
866
|
if (!(0, project_registry_1.getProject)(root)) {
|
|
858
867
|
(0, project_registry_1.registerProject)({
|
|
859
868
|
root,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
112
|
+
deps.signal(owner.pid);
|
|
103
113
|
}
|
|
104
114
|
catch (error) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 =
|
|
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,81 @@
|
|
|
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.autostartDisabledNotice = autostartDisabledNotice;
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
const config_1 = require("../../config");
|
|
41
|
+
/**
|
|
42
|
+
* Kill switch for *implicit* daemon auto-start. Create
|
|
43
|
+
* ~/.gmax/autostart-disabled (or export GMAX_NO_AUTOSTART=1) to keep session
|
|
44
|
+
* hooks and ordinary commands (`gmax add`, `gmax index`, search, MCP) from
|
|
45
|
+
* reviving the daemon — needed when the host is quarantined and the daemon's
|
|
46
|
+
* write volume is the thing under investigation.
|
|
47
|
+
*
|
|
48
|
+
* Explicit `gmax watch --daemon` is never gated: typing it is the user asking
|
|
49
|
+
* for a daemon in that moment.
|
|
50
|
+
*
|
|
51
|
+
* plugins/grepmax/hooks/start.js carries a plain-JS copy of these semantics
|
|
52
|
+
* (env var checked first, then the file) because a SessionStart hook cannot
|
|
53
|
+
* import from dist. Keep the two in sync.
|
|
54
|
+
*/
|
|
55
|
+
function autostartDisabledReason() {
|
|
56
|
+
if (process.env.GMAX_NO_AUTOSTART === "1")
|
|
57
|
+
return "env";
|
|
58
|
+
try {
|
|
59
|
+
return fs.existsSync(config_1.PATHS.autostartDisabledFile) ? "file" : null;
|
|
60
|
+
}
|
|
61
|
+
catch (_a) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function isAutostartDisabled() {
|
|
66
|
+
return autostartDisabledReason() !== null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* One-line notice for commands that fell back to in-process work because the
|
|
70
|
+
* kill switch is on, or null when it isn't. Names the specific undo step so
|
|
71
|
+
* the message is actionable whichever way autostart was disabled.
|
|
72
|
+
*/
|
|
73
|
+
function autostartDisabledNotice() {
|
|
74
|
+
const reason = autostartDisabledReason();
|
|
75
|
+
if (!reason)
|
|
76
|
+
return null;
|
|
77
|
+
const undo = reason === "env"
|
|
78
|
+
? "unset GMAX_NO_AUTOSTART"
|
|
79
|
+
: `rm ${config_1.PATHS.autostartDisabledFile}`;
|
|
80
|
+
return `Daemon autostart is disabled — running in-process. Re-enable with: ${undo}`;
|
|
81
|
+
}
|
|
@@ -33,9 +33,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.BLOCKED_ROOTS_DESCRIPTION = void 0;
|
|
36
|
+
exports.WORKTREE_REFUSAL = exports.BLOCKED_ROOTS_DESCRIPTION = void 0;
|
|
37
37
|
exports.getBlockedProjectRoots = getBlockedProjectRoots;
|
|
38
38
|
exports.isBlockedProjectRoot = isBlockedProjectRoot;
|
|
39
|
+
exports.isGitWorktreeRoot = isGitWorktreeRoot;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
39
41
|
const os = __importStar(require("node:os"));
|
|
40
42
|
const path = __importStar(require("node:path"));
|
|
41
43
|
exports.BLOCKED_ROOTS_DESCRIPTION = "home, /, /Users, /tmp, /private, /var, /usr, /opt, /etc, /System, /Library, /Applications";
|
|
@@ -61,3 +63,27 @@ function getBlockedProjectRoots() {
|
|
|
61
63
|
function isBlockedProjectRoot(root) {
|
|
62
64
|
return getBlockedProjectRoots().has(path.resolve(root));
|
|
63
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* A linked git worktree has a `.git` FILE (not a directory) pointing back into
|
|
68
|
+
* the main repo's `.git/worktrees/<name>`. Indexing one duplicates the whole
|
|
69
|
+
* corpus of the repo it was cut from — three platform worktrees once put 680k
|
|
70
|
+
* duplicate chunks in the index and pinned seven embed workers at ~1GB each.
|
|
71
|
+
*/
|
|
72
|
+
function isGitWorktreeRoot(root) {
|
|
73
|
+
const gitPath = path.join(path.resolve(root), ".git");
|
|
74
|
+
let contents;
|
|
75
|
+
try {
|
|
76
|
+
if (!fs.statSync(gitPath).isFile())
|
|
77
|
+
return false;
|
|
78
|
+
contents = fs.readFileSync(gitPath, "utf8");
|
|
79
|
+
}
|
|
80
|
+
catch (_a) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const match = /^gitdir:\s*(.+)$/m.exec(contents);
|
|
84
|
+
if (!(match === null || match === void 0 ? void 0 : match[1]))
|
|
85
|
+
return false;
|
|
86
|
+
const gitdir = match[1].trim().replace(/\\/g, "/");
|
|
87
|
+
return /(^|\/)\.git\/worktrees\//.test(gitdir);
|
|
88
|
+
}
|
|
89
|
+
exports.WORKTREE_REFUSAL = "gmax does not index git worktrees — they duplicate the repository they were cut from. Add the main repository root instead.";
|
|
@@ -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)
|
|
@@ -63,6 +63,7 @@ const path = __importStar(require("node:path"));
|
|
|
63
63
|
const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
|
|
64
64
|
const config_1 = require("../../config");
|
|
65
65
|
const embedding_generation_1 = require("../index/embedding-generation");
|
|
66
|
+
const blocked_roots_1 = require("./blocked-roots");
|
|
66
67
|
const REGISTRY_PATH = path.join(config_1.PATHS.globalRoot, "projects.json");
|
|
67
68
|
const REBUILD_JOURNAL_PATH = path.join(config_1.PATHS.globalRoot, "rebuild-journal.json");
|
|
68
69
|
class ProjectRegistryConflictError extends Error {
|
|
@@ -207,6 +208,10 @@ function registerProject(entry) {
|
|
|
207
208
|
}
|
|
208
209
|
withRegistryLock(() => {
|
|
209
210
|
const entries = loadRegistry();
|
|
211
|
+
if (!entries.some((e) => e.root === entry.root) &&
|
|
212
|
+
(0, blocked_roots_1.isGitWorktreeRoot)(entry.root)) {
|
|
213
|
+
throw new Error(`${blocked_roots_1.WORKTREE_REFUSAL} (${entry.root})`);
|
|
214
|
+
}
|
|
210
215
|
let canonicalRoot;
|
|
211
216
|
try {
|
|
212
217
|
canonicalRoot = fs.realpathSync(entry.root);
|
|
@@ -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
|
@@ -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 {
|