feat: add .runtime/overlay/ support for polecat/crew worktrees

Add overlay directory support to automatically copy gitignored files
(like .env, config files) from <rig>/.runtime/overlay/ to polecat
and crew worktree roots when they are spawned.

This allows services started by polecats/crew to have their required
configuration files at the root without committing them to git.

Changes:
- Add copyOverlay() function to polecat and crew managers
- Call copyOverlay() after setupSharedBeads() in AddWithOptions/RepairWorktreeWithOptions
- Non-fatal: overlay copy failures only log warnings, don't block spawn

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cstar
2026-01-08 14:21:05 +01:00
committed by Eric Cestari
parent f4cbcb4ce9
commit 03b0f7ff52
2 changed files with 131 additions and 0 deletions

View File

@@ -174,6 +174,13 @@ func (m *Manager) Add(name string, createBranch bool) (*CrewWorker, error) {
fmt.Printf("Warning: could not set up shared beads: %v\n", err)
}
// Copy overlay files from .runtime/overlay/ to crew root.
// This allows services to have .env and other config files at their root.
if err := m.copyOverlay(crewPath); err != nil {
// Non-fatal - log warning but continue
fmt.Printf("Warning: could not copy overlay files: %v\n", err)
}
// NOTE: Slash commands (.claude/commands/) are provisioned at town level by gt install.
// All agents inherit them via Claude's directory traversal - no per-workspace copies needed.
@@ -570,3 +577,59 @@ func (m *Manager) IsRunning(name string) (bool, error) {
sessionID := m.SessionName(name)
return t.HasSession(sessionID)
}
// copyOverlay copies files from <rig>/.runtime/overlay/ to the crew worker root.
// This allows storing gitignored files (like .env) that services need at their root.
// The overlay is copied non-recursively - only files, not subdirectories.
//
// Structure:
//
// rig/
// .runtime/
// overlay/
// .env <- Copied to crew root
// config.json <- Copied to crew root
// crew/
// <name>/
// .env <- Copied from overlay
// config.json <- Copied from overlay
func (m *Manager) copyOverlay(crewPath string) error {
overlayDir := filepath.Join(m.rig.Path, ".runtime", "overlay")
// Check if overlay directory exists
entries, err := os.ReadDir(overlayDir)
if err != nil {
if os.IsNotExist(err) {
// No overlay directory - not an error, just nothing to copy
return nil
}
return fmt.Errorf("reading overlay dir: %w", err)
}
// Copy each file (not directories) from overlay to crew root
for _, entry := range entries {
if entry.IsDir() {
// Skip subdirectories - only copy files at overlay root
continue
}
srcPath := filepath.Join(overlayDir, entry.Name())
dstPath := filepath.Join(crewPath, entry.Name())
// Read source file
data, err := os.ReadFile(srcPath)
if err != nil {
// Log warning but continue - don't fail spawn for overlay issues
fmt.Printf("Warning: could not read overlay file %s: %v\n", entry.Name(), err)
continue
}
// Write to destination
if err := os.WriteFile(dstPath, data, 0644); err != nil {
fmt.Printf("Warning: could not write overlay file %s: %v\n", entry.Name(), err)
continue
}
}
return nil
}