grepmax 0.26.13 → 0.26.15
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/doctor.js +25 -4
- package/dist/lib/daemon/daemon.js +5 -1
- package/dist/lib/index/syncer.js +6 -2
- package/dist/lib/utils/file-utils.js +101 -8
- package/dist/lib/utils/root-availability.js +105 -0
- package/dist/lib/workers/orchestrator.js +1 -1
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -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.15",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/dist/commands/doctor.js
CHANGED
|
@@ -56,6 +56,7 @@ const lock_1 = require("../lib/utils/lock");
|
|
|
56
56
|
const mlx_hf_cache_1 = require("../lib/utils/mlx-hf-cache");
|
|
57
57
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
58
58
|
const project_root_1 = require("../lib/utils/project-root");
|
|
59
|
+
const root_availability_1 = require("../lib/utils/root-availability");
|
|
59
60
|
function formatSize(bytes) {
|
|
60
61
|
if (bytes < 1024)
|
|
61
62
|
return `${bytes} B`;
|
|
@@ -197,6 +198,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
197
198
|
let needsOptimize = false;
|
|
198
199
|
let staleLock = false;
|
|
199
200
|
const orphanedProjects = [];
|
|
201
|
+
const unavailableProjects = [];
|
|
200
202
|
try {
|
|
201
203
|
const { VectorDB, findStaleLanceTempFiles, sweepStaleLanceTempFiles } = yield Promise.resolve().then(() => __importStar(require("../lib/store/vector-db")));
|
|
202
204
|
const db = new VectorDB(config_1.PATHS.lancedbDir);
|
|
@@ -250,12 +252,21 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
250
252
|
// Daemon status
|
|
251
253
|
const { isDaemonRunning } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
252
254
|
const daemonUp = yield isDaemonRunning();
|
|
253
|
-
// Project registry health
|
|
255
|
+
// Project registry health.
|
|
256
|
+
//
|
|
257
|
+
// A root can be unreadable for two very different reasons, and only one of
|
|
258
|
+
// them is the project's fault. An unplugged external drive or a downed
|
|
259
|
+
// network share leaves the project perfectly intact — deregistering it
|
|
260
|
+
// would strand its vectors in the shared table with no owning project and
|
|
261
|
+
// would not come back when the volume returns. Only a root whose volume is
|
|
262
|
+
// mounted, and whose directory is still gone, is actually orphaned.
|
|
254
263
|
const projects = (0, project_registry_1.listProjects)();
|
|
255
264
|
for (const p of projects) {
|
|
256
|
-
|
|
265
|
+
const availability = (0, root_availability_1.classifyRoot)(p.root);
|
|
266
|
+
if (availability === "missing")
|
|
257
267
|
orphanedProjects.push(p.root);
|
|
258
|
-
|
|
268
|
+
else if (availability === "unavailable")
|
|
269
|
+
unavailableProjects.push(p.root);
|
|
259
270
|
}
|
|
260
271
|
// Compute warning flags
|
|
261
272
|
//
|
|
@@ -313,6 +324,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
313
324
|
`lock=${lockStatus.split(" ")[0]}`,
|
|
314
325
|
`daemon=${daemonUp ? "running" : "stopped"}`,
|
|
315
326
|
`orphaned=${orphanedProjects.length}`,
|
|
327
|
+
`offline=${unavailableProjects.length}`,
|
|
316
328
|
`stale_temp=${staleTempFiles.length}`,
|
|
317
329
|
`stale_temp_bytes=${staleTempBytes}`,
|
|
318
330
|
`stale_chunker=${staleChunkerProjects.length}`,
|
|
@@ -455,13 +467,19 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
455
467
|
if (legacyNotice)
|
|
456
468
|
console.log(legacyNotice);
|
|
457
469
|
// Projects
|
|
470
|
+
if (unavailableProjects.length > 0) {
|
|
471
|
+
console.log(`INFO Offline projects: ${unavailableProjects.length} (volume not mounted — kept, reconnect to resume)`);
|
|
472
|
+
for (const up of unavailableProjects) {
|
|
473
|
+
console.log(` - ${up}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
458
476
|
if (orphanedProjects.length > 0) {
|
|
459
477
|
console.log(`WARN Orphaned projects: ${orphanedProjects.length} (directories no longer exist)`);
|
|
460
478
|
for (const op of orphanedProjects) {
|
|
461
479
|
console.log(` - ${op}`);
|
|
462
480
|
}
|
|
463
481
|
}
|
|
464
|
-
else if (projects.length > 0) {
|
|
482
|
+
else if (projects.length > 0 && unavailableProjects.length === 0) {
|
|
465
483
|
console.log(`ok Projects: ${projects.length} registered, all directories exist`);
|
|
466
484
|
}
|
|
467
485
|
// Cache Coherence
|
|
@@ -563,6 +581,9 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
563
581
|
console.log(`ok Optimize complete (${via})`);
|
|
564
582
|
fixed++;
|
|
565
583
|
}
|
|
584
|
+
// Only roots whose volume is mounted and whose directory is still gone
|
|
585
|
+
// reach this list — projects on an unplugged drive are classified
|
|
586
|
+
// `unavailable` and never enter it, so --fix cannot deregister them.
|
|
566
587
|
if (orphanedProjects.length > 0) {
|
|
567
588
|
for (const op of orphanedProjects) {
|
|
568
589
|
(0, project_registry_1.removeProject)(op);
|
|
@@ -67,6 +67,7 @@ const logger_1 = require("../utils/logger");
|
|
|
67
67
|
const operation_coordinator_1 = require("../utils/operation-coordinator");
|
|
68
68
|
const process_1 = require("../utils/process");
|
|
69
69
|
const project_registry_1 = require("../utils/project-registry");
|
|
70
|
+
const root_availability_1 = require("../utils/root-availability");
|
|
70
71
|
const watcher_store_1 = require("../utils/watcher-store");
|
|
71
72
|
const pool_1 = require("../workers/pool");
|
|
72
73
|
const ipc_handler_1 = require("./ipc-handler");
|
|
@@ -352,7 +353,10 @@ class Daemon {
|
|
|
352
353
|
const indexed = allProjects.filter((p) => p.status === "indexed");
|
|
353
354
|
for (const p of indexed) {
|
|
354
355
|
if (!fs.existsSync(p.root)) {
|
|
355
|
-
|
|
356
|
+
// Skipping is already the safe behavior — the project keeps its registry
|
|
357
|
+
// entry and its vectors either way. Naming the reason keeps an unplugged
|
|
358
|
+
// drive from reading like data loss in the log.
|
|
359
|
+
console.log(`[daemon] Skipping ${path.basename(p.root)} — ${(0, root_availability_1.describeRoot)(p.root)}`);
|
|
356
360
|
continue;
|
|
357
361
|
}
|
|
358
362
|
try {
|
package/dist/lib/index/syncer.js
CHANGED
|
@@ -324,9 +324,13 @@ function initialSync(options) {
|
|
|
324
324
|
yield currentFlush;
|
|
325
325
|
(0, logger_1.debug)("index", `flush done: ${Date.now() - flushStart}ms`);
|
|
326
326
|
flushCount++;
|
|
327
|
-
// Periodically compact during sync to prevent fragment accumulation
|
|
327
|
+
// Periodically compact during sync to prevent fragment accumulation.
|
|
328
|
+
// Uses the shared threshold deliberately: a hardcoded 30 here meant
|
|
329
|
+
// the full-index path kept compacting at the old rate long after the
|
|
330
|
+
// threshold was raised to 400, re-triggering the whole-table rewrite
|
|
331
|
+
// that raising it exists to avoid.
|
|
328
332
|
if (flushCount % 10 === 0) {
|
|
329
|
-
yield vectorDb.compactIfNeeded(
|
|
333
|
+
yield vectorDb.compactIfNeeded(config_1.FRAGMENT_COMPACT_THRESHOLD);
|
|
330
334
|
}
|
|
331
335
|
}
|
|
332
336
|
catch (err) {
|
|
@@ -187,22 +187,115 @@ function isIndexableFile(filePath, size) {
|
|
|
187
187
|
function isIndexablePath(filePath) {
|
|
188
188
|
return isIndexableFile(filePath);
|
|
189
189
|
}
|
|
190
|
-
//
|
|
191
|
-
// Tools (protoc, graphql-codegen, Apollo, Relay, Go `stringer`,
|
|
192
|
-
// stamp one of these
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
// Markers that only ever appear in a codegen banner. Any one of these, on its
|
|
191
|
+
// own, is enough. Tools (protoc, graphql-codegen, Apollo, Relay, Go `stringer`,
|
|
192
|
+
// OpenAPI) stamp one of these; doc generators fence their output with the
|
|
193
|
+
// `generated-body` / `AUTO-GENERATED-CONTENT` style directives.
|
|
194
|
+
const STRONG_GENERATED_MARKER = /@generated\b|@autogenerated\b|\bCode generated by |\bauto-?generated by |\bThis (?:file|code) (?:is|was) (?:\w+[ -])?generated|\bmachine[ -]generated\b|\bauto-?generated[-_ ]?content\b|\bgenerated[-_](?:body|content|section|block)\b|\bBEGIN GENERATED\b/i;
|
|
195
|
+
// "DO NOT EDIT" is not proof of codegen on its own — humans write it on prose
|
|
196
|
+
// they want left alone (archived vendor snapshots, verbatim captures). Real
|
|
197
|
+
// codegen banners pair it with a generation word, so require that pairing.
|
|
198
|
+
const DO_NOT_EDIT_MARKER = /\bDO NOT EDIT\b/i;
|
|
199
|
+
const GENERATION_CONTEXT = /generat|codegen|autogen/i;
|
|
196
200
|
const GENERATED_SNIFF_BYTES = 2048;
|
|
201
|
+
const DOC_LIKE_EXTENSIONS = /\.(?:md|mdx|markdown|txt|rst|adoc|asciidoc)$/i;
|
|
202
|
+
/**
|
|
203
|
+
* Extract the leading banner: the run of shebang, frontmatter, blank, and
|
|
204
|
+
* comment lines at the very top, stopping at the first line of actual code.
|
|
205
|
+
*
|
|
206
|
+
* Scanning raw head bytes instead was the bug this replaces. Two ways it
|
|
207
|
+
* misfired, both silently erasing hand-written files from the index:
|
|
208
|
+
*
|
|
209
|
+
* - A *generator* was classified as its own output, because the banner it
|
|
210
|
+
* emits appears as a string literal in its body
|
|
211
|
+
* (`lines.push('# Auto-generated by ...')`).
|
|
212
|
+
* - A comment quoting another file's rules ("its README says do not edit")
|
|
213
|
+
* counted as a banner.
|
|
214
|
+
*
|
|
215
|
+
* A real codegen banner precedes all code, so anchoring here removes both
|
|
216
|
+
* without weakening detection.
|
|
217
|
+
*
|
|
218
|
+
* In doc-like files `#` starts a heading, not a comment, so only frontmatter
|
|
219
|
+
* and HTML comments count — otherwise the whole document body would be read as
|
|
220
|
+
* banner.
|
|
221
|
+
*/
|
|
222
|
+
function leadingBanner(head, filePath) {
|
|
223
|
+
var _a, _b;
|
|
224
|
+
const docLike = filePath ? DOC_LIKE_EXTENSIONS.test(filePath) : false;
|
|
225
|
+
const lines = head.split("\n");
|
|
226
|
+
const banner = [];
|
|
227
|
+
let i = 0;
|
|
228
|
+
if (!docLike && ((_a = lines[0]) === null || _a === void 0 ? void 0 : _a.startsWith("#!"))) {
|
|
229
|
+
banner.push(lines[0]);
|
|
230
|
+
i = 1;
|
|
231
|
+
}
|
|
232
|
+
// YAML frontmatter is metadata, not code — a generated-doc marker often sits
|
|
233
|
+
// directly after it.
|
|
234
|
+
if (((_b = lines[i]) === null || _b === void 0 ? void 0 : _b.trim()) === "---") {
|
|
235
|
+
banner.push(lines[i]);
|
|
236
|
+
i++;
|
|
237
|
+
while (i < lines.length && lines[i].trim() !== "---")
|
|
238
|
+
banner.push(lines[i++]);
|
|
239
|
+
if (i < lines.length)
|
|
240
|
+
banner.push(lines[i++]);
|
|
241
|
+
}
|
|
242
|
+
let blockEnd;
|
|
243
|
+
for (; i < lines.length; i++) {
|
|
244
|
+
const line = lines[i];
|
|
245
|
+
const trimmed = line.trim();
|
|
246
|
+
if (blockEnd) {
|
|
247
|
+
banner.push(line);
|
|
248
|
+
if (trimmed.includes(blockEnd))
|
|
249
|
+
blockEnd = undefined;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (trimmed === "") {
|
|
253
|
+
banner.push(line);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (trimmed.startsWith("<!--")) {
|
|
257
|
+
banner.push(line);
|
|
258
|
+
if (!trimmed.includes("-->"))
|
|
259
|
+
blockEnd = "-->";
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (!docLike) {
|
|
263
|
+
if (trimmed.startsWith("/*")) {
|
|
264
|
+
banner.push(line);
|
|
265
|
+
if (!trimmed.includes("*/"))
|
|
266
|
+
blockEnd = "*/";
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (trimmed.startsWith("//") ||
|
|
270
|
+
trimmed.startsWith("#") ||
|
|
271
|
+
trimmed.startsWith("--") ||
|
|
272
|
+
trimmed.startsWith(";") ||
|
|
273
|
+
trimmed.startsWith("*")) {
|
|
274
|
+
banner.push(line);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
break; // first real line of content — the banner is over
|
|
279
|
+
}
|
|
280
|
+
return banner.join("\n");
|
|
281
|
+
}
|
|
197
282
|
/**
|
|
198
283
|
* Detect machine-generated source by its banner. Generated files flood both the
|
|
199
284
|
* vector index and the symbol graph (codegen types rank as god nodes), so they
|
|
200
285
|
* are skipped at index time. Path globs in `ignore-patterns.ts` catch the
|
|
201
286
|
* obvious filenames; this catches the rest by content.
|
|
287
|
+
*
|
|
288
|
+
* `filePath` is optional but worth passing: without it, doc-like files are
|
|
289
|
+
* scanned with code comment rules, so a markdown `# Heading` reads as a comment.
|
|
202
290
|
*/
|
|
203
|
-
function isGeneratedContent(buffer) {
|
|
291
|
+
function isGeneratedContent(buffer, filePath) {
|
|
204
292
|
const head = buffer.subarray(0, GENERATED_SNIFF_BYTES).toString("utf-8");
|
|
205
|
-
|
|
293
|
+
const banner = leadingBanner(head, filePath);
|
|
294
|
+
if (!banner.trim())
|
|
295
|
+
return false;
|
|
296
|
+
if (STRONG_GENERATED_MARKER.test(banner))
|
|
297
|
+
return true;
|
|
298
|
+
return DO_NOT_EDIT_MARKER.test(banner) && GENERATION_CONTEXT.test(banner);
|
|
206
299
|
}
|
|
207
300
|
function formatDenseSnippet(text, maxLength = 1500) {
|
|
208
301
|
const clean = text !== null && text !== void 0 ? text : "";
|
|
@@ -0,0 +1,105 @@
|
|
|
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.classifyRoot = classifyRoot;
|
|
37
|
+
exports.isRootUnavailable = isRootUnavailable;
|
|
38
|
+
exports.describeRoot = describeRoot;
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
/**
|
|
41
|
+
* Directories that hold mount points rather than user data, with how many path
|
|
42
|
+
* segments below them the mount point itself sits.
|
|
43
|
+
*
|
|
44
|
+
* `mountDepth` errs high where a platform is ambiguous (Linux uses both
|
|
45
|
+
* `/media/<volume>` and `/media/<user>/<volume>`). Overshooting biases the
|
|
46
|
+
* classifier toward `unavailable`, which is the safe direction: a wrong
|
|
47
|
+
* `unavailable` only means we decline to auto-remove a genuinely dead project,
|
|
48
|
+
* while a wrong `missing` silently deregisters a live one.
|
|
49
|
+
*/
|
|
50
|
+
const MOUNT_CONTAINERS = [
|
|
51
|
+
{ prefix: "/Volumes", mountDepth: 1 }, // macOS
|
|
52
|
+
{ prefix: "/run/media", mountDepth: 2 }, // Linux, udisks2
|
|
53
|
+
{ prefix: "/media", mountDepth: 2 },
|
|
54
|
+
{ prefix: "/mnt", mountDepth: 1 },
|
|
55
|
+
{ prefix: "/net", mountDepth: 1 }, // autofs network mounts
|
|
56
|
+
];
|
|
57
|
+
function exists(p) {
|
|
58
|
+
try {
|
|
59
|
+
fs.statSync(p);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch (_a) {
|
|
63
|
+
// A disconnected network mount can throw EIO/ETIMEDOUT rather than ENOENT.
|
|
64
|
+
// Either way the path is not readable, and the caller's container check
|
|
65
|
+
// decides whether that means unavailable or missing.
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Classify a project root that may live on removable or network storage.
|
|
71
|
+
*
|
|
72
|
+
* Longest matching container wins, so `/run/media` is not shadowed by `/media`.
|
|
73
|
+
*/
|
|
74
|
+
function classifyRoot(root) {
|
|
75
|
+
if (exists(root))
|
|
76
|
+
return "present";
|
|
77
|
+
const container = MOUNT_CONTAINERS.filter((c) => root === c.prefix || root.startsWith(`${c.prefix}/`)).sort((a, b) => b.prefix.length - a.prefix.length)[0];
|
|
78
|
+
if (!container)
|
|
79
|
+
return "missing";
|
|
80
|
+
// The mount point is the container plus `mountDepth` segments. If it is
|
|
81
|
+
// absent, nothing is mounted there and the project is merely out of reach.
|
|
82
|
+
const rest = root.slice(container.prefix.length).split("/").filter(Boolean);
|
|
83
|
+
if (rest.length < container.mountDepth)
|
|
84
|
+
return "missing";
|
|
85
|
+
const mountPoint = `${container.prefix}/${rest.slice(0, container.mountDepth).join("/")}`;
|
|
86
|
+
return exists(mountPoint) ? "missing" : "unavailable";
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* True when the root is absent only because its volume is not mounted.
|
|
90
|
+
* Callers that delete or rewrite persistent state must check this first.
|
|
91
|
+
*/
|
|
92
|
+
function isRootUnavailable(root) {
|
|
93
|
+
return classifyRoot(root) === "unavailable";
|
|
94
|
+
}
|
|
95
|
+
/** Human-readable reason for a non-present root, for logs and doctor output. */
|
|
96
|
+
function describeRoot(root) {
|
|
97
|
+
switch (classifyRoot(root)) {
|
|
98
|
+
case "present":
|
|
99
|
+
return "available";
|
|
100
|
+
case "unavailable":
|
|
101
|
+
return "volume not mounted";
|
|
102
|
+
case "missing":
|
|
103
|
+
return "directory not found";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -256,7 +256,7 @@ class WorkerOrchestrator {
|
|
|
256
256
|
(0, logger_1.debug)("orch", `skip ${input.path} (empty or binary)`);
|
|
257
257
|
return { vectors: [], hash, mtimeMs, size, shouldDelete: true };
|
|
258
258
|
}
|
|
259
|
-
if ((0, file_utils_1.isGeneratedContent)(buffer)) {
|
|
259
|
+
if ((0, file_utils_1.isGeneratedContent)(buffer, absolutePath)) {
|
|
260
260
|
(0, logger_1.debug)("orch", `skip ${input.path} (machine-generated header)`);
|
|
261
261
|
return { vectors: [], hash, mtimeMs, size, shouldDelete: true };
|
|
262
262
|
}
|
package/package.json
CHANGED