From a3da3302a30eb97744855536089685fe754f0836 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sun, 21 Dec 2025 14:27:58 -0800 Subject: [PATCH] fix(mail): use slash-based identity format for addresses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes mail identity format from hyphenated (gastown-Toast) to slash-based (gastown/polecats/Toast) to: - Match directory structure (REST-like) - Distinguish from hyphenated bead IDs (gt-xyz) and molecule names Updated: - addressToIdentity(): preserve slashes instead of replacing with dashes - identityToAddress(): simplified, identity == address now - detectSender(): include /polecats/ in polecat addresses - Tests updated for new format Closes gt-cxtu: shared beads architecture verified working. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/cmd/mail.go | 4 +-- internal/mail/mailbox.go | 2 +- internal/mail/types.go | 51 ++++++++++--------------------------- internal/mail/types_test.go | 22 ++++++++-------- 4 files changed, 28 insertions(+), 51 deletions(-) diff --git a/internal/cmd/mail.go b/internal/cmd/mail.go index 33e561fa..3031a47e 100644 --- a/internal/cmd/mail.go +++ b/internal/cmd/mail.go @@ -554,14 +554,14 @@ func detectSender() string { return "mayor/" } - // If in a rig's polecats directory, extract address + // If in a rig's polecats directory, extract address (format: rig/polecats/name) if strings.Contains(cwd, "/polecats/") { parts := strings.Split(cwd, "/polecats/") if len(parts) >= 2 { rigPath := parts[0] polecatPath := strings.Split(parts[1], "/")[0] rigName := filepath.Base(rigPath) - return fmt.Sprintf("%s/%s", rigName, polecatPath) + return fmt.Sprintf("%s/polecats/%s", rigName, polecatPath) } } diff --git a/internal/mail/mailbox.go b/internal/mail/mailbox.go index 2e047aa5..58a36b77 100644 --- a/internal/mail/mailbox.go +++ b/internal/mail/mailbox.go @@ -20,7 +20,7 @@ var ( // Mailbox manages messages for an identity via beads. type Mailbox struct { - identity string // beads identity (e.g., "gastown-Toast") + identity string // beads identity (e.g., "gastown/polecats/Toast") workDir string // directory to run bd commands in beadsDir string // explicit .beads directory path (set via BEADS_DIR) path string // for legacy JSONL mode (crew workers) diff --git a/internal/mail/types.go b/internal/mail/types.go index 32136afc..a6d51004 100644 --- a/internal/mail/types.go +++ b/internal/mail/types.go @@ -276,16 +276,15 @@ func ParseMessageType(s string) MessageType { // addressToIdentity converts a GGT address to a beads identity. // -// Examples: +// Addresses use slash format matching directory structure: // - "mayor/" → "mayor/" // - "mayor" → "mayor/" // - "deacon/" → "deacon/" // - "deacon" → "deacon/" -// - "gastown/Toast" → "gastown-Toast" -// - "gastown/refinery" → "gastown-refinery" +// - "gastown/polecats/Toast" → "gastown/polecats/Toast" +// - "gastown/crew/max" → "gastown/crew/max" +// - "gastown/refinery" → "gastown/refinery" // - "gastown/" → "gastown" (rig broadcast) -// - "beads/crew/dave" → "beads-dave" (crew/ stripped) -// - "beads/polecats/nux" → "beads-nux" (polecats/ stripped) func addressToIdentity(address string) string { // Town-level agents: mayor and deacon keep trailing slash if address == "mayor" || address == "mayor/" { @@ -300,36 +299,20 @@ func addressToIdentity(address string) string { address = address[:len(address)-1] } - // Normalize worker paths: strip crew/ and polecats/ from path - // beads/crew/dave → beads/dave (both resolve to beads-dave) - // beads/polecats/nux → beads/nux (both resolve to beads-nux) - address = strings.Replace(address, "/crew/", "/", 1) - address = strings.Replace(address, "/polecats/", "/", 1) - - // Replace / with - for beads identity - // gastown/Toast → gastown-Toast - result := "" - for _, c := range address { - if c == '/' { - result += "-" - } else { - result = result + string(c) - } - } - return result + // Keep slashes - addresses map to directory structure + return address } // identityToAddress converts a beads identity back to a GGT address. // -// Examples: -// - "mayor" → "mayor/" +// Identity format matches address format (slash-based): // - "mayor/" → "mayor/" -// - "deacon" → "deacon/" // - "deacon/" → "deacon/" -// - "gastown-Toast" → "gastown/Toast" -// - "gastown-refinery" → "gastown/refinery" +// - "gastown/polecats/Toast" → "gastown/polecats/Toast" +// - "gastown/crew/max" → "gastown/crew/max" +// - "gastown/refinery" → "gastown/refinery" func identityToAddress(identity string) string { - // Town-level agents + // Town-level agents ensure trailing slash if identity == "mayor" || identity == "mayor/" { return "mayor/" } @@ -337,14 +320,6 @@ func identityToAddress(identity string) string { return "deacon/" } - // Find first dash and replace with / - // gastown-Toast → gastown/Toast - for i, c := range identity { - if c == '-' { - return identity[:i] + "/" + identity[i+1:] - } - } - - // No dash found, return as-is with trailing slash - return identity + "/" + // Identity already in slash format, return as-is + return identity } diff --git a/internal/mail/types_test.go b/internal/mail/types_test.go index f49d40cc..ff61e8ac 100644 --- a/internal/mail/types_test.go +++ b/internal/mail/types_test.go @@ -13,10 +13,11 @@ func TestAddressToIdentity(t *testing.T) { {"deacon", "deacon/"}, {"deacon/", "deacon/"}, - // Rig-level agents use dash separator - {"gastown/Toast", "gastown-Toast"}, - {"gastown/refinery", "gastown-refinery"}, - {"gastown/witness", "gastown-witness"}, + // Rig-level agents: slash format matches directory structure + {"gastown/polecats/Toast", "gastown/polecats/Toast"}, + {"gastown/crew/max", "gastown/crew/max"}, + {"gastown/refinery", "gastown/refinery"}, + {"gastown/witness", "gastown/witness"}, // Rig broadcast (trailing slash removed) {"gastown/", "gastown"}, @@ -43,13 +44,14 @@ func TestIdentityToAddress(t *testing.T) { {"deacon", "deacon/"}, {"deacon/", "deacon/"}, - // Rig-level agents - {"gastown-Toast", "gastown/Toast"}, - {"gastown-refinery", "gastown/refinery"}, - {"gastown-witness", "gastown/witness"}, + // Rig-level agents: identity == address (slash format) + {"gastown/polecats/Toast", "gastown/polecats/Toast"}, + {"gastown/crew/max", "gastown/crew/max"}, + {"gastown/refinery", "gastown/refinery"}, + {"gastown/witness", "gastown/witness"}, - // Rig name only (adds trailing slash) - {"gastown", "gastown/"}, + // Rig name only (no transformation) + {"gastown", "gastown"}, } for _, tt := range tests {