grepmax 0.17.13 → 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.
- package/dist/commands/search.js +25 -1
- package/dist/lib/daemon/daemon.js +60 -10
- package/dist/lib/index/batch-processor.js +4 -0
- package/dist/lib/index/ignore-patterns.js +5 -0
- package/dist/lib/output/index-state-footer.js +29 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
package/dist/commands/search.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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 ((
|
|
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 = (
|
|
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 (
|
|
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 (
|
|
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;
|
|
@@ -59,6 +59,11 @@ exports.DEFAULT_IGNORE_PATTERNS = [
|
|
|
59
59
|
"*.designer.cs", // C# designer
|
|
60
60
|
"*.generated.ts",
|
|
61
61
|
"*.generated.tsx", // graphql-codegen / common TS
|
|
62
|
+
// graphql-codegen client-preset output dir (emits no @generated banner, so
|
|
63
|
+
// the header sniff can't catch these — match the canonical filenames).
|
|
64
|
+
"**/gql/graphql.ts",
|
|
65
|
+
"**/gql/gql.ts",
|
|
66
|
+
"**/gql/fragment-masking.ts",
|
|
62
67
|
// Test fixtures and benchmark data
|
|
63
68
|
"**/fixtures/**",
|
|
64
69
|
"**/benchmark/**",
|
|
@@ -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