grepmax 0.26.26 → 0.26.28
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/README.md +40 -0
- package/dist/commands/audit.js +28 -260
- package/dist/commands/context.js +42 -26
- package/dist/commands/dead.js +37 -35
- package/dist/commands/doctor.js +280 -10
- package/dist/commands/extract.js +79 -49
- package/dist/commands/impact.js +74 -20
- package/dist/commands/log.js +25 -25
- package/dist/commands/mcp.js +622 -474
- package/dist/commands/peek.js +49 -47
- package/dist/commands/project.js +29 -107
- package/dist/commands/related.js +43 -52
- package/dist/commands/search-run.js +23 -8
- package/dist/commands/search.js +7 -2
- package/dist/commands/similar.js +50 -76
- package/dist/commands/skeleton.js +137 -83
- package/dist/commands/status.js +90 -25
- package/dist/commands/surprises.js +73 -16
- package/dist/commands/symbols.js +58 -77
- package/dist/commands/test-find.js +51 -17
- package/dist/commands/trace.js +26 -11
- package/dist/lib/daemon/daemon.js +26 -0
- package/dist/lib/daemon/graph-handler.js +853 -0
- package/dist/lib/daemon/ipc-handler.js +19 -2
- package/dist/lib/daemon/read-verbs.js +105 -0
- package/dist/lib/daemon/rows-handler.js +665 -0
- package/dist/lib/daemon/vector-handler.js +199 -0
- package/dist/lib/store/vector-db.js +1 -1
- package/dist/lib/utils/daemon-client.js +11 -0
- package/dist/lib/utils/store-access.js +381 -0
- package/dist/lib/utils/tests-footer.js +19 -2
- package/dist/lib/utils/watcher-launcher.js +10 -0
- package/package.json +2 -2
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/scripts/postinstall.js +0 -69
package/dist/commands/doctor.js
CHANGED
|
@@ -43,6 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.doctor = void 0;
|
|
46
|
+
exports.checkClaudeSandbox = checkClaudeSandbox;
|
|
46
47
|
const fs = __importStar(require("node:fs"));
|
|
47
48
|
const os = __importStar(require("node:os"));
|
|
48
49
|
const path = __importStar(require("node:path"));
|
|
@@ -57,6 +58,7 @@ const mlx_hf_cache_1 = require("../lib/utils/mlx-hf-cache");
|
|
|
57
58
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
58
59
|
const project_root_1 = require("../lib/utils/project-root");
|
|
59
60
|
const root_availability_1 = require("../lib/utils/root-availability");
|
|
61
|
+
const store_access_1 = require("../lib/utils/store-access");
|
|
60
62
|
function formatSize(bytes) {
|
|
61
63
|
if (bytes < 1024)
|
|
62
64
|
return `${bytes} B`;
|
|
@@ -84,12 +86,263 @@ function getDirectorySize(dirPath) {
|
|
|
84
86
|
catch (_a) { }
|
|
85
87
|
return totalSize;
|
|
86
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* The settings files Claude Code merges, lowest precedence first. Project
|
|
91
|
+
* settings override user settings, and `.local.json` overrides its sibling.
|
|
92
|
+
*/
|
|
93
|
+
function claudeSettingsFiles(home, projectRoot) {
|
|
94
|
+
const files = [
|
|
95
|
+
path.join(home, ".claude", "settings.json"),
|
|
96
|
+
path.join(home, ".claude", "settings.local.json"),
|
|
97
|
+
];
|
|
98
|
+
if (projectRoot) {
|
|
99
|
+
files.push(path.join(projectRoot, ".claude", "settings.json"));
|
|
100
|
+
files.push(path.join(projectRoot, ".claude", "settings.local.json"));
|
|
101
|
+
}
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
function asObject(value) {
|
|
105
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
106
|
+
? value
|
|
107
|
+
: undefined;
|
|
108
|
+
}
|
|
109
|
+
function readSandboxScopes(files) {
|
|
110
|
+
var _a;
|
|
111
|
+
const scopes = [];
|
|
112
|
+
for (const file of files) {
|
|
113
|
+
let raw;
|
|
114
|
+
try {
|
|
115
|
+
raw = fs.readFileSync(file, "utf8");
|
|
116
|
+
}
|
|
117
|
+
catch (_b) {
|
|
118
|
+
continue; // absent (or unreadable) — that scope simply contributes nothing
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const sandbox = asObject((_a = asObject(JSON.parse(raw))) === null || _a === void 0 ? void 0 : _a.sandbox);
|
|
122
|
+
scopes.push({ path: file, sandbox });
|
|
123
|
+
}
|
|
124
|
+
catch (_c) {
|
|
125
|
+
scopes.push({ path: file, unreadable: true });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return scopes;
|
|
129
|
+
}
|
|
130
|
+
/** Matches a leading `$HOME` or `${HOME}`, whole segment only. */
|
|
131
|
+
const HOME_VAR = /^\$\{?HOME\}?(?=$|\/)/;
|
|
132
|
+
/** Expand `~`/`$HOME` and resolve relative entries against the project dir. */
|
|
133
|
+
function expandSettingsPath(entry, home, base) {
|
|
134
|
+
let p = entry.trim();
|
|
135
|
+
if (p === "~")
|
|
136
|
+
p = home;
|
|
137
|
+
else if (p.startsWith("~/"))
|
|
138
|
+
p = path.join(home, p.slice(2));
|
|
139
|
+
// Function replacement: a `$` inside the home path must not be read as a
|
|
140
|
+
// capture reference.
|
|
141
|
+
else
|
|
142
|
+
p = p.replace(HOME_VAR, () => home);
|
|
143
|
+
return path.isAbsolute(p) ? path.normalize(p) : path.resolve(base, p);
|
|
144
|
+
}
|
|
145
|
+
function globToRegExp(pattern) {
|
|
146
|
+
let out = "";
|
|
147
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
148
|
+
const ch = pattern[i];
|
|
149
|
+
if (ch === "*") {
|
|
150
|
+
if (pattern[i + 1] === "*") {
|
|
151
|
+
out += ".*";
|
|
152
|
+
i++;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
out += "[^/]*";
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
out += ch.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
160
|
+
}
|
|
161
|
+
return new RegExp(`^${out}$`);
|
|
162
|
+
}
|
|
163
|
+
/** `~/.gmax/**` and `~/.gmax/*` both stand for the directory itself. */
|
|
164
|
+
function stripGlobSuffix(p) {
|
|
165
|
+
return p.replace(/\/(?:\*\*\/\*|\*\*|\*)$/, "");
|
|
166
|
+
}
|
|
167
|
+
/** An allow entry covers a target when it is the target or contains it. */
|
|
168
|
+
function coversPath(entry, target) {
|
|
169
|
+
const normalized = entry.replace(/\/+$/, "");
|
|
170
|
+
return target === normalized || target.startsWith(`${normalized}/`);
|
|
171
|
+
}
|
|
172
|
+
function entryAllows(entry, target, home, base) {
|
|
173
|
+
if (typeof entry !== "string" || entry.trim() === "")
|
|
174
|
+
return false;
|
|
175
|
+
const expanded = expandSettingsPath(entry, home, base);
|
|
176
|
+
if (expanded.includes("*")) {
|
|
177
|
+
if (globToRegExp(expanded).test(target))
|
|
178
|
+
return true;
|
|
179
|
+
return coversPath(stripGlobSuffix(expanded), target);
|
|
180
|
+
}
|
|
181
|
+
return coversPath(expanded, target);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Collapse the scopes into what Claude Code would actually apply: the
|
|
185
|
+
* highest-precedence definition of each leaf key wins. Union-ing the arrays
|
|
186
|
+
* instead would report a socket as allowed when a project override had in fact
|
|
187
|
+
* replaced the user-level list — a false OK, which is the worse failure here.
|
|
188
|
+
*/
|
|
189
|
+
function effectiveSandbox(scopes) {
|
|
190
|
+
const eff = {
|
|
191
|
+
present: false,
|
|
192
|
+
enabled: true,
|
|
193
|
+
allowAllUnixSockets: false,
|
|
194
|
+
allowUnixSockets: [],
|
|
195
|
+
allowWrite: [],
|
|
196
|
+
};
|
|
197
|
+
for (const scope of scopes) {
|
|
198
|
+
const sandbox = scope.sandbox;
|
|
199
|
+
if (!sandbox)
|
|
200
|
+
continue;
|
|
201
|
+
eff.present = true;
|
|
202
|
+
eff.source = scope.path;
|
|
203
|
+
// A `sandbox` block with no `enabled` key is on: the key only exists to
|
|
204
|
+
// turn it off, and the default flipped to on for macOS Bash.
|
|
205
|
+
if (typeof sandbox.enabled === "boolean")
|
|
206
|
+
eff.enabled = sandbox.enabled;
|
|
207
|
+
const network = asObject(sandbox.network);
|
|
208
|
+
if (network) {
|
|
209
|
+
if (typeof network.allowAllUnixSockets === "boolean")
|
|
210
|
+
eff.allowAllUnixSockets = network.allowAllUnixSockets;
|
|
211
|
+
if (Array.isArray(network.allowUnixSockets))
|
|
212
|
+
eff.allowUnixSockets = network.allowUnixSockets;
|
|
213
|
+
}
|
|
214
|
+
const filesystem = asObject(sandbox.filesystem);
|
|
215
|
+
if (filesystem && Array.isArray(filesystem.allowWrite))
|
|
216
|
+
eff.allowWrite = filesystem.allowWrite;
|
|
217
|
+
}
|
|
218
|
+
return eff;
|
|
219
|
+
}
|
|
220
|
+
function tildify(p, home) {
|
|
221
|
+
return p === home || p.startsWith(`${home}/`)
|
|
222
|
+
? `~${p.slice(home.length)}`
|
|
223
|
+
: p;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Report whether a sandboxed Claude Code shell can reach the gmax store.
|
|
227
|
+
*
|
|
228
|
+
* Returns null when the machine has no Claude Code settings at all — gmax has
|
|
229
|
+
* nothing to say about a sandbox that is not configured anywhere.
|
|
230
|
+
*/
|
|
231
|
+
function checkClaudeSandbox(opts) {
|
|
232
|
+
var _a, _b, _c, _d, _e, _f;
|
|
233
|
+
const home = opts.home;
|
|
234
|
+
const socketPath = (_a = opts.socketPath) !== null && _a !== void 0 ? _a : config_1.PATHS.daemonSocket;
|
|
235
|
+
const storeDir = (_b = opts.storeDir) !== null && _b !== void 0 ? _b : config_1.PATHS.globalRoot;
|
|
236
|
+
const base = (_c = opts.projectRoot) !== null && _c !== void 0 ? _c : home;
|
|
237
|
+
const platform = (_d = opts.platform) !== null && _d !== void 0 ? _d : process.platform;
|
|
238
|
+
const scopes = readSandboxScopes(claudeSettingsFiles(home, opts.projectRoot));
|
|
239
|
+
if (scopes.length === 0)
|
|
240
|
+
return null;
|
|
241
|
+
const unreadable = scopes.filter((s) => s.unreadable).map((s) => s.path);
|
|
242
|
+
const eff = effectiveSandbox(scopes);
|
|
243
|
+
const parseNote = unreadable.length > 0
|
|
244
|
+
? [
|
|
245
|
+
`could not parse ${unreadable.map((p) => tildify(p, home)).join(", ")} — checked the remaining scopes only`,
|
|
246
|
+
]
|
|
247
|
+
: [];
|
|
248
|
+
if (!eff.present || !eff.enabled) {
|
|
249
|
+
// Nothing under ~/.gmax is at risk, so there is nothing to prescribe. Say
|
|
250
|
+
// so rather than staying silent: "not enabled" is the answer to the
|
|
251
|
+
// question anyone reaching for this check is actually asking.
|
|
252
|
+
if (!eff.present && unreadable.length > 0) {
|
|
253
|
+
return {
|
|
254
|
+
symbol: "INFO",
|
|
255
|
+
message: `Claude Code sandbox: ${parseNote[0]}`,
|
|
256
|
+
details: [],
|
|
257
|
+
agentRow: ["claude_sandbox", "enabled=unknown"].join("\t"),
|
|
258
|
+
enabled: false,
|
|
259
|
+
socketAllowed: true,
|
|
260
|
+
writeAllowed: true,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
symbol: "ok",
|
|
265
|
+
message: eff.present
|
|
266
|
+
? "Claude Code sandbox: disabled in settings"
|
|
267
|
+
: "Claude Code sandbox: not enabled",
|
|
268
|
+
details: parseNote,
|
|
269
|
+
agentRow: ["claude_sandbox", "enabled=false"].join("\t"),
|
|
270
|
+
enabled: false,
|
|
271
|
+
socketAllowed: true,
|
|
272
|
+
writeAllowed: true,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const socketAllowed = eff.allowAllUnixSockets ||
|
|
276
|
+
eff.allowUnixSockets.some((e) => entryAllows(e, socketPath, home, base));
|
|
277
|
+
const writeAllowed = eff.allowWrite.some((e) => entryAllows(e, storeDir, home, base));
|
|
278
|
+
const agentRow = [
|
|
279
|
+
"claude_sandbox",
|
|
280
|
+
"enabled=true",
|
|
281
|
+
`socket=${socketAllowed ? (eff.allowAllUnixSockets ? "all" : "allowed") : "blocked"}`,
|
|
282
|
+
`write=${writeAllowed ? "allowed" : "blocked"}`,
|
|
283
|
+
`source=${(_e = eff.source) !== null && _e !== void 0 ? _e : "none"}`,
|
|
284
|
+
].join("\t");
|
|
285
|
+
if (socketAllowed && writeAllowed) {
|
|
286
|
+
return {
|
|
287
|
+
symbol: "ok",
|
|
288
|
+
message: `Claude Code sandbox: enabled; daemon socket ${eff.allowAllUnixSockets ? "reachable (all Unix sockets allowed)" : "allowed"} and ${tildify(storeDir, home)} writable`,
|
|
289
|
+
details: parseNote,
|
|
290
|
+
agentRow,
|
|
291
|
+
enabled: true,
|
|
292
|
+
socketAllowed,
|
|
293
|
+
writeAllowed,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const blocked = [
|
|
297
|
+
socketAllowed ? null : "the daemon socket",
|
|
298
|
+
writeAllowed ? null : `writes to ${tildify(storeDir, home)}`,
|
|
299
|
+
].filter(Boolean);
|
|
300
|
+
const details = [...parseNote];
|
|
301
|
+
if (!socketAllowed) {
|
|
302
|
+
details.push(store_access_1.SOCKET_DENIED_MESSAGE);
|
|
303
|
+
if (platform === "linux") {
|
|
304
|
+
details.push('on Linux the per-socket list does not exist — use "allowAllUnixSockets": true');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (!writeAllowed) {
|
|
308
|
+
details.push(store_access_1.LEASE_DENIED_MESSAGE);
|
|
309
|
+
details.push("the write allowance only matters with no daemon running; with one up every read command goes over the socket");
|
|
310
|
+
}
|
|
311
|
+
// The snippet is the point of the WARN: the fix is one or two keys in a file
|
|
312
|
+
// gmax must not touch, so print exactly what to paste, carrying only the keys
|
|
313
|
+
// that are actually missing.
|
|
314
|
+
const snippetKeys = [];
|
|
315
|
+
if (!socketAllowed) {
|
|
316
|
+
snippetKeys.push(platform === "linux"
|
|
317
|
+
? ' "network": { "allowAllUnixSockets": true }'
|
|
318
|
+
: ' "network": { "allowUnixSockets": ["~/.gmax/daemon.sock"] }');
|
|
319
|
+
}
|
|
320
|
+
if (!writeAllowed) {
|
|
321
|
+
snippetKeys.push(' "filesystem": { "allowWrite": ["~/.gmax"] }');
|
|
322
|
+
}
|
|
323
|
+
const target = (_f = eff.source) !== null && _f !== void 0 ? _f : path.join(home, ".claude", "settings.json");
|
|
324
|
+
details.push(`add to ${tildify(target, home)} (gmax doctor --fix never edits Claude Code settings):`);
|
|
325
|
+
details.push('"sandbox": {');
|
|
326
|
+
snippetKeys.forEach((line, i) => {
|
|
327
|
+
details.push(i < snippetKeys.length - 1 ? `${line},` : line);
|
|
328
|
+
});
|
|
329
|
+
details.push("}");
|
|
330
|
+
return {
|
|
331
|
+
symbol: "WARN",
|
|
332
|
+
message: `Claude Code sandbox: enabled, but ${blocked.join(" and ")} ${blocked.length > 1 ? "are" : "is"} blocked`,
|
|
333
|
+
details,
|
|
334
|
+
agentRow,
|
|
335
|
+
enabled: true,
|
|
336
|
+
socketAllowed,
|
|
337
|
+
writeAllowed,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
87
340
|
exports.doctor = new commander_1.Command("doctor")
|
|
88
341
|
.description("Check installation health, models, and index status")
|
|
89
342
|
.option("--fix", "Auto-fix detected issues (compact, prune, remove stale locks)", false)
|
|
90
343
|
.option("--agent", "Compact output for AI agents", false)
|
|
91
344
|
.action((opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
345
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
93
346
|
if (!opts.agent)
|
|
94
347
|
console.log("gmax Doctor\n");
|
|
95
348
|
const root = config_1.PATHS.globalRoot;
|
|
@@ -194,6 +447,23 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
194
447
|
const summarizerStatus = (0, doctor_status_1.summarizerServerStatus)(summarizerUp);
|
|
195
448
|
console.log(`${summarizerStatus.symbol} ${summarizerStatus.message}`);
|
|
196
449
|
}
|
|
450
|
+
// --- Claude Code sandbox ---
|
|
451
|
+
// Runs in both modes: an agent reading `--agent` output is exactly the
|
|
452
|
+
// caller most likely to be inside the sandbox this check is about.
|
|
453
|
+
const sandbox = checkClaudeSandbox({
|
|
454
|
+
home: os.homedir(),
|
|
455
|
+
projectRoot: (_f = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _f !== void 0 ? _f : process.cwd(),
|
|
456
|
+
});
|
|
457
|
+
if (sandbox) {
|
|
458
|
+
if (opts.agent) {
|
|
459
|
+
console.log(sandbox.agentRow);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
console.log(`${sandbox.symbol} ${sandbox.message}`);
|
|
463
|
+
for (const line of sandbox.details)
|
|
464
|
+
console.log(` ${line}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
197
467
|
// --- Index Health ---
|
|
198
468
|
let needsOptimize = false;
|
|
199
469
|
let staleLock = false;
|
|
@@ -301,7 +571,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
301
571
|
? "LOW"
|
|
302
572
|
: "ok";
|
|
303
573
|
}
|
|
304
|
-
catch (
|
|
574
|
+
catch (_q) { }
|
|
305
575
|
const staleChunkerProjects = projects.filter((p) => {
|
|
306
576
|
var _a;
|
|
307
577
|
return p.status === "indexed" &&
|
|
@@ -333,9 +603,9 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
333
603
|
`schema_dim=${physicalDim !== null && physicalDim !== void 0 ? physicalDim : "none"}`,
|
|
334
604
|
`schema_dim_ok=${schemaGap ? "false" : "true"}`,
|
|
335
605
|
`ann_index=${vectorIndex ? "present" : "absent"}`,
|
|
336
|
-
`ann_indexed=${(
|
|
337
|
-
`ann_unindexed=${(
|
|
338
|
-
`ann_metric=${(
|
|
606
|
+
`ann_indexed=${(_g = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numIndexedRows) !== null && _g !== void 0 ? _g : 0}`,
|
|
607
|
+
`ann_unindexed=${(_h = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numUnindexedRows) !== null && _h !== void 0 ? _h : 0}`,
|
|
608
|
+
`ann_metric=${(_j = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.distanceType) !== null && _j !== void 0 ? _j : "none"}`,
|
|
339
609
|
];
|
|
340
610
|
console.log(fields.join("\t"));
|
|
341
611
|
if (schemaGap) {
|
|
@@ -390,7 +660,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
390
660
|
console.log(`ok Schema: vector table is ${physicalDim}d`);
|
|
391
661
|
}
|
|
392
662
|
if (vectorIndexStats) {
|
|
393
|
-
console.log(`ok ANN: ${vectorIndexStats.numIndexedRows.toLocaleString()} indexed, ${vectorIndexStats.numUnindexedRows.toLocaleString()} unindexed, metric=${(
|
|
663
|
+
console.log(`ok ANN: ${vectorIndexStats.numIndexedRows.toLocaleString()} indexed, ${vectorIndexStats.numUnindexedRows.toLocaleString()} unindexed, metric=${(_k = vectorIndexStats.distanceType) !== null && _k !== void 0 ? _k : "unknown"}`);
|
|
394
664
|
}
|
|
395
665
|
else {
|
|
396
666
|
console.log("INFO ANN: vector index not built (set GMAX_ANN=1)");
|
|
@@ -519,7 +789,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
519
789
|
yield mc.close();
|
|
520
790
|
}
|
|
521
791
|
}
|
|
522
|
-
catch (
|
|
792
|
+
catch (_r) { }
|
|
523
793
|
}
|
|
524
794
|
}
|
|
525
795
|
// --fix auto-remediation
|
|
@@ -558,7 +828,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
558
828
|
// Only fall back when the daemon could not take it. A daemon that
|
|
559
829
|
// answered with a real failure should surface, not be retried here
|
|
560
830
|
// as a competing writer.
|
|
561
|
-
const reason = String((
|
|
831
|
+
const reason = String((_l = resp.error) !== null && _l !== void 0 ? _l : "");
|
|
562
832
|
// Fall back ONLY when nothing is listening. A daemon that is merely
|
|
563
833
|
// too old ("unknown command") or busy ("timeout") is still a live
|
|
564
834
|
// writer on the store, so optimizing here would recreate the
|
|
@@ -609,7 +879,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
609
879
|
const done = yield sendStreamingCommand({ cmd: "index", root: p.root, reset: true }, () => { }).catch((e) => ({ ok: false, error: String(e) }));
|
|
610
880
|
if (done.ok &&
|
|
611
881
|
(!("degraded" in done) || done.degraded !== true)) {
|
|
612
|
-
const chunks = (
|
|
882
|
+
const chunks = (_m = done.indexed) !== null && _m !== void 0 ? _m : 0;
|
|
613
883
|
if (opts.agent) {
|
|
614
884
|
console.log(`stale_chunker_reindexed\tname=${name}\tchunks=${chunks}`);
|
|
615
885
|
}
|
|
@@ -621,7 +891,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
621
891
|
else if (!opts.agent) {
|
|
622
892
|
const degraded = "degraded" in done && done.degraded === true;
|
|
623
893
|
const reason = degraded
|
|
624
|
-
? `${(
|
|
894
|
+
? `${(_o = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _o !== void 0 ? _o : 0} scan errors, ${(_p = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _p !== void 0 ? _p : 0} file failures`
|
|
625
895
|
: done.error;
|
|
626
896
|
console.log(`FAIL ${name}: reindex failed (${reason})`);
|
|
627
897
|
}
|
package/dist/commands/extract.js
CHANGED
|
@@ -45,15 +45,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.extract = void 0;
|
|
46
46
|
const fs = __importStar(require("node:fs"));
|
|
47
47
|
const commander_1 = require("commander");
|
|
48
|
-
const
|
|
48
|
+
const graph_handler_1 = require("../lib/daemon/graph-handler");
|
|
49
|
+
const rows_handler_1 = require("../lib/daemon/rows-handler");
|
|
49
50
|
const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
50
51
|
const exit_1 = require("../lib/utils/exit");
|
|
51
|
-
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
52
52
|
const import_extractor_1 = require("../lib/utils/import-extractor");
|
|
53
53
|
const language_1 = require("../lib/utils/language");
|
|
54
54
|
const path_containment_1 = require("../lib/utils/path-containment");
|
|
55
55
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
56
56
|
const project_root_1 = require("../lib/utils/project-root");
|
|
57
|
+
const store_access_1 = require("../lib/utils/store-access");
|
|
58
|
+
const tests_footer_1 = require("../lib/utils/tests-footer");
|
|
57
59
|
const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
58
60
|
const style = {
|
|
59
61
|
bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
|
|
@@ -66,34 +68,14 @@ const ROLE_PRIORITY = {
|
|
|
66
68
|
DEFINITION: 2,
|
|
67
69
|
IMPLEMENTATION: 1,
|
|
68
70
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"end_line",
|
|
78
|
-
"role",
|
|
79
|
-
"is_exported",
|
|
80
|
-
"defined_symbols",
|
|
81
|
-
])
|
|
82
|
-
.where(whereClause)
|
|
83
|
-
.limit(10)
|
|
84
|
-
.toArray();
|
|
85
|
-
return rows.map((row) => ({
|
|
86
|
-
path: String(row.path || ""),
|
|
87
|
-
startLine: Number(row.start_line || 0),
|
|
88
|
-
endLine: Number(row.end_line || 0),
|
|
89
|
-
role: String(row.role || "IMPLEMENTATION"),
|
|
90
|
-
exported: Boolean(row.is_exported),
|
|
91
|
-
definedSymbols: Array.isArray(row.defined_symbols)
|
|
92
|
-
? row.defined_symbols
|
|
93
|
-
: [],
|
|
94
|
-
}));
|
|
95
|
-
});
|
|
96
|
-
}
|
|
71
|
+
const CHUNK_COLUMNS = [
|
|
72
|
+
"path",
|
|
73
|
+
"start_line",
|
|
74
|
+
"end_line",
|
|
75
|
+
"role",
|
|
76
|
+
"is_exported",
|
|
77
|
+
"defined_symbols",
|
|
78
|
+
];
|
|
97
79
|
function pickBestMatch(chunks, symbol) {
|
|
98
80
|
// Prefer chunks where the symbol is first in defined_symbols, then by role priority
|
|
99
81
|
return chunks.sort((a, b) => {
|
|
@@ -104,6 +86,36 @@ function pickBestMatch(chunks, symbol) {
|
|
|
104
86
|
return (ROLE_PRIORITY[b.role] || 0) - (ROLE_PRIORITY[a.role] || 0);
|
|
105
87
|
})[0];
|
|
106
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* The tests footer: `findTests`, which walks the call graph — several LanceDB
|
|
91
|
+
* queries per hop — so it belongs on the daemon's warm store for the same
|
|
92
|
+
* reason the location lookup does.
|
|
93
|
+
*
|
|
94
|
+
* It rides `graph.tests`, not a footer-specific verb. WP-C shipped `rows.tests`
|
|
95
|
+
* for this one caller before `graph.tests` existed; the two wrapped the same
|
|
96
|
+
* library call with the same arguments, so the duplicate is gone.
|
|
97
|
+
*
|
|
98
|
+
* The footer's 1.5 s budget stays a *client-side* race (`withFooterTimeout`)
|
|
99
|
+
* rather than a daemon timeout: `null` means "the footer did not finish", and
|
|
100
|
+
* that has to include a slow round trip, not just a slow query. The verb's own
|
|
101
|
+
* 60 s socket timeout is the outer bound.
|
|
102
|
+
*/
|
|
103
|
+
function fetchTests(symbol, projectRoot, lancedbDir, scope, queryRoot) {
|
|
104
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
105
|
+
return (0, tests_footer_1.withFooterTimeout)((0, graph_handler_1.callGraphVerb)("graph.tests", {
|
|
106
|
+
projectRoot,
|
|
107
|
+
scope,
|
|
108
|
+
payload: { symbols: [symbol], depth: 1 },
|
|
109
|
+
render: (resp) => { var _a; return (_a = resp.hits) !== null && _a !== void 0 ? _a : []; },
|
|
110
|
+
inProcess: () => (0, rows_handler_1.withLocalStore)(lancedbDir, (deps) => (0, graph_handler_1.runGraphTests)(deps.vectorDb, {
|
|
111
|
+
symbols: [symbol],
|
|
112
|
+
queryRoot,
|
|
113
|
+
depth: 1,
|
|
114
|
+
excludePrefixes: scope.excludePrefixes,
|
|
115
|
+
})),
|
|
116
|
+
}));
|
|
117
|
+
});
|
|
118
|
+
}
|
|
107
119
|
exports.extract = new commander_1.Command("extract")
|
|
108
120
|
.description("Extract full function/class body by symbol name")
|
|
109
121
|
.argument("<symbol>", "The symbol to extract")
|
|
@@ -115,22 +127,44 @@ exports.extract = new commander_1.Command("extract")
|
|
|
115
127
|
.option("--no-tests", "Suppress the tests footer")
|
|
116
128
|
.action((symbol, opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
129
|
var _a;
|
|
118
|
-
let vectorDb = null;
|
|
119
130
|
const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
|
|
120
131
|
if (root === null)
|
|
121
132
|
return;
|
|
122
133
|
try {
|
|
123
134
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
124
135
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
125
|
-
|
|
126
|
-
const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
136
|
+
const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
127
137
|
const scope = resolveScope({
|
|
128
138
|
projectRoot,
|
|
129
139
|
in: opts.in,
|
|
130
140
|
exclude: opts.exclude,
|
|
131
141
|
});
|
|
132
|
-
|
|
133
|
-
const
|
|
142
|
+
// Matches `queryRootFor` daemon-side, so both paths query the same root.
|
|
143
|
+
const queryRoot = opts.in && opts.in.length > 0
|
|
144
|
+
? scope.pathPrefix.replace(/\/$/, "")
|
|
145
|
+
: projectRoot;
|
|
146
|
+
// Locations come from the daemon; the body is read here, from this
|
|
147
|
+
// process's own filesystem view.
|
|
148
|
+
const [indexedRows] = yield (0, rows_handler_1.readRows)({
|
|
149
|
+
name: "extract",
|
|
150
|
+
projectRoot,
|
|
151
|
+
lancedbDir: paths.lancedbDir,
|
|
152
|
+
scope,
|
|
153
|
+
select: CHUNK_COLUMNS,
|
|
154
|
+
matches: [{ kind: "definedSymbol", symbol }],
|
|
155
|
+
limit: 10,
|
|
156
|
+
});
|
|
157
|
+
const indexedChunks = (indexedRows !== null && indexedRows !== void 0 ? indexedRows : []).map((row) => {
|
|
158
|
+
var _a;
|
|
159
|
+
return ({
|
|
160
|
+
path: String(row.path || ""),
|
|
161
|
+
startLine: Number(row.start_line || 0),
|
|
162
|
+
endLine: Number(row.end_line || 0),
|
|
163
|
+
role: String(row.role || "IMPLEMENTATION"),
|
|
164
|
+
exported: Boolean(row.is_exported),
|
|
165
|
+
definedSymbols: (_a = row.defined_symbols) !== null && _a !== void 0 ? _a : [],
|
|
166
|
+
});
|
|
167
|
+
});
|
|
134
168
|
const chunks = indexedChunks.flatMap((chunk) => {
|
|
135
169
|
try {
|
|
136
170
|
return [
|
|
@@ -186,8 +220,8 @@ exports.extract = new commander_1.Command("extract")
|
|
|
186
220
|
console.log(`${relPath}:${startLine + 1}-${endLine + 1}`);
|
|
187
221
|
console.log(body.join("\n"));
|
|
188
222
|
if (opts.tests !== false) {
|
|
189
|
-
const {
|
|
190
|
-
const tests = yield
|
|
223
|
+
const { renderTestsFooterAgent } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/tests-footer")));
|
|
224
|
+
const tests = yield fetchTests(symbol, projectRoot, paths.lancedbDir, scope, queryRoot);
|
|
191
225
|
if (tests && tests.length > 0) {
|
|
192
226
|
console.log("--- tests:");
|
|
193
227
|
for (const line of renderTestsFooterAgent(tests, projectRoot)) {
|
|
@@ -210,7 +244,7 @@ exports.extract = new commander_1.Command("extract")
|
|
|
210
244
|
const lineNumWidth = String(endLine + 1).length;
|
|
211
245
|
for (let i = 0; i < body.length; i++) {
|
|
212
246
|
const lineNum = String(startLine + 1 + i).padStart(lineNumWidth);
|
|
213
|
-
console.log(`${style.dim(`${lineNum}
|
|
247
|
+
console.log(`${style.dim(`${lineNum}│`)} ${body[i]}`);
|
|
214
248
|
}
|
|
215
249
|
}
|
|
216
250
|
// Show other definitions if symbol exists in multiple files
|
|
@@ -227,8 +261,8 @@ exports.extract = new commander_1.Command("extract")
|
|
|
227
261
|
console.log(`\n${style.dim(`Also defined in: ${otherLocs}`)}`);
|
|
228
262
|
}
|
|
229
263
|
if (!opts.agent && opts.tests !== false) {
|
|
230
|
-
const {
|
|
231
|
-
const tests = yield
|
|
264
|
+
const { renderTestsFooterHuman } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/tests-footer")));
|
|
265
|
+
const tests = yield fetchTests(symbol, projectRoot, paths.lancedbDir, scope, queryRoot);
|
|
232
266
|
if (tests && tests.length > 0) {
|
|
233
267
|
for (const line of renderTestsFooterHuman(tests, projectRoot)) {
|
|
234
268
|
console.log(line);
|
|
@@ -237,17 +271,13 @@ exports.extract = new commander_1.Command("extract")
|
|
|
237
271
|
}
|
|
238
272
|
}
|
|
239
273
|
catch (error) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
274
|
+
if (!(0, store_access_1.reportStoreAccessRefusal)(error)) {
|
|
275
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
276
|
+
console.error("Extract failed:", message);
|
|
277
|
+
process.exitCode = 1;
|
|
278
|
+
}
|
|
243
279
|
}
|
|
244
280
|
finally {
|
|
245
|
-
if (vectorDb) {
|
|
246
|
-
try {
|
|
247
|
-
yield vectorDb.close();
|
|
248
|
-
}
|
|
249
|
-
catch (_b) { }
|
|
250
|
-
}
|
|
251
281
|
yield (0, exit_1.gracefulExit)();
|
|
252
282
|
}
|
|
253
283
|
}));
|