- Fix slashes in agent identity causing invalid hook file paths (gt-vqhc) - Add Prerequisites section to README (gt-vzic) - Create CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md (gt-xbfw) - Update Install section for future package managers (gt-7wcf) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
// Package wisp provides hook file support for Gas Town agents.
|
|
//
|
|
// Hooks are used to attach work to an agent for restart-and-resume:
|
|
// - hook-<agent>.json files track what bead is assigned to an agent
|
|
// - Created by `gt hook`, `gt sling`, `gt handoff`
|
|
// - Read on session start to restore work context
|
|
// - Burned after pickup
|
|
//
|
|
// Hook files live in .beads/ alongside other beads data.
|
|
package wisp
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// WispType identifies the kind of hook file.
|
|
type WispType string
|
|
|
|
const (
|
|
// TypeSlungWork is a hook that attaches a bead to an agent's hook.
|
|
// Created by `gt hook`, `gt sling`, or `gt handoff`, and burned after pickup.
|
|
TypeSlungWork WispType = "slung-work"
|
|
)
|
|
|
|
// WispDir is the directory where hook files are stored.
|
|
// Hook files (hook-<agent>.json) live alongside other beads data.
|
|
const WispDir = ".beads"
|
|
|
|
// HookPrefix is the filename prefix for hook files.
|
|
const HookPrefix = "hook-"
|
|
|
|
// HookSuffix is the filename suffix for hook files.
|
|
const HookSuffix = ".json"
|
|
|
|
// Wisp is the common header for hook files.
|
|
type Wisp struct {
|
|
// Type identifies what kind of hook file this is.
|
|
Type WispType `json:"type"`
|
|
|
|
// CreatedAt is when the hook was created.
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
// CreatedBy identifies who created the hook (e.g., "crew/joe", "deacon").
|
|
CreatedBy string `json:"created_by"`
|
|
}
|
|
|
|
// SlungWork represents work attached to an agent's hook.
|
|
// Created by `gt hook`, `gt sling`, or `gt handoff` and burned after pickup.
|
|
type SlungWork struct {
|
|
Wisp
|
|
|
|
// BeadID is the issue/bead to work on (e.g., "gt-xxx").
|
|
BeadID string `json:"bead_id"`
|
|
|
|
// Context is optional additional context from the slinger.
|
|
Context string `json:"context,omitempty"`
|
|
|
|
// Subject is optional subject line (used in handoff mail).
|
|
Subject string `json:"subject,omitempty"`
|
|
}
|
|
|
|
// NewSlungWork creates a new slung work hook file.
|
|
func NewSlungWork(beadID, createdBy string) *SlungWork {
|
|
return &SlungWork{
|
|
Wisp: Wisp{
|
|
Type: TypeSlungWork,
|
|
CreatedAt: time.Now(),
|
|
CreatedBy: createdBy,
|
|
},
|
|
BeadID: beadID,
|
|
}
|
|
}
|
|
|
|
// HookFilename returns the filename for an agent's hook file.
|
|
// Agent identities may contain slashes (e.g., "gastown/crew/max"),
|
|
// which are replaced with underscores to create valid filenames.
|
|
func HookFilename(agent string) string {
|
|
safe := strings.ReplaceAll(agent, "/", "_")
|
|
return HookPrefix + safe + HookSuffix
|
|
}
|
|
|
|
// AgentFromHookFilename extracts the agent identity from a hook filename.
|
|
// Reverses the slash-to-underscore transformation done by HookFilename.
|
|
func AgentFromHookFilename(filename string) string {
|
|
if len(filename) <= len(HookPrefix)+len(HookSuffix) {
|
|
return ""
|
|
}
|
|
if filename[:len(HookPrefix)] != HookPrefix {
|
|
return ""
|
|
}
|
|
if filename[len(filename)-len(HookSuffix):] != HookSuffix {
|
|
return ""
|
|
}
|
|
safe := filename[len(HookPrefix) : len(filename)-len(HookSuffix)]
|
|
return strings.ReplaceAll(safe, "_", "/")
|
|
}
|