Merge pull request #456 from abhinav/add-setup-stealth-flag

feat(setup claude): add --stealth flag
This commit is contained in:
Steve Yegge
2025-12-03 20:46:52 -08:00
committed by GitHub
3 changed files with 153 additions and 11 deletions

View File

@@ -9,6 +9,7 @@ var (
setupProject bool
setupCheck bool
setupRemove bool
setupStealth bool
)
var setupCmd = &cobra.Command{
@@ -86,7 +87,7 @@ agents from forgetting bd workflow after context compaction.`,
return
}
setup.InstallClaude(setupProject)
setup.InstallClaude(setupProject, setupStealth)
},
}
@@ -94,6 +95,7 @@ func init() {
setupClaudeCmd.Flags().BoolVar(&setupProject, "project", false, "Install for this project only (not globally)")
setupClaudeCmd.Flags().BoolVar(&setupCheck, "check", false, "Check if Claude integration is installed")
setupClaudeCmd.Flags().BoolVar(&setupRemove, "remove", false, "Remove bd hooks from Claude settings")
setupClaudeCmd.Flags().BoolVar(&setupStealth, "stealth", false, "Use 'bd prime --stealth' (flush only, no git operations)")
setupCursorCmd.Flags().BoolVar(&setupCheck, "check", false, "Check if Cursor integration is installed")
setupCursorCmd.Flags().BoolVar(&setupRemove, "remove", false, "Remove bd rules from Cursor")

View File

@@ -8,7 +8,7 @@ import (
)
// InstallClaude installs Claude Code hooks
func InstallClaude(project bool) {
func InstallClaude(project bool, stealth bool) {
var settingsPath string
if project {
@@ -49,13 +49,19 @@ func InstallClaude(project bool) {
settings["hooks"] = hooks
}
// Determine which command to use
command := "bd prime"
if stealth {
command = "bd prime --stealth"
}
// Add SessionStart hook
if addHookCommand(hooks, "SessionStart", "bd prime") {
if addHookCommand(hooks, "SessionStart", command) {
fmt.Println("✓ Registered SessionStart hook")
}
// Add PreCompact hook
if addHookCommand(hooks, "PreCompact", "bd prime") {
if addHookCommand(hooks, "PreCompact", command) {
fmt.Println("✓ Registered PreCompact hook")
}
@@ -137,9 +143,11 @@ func RemoveClaude(project bool) {
return
}
// Remove bd prime hooks
// Remove bd prime hooks (both variants for backwards compatibility)
removeHookCommand(hooks, "SessionStart", "bd prime")
removeHookCommand(hooks, "PreCompact", "bd prime")
removeHookCommand(hooks, "SessionStart", "bd prime --stealth")
removeHookCommand(hooks, "PreCompact", "bd prime --stealth")
// Write back
data, err = json.MarshalIndent(settings, "", " ")
@@ -284,7 +292,9 @@ func hasBeadsHooks(settingsPath string) bool {
if !ok {
continue
}
if cmdMap["command"] == "bd prime" {
// Check for either variant
cmd := cmdMap["command"]
if cmd == "bd prime" || cmd == "bd prime --stealth" {
return true
}
}

View File

@@ -22,6 +22,13 @@ func TestAddHookCommand(t *testing.T) {
command: "bd prime",
wantAdded: true,
},
{
name: "add stealth hook to empty hooks",
existingHooks: make(map[string]interface{}),
event: "SessionStart",
command: "bd prime --stealth",
wantAdded: true,
},
{
name: "hook already exists",
existingHooks: map[string]interface{}{
@@ -41,6 +48,25 @@ func TestAddHookCommand(t *testing.T) {
command: "bd prime",
wantAdded: false,
},
{
name: "stealth hook already exists",
existingHooks: map[string]interface{}{
"SessionStart": []interface{}{
map[string]interface{}{
"matcher": "",
"hooks": []interface{}{
map[string]interface{}{
"type": "command",
"command": "bd prime --stealth",
},
},
},
},
},
event: "SessionStart",
command: "bd prime --stealth",
wantAdded: false,
},
{
name: "add second hook alongside existing",
existingHooks: map[string]interface{}{
@@ -122,6 +148,25 @@ func TestRemoveHookCommand(t *testing.T) {
command: "bd prime",
wantRemaining: 0,
},
{
name: "remove stealth hook",
existingHooks: map[string]interface{}{
"SessionStart": []interface{}{
map[string]interface{}{
"matcher": "",
"hooks": []interface{}{
map[string]interface{}{
"type": "command",
"command": "bd prime --stealth",
},
},
},
},
},
event: "SessionStart",
command: "bd prime --stealth",
wantRemaining: 0,
},
{
name: "remove one of multiple hooks",
existingHooks: map[string]interface{}{
@@ -184,9 +229,9 @@ func TestHasBeadsHooks(t *testing.T) {
tmpDir := t.TempDir()
tests := []struct {
name string
name string
settingsData map[string]interface{}
want bool
want bool
}{
{
name: "has bd prime hook",
@@ -208,9 +253,66 @@ func TestHasBeadsHooks(t *testing.T) {
want: true,
},
{
name: "no hooks",
name: "has bd prime --stealth hook",
settingsData: map[string]interface{}{
"hooks": map[string]interface{}{
"SessionStart": []interface{}{
map[string]interface{}{
"matcher": "",
"hooks": []interface{}{
map[string]interface{}{
"type": "command",
"command": "bd prime --stealth",
},
},
},
},
},
},
want: true,
},
{
name: "has bd prime in PreCompact",
settingsData: map[string]interface{}{
"hooks": map[string]interface{}{
"PreCompact": []interface{}{
map[string]interface{}{
"matcher": "",
"hooks": []interface{}{
map[string]interface{}{
"type": "command",
"command": "bd prime",
},
},
},
},
},
},
want: true,
},
{
name: "has bd prime --stealth in PreCompact",
settingsData: map[string]interface{}{
"hooks": map[string]interface{}{
"PreCompact": []interface{}{
map[string]interface{}{
"matcher": "",
"hooks": []interface{}{
map[string]interface{}{
"type": "command",
"command": "bd prime --stealth",
},
},
},
},
},
},
want: true,
},
{
name: "no hooks",
settingsData: map[string]interface{}{},
want: false,
want: false,
},
{
name: "has other hooks but not bd prime",
@@ -242,7 +344,7 @@ func TestHasBeadsHooks(t *testing.T) {
t.Fatalf("Failed to marshal test data: %v", err)
}
if err := os.WriteFile(settingsPath, data, 0644); err != nil {
if err := os.WriteFile(settingsPath, data, 0o644); err != nil {
t.Fatalf("Failed to write test file: %v", err)
}
@@ -276,3 +378,31 @@ func TestIdempotency(t *testing.T) {
t.Errorf("Expected 1 hook, got %d", len(eventHooks))
}
}
// Test that running addHookCommand twice with stealth doesn't duplicate hooks
func TestIdempotencyWithStealth(t *testing.T) {
hooks := make(map[string]any)
if !addHookCommand(hooks, "SessionStart", "bd prime --stealth") {
t.Error("First call should have added the stealth hook")
}
// Second add (should detect existing)
if addHookCommand(hooks, "SessionStart", "bd prime --stealth") {
t.Error("Second call should have detected existing stealth hook")
}
// Verify only one hook exists
eventHooks := hooks["SessionStart"].([]any)
if len(eventHooks) != 1 {
t.Errorf("Expected 1 hook, got %d", len(eventHooks))
}
// and that it's the correct one
hookMap := eventHooks[0].(map[string]any)
commands := hookMap["hooks"].([]any)
cmdMap := commands[0].(map[string]any)
if cmdMap["command"] != "bd prime --stealth" {
t.Errorf("Expected 'bd prime --stealth', got %v", cmdMap["command"])
}
}