diff --git a/cmd/entire/cli/hooks_git_cmd.go b/cmd/entire/cli/hooks_git_cmd.go index 5cfe1b19b..7da6f4e6d 100644 --- a/cmd/entire/cli/hooks_git_cmd.go +++ b/cmd/entire/cli/hooks_git_cmd.go @@ -3,7 +3,9 @@ package cli import ( "context" "log/slog" + "time" + "github.com/entireio/cli/cmd/entire/cli/agent/external" "github.com/entireio/cli/cmd/entire/cli/logging" "github.com/entireio/cli/cmd/entire/cli/settings" "github.com/entireio/cli/cmd/entire/cli/strategy" @@ -97,6 +99,13 @@ func newHooksGitCmd() *cobra.Command { gitHooksDisabled = true return nil } + // Discover external agent plugins so GetByAgentType works correctly + // during condensation (e.g. post-commit). Without this, external agents + // registered in the hook phase cannot be resolved here, causing token + // usage and other agent-specific data to be missing from metadata.json. + discoveryCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + external.DiscoverAndRegister(discoveryCtx) hookLogCleanup = initHookLogging(ctx) return nil }, diff --git a/cmd/entire/cli/hooks_git_cmd_test.go b/cmd/entire/cli/hooks_git_cmd_test.go index f5727c00f..674ede722 100644 --- a/cmd/entire/cli/hooks_git_cmd_test.go +++ b/cmd/entire/cli/hooks_git_cmd_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/entireio/cli/cmd/entire/cli/agent" + "github.com/entireio/cli/cmd/entire/cli/agent/types" "github.com/entireio/cli/cmd/entire/cli/paths" "github.com/entireio/cli/cmd/entire/cli/session" ) @@ -167,3 +169,79 @@ func TestInitHookLogging_SkipsWhenDisabled(t *testing.T) { t.Errorf("expected .entire/logs to NOT be created when Entire is disabled, but it exists") } } + +// TestHooksGitCmd_DiscoverExternalAgents_WhenEnabled verifies that when Entire is set up +// and enabled, PersistentPreRunE calls external.DiscoverAndRegister so that external +// agents are available during hook execution (e.g. post-commit condensation). +func TestHooksGitCmd_DiscoverExternalAgents_WhenEnabled(t *testing.T) { + if _, err := exec.LookPath("sh"); err != nil { + t.Skip("sh not available") + } + + tmpDir := t.TempDir() + + // Initialize git repo first, then chdir so paths cache is correct + gitInit := exec.CommandContext(context.Background(), "git", "init") + gitInit.Dir = tmpDir + if err := gitInit.Run(); err != nil { + t.Fatalf("failed to init git repo: %v", err) + } + + t.Chdir(tmpDir) + paths.ClearWorktreeRootCache() + session.ClearGitCommonDirCache() + + // Reset global state before the test + gitHooksDisabled = false + + // Create .entire/settings.json with enabled: true and external_agents: true + entireDir := filepath.Join(tmpDir, paths.EntireDir) + if err := os.MkdirAll(entireDir, 0o755); err != nil { + t.Fatalf("failed to create .entire directory: %v", err) + } + settingsFile := filepath.Join(entireDir, "settings.json") + if err := os.WriteFile(settingsFile, []byte(`{"enabled":true,"external_agents":true}`), 0o644); err != nil { + t.Fatalf("failed to write settings file: %v", err) + } + + // Create a mock external agent binary in a temp PATH directory. + // Use a unique name to avoid conflicts with agents registered by other tests. + agentName := types.AgentName("hooktest-discovery-agent") + binDir := t.TempDir() + binPath := filepath.Join(binDir, "entire-agent-"+string(agentName)) + infoJSON := `{ + "protocol_version": 1, + "name": "` + string(agentName) + `", + "type": "Hook Test Agent", + "description": "Agent for hook discovery test", + "is_preview": false, + "protected_dirs": [], + "hook_names": [], + "capabilities": {} +}` + script := "#!/bin/sh\nif [ \"$1\" = \"info\" ]; then\n echo '" + infoJSON + "'\nfi\n" + if err := os.WriteFile(binPath, []byte(script), 0o755); err != nil { + t.Fatalf("failed to write mock agent binary: %v", err) + } + t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) + + // Execute the git hook command (post-commit) so PersistentPreRunE runs + cmd := newHooksGitCmd() + cmd.SetArgs([]string{"post-commit"}) + ctx := context.Background() + cmd.SetContext(ctx) + if err := cmd.Execute(); err != nil { + t.Fatalf("git hook command failed: %v", err) + } + + // PersistentPreRunE should not have disabled hooks + if gitHooksDisabled { + t.Fatal("gitHooksDisabled should be false when Entire is enabled") + } + + // The external agent should have been discovered and registered in the agent registry, + // confirming that DiscoverAndRegister was called during PersistentPreRunE. + if _, err := agent.Get(agentName); err != nil { + t.Errorf("expected external agent %q to be registered after hook pre-run, got: %v", agentName, err) + } +}