fix: stop writing Gas Town context to polecat CLAUDE.md

Same fix as crew: polecat manager was overwriting project CLAUDE.md with
Gas Town polecat context. Gas Town context is now injected ephemerally via
SessionStart hook (gt prime) only.

Removed installCLAUDETemplate function and associated tests.
This commit is contained in:
Steve Yegge
2025-12-30 12:01:27 -08:00
parent 6c3e59d015
commit 9ebc733623
2 changed files with 10 additions and 215 deletions

View File

@@ -6,7 +6,6 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/steveyegge/gastown/internal/beads"
@@ -203,10 +202,10 @@ func (m *Manager) Add(name string) (*Polecat, error) {
return nil, fmt.Errorf("creating worktree: %w", err)
}
// Install polecat CLAUDE.md template (non-fatal if template missing)
if err := m.installCLAUDETemplate(polecatPath, name); err != nil {
fmt.Printf("Warning: could not install CLAUDE.md template: %v\n", err)
}
// NOTE: We intentionally do NOT write to CLAUDE.md here.
// Gas Town context is injected ephemerally via SessionStart hook (gt prime).
// Writing to CLAUDE.md would overwrite project instructions and could leak
// Gas Town internals into the project repo if merged.
// Set up shared beads: polecat uses rig's .beads via redirect file.
// This eliminates git sync overhead - all polecats share one database.
@@ -416,10 +415,8 @@ func (m *Manager) Recreate(name string, force bool) (*Polecat, error) {
return nil, fmt.Errorf("creating fresh worktree: %w", err)
}
// Install polecat CLAUDE.md template (non-fatal if template missing)
if err := m.installCLAUDETemplate(polecatPath, name); err != nil {
fmt.Printf("Warning: could not install CLAUDE.md template: %v\n", err)
}
// NOTE: We intentionally do NOT write to CLAUDE.md here.
// Gas Town context is injected ephemerally via SessionStart hook (gt prime).
// Set up shared beads
if err := m.setupSharedBeads(polecatPath); err != nil {
@@ -725,79 +722,6 @@ func (m *Manager) loadFromBeads(name string) (*Polecat, error) {
}, nil
}
// installCLAUDETemplate copies the polecat CLAUDE.md template into the worktree.
// Template variables {{rig}} and {{name}} are substituted with actual values.
// This provides polecats with context about their role and available commands.
func (m *Manager) installCLAUDETemplate(polecatPath, name string) error {
// Try multiple template locations in order of precedence:
// 1. Rig-specific template: <rig>/mayor/rig/templates/polecat-CLAUDE.md
// 2. Gastown (canonical) template: <town>/gastown/mayor/rig/templates/polecat-CLAUDE.md
templatePaths := []string{
filepath.Join(m.rig.Path, "mayor", "rig", "templates", "polecat-CLAUDE.md"),
}
// Add gastown fallback if we can find the town root
if townRoot, err := findTownRoot(m.rig.Path); err == nil && townRoot != "" {
gasTownTemplate := filepath.Join(townRoot, "gastown", "mayor", "rig", "templates", "polecat-CLAUDE.md")
// Only add fallback if different from rig-specific path
if gasTownTemplate != templatePaths[0] {
templatePaths = append(templatePaths, gasTownTemplate)
}
}
var content []byte
for _, templatePath := range templatePaths {
var err error
content, err = os.ReadFile(templatePath)
if err == nil {
break // Found a template
}
if !os.IsNotExist(err) {
return fmt.Errorf("reading template %s: %w", templatePath, err)
}
}
if content == nil {
// No template found in any location - warn and skip
fmt.Printf("Warning: polecat template not found (checked %d locations)\n", len(templatePaths))
return nil
}
// Substitute template variables
output := string(content)
output = strings.ReplaceAll(output, "{{rig}}", m.rig.Name)
output = strings.ReplaceAll(output, "{{name}}", name)
// Write to polecat's CLAUDE.md
claudePath := filepath.Join(polecatPath, "CLAUDE.md")
if err := os.WriteFile(claudePath, []byte(output), 0644); err != nil {
return fmt.Errorf("writing CLAUDE.md: %w", err)
}
return nil
}
// findTownRoot locates the Gas Town root directory by walking up from startDir.
func findTownRoot(startDir string) (string, error) {
absDir, err := filepath.Abs(startDir)
if err != nil {
return "", err
}
current := absDir
for {
// Check for primary marker (mayor/town.json)
if _, err := os.Stat(filepath.Join(current, "mayor", "town.json")); err == nil {
return current, nil
}
parent := filepath.Dir(current)
if parent == current {
return "", fmt.Errorf("town root not found")
}
current = parent
}
}
// setupSharedBeads creates a redirect file so the polecat uses the rig's shared .beads database.
// This eliminates the need for git sync between polecat clones - all polecats share one database.
//

View File

@@ -3,7 +3,6 @@ package polecat
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/steveyegge/gastown/internal/git"
@@ -266,135 +265,7 @@ func TestClearIssueWithoutAssignment(t *testing.T) {
}
}
// TestInstallCLAUDETemplate verifies the polecat CLAUDE.md template is installed
// from mayor/rig/templates/ (not rig root) with correct variable substitution.
// This is a regression test for gt-si6am.
func TestInstallCLAUDETemplate(t *testing.T) {
root := t.TempDir()
// Create polecat directory
polecatDir := filepath.Join(root, "polecats", "testcat")
if err := os.MkdirAll(polecatDir, 0755); err != nil {
t.Fatalf("mkdir polecat: %v", err)
}
// Create template at mayor/rig/templates/ (the correct location)
templateDir := filepath.Join(root, "mayor", "rig", "templates")
if err := os.MkdirAll(templateDir, 0755); err != nil {
t.Fatalf("mkdir templates: %v", err)
}
// Write a template with variables
templateContent := `# Polecat Context
**YOU ARE IN: {{rig}}/polecats/{{name}}/** - This is YOUR worktree.
Your Role: POLECAT
Your rig: {{rig}}
Your name: {{name}}
`
templatePath := filepath.Join(templateDir, "polecat-CLAUDE.md")
if err := os.WriteFile(templatePath, []byte(templateContent), 0644); err != nil {
t.Fatalf("write template: %v", err)
}
// Also create a WRONG template at rig root (the old buggy location)
// This should NOT be used
wrongTemplateDir := filepath.Join(root, "templates")
if err := os.MkdirAll(wrongTemplateDir, 0755); err != nil {
t.Fatalf("mkdir wrong templates: %v", err)
}
wrongContent := `# Mayor Context - THIS IS WRONG`
if err := os.WriteFile(filepath.Join(wrongTemplateDir, "polecat-CLAUDE.md"), []byte(wrongContent), 0644); err != nil {
t.Fatalf("write wrong template: %v", err)
}
// Create manager and install template
r := &rig.Rig{
Name: "gastown",
Path: root,
}
m := NewManager(r, git.NewGit(root))
err := m.installCLAUDETemplate(polecatDir, "testcat")
if err != nil {
t.Fatalf("installCLAUDETemplate: %v", err)
}
// Read the installed CLAUDE.md
installedPath := filepath.Join(polecatDir, "CLAUDE.md")
content, err := os.ReadFile(installedPath)
if err != nil {
t.Fatalf("read installed CLAUDE.md: %v", err)
}
// Verify it's the polecat template (not mayor)
if !strings.Contains(string(content), "Polecat Context") {
t.Error("CLAUDE.md should contain 'Polecat Context'")
}
if strings.Contains(string(content), "Mayor Context") {
t.Error("CLAUDE.md should NOT contain 'Mayor Context' (wrong template used)")
}
// Verify variables were substituted
if strings.Contains(string(content), "{{rig}}") {
t.Error("{{rig}} should be substituted")
}
if strings.Contains(string(content), "{{name}}") {
t.Error("{{name}} should be substituted")
}
if !strings.Contains(string(content), "gastown/polecats/testcat/") {
t.Error("CLAUDE.md should contain substituted path 'gastown/polecats/testcat/'")
}
if !strings.Contains(string(content), "Your rig: gastown") {
t.Error("CLAUDE.md should contain 'Your rig: gastown'")
}
if !strings.Contains(string(content), "Your name: testcat") {
t.Error("CLAUDE.md should contain 'Your name: testcat'")
}
}
// TestInstallCLAUDETemplateNotAtRigRoot verifies that templates at rig root
// (the old buggy location) are NOT used.
func TestInstallCLAUDETemplateNotAtRigRoot(t *testing.T) {
root := t.TempDir()
// Create polecat directory
polecatDir := filepath.Join(root, "polecats", "testcat")
if err := os.MkdirAll(polecatDir, 0755); err != nil {
t.Fatalf("mkdir polecat: %v", err)
}
// Only create template at rig root (wrong location)
// Do NOT create at mayor/rig/templates/
wrongTemplateDir := filepath.Join(root, "templates")
if err := os.MkdirAll(wrongTemplateDir, 0755); err != nil {
t.Fatalf("mkdir wrong templates: %v", err)
}
wrongContent := `# Mayor Context - THIS IS WRONG`
if err := os.WriteFile(filepath.Join(wrongTemplateDir, "polecat-CLAUDE.md"), []byte(wrongContent), 0644); err != nil {
t.Fatalf("write wrong template: %v", err)
}
// Create manager and try to install template
r := &rig.Rig{
Name: "gastown",
Path: root,
}
m := NewManager(r, git.NewGit(root))
// Should not error (missing template is OK) but should NOT install wrong one
err := m.installCLAUDETemplate(polecatDir, "testcat")
if err != nil {
t.Fatalf("installCLAUDETemplate: %v", err)
}
// CLAUDE.md should NOT exist (template not found at correct location)
installedPath := filepath.Join(polecatDir, "CLAUDE.md")
if _, err := os.Stat(installedPath); err == nil {
content, _ := os.ReadFile(installedPath)
if strings.Contains(string(content), "Mayor Context") {
t.Error("Template from rig root was incorrectly used - should use mayor/rig/templates/")
}
}
}
// NOTE: TestInstallCLAUDETemplate tests were removed.
// We no longer write CLAUDE.md to worktrees - Gas Town context is injected
// ephemerally via SessionStart hook (gt prime) to prevent leaking internal
// architecture into project repos.