grepmax 0.26.23 → 0.26.26
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/lib/daemon/watcher-manager.js +49 -5
- package/dist/lib/index/batch-processor.js +14 -0
- package/dist/lib/index/file-policy.js +39 -2
- package/dist/lib/index/ignore-patterns.js +2 -0
- package/dist/lib/index/watcher.js +2 -0
- package/package.json +13 -13
- 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.26",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
|
@@ -62,8 +62,16 @@ const logger_1 = require("../utils/logger");
|
|
|
62
62
|
const project_registry_1 = require("../utils/project-registry");
|
|
63
63
|
const watcher_store_1 = require("../utils/watcher-store");
|
|
64
64
|
// Watcher health windows used for FSEvents auto-recovery.
|
|
65
|
-
|
|
65
|
+
// Poll mode is the expensive state (a full catchup scan every 5 min), and in
|
|
66
|
+
// practice the first reattach attempt succeeds once the burst that caused the
|
|
67
|
+
// overflows has passed. Probe often; a failed probe just drops back to polling.
|
|
68
|
+
const FSEVENTS_RECOVERY_INTERVAL_MS = 15 * 60 * 1000;
|
|
66
69
|
const FSEVENTS_HEALTH_WINDOW_MS = 5 * 60 * 1000; // 5 min of quiet = "healthy"
|
|
70
|
+
// Overflows arrive in bursts (a build, a checkout, a venv install). Every
|
|
71
|
+
// recovery inside this window folds into one deferred catchup scan instead of
|
|
72
|
+
// each rescanning the project (~25s on a 26k-file tree) or skipping outright
|
|
73
|
+
// and leaving its drop window uncovered.
|
|
74
|
+
const CATCHUP_COOLDOWN_MS = 5 * 60 * 1000;
|
|
67
75
|
function abortableDelay(ms, signal) {
|
|
68
76
|
if (signal.aborted)
|
|
69
77
|
return Promise.resolve();
|
|
@@ -97,6 +105,7 @@ class WatcherManager {
|
|
|
97
105
|
this.recoveryTimeouts = new Map();
|
|
98
106
|
this.lastOverflowMs = new Map();
|
|
99
107
|
this.lastCatchupEndMs = new Map();
|
|
108
|
+
this.deferredCatchups = new Map();
|
|
100
109
|
this.catchups = new Map();
|
|
101
110
|
this.degradedRoots = new Set();
|
|
102
111
|
this.terminalFailures = new Map();
|
|
@@ -354,10 +363,12 @@ class WatcherManager {
|
|
|
354
363
|
yield this.subscribeWatcher(root, processor);
|
|
355
364
|
if (this.deps.processors.get(root) !== processor)
|
|
356
365
|
return;
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
366
|
+
const sinceLastMs = Date.now() - ((_a = this.lastCatchupEndMs.get(root)) !== null && _a !== void 0 ? _a : 0);
|
|
367
|
+
if (sinceLastMs < CATCHUP_COOLDOWN_MS) {
|
|
368
|
+
const delayMs = CATCHUP_COOLDOWN_MS - sinceLastMs;
|
|
369
|
+
if (this.deferCatchup(root, processor, delayMs)) {
|
|
370
|
+
console.log(`[daemon:${name}] Deferring catchup scan (last completed ${Math.round(sinceLastMs / 1000)}s ago; one coalesced scan in ${Math.round(delayMs / 1000)}s)`);
|
|
371
|
+
}
|
|
361
372
|
}
|
|
362
373
|
else {
|
|
363
374
|
yield this.runCatchup(root, processor);
|
|
@@ -374,6 +385,34 @@ class WatcherManager {
|
|
|
374
385
|
}, delayMs);
|
|
375
386
|
this.recoveryTimeouts.set(root, timeout);
|
|
376
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Schedule one catchup scan for the root at the end of the cooldown. Returns
|
|
390
|
+
* false when a deferred scan is already pending — every overflow inside the
|
|
391
|
+
* window is covered by that single scan, since it walks the whole project.
|
|
392
|
+
*/
|
|
393
|
+
deferCatchup(root, processor, delayMs) {
|
|
394
|
+
if (this.deferredCatchups.has(root))
|
|
395
|
+
return false;
|
|
396
|
+
const name = path.basename(root);
|
|
397
|
+
const timer = setTimeout(() => {
|
|
398
|
+
this.deferredCatchups.delete(root);
|
|
399
|
+
if (this.deps.getShuttingDown() ||
|
|
400
|
+
this.deps.processors.get(root) !== processor)
|
|
401
|
+
return;
|
|
402
|
+
this.runCatchup(root, processor).catch((err) => {
|
|
403
|
+
console.error(`[daemon:${name}] Deferred catchup failed:`, err);
|
|
404
|
+
});
|
|
405
|
+
}, delayMs);
|
|
406
|
+
this.deferredCatchups.set(root, timer);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
cancelDeferredCatchup(root) {
|
|
410
|
+
const timer = this.deferredCatchups.get(root);
|
|
411
|
+
if (!timer)
|
|
412
|
+
return;
|
|
413
|
+
clearTimeout(timer);
|
|
414
|
+
this.deferredCatchups.delete(root);
|
|
415
|
+
}
|
|
377
416
|
/**
|
|
378
417
|
* Once a project has fallen back to poll mode, periodically try to upgrade
|
|
379
418
|
* back to native FSEvents. The buffer overflows that triggered the fallback
|
|
@@ -672,6 +711,7 @@ class WatcherManager {
|
|
|
672
711
|
clearTimeout(recoveryTimeout);
|
|
673
712
|
this.recoveryTimeouts.delete(root);
|
|
674
713
|
}
|
|
714
|
+
this.cancelDeferredCatchup(root);
|
|
675
715
|
this.pendingOps.delete(`recover:${root}`);
|
|
676
716
|
const processor = this.deps.processors.get(root);
|
|
677
717
|
this.deps.processors.delete(root);
|
|
@@ -744,6 +784,10 @@ class WatcherManager {
|
|
|
744
784
|
for (const timeout of this.recoveryTimeouts.values()) {
|
|
745
785
|
clearTimeout(timeout);
|
|
746
786
|
}
|
|
787
|
+
for (const timeout of this.deferredCatchups.values()) {
|
|
788
|
+
clearTimeout(timeout);
|
|
789
|
+
}
|
|
790
|
+
this.deferredCatchups.clear();
|
|
747
791
|
this.recoveryTimeouts.clear();
|
|
748
792
|
const catchups = [...this.catchups.values()];
|
|
749
793
|
for (const active of catchups)
|
|
@@ -123,6 +123,20 @@ class ProjectBatchProcessor {
|
|
|
123
123
|
!this.metaCache.get(normalized) &&
|
|
124
124
|
!(event === "unlink" && (options === null || options === void 0 ? void 0 : options.forceDelete)))
|
|
125
125
|
return;
|
|
126
|
+
// A policy-ignored path that was never indexed has nothing to add and
|
|
127
|
+
// nothing to delete. Drop it here rather than after a debounce, a batch
|
|
128
|
+
// slot, and an lstat — transient lock/build files under a gitignored
|
|
129
|
+
// directory can arrive thousands of times a day. Anything with a meta
|
|
130
|
+
// entry still flows through so a policy change can retire its vectors,
|
|
131
|
+
// and forced repairs/deletes bypass the check because they target
|
|
132
|
+
// vectors the meta cache may not know about.
|
|
133
|
+
const isIgnored = this.filePolicy.isIgnoredByLoadedPolicy;
|
|
134
|
+
if (isIgnored &&
|
|
135
|
+
!(options === null || options === void 0 ? void 0 : options.forceDelete) &&
|
|
136
|
+
!(options === null || options === void 0 ? void 0 : options.forceReprocess) &&
|
|
137
|
+
!this.metaCache.get(normalized) &&
|
|
138
|
+
isIgnored.call(this.filePolicy, normalized))
|
|
139
|
+
return;
|
|
126
140
|
if (options === null || options === void 0 ? void 0 : options.forceReprocess) {
|
|
127
141
|
this.forcedReprocess.set(normalized, ++this.forceGeneration);
|
|
128
142
|
}
|
|
@@ -66,6 +66,8 @@ class ProjectFilePolicy {
|
|
|
66
66
|
constructor(projectRoot, options = {}) {
|
|
67
67
|
var _a, _b;
|
|
68
68
|
this.ignoreCache = new Map();
|
|
69
|
+
// Scopes whose ignore files have finished loading, for synchronous checks.
|
|
70
|
+
this.loadedScopes = new Map();
|
|
69
71
|
this.projectRoot = path.resolve(projectRoot);
|
|
70
72
|
try {
|
|
71
73
|
this.canonicalRoot = fs.realpathSync(this.projectRoot);
|
|
@@ -88,6 +90,36 @@ class ProjectFilePolicy {
|
|
|
88
90
|
}
|
|
89
91
|
invalidateIgnoreCache() {
|
|
90
92
|
this.ignoreCache.clear();
|
|
93
|
+
this.loadedScopes.clear();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Synchronous, best-effort ignore check against policy already in memory:
|
|
97
|
+
* the default patterns plus every .gitignore/.gmaxignore scope an earlier
|
|
98
|
+
* classify has loaded. Never touches the filesystem, so it is safe on the
|
|
99
|
+
* watcher's event path. `false` only means "not provably ignored yet" —
|
|
100
|
+
* callers must still classify before indexing.
|
|
101
|
+
*/
|
|
102
|
+
isIgnoredByLoadedPolicy(absPath) {
|
|
103
|
+
const resolved = path.resolve(absPath);
|
|
104
|
+
if (!this.isLexicallyContained(resolved))
|
|
105
|
+
return true;
|
|
106
|
+
const rootRelative = this.relative(resolved);
|
|
107
|
+
if (!rootRelative)
|
|
108
|
+
return false;
|
|
109
|
+
if (this.rootFilter.ignores(rootRelative))
|
|
110
|
+
return true;
|
|
111
|
+
for (const dir of this.scopeDirectories(path.dirname(resolved))) {
|
|
112
|
+
const scope = this.loadedScopes.get(dir);
|
|
113
|
+
if (!(scope === null || scope === void 0 ? void 0 : scope.filter))
|
|
114
|
+
continue;
|
|
115
|
+
const relative = path
|
|
116
|
+
.relative(scope.dir, resolved)
|
|
117
|
+
.split(path.sep)
|
|
118
|
+
.join("/");
|
|
119
|
+
if (relative && scope.filter.ignores(relative))
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
91
123
|
}
|
|
92
124
|
isLexicallyContained(absPath) {
|
|
93
125
|
return (0, path_containment_1.isPathWithin)(this.projectRoot, path.resolve(absPath));
|
|
@@ -127,9 +159,14 @@ class ProjectFilePolicy {
|
|
|
127
159
|
return { dir, filter };
|
|
128
160
|
}))();
|
|
129
161
|
this.ignoreCache.set(dir, pending);
|
|
130
|
-
void pending.
|
|
131
|
-
if (this.ignoreCache.get(dir) === pending)
|
|
162
|
+
void pending.then((scope) => {
|
|
163
|
+
if (this.ignoreCache.get(dir) === pending) {
|
|
164
|
+
this.loadedScopes.set(dir, scope);
|
|
165
|
+
}
|
|
166
|
+
}, () => {
|
|
167
|
+
if (this.ignoreCache.get(dir) === pending) {
|
|
132
168
|
this.ignoreCache.delete(dir);
|
|
169
|
+
}
|
|
133
170
|
});
|
|
134
171
|
return pending;
|
|
135
172
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.26",
|
|
4
4
|
"author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
|
|
5
5
|
"homepage": "https://github.com/reowens/grepmax",
|
|
6
6
|
"bugs": {
|
|
@@ -77,37 +77,37 @@
|
|
|
77
77
|
"license": "Apache-2.0",
|
|
78
78
|
"description": "Semantic code search for coding agents. Local embeddings, LLM summaries, call graph tracing.",
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@clack/prompts": "^1.
|
|
80
|
+
"@clack/prompts": "^1.7.0",
|
|
81
81
|
"@huggingface/transformers": "^4.2.0",
|
|
82
82
|
"@lancedb/lancedb": "0.38.0-beta.3",
|
|
83
83
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
84
|
-
"@parcel/watcher": "^2.
|
|
84
|
+
"@parcel/watcher": "^2.6.0",
|
|
85
85
|
"apache-arrow": "^18.1.0",
|
|
86
86
|
"chalk": "^5.6.2",
|
|
87
87
|
"cli-highlight": "^2.1.11",
|
|
88
88
|
"commander": "^15.0.0",
|
|
89
89
|
"dotenv": "^17.4.2",
|
|
90
90
|
"fast-glob": "^3.3.3",
|
|
91
|
-
"ignore": "^7.0.
|
|
91
|
+
"ignore": "^7.0.8",
|
|
92
92
|
"lmdb": "^3.5.6",
|
|
93
|
-
"numkong": "^7.
|
|
93
|
+
"numkong": "^7.8.2",
|
|
94
94
|
"onnxruntime-node": "1.24.3",
|
|
95
95
|
"openai": "^6.45.0",
|
|
96
96
|
"ora": "^9.4.1",
|
|
97
97
|
"proper-lockfile": "^4.1.2",
|
|
98
|
-
"uuid": "^14.0.
|
|
98
|
+
"uuid": "^14.0.2",
|
|
99
99
|
"web-tree-sitter": "^0.26.9",
|
|
100
|
-
"zod": "^4.4
|
|
100
|
+
"zod": "^4.5.4"
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@biomejs/biome": "2.
|
|
104
|
-
"@types/node": "^26.
|
|
103
|
+
"@biomejs/biome": "2.5.11",
|
|
104
|
+
"@types/node": "^26.4.1",
|
|
105
105
|
"@types/proper-lockfile": "^4.1.4",
|
|
106
|
-
"node-gyp": "^13.0.
|
|
106
|
+
"node-gyp": "^13.0.2",
|
|
107
107
|
"ts-node": "^10.9.2",
|
|
108
|
-
"tsx": "^4.
|
|
108
|
+
"tsx": "^4.23.13",
|
|
109
109
|
"typescript": "^6.0.3",
|
|
110
|
-
"vite": "^8.
|
|
111
|
-
"vitest": "^4.1.
|
|
110
|
+
"vite": "^8.2.2",
|
|
111
|
+
"vitest": "^4.1.11"
|
|
112
112
|
}
|
|
113
113
|
}
|