From cd109e9db7d5f82a74a9d012428cbc500db46a1f Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Thu, 25 Dec 2025 02:25:43 -0800 Subject: [PATCH] Delete legacy Go-defined molecules (gt-ingm.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Molecules are now defined as formula files in .beads/formulas/ and cooked into proto beads via `bd cook`. This removes: - molecules_patrol.go (695 lines) - molecules_session.go (544 lines) - molecules_work.go (444 lines) - builtin_molecules_test.go - christmas_ornament_test.go Updates: - builtin_molecules.go: stub deprecated functions - install.go: remove molecule seeding (formulas are cooked on-demand) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/beads/builtin_molecules.go | 74 +-- internal/beads/builtin_molecules_test.go | 414 ------------- internal/beads/christmas_ornament_test.go | 545 ----------------- internal/beads/molecules_patrol.go | 695 ---------------------- internal/beads/molecules_session.go | 544 ----------------- internal/beads/molecules_work.go | 444 -------------- internal/cmd/install.go | 15 - 7 files changed, 12 insertions(+), 2719 deletions(-) delete mode 100644 internal/beads/builtin_molecules_test.go delete mode 100644 internal/beads/christmas_ornament_test.go delete mode 100644 internal/beads/molecules_patrol.go delete mode 100644 internal/beads/molecules_session.go delete mode 100644 internal/beads/molecules_work.go diff --git a/internal/beads/builtin_molecules.go b/internal/beads/builtin_molecules.go index f8d20705..73b4967f 100644 --- a/internal/beads/builtin_molecules.go +++ b/internal/beads/builtin_molecules.go @@ -2,6 +2,9 @@ package beads // BuiltinMolecule defines a built-in molecule template. +// Deprecated: Molecules are now defined as formula files in .beads/formulas/ +// and cooked into proto beads via `bd cook`. This type remains for backward +// compatibility but is no longer used. type BuiltinMolecule struct { ID string // Well-known ID (e.g., "mol-engineer-in-box") Title string @@ -9,70 +12,17 @@ type BuiltinMolecule struct { } // BuiltinMolecules returns all built-in molecule definitions. -// Molecules are defined in separate files by category: -// - molecules_work.go: EngineerInBox, QuickFix, Research, PolecatWork, ReadyWork -// - molecules_patrol.go: DeaconPatrol, WitnessPatrol, RefineryPatrol -// - molecules_session.go: CrewSession, PolecatSession, Bootstrap, VersionBump, InstallGoBinary +// Deprecated: Molecules are now defined as formula files (.beads/formulas/*.formula.json) +// and cooked into proto beads via `bd cook`. This function returns an empty slice. +// Use `bd cook` to create proto beads from formulas instead. func BuiltinMolecules() []BuiltinMolecule { - return []BuiltinMolecule{ - // Work molecules - EngineerInBoxMolecule(), - QuickFixMolecule(), - ResearchMolecule(), - PolecatWorkMolecule(), - ReadyWorkMolecule(), - - // Patrol molecules - DeaconPatrolMolecule(), - RefineryPatrolMolecule(), - WitnessPatrolMolecule(), - PolecatArmMolecule(), - - // Session and utility molecules - CrewSessionMolecule(), - PolecatSessionMolecule(), - BootstrapGasTownMolecule(), - VersionBumpMolecule(), - InstallGoBinaryMolecule(), - } + return []BuiltinMolecule{} } -// SeedBuiltinMolecules creates all built-in molecules in the beads database. -// It skips molecules that already exist (by title match). -// Returns the number of molecules created. +// SeedBuiltinMolecules is deprecated and does nothing. +// Molecules are now created by cooking formula files with `bd cook`. +// This function remains for backward compatibility with existing installations. func (b *Beads) SeedBuiltinMolecules() (int, error) { - molecules := BuiltinMolecules() - created := 0 - - // Get existing molecules to avoid duplicates - existing, err := b.List(ListOptions{Type: "molecule", Priority: -1}) - if err != nil { - return 0, err - } - - // Build map of existing molecule titles - existingTitles := make(map[string]bool) - for _, issue := range existing { - existingTitles[issue.Title] = true - } - - // Create each molecule if it doesn't exist - for _, mol := range molecules { - if existingTitles[mol.Title] { - continue // Already exists - } - - _, err := b.Create(CreateOptions{ - Title: mol.Title, - Type: "molecule", - Priority: 2, // Medium priority - Description: mol.Description, - }) - if err != nil { - return created, err - } - created++ - } - - return created, nil + // No-op: formulas are cooked on-demand, not seeded at install time + return 0, nil } diff --git a/internal/beads/builtin_molecules_test.go b/internal/beads/builtin_molecules_test.go deleted file mode 100644 index 71d49c45..00000000 --- a/internal/beads/builtin_molecules_test.go +++ /dev/null @@ -1,414 +0,0 @@ -package beads - -import "testing" - -func TestBuiltinMolecules(t *testing.T) { - molecules := BuiltinMolecules() - - if len(molecules) != 14 { - t.Errorf("expected 14 built-in molecules, got %d", len(molecules)) - } - - // Verify each molecule can be parsed and validated - for _, mol := range molecules { - t.Run(mol.Title, func(t *testing.T) { - // Check required fields - if mol.ID == "" { - t.Error("molecule missing ID") - } - if mol.Title == "" { - t.Error("molecule missing Title") - } - if mol.Description == "" { - t.Error("molecule missing Description") - } - - // Parse the molecule steps - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse molecule steps: %v", err) - } - - if len(steps) == 0 { - t.Error("molecule has no steps") - } - - // Validate the molecule as if it were an issue - issue := &Issue{ - Type: "molecule", - Title: mol.Title, - Description: mol.Description, - } - - if err := ValidateMolecule(issue); err != nil { - t.Errorf("molecule validation failed: %v", err) - } - }) - } -} - -func TestEngineerInBoxMolecule(t *testing.T) { - mol := EngineerInBoxMolecule() - - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - // Should have 5 steps: design, implement, review, test, submit - if len(steps) != 5 { - t.Errorf("expected 5 steps, got %d", len(steps)) - } - - // Verify step refs - expectedRefs := []string{"design", "implement", "review", "test", "submit"} - for i, expected := range expectedRefs { - if steps[i].Ref != expected { - t.Errorf("step %d: expected ref %q, got %q", i, expected, steps[i].Ref) - } - } - - // Verify dependencies - // design has no deps - if len(steps[0].Needs) != 0 { - t.Errorf("design should have no deps, got %v", steps[0].Needs) - } - - // implement needs design - if len(steps[1].Needs) != 1 || steps[1].Needs[0] != "design" { - t.Errorf("implement should need design, got %v", steps[1].Needs) - } - - // review needs implement - if len(steps[2].Needs) != 1 || steps[2].Needs[0] != "implement" { - t.Errorf("review should need implement, got %v", steps[2].Needs) - } - - // test needs implement - if len(steps[3].Needs) != 1 || steps[3].Needs[0] != "implement" { - t.Errorf("test should need implement, got %v", steps[3].Needs) - } - - // submit needs review and test - if len(steps[4].Needs) != 2 { - t.Errorf("submit should need 2 deps, got %v", steps[4].Needs) - } -} - -func TestQuickFixMolecule(t *testing.T) { - mol := QuickFixMolecule() - - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - // Should have 3 steps: implement, test, submit - if len(steps) != 3 { - t.Errorf("expected 3 steps, got %d", len(steps)) - } - - expectedRefs := []string{"implement", "test", "submit"} - for i, expected := range expectedRefs { - if steps[i].Ref != expected { - t.Errorf("step %d: expected ref %q, got %q", i, expected, steps[i].Ref) - } - } -} - -func TestResearchMolecule(t *testing.T) { - mol := ResearchMolecule() - - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - // Should have 2 steps: investigate, document - if len(steps) != 2 { - t.Errorf("expected 2 steps, got %d", len(steps)) - } - - expectedRefs := []string{"investigate", "document"} - for i, expected := range expectedRefs { - if steps[i].Ref != expected { - t.Errorf("step %d: expected ref %q, got %q", i, expected, steps[i].Ref) - } - } - - // document needs investigate - if len(steps[1].Needs) != 1 || steps[1].Needs[0] != "investigate" { - t.Errorf("document should need investigate, got %v", steps[1].Needs) - } -} - -func TestInstallGoBinaryMolecule(t *testing.T) { - mol := InstallGoBinaryMolecule() - - if mol.ID != "mol-install-go-binary" { - t.Errorf("expected ID 'mol-install-go-binary', got %q", mol.ID) - } - - if mol.Title != "Install Go Binary" { - t.Errorf("expected Title 'Install Go Binary', got %q", mol.Title) - } - - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - // Should have 1 step: install - if len(steps) != 1 { - t.Errorf("expected 1 step, got %d", len(steps)) - } - - if steps[0].Ref != "install" { - t.Errorf("expected ref 'install', got %q", steps[0].Ref) - } - - // install has no deps - if len(steps[0].Needs) != 0 { - t.Errorf("install should have no deps, got %v", steps[0].Needs) - } -} - -func TestPolecatWorkMolecule(t *testing.T) { - mol := PolecatWorkMolecule() - - if mol.ID != "mol-polecat-work" { - t.Errorf("expected ID 'mol-polecat-work', got %q", mol.ID) - } - - if mol.Title != "Polecat Work" { - t.Errorf("expected Title 'Polecat Work', got %q", mol.Title) - } - - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - // Should have 8 steps: load-context, implement, self-review, verify-tests, - // rebase-main, submit-merge, exit-decision, request-shutdown - if len(steps) != 8 { - t.Errorf("expected 8 steps, got %d", len(steps)) - } - - expectedRefs := []string{ - "load-context", "implement", "self-review", "verify-tests", - "rebase-main", "submit-merge", "exit-decision", "request-shutdown", - } - for i, expected := range expectedRefs { - if i >= len(steps) { - t.Errorf("missing step %d: expected %q", i, expected) - continue - } - if steps[i].Ref != expected { - t.Errorf("step %d: expected ref %q, got %q", i, expected, steps[i].Ref) - } - } - - // Verify key dependencies - // load-context has no deps - if len(steps[0].Needs) != 0 { - t.Errorf("load-context should have no deps, got %v", steps[0].Needs) - } - - // implement needs load-context - if len(steps[1].Needs) != 1 || steps[1].Needs[0] != "load-context" { - t.Errorf("implement should need load-context, got %v", steps[1].Needs) - } - - // rebase-main needs self-review and verify-tests - if len(steps[4].Needs) != 2 { - t.Errorf("rebase-main should need 2 deps, got %v", steps[4].Needs) - } - - // request-shutdown needs exit-decision - if len(steps[7].Needs) != 1 || steps[7].Needs[0] != "exit-decision" { - t.Errorf("request-shutdown should need exit-decision, got %v", steps[7].Needs) - } -} - -func TestDeaconPatrolMolecule(t *testing.T) { - mol := DeaconPatrolMolecule() - - if mol.ID != "mol-deacon-patrol" { - t.Errorf("expected ID 'mol-deacon-patrol', got %q", mol.ID) - } - - if mol.Title != "Deacon Patrol" { - t.Errorf("expected Title 'Deacon Patrol', got %q", mol.Title) - } - - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - // Should have 8 steps: inbox-check, trigger-pending-spawns, health-scan, plugin-run, - // orphan-check, session-gc, context-check, loop-or-exit - if len(steps) != 8 { - t.Errorf("expected 8 steps, got %d", len(steps)) - } - - expectedRefs := []string{ - "inbox-check", "trigger-pending-spawns", "health-scan", "plugin-run", - "orphan-check", "session-gc", "context-check", "loop-or-exit", - } - for i, expected := range expectedRefs { - if i >= len(steps) { - t.Errorf("missing step %d: expected %q", i, expected) - continue - } - if steps[i].Ref != expected { - t.Errorf("step %d: expected ref %q, got %q", i, expected, steps[i].Ref) - } - } - - // Verify key dependencies - // inbox-check has no deps (first step) - if len(steps[0].Needs) != 0 { - t.Errorf("inbox-check should have no deps, got %v", steps[0].Needs) - } - - // trigger-pending-spawns needs inbox-check - if len(steps[1].Needs) != 1 || steps[1].Needs[0] != "inbox-check" { - t.Errorf("trigger-pending-spawns should need inbox-check, got %v", steps[1].Needs) - } - - // loop-or-exit needs context-check - if len(steps[7].Needs) != 1 || steps[7].Needs[0] != "context-check" { - t.Errorf("loop-or-exit should need context-check, got %v", steps[7].Needs) - } -} - -func TestWitnessPatrolMolecule(t *testing.T) { - mol := WitnessPatrolMolecule() - - if mol.ID != "mol-witness-patrol" { - t.Errorf("expected ID 'mol-witness-patrol', got %q", mol.ID) - } - - if mol.Title != "Witness Patrol" { - t.Errorf("expected Title 'Witness Patrol', got %q", mol.Title) - } - - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - // Should have 9 steps using Christmas Ornament pattern: - // PREFLIGHT: inbox-check, check-refinery, load-state - // DISCOVERY: survey-workers (bonds mol-polecat-arm dynamically) - // CLEANUP: aggregate, save-state, generate-summary, context-check, burn-or-loop - if len(steps) != 9 { - t.Errorf("expected 9 steps, got %d", len(steps)) - } - - expectedRefs := []string{ - "inbox-check", "check-refinery", "load-state", "survey-workers", - "aggregate", "save-state", "generate-summary", "context-check", "burn-or-loop", - } - for i, expected := range expectedRefs { - if i >= len(steps) { - t.Errorf("missing step %d: expected %q", i, expected) - continue - } - if steps[i].Ref != expected { - t.Errorf("step %d: expected ref %q, got %q", i, expected, steps[i].Ref) - } - } - - // Verify key dependencies - // inbox-check has no deps (first step) - if len(steps[0].Needs) != 0 { - t.Errorf("inbox-check should have no deps, got %v", steps[0].Needs) - } - - // check-refinery needs inbox-check - if len(steps[1].Needs) != 1 || steps[1].Needs[0] != "inbox-check" { - t.Errorf("check-refinery should need inbox-check, got %v", steps[1].Needs) - } - - // load-state needs check-refinery - if len(steps[2].Needs) != 1 || steps[2].Needs[0] != "check-refinery" { - t.Errorf("load-state should need check-refinery, got %v", steps[2].Needs) - } - - // aggregate needs survey-workers (fanout gate) - if len(steps[4].Needs) != 1 || steps[4].Needs[0] != "survey-workers" { - t.Errorf("aggregate should need survey-workers, got %v", steps[4].Needs) - } - - // aggregate should have WaitsFor: all-children - if len(steps[4].WaitsFor) != 1 || steps[4].WaitsFor[0] != "all-children" { - t.Errorf("aggregate should WaitsFor all-children, got %v", steps[4].WaitsFor) - } - - // burn-or-loop needs context-check - if len(steps[8].Needs) != 1 || steps[8].Needs[0] != "context-check" { - t.Errorf("burn-or-loop should need context-check, got %v", steps[8].Needs) - } -} - -func TestPolecatArmMolecule(t *testing.T) { - mol := PolecatArmMolecule() - - if mol.ID != "mol-polecat-arm" { - t.Errorf("expected ID 'mol-polecat-arm', got %q", mol.ID) - } - - if mol.Title != "Polecat Arm" { - t.Errorf("expected Title 'Polecat Arm', got %q", mol.Title) - } - - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - // Should have 5 steps: capture, assess, load-history, decide, execute - if len(steps) != 5 { - t.Errorf("expected 5 steps, got %d", len(steps)) - } - - expectedRefs := []string{"capture", "assess", "load-history", "decide", "execute"} - for i, expected := range expectedRefs { - if i >= len(steps) { - t.Errorf("missing step %d: expected %q", i, expected) - continue - } - if steps[i].Ref != expected { - t.Errorf("step %d: expected ref %q, got %q", i, expected, steps[i].Ref) - } - } - - // Verify dependencies form a chain - // capture has no deps (first step) - if len(steps[0].Needs) != 0 { - t.Errorf("capture should have no deps, got %v", steps[0].Needs) - } - - // assess needs capture - if len(steps[1].Needs) != 1 || steps[1].Needs[0] != "capture" { - t.Errorf("assess should need capture, got %v", steps[1].Needs) - } - - // load-history needs assess - if len(steps[2].Needs) != 1 || steps[2].Needs[0] != "assess" { - t.Errorf("load-history should need assess, got %v", steps[2].Needs) - } - - // decide needs load-history - if len(steps[3].Needs) != 1 || steps[3].Needs[0] != "load-history" { - t.Errorf("decide should need load-history, got %v", steps[3].Needs) - } - - // execute needs decide - if len(steps[4].Needs) != 1 || steps[4].Needs[0] != "decide" { - t.Errorf("execute should need decide, got %v", steps[4].Needs) - } -} diff --git a/internal/beads/christmas_ornament_test.go b/internal/beads/christmas_ornament_test.go deleted file mode 100644 index 02350ae6..00000000 --- a/internal/beads/christmas_ornament_test.go +++ /dev/null @@ -1,545 +0,0 @@ -package beads - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -// TestChristmasOrnamentPattern tests the dynamic bonding pattern used by mol-witness-patrol. -// This pattern allows a parent molecule step to dynamically create child molecules -// at runtime, with a fanout gate (WaitsFor: all-children) for aggregation. -func TestChristmasOrnamentPattern(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test in short mode") - } - - // Find beads repo - workDir := findBeadsDir(t) - if workDir == "" { - t.Skip("no .beads directory found") - } - - b := New(workDir) - - // Test 1: Verify mol-witness-patrol has correct structure - t.Run("WitnessPatrolStructure", func(t *testing.T) { - mol := WitnessPatrolMolecule() - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse mol-witness-patrol: %v", err) - } - - // Find aggregate step - var aggregateStep *MoleculeStep - for i := range steps { - if steps[i].Ref == "aggregate" { - aggregateStep = &steps[i] - break - } - } - - if aggregateStep == nil { - t.Fatal("aggregate step not found in mol-witness-patrol") - } - - // Verify WaitsFor: all-children - hasAllChildren := false - for _, cond := range aggregateStep.WaitsFor { - if strings.ToLower(cond) == "all-children" { - hasAllChildren = true - break - } - } - if !hasAllChildren { - t.Errorf("aggregate step should have WaitsFor: all-children, got %v", aggregateStep.WaitsFor) - } - - // Verify aggregate needs survey-workers - needsSurvey := false - for _, dep := range aggregateStep.Needs { - if dep == "survey-workers" { - needsSurvey = true - break - } - } - if !needsSurvey { - t.Errorf("aggregate step should need survey-workers, got %v", aggregateStep.Needs) - } - }) - - // Test 2: Verify mol-polecat-arm has correct structure - t.Run("PolecatArmStructure", func(t *testing.T) { - mol := PolecatArmMolecule() - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse mol-polecat-arm: %v", err) - } - - // Should have 5 steps in order: capture, assess, load-history, decide, execute - expectedRefs := []string{"capture", "assess", "load-history", "decide", "execute"} - if len(steps) != len(expectedRefs) { - t.Errorf("expected %d steps, got %d", len(expectedRefs), len(steps)) - } - - for i, expected := range expectedRefs { - if i < len(steps) && steps[i].Ref != expected { - t.Errorf("step %d: expected %q, got %q", i, expected, steps[i].Ref) - } - } - - // Verify template variables are present in description - if !strings.Contains(mol.Description, "{{polecat_name}}") { - t.Error("mol-polecat-arm should have {{polecat_name}} template variable") - } - if !strings.Contains(mol.Description, "{{rig}}") { - t.Error("mol-polecat-arm should have {{rig}} template variable") - } - }) - - // Test 3: Template variable expansion - t.Run("TemplateVariableExpansion", func(t *testing.T) { - mol := PolecatArmMolecule() - - ctx := map[string]string{ - "polecat_name": "toast", - "rig": "gastown", - } - - expanded := ExpandTemplateVars(mol.Description, ctx) - - // Template variables should be expanded - if strings.Contains(expanded, "{{polecat_name}}") { - t.Error("{{polecat_name}} was not expanded") - } - if strings.Contains(expanded, "{{rig}}") { - t.Error("{{rig}} was not expanded") - } - - // Values should be present - if !strings.Contains(expanded, "toast") { - t.Error("polecat_name value 'toast' not found in expanded description") - } - if !strings.Contains(expanded, "gastown") { - t.Error("rig value 'gastown' not found in expanded description") - } - }) - - // Test 4: Create parent and verify bonding metadata parsing - t.Run("BondingMetadataParsing", func(t *testing.T) { - // Create a test issue with bonding metadata (simulating what mol bond creates) - bondingDesc := `Polecat Arm (arm-toast) - ---- -bonded_from: mol-polecat-arm -bonded_to: patrol-x7k -bonded_ref: arm-toast -bonded_at: 2025-12-23T10:00:00Z -` - // Verify we can parse the bonding metadata - if !strings.Contains(bondingDesc, "bonded_from:") { - t.Error("bonding metadata should contain bonded_from") - } - if !strings.Contains(bondingDesc, "bonded_to:") { - t.Error("bonding metadata should contain bonded_to") - } - if !strings.Contains(bondingDesc, "bonded_ref:") { - t.Error("bonding metadata should contain bonded_ref") - } - }) - - // Test 5: Verify issue creation with parent relationship works - t.Run("ParentChildRelationship", func(t *testing.T) { - // Create a test parent issue - parent, err := b.Create(CreateOptions{ - Title: "Test Patrol Parent", - Type: "task", - Priority: 2, - Description: "Test parent for Christmas Ornament pattern", - }) - if err != nil { - t.Fatalf("failed to create parent issue: %v", err) - } - defer func() { - _ = b.Close(parent.ID) - }() - - // Create a child issue under the parent - child, err := b.Create(CreateOptions{ - Title: "Test Polecat Arm", - Type: "task", - Priority: parent.Priority, - Parent: parent.ID, - Description: "Test child for bonding pattern", - }) - if err != nil { - t.Fatalf("failed to create child issue: %v", err) - } - defer func() { - _ = b.Close(child.ID) - }() - - // Verify parent-child relationship exists - // The child should have a dependency on the parent - t.Logf("Created parent %s and child %s", parent.ID, child.ID) - }) -} - -// TestEmptyPatrol tests the scenario where witness patrol runs with 0 polecats. -func TestEmptyPatrol(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test in short mode") - } - - mol := WitnessPatrolMolecule() - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse mol-witness-patrol: %v", err) - } - - // Find survey-workers step - var surveyStep *MoleculeStep - for i := range steps { - if steps[i].Ref == "survey-workers" { - surveyStep = &steps[i] - break - } - } - - if surveyStep == nil { - t.Fatal("survey-workers step not found") - } - - // Verify the step description mentions handling 0 polecats - if !strings.Contains(surveyStep.Instructions, "no polecats") && - !strings.Contains(surveyStep.Instructions, "If no polecats") { - t.Log("Note: survey-workers step should document handling of 0 polecats case") - } - - // With 0 polecats: - // - survey-workers bonds no children - // - aggregate step with WaitsFor: all-children should complete immediately - // This is correct behavior - an empty set of children is vacuously complete -} - -// TestNudgeProgression tests the nudge matrix logic documented in mol-polecat-arm. -func TestNudgeProgression(t *testing.T) { - mol := PolecatArmMolecule() - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse mol-polecat-arm: %v", err) - } - - // Find decide step (contains the nudge matrix) - var decideStep *MoleculeStep - for i := range steps { - if steps[i].Ref == "decide" { - decideStep = &steps[i] - break - } - } - - if decideStep == nil { - t.Fatal("decide step not found in mol-polecat-arm") - } - - // Verify the nudge matrix is documented - nudgeKeywords := []string{"nudge-1", "nudge-2", "nudge-3", "escalate"} - for _, keyword := range nudgeKeywords { - if !strings.Contains(decideStep.Instructions, keyword) { - t.Errorf("decide step should document %s action", keyword) - } - } - - // Verify idle time thresholds are documented - timeThresholds := []string{"10-15min", "15-20min", "20+min"} - for _, threshold := range timeThresholds { - if !strings.Contains(decideStep.Instructions, threshold) && - !strings.Contains(decideStep.Instructions, strings.ReplaceAll(threshold, "-", " ")) { - t.Logf("Note: decide step should document %s threshold", threshold) - } - } -} - -// TestPreKillVerification tests that the execute step documents pre-kill verification. -func TestPreKillVerification(t *testing.T) { - mol := PolecatArmMolecule() - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse mol-polecat-arm: %v", err) - } - - // Find execute step - var executeStep *MoleculeStep - for i := range steps { - if steps[i].Ref == "execute" { - executeStep = &steps[i] - break - } - } - - if executeStep == nil { - t.Fatal("execute step not found in mol-polecat-arm") - } - - // Verify pre-kill verification is documented - if !strings.Contains(executeStep.Instructions, "pre-kill") && - !strings.Contains(executeStep.Instructions, "git status") { - t.Error("execute step should document pre-kill verification") - } - - // Verify clean git state check - if !strings.Contains(executeStep.Instructions, "clean") { - t.Error("execute step should check for clean git state") - } - - // Verify unpushed commits check - if !strings.Contains(executeStep.Instructions, "unpushed") { - t.Log("Note: execute step should document unpushed commits check") - } -} - -// TestWaitsForAllChildren tests the fanout gate semantics. -func TestWaitsForAllChildren(t *testing.T) { - // Test the WaitsFor parsing - desc := `## Step: survey -Discover items. - -## Step: aggregate -Collect results. -WaitsFor: all-children -Needs: survey` - - steps, err := ParseMoleculeSteps(desc) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - if len(steps) != 2 { - t.Fatalf("expected 2 steps, got %d", len(steps)) - } - - aggregate := steps[1] - if aggregate.Ref != "aggregate" { - t.Errorf("expected aggregate step, got %s", aggregate.Ref) - } - - if len(aggregate.WaitsFor) != 1 || aggregate.WaitsFor[0] != "all-children" { - t.Errorf("expected WaitsFor: [all-children], got %v", aggregate.WaitsFor) - } -} - -// TestMultipleWaitsForConditions tests parsing multiple WaitsFor conditions. -func TestMultipleWaitsForConditions(t *testing.T) { - desc := `## Step: finalize -Complete the process. -WaitsFor: all-children, external-signal, timeout` - - steps, err := ParseMoleculeSteps(desc) - if err != nil { - t.Fatalf("failed to parse: %v", err) - } - - if len(steps) != 1 { - t.Fatalf("expected 1 step, got %d", len(steps)) - } - - expected := []string{"all-children", "external-signal", "timeout"} - if len(steps[0].WaitsFor) != len(expected) { - t.Errorf("expected %d WaitsFor conditions, got %d", len(expected), len(steps[0].WaitsFor)) - } - - for i, exp := range expected { - if i < len(steps[0].WaitsFor) && steps[0].WaitsFor[i] != exp { - t.Errorf("WaitsFor[%d]: expected %q, got %q", i, exp, steps[0].WaitsFor[i]) - } - } -} - -// TestMolBondCLI tests the mol bond command via CLI integration. -// This test requires the gt binary to be built and in PATH. -func TestMolBondCLI(t *testing.T) { - if testing.Short() { - t.Skip("skipping CLI integration test in short mode") - } - - workDir := findBeadsDir(t) - if workDir == "" { - t.Skip("no .beads directory found") - } - - b := New(workDir) - - // Create a parent issue to bond to - parent, err := b.Create(CreateOptions{ - Title: "Test Patrol for Bonding", - Type: "task", - Priority: 2, - Description: "Parent issue for mol bond CLI test", - }) - if err != nil { - t.Fatalf("failed to create parent issue: %v", err) - } - defer func() { - // Clean up: close parent (children are auto-closed as children) - _ = b.Close(parent.ID) - }() - - // Test bonding mol-polecat-arm to the parent - t.Run("BondPolecatArm", func(t *testing.T) { - // Use bd mol bond command (the underlying command) - // gt mol bond mol-polecat-arm --parent= --ref=arm-test --var polecat_name=toast --var rig=gastown - args := []string{ - "mol", "bond", "mol-polecat-arm", - "--parent", parent.ID, - "--ref", "arm-toast", - "--var", "polecat_name=toast", - "--var", "rig=gastown", - } - - // Execute via the beads wrapper - // Since we can't easily call the cmd package from here, - // we verify the bonding logic works by testing the building blocks - - // 1. Verify mol-polecat-arm exists in catalog - catalog := BuiltinMolecules() - - var polecatArm *BuiltinMolecule - for i := range catalog { - if catalog[i].ID == "mol-polecat-arm" { - polecatArm = &catalog[i] - break - } - } - if polecatArm == nil { - t.Fatal("mol-polecat-arm not found in catalog") - } - - // 2. Verify template expansion works - ctx := map[string]string{ - "polecat_name": "toast", - "rig": "gastown", - } - expanded := ExpandTemplateVars(polecatArm.Description, ctx) - if strings.Contains(expanded, "{{polecat_name}}") { - t.Error("template variable polecat_name was not expanded") - } - if strings.Contains(expanded, "{{rig}}") { - t.Error("template variable rig was not expanded") - } - - // 3. Create a child issue manually to simulate what mol bond does - childTitle := "Polecat Arm (arm-toast)" - bondingMeta := ` ---- -bonded_from: mol-polecat-arm -bonded_to: ` + parent.ID + ` -bonded_ref: arm-toast -bonded_at: 2025-12-23T10:00:00Z -` - childDesc := expanded + bondingMeta - - child, err := b.Create(CreateOptions{ - Title: childTitle, - Type: "task", - Priority: parent.Priority, - Parent: parent.ID, - Description: childDesc, - }) - if err != nil { - t.Fatalf("failed to create bonded child: %v", err) - } - defer func() { - _ = b.Close(child.ID) - }() - - // 4. Verify the child was created with correct properties - fetched, err := b.Show(child.ID) - if err != nil { - t.Fatalf("failed to fetch child: %v", err) - } - - if !strings.Contains(fetched.Title, "arm-toast") { - t.Errorf("child title should contain arm-toast, got %s", fetched.Title) - } - if !strings.Contains(fetched.Description, "bonded_from: mol-polecat-arm") { - t.Error("child description should contain bonding metadata") - } - if !strings.Contains(fetched.Description, "toast") { - t.Error("child description should have expanded polecat_name") - } - - t.Logf("Created bonded child: %s (%s)", child.ID, childTitle) - t.Logf("Args that would be used: %v", args) - }) -} - -// TestActivityFeed tests the activity feed output from witness patrol. -func TestActivityFeed(t *testing.T) { - // The activity feed should show: - // - Polecats inspected - // - Nudges sent - // - Sessions killed - // - Escalations - - mol := WitnessPatrolMolecule() - steps, err := ParseMoleculeSteps(mol.Description) - if err != nil { - t.Fatalf("failed to parse mol-witness-patrol: %v", err) - } - - // Find generate-summary step (produces activity feed) - var summaryStep *MoleculeStep - for i := range steps { - if steps[i].Ref == "generate-summary" { - summaryStep = &steps[i] - break - } - } - - if summaryStep == nil { - t.Fatal("generate-summary step not found in mol-witness-patrol") - } - - // Verify the step documents key metrics - expectedMetrics := []string{ - "Workers inspected", - "Nudges sent", - "Sessions killed", - "Escalations", - } - - for _, metric := range expectedMetrics { - if !strings.Contains(strings.ToLower(summaryStep.Instructions), strings.ToLower(metric)) { - t.Logf("Note: generate-summary should document %q metric", metric) - } - } - - // Verify it mentions digests (for squashing) - if !strings.Contains(summaryStep.Instructions, "digest") { - t.Log("Note: generate-summary should mention digest creation") - } -} - -// findBeadsDir walks up from current directory to find .beads -func findBeadsDir(t *testing.T) string { - t.Helper() - - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("failed to get cwd: %v", err) - } - - dir := cwd - for { - if _, err := os.Stat(filepath.Join(dir, ".beads")); err == nil { - return dir - } - parent := filepath.Dir(dir) - if parent == dir { - return "" - } - dir = parent - } -} diff --git a/internal/beads/molecules_patrol.go b/internal/beads/molecules_patrol.go deleted file mode 100644 index 8654a705..00000000 --- a/internal/beads/molecules_patrol.go +++ /dev/null @@ -1,695 +0,0 @@ -// Package beads provides a wrapper for the bd (beads) CLI. -package beads - -// DeaconPatrolMolecule returns the deacon-patrol molecule definition. -// This is the Mayor's daemon loop for handling callbacks, health checks, and cleanup. -func DeaconPatrolMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-deacon-patrol", - Title: "Deacon Patrol", - Description: `Mayor's daemon patrol loop. - -The Deacon is the Mayor's background process that runs continuously, -handling callbacks, monitoring rig health, and performing cleanup. -Each patrol cycle runs these steps in sequence, then loops or exits. - -## Step: inbox-check -Handle callbacks from agents. - -Check the Mayor's inbox for messages from: -- Witnesses reporting polecat status -- Refineries reporting merge results -- Polecats requesting help or escalation -- External triggers (webhooks, timers) - -Process each message: -` + "```" + `bash -gt mail inbox -# For each message: -gt mail read -# Handle based on message type -` + "```" + ` - -Callbacks may spawn new polecats, update issue state, or trigger other actions. - -## Step: trigger-pending-spawns -Nudge newly spawned polecats that are ready for input. - -When polecats are spawned, their Claude session takes 10-20 seconds to initialize. -The spawn command returns immediately without waiting. This step finds spawned -polecats that are now ready and sends them a trigger to start working. - -` + "```" + `bash -# For each rig with polecats -for rig in gastown beads; do - gt polecats $rig - # For each working polecat, check if Claude is ready - # Use tmux capture-pane to look for "> " prompt -done -` + "```" + ` - -For each ready polecat that hasn't been triggered yet: -1. Send "Begin." to trigger UserPromptSubmit hook -2. The hook injects mail, polecat sees its assignment -3. Mark polecat as triggered in state - -Use WaitForClaudeReady from tmux package (polls for "> " prompt). -Timeout: 60 seconds per polecat. If not ready, try again next cycle. -Needs: inbox-check - -## Step: health-scan -Check Witness and Refinery health for each rig. - -**ZFC Principle**: You (Claude) make the judgment call about what is "stuck" or -"unresponsive" - there are no hardcoded thresholds in Go. Read the signals, -consider context, and decide. - -For each rig, run: -` + "```" + `bash -gt witness status -gt refinery status -` + "```" + ` - -**Signals to assess:** - -| Component | Healthy Signals | Concerning Signals | -|-----------|-----------------|-------------------| -| Witness | State: running, recent activity | State: not running, no heartbeat | -| Refinery | State: running, queue processing | Queue stuck, merge failures | - -**Tracking unresponsive cycles:** - -Maintain in your patrol state (persisted across cycles): -` + "```" + ` -health_state: - : - witness: - unresponsive_cycles: 0 - last_seen_healthy: - refinery: - unresponsive_cycles: 0 - last_seen_healthy: -` + "```" + ` - -**Decision matrix** (you decide the thresholds based on context): - -| Cycles Unresponsive | Suggested Action | -|---------------------|------------------| -| 1-2 | Note it, check again next cycle | -| 3-4 | Attempt restart: gt witness restart | -| 5+ | Escalate to Mayor with context | - -**Restart commands:** -` + "```" + `bash -gt witness restart -gt refinery restart -` + "```" + ` - -**Escalation:** -` + "```" + `bash -gt mail send mayor/ -s "Health: unresponsive" \ - -m "Component has been unresponsive for N cycles. Restart attempts failed. - Last healthy: - Error signals:
" -` + "```" + ` - -Reset unresponsive_cycles to 0 when component responds normally. -Needs: trigger-pending-spawns - -## Step: plugin-run -Execute registered plugins. - -Scan ~/gt/plugins/ for plugin directories. Each plugin has a plugin.md with -YAML frontmatter defining its gate (when to run) and instructions (what to do). - -See docs/deacon-plugins.md for full documentation. - -Gate types: -- cooldown: Time since last run (e.g., 24h) -- cron: Schedule-based (e.g., "0 9 * * *") -- condition: Metric threshold (e.g., wisp count > 50) -- event: Trigger-based (e.g., startup, heartbeat) - -For each plugin: -1. Read plugin.md frontmatter to check gate -2. Compare against state.json (last run, etc.) -3. If gate is open, execute the plugin - -Plugins marked parallel: true can run concurrently using Task tool subagents. -Sequential plugins run one at a time in directory order. - -Skip this step if ~/gt/plugins/ does not exist or is empty. -Needs: health-scan - -## Step: orphan-check -Find abandoned work. - -Scan for orphaned state: -- Issues marked in_progress with no active polecat -- Polecats that stopped responding mid-work -- Merge queue entries with no polecat owner -- Wisp sessions that outlived their spawner - -` + "```" + `bash -bd list --status=in_progress -gt polecats --all --orphan -` + "```" + ` - -For each orphan: -- Check if polecat session still exists -- If not, mark issue for reassignment or retry -- File incident beads if data loss occurred -Needs: health-scan - -## Step: session-gc -Clean dead sessions. - -Garbage collect terminated sessions: -- Remove stale polecat directories -- Clean up wisp session artifacts -- Prune old logs and temp files -- Archive completed molecule state - -` + "```" + `bash -gt gc --sessions -gt gc --wisps --age=1h -` + "```" + ` - -Preserve audit trail. Only clean sessions confirmed dead. -Needs: orphan-check - -## Step: context-check -Check own context limit. - -The Deacon runs in a Claude session with finite context. -Check if approaching the limit: - -` + "```" + `bash -gt context --usage -` + "```" + ` - -If context is high (>80%), prepare for handoff: -- Summarize current state -- Note any pending work -- Write handoff to molecule state - -This enables the Deacon to burn and respawn cleanly. -Needs: session-gc - -## Step: loop-or-exit -Burn and let daemon respawn, or exit if context high. - -Decision point at end of patrol cycle: - -If context is LOW: -- Sleep briefly (avoid tight loop) -- Return to inbox-check step - -If context is HIGH: -- Write state to persistent storage -- Exit cleanly -- Let the daemon orchestrator respawn a fresh Deacon - -The daemon ensures Deacon is always running: -` + "```" + `bash -# Daemon respawns on exit -gt daemon status -` + "```" + ` - -This enables infinite patrol duration via context-aware respawning. -Needs: context-check`, - } -} - -// WitnessPatrolMolecule returns the witness-patrol molecule definition. -// This is the per-rig worker monitor's patrol loop using the Christmas Ornament pattern. -func WitnessPatrolMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-witness-patrol", - Title: "Witness Patrol", - Description: `Per-rig worker monitor patrol loop using the Christmas Ornament pattern. - -The Witness is the Pit Boss for your rig. You watch polecats, nudge them toward -completion, verify clean git state before kills, and escalate stuck workers. - -**You do NOT do implementation work.** Your job is oversight, not coding. - -This molecule uses dynamic bonding to create mol-polecat-arm for each worker, -enabling parallel inspection with a fanout gate for aggregation. - -## The Christmas Ornament Shape - -` + "```" + ` - ★ mol-witness-patrol (trunk) - /|\ - ┌────────┘ │ └────────┐ - PREFLIGHT DISCOVERY CLEANUP - │ │ │ - inbox-check survey aggregate (WaitsFor: all-children) - check-refnry │ save-state - load-state │ generate-summary - ↓ context-check - ┌───────┼───────┐ burn-or-loop - ● ● ● mol-polecat-arm (dynamic) - ace nux toast -` + "```" + ` - ---- -# PREFLIGHT PHASE ---- - -## Step: inbox-check -Process witness mail: lifecycle requests, help requests. - -` + "```" + `bash -gt mail inbox -` + "```" + ` - -Handle by message type: -- **LIFECYCLE/Shutdown**: Queue for pre-kill verification -- **Blocked/Help**: Assess if resolvable or escalate -- **HANDOFF**: Load predecessor state -- **Work complete**: Verify issue closed, proceed to pre-kill - -Record any pending actions for later steps. -Mark messages as processed when complete. - -## Step: check-refinery -Ensure the refinery is alive and processing merge requests. - -**Redundant system**: This check runs in both gt spawn and Witness patrol -to ensure the merge queue processor stays operational. - -` + "```" + `bash -# Check if refinery session is running -gt session status /refinery - -# Check for merge requests in queue -bd list --type=merge-request --status=open -` + "```" + ` - -If merge requests are waiting AND refinery is not running: -` + "```" + `bash -gt session start /refinery -gt mail send /refinery -s "PATROL: Wake up" -m "Merge requests in queue. Please process." -` + "```" + ` - -If refinery is running but queue is non-empty for >30 min, send nudge. -This ensures polecats don't wait forever for their branches to merge. -Needs: inbox-check - -## Step: load-state -Read handoff bead and get nudge counts. - -Load persistent state from the witness handoff bead: -- Active workers and their status from last cycle -- Nudge counts per worker per issue -- Last nudge timestamps -- Pending escalations - -` + "```" + `bash -bd show -` + "```" + ` - -If no handoff exists (fresh start), initialize empty state. -This state persists across wisp burns and session cycles. -Needs: check-refinery - ---- -# DISCOVERY PHASE (Dynamic Bonding) ---- - -## Step: survey-workers -List polecats and bond mol-polecat-arm for each one. - -` + "```" + `bash -# Get list of polecats -gt polecat list -` + "```" + ` - -For each polecat discovered, dynamically bond an inspection arm: - -` + "```" + `bash -# Bond mol-polecat-arm for each polecat -for polecat in $(gt polecat list --names); do - bd mol bond mol-polecat-arm $PATROL_WISP_ID \ - --ref arm-$polecat \ - --var polecat_name=$polecat \ - --var rig= -done -` + "```" + ` - -This creates child wisps like: -- patrol-x7k.arm-ace (5 steps) -- patrol-x7k.arm-nux (5 steps) -- patrol-x7k.arm-toast (5 steps) - -Each arm runs in PARALLEL. The aggregate step will wait for all to complete. - -If no polecats are found, this step completes immediately with no children. -Needs: load-state - ---- -# CLEANUP PHASE (Gate + Fixed Steps) ---- - -## Step: aggregate -Collect outcomes from all polecat inspection arms. -WaitsFor: all-children - -This is a **fanout gate** - it cannot proceed until ALL dynamically-bonded -polecat arms have completed their inspection cycles. - -Once all arms complete, collect their outcomes: -- Actions taken per polecat (nudge, kill, escalate, none) -- Updated nudge counts -- Any errors or issues discovered - -Build the consolidated state for save-state. -Needs: survey-workers - -## Step: save-state -Update handoff bead with new states. - -Persist state to the witness handoff bead: -- Updated worker statuses from all arms -- Current nudge counts per worker -- Nudge timestamps -- Actions taken this cycle -- Pending items for next cycle - -` + "```" + `bash -bd update --description="" -` + "```" + ` - -This state survives wisp burns and session cycles. -Needs: aggregate - -## Step: generate-summary -Summarize this patrol cycle for digest. - -Include: -- Workers inspected (count, names) -- Nudges sent (count, to whom) -- Sessions killed (count, names) -- Escalations (count, issues) -- Issues found (brief descriptions) -- Actions pending for next cycle - -This becomes the digest when the patrol wisp is squashed. -Needs: save-state - -## Step: context-check -Check own context usage. - -If context is HIGH (>80%): -- Ensure state is saved to handoff bead -- Prepare for burn/respawn - -If context is LOW: -- Can continue patrolling -Needs: generate-summary - -## Step: burn-or-loop -End of patrol cycle decision. - -If context is LOW: -- Burn this wisp (no audit trail needed for patrol cycles) -- Sleep briefly to avoid tight loop (30-60 seconds) -- Return to inbox-check step - -If context is HIGH: -- Burn wisp with summary digest -- Exit cleanly (daemon will respawn fresh Witness) - -` + "```" + `bash -bd mol burn # Destroy ephemeral wisp -` + "```" + ` - -The daemon ensures Witness is always running. -Needs: context-check`, - } -} - -// PolecatArmMolecule returns the polecat-arm molecule definition. -// This is dynamically bonded by mol-witness-patrol for each polecat being monitored. -func PolecatArmMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-polecat-arm", - Title: "Polecat Arm", - Description: `Single polecat inspection and action cycle. - -This molecule is bonded dynamically by mol-witness-patrol's survey-workers step. -Each polecat being monitored gets one arm that runs in parallel with other arms. - -## Variables - -| Variable | Required | Description | -|----------|----------|-------------| -| polecat_name | Yes | Name of the polecat to inspect | -| rig | Yes | Rig containing the polecat | - -## Step: capture -Capture recent tmux output for {{polecat_name}}. - -` + "```" + `bash -tmux capture-pane -t gt-{{rig}}-{{polecat_name}} -p | tail -50 -` + "```" + ` - -Record: -- Last activity timestamp (when was last tool call?) -- Visible errors or stack traces -- Completion indicators ("Done", "Finished", etc.) - -## Step: assess -Categorize polecat state based on captured output. - -States: -- **working**: Recent tool calls, active processing -- **idle**: At prompt, no recent activity -- **error**: Showing errors or stack traces -- **requesting_shutdown**: Sent LIFECYCLE/Shutdown mail -- **done**: Showing completion indicators - -Calculate: minutes since last activity. -Needs: capture - -## Step: load-history -Read nudge history for {{polecat_name}} from patrol state. - -` + "```" + ` -nudge_count = state.nudges[{{polecat_name}}].count -last_nudge_time = state.nudges[{{polecat_name}}].timestamp -` + "```" + ` - -This data was loaded by the parent patrol's load-state step and passed -to the arm via the bonding context. -Needs: assess - -## Step: decide -Apply the nudge matrix to determine action for {{polecat_name}}. - -| State | Idle Time | Nudge Count | Action | -|-------|-----------|-------------|--------| -| working | any | any | none | -| idle | <10min | any | none | -| idle | 10-15min | 0 | nudge-1 (gentle) | -| idle | 15-20min | 1 | nudge-2 (direct) | -| idle | 20+min | 2 | nudge-3 (final) | -| idle | any | 3 | escalate | -| error | any | any | assess-severity | -| requesting_shutdown | any | any | pre-kill-verify | -| done | any | any | pre-kill-verify | - -Nudge text: -1. "How's progress? Need any help?" -2. "Please wrap up soon. What's blocking you?" -3. "Final check. Will escalate in 5 min if no response." - -Record decision and rationale. -Needs: load-history - -## Step: execute -Take the decided action for {{polecat_name}}. - -**nudge-N**: -` + "```" + `bash -tmux send-keys -t gt-{{rig}}-{{polecat_name}} "{{nudge_text}}" Enter -` + "```" + ` - -**pre-kill-verify**: -` + "```" + `bash -cd polecats/{{polecat_name}} -git status # Must be clean -git log origin/main..HEAD # Check for unpushed -bd show # Verify closed/deferred -` + "```" + ` -If clean: kill session, remove worktree, delete branch -If dirty: record failure, retry next cycle - -**escalate**: -` + "```" + `bash -gt mail send mayor/ -s "Escalation: {{polecat_name}} stuck" -m "..." -` + "```" + ` - -**none**: No action needed. - -Record: action taken, result, updated nudge count. -Needs: decide - -## Output - -The arm completes with: -- action_taken: none | nudge-1 | nudge-2 | nudge-3 | killed | escalated -- result: success | failed | pending -- updated_state: New nudge count and timestamp for {{polecat_name}} - -This data feeds back to the parent patrol's aggregate step.`, - } -} - -// RefineryPatrolMolecule returns the refinery-patrol molecule definition. -// This is the merge queue processor's patrol loop with verification gates. -func RefineryPatrolMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-refinery-patrol", - Title: "Refinery Patrol", - Description: `Merge queue processor patrol loop. - -The Refinery is the Engineer in the engine room. You process polecat branches, -merging them to main one at a time with sequential rebasing. - -**The Scotty Test**: Before proceeding past any failure, ask yourself: -"Would Scotty walk past a warp core leak because it existed before his shift?" - -## Step: inbox-check -Check mail for MR submissions, escalations, messages. - -` + "```" + `bash -gt mail inbox -# Process any urgent items -` + "```" + ` - -Handle shutdown requests, escalations, and status queries. - -## Step: queue-scan -Fetch remote and identify polecat branches waiting. - -` + "```" + `bash -git fetch origin -git branch -r | grep polecat -gt refinery queue -` + "```" + ` - -If queue empty, skip to context-check step. -Track branch list for this cycle. -Needs: inbox-check - -## Step: process-branch -Pick next branch. Rebase on current main. - -` + "```" + `bash -git checkout -b temp origin/ -git rebase origin/main -` + "```" + ` - -If rebase conflicts and unresolvable: -- git rebase --abort -- Notify polecat to fix and resubmit -- Skip to loop-check for next branch - -Needs: queue-scan - -## Step: run-tests -Run the test suite. - -` + "```" + `bash -go test ./... -` + "```" + ` - -Track results: pass count, fail count, specific failures. -Needs: process-branch - -## Step: handle-failures -**VERIFICATION GATE**: This step enforces the Beads Promise. - -If tests PASSED: This step auto-completes. Proceed to merge. - -If tests FAILED: -1. Diagnose: Is this a branch regression or pre-existing on main? -2. If branch caused it: - - Abort merge - - Notify polecat: "Tests failing. Please fix and resubmit." - - Skip to loop-check -3. If pre-existing on main: - - Option A: Fix it yourself (you're the Engineer!) - - Option B: File a bead: bd create --type=bug --priority=1 --title="..." - -**GATE REQUIREMENT**: You CANNOT proceed to merge-push without: -- Tests passing, OR -- Fix committed, OR -- Bead filed for the failure - -This is non-negotiable. Never disavow. Never "note and proceed." -Needs: run-tests - -## Step: merge-push -Merge to main and push immediately. - -` + "```" + `bash -git checkout main -git merge --ff-only temp -git push origin main -git branch -d temp -git branch -D # Local delete (branches never go to origin) -` + "```" + ` - -Main has moved. Any remaining branches need rebasing on new baseline. -Needs: handle-failures - -## Step: loop-check -More branches to process? - -If yes: Return to process-branch with next branch. -If no: Continue to generate-summary. - -Track: branches processed, branches skipped (with reasons). -Needs: merge-push - -## Step: generate-summary -Summarize this patrol cycle. - -Include: -- Branches processed (count, names) -- Test results (pass/fail) -- Issues filed (if any) -- Branches skipped (with reasons) -- Any escalations sent - -This becomes the digest when the patrol is squashed. -Needs: loop-check - -## Step: context-check -Check own context usage. - -If context is HIGH (>80%): -- Write handoff summary -- Prepare for burn/respawn - -If context is LOW: -- Can continue processing -Needs: generate-summary - -## Step: burn-or-loop -End of patrol cycle decision. - -If queue non-empty AND context LOW: -- Burn this wisp, start fresh patrol -- Return to inbox-check - -If queue empty OR context HIGH: -- Burn wisp with summary digest -- Exit (daemon will respawn if needed) -Needs: context-check`, - } -} diff --git a/internal/beads/molecules_session.go b/internal/beads/molecules_session.go deleted file mode 100644 index e8dace95..00000000 --- a/internal/beads/molecules_session.go +++ /dev/null @@ -1,544 +0,0 @@ -// Package beads provides a wrapper for the bd (beads) CLI. -package beads - -// InstallGoBinaryMolecule returns the install-go-binary molecule definition. -// This is a single step to rebuild and install the gt binary after code changes. -func InstallGoBinaryMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-install-go-binary", - Title: "Install Go Binary", - Description: `Single step to rebuild and install the gt binary after code changes. - -## Step: install -Build and install the gt binary locally. - -Run from the rig directory: -` + "```" + ` -go build -o gt ./cmd/gt -go install ./cmd/gt -` + "```" + ` - -Verify the installed binary is updated: -` + "```" + ` -which gt -gt --version # if version command exists -` + "```", - } -} - -// BootstrapGasTownMolecule returns the bootstrap molecule for new Gas Town installations. -// This walks a user through setting up Gas Town from scratch after brew install. -func BootstrapGasTownMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-bootstrap", - Title: "Bootstrap Gas Town", - Description: `Complete setup of a new Gas Town installation. - -Run this after installing gt and bd via Homebrew. This molecule guides you through -creating an HQ, setting up rigs, and configuring your environment. - -## Step: locate-hq -Determine where to install the Gas Town HQ. - -Ask the user for their preferred location. Common choices: -- ~/gt (recommended - short, easy to type) -- ~/gastown -- ~/workspace/gt - -Validate the path: -- Must not already exist (or be empty) -- Parent directory must be writable -- Avoid paths with spaces - -Store the chosen path for subsequent steps. - -## Step: create-hq -Create the HQ directory structure. - -` + "```" + `bash -mkdir -p {{hq_path}} -cd {{hq_path}} -gt install . --name {{hq_name}} -` + "```" + ` - -If the user wants to track the HQ in git: -` + "```" + `bash -gt git-init --github={{github_repo}} --private -` + "```" + ` - -The HQ now has: -- mayor/ directory -- .beads/ for town-level tracking -- CLAUDE.md for mayor context - -Needs: locate-hq - -## Step: setup-rigs -Configure which rigs to add to the HQ. - -Default rigs for Gas Town development: -- gastown (git@github.com:steveyegge/gastown.git) -- beads (git@github.com:steveyegge/beads.git) - -For each rig, run: -` + "```" + `bash -gt rig add --prefix -` + "```" + ` - -This creates the full rig structure: -- refinery/rig/ (canonical main clone) -- mayor/rig/ (mayor's working clone) -- crew/main/ (default human workspace) -- witness/ (polecat monitor) -- polecats/ (worker directory) - -Needs: create-hq - -## Step: build-gt -Build the gt binary from source. - -` + "```" + `bash -cd {{hq_path}}/gastown/mayor/rig -go build -o gt ./cmd/gt -` + "```" + ` - -Verify the build succeeded: -` + "```" + `bash -./gt version -` + "```" + ` - -Needs: setup-rigs -Tier: haiku - -## Step: install-paths -Install gt to a location in PATH. - -Check if ~/bin or ~/.local/bin is in PATH: -` + "```" + `bash -echo $PATH | tr ':' '\n' | grep -E '(~/bin|~/.local/bin|/home/.*/bin)' -` + "```" + ` - -Copy the binary: -` + "```" + `bash -mkdir -p ~/bin -cp {{hq_path}}/gastown/mayor/rig/gt ~/bin/gt -` + "```" + ` - -If ~/bin is not in PATH, add to shell config: -` + "```" + `bash -echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc -# or ~/.bashrc for bash users -` + "```" + ` - -Verify: -` + "```" + `bash -which gt -gt version -` + "```" + ` - -Needs: build-gt -Tier: haiku - -## Step: init-beads -Initialize beads databases in all clones. - -For each rig's mayor clone: -` + "```" + `bash -cd {{hq_path}}//mayor/rig -bd init --prefix -` + "```" + ` - -For the town-level beads: -` + "```" + `bash -cd {{hq_path}} -bd init --prefix hq -` + "```" + ` - -Configure sync-branch for multi-clone setups: -` + "```" + `bash -echo "sync-branch: beads-sync" >> .beads/config.yaml -` + "```" + ` - -Needs: setup-rigs -Tier: haiku - -## Step: sync-beads -Sync beads from remotes and fix any issues. - -For each initialized beads database: -` + "```" + `bash -bd sync -bd doctor --fix -` + "```" + ` - -This imports existing issues from JSONL and sets up git hooks. - -Needs: init-beads -Tier: haiku - -## Step: verify -Verify the installation is complete and working. - -Run health checks: -` + "```" + `bash -gt status # Should show rigs with crew/refinery/mayor -gt doctor # Check for issues -bd list # Should show issues from synced beads -` + "```" + ` - -Test spawning capability (dry run): -` + "```" + `bash -gt spawn --help -` + "```" + ` - -Print summary: -- HQ location -- Installed rigs -- gt version -- bd version - -Needs: sync-beads, install-paths`, - } -} - -// VersionBumpMolecule returns the version-bump molecule definition. -// This is the release checklist for Gas Town versions. -func VersionBumpMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-version-bump", - Title: "Version Bump", - Description: `Release checklist for Gas Town version {{version}}. - -This molecule ensures all release steps are completed properly. -Replace {{version}} with the target version (e.g., 0.1.0). - -## Step: update-version -Update version string in internal/cmd/version.go. - -Change the Version variable to the new version: -` + "```" + `go -var ( - Version = "{{version}}" - BuildTime = "unknown" - GitCommit = "unknown" -) -` + "```" + ` - -## Step: rebuild-binary -Rebuild the gt binary with version info. - -` + "```" + `bash -go build -ldflags="-X github.com/steveyegge/gastown/internal/cmd.Version={{version}} \ - -X github.com/steveyegge/gastown/internal/cmd.GitCommit=$(git rev-parse --short HEAD) \ - -X github.com/steveyegge/gastown/internal/cmd.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - -o gt ./cmd/gt -` + "```" + ` - -Verify the version: -` + "```" + `bash -./gt version -` + "```" + ` - -Needs: update-version - -## Step: run-tests -Run the full test suite. - -` + "```" + `bash -go test ./... -` + "```" + ` - -Fix any failures before proceeding. -Needs: rebuild-binary - -## Step: update-changelog -Update CHANGELOG.md with release notes. - -Add a new section at the top: -` + "```" + `markdown -## [{{version}}] - YYYY-MM-DD - -### Added -- Feature descriptions - -### Changed -- Change descriptions - -### Fixed -- Bug fix descriptions -` + "```" + ` - -Needs: run-tests - -## Step: commit-release -Commit the release changes. - -` + "```" + `bash -git add -A -git commit -m "release: v{{version}}" -` + "```" + ` - -Needs: update-changelog - -## Step: tag-release -Create and push the release tag. - -` + "```" + `bash -git tag -a v{{version}} -m "Release v{{version}}" -git push origin main -git push origin v{{version}} -` + "```" + ` - -Needs: commit-release - -## Step: verify-release -Verify the release is complete. - -- Check that the tag exists on GitHub -- Verify CI/CD (if configured) completed successfully -- Test installation from the new tag: -` + "```" + `bash -go install github.com/steveyegge/gastown/cmd/gt@v{{version}} -gt version -` + "```" + ` - -Needs: tag-release - -## Step: update-installations -Update local installations and restart daemons. - -` + "```" + `bash -# Rebuild and install -go install ./cmd/gt - -# Restart any running daemons -pkill -f "gt daemon" || true -gt daemon start -` + "```" + ` - -Needs: verify-release`, - } -} - -// CrewSessionMolecule returns the crew-session molecule definition. -// This is a light harness for crew workers that enables autonomous overnight work. -// Key insight: if there's an attached mol, continue working without awaiting input. -func CrewSessionMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-crew-session", - Title: "Crew Session", - Description: `Light session harness for crew workers. - -This molecule enables autonomous work on long-lived molecules. The key insight: -**If there's an attached mol, continue working without awaiting input.** - -This transforms crew workers from interactive assistants to autonomous workers -that can churn through long molecules overnight. - -## Step: orient -Load context and identify self. - -` + "```" + `bash -gt prime # Load Gas Town context -` + "```" + ` - -Identify yourself: -- Read crew.md for role context -- Note your rig and crew member name -- Understand the session wisp model - -## Step: handoff-read -Check inbox for predecessor handoff. - -` + "```" + `bash -gt mail inbox -` + "```" + ` - -Look for 🤝 HANDOFF messages from your previous session. -If found: -- Read the handoff carefully -- Load predecessor's context and state -- Note where they left off - -If no handoff found, this is a fresh start. -Needs: orient - -## Step: check-attachment -Look for pinned work to continue. - -` + "```" + `bash -bd list --pinned --assignee=$(gt whoami) --status=in_progress -gt mol status -` + "```" + ` - -**DECISION POINT:** - -If attachment found: -- This is autonomous continuation mode -- Proceed directly to execute step -- NO human input needed - -If no attachment found: -- This is interactive mode -- Await user instruction before proceeding -- Mark this step complete when user provides direction -Needs: handoff-read - -## Step: execute -Work the attached molecule. - -Find next ready step in the attached mol: -` + "```" + `bash -bd ready --parent= -bd update --status=in_progress -` + "```" + ` - -Work until one of: -- All steps in mol completed -- Context approaching limit (>80%) -- Natural stopping point reached -- Blocked by external dependency - -Track progress in the mol itself (close completed steps). -File discovered work as new issues. -Needs: check-attachment - -## Step: cleanup -End session with proper handoff. - -1. Sync all state: -` + "```" + `bash -git add -A && git commit -m "WIP: " || true -bd sync -` + "```" + ` -Note: Branch stays local (commits saved in shared .git). - -2. Write handoff to successor (yourself): -` + "```" + `bash -gt mail send -s "🤝 HANDOFF: " -m " -## Progress -- Completed: -- Next: - -## State -- Current step: -- Blockers: - -## Notes - -" -` + "```" + ` - -3. Session ends. Successor will pick up from handoff. -Needs: execute`, - } -} - -// PolecatSessionMolecule returns the polecat-session molecule definition. -// This is a one-shot session wisp that wraps polecat work. -// Unlike patrol wisps (which loop), this wisp terminates with the session. -func PolecatSessionMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-polecat-session", - Title: "Polecat Session", - Description: `One-shot session wisp for polecat workers. - -This molecule wraps the polecat's work assignment. It handles: -1. Onboarding - read polecat.md, load context -2. Execution - run the attached work molecule -3. Cleanup - sync, burn, request shutdown - -Unlike patrol wisps (which loop), this wisp terminates when work is done. -The attached work molecule is permanent and auditable. - -## Step: orient -Read polecat.md protocol and initialize context. - -` + "```" + `bash -gt prime # Load Gas Town context -gt mail inbox # Check for work assignment -` + "```" + ` - -Understand: -- Your identity (rig/polecat-name) -- The beads system -- Exit strategies (COMPLETED, BLOCKED, REFACTOR, ESCALATE) -- Handoff protocols - -## Step: handoff-read -Check for predecessor session handoff. - -If this polecat was respawned after a crash or context cycle: -- Check mail for 🤝 HANDOFF from previous session -- Load state from the attached work mol -- Resume from last completed step - -` + "```" + `bash -gt mail inbox | grep HANDOFF -bd show # Check step completion state -` + "```" + ` -Needs: orient - -## Step: find-work -Locate attached work molecule. - -` + "```" + `bash -gt mol status # Shows what's on your hook -` + "```" + ` - -The work mol should already be attached (done by spawn). -If not attached, check mail for work assignment. - -Verify you have: -- A work mol ID -- Understanding of the work scope -- No blockers to starting -Needs: handoff-read - -## Step: execute -Run the attached work molecule to completion. - -For each ready step in the work mol: -` + "```" + `bash -bd ready --parent= -bd update --status=in_progress -# ... do the work ... -bd close -` + "```" + ` - -Continue until reaching the exit-decision step in the work mol. -All exit types (COMPLETED, BLOCKED, REFACTOR, ESCALATE) proceed to cleanup. - -**Dynamic modifications allowed**: -- Add review or test steps if needed -- File discovered blockers as issues -- Request session refresh if context filling -Needs: find-work - -## Step: cleanup -Finalize session and request termination. - -1. Sync all state: -` + "```" + `bash -bd sync -` + "```" + ` -Note: Branch stays local (commits saved in shared .git). - -2. Update work mol based on exit type: - - COMPLETED: ` + "`bd close `" + ` - - BLOCKED/REFACTOR/ESCALATE: ` + "`bd update --status=deferred`" + ` - -3. Burn this session wisp (no audit needed): -` + "```" + `bash -bd mol burn -` + "```" + ` - -4. Request shutdown from Witness: -` + "```" + `bash -gt mail send /witness -s "SHUTDOWN: " -m "Session complete. Exit: " -` + "```" + ` - -5. Wait for Witness to terminate session. Do not exit directly. -Needs: execute`, - } -} diff --git a/internal/beads/molecules_work.go b/internal/beads/molecules_work.go deleted file mode 100644 index 18afe8b8..00000000 --- a/internal/beads/molecules_work.go +++ /dev/null @@ -1,444 +0,0 @@ -// Package beads provides a wrapper for the bd (beads) CLI. -package beads - -// EngineerInBoxMolecule returns the engineer-in-box molecule definition. -// This is a full workflow from design to merge. -func EngineerInBoxMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-engineer-in-box", - Title: "Engineer in a Box", - Description: `Full workflow from design to merge. - -## Step: design -Think carefully about architecture. Consider: -- Existing patterns in the codebase -- Trade-offs between approaches -- Testability and maintainability - -Write a brief design summary before proceeding. - -## Step: implement -Write the code. Follow codebase conventions. -Needs: design - -## Step: review -Self-review the changes. Look for: -- Bugs and edge cases -- Style issues -- Missing error handling -Needs: implement - -## Step: test -Write and run tests. Cover happy path and edge cases. -Fix any failures before proceeding. -Needs: implement - -## Step: submit -Submit for merge via refinery. -Needs: review, test`, - } -} - -// QuickFixMolecule returns the quick-fix molecule definition. -// This is a fast path for small changes. -func QuickFixMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-quick-fix", - Title: "Quick Fix", - Description: `Fast path for small changes. - -## Step: implement -Make the fix. Keep it focused. - -## Step: test -Run relevant tests. Fix any regressions. -Needs: implement - -## Step: submit -Submit for merge. -Needs: test`, - } -} - -// ResearchMolecule returns the research molecule definition. -// This is an investigation workflow. -func ResearchMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-research", - Title: "Research", - Description: `Investigation workflow. - -## Step: investigate -Explore the question. Search code, read docs, -understand context. Take notes. - -## Step: document -Write up findings. Include: -- What you learned -- Recommendations -- Open questions -Needs: investigate`, - } -} - -// PolecatWorkMolecule returns the polecat-work molecule definition. -// This is the full polecat lifecycle from assignment to decommission. -// It's an operational molecule that enables crash recovery and context survival. -func PolecatWorkMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-polecat-work", - Title: "Polecat Work", - Description: `Full polecat lifecycle from assignment to decommission. - -This molecule is your contract. Follow it to one of its defined exits. -The Witness doesn't care which exit you take, only that you exit properly. - -**State Machine**: A polecat that crashes can restart, read its molecule state, -and continue from the last completed step. No work is lost. - -**Non-Linear Exits**: If blocked at any step, skip to exit-decision directly. - -## Step: load-context -Run gt prime and bd prime. Verify issue assignment. -Check inbox for any relevant messages. - -Read the assigned issue and understand the requirements. -Identify any blockers or missing information. - -**If blocked here**: Missing requirements? Unclear scope? Jump to exit-decision -with exit_type=escalate. - -## Step: implement -Implement the solution. Follow codebase conventions. -File discovered work as new issues with bd create. - -Make regular commits with clear messages. -Keep changes focused on the assigned issue. - -**Dynamic modifications allowed**: -- Add extra review or test steps if needed -- File discovered blockers as issues -- Request session refresh if context is filling up - -**If blocked here**: Dependency missing? Work too large? Jump to exit-decision. -Needs: load-context - -## Step: self-review -Review your own changes. Look for: -- Bugs and edge cases -- Style issues -- Missing error handling -- Security concerns - -Fix any issues found before proceeding. -Needs: implement - -## Step: verify-tests -Run existing tests. Add new tests for new functionality. -Ensure adequate coverage. - -` + "```" + `bash -go test ./... -` + "```" + ` - -Fix any test failures before proceeding. -Needs: implement - -## Step: rebase-main -Rebase against main to incorporate any changes. -Resolve conflicts if needed. - -` + "```" + `bash -git fetch origin main -git rebase origin/main -` + "```" + ` - -If there are conflicts, resolve them carefully and -continue the rebase. If conflicts are unresolvable, jump to exit-decision -with exit_type=escalate. -Needs: self-review, verify-tests - -## Step: submit-merge -Submit to merge queue via beads. - -**IMPORTANT**: Do NOT use gh pr create or GitHub PRs. -The Refinery processes merges via beads merge-request issues. -Branch stays local (refinery sees it via shared worktree). - -1. Create a beads merge-request: bd create --type=merge-request --title="Merge: " -2. Signal ready: gt done - -` + "```" + `bash -bd create --type=merge-request --title="Merge: " -gt done # Signal work ready for merge queue -` + "```" + ` - -If there are CI failures, fix them before proceeding. -Needs: rebase-main - -## Step: exit-decision -**CONVERGENCE POINT**: All exits pass through here. - -Determine your exit type and take appropriate action: - -### Exit Type: COMPLETED (normal) -Work finished successfully. Submit-merge done. -` + "```" + `bash -# Document completion -bd update --status=closed -` + "```" + ` - -### Exit Type: BLOCKED -External dependency prevents progress. -` + "```" + `bash -# 1. File the blocker -bd create --type=task --title="Blocker: " --priority=1 - -# 2. Link dependency -bd dep add - -# 3. Defer your issue -bd update --status=deferred - -# 4. Notify witness -gt mail send /witness -s "Blocked: " -m "Blocked by . Deferring." -` + "```" + ` - -### Exit Type: REFACTOR -Work is too large for one polecat session. -` + "```" + `bash -# Option A: Self-refactor -# 1. Break into sub-issues -bd create --type=task --title="Sub: part 1" --parent= -bd create --type=task --title="Sub: part 2" --parent= - -# 2. Close what you completed, defer the rest -bd close -bd update --status=deferred - -# Option B: Request refactor -gt mail send mayor/ -s "Refactor needed: " -m " -Issue too large. Completed X, remaining Y needs breakdown. -Recommend splitting into: ... -" -bd update --status=deferred -` + "```" + ` - -### Exit Type: ESCALATE -Need human judgment or authority. -` + "```" + `bash -# 1. Document what you know -bd comment "Escalating because: . Context:
" - -# 2. Mail human -gt mail send --human -s "Escalation: " -m " -Need human decision on: -Context: -Options I see: -" - -# 3. Defer the issue -bd update --status=deferred -` + "```" + ` - -**Record your exit**: Update this step with your exit type and actions taken. -Needs: load-context - -## Step: request-shutdown -Wait for termination. - -All exit paths converge here. Your work is either: -- Merged (COMPLETED) -- Deferred with proper handoff (BLOCKED/REFACTOR/ESCALATE) - -The polecat is now ready to be cleaned up. -Do not exit directly - wait for Witness to kill the session. -Needs: exit-decision`, - } -} - -// ReadyWorkMolecule returns the ready-work molecule definition. -// This is an autonomous backlog processing patrol for crew workers. -// It's a vapor-phase molecule (wisp) that scans backlogs, selects work, -// and processes items until context is low. -func ReadyWorkMolecule() BuiltinMolecule { - return BuiltinMolecule{ - ID: "mol-ready-work", - Title: "Ready Work", - Description: `Autonomous backlog processing patrol for crew workers. - -**Phase**: vapor (wisp) - ephemeral patrol cycles -**Squash**: after each work item or context threshold - -This molecule enables crew workers to autonomously process backlog items -using an ROI heuristic. It scans multiple backlogs, selects the highest-value -achievable item, executes it, and loops until context is low. - -## Variables - -| Variable | Default | Description | -|----------|---------|-------------| -| backlog_priority | (see scan order) | Override backlog scan order | -| context_threshold | 20 | Percentage at which to handoff | -| max_items | unlimited | Maximum items to process per session | - -## Step: orient -Load context and check for interrupts. - -` + "```" + `bash -gt prime # Load Gas Town context -` + "```" + ` - -Check for: -- Mail with overseer instructions: ` + "`gt mail inbox`" + ` -- Predecessor handoff: Look for 🤝 HANDOFF messages -- Current context state - -If overseer mail directs specific work, attach that instead of autonomous scan. -If handoff exists, resume from handoff state. - -## Step: scan-backlogs -Survey all backlogs in priority order. - -Scan order (highest to lowest priority): -1. ` + "`gh pr list --state open`" + ` - PRs need review/merge -2. ` + "`gh issue list --state open --label untriaged`" + ` - Untriaged issues -3. ` + "`bd ready`" + ` - Beads issues ready to work -4. ` + "`gh issue list --state open --label triaged`" + ` - Triaged GitHub issues - -For each backlog, capture: -- Total count of items -- Top 3-5 candidates with brief descriptions -- Estimated size category (small/medium/large) - -` + "```" + `bash -# Example scan -echo "=== PRs ===" && gh pr list --state open --limit 10 -echo "=== Untriaged ===" && gh issue list --state open --label untriaged --limit 10 -echo "=== Beads Ready ===" && bd ready -echo "=== Triaged ===" && gh issue list --state open --label triaged --limit 10 -` + "```" + ` - -If all backlogs empty: exit patrol (nothing to do). -Needs: orient - -## Step: select-work -Apply ROI heuristic to select best work item. - -**ROI Formula**: Impact / Effort, constrained by remaining context - -Evaluation criteria: -1. **Estimate size** - Tokens needed (small=1k, medium=5k, large=20k+) -2. **Check context capacity** - Can this item fit in remaining context? -3. **Weight by impact**: - - PRs: High (blocking others) → weight 3x - - Untriaged: Medium (needs triage) → weight 2x - - Beads ready: Medium (concrete work) → weight 2x - - Triaged GH: Lower (already processed) → weight 1x -4. **Adjust by priority** - P0/P1 issues get 2x multiplier - -Selection algorithm: -1. Filter to items that fit in remaining context -2. Score each: (impact_weight × priority_multiplier) / estimated_effort -3. Select highest scoring item -4. If tie: prefer PRs > untriaged > beads > triaged - -If no achievable items (all too large): goto handoff step. -Record selection rationale for audit. -Needs: scan-backlogs - -## Step: execute-work -Work the selected item based on its type. - -**For PRs (gh pr)**: -- Review the changes -- If good: approve and merge -- If issues: request changes with specific feedback -- Close or comment as appropriate - -**For untriaged issues (gh issue, no label)**: -- Read and understand the issue -- Add appropriate labels (bug, feature, enhancement, etc.) -- Set priority if determinable -- Convert to beads if actionable: ` + "`bd create --title=\"...\" --type=...`" + ` -- Close if duplicate/invalid/wontfix - -**For beads ready (bd)**: -- Claim: ` + "`bd update --status=in_progress`" + ` -- Implement the fix/feature -- Test changes -- Commit and push -- Close: ` + "`bd close `" + ` - -**For triaged GitHub issues**: -- Implement the fix/feature -- Create PR or push directly -- Link to issue: ` + "`Fixes #`" + ` -- Close issue when merged - -Commit regularly. Push changes. Update issue state. -Needs: select-work - -## Step: check-context -Assess context state after completing work item. - -` + "```" + `bash -# Estimate context usage (if tool available) -gt context --usage -` + "```" + ` - -Decision matrix: -| Context Used | Action | -|--------------|--------| -| < 60% | Loop to scan-backlogs (continue working) | -| 60-80% | One more small item, then handoff | -| > 80% | Goto handoff immediately | - -Also check: -- Items processed this session vs max_items limit -- Time elapsed (soft limit for long sessions) -- Any new high-priority mail that should interrupt - -If continuing: return to scan-backlogs step. -Needs: execute-work - -## Step: handoff -Prepare for session transition. - -1. **Summarize work completed**: - - Items processed (count and types) - - PRs reviewed/merged - - Issues triaged - - Beads closed - - Any issues filed - -2. **Note in-progress items**: - - If interrupted mid-work, record state - - File continuation issue if needed - -3. **Send handoff mail**: -` + "```" + `bash -gt mail send -s "🤝 HANDOFF: Ready-work patrol" -m " -## Completed This Session -- - -## Backlog State -- PRs remaining: -- Untriaged: -- Beads ready: -- Triaged: - -## Notes - -" -` + "```" + ` - -4. **Squash wisp to digest**: -` + "```" + `bash -bd mol squash --summary="Processed N items: X PRs, Y issues, Z beads" -` + "```" + ` - -5. **Exit for fresh session** - Successor picks up from handoff. -Needs: check-context`, - } -} diff --git a/internal/cmd/install.go b/internal/cmd/install.go index 62c1feed..1af96ee7 100644 --- a/internal/cmd/install.go +++ b/internal/cmd/install.go @@ -10,7 +10,6 @@ import ( "time" "github.com/spf13/cobra" - "github.com/steveyegge/gastown/internal/beads" "github.com/steveyegge/gastown/internal/config" "github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/templates" @@ -173,13 +172,6 @@ func runInstall(cmd *cobra.Command, args []string) error { fmt.Printf(" %s Could not initialize town beads: %v\n", style.Dim.Render("⚠"), err) } else { fmt.Printf(" ✓ Initialized .beads/ (town-level beads with gm- prefix)\n") - - // Seed built-in molecules - if err := seedBuiltinMolecules(absPath); err != nil { - fmt.Printf(" %s Could not seed built-in molecules: %v\n", style.Dim.Render("⚠"), err) - } else { - fmt.Printf(" ✓ Seeded built-in molecules\n") - } } } @@ -253,10 +245,3 @@ func initTownBeads(townPath string) error { return nil } -// seedBuiltinMolecules creates built-in molecule definitions in the beads database. -// These molecules provide standard workflows like engineer-in-box, quick-fix, and research. -func seedBuiltinMolecules(townPath string) error { - b := beads.New(townPath) - _, err := b.SeedBuiltinMolecules() - return err -}