chore: add design docs and ready command

- Add convoy-lifecycle.md design doc
- Add formula-resolution.md design doc
- Add mol-mall-design.md design doc
- Add ready.go command implementation
- Move dog-pool-architecture.md to docs/design/
- Update .gitignore for beads sync files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
mayor
2026-01-13 03:18:09 -08:00
committed by beads/crew/emma
parent e7b0af0295
commit 791b388a93
6 changed files with 1214 additions and 0 deletions

2
.gitignore vendored
View File

@@ -42,6 +42,8 @@ state.json
.beads/mq/
.beads/last-touched
.beads/daemon-*.log.gz
.beads/.sync.lock
.beads/sync_base.jsonl
.beads-wisp/
# Clone-specific CLAUDE.md (regenerated locally per clone)

View File

@@ -0,0 +1,197 @@
# Convoy Lifecycle Design
> Making convoys actively converge on completion.
## Problem Statement
Convoys are passive trackers. They group work but don't drive it. The completion
loop has a structural gap:
```
Create → Assign → Execute → Issues close → ??? → Convoy closes
```
The `???` is "Deacon patrol runs `gt convoy check`" - a poll-based single point of
failure. When Deacon is down, convoys don't close. Work completes but the loop
never lands.
## Current State
### What Works
- Convoy creation and issue tracking
- `gt convoy status` shows progress
- `gt convoy stranded` finds unassigned work
- `gt convoy check` auto-closes completed convoys
### What Breaks
1. **Poll-based completion**: Only Deacon runs `gt convoy check`
2. **No event-driven trigger**: Issue close doesn't propagate to convoy
3. **No manual close**: Can't force-close abandoned convoys
4. **Single observer**: No redundant completion detection
5. **Weak notification**: Convoy owner not always clear
## Design: Active Convoy Convergence
### Principle: Event-Driven, Redundantly Observed
Convoy completion should be:
1. **Event-driven**: Triggered by issue close, not polling
2. **Redundantly observed**: Multiple agents can detect and close
3. **Manually overridable**: Humans can force-close
### Event-Driven Completion
When an issue closes, check if it's tracked by a convoy:
```
Issue closes
Is issue tracked by convoy? ──(no)──► done
(yes)
Run gt convoy check <convoy-id>
All tracked issues closed? ──(no)──► done
(yes)
Close convoy, send notifications
```
**Implementation options:**
1. Daemon hook on `bd update --status=closed`
2. Refinery step after successful merge
3. Witness step after verifying polecat completion
Option 1 is most reliable - catches all closes regardless of source.
### Redundant Observers
Per PRIMING.md: "Redundant Monitoring Is Resilience."
Three places should check convoy completion:
| Observer | When | Scope |
|----------|------|-------|
| **Daemon** | On any issue close | All convoys |
| **Witness** | After verifying polecat work | Rig's convoy work |
| **Deacon** | Periodic patrol | All convoys (backup) |
Any observer noticing completion triggers close. Idempotent - closing
an already-closed convoy is a no-op.
### Manual Close Command
**Desire path**: `gt convoy close` is expected but missing.
```bash
# Close a completed convoy
gt convoy close hq-cv-abc
# Force-close an abandoned convoy
gt convoy close hq-cv-xyz --reason="work done differently"
# Close with explicit notification
gt convoy close hq-cv-abc --notify mayor/
```
Use cases:
- Abandoned convoys no longer relevant
- Work completed outside tracked path
- Force-closing stuck convoys
### Convoy Owner/Requester
Track who requested the convoy for targeted notifications:
```bash
gt convoy create "Feature X" gt-abc --owner mayor/ --notify overseer
```
| Field | Purpose |
|-------|---------|
| `owner` | Who requested (gets completion notification) |
| `notify` | Additional subscribers |
If `owner` not specified, defaults to creator (from `created_by`).
### Convoy States
```
OPEN ──(all issues close)──► CLOSED
│ │
│ ▼
│ (add issues)
│ │
└─────────────────────────────┘
(auto-reopens)
```
Adding issues to closed convoy reopens automatically.
**New state for abandonment:**
```
OPEN ──► CLOSED (completed)
└────► ABANDONED (force-closed without completion)
```
### Timeout/SLA (Future)
Optional `due_at` field for convoy deadline:
```bash
gt convoy create "Sprint work" gt-abc --due="2026-01-15"
```
Overdue convoys surface in `gt convoy stranded --overdue`.
## Commands
### New: `gt convoy close`
```bash
gt convoy close <convoy-id> [--reason=<reason>] [--notify=<agent>]
```
- Closes convoy regardless of tracked issue status
- Sets `close_reason` field
- Sends notification to owner and subscribers
- Idempotent - closing closed convoy is no-op
### Enhanced: `gt convoy check`
```bash
# Check all convoys (current behavior)
gt convoy check
# Check specific convoy (new)
gt convoy check <convoy-id>
# Dry-run mode
gt convoy check --dry-run
```
### New: `gt convoy reopen`
```bash
gt convoy reopen <convoy-id>
```
Explicit reopen for clarity (currently implicit via add).
## Implementation Priority
1. **P0: `gt convoy close`** - Desire path, escape hatch
2. **P0: Event-driven check** - Daemon hook on issue close
3. **P1: Redundant observers** - Witness/Refinery integration
4. **P2: Owner field** - Targeted notifications
5. **P3: Timeout/SLA** - Deadline tracking
## Related
- [convoy.md](../concepts/convoy.md) - Convoy concept and usage
- [watchdog-chain.md](watchdog-chain.md) - Deacon patrol system
- [mail-protocol.md](mail-protocol.md) - Notification delivery

248
docs/formula-resolution.md Normal file
View File

@@ -0,0 +1,248 @@
# Formula Resolution Architecture
> Where formulas live, how they're found, and how they'll scale to Mol Mall
## The Problem
Formulas currently exist in multiple locations with no clear precedence:
- `.beads/formulas/` (source of truth for a project)
- `internal/formula/formulas/` (embedded copy for `go install`)
- Crew directories have their own `.beads/formulas/` (diverging copies)
When an agent runs `bd cook mol-polecat-work`, which version do they get?
## Design Goals
1. **Predictable resolution** - Clear precedence rules
2. **Local customization** - Override system defaults without forking
3. **Project-specific formulas** - Committed workflows for collaborators
4. **Mol Mall ready** - Architecture supports remote formula installation
5. **Federation ready** - Formulas are shareable across towns via HOP (Highway Operations Protocol)
## Three-Tier Resolution
```
┌─────────────────────────────────────────────────────────────────┐
│ FORMULA RESOLUTION ORDER │
│ (most specific wins) │
└─────────────────────────────────────────────────────────────────┘
TIER 1: PROJECT (rig-level)
Location: <project>/.beads/formulas/
Source: Committed to project repo
Use case: Project-specific workflows (deploy, test, release)
Example: ~/gt/gastown/.beads/formulas/mol-gastown-release.formula.toml
TIER 2: TOWN (user-level)
Location: ~/gt/.beads/formulas/
Source: Mol Mall installs, user customizations
Use case: Cross-project workflows, personal preferences
Example: ~/gt/.beads/formulas/mol-polecat-work.formula.toml (customized)
TIER 3: SYSTEM (embedded)
Location: Compiled into gt binary
Source: gastown/mayor/rig/.beads/formulas/ at build time
Use case: Defaults, blessed patterns, fallback
Example: mol-polecat-work.formula.toml (factory default)
```
### Resolution Algorithm
```go
func ResolveFormula(name string, cwd string) (Formula, Tier, error) {
// Tier 1: Project-level (walk up from cwd to find .beads/formulas/)
if projectDir := findProjectRoot(cwd); projectDir != "" {
path := filepath.Join(projectDir, ".beads", "formulas", name+".formula.toml")
if f, err := loadFormula(path); err == nil {
return f, TierProject, nil
}
}
// Tier 2: Town-level
townDir := getTownRoot() // ~/gt or $GT_HOME
path := filepath.Join(townDir, ".beads", "formulas", name+".formula.toml")
if f, err := loadFormula(path); err == nil {
return f, TierTown, nil
}
// Tier 3: Embedded (system)
if f, err := loadEmbeddedFormula(name); err == nil {
return f, TierSystem, nil
}
return nil, 0, ErrFormulaNotFound
}
```
### Why This Order
**Project wins** because:
- Project maintainers know their workflows best
- Collaborators get consistent behavior via git
- CI/CD uses the same formulas as developers
**Town is middle** because:
- User customizations override system defaults
- Mol Mall installs don't require project changes
- Cross-project consistency for the user
**System is fallback** because:
- Always available (compiled in)
- Factory reset target
- The "blessed" versions
## Formula Identity
### Current Format
```toml
formula = "mol-polecat-work"
version = 4
description = "..."
```
### Extended Format (Mol Mall Ready)
```toml
[formula]
name = "mol-polecat-work"
version = "4.0.0" # Semver
author = "steve@gastown.io" # Author identity
license = "MIT"
repository = "https://github.com/steveyegge/gastown"
[formula.registry]
uri = "hop://molmall.gastown.io/formulas/mol-polecat-work@4.0.0"
checksum = "sha256:abc123..." # Integrity verification
signed_by = "steve@gastown.io" # Optional signing
[formula.capabilities]
# What capabilities does this formula exercise? Used for agent routing.
primary = ["go", "testing", "code-review"]
secondary = ["git", "ci-cd"]
```
### Version Resolution
When multiple versions exist:
```bash
bd cook mol-polecat-work # Resolves per tier order
bd cook mol-polecat-work@4 # Specific major version
bd cook mol-polecat-work@4.0.0 # Exact version
bd cook mol-polecat-work@latest # Explicit latest
```
## Crew Directory Problem
### Current State
Crew directories (`gastown/crew/max/`) are sparse checkouts of gastown. They have:
- Their own `.beads/formulas/` (from the checkout)
- These can diverge from `mayor/rig/.beads/formulas/`
### The Fix
Crew should NOT have their own formula copies. Options:
**Option A: Symlink/Redirect**
```bash
# crew/max/.beads/formulas -> ../../mayor/rig/.beads/formulas
```
All crew share the rig's formulas.
**Option B: Provision on Demand**
Crew directories don't have `.beads/formulas/`. Resolution falls through to:
1. Town-level (~/gt/.beads/formulas/)
2. System (embedded)
**Option C: Sparse Checkout Exclusion**
Exclude `.beads/formulas/` from crew sparse checkouts entirely.
**Recommendation: Option B** - Crew shouldn't need project-level formulas. They work on the project, they don't define its workflows.
## Commands
### Existing
```bash
bd formula list # Available formulas (should show tier)
bd formula show <name> # Formula details
bd cook <formula> # Formula → Proto
```
### Enhanced
```bash
# List with tier information
bd formula list
mol-polecat-work v4 [project]
mol-polecat-code-review v1 [town]
mol-witness-patrol v2 [system]
# Show resolution path
bd formula show mol-polecat-work --resolve
Resolving: mol-polecat-work
✓ Found at: ~/gt/gastown/.beads/formulas/mol-polecat-work.formula.toml
Tier: project
Version: 4
Resolution path checked:
1. [project] ~/gt/gastown/.beads/formulas/ ← FOUND
2. [town] ~/gt/.beads/formulas/
3. [system] <embedded>
# Override tier for testing
bd cook mol-polecat-work --tier=system # Force embedded version
bd cook mol-polecat-work --tier=town # Force town version
```
### Future (Mol Mall)
```bash
# Install from Mol Mall
gt formula install mol-code-review-strict
gt formula install mol-code-review-strict@2.0.0
gt formula install hop://acme.corp/formulas/mol-deploy
# Manage installed formulas
gt formula list --installed # What's in town-level
gt formula upgrade mol-polecat-work # Update to latest
gt formula pin mol-polecat-work@4.0.0 # Lock version
gt formula uninstall mol-code-review-strict
```
## Migration Path
### Phase 1: Resolution Order (Now)
1. Implement three-tier resolution in `bd cook`
2. Add `--resolve` flag to show resolution path
3. Update `bd formula list` to show tiers
4. Fix crew directories (Option B)
### Phase 2: Town-Level Formulas
1. Establish `~/gt/.beads/formulas/` as town formula location
2. Add `gt formula` commands for managing town formulas
3. Support manual installation (copy file, track in `.installed.json`)
### Phase 3: Mol Mall Integration
1. Define registry API (see mol-mall-design.md)
2. Implement `gt formula install` from remote
3. Add version pinning and upgrade flows
4. Add integrity verification (checksums, optional signing)
### Phase 4: Federation (HOP)
1. Add capability tags to formula schema
2. Track formula execution for agent accountability
3. Enable federation (cross-town formula sharing via Highway Operations Protocol)
4. Author attribution and validation records
## Related Documents
- [Mol Mall Design](mol-mall-design.md) - Registry architecture
- [molecules.md](molecules.md) - Formula → Proto → Mol lifecycle
- [understanding-gas-town.md](../../../docs/understanding-gas-town.md) - Gas Town architecture

476
docs/mol-mall-design.md Normal file
View File

@@ -0,0 +1,476 @@
# Mol Mall Design
> A marketplace for Gas Town formulas
## Vision
**Mol Mall** is a registry for sharing formulas across Gas Town installations. Think npm for molecules, or Terraform Registry for workflows.
```
"Cook a formula, sling it to a polecat, the witness watches, refinery merges."
What if you could browse a mall of formulas, install one, and immediately
have your polecats executing world-class workflows?
```
### The Network Effect
A well-designed formula for "code review" or "security audit" or "deploy to K8s" can spread across thousands of Gas Town installations. Each adoption means:
- More agents executing proven workflows
- More structured, trackable work output
- Better capability routing (agents with track records on a formula get similar work)
## Architecture
### Registry Types
```
┌─────────────────────────────────────────────────────────────────┐
│ MOL MALL REGISTRIES │
└─────────────────────────────────────────────────────────────────┘
PUBLIC REGISTRY (molmall.gastown.io)
├── Community formulas (MIT licensed)
├── Official Gas Town formulas (blessed)
├── Verified publisher formulas
└── Open contribution model
PRIVATE REGISTRY (self-hosted)
├── Organization-specific formulas
├── Proprietary workflows
├── Internal deployment patterns
└── Enterprise compliance formulas
FEDERATED REGISTRY (HOP future)
├── Cross-organization discovery
├── Skill-based search
└── Attribution chain tracking
└── hop:// URI resolution
```
### URI Scheme
```
hop://molmall.gastown.io/formulas/mol-polecat-work@4.0.0
└──────────────────┘ └──────────────┘ └───┘
registry host formula name version
# Short forms
mol-polecat-work # Default registry, latest version
mol-polecat-work@4 # Major version
mol-polecat-work@4.0.0 # Exact version
@acme/mol-deploy # Scoped to publisher
hop://acme.corp/formulas/mol-deploy # Full HOP URI
```
### Registry API
```yaml
# OpenAPI-style specification
GET /formulas
# List all formulas
Query:
- q: string # Search query
- capabilities: string[] # Filter by capability tags
- author: string # Filter by author
- limit: int
- offset: int
Response:
formulas:
- name: mol-polecat-work
version: 4.0.0
description: "Full polecat work lifecycle..."
author: steve@gastown.io
downloads: 12543
capabilities: [go, testing, code-review]
GET /formulas/{name}
# Get formula metadata
Response:
name: mol-polecat-work
versions: [4.0.0, 3.2.1, 3.2.0, ...]
latest: 4.0.0
author: steve@gastown.io
repository: https://github.com/steveyegge/gastown
license: MIT
capabilities:
primary: [go, testing]
secondary: [git, code-review]
stats:
downloads: 12543
stars: 234
used_by: 89 # towns using this formula
GET /formulas/{name}/{version}
# Get specific version
Response:
name: mol-polecat-work
version: 4.0.0
checksum: sha256:abc123...
signature: <optional PGP signature>
content: <base64 or URL to .formula.toml>
changelog: "Added self-cleaning model..."
published_at: 2026-01-10T00:00:00Z
POST /formulas
# Publish formula (authenticated)
Body:
name: mol-my-workflow
version: 1.0.0
content: <formula TOML>
changelog: "Initial release"
Auth: Bearer token (linked to HOP identity)
GET /formulas/{name}/{version}/download
# Download formula content
Response: raw .formula.toml content
```
## Formula Package Format
### Simple Case: Single File
Most formulas are single `.formula.toml` files:
```bash
gt formula install mol-polecat-code-review
# Downloads mol-polecat-code-review.formula.toml to ~/gt/.beads/formulas/
```
### Complex Case: Formula Bundle
Some formulas need supporting files (scripts, templates, configs):
```
mol-deploy-k8s.formula.bundle/
├── formula.toml # Main formula
├── templates/
│ ├── deployment.yaml.tmpl
│ └── service.yaml.tmpl
├── scripts/
│ └── healthcheck.sh
└── README.md
```
Bundle format:
```bash
# Bundles are tarballs
mol-deploy-k8s-1.0.0.bundle.tar.gz
```
Installation:
```bash
gt formula install mol-deploy-k8s
# Extracts to ~/gt/.beads/formulas/mol-deploy-k8s/
# formula.toml is at mol-deploy-k8s/formula.toml
```
## Installation Flow
### Basic Install
```bash
$ gt formula install mol-polecat-code-review
Resolving mol-polecat-code-review...
Registry: molmall.gastown.io
Version: 1.2.0 (latest)
Author: steve@gastown.io
Skills: code-review, security
Downloading... ████████████████████ 100%
Verifying checksum... ✓
Installed to: ~/gt/.beads/formulas/mol-polecat-code-review.formula.toml
```
### Version Pinning
```bash
$ gt formula install mol-polecat-work@4.0.0
Installing mol-polecat-work@4.0.0 (pinned)...
✓ Installed
$ gt formula list --installed
mol-polecat-work 4.0.0 [pinned]
mol-polecat-code-review 1.2.0 [latest]
```
### Upgrade Flow
```bash
$ gt formula upgrade mol-polecat-code-review
Checking for updates...
Current: 1.2.0
Latest: 1.3.0
Changelog for 1.3.0:
- Added security focus option
- Improved test coverage step
Upgrade? [y/N] y
Downloading... ✓
Installed: mol-polecat-code-review@1.3.0
```
### Lock File
```json
// ~/gt/.beads/formulas/.lock.json
{
"version": 1,
"formulas": {
"mol-polecat-work": {
"version": "4.0.0",
"pinned": true,
"checksum": "sha256:abc123...",
"installed_at": "2026-01-10T00:00:00Z",
"source": "hop://molmall.gastown.io/formulas/mol-polecat-work@4.0.0"
},
"mol-polecat-code-review": {
"version": "1.3.0",
"pinned": false,
"checksum": "sha256:def456...",
"installed_at": "2026-01-10T12:00:00Z",
"source": "hop://molmall.gastown.io/formulas/mol-polecat-code-review@1.3.0"
}
}
}
```
## Publishing Flow
### First-Time Setup
```bash
$ gt formula publish --init
Setting up Mol Mall publishing...
1. Create account at https://molmall.gastown.io/signup
2. Generate API token at https://molmall.gastown.io/settings/tokens
3. Run: gt formula login
$ gt formula login
Token: ********
Logged in as: steve@gastown.io
```
### Publishing
```bash
$ gt formula publish mol-polecat-work
Publishing mol-polecat-work...
Pre-flight checks:
✓ formula.toml is valid
✓ Version 4.0.0 not yet published
✓ Required fields present (name, version, description)
✓ Skills declared
Publish to molmall.gastown.io? [y/N] y
Uploading... ✓
Published: hop://molmall.gastown.io/formulas/mol-polecat-work@4.0.0
View at: https://molmall.gastown.io/formulas/mol-polecat-work
```
### Verification Levels
```
┌─────────────────────────────────────────────────────────────────┐
│ FORMULA TRUST LEVELS │
└─────────────────────────────────────────────────────────────────┘
UNVERIFIED (default)
Anyone can publish
Basic validation only
Displayed with ⚠️ warning
VERIFIED PUBLISHER
Publisher identity confirmed
Displayed with ✓ checkmark
Higher search ranking
OFFICIAL
Maintained by Gas Town team
Displayed with 🏛️ badge
Included in embedded defaults
AUDITED
Security review completed
Displayed with 🔒 badge
Required for enterprise registries
```
## Capability Tagging
### Formula Capability Declaration
```toml
[formula.capabilities]
# What capabilities does this formula exercise? Used for agent routing.
primary = ["go", "testing", "code-review"]
secondary = ["git", "ci-cd"]
# Capability weights (optional, for fine-grained routing)
[formula.capabilities.weights]
go = 0.3 # 30% of formula work is Go
testing = 0.4 # 40% is testing
code-review = 0.3 # 30% is code review
```
### Capability-Based Search
```bash
$ gt formula search --capabilities="security,go"
Formulas matching capabilities: security, go
mol-security-audit v2.1.0 ⭐ 4.8 📥 8,234
Capabilities: security, go, code-review
"Comprehensive security audit workflow"
mol-dependency-scan v1.0.0 ⭐ 4.2 📥 3,102
Capabilities: security, go, supply-chain
"Scan Go dependencies for vulnerabilities"
```
### Agent Accountability
When a polecat completes a formula, the execution is tracked:
```
Polecat: beads/amber
Formula: mol-polecat-code-review@1.3.0
Completed: 2026-01-10T15:30:00Z
Capabilities exercised:
- code-review (primary)
- security (secondary)
- go (secondary)
```
This execution record enables:
1. **Routing** - Agents with successful track records get similar work
2. **Debugging** - Trace which agent did what, when
3. **Quality metrics** - Track success rates by agent and formula
## Private Registries
### Enterprise Deployment
```yaml
# ~/.gtconfig.yaml
registries:
- name: acme
url: https://molmall.acme.corp
auth: token
priority: 1 # Check first
- name: public
url: https://molmall.gastown.io
auth: none
priority: 2 # Fallback
```
### Self-Hosted Registry
```bash
# Docker deployment
docker run -d \
-p 8080:8080 \
-v /data/formulas:/formulas \
-e AUTH_PROVIDER=oidc \
gastown/molmall-registry:latest
# Configuration
MOLMALL_STORAGE=s3://bucket/formulas
MOLMALL_AUTH=oidc
MOLMALL_OIDC_ISSUER=https://auth.acme.corp
```
## Federation
Federation enables formula sharing across organizations using the Highway Operations Protocol (HOP).
### Cross-Registry Discovery
```bash
$ gt formula search "deploy kubernetes" --federated
Searching across federated registries...
molmall.gastown.io:
mol-deploy-k8s v3.0.0 🏛️ Official
molmall.acme.corp:
@acme/mol-deploy-k8s v2.1.0 ✓ Verified
molmall.bigco.io:
@bigco/k8s-workflow v1.0.0 ⚠️ Unverified
```
### HOP URI Resolution
The `hop://` URI scheme provides cross-registry entity references:
```bash
# Full HOP URI
gt formula install hop://molmall.acme.corp/formulas/@acme/mol-deploy@2.1.0
# Resolution via HOP (Highway Operations Protocol)
1. Parse hop:// URI
2. Resolve registry endpoint (DNS/HOP discovery)
3. Authenticate (if required)
4. Download formula
5. Verify checksum/signature
6. Install to town-level
```
## Implementation Phases
### Phase 1: Local Commands (Now)
- `gt formula list` with tier display
- `gt formula show --resolve`
- Formula resolution order (project → town → system)
### Phase 2: Manual Sharing
- Formula export/import
- `gt formula export mol-polecat-work > mol-polecat-work.formula.toml`
- `gt formula import < mol-polecat-work.formula.toml`
- Lock file format
### Phase 3: Public Registry
- molmall.gastown.io launch
- `gt formula install` from registry
- `gt formula publish` flow
- Basic search and browse
### Phase 4: Enterprise Features
- Private registry support
- Authentication integration
- Verification levels
- Audit logging
### Phase 5: Federation (HOP)
- Capability tags in schema
- Federation protocol (Highway Operations Protocol)
- Cross-registry search
- Agent execution tracking for accountability
## Related Documents
- [Formula Resolution](formula-resolution.md) - Local resolution order
- [molecules.md](molecules.md) - Formula lifecycle (cook, pour, squash)
- [understanding-gas-town.md](../../../docs/understanding-gas-town.md) - Gas Town architecture

291
internal/cmd/ready.go Normal file
View File

@@ -0,0 +1,291 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"sort"
"strings"
"sync"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/workspace"
)
var readyJSON bool
var readyRig string
var readyCmd = &cobra.Command{
Use: "ready",
GroupID: GroupWork,
Short: "Show work ready across town",
Long: `Display all ready work items across the town and all rigs.
Aggregates ready issues from:
- Town beads (hq-* items: convoys, cross-rig coordination)
- Each rig's beads (project-level issues, MRs)
Ready items have no blockers and can be worked immediately.
Results are sorted by priority (highest first) then by source.
Examples:
gt ready # Show all ready work
gt ready --json # Output as JSON
gt ready --rig=gastown # Show only one rig`,
RunE: runReady,
}
func init() {
readyCmd.Flags().BoolVar(&readyJSON, "json", false, "Output as JSON")
readyCmd.Flags().StringVar(&readyRig, "rig", "", "Filter to a specific rig")
rootCmd.AddCommand(readyCmd)
}
// ReadySource represents ready items from a single source (town or rig).
type ReadySource struct {
Name string `json:"name"` // "town" or rig name
Issues []*beads.Issue `json:"issues"` // Ready issues from this source
Error string `json:"error,omitempty"`
}
// ReadyResult is the aggregated result of gt ready.
type ReadyResult struct {
Sources []ReadySource `json:"sources"`
Summary ReadySummary `json:"summary"`
TownRoot string `json:"town_root,omitempty"`
}
// ReadySummary provides counts for the ready report.
type ReadySummary struct {
Total int `json:"total"`
BySource map[string]int `json:"by_source"`
P0Count int `json:"p0_count"`
P1Count int `json:"p1_count"`
P2Count int `json:"p2_count"`
P3Count int `json:"p3_count"`
P4Count int `json:"p4_count"`
}
func runReady(cmd *cobra.Command, args []string) error {
// Find town root
townRoot, err := workspace.FindFromCwdOrError()
if err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
// Load rigs config
rigsConfigPath := constants.MayorRigsPath(townRoot)
rigsConfig, err := config.LoadRigsConfig(rigsConfigPath)
if err != nil {
rigsConfig = &config.RigsConfig{Rigs: make(map[string]config.RigEntry)}
}
// Create rig manager and discover rigs
g := git.NewGit(townRoot)
mgr := rig.NewManager(townRoot, rigsConfig, g)
rigs, err := mgr.DiscoverRigs()
if err != nil {
return fmt.Errorf("discovering rigs: %w", err)
}
// Filter rigs if --rig flag provided
if readyRig != "" {
var filtered []*rig.Rig
for _, r := range rigs {
if r.Name == readyRig {
filtered = append(filtered, r)
break
}
}
if len(filtered) == 0 {
return fmt.Errorf("rig not found: %s", readyRig)
}
rigs = filtered
}
// Collect results from all sources in parallel
var wg sync.WaitGroup
var mu sync.Mutex
sources := make([]ReadySource, 0, len(rigs)+1)
// Fetch town beads (only if not filtering to a specific rig)
if readyRig == "" {
wg.Add(1)
go func() {
defer wg.Done()
townBeadsPath := beads.GetTownBeadsPath(townRoot)
townBeads := beads.New(townBeadsPath)
issues, err := townBeads.Ready()
mu.Lock()
defer mu.Unlock()
src := ReadySource{Name: "town"}
if err != nil {
src.Error = err.Error()
} else {
src.Issues = issues
}
sources = append(sources, src)
}()
}
// Fetch from each rig in parallel
for _, r := range rigs {
wg.Add(1)
go func(r *rig.Rig) {
defer wg.Done()
// Use mayor/rig path where rig-level beads are stored
rigBeadsPath := constants.RigMayorPath(r.Path)
rigBeads := beads.New(rigBeadsPath)
issues, err := rigBeads.Ready()
mu.Lock()
defer mu.Unlock()
src := ReadySource{Name: r.Name}
if err != nil {
src.Error = err.Error()
} else {
src.Issues = issues
}
sources = append(sources, src)
}(r)
}
wg.Wait()
// Sort sources: town first, then rigs alphabetically
sort.Slice(sources, func(i, j int) bool {
if sources[i].Name == "town" {
return true
}
if sources[j].Name == "town" {
return false
}
return sources[i].Name < sources[j].Name
})
// Sort issues within each source by priority (lower number = higher priority)
for i := range sources {
sort.Slice(sources[i].Issues, func(a, b int) bool {
return sources[i].Issues[a].Priority < sources[i].Issues[b].Priority
})
}
// Build summary
summary := ReadySummary{
BySource: make(map[string]int),
}
for _, src := range sources {
count := len(src.Issues)
summary.Total += count
summary.BySource[src.Name] = count
for _, issue := range src.Issues {
switch issue.Priority {
case 0:
summary.P0Count++
case 1:
summary.P1Count++
case 2:
summary.P2Count++
case 3:
summary.P3Count++
case 4:
summary.P4Count++
}
}
}
result := ReadyResult{
Sources: sources,
Summary: summary,
TownRoot: townRoot,
}
// Output
if readyJSON {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(result)
}
return printReadyHuman(result)
}
func printReadyHuman(result ReadyResult) error {
if result.Summary.Total == 0 {
fmt.Println("No ready work across town.")
return nil
}
fmt.Printf("%s Ready work across town:\n\n", style.Bold.Render("📋"))
for _, src := range result.Sources {
if src.Error != "" {
fmt.Printf("%s %s\n", style.Dim.Render(src.Name+"/"), style.Warning.Render("(error: "+src.Error+")"))
continue
}
count := len(src.Issues)
if count == 0 {
fmt.Printf("%s %s\n", style.Dim.Render(src.Name+"/"), style.Dim.Render("(none)"))
continue
}
fmt.Printf("%s (%d items)\n", style.Bold.Render(src.Name+"/"), count)
for _, issue := range src.Issues {
priorityStr := fmt.Sprintf("P%d", issue.Priority)
var priorityStyled string
switch issue.Priority {
case 0:
priorityStyled = style.Error.Render(priorityStr) // P0 is critical
case 1:
priorityStyled = style.Error.Render(priorityStr)
case 2:
priorityStyled = style.Warning.Render(priorityStr)
default:
priorityStyled = style.Dim.Render(priorityStr)
}
// Truncate title if too long
title := issue.Title
if len(title) > 60 {
title = title[:57] + "..."
}
fmt.Printf(" [%s] %s %s\n", priorityStyled, style.Dim.Render(issue.ID), title)
}
fmt.Println()
}
// Summary line
parts := []string{}
if result.Summary.P0Count > 0 {
parts = append(parts, fmt.Sprintf("%d P0", result.Summary.P0Count))
}
if result.Summary.P1Count > 0 {
parts = append(parts, fmt.Sprintf("%d P1", result.Summary.P1Count))
}
if result.Summary.P2Count > 0 {
parts = append(parts, fmt.Sprintf("%d P2", result.Summary.P2Count))
}
if result.Summary.P3Count > 0 {
parts = append(parts, fmt.Sprintf("%d P3", result.Summary.P3Count))
}
if result.Summary.P4Count > 0 {
parts = append(parts, fmt.Sprintf("%d P4", result.Summary.P4Count))
}
if len(parts) > 0 {
fmt.Printf("Total: %d items ready (%s)\n", result.Summary.Total, strings.Join(parts, ", "))
} else {
fmt.Printf("Total: %d items ready\n", result.Summary.Total)
}
return nil
}