Skip to content

feat: wire Gemini CLI post-file-edit hook#648

Draft
khaong wants to merge 4 commits intorefactoring-transcript-readsfrom
post-file-edit-gemini
Draft

feat: wire Gemini CLI post-file-edit hook#648
khaong wants to merge 4 commits intorefactoring-transcript-readsfrom
post-file-edit-gemini

Conversation

@khaong
Copy link
Contributor

@khaong khaong commented Mar 6, 2026

Summary

  • Wire post-file-edit hook for Gemini CLI agent's file-modifying tools (write_file, edit_file, save_file, replace)
  • Parse AfterTool hook input with fallback path resolution (file_pathpathfilename)
  • Add observability logging to resolveFilesTouched to distinguish hook-tracked files from transcript fallback
  • Update hook counts in integration tests (12 → 16 with 4 new AfterTool matchers)

Details

Part of the post-file-edit hook rollout (one PR per agent). Framework + Claude Code wiring in #637.

Gemini CLI's AfterTool hooks use matcher-based config in .gemini/settings.json. Each file-modifying tool gets its own matcher entry that routes to entire hooks gemini-cli post-file-edit. The hook parses the tool input JSON to extract the file path, handling different field names across tools.

Test plan

  • Unit tests: parseFileEdit with valid input, fallback path fields, and missing path
  • Hook installation tests: verify 4 new AfterTool matchers + idempotency
  • Integration tests: updated hook count expectations
  • E2E: TestSingleSessionManualCommit --agent gemini-cli — confirmed hook fires and file tracked
  • CI: mise run fmt && mise run lint && mise run test:ci

🤖 Generated with Claude Code

khaong and others added 2 commits March 6, 2026 23:56
Add AfterTool matchers for write_file, edit_file, save_file, and replace
that fire `entire hooks gemini post-file-edit`. The hook extracts file_path
from tool_input (falling back to path/filename fields) and emits a FileEdit
lifecycle event for real-time file tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: c0d6b1e17aba
Log whether files came from hook tracking or transcript fallback, with
counts from each source. Helps verify post-file-edit hook wiring in E2E.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 53bde199cff1
Copilot AI review requested due to automatic review settings March 6, 2026 22:23
@cursor
Copy link

cursor bot commented Mar 6, 2026

PR Summary

Medium Risk
Touches lifecycle hook handling and file-tracking/commit logic; while mostly additive and well-tested, incorrect parsing or hook installation could lead to missed or extra tracked files during commits.

Overview
Adds Gemini CLI support for a new post-file-edit hook by installing per-tool AfterTool matchers for file-modifying tools and exposing the hook verb via HookNames().

Implements GeminiCLIAgent parsing of AfterTool hook payloads into agent.FileEdit events (with file_path/path/filename fallback and non-blocking debug logging on unexpected formats), and updates manual-commit file resolution to merge hook-tracked files with the on-disk tracking file before falling back to transcript extraction.

Updates hook registry classification and expands unit/integration coverage, including new end-to-end Gemini file-tracking tests and updated hook-count expectations (12 → 16).

Written by Cursor Bugbot for commit e568ab8. Configure here.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Wires Gemini CLI’s post-file-edit hook into Entire’s lifecycle framework so file modifications can be tracked in real time (reducing reliance on transcript parsing), and updates tests/observability accordingly.

Changes:

  • Add Gemini CLI hook installation entries (AfterTool matchers) for file-modifying tools and parse AfterTool hook payloads into FileEdit lifecycle events.
  • Add debug logging in resolveFilesTouched to distinguish hook-tracked files vs transcript-extraction fallback.
  • Update unit/integration tests to reflect the new hooks and parsing behavior.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
cmd/entire/cli/strategy/manual_commit_hooks.go Adds debug logs to clarify whether files came from hook tracking vs transcript fallback.
cmd/entire/cli/strategy/common_test.go Minor formatting alignment in tests.
cmd/entire/cli/integration_test/agent_test.go Updates Gemini hook installation expected count to include new matchers.
cmd/entire/cli/hook_registry.go Treats post-file-edit as a tool hook type for logging/spans.
cmd/entire/cli/agent/geminicli/types.go Adds AfterTool hook payload type and defines file-modification tool list.
cmd/entire/cli/agent/geminicli/lifecycle.go Parses post-file-edit hook payload into agent.FileEdit events.
cmd/entire/cli/agent/geminicli/lifecycle_test.go Adds unit tests for Gemini post-file-edit parsing (including fallback path fields).
cmd/entire/cli/agent/geminicli/hooks.go Installs new AfterTool matchers to route file-modifying tools to post-file-edit.
cmd/entire/cli/agent/geminicli/hooks_test.go Updates hook installation expectations and verifies the new AfterTool matchers.
Comments suppressed due to low confidence (1)

cmd/entire/cli/agent/geminicli/hooks_test.go:138

  • The current idempotency test covers “second install returns 0”, but there isn’t a regression test for upgrading an existing settings.json from the previous Gemini hook set (without post-file-edit matchers). Adding a test that starts with a pre-existing AfterTool config from the old version and asserts InstallHooks(non-force) adds the new matchers would prevent upgrade regressions.
	// Second install should add 0 hooks
	count2, err := agent.InstallHooks(context.Background(), false, false)
	if err != nil {
		t.Fatalf("second InstallHooks() error = %v", err)
	}

Comment on lines +164 to +168
// File-edit hooks (AfterTool with specific matchers for file-modifying tools)
postFileEditCmd := cmdPrefix + "post-file-edit"
for _, toolName := range FileModificationTools {
afterTool = addGeminiHook(afterTool, toolName, "entire-post-file-edit-"+toolName, postFileEditCmd)
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InstallHooks() can return 0 (idempotent) based solely on the existing SessionStart hook command, which means repos that already had the older Gemini hook set installed won’t get these newly-added post-file-edit AfterTool matchers unless users pass --force. Consider updating the idempotency check to verify that all required AfterTool matchers (including the new file-edit tool matchers) are present before early-returning 0, so upgrades happen automatically.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idempotency check uses the SessionStart command string — if the binary path matches, it early-returns. But addGeminiHook appends new matchers to the existing array regardless, so existing installs do get the new matchers on re-enable.

That said, there's a valid broader concern: users who never re-run entire enable won't get new hooks. We've noted this as future work — need a hook auto-update mechanism (version check or count comparison during session-start). Tracking separately from the per-agent rollout.

// - pre-compress (1)
// - notification (1)
count := 12
count := 16
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InstallHooks() returns a hard-coded hook count (16). Since this now depends on FileModificationTools, it’s easy for the count/comment/tests to drift if that list changes. Consider computing the return count from a base constant plus len(FileModificationTools) (or counting the hooks you add) to keep it correct automatically.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — all agents currently hardcode their count and this matches the existing pattern. The comment enumerates every hook. A dynamic count is a nice-to-have but low priority given the pattern is consistent across agents.

Comment on lines +208 to +211
var toolInput fileEditToolInput
if err := json.Unmarshal(raw.ToolInput, &toolInput); err != nil {
return nil, nil //nolint:nilnil,nilerr // tool_input doesn't match expected schema
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseFileEdit silently drops events when tool_input can’t be unmarshaled, which makes it hard to diagnose schema mismatches (e.g., tool_input missing/null or using an unexpected structure). Consider adding a Debug log (similar to ClaudeCode’s parseFileEdit) that includes session_id and tool_name when Unmarshal fails, while still fail-opening (returning nil event).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debug log was already there (lines 212-215 in the diff). Added tool_name to it in e1ae412 for better diagnostics.

- Add debug logging in clearFilesTracking when getStateStore fails
- Add debug logging in Gemini parseFileEdit on unmarshal failure
- Add filename fallback test for fileEditToolInput.filePath()
- Add Gemini-specific integration test for post-file-edit flow
- Update stale resolveFilesTouched doc comment (three sources)
- Fix comments: FilePath field, hook_registry, integration test helpers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 1cd4a907d16c
@khaong khaong requested a review from Copilot March 6, 2026 23:33
@khaong
Copy link
Contributor Author

khaong commented Mar 6, 2026

bugbot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

return "subagent"
case geminicli.HookNameBeforeTool, geminicli.HookNameAfterTool,
claudecode.HookNamePostFileEdit:
claudecode.HookNamePostFileEdit: // all agents use "post-file-edit"; only one constant needed (Go disallows duplicate case values)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getHookType() classifies "post-file-edit" hooks by referencing claudecode.HookNamePostFileEdit, which couples generic hook behavior to the Claude Code agent package. Since Gemini now defines HookNamePostFileEdit too, consider using the string literal "post-file-edit" (or a shared constant in a common package) in the switch to avoid cross-agent dependency and keep the comment accurate.

Suggested change
claudecode.HookNamePostFileEdit: // all agents use "post-file-edit"; only one constant needed (Go disallows duplicate case values)
"post-file-edit": // all agents use the "post-file-edit" hook name; match by literal to avoid cross-agent dependency

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reference predates this PR — it was introduced in #637 (the upstack). All agents define the same "post-file-edit" string value so Go's switch doesn't allow duplicates. A shared constant in a common package would be the cleaner long-term fix; will address in the base PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: a1d57ae9e8d0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants