Skip to content

Commit d6cd9da

Browse files
easelclaude
andcommitted
feat(rig): add manifest support for rig configuration
Add .gt/rig.toml manifest file support for standardizing rig setup: - Parse and apply manifest defaults during gt rig add - Fork detection and upstream/origin remote configuration - Crew presets with agent, model, and account support - gt rig update command for checking/pulling updates - Setup command execution from manifest The manifest enables one-liner rig installation with automatic crew workspace creation and proper remote configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c3b7d5f commit d6cd9da

27 files changed

Lines changed: 2042 additions & 203 deletions

.gt/rig.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version = 1
2+
3+
[rig]
4+
name = "gastown"
5+
prefix = "gt"
6+
default_branch = "main"
7+
8+
[git]
9+
upstream = "https://github.com/steveyegge/gastown.git"
10+
fork_policy = "prompt"
11+
12+
[setup]
13+
command = "go install ./cmd/gt"
14+
workdir = "."
15+
16+
[[crew]]
17+
name = "max"

README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,8 @@ export PATH="$PATH:$HOME/go/bin"
108108
gt install ~/gt --git
109109
cd ~/gt
110110

111-
# Add your first project
112-
gt rig add myproject https://github.com/you/repo.git
113-
114-
# Create your crew workspace
115-
gt crew add yourname --rig myproject
111+
# Add your first project + crew workspace
112+
gt rig add myproject https://github.com/you/repo.git --crew yourname
116113
cd myproject/crew/yourname
117114

118115
# Start the Mayor session (your main interface)
@@ -320,9 +317,10 @@ Gas Town supports multiple AI coding runtimes. Per-rig runtime settings are in `
320317

321318
```bash
322319
gt install <path> # Initialize workspace
323-
gt rig add <name> <repo> # Add project
320+
gt rig add <name> <repo> --crew <name> # Add project + crew
324321
gt rig list # List projects
325-
gt crew add <name> --rig <rig> # Create crew workspace
322+
gt rig update <rig> --check # Check upstream status
323+
gt crew add <name> --rig <rig> # Add another crew workspace
326324
```
327325

328326
### Agent Operations

docs/INSTALLING.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ gt install ~/gt
112112
### Step 3: Add a Project (Rig)
113113

114114
```bash
115-
# Add your first project
116-
gt rig add myproject https://github.com/you/repo.git
115+
# Add your first project + crew workspace
116+
gt rig add myproject https://github.com/you/repo.git --crew $USER
117117

118118
# This clones the repo and sets up:
119119
# ~/gt/myproject/
@@ -122,6 +122,9 @@ gt rig add myproject https://github.com/you/repo.git
122122
# ├── refinery/rig/ # Merge queue processor
123123
# ├── witness/ # Worker monitor
124124
# └── polecats/ # Worker clones (created on demand)
125+
#
126+
# Your crew workspace is created at:
127+
# ~/gt/myproject/crew/$USER
125128
```
126129

127130
### Step 4: Verify Installation
@@ -286,6 +289,7 @@ To update Gas Town and Beads:
286289
go install github.com/steveyegge/gastown/cmd/gt@latest
287290
go install github.com/steveyegge/beads/cmd/bd@latest
288291
gt doctor --fix # Fix any post-update issues
292+
gt rig update myproject --pull # Update rig clones from upstream
289293
```
290294

291295
## Uninstalling

docs/design/rig-install.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Rig Add Shorthand And .gt/rig.toml
2+
3+
This document defines a shorthand for adding rigs to a town, along with a
4+
repository manifest (`.gt/rig.toml`) that standardizes crew defaults, naming,
5+
remotes, and setup/update behavior across towns.
6+
7+
## Goals
8+
9+
- Provide a one-liner to install a rig plus a crew workspace.
10+
- Reuse rig conventions (crew/polecat settings, beads prefix, upstreams).
11+
- Capture upstream vs origin (fork vs owner) and configure remotes correctly.
12+
- Offer a reliable rig-level setup/update behavior (self-update).
13+
- Keep current PR submission behavior unchanged by default.
14+
15+
## Non-Goals
16+
17+
- Replace `gt rig add` or `gt rig quick-add` (they remain supported).
18+
- Change default PR submission workflows.
19+
- Require forks for non-owners (prompt, don't enforce).
20+
21+
## Terminology
22+
23+
- **Upstream**: Canonical repo (e.g., `steveyegge/gastown`).
24+
- **Origin**: User's writable fork or local remote.
25+
- **Manifest**: `.gt/rig.toml` file in the repo.
26+
27+
## CLI Overview
28+
29+
### `gt rig add`
30+
31+
Adds a rig and applies manifest defaults when `.gt/rig.toml` is present (unless `--ignore-manifest`).
32+
33+
Examples:
34+
35+
```bash
36+
gt rig add gastown https://github.com/steveyegge/gastown.git --crew $USER
37+
gt rig add beads https://github.com/steveyegge/beads.git --crew $USER
38+
gt rig add myproject https://github.com/acme/myproject.git --crew alice
39+
gt rig add https://github.com/acme/myproject.git --crew alice
40+
gt rig add . --crew alice # local repo (quick-add)
41+
```
42+
43+
Core flags:
44+
45+
- `--crew <name>`: Create ONLY these crew workspaces (overrides manifest crew list).
46+
- `--no-crew`: Skip crew creation entirely.
47+
- `--start`: Autostart all created crew sessions.
48+
- `--ignore-manifest`: Ignore `.gt/rig.toml` entirely (no defaults, no setup, no crew list).
49+
- `--yes`: Non-interactive (accept defaults, skip prompts).
50+
- `--prefix <p>`: Override beads prefix.
51+
- `--branch <name>`: Override default branch.
52+
- `--local-repo <path>`: Use local reference repo for cloning.
53+
- `--upstream <url>`: Explicitly set upstream remote.
54+
- `--origin <url>`: Explicitly set origin remote.
55+
- `--fork <prompt|require|never>`: Fork handling policy (default: `prompt`).
56+
57+
`gt rig quick-add` should delegate to `gt rig add .` so it gains
58+
the same manifest and fork logic.
59+
60+
### `gt rig update`
61+
62+
Check or update a rig against upstream/origin.
63+
64+
Examples:
65+
66+
```bash
67+
gt rig update gastown --check
68+
gt rig update gastown --pull
69+
gt rig update gastown --pull --ignore-manifest
70+
```
71+
72+
Behavior:
73+
- `--check`: fetch and report ahead/behind vs configured default branch.
74+
- `--pull`: update mayor/refinery clones (no crew updates by default).
75+
- `--pull` runs manifest setup by default; use `--ignore-manifest` to skip.
76+
- If neither flag is provided, default to `--check`.
77+
78+
## Presets
79+
80+
Built-in presets for common rigs:
81+
82+
| Preset | Upstream | Default Setup Command |
83+
|--------|----------|-------------------------|
84+
| gastown | `https://github.com/steveyegge/gastown.git` | `go install ./cmd/gt` |
85+
| beads | `https://github.com/steveyegge/beads.git` | `go install ./cmd/bd` |
86+
87+
Presets are shorthand for a manifest (see below) and still honor CLI overrides.
88+
89+
## Manifest: `.gt/rig.toml`
90+
91+
The manifest is optional. If present, it provides rig defaults and the setup
92+
command. TOML aligns with existing usage (formulas and recipes).
93+
94+
Example:
95+
96+
```toml
97+
version = 1
98+
99+
[rig]
100+
name = "gastown"
101+
prefix = "gt"
102+
default_branch = "main"
103+
104+
[git]
105+
upstream = "https://github.com/steveyegge/gastown.git"
106+
fork_policy = "prompt" # prompt|require|never
107+
108+
[setup]
109+
command = "go install ./cmd/gt"
110+
workdir = "."
111+
112+
[settings]
113+
path = ".gt/rig-settings.json"
114+
115+
[[crew]]
116+
name = "max"
117+
agent = "codex"
118+
model = "gpt-5"
119+
account = "work"
120+
branch = true
121+
122+
[[crew]]
123+
name = "alex"
124+
agent = "claude"
125+
account = "personal"
126+
```
127+
128+
### Field Semantics
129+
130+
- `rig.name`: default rig name (sanitized if needed).
131+
- `rig.prefix`: default beads prefix, used only when no tracked `.beads/` exists.
132+
- `rig.default_branch`: fallback if remote default can't be detected.
133+
- `git.upstream`: canonical repo URL (used for update checks).
134+
- `git.fork_policy`: how to prompt for fork (`prompt` default).
135+
- `setup.command`: command run after `gt rig add` or `gt rig update --pull` unless `--ignore-manifest`.
136+
- `setup.workdir`: relative path inside repo (default: repo root).
137+
- `settings.path`: optional JSON file copied into `<rig>/settings/config.json`.
138+
- `crew`: list of crew entries (created but not started).
139+
140+
Crew entry fields:
141+
142+
- `crew.name` (required): crew workspace name.
143+
- `crew.agent` (optional): agent alias or built-in preset (e.g., `codex`, `claude`).
144+
- `crew.model` (optional): model name for the agent (appended to args when supported).
145+
- `crew.account` (optional): account handle for the agent runtime.
146+
- `crew.branch` (optional): `true` to create `crew/<name>` branch or a string to set an explicit branch.
147+
- `crew.args` (optional): extra CLI args appended when starting the crew session.
148+
- `crew.env` (optional): environment variables to set when starting the session.
149+
150+
### Crew Behavior
151+
152+
- If the manifest includes `[[crew]]` entries, those crew are created (not started).
153+
- If `--crew` is provided, only those crew are created. If a provided name
154+
matches a manifest entry, its config is used; otherwise defaults apply.
155+
- `--no-crew` disables crew creation entirely.
156+
- `--start` autostarts all crew created by the command.
157+
- Model support is determined by the agent preset in `settings/agents.json`.
158+
If `supports_model` is true, append `model_flag` (or `--model` if unset).
159+
- If `crew.model` is set but the agent does not support models, warn and skip it
160+
(users can still pass custom flags via `crew.args`).
161+
162+
## Fork Workflow
163+
164+
When upstream is `steveyegge/gastown` or `steveyegge/beads` and the GitHub
165+
user is not `steveyegge`, `gt rig add` should:
166+
167+
1. Detect if a fork exists (`gh repo view <user>/<repo>`).
168+
2. Prompt to use existing fork, create a fork, or continue read-only.
169+
3. Configure remotes:
170+
- `upstream` = canonical repo
171+
- `origin` = user fork (or upstream if owner)
172+
173+
If forked, configure beads sync to pull from upstream:
174+
175+
```bash
176+
bd config set sync.remote upstream
177+
```
178+
179+
## Data Model Changes
180+
181+
Extend rig metadata so update/fork info persists:
182+
183+
- `<rig>/config.json`: store `git.upstream`, `git.origin`, `setup.command`.
184+
- `mayor/rigs.json`: store upstream/origin for routing and future `gt crew add`.
185+
- `crew/<name>/state.json`: store default crew start config (agent, model, account, args, env).
186+
- `settings/agents.json`: add optional `model_flag` and `supports_model` for agent presets.
187+
188+
Back-compat: if these fields are missing, assume `origin` only.
189+
190+
## Error Handling
191+
192+
- If `gh` is missing, fork prompting should fall back to a read-only flow.
193+
- If the manifest is missing or invalid, proceed with defaults and warn.
194+
- If `setup.command` fails, print the command and exit non-zero.
195+
196+
## Testing
197+
198+
- Integration test: `gt rig add` with a local repo containing `.gt/rig.toml`.
199+
- Integration test: fork flow using a local "upstream + fork" repo pair.
200+
- Verify remotes, settings copy, crew creation, and update check output.
201+
202+
## Compatibility Notes
203+
204+
- `gt rig add` remains the primary command; manifest handling is additive.
205+
- `gt rig quick-add` becomes a thin wrapper over `gt rig add`.
206+
- PR submission defaults remain unchanged; optional `gt rig submit` can be added later.

docs/reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ export OPENCODE_PERMISSION='{"*":"allow"}'
509509

510510
```bash
511511
gt rig add <name> <url>
512+
gt rig add <name> <url> --crew <name>
513+
gt rig update <rig> --check|--pull
512514
gt rig list
513515
gt rig remove <name>
514516
```

internal/beads/beads.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,15 @@ func (b *Beads) run(args ...string) ([]byte, error) {
212212
} else {
213213
env = os.Environ()
214214
}
215-
cmd.Env = append(env, "BEADS_DIR="+beadsDir)
215+
env = append(env, "BEADS_DIR="+beadsDir)
216+
beadsDB := filepath.Join(beadsDir, "beads.db")
217+
if _, err := os.Stat(beadsDB); err == nil {
218+
env = append(env, "BEADS_DB="+beadsDB)
219+
}
220+
// Disable auto-routing when BEADS_DIR is explicitly set.
221+
env = setEnv(env, "BD_ROUTING_MODE", "explicit")
222+
env = setEnv(env, "BD_ROUTING_DEFAULT", ".")
223+
cmd.Env = env
216224

217225
var stdout, stderr bytes.Buffer
218226
cmd.Stdout = &stdout
@@ -233,6 +241,21 @@ func (b *Beads) run(args ...string) ([]byte, error) {
233241
return stdout.Bytes(), nil
234242
}
235243

244+
func setEnv(env []string, key, value string) []string {
245+
prefix := key + "="
246+
replaced := false
247+
for i, entry := range env {
248+
if strings.HasPrefix(entry, prefix) {
249+
env[i] = prefix + value
250+
replaced = true
251+
}
252+
}
253+
if !replaced {
254+
env = append(env, prefix+value)
255+
}
256+
return env
257+
}
258+
236259
// Run executes a bd command and returns stdout.
237260
// This is a public wrapper around the internal run method for cases where
238261
// callers need to run arbitrary bd commands.
@@ -496,6 +519,7 @@ func (b *Beads) Create(opts CreateOptions) (*Issue, error) {
496519
if actor != "" {
497520
args = append(args, "--actor="+actor)
498521
}
522+
args = b.maybeForceRepo(args)
499523

500524
out, err := b.run(args...)
501525
if err != nil {
@@ -544,6 +568,7 @@ func (b *Beads) CreateWithID(id string, opts CreateOptions) (*Issue, error) {
544568
if actor != "" {
545569
args = append(args, "--actor="+actor)
546570
}
571+
args = b.maybeForceRepo(args)
547572

548573
out, err := b.run(args...)
549574
if err != nil {
@@ -558,6 +583,41 @@ func (b *Beads) CreateWithID(id string, opts CreateOptions) (*Issue, error) {
558583
return &issue, nil
559584
}
560585

586+
func (b *Beads) maybeForceRepo(args []string) []string {
587+
for _, arg := range args {
588+
if strings.HasPrefix(arg, "--repo=") {
589+
return args
590+
}
591+
}
592+
593+
repoRoot := b.forceRepoRoot()
594+
if repoRoot == "" {
595+
return args
596+
}
597+
598+
return append(args, "--repo="+repoRoot)
599+
}
600+
601+
func (b *Beads) forceRepoRoot() string {
602+
beadsDir := b.beadsDir
603+
if beadsDir == "" {
604+
beadsDir = ResolveBeadsDir(b.workDir)
605+
}
606+
if !isTempPath(beadsDir) {
607+
return ""
608+
}
609+
return filepath.Dir(beadsDir)
610+
}
611+
612+
func isTempPath(path string) bool {
613+
tempDir := filepath.Clean(os.TempDir())
614+
clean := filepath.Clean(path)
615+
if clean == tempDir {
616+
return true
617+
}
618+
return strings.HasPrefix(clean, tempDir+string(os.PathSeparator))
619+
}
620+
561621
// Update updates an existing issue.
562622
func (b *Beads) Update(id string, opts UpdateOptions) error {
563623
args := []string{"update", id}

internal/beads/beads_redirect.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ func ResolveBeadsDir(workDir string) string {
3838
workDir = filepath.Dir(workDir)
3939
}
4040
beadsDir := filepath.Join(workDir, ".beads")
41+
if filepath.Base(workDir) == ".beads" {
42+
beadsDir = workDir
43+
workDir = filepath.Dir(workDir)
44+
}
4145
redirectPath := filepath.Join(beadsDir, "redirect")
4246

4347
// Check for redirect file

0 commit comments

Comments
 (0)