grepmax 0.26.2 → 0.26.4
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/context.js +4 -16
- package/dist/commands/related.js +4 -3
- package/dist/commands/search.js +28 -5
- package/dist/commands/skeleton.js +10 -8
- package/dist/lib/daemon/watcher-manager.js +9 -10
- package/dist/lib/index/batch-processor.js +4 -1
- package/dist/lib/index/watcher.js +1 -0
- package/dist/lib/store/vector-db.js +34 -0
- package/dist/lib/utils/path-containment.js +38 -0
- package/dist/lib/utils/project-root.js +41 -10
- package/dist/lib/utils/scope-filter.js +14 -4
- 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.4",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/dist/commands/context.js
CHANGED
|
@@ -45,7 +45,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.context = void 0;
|
|
46
46
|
exports.findEnclosingSignature = findEnclosingSignature;
|
|
47
47
|
const fs = __importStar(require("node:fs"));
|
|
48
|
-
const path = __importStar(require("node:path"));
|
|
49
48
|
const commander_1 = require("commander");
|
|
50
49
|
const searcher_1 = require("../lib/search/searcher");
|
|
51
50
|
const skeleton_1 = require("../lib/skeleton");
|
|
@@ -102,20 +101,6 @@ function findEnclosingSignature(lines, startLine, parentSymbol) {
|
|
|
102
101
|
}
|
|
103
102
|
return null;
|
|
104
103
|
}
|
|
105
|
-
function resolveExistingPath(target, root, projectRoot) {
|
|
106
|
-
const candidates = [
|
|
107
|
-
path.isAbsolute(target) ? target : path.resolve(root, target),
|
|
108
|
-
path.resolve(projectRoot, target),
|
|
109
|
-
];
|
|
110
|
-
for (const candidate of candidates) {
|
|
111
|
-
if (!fs.existsSync(candidate))
|
|
112
|
-
continue;
|
|
113
|
-
return (0, path_containment_1.resolveContainedPath)(projectRoot, candidate, {
|
|
114
|
-
verifyExistingTarget: true,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
104
|
function renderPathContext(target, absPath, projectRoot, budget) {
|
|
120
105
|
return __awaiter(this, void 0, void 0, function* () {
|
|
121
106
|
const state = {
|
|
@@ -192,7 +177,10 @@ exports.context = new commander_1.Command("context")
|
|
|
192
177
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
193
178
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
194
179
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
195
|
-
const pathTarget =
|
|
180
|
+
const pathTarget = (0, path_containment_1.resolveContainedExistingPath)(projectRoot, topic, {
|
|
181
|
+
cwd: root,
|
|
182
|
+
onOutside: "throw",
|
|
183
|
+
});
|
|
196
184
|
if (pathTarget) {
|
|
197
185
|
const rendered = yield renderPathContext(topic, pathTarget, projectRoot, budget);
|
|
198
186
|
rendered.sections.push(`\n(~${rendered.tokensUsed}/${budget} tokens used)`);
|
package/dist/commands/related.js
CHANGED
|
@@ -50,6 +50,7 @@ const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
|
50
50
|
const arrow_1 = require("../lib/utils/arrow");
|
|
51
51
|
const exit_1 = require("../lib/utils/exit");
|
|
52
52
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
53
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
53
54
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
54
55
|
const project_root_1 = require("../lib/utils/project-root");
|
|
55
56
|
const query_timeout_1 = require("../lib/utils/query-timeout");
|
|
@@ -63,7 +64,7 @@ exports.related = new commander_1.Command("related")
|
|
|
63
64
|
.option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
|
|
64
65
|
.option("--agent", "Compact output for AI agents", false)
|
|
65
66
|
.action((file, opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
-
var _a;
|
|
67
|
+
var _a, _b;
|
|
67
68
|
const limit = Math.min(Math.max(Number.parseInt(opts.limit || "10", 10), 1), 25);
|
|
68
69
|
let vectorDb = null;
|
|
69
70
|
try {
|
|
@@ -75,7 +76,7 @@ exports.related = new commander_1.Command("related")
|
|
|
75
76
|
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
76
77
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
77
78
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
78
|
-
const absPath = path.resolve(projectRoot, file);
|
|
79
|
+
const absPath = (_b = (0, path_containment_1.resolveContainedExistingPath)(projectRoot, file)) !== null && _b !== void 0 ? _b : path.resolve(projectRoot, file);
|
|
79
80
|
const table = yield vectorDb.ensureTable();
|
|
80
81
|
const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
81
82
|
const scope = resolveScope({
|
|
@@ -267,7 +268,7 @@ exports.related = new commander_1.Command("related")
|
|
|
267
268
|
try {
|
|
268
269
|
yield vectorDb.close();
|
|
269
270
|
}
|
|
270
|
-
catch (
|
|
271
|
+
catch (_c) { }
|
|
271
272
|
}
|
|
272
273
|
yield (0, exit_1.gracefulExit)();
|
|
273
274
|
}
|
package/dist/commands/search.js
CHANGED
|
@@ -43,10 +43,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.search = void 0;
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
46
47
|
const commander_1 = require("commander");
|
|
47
48
|
const setup_helpers_1 = require("../lib/setup/setup-helpers");
|
|
48
49
|
const cross_project_1 = require("../lib/utils/cross-project");
|
|
49
50
|
const exit_1 = require("../lib/utils/exit");
|
|
51
|
+
const path_containment_1 = require("../lib/utils/path-containment");
|
|
50
52
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
51
53
|
const project_root_1 = require("../lib/utils/project-root");
|
|
52
54
|
const scope_filter_1 = require("../lib/utils/scope-filter");
|
|
@@ -99,7 +101,7 @@ Examples:
|
|
|
99
101
|
gmax "auth middleware" --projects api,gateway --plain
|
|
100
102
|
`)
|
|
101
103
|
.action((pattern, exec_path, _options, cmd) => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
-
var _a, _b, _c, _d, _e, _f;
|
|
104
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
103
105
|
const options = cmd.optsWithGlobals();
|
|
104
106
|
const root = process.cwd();
|
|
105
107
|
const minScore = Number.isFinite(Number.parseFloat(options.minScore))
|
|
@@ -161,6 +163,17 @@ Examples:
|
|
|
161
163
|
}
|
|
162
164
|
const cwdProjectRoot = (_d = (0, project_root_1.findProjectRoot)(root)) !== null && _d !== void 0 ? _d : root;
|
|
163
165
|
const projectRootForServer = resolvedOptionRoot !== null && resolvedOptionRoot !== void 0 ? resolvedOptionRoot : cwdProjectRoot;
|
|
166
|
+
// A cwd inside a nested git repo covered by a registered umbrella project
|
|
167
|
+
// resolves to the umbrella (findProjectRoot). Scope the search to the
|
|
168
|
+
// subrepo and resolve relative subpaths against it — that's what a caller
|
|
169
|
+
// inside the subrepo means by "this project". --root overrides this.
|
|
170
|
+
const cwdGitRoot = crossProject.active ? null : (0, project_root_1.findGitRoot)(root);
|
|
171
|
+
const nestedScopeRoot = !options.root &&
|
|
172
|
+
cwdGitRoot &&
|
|
173
|
+
cwdGitRoot !== cwdProjectRoot &&
|
|
174
|
+
(0, path_containment_1.isPathWithin)(cwdProjectRoot, cwdGitRoot)
|
|
175
|
+
? cwdGitRoot
|
|
176
|
+
: undefined;
|
|
164
177
|
if (exec_path && options.in && options.in.length > 0) {
|
|
165
178
|
console.warn("Warning: --in overrides positional [path]; using --in.");
|
|
166
179
|
}
|
|
@@ -168,11 +181,14 @@ Examples:
|
|
|
168
181
|
try {
|
|
169
182
|
scope = (0, scope_filter_1.resolveScope)({
|
|
170
183
|
projectRoot: projectRootForServer,
|
|
184
|
+
base: nestedScopeRoot,
|
|
171
185
|
in: options.in && options.in.length > 0
|
|
172
186
|
? options.in
|
|
173
187
|
: exec_path
|
|
174
188
|
? [exec_path]
|
|
175
|
-
:
|
|
189
|
+
: nestedScopeRoot
|
|
190
|
+
? [nestedScopeRoot]
|
|
191
|
+
: undefined,
|
|
176
192
|
exclude: options.exclude,
|
|
177
193
|
});
|
|
178
194
|
}
|
|
@@ -182,6 +198,13 @@ Examples:
|
|
|
182
198
|
yield (0, exit_1.gracefulExit)(1);
|
|
183
199
|
return;
|
|
184
200
|
}
|
|
201
|
+
// Only announce the scope when it was implied by the cwd; an explicit
|
|
202
|
+
// --in/[path] already states what the caller scoped to.
|
|
203
|
+
if (nestedScopeRoot && !((_e = options.in) === null || _e === void 0 ? void 0 : _e.length) && !exec_path) {
|
|
204
|
+
const subName = path.relative(cwdProjectRoot, nestedScopeRoot);
|
|
205
|
+
const projName = path.basename(cwdProjectRoot);
|
|
206
|
+
console.error(`Scoped to ${subName}/ within ${projName}; pass --root ${projName} to search the whole project.`);
|
|
207
|
+
}
|
|
185
208
|
const containedExecPath = exec_path && !(options.in && options.in.length > 0)
|
|
186
209
|
? scope.pathPrefix.replace(/\/$/, "")
|
|
187
210
|
: undefined;
|
|
@@ -212,7 +235,7 @@ Examples:
|
|
|
212
235
|
: (0, project_registry_1.resolveRootOrExit)(options.root);
|
|
213
236
|
if (resolved === null)
|
|
214
237
|
return;
|
|
215
|
-
checkRoot = (
|
|
238
|
+
checkRoot = (_f = (0, project_root_1.findProjectRoot)(resolved)) !== null && _f !== void 0 ? _f : resolved;
|
|
216
239
|
}
|
|
217
240
|
const projectRoot = crossProject.active ? cwdProjectRoot : checkRoot;
|
|
218
241
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
@@ -320,13 +343,13 @@ Examples:
|
|
|
320
343
|
source: "cli",
|
|
321
344
|
tool: "search",
|
|
322
345
|
query: pattern,
|
|
323
|
-
project: (
|
|
346
|
+
project: (_g = (0, project_root_1.findProjectRoot)(root)) !== null && _g !== void 0 ? _g : root,
|
|
324
347
|
results: _searchResultCount,
|
|
325
348
|
ms: Date.now() - _searchStartMs,
|
|
326
349
|
error: _searchError,
|
|
327
350
|
});
|
|
328
351
|
}
|
|
329
|
-
catch (
|
|
352
|
+
catch (_h) { }
|
|
330
353
|
if (vectorDb) {
|
|
331
354
|
try {
|
|
332
355
|
yield vectorDb.close();
|
|
@@ -128,7 +128,7 @@ Examples:
|
|
|
128
128
|
gmax skeleton "auth logic" Search, skeletonize top matches
|
|
129
129
|
`)
|
|
130
130
|
.action((target, options, _cmd) => __awaiter(void 0, void 0, void 0, function* () {
|
|
131
|
-
var _a, _b;
|
|
131
|
+
var _a, _b, _c, _d, _e;
|
|
132
132
|
let vectorDb = null;
|
|
133
133
|
try {
|
|
134
134
|
// Initialize
|
|
@@ -166,8 +166,10 @@ Examples:
|
|
|
166
166
|
const skeletonOpts = {
|
|
167
167
|
includeSummary: !options.noSummary,
|
|
168
168
|
};
|
|
169
|
-
// Determine mode based on target
|
|
170
|
-
|
|
169
|
+
// Determine mode based on target. Prefer an existing cwd-relative
|
|
170
|
+
// match so callers inside a subdirectory (or nested subrepo) can pass
|
|
171
|
+
// paths relative to where they are.
|
|
172
|
+
const resolvedTarget = (_b = (0, path_containment_1.resolveContainedExistingPath)(projectRoot, target)) !== null && _b !== void 0 ? _b : (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
171
173
|
verifyExistingTarget: true,
|
|
172
174
|
});
|
|
173
175
|
// Directory mode is unsupported. Auto-picking files from a directory
|
|
@@ -191,7 +193,7 @@ Examples:
|
|
|
191
193
|
.map((t) => t.trim())
|
|
192
194
|
.filter(Boolean);
|
|
193
195
|
for (const t of targets) {
|
|
194
|
-
const filePath = (0, path_containment_1.resolveContainedPath)(projectRoot, t, {
|
|
196
|
+
const filePath = (_c = (0, path_containment_1.resolveContainedExistingPath)(projectRoot, t)) !== null && _c !== void 0 ? _c : (0, path_containment_1.resolveContainedPath)(projectRoot, t, {
|
|
195
197
|
verifyExistingTarget: true,
|
|
196
198
|
});
|
|
197
199
|
if (!fs.existsSync(filePath)) {
|
|
@@ -206,7 +208,7 @@ Examples:
|
|
|
206
208
|
}
|
|
207
209
|
if (isFilePath(target)) {
|
|
208
210
|
// === FILE MODE ===
|
|
209
|
-
const filePath = (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
211
|
+
const filePath = (_d = (0, path_containment_1.resolveContainedExistingPath)(projectRoot, target)) !== null && _d !== void 0 ? _d : (0, path_containment_1.resolveContainedPath)(projectRoot, target, {
|
|
210
212
|
verifyExistingTarget: true,
|
|
211
213
|
});
|
|
212
214
|
if (!fs.existsSync(filePath)) {
|
|
@@ -275,7 +277,7 @@ Examples:
|
|
|
275
277
|
const seenPaths = new Set();
|
|
276
278
|
const filePaths = [];
|
|
277
279
|
for (const result of searchResults.data) {
|
|
278
|
-
const resultPath = (
|
|
280
|
+
const resultPath = (_e = result.metadata) === null || _e === void 0 ? void 0 : _e.path;
|
|
279
281
|
if (resultPath && !seenPaths.has(resultPath)) {
|
|
280
282
|
seenPaths.add(resultPath);
|
|
281
283
|
filePaths.push(resultPath);
|
|
@@ -293,7 +295,7 @@ Examples:
|
|
|
293
295
|
verifyExistingTarget: true,
|
|
294
296
|
});
|
|
295
297
|
}
|
|
296
|
-
catch (
|
|
298
|
+
catch (_f) {
|
|
297
299
|
results.push({
|
|
298
300
|
file: filePath,
|
|
299
301
|
skeleton: `// File outside selected project: ${filePath}`,
|
|
@@ -352,7 +354,7 @@ Examples:
|
|
|
352
354
|
try {
|
|
353
355
|
yield vectorDb.close();
|
|
354
356
|
}
|
|
355
|
-
catch (
|
|
357
|
+
catch (_g) {
|
|
356
358
|
// Ignore close errors
|
|
357
359
|
}
|
|
358
360
|
}
|
|
@@ -121,12 +121,14 @@ class WatcherManager {
|
|
|
121
121
|
try {
|
|
122
122
|
const filePolicy = new file_policy_1.ProjectFilePolicy(root);
|
|
123
123
|
let processor;
|
|
124
|
+
let lastReindexAt;
|
|
124
125
|
processor = new batch_processor_1.ProjectBatchProcessor(Object.assign(Object.assign({ projectRoot: root, vectorDb,
|
|
125
126
|
metaCache }, (workerPool ? { workerPool } : {})), { filePolicy, onPolicyChange: () => {
|
|
126
127
|
void this.runCatchup(root, processor).catch((err) => {
|
|
127
128
|
console.error(`[daemon:${path.basename(root)}] Policy reconciliation failed:`, err);
|
|
128
129
|
});
|
|
129
130
|
}, onReindex: (files, ms) => __awaiter(this, void 0, void 0, function* () {
|
|
131
|
+
lastReindexAt = Date.now();
|
|
130
132
|
console.log(`[daemon:${path.basename(root)}] Reindexed ${files} file${files !== 1 ? "s" : ""} (${(ms / 1000).toFixed(1)}s)`);
|
|
131
133
|
// Update project registry so gmax status shows fresh data
|
|
132
134
|
const proj = (0, project_registry_1.getProject)(root);
|
|
@@ -140,16 +142,13 @@ class WatcherManager {
|
|
|
140
142
|
}
|
|
141
143
|
(0, project_registry_1.registerProject)(Object.assign(Object.assign({}, proj), { lastIndexed: new Date().toISOString(), chunkCount }));
|
|
142
144
|
}
|
|
143
|
-
|
|
144
|
-
(0, watcher_store_1.registerWatcher)({
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
lastReindex: Date.now(),
|
|
151
|
-
});
|
|
152
|
-
}), onActivity: () => {
|
|
145
|
+
}), onBatchSettled: () => {
|
|
146
|
+
(0, watcher_store_1.registerWatcher)(Object.assign({ pid: process.pid, projectRoot: root, startTime: Date.now(), status: this.isRootDegraded(root)
|
|
147
|
+
? "degraded"
|
|
148
|
+
: processor.progress.pendingFiles > 0
|
|
149
|
+
? "syncing"
|
|
150
|
+
: "watching", lastHeartbeat: Date.now() }, (lastReindexAt ? { lastReindex: lastReindexAt } : {})));
|
|
151
|
+
}, onActivity: () => {
|
|
153
152
|
this.deps.touchActivity();
|
|
154
153
|
// Mark as syncing while processing
|
|
155
154
|
(0, watcher_store_1.registerWatcher)({
|
|
@@ -78,6 +78,7 @@ class ProjectBatchProcessor {
|
|
|
78
78
|
this.workerPool = (_a = opts.workerPool) !== null && _a !== void 0 ? _a : (0, pool_1.getWorkerPool)();
|
|
79
79
|
this.onReindex = opts.onReindex;
|
|
80
80
|
this.onActivity = opts.onActivity;
|
|
81
|
+
this.onBatchSettled = opts.onBatchSettled;
|
|
81
82
|
this.onPolicyChange = opts.onPolicyChange;
|
|
82
83
|
this.onTerminalFailure = opts.onTerminalFailure;
|
|
83
84
|
this.onPathSuccess = opts.onPathSuccess;
|
|
@@ -190,7 +191,7 @@ class ProjectBatchProcessor {
|
|
|
190
191
|
}
|
|
191
192
|
processBatch(operationSignal) {
|
|
192
193
|
return __awaiter(this, void 0, void 0, function* () {
|
|
193
|
-
var _a, _b, _c, _d, _e;
|
|
194
|
+
var _a, _b, _c, _d, _e, _f;
|
|
194
195
|
if (this.closed || this.processing || this.pending.size === 0)
|
|
195
196
|
return;
|
|
196
197
|
const diskPressure = this.vectorDb.checkDiskPressure();
|
|
@@ -574,6 +575,8 @@ class ProjectBatchProcessor {
|
|
|
574
575
|
this.schedulePendingBatch();
|
|
575
576
|
}
|
|
576
577
|
}
|
|
578
|
+
if (!this.closed)
|
|
579
|
+
(_f = this.onBatchSettled) === null || _f === void 0 ? void 0 : _f.call(this);
|
|
577
580
|
}
|
|
578
581
|
});
|
|
579
582
|
}
|
|
@@ -195,6 +195,7 @@ function startWatcher(opts) {
|
|
|
195
195
|
processor = new batch_processor_1.ProjectBatchProcessor(Object.assign(Object.assign({}, opts), { filePolicy, onPolicyChange: reconcile, onReindex: (files, durationMs) => {
|
|
196
196
|
var _a;
|
|
197
197
|
(_a = opts.onReindex) === null || _a === void 0 ? void 0 : _a.call(opts, files, durationMs);
|
|
198
|
+
}, onBatchSettled: () => {
|
|
198
199
|
reportHealthyIfSettled();
|
|
199
200
|
}, onTerminalFailure: (absPath) => {
|
|
200
201
|
var _a;
|
|
@@ -96,6 +96,10 @@ class VectorDB {
|
|
|
96
96
|
this.maintenancePromise = null;
|
|
97
97
|
this.maintenanceTimer = null;
|
|
98
98
|
this.maintenanceRunner = (fn) => fn();
|
|
99
|
+
/** Latched when a from-scratch FTS rebuild failed to stop an optimize
|
|
100
|
+
* panic. Stops the (expensive) rebuild from re-running every maintenance
|
|
101
|
+
* tick for a panic it cannot fix; cleared when an optimize succeeds. */
|
|
102
|
+
this.ftsPanicRecoveryExhausted = false;
|
|
99
103
|
this.lastCorruptionLogMs = 0;
|
|
100
104
|
this.diskPressure = "ok";
|
|
101
105
|
this.lastDiskCheckMs = 0;
|
|
@@ -781,6 +785,7 @@ class VectorDB {
|
|
|
781
785
|
try {
|
|
782
786
|
const table = yield this.ensureTableUnsafe();
|
|
783
787
|
const cutoff = new Date(Date.now() - retentionMs);
|
|
788
|
+
let rebuiltFts = false;
|
|
784
789
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
785
790
|
yield this.drainWrites();
|
|
786
791
|
try {
|
|
@@ -790,6 +795,7 @@ class VectorDB {
|
|
|
790
795
|
deleteUnverified: true,
|
|
791
796
|
});
|
|
792
797
|
done();
|
|
798
|
+
this.ftsPanicRecoveryExhausted = false;
|
|
793
799
|
const { compaction, prune } = stats;
|
|
794
800
|
if (compaction.fragmentsRemoved > 0 ||
|
|
795
801
|
prune.oldVersionsRemoved > 0 ||
|
|
@@ -822,6 +828,34 @@ class VectorDB {
|
|
|
822
828
|
yield new Promise((r) => setTimeout(r, delay));
|
|
823
829
|
continue;
|
|
824
830
|
}
|
|
831
|
+
// A Rust panic here is the lance-index inverted (FTS) merge bug
|
|
832
|
+
// (out-of-bounds in scalar/inverted/builder.rs): the incremental
|
|
833
|
+
// merge trips on inconsistent index state, and because optimize()
|
|
834
|
+
// is all-or-nothing it blocks compaction and pruning entirely.
|
|
835
|
+
// Rebuild the FTS index from scratch once and retry. Must use the
|
|
836
|
+
// Unsafe variant: the public createFTSIndex waits on
|
|
837
|
+
// compactingPromise, which we hold.
|
|
838
|
+
if (msg.includes("Panic")) {
|
|
839
|
+
if (attempt < retries &&
|
|
840
|
+
!rebuiltFts &&
|
|
841
|
+
!this.ftsPanicRecoveryExhausted) {
|
|
842
|
+
rebuiltFts = true;
|
|
843
|
+
(0, logger_1.log)("vectordb", "Optimize panicked (likely corrupt FTS merge) — rebuilding FTS index from scratch and retrying");
|
|
844
|
+
try {
|
|
845
|
+
yield this.createFTSIndexUnsafe(true);
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
catch (rebuildErr) {
|
|
849
|
+
this.ftsPanicRecoveryExhausted = true;
|
|
850
|
+
(0, logger_1.log)("vectordb", `FTS rebuild failed: ${rebuildErr}`);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (rebuiltFts) {
|
|
855
|
+
this.ftsPanicRecoveryExhausted = true;
|
|
856
|
+
(0, logger_1.log)("vectordb", "Optimize still panicking after FTS rebuild — disabling auto-rebuild until an optimize succeeds");
|
|
857
|
+
}
|
|
858
|
+
}
|
|
825
859
|
(0, logger_1.log)("vectordb", `Optimize failed: ${msg}`);
|
|
826
860
|
return;
|
|
827
861
|
}
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.PathContainmentError = void 0;
|
|
37
37
|
exports.isPathWithin = isPathWithin;
|
|
38
38
|
exports.resolveContainedPath = resolveContainedPath;
|
|
39
|
+
exports.resolveContainedExistingPath = resolveContainedExistingPath;
|
|
39
40
|
exports.resolveContainedFile = resolveContainedFile;
|
|
40
41
|
const fs = __importStar(require("node:fs"));
|
|
41
42
|
const path = __importStar(require("node:path"));
|
|
@@ -92,6 +93,43 @@ function resolveContainedPath(root, input, options = {}) {
|
|
|
92
93
|
}
|
|
93
94
|
return candidate;
|
|
94
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve a target that may be cwd-relative or root-relative, preferring an
|
|
98
|
+
* existing cwd-relative match — what a caller sitting in a subdirectory (or
|
|
99
|
+
* a nested subrepo of an umbrella project) means by the path. A candidate
|
|
100
|
+
* that exists but lies outside the root is skipped so the root-relative
|
|
101
|
+
* interpretation can still win. Returns null when no contained candidate
|
|
102
|
+
* exists; with onOutside: "throw", an existing-but-outside match raises
|
|
103
|
+
* PathContainmentError instead (for callers that must loudly reject it).
|
|
104
|
+
*/
|
|
105
|
+
function resolveContainedExistingPath(root, target, options = {}) {
|
|
106
|
+
var _a;
|
|
107
|
+
const cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
|
|
108
|
+
const candidates = [
|
|
109
|
+
path.isAbsolute(target) ? path.resolve(target) : path.resolve(cwd, target),
|
|
110
|
+
path.resolve(root, target),
|
|
111
|
+
];
|
|
112
|
+
let outside = null;
|
|
113
|
+
for (const candidate of candidates) {
|
|
114
|
+
if (!fs.existsSync(candidate))
|
|
115
|
+
continue;
|
|
116
|
+
try {
|
|
117
|
+
return resolveContainedPath(root, candidate, {
|
|
118
|
+
verifyExistingTarget: true,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
if (err instanceof PathContainmentError) {
|
|
123
|
+
outside !== null && outside !== void 0 ? outside : (outside = err);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (options.onOutside === "throw" && outside)
|
|
130
|
+
throw outside;
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
95
133
|
/** Resolve an existing regular file and return its canonical, contained path. */
|
|
96
134
|
function resolveContainedFile(root, input) {
|
|
97
135
|
const candidate = resolveContainedPath(root, input);
|
|
@@ -33,30 +33,61 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.findGitRoot = findGitRoot;
|
|
36
37
|
exports.findProjectRoot = findProjectRoot;
|
|
37
38
|
exports.ensureProjectPaths = ensureProjectPaths;
|
|
38
39
|
const fs = __importStar(require("node:fs"));
|
|
39
40
|
const path = __importStar(require("node:path"));
|
|
40
41
|
const config_1 = require("../../config");
|
|
42
|
+
const path_containment_1 = require("./path-containment");
|
|
43
|
+
const project_registry_1 = require("./project-registry");
|
|
41
44
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* Falls back to the directory itself if no .git found.
|
|
45
|
+
* Walk up from a directory to the nearest ancestor containing .git.
|
|
46
|
+
* Returns null when no .git exists anywhere up the tree.
|
|
45
47
|
*/
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
// Walk up to find .git
|
|
49
|
-
let dir = start;
|
|
48
|
+
function findGitRoot(startDir = process.cwd()) {
|
|
49
|
+
let dir = path.resolve(startDir);
|
|
50
50
|
while (true) {
|
|
51
51
|
if (fs.existsSync(path.join(dir, ".git")))
|
|
52
52
|
return dir;
|
|
53
53
|
const parent = path.dirname(dir);
|
|
54
54
|
if (parent === dir)
|
|
55
|
-
|
|
55
|
+
return null; // reached filesystem root
|
|
56
56
|
dir = parent;
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Find the project root for a given directory.
|
|
61
|
+
*
|
|
62
|
+
* The nearest .git is only a candidate boundary: a registered project may be
|
|
63
|
+
* an umbrella directory with no .git of its own whose children are separate
|
|
64
|
+
* git repos (so the .git walk lands on an unregistered subrepo), or the
|
|
65
|
+
* nearest .git may sit above the registered root (e.g. a git-tracked home
|
|
66
|
+
* dir). Resolution order:
|
|
67
|
+
* 1. the nearest .git root, when it is itself a registered project
|
|
68
|
+
* 2. the deepest registered project containing startDir
|
|
69
|
+
* 3. the nearest .git root, else startDir (unregistered fallback)
|
|
70
|
+
*/
|
|
71
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
72
|
+
const start = path.resolve(startDir);
|
|
73
|
+
const gitRoot = findGitRoot(start);
|
|
74
|
+
let registered = [];
|
|
75
|
+
try {
|
|
76
|
+
registered = (0, project_registry_1.listProjects)();
|
|
77
|
+
}
|
|
78
|
+
catch (_a) {
|
|
79
|
+
// A corrupt registry shouldn't break root detection; commands that need
|
|
80
|
+
// the registry surface the read error themselves.
|
|
81
|
+
}
|
|
82
|
+
const roots = registered.map((p) => path.resolve(p.root));
|
|
83
|
+
if (gitRoot && roots.includes(gitRoot))
|
|
84
|
+
return gitRoot;
|
|
85
|
+
const ancestor = roots
|
|
86
|
+
.filter((root) => (0, path_containment_1.isPathWithin)(root, start))
|
|
87
|
+
.sort((a, b) => b.length - a.length)[0];
|
|
88
|
+
if (ancestor)
|
|
89
|
+
return ancestor;
|
|
90
|
+
return gitRoot !== null && gitRoot !== void 0 ? gitRoot : start;
|
|
60
91
|
}
|
|
61
92
|
/**
|
|
62
93
|
* Returns centralized paths for storage.
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.resolveScope = resolveScope;
|
|
37
37
|
exports.buildScopeWhere = buildScopeWhere;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
38
39
|
const path = __importStar(require("node:path"));
|
|
39
40
|
const filter_builder_1 = require("./filter-builder");
|
|
40
41
|
const path_containment_1 = require("./path-containment");
|
|
@@ -49,19 +50,28 @@ function toArray(value) {
|
|
|
49
50
|
.map((v) => v.trim())
|
|
50
51
|
.filter(Boolean);
|
|
51
52
|
}
|
|
52
|
-
function joinSubpath(projectRoot, sub) {
|
|
53
|
-
|
|
53
|
+
function joinSubpath(projectRoot, base, sub) {
|
|
54
|
+
let candidate = path.isAbsolute(sub) ? sub : path.resolve(base, sub);
|
|
55
|
+
if (!path.isAbsolute(sub) &&
|
|
56
|
+
path.resolve(base) !== path.resolve(projectRoot) &&
|
|
57
|
+
!fs.existsSync(candidate) &&
|
|
58
|
+
fs.existsSync(path.resolve(projectRoot, sub))) {
|
|
59
|
+
candidate = path.resolve(projectRoot, sub);
|
|
60
|
+
}
|
|
61
|
+
const resolved = (0, path_containment_1.resolveContainedPath)(projectRoot, candidate);
|
|
54
62
|
return resolved.endsWith(path.sep) ? resolved : `${resolved}${path.sep}`;
|
|
55
63
|
}
|
|
56
64
|
function resolveScope(opts) {
|
|
65
|
+
var _a;
|
|
57
66
|
const { projectRoot } = opts;
|
|
67
|
+
const base = (_a = opts.base) !== null && _a !== void 0 ? _a : projectRoot;
|
|
58
68
|
const ins = toArray(opts.in);
|
|
59
69
|
const excludes = toArray(opts.exclude);
|
|
60
70
|
const projectPrefix = projectRoot.endsWith("/")
|
|
61
71
|
? projectRoot
|
|
62
72
|
: `${projectRoot}/`;
|
|
63
|
-
const inPrefixesAll = ins.map((v) => joinSubpath(projectRoot, v));
|
|
64
|
-
const excludePrefixes = excludes.map((v) => joinSubpath(projectRoot, v));
|
|
73
|
+
const inPrefixesAll = ins.map((v) => joinSubpath(projectRoot, base, v));
|
|
74
|
+
const excludePrefixes = excludes.map((v) => joinSubpath(projectRoot, base, v));
|
|
65
75
|
// Collapse a single --in into pathPrefix to keep WHERE clauses simple.
|
|
66
76
|
if (inPrefixesAll.length === 1) {
|
|
67
77
|
return {
|
package/package.json
CHANGED