grepmax 0.26.49 → 0.26.51
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 +10 -6
- package/dist/commands/claude-code.js +64 -32
- package/dist/commands/codex.js +26 -8
- package/dist/commands/plugin.js +2 -1
- package/dist/commands/search-output.js +22 -9
- package/dist/commands/search-run.js +6 -2
- package/dist/commands/search.js +10 -1
- package/dist/commands/serve.js +14 -8
- package/dist/commands/watch.js +8 -0
- package/dist/lib/daemon/ipc-handler.js +10 -0
- package/dist/lib/daemon/search-handler.js +11 -2
- package/dist/lib/eval/relevance-baseline.js +4 -2
- package/dist/lib/search/per-file.js +24 -0
- package/dist/lib/search/searcher.js +3 -1
- package/dist/lib/utils/autostart.js +1 -1
- package/dist/lib/utils/codex-home.js +44 -0
- package/dist/lib/utils/daemon-search.js +34 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/activity.js +12 -0
- package/plugins/grepmax/hooks/cwd-changed.js +2 -15
- package/plugins/grepmax/hooks/start.js +2 -35
- package/plugins/grepmax/hooks/watch-lease.js +53 -7
- package/plugins/grepmax/hooks.json +29 -6
|
@@ -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.51",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/README.md
CHANGED
|
@@ -119,9 +119,11 @@ gmax plugin remove claude # Remove specific plugin
|
|
|
119
119
|
|
|
120
120
|
After upgrading with `npm install -g grepmax@latest`, run `gmax plugin update` (or `gmax plugin update <client>`) to refresh integrations. Package installation does not modify your agent configuration; its postinstall script only prints this reminder.
|
|
121
121
|
|
|
122
|
+
Starting with v0.26.51, the installer refreshes an existing Claude marketplace from its configured source and updates each existing user/project/local installation in its original scope. It does not remove the marketplace, reset disabled preferences, or reinstall after a refresh failure. New installations use the local npm package. An existing source is retained; moving to another source is a separate migration. Codex instructions follow `CODEX_HOME` (default `~/.codex`), and a matching STDIO registration retains its existing options. A different existing launch configuration is reported for review instead of overwritten. Refresh the plugins after upgrading to activate the hook and installer repairs.
|
|
123
|
+
|
|
122
124
|
### How it works per client
|
|
123
125
|
|
|
124
|
-
- **Claude Code:** Plugin with hooks
|
|
126
|
+
- **Claude Code:** Plugin with session/directory hooks, guidance hooks and activity renewal on UserPromptSubmit/PostToolUse for Bash. Model uses CLI via `Bash(gmax ... --agent)`; the plugin does not register an MCP server. Source hooks honor the autostart kill switch, start only after a definite absent socket and never fall back to a separate watcher beside a live daemon.
|
|
125
127
|
- **OpenCode:** Tool shim with dynamic SKILL + session plugin for daemon startup. Model calls gmax tool directly.
|
|
126
128
|
- **Codex:** MCP server registration + AGENTS.md skill instructions.
|
|
127
129
|
- **Factory Droid:** Skills + SessionStart/SessionEnd hooks for daemon lifecycle.
|
|
@@ -205,6 +207,8 @@ gmax "query" [options]
|
|
|
205
207
|
| `--exclude-projects <list>` | With `--all-projects`, skip these projects. | — |
|
|
206
208
|
| `--min-score <n>` | Minimum score relative to this query’s top match, not an absolute confidence threshold. | `0` |
|
|
207
209
|
|
|
210
|
+
v0.26.50 carries an explicit `--per-file` value into retrieval as well as formatting. Values must be positive safe integers. Omitted requests retain the searcher's configured `GMAX_MAX_PER_FILE` cap or three; larger explicit values still respect `-m` and the existing candidate/rerank bounds. Explicit overrides require a daemon advertising `capabilities.perFileSearch: 1`; older live daemons are refused with an update/restart hint, without opening a fallback reader. An incompatible optional local HTTP fast path is skipped. v0.26.49 and older releases do not support this retrieval override.
|
|
211
|
+
|
|
208
212
|
## Experimental Orientation
|
|
209
213
|
|
|
210
214
|
`gmax surprises --experimental` finds file pairs that are semantically similar but not already connected by the indexed static graph. Use it for architecture orientation, duplicate-logic sweeps, and cross-package drift checks, not as proof that two files are unrelated.
|
|
@@ -399,7 +403,7 @@ embedding paths, with and without ColBERT rerank:
|
|
|
399
403
|
| `small` / 384d | **0.72** | **0.51** |
|
|
400
404
|
| `standard` / 768d | 0.62 | 0.44 |
|
|
401
405
|
|
|
402
|
-
<sub>Historical gmax-repo comparison: 97 cases, MLX GPU, rerank off. CPU/q4 and rerank-on comparisons showed a similar gap. These figures are not a
|
|
406
|
+
<sub>Historical gmax-repo comparison: 97 cases, MLX GPU, rerank off. CPU/q4 and rerank-on comparisons showed a similar gap. These figures are not a current-release benchmark; see the evaluation caveats below.</sub>
|
|
403
407
|
|
|
404
408
|
The larger tier performed worse on that fixture and doubles dense-vector width; compute and memory costs depend on the backend.
|
|
405
409
|
Unless you have a measured reason to switch (e.g. a recall complaint on a very
|
|
@@ -484,7 +488,7 @@ whether you need to act.
|
|
|
484
488
|
| `disabling auto-rebuild until an optimize succeeds` | Transient, not a wedge. The next successful optimize clears it. Search stays available. |
|
|
485
489
|
| `ANN: vector index not built` | Normal — exact search is the default. |
|
|
486
490
|
| `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. |
|
|
487
|
-
| `npm audit` reports advisories on install | v0.26.
|
|
491
|
+
| `npm audit` reports advisories on install | v0.26.50 passed production and fresh packed-consumer audits. Audit findings can change: record the installed version and dependency path and report new findings; do not assume historical advisories still apply. |
|
|
488
492
|
|
|
489
493
|
## Contributing
|
|
490
494
|
|
|
@@ -504,7 +508,7 @@ GMAX_EVAL_RERANK=1 pnpm bench:oss # toggle ColBERT rerank
|
|
|
504
508
|
|
|
505
509
|
The OSS harness expects indexed express, lodash and platform fixtures at the paths defined in [src/eval-oss.ts](src/eval-oss.ts). The historical comparison in [known limitations](docs/known-limitations.md#colbert-rerank-is-opt-in-shape-sensitive-helps-monolithic-files-hurts-modular-repos) combines these with the internal fixture: four datasets / 131 cases. It is not a current release acceptance run.
|
|
506
510
|
|
|
507
|
-
Both retrieval harnesses request 20 results for diagnostics, but `mrrAt10` and Recall@10 credit only ranks 1–10. Later matches retain their actual ranks and count as found/hits, with zero reciprocal-rank credit. This cutoff
|
|
511
|
+
Both retrieval harnesses request 20 results for diagnostics, but `mrrAt10` and Recall@10 credit only ranks 1–10. Later matches retain their actual ranks and count as found/hits, with zero reciprocal-rank credit. This cutoff correction shipped in v0.26.49; historical MRR figures recorded before the correction may include ranks 11–20 and require a new run or rescoring before comparison. Also set `GMAX_CONCENTRATION_THRESHOLD=2` for a true rerank-off comparison: `GMAX_EVAL_RERANK=0` alone does not disable automatic concentration gating. Freeze fixtures and record source, embedding/index identity and ranking configuration before drawing conclusions.
|
|
508
512
|
|
|
509
513
|
`pnpm bench:recall` also drives the model-tier comparison behind the 384d default — see [Model Tier](#model-tier) for why the larger 768d model is *not* the default.
|
|
510
514
|
|
|
@@ -521,13 +525,13 @@ pnpm bench:relevance --fixture docs/measurements/relevance/fixture-v1.json \
|
|
|
521
525
|
|
|
522
526
|
The fixture schema is in [relevance-baseline.ts](src/lib/eval/relevance-baseline.ts). Its JSON contains `schemaVersion: 1`, `createdAt`, `purpose`, `corpora` and `cases`. Each corpus has an `id`, `root` (absolute or relative to the fixture) and `language`. Each case declares an `id`, `corpus`, `split` (`dev` or `heldout`), `origin` (`curated-source` or `observed-session`), `intent`, `query` and one or more `expected` targets. Targets require an exact relative `file`, definition `symbol`, one-based `startLine`/`endLine` and the SHA-256 of the source file.
|
|
523
527
|
|
|
524
|
-
A hit requires the exact file and either the definition symbol or overlap with the declared line range. Choose answer-bearing ranges and reviewed alternatives: a declaration-only target can miss a useful function-body chunk. The October 4 baseline intentionally preserves its frozen declaration targets, so its 57.5% Recall@10 / 0.3275 MRR@10 describes target retrieval, not general answer quality. It covers 40 curated cases across TypeScript, JavaScript, Python and Swift; both repetitions produced identical ranks. These are not observed user-session misses or a ranking acceptance gate.
|
|
528
|
+
A hit requires the exact file and either the definition symbol or overlap with the declared line range. The source runner also accepts target `match: "range"` to require range overlap without a symbol shortcut; omitted or `"symbol-or-range"` preserves legacy matching. Range overlap measures useful context discovery, not complete answer coverage. Choose answer-bearing ranges and reviewed alternatives: a declaration-only target can miss a useful function-body chunk. The October 4 baseline intentionally preserves its frozen declaration targets, so its 57.5% Recall@10 / 0.3275 MRR@10 describes target retrieval, not general answer quality. It covers 40 curated cases across TypeScript, JavaScript, Python and Swift; both repetitions produced identical ranks. These are not observed user-session misses or a ranking acceptance gate. Previously inspected cases must be treated as exposed development cases in successor measurements.
|
|
525
529
|
|
|
526
530
|
The report records source/index hashes, registry embedding identity, daemon snapshots, exclusions, per-case ranks, aggregate metrics and ordinary-session latency. A sibling `.samples.jsonl` checkpoints each completed request; the final JSON applies run-wide exclusions and is authoritative. Changed source/cache hashes, missing targets, unsettled indexes, search warnings and daemon replacement invalidate samples. The runner refuses to overwrite artifacts and exits 2 when any samples are excluded.
|
|
527
531
|
|
|
528
532
|
Requests use `rerank: false`, but the daemon's concentration gate can still enable reranking. Client environment settings cannot change an existing daemon's settings, and `scoreBreakdown.rerank` also holds the fallback base score when reranking is off.
|
|
529
533
|
|
|
530
|
-
|
|
534
|
+
v0.26.49 supports `--diagnostics`. This requires a ready daemon advertising `capabilities.searchDiagnostics: 1`; older daemons without that capability require an update. The evaluator refuses incompatible daemons before measuring and excludes responses missing requested diagnostics. Each sample records FTS availability/failure, effective settings and allowlisted daemon ranking environment, concentration-gate evaluation/activation and whether reranking was actually invoked.
|
|
531
535
|
|
|
532
536
|
The candidate trace contains pointers and stage ranks for at most the first 200 post-seed fusion candidates, plus full stage counts and an explicit truncation flag. Ranks cover vector/FTS, RRF, seeded fusion, stage-one selection, pooled filtering, selected rerank batch, final scoring, overlap deduplication and final display. Rerank-batch membership does not imply execution; use `gate.rerankInvoked`. Zero at a later stage means a traced candidate was removed; a target missing from a truncated trace may lie outside the reported head. Diagnostics add no database reads and preserve ranking/results. The frozen October 4 artifacts retain their original unknowns. Review ground truth and collect new diagnostic artifacts before tuning.
|
|
533
537
|
|
|
@@ -46,32 +46,36 @@ exports.installClaudeCode = void 0;
|
|
|
46
46
|
const node_child_process_1 = require("node:child_process");
|
|
47
47
|
const fs = __importStar(require("node:fs"));
|
|
48
48
|
const path = __importStar(require("node:path"));
|
|
49
|
+
const node_util_1 = require("node:util");
|
|
49
50
|
const commander_1 = require("commander");
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
52
|
+
function runClaudeCommand(args_1) {
|
|
53
|
+
return __awaiter(this, arguments, void 0, function* (args, options = {}) {
|
|
54
|
+
const { stdout, stderr } = yield execFileAsync("claude", ["plugin", ...args], {
|
|
53
55
|
env: process.env,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
reject(new Error("claude command timed out after 60s"));
|
|
59
|
-
}, 60000);
|
|
60
|
-
child.on("error", (error) => {
|
|
61
|
-
clearTimeout(timeout);
|
|
62
|
-
reject(error);
|
|
63
|
-
});
|
|
64
|
-
child.on("exit", (code) => {
|
|
65
|
-
clearTimeout(timeout);
|
|
66
|
-
if (code === 0) {
|
|
67
|
-
resolve();
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
reject(new Error(`claude exited with code ${code !== null && code !== void 0 ? code : "unknown"}`));
|
|
71
|
-
}
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
timeout: 60000,
|
|
58
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
59
|
+
cwd: options.cwd,
|
|
72
60
|
});
|
|
61
|
+
if (!options.quiet) {
|
|
62
|
+
process.stdout.write(stdout);
|
|
63
|
+
process.stderr.write(stderr);
|
|
64
|
+
}
|
|
65
|
+
return stdout;
|
|
73
66
|
});
|
|
74
67
|
}
|
|
68
|
+
function parseList(raw, label) {
|
|
69
|
+
const parsed = JSON.parse(raw);
|
|
70
|
+
const key = label === "marketplace list" ? "name" : "id";
|
|
71
|
+
if (!Array.isArray(parsed) ||
|
|
72
|
+
parsed.some((entry) => !entry ||
|
|
73
|
+
typeof entry !== "object" ||
|
|
74
|
+
typeof entry[key] !== "string" ||
|
|
75
|
+
!entry[key]))
|
|
76
|
+
throw new Error(`Unexpected Claude ${label} response; configuration preserved`);
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
75
79
|
/**
|
|
76
80
|
* Resolve the gmax package root directory.
|
|
77
81
|
* Works for both npm global installs (symlinked binary) and dev mode.
|
|
@@ -94,19 +98,47 @@ function installPlugin() {
|
|
|
94
98
|
return;
|
|
95
99
|
}
|
|
96
100
|
console.log(`Installing plugin from ${marketplacePath}`);
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
// Query the CLI so account selection and CLAUDE_CONFIG_DIR match the client.
|
|
102
|
+
// A failed/invalid inventory must never trigger a destructive reinstall.
|
|
103
|
+
const marketplaces = parseList(yield runClaudeCommand(["marketplace", "list", "--json"], {
|
|
104
|
+
quiet: true,
|
|
105
|
+
}), "marketplace list");
|
|
106
|
+
const installed = parseList(yield runClaudeCommand(["list", "--json"], { quiet: true }), "plugin list").filter((entry) => entry.id === "grepmax@grepmax");
|
|
107
|
+
const updates = installed.filter((entry) => entry.scope !== "managed");
|
|
108
|
+
// Validate every target before even refreshing the source.
|
|
109
|
+
for (const entry of updates) {
|
|
110
|
+
if (!["user", "project", "local"].includes(String(entry.scope)))
|
|
111
|
+
throw new Error("Unknown gmax installation scope; configuration preserved");
|
|
112
|
+
if (entry.scope !== "user" &&
|
|
113
|
+
(typeof entry.projectPath !== "string" ||
|
|
114
|
+
!fs.existsSync(entry.projectPath)))
|
|
115
|
+
throw new Error("Missing gmax installation project; configuration preserved");
|
|
100
116
|
}
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
if (installed.length > 0 && updates.length === 0) {
|
|
118
|
+
console.log("gmax is managed by your administrator; installation preserved.");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (marketplaces.some((entry) => entry.name === "grepmax")) {
|
|
122
|
+
// Retain the user's chosen source, including GitHub or another npm prefix.
|
|
123
|
+
// Retargeting by removing a marketplace also deletes plugin-owned state.
|
|
124
|
+
yield runClaudeCommand(["marketplace", "update", "grepmax"]);
|
|
125
|
+
console.log("✔ Marketplace refreshed (existing source preserved)");
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
yield runClaudeCommand(["marketplace", "add", marketplacePath]);
|
|
129
|
+
console.log("✔ Marketplace registered (local)");
|
|
130
|
+
}
|
|
131
|
+
if (updates.length === 0) {
|
|
132
|
+
yield runClaudeCommand(["install", "grepmax@grepmax", "--scope", "user"]);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
for (const entry of updates) {
|
|
136
|
+
yield runClaudeCommand(["update", "grepmax@grepmax", "--scope", String(entry.scope)], {
|
|
137
|
+
cwd: entry.scope === "user" ? undefined : String(entry.projectPath),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
103
140
|
}
|
|
104
|
-
|
|
105
|
-
yield runClaudeCommand(["marketplace", "add", marketplacePath]);
|
|
106
|
-
console.log("✔ Marketplace registered (local)");
|
|
107
|
-
// Install the plugin from the local marketplace
|
|
108
|
-
yield runClaudeCommand(["install", "grepmax"]);
|
|
109
|
-
console.log("✅ Successfully installed the gmax plugin for Claude Code");
|
|
141
|
+
console.log("✅ Successfully refreshed the gmax plugin for Claude Code");
|
|
110
142
|
console.log("\nNext steps:");
|
|
111
143
|
console.log("1. Restart Claude Code if it's running");
|
|
112
144
|
console.log("2. Run `gmax add` in your project to index it");
|
package/dist/commands/codex.js
CHANGED
|
@@ -15,13 +15,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.uninstallCodex = exports.installCodex = void 0;
|
|
16
16
|
const node_child_process_1 = require("node:child_process");
|
|
17
17
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
19
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
20
19
|
const node_util_1 = require("node:util");
|
|
21
20
|
const commander_1 = require("commander");
|
|
21
|
+
const codex_home_1 = require("../lib/utils/codex-home");
|
|
22
22
|
const shell = process.env.SHELL || (process.platform === "win32" ? "cmd.exe" : "/bin/sh");
|
|
23
23
|
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
24
|
-
const AGENTS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".codex", "AGENTS.md");
|
|
25
24
|
const SKILL_START = "<!-- gmax:start -->";
|
|
26
25
|
const SKILL_END = "<!-- gmax:end -->";
|
|
27
26
|
function ownedSkillBlocks() {
|
|
@@ -48,6 +47,7 @@ function loadSkill() {
|
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
function writeSkillToAgents(skill) {
|
|
50
|
+
const AGENTS_PATH = (0, codex_home_1.codexAgentsPath)();
|
|
51
51
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(AGENTS_PATH), { recursive: true });
|
|
52
52
|
const block = `${SKILL_START}\n${skill.trim()}\n${SKILL_END}`;
|
|
53
53
|
if (!node_fs_1.default.existsSync(AGENTS_PATH)) {
|
|
@@ -74,20 +74,37 @@ function writeSkillToAgents(skill) {
|
|
|
74
74
|
}
|
|
75
75
|
function installPlugin() {
|
|
76
76
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
+
var _a;
|
|
77
78
|
try {
|
|
79
|
+
const inventory = yield execAsync("codex mcp list --json", {
|
|
80
|
+
shell,
|
|
81
|
+
env: process.env,
|
|
82
|
+
});
|
|
83
|
+
const servers = JSON.parse(inventory.stdout);
|
|
84
|
+
if (!Array.isArray(servers) ||
|
|
85
|
+
servers.some((server) => !server || typeof server.name !== "string"))
|
|
86
|
+
throw new Error("Unexpected Codex MCP inventory; configuration preserved");
|
|
87
|
+
const existing = servers.find((server) => (server === null || server === void 0 ? void 0 : server.name) === "gmax");
|
|
88
|
+
if (existing &&
|
|
89
|
+
(((_a = existing.transport) === null || _a === void 0 ? void 0 : _a.type) !== "stdio" ||
|
|
90
|
+
node_path_1.default.basename(String(existing.transport.command)) !== "gmax" ||
|
|
91
|
+
JSON.stringify(existing.transport.args) !== JSON.stringify(["mcp"])))
|
|
92
|
+
throw new Error("Existing gmax MCP launch differs; review it before replacing");
|
|
78
93
|
// 1. Register MCP tool. Codex requires the stdio command after `--`:
|
|
79
94
|
// codex mcp add [OPTIONS] <NAME> -- <COMMAND>...
|
|
80
95
|
// Without the separator the launch command is misparsed. AGENTS.md is only
|
|
81
96
|
// written after this resolves, so a failed registration leaves it untouched.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
if (!existing) {
|
|
98
|
+
yield execAsync("codex mcp add gmax -- gmax mcp", {
|
|
99
|
+
shell,
|
|
100
|
+
env: process.env,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
console.log("✅ gmax MCP registration ready (existing options preserved)");
|
|
87
104
|
// 2. Write SKILL to AGENTS.md (idempotent)
|
|
88
105
|
const skill = loadSkill();
|
|
89
106
|
writeSkillToAgents(skill);
|
|
90
|
-
console.log("✅ gmax skill instructions written to",
|
|
107
|
+
console.log("✅ gmax skill instructions written to", (0, codex_home_1.codexAgentsPath)());
|
|
91
108
|
}
|
|
92
109
|
catch (error) {
|
|
93
110
|
console.error(`❌ Error installing Codex plugin: ${error}`);
|
|
@@ -97,6 +114,7 @@ function installPlugin() {
|
|
|
97
114
|
}
|
|
98
115
|
function uninstallPlugin() {
|
|
99
116
|
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
const AGENTS_PATH = (0, codex_home_1.codexAgentsPath)();
|
|
100
118
|
try {
|
|
101
119
|
yield execAsync("codex mcp remove gmax", { shell, env: process.env });
|
|
102
120
|
console.log("✅ gmax MCP tool removed");
|
package/dist/commands/plugin.js
CHANGED
|
@@ -49,6 +49,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
49
49
|
const os = __importStar(require("node:os"));
|
|
50
50
|
const path = __importStar(require("node:path"));
|
|
51
51
|
const commander_1 = require("commander");
|
|
52
|
+
const codex_home_1 = require("../lib/utils/codex-home");
|
|
52
53
|
const exit_1 = require("../lib/utils/exit");
|
|
53
54
|
function commandExists(cmd) {
|
|
54
55
|
try {
|
|
@@ -106,7 +107,7 @@ function getClients() {
|
|
|
106
107
|
id: "codex",
|
|
107
108
|
detect: () => commandExists("codex"),
|
|
108
109
|
isInstalled: () => {
|
|
109
|
-
const p =
|
|
110
|
+
const p = (0, codex_home_1.codexAgentsPath)();
|
|
110
111
|
try {
|
|
111
112
|
return (fs.existsSync(p) && fs.readFileSync(p, "utf-8").includes("gmax"));
|
|
112
113
|
}
|
|
@@ -48,6 +48,7 @@ const path = __importStar(require("node:path"));
|
|
|
48
48
|
const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
|
|
49
49
|
const compact_results_1 = require("../lib/output/compact-results");
|
|
50
50
|
const index_state_footer_1 = require("../lib/output/index-state-footer");
|
|
51
|
+
const per_file_1 = require("../lib/search/per-file");
|
|
51
52
|
const cross_project_1 = require("../lib/utils/cross-project");
|
|
52
53
|
const formatter_1 = require("../lib/utils/formatter");
|
|
53
54
|
const import_extractor_1 = require("../lib/utils/import-extractor");
|
|
@@ -69,14 +70,26 @@ const search_skeletons_1 = require("./search-skeletons");
|
|
|
69
70
|
*/
|
|
70
71
|
function executeServerSearch(params) {
|
|
71
72
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
var _a;
|
|
74
|
+
var _b;
|
|
72
75
|
const { server, pattern, exec_path, projectRootForServer, options, minScore, } = params;
|
|
76
|
+
const maxPerFile = (0, per_file_1.parseCliPerFile)(options.perFile);
|
|
73
77
|
try {
|
|
78
|
+
if (maxPerFile !== undefined) {
|
|
79
|
+
const health = yield fetch(`http://127.0.0.1:${server.port}/health`);
|
|
80
|
+
if (!health.ok)
|
|
81
|
+
return false;
|
|
82
|
+
const info = (yield health.json());
|
|
83
|
+
if (((_a = info.capabilities) === null || _a === void 0 ? void 0 : _a.perFileSearch) !== 1)
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
74
86
|
const response = yield fetch(`http://127.0.0.1:${server.port}/search`, {
|
|
75
87
|
method: "POST",
|
|
76
88
|
headers: { "Content-Type": "application/json" },
|
|
77
89
|
body: JSON.stringify({
|
|
78
90
|
query: pattern,
|
|
79
91
|
limit: parseInt(options.m, 10),
|
|
92
|
+
maxPerFile,
|
|
80
93
|
path: exec_path
|
|
81
94
|
? path.relative(projectRootForServer, path.resolve(exec_path))
|
|
82
95
|
: undefined,
|
|
@@ -161,7 +174,7 @@ function executeServerSearch(params) {
|
|
|
161
174
|
isPlain: true,
|
|
162
175
|
compact: options.compact,
|
|
163
176
|
content: options.content,
|
|
164
|
-
perFile: parseInt(options.perFile, 10),
|
|
177
|
+
perFile: parseInt((_b = options.perFile) !== null && _b !== void 0 ? _b : "3", 10),
|
|
165
178
|
showScores: options.scores,
|
|
166
179
|
});
|
|
167
180
|
console.log(output);
|
|
@@ -195,7 +208,7 @@ function executeServerSearch(params) {
|
|
|
195
208
|
function renderSearchOutput(params) {
|
|
196
209
|
return __awaiter(this, void 0, void 0, function* () {
|
|
197
210
|
var _a, _b, _c;
|
|
198
|
-
var _d, _e, _f, _g;
|
|
211
|
+
var _d, _e, _f, _g, _h, _j;
|
|
199
212
|
const { searchResult, options, minScore, crossProject, pattern, effectiveRoot, projectRoot, vectorDb, precomputedSkeletons, precomputedGraph, indexState, } = params;
|
|
200
213
|
if (!options.agent && ((_a = searchResult.warnings) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
201
214
|
for (const w of searchResult.warnings) {
|
|
@@ -220,7 +233,7 @@ function renderSearchOutput(params) {
|
|
|
220
233
|
return defs.some((d) => regex.test(d));
|
|
221
234
|
});
|
|
222
235
|
}
|
|
223
|
-
catch (
|
|
236
|
+
catch (_k) {
|
|
224
237
|
// Invalid regex — skip
|
|
225
238
|
}
|
|
226
239
|
}
|
|
@@ -293,7 +306,7 @@ function renderSearchOutput(params) {
|
|
|
293
306
|
isPlain: true,
|
|
294
307
|
compact: options.compact,
|
|
295
308
|
content: options.content,
|
|
296
|
-
perFile: parseInt(options.perFile, 10),
|
|
309
|
+
perFile: parseInt((_d = options.perFile) !== null && _d !== void 0 ? _d : "3", 10),
|
|
297
310
|
showScores: options.scores,
|
|
298
311
|
});
|
|
299
312
|
}
|
|
@@ -355,7 +368,7 @@ function renderSearchOutput(params) {
|
|
|
355
368
|
}
|
|
356
369
|
}
|
|
357
370
|
}
|
|
358
|
-
catch (
|
|
371
|
+
catch (_l) { }
|
|
359
372
|
}
|
|
360
373
|
// Partial-index footer last, so it's the final line the agent reads —
|
|
361
374
|
// and emitted even on "(none)", where an empty result may just mean the
|
|
@@ -453,7 +466,7 @@ function renderSearchOutput(params) {
|
|
|
453
466
|
if (options.imports) {
|
|
454
467
|
const seenFiles = new Set();
|
|
455
468
|
for (const r of filteredData) {
|
|
456
|
-
const absP = (
|
|
469
|
+
const absP = (_f = (_e = r.path) !== null && _e !== void 0 ? _e : (_b = r.metadata) === null || _b === void 0 ? void 0 : _b.path) !== null && _f !== void 0 ? _f : "";
|
|
457
470
|
if (absP && !seenFiles.has(absP)) {
|
|
458
471
|
seenFiles.add(absP);
|
|
459
472
|
const imports = getImportsForFile(absP);
|
|
@@ -472,7 +485,7 @@ function renderSearchOutput(params) {
|
|
|
472
485
|
isPlain: true,
|
|
473
486
|
compact: options.compact,
|
|
474
487
|
content: options.content,
|
|
475
|
-
perFile: parseInt(options.perFile, 10),
|
|
488
|
+
perFile: parseInt((_g = options.perFile) !== null && _g !== void 0 ? _g : "3", 10),
|
|
476
489
|
showScores: options.scores,
|
|
477
490
|
});
|
|
478
491
|
console.log(output);
|
|
@@ -480,7 +493,7 @@ function renderSearchOutput(params) {
|
|
|
480
493
|
for (const r of filteredData) {
|
|
481
494
|
const b = r.scoreBreakdown;
|
|
482
495
|
if (b) {
|
|
483
|
-
const absP = (
|
|
496
|
+
const absP = (_j = (_h = r.path) !== null && _h !== void 0 ? _h : (_c = r.metadata) === null || _c === void 0 ? void 0 : _c.path) !== null && _j !== void 0 ? _j : "";
|
|
484
497
|
const relPath = absP.startsWith(projectRoot)
|
|
485
498
|
? absP.slice(projectRoot.length + 1)
|
|
486
499
|
: absP;
|
|
@@ -542,7 +555,7 @@ function renderSearchOutput(params) {
|
|
|
542
555
|
console.log(lines.join("\n"));
|
|
543
556
|
}
|
|
544
557
|
}
|
|
545
|
-
catch (
|
|
558
|
+
catch (_m) {
|
|
546
559
|
// Trace failed — skip silently
|
|
547
560
|
}
|
|
548
561
|
}
|
|
@@ -51,6 +51,7 @@ const grammar_loader_1 = require("../lib/index/grammar-loader");
|
|
|
51
51
|
const index_config_1 = require("../lib/index/index-config");
|
|
52
52
|
const sync_helpers_1 = require("../lib/index/sync-helpers");
|
|
53
53
|
const syncer_1 = require("../lib/index/syncer");
|
|
54
|
+
const per_file_1 = require("../lib/search/per-file");
|
|
54
55
|
const searcher_1 = require("../lib/search/searcher");
|
|
55
56
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
56
57
|
const lock_1 = require("../lib/utils/lock");
|
|
@@ -76,6 +77,7 @@ function runSearch(params) {
|
|
|
76
77
|
var _a, _b;
|
|
77
78
|
var _c;
|
|
78
79
|
const { pattern, options, projectRoot, effectiveRoot, paths, projectStatus, searchFilters, pathFilter, seeds, } = params;
|
|
80
|
+
const maxPerFile = (0, per_file_1.parseCliPerFile)(options.perFile);
|
|
79
81
|
// Tracks a DB opened by the in-process path so it can be (a) returned to the
|
|
80
82
|
// caller for closing after render, or (b) closed here if indexing throws.
|
|
81
83
|
let openedDb = null;
|
|
@@ -96,13 +98,14 @@ function runSearch(params) {
|
|
|
96
98
|
let indexState;
|
|
97
99
|
if (!options.sync && !options.dryRun) {
|
|
98
100
|
try {
|
|
99
|
-
const {
|
|
101
|
+
const { sendSearchCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-search")));
|
|
100
102
|
const crossProject = Array.isArray(searchFilters.projectRoots);
|
|
101
|
-
const resp = yield
|
|
103
|
+
const resp = yield sendSearchCommand({
|
|
102
104
|
cmd: crossProject ? "search-v2" : "search",
|
|
103
105
|
projectRoot: effectiveRoot,
|
|
104
106
|
query: pattern,
|
|
105
107
|
limit: parseInt(options.m, 10),
|
|
108
|
+
maxPerFile,
|
|
106
109
|
filters: Object.keys(searchFilters).length > 0 ? searchFilters : undefined,
|
|
107
110
|
pathPrefix: pathFilter,
|
|
108
111
|
rerank: process.env.GMAX_RERANK === "1",
|
|
@@ -253,6 +256,7 @@ function runSearch(params) {
|
|
|
253
256
|
const searcher = new searcher_1.Searcher(vectorDb);
|
|
254
257
|
searchResult = yield searcher.search(pattern, parseInt(options.m, 10), {
|
|
255
258
|
rerank: process.env.GMAX_RERANK === "1",
|
|
259
|
+
maxPerFile,
|
|
256
260
|
explain: options.explain,
|
|
257
261
|
seeds,
|
|
258
262
|
}, Object.keys(searchFilters).length > 0
|
package/dist/commands/search.js
CHANGED
|
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.search = void 0;
|
|
46
46
|
const path = __importStar(require("node:path"));
|
|
47
47
|
const commander_1 = require("commander");
|
|
48
|
+
const per_file_1 = require("../lib/search/per-file");
|
|
48
49
|
const setup_helpers_1 = require("../lib/setup/setup-helpers");
|
|
49
50
|
const cross_project_1 = require("../lib/utils/cross-project");
|
|
50
51
|
const exit_1 = require("../lib/utils/exit");
|
|
@@ -61,7 +62,7 @@ exports.search = new commander_1.Command("search")
|
|
|
61
62
|
.description("Search code by meaning (default command)")
|
|
62
63
|
.option("-m <max_count>, --max-count <max_count>", "The maximum number of results to return (total)", "5")
|
|
63
64
|
.option("-c, --content", "Show full chunk content instead of snippets", false)
|
|
64
|
-
.option("--per-file <n>", "
|
|
65
|
+
.option("--per-file <n>", "Maximum retrieved matches per file (default: configured cap or 3)")
|
|
65
66
|
.option("--scores", "Show relevance scores", false)
|
|
66
67
|
.option("--explain", "Show scoring breakdown per result", false)
|
|
67
68
|
.option("--context-for-llm", "Return full function body + imports per result", false)
|
|
@@ -105,6 +106,14 @@ Examples:
|
|
|
105
106
|
var _a, _b, _c;
|
|
106
107
|
var _d, _e, _f, _g;
|
|
107
108
|
const options = cmd.optsWithGlobals();
|
|
109
|
+
try {
|
|
110
|
+
(0, per_file_1.parseCliPerFile)(options.perFile);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error(error.message);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
108
117
|
const root = process.cwd();
|
|
109
118
|
const minScore = Number.isFinite(Number.parseFloat(options.minScore))
|
|
110
119
|
? Number.parseFloat(options.minScore)
|
package/dist/commands/serve.js
CHANGED
|
@@ -64,8 +64,10 @@ const http = __importStar(require("node:http"));
|
|
|
64
64
|
const path = __importStar(require("node:path"));
|
|
65
65
|
const commander_1 = require("commander");
|
|
66
66
|
const config_1 = require("../config");
|
|
67
|
+
const per_file_1 = require("../lib/search/per-file");
|
|
67
68
|
const blocked_roots_1 = require("../lib/utils/blocked-roots");
|
|
68
69
|
const daemon_client_1 = require("../lib/utils/daemon-client");
|
|
70
|
+
const daemon_search_1 = require("../lib/utils/daemon-search");
|
|
69
71
|
const exit_1 = require("../lib/utils/exit");
|
|
70
72
|
const log_rotate_1 = require("../lib/utils/log-rotate");
|
|
71
73
|
const path_containment_1 = require("../lib/utils/path-containment");
|
|
@@ -194,7 +196,7 @@ function createServeHttpServer(projectRoot, deps) {
|
|
|
194
196
|
var _b, _c, _d;
|
|
195
197
|
(_a = deps.onActivity) === null || _a === void 0 ? void 0 : _a.call(deps);
|
|
196
198
|
if (req.method === "GET" && req.url === "/health") {
|
|
197
|
-
writeJson(res, 200, { status: "ok" });
|
|
199
|
+
writeJson(res, 200, { status: "ok", capabilities: { perFileSearch: 1 } });
|
|
198
200
|
return;
|
|
199
201
|
}
|
|
200
202
|
const ac = new AbortController();
|
|
@@ -223,6 +225,13 @@ function createServeHttpServer(projectRoot, deps) {
|
|
|
223
225
|
const query = typeof body.query === "string" ? body.query.trim() : "";
|
|
224
226
|
if (!query)
|
|
225
227
|
throw new HttpRequestError(400, "invalid_query");
|
|
228
|
+
let maxPerFile;
|
|
229
|
+
try {
|
|
230
|
+
maxPerFile = (0, per_file_1.validateMaxPerFile)(body.maxPerFile);
|
|
231
|
+
}
|
|
232
|
+
catch (_e) {
|
|
233
|
+
throw new HttpRequestError(400, "invalid_maxPerFile");
|
|
234
|
+
}
|
|
226
235
|
const limit = body.limit === undefined ? 10 : body.limit;
|
|
227
236
|
if (typeof limit !== "number" ||
|
|
228
237
|
!Number.isInteger(limit) ||
|
|
@@ -262,12 +271,8 @@ function createServeHttpServer(projectRoot, deps) {
|
|
|
262
271
|
if (scope.excludePrefixes.length > 0) {
|
|
263
272
|
filters.excludePrefixes = scope.excludePrefixes;
|
|
264
273
|
}
|
|
265
|
-
const response = yield deps.search({
|
|
266
|
-
|
|
267
|
-
limit,
|
|
268
|
-
pathPrefix: scope.pathPrefix,
|
|
269
|
-
filters: Object.keys(filters).length ? filters : undefined,
|
|
270
|
-
}, ac.signal);
|
|
274
|
+
const response = yield deps.search(Object.assign(Object.assign({ query,
|
|
275
|
+
limit }, (maxPerFile === undefined ? {} : { maxPerFile })), { pathPrefix: scope.pathPrefix, filters: Object.keys(filters).length ? filters : undefined }), ac.signal);
|
|
271
276
|
if (!response.ok) {
|
|
272
277
|
writeJson(res, daemonErrorStatus(response.error), {
|
|
273
278
|
error: (_c = response.error) !== null && _c !== void 0 ? _c : "search_failed",
|
|
@@ -453,11 +458,12 @@ exports.serve = new commander_1.Command("serve")
|
|
|
453
458
|
onActivity: () => {
|
|
454
459
|
lastActivity = Date.now();
|
|
455
460
|
},
|
|
456
|
-
search: (input, signal) => (0,
|
|
461
|
+
search: (input, signal) => (0, daemon_search_1.sendSearchCommand)({
|
|
457
462
|
cmd: "search",
|
|
458
463
|
projectRoot,
|
|
459
464
|
query: input.query,
|
|
460
465
|
limit: input.limit,
|
|
466
|
+
maxPerFile: input.maxPerFile,
|
|
461
467
|
pathPrefix: input.pathPrefix,
|
|
462
468
|
filters: input.filters,
|
|
463
469
|
rerank: true,
|
package/dist/commands/watch.js
CHANGED
|
@@ -53,6 +53,7 @@ const watcher_1 = require("../lib/index/watcher");
|
|
|
53
53
|
const index_state_footer_1 = require("../lib/output/index-state-footer");
|
|
54
54
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
55
55
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
56
|
+
const autostart_1 = require("../lib/utils/autostart");
|
|
56
57
|
const daemon_restart_1 = require("../lib/utils/daemon-restart");
|
|
57
58
|
const exit_1 = require("../lib/utils/exit");
|
|
58
59
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
@@ -92,11 +93,18 @@ exports.watch = new commander_1.Command("watch")
|
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
94
95
|
if (options.background) {
|
|
96
|
+
const startOnly = process.env.GMAX_DAEMON_START_ONLY === "1";
|
|
97
|
+
// Plugin hooks are implicit starts. Recheck quarantine in this child
|
|
98
|
+
// and never use their launch as a version-upgrade restart.
|
|
99
|
+
if (startOnly && (0, autostart_1.isAutostartDisabled)())
|
|
100
|
+
return;
|
|
95
101
|
// Skip spawn if daemon already running at the same version.
|
|
96
102
|
// If version mismatches (e.g. after npm install -g), shut down the old
|
|
97
103
|
// daemon so we can start a fresh one with the new code.
|
|
98
104
|
const { isDaemonRunning, isDaemonHeartbeatFresh, sendDaemonCommand, readDaemonPid, waitForProcessExit, } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
99
105
|
if (yield isDaemonRunning()) {
|
|
106
|
+
if (startOnly)
|
|
107
|
+
return;
|
|
100
108
|
const cliVersion = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf-8")).version;
|
|
101
109
|
const resp = yield sendDaemonCommand({ cmd: "ping" });
|
|
102
110
|
if (resp.version && resp.version === cliVersion) {
|
|
@@ -50,6 +50,7 @@ exports.handleCommand = handleCommand;
|
|
|
50
50
|
const fs = __importStar(require("node:fs"));
|
|
51
51
|
const path = __importStar(require("node:path"));
|
|
52
52
|
const config_1 = require("../../config");
|
|
53
|
+
const per_file_1 = require("../search/per-file");
|
|
53
54
|
const logger_1 = require("../utils/logger");
|
|
54
55
|
const operation_coordinator_1 = require("../utils/operation-coordinator");
|
|
55
56
|
const path_containment_1 = require("../utils/path-containment");
|
|
@@ -153,6 +154,7 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
153
154
|
exclusiveGenerationRebuild: exports.EXCLUSIVE_GENERATION_REBUILD_PROTOCOL,
|
|
154
155
|
readVerbs: read_verbs_1.READ_VERBS_PROTOCOL,
|
|
155
156
|
searchDiagnostics: 1,
|
|
157
|
+
perFileSearch: 1,
|
|
156
158
|
// `watch`/`unwatch` accept holder/pid/ttlMs. A daemon without this
|
|
157
159
|
// treats `unwatch` as unwatch-for-everyone, so clients releasing a
|
|
158
160
|
// single lease must check it first.
|
|
@@ -232,6 +234,13 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
232
234
|
conn.on("close", onClose);
|
|
233
235
|
try {
|
|
234
236
|
const limitRaw = typeof cmd.limit === "number" ? cmd.limit : 10;
|
|
237
|
+
let maxPerFile;
|
|
238
|
+
try {
|
|
239
|
+
maxPerFile = (0, per_file_1.validateMaxPerFile)(cmd.maxPerFile);
|
|
240
|
+
}
|
|
241
|
+
catch (_m) {
|
|
242
|
+
return { ok: false, error: "invalid maxPerFile" };
|
|
243
|
+
}
|
|
235
244
|
if (!Number.isInteger(limitRaw) || limitRaw < 1 || limitRaw > 1000) {
|
|
236
245
|
return { ok: false, error: "invalid limit" };
|
|
237
246
|
}
|
|
@@ -296,6 +305,7 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
296
305
|
projectRoot,
|
|
297
306
|
query,
|
|
298
307
|
limit: limitRaw,
|
|
308
|
+
maxPerFile,
|
|
299
309
|
filters,
|
|
300
310
|
pathPrefix,
|
|
301
311
|
rerank: cmd.rerank === true,
|
|
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.handleDaemonSearch = handleDaemonSearch;
|
|
46
46
|
const embedding_generation_1 = require("../index/embedding-generation");
|
|
47
47
|
const index_state_footer_1 = require("../output/index-state-footer");
|
|
48
|
+
const per_file_1 = require("../search/per-file");
|
|
48
49
|
const searcher_1 = require("../search/searcher");
|
|
49
50
|
const retriever_1 = require("../skeleton/retriever");
|
|
50
51
|
const project_registry_1 = require("../utils/project-registry");
|
|
@@ -57,6 +58,13 @@ function handleDaemonSearch(deps, payload, signal) {
|
|
|
57
58
|
return __awaiter(this, void 0, void 0, function* () {
|
|
58
59
|
var _a, _b, _c, _d;
|
|
59
60
|
var _e, _f, _g;
|
|
61
|
+
let maxPerFile;
|
|
62
|
+
try {
|
|
63
|
+
maxPerFile = (0, per_file_1.validateMaxPerFile)(payload.maxPerFile);
|
|
64
|
+
}
|
|
65
|
+
catch (_h) {
|
|
66
|
+
return { ok: false, error: "invalid maxPerFile" };
|
|
67
|
+
}
|
|
60
68
|
const { vectorDb, workerPool } = deps;
|
|
61
69
|
if (!vectorDb || !workerPool) {
|
|
62
70
|
return { ok: false, error: "daemon not ready" };
|
|
@@ -104,6 +112,7 @@ function handleDaemonSearch(deps, payload, signal) {
|
|
|
104
112
|
rerank: payload.rerank === true,
|
|
105
113
|
explain: payload.explain === true,
|
|
106
114
|
diagnostics: payload.diagnostics === true,
|
|
115
|
+
maxPerFile,
|
|
107
116
|
seeds: payload.seeds,
|
|
108
117
|
}, payload.filters, payload.pathPrefix, undefined, signal);
|
|
109
118
|
}
|
|
@@ -155,7 +164,7 @@ function handleDaemonSearch(deps, payload, signal) {
|
|
|
155
164
|
if (sk)
|
|
156
165
|
skeletons[p] = sk;
|
|
157
166
|
}
|
|
158
|
-
catch (
|
|
167
|
+
catch (_j) {
|
|
159
168
|
// best-effort — drop the entry, keep the search result
|
|
160
169
|
}
|
|
161
170
|
}
|
|
@@ -170,7 +179,7 @@ function handleDaemonSearch(deps, payload, signal) {
|
|
|
170
179
|
const builder = new GraphBuilder(vectorDb, root);
|
|
171
180
|
response.graph = yield builder.buildGraphMultiHop(payload.query, 1);
|
|
172
181
|
}
|
|
173
|
-
catch (
|
|
182
|
+
catch (_k) {
|
|
174
183
|
// best-effort — drop graph, keep results
|
|
175
184
|
}
|
|
176
185
|
}
|
|
@@ -48,6 +48,7 @@ const targetSchema = zod_1.z
|
|
|
48
48
|
.object({
|
|
49
49
|
file: zod_1.z.string().min(1),
|
|
50
50
|
symbol: zod_1.z.string().min(1),
|
|
51
|
+
match: zod_1.z.enum(["symbol-or-range", "range"]).optional(),
|
|
51
52
|
startLine: zod_1.z.number().int().positive(),
|
|
52
53
|
endLine: zod_1.z.number().int().positive(),
|
|
53
54
|
sourceSha256: digest,
|
|
@@ -117,14 +118,15 @@ function parseFrozenFixture(bytes, expectedSha256) {
|
|
|
117
118
|
}
|
|
118
119
|
return exports.relevanceFixtureSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
119
120
|
}
|
|
120
|
-
/** Exact file plus
|
|
121
|
+
/** Exact file plus the frozen matching rule; legacy fixtures allow symbol or range. */
|
|
121
122
|
function matchesTarget(chunk, root, target) {
|
|
122
123
|
var _a, _b, _c, _d;
|
|
123
124
|
if (typeof ((_a = chunk.metadata) === null || _a === void 0 ? void 0 : _a.path) !== "string")
|
|
124
125
|
return false;
|
|
125
126
|
if (path.resolve(chunk.metadata.path) !== path.resolve(root, target.file))
|
|
126
127
|
return false;
|
|
127
|
-
if (
|
|
128
|
+
if (target.match !== "range" &&
|
|
129
|
+
((_b = chunk.defined_symbols) === null || _b === void 0 ? void 0 : _b.includes(target.symbol)))
|
|
128
130
|
return true;
|
|
129
131
|
const start = (_c = chunk.generated_metadata) === null || _c === void 0 ? void 0 : _c.start_line;
|
|
130
132
|
const count = (_d = chunk.generated_metadata) === null || _d === void 0 ? void 0 : _d.num_lines;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateMaxPerFile = validateMaxPerFile;
|
|
4
|
+
exports.parseCliPerFile = parseCliPerFile;
|
|
5
|
+
/** Omitted requests retain the daemon's configured default. */
|
|
6
|
+
function validateMaxPerFile(value) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
10
|
+
throw new Error("maxPerFile must be a positive safe integer");
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function parseCliPerFile(value) {
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (!/^\d+$/.test(value))
|
|
17
|
+
throw new Error("--per-file must be a positive safe integer");
|
|
18
|
+
try {
|
|
19
|
+
return validateMaxPerFile(Number(value));
|
|
20
|
+
}
|
|
21
|
+
catch (_a) {
|
|
22
|
+
throw new Error("--per-file must be a positive safe integer");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -21,6 +21,7 @@ const pool_1 = require("../workers/pool");
|
|
|
21
21
|
const diagnostics_1 = require("./diagnostics");
|
|
22
22
|
const intent_1 = require("./intent");
|
|
23
23
|
const pagerank_1 = require("./pagerank");
|
|
24
|
+
const per_file_1 = require("./per-file");
|
|
24
25
|
const seed_weight_1 = require("./seed-weight");
|
|
25
26
|
// Reads a defined_symbols / referenced_symbols column that may arrive as a plain
|
|
26
27
|
// array or a LanceDB Arrow proxy (.toArray()).
|
|
@@ -397,6 +398,7 @@ class Searcher {
|
|
|
397
398
|
search(query, top_k, _search_options, _filters, pathPrefix, intent, signal) {
|
|
398
399
|
return __awaiter(this, void 0, void 0, function* () {
|
|
399
400
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
401
|
+
const requestedMaxPerFile = (0, per_file_1.validateMaxPerFile)(_search_options === null || _search_options === void 0 ? void 0 : _search_options.maxPerFile);
|
|
400
402
|
const finalLimit = top_k !== null && top_k !== void 0 ? top_k : 10;
|
|
401
403
|
// ColBERT rerank is opt-in as of v0.17.1. On the 97-case eval it
|
|
402
404
|
// regresses MRR@10 by ~3% and doubles query latency; sweep across
|
|
@@ -742,7 +744,7 @@ class Searcher {
|
|
|
742
744
|
: {}));
|
|
743
745
|
}
|
|
744
746
|
const envMaxPerFile = Number.parseInt((_m = process.env.GMAX_MAX_PER_FILE) !== null && _m !== void 0 ? _m : "", 10);
|
|
745
|
-
const MAX_PER_FILE = Number.isFinite(envMaxPerFile) && envMaxPerFile > 0 ? envMaxPerFile : 3;
|
|
747
|
+
const MAX_PER_FILE = requestedMaxPerFile !== null && requestedMaxPerFile !== void 0 ? requestedMaxPerFile : (Number.isFinite(envMaxPerFile) && envMaxPerFile > 0 ? envMaxPerFile : 3);
|
|
746
748
|
// Rank the entire bounded candidate pool before trimming the result count.
|
|
747
749
|
// A limit-dependent cut here discarded contenders before structure boosts
|
|
748
750
|
// and dedup, so requesting three results could hide the top match returned
|
|
@@ -49,7 +49,7 @@ const config_1 = require("../../config");
|
|
|
49
49
|
* Explicit `gmax watch --daemon` is never gated: typing it is the user asking
|
|
50
50
|
* for a daemon in that moment.
|
|
51
51
|
*
|
|
52
|
-
* plugins/grepmax/hooks/
|
|
52
|
+
* plugins/grepmax/hooks/watch-lease.js carries a plain-JS copy of these semantics
|
|
53
53
|
* (env var checked first, then the file) because a SessionStart hook cannot
|
|
54
54
|
* import from dist. Keep the two in sync.
|
|
55
55
|
*/
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.codexAgentsPath = codexAgentsPath;
|
|
37
|
+
const os = __importStar(require("node:os"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
function codexAgentsPath() {
|
|
40
|
+
const home = process.env.CODEX_HOME
|
|
41
|
+
? path.resolve(process.env.CODEX_HOME)
|
|
42
|
+
: path.join(os.homedir(), ".codex");
|
|
43
|
+
return path.join(home, "AGENTS.md");
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.sendSearchCommand = sendSearchCommand;
|
|
13
|
+
const per_file_1 = require("../search/per-file");
|
|
14
|
+
const daemon_client_1 = require("./daemon-client");
|
|
15
|
+
/** An old live daemon must not silently ignore a request-scoped override. */
|
|
16
|
+
function sendSearchCommand(command, options) {
|
|
17
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
18
|
+
var _a;
|
|
19
|
+
const maxPerFile = (0, per_file_1.validateMaxPerFile)(command.maxPerFile);
|
|
20
|
+
if (maxPerFile !== undefined) {
|
|
21
|
+
const ping = yield (0, daemon_client_1.sendDaemonCommand)({ cmd: "ping" }, options);
|
|
22
|
+
if (!ping.ok)
|
|
23
|
+
return ping;
|
|
24
|
+
if (((_a = ping.capabilities) === null || _a === void 0 ? void 0 : _a.perFileSearch) !== 1) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: "unsupported per-file retrieval control",
|
|
28
|
+
hint: "run gmax watch restart with the updated CLI",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return (0, daemon_client_1.sendDaemonCommand)(command, options);
|
|
33
|
+
});
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { ensureSessionLease, readHookInput } = require("./watch-lease");
|
|
2
|
+
|
|
3
|
+
// Bounded renewal on turns and Bash activity keeps long CLI sessions watched.
|
|
4
|
+
// No detached timer, global watcher or additional MCP process is needed.
|
|
5
|
+
async function main() {
|
|
6
|
+
const input = await readHookInput();
|
|
7
|
+
await ensureSessionLease(input, input.cwd || process.cwd(), {
|
|
8
|
+
allowStart: true,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
main();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
const { execFileSync } = require("node:child_process");
|
|
2
1
|
const {
|
|
3
|
-
|
|
2
|
+
ensureSessionLease,
|
|
4
3
|
readHookInput,
|
|
5
4
|
registeredRootFor,
|
|
6
5
|
releaseSessionLeases,
|
|
@@ -20,19 +19,7 @@ async function main() {
|
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
execFileSync("gmax", ["watch", "--daemon", "-b"], {
|
|
27
|
-
timeout: 5000,
|
|
28
|
-
stdio: "ignore",
|
|
29
|
-
});
|
|
30
|
-
} catch {
|
|
31
|
-
try {
|
|
32
|
-
execFileSync("gmax", ["watch", "-b"], { timeout: 5000, stdio: "ignore" });
|
|
33
|
-
} catch {}
|
|
34
|
-
}
|
|
35
|
-
await acquireSessionLease(input, newCwd);
|
|
22
|
+
await ensureSessionLease(input, newCwd, { allowStart: true });
|
|
36
23
|
}
|
|
37
24
|
|
|
38
25
|
main();
|
|
@@ -2,7 +2,7 @@ const fs = require("node:fs");
|
|
|
2
2
|
const _path = require("node:path");
|
|
3
3
|
const { execFileSync } = require("node:child_process");
|
|
4
4
|
const {
|
|
5
|
-
|
|
5
|
+
ensureSessionLease,
|
|
6
6
|
readHookInput,
|
|
7
7
|
registeredRootFor,
|
|
8
8
|
} = require("./watch-lease");
|
|
@@ -70,43 +70,10 @@ function isProjectRegistered(dir) {
|
|
|
70
70
|
return registeredRootFor(dir) !== null;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
// A kill switch for the auto-start. Create ~/.gmax/autostart-disabled (or export
|
|
74
|
-
// GMAX_NO_AUTOSTART=1) to keep sessions from reviving the daemon — needed when the
|
|
75
|
-
// host is quarantined after a kernel-zone incident and the daemon's write volume is
|
|
76
|
-
// the thing under investigation. Delete the file to restore normal behavior.
|
|
77
|
-
//
|
|
78
|
-
// The CLI honors the same switch via src/lib/utils/autostart.ts (PATHS.autostartDisabledFile).
|
|
79
|
-
// This copy exists because a hook cannot import from dist — keep the two in sync.
|
|
80
|
-
function isAutostartDisabled() {
|
|
81
|
-
if (process.env.GMAX_NO_AUTOSTART === "1") return true;
|
|
82
|
-
try {
|
|
83
|
-
return require("node:fs").existsSync(
|
|
84
|
-
_path.join(require("node:os").homedir(), ".gmax", "autostart-disabled"),
|
|
85
|
-
);
|
|
86
|
-
} catch {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
73
|
async function startWatcher(input) {
|
|
92
|
-
if (isAutostartDisabled()) return;
|
|
93
74
|
const dir = input.cwd || process.cwd();
|
|
94
75
|
if (!isProjectRegistered(dir)) return;
|
|
95
|
-
|
|
96
|
-
execFileSync("gmax", ["watch", "--daemon", "-b"], {
|
|
97
|
-
timeout: 5000,
|
|
98
|
-
stdio: "ignore",
|
|
99
|
-
});
|
|
100
|
-
} catch {
|
|
101
|
-
// Fallback to per-project mode (older gmax without --daemon)
|
|
102
|
-
try {
|
|
103
|
-
execFileSync("gmax", ["watch", "-b"], { timeout: 5000, stdio: "ignore" });
|
|
104
|
-
} catch {
|
|
105
|
-
// Watcher may already be running or gmax not in PATH — ignore
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// The daemon only watches projects a session holds a lease on.
|
|
109
|
-
await acquireSessionLease(input, dir);
|
|
76
|
+
await ensureSessionLease(input, dir, { allowStart: true });
|
|
110
77
|
}
|
|
111
78
|
|
|
112
79
|
async function main() {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// Session watch leases for the hooks.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
// speaks the daemon's one-line JSON protocol directly.
|
|
3
|
+
// Claude's CLI integration takes a lease at startup, renews it during activity,
|
|
4
|
+
// and releases it at SessionEnd. Other MCP clients own separate leases. A hook
|
|
5
|
+
// cannot import from dist, so this speaks the one-line Unix socket protocol.
|
|
6
|
+
const { spawn } = require("node:child_process");
|
|
8
7
|
const fs = require("node:fs");
|
|
9
8
|
const net = require("node:net");
|
|
10
9
|
const os = require("node:os");
|
|
@@ -13,10 +12,18 @@ const path = require("node:path");
|
|
|
13
12
|
const GMAX_DIR = path.join(os.homedir(), ".gmax");
|
|
14
13
|
const SOCKET = path.join(GMAX_DIR, "daemon.sock");
|
|
15
14
|
|
|
16
|
-
//
|
|
17
|
-
// early, and the daemon caps any lease at 12h.
|
|
15
|
+
// Activity refreshes this TTL; idle sessions do not watch forever.
|
|
18
16
|
const SESSION_LEASE_TTL_MS = 4 * 60 * 60 * 1000;
|
|
19
17
|
|
|
18
|
+
function isAutostartDisabled() {
|
|
19
|
+
if (process.env.GMAX_NO_AUTOSTART === "1") return true;
|
|
20
|
+
try {
|
|
21
|
+
return fs.existsSync(path.join(GMAX_DIR, "autostart-disabled"));
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
function readHookInput(timeoutMs = 1000) {
|
|
21
28
|
return new Promise((resolve) => {
|
|
22
29
|
let data = "";
|
|
@@ -102,6 +109,7 @@ function sendOnce(cmd, timeoutMs) {
|
|
|
102
109
|
socket.on("error", (err) =>
|
|
103
110
|
finish({
|
|
104
111
|
ok: false,
|
|
112
|
+
absent: err.code === "ENOENT" || err.code === "ECONNREFUSED",
|
|
105
113
|
retry: err.code === "ENOENT" || err.code === "ECONNREFUSED",
|
|
106
114
|
}),
|
|
107
115
|
);
|
|
@@ -143,6 +151,42 @@ async function acquireSessionLease(input, dir) {
|
|
|
143
151
|
return resp !== null;
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
/** Reuse a lease-capable daemon; start one only after a definite absent socket. */
|
|
155
|
+
async function ensureSessionLease(input, dir, { allowStart = false } = {}) {
|
|
156
|
+
if (isAutostartDisabled() || !sessionHolder(input) || !registeredRootFor(dir))
|
|
157
|
+
return false;
|
|
158
|
+
const ping = await sendOnce({ cmd: "ping" }, 500);
|
|
159
|
+
if (ping.ok) {
|
|
160
|
+
if (!ping.resp.capabilities?.watchLeases) return false;
|
|
161
|
+
return acquireSessionLease(input, dir);
|
|
162
|
+
}
|
|
163
|
+
// Busy, initializing, denied and timed-out sockets belong to an existing
|
|
164
|
+
// daemon. Never start a per-project writer or replace that process here.
|
|
165
|
+
if (!allowStart || !ping.absent) return false;
|
|
166
|
+
try {
|
|
167
|
+
const child = spawn("gmax", ["watch", "--daemon", "-b"], {
|
|
168
|
+
detached: true,
|
|
169
|
+
stdio: "ignore",
|
|
170
|
+
cwd: dir,
|
|
171
|
+
// Another session can win the race after the absent-socket probe. The
|
|
172
|
+
// launcher must reuse that peer, even if its package version differs.
|
|
173
|
+
env: { ...process.env, GMAX_DAEMON_START_ONLY: "1" },
|
|
174
|
+
});
|
|
175
|
+
await new Promise((resolve, reject) => {
|
|
176
|
+
child.once("spawn", resolve);
|
|
177
|
+
child.once("error", reject);
|
|
178
|
+
});
|
|
179
|
+
child.unref();
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
// A cold daemon may take longer than a hook's budget to become ready. Later
|
|
184
|
+
// activity retries ownership instead of opening another watcher.
|
|
185
|
+
const ready = await sendDaemonLine({ cmd: "ping" }, 1500);
|
|
186
|
+
if (!ready?.capabilities?.watchLeases) return false;
|
|
187
|
+
return acquireSessionLease(input, dir);
|
|
188
|
+
}
|
|
189
|
+
|
|
146
190
|
async function releaseSessionLeases(input, dir) {
|
|
147
191
|
const holder = sessionHolder(input);
|
|
148
192
|
const root = registeredRootFor(dir);
|
|
@@ -157,6 +201,8 @@ async function releaseSessionLeases(input, dir) {
|
|
|
157
201
|
|
|
158
202
|
module.exports = {
|
|
159
203
|
acquireSessionLease,
|
|
204
|
+
ensureSessionLease,
|
|
205
|
+
isAutostartDisabled,
|
|
160
206
|
readHookInput,
|
|
161
207
|
registeredRootFor,
|
|
162
208
|
releaseSessionLeases,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"hooks": [
|
|
6
6
|
{
|
|
7
7
|
"type": "command",
|
|
8
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/start.js",
|
|
8
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/start.js\"",
|
|
9
9
|
"timeout": 10
|
|
10
10
|
}
|
|
11
11
|
]
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"hooks": [
|
|
17
17
|
{
|
|
18
18
|
"type": "command",
|
|
19
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/stop.js",
|
|
19
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/stop.js\"",
|
|
20
20
|
"timeout": 10
|
|
21
21
|
}
|
|
22
22
|
]
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"hooks": [
|
|
28
28
|
{
|
|
29
29
|
"type": "command",
|
|
30
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/cwd-changed.js",
|
|
31
|
-
"timeout":
|
|
30
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/cwd-changed.js\"",
|
|
31
|
+
"timeout": 10
|
|
32
32
|
}
|
|
33
33
|
]
|
|
34
34
|
}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"hooks": [
|
|
39
39
|
{
|
|
40
40
|
"type": "command",
|
|
41
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/subagent-start.js",
|
|
41
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/subagent-start.js\"",
|
|
42
42
|
"timeout": 3
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -50,11 +50,34 @@
|
|
|
50
50
|
"hooks": [
|
|
51
51
|
{
|
|
52
52
|
"type": "command",
|
|
53
|
-
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/pre-grep.js",
|
|
53
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-grep.js\"",
|
|
54
54
|
"timeout": 2
|
|
55
55
|
}
|
|
56
56
|
]
|
|
57
57
|
}
|
|
58
|
+
],
|
|
59
|
+
"UserPromptSubmit": [
|
|
60
|
+
{
|
|
61
|
+
"hooks": [
|
|
62
|
+
{
|
|
63
|
+
"type": "command",
|
|
64
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/activity.js\"",
|
|
65
|
+
"timeout": 10
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"PostToolUse": [
|
|
71
|
+
{
|
|
72
|
+
"matcher": "Bash",
|
|
73
|
+
"hooks": [
|
|
74
|
+
{
|
|
75
|
+
"type": "command",
|
|
76
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/activity.js\"",
|
|
77
|
+
"timeout": 10
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
58
81
|
]
|
|
59
82
|
}
|
|
60
83
|
}
|