refactor: Mayor's per-rig clone is canonical for beads and worktrees
- Mayor has <rig>/mayor/rig/ clone (decentralized, discoverable) - Rig .beads/ symlinks to mayor/rig/.beads (Mayor is beads authority) - Polecats are worktrees from Mayor's clone (not Refinery) - Updated architecture.md with new structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -186,13 +186,17 @@ Created by `gt rig add <name> <git-url>`:
|
|||||||
```
|
```
|
||||||
gastown/ # Rig = container (NOT a git clone)
|
gastown/ # Rig = container (NOT a git clone)
|
||||||
├── config.json # Rig configuration (git_url, beads prefix)
|
├── config.json # Rig configuration (git_url, beads prefix)
|
||||||
├── .beads/ → refinery/rig/.beads # Symlink to canonical beads in refinery
|
├── .beads/ → mayor/rig/.beads # Symlink to canonical beads in Mayor
|
||||||
│
|
│
|
||||||
├── refinery/ # Refinery agent
|
├── mayor/ # Mayor's per-rig presence
|
||||||
│ ├── rig/ # Authoritative "main" clone
|
│ ├── rig/ # CANONICAL clone (beads authority)
|
||||||
│ │ └── .beads/ # Canonical rig beads (prefix: gt-, etc.)
|
│ │ └── .beads/ # Canonical rig beads (prefix: gt-, etc.)
|
||||||
│ └── state.json
|
│ └── state.json
|
||||||
│
|
│
|
||||||
|
├── refinery/ # Refinery agent (merge queue processor)
|
||||||
|
│ ├── rig/ # Refinery's clone (for merge operations)
|
||||||
|
│ └── state.json
|
||||||
|
│
|
||||||
├── witness/ # Witness agent (per-rig pit boss)
|
├── witness/ # Witness agent (per-rig pit boss)
|
||||||
│ └── state.json # No clone needed (monitors polecats)
|
│ └── state.json # No clone needed (monitors polecats)
|
||||||
│
|
│
|
||||||
@@ -200,15 +204,15 @@ gastown/ # Rig = container (NOT a git clone)
|
|||||||
│ └── <name>/ # Workspace (full git clone)
|
│ └── <name>/ # Workspace (full git clone)
|
||||||
│
|
│
|
||||||
└── polecats/ # Worker directories (git worktrees)
|
└── polecats/ # Worker directories (git worktrees)
|
||||||
├── Nux/ # Worktree from refinery (faster than clone)
|
├── Nux/ # Worktree from Mayor's clone
|
||||||
└── Toast/ # Worktree from refinery
|
└── Toast/ # Worktree from Mayor's clone
|
||||||
```
|
```
|
||||||
|
|
||||||
**Beads architecture:**
|
**Beads architecture:**
|
||||||
- Refinery's clone holds the canonical `.beads/` for the rig
|
- Mayor's clone holds the canonical `.beads/` for the rig
|
||||||
- Rig root symlinks `.beads/` → `refinery/rig/.beads`
|
- Rig root symlinks `.beads/` → `mayor/rig/.beads`
|
||||||
- All agents (crew, polecats) inherit beads via parent directory lookup
|
- All agents (crew, polecats, refinery) inherit beads via parent lookup
|
||||||
- Polecats are git worktrees, not full clones (much faster)
|
- Polecats are git worktrees from Mayor's clone (much faster than full clones)
|
||||||
|
|
||||||
**Key points:**
|
**Key points:**
|
||||||
- The rig root has no `.git/` - it's not a repository
|
- The rig root has no `.git/` - it's not a repository
|
||||||
@@ -271,15 +275,21 @@ For reference without mermaid rendering:
|
|||||||
│
|
│
|
||||||
├── gastown/ # RIG (container, NOT a git clone)
|
├── gastown/ # RIG (container, NOT a git clone)
|
||||||
│ ├── config.json # Rig configuration
|
│ ├── config.json # Rig configuration
|
||||||
│ ├── .beads/ → refinery/rig/.beads # Symlink to canonical beads
|
│ ├── .beads/ → mayor/rig/.beads # Symlink to Mayor's canonical beads
|
||||||
│ │
|
│ │
|
||||||
│ ├── refinery/ # Refinery agent
|
│ ├── mayor/ # Mayor's per-rig presence
|
||||||
│ │ ├── rig/ # Canonical "main" clone
|
│ │ ├── rig/ # CANONICAL clone (beads + worktree base)
|
||||||
│ │ │ ├── .git/
|
│ │ │ ├── .git/
|
||||||
│ │ │ ├── .beads/ # CANONICAL rig beads (gt-* prefix)
|
│ │ │ ├── .beads/ # CANONICAL rig beads (gt-* prefix)
|
||||||
│ │ │ └── <project files>
|
│ │ │ └── <project files>
|
||||||
│ │ └── state.json
|
│ │ └── state.json
|
||||||
│ │
|
│ │
|
||||||
|
│ ├── refinery/ # Refinery agent (merge queue)
|
||||||
|
│ │ ├── rig/ # Refinery's clone (for merges)
|
||||||
|
│ │ │ ├── .git/
|
||||||
|
│ │ │ └── <project files>
|
||||||
|
│ │ └── state.json
|
||||||
|
│ │
|
||||||
│ ├── witness/ # Witness agent (pit boss)
|
│ ├── witness/ # Witness agent (pit boss)
|
||||||
│ │ └── state.json # No clone needed
|
│ │ └── state.json # No clone needed
|
||||||
│ │
|
│ │
|
||||||
@@ -289,9 +299,9 @@ For reference without mermaid rendering:
|
|||||||
│ │ └── <project files>
|
│ │ └── <project files>
|
||||||
│ │
|
│ │
|
||||||
│ ├── polecats/ # Worker directories (worktrees)
|
│ ├── polecats/ # Worker directories (worktrees)
|
||||||
│ │ ├── Nux/ # Git worktree from refinery
|
│ │ ├── Nux/ # Git worktree from Mayor's clone
|
||||||
│ │ │ └── <project files> # (inherits beads from rig)
|
│ │ │ └── <project files> # (inherits beads from rig)
|
||||||
│ │ └── Toast/ # Git worktree from refinery
|
│ │ └── Toast/ # Git worktree from Mayor's clone
|
||||||
│ │
|
│ │
|
||||||
│ └── plugins/ # Optional plugins
|
│ └── plugins/ # Optional plugins
|
||||||
│ └── merge-oracle/
|
│ └── merge-oracle/
|
||||||
@@ -300,18 +310,19 @@ For reference without mermaid rendering:
|
|||||||
│
|
│
|
||||||
└── wyvern/ # Another rig (same structure)
|
└── wyvern/ # Another rig (same structure)
|
||||||
├── config.json
|
├── config.json
|
||||||
├── .beads/ → refinery/rig/.beads
|
├── .beads/ → mayor/rig/.beads
|
||||||
├── polecats/
|
├── mayor/
|
||||||
├── refinery/
|
├── refinery/
|
||||||
├── witness/
|
├── witness/
|
||||||
└── crew/
|
├── crew/
|
||||||
|
└── polecats/
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key changes from earlier design:**
|
**Key changes from earlier design:**
|
||||||
- Town beads (`gm-*`) hold Mayor mail instead of JSONL files
|
- Town beads (`gm-*`) hold Mayor mail instead of JSONL files
|
||||||
- Rig `.beads/` symlinks to refinery's canonical beads
|
- Mayor has per-rig clone that's canonical for beads and worktrees
|
||||||
- Polecats use git worktrees (not full clones) for speed
|
- Rig `.beads/` symlinks to Mayor's canonical beads
|
||||||
- No `mayor/rig/` in each rig (Mayor works from town level)
|
- Polecats are git worktrees from Mayor's clone (fast)
|
||||||
|
|
||||||
### Why Decentralized?
|
### Why Decentralized?
|
||||||
|
|
||||||
|
|||||||
@@ -65,18 +65,18 @@ func (m *Manager) Add(name string) (*Polecat, error) {
|
|||||||
return nil, fmt.Errorf("creating polecats dir: %w", err)
|
return nil, fmt.Errorf("creating polecats dir: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use refinery clone as the base for worktrees
|
// Use Mayor's clone as the base for worktrees (Mayor is canonical for the rig)
|
||||||
refineryPath := filepath.Join(m.rig.Path, "refinery", "rig")
|
mayorPath := filepath.Join(m.rig.Path, "mayor", "rig")
|
||||||
refineryGit := git.NewGit(refineryPath)
|
mayorGit := git.NewGit(mayorPath)
|
||||||
|
|
||||||
// Verify refinery clone exists
|
// Verify Mayor's clone exists
|
||||||
if _, err := os.Stat(refineryPath); os.IsNotExist(err) {
|
if _, err := os.Stat(mayorPath); os.IsNotExist(err) {
|
||||||
return nil, fmt.Errorf("refinery clone not found at %s (run 'gt rig add' to set up rig structure)", refineryPath)
|
return nil, fmt.Errorf("mayor clone not found at %s (run 'gt rig add' to set up rig structure)", mayorPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create worktree with new branch
|
// Create worktree with new branch
|
||||||
// git worktree add -b polecat/<name> <path>
|
// git worktree add -b polecat/<name> <path>
|
||||||
if err := refineryGit.WorktreeAdd(polecatPath, branchName); err != nil {
|
if err := mayorGit.WorktreeAdd(polecatPath, branchName); err != nil {
|
||||||
return nil, fmt.Errorf("creating worktree: %w", err)
|
return nil, fmt.Errorf("creating worktree: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ func (m *Manager) Add(name string) (*Polecat, error) {
|
|||||||
// Save state
|
// Save state
|
||||||
if err := m.saveState(polecat); err != nil {
|
if err := m.saveState(polecat); err != nil {
|
||||||
// Clean up worktree on failure
|
// Clean up worktree on failure
|
||||||
refineryGit.WorktreeRemove(polecatPath, true)
|
mayorGit.WorktreeRemove(polecatPath, true)
|
||||||
return nil, fmt.Errorf("saving state: %w", err)
|
return nil, fmt.Errorf("saving state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +120,12 @@ func (m *Manager) Remove(name string, force bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use refinery to remove the worktree properly
|
// Use Mayor's clone to remove the worktree properly
|
||||||
refineryPath := filepath.Join(m.rig.Path, "refinery", "rig")
|
mayorPath := filepath.Join(m.rig.Path, "mayor", "rig")
|
||||||
refineryGit := git.NewGit(refineryPath)
|
mayorGit := git.NewGit(mayorPath)
|
||||||
|
|
||||||
// Try to remove as a worktree first (use force flag for worktree removal too)
|
// Try to remove as a worktree first (use force flag for worktree removal too)
|
||||||
if err := refineryGit.WorktreeRemove(polecatPath, force); err != nil {
|
if err := mayorGit.WorktreeRemove(polecatPath, force); err != nil {
|
||||||
// Fall back to direct removal if worktree removal fails
|
// Fall back to direct removal if worktree removal fails
|
||||||
// (e.g., if this is an old-style clone, not a worktree)
|
// (e.g., if this is an old-style clone, not a worktree)
|
||||||
if removeErr := os.RemoveAll(polecatPath); removeErr != nil {
|
if removeErr := os.RemoveAll(polecatPath); removeErr != nil {
|
||||||
@@ -134,7 +134,7 @@ func (m *Manager) Remove(name string, force bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prune any stale worktree entries
|
// Prune any stale worktree entries
|
||||||
refineryGit.WorktreePrune()
|
mayorGit.WorktreePrune()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user