diff --git a/internal/beads/beads.go b/internal/beads/beads.go index 736b9c08..7cee91bc 100644 --- a/internal/beads/beads.go +++ b/internal/beads/beads.go @@ -30,6 +30,7 @@ type Issue struct { Priority int `json:"priority"` Type string `json:"issue_type"` CreatedAt string `json:"created_at"` + CreatedBy string `json:"created_by,omitempty"` UpdatedAt string `json:"updated_at"` ClosedAt string `json:"closed_at,omitempty"` Parent string `json:"parent,omitempty"` @@ -76,6 +77,7 @@ type CreateOptions struct { Priority int // 0-4 Description string Parent string + Actor string // Who is creating this issue (populates created_by) } // UpdateOptions specifies options for updating an issue. @@ -315,6 +317,9 @@ func (b *Beads) Create(opts CreateOptions) (*Issue, error) { if opts.Parent != "" { args = append(args, "--parent="+opts.Parent) } + if opts.Actor != "" { + args = append(args, "--actor="+opts.Actor) + } out, err := b.run(args...) if err != nil { @@ -534,6 +539,7 @@ func (b *Beads) GetOrCreateHandoffBead(role string) (*Issue, error) { Type: "task", Priority: 2, Description: "", // Empty until first handoff + Actor: role, }) if err != nil { return nil, fmt.Errorf("creating handoff bead: %w", err) diff --git a/internal/cmd/molecule_lifecycle.go b/internal/cmd/molecule_lifecycle.go index c948516a..3cbf284c 100644 --- a/internal/cmd/molecule_lifecycle.go +++ b/internal/cmd/molecule_lifecycle.go @@ -476,6 +476,7 @@ squashed_at: %s Description: digestDesc, Type: "task", Priority: 4, // P4 - backlog priority for digests + Actor: target, }) if err != nil { return fmt.Errorf("creating digest: %w", err) diff --git a/internal/cmd/patrol_helpers.go b/internal/cmd/patrol_helpers.go index 8e6ac8d3..bf8c5f8e 100644 --- a/internal/cmd/patrol_helpers.go +++ b/internal/cmd/patrol_helpers.go @@ -117,7 +117,7 @@ func autoSpawnPatrol(cfg PatrolConfig) (string, error) { } // Create the patrol wisp - cmdSpawn := exec.Command("bd", "--no-daemon", "wisp", "create", protoID) + cmdSpawn := exec.Command("bd", "--no-daemon", "wisp", "create", protoID, "--actor", cfg.RoleName) cmdSpawn.Dir = cfg.BeadsDir var stdoutSpawn, stderrSpawn bytes.Buffer cmdSpawn.Stdout = &stdoutSpawn diff --git a/internal/cmd/role.go b/internal/cmd/role.go index e25bdab1..0a7a1d71 100644 --- a/internal/cmd/role.go +++ b/internal/cmd/role.go @@ -230,6 +230,42 @@ func parseRoleString(s string) (Role, string, string) { } } +// ActorString returns the actor identity string for beads attribution. +// Format matches beads created_by convention: +// - Simple roles: "mayor", "deacon" +// - Rig-specific: "gastown/witness", "gastown/refinery" +// - Workers: "gastown/crew/max", "gastown/polecats/Toast" +func (info RoleInfo) ActorString() string { + switch info.Role { + case RoleMayor: + return "mayor" + case RoleDeacon: + return "deacon" + case RoleWitness: + if info.Rig != "" { + return fmt.Sprintf("%s/witness", info.Rig) + } + return "witness" + case RoleRefinery: + if info.Rig != "" { + return fmt.Sprintf("%s/refinery", info.Rig) + } + return "refinery" + case RolePolecat: + if info.Rig != "" && info.Polecat != "" { + return fmt.Sprintf("%s/polecats/%s", info.Rig, info.Polecat) + } + return "polecat" + case RoleCrew: + if info.Rig != "" && info.Polecat != "" { + return fmt.Sprintf("%s/crew/%s", info.Rig, info.Polecat) + } + return "crew" + default: + return string(info.Role) + } +} + // getRoleHome returns the canonical home directory for a role. func getRoleHome(role Role, rig, polecat, townRoot string) string { switch role { diff --git a/internal/doctor/patrol_check.go b/internal/doctor/patrol_check.go index d3191b75..4050ca0e 100644 --- a/internal/doctor/patrol_check.go +++ b/internal/doctor/patrol_check.go @@ -118,6 +118,7 @@ func (c *PatrolMoleculesExistCheck) Fix(ctx *CheckContext) error { "--title="+mol, "--description="+desc, "--priority=2", + "--actor=gt-doctor", ) cmd.Dir = rigPath if err := cmd.Run(); err != nil { diff --git a/internal/mail/router.go b/internal/mail/router.go index fc5e5b83..497b06bf 100644 --- a/internal/mail/router.go +++ b/internal/mail/router.go @@ -144,6 +144,9 @@ func (r *Router) Send(msg *Message) error { args = append(args, "--labels", strings.Join(labels, ",")) } + // Add actor for attribution (sender identity) + args = append(args, "--actor", msg.From) + // Add --wisp flag for ephemeral messages (stored in single DB, filtered from JSONL export) if r.shouldBeWisp(msg) { args = append(args, "--wisp") diff --git a/internal/rig/manager.go b/internal/rig/manager.go index 19085797..100fab2b 100644 --- a/internal/rig/manager.go +++ b/internal/rig/manager.go @@ -534,6 +534,7 @@ func (m *Manager) seedPatrolMoleculesManually(rigPath string) error { "--title="+mol.title, "--description="+mol.desc, "--priority=2", + "--actor=gt-rig-init", ) cmd.Dir = rigPath if err := cmd.Run(); err != nil {