grepmax 0.26.46 → 0.26.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.46",
13
+ "version": "0.26.48",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -130,6 +130,8 @@ MCP is local-only: the client spawns it and communicates through stdin/stdout pi
130
130
 
131
131
  `gmax mcp` uses the official MCP SDK v2.3.0 and serves both legacy (2025 handshake) and modern (2026-07-28 discovery and per-request metadata) clients over the same stdio entry. Discovery and catalog listing perform no daemon, model or watcher startup. Index-reading tools acquire watch leases lazily; one timer renews the primary projects actually used, and disconnect closes the SDK connection and its timer.
132
132
 
133
+ Clients requesting progress receive operation-stage messages with increasing stage counts and no estimated percentage. Cancelling a daemon read closes that request's IPC connection and prevents subsequent tool operations or fallback work; concurrent requests and the session's watch leases continue. An already-running local native query finishes under its existing deadline before its store closes. Cancellation does not undo indexing writes or interrupt optional LLM work.
134
+
133
135
  `gmax mcp` starts a stdio-based MCP server for clients that support MCP but can't run shell commands (Cursor, Windsurf, custom agents). Search-backed tools use the singleton daemon when available, avoiding a separate embedding worker per MCP session, and use an in-process worker for supported offline/secondary-store access. An unsupported daemon command requires `gmax watch restart`.
134
136
 
135
137
  `semantic_search`, `trace_calls`, `dead`, and `index_status` also advertise output schemas and return `structuredContent` with `schemaVersion: 1`, alongside the existing text. Search matches follow the same scope and display filters and include paths, one-based start/end lines, symbols, scores (null when unavailable), and warnings. Trace results preserve caller ancestry and edge confidence; unresolved callees have no location, and omitted callee/importer counts are explicit. Graph conclusions carry `approximate: true`. Index health includes embedding identity, watcher/reconciliation state, and the last compaction result; unavailable health is null or unobserved. These four tools advertise read-only, non-destructive, closed-world annotations. Other tools retain their current contracts.
@@ -97,6 +97,7 @@ const daemon_client_1 = require("../lib/utils/daemon-client");
97
97
  const file_utils_1 = require("../lib/utils/file-utils");
98
98
  const format_helpers_1 = require("../lib/utils/format-helpers");
99
99
  const import_extractor_1 = require("../lib/utils/import-extractor");
100
+ const mcp_execution_1 = require("../lib/utils/mcp-execution");
100
101
  const path_containment_1 = require("../lib/utils/path-containment");
101
102
  const project_registry_1 = require("../lib/utils/project-registry");
102
103
  const project_root_1 = require("../lib/utils/project-root");
@@ -414,20 +415,26 @@ exports.mcp = new commander_1.Command("mcp")
414
415
  // Propagate project root to worker processes
415
416
  process.env.GMAX_PROJECT_ROOT = paths.root;
416
417
  // --- Store access ---
418
+ function sendMcpCommand(cmd, opts) {
419
+ return (0, mcp_execution_1.mcpOperation)(`Daemon: ${cmd.cmd}`, () => (0, daemon_client_1.sendDaemonCommand)(cmd, Object.assign(Object.assign({}, opts), { signal: (0, mcp_execution_1.mcpSignal)() })));
420
+ }
417
421
  /**
418
422
  * One read through the access policy, with MCP's fallback allowances. The
419
423
  * `inProcess` branch is entered only when no daemon serves the verb.
420
424
  */
421
425
  function readStore(name, opts) {
422
- return (0, store_access_1.withStoreRead)(name, Object.assign(Object.assign(Object.assign({}, opts), MCP_STORE_FALLBACK), { skipDaemon: secondary() }));
426
+ return (0, store_access_1.withStoreRead)(name, Object.assign(Object.assign(Object.assign({ signal: (0, mcp_execution_1.mcpSignal)() }, opts), MCP_STORE_FALLBACK), { skipDaemon: secondary() }));
423
427
  }
424
428
  /** Open the shared store for the duration of one fallback, then close it. */
425
429
  function localStore(fn) {
426
- return (0, rows_handler_1.withLocalStore)(storeDir(), fn, storeConfig().vectorDim);
430
+ return (0, mcp_execution_1.mcpOperation)("Reading selected local store", () => (0, rows_handler_1.withLocalStore)(storeDir(), (deps) => __awaiter(this, void 0, void 0, function* () {
431
+ (0, mcp_execution_1.checkMcpCancellation)();
432
+ return fn(deps);
433
+ }), storeConfig().vectorDim));
427
434
  }
428
435
  /** A graph verb with MCP's fallback allowances. */
429
436
  function graphVerb(verb, call) {
430
- return (0, graph_handler_1.callGraphVerb)(verb, Object.assign(Object.assign({}, call), { fallback: Object.assign(Object.assign({}, MCP_STORE_FALLBACK), (secondary() ? { skipDaemon: true } : {})) }));
437
+ return (0, mcp_execution_1.mcpOperation)(`Reading ${verb}`, () => (0, graph_handler_1.callGraphVerb)(verb, Object.assign(Object.assign({}, call), { signal: (0, mcp_execution_1.mcpSignal)(), fallback: Object.assign(Object.assign({}, MCP_STORE_FALLBACK), (secondary() ? { skipDaemon: true } : {})) })));
431
438
  }
432
439
  /**
433
440
  * MCP tools take no `--in`/`--exclude`, so a tool's scope is always the
@@ -457,7 +464,7 @@ exports.mcp = new commander_1.Command("mcp")
457
464
  */
458
465
  function searchStore(args) {
459
466
  return readStore("search", {
460
- daemon: () => { var _a; return (0, daemon_client_1.sendDaemonCommand)(Object.assign({ cmd: ((_a = args.filters) === null || _a === void 0 ? void 0 : _a.projectRoots) ? "search-v2" : "search" }, args), { timeoutMs: 60000 }); },
467
+ daemon: () => { var _a; return sendMcpCommand(Object.assign({ cmd: ((_a = args.filters) === null || _a === void 0 ? void 0 : _a.projectRoots) ? "search-v2" : "search" }, args), { timeoutMs: 60000 }); },
461
468
  render: (resp) => {
462
469
  const warnings = Array.isArray(resp.warnings)
463
470
  ? resp.warnings.filter((w) => typeof w === "string")
@@ -476,7 +483,7 @@ exports.mcp = new commander_1.Command("mcp")
476
483
  : undefined;
477
484
  try {
478
485
  const searcher = new searcher_1.Searcher(deps.vectorDb, pool);
479
- return yield searcher.search(args.query, args.limit, { rerank: args.rerank, seeds: args.seeds }, args.filters, args.pathPrefix);
486
+ return yield searcher.search(args.query, args.limit, { rerank: args.rerank, seeds: args.seeds }, args.filters, args.pathPrefix, undefined, (0, mcp_execution_1.mcpSignal)());
480
487
  }
481
488
  finally {
482
489
  yield pool === null || pool === void 0 ? void 0 : pool.destroy();
@@ -548,7 +555,7 @@ exports.mcp = new commander_1.Command("mcp")
548
555
  * order. Batched so a per-symbol loop costs one round trip, not hundreds.
549
556
  */
550
557
  function locateRows(root, req) {
551
- return (0, rows_handler_1.readRows)(Object.assign({ name: "locate", projectRoot: root, lancedbDir: storeDir(), vectorDim: storeConfig().vectorDim, scope: projectScope(root), fallback: Object.assign(Object.assign({}, MCP_STORE_FALLBACK), { skipDaemon: secondary() }) }, req));
558
+ return (0, mcp_execution_1.mcpOperation)("Locating indexed rows", () => (0, rows_handler_1.readRows)(Object.assign({ signal: (0, mcp_execution_1.mcpSignal)(), name: "locate", projectRoot: root, lancedbDir: storeDir(), vectorDim: storeConfig().vectorDim, scope: projectScope(root), fallback: Object.assign(Object.assign({}, MCP_STORE_FALLBACK), { skipDaemon: secondary() }) }, req)));
552
559
  }
553
560
  /**
554
561
  * Chunk and file totals for `index_status`, summed per registered project.
@@ -566,8 +573,9 @@ exports.mcp = new commander_1.Command("mcp")
566
573
  let chunks = 0;
567
574
  let files = 0;
568
575
  for (const project of projects) {
576
+ (0, mcp_execution_1.checkMcpCancellation)();
569
577
  const totals = yield readStore("project-stats", {
570
- daemon: () => (0, daemon_client_1.sendDaemonCommand)({ cmd: "project-stats", root: project.root }, { timeoutMs: 30000 }),
578
+ daemon: () => sendMcpCommand({ cmd: "project-stats", root: project.root }, { timeoutMs: 30000 }),
571
579
  render: (resp) => ({
572
580
  chunks: typeof resp.chunks === "number" ? resp.chunks : 0,
573
581
  files: typeof resp.files === "number" ? resp.files : 0,
@@ -591,7 +599,7 @@ exports.mcp = new commander_1.Command("mcp")
591
599
  /** `rows.skeleton`: the stored per-file skeleton (and the symbol lookup). */
592
600
  function lookupSkeleton(root, req) {
593
601
  return readStore("skeleton", {
594
- daemon: () => (0, daemon_client_1.sendDaemonCommand)(Object.assign({ cmd: "rows.skeleton", projectRoot: root }, req), { timeoutMs: 30000 }),
602
+ daemon: () => sendMcpCommand(Object.assign({ cmd: "rows.skeleton", projectRoot: root }, req), { timeoutMs: 30000 }),
595
603
  render: (resp) => { var _a, _b; return ({
596
604
  path: ((_a = resp.path) !== null && _a !== void 0 ? _a : null),
597
605
  skeleton: ((_b = resp.skeleton) !== null && _b !== void 0 ? _b : null),
@@ -618,32 +626,34 @@ exports.mcp = new commander_1.Command("mcp")
618
626
  };
619
627
  function ensureWatcher(root) {
620
628
  return __awaiter(this, void 0, void 0, function* () {
621
- var _a;
622
- try {
623
- const watchRoot = root !== null && root !== void 0 ? root : (_a = resolveMcpProject(undefined, currentRoot(), storeProjects())) === null || _a === void 0 ? void 0 : _a.root;
624
- if (!watchRoot ||
625
- secondary() ||
626
- (0, stores_1.matchStore)(watchRoot).kind !== "primary")
627
- return;
628
- const result = yield (0, watcher_launcher_1.launchWatcher)(watchRoot, WATCH_LEASE);
629
- if (closing)
630
- return;
631
- if (result.ok) {
632
- watchedRoots.add(watchRoot);
633
- // Discovery/catalog requests never reach this path. Renew only roots
634
- // actually read by this session, with one timer for the whole process.
635
- watchTimer !== null && watchTimer !== void 0 ? watchTimer : (watchTimer = setInterval(() => {
636
- for (const root of watchedRoots)
637
- void ensureWatcher(root);
638
- }, MCP_WATCH_LEASE_RENEW_MS).unref());
639
- }
640
- if (result.ok && !result.reused) {
641
- console.log(`[MCP] Started background watcher for ${watchRoot} (PID: ${result.pid})`);
642
- }
643
- }
644
- catch (err) {
645
- console.error("[MCP] Watcher startup failed:", err);
646
- }
629
+ return (0, mcp_execution_1.withoutMcpExecution)(() => __awaiter(this, void 0, void 0, function* () {
630
+ var _a;
631
+ try {
632
+ const watchRoot = root !== null && root !== void 0 ? root : (_a = resolveMcpProject(undefined, currentRoot(), storeProjects())) === null || _a === void 0 ? void 0 : _a.root;
633
+ if (!watchRoot ||
634
+ secondary() ||
635
+ (0, stores_1.matchStore)(watchRoot).kind !== "primary")
636
+ return;
637
+ const result = yield (0, watcher_launcher_1.launchWatcher)(watchRoot, WATCH_LEASE);
638
+ if (closing)
639
+ return;
640
+ if (result.ok) {
641
+ watchedRoots.add(watchRoot);
642
+ // Discovery/catalog requests never reach this path. Renew only roots
643
+ // actually read by this session, with one timer for the whole process.
644
+ watchTimer !== null && watchTimer !== void 0 ? watchTimer : (watchTimer = setInterval(() => {
645
+ for (const root of watchedRoots)
646
+ void ensureWatcher(root);
647
+ }, MCP_WATCH_LEASE_RENEW_MS).unref());
648
+ }
649
+ if (result.ok && !result.reused) {
650
+ console.log(`[MCP] Started background watcher for ${watchRoot} (PID: ${result.pid})`);
651
+ }
652
+ }
653
+ catch (err) {
654
+ console.error("[MCP] Watcher startup failed:", err);
655
+ }
656
+ }));
647
657
  });
648
658
  }
649
659
  // Resolve the registered project this server scopes to. The server pins to
@@ -1566,7 +1576,7 @@ exports.mcp = new commander_1.Command("mcp")
1566
1576
  // returns the summary plus the findings this will print, and drops the
1567
1577
  // raw `pairs` array entirely.
1568
1578
  const result = yield readStore("surprises", {
1569
- daemon: () => (0, daemon_client_1.sendDaemonCommand)({
1579
+ daemon: () => sendMcpCommand({
1570
1580
  cmd: "vector.surprises",
1571
1581
  projectRoot: root,
1572
1582
  options,
@@ -1742,7 +1752,7 @@ exports.mcp = new commander_1.Command("mcp")
1742
1752
  ? root
1743
1753
  : `${root}/`;
1744
1754
  const entries = yield readStore("symbols", {
1745
- daemon: () => (0, daemon_client_1.sendDaemonCommand)({
1755
+ daemon: () => sendMcpCommand({
1746
1756
  cmd: "rows.symbols",
1747
1757
  projectRoot: root,
1748
1758
  pathPrefix: absPrefix,
@@ -1828,7 +1838,7 @@ exports.mcp = new commander_1.Command("mcp")
1828
1838
  const { chunks, files } = yield indexTotals(projects);
1829
1839
  const daemonStatus = secondary()
1830
1840
  ? { ok: true, projects: [] }
1831
- : yield (0, daemon_client_1.sendDaemonCommand)({ cmd: "status" });
1841
+ : yield sendMcpCommand({ cmd: "status" });
1832
1842
  const watched = (Array.isArray(daemonStatus.projects) ? daemonStatus.projects : []);
1833
1843
  const daemonProject = watched.find((p) => p.root === (currentProject === null || currentProject === void 0 ? void 0 : currentProject.root));
1834
1844
  // Only use the registry when no daemon answered; sandboxed reads stay IPC-only.
@@ -1954,7 +1964,7 @@ exports.mcp = new commander_1.Command("mcp")
1954
1964
  // rows it was computed from — the same aggregation `gmax project` runs,
1955
1965
  // in whichever process holds the store.
1956
1966
  const overview = yield readStore("project", {
1957
- daemon: () => (0, daemon_client_1.sendDaemonCommand)({ cmd: "rows.project", projectRoot: root }, { timeoutMs: 120000 }),
1967
+ daemon: () => sendMcpCommand({ cmd: "rows.project", projectRoot: root }, { timeoutMs: 120000 }),
1958
1968
  render: (resp) => resp.overview,
1959
1969
  inProcess: () => localStore((deps) => (0, rows_handler_1.runProject)(deps, root)),
1960
1970
  });
@@ -2390,7 +2400,7 @@ exports.mcp = new commander_1.Command("mcp")
2390
2400
  // The source chunk's vector never leaves the store: the daemon looks it
2391
2401
  // up, runs the vector search, and returns the ranked chunks.
2392
2402
  const result = yield readStore("similar", {
2393
- daemon: () => (0, daemon_client_1.sendDaemonCommand)({
2403
+ daemon: () => sendMcpCommand({
2394
2404
  cmd: "vector.similar",
2395
2405
  projectRoot: root,
2396
2406
  absPath,
@@ -2663,7 +2673,7 @@ exports.mcp = new commander_1.Command("mcp")
2663
2673
  });
2664
2674
  // Best-effort query logging, applied uniformly to every tool exactly as the
2665
2675
  // old single CallToolRequestSchema dispatch did before each return.
2666
- const logToolCall = (name, toolArgs, startMs, result) => __awaiter(this, void 0, void 0, function* () {
2676
+ const logToolCall = (name_1, toolArgs_1, startMs_1, result_1, ...args_1) => __awaiter(this, [name_1, toolArgs_1, startMs_1, result_1, ...args_1], void 0, function* (name, toolArgs, startMs, result, root = currentRoot()) {
2667
2677
  var _a, _b;
2668
2678
  var _c;
2669
2679
  try {
@@ -2675,7 +2685,7 @@ exports.mcp = new commander_1.Command("mcp")
2675
2685
  source: "mcp",
2676
2686
  tool: name,
2677
2687
  query: mcpLogQuery(name, toolArgs),
2678
- project: currentRoot(),
2688
+ project: root,
2679
2689
  results: resultLines,
2680
2690
  ms: Date.now() - startMs,
2681
2691
  error: result.isError ? text.slice(0, 200) : undefined,
@@ -2696,42 +2706,47 @@ exports.mcp = new commander_1.Command("mcp")
2696
2706
  openWorldHint: false,
2697
2707
  },
2698
2708
  }
2699
- : {})), { inputSchema: zod_1.z.object(Object.assign({ root: zod_1.z.string().optional() }, config.inputSchema)) }), (rawArgs) => __awaiter(this, void 0, void 0, function* () {
2700
- let args = (rawArgs !== null && rawArgs !== void 0 ? rawArgs : {});
2701
- const startMs = Date.now();
2702
- let target = projectRoot;
2703
- try {
2704
- if (typeof args.root === "string" && args.root.trim()) {
2705
- const raw = args.root.trim();
2706
- if (path.isAbsolute(raw) || raw.includes(path.sep))
2707
- target = path.resolve(raw);
2708
- else {
2709
- const projects = [
2710
- ...(0, project_registry_1.listProjects)(),
2711
- ...(0, store_context_1.storeInventory)().flatMap((store) => store.projects),
2712
- ].filter((project, i, all) => all.findIndex((p) => p.root === project.root) === i);
2713
- const selected = resolveMcpProject(raw, projectRoot, projects);
2714
- if (!selected)
2715
- return err(`Unknown or ambiguous registered project: ${raw}. Use list_projects or an exact root.`);
2716
- target = selected.root;
2709
+ : {})), { inputSchema: zod_1.z.object(Object.assign({ root: zod_1.z.string().optional() }, config.inputSchema)) }), (rawArgs, context) => __awaiter(this, void 0, void 0, function* () {
2710
+ return (0, mcp_execution_1.withMcpExecution)(context === null || context === void 0 ? void 0 : context.mcpReq, () => __awaiter(this, void 0, void 0, function* () {
2711
+ let args = (rawArgs !== null && rawArgs !== void 0 ? rawArgs : {});
2712
+ const startMs = Date.now();
2713
+ let target = projectRoot;
2714
+ try {
2715
+ (0, mcp_execution_1.checkMcpCancellation)();
2716
+ if (typeof args.root === "string" && args.root.trim()) {
2717
+ const raw = args.root.trim();
2718
+ if (path.isAbsolute(raw) || raw.includes(path.sep))
2719
+ target = path.resolve(raw);
2720
+ else {
2721
+ const projects = [
2722
+ ...(0, project_registry_1.listProjects)(),
2723
+ ...(0, store_context_1.storeInventory)().flatMap((store) => store.projects),
2724
+ ].filter((project, i, all) => all.findIndex((p) => p.root === project.root) === i);
2725
+ const selected = resolveMcpProject(raw, projectRoot, projects);
2726
+ if (!selected)
2727
+ return err(`Unknown or ambiguous registered project: ${raw}. Use list_projects or an exact root.`);
2728
+ target = selected.root;
2729
+ }
2717
2730
  }
2731
+ return yield (0, store_context_1.withStoreContext)(target, () => __awaiter(this, void 0, void 0, function* () {
2732
+ if (args.root !== undefined) {
2733
+ const selected = resolveMcpProject(args.root, currentRoot(), storeProjects());
2734
+ if (!selected)
2735
+ return err(`Unknown registered project: ${args.root}. Use list_projects to select a project name or exact root.`);
2736
+ args = Object.assign(Object.assign({}, args), { root: selected.root });
2737
+ (0, store_context_1.currentStoreContext)().root = selected.root;
2738
+ }
2739
+ const result = yield (0, mcp_execution_1.mcpOperation)(`Starting ${name}`, () => handler(args));
2740
+ yield logToolCall(name, args, startMs, result);
2741
+ return result;
2742
+ }));
2718
2743
  }
2719
- return yield (0, store_context_1.withStoreContext)(target, () => __awaiter(this, void 0, void 0, function* () {
2720
- if (args.root !== undefined) {
2721
- const selected = resolveMcpProject(args.root, currentRoot(), storeProjects());
2722
- if (!selected)
2723
- return err(`Unknown registered project: ${args.root}. Use list_projects to select a project name or exact root.`);
2724
- args = Object.assign(Object.assign({}, args), { root: selected.root });
2725
- (0, store_context_1.currentStoreContext)().root = selected.root;
2726
- }
2727
- const result = yield handler(args);
2728
- yield logToolCall(name, args, startMs, result);
2744
+ catch (error) {
2745
+ const result = toolError("Tool failed", error);
2746
+ yield logToolCall(name, args, startMs, result, target);
2729
2747
  return result;
2730
- }));
2731
- }
2732
- catch (error) {
2733
- return toolError("Tool failed", error);
2734
- }
2748
+ }
2749
+ }));
2735
2750
  }));
2736
2751
  }
2737
2752
  tool("semantic_search", {
@@ -856,5 +856,5 @@ exports.GRAPH_VERB_TIMEOUT_MS = 60000;
856
856
  * project skips the daemon outright — see isDaemonReadableProject.
857
857
  */
858
858
  function callGraphVerb(verb, call) {
859
- return (0, store_access_1.withStoreRead)(verb, Object.assign({ skipDaemon: !isDaemonReadableProject(call.projectRoot), daemon: () => (0, daemon_client_1.sendDaemonCommand)(Object.assign(Object.assign({ cmd: verb }, encodeScope(call.projectRoot, call.scope)), call.payload), { timeoutMs: exports.GRAPH_VERB_TIMEOUT_MS }), render: call.render, inProcess: call.inProcess }, call.fallback));
859
+ return (0, store_access_1.withStoreRead)(verb, Object.assign({ signal: call.signal, skipDaemon: !isDaemonReadableProject(call.projectRoot), daemon: () => (0, daemon_client_1.sendDaemonCommand)(Object.assign(Object.assign({ cmd: verb }, encodeScope(call.projectRoot, call.scope)), call.payload), { timeoutMs: exports.GRAPH_VERB_TIMEOUT_MS, signal: call.signal }), render: call.render, inProcess: call.inProcess }, call.fallback));
860
860
  }
@@ -570,7 +570,7 @@ function readRows(opts) {
570
570
  return [];
571
571
  const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../utils/daemon-client")));
572
572
  const { withStoreRead } = yield Promise.resolve().then(() => __importStar(require("../utils/store-access")));
573
- return withStoreRead(opts.name, Object.assign({ daemon: () => { var _a; return sendDaemonCommand({
573
+ return withStoreRead(opts.name, Object.assign({ signal: opts.signal, daemon: () => { var _a; return sendDaemonCommand({
574
574
  cmd: "rows.locate",
575
575
  projectRoot: opts.projectRoot,
576
576
  scope: scopeToWire(opts.scope),
@@ -578,14 +578,18 @@ function readRows(opts) {
578
578
  matches: opts.matches,
579
579
  limit: opts.limit,
580
580
  scoped: opts.scoped,
581
- }, { timeoutMs: (_a = opts.timeoutMs) !== null && _a !== void 0 ? _a : 60000 }); }, render: (resp) => { var _a; return ((_a = resp.rows) !== null && _a !== void 0 ? _a : []); }, inProcess: () => withLocalStore(opts.lancedbDir, (deps) => runLocate(deps, {
582
- projectRoot: opts.projectRoot,
583
- scope: opts.scope,
584
- select: opts.select,
585
- matches: opts.matches,
586
- limit: opts.limit,
587
- scoped: opts.scoped,
588
- }), opts.vectorDim) }, opts.fallback));
581
+ }, { timeoutMs: (_a = opts.timeoutMs) !== null && _a !== void 0 ? _a : 60000, signal: opts.signal }); }, render: (resp) => { var _a; return ((_a = resp.rows) !== null && _a !== void 0 ? _a : []); }, inProcess: () => withLocalStore(opts.lancedbDir, (deps) => {
582
+ var _a;
583
+ (_a = opts.signal) === null || _a === void 0 ? void 0 : _a.throwIfAborted();
584
+ return runLocate(deps, {
585
+ projectRoot: opts.projectRoot,
586
+ scope: opts.scope,
587
+ select: opts.select,
588
+ matches: opts.matches,
589
+ limit: opts.limit,
590
+ scoped: opts.scoped,
591
+ });
592
+ }, opts.vectorDim) }, opts.fallback));
589
593
  });
590
594
  }
591
595
  function handleRowsSkeleton(deps, payload) {
@@ -699,8 +699,12 @@ class Searcher {
699
699
  }
700
700
  const envMaxPerFile = Number.parseInt((_m = process.env.GMAX_MAX_PER_FILE) !== null && _m !== void 0 ? _m : "", 10);
701
701
  const MAX_PER_FILE = Number.isFinite(envMaxPerFile) && envMaxPerFile > 0 ? envMaxPerFile : 3;
702
- const displayWindow = Math.min(stage2Candidates.length, Math.max(finalLimit * MAX_PER_FILE, finalLimit, RERANK_TOP));
703
- const displayCandidates = stage2Candidates.slice(0, displayWindow);
702
+ // Rank the entire bounded candidate pool before trimming the result count.
703
+ // A limit-dependent cut here discarded contenders before structure boosts
704
+ // and dedup, so requesting three results could hide the top match returned
705
+ // by a ten-result request. Expensive reranking stays independently bounded;
706
+ // display columns are still fetched only for the final results below.
707
+ const displayCandidates = [...stage2Candidates];
704
708
  const rerankCandidates = stage2Candidates.slice(0, RERANK_TOP);
705
709
  // Symbol-definition promotion (1/2): membership. For a bare-symbol query,
706
710
  // ensure the chunk(s) that actually DEFINE the symbol reach the rerank set
@@ -69,7 +69,10 @@ const HEARTBEAT_FRESH_THRESHOLD_MS = 150000;
69
69
  */
70
70
  function sendDaemonCommand(cmd, opts) {
71
71
  var _a;
72
- const timeout = (_a = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_TIMEOUT_MS;
72
+ var _b;
73
+ if ((_a = opts === null || opts === void 0 ? void 0 : opts.signal) === null || _a === void 0 ? void 0 : _a.aborted)
74
+ return Promise.resolve({ ok: false, error: "aborted" });
75
+ const timeout = (_b = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _b !== void 0 ? _b : DEFAULT_TIMEOUT_MS;
73
76
  return new Promise((resolve) => {
74
77
  var _a, _b;
75
78
  let settled = false;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.withoutMcpExecution = withoutMcpExecution;
13
+ exports.mcpSignal = mcpSignal;
14
+ exports.checkMcpCancellation = checkMcpCancellation;
15
+ exports.mcpOperation = mcpOperation;
16
+ exports.withMcpExecution = withMcpExecution;
17
+ const node_async_hooks_1 = require("node:async_hooks");
18
+ const executions = new node_async_hooks_1.AsyncLocalStorage();
19
+ /** Session background work must not retain the triggering request's signal. */
20
+ function withoutMcpExecution(work) {
21
+ return executions.exit(work);
22
+ }
23
+ function mcpSignal() {
24
+ var _a;
25
+ return (_a = executions.getStore()) === null || _a === void 0 ? void 0 : _a.request.signal;
26
+ }
27
+ /** Check between operations, never race a native query against store close. */
28
+ function checkMcpCancellation() {
29
+ var _a;
30
+ (_a = mcpSignal()) === null || _a === void 0 ? void 0 : _a.throwIfAborted();
31
+ }
32
+ /** Counts started operation stages, not completion percentages. No timers. */
33
+ function mcpOperation(message, work) {
34
+ return __awaiter(this, void 0, void 0, function* () {
35
+ var _a;
36
+ checkMcpCancellation();
37
+ const execution = executions.getStore();
38
+ const progressToken = (_a = execution === null || execution === void 0 ? void 0 : execution.request._meta) === null || _a === void 0 ? void 0 : _a.progressToken;
39
+ if (execution && progressToken !== undefined) {
40
+ const progress = ++execution.stages;
41
+ execution.notifications = execution.notifications.then(() => __awaiter(this, void 0, void 0, function* () {
42
+ if (execution.request.signal.aborted)
43
+ return;
44
+ try {
45
+ yield execution.request.notify({
46
+ method: "notifications/progress",
47
+ params: { progressToken, progress, message },
48
+ });
49
+ }
50
+ catch (_a) {
51
+ // Progress is advisory; a peer declining it must not break a read.
52
+ }
53
+ }));
54
+ yield execution.notifications;
55
+ }
56
+ checkMcpCancellation();
57
+ const result = yield work();
58
+ checkMcpCancellation();
59
+ return result;
60
+ });
61
+ }
62
+ function withMcpExecution(request, work) {
63
+ // Non-protocol callers (including the catalog's unit fixtures) have no context.
64
+ if (!request)
65
+ return work();
66
+ return executions.run({ request, stages: 0, notifications: Promise.resolve() }, work);
67
+ }
@@ -271,11 +271,16 @@ function isDaemonBypassed() {
271
271
  */
272
272
  function withStoreRead(name, opts) {
273
273
  return __awaiter(this, void 0, void 0, function* () {
274
- var _a;
275
- var _b;
274
+ var _a, _b, _c, _d;
275
+ var _e;
276
+ (_a = opts.signal) === null || _a === void 0 ? void 0 : _a.throwIfAborted();
276
277
  const runInProcess = () => __awaiter(this, void 0, void 0, function* () {
278
+ var _a, _b;
279
+ (_a = opts.signal) === null || _a === void 0 ? void 0 : _a.throwIfAborted();
277
280
  try {
278
- return yield opts.inProcess();
281
+ const result = yield opts.inProcess();
282
+ (_b = opts.signal) === null || _b === void 0 ? void 0 : _b.throwIfAborted();
283
+ return result;
279
284
  }
280
285
  catch (err) {
281
286
  throw asStoreAccessError(err);
@@ -288,6 +293,7 @@ function withStoreRead(name, opts) {
288
293
  resp = yield opts.daemon();
289
294
  }
290
295
  catch (err) {
296
+ (_b = opts.signal) === null || _b === void 0 ? void 0 : _b.throwIfAborted();
291
297
  if (isStoreAccessRefused(err))
292
298
  throw err;
293
299
  const cls = classifyDaemonError(err === null || err === void 0 ? void 0 : err.code);
@@ -297,6 +303,7 @@ function withStoreRead(name, opts) {
297
303
  throw err;
298
304
  return runInProcess();
299
305
  }
306
+ (_c = opts.signal) === null || _c === void 0 ? void 0 : _c.throwIfAborted();
300
307
  if (resp.ok) {
301
308
  return opts.render ? yield opts.render(resp) : resp;
302
309
  }
@@ -308,13 +315,13 @@ function withStoreRead(name, opts) {
308
315
  }
309
316
  if (cls === "no-daemon" ||
310
317
  (opts.fallbackOnOversize && isOversizeError(resp.error)) ||
311
- ((_a = opts.extraFallback) === null || _a === void 0 ? void 0 : _a.call(opts, resp.error)) === true) {
318
+ ((_d = opts.extraFallback) === null || _d === void 0 ? void 0 : _d.call(opts, resp.error)) === true) {
312
319
  if (process.env.GMAX_DEBUG === "1") {
313
320
  console.error(`[${name}] daemon path unavailable: ${resp.error}`);
314
321
  }
315
322
  return runInProcess();
316
323
  }
317
- throw new Error(((_b = opts.daemonErrorMessage) !== null && _b !== void 0 ? _b : ((r) => defaultDaemonErrorMessage(name, r)))(resp));
324
+ throw new Error(((_e = opts.daemonErrorMessage) !== null && _e !== void 0 ? _e : ((r) => defaultDaemonErrorMessage(name, r)))(resp));
318
325
  });
319
326
  }
320
327
  // --- Spawn guard -----------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.46",
3
+ "version": "0.26.48",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.46",
3
+ "version": "0.26.48",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",