grepmax 0.26.27 → 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.
@@ -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 (_p) { }
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=${(_f = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numIndexedRows) !== null && _f !== void 0 ? _f : 0}`,
337
- `ann_unindexed=${(_g = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numUnindexedRows) !== null && _g !== void 0 ? _g : 0}`,
338
- `ann_metric=${(_h = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.distanceType) !== null && _h !== void 0 ? _h : "none"}`,
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=${(_j = vectorIndexStats.distanceType) !== null && _j !== void 0 ? _j : "unknown"}`);
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 (_q) { }
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((_k = resp.error) !== null && _k !== void 0 ? _k : "");
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 = (_l = done.indexed) !== null && _l !== void 0 ? _l : 0;
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
- ? `${(_m = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _m !== void 0 ? _m : 0} scan errors, ${(_o = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _o !== void 0 ? _o : 0} file failures`
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
  }
@@ -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 vector_db_1 = require("../lib/store/vector-db");
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
- function findSymbolChunks(db, whereClause) {
70
- return __awaiter(this, void 0, void 0, function* () {
71
- const table = yield db.ensureTable();
72
- const rows = yield table
73
- .query()
74
- .select([
75
- "path",
76
- "start_line",
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
- vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
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
- const where = buildScopeWhere(scope, `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbol)}')`);
133
- const indexedChunks = yield findSymbolChunks(vectorDb, where);
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 { fetchTestsForFooter, renderTestsFooterAgent } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/tests-footer")));
190
- const tests = yield fetchTestsForFooter(symbol, vectorDb, scope.pathPrefix, scope.excludePrefixes);
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}\u2502`)} ${body[i]}`);
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 { fetchTestsForFooter, renderTestsFooterHuman } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/tests-footer")));
231
- const tests = yield fetchTestsForFooter(symbol, vectorDb, scope.pathPrefix, scope.excludePrefixes);
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
- const message = error instanceof Error ? error.message : "Unknown error";
241
- console.error("Extract failed:", message);
242
- process.exitCode = 1;
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
  }));