Skip to content

Commit eb53c48

Browse files
authored
Merge pull request #429 from entireio/rwr/move-hooks-to-hook-interface
Move HookNames and ParseHookEvent to HookSupport interface
2 parents c71f4df + 4d9c936 commit eb53c48

5 files changed

Lines changed: 36 additions & 34 deletions

File tree

cmd/entire/cli/agent/agent.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ import (
1111
// Each agent implementation (Claude Code, Cursor, Aider, etc.) converts its
1212
// native format to the normalized types defined in this package.
1313
//
14-
// The interface is organized into four groups:
15-
//
14+
// The interface is organized into three groups:
1615
// - Identity (5 methods): Name, Type, Description, DetectPresence, ProtectedDirs
17-
// - Event Mapping (2 methods): HookNames, ParseHookEvent
1816
// - Transcript Storage (3 methods): ReadTranscript, ChunkTranscript, ReassembleTranscript
19-
// - Legacy (8 methods): Will be moved to optional interfaces or removed in a future phase
17+
// - Legacy (6 methods): Will be moved to optional interfaces or removed in a future phase
2018
type Agent interface {
2119
// --- Identity ---
2220

@@ -41,18 +39,6 @@ type Agent interface {
4139
// Examples: [".claude"] for Claude, [".gemini"] for Gemini.
4240
ProtectedDirs() []string
4341

44-
// --- Event Mapping ---
45-
46-
// HookNames returns the hook verbs this agent supports.
47-
// These become subcommands under `entire hooks <agent>`.
48-
// e.g., ["stop", "user-prompt-submit", "session-start", "session-end"]
49-
HookNames() []string
50-
51-
// ParseHookEvent translates an agent-native hook into a normalized lifecycle Event.
52-
// Returns nil if the hook has no lifecycle significance (e.g., pass-through hooks).
53-
// This is the core contribution surface for new agent implementations.
54-
ParseHookEvent(hookName string, stdin io.Reader) (*Event, error)
55-
5642
// --- Transcript Storage ---
5743

5844
// ReadTranscript reads the raw transcript bytes for a session.
@@ -91,9 +77,23 @@ type Agent interface {
9177
// HookSupport is implemented by agents with lifecycle hooks.
9278
// This optional interface allows agents like Claude Code and Cursor to
9379
// install and manage hooks that notify Entire of agent events.
80+
//
81+
// The interface is organized into two groups:
82+
// - Hook Mapping (2 methods): HookNames, ParseHookEvent
83+
// - Hook Management (3 methods): InstallHooks, UninstallHooks, AreHooksInstalled
9484
type HookSupport interface {
9585
Agent
9686

87+
// HookNames returns the hook verbs this agent supports.
88+
// These become subcommands under `entire hooks <agent>`.
89+
// e.g., ["stop", "user-prompt-submit", "session-start", "session-end"]
90+
HookNames() []string
91+
92+
// ParseHookEvent translates an agent-native hook into a normalized lifecycle Event.
93+
// Returns nil if the hook has no lifecycle significance (e.g., pass-through hooks).
94+
// This is the core contribution surface for new agent implementations.
95+
ParseHookEvent(hookName string, stdin io.Reader) (*Event, error)
96+
9797
// InstallHooks installs agent-specific hooks.
9898
// If localDev is true, hooks point to local development build.
9999
// If force is true, removes existing Entire hooks before installing.

cmd/entire/cli/agent/agent_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@ func (m *mockAgent) DetectPresence() (bool, error) { return false, nil }
2121

2222
func (m *mockAgent) GetSessionID(_ *HookInput) string { return "" }
2323
func (m *mockAgent) ProtectedDirs() []string { return nil }
24-
func (m *mockAgent) HookNames() []string { return nil }
2524

26-
//nolint:nilnil // Mock implementation
27-
func (m *mockAgent) ParseHookEvent(_ string, _ io.Reader) (*Event, error) { return nil, nil }
28-
func (m *mockAgent) ReadTranscript(_ string) ([]byte, error) { return nil, nil }
25+
func (m *mockAgent) ReadTranscript(_ string) ([]byte, error) { return nil, nil }
2926
func (m *mockAgent) ChunkTranscript(content []byte, _ int) ([][]byte, error) {
3027
return [][]byte{content}, nil
3128
}
@@ -53,9 +50,13 @@ type mockHookSupport struct {
5350

5451
var _ HookSupport = (*mockHookSupport)(nil) // Compile-time interface check
5552

56-
func (m *mockHookSupport) InstallHooks(_, _ bool) (int, error) { return 0, nil }
57-
func (m *mockHookSupport) UninstallHooks() error { return nil }
58-
func (m *mockHookSupport) AreHooksInstalled() bool { return false }
53+
func (m *mockHookSupport) HookNames() []string { return nil }
54+
55+
//nolint:nilnil // Mock implementation
56+
func (m *mockHookSupport) ParseHookEvent(_ string, _ io.Reader) (*Event, error) { return nil, nil }
57+
func (m *mockHookSupport) InstallHooks(_, _ bool) (int, error) { return 0, nil }
58+
func (m *mockHookSupport) UninstallHooks() error { return nil }
59+
func (m *mockHookSupport) AreHooksInstalled() bool { return false }
5960

6061
// mockFileWatcher implements both Agent and FileWatcher interfaces.
6162
type mockFileWatcher struct {

cmd/entire/cli/hook_registry.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ func GetCurrentHookAgent() (agent.Agent, error) {
4444
return ag, nil
4545
}
4646

47-
// newAgentHooksCmd creates a hooks subcommand for an agent.
47+
// newAgentHooksCmd creates a hooks subcommand for an agent that implements HookSupport.
4848
// It dynamically creates subcommands for each hook the agent supports.
49-
func newAgentHooksCmd(agentName agent.AgentName, handler agent.Agent) *cobra.Command {
49+
func newAgentHooksCmd(agentName agent.AgentName, handler agent.HookSupport) *cobra.Command {
5050
cmd := &cobra.Command{
5151
Use: string(agentName),
5252
Short: handler.Description() + " hook handlers",
@@ -132,8 +132,13 @@ func newAgentHookVerbCmdWithLogging(agentName agent.AgentName, hookName string)
132132
return fmt.Errorf("failed to get agent %q: %w", agentName, agentErr)
133133
}
134134

135+
handler, ok := ag.(agent.HookSupport)
136+
if !ok {
137+
return fmt.Errorf("agent %q does not support hooks", agentName)
138+
}
139+
135140
// Use cmd.InOrStdin() to support testing with cmd.SetIn()
136-
event, parseErr := ag.ParseHookEvent(hookName, cmd.InOrStdin())
141+
event, parseErr := handler.ParseHookEvent(hookName, cmd.InOrStdin())
137142
if parseErr != nil {
138143
return fmt.Errorf("failed to parse hook event: %w", parseErr)
139144
}

cmd/entire/cli/hooks_cmd.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ func newHooksCmd() *cobra.Command {
2222
cmd.AddCommand(newHooksGitCmd())
2323

2424
// Dynamically add agent hook subcommands
25+
// Each agent that implements HookSupport gets its own subcommand tree
2526
for _, agentName := range agent.List() {
2627
ag, err := agent.Get(agentName)
2728
if err != nil {
2829
continue
2930
}
30-
cmd.AddCommand(newAgentHooksCmd(agentName, ag))
31+
if handler, ok := ag.(agent.HookSupport); ok {
32+
cmd.AddCommand(newAgentHooksCmd(agentName, handler))
33+
}
3134
}
3235

3336
return cmd

cmd/entire/cli/lifecycle_test.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cli
22

33
import (
44
"errors"
5-
"io"
65
"os"
76
"path/filepath"
87
"strings"
@@ -32,14 +31,8 @@ func (m *mockLifecycleAgent) Description() string { return "M
3231
func (m *mockLifecycleAgent) IsPreview() bool { return false }
3332
func (m *mockLifecycleAgent) DetectPresence() (bool, error) { return false, nil }
3433
func (m *mockLifecycleAgent) ProtectedDirs() []string { return nil }
35-
func (m *mockLifecycleAgent) HookNames() []string { return nil }
3634
func (m *mockLifecycleAgent) GetSessionID(_ *agent.HookInput) string { return "" }
3735

38-
//nolint:nilnil // Mock implementation
39-
func (m *mockLifecycleAgent) ParseHookEvent(_ string, _ io.Reader) (*agent.Event, error) {
40-
return nil, nil
41-
}
42-
4336
func (m *mockLifecycleAgent) ReadTranscript(_ string) ([]byte, error) {
4437
if m.transcriptErr != nil {
4538
return nil, m.transcriptErr

0 commit comments

Comments
 (0)