feat: implement priming subsystem improvements

Phase 1 of dynamic priming subsystem:

1. PRIME.md provisioning for all workers (hq-5z76w, hq-ukjrr Part A)
   - Added ProvisionPrimeMD to beads package with Gas Town context template
   - Provision at rig level in AddRig() so all workers inherit it
   - Added fallback provisioning in crew and polecat managers
   - Created PRIME.md for existing rigs

2. Post-handoff detection to prevent handoff loop bug (hq-ukjrr Part B)
   - Added FileHandoffMarker constant (.runtime/handoff_to_successor)
   - gt handoff writes marker before respawn
   - gt prime detects marker and outputs "HANDOFF COMPLETE" warning
   - Marker cleared after detection to prevent duplicate warnings

3. Priming health checks for gt doctor (hq-5scnt)
   - New priming_check.go validates priming subsystem configuration
   - Checks: SessionStart hook, gt prime command, PRIME.md presence
   - Warns if CLAUDE.md is too large (should be bootstrap pointer)
   - Fixable: provisions missing PRIME.md files

This ensures crew workers get Gas Town context (GUPP, hooks, propulsion)
even if the gt prime hook fails, via bd prime fallback.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
mayor
2026-01-09 23:56:38 -08:00
committed by Steve Yegge
parent 7533fed55e
commit db353c247b
10 changed files with 567 additions and 7 deletions

View File

@@ -161,6 +161,9 @@ func runDoctor(cmd *cobra.Command, args []string) error {
d.Register(doctor.NewLegacyGastownCheck())
d.Register(doctor.NewClaudeSettingsCheck())
// Priming subsystem check
d.Register(doctor.NewPrimingCheck())
// Crew workspace checks
d.Register(doctor.NewCrewStateCheck())
d.Register(doctor.NewCrewWorktreeCheck())

View File

@@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/events"
"github.com/steveyegge/gastown/internal/session"
"github.com/steveyegge/gastown/internal/style"
@@ -192,6 +193,16 @@ func runHandoff(cmd *cobra.Command, args []string) error {
style.PrintWarning("could not clear history: %v", err)
}
// Write handoff marker for successor detection (prevents handoff loop bug).
// The marker is cleared by gt prime after it outputs the warning.
// This tells the new session "you're post-handoff, don't re-run /handoff"
if cwd, err := os.Getwd(); err == nil {
runtimeDir := filepath.Join(cwd, constants.DirRuntime)
_ = os.MkdirAll(runtimeDir, 0755)
markerPath := filepath.Join(runtimeDir, constants.FileHandoffMarker)
_ = os.WriteFile(markerPath, []byte(currentSession), 0644)
}
// Use exec to respawn the pane - this kills us and restarts
return t.RespawnPane(pane, restartCmd)
}

View File

@@ -83,10 +83,6 @@ func init() {
type RoleContext = RoleInfo
func runPrime(cmd *cobra.Command, args []string) error {
if !state.IsEnabled() {
return nil
}
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("getting current directory: %w", err)
@@ -96,7 +92,17 @@ func runPrime(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("finding workspace: %w", err)
}
// "Discover, Don't Track" principle:
// - If we're in a workspace, proceed - the workspace's existence IS the enable signal
// - If we're NOT in a workspace, check the global enabled state
// This ensures a missing/stale state file doesn't break workspace users
if townRoot == "" {
// Not in a workspace - check global enabled state
// (This matters for hooks that might run from random directories)
if !state.IsEnabled() {
return nil // Silent exit - not in workspace and not enabled
}
return fmt.Errorf("not in a Gas Town workspace")
}
@@ -117,6 +123,9 @@ func runPrime(cmd *cobra.Command, args []string) error {
}
}
// Check for handoff marker (prevents handoff loop bug)
checkHandoffMarker(cwd)
// Get role using env-aware detection
roleInfo, err := GetRoleWithContext(cwd, townRoot)
if err != nil {
@@ -1632,3 +1641,38 @@ func readSessionFile(dir string) string {
}
return ""
}
// checkHandoffMarker checks for a handoff marker file and outputs a warning if found.
// This prevents the "handoff loop" bug where a new session sees /handoff in context
// and incorrectly runs it again. The marker tells the new session: "handoff is DONE,
// the /handoff you see in context was from YOUR PREDECESSOR, not a request for you."
func checkHandoffMarker(workDir string) {
markerPath := filepath.Join(workDir, constants.DirRuntime, constants.FileHandoffMarker)
data, err := os.ReadFile(markerPath)
if err != nil {
// No marker = not post-handoff, normal startup
return
}
// Marker found - this is a post-handoff session
prevSession := strings.TrimSpace(string(data))
// Remove the marker FIRST so we don't warn twice
_ = os.Remove(markerPath)
// Output prominent warning
fmt.Println()
fmt.Println(style.Bold.Render("╔══════════════════════════════════════════════════════════════════╗"))
fmt.Println(style.Bold.Render("║ ✅ HANDOFF COMPLETE - You are the NEW session ║"))
fmt.Println(style.Bold.Render("╚══════════════════════════════════════════════════════════════════╝"))
fmt.Println()
if prevSession != "" {
fmt.Printf("Your predecessor (%s) handed off to you.\n", prevSession)
}
fmt.Println()
fmt.Println(style.Bold.Render("⚠️ DO NOT run /handoff - that was your predecessor's action."))
fmt.Println(" The /handoff you see in context is NOT a request for you.")
fmt.Println()
fmt.Println("Instead: Check your hook (`gt mol status`) and mail (`gt mail inbox`).")
fmt.Println()
}