grepmax 0.17.14 → 0.17.15

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.
@@ -50,6 +50,7 @@ const grammar_loader_1 = require("../lib/index/grammar-loader");
50
50
  const sync_helpers_1 = require("../lib/index/sync-helpers");
51
51
  const syncer_1 = require("../lib/index/syncer");
52
52
  const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
53
+ const index_state_footer_1 = require("../lib/output/index-state-footer");
53
54
  const searcher_1 = require("../lib/search/searcher");
54
55
  const setup_helpers_1 = require("../lib/setup/setup-helpers");
55
56
  const skeleton_1 = require("../lib/skeleton");
@@ -575,6 +576,7 @@ Examples:
575
576
  let searchResult = null;
576
577
  let precomputedSkeletons;
577
578
  let precomputedGraph;
579
+ let indexState;
578
580
  if (!options.sync && !options.dryRun) {
579
581
  try {
580
582
  const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
@@ -600,6 +602,7 @@ Examples:
600
602
  };
601
603
  precomputedSkeletons = resp.skeletons;
602
604
  precomputedGraph = resp.graph;
605
+ indexState = resp.indexState;
603
606
  }
604
607
  else if (process.env.GMAX_DEBUG === "1") {
605
608
  console.error(`[search] daemon path unavailable: ${(_d = resp.error) !== null && _d !== void 0 ? _d : "unknown"}`);
@@ -617,9 +620,16 @@ Examples:
617
620
  if (!searchResult) {
618
621
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
619
622
  // Check for active indexing lock and warn if present
620
- if (!options.agent && (0, lock_1.isLocked)(paths.dataDir)) {
623
+ const locked = (0, lock_1.isLocked)(paths.dataDir);
624
+ if (!options.agent && locked) {
621
625
  console.warn("⚠️ Warning: Indexing in progress... search results may be incomplete.");
622
626
  }
627
+ // No daemon here, so no precise pending count — surface the coarse
628
+ // signal (active lock or initial index not yet complete) so agent mode
629
+ // still gets a partial-index footer.
630
+ if (!indexState && (locked || project.status === "pending")) {
631
+ indexState = { indexing: true, pendingFiles: 0 };
632
+ }
623
633
  const hasRows = yield vectorDb.hasAnyRows();
624
634
  const needsSync = options.sync || !hasRows;
625
635
  if (needsSync) {
@@ -694,6 +704,14 @@ Examples:
694
704
  console.warn(`Warning: ${w}`);
695
705
  }
696
706
  }
707
+ // Partial-index signal (Phase 6): when the index is mid-catchup, results
708
+ // may be incomplete. Non-agent renders it now as a warning; agent mode
709
+ // appends a machine-readable footer after the results below.
710
+ if (!options.agent) {
711
+ const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, { agent: false });
712
+ if (footer)
713
+ console.warn(footer);
714
+ }
697
715
  let filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
698
716
  // Post-filter by symbol name regex
699
717
  if (options.name) {
@@ -766,6 +784,12 @@ Examples:
766
784
  }
767
785
  catch (_1) { }
768
786
  }
787
+ // Partial-index footer last, so it's the final line the agent reads —
788
+ // and emitted even on "(none)", where an empty result may just mean the
789
+ // relevant files aren't indexed yet.
790
+ const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, { agent: true });
791
+ if (footer)
792
+ console.log(footer);
769
793
  return;
770
794
  }
771
795
  if (options.skeleton) {
@@ -142,6 +142,10 @@ class Daemon {
142
142
  this.lastOverflowMs = new Map();
143
143
  this.lastCatchupEndMs = new Map();
144
144
  this.projectLocks = new Map();
145
+ // Full-index progress per root while initialSync runs (--reset / initial
146
+ // index). Presence = a full index is in flight; value drives the partial-
147
+ // result pending count (Phase 6). Cleared in the indexProject finally.
148
+ this.indexProgress = new Map();
145
149
  this.shutdownAbortControllers = new Set();
146
150
  this.llmServer = null;
147
151
  this.mlxChild = null;
@@ -809,19 +813,51 @@ class Daemon {
809
813
  * caller is responsible for binding `signal` to socket close so we abort if
810
814
  * the client disconnects mid-search.
811
815
  */
816
+ /**
817
+ * Live (re)index progress for a project: whether indexing is underway and how
818
+ * many files are still queued. Derived from the batch processor's pending map
819
+ * plus the registry's initial-index status. Cheap (in-memory) — safe to call
820
+ * on every search to annotate partial-result responses (Phase 6).
821
+ */
822
+ indexState(root) {
823
+ var _a, _b, _c;
824
+ const processor = this.processors.get(root);
825
+ const batchPending = (_a = processor === null || processor === void 0 ? void 0 : processor.progress.pendingFiles) !== null && _a !== void 0 ? _a : 0;
826
+ const processing = (_b = processor === null || processor === void 0 ? void 0 : processor.progress.processing) !== null && _b !== void 0 ? _b : false;
827
+ // status === "pending" means the initial full index hasn't completed.
828
+ const initialPending = ((_c = (0, project_registry_1.getProject)(root)) === null || _c === void 0 ? void 0 : _c.status) === "pending";
829
+ // A full index (--reset / initial) bypasses the batch processor; its
830
+ // onProgress feeds indexProgress, giving a real remaining count.
831
+ const fullIdx = this.indexProgress.get(root);
832
+ let pendingFiles = batchPending;
833
+ if (fullIdx && fullIdx.total > 0) {
834
+ pendingFiles = Math.max(pendingFiles, fullIdx.total - fullIdx.processed);
835
+ }
836
+ return {
837
+ indexing: !!fullIdx || processing || batchPending > 0 || initialPending,
838
+ pendingFiles,
839
+ };
840
+ }
812
841
  search(payload, signal) {
813
842
  return __awaiter(this, void 0, void 0, function* () {
814
- var _a, _b, _c;
843
+ var _a, _b, _c, _d;
815
844
  if (!this.vectorDb) {
816
845
  return { ok: false, error: "daemon not ready" };
817
846
  }
818
847
  const root = payload.projectRoot;
819
848
  if (!this.processors.has(root)) {
820
- return {
821
- ok: false,
822
- error: "project not watched",
823
- hint: `run: gmax add ${root}`,
824
- };
849
+ // A full index (--reset) or the initial index removes/defers the
850
+ // processor while (re)building. The partial index is still queryable, so
851
+ // answer the search and flag it partial (below) rather than erroring —
852
+ // only truly-unwatched, not-indexing projects get "not watched".
853
+ const indexingNow = this.indexProgress.has(root) || ((_a = (0, project_registry_1.getProject)(root)) === null || _a === void 0 ? void 0 : _a.status) === "pending";
854
+ if (!indexingNow) {
855
+ return {
856
+ ok: false,
857
+ error: "project not watched",
858
+ hint: `run: gmax add ${root}`,
859
+ };
860
+ }
825
861
  }
826
862
  let searcher = this.searchers.get(root);
827
863
  if (!searcher) {
@@ -841,8 +877,14 @@ class Daemon {
841
877
  return { ok: false, error: "search_failed", hint: msg };
842
878
  }
843
879
  const response = { ok: true, data: result.data };
844
- if ((_a = result.warnings) === null || _a === void 0 ? void 0 : _a.length)
880
+ if ((_b = result.warnings) === null || _b === void 0 ? void 0 : _b.length)
845
881
  response.warnings = result.warnings;
882
+ // Annotate partial results when the index is still catching up, so an
883
+ // agent can caveat or retry. Only attached when actually indexing (the
884
+ // formatter suppresses the settled case anyway).
885
+ const idx = this.indexState(root);
886
+ if (idx.indexing)
887
+ response.indexState = idx;
846
888
  // --skeleton support: fetch per-file skeletons inline so the CLI doesn't
847
889
  // have to open its own VectorDB. getStoredSkeleton is a single LIMIT-1
848
890
  // lookup; cheap enough to call for the top N distinct paths.
@@ -851,7 +893,7 @@ class Daemon {
851
893
  const seen = new Set();
852
894
  const skeletons = {};
853
895
  for (const chunk of result.data) {
854
- const p = (_b = chunk.path) !== null && _b !== void 0 ? _b : (_c = chunk.metadata) === null || _c === void 0 ? void 0 : _c.path;
896
+ const p = (_c = chunk.path) !== null && _c !== void 0 ? _c : (_d = chunk.metadata) === null || _d === void 0 ? void 0 : _d.path;
855
897
  if (!p || seen.has(p))
856
898
  continue;
857
899
  seen.add(p);
@@ -862,7 +904,7 @@ class Daemon {
862
904
  if (sk)
863
905
  skeletons[p] = sk;
864
906
  }
865
- catch (_d) {
907
+ catch (_e) {
866
908
  // best-effort — drop the entry, keep the search result
867
909
  }
868
910
  }
@@ -877,7 +919,7 @@ class Daemon {
877
919
  const builder = new GraphBuilder(this.vectorDb, root);
878
920
  response.graph = yield builder.buildGraphMultiHop(payload.query, 1);
879
921
  }
880
- catch (_e) {
922
+ catch (_f) {
881
923
  // best-effort — drop graph, keep results
882
924
  }
883
925
  }
@@ -1018,6 +1060,9 @@ class Daemon {
1018
1060
  this.vectorDb.pauseMaintenanceLoop();
1019
1061
  const stopHeartbeat = (0, ipc_handler_1.startHeartbeat)(conn);
1020
1062
  let lastProgressTime = 0;
1063
+ // Mark this root as full-indexing so concurrent searches get a
1064
+ // partial-result footer (Phase 6); seeded at 0/0 until the first tick.
1065
+ this.indexProgress.set(root, { processed: 0, total: 0 });
1021
1066
  try {
1022
1067
  const result = yield (0, syncer_1.initialSync)({
1023
1068
  projectRoot: root,
@@ -1028,6 +1073,10 @@ class Daemon {
1028
1073
  signal: ac.signal,
1029
1074
  onProgress: (info) => {
1030
1075
  this.resetActivity();
1076
+ this.indexProgress.set(root, {
1077
+ processed: info.processed,
1078
+ total: info.total,
1079
+ });
1031
1080
  const now = Date.now();
1032
1081
  if (now - lastProgressTime < 100)
1033
1082
  return;
@@ -1057,6 +1106,7 @@ class Daemon {
1057
1106
  }
1058
1107
  finally {
1059
1108
  stopHeartbeat();
1109
+ this.indexProgress.delete(root);
1060
1110
  this.shutdownAbortControllers.delete(ac);
1061
1111
  (_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.resumeMaintenanceLoop();
1062
1112
  // Re-enable watcher (skip if shutting down)
@@ -96,6 +96,10 @@ class ProjectBatchProcessor {
96
96
  (_a = this.onActivity) === null || _a === void 0 ? void 0 : _a.call(this);
97
97
  this.scheduleBatch();
98
98
  }
99
+ /** Live (re)index progress: files queued + whether a batch is running. */
100
+ get progress() {
101
+ return { pendingFiles: this.pending.size, processing: this.processing };
102
+ }
99
103
  close() {
100
104
  return __awaiter(this, void 0, void 0, function* () {
101
105
  var _a;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ // Phase 6 — partial-index signal for agent-mode search.
3
+ //
4
+ // During the catchup window the index is incomplete but search still returns
5
+ // (partial) results. Non-agent output already warns about this; agent output
6
+ // historically did not. This formats a single machine-readable footer so an
7
+ // agent can decide to caveat its answer or retry once indexing settles.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.formatIndexStateFooter = formatIndexStateFooter;
10
+ /**
11
+ * One-line footer describing an in-progress index, or null when there's
12
+ * nothing to say (no state, or the index is settled). Suppressing the
13
+ * settled case keeps steady-state search silent — the footer only appears
14
+ * while results may actually be incomplete.
15
+ */
16
+ function formatIndexStateFooter(state, opts) {
17
+ if (!state || !state.indexing)
18
+ return null;
19
+ const count = state.pendingFiles > 0 ? `~${state.pendingFiles} files pending` : null;
20
+ if (opts.agent) {
21
+ const parts = ["index: syncing"];
22
+ if (count)
23
+ parts.push(count);
24
+ parts.push("results may be incomplete — retry for full coverage");
25
+ return `[${parts.join(" · ")}]`;
26
+ }
27
+ const detail = count ? ` (${count})` : "";
28
+ return `⚠️ Index still syncing${detail} — results may be incomplete.`;
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.17.14",
3
+ "version": "0.17.15",
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.17.14",
3
+ "version": "0.17.15",
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",