Add delegation relationship type to beads (gt-6r18e.5)

Add Delegation and DelegationTerms structs for tracking work delegation
between work units. This enables the HOP pattern of work flowing down
and credit cascading up.

New types:
- Delegation: Links parent and child work units with delegated_by/to
- DelegationTerms: Optional terms including portion, deadline, credit_share

New functions:
- AddDelegation: Create a delegation relationship
- RemoveDelegation: Remove a delegation relationship
- GetDelegation: Retrieve delegation info for a child work unit
- ListDelegationsFrom: List all delegations from a parent

🤖 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-30 16:25:15 -08:00
parent 020784a70d
commit 1812ffa430
2 changed files with 252 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
package beads
import (
"encoding/json"
"os"
"path/filepath"
"strings"
@@ -1377,3 +1378,102 @@ func TestRoleBeadID(t *testing.T) {
})
}
}
// TestDelegationStruct tests the Delegation struct serialization.
func TestDelegationStruct(t *testing.T) {
tests := []struct {
name string
delegation Delegation
wantJSON string
}{
{
name: "full delegation",
delegation: Delegation{
Parent: "hop://accenture.com/eng/proj-123/task-a",
Child: "hop://alice@example.com/main-town/gastown/gt-xyz",
DelegatedBy: "hop://accenture.com",
DelegatedTo: "hop://alice@example.com",
Terms: &DelegationTerms{
Portion: "backend-api",
Deadline: "2025-06-01",
CreditShare: 80,
},
CreatedAt: "2025-01-15T10:00:00Z",
},
wantJSON: `{"parent":"hop://accenture.com/eng/proj-123/task-a","child":"hop://alice@example.com/main-town/gastown/gt-xyz","delegated_by":"hop://accenture.com","delegated_to":"hop://alice@example.com","terms":{"portion":"backend-api","deadline":"2025-06-01","credit_share":80},"created_at":"2025-01-15T10:00:00Z"}`,
},
{
name: "minimal delegation",
delegation: Delegation{
Parent: "gt-abc",
Child: "gt-xyz",
DelegatedBy: "steve",
DelegatedTo: "alice",
},
wantJSON: `{"parent":"gt-abc","child":"gt-xyz","delegated_by":"steve","delegated_to":"alice"}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := json.Marshal(tt.delegation)
if err != nil {
t.Fatalf("json.Marshal failed: %v", err)
}
if string(got) != tt.wantJSON {
t.Errorf("json.Marshal = %s, want %s", string(got), tt.wantJSON)
}
// Test round-trip
var parsed Delegation
if err := json.Unmarshal(got, &parsed); err != nil {
t.Fatalf("json.Unmarshal failed: %v", err)
}
if parsed.Parent != tt.delegation.Parent {
t.Errorf("parsed.Parent = %s, want %s", parsed.Parent, tt.delegation.Parent)
}
if parsed.Child != tt.delegation.Child {
t.Errorf("parsed.Child = %s, want %s", parsed.Child, tt.delegation.Child)
}
if parsed.DelegatedBy != tt.delegation.DelegatedBy {
t.Errorf("parsed.DelegatedBy = %s, want %s", parsed.DelegatedBy, tt.delegation.DelegatedBy)
}
if parsed.DelegatedTo != tt.delegation.DelegatedTo {
t.Errorf("parsed.DelegatedTo = %s, want %s", parsed.DelegatedTo, tt.delegation.DelegatedTo)
}
})
}
}
// TestDelegationTerms tests the DelegationTerms struct.
func TestDelegationTerms(t *testing.T) {
terms := &DelegationTerms{
Portion: "frontend",
Deadline: "2025-03-15",
AcceptanceCriteria: "All tests passing, code reviewed",
CreditShare: 70,
}
got, err := json.Marshal(terms)
if err != nil {
t.Fatalf("json.Marshal failed: %v", err)
}
var parsed DelegationTerms
if err := json.Unmarshal(got, &parsed); err != nil {
t.Fatalf("json.Unmarshal failed: %v", err)
}
if parsed.Portion != terms.Portion {
t.Errorf("parsed.Portion = %s, want %s", parsed.Portion, terms.Portion)
}
if parsed.Deadline != terms.Deadline {
t.Errorf("parsed.Deadline = %s, want %s", parsed.Deadline, terms.Deadline)
}
if parsed.AcceptanceCriteria != terms.AcceptanceCriteria {
t.Errorf("parsed.AcceptanceCriteria = %s, want %s", parsed.AcceptanceCriteria, terms.AcceptanceCriteria)
}
if parsed.CreditShare != terms.CreditShare {
t.Errorf("parsed.CreditShare = %d, want %d", parsed.CreditShare, terms.CreditShare)
}
}