grepmax 0.17.24 → 0.18.1
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/dead.js +1 -0
- package/dist/commands/doctor.js +44 -0
- package/dist/commands/impact.js +1 -0
- package/dist/commands/peek.js +1 -0
- package/dist/commands/related.js +1 -0
- package/dist/commands/search-output.js +531 -0
- package/dist/commands/search.js +45 -463
- package/dist/commands/similar.js +1 -0
- package/dist/commands/test-find.js +1 -0
- package/dist/commands/trace.js +41 -5
- package/dist/config.js +34 -0
- package/dist/lib/daemon/daemon.js +29 -643
- package/dist/lib/daemon/mlx-server-manager.js +191 -0
- package/dist/lib/daemon/process-manager.js +139 -0
- package/dist/lib/daemon/watcher-manager.js +484 -0
- package/dist/lib/help/agent-cheatsheet.js +1 -1
- package/dist/lib/utils/stale-hint.js +115 -13
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
package/dist/commands/dead.js
CHANGED
|
@@ -120,6 +120,7 @@ exports.dead = new commander_1.Command("dead")
|
|
|
120
120
|
try {
|
|
121
121
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
122
122
|
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
123
|
+
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
123
124
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
124
125
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
125
126
|
const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
package/dist/commands/doctor.js
CHANGED
|
@@ -242,6 +242,12 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
242
242
|
return p.status === "indexed" &&
|
|
243
243
|
((_a = p.chunkerVersion) !== null && _a !== void 0 ? _a : 1) < config_1.CONFIG.CHUNKER_VERSION;
|
|
244
244
|
});
|
|
245
|
+
// Projects whose stored embedding model/dim no longer matches the global
|
|
246
|
+
// config. Visibility only — recovery is a manual `gmax index --reset`
|
|
247
|
+
// (a dim change can't be auto-fixed in the shared fixed-dim table; that's
|
|
248
|
+
// the deferred Phase 1B re-embed work).
|
|
249
|
+
const staleEmbeddingProjects = projects.filter((p) => p.status === "indexed" &&
|
|
250
|
+
(0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, { modelTier: globalConfig.modelTier, vectorDim: globalConfig.vectorDim }) !== null);
|
|
245
251
|
if (opts.agent) {
|
|
246
252
|
const fields = [
|
|
247
253
|
"index_health",
|
|
@@ -257,6 +263,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
257
263
|
`daemon=${daemonUp ? "running" : "stopped"}`,
|
|
258
264
|
`orphaned=${orphanedProjects.length}`,
|
|
259
265
|
`stale_chunker=${staleChunkerProjects.length}`,
|
|
266
|
+
`stale_embedding=${staleEmbeddingProjects.length}`,
|
|
260
267
|
];
|
|
261
268
|
console.log(fields.join("\t"));
|
|
262
269
|
for (const p of staleChunkerProjects) {
|
|
@@ -273,6 +280,22 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
273
280
|
`fix=gmax index --reset (in ${p.root})`,
|
|
274
281
|
].join("\t"));
|
|
275
282
|
}
|
|
283
|
+
for (const p of staleEmbeddingProjects) {
|
|
284
|
+
const gap = (0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, { modelTier: globalConfig.modelTier, vectorDim: globalConfig.vectorDim });
|
|
285
|
+
if (!gap)
|
|
286
|
+
continue;
|
|
287
|
+
console.log([
|
|
288
|
+
"stale_embedding_project",
|
|
289
|
+
`name=${p.name || path.basename(p.root)}`,
|
|
290
|
+
`indexed_model=${gap.fromModel}`,
|
|
291
|
+
`current_model=${gap.toModel}`,
|
|
292
|
+
`indexed_dim=${gap.fromDim}`,
|
|
293
|
+
`current_dim=${gap.toDim}`,
|
|
294
|
+
`dim_changed=${gap.dimChanged}`,
|
|
295
|
+
`severity=${gap.severity}`,
|
|
296
|
+
`fix=gmax index --reset (in ${p.root})`,
|
|
297
|
+
].join("\t"));
|
|
298
|
+
}
|
|
276
299
|
}
|
|
277
300
|
else {
|
|
278
301
|
console.log("\nIndex Health\n");
|
|
@@ -331,6 +354,27 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
331
354
|
console.log(` run 'gmax index --reset' in ${p.root}`);
|
|
332
355
|
}
|
|
333
356
|
}
|
|
357
|
+
// Index built with a different embedding model/dim than the current
|
|
358
|
+
// config. A dim change is breaking (search scores are invalid until a
|
|
359
|
+
// re-embed); a same-dim model swap is additive. Unlike the stale-chunker
|
|
360
|
+
// case this is NOT auto-fixed by `--fix`: a dim change can't coexist in
|
|
361
|
+
// the shared fixed-dim table (deferred Phase 1B), so we point at the
|
|
362
|
+
// manual reset instead.
|
|
363
|
+
if (staleEmbeddingProjects.length > 0) {
|
|
364
|
+
const gaps = staleEmbeddingProjects.map((p) => (0, config_1.describeEmbeddingGap)({ modelTier: p.modelTier, vectorDim: p.vectorDim }, { modelTier: globalConfig.modelTier, vectorDim: globalConfig.vectorDim }));
|
|
365
|
+
const anyBreaking = gaps.some((g) => (g === null || g === void 0 ? void 0 : g.severity) === "breaking");
|
|
366
|
+
console.log(`${anyBreaking ? "WARN" : "INFO"} Stale embedding: ${staleEmbeddingProjects.length} project(s) indexed with a different embedding model/dim — run 'gmax index --reset' per project`);
|
|
367
|
+
staleEmbeddingProjects.forEach((p, i) => {
|
|
368
|
+
const gap = gaps[i];
|
|
369
|
+
if (!gap)
|
|
370
|
+
return;
|
|
371
|
+
const change = gap.dimChanged
|
|
372
|
+
? `${gap.fromDim}d→${gap.toDim}d`
|
|
373
|
+
: `model ${gap.fromModel}→${gap.toModel}`;
|
|
374
|
+
console.log(` - ${p.name || path.basename(p.root)} (${change}, ${gap.severity})`);
|
|
375
|
+
console.log(` run 'gmax index --reset' in ${p.root}`);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
334
378
|
// Projects
|
|
335
379
|
if (orphanedProjects.length > 0) {
|
|
336
380
|
console.log(`WARN Orphaned projects: ${orphanedProjects.length} (directories no longer exist)`);
|
package/dist/commands/impact.js
CHANGED
|
@@ -74,6 +74,7 @@ exports.impact = new commander_1.Command("impact")
|
|
|
74
74
|
return;
|
|
75
75
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
76
76
|
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
77
|
+
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
77
78
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
78
79
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
79
80
|
const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
|
package/dist/commands/peek.js
CHANGED
|
@@ -131,6 +131,7 @@ exports.peek = new commander_1.Command("peek")
|
|
|
131
131
|
try {
|
|
132
132
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
133
133
|
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
134
|
+
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
134
135
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
135
136
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
136
137
|
const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
package/dist/commands/related.js
CHANGED
|
@@ -72,6 +72,7 @@ exports.related = new commander_1.Command("related")
|
|
|
72
72
|
return;
|
|
73
73
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
74
74
|
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
75
|
+
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
75
76
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
76
77
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
77
78
|
const absPath = path.resolve(projectRoot, file);
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.executeServerSearch = executeServerSearch;
|
|
46
|
+
exports.renderSearchOutput = renderSearchOutput;
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
48
|
+
const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
|
|
49
|
+
const compact_results_1 = require("../lib/output/compact-results");
|
|
50
|
+
const index_state_footer_1 = require("../lib/output/index-state-footer");
|
|
51
|
+
const cross_project_1 = require("../lib/utils/cross-project");
|
|
52
|
+
const formatter_1 = require("../lib/utils/formatter");
|
|
53
|
+
const import_extractor_1 = require("../lib/utils/import-extractor");
|
|
54
|
+
const search_skeletons_1 = require("./search-skeletons");
|
|
55
|
+
/**
|
|
56
|
+
* Standalone HTTP-server search path. The per-project server answers the query
|
|
57
|
+
* over HTTP and returns JSON; we render it here with the same presentation
|
|
58
|
+
* modes as the local path (minus the modes the server can't precompute).
|
|
59
|
+
*
|
|
60
|
+
* Returns `true` when the server answered and rendering is complete (the caller
|
|
61
|
+
* should return without touching the local path); `false` when the request
|
|
62
|
+
* failed (`!response.ok` or a thrown error) and the caller should fall back to
|
|
63
|
+
* the in-process / daemon-mediated path.
|
|
64
|
+
*
|
|
65
|
+
* Mirrors the original action's structure: a handled server search returns
|
|
66
|
+
* BEFORE the command's main try/finally, so it intentionally skips query
|
|
67
|
+
* logging and gracefulExit.
|
|
68
|
+
*/
|
|
69
|
+
function executeServerSearch(params) {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const { server, pattern, exec_path, projectRootForServer, options, minScore, } = params;
|
|
72
|
+
try {
|
|
73
|
+
const response = yield fetch(`http://localhost:${server.port}/search`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
query: pattern,
|
|
78
|
+
limit: parseInt(options.m, 10),
|
|
79
|
+
path: exec_path
|
|
80
|
+
? path.relative(projectRootForServer, path.resolve(exec_path))
|
|
81
|
+
: undefined,
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
if (response.ok) {
|
|
85
|
+
const body = (yield response.json());
|
|
86
|
+
const searchResult = { data: body.results };
|
|
87
|
+
const filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
|
|
88
|
+
if (options.skeleton) {
|
|
89
|
+
yield (0, search_skeletons_1.outputSkeletons)(filteredData, projectRootForServer, parseInt(options.m, 10),
|
|
90
|
+
// Server doesn't easily expose DB instance here in HTTP client mode,
|
|
91
|
+
// but we are in client. Wait, this text implies "Server Search" block.
|
|
92
|
+
// Client talks to server. The server returns JSON.
|
|
93
|
+
// We don't have DB access here.
|
|
94
|
+
// So we pass null, and it will fallback to generating local skeleton (if file exists locally).
|
|
95
|
+
// This is acceptable for Phase 3.
|
|
96
|
+
null);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
const compactHits = options.compact ? (0, compact_results_1.toCompactHits)(filteredData) : [];
|
|
100
|
+
if (options.compact) {
|
|
101
|
+
if (!compactHits.length) {
|
|
102
|
+
console.log("No matches found.");
|
|
103
|
+
console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
console.log((0, compact_results_1.formatCompactTable)(compactHits, projectRootForServer, pattern, {
|
|
108
|
+
isTTY: !!process.stdout.isTTY,
|
|
109
|
+
plain: !!options.plain,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
return true; // EXIT
|
|
113
|
+
}
|
|
114
|
+
if (!filteredData.length) {
|
|
115
|
+
console.log("No matches found.");
|
|
116
|
+
console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return true; // EXIT
|
|
119
|
+
}
|
|
120
|
+
if (options.agent) {
|
|
121
|
+
const importCache = new Map();
|
|
122
|
+
const getImportsForFile = (absPath) => {
|
|
123
|
+
var _a;
|
|
124
|
+
if (!options.imports || !absPath)
|
|
125
|
+
return "";
|
|
126
|
+
if (!importCache.has(absPath)) {
|
|
127
|
+
importCache.set(absPath, (0, import_extractor_1.extractImports)(absPath));
|
|
128
|
+
}
|
|
129
|
+
return (_a = importCache.get(absPath)) !== null && _a !== void 0 ? _a : "";
|
|
130
|
+
};
|
|
131
|
+
console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, projectRootForServer, {
|
|
132
|
+
includeImports: options.imports,
|
|
133
|
+
query: pattern,
|
|
134
|
+
getImportsForFile,
|
|
135
|
+
explain: options.explain,
|
|
136
|
+
}));
|
|
137
|
+
return true; // EXIT
|
|
138
|
+
}
|
|
139
|
+
const isTTY = process.stdout.isTTY;
|
|
140
|
+
const shouldBePlain = options.plain || !isTTY;
|
|
141
|
+
if (!options.agent && !options.compact) {
|
|
142
|
+
console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
|
|
143
|
+
console.log();
|
|
144
|
+
}
|
|
145
|
+
if (shouldBePlain) {
|
|
146
|
+
const mappedResults = (0, compact_results_1.toTextResults)(filteredData);
|
|
147
|
+
const output = (0, formatter_1.formatTextResults)(mappedResults, pattern, projectRootForServer, {
|
|
148
|
+
isPlain: true,
|
|
149
|
+
compact: options.compact,
|
|
150
|
+
content: options.content,
|
|
151
|
+
perFile: parseInt(options.perFile, 10),
|
|
152
|
+
showScores: options.scores,
|
|
153
|
+
});
|
|
154
|
+
console.log(output);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
|
|
158
|
+
const output = formatResults(filteredData, projectRootForServer, {
|
|
159
|
+
content: options.content,
|
|
160
|
+
explain: options.explain,
|
|
161
|
+
});
|
|
162
|
+
console.log(output);
|
|
163
|
+
}
|
|
164
|
+
return true; // EXIT successful server search
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
if (process.env.DEBUG) {
|
|
169
|
+
console.error("[search] server request failed, falling back to local:", e);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Presentation stage shared by the daemon-mediated and in-process search paths.
|
|
177
|
+
* Applies the min-score + name-regex post-filters, then dispatches to one of the
|
|
178
|
+
* seven presentation modes (cross-project, agent, skeleton, no-results, compact,
|
|
179
|
+
* context-for-llm, standard). Writes directly to stdout/stderr and may set
|
|
180
|
+
* `process.exitCode`. Returns the post-filter result count for query logging.
|
|
181
|
+
*/
|
|
182
|
+
function renderSearchOutput(params) {
|
|
183
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
184
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
185
|
+
const { searchResult, options, minScore, crossProject, pattern, effectiveRoot, projectRoot, vectorDb, precomputedSkeletons, precomputedGraph, indexState, } = params;
|
|
186
|
+
if (!options.agent && ((_a = searchResult.warnings) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
187
|
+
for (const w of searchResult.warnings) {
|
|
188
|
+
console.warn(`Warning: ${w}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Partial-index signal (Phase 6): when the index is mid-catchup, results
|
|
192
|
+
// may be incomplete. Non-agent renders it now as a warning; agent mode
|
|
193
|
+
// appends a machine-readable footer after the results below.
|
|
194
|
+
if (!options.agent) {
|
|
195
|
+
const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, { agent: false });
|
|
196
|
+
if (footer)
|
|
197
|
+
console.warn(footer);
|
|
198
|
+
}
|
|
199
|
+
let filteredData = searchResult.data.filter((r) => typeof r.score !== "number" || r.score >= minScore);
|
|
200
|
+
// Post-filter by symbol name regex
|
|
201
|
+
if (options.name) {
|
|
202
|
+
try {
|
|
203
|
+
const regex = new RegExp(options.name, "i");
|
|
204
|
+
filteredData = filteredData.filter((r) => {
|
|
205
|
+
const defs = Array.isArray(r.defined_symbols) ? r.defined_symbols : [];
|
|
206
|
+
return defs.some((d) => regex.test(d));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch (_h) {
|
|
210
|
+
// Invalid regex — skip
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Build import cache when --imports is requested
|
|
214
|
+
const importCache = new Map();
|
|
215
|
+
const getImportsForFile = (absPath) => {
|
|
216
|
+
var _a;
|
|
217
|
+
if (!options.imports || !absPath)
|
|
218
|
+
return "";
|
|
219
|
+
if (!importCache.has(absPath)) {
|
|
220
|
+
importCache.set(absPath, (0, import_extractor_1.extractImports)(absPath));
|
|
221
|
+
}
|
|
222
|
+
return (_a = importCache.get(absPath)) !== null && _a !== void 0 ? _a : "";
|
|
223
|
+
};
|
|
224
|
+
// Agent mode: ultra-compact one-line-per-result output
|
|
225
|
+
const resultCount = filteredData.length;
|
|
226
|
+
// Cross-project (Phase 6): render grouped by owning project so idioms
|
|
227
|
+
// from different stacks don't blur into one flat list. Only the
|
|
228
|
+
// string-formatter modes reach here — skeleton/context-for-llm/symbol
|
|
229
|
+
// were rejected up front.
|
|
230
|
+
if (crossProject.active) {
|
|
231
|
+
const emitFooter = () => {
|
|
232
|
+
const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, {
|
|
233
|
+
agent: !!options.agent,
|
|
234
|
+
});
|
|
235
|
+
if (footer) {
|
|
236
|
+
if (options.agent)
|
|
237
|
+
console.log(footer);
|
|
238
|
+
else
|
|
239
|
+
console.warn(footer);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
if (!filteredData.length) {
|
|
243
|
+
console.log(options.agent ? "(none)" : "No matches found.");
|
|
244
|
+
process.exitCode = 1;
|
|
245
|
+
emitFooter();
|
|
246
|
+
return { resultCount };
|
|
247
|
+
}
|
|
248
|
+
const getPath = (r) => {
|
|
249
|
+
var _a, _b, _c;
|
|
250
|
+
return String((_c = (_a = r.path) !== null && _a !== void 0 ? _a : (_b = r.metadata) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : "");
|
|
251
|
+
};
|
|
252
|
+
const groups = (0, cross_project_1.groupResultsByProject)(filteredData, crossProject.roots, getPath);
|
|
253
|
+
const isTTY = process.stdout.isTTY;
|
|
254
|
+
const shouldBePlain = options.plain || !isTTY;
|
|
255
|
+
const blocks = [];
|
|
256
|
+
for (const g of groups) {
|
|
257
|
+
let body;
|
|
258
|
+
if (options.agent) {
|
|
259
|
+
body = (0, agent_search_formatter_1.formatAgentSearchResults)(g.items, g.root, {
|
|
260
|
+
includeImports: options.imports,
|
|
261
|
+
query: pattern,
|
|
262
|
+
getImportsForFile,
|
|
263
|
+
explain: options.explain,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
else if (options.compact) {
|
|
267
|
+
body = (0, compact_results_1.formatCompactTable)((0, compact_results_1.toCompactHits)(g.items), g.root, pattern, {
|
|
268
|
+
isTTY: !!isTTY,
|
|
269
|
+
plain: !!options.plain,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
else if (shouldBePlain) {
|
|
273
|
+
body = (0, formatter_1.formatTextResults)((0, compact_results_1.toTextResults)(g.items), pattern, g.root, {
|
|
274
|
+
isPlain: true,
|
|
275
|
+
compact: options.compact,
|
|
276
|
+
content: options.content,
|
|
277
|
+
perFile: parseInt(options.perFile, 10),
|
|
278
|
+
showScores: options.scores,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
|
|
283
|
+
body = formatResults(g.items, g.root, {
|
|
284
|
+
content: options.content,
|
|
285
|
+
explain: options.explain,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const header = options.agent
|
|
289
|
+
? `## ${g.name} (${g.items.length})`
|
|
290
|
+
: `=== ${g.name} (${g.items.length}) ===`;
|
|
291
|
+
blocks.push(`${header}\n${body}`);
|
|
292
|
+
}
|
|
293
|
+
console.log(blocks.join("\n\n"));
|
|
294
|
+
emitFooter();
|
|
295
|
+
return { resultCount };
|
|
296
|
+
}
|
|
297
|
+
if (options.agent) {
|
|
298
|
+
if (!filteredData.length) {
|
|
299
|
+
console.log("(none)");
|
|
300
|
+
process.exitCode = 1;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, effectiveRoot, {
|
|
304
|
+
includeImports: options.imports,
|
|
305
|
+
query: pattern,
|
|
306
|
+
getImportsForFile,
|
|
307
|
+
explain: options.explain,
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
// Agent trace (compact)
|
|
311
|
+
if (options.symbol && filteredData.length > 0) {
|
|
312
|
+
try {
|
|
313
|
+
let graph = precomputedGraph;
|
|
314
|
+
if (!graph) {
|
|
315
|
+
if (!vectorDb)
|
|
316
|
+
throw new Error("no graph source");
|
|
317
|
+
const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
|
|
318
|
+
const builder = new GraphBuilder(vectorDb, effectiveRoot);
|
|
319
|
+
graph = yield builder.buildGraphMultiHop(pattern, 1);
|
|
320
|
+
}
|
|
321
|
+
if (graph === null || graph === void 0 ? void 0 : graph.center) {
|
|
322
|
+
console.log("---");
|
|
323
|
+
for (const t of graph.callerTree) {
|
|
324
|
+
const rel = t.node.file.startsWith(effectiveRoot)
|
|
325
|
+
? t.node.file.slice(effectiveRoot.length + 1)
|
|
326
|
+
: t.node.file;
|
|
327
|
+
console.log(`<- ${t.node.symbol} ${rel}:${t.node.line + 1}`);
|
|
328
|
+
}
|
|
329
|
+
for (const c of graph.callees.slice(0, 10)) {
|
|
330
|
+
if (c.file) {
|
|
331
|
+
const rel = c.file.startsWith(effectiveRoot)
|
|
332
|
+
? c.file.slice(effectiveRoot.length + 1)
|
|
333
|
+
: c.file;
|
|
334
|
+
console.log(`-> ${c.symbol} ${rel}:${c.line + 1}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (_j) { }
|
|
340
|
+
}
|
|
341
|
+
// Partial-index footer last, so it's the final line the agent reads —
|
|
342
|
+
// and emitted even on "(none)", where an empty result may just mean the
|
|
343
|
+
// relevant files aren't indexed yet.
|
|
344
|
+
const footer = (0, index_state_footer_1.formatIndexStateFooter)(indexState, { agent: true });
|
|
345
|
+
if (footer)
|
|
346
|
+
console.log(footer);
|
|
347
|
+
return { resultCount };
|
|
348
|
+
}
|
|
349
|
+
if (options.skeleton) {
|
|
350
|
+
yield (0, search_skeletons_1.outputSkeletons)(filteredData, projectRoot, parseInt(options.m, 10), vectorDb, precomputedSkeletons);
|
|
351
|
+
return { resultCount };
|
|
352
|
+
}
|
|
353
|
+
if (!filteredData.length) {
|
|
354
|
+
console.log("No matches found.");
|
|
355
|
+
console.log("\nTry: broaden your query, use fewer keywords, or check `gmax status` to verify the project is indexed.");
|
|
356
|
+
process.exitCode = 1;
|
|
357
|
+
return { resultCount };
|
|
358
|
+
}
|
|
359
|
+
if (options.compact) {
|
|
360
|
+
const compactHits = (0, compact_results_1.toCompactHits)(filteredData);
|
|
361
|
+
console.log((0, compact_results_1.formatCompactTable)(compactHits, projectRoot, pattern, {
|
|
362
|
+
isTTY: !!process.stdout.isTTY,
|
|
363
|
+
plain: !!options.plain,
|
|
364
|
+
}));
|
|
365
|
+
return { resultCount };
|
|
366
|
+
}
|
|
367
|
+
// Context-for-LLM mode: full function body + imports per result
|
|
368
|
+
if (options.contextForLlm) {
|
|
369
|
+
const fs = yield Promise.resolve().then(() => __importStar(require("node:fs")));
|
|
370
|
+
const { extractImportsFromContent } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/import-extractor")));
|
|
371
|
+
const { packByBudget } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/budget-pack")));
|
|
372
|
+
const budget = parseInt(options.budget, 10) || 8000;
|
|
373
|
+
console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
|
|
374
|
+
// Build every candidate blob up front (token cost needs the rendered
|
|
375
|
+
// text), then pack to budget. Token-aware packing skips an oversized
|
|
376
|
+
// chunk and keeps filling with smaller, still-relevant ones rather than
|
|
377
|
+
// aborting the loop — recovering budget the old greedy `break` wasted.
|
|
378
|
+
const candidates = filteredData.map((r, idx) => {
|
|
379
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
380
|
+
const absP = (_c = (_a = r.path) !== null && _a !== void 0 ? _a : (_b = r.metadata) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : "";
|
|
381
|
+
const startLine = (_g = (_e = (_d = r.startLine) !== null && _d !== void 0 ? _d : r.start_line) !== null && _e !== void 0 ? _e : (_f = r.generated_metadata) === null || _f === void 0 ? void 0 : _f.start_line) !== null && _g !== void 0 ? _g : 0;
|
|
382
|
+
const endLine = (_l = (_j = (_h = r.endLine) !== null && _h !== void 0 ? _h : r.end_line) !== null && _j !== void 0 ? _j : (_k = r.generated_metadata) === null || _k === void 0 ? void 0 : _k.end_line) !== null && _l !== void 0 ? _l : startLine;
|
|
383
|
+
const relPath = absP.startsWith(projectRoot)
|
|
384
|
+
? absP.slice(projectRoot.length + 1)
|
|
385
|
+
: absP;
|
|
386
|
+
const role = r.role || "IMPLEMENTATION";
|
|
387
|
+
const symbol = Array.isArray(r.defined_symbols) &&
|
|
388
|
+
r.defined_symbols.length > 0
|
|
389
|
+
? r.defined_symbols[0]
|
|
390
|
+
: "";
|
|
391
|
+
let blobText;
|
|
392
|
+
try {
|
|
393
|
+
const content = fs.readFileSync(absP, "utf-8");
|
|
394
|
+
const allLines = content.split("\n");
|
|
395
|
+
const body = allLines
|
|
396
|
+
.slice(startLine, Math.min(endLine + 1, allLines.length))
|
|
397
|
+
.join("\n");
|
|
398
|
+
const imports = extractImportsFromContent(content);
|
|
399
|
+
const blob = [
|
|
400
|
+
`--- ${relPath}:${startLine + 1}${symbol ? ` ${symbol}` : ""} [${role}] ---`,
|
|
401
|
+
];
|
|
402
|
+
if (imports)
|
|
403
|
+
blob.push("[imports]", imports, "");
|
|
404
|
+
blob.push("[body]", body);
|
|
405
|
+
blobText = blob.join("\n");
|
|
406
|
+
}
|
|
407
|
+
catch (_m) {
|
|
408
|
+
blobText = `--- ${relPath} (file not readable) ---`;
|
|
409
|
+
}
|
|
410
|
+
// Preserve relevance order when scores are absent (rank-derived
|
|
411
|
+
// fallback) so the density tiebreaker never reshuffles arbitrarily.
|
|
412
|
+
const score = typeof r.score === "number"
|
|
413
|
+
? r.score
|
|
414
|
+
: (filteredData.length - idx) / filteredData.length;
|
|
415
|
+
return { blobText, tokens: Math.ceil(blobText.length / 4), score };
|
|
416
|
+
});
|
|
417
|
+
const pack = packByBudget(candidates.map((c) => ({ tokens: c.tokens, score: c.score })), budget);
|
|
418
|
+
for (const i of pack.selected) {
|
|
419
|
+
console.log(`\n${candidates[i].blobText}`);
|
|
420
|
+
}
|
|
421
|
+
if (pack.dropped > 0) {
|
|
422
|
+
console.log(`\n(budget: ~${pack.tokensUsed}/${budget} tokens, ${pack.dropped} lower-density result${pack.dropped > 1 ? "s" : ""} not shown)`);
|
|
423
|
+
}
|
|
424
|
+
return { resultCount };
|
|
425
|
+
}
|
|
426
|
+
const isTTY = process.stdout.isTTY;
|
|
427
|
+
const shouldBePlain = options.plain || !isTTY;
|
|
428
|
+
if (!options.agent && !options.compact) {
|
|
429
|
+
console.log((0, compact_results_1.resultCountHeader)(filteredData, parseInt(options.m, 10)));
|
|
430
|
+
console.log();
|
|
431
|
+
}
|
|
432
|
+
// Print imports per unique file before results when --imports is used
|
|
433
|
+
if (options.imports) {
|
|
434
|
+
const seenFiles = new Set();
|
|
435
|
+
for (const r of filteredData) {
|
|
436
|
+
const absP = (_d = (_b = r.path) !== null && _b !== void 0 ? _b : (_c = r.metadata) === null || _c === void 0 ? void 0 : _c.path) !== null && _d !== void 0 ? _d : "";
|
|
437
|
+
if (absP && !seenFiles.has(absP)) {
|
|
438
|
+
seenFiles.add(absP);
|
|
439
|
+
const imports = getImportsForFile(absP);
|
|
440
|
+
if (imports) {
|
|
441
|
+
const relP = absP.startsWith(effectiveRoot)
|
|
442
|
+
? absP.slice(effectiveRoot.length + 1)
|
|
443
|
+
: absP;
|
|
444
|
+
console.log(`--- imports: ${relP} ---\n${imports}\n`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (shouldBePlain) {
|
|
450
|
+
const mappedResults = (0, compact_results_1.toTextResults)(filteredData);
|
|
451
|
+
const output = (0, formatter_1.formatTextResults)(mappedResults, pattern, projectRoot, {
|
|
452
|
+
isPlain: true,
|
|
453
|
+
compact: options.compact,
|
|
454
|
+
content: options.content,
|
|
455
|
+
perFile: parseInt(options.perFile, 10),
|
|
456
|
+
showScores: options.scores,
|
|
457
|
+
});
|
|
458
|
+
console.log(output);
|
|
459
|
+
if (options.explain) {
|
|
460
|
+
for (const r of filteredData) {
|
|
461
|
+
const b = r.scoreBreakdown;
|
|
462
|
+
if (b) {
|
|
463
|
+
const absP = (_g = (_e = r.path) !== null && _e !== void 0 ? _e : (_f = r.metadata) === null || _f === void 0 ? void 0 : _f.path) !== null && _g !== void 0 ? _g : "";
|
|
464
|
+
const relPath = absP.startsWith(projectRoot)
|
|
465
|
+
? absP.slice(projectRoot.length + 1)
|
|
466
|
+
: absP;
|
|
467
|
+
console.log(` [explain ${relPath}] rerank=${b.rerank.toFixed(3)} fused=${b.fused.toFixed(3)} boost=${b.boost.toFixed(2)}x final=${b.normalized.toFixed(3)}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// Use new holographic formatter for TTY
|
|
474
|
+
const { formatResults } = yield Promise.resolve().then(() => __importStar(require("../lib/output/formatter")));
|
|
475
|
+
const output = formatResults(filteredData, projectRoot, {
|
|
476
|
+
content: options.content,
|
|
477
|
+
explain: options.explain,
|
|
478
|
+
});
|
|
479
|
+
console.log(output);
|
|
480
|
+
}
|
|
481
|
+
// Symbol mode: append call graph
|
|
482
|
+
if (options.symbol) {
|
|
483
|
+
try {
|
|
484
|
+
let graph = precomputedGraph;
|
|
485
|
+
if (!graph) {
|
|
486
|
+
if (!vectorDb)
|
|
487
|
+
throw new Error("no graph source");
|
|
488
|
+
const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
|
|
489
|
+
const builder = new GraphBuilder(vectorDb, effectiveRoot);
|
|
490
|
+
graph = yield builder.buildGraphMultiHop(pattern, 1);
|
|
491
|
+
}
|
|
492
|
+
if (graph === null || graph === void 0 ? void 0 : graph.center) {
|
|
493
|
+
const lines = ["\n--- Call graph ---"];
|
|
494
|
+
const centerRel = path.relative(effectiveRoot, graph.center.file);
|
|
495
|
+
lines.push(`${graph.center.symbol} [${graph.center.role}] ${centerRel}:${graph.center.line + 1}`);
|
|
496
|
+
if (graph.importers.length > 0) {
|
|
497
|
+
const filtered = graph.importers.filter((p) => p !== graph.center.file);
|
|
498
|
+
if (filtered.length > 0) {
|
|
499
|
+
lines.push("Imported by:");
|
|
500
|
+
for (const imp of filtered.slice(0, 10)) {
|
|
501
|
+
lines.push(` ${path.relative(effectiveRoot, imp)}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (graph.callerTree.length > 0) {
|
|
506
|
+
lines.push("Callers:");
|
|
507
|
+
for (const t of graph.callerTree) {
|
|
508
|
+
lines.push(` <- ${t.node.symbol} ${path.relative(effectiveRoot, t.node.file)}:${t.node.line + 1}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (graph.callees.length > 0) {
|
|
512
|
+
lines.push("Calls:");
|
|
513
|
+
for (const c of graph.callees.slice(0, 15)) {
|
|
514
|
+
if (c.file) {
|
|
515
|
+
lines.push(` -> ${c.symbol} ${path.relative(effectiveRoot, c.file)}:${c.line + 1}`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
lines.push(` -> ${c.symbol} (not indexed)`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
console.log(lines.join("\n"));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (_k) {
|
|
526
|
+
// Trace failed — skip silently
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return { resultCount };
|
|
530
|
+
});
|
|
531
|
+
}
|