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:
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user