diff --git a/.gitignore b/.gitignore index 0a67d375..9d99ed3a 100644 --- a/.gitignore +++ b/.gitignore @@ -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) diff --git a/docs/design/convoy-lifecycle.md b/docs/design/convoy-lifecycle.md new file mode 100644 index 00000000..89d7cf23 --- /dev/null +++ b/docs/design/convoy-lifecycle.md @@ -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 + ↓ +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 [--reason=] [--notify=] +``` + +- 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 + +# Dry-run mode +gt convoy check --dry-run +``` + +### New: `gt convoy reopen` + +```bash +gt convoy reopen +``` + +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 diff --git a/dog-pool-architecture.md b/docs/design/dog-pool-architecture.md similarity index 100% rename from dog-pool-architecture.md rename to docs/design/dog-pool-architecture.md diff --git a/docs/formula-resolution.md b/docs/formula-resolution.md new file mode 100644 index 00000000..f20dd1b5 --- /dev/null +++ b/docs/formula-resolution.md @@ -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: /.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 # Formula details +bd cook # 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] + +# 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 diff --git a/docs/mol-mall-design.md b/docs/mol-mall-design.md new file mode 100644 index 00000000..e3a30586 --- /dev/null +++ b/docs/mol-mall-design.md @@ -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: + content: + 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: + 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 diff --git a/internal/cmd/ready.go b/internal/cmd/ready.go new file mode 100644 index 00000000..e590eb2d --- /dev/null +++ b/internal/cmd/ready.go @@ -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 +}