grepmax 0.26.61 → 0.26.62

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.61",
13
+ "version": "0.26.62",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -570,3 +570,9 @@ worker limits remain. The default `strict` policy keeps aggregate admission.
570
570
  Restart the daemon after changing policy. An existing safety-stop marker requires
571
571
  explicit operator review and clearing; selecting a policy never clears it.
572
572
  Full-table compaction remains disabled in either policy.
573
+
574
+ Root `.gmaxignore` exclusions also filter native filesystem events. Simple
575
+ positive rules update both daemon and standalone subscriptions when the policy
576
+ changes. Rules with negations, escapes or advanced pattern syntax remain in the
577
+ indexing policy only, preserving re-included files. Source JSON outside an
578
+ excluded path stays indexable.
@@ -57,6 +57,7 @@ const cache_coherence_1 = require("../index/cache-coherence");
57
57
  const file_policy_1 = require("../index/file-policy");
58
58
  const walker_1 = require("../index/walker");
59
59
  const watcher_1 = require("../index/watcher");
60
+ const watcher_ignore_1 = require("../index/watcher-ignore");
60
61
  const file_utils_1 = require("../utils/file-utils");
61
62
  const logger_1 = require("../utils/logger");
62
63
  const project_registry_1 = require("../utils/project-registry");
@@ -114,6 +115,7 @@ class WatcherManager {
114
115
  this.degradedRoots = new Set();
115
116
  this.terminalFailures = new Map();
116
117
  this.watchLifecycles = new Map();
118
+ this.subscriptionUpdates = new Map();
117
119
  }
118
120
  health(root) {
119
121
  var _a;
@@ -155,7 +157,17 @@ class WatcherManager {
155
157
  let lastReindexAt;
156
158
  processor = new batch_processor_1.ProjectBatchProcessor(Object.assign(Object.assign({ projectRoot: root, vectorDb,
157
159
  metaCache }, (workerPool ? { workerPool } : {})), { filePolicy, onPolicyChange: () => {
158
- void this.runCatchup(root, processor).catch((err) => {
160
+ void this.subscribeWatcher(root, processor)
161
+ .then(() => {
162
+ if (this.deps.processors.get(root) === processor &&
163
+ !this.deps.getShuttingDown())
164
+ return this.runCatchup(root, processor);
165
+ return;
166
+ }, (error) => {
167
+ this.recoverWatcher(root, processor);
168
+ throw error;
169
+ })
170
+ .catch((err) => {
159
171
  console.error(`[daemon:${path.basename(root)}] Policy reconciliation failed:`, err);
160
172
  });
161
173
  }, onReindex: (files, ms) => __awaiter(this, void 0, void 0, function* () {
@@ -277,8 +289,27 @@ class WatcherManager {
277
289
  }
278
290
  subscribeWatcher(root, processor) {
279
291
  return __awaiter(this, void 0, void 0, function* () {
292
+ var _a;
293
+ const previous = (_a = this.subscriptionUpdates.get(root)) !== null && _a !== void 0 ? _a : Promise.resolve();
294
+ const update = previous
295
+ .catch(() => { })
296
+ .then(() => this.subscribeWatcherNow(root, processor));
297
+ this.subscriptionUpdates.set(root, update);
298
+ try {
299
+ yield update;
300
+ }
301
+ finally {
302
+ if (this.subscriptionUpdates.get(root) === update)
303
+ this.subscriptionUpdates.delete(root);
304
+ }
305
+ });
306
+ }
307
+ subscribeWatcherNow(root, processor) {
308
+ return __awaiter(this, void 0, void 0, function* () {
309
+ var _a, _b;
280
310
  const name = path.basename(root);
281
- if (this.deps.processors.get(root) !== processor)
311
+ if (this.deps.processors.get(root) !== processor ||
312
+ ((_b = (_a = this.deps).getShuttingDown) === null || _b === void 0 ? void 0 : _b.call(_a)))
282
313
  return;
283
314
  // Unsubscribe existing watcher if any (e.g. during recovery)
284
315
  const existingSub = this.deps.subscriptions.get(root);
@@ -286,11 +317,13 @@ class WatcherManager {
286
317
  try {
287
318
  yield existingSub.unsubscribe();
288
319
  }
289
- catch (_a) { }
320
+ catch (_c) { }
290
321
  this.deps.subscriptions.delete(root);
291
322
  }
292
323
  const sub = yield watcher.subscribe(root, (err, events) => {
293
324
  var _a, _b;
325
+ if (this.deps.processors.get(root) !== processor)
326
+ return;
294
327
  if (err) {
295
328
  this.overflowCounts.set(root, ((_a = this.overflowCounts.get(root)) !== null && _a !== void 0 ? _a : 0) + 1);
296
329
  const now = Date.now();
@@ -311,7 +344,12 @@ class WatcherManager {
311
344
  processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
312
345
  }
313
346
  this.deps.touchActivity();
314
- }, { ignore: watcher_1.WATCHER_IGNORE_GLOBS });
347
+ }, {
348
+ ignore: [
349
+ ...watcher_1.WATCHER_IGNORE_GLOBS,
350
+ ...(yield (0, watcher_ignore_1.readProjectWatcherIgnores)(root)),
351
+ ],
352
+ });
315
353
  if (this.deps.processors.get(root) !== processor) {
316
354
  yield sub.unsubscribe();
317
355
  return;
@@ -725,7 +763,7 @@ class WatcherManager {
725
763
  }
726
764
  unwatchProject(root) {
727
765
  return __awaiter(this, void 0, void 0, function* () {
728
- var _a;
766
+ var _a, _b;
729
767
  // Stop poll-mode timers + their FSEvents recovery probe first, so a removed
730
768
  // project can't keep scanning until full daemon shutdown. These live
731
769
  // independently of the processor, so clear them even on the early return.
@@ -750,6 +788,7 @@ class WatcherManager {
750
788
  this.pendingOps.delete(`recover:${root}`);
751
789
  const processor = this.deps.processors.get(root);
752
790
  this.deps.processors.delete(root);
791
+ yield (_b = this.subscriptionUpdates.get(root)) === null || _b === void 0 ? void 0 : _b.catch(() => { });
753
792
  yield this.stopCatchup(root);
754
793
  if (processor)
755
794
  yield processor.close();
@@ -0,0 +1,104 @@
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
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.nativeProjectIgnoreGlobs = nativeProjectIgnoreGlobs;
49
+ exports.readProjectWatcherIgnores = readProjectWatcherIgnores;
50
+ const fs = __importStar(require("node:fs/promises"));
51
+ const path = __importStar(require("node:path"));
52
+ const ignore_1 = __importDefault(require("ignore"));
53
+ /** Only the common positive gitignore/glob subset is safe to push into Parcel.
54
+ * Negations, escapes and unsupported syntax stay with ProjectFilePolicy, so a
55
+ * native ignore can never hide a file that an explicit re-inclusion needs. */
56
+ function nativeProjectIgnoreGlobs(content) {
57
+ const lines = content
58
+ .split("\n")
59
+ .map((line) => line.replace(/\r$/, "").replace(/ +$/, ""));
60
+ if (lines.some((line) => line.startsWith("!")))
61
+ return [];
62
+ const result = new Set();
63
+ for (const line of lines) {
64
+ if (!line || line.startsWith("#") || !/^[A-Za-z0-9_.*/-]+$/.test(line))
65
+ continue;
66
+ const pattern = line.replace(/^\//, "").replace(/\/$/, "");
67
+ if (!pattern ||
68
+ pattern.split("/").some((part) => part === "." || part === ".."))
69
+ continue;
70
+ // Keep root policy changes observable, including removal of a blanket rule.
71
+ const filter = (0, ignore_1.default)().add(line);
72
+ if (filter.ignores(".gmaxignore") || filter.ignores(".gitignore"))
73
+ continue;
74
+ if (pattern.split("/").some((part) => part.includes("**") && part !== "**"))
75
+ continue;
76
+ const glob = line.startsWith("/") || pattern.includes("/") ? pattern : `**/${pattern}`;
77
+ // A trailing slash ignores directories, never a same-named source file.
78
+ if (!line.endsWith("/"))
79
+ result.add(glob);
80
+ result.add(`${glob}/**`);
81
+ }
82
+ return [...result];
83
+ }
84
+ /** Root .gmaxignore rules are explicit project choices. Nested scopes and
85
+ * .gitignore remain authoritative in file policy, without lossy translation. */
86
+ function readProjectWatcherIgnores(root) {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ try {
89
+ const file = path.join(root, ".gmaxignore");
90
+ const stat = yield fs.stat(file);
91
+ if (!stat.isFile() || stat.size > 65536)
92
+ return [];
93
+ const contents = yield fs.readFile(file, "utf8");
94
+ if (Buffer.byteLength(contents) > 65536)
95
+ return [];
96
+ return nativeProjectIgnoreGlobs(contents);
97
+ }
98
+ catch (_a) {
99
+ // File policy handles unreadable policy conservatively. Native watching
100
+ // must still deliver changes rather than treating a failed read as empty.
101
+ return [];
102
+ }
103
+ });
104
+ }
@@ -58,6 +58,7 @@ const cache_coherence_1 = require("./cache-coherence");
58
58
  const file_policy_1 = require("./file-policy");
59
59
  const ignore_patterns_1 = require("./ignore-patterns");
60
60
  const walker_1 = require("./walker");
61
+ const watcher_ignore_1 = require("./watcher-ignore");
61
62
  // Directory names ignored at ANY depth. @parcel/watcher treats a non-glob
62
63
  // ignore entry as a literal path resolved against the watch root, so a bare
63
64
  // "dist" only covered <root>/dist — nested build dirs (packages/*/dist,
@@ -121,6 +122,42 @@ function startWatcher(opts) {
121
122
  let degradedRepairQueued = false;
122
123
  const terminalFailures = new Set();
123
124
  let processor;
125
+ let subscription = null;
126
+ let subscriptionUpdate = Promise.resolve();
127
+ let subscriptionPolicy = "";
128
+ const updateSubscription = () => {
129
+ const update = subscriptionUpdate
130
+ .catch(() => { })
131
+ .then(() => __awaiter(this, void 0, void 0, function* () {
132
+ const ignores = [
133
+ ...exports.WATCHER_IGNORE_GLOBS,
134
+ ...(yield (0, watcher_ignore_1.readProjectWatcherIgnores)(projectRoot)),
135
+ ];
136
+ const policy = JSON.stringify(ignores);
137
+ if (closing || (subscription && subscriptionPolicy === policy))
138
+ return;
139
+ yield subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe();
140
+ subscription = null;
141
+ if (closing)
142
+ return;
143
+ const next = yield watcher.subscribe(projectRoot, (err, events) => {
144
+ if (err) {
145
+ console.error(`[${wtag}] Watcher error:`, err);
146
+ return;
147
+ }
148
+ for (const event of events)
149
+ processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
150
+ }, { ignore: ignores });
151
+ if (closing) {
152
+ yield next.unsubscribe();
153
+ return;
154
+ }
155
+ subscription = next;
156
+ subscriptionPolicy = policy;
157
+ }));
158
+ subscriptionUpdate = update;
159
+ return update;
160
+ };
124
161
  const reportHealthyIfSettled = () => {
125
162
  var _a;
126
163
  if (scanHealthy &&
@@ -144,6 +181,9 @@ function startWatcher(opts) {
144
181
  do {
145
182
  reconcileRequested = false;
146
183
  filePolicy.invalidateIgnoreCache();
184
+ yield updateSubscription();
185
+ if (closing)
186
+ return;
147
187
  const rootPrefix = projectRoot.endsWith("/")
148
188
  ? projectRoot
149
189
  : `${projectRoot}/`;
@@ -234,15 +274,7 @@ function startWatcher(opts) {
234
274
  }
235
275
  reportHealthyIfSettled();
236
276
  } }));
237
- const subscription = yield watcher.subscribe(projectRoot, (err, events) => {
238
- if (err) {
239
- console.error(`[${wtag}] Watcher error:`, err);
240
- return;
241
- }
242
- for (const event of events) {
243
- processor.handleFileEvent(event.type === "delete" ? "unlink" : "change", event.path);
244
- }
245
- }, { ignore: exports.WATCHER_IGNORE_GLOBS });
277
+ yield updateSubscription();
246
278
  reconcile();
247
279
  return {
248
280
  get progress() {
@@ -250,7 +282,8 @@ function startWatcher(opts) {
250
282
  },
251
283
  close: () => __awaiter(this, void 0, void 0, function* () {
252
284
  closing = true;
253
- yield subscription.unsubscribe();
285
+ yield subscriptionUpdate.catch(() => { });
286
+ yield subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe();
254
287
  yield reconciliation;
255
288
  yield processor.close();
256
289
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.61",
3
+ "version": "0.26.62",
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.61",
3
+ "version": "0.26.62",
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",