docs: comprehensive hook/sling/handoff/nudge audit

- Update propulsion-principle.md: implementation status now accurate
- Update beads-data-plane.md: correct command syntax
- Fix hook.go: clarify durability semantics, add related commands
- Fix sling.go: use reliable NudgePane instead of raw tmux send-keys
- Add tmux.NudgePane: pane-targeted reliable message delivery

The command menagerie:
  gt hook    = assign (durability)
  gt nudge   = communicate (generic messaging)
  gt sling   = hook + nudge "start working"
  gt handoff = hook + restart (GUPP kicks in)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-24 21:17:25 -08:00
parent 57179c1e33
commit 23600af50c
6 changed files with 62 additions and 19 deletions

View File

@@ -16,12 +16,13 @@ var hookCmd = &cobra.Command{
Short: "Attach work to your hook (durable across restarts)",
Long: `Attach a bead (issue) to your hook for durable work tracking.
The hook is a lightweight, ephemeral attachment point. When you restart
(via gt handoff), your SessionStart hook finds the slung work and you
continue from where you left off.
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 the "durability primitive" - work on your hook survives session
restarts. For the full restart-and-resume flow, use: gt handoff <bead>
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
@@ -30,8 +31,9 @@ Examples:
Related commands:
gt mol status # See what's on your hook
gt handoff <bead> # Hook + restart in one step
gt handoff # Restart (uses existing hook if present)`,
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,
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/tmux"
"github.com/steveyegge/gastown/internal/wisp"
)
@@ -134,6 +135,7 @@ func runSling(cmd *cobra.Command, args []string) error {
}
// injectStartPrompt sends a prompt to the target pane to start working.
// Uses the reliable nudge pattern: literal mode + 500ms debounce + separate Enter.
func injectStartPrompt(pane, beadID, subject string) error {
if pane == "" {
return fmt.Errorf("no target pane")
@@ -147,9 +149,9 @@ func injectStartPrompt(pane, beadID, subject string) error {
prompt = fmt.Sprintf("Work slung: %s. Start working on it now - run `gt mol status` to see the hook, then begin.", beadID)
}
// Use tmux send-keys to inject the prompt
// Add Enter to submit it
return exec.Command("tmux", "send-keys", "-t", pane, prompt, "Enter").Run()
// Use the reliable nudge pattern (same as gt nudge / tmux.NudgeSession)
t := tmux.NewTmux()
return t.NudgePane(pane, prompt)
}
// resolveTargetAgent converts a target spec to agent ID and pane.

View File

@@ -207,6 +207,25 @@ func (t *Tmux) NudgeSession(session, message string) error {
return nil
}
// NudgePane sends a message to a specific pane reliably.
// Same pattern as NudgeSession but targets a pane ID (e.g., "%9") instead of session name.
func (t *Tmux) NudgePane(pane, message string) error {
// 1. Send text in literal mode (handles special characters)
if _, err := t.run("send-keys", "-t", pane, "-l", message); err != nil {
return err
}
// 2. Wait 500ms for paste to complete (tested, required)
time.Sleep(500 * time.Millisecond)
// 3. Send Enter as separate command (key to reliability)
if _, err := t.run("send-keys", "-t", pane, "Enter"); err != nil {
return err
}
return nil
}
// GetPaneCommand returns the current command running in a pane.
// Returns "bash", "zsh", "claude", "node", etc.
func (t *Tmux) GetPaneCommand(session string) (string, error) {