Initial commit: Go port scaffolding

- Go project structure (go.mod, cmd/gt/main.go)
- Beads database initialized with gt- prefix
- Town management design doc (docs/town-design.md)
- Basic README and CLAUDE.md

Epics tracked:
- gt-u1j: Port Gas Town to Go
- gt-f9x: Town & Rig Management

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-15 16:38:23 -08:00
commit 4c782bc59d
12 changed files with 835 additions and 0 deletions

32
.beads/.gitignore vendored Normal file
View File

@@ -0,0 +1,32 @@
# SQLite databases
*.db
*.db?*
*.db-journal
*.db-wal
*.db-shm
# Daemon runtime files
daemon.lock
daemon.log
daemon.pid
bd.sock
# Local version tracking (prevents upgrade notification spam after git ops)
.local_version
# Legacy database files
db.sqlite
bd.db
# Merge artifacts (temporary files from 3-way merge)
beads.base.jsonl
beads.base.meta.json
beads.left.jsonl
beads.left.meta.json
beads.right.jsonl
beads.right.meta.json
# Keep JSONL exports and config (source of truth for git)
!issues.jsonl
!metadata.json
!config.json

81
.beads/README.md Normal file
View File

@@ -0,0 +1,81 @@
# Beads - AI-Native Issue Tracking
Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code.
## What is Beads?
Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git.
**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads)
## Quick Start
### Essential Commands
```bash
# Create new issues
bd create "Add user authentication"
# View all issues
bd list
# View issue details
bd show <issue-id>
# Update issue status
bd update <issue-id> --status in_progress
bd update <issue-id> --status done
# Sync with git remote
bd sync
```
### Working with Issues
Issues in Beads are:
- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code
- **AI-friendly**: CLI-first design works perfectly with AI coding agents
- **Branch-aware**: Issues can follow your branch workflow
- **Always in sync**: Auto-syncs with your commits
## Why Beads?
**AI-Native Design**
- Built specifically for AI-assisted development workflows
- CLI-first interface works seamlessly with AI coding agents
- No context switching to web UIs
🚀 **Developer Focused**
- Issues live in your repo, right next to your code
- Works offline, syncs when you push
- Fast, lightweight, and stays out of your way
🔧 **Git Integration**
- Automatic sync with git commits
- Branch-aware issue tracking
- Intelligent JSONL merge resolution
## Get Started with Beads
Try Beads in your own projects:
```bash
# Install Beads
curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
# Initialize in your repo
bd init
# Create your first issue
bd create "Try out Beads"
```
## Learn More
- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs)
- **Quick Start Guide**: Run `bd quickstart`
- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples)
---
*Beads: Issue tracking that moves at the speed of thought*

63
.beads/config.yaml Normal file
View File

@@ -0,0 +1,63 @@
# Beads Configuration File
# This file configures default behavior for all bd commands in this repository
# All settings can also be set via environment variables (BD_* prefix)
# or overridden with command-line flags
# Issue prefix for this repository (used by bd init)
# If not set, bd init will auto-detect from directory name
# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc.
# issue-prefix: ""
# Use no-db mode: load from JSONL, no SQLite, write back after each command
# When true, bd will use .beads/issues.jsonl as the source of truth
# instead of SQLite database
# no-db: false
# Disable daemon for RPC communication (forces direct database access)
# no-daemon: false
# Disable auto-flush of database to JSONL after mutations
# no-auto-flush: false
# Disable auto-import from JSONL when it's newer than database
# no-auto-import: false
# Enable JSON output by default
# json: false
# Default actor for audit trails (overridden by BD_ACTOR or --actor)
# actor: ""
# Path to database (overridden by BEADS_DB or --db)
# db: ""
# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON)
# auto-start-daemon: true
# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE)
# flush-debounce: "5s"
# Git branch for beads commits (bd sync will commit to this branch)
# IMPORTANT: Set this for team projects so all clones use the same sync branch.
# This setting persists across clones (unlike database config which is gitignored).
# Can also use BEADS_SYNC_BRANCH env var for local override.
# If not set, bd sync will require you to run 'bd config set sync.branch <branch>'.
# sync-branch: "beads-sync"
# Multi-repo configuration (experimental - bd-307)
# Allows hydrating from multiple repositories and routing writes to the correct JSONL
# repos:
# primary: "." # Primary repo (where this database lives)
# additional: # Additional repos to hydrate from (read-only)
# - ~/beads-planning # Personal planning repo
# - ~/work-planning # Work planning repo
# Integration settings (access with 'bd config get/set')
# These are stored in the database, not in this file:
# - jira.url
# - jira.project
# - linear.url
# - linear.api-key
# - github.org
# - github.repo
sync-branch: beads-sync

13
.beads/issues.jsonl Normal file
View File

@@ -0,0 +1,13 @@
{"id":"gt-f9x","title":"Town \u0026 Rig Management: install, doctor, federation","description":"Reify the Gas Town installation as a first-class concept.\n\n## Goals\n- Installable: gt install [path] creates complete installation\n- Diagnosable: gt doctor checks and fixes issues\n- Federable: Clone town to VMs with central control\n\n## Design Doc\nSee docs/town-design.md for full design.","status":"open","priority":1,"issue_type":"epic","created_at":"2025-12-15T16:36:37.344283-08:00","updated_at":"2025-12-15T16:36:37.344283-08:00","dependencies":[{"issue_id":"gt-f9x","depends_on_id":"gt-u1j.1","type":"blocks","created_at":"2025-12-15T16:37:32.3363-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.1","title":"Config package: Config, State types and JSON serialization","description":"Define workspace and rig config structures","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T16:36:50.163851-08:00","updated_at":"2025-12-15T16:36:50.163851-08:00","dependencies":[{"issue_id":"gt-f9x.1","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:36:50.164178-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.10","title":"Extended addressing: Parse [machine:]rig/polecat","description":"Support machine-prefixed polecat addresses","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-15T16:37:23.426567-08:00","updated_at":"2025-12-15T16:37:23.426567-08:00","dependencies":[{"issue_id":"gt-f9x.10","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:37:23.426926-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.2","title":"Workspace detection: Find() walking up directory tree","description":"Find workspace root by looking for mayor/ or .gastown/","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T16:36:51.419316-08:00","updated_at":"2025-12-15T16:36:51.419316-08:00","dependencies":[{"issue_id":"gt-f9x.2","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:36:51.419635-08:00","created_by":"daemon"},{"issue_id":"gt-f9x.2","depends_on_id":"gt-f9x.1","type":"blocks","created_at":"2025-12-15T16:37:32.426416-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.3","title":"gt install command: Create workspace structure","description":"Create mayor/, mail/, rigs/, config.json, state.json","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T16:36:53.455589-08:00","updated_at":"2025-12-15T16:36:53.455589-08:00","dependencies":[{"issue_id":"gt-f9x.3","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:36:53.455924-08:00","created_by":"daemon"},{"issue_id":"gt-f9x.3","depends_on_id":"gt-f9x.1","type":"blocks","created_at":"2025-12-15T16:37:32.513796-08:00","created_by":"daemon"},{"issue_id":"gt-f9x.3","depends_on_id":"gt-f9x.2","type":"blocks","created_at":"2025-12-15T16:37:32.597456-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.4","title":"Doctor framework: Check interface, Result types, Report","description":"Framework for gt doctor health checks","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T16:37:03.81542-08:00","updated_at":"2025-12-15T16:37:03.81542-08:00","dependencies":[{"issue_id":"gt-f9x.4","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:37:03.815763-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.5","title":"Workspace doctor checks: Config, state, mail, boss, rigs","description":"Health checks for workspace-level components","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T16:37:05.267701-08:00","updated_at":"2025-12-15T16:37:05.267701-08:00","dependencies":[{"issue_id":"gt-f9x.5","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:37:05.268035-08:00","created_by":"daemon"},{"issue_id":"gt-f9x.5","depends_on_id":"gt-f9x.4","type":"blocks","created_at":"2025-12-15T16:37:34.289236-08:00","created_by":"daemon"},{"issue_id":"gt-f9x.5","depends_on_id":"gt-f9x.2","type":"blocks","created_at":"2025-12-15T16:37:34.380374-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.6","title":"Rig doctor checks: Refinery health, clones, gitignore","description":"Health checks for rig-level components","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T16:37:06.543281-08:00","updated_at":"2025-12-15T16:37:06.543281-08:00","dependencies":[{"issue_id":"gt-f9x.6","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:37:06.543796-08:00","created_by":"daemon"},{"issue_id":"gt-f9x.6","depends_on_id":"gt-f9x.4","type":"blocks","created_at":"2025-12-15T16:37:34.46868-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.7","title":"Connection interface: Protocol for local/remote ops","description":"Abstract interface for local vs SSH operations","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-15T16:37:07.764838-08:00","updated_at":"2025-12-15T16:37:07.764838-08:00","dependencies":[{"issue_id":"gt-f9x.7","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:37:07.765169-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.8","title":"LocalConnection: Local file/exec/tmux operations","description":"Implementation for local machine operations","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-15T16:37:19.879102-08:00","updated_at":"2025-12-15T16:37:19.879102-08:00","dependencies":[{"issue_id":"gt-f9x.8","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:37:19.879451-08:00","created_by":"daemon"},{"issue_id":"gt-f9x.8","depends_on_id":"gt-f9x.7","type":"blocks","created_at":"2025-12-15T16:37:36.087392-08:00","created_by":"daemon"}]}
{"id":"gt-f9x.9","title":"Machine registry: Store and manage machine configs","description":"Registry for federation machine management","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-15T16:37:21.968099-08:00","updated_at":"2025-12-15T16:37:21.968099-08:00","dependencies":[{"issue_id":"gt-f9x.9","depends_on_id":"gt-f9x","type":"parent-child","created_at":"2025-12-15T16:37:21.968442-08:00","created_by":"daemon"},{"issue_id":"gt-f9x.9","depends_on_id":"gt-f9x.7","type":"blocks","created_at":"2025-12-15T16:37:36.174052-08:00","created_by":"daemon"}]}
{"id":"gt-u1j","title":"Port Gas Town to Go","description":"Complete rewrite of Gas Town in Go for improved performance and single-binary distribution.\n\n## Goals\n- Single installable binary (gt)\n- All Python functionality ported\n- Federation support built-in\n- Improved performance\n\n## Phases\n1. Core infrastructure (config, workspace, git wrapper)\n2. Rig \u0026 polecat management\n3. Session \u0026 tmux operations\n4. Mail system\n5. CLI commands\n6. TUI (optional)","status":"open","priority":0,"issue_type":"epic","created_at":"2025-12-15T16:36:28.769343-08:00","updated_at":"2025-12-15T16:36:28.769343-08:00"}
{"id":"gt-u1j.1","title":"Go scaffolding: cmd/gt, go.mod, Cobra setup","description":"Set up Go project structure with Cobra CLI framework","status":"open","priority":0,"issue_type":"task","created_at":"2025-12-15T16:36:48.376267-08:00","updated_at":"2025-12-15T16:36:48.376267-08:00","dependencies":[{"issue_id":"gt-u1j.1","depends_on_id":"gt-u1j","type":"parent-child","created_at":"2025-12-15T16:36:48.376622-08:00","created_by":"daemon"}]}

4
.beads/metadata.json Normal file
View File

@@ -0,0 +1,4 @@
{
"database": "beads.db",
"jsonl_export": "issues.jsonl"
}

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
# Use bd merge for beads JSONL files
.beads/issues.jsonl merge=beads

26
.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# Binaries
/gt
*.exe
# Build
/dist/
/build/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Test
coverage.out
*.test
# Local config
config.toml
!config.example.toml

23
CLAUDE.md Normal file
View File

@@ -0,0 +1,23 @@
# Claude: Gastown Go Port
Run `bd prime` for beads context.
## Project Info
This is the **Go port** of Gas Town, a multi-agent workspace manager.
- **Issue prefix**: `gt-`
- **Python version**: ~/ai/gastown-py (reference implementation)
- **Design docs**: docs/town-design.md
## Development
```bash
go build -o gt ./cmd/gt
go test ./...
```
## Key Epics
- `gt-fqwd`: Port Gas Town to Go (main tracking epic)
- `gt-evp2`: Town & Rig Management (install, doctor, federation)

31
README.md Normal file
View File

@@ -0,0 +1,31 @@
# Gastown (Go)
Go port of [Gas Town](https://github.com/steveyegge/gastown-py) - a multi-agent workspace manager.
## Status
**Work in Progress** - This is the Go rewrite of the Python gastown tool.
See the [Python version](https://github.com/steveyegge/gastown-py) for current functionality.
## Goals
- Single binary installation (`gt`)
- Self-diagnosing (`gt doctor`)
- Federation support (coordinate agents across VMs)
- Performance improvements over Python version
## Development
```bash
# Build
go build -o gt ./cmd/gt
# Run
./gt --help
```
## Related
- [gastown-py](https://github.com/steveyegge/gastown-py) - Python version (current)
- [beads](https://github.com/steveyegge/beads) - Issue tracking for agents

15
cmd/gt/main.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("gt - Gas Town CLI")
fmt.Println("Version: 0.0.1 (development)")
if len(os.Args) > 1 {
fmt.Printf("Command: %s\n", os.Args[1])
fmt.Println("Not yet implemented. See gastown-py for current functionality.")
}
}

541
docs/town-design.md Normal file
View File

@@ -0,0 +1,541 @@
# Town Management Design
Design for `gt install`, `gt doctor`, and federation in the Gas Town Go port.
## Overview
A **Town** is a complete Gas Town installation containing:
- Workspace config (`.gastown/` or `mayor/`)
- Rigs (project workspaces)
- Polecats (worker clones)
- Mail system
- Beads integration
## Config Files
### Workspace Root
A town is identified by a config directory (`mayor/` or `.gastown/`) containing `config.json` with `type: "workspace"`.
```
~/ai/ # Workspace root
├── mayor/ # or .gastown/
│ ├── config.json # Workspace identity
│ ├── state.json # Workspace state
│ ├── mail/ # Mail system
│ │ └── inbox.jsonl # Mayor's inbox
│ ├── boss/ # Boss state
│ │ └── state.json
│ └── rigs/ # Mayor's rig clones (for beads access)
│ ├── gastown/
│ └── beads/
├── gastown/ # Rig
│ ├── .gastown/ # Rig config (type: "rig")
│ │ └── config.json
│ ├── refinery/ # Refinery infrastructure
│ │ ├── README.md
│ │ ├── state.json
│ │ ├── state.json.lock
│ │ └── rig/ # Refinery's git clone
│ └── <polecats>/ # Worker clones
└── beads/ # Another rig
```
### Workspace config.json
```json
{
"type": "workspace",
"version": 1,
"created_at": "2024-01-15T10:30:00Z"
}
```
### Workspace state.json
```json
{
"version": 1,
"projects": {}
}
```
### Rig config.json
```json
{
"type": "rig",
"git_url": "https://github.com/steveyegge/gastown",
"beads_path": "~/.gastown/rigs/gastown/.beads",
"federation": {
"machines": ["local", "gcp-west-1"],
"preferred_machine": "local"
}
}
```
### Refinery state.json
```json
{
"version": 1,
"state": "stopped",
"awake": false,
"created_at": "2024-01-15T10:30:00Z",
"last_started": null,
"last_stopped": null,
"last_wake": null,
"last_sleep": null
}
```
## gt install
### Command
```bash
gt install [path] # Default: current directory
gt install ~/ai
```
### Behavior
1. **Check if already installed**: Look for `mayor/` or `.gastown/` with `type: "workspace"`
2. **Create workspace structure**:
- `mayor/config.json` - workspace identity
- `mayor/state.json` - workspace state
- `mayor/mail/` - mail directory
- `mayor/boss/state.json` - boss state
- `mayor/rigs/` - empty, populated when rigs are added
3. **Create .gitignore** - ignore ephemeral state, polecat clones
4. **Create CLAUDE.md** - Mayor instructions
5. **Initialize git** if not present
### Implementation (Go)
```go
// pkg/workspace/install.go
package workspace
type InstallOptions struct {
Path string
Force bool // Overwrite existing
}
func Install(opts InstallOptions) (*Workspace, error) {
path := opts.Path
if path == "" {
path = "."
}
// Resolve and validate
absPath, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("invalid path: %w", err)
}
// Check existing
if ws, _ := Find(absPath); ws != nil && !opts.Force {
return nil, ErrAlreadyInstalled
}
// Create structure
mayorDir := filepath.Join(absPath, "mayor")
if err := os.MkdirAll(mayorDir, 0755); err != nil {
return nil, err
}
// Write config
config := Config{
Type: "workspace",
Version: 1,
CreatedAt: time.Now().UTC(),
}
if err := writeJSON(filepath.Join(mayorDir, "config.json"), config); err != nil {
return nil, err
}
// ... create state, mail, boss, rigs
return &Workspace{Path: absPath, Config: config}, nil
}
```
## gt doctor
### Command
```bash
gt doctor # Check workspace health
gt doctor --fix # Auto-fix issues
gt doctor <rig> # Check specific rig
```
### Checks
#### Workspace Level
1. **Workspace exists**: `mayor/` or `.gastown/` directory
2. **Valid config**: `config.json` has `type: "workspace"`
3. **State file**: `state.json` exists and is valid JSON
4. **Mail directory**: `mail/` exists
5. **Boss state**: `boss/state.json` exists
6. **Rigs directory**: `rigs/` exists
#### Per-Rig Checks
1. **Refinery directory**: `<rig>/refinery/` exists
2. **Refinery README**: `refinery/README.md` exists
3. **Refinery state**: `refinery/state.json` exists
4. **Refinery lock**: `refinery/state.json.lock` exists
5. **Refinery clone**: `refinery/rig/` has valid `.git`
6. **Boss rig clone**: `mayor/rigs/<rig>/` has valid `.git`
7. **Gitignore entries**: workspace `.gitignore` has rig patterns
### Output Format
```
$ gt doctor
Workspace: ~/ai
✓ Workspace config valid
✓ Workspace state valid
✓ Mail directory exists
✓ Boss state valid
Rig: gastown
✓ Refinery directory exists
✓ Refinery README exists
✓ Refinery state valid
✗ Missing refinery/rig/ clone
✓ Mayor rig clone exists
✓ Gitignore entries present
Rig: beads
✓ Refinery directory exists
✓ Refinery README exists
✓ Refinery state valid
✓ Refinery clone valid
✓ Mayor rig clone exists
✓ Gitignore entries present
Issues: 1 found, 0 fixed
Run with --fix to auto-repair
```
### Implementation (Go)
```go
// pkg/doctor/doctor.go
package doctor
type CheckResult struct {
Name string
Status Status // Pass, Fail, Warn
Message string
Fixable bool
}
type DoctorOptions struct {
Fix bool
Rig string // Empty = all rigs
Verbose bool
}
func Run(ws *workspace.Workspace, opts DoctorOptions) (*Report, error) {
report := &Report{}
// Workspace checks
report.Add(checkWorkspaceConfig(ws))
report.Add(checkWorkspaceState(ws))
report.Add(checkMailDir(ws))
report.Add(checkBossState(ws))
report.Add(checkRigsDir(ws))
// Per-rig checks
rigs, _ := ws.ListRigs()
for _, rig := range rigs {
if opts.Rig != "" && rig.Name != opts.Rig {
continue
}
report.AddRig(rig.Name, checkRig(rig, ws, opts.Fix))
}
return report, nil
}
func checkRefineryHealth(rig *rig.Rig, fix bool) []CheckResult {
var results []CheckResult
refineryDir := filepath.Join(rig.Path, "refinery")
// Check refinery directory
if !dirExists(refineryDir) {
r := CheckResult{Name: "Refinery directory", Status: Fail, Fixable: true}
if fix {
if err := os.MkdirAll(refineryDir, 0755); err == nil {
r.Status = Fixed
}
}
results = append(results, r)
}
// Check README.md
readmePath := filepath.Join(refineryDir, "README.md")
if !fileExists(readmePath) {
r := CheckResult{Name: "Refinery README", Status: Fail, Fixable: true}
if fix {
if err := writeRefineryReadme(readmePath); err == nil {
r.Status = Fixed
}
}
results = append(results, r)
}
// ... more checks
return results
}
```
## Workspace Detection
Find workspace root by walking up from current directory:
```go
// pkg/workspace/find.go
func Find(startPath string) (*Workspace, error) {
current, _ := filepath.Abs(startPath)
for current != filepath.Dir(current) {
// Check both "mayor" and ".gastown" directories
for _, dirName := range []string{"mayor", ".gastown"} {
configDir := filepath.Join(current, dirName)
configPath := filepath.Join(configDir, "config.json")
if fileExists(configPath) {
var config Config
if err := readJSON(configPath, &config); err != nil {
continue
}
if config.Type == "workspace" {
return &Workspace{
Path: current,
ConfigDir: dirName,
Config: config,
}, nil
}
}
}
current = filepath.Dir(current)
}
return nil, ErrNotFound
}
```
## Minimal Federation Protocol
Federation enables work distribution across multiple machines via SSH.
### Core Abstractions
#### Connection Interface
```go
// pkg/connection/connection.go
type Connection interface {
// Command execution
Execute(ctx context.Context, cmd string, opts ExecOpts) (*Result, error)
// File operations
ReadFile(path string) ([]byte, error)
WriteFile(path string, data []byte) error
AppendFile(path string, data []byte) error
FileExists(path string) (bool, error)
ListDir(path string) ([]string, error)
MkdirAll(path string) error
// Tmux operations
TmuxSend(session string, text string) error
TmuxCapture(session string, lines int) (string, error)
TmuxHasSession(session string) (bool, error)
// Health
IsHealthy() bool
}
```
#### LocalConnection
```go
// pkg/connection/local.go
type LocalConnection struct{}
func (c *LocalConnection) Execute(ctx context.Context, cmd string, opts ExecOpts) (*Result, error) {
// Direct exec.Command
}
func (c *LocalConnection) ReadFile(path string) ([]byte, error) {
return os.ReadFile(path)
}
```
#### SSHConnection
```go
// pkg/connection/ssh.go
type SSHConnection struct {
Host string
User string
KeyPath string
client *ssh.Client
}
func (c *SSHConnection) Execute(ctx context.Context, cmd string, opts ExecOpts) (*Result, error) {
session, err := c.client.NewSession()
if err != nil {
return nil, err
}
defer session.Close()
// Run command via SSH
}
```
### Machine Registry
```go
// pkg/federation/registry.go
type Machine struct {
Name string
Type string // "local", "ssh", "gcp"
Workspace string // Remote workspace path
SSHHost string
SSHUser string
SSHKeyPath string
GCPProject string
GCPZone string
GCPInstance string
}
type Registry struct {
machines map[string]*Machine
conns map[string]Connection
}
func (r *Registry) GetConnection(name string) (Connection, error) {
if conn, ok := r.conns[name]; ok {
return conn, nil
}
machine, ok := r.machines[name]
if !ok {
return nil, ErrMachineNotFound
}
var conn Connection
switch machine.Type {
case "local":
conn = &LocalConnection{}
case "ssh", "gcp":
conn = NewSSHConnection(machine.SSHHost, machine.SSHUser, machine.SSHKeyPath)
}
r.conns[name] = conn
return conn, nil
}
```
### Extended Addressing
Polecat addresses support optional machine prefix:
```
[machine:]rig/polecat
Examples:
beads/happy # Local machine (default)
gcp-west:beads/happy # Remote machine
```
```go
// pkg/identity/address.go
type PolecatAddress struct {
Machine string // Default: "local"
Rig string
Polecat string
}
func ParseAddress(addr string) (*PolecatAddress, error) {
parts := strings.SplitN(addr, ":", 2)
if len(parts) == 2 {
// machine:rig/polecat
machine := parts[0]
rigPolecat := strings.SplitN(parts[1], "/", 2)
return &PolecatAddress{Machine: machine, Rig: rigPolecat[0], Polecat: rigPolecat[1]}, nil
}
// rig/polecat (local default)
rigPolecat := strings.SplitN(addr, "/", 2)
return &PolecatAddress{Machine: "local", Rig: rigPolecat[0], Polecat: rigPolecat[1]}, nil
}
```
### Mail Routing
For federation < 50 agents, use centralized mail through Mayor's machine:
```go
// pkg/mail/router.go
type MailRouter struct {
registry *federation.Registry
}
func (r *MailRouter) Deliver(msg *Message) error {
addr, _ := identity.ParseAddress(msg.Recipient)
conn, err := r.registry.GetConnection(addr.Machine)
if err != nil {
return err
}
mailboxPath := filepath.Join(addr.Rig, addr.Polecat, "mail", "inbox.jsonl")
return conn.AppendFile(mailboxPath, msg.ToJSONL())
}
```
## Implementation Plan
### Subtasks for gt-evp2
1. **Config package** - Config, State types and JSON serialization
2. **Workspace detection** - Find() walking up directory tree
3. **gt install command** - Create workspace structure
4. **Doctor framework** - Check interface, Result types, Report
5. **Workspace doctor checks** - Config, state, mail, boss, rigs
6. **Rig doctor checks** - Refinery health, clones, gitignore
7. **Connection interface** - Define protocol for local/remote ops
8. **LocalConnection** - Local file/exec/tmux operations
9. **Machine registry** - Store and manage machine configs
10. **Extended addressing** - Parse `[machine:]rig/polecat`
### Deferred (Federation Phase 2)
- SSHConnection implementation
- GCPConnection with gcloud integration
- Cross-machine mail routing
- Remote session management
- Worker pool across machines
## CLI Commands Summary
```bash
# Installation
gt install [path] # Install workspace at path
gt install --force # Overwrite existing
# Diagnostics
gt doctor # Check workspace health
gt doctor --fix # Auto-fix issues
gt doctor <rig> # Check specific rig
gt doctor --verbose # Show all checks (not just failures)
# Future (federation)
gt machine list # List machines
gt machine add <name> # Add machine
gt machine status # Check all machine health
```

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/steveyegge/gastown
go 1.23