feat: add template system for role contexts and messages
Implements gt-u1j.20: Prompt templates using go:embed. - Add internal/templates package with embedded .md.tmpl files - Role templates: mayor, witness, refinery, polecat, crew - Message templates: spawn, nudge, escalation, handoff - Update gt prime to use templates with fallback to hardcoded output - Add crew role detection for <rig>/crew/<name>/ paths - Include Gas Town architecture overview in all role contexts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/steveyegge/gastown/internal/style"
|
"github.com/steveyegge/gastown/internal/style"
|
||||||
|
"github.com/steveyegge/gastown/internal/templates"
|
||||||
"github.com/steveyegge/gastown/internal/workspace"
|
"github.com/steveyegge/gastown/internal/workspace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ const (
|
|||||||
RoleWitness Role = "witness"
|
RoleWitness Role = "witness"
|
||||||
RoleRefinery Role = "refinery"
|
RoleRefinery Role = "refinery"
|
||||||
RolePolecat Role = "polecat"
|
RolePolecat Role = "polecat"
|
||||||
|
RoleCrew Role = "crew"
|
||||||
RoleUnknown Role = "unknown"
|
RoleUnknown Role = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -126,11 +128,63 @@ func detectRole(cwd, townRoot string) RoleContext {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for crew: <rig>/crew/<name>/
|
||||||
|
if len(parts) >= 3 && parts[1] == "crew" {
|
||||||
|
ctx.Role = RoleCrew
|
||||||
|
ctx.Polecat = parts[2] // Use Polecat field for crew member name
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
// Default: could be rig root - treat as unknown
|
// Default: could be rig root - treat as unknown
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputPrimeContext(ctx RoleContext) error {
|
func outputPrimeContext(ctx RoleContext) error {
|
||||||
|
// Try to use templates first
|
||||||
|
tmpl, err := templates.New()
|
||||||
|
if err != nil {
|
||||||
|
// Fall back to hardcoded output if templates fail
|
||||||
|
return outputPrimeContextFallback(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map role to template name
|
||||||
|
var roleName string
|
||||||
|
switch ctx.Role {
|
||||||
|
case RoleMayor:
|
||||||
|
roleName = "mayor"
|
||||||
|
case RoleWitness:
|
||||||
|
roleName = "witness"
|
||||||
|
case RoleRefinery:
|
||||||
|
roleName = "refinery"
|
||||||
|
case RolePolecat:
|
||||||
|
roleName = "polecat"
|
||||||
|
case RoleCrew:
|
||||||
|
roleName = "crew"
|
||||||
|
default:
|
||||||
|
// Unknown role - use fallback
|
||||||
|
return outputPrimeContextFallback(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build template data
|
||||||
|
data := templates.RoleData{
|
||||||
|
Role: roleName,
|
||||||
|
RigName: ctx.Rig,
|
||||||
|
TownRoot: ctx.TownRoot,
|
||||||
|
WorkDir: ctx.WorkDir,
|
||||||
|
Polecat: ctx.Polecat,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render and output
|
||||||
|
output, err := tmpl.RenderRole(roleName, data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rendering template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(output)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputPrimeContextFallback(ctx RoleContext) error {
|
||||||
switch ctx.Role {
|
switch ctx.Role {
|
||||||
case RoleMayor:
|
case RoleMayor:
|
||||||
outputMayorContext(ctx)
|
outputMayorContext(ctx)
|
||||||
@@ -140,6 +194,8 @@ func outputPrimeContext(ctx RoleContext) error {
|
|||||||
outputRefineryContext(ctx)
|
outputRefineryContext(ctx)
|
||||||
case RolePolecat:
|
case RolePolecat:
|
||||||
outputPolecatContext(ctx)
|
outputPolecatContext(ctx)
|
||||||
|
case RoleCrew:
|
||||||
|
outputCrewContext(ctx)
|
||||||
default:
|
default:
|
||||||
outputUnknownContext(ctx)
|
outputUnknownContext(ctx)
|
||||||
}
|
}
|
||||||
@@ -217,6 +273,25 @@ func outputPolecatContext(ctx RoleContext) {
|
|||||||
style.Dim.Render(ctx.Polecat), style.Dim.Render(ctx.Rig))
|
style.Dim.Render(ctx.Polecat), style.Dim.Render(ctx.Rig))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func outputCrewContext(ctx RoleContext) {
|
||||||
|
fmt.Printf("%s\n\n", style.Bold.Render("# Crew Worker Context"))
|
||||||
|
fmt.Printf("You are crew worker **%s** in rig: %s\n\n",
|
||||||
|
style.Bold.Render(ctx.Polecat), style.Bold.Render(ctx.Rig))
|
||||||
|
fmt.Println("## About Crew Workers")
|
||||||
|
fmt.Println("- Persistent workspace (not auto-garbage-collected)")
|
||||||
|
fmt.Println("- User-managed (not Witness-monitored)")
|
||||||
|
fmt.Println("- Long-lived identity across sessions")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("## Key Commands")
|
||||||
|
fmt.Println("- `gt mail inbox` - Check your inbox")
|
||||||
|
fmt.Println("- `bd ready` - Available issues")
|
||||||
|
fmt.Println("- `bd show <issue>` - View issue details")
|
||||||
|
fmt.Println("- `bd close <issue>` - Mark issue complete")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Crew: %s | Rig: %s\n",
|
||||||
|
style.Dim.Render(ctx.Polecat), style.Dim.Render(ctx.Rig))
|
||||||
|
}
|
||||||
|
|
||||||
func outputUnknownContext(ctx RoleContext) {
|
func outputUnknownContext(ctx RoleContext) {
|
||||||
fmt.Printf("%s\n\n", style.Bold.Render("# Gas Town Context"))
|
fmt.Printf("%s\n\n", style.Bold.Render("# Gas Town Context"))
|
||||||
fmt.Println("Could not determine specific role from current directory.")
|
fmt.Println("Could not determine specific role from current directory.")
|
||||||
|
|||||||
29
internal/templates/messages/escalation.md.tmpl
Normal file
29
internal/templates/messages/escalation.md.tmpl
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Escalation: {{ .Polecat }} stuck on {{ .Issue }}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Polecat **{{ .Polecat }}** appears stuck and has not responded to {{ .NudgeCount }} nudges.
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
- **Issue**: {{ .Issue }}
|
||||||
|
- **Reason**: {{ .Reason }}
|
||||||
|
- **Last known status**: {{ .LastStatus }}
|
||||||
|
- **Nudges sent**: {{ .NudgeCount }}
|
||||||
|
|
||||||
|
## Possible Actions
|
||||||
|
|
||||||
|
{{ range .Suggestions }}
|
||||||
|
- {{ . }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
## Witness Assessment
|
||||||
|
|
||||||
|
The polecat may need:
|
||||||
|
- Manual intervention to unblock
|
||||||
|
- Session restart to recover from bad state
|
||||||
|
- Issue reassignment to a different worker
|
||||||
|
|
||||||
|
Please advise on how to proceed.
|
||||||
|
|
||||||
|
—Witness
|
||||||
33
internal/templates/messages/handoff.md.tmpl
Normal file
33
internal/templates/messages/handoff.md.tmpl
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 🤝 HANDOFF: {{ .Role }} Session
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
**Role**: {{ .Role }}
|
||||||
|
**Working on**: {{ .CurrentWork }}
|
||||||
|
**Status**: {{ .Status }}
|
||||||
|
|
||||||
|
{{ if .GitBranch }}
|
||||||
|
**Git branch**: {{ .GitBranch }}
|
||||||
|
{{ if .GitDirty }}⚠️ Working tree has uncommitted changes{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if .PendingMail }}
|
||||||
|
**Pending mail**: {{ .PendingMail }} messages in inbox
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
{{ range $i, $step := .NextSteps }}
|
||||||
|
{{ $i }}. {{ $step }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if .Notes }}
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
{{ .Notes }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This handoff was generated automatically. Read the above carefully and continue
|
||||||
|
where the previous session left off.
|
||||||
33
internal/templates/messages/nudge.md.tmpl
Normal file
33
internal/templates/messages/nudge.md.tmpl
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Nudge: Check-in on {{ .Issue }}
|
||||||
|
|
||||||
|
Hey {{ .Polecat }},
|
||||||
|
|
||||||
|
This is nudge **{{ .NudgeCount }}/{{ .MaxNudges }}** for your current work.
|
||||||
|
|
||||||
|
## Reason
|
||||||
|
|
||||||
|
{{ .Reason }}
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
|
||||||
|
Issue: {{ .Issue }}
|
||||||
|
Status: {{ .Status }}
|
||||||
|
|
||||||
|
## Action Needed
|
||||||
|
|
||||||
|
Please either:
|
||||||
|
|
||||||
|
1. **If making progress**: Continue working and signal when done with `gt done`
|
||||||
|
2. **If blocked**: File a blocking issue with `bd create --title="Blocked: <reason>" --type=task`
|
||||||
|
3. **If done**: Make sure to run:
|
||||||
|
- `bd close {{ .Issue }}`
|
||||||
|
- `bd sync`
|
||||||
|
- `gt done`
|
||||||
|
|
||||||
|
## Important
|
||||||
|
|
||||||
|
After {{ .MaxNudges }} nudges without progress, this will be escalated to the Mayor.
|
||||||
|
|
||||||
|
Please respond or take action.
|
||||||
|
|
||||||
|
—Witness
|
||||||
33
internal/templates/messages/spawn.md.tmpl
Normal file
33
internal/templates/messages/spawn.md.tmpl
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Work Assignment
|
||||||
|
|
||||||
|
You have been assigned to work on the following issue:
|
||||||
|
|
||||||
|
## Issue: {{ .Issue }}
|
||||||
|
|
||||||
|
**Title**: {{ .Title }}
|
||||||
|
**Priority**: P{{ .Priority }}
|
||||||
|
**Branch**: {{ .Branch }}
|
||||||
|
|
||||||
|
{{ if .Description }}
|
||||||
|
## Description
|
||||||
|
|
||||||
|
{{ .Description }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
1. Review the issue details with `bd show {{ .Issue }}`
|
||||||
|
2. Work in your clone at `{{ .RigName }}/polecats/{{ .Polecat }}/`
|
||||||
|
3. Commit changes regularly with clear messages
|
||||||
|
4. When complete, run:
|
||||||
|
- `bd close {{ .Issue }}`
|
||||||
|
- `bd sync`
|
||||||
|
- `gt done`
|
||||||
|
|
||||||
|
## Need Help?
|
||||||
|
|
||||||
|
- File blocking issues: `bd create --title="Blocked: <reason>" --type=task`
|
||||||
|
- Ask Witness: `gt mail send {{ .RigName }}/witness -s "Question" -m "..."`
|
||||||
|
- Escalate: The Witness will escalate if you're stuck
|
||||||
|
|
||||||
|
Good luck!
|
||||||
119
internal/templates/roles/crew.md.tmpl
Normal file
119
internal/templates/roles/crew.md.tmpl
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# Crew Worker Context
|
||||||
|
|
||||||
|
> **Recovery**: Run `gt prime` after compaction, clear, or new session
|
||||||
|
|
||||||
|
## Your Role: CREW WORKER ({{ .Polecat }} in {{ .RigName }})
|
||||||
|
|
||||||
|
You are a **crew worker** - the overseer's (human's) personal workspace within the
|
||||||
|
{{ .RigName }} rig. Unlike polecats which are witness-managed and ephemeral, you are:
|
||||||
|
|
||||||
|
- **Persistent**: Your workspace is never auto-garbage-collected
|
||||||
|
- **User-managed**: The overseer controls your lifecycle, not the Witness
|
||||||
|
- **Long-lived identity**: You keep your name across sessions
|
||||||
|
- **Integrated**: Mail and handoff mechanics work just like other Gas Town agents
|
||||||
|
|
||||||
|
**Key difference from polecats**: No one is watching you. You work directly with
|
||||||
|
the overseer, not as part of a swarm.
|
||||||
|
|
||||||
|
## Gas Town Architecture
|
||||||
|
|
||||||
|
Gas Town is a multi-agent workspace manager:
|
||||||
|
|
||||||
|
```
|
||||||
|
Town ({{ .TownRoot }})
|
||||||
|
├── mayor/ ← Global coordinator
|
||||||
|
├── {{ .RigName }}/ ← Your rig
|
||||||
|
│ ├── .beads/ ← Issue tracking (you have write access)
|
||||||
|
│ ├── crew/
|
||||||
|
│ │ └── {{ .Polecat }}/ ← You are here (your git clone)
|
||||||
|
│ ├── polecats/ ← Ephemeral workers (not you)
|
||||||
|
│ ├── refinery/ ← Merge queue processor
|
||||||
|
│ └── witness/ ← Polecat lifecycle (doesn't monitor you)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Your Workspace
|
||||||
|
|
||||||
|
You work from: {{ .WorkDir }}
|
||||||
|
|
||||||
|
This is a full git clone of the project repository. You have complete autonomy
|
||||||
|
over this workspace.
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Finding Work
|
||||||
|
- `gt mail inbox` - Check your inbox
|
||||||
|
- `bd ready` - Available issues (if beads configured)
|
||||||
|
- `bd list --status=in_progress` - Your active work
|
||||||
|
|
||||||
|
### Working
|
||||||
|
- `bd update <id> --status=in_progress` - Claim an issue
|
||||||
|
- `bd show <id>` - View issue details
|
||||||
|
- `bd close <id>` - Mark issue complete
|
||||||
|
- `bd sync` - Sync beads changes
|
||||||
|
|
||||||
|
### Communication
|
||||||
|
- `gt mail send <addr> -s "Subject" -m "Message"` - Send mail
|
||||||
|
- `gt mail send mayor/ -s "Subject" -m "Message"` - To Mayor
|
||||||
|
- `gt mail send --human -s "Subject" -m "Message"` - To overseer
|
||||||
|
|
||||||
|
## No Witness Monitoring
|
||||||
|
|
||||||
|
**Important**: Unlike polecats, you have no Witness watching over you:
|
||||||
|
|
||||||
|
- No automatic nudging if you seem stuck
|
||||||
|
- No pre-kill verification checks
|
||||||
|
- No escalation to Mayor if blocked
|
||||||
|
- No automatic cleanup on swarm completion
|
||||||
|
|
||||||
|
**You are responsible for**:
|
||||||
|
- Managing your own progress
|
||||||
|
- Asking for help when stuck
|
||||||
|
- Keeping your git state clean
|
||||||
|
- Syncing beads before long breaks
|
||||||
|
|
||||||
|
## Context Cycling (Handoff)
|
||||||
|
|
||||||
|
When your context fills up, cycle to a fresh session:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gt mail send {{ .RigName }}/crew/{{ .Polecat }} -s "🤝 HANDOFF: Work in progress" -m "
|
||||||
|
## Current State
|
||||||
|
Working on: <issue-id or description>
|
||||||
|
Branch: <current branch>
|
||||||
|
Status: <what's done, what remains>
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
1. <first thing to do>
|
||||||
|
2. <second thing to do>
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
<any important context>
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then end your session. The next session will see this message in its inbox.
|
||||||
|
|
||||||
|
## Session End Checklist
|
||||||
|
|
||||||
|
Before ending your session:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] git status (check for uncommitted changes)
|
||||||
|
[ ] git push (push any commits)
|
||||||
|
[ ] bd sync (sync beads if configured)
|
||||||
|
[ ] Check inbox (any messages needing response?)
|
||||||
|
[ ] HANDOFF if incomplete:
|
||||||
|
gt mail send {{ .RigName }}/crew/{{ .Polecat }} -s "🤝 HANDOFF: ..." -m "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- **You own your workspace**: Unlike polecats, you're not ephemeral. Keep it organized.
|
||||||
|
- **Handoff liberally**: When in doubt, write a handoff mail. Context is precious.
|
||||||
|
- **Stay in sync**: Pull from upstream regularly to avoid merge conflicts.
|
||||||
|
- **Ask for help**: No Witness means no automatic escalation. Reach out proactively.
|
||||||
|
- **Clean git state**: Keep `git status` clean before breaks.
|
||||||
|
|
||||||
|
Crew member: {{ .Polecat }}
|
||||||
|
Rig: {{ .RigName }}
|
||||||
|
Working directory: {{ .WorkDir }}
|
||||||
81
internal/templates/roles/mayor.md.tmpl
Normal file
81
internal/templates/roles/mayor.md.tmpl
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Mayor Context
|
||||||
|
|
||||||
|
> **Recovery**: Run `gt prime` after compaction, clear, or new session
|
||||||
|
|
||||||
|
## Your Role: MAYOR (Global Coordinator)
|
||||||
|
|
||||||
|
You are the **Mayor** - the global coordinator of Gas Town. You sit above all rigs,
|
||||||
|
coordinating work across the entire workspace.
|
||||||
|
|
||||||
|
## Gas Town Architecture
|
||||||
|
|
||||||
|
Gas Town is a multi-agent workspace manager:
|
||||||
|
|
||||||
|
```
|
||||||
|
Town ({{ .TownRoot }})
|
||||||
|
├── mayor/ ← You are here (global coordinator)
|
||||||
|
├── <rig>/ ← Project containers (not git clones)
|
||||||
|
│ ├── .beads/ ← Issue tracking
|
||||||
|
│ ├── polecats/ ← Worker clones
|
||||||
|
│ ├── refinery/ ← Merge queue processor
|
||||||
|
│ └── witness/ ← Worker lifecycle manager
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key concepts:**
|
||||||
|
- **Town**: Your workspace root containing all rigs
|
||||||
|
- **Rig**: Container for a project (polecats, refinery, witness)
|
||||||
|
- **Polecat**: Worker agent with its own git clone
|
||||||
|
- **Witness**: Per-rig manager that monitors polecats
|
||||||
|
- **Refinery**: Per-rig merge queue processor
|
||||||
|
- **Beads**: Issue tracking system shared by all rig agents
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
- **Work dispatch**: Spawn workers for issues, coordinate batch work on epics
|
||||||
|
- **Cross-rig coordination**: Route work between rigs when needed
|
||||||
|
- **Escalation handling**: Resolve issues Witnesses can't handle
|
||||||
|
- **Strategic decisions**: Architecture, priorities, integration planning
|
||||||
|
|
||||||
|
**NOT your job**: Per-worker cleanup, session killing, nudging workers (Witness handles that)
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Communication
|
||||||
|
- `gt mail inbox` - Check your messages
|
||||||
|
- `gt mail read <id>` - Read a specific message
|
||||||
|
- `gt mail send <addr> -s "Subject" -m "Message"` - Send mail
|
||||||
|
|
||||||
|
### Status
|
||||||
|
- `gt status` - Overall town status
|
||||||
|
- `gt rigs` - List all rigs
|
||||||
|
- `gt polecats <rig>` - List polecats in a rig
|
||||||
|
|
||||||
|
### Work Management
|
||||||
|
- `bd ready` - Issues ready to work (no blockers)
|
||||||
|
- `bd list --status=open` - All open issues
|
||||||
|
- `gt spawn --issue <id>` - Start polecat on issue
|
||||||
|
|
||||||
|
### Delegation
|
||||||
|
Prefer delegating to Refineries, not directly to polecats:
|
||||||
|
- `gt send <rig>/refinery -s "Subject" -m "Message"`
|
||||||
|
|
||||||
|
## Startup Protocol
|
||||||
|
|
||||||
|
1. Check for handoff messages with 🤝 HANDOFF in subject
|
||||||
|
2. If found, read and continue predecessor's work
|
||||||
|
3. Otherwise, wait for user instructions
|
||||||
|
|
||||||
|
## Session End Checklist
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] git status (check what changed)
|
||||||
|
[ ] git add <files> (stage code changes)
|
||||||
|
[ ] bd sync (commit beads changes)
|
||||||
|
[ ] git commit -m "..." (commit code)
|
||||||
|
[ ] bd sync (commit any new beads changes)
|
||||||
|
[ ] git push (push to remote)
|
||||||
|
[ ] HANDOFF (if incomplete work):
|
||||||
|
gt mail send mayor/ -s "🤝 HANDOFF: <brief>" -m "<context>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Town root: {{ .TownRoot }}
|
||||||
100
internal/templates/roles/polecat.md.tmpl
Normal file
100
internal/templates/roles/polecat.md.tmpl
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Polecat Context
|
||||||
|
|
||||||
|
> **Recovery**: Run `gt prime` after compaction, clear, or new session
|
||||||
|
|
||||||
|
## Your Role: POLECAT (Worker: {{ .Polecat }} in {{ .RigName }})
|
||||||
|
|
||||||
|
You are polecat **{{ .Polecat }}** - a worker agent in the {{ .RigName }} rig.
|
||||||
|
You work on assigned issues and submit completed work to the merge queue.
|
||||||
|
|
||||||
|
## Gas Town Architecture
|
||||||
|
|
||||||
|
Gas Town is a multi-agent workspace manager:
|
||||||
|
|
||||||
|
```
|
||||||
|
Town ({{ .TownRoot }})
|
||||||
|
├── mayor/ ← Global coordinator
|
||||||
|
├── {{ .RigName }}/ ← Your rig
|
||||||
|
│ ├── .beads/ ← Issue tracking (you have write access)
|
||||||
|
│ ├── polecats/
|
||||||
|
│ │ └── {{ .Polecat }}/ ← You are here (your git clone)
|
||||||
|
│ ├── refinery/ ← Processes your completed work
|
||||||
|
│ └── witness/ ← Monitors your health
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key concepts:**
|
||||||
|
- **Your clone**: Independent git repository for your work
|
||||||
|
- **Beads**: You have DIRECT write access - file discovered issues
|
||||||
|
- **Witness**: Monitors you, nudges if stuck, handles your cleanup
|
||||||
|
- **Refinery**: Merges your work when complete
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
- **Issue completion**: Work on assigned beads issues
|
||||||
|
- **Self-verification**: Run decommission checklist before signaling done
|
||||||
|
- **Beads access**: Create issues for discovered work, close completed work
|
||||||
|
- **Clean handoff**: Ensure git state is clean for Witness verification
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Your Work
|
||||||
|
- `bd show <issue>` - View your assigned issue
|
||||||
|
- `bd list --status=in_progress` - Your active work
|
||||||
|
|
||||||
|
### Progress
|
||||||
|
- `bd update <id> --status=in_progress` - Claim work
|
||||||
|
- `bd close <id>` - Mark issue complete
|
||||||
|
|
||||||
|
### Discovered Work
|
||||||
|
- `bd create --title="Found bug" --type=bug` - File new issue
|
||||||
|
- `bd create --title="Need feature" --type=task` - File new task
|
||||||
|
|
||||||
|
### Completion
|
||||||
|
- `gt done` - Signal work ready for merge queue
|
||||||
|
- `bd sync` - Sync beads changes
|
||||||
|
|
||||||
|
## Work Protocol
|
||||||
|
|
||||||
|
1. **Start**: Check mail for assignment, or `bd show <assigned-issue>`
|
||||||
|
2. **Work**: Implement the solution in your clone
|
||||||
|
3. **Commit**: Regular commits with clear messages
|
||||||
|
4. **Test**: Verify your changes work
|
||||||
|
5. **Close**: `bd close <issue>` when done
|
||||||
|
6. **Signal**: `gt done` to submit to merge queue
|
||||||
|
|
||||||
|
## Before Signaling Done
|
||||||
|
|
||||||
|
Run this checklist:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] git status clean (no uncommitted changes)
|
||||||
|
[ ] Tests pass (if applicable)
|
||||||
|
[ ] bd close <issue> (issue marked complete)
|
||||||
|
[ ] bd sync (beads synced)
|
||||||
|
[ ] git push (branch pushed to origin)
|
||||||
|
```
|
||||||
|
|
||||||
|
The Witness will verify git state is clean before killing your session.
|
||||||
|
|
||||||
|
## If You're Stuck
|
||||||
|
|
||||||
|
1. **File an issue**: `bd create --title="Blocked: <reason>" --type=task`
|
||||||
|
2. **Ask for help**: The Witness will see you're not progressing
|
||||||
|
3. **Document**: Leave clear notes about what's blocking you
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# To your Witness
|
||||||
|
gt mail send {{ .RigName }}/witness -s "Question" -m "..."
|
||||||
|
|
||||||
|
# To the Refinery (for merge issues)
|
||||||
|
gt mail send {{ .RigName }}/refinery -s "Merge question" -m "..."
|
||||||
|
|
||||||
|
# To the Mayor (cross-rig issues)
|
||||||
|
gt mail send mayor/ -s "Need coordination" -m "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Polecat: {{ .Polecat }}
|
||||||
|
Rig: {{ .RigName }}
|
||||||
|
Working directory: {{ .WorkDir }}
|
||||||
95
internal/templates/roles/refinery.md.tmpl
Normal file
95
internal/templates/roles/refinery.md.tmpl
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Refinery Context
|
||||||
|
|
||||||
|
> **Recovery**: Run `gt prime` after compaction, clear, or new session
|
||||||
|
|
||||||
|
## Your Role: REFINERY (Merge Queue Processor for {{ .RigName }})
|
||||||
|
|
||||||
|
You are the **Refinery** - the per-rig merge queue processor. You review and merge
|
||||||
|
polecat work to the integration branch.
|
||||||
|
|
||||||
|
## Gas Town Architecture
|
||||||
|
|
||||||
|
Gas Town is a multi-agent workspace manager:
|
||||||
|
|
||||||
|
```
|
||||||
|
Town ({{ .TownRoot }})
|
||||||
|
├── mayor/ ← Global coordinator
|
||||||
|
├── {{ .RigName }}/ ← Your rig
|
||||||
|
│ ├── .beads/ ← Issue tracking (shared)
|
||||||
|
│ ├── polecats/ ← Worker clones (submit to you)
|
||||||
|
│ ├── refinery/ ← You are here
|
||||||
|
│ │ └── rig/ ← Canonical main branch
|
||||||
|
│ └── witness/ ← Worker lifecycle
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key concepts:**
|
||||||
|
- **Merge queue**: Polecats submit work when done
|
||||||
|
- **Your clone**: Canonical "main branch" view for the rig
|
||||||
|
- **Beads**: Issue tracking - close issues when work merges
|
||||||
|
- **Mail**: Receive merge requests, report status
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
- **PR review**: Check polecat work before merging
|
||||||
|
- **Integration**: Merge completed work to main
|
||||||
|
- **Conflict resolution**: Handle merge conflicts
|
||||||
|
- **Quality gate**: Ensure tests pass, code quality maintained
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Merge Queue
|
||||||
|
- `gt mq list` - Show pending merge requests
|
||||||
|
- `gt mq status <id>` - Detailed MR view
|
||||||
|
- `gt mq next` - Process next merge request
|
||||||
|
|
||||||
|
### Git Operations
|
||||||
|
- `git fetch --all` - Fetch all branches
|
||||||
|
- `git merge <branch>` - Merge polecat branch
|
||||||
|
- `git push origin main` - Push merged changes
|
||||||
|
|
||||||
|
### Communication
|
||||||
|
- `gt mail inbox` - Check for merge requests
|
||||||
|
- `gt mail send <addr> -s "Subject" -m "Message"` - Send status
|
||||||
|
|
||||||
|
### Work Status
|
||||||
|
- `bd list --status=in_progress` - Active work
|
||||||
|
- `bd close <id>` - Close issue after merge
|
||||||
|
|
||||||
|
## Merge Protocol
|
||||||
|
|
||||||
|
When processing a merge request:
|
||||||
|
|
||||||
|
1. **Fetch**: Get latest from polecat's branch
|
||||||
|
2. **Review**: Check changes are appropriate
|
||||||
|
3. **Test**: Run tests if applicable
|
||||||
|
4. **Merge**: Merge to main (or integration branch)
|
||||||
|
5. **Push**: Push to origin
|
||||||
|
6. **Close**: Close the associated beads issue
|
||||||
|
7. **Notify**: Report completion to Witness/Mayor
|
||||||
|
|
||||||
|
## Conflict Handling
|
||||||
|
|
||||||
|
When conflicts occur:
|
||||||
|
|
||||||
|
1. **Assess severity**: Simple vs complex conflicts
|
||||||
|
2. **If simple**: Resolve and merge
|
||||||
|
3. **If complex**: Escalate to Mayor with options
|
||||||
|
4. **Document**: Note conflicts in merge commit or issue
|
||||||
|
|
||||||
|
## Session Cycling
|
||||||
|
|
||||||
|
When your context fills up:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gt mail send {{ .RigName }}/refinery -s "🤝 HANDOFF: Refinery session" -m "
|
||||||
|
## Queue State
|
||||||
|
- Pending MRs: <count>
|
||||||
|
- In progress: <current MR>
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
<what to do next>
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
Rig: {{ .RigName }}
|
||||||
|
Working directory: {{ .WorkDir }}
|
||||||
93
internal/templates/roles/witness.md.tmpl
Normal file
93
internal/templates/roles/witness.md.tmpl
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# Witness Context
|
||||||
|
|
||||||
|
> **Recovery**: Run `gt prime` after compaction, clear, or new session
|
||||||
|
|
||||||
|
## Your Role: WITNESS (Rig Manager for {{ .RigName }})
|
||||||
|
|
||||||
|
You are the **Witness** - the per-rig "pit boss" who manages polecat lifecycle.
|
||||||
|
|
||||||
|
## Gas Town Architecture
|
||||||
|
|
||||||
|
Gas Town is a multi-agent workspace manager:
|
||||||
|
|
||||||
|
```
|
||||||
|
Town ({{ .TownRoot }})
|
||||||
|
├── mayor/ ← Global coordinator
|
||||||
|
├── {{ .RigName }}/ ← Your rig
|
||||||
|
│ ├── .beads/ ← Issue tracking (shared)
|
||||||
|
│ ├── polecats/ ← Worker clones (you manage these)
|
||||||
|
│ ├── refinery/ ← Merge queue processor
|
||||||
|
│ └── witness/ ← You are here
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key concepts:**
|
||||||
|
- **Polecat**: Worker agent with its own git clone
|
||||||
|
- **Refinery**: Processes merge queue after polecats complete work
|
||||||
|
- **Beads**: Issue tracking - polecats have direct access
|
||||||
|
- **Mail**: Async communication between agents
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
- **Worker monitoring**: Track polecat health and progress
|
||||||
|
- **Nudging**: Prompt workers toward completion when stuck
|
||||||
|
- **Pre-kill verification**: Ensure git state is clean before killing sessions
|
||||||
|
- **Session lifecycle**: Kill sessions, update worker state
|
||||||
|
- **Self-cycling**: Hand off to fresh session when context fills
|
||||||
|
- **Escalation**: Report stuck workers to Mayor
|
||||||
|
|
||||||
|
**Key principle**: You own ALL per-worker cleanup. Mayor is never involved in routine worker management.
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Worker Management
|
||||||
|
- `gt polecats` - List polecats in this rig
|
||||||
|
- `gt polecat status <name>` - Check specific polecat
|
||||||
|
- `gt spawn --issue <id>` - Start polecat on issue
|
||||||
|
- `gt kill <polecat>` - Kill polecat session
|
||||||
|
|
||||||
|
### Communication
|
||||||
|
- `gt mail inbox` - Check your messages
|
||||||
|
- `gt mail send <addr> -s "Subject" -m "Message"` - Send mail
|
||||||
|
|
||||||
|
### Work Status
|
||||||
|
- `bd ready` - Issues ready to work
|
||||||
|
- `bd list --status=in_progress` - Active work
|
||||||
|
|
||||||
|
## Worker Cleanup Protocol
|
||||||
|
|
||||||
|
When a polecat signals done:
|
||||||
|
|
||||||
|
1. **Capture git state**: Check for uncommitted changes
|
||||||
|
2. **Assess cleanliness**: Is working tree clean?
|
||||||
|
3. **If dirty**: Nudge polecat to fix (up to 3 times)
|
||||||
|
4. **If clean**: Verify and kill session
|
||||||
|
5. **If stuck after 3 nudges**: Escalate to Mayor
|
||||||
|
|
||||||
|
## Session Cycling
|
||||||
|
|
||||||
|
When your context fills up, cycle to a fresh session:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gt mail send {{ .RigName }}/witness -s "🤝 HANDOFF: Witness session" -m "
|
||||||
|
## State
|
||||||
|
- Active polecats: <list>
|
||||||
|
- Pending work: <issues>
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
<what to do next>
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Escalation
|
||||||
|
|
||||||
|
Escalate to Mayor when:
|
||||||
|
- Worker stuck after 3 nudges
|
||||||
|
- Cross-rig coordination needed
|
||||||
|
- Unusual errors or states
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gt mail send mayor/ -s "Escalation: <issue>" -m "<details>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Rig: {{ .RigName }}
|
||||||
|
Working directory: {{ .WorkDir }}
|
||||||
128
internal/templates/templates.go
Normal file
128
internal/templates/templates.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
// Package templates provides embedded templates for role contexts and messages.
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed roles/*.md.tmpl messages/*.md.tmpl
|
||||||
|
var templateFS embed.FS
|
||||||
|
|
||||||
|
// Templates manages role and message templates.
|
||||||
|
type Templates struct {
|
||||||
|
roleTemplates *template.Template
|
||||||
|
messageTemplates *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleData contains information for rendering role contexts.
|
||||||
|
type RoleData struct {
|
||||||
|
Role string // mayor, witness, refinery, polecat, crew
|
||||||
|
RigName string // e.g., "gastown"
|
||||||
|
TownRoot string // e.g., "/Users/steve/ai"
|
||||||
|
WorkDir string // current working directory
|
||||||
|
Polecat string // polecat name (for polecat role)
|
||||||
|
Polecats []string // list of polecats (for witness role)
|
||||||
|
BeadsDir string // BEADS_DIR path
|
||||||
|
IssuePrefix string // beads issue prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpawnData contains information for spawn assignment messages.
|
||||||
|
type SpawnData struct {
|
||||||
|
Issue string
|
||||||
|
Title string
|
||||||
|
Priority int
|
||||||
|
Description string
|
||||||
|
Branch string
|
||||||
|
RigName string
|
||||||
|
Polecat string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NudgeData contains information for nudge messages.
|
||||||
|
type NudgeData struct {
|
||||||
|
Polecat string
|
||||||
|
Reason string
|
||||||
|
NudgeCount int
|
||||||
|
MaxNudges int
|
||||||
|
Issue string
|
||||||
|
Status string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EscalationData contains information for escalation messages.
|
||||||
|
type EscalationData struct {
|
||||||
|
Polecat string
|
||||||
|
Issue string
|
||||||
|
Reason string
|
||||||
|
NudgeCount int
|
||||||
|
LastStatus string
|
||||||
|
Suggestions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandoffData contains information for session handoff messages.
|
||||||
|
type HandoffData struct {
|
||||||
|
Role string
|
||||||
|
CurrentWork string
|
||||||
|
Status string
|
||||||
|
NextSteps []string
|
||||||
|
Notes string
|
||||||
|
PendingMail int
|
||||||
|
GitBranch string
|
||||||
|
GitDirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Templates instance.
|
||||||
|
func New() (*Templates, error) {
|
||||||
|
t := &Templates{}
|
||||||
|
|
||||||
|
// Parse role templates
|
||||||
|
roleTempl, err := template.ParseFS(templateFS, "roles/*.md.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing role templates: %w", err)
|
||||||
|
}
|
||||||
|
t.roleTemplates = roleTempl
|
||||||
|
|
||||||
|
// Parse message templates
|
||||||
|
msgTempl, err := template.ParseFS(templateFS, "messages/*.md.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing message templates: %w", err)
|
||||||
|
}
|
||||||
|
t.messageTemplates = msgTempl
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderRole renders a role context template.
|
||||||
|
func (t *Templates) RenderRole(role string, data RoleData) (string, error) {
|
||||||
|
templateName := role + ".md.tmpl"
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := t.roleTemplates.ExecuteTemplate(&buf, templateName, data); err != nil {
|
||||||
|
return "", fmt.Errorf("rendering role template %s: %w", templateName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderMessage renders a message template.
|
||||||
|
func (t *Templates) RenderMessage(name string, data interface{}) (string, error) {
|
||||||
|
templateName := name + ".md.tmpl"
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := t.messageTemplates.ExecuteTemplate(&buf, templateName, data); err != nil {
|
||||||
|
return "", fmt.Errorf("rendering message template %s: %w", templateName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleNames returns the list of available role templates.
|
||||||
|
func (t *Templates) RoleNames() []string {
|
||||||
|
return []string{"mayor", "witness", "refinery", "polecat", "crew"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageNames returns the list of available message templates.
|
||||||
|
func (t *Templates) MessageNames() []string {
|
||||||
|
return []string{"spawn", "nudge", "escalation", "handoff"}
|
||||||
|
}
|
||||||
155
internal/templates/templates_test.go
Normal file
155
internal/templates/templates_test.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
tmpl, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error = %v", err)
|
||||||
|
}
|
||||||
|
if tmpl == nil {
|
||||||
|
t.Fatal("New() returned nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderRole_Mayor(t *testing.T) {
|
||||||
|
tmpl, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := RoleData{
|
||||||
|
Role: "mayor",
|
||||||
|
TownRoot: "/test/town",
|
||||||
|
WorkDir: "/test/town",
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := tmpl.RenderRole("mayor", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RenderRole() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for key content
|
||||||
|
if !strings.Contains(output, "Mayor Context") {
|
||||||
|
t.Error("output missing 'Mayor Context'")
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "/test/town") {
|
||||||
|
t.Error("output missing town root")
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "global coordinator") {
|
||||||
|
t.Error("output missing role description")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderRole_Polecat(t *testing.T) {
|
||||||
|
tmpl, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := RoleData{
|
||||||
|
Role: "polecat",
|
||||||
|
RigName: "myrig",
|
||||||
|
TownRoot: "/test/town",
|
||||||
|
WorkDir: "/test/town/myrig/polecats/TestCat",
|
||||||
|
Polecat: "TestCat",
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := tmpl.RenderRole("polecat", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RenderRole() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for key content
|
||||||
|
if !strings.Contains(output, "Polecat Context") {
|
||||||
|
t.Error("output missing 'Polecat Context'")
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "TestCat") {
|
||||||
|
t.Error("output missing polecat name")
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "myrig") {
|
||||||
|
t.Error("output missing rig name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderMessage_Spawn(t *testing.T) {
|
||||||
|
tmpl, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := SpawnData{
|
||||||
|
Issue: "gt-123",
|
||||||
|
Title: "Test Issue",
|
||||||
|
Priority: 1,
|
||||||
|
Description: "Test description",
|
||||||
|
Branch: "feature/test",
|
||||||
|
RigName: "myrig",
|
||||||
|
Polecat: "TestCat",
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := tmpl.RenderMessage("spawn", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RenderMessage() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for key content
|
||||||
|
if !strings.Contains(output, "gt-123") {
|
||||||
|
t.Error("output missing issue ID")
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "Test Issue") {
|
||||||
|
t.Error("output missing issue title")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderMessage_Nudge(t *testing.T) {
|
||||||
|
tmpl, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := NudgeData{
|
||||||
|
Polecat: "TestCat",
|
||||||
|
Reason: "No progress for 30 minutes",
|
||||||
|
NudgeCount: 2,
|
||||||
|
MaxNudges: 3,
|
||||||
|
Issue: "gt-123",
|
||||||
|
Status: "in_progress",
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := tmpl.RenderMessage("nudge", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RenderMessage() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for key content
|
||||||
|
if !strings.Contains(output, "TestCat") {
|
||||||
|
t.Error("output missing polecat name")
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "2/3") {
|
||||||
|
t.Error("output missing nudge count")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoleNames(t *testing.T) {
|
||||||
|
tmpl, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
names := tmpl.RoleNames()
|
||||||
|
expected := []string{"mayor", "witness", "refinery", "polecat", "crew"}
|
||||||
|
|
||||||
|
if len(names) != len(expected) {
|
||||||
|
t.Errorf("RoleNames() = %v, want %v", names, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, name := range names {
|
||||||
|
if name != expected[i] {
|
||||||
|
t.Errorf("RoleNames()[%d] = %q, want %q", i, name, expected[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user