diff --git a/internal/beads/builtin_molecules.go b/internal/beads/builtin_molecules.go index 492cd466..187366da 100644 --- a/internal/beads/builtin_molecules.go +++ b/internal/beads/builtin_molecules.go @@ -16,6 +16,7 @@ func BuiltinMolecules() []BuiltinMolecule { ResearchMolecule(), InstallGoBinaryMolecule(), BootstrapGasTownMolecule(), + PolecatWorkMolecule(), } } @@ -299,6 +300,97 @@ Needs: sync-beads, install-paths`, } } +// 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 enables nondeterministic idempotence for polecat work. +A polecat that crashes after any step can restart, read its molecule state, +and continue from the last completed step. No work is lost. + +## 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. + +## 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. +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. +Needs: self-review, verify-tests + +## Step: submit-merge +Submit to merge queue. Create PR if needed. +Verify CI passes. + +` + "```" + `bash +gt done # Signal work ready for merge queue +` + "```" + ` + +If there are CI failures, fix them before proceeding. +Needs: rebase-main + +## Step: update-handoff +Update handoff bead with final state. +File any remaining work as issues. + +Document any important context for the next session +or for anyone reviewing the work. +Needs: submit-merge + +## Step: request-shutdown +Send shutdown request to Witness. +Wait for termination. + +The polecat is now ready to be cleaned up. +Do not exit directly - wait for Witness to kill the session. +Needs: update-handoff`, + } +} + // 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. diff --git a/internal/beads/builtin_molecules_test.go b/internal/beads/builtin_molecules_test.go index ba8b1c19..c0045374 100644 --- a/internal/beads/builtin_molecules_test.go +++ b/internal/beads/builtin_molecules_test.go @@ -5,8 +5,8 @@ import "testing" func TestBuiltinMolecules(t *testing.T) { molecules := BuiltinMolecules() - if len(molecules) != 4 { - t.Errorf("expected 4 built-in molecules, got %d", len(molecules)) + if len(molecules) != 6 { + t.Errorf("expected 6 built-in molecules, got %d", len(molecules)) } // Verify each molecule can be parsed and validated @@ -172,3 +172,61 @@ func TestInstallGoBinaryMolecule(t *testing.T) { 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, update-handoff, 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", "update-handoff", "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 update-handoff + if len(steps[7].Needs) != 1 || steps[7].Needs[0] != "update-handoff" { + t.Errorf("request-shutdown should need update-handoff, got %v", steps[7].Needs) + } +}