Files
gastown/internal/beads/beads_delegation.go
gastown/crew/dennis b60f016955 refactor(beads,mail): split large files into focused modules
Break down monolithic beads.go and mail.go into smaller, single-purpose files:

beads package:
- beads_agent.go: Agent-related bead operations
- beads_delegation.go: Delegation bead handling
- beads_dog.go: Dog pool operations
- beads_merge_slot.go: Merge slot management
- beads_mr.go: Merge request operations
- beads_redirect.go: Redirect bead handling
- beads_rig.go: Rig bead operations
- beads_role.go: Role bead management

cmd package:
- mail_announce.go: Announcement subcommand
- mail_check.go: Mail check subcommand
- mail_identity.go: Identity management
- mail_inbox.go: Inbox operations
- mail_queue.go: Queue subcommand
- mail_search.go: Search functionality
- mail_send.go: Send subcommand
- mail_thread.go: Thread operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 23:01:55 -08:00

156 lines
5.2 KiB
Go

// Package beads provides delegation tracking for work units.
package beads
import (
"encoding/json"
"fmt"
"strings"
)
// Delegation represents a work delegation relationship between work units.
// Delegation links a parent work unit to a child work unit, tracking who
// delegated the work and to whom, along with any terms of the delegation.
// This enables work distribution with credit cascade - work flows down,
// validation and credit flow up.
type Delegation struct {
// Parent is the work unit ID that delegated the work
Parent string `json:"parent"`
// Child is the work unit ID that received the delegated work
Child string `json:"child"`
// DelegatedBy is the entity (hop:// URI or actor string) that delegated
DelegatedBy string `json:"delegated_by"`
// DelegatedTo is the entity (hop:// URI or actor string) receiving delegation
DelegatedTo string `json:"delegated_to"`
// Terms contains optional conditions of the delegation
Terms *DelegationTerms `json:"terms,omitempty"`
// CreatedAt is when the delegation was created
CreatedAt string `json:"created_at,omitempty"`
}
// DelegationTerms holds optional terms/conditions for a delegation.
type DelegationTerms struct {
// Portion describes what part of the parent work is delegated
Portion string `json:"portion,omitempty"`
// Deadline is the expected completion date
Deadline string `json:"deadline,omitempty"`
// AcceptanceCriteria describes what constitutes completion
AcceptanceCriteria string `json:"acceptance_criteria,omitempty"`
// CreditShare is the percentage of credit that flows to the delegate (0-100)
CreditShare int `json:"credit_share,omitempty"`
}
// AddDelegation creates a delegation relationship from parent to child work unit.
// The delegation tracks who delegated (delegatedBy) and who received (delegatedTo),
// along with optional terms. Delegations enable credit cascade - when child work
// is completed, credit flows up to the parent work unit and its delegator.
//
// Note: This is stored as metadata on the child issue until bd CLI has native
// delegation support. Once bd supports `bd delegate add`, this will be updated.
func (b *Beads) AddDelegation(d *Delegation) error {
if d.Parent == "" || d.Child == "" {
return fmt.Errorf("delegation requires both parent and child work unit IDs")
}
if d.DelegatedBy == "" || d.DelegatedTo == "" {
return fmt.Errorf("delegation requires both delegated_by and delegated_to entities")
}
// Store delegation as JSON in the child issue's delegated_from slot
delegationJSON, err := json.Marshal(d)
if err != nil {
return fmt.Errorf("marshaling delegation: %w", err)
}
// Set the delegated_from slot on the child issue
_, err = b.run("slot", "set", d.Child, "delegated_from", string(delegationJSON))
if err != nil {
return fmt.Errorf("setting delegation slot: %w", err)
}
// Also add a dependency so child blocks parent (work must complete before parent can close)
if err := b.AddDependency(d.Parent, d.Child); err != nil {
// Log but don't fail - the delegation is still recorded
fmt.Printf("Warning: could not add blocking dependency for delegation: %v\n", err)
}
return nil
}
// RemoveDelegation removes a delegation relationship.
func (b *Beads) RemoveDelegation(parent, child string) error {
// Clear the delegated_from slot on the child
_, err := b.run("slot", "clear", child, "delegated_from")
if err != nil {
return fmt.Errorf("clearing delegation slot: %w", err)
}
// Also remove the blocking dependency
if err := b.RemoveDependency(parent, child); err != nil {
// Log but don't fail
fmt.Printf("Warning: could not remove blocking dependency: %v\n", err)
}
return nil
}
// GetDelegation retrieves the delegation information for a child work unit.
// Returns nil if the issue has no delegation.
func (b *Beads) GetDelegation(child string) (*Delegation, error) {
// Verify the issue exists first
if _, err := b.Show(child); err != nil {
return nil, fmt.Errorf("getting issue: %w", err)
}
// Get delegation from the slot
out, err := b.run("slot", "get", child, "delegated_from")
if err != nil {
// No delegation slot means no delegation
if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "no slot") {
return nil, nil
}
return nil, fmt.Errorf("getting delegation slot: %w", err)
}
slotValue := strings.TrimSpace(string(out))
if slotValue == "" || slotValue == "null" {
return nil, nil
}
var delegation Delegation
if err := json.Unmarshal([]byte(slotValue), &delegation); err != nil {
return nil, fmt.Errorf("parsing delegation: %w", err)
}
return &delegation, nil
}
// ListDelegationsFrom returns all delegations from a parent work unit.
// This searches for issues that have delegated_from pointing to the parent.
func (b *Beads) ListDelegationsFrom(parent string) ([]*Delegation, error) {
// List all issues that depend on this parent (delegated work blocks parent)
issues, err := b.List(ListOptions{Status: "all"})
if err != nil {
return nil, fmt.Errorf("listing issues: %w", err)
}
var delegations []*Delegation
for _, issue := range issues {
d, err := b.GetDelegation(issue.ID)
if err != nil {
continue // Skip issues with errors
}
if d != nil && d.Parent == parent {
delegations = append(delegations, d)
}
}
return delegations, nil
}