Files
gastown/internal/cmd/hook.go
Steve Yegge 8131052207 Deprecate hook files, use pinned beads for propulsion (gt-rgd9x)
Replace hook file mechanism with discovery-based pinned beads:
- gt hook: now runs bd update <bead> --status=pinned
- gt sling: same, plus nudge to target
- gt handoff: same when bead ID provided
- gt prime: checks pinned beads instead of hook files
- gt mol status: no longer checks hook files

Key changes:
- outputAttachmentStatus: extended to all roles (was Crew/Polecat only)
- checkSlungWork: now queries pinned beads instead of reading hook files
- wisp/io.go functions: marked deprecated with migration notes

This follows Gas Town discovery over explicit state principle.
Hook files are kept for backward compatibility but no longer written.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 15:56:11 -08:00

110 lines
3.3 KiB
Go

package cmd
import (
"fmt"
"os"
"os/exec"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/style"
)
var hookCmd = &cobra.Command{
Use: "hook <bead-id>",
GroupID: GroupWork,
Short: "Attach work to your hook (durable across restarts)",
Long: `Attach a bead (issue) to your hook for durable work tracking.
The hook is the "durability primitive" - work on your hook survives session
restarts, context compaction, and handoffs. When you restart (via gt handoff),
your SessionStart hook finds the attached work and you continue from where
you left off.
This is "assign without action" - use gt sling to also start immediately,
or gt handoff to hook and restart with fresh context.
Examples:
gt hook gt-abc # Attach issue gt-abc to your hook
gt hook gt-abc -s "Fix the bug" # With subject for handoff mail
gt hook gt-abc -m "Check tests" # With context message
Related commands:
gt mol status # See what's on your hook
gt sling <bead> # Hook + start now (keep context)
gt handoff <bead> # Hook + restart (fresh context)
gt nudge <agent> # Send message to trigger execution`,
Args: cobra.ExactArgs(1),
RunE: runHook,
}
var (
hookSubject string
hookMessage string
hookDryRun bool
)
func init() {
hookCmd.Flags().StringVarP(&hookSubject, "subject", "s", "", "Subject for handoff mail (optional)")
hookCmd.Flags().StringVarP(&hookMessage, "message", "m", "", "Message for handoff mail (optional)")
hookCmd.Flags().BoolVarP(&hookDryRun, "dry-run", "n", false, "Show what would be done")
rootCmd.AddCommand(hookCmd)
}
func runHook(cmd *cobra.Command, args []string) error {
beadID := args[0]
// Polecats cannot hook - they use gt done for lifecycle
if polecatName := os.Getenv("GT_POLECAT"); polecatName != "" {
return fmt.Errorf("polecats cannot hook work (use gt done for handoff)")
}
// Verify the bead exists
if err := verifyBeadExists(beadID); err != nil {
return err
}
// Determine agent identity
agentID, _, _, err := resolveSelfTarget()
if err != nil {
return fmt.Errorf("detecting agent identity: %w", err)
}
fmt.Printf("%s Hooking %s...\n", style.Bold.Render("🪝"), beadID)
if hookDryRun {
fmt.Printf("Would run: bd update %s --status=pinned --assignee=%s\n", beadID, agentID)
if hookSubject != "" {
fmt.Printf(" subject (for handoff mail): %s\n", hookSubject)
}
if hookMessage != "" {
fmt.Printf(" context (for handoff mail): %s\n", hookMessage)
}
return nil
}
// Pin the bead using bd update (discovery-based approach)
pinCmd := exec.Command("bd", "update", beadID, "--status=pinned", "--assignee="+agentID)
pinCmd.Stderr = os.Stderr
if err := pinCmd.Run(); err != nil {
return fmt.Errorf("pinning bead: %w", err)
}
fmt.Printf("%s Work attached to hook (pinned bead)\n", style.Bold.Render("✓"))
fmt.Printf(" Use 'gt handoff' to restart with this work\n")
fmt.Printf(" Use 'gt mol status' to see hook status\n")
return nil
}
// verifyBeadExists checks that the bead exists using bd show.
// Defined in sling.go but duplicated here for clarity. Will be consolidated
// when sling.go is removed.
func verifyBeadExistsForHook(beadID string) error {
cmd := exec.Command("bd", "show", beadID, "--json")
if err := cmd.Run(); err != nil {
return fmt.Errorf("bead '%s' not found (bd show failed)", beadID)
}
return nil
}