From 6789e4fd9ac34c72b412d4204d7daab775da46e3 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Thu, 4 Dec 2025 22:44:42 -0800 Subject: [PATCH] feat: add hooks to plugin.json, eliminating need for global install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The beads plugin now provides SessionStart and PreCompact hooks directly via plugin.json. Users no longer need to run 'bd setup claude' when using the plugin - hooks are automatically available. Changes: - Add hooks section to .claude-plugin/plugin.json with SessionStart and PreCompact hooks that run 'bd prime' - Update doctor/claude.go to recognize plugin-provided hooks as valid - Update tips.go to check for plugin installation when determining if Claude integration is complete - Update messaging to recommend plugin installation as primary option The 'bd setup claude' command remains available for CLI-only users who do not want to install the plugin. Closes #462 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .claude-plugin/plugin.json | 24 +++++++++ cmd/bd/doctor/claude.go | 34 ++++++------ cmd/bd/tips.go | 105 +++++++++++++++++++++++++++---------- 3 files changed, 116 insertions(+), 47 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index cb47f0d6..58f7899d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -27,5 +27,29 @@ ], "env": {} } + }, + "hooks": { + "SessionStart": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "bd prime" + } + ] + } + ], + "PreCompact": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "bd prime" + } + ] + } + ] } } diff --git a/cmd/bd/doctor/claude.go b/cmd/bd/doctor/claude.go index f0745b7d..d233ba9f 100644 --- a/cmd/bd/doctor/claude.go +++ b/cmd/bd/doctor/claude.go @@ -24,20 +24,14 @@ func CheckClaude() DoctorCheck { hasMCP := isMCPServerInstalled() hasHooks := hasClaudeHooks() - // Plugin provides slash commands and MCP server - if hasPlugin && hasHooks { + // Plugin now provides hooks directly via plugin.json, so if plugin is installed + // we consider hooks to be available (plugin hooks + any user-configured hooks) + if hasPlugin { return DoctorCheck{ Name: "Claude Integration", Status: "ok", - Message: "Plugin and hooks installed", - Detail: "Slash commands and workflow reminders enabled", - } - } else if hasPlugin && !hasHooks { - return DoctorCheck{ - Name: "Claude Integration", - Status: "warning", - Message: "Plugin installed but hooks missing", - Fix: "Run: bd setup claude", + Message: "Plugin installed", + Detail: "Slash commands and workflow hooks enabled via plugin", } } else if hasMCP && hasHooks { return DoctorCheck{ @@ -75,17 +69,19 @@ func CheckClaude() DoctorCheck { Name: "Claude Integration", Status: "warning", Message: "Not configured", - Detail: "Claude can use bd more effectively with hooks and optional plugin", + Detail: "Claude can use bd more effectively with the beads plugin", Fix: "Set up Claude integration:\n" + - " 1. Run 'bd setup claude' to add SessionStart/PreCompact hooks\n" + - " 2. (Optional) Install beads plugin for slash commands\n" + + " Option 1: Install the beads plugin (recommended)\n" + + " • Provides hooks, slash commands, and MCP tools automatically\n" + + " • See: https://github.com/steveyegge/beads#claude-code-plugin\n" + + "\n" + + " Option 2: CLI-only mode\n" + + " • Run 'bd setup claude' to add SessionStart/PreCompact hooks\n" + + " • No slash commands, but hooks provide workflow context\n" + "\n" + "Benefits:\n" + - " • Hooks: Auto-inject workflow context (~50-2k tokens)\n" + - " • Plugin: Convenient slash commands + MCP tools\n" + - " • CLI mode: Works without plugin (hooks + manual 'bd prime')\n" + - "\n" + - "See: bd setup claude --help", + " • Auto-inject workflow context on session start (~50-2k tokens)\n" + + " • Automatic context recovery before compaction", } } } diff --git a/cmd/bd/tips.go b/cmd/bd/tips.go index d679c76f..872d773b 100644 --- a/cmd/bd/tips.go +++ b/cmd/bd/tips.go @@ -2,12 +2,14 @@ package main import ( "context" + "encoding/json" "fmt" "math/rand" "os" "path/filepath" "sort" "strconv" + "strings" "sync" "time" @@ -244,41 +246,88 @@ func isClaudeDetected() bool { } // isClaudeSetupComplete checks if the beads Claude integration is properly configured. -// Checks for either global or project-level installation of the beads hooks. +// Returns true if the beads plugin is installed (provides hooks via plugin.json), +// or if hooks were manually installed via 'bd setup claude'. func isClaudeSetupComplete() bool { - // Check for global installation home, err := os.UserHomeDir() - if err == nil { - commandFile := filepath.Join(home, ".claude", "commands", "prime_beads.md") - hooksDir := filepath.Join(home, ".claude", "hooks") + if err != nil { + return false + } - // Check for prime_beads command - if _, err := os.Stat(commandFile); err == nil { - // Check for sessionstart hook (could be a file or directory) - hookPath := filepath.Join(hooksDir, "sessionstart") - if _, err := os.Stat(hookPath); err == nil { - return true // Global hooks installed - } - // Also check PreToolUse hook which is used by beads - preToolUsePath := filepath.Join(hooksDir, "PreToolUse") - if _, err := os.Stat(preToolUsePath); err == nil { - return true // Global hooks installed + // Check if beads plugin is installed - plugin now provides hooks automatically + settingsPath := filepath.Join(home, ".claude", "settings.json") + if data, err := os.ReadFile(settingsPath); err == nil { + var settings map[string]interface{} + if err := json.Unmarshal(data, &settings); err == nil { + if enabledPlugins, ok := settings["enabledPlugins"].(map[string]interface{}); ok { + for key, value := range enabledPlugins { + if strings.Contains(strings.ToLower(key), "beads") { + if enabled, ok := value.(bool); ok && enabled { + return true // Plugin installed - provides hooks + } + } + } } } } - // Check for project-level installation - commandFile := ".claude/commands/prime_beads.md" - hooksDir := ".claude/hooks" + // Check for manual hooks installation via 'bd setup claude' + // Global hooks in settings.json + if hasBeadsPrimeHooks(settingsPath) { + return true + } - if _, err := os.Stat(commandFile); err == nil { - hookPath := filepath.Join(hooksDir, "sessionstart") - if _, err := os.Stat(hookPath); err == nil { - return true // Project hooks installed + // Project-level hooks in .claude/settings.local.json + if hasBeadsPrimeHooks(".claude/settings.local.json") { + return true + } + + return false +} + +// hasBeadsPrimeHooks checks if a settings file has bd prime hooks configured +func hasBeadsPrimeHooks(settingsPath string) bool { + data, err := os.ReadFile(settingsPath) + if err != nil { + return false + } + + var settings map[string]interface{} + if err := json.Unmarshal(data, &settings); err != nil { + return false + } + + hooks, ok := settings["hooks"].(map[string]interface{}) + if !ok { + return false + } + + // Check SessionStart and PreCompact for "bd prime" + for _, event := range []string{"SessionStart", "PreCompact"} { + eventHooks, ok := hooks[event].([]interface{}) + if !ok { + continue } - preToolUsePath := filepath.Join(hooksDir, "PreToolUse") - if _, err := os.Stat(preToolUsePath); err == nil { - return true // Project hooks installed + + for _, hook := range eventHooks { + hookMap, ok := hook.(map[string]interface{}) + if !ok { + continue + } + commands, ok := hookMap["hooks"].([]interface{}) + if !ok { + continue + } + for _, cmd := range commands { + cmdMap, ok := cmd.(map[string]interface{}) + if !ok { + continue + } + cmdStr, _ := cmdMap["command"].(string) + if cmdStr == "bd prime" || cmdStr == "bd prime --stealth" { + return true + } + } } } @@ -288,11 +337,11 @@ func isClaudeSetupComplete() bool { // initDefaultTips registers the built-in tips. // Called during initialization to populate the tip registry. func initDefaultTips() { - // Claude setup tip - suggest running bd setup claude when Claude is detected + // Claude setup tip - suggest installing the beads plugin when Claude is detected // but the integration is not configured InjectTip( "claude_setup", - "Run 'bd setup claude' to enable automatic context recovery in Claude Code", + "Install the beads plugin for automatic workflow context, or run 'bd setup claude' for CLI-only mode", 100, // Highest priority - this is important for Claude users 24*time.Hour, // Daily minimum gap 0.6, // 60% chance when eligible (~4 times per week)