Skip to content

Commit c5d4a65

Browse files
authored
Merge pull request #2250 from dyoshikawa/resolve-scrap-issue-1983-grok-hooks
feat(hooks): add Grok CLI hooks adapter
2 parents eb93295 + b744cf4 commit c5d4a65

14 files changed

Lines changed: 576 additions & 5 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ rulesync.local.jsonc
372372
**/.devin/hooks.v1.json
373373
**/.vibe/hooks.toml
374374
**/.reasonix/settings.json
375+
**/.grok/hooks/rulesync.json
375376
**/.cline/command-permissions.json
376377
**/.cursor/cli.json
377378
**/.junie/allowlist.json

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ The tables below show whether each tool supports a given feature (✅ = supporte
9595
| GitHub Copilot CLI || || |||| |
9696
| Goose |||||||||
9797
| Hermes Agent || |||||||
98-
| Grok CLI || || ||| ||
98+
| Grok CLI || || ||| ||
9999
| Cursor |||||||||
100100
| deepagents-cli || || |||| |
101101
| Factory Droid || |||||||

docs/reference/file-formats.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Example:
117117

118118
- `version`: Schema version (currently `1`).
119119
- `hooks`: Map of canonical event names to an array of hook entries. These are dispatched to every tool that supports the given event.
120-
- `cursor.hooks`, `claudecode.hooks`, `opencode.hooks`, `kilo.hooks`, `copilot.hooks`, `copilotcli.hooks`, `factorydroid.hooks`, `codexcli.hooks`, `goose.hooks`, `deepagents.hooks`, `kiro.hooks`, `kiro-ide.hooks`, `qwencode.hooks`: Tool-specific **override keys**. Entries under these keys are emitted only for the corresponding tool, so tool-only events (e.g. `afterFileEdit` for Cursor/OpenCode/Kilo, `worktreeCreate` for Claude Code, `afterError` for Copilot/Copilot CLI, `PostFileSave`/`PreTaskExec` for Kiro IDE) can coexist with shared ones without leaking to other tools. `copilotcli.hooks` falls back to `copilot.hooks`, which in turn falls back to the shared `hooks` block.
120+
- `cursor.hooks`, `claudecode.hooks`, `opencode.hooks`, `kilo.hooks`, `copilot.hooks`, `copilotcli.hooks`, `factorydroid.hooks`, `codexcli.hooks`, `goose.hooks`, `deepagents.hooks`, `kiro.hooks`, `kiro-ide.hooks`, `qwencode.hooks`, `grokcli.hooks`: Tool-specific **override keys**. Entries under these keys are emitted only for the corresponding tool, so tool-only events (e.g. `afterFileEdit` for Cursor/OpenCode/Kilo, `worktreeCreate` for Claude Code, `afterError` for Copilot/Copilot CLI, `PostFileSave`/`PreTaskExec` for Kiro IDE) can coexist with shared ones without leaking to other tools. `copilotcli.hooks` falls back to `copilot.hooks`, which in turn falls back to the shared `hooks` block.
121121

122122
**Hook entry keys:**
123123

@@ -214,6 +214,8 @@ Events present in the shared `hooks` block but unsupported by a given tool are s
214214

215215
> **Note:** Reasonix hooks are written to a dedicated `.reasonix/settings.json` (project) / `~/.reasonix/settings.json` (global) — a Claude-Code-style but standalone JSON file, separate from the `[permissions]`/`[[plugins]]` TOML config. Unlike Claude Code, each event key maps directly to a **flat array** of hook objects (no `matcher`/`hooks` wrapper): `{ "EventName": [ { "match": "...", "command": "...", "description": "...", "timeout": ... } ] }`. All ten of Reasonix's documented events are mapped — `preToolUse` ⇄ `PreToolUse`, `postToolUse` ⇄ `PostToolUse`, `beforeSubmitPrompt` ⇄ `UserPromptSubmit`, `stop` ⇄ `Stop`, `sessionStart` ⇄ `SessionStart`, `sessionEnd` ⇄ `SessionEnd`, `subagentStop` ⇄ `SubagentStop`, `postModelInvocation` ⇄ `PostLLMCall`, `notification` ⇄ `Notification`, and `preCompact` ⇄ `PreCompact`. `match` (Reasonix's matcher field name) is honored only on `PreToolUse`/`PostToolUse`; a matcher on any other event is dropped with a warning. The canonical `timeout` field is documented in seconds, while Reasonix's `timeout` is milliseconds, so rulesync converts (`× 1000` on generate, `÷ 1000` on import). Only `command`-type hooks are supported. The `settings.json` file is not documented as holding anything besides hooks today, but rulesync merges non-destructively and never deletes it, in case a future Reasonix version adds other keys. See the [Reasonix Hooks guide](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md).
216216

217+
> **Note:** Grok CLI (xAI Grok Build) hooks are written to a dedicated, standalone `rulesync.json` that Grok auto-discovers from `.grok/hooks/*.json` (project) / `~/.grok/hooks/*.json` (global). The JSON shape is Claude-Code-compatible: each event nests under the top-level `hooks` key as a per-matcher array (`{ "hooks": { "EventName": [ { "matcher": "...", "hooks": [ { "type": "command", "command": "...", "timeout": ... } ] } ] } }`). All fourteen documented events map 1:1 onto canonical arms — `sessionStart` ⇄ `SessionStart`, `sessionEnd` ⇄ `SessionEnd`, `beforeSubmitPrompt` ⇄ `UserPromptSubmit`, `preToolUse` ⇄ `PreToolUse`, `postToolUse` ⇄ `PostToolUse`, `postToolUseFailure` ⇄ `PostToolUseFailure`, `permissionDenied` ⇄ `PermissionDenied`, `stop` ⇄ `Stop`, `stopFailure` ⇄ `StopFailure`, `notification` ⇄ `Notification`, `subagentStart` ⇄ `SubagentStart`, `subagentStop` ⇄ `SubagentStop`, `preCompact` ⇄ `PreCompact`, and `postCompact` ⇄ `PostCompact`. A `matcher` (a regex tested against the tool name) is honored on the tool-name events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`), matching Claude Code's semantics; a matcher on any other event is dropped with a warning. Commands are emitted verbatim (Grok documents no project-directory variable). Grok also supports `http` hooks natively, but Rulesync's shared Claude-style converter emits only `command`-type hooks. See the [Grok hooks docs](https://docs.x.ai/build/features/hooks).
218+
217219
## `.github/mcp.json` and `.copilot/mcp-config.json`
218220

219221
Example:

docs/reference/supported-tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod
1515
| GitHub Copilot CLI | copilotcli | ✅ 🌏 | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | |
1616
| Goose | goose | ✅ 🌏 || ✅ 🌏 | ✅ 🌏 | ✅ 🌏 || ✅ 🌏 | 🌏 |
1717
| Hermes Agent | hermesagent || | 🌏 | 🌏 | ✅ 🌏 | 🌏 | 🌏 | 🌏 |
18-
| Grok CLI | grokcli | ✅ 🌏 | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | | ✅ 🌏 |
18+
| Grok CLI | grokcli | ✅ 🌏 | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
1919
| Cursor | cursor ||| ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
2020
| deepagents-cli | deepagents | ✅ 🌏 | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | 🌏 | |
2121
| Factory Droid | factorydroid | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |

skills/rulesync/file-formats.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Example:
117117

118118
- `version`: Schema version (currently `1`).
119119
- `hooks`: Map of canonical event names to an array of hook entries. These are dispatched to every tool that supports the given event.
120-
- `cursor.hooks`, `claudecode.hooks`, `opencode.hooks`, `kilo.hooks`, `copilot.hooks`, `copilotcli.hooks`, `factorydroid.hooks`, `codexcli.hooks`, `goose.hooks`, `deepagents.hooks`, `kiro.hooks`, `kiro-ide.hooks`, `qwencode.hooks`: Tool-specific **override keys**. Entries under these keys are emitted only for the corresponding tool, so tool-only events (e.g. `afterFileEdit` for Cursor/OpenCode/Kilo, `worktreeCreate` for Claude Code, `afterError` for Copilot/Copilot CLI, `PostFileSave`/`PreTaskExec` for Kiro IDE) can coexist with shared ones without leaking to other tools. `copilotcli.hooks` falls back to `copilot.hooks`, which in turn falls back to the shared `hooks` block.
120+
- `cursor.hooks`, `claudecode.hooks`, `opencode.hooks`, `kilo.hooks`, `copilot.hooks`, `copilotcli.hooks`, `factorydroid.hooks`, `codexcli.hooks`, `goose.hooks`, `deepagents.hooks`, `kiro.hooks`, `kiro-ide.hooks`, `qwencode.hooks`, `grokcli.hooks`: Tool-specific **override keys**. Entries under these keys are emitted only for the corresponding tool, so tool-only events (e.g. `afterFileEdit` for Cursor/OpenCode/Kilo, `worktreeCreate` for Claude Code, `afterError` for Copilot/Copilot CLI, `PostFileSave`/`PreTaskExec` for Kiro IDE) can coexist with shared ones without leaking to other tools. `copilotcli.hooks` falls back to `copilot.hooks`, which in turn falls back to the shared `hooks` block.
121121

122122
**Hook entry keys:**
123123

@@ -214,6 +214,8 @@ Events present in the shared `hooks` block but unsupported by a given tool are s
214214

215215
> **Note:** Reasonix hooks are written to a dedicated `.reasonix/settings.json` (project) / `~/.reasonix/settings.json` (global) — a Claude-Code-style but standalone JSON file, separate from the `[permissions]`/`[[plugins]]` TOML config. Unlike Claude Code, each event key maps directly to a **flat array** of hook objects (no `matcher`/`hooks` wrapper): `{ "EventName": [ { "match": "...", "command": "...", "description": "...", "timeout": ... } ] }`. All ten of Reasonix's documented events are mapped — `preToolUse` ⇄ `PreToolUse`, `postToolUse` ⇄ `PostToolUse`, `beforeSubmitPrompt` ⇄ `UserPromptSubmit`, `stop` ⇄ `Stop`, `sessionStart` ⇄ `SessionStart`, `sessionEnd` ⇄ `SessionEnd`, `subagentStop` ⇄ `SubagentStop`, `postModelInvocation` ⇄ `PostLLMCall`, `notification` ⇄ `Notification`, and `preCompact` ⇄ `PreCompact`. `match` (Reasonix's matcher field name) is honored only on `PreToolUse`/`PostToolUse`; a matcher on any other event is dropped with a warning. The canonical `timeout` field is documented in seconds, while Reasonix's `timeout` is milliseconds, so rulesync converts (`× 1000` on generate, `÷ 1000` on import). Only `command`-type hooks are supported. The `settings.json` file is not documented as holding anything besides hooks today, but rulesync merges non-destructively and never deletes it, in case a future Reasonix version adds other keys. See the [Reasonix Hooks guide](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md).
216216

217+
> **Note:** Grok CLI (xAI Grok Build) hooks are written to a dedicated, standalone `rulesync.json` that Grok auto-discovers from `.grok/hooks/*.json` (project) / `~/.grok/hooks/*.json` (global). The JSON shape is Claude-Code-compatible: each event nests under the top-level `hooks` key as a per-matcher array (`{ "hooks": { "EventName": [ { "matcher": "...", "hooks": [ { "type": "command", "command": "...", "timeout": ... } ] } ] } }`). All fourteen documented events map 1:1 onto canonical arms — `sessionStart` ⇄ `SessionStart`, `sessionEnd` ⇄ `SessionEnd`, `beforeSubmitPrompt` ⇄ `UserPromptSubmit`, `preToolUse` ⇄ `PreToolUse`, `postToolUse` ⇄ `PostToolUse`, `postToolUseFailure` ⇄ `PostToolUseFailure`, `permissionDenied` ⇄ `PermissionDenied`, `stop` ⇄ `Stop`, `stopFailure` ⇄ `StopFailure`, `notification` ⇄ `Notification`, `subagentStart` ⇄ `SubagentStart`, `subagentStop` ⇄ `SubagentStop`, `preCompact` ⇄ `PreCompact`, and `postCompact` ⇄ `PostCompact`. A `matcher` (a regex tested against the tool name) is honored on the tool-name events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`), matching Claude Code's semantics; a matcher on any other event is dropped with a warning. Commands are emitted verbatim (Grok documents no project-directory variable). Grok also supports `http` hooks natively, but Rulesync's shared Claude-style converter emits only `command`-type hooks. See the [Grok hooks docs](https://docs.x.ai/build/features/hooks).
218+
217219
## `.github/mcp.json` and `.copilot/mcp-config.json`
218220

219221
Example:

skills/rulesync/supported-tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod
1515
| GitHub Copilot CLI | copilotcli | ✅ 🌏 | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | |
1616
| Goose | goose | ✅ 🌏 || ✅ 🌏 | ✅ 🌏 | ✅ 🌏 || ✅ 🌏 | 🌏 |
1717
| Hermes Agent | hermesagent || | 🌏 | 🌏 | ✅ 🌏 | 🌏 | 🌏 | 🌏 |
18-
| Grok CLI | grokcli | ✅ 🌏 | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | | ✅ 🌏 |
18+
| Grok CLI | grokcli | ✅ 🌏 | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
1919
| Cursor | cursor ||| ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
2020
| deepagents-cli | deepagents | ✅ 🌏 | | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | 🌏 | |
2121
| Factory Droid | factorydroid | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |

src/constants/grokcli-paths.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ export const GROKCLI_CONFIG_FILE_NAME = "config.toml";
3030
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
3131
export const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
3232

33+
/**
34+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
35+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
36+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
37+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
38+
* @see https://docs.x.ai/build/features/hooks
39+
*/
40+
export const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
41+
42+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
43+
export const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
44+
3345
/**
3446
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
3547
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`

src/e2e/e2e-hooks.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const hooksGenerateTargets = [
5252
{ target: "antigravity-ide", outputPath: join(".agents", "hooks.json") },
5353
{ target: "antigravity-cli", outputPath: join(".agents", "hooks.json") },
5454
{ target: "augmentcode", outputPath: join(".augment", "settings.json") },
55+
{ target: "grokcli", outputPath: join(".grok", "hooks", "rulesync.json") },
5556
] as const;
5657

5758
// Targets exercised by dedicated `it`s (bespoke per-tool serialization).
@@ -585,6 +586,7 @@ const hooksGlobalTargets = [
585586
{ target: "antigravity-cli", outputPath: join(".gemini", "config", "hooks.json") },
586587
{ target: "augmentcode", outputPath: join(".augment", "settings.json") },
587588
{ target: "kiro-ide", outputPath: join(".kiro", "hooks", "rulesync.json") },
589+
{ target: "grokcli", outputPath: join(".grok", "hooks", "rulesync.json") },
588590
] as const;
589591

590592
// Global targets exercised by dedicated `it`s (bespoke per-tool serialization).

0 commit comments

Comments
 (0)