grepmax 0.26.13 → 0.26.14

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.13",
13
+ "version": "0.26.14",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
@@ -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
- if (!fs.existsSync(p.root)) {
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
- console.log(`[daemon] Skipping ${path.basename(p.root)} — directory not found`);
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 {
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.13",
3
+ "version": "0.26.14",
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.13",
3
+ "version": "0.26.14",
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",