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>
156 lines
5.2 KiB
Go
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
|
|
}
|