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:
32
.beads/.gitignore
vendored
Normal file
32
.beads/.gitignore
vendored
Normal 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
81
.beads/README.md
Normal 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
63
.beads/config.yaml
Normal 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
13
.beads/issues.jsonl
Normal 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
4
.beads/metadata.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"database": "beads.db",
|
||||||
|
"jsonl_export": "issues.jsonl"
|
||||||
|
}
|
||||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
# Use bd merge for beads JSONL files
|
||||||
|
.beads/issues.jsonl merge=beads
|
||||||
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal 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
23
CLAUDE.md
Normal 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
31
README.md
Normal 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
15
cmd/gt/main.go
Normal 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
541
docs/town-design.md
Normal 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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user