refactor(development): move gastown patches to separate files
All checks were successful
CI / check (push) Successful in 3m37s

Replace inline postPatch substituteInPlace calls with proper unified
diff patch files, following the pattern established by beads.

This improves maintainability:
- Each patch is in its own file with clear naming
- Patches use proper unified diff format
- Easier to review, update, and track individual fixes
- Default.nix is cleaner (237 lines of substituteInPlace -> 15 lines)

Patches included:
- gastown-fix-validate-recipient.patch
- gastown-fix-agent-bead-address-title.patch
- gastown-fix-agent-bead-rig-prefix.patch
- gastown-fix-role-home-paths.patch
- gastown-fix-town-root-detection.patch
- gastown-fix-copydir-symlinks.patch
- gastown-statusline-optimization.patch

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nixos_configs/crew/harry
2026-01-31 09:06:05 -08:00
committed by John Ogle
parent 21a8b5c5d9
commit 56097aefa4
8 changed files with 274 additions and 237 deletions

View File

@@ -41,251 +41,24 @@ let
];
# Bug fixes not yet merged upstream
postPatch = ''
# Each patch is stored in a separate file for clarity and maintainability
patches = [
# Fix validateRecipient bug: normalize addresses before comparison
# See: https://github.com/steveyegge/gastown/issues/TBD
substituteInPlace internal/mail/router.go \
--replace-fail \
'if agentBeadToAddress(agent) == identity {' \
'if AddressToIdentity(agentBeadToAddress(agent)) == AddressToIdentity(identity) {'
./gastown-fix-validate-recipient.patch
# Fix agentBeadToAddress to use title field for hq- prefixed beads
# Title should contain the address (e.g., "java/crew/americano")
substituteInPlace internal/mail/router.go \
--replace-fail \
'return parseAgentAddressFromDescription(bead.Description)' \
'if bead.Title != "" && strings.Contains(bead.Title, "/") { return bead.Title }; return parseAgentAddressFromDescription(bead.Description)'
./gastown-fix-agent-bead-address-title.patch
# Fix agentBeadToAddress to handle rig-specific prefixes (j-, sc-, etc.)
# Bead IDs like j-java-crew-americano should map to java/crew/americano
substituteInPlace internal/mail/router.go \
--replace-fail \
'// Handle gt- prefixed IDs (legacy format)
if !strings.HasPrefix(id, "gt-") {
return "" // Not a valid agent bead ID
}' \
'// Handle rig-specific prefixes: <prefix>-<rig>-<role>-<name>
// Examples: j-java-crew-americano -> java/crew/americano
idParts := strings.Split(id, "-")
if len(idParts) >= 3 {
for i, part := range idParts {
if part == "crew" || part == "polecat" || part == "polecats" {
if i >= 1 && i < len(idParts)-1 {
rig := idParts[i-1]
name := strings.Join(idParts[i+1:], "-")
return rig + "/" + part + "/" + name
}
}
if part == "witness" || part == "refinery" {
if i >= 1 {
return idParts[i-1] + "/" + part
}
}
}
}
// Handle gt- prefixed IDs (legacy format)
if !strings.HasPrefix(id, "gt-") {
return "" // Not a valid agent bead ID
}'
./gastown-fix-agent-bead-rig-prefix.patch
# Fix crew/polecat home paths: remove incorrect /rig suffix
substituteInPlace internal/cmd/role.go \
--replace-fail \
'return filepath.Join(townRoot, rig, "polecats", polecat, "rig")' \
'return filepath.Join(townRoot, rig, "polecats", polecat)' \
--replace-fail \
'return filepath.Join(townRoot, rig, "crew", polecat, "rig")' \
'return filepath.Join(townRoot, rig, "crew", polecat)'
./gastown-fix-role-home-paths.patch
# Fix town root detection: don't map to Mayor (causes spurious mismatch warnings)
substituteInPlace internal/cmd/prime.go \
--replace-fail \
'if relPath == "." || relPath == "" {
ctx.Role = RoleMayor
return ctx
}
if len(parts) >= 1 && parts[0] == "mayor" {' \
'if relPath == "." || relPath == "" {
return ctx // RoleUnknown - town root is shared space
}
// Check for mayor role: mayor/ or mayor/rig/
if len(parts) >= 1 && parts[0] == "mayor" {'
./gastown-fix-town-root-detection.patch
# Fix copyDir to handle symlinks (broken symlinks cause "no such file" errors)
# See: https://github.com/steveyegge/gastown/issues/TBD
substituteInPlace internal/git/git.go \
--replace-fail \
'if entry.IsDir() {' \
'// Handle symlinks (recreate them, do not follow)
if entry.Type()&os.ModeSymlink != 0 {
linkTarget, err := os.Readlink(srcPath)
if err != nil {
return err
}
if err := os.Symlink(linkTarget, destPath); err != nil {
return err
}
continue
}
if entry.IsDir() {'
./gastown-fix-copydir-symlinks.patch
# Statusline optimization: skip detached sessions and cache results
# Reduces Dolt CPU from ~70% to ~20% by avoiding beads queries for sessions nobody is watching
# See: https://github.com/steveyegge/gastown/issues/TBD
substituteInPlace internal/cmd/statusline.go \
--replace-fail \
'"strings"' \
'"strings"
"time"' \
--replace-fail \
'var (
statusLineSession string
)' \
'// statusLineCacheTTL is how long cached status output remains valid.
const statusLineCacheTTL = 10 * time.Second
// statusLineCachePath returns the cache file path for a session.
func statusLineCachePath(session string) string {
return filepath.Join(os.TempDir(), fmt.Sprintf("gt-status-%s", session))
}
// getStatusLineCache returns cached status if fresh, empty string otherwise.
func getStatusLineCache(session string) string {
path := statusLineCachePath(session)
info, err := os.Stat(path)
if err != nil {
return ""
}
if time.Since(info.ModTime()) > statusLineCacheTTL {
return ""
}
data, err := os.ReadFile(path)
if err != nil {
return ""
}
return string(data)
}
// setStatusLineCache writes status to cache file.
func setStatusLineCache(session, status string) {
path := statusLineCachePath(session)
_ = os.WriteFile(path, []byte(status), 0644)
}
var (
statusLineSession string
)' \
--replace-fail \
'func runStatusLine(cmd *cobra.Command, args []string) error {
t := tmux.NewTmux()
// Get session environment' \
'func runStatusLine(cmd *cobra.Command, args []string) error {
t := tmux.NewTmux()
// Optimization: skip expensive beads queries for detached sessions
if statusLineSession != "" {
if !t.IsSessionAttached(statusLineSession) {
fmt.Print(" |")
return nil
}
// Check cache for attached sessions too
if cached := getStatusLineCache(statusLineSession); cached != "" {
fmt.Print(cached)
return nil
}
}
// Get session environment' \
--replace-fail \
' // Output
if len(parts) > 0 {
fmt.Print(strings.Join(parts, " | ") + " |")
}
return nil
}
func runMayorStatusLine(t *tmux.Tmux) error {' \
' // Output
if len(parts) > 0 {
output := strings.Join(parts, " | ") + " |"
if statusLineSession != "" {
setStatusLineCache(statusLineSession, output)
}
fmt.Print(output)
}
return nil
}
func runMayorStatusLine(t *tmux.Tmux) error {' \
--replace-fail \
'fmt.Print(strings.Join(parts, " | ") + " |")
return nil
}
// runDeaconStatusLine outputs status for the deacon session.' \
'output := strings.Join(parts, " | ") + " |"
if statusLineSession != "" {
setStatusLineCache(statusLineSession, output)
}
fmt.Print(output)
return nil
}
// runDeaconStatusLine outputs status for the deacon session.' \
--replace-fail \
'fmt.Print(strings.Join(parts, " | ") + " |")
return nil
}
// runWitnessStatusLine outputs status for a witness session.
// Shows: crew count, hook or mail preview' \
'output := strings.Join(parts, " | ") + " |"
if statusLineSession != "" {
setStatusLineCache(statusLineSession, output)
}
fmt.Print(output)
return nil
}
// runWitnessStatusLine outputs status for a witness session.
// Shows: crew count, hook or mail preview' \
--replace-fail \
'fmt.Print(strings.Join(parts, " | ") + " |")
return nil
}
// runRefineryStatusLine outputs status for a refinery session.' \
'output := strings.Join(parts, " | ") + " |"
if statusLineSession != "" {
setStatusLineCache(statusLineSession, output)
}
fmt.Print(output)
return nil
}
// runRefineryStatusLine outputs status for a refinery session.' \
--replace-fail \
'fmt.Print(strings.Join(parts, " | ") + " |")
return nil
}
// isSessionWorking detects' \
'output := strings.Join(parts, " | ") + " |"
if statusLineSession != "" {
setStatusLineCache(statusLineSession, output)
}
fmt.Print(output)
return nil
}
// isSessionWorking detects'
'';
./gastown-statusline-optimization.patch
];
meta = with lib; {
description = "Gas Town - multi-agent workspace manager by Steve Yegge";