Skip to content

Comments

Gemini CLI Hook Support (PR #131 Part 3)#158

Open
ahundt wants to merge 5 commits intortk-ai:masterfrom
ahundt:feat/gemini-support-v2
Open

Gemini CLI Hook Support (PR #131 Part 3)#158
ahundt wants to merge 5 commits intortk-ai:masterfrom
ahundt:feat/gemini-support-v2

Conversation

@ahundt
Copy link

@ahundt ahundt commented Feb 17, 2026

PR 131 Part 3: Gemini CLI Hook Support

Branch: feat/gemini-support-v2 | Base: master (stacks on Part 1)
Tests: 560 pass | Parallel to: Part 2 (zero file conflicts)
Split from: PR #131
New deps: None
PR: #158


Context

Addresses item 1 from FlorianBruniaux's split request:

Gemini CLI support (rtk init --gemini) -- standalone, no deps on the rest

FlorianBruniaux said "standalone" but code analysis shows gemini_hook.rs imports 6 items from hook.rs (types and functions). This PR depends on PR 1 for the shared hook infrastructure, but is parallel to PR 2 (data safety).

Coordination with PR #141: FlorianBruniaux noted overlap with #141's JS-based hook. This PR achieves the same Windows support goal via compiled Rust binary -- no bash, node, or bun. Both PRs wanted cross-platform support; this approach has zero external runtime dependencies.


Merge Sequence

After PR 1 merges: retarget feat/gemini-support-v2 -> master, then merge. Can merge before or after PR 2.


Summary

Adds Gemini CLI support via rtk hook gemini and rtk init --gemini. Shares 65% of hook logic with Claude Code -- only the JSON wire format differs.

Impact: First multi-platform support. Future platforms (Cursor, Windsurf, etc.) only need a new protocol handler -- 35% code per platform, 65% shared.

rtk hook gemini -- BeforeTool handler

Different wire format from Claude:

  • Input: hook_event_name (must be "BeforeTool") + tool_name (must be "run_shell_command")
  • Output: decision/reason/updated_input (not permissionDecision/updatedInput)
  • Exit 0 with decision: "deny" works (unlike Claude bug #4669)

rtk init --gemini -- Setup

Patches ~/.gemini/settings.json, creates ~/.gemini/GEMINI.md with @RTK.md reference.

rtk init --gemini             # Gemini only
rtk init --claude             # Claude only
rtk init                      # Both platforms (default)
rtk init --gemini --uninstall # Remove Gemini hooks

Manual setup:

// ~/.gemini/settings.json
{"hooks":{"BeforeTool":[{"matcher":"run_shell_command","hooks":[{"type":"command","command":"rtk hook gemini"}]}]}}

Multi-platform init logic

Command Claude Gemini
rtk init Yes Yes
rtk init --claude Yes No
rtk init --gemini No Yes
rtk init --claude --gemini Yes Yes

Shared infrastructure extracted from init.rs:

  • patch_settings_shared() -- JSON patching for both Claude and Gemini
  • patch_instruction_file() -- Adds @RTK.md reference to CLAUDE.md or GEMINI.md
  • remove_hook_from_settings_file() -- Uninstall hook entry

Changes

6 files changed (+1037, -6)

New: src/cmd/gemini_hook.rs (490 lines, 19 tests)

Modified: src/cmd/mod.rs (+gemini_hook), src/main.rs (+HookCommands::Gemini, +--gemini/--claude init flags), src/init.rs (+run_gemini(), +uninstall_gemini(), +patch_gemini_settings(), extracted shared functions), INSTALL.md (+Gemini setup), README.md (+Gemini integration section)


Review Guide

Focus areas:

  1. src/cmd/gemini_hook.rs -- Gemini protocol compliance, guard checks
  2. src/init.rs -- Multi-platform initialization, shared function extraction

Not Included (Future)

Additional protocol handlers for other LLM CLIs (Cursor, Windsurf, Aider) will follow the same 35% code pattern established here.


Test Plan

  • cargo test -- 560 tests pass (gemini_hook: 19 new)
  • echo '{"hook_event_name":"BeforeTool","tool_name":"run_shell_command","tool_input":{"command":"git status"}}' | cargo run -- hook gemini -- allow with rewrite
  • echo '{"hook_event_name":"BeforeTool","tool_name":"write_file","tool_input":{"path":"test.txt"}}' | cargo run -- hook gemini -- NoOpinion (exit 0, no output)
  • cargo run -- init --gemini --auto-patch -- settings patched
  • cargo run -- init -- both platforms configured

Related PRs (Split from PR #131)

Part PR Description
1 #156 Hook Engine + Chained Commands
2 #157 Data Safety Rules
3 #158 Gemini CLI Support (this PR)

Merge order: Part 1 first → retarget Parts 2 & 3 to master → merge in any order

Rust binary replaces 204-line bash script as Claude Code PreToolUse hook.
Adds rtk hook claude, rtk run -c, and Windows support via cfg!(windows).
Closes rtk-ai#112 (chained commands missed).

Based on updated master (70c3786) which includes:
- Hook audit mode (rtk-ai#151)
- Claude Code agents and skills (d8f4659)
- tee raw output feature (rtk-ai#134)

Migrated from feat/rust-hooks (571bd86) with conflict resolution for:
- src/main.rs: Commands enum (preserved both hook audit + our hook commands)
- src/init.rs: Hook registration (integrated both approaches)

New files (src/cmd/ module):
- mod.rs: Module declarations (10 modules, excluding safety/trash/gemini for PR 1)
- hook.rs: Shared hook decision logic (21 tests, 3 safety tests removed for PR 2)
- claude_hook.rs: Claude Code JSON protocol handler (18 tests)
- lexer.rs: Quote-aware tokenizer (28 tests)
- analysis.rs: Chain parsing and shellism detection (10 tests)
- builtins.rs: cd/export/pwd/echo/true/false (8 tests)
- exec.rs: Command executor with recursion guard (22 tests, safety dispatch removed for PR 2)
- filters.rs: Output filter registry (5 tests)
- predicates.rs: Context predicates (4 tests)
- test_helpers.rs: Test utilities

Modified files:
- src/main.rs: Added Commands::Run, Commands::Hook, HookCommands enum, routing
- src/init.rs: Changed patch_settings_json to use rtk hook claude binary command
- hooks/rtk-rewrite.sh: Replaced 204-line bash script with 4-line shim (exec rtk hook claude)
- Cargo.toml: Added which = 7 for PATH resolution
- INSTALL.md: Added Windows installation section

Windows support:
- exec.rs:175-176: cfg!(windows) selects cmd /C vs sh -c for shell passthrough
- predicates.rs:26: USERPROFILE fallback for Windows home directory
- No bash, node, or bun dependency - rtk hook claude is a compiled Rust binary

Tests: All 541 tests pass
…commands

Replaces stub check_for_hook_inner with full tokenize+native-path dispatch.
Adds route_native_command() with replace_first_word/route_pnpm/route_npx
helpers to route single parsed commands to optimized RTK subcommands.

Chains (&&/||/;) and shellisms still use rtk run -c. No safety integration
(PR rtk-ai#157 adds that). Mirrors ~/.claude/hooks/rtk-rewrite.sh routing table.
Corrects shell script vitest double-run bug for pnpm vitest run flags.
@ahundt ahundt force-pushed the feat/gemini-support-v2 branch from a0ae712 to c84955a Compare February 19, 2026 07:56
rtk has no `tail` subcommand — routing to "rtk tail" was silently
broken (rtk would error "unrecognized subcommand"). Remove the Route
entry so the command falls through to `rtk run -c '...'` correctly.

Move the log-tailing test cases from test_routing_native_commands
(which asserted the broken path) into test_routing_fallbacks_to_rtk_run
where they correctly verify the rtk-run-c fallback behavior.
Port tests added during the ROUTES table integration that were missing
from the v2 worktree:

registry.rs:
- 12 classify tests for Python/Go commands (pytest, go×4, ruff×2,
  pip×3, golangci-lint) that verify PATTERNS/RULES and ROUTES alignment
- 11 lookup tests (test_lookup_*, test_no_duplicate_binaries_in_routes,
  test_lookup_is_o1_consistent) that verify O(1) HashMap routing

hook.rs:
- Extend test_routing_native_commands from 20 to 47 cases covering all
  ROUTES entries: docker, kubectl, curl, eslint, tsc, prettier,
  playwright, prisma, pytest, golangci-lint, ruff, pip, gh variants
- Add test_routing_subcommand_filter_fallback (14 cases) verifying that
  Only[] subcommand filters correctly reject unmatched subcommands

Total: 545 → 569 tests (+24)
Add rtk hook gemini for Gemini CLI BeforeTool hook handler.
Different wire format from Claude: uses decision/reason fields,
hook_event_name/tool_name guards, and run_shell_command matcher.

Add rtk init --gemini for Gemini CLI integration with:
- RTK.md deployment to ~/.gemini/
- GEMINI.md patching with @RTK.md reference
- settings.json hook registration

Multi-platform init: --claude (default) and --gemini flags.
Both can coexist. Shared infrastructure: patch_settings_shared,
patch_instruction_file, remove_hook_from_settings_file.

Additive platform selection: rtk init with no flags sets up both
Claude Code and Gemini CLI. --claude alone skips Gemini, --gemini
alone skips Claude.

Tests: 560 total (19 gemini_hook tests)
@ahundt ahundt force-pushed the feat/gemini-support-v2 branch from c84955a to d2c2dca Compare February 19, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant