Previously GT_ROOT was documented as a formula search path but never actually set, making $GT_ROOT/.beads/formulas/ unreachable for agents. Now BuildStartupCommand automatically sets GT_ROOT to the town root, enabling all agents (witness, refinery, polecat, crew, etc.) to find town-level formulas without relying on cwd-relative paths. Also adds a doctor check (gt-root-env) that warns when existing sessions are missing GT_ROOT, with instructions to restart sessions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
120 lines
2.9 KiB
Go
120 lines
2.9 KiB
Go
package doctor
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/steveyegge/gastown/internal/tmux"
|
|
)
|
|
|
|
// GTRootCheck verifies that tmux sessions have GT_ROOT set.
|
|
// Sessions without GT_ROOT cannot find town-level formulas.
|
|
type GTRootCheck struct {
|
|
BaseCheck
|
|
tmux TmuxEnvGetter // nil means use real tmux
|
|
}
|
|
|
|
// TmuxEnvGetter abstracts tmux environment access for testing.
|
|
type TmuxEnvGetter interface {
|
|
ListSessions() ([]string, error)
|
|
GetEnvironment(session, key string) (string, error)
|
|
}
|
|
|
|
// realTmux wraps real tmux operations.
|
|
type realTmux struct {
|
|
t *tmux.Tmux
|
|
}
|
|
|
|
func (r *realTmux) ListSessions() ([]string, error) {
|
|
return r.t.ListSessions()
|
|
}
|
|
|
|
func (r *realTmux) GetEnvironment(session, key string) (string, error) {
|
|
return r.t.GetEnvironment(session, key)
|
|
}
|
|
|
|
// NewGTRootCheck creates a new GT_ROOT check.
|
|
func NewGTRootCheck() *GTRootCheck {
|
|
return >RootCheck{
|
|
BaseCheck: BaseCheck{
|
|
CheckName: "gt-root-env",
|
|
CheckDescription: "Verify sessions have GT_ROOT set for formula discovery",
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewGTRootCheckWithTmux creates a check with a custom tmux interface (for testing).
|
|
func NewGTRootCheckWithTmux(t TmuxEnvGetter) *GTRootCheck {
|
|
c := NewGTRootCheck()
|
|
c.tmux = t
|
|
return c
|
|
}
|
|
|
|
// Run checks GT_ROOT environment variable for all Gas Town sessions.
|
|
func (c *GTRootCheck) Run(ctx *CheckContext) *CheckResult {
|
|
t := c.tmux
|
|
if t == nil {
|
|
t = &realTmux{t: tmux.NewTmux()}
|
|
}
|
|
|
|
sessions, err := t.ListSessions()
|
|
if err != nil {
|
|
// No tmux server - not an error, Gas Town might just be down
|
|
return &CheckResult{
|
|
Name: c.Name(),
|
|
Status: StatusOK,
|
|
Message: "No tmux sessions running",
|
|
}
|
|
}
|
|
|
|
// Filter to Gas Town sessions (gt-* and hq-*)
|
|
var gtSessions []string
|
|
for _, sess := range sessions {
|
|
if strings.HasPrefix(sess, "gt-") || strings.HasPrefix(sess, "hq-") {
|
|
gtSessions = append(gtSessions, sess)
|
|
}
|
|
}
|
|
|
|
if len(gtSessions) == 0 {
|
|
return &CheckResult{
|
|
Name: c.Name(),
|
|
Status: StatusOK,
|
|
Message: "No Gas Town sessions running",
|
|
}
|
|
}
|
|
|
|
var missingSessions []string
|
|
var okCount int
|
|
|
|
for _, sess := range gtSessions {
|
|
gtRoot, err := t.GetEnvironment(sess, "GT_ROOT")
|
|
if err != nil || gtRoot == "" {
|
|
missingSessions = append(missingSessions, sess)
|
|
} else {
|
|
okCount++
|
|
}
|
|
}
|
|
|
|
if len(missingSessions) == 0 {
|
|
return &CheckResult{
|
|
Name: c.Name(),
|
|
Status: StatusOK,
|
|
Message: fmt.Sprintf("All %d session(s) have GT_ROOT set", okCount),
|
|
}
|
|
}
|
|
|
|
details := make([]string, 0, len(missingSessions)+2)
|
|
for _, sess := range missingSessions {
|
|
details = append(details, fmt.Sprintf("Missing GT_ROOT: %s", sess))
|
|
}
|
|
details = append(details, "", "Sessions without GT_ROOT cannot find town-level formulas.")
|
|
|
|
return &CheckResult{
|
|
Name: c.Name(),
|
|
Status: StatusWarning,
|
|
Message: fmt.Sprintf("%d session(s) missing GT_ROOT environment variable", len(missingSessions)),
|
|
Details: details,
|
|
FixHint: "Restart sessions to pick up GT_ROOT: gt shutdown && gt up",
|
|
}
|
|
}
|