grepmax 0.26.26 → 0.26.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +40 -0
- package/dist/commands/audit.js +28 -260
- package/dist/commands/context.js +42 -26
- package/dist/commands/dead.js +37 -35
- package/dist/commands/doctor.js +280 -10
- package/dist/commands/extract.js +79 -49
- package/dist/commands/impact.js +74 -20
- package/dist/commands/log.js +25 -25
- package/dist/commands/mcp.js +622 -474
- package/dist/commands/peek.js +49 -47
- package/dist/commands/project.js +29 -107
- package/dist/commands/related.js +43 -52
- package/dist/commands/search-run.js +23 -8
- package/dist/commands/search.js +7 -2
- package/dist/commands/similar.js +50 -76
- package/dist/commands/skeleton.js +137 -83
- package/dist/commands/status.js +90 -25
- package/dist/commands/surprises.js +73 -16
- package/dist/commands/symbols.js +58 -77
- package/dist/commands/test-find.js +51 -17
- package/dist/commands/trace.js +26 -11
- package/dist/lib/daemon/daemon.js +26 -0
- package/dist/lib/daemon/graph-handler.js +853 -0
- package/dist/lib/daemon/ipc-handler.js +19 -2
- package/dist/lib/daemon/read-verbs.js +105 -0
- package/dist/lib/daemon/rows-handler.js +665 -0
- package/dist/lib/daemon/vector-handler.js +199 -0
- package/dist/lib/store/vector-db.js +1 -1
- package/dist/lib/utils/daemon-client.js +11 -0
- package/dist/lib/utils/store-access.js +381 -0
- package/dist/lib/utils/tests-footer.js +19 -2
- package/dist/lib/utils/watcher-launcher.js +10 -0
- package/package.json +2 -2
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/scripts/postinstall.js +0 -69
|
@@ -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.
|
|
13
|
+
"version": "0.26.28",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/README.md
CHANGED
|
@@ -217,6 +217,45 @@ gmax status # See all projects + watcher status
|
|
|
217
217
|
|
|
218
218
|
The daemon auto-starts when you run `gmax add`, `gmax index`, `gmax remove`, `gmax summarize`, or `gmax mcp`. It shuts down after 30 minutes of inactivity. File-change batches are processed concurrently while reserving worker capacity for searches; LanceDB compaction runs after writes rather than on every maintenance tick.
|
|
219
219
|
|
|
220
|
+
## Running under the Claude Code sandbox
|
|
221
|
+
|
|
222
|
+
Claude Code's Bash sandbox (the default on macOS) allows writes only under the working directory,
|
|
223
|
+
the added directories, and the session temp dir, and blocks every Unix socket unless it is listed.
|
|
224
|
+
Child processes inherit the profile, so a gmax command run from an agent shell inherits it too —
|
|
225
|
+
and gmax needs both kinds of access. A read command either asks the daemon over
|
|
226
|
+
`~/.gmax/daemon.sock`, or, with no daemon running, opens the shared store itself; opening the store
|
|
227
|
+
takes a lease, which means a `mkdir` under `~/.gmax`. A "read" is a writer as far as the filesystem
|
|
228
|
+
is concerned.
|
|
229
|
+
|
|
230
|
+
Two settings keys cover it (`~/.claude/settings.json`, or the project's `.claude/settings.json`):
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
"sandbox": {
|
|
234
|
+
"network": { "allowUnixSockets": ["~/.gmax/daemon.sock"] },
|
|
235
|
+
"filesystem": { "allowWrite": ["~/.gmax"] }
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
| Key | What it unlocks |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `sandbox.network.allowUnixSockets` | Every read command — search, `test`, `impact`, `trace`, `peek`, `status` — via the daemon. This is the one that matters day to day. |
|
|
242
|
+
| `sandbox.filesystem.allowWrite` | The in-process fallback, used only when no daemon is running (autostart disabled, CI). With a daemon up, nothing needs it. |
|
|
243
|
+
|
|
244
|
+
Without them gmax refuses rather than failing with a bare `EPERM`, exits `2`, and prints the key to add:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
gmax: cannot reach the daemon socket from this sandbox. Add to Claude Code settings: "sandbox": {"network": {"allowUnixSockets": ["~/.gmax/daemon.sock"]}}
|
|
248
|
+
gmax: cannot open the store from this sandbox. Add to Claude Code settings: "sandbox": {"filesystem": {"allowWrite": ["~/.gmax"]}}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`gmax doctor` reports the same thing ahead of time as `WARN Claude Code sandbox` whenever your
|
|
252
|
+
settings enable the sandbox without allowing the socket or the store directory. It never edits
|
|
253
|
+
Claude Code settings, `--fix` included — they are not gmax's files to write.
|
|
254
|
+
|
|
255
|
+
On Linux the per-socket list does not exist; the only option is
|
|
256
|
+
`"network": { "allowAllUnixSockets": true }`. See
|
|
257
|
+
[`docs/known-limitations.md`](docs/known-limitations.md).
|
|
258
|
+
|
|
220
259
|
## Local LLM (optional)
|
|
221
260
|
|
|
222
261
|
gmax can use a local LLM (via llama-server) for agentic codebase investigation. This is entirely opt-in and disabled by default — gmax works fine without it.
|
|
@@ -413,6 +452,7 @@ whether you need to act.
|
|
|
413
452
|
| `Optimize panicked ... inverted/builder.rs` | Upstream Lance bug in incremental FTS merge ([lance#8310](https://github.com/lance-format/lance/issues/8310)). gmax rebuilds the index and retries automatically; no data loss, no action needed. |
|
|
414
453
|
| `disabling auto-rebuild until an optimize succeeds` | Transient, not a wedge. The next successful optimize clears it. Search stays available. |
|
|
415
454
|
| `ANN: vector index not built` | Normal — exact search is the default. |
|
|
455
|
+
| `cannot reach the daemon socket from this sandbox` (exit 2) | The shell is sandboxed. Add the two keys in [Running under the Claude Code sandbox](#running-under-the-claude-code-sandbox); `gmax doctor` warns about the same gap. |
|
|
416
456
|
| `npm audit` reports advisories on install | Two transitive advisories (`sharp`, `adm-zip`) have no upstream fix yet; neither is on a code path gmax executes. |
|
|
417
457
|
|
|
418
458
|
## Contributing
|
package/dist/commands/audit.js
CHANGED
|
@@ -43,14 +43,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.audit = void 0;
|
|
46
|
-
exports.computeAudit = computeAudit;
|
|
47
46
|
const commander_1 = require("commander");
|
|
48
|
-
const
|
|
47
|
+
const graph_handler_1 = require("../lib/daemon/graph-handler");
|
|
49
48
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
50
|
-
const arrow_1 = require("../lib/utils/arrow");
|
|
51
49
|
const exit_1 = require("../lib/utils/exit");
|
|
52
50
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
53
51
|
const project_root_1 = require("../lib/utils/project-root");
|
|
52
|
+
const store_access_1 = require("../lib/utils/store-access");
|
|
54
53
|
const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
55
54
|
const style = {
|
|
56
55
|
bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
|
|
@@ -59,222 +58,6 @@ const style = {
|
|
|
59
58
|
yellow: (s) => (useColors ? `\x1b[33m${s}\x1b[39m` : s),
|
|
60
59
|
cyan: (s) => (useColors ? `\x1b[36m${s}\x1b[39m` : s),
|
|
61
60
|
};
|
|
62
|
-
// Names too generic to be useful as god-node signal (`id`, `el`, `fn`, …).
|
|
63
|
-
const MIN_GOD_NAME_LEN = 3;
|
|
64
|
-
function rel(p, prefix) {
|
|
65
|
-
return p.startsWith(prefix) ? p.slice(prefix.length) : p;
|
|
66
|
-
}
|
|
67
|
-
function findFileCycles(deps, prefix, top) {
|
|
68
|
-
const indexByFile = new Map();
|
|
69
|
-
const lowlink = new Map();
|
|
70
|
-
const stack = [];
|
|
71
|
-
const onStack = new Set();
|
|
72
|
-
const components = [];
|
|
73
|
-
let index = 0;
|
|
74
|
-
const strongConnect = (file) => {
|
|
75
|
-
var _a, _b, _c, _d, _e;
|
|
76
|
-
indexByFile.set(file, index);
|
|
77
|
-
lowlink.set(file, index);
|
|
78
|
-
index++;
|
|
79
|
-
stack.push(file);
|
|
80
|
-
onStack.add(file);
|
|
81
|
-
for (const dep of (_a = deps.get(file)) !== null && _a !== void 0 ? _a : []) {
|
|
82
|
-
if (!indexByFile.has(dep)) {
|
|
83
|
-
strongConnect(dep);
|
|
84
|
-
lowlink.set(file, Math.min((_b = lowlink.get(file)) !== null && _b !== void 0 ? _b : 0, (_c = lowlink.get(dep)) !== null && _c !== void 0 ? _c : 0));
|
|
85
|
-
}
|
|
86
|
-
else if (onStack.has(dep)) {
|
|
87
|
-
lowlink.set(file, Math.min((_d = lowlink.get(file)) !== null && _d !== void 0 ? _d : 0, (_e = indexByFile.get(dep)) !== null && _e !== void 0 ? _e : 0));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (lowlink.get(file) !== indexByFile.get(file))
|
|
91
|
-
return;
|
|
92
|
-
const component = [];
|
|
93
|
-
while (stack.length > 0) {
|
|
94
|
-
const dep = stack.pop();
|
|
95
|
-
onStack.delete(dep);
|
|
96
|
-
component.push(dep);
|
|
97
|
-
if (dep === file)
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
if (component.length > 1)
|
|
101
|
-
components.push(component);
|
|
102
|
-
};
|
|
103
|
-
const files = new Set();
|
|
104
|
-
for (const [file, targets] of deps) {
|
|
105
|
-
files.add(file);
|
|
106
|
-
for (const target of targets)
|
|
107
|
-
files.add(target);
|
|
108
|
-
}
|
|
109
|
-
for (const file of [...files].sort()) {
|
|
110
|
-
if (!indexByFile.has(file))
|
|
111
|
-
strongConnect(file);
|
|
112
|
-
}
|
|
113
|
-
return components
|
|
114
|
-
.map((component) => {
|
|
115
|
-
var _a;
|
|
116
|
-
const set = new Set(component);
|
|
117
|
-
let edgeCount = 0;
|
|
118
|
-
for (const file of component) {
|
|
119
|
-
for (const dep of (_a = deps.get(file)) !== null && _a !== void 0 ? _a : []) {
|
|
120
|
-
if (set.has(dep))
|
|
121
|
-
edgeCount++;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return {
|
|
125
|
-
files: component.map((f) => rel(f, prefix)).sort(),
|
|
126
|
-
edgeCount,
|
|
127
|
-
};
|
|
128
|
-
})
|
|
129
|
-
.sort((a, b) => b.files.length - a.files.length ||
|
|
130
|
-
b.edgeCount - a.edgeCount ||
|
|
131
|
-
a.files.join("\0").localeCompare(b.files.join("\0")))
|
|
132
|
-
.slice(0, top);
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Pure aggregation over chunk rows — no DB, no I/O. Builds the symbol→def map,
|
|
136
|
-
* cross-file inbound edges, and per-file fan-in/out, then derives god nodes
|
|
137
|
-
* (most depended-upon symbols), hub files (most depended-upon files), and
|
|
138
|
-
* dead-code candidates (non-exported symbols with zero inbound references).
|
|
139
|
-
* `top` caps each list; `deadTotal` reports the full pre-cap dead count.
|
|
140
|
-
*/
|
|
141
|
-
function computeAudit(rows, prefix, top) {
|
|
142
|
-
var _a, _b;
|
|
143
|
-
// First definition of a symbol wins (matches GraphBuilder semantics).
|
|
144
|
-
const defs = new Map();
|
|
145
|
-
// Distinct files defining each name — name-based edges can't tell same-name
|
|
146
|
-
// symbols apart, so multi-file definitions get flagged in the output.
|
|
147
|
-
const defFileCounts = new Map();
|
|
148
|
-
// Distinct files that reference a symbol (cross-file inbound edges).
|
|
149
|
-
const inboundFiles = new Map();
|
|
150
|
-
const inboundTotal = new Map();
|
|
151
|
-
// Per-file aggregates.
|
|
152
|
-
const fileDefs = new Map();
|
|
153
|
-
const fileOutRefs = new Map();
|
|
154
|
-
const files = new Set();
|
|
155
|
-
for (const row of rows) {
|
|
156
|
-
const file = String(row.path || "");
|
|
157
|
-
const line = Number(row.start_line || 0);
|
|
158
|
-
const exported = Boolean(row.is_exported);
|
|
159
|
-
const defSyms = (0, arrow_1.toArr)(row.defined_symbols);
|
|
160
|
-
const refSyms = (0, arrow_1.toArr)(row.referenced_symbols);
|
|
161
|
-
files.add(file);
|
|
162
|
-
for (const s of defSyms) {
|
|
163
|
-
if (!defs.has(s)) {
|
|
164
|
-
defs.set(s, { file, line, exported, complexity: 0 });
|
|
165
|
-
}
|
|
166
|
-
if (!defFileCounts.has(s))
|
|
167
|
-
defFileCounts.set(s, new Set());
|
|
168
|
-
defFileCounts.get(s).add(file);
|
|
169
|
-
if (!fileDefs.has(file))
|
|
170
|
-
fileDefs.set(file, new Set());
|
|
171
|
-
fileDefs.get(file).add(s);
|
|
172
|
-
}
|
|
173
|
-
for (const s of refSyms) {
|
|
174
|
-
if (!inboundFiles.has(s))
|
|
175
|
-
inboundFiles.set(s, new Set());
|
|
176
|
-
inboundFiles.get(s).add(file);
|
|
177
|
-
inboundTotal.set(s, (inboundTotal.get(s) || 0) + 1);
|
|
178
|
-
if (!fileOutRefs.has(file))
|
|
179
|
-
fileOutRefs.set(file, new Set());
|
|
180
|
-
fileOutRefs.get(file).add(s);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// God nodes — in-project symbols by distinct external inbound files.
|
|
184
|
-
const godNodes = [];
|
|
185
|
-
for (const [symbol, info] of defs) {
|
|
186
|
-
if (symbol.length < MIN_GOD_NAME_LEN)
|
|
187
|
-
continue;
|
|
188
|
-
// Builtin method names (get, set, push, …) leak in via prototype/member
|
|
189
|
-
// definitions and their inbound counts are meaningless name collisions.
|
|
190
|
-
if ((0, callsites_1.isBuiltinCallee)(symbol))
|
|
191
|
-
continue;
|
|
192
|
-
const refFiles = inboundFiles.get(symbol);
|
|
193
|
-
if (!refFiles)
|
|
194
|
-
continue;
|
|
195
|
-
let external = 0;
|
|
196
|
-
for (const f of refFiles)
|
|
197
|
-
if (f !== info.file)
|
|
198
|
-
external++;
|
|
199
|
-
if (external === 0)
|
|
200
|
-
continue;
|
|
201
|
-
godNodes.push({
|
|
202
|
-
symbol,
|
|
203
|
-
file: rel(info.file, prefix),
|
|
204
|
-
line: info.line,
|
|
205
|
-
inboundFiles: external,
|
|
206
|
-
totalRefs: inboundTotal.get(symbol) || 0,
|
|
207
|
-
defFiles: (_b = (_a = defFileCounts.get(symbol)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 1,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
godNodes.sort((a, b) => b.inboundFiles - a.inboundFiles || b.totalRefs - a.totalRefs);
|
|
211
|
-
// Hub files — distinct external files depending on each file (a file G
|
|
212
|
-
// depends on F if G references any symbol F defines).
|
|
213
|
-
const hubFiles = [];
|
|
214
|
-
for (const [file, syms] of fileDefs) {
|
|
215
|
-
const dependents = new Set();
|
|
216
|
-
for (const s of syms) {
|
|
217
|
-
const refFiles = inboundFiles.get(s);
|
|
218
|
-
if (!refFiles)
|
|
219
|
-
continue;
|
|
220
|
-
for (const f of refFiles)
|
|
221
|
-
if (f !== file)
|
|
222
|
-
dependents.add(f);
|
|
223
|
-
}
|
|
224
|
-
// Fan-out: distinct referenced symbols that are defined somewhere
|
|
225
|
-
// in-project (external-library calls don't count as coupling).
|
|
226
|
-
let fanOut = 0;
|
|
227
|
-
const out = fileOutRefs.get(file);
|
|
228
|
-
if (out)
|
|
229
|
-
for (const s of out)
|
|
230
|
-
if (defs.has(s))
|
|
231
|
-
fanOut++;
|
|
232
|
-
hubFiles.push({
|
|
233
|
-
file: rel(file, prefix),
|
|
234
|
-
dependents: dependents.size,
|
|
235
|
-
defines: syms.size,
|
|
236
|
-
fanOut,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
hubFiles.sort((a, b) => b.dependents - a.dependents || b.defines - a.defines);
|
|
240
|
-
const fileDeps = new Map();
|
|
241
|
-
for (const [file, refs] of fileOutRefs) {
|
|
242
|
-
for (const s of refs) {
|
|
243
|
-
if ((0, callsites_1.isBuiltinCallee)(s))
|
|
244
|
-
continue;
|
|
245
|
-
const defFiles = defFileCounts.get(s);
|
|
246
|
-
if (!defFiles || defFiles.size !== 1)
|
|
247
|
-
continue;
|
|
248
|
-
const [depFile] = defFiles;
|
|
249
|
-
if (!depFile || depFile === file)
|
|
250
|
-
continue;
|
|
251
|
-
if (!fileDeps.has(file))
|
|
252
|
-
fileDeps.set(file, new Set());
|
|
253
|
-
fileDeps.get(file).add(depFile);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
const fileCycles = findFileCycles(fileDeps, prefix, top);
|
|
257
|
-
// Dead candidates — non-exported in-project symbols with zero inbound
|
|
258
|
-
// references anywhere (including their own file).
|
|
259
|
-
const deadAll = [];
|
|
260
|
-
for (const [symbol, info] of defs) {
|
|
261
|
-
if (info.exported)
|
|
262
|
-
continue;
|
|
263
|
-
if ((inboundTotal.get(symbol) || 0) > 0)
|
|
264
|
-
continue;
|
|
265
|
-
deadAll.push({ symbol, file: rel(info.file, prefix), line: info.line });
|
|
266
|
-
}
|
|
267
|
-
deadAll.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
268
|
-
return {
|
|
269
|
-
scannedChunks: rows.length,
|
|
270
|
-
scannedFiles: files.size,
|
|
271
|
-
godNodes: godNodes.slice(0, top),
|
|
272
|
-
hubFiles: hubFiles.filter((h) => h.dependents > 0).slice(0, top),
|
|
273
|
-
fileCycles,
|
|
274
|
-
deadCandidates: deadAll.slice(0, top),
|
|
275
|
-
deadTotal: deadAll.length,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
61
|
function formatCycle(files) {
|
|
279
62
|
const shown = files.length > 6
|
|
280
63
|
? [...files.slice(0, 6), `... ${files.length - 6} more`]
|
|
@@ -374,67 +157,52 @@ exports.audit = new commander_1.Command("audit")
|
|
|
374
157
|
const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
|
|
375
158
|
if (root === null)
|
|
376
159
|
return;
|
|
377
|
-
|
|
160
|
+
// Opened only if the daemon is unreachable; withStoreRead decides.
|
|
161
|
+
const local = { db: null };
|
|
378
162
|
try {
|
|
379
163
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
380
|
-
const
|
|
381
|
-
? projectRoot
|
|
382
|
-
: `${projectRoot}/`;
|
|
383
|
-
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
384
|
-
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
385
|
-
const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
164
|
+
const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
386
165
|
const scope = resolveScope({
|
|
387
166
|
projectRoot,
|
|
388
167
|
in: opts.in,
|
|
389
168
|
exclude: opts.exclude,
|
|
390
169
|
});
|
|
391
170
|
const top = Math.max(1, Number.parseInt(opts.top, 10) || 10);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (rows.length === 0) {
|
|
171
|
+
// Up to 500k rows are read and aggregated wherever the store is open;
|
|
172
|
+
// what crosses the socket is the finished report.
|
|
173
|
+
const result = yield (0, graph_handler_1.callGraphVerb)("graph.audit", {
|
|
174
|
+
projectRoot,
|
|
175
|
+
scope,
|
|
176
|
+
payload: { top },
|
|
177
|
+
render: (resp) => { var _a; return (_a = resp.audit) !== null && _a !== void 0 ? _a : null; },
|
|
178
|
+
inProcess: () => {
|
|
179
|
+
var _a;
|
|
180
|
+
(_a = local.db) !== null && _a !== void 0 ? _a : (local.db = new vector_db_1.VectorDB((0, project_root_1.ensureProjectPaths)(projectRoot).lancedbDir));
|
|
181
|
+
return (0, graph_handler_1.runGraphAudit)(local.db, { projectRoot, scope, top });
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
if (result === null) {
|
|
407
185
|
console.log(opts.agent
|
|
408
186
|
? "(no indexed data)"
|
|
409
187
|
: `No indexed data for ${projectRoot}. Run: gmax index --path ${projectRoot}`);
|
|
410
188
|
process.exitCode = 1;
|
|
411
189
|
return;
|
|
412
190
|
}
|
|
413
|
-
const result = computeAudit(rows.map((r) => ({
|
|
414
|
-
path: String(r.path || ""),
|
|
415
|
-
start_line: Number(r.start_line || 0),
|
|
416
|
-
is_exported: Boolean(r.is_exported),
|
|
417
|
-
defined_symbols: (0, arrow_1.toArr)(r.defined_symbols),
|
|
418
|
-
// Union call-position + type-position edges so god-node ranking and
|
|
419
|
-
// dead-candidate detection see references made purely in type position.
|
|
420
|
-
referenced_symbols: [
|
|
421
|
-
...new Set([
|
|
422
|
-
...(0, arrow_1.toArr)(r.referenced_symbols),
|
|
423
|
-
...(0, arrow_1.toArr)(r.type_referenced_symbols),
|
|
424
|
-
]),
|
|
425
|
-
],
|
|
426
|
-
})), prefix, top);
|
|
427
191
|
console.log(opts.agent ? formatAgent(result) : formatHuman(result));
|
|
428
192
|
}
|
|
429
193
|
catch (error) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
194
|
+
// A sandbox refusal is already one actionable line; an "Audit failed:"
|
|
195
|
+
// prefix would bury the settings key that fixes it.
|
|
196
|
+
if (!(0, store_access_1.reportStoreAccessRefusal)(error)) {
|
|
197
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
198
|
+
console.error("Audit failed:", message);
|
|
199
|
+
process.exitCode = 1;
|
|
200
|
+
}
|
|
433
201
|
}
|
|
434
202
|
finally {
|
|
435
|
-
if (
|
|
203
|
+
if (local.db) {
|
|
436
204
|
try {
|
|
437
|
-
yield
|
|
205
|
+
yield local.db.close();
|
|
438
206
|
}
|
|
439
207
|
catch (err) {
|
|
440
208
|
console.error("Failed to close VectorDB:", err);
|
package/dist/commands/context.js
CHANGED
|
@@ -46,17 +46,18 @@ exports.context = void 0;
|
|
|
46
46
|
exports.findEnclosingSignature = findEnclosingSignature;
|
|
47
47
|
const fs = __importStar(require("node:fs"));
|
|
48
48
|
const commander_1 = require("commander");
|
|
49
|
+
const rows_handler_1 = require("../lib/daemon/rows-handler");
|
|
49
50
|
const searcher_1 = require("../lib/search/searcher");
|
|
50
51
|
const skeleton_1 = require("../lib/skeleton");
|
|
51
|
-
const vector_db_1 = require("../lib/store/vector-db");
|
|
52
52
|
const arrow_1 = require("../lib/utils/arrow");
|
|
53
53
|
const budget_pack_1 = require("../lib/utils/budget-pack");
|
|
54
|
+
const daemon_client_1 = require("../lib/utils/daemon-client");
|
|
54
55
|
const exit_1 = require("../lib/utils/exit");
|
|
55
56
|
const file_utils_1 = require("../lib/utils/file-utils");
|
|
56
|
-
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
57
57
|
const path_containment_1 = require("../lib/utils/path-containment");
|
|
58
58
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
59
59
|
const project_root_1 = require("../lib/utils/project-root");
|
|
60
|
+
const store_access_1 = require("../lib/utils/store-access");
|
|
60
61
|
function estimateTokens(text) {
|
|
61
62
|
return Math.ceil(text.length / 4);
|
|
62
63
|
}
|
|
@@ -169,14 +170,12 @@ exports.context = new commander_1.Command("context")
|
|
|
169
170
|
var _a, _b, _c;
|
|
170
171
|
const budget = Number.parseInt(opts.budget || "4000", 10) || 4000;
|
|
171
172
|
const maxResults = Number.parseInt(opts.maxResults || "10", 10) || 10;
|
|
172
|
-
let vectorDb = null;
|
|
173
173
|
try {
|
|
174
174
|
const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
|
|
175
175
|
if (root === null)
|
|
176
176
|
return;
|
|
177
177
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
178
178
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
179
|
-
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
180
179
|
const pathTarget = (0, path_containment_1.resolveContainedExistingPath)(projectRoot, topic, {
|
|
181
180
|
cwd: root,
|
|
182
181
|
onOutside: "throw",
|
|
@@ -187,10 +186,26 @@ exports.context = new commander_1.Command("context")
|
|
|
187
186
|
console.log(rendered.sections.join("\n"));
|
|
188
187
|
return;
|
|
189
188
|
}
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
const
|
|
189
|
+
// Phase 1: Semantic search. Routed through the daemon so the Searcher —
|
|
190
|
+
// and the PageRank cache under ~/.gmax/pagerank/ that it may write — lives
|
|
191
|
+
// in exactly one process.
|
|
192
|
+
const responseData = yield (0, store_access_1.withStoreRead)("context", {
|
|
193
|
+
daemon: () => (0, daemon_client_1.sendDaemonCommand)({
|
|
194
|
+
cmd: "search",
|
|
195
|
+
projectRoot,
|
|
196
|
+
query: topic,
|
|
197
|
+
limit: maxResults,
|
|
198
|
+
pathPrefix: projectRoot,
|
|
199
|
+
rerank: true,
|
|
200
|
+
}, { timeoutMs: 60000 }),
|
|
201
|
+
render: (resp) => { var _a; return ((_a = resp.data) !== null && _a !== void 0 ? _a : []); },
|
|
202
|
+
inProcess: () => (0, rows_handler_1.withLocalStore)(paths.lancedbDir, (deps) => __awaiter(void 0, void 0, void 0, function* () {
|
|
203
|
+
const searcher = new searcher_1.Searcher(deps.vectorDb);
|
|
204
|
+
const response = yield searcher.search(topic, maxResults, { rerank: true }, {}, `${projectRoot}/`);
|
|
205
|
+
return response.data;
|
|
206
|
+
})),
|
|
207
|
+
});
|
|
208
|
+
const scopedData = responseData.filter((result) => (0, path_containment_1.isPathWithin)(projectRoot, chunkPath(result)));
|
|
194
209
|
if (scopedData.length === 0) {
|
|
195
210
|
console.log(`No results found for "${topic}".`);
|
|
196
211
|
return;
|
|
@@ -322,23 +337,28 @@ exports.context = new commander_1.Command("context")
|
|
|
322
337
|
sections.push(skelSection.join(""));
|
|
323
338
|
}
|
|
324
339
|
// Phase 5: Related files summary
|
|
325
|
-
const table = yield vectorDb.ensureTable();
|
|
326
340
|
const allSymbols = new Set();
|
|
327
341
|
for (const r of scopedData) {
|
|
328
342
|
for (const s of (0, arrow_1.toArr)(r.defined_symbols))
|
|
329
343
|
allSymbols.add(s);
|
|
330
344
|
}
|
|
331
345
|
if (allSymbols.size > 0) {
|
|
332
|
-
const pathScope = (0, filter_builder_1.pathStartsWith)(`${projectRoot}/`);
|
|
333
346
|
const relatedCounts = new Map();
|
|
334
347
|
const searchedFiles = new Set(uniqueFiles);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
348
|
+
// One batched round trip for the ≤20 symbol lookups.
|
|
349
|
+
const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
350
|
+
const relatedRows = yield (0, rows_handler_1.readRows)({
|
|
351
|
+
name: "context",
|
|
352
|
+
projectRoot,
|
|
353
|
+
lancedbDir: paths.lancedbDir,
|
|
354
|
+
scope: resolveScope({ projectRoot }),
|
|
355
|
+
select: ["path"],
|
|
356
|
+
matches: [...allSymbols]
|
|
357
|
+
.slice(0, 20)
|
|
358
|
+
.map((symbol) => ({ kind: "referencedSymbol", symbol })),
|
|
359
|
+
limit: 5,
|
|
360
|
+
});
|
|
361
|
+
for (const rows of relatedRows) {
|
|
342
362
|
for (const row of rows) {
|
|
343
363
|
const p = String(row.path || "");
|
|
344
364
|
if (searchedFiles.has(p))
|
|
@@ -366,17 +386,13 @@ exports.context = new commander_1.Command("context")
|
|
|
366
386
|
console.log(sections.join("\n"));
|
|
367
387
|
}
|
|
368
388
|
catch (error) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
389
|
+
if (!(0, store_access_1.reportStoreAccessRefusal)(error)) {
|
|
390
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
391
|
+
console.error("Context generation failed:", msg);
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
}
|
|
372
394
|
}
|
|
373
395
|
finally {
|
|
374
|
-
if (vectorDb) {
|
|
375
|
-
try {
|
|
376
|
-
yield vectorDb.close();
|
|
377
|
-
}
|
|
378
|
-
catch (_e) { }
|
|
379
|
-
}
|
|
380
396
|
yield (0, exit_1.gracefulExit)();
|
|
381
397
|
}
|
|
382
398
|
}));
|
package/dist/commands/dead.js
CHANGED
|
@@ -44,15 +44,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.dead = void 0;
|
|
46
46
|
const commander_1 = require("commander");
|
|
47
|
-
const
|
|
48
|
-
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
47
|
+
const graph_handler_1 = require("../lib/daemon/graph-handler");
|
|
49
48
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
50
49
|
const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
51
50
|
const exit_1 = require("../lib/utils/exit");
|
|
52
|
-
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
53
51
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
54
52
|
const project_root_1 = require("../lib/utils/project-root");
|
|
55
53
|
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
54
|
+
const store_access_1 = require("../lib/utils/store-access");
|
|
56
55
|
const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
57
56
|
const style = {
|
|
58
57
|
bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
|
|
@@ -117,63 +116,66 @@ exports.dead = new commander_1.Command("dead")
|
|
|
117
116
|
const root = (0, project_registry_1.resolveRootOrExit)(opts.root);
|
|
118
117
|
if (root === null)
|
|
119
118
|
return;
|
|
120
|
-
|
|
119
|
+
// Opened only if the daemon is unreachable; withStoreRead decides.
|
|
120
|
+
const local = { db: null };
|
|
121
121
|
try {
|
|
122
122
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
123
123
|
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
124
124
|
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
125
|
-
const
|
|
126
|
-
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
127
|
-
const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
125
|
+
const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
128
126
|
const scope = resolveScope({
|
|
129
127
|
projectRoot,
|
|
130
128
|
in: opts.in,
|
|
131
129
|
exclude: opts.exclude,
|
|
132
130
|
});
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
// The defining chunk plus its inbound callers, counted store-side: the
|
|
132
|
+
// caller set can be large and only its size and top 3 are rendered.
|
|
133
|
+
const facts = yield (0, graph_handler_1.callGraphVerb)("graph.dead", {
|
|
134
|
+
projectRoot,
|
|
135
|
+
scope,
|
|
136
|
+
payload: { target: symbol },
|
|
137
|
+
render: (resp) => resp.dead,
|
|
138
|
+
inProcess: () => {
|
|
139
|
+
var _a;
|
|
140
|
+
(_a = local.db) !== null && _a !== void 0 ? _a : (local.db = new vector_db_1.VectorDB((0, project_root_1.ensureProjectPaths)(projectRoot).lancedbDir));
|
|
141
|
+
return (0, graph_handler_1.runGraphDead)(local.db, { symbol, scope });
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
if (!facts.found) {
|
|
142
145
|
console.log((0, agent_errors_1.symbolNotFoundLines)(symbol, { agent: opts.agent }).join("\n"));
|
|
143
146
|
process.exitCode = 1;
|
|
144
147
|
return;
|
|
145
148
|
}
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const callers = yield builder.getCallers(symbol, (0, languages_1.languageFamilyForPath)(defPath));
|
|
152
|
-
const status = callers.length === 0 ? (isExported ? "PUBLIC_EXPORT" : "DEAD") : "LIVE";
|
|
153
|
-
const topCallers = callers
|
|
154
|
-
.slice(0, TOP_CALLERS)
|
|
155
|
-
.map((c) => ({ file: c.file, line: c.line }));
|
|
149
|
+
const status = facts.callerCount === 0
|
|
150
|
+
? facts.isExported
|
|
151
|
+
? "PUBLIC_EXPORT"
|
|
152
|
+
: "DEAD"
|
|
153
|
+
: "LIVE";
|
|
156
154
|
const result = {
|
|
157
155
|
status,
|
|
158
156
|
symbol,
|
|
159
|
-
defPath,
|
|
160
|
-
defLine,
|
|
161
|
-
callerCount:
|
|
162
|
-
topCallers,
|
|
157
|
+
defPath: facts.defPath,
|
|
158
|
+
defLine: facts.defLine,
|
|
159
|
+
callerCount: facts.callerCount,
|
|
160
|
+
topCallers: facts.topCallers,
|
|
163
161
|
};
|
|
164
162
|
console.log(opts.agent
|
|
165
163
|
? formatAgent(result, projectRoot)
|
|
166
164
|
: formatHuman(result, projectRoot));
|
|
167
165
|
}
|
|
168
166
|
catch (error) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
// A sandbox refusal is already one actionable line; a "Dead check
|
|
168
|
+
// failed:" prefix would bury the settings key that fixes it.
|
|
169
|
+
if (!(0, store_access_1.reportStoreAccessRefusal)(error)) {
|
|
170
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
171
|
+
console.error("Dead check failed:", message);
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
}
|
|
172
174
|
}
|
|
173
175
|
finally {
|
|
174
|
-
if (
|
|
176
|
+
if (local.db) {
|
|
175
177
|
try {
|
|
176
|
-
yield
|
|
178
|
+
yield local.db.close();
|
|
177
179
|
}
|
|
178
180
|
catch (err) {
|
|
179
181
|
console.error("Failed to close VectorDB:", err);
|