feat(beads): add built-in molecules for standard workflows
Add three built-in molecule definitions that are automatically seeded during `gt install`: - engineer-in-box: Full workflow from design to merge (5 steps) - quick-fix: Fast path for small changes (3 steps) - research: Investigation workflow (2 steps) These molecules provide reusable workflow templates that polecats can instantiate to execute multi-step procedures with proper dependency tracking between steps. Closes gt-4nn.4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
143
internal/beads/builtin_molecules_test.go
Normal file
143
internal/beads/builtin_molecules_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package beads
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBuiltinMolecules(t *testing.T) {
|
||||
molecules := BuiltinMolecules()
|
||||
|
||||
if len(molecules) != 3 {
|
||||
t.Errorf("expected 3 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user