feat(mol): add compound visualization in bd mol show (bd-iw4z)

Add visualization for compound molecules (those created by bonding protos):

- Detect compound molecules via IsCompound() check on root issue
- Display "Bonded from:" section showing constituent protos with bond types
- Show bond point when specified (attachment site within molecule)
- Format bond types as human-readable: sequential, parallel, on-failure, root
- Include is_compound and bonded_from in JSON output

Example output for compound molecules:
  🧪 Compound: proto-feature-with-tests
     ID: bd-abc123
     Steps: 5

  🔗 Bonded from:
     ├── proto-feature (sequential)
     └── proto-testing (sequential, at step-2)

Added unit tests for compound detection and bond type formatting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/grip
2026-01-02 13:43:07 -08:00
committed by Steve Yegge
parent 3d93166b3f
commit bb0a0ea703
2 changed files with 174 additions and 2 deletions

View File

@@ -2527,3 +2527,103 @@ func TestSpawnMoleculeFromFormulaEphemeral(t *testing.T) {
}
}
}
// TestCompoundMoleculeVisualization tests the compound molecule display in mol show
func TestCompoundMoleculeVisualization(t *testing.T) {
// Test IsCompound() and GetConstituents()
tests := []struct {
name string
bondedFrom []types.BondRef
isCompound bool
expectedCount int
}{
{
name: "not a compound - no BondedFrom",
bondedFrom: nil,
isCompound: false,
expectedCount: 0,
},
{
name: "not a compound - empty BondedFrom",
bondedFrom: []types.BondRef{},
isCompound: false,
expectedCount: 0,
},
{
name: "compound with one constituent",
bondedFrom: []types.BondRef{
{SourceID: "proto-a", BondType: types.BondTypeSequential},
},
isCompound: true,
expectedCount: 1,
},
{
name: "compound with two constituents - sequential bond",
bondedFrom: []types.BondRef{
{SourceID: "proto-a", BondType: types.BondTypeSequential},
{SourceID: "proto-b", BondType: types.BondTypeSequential},
},
isCompound: true,
expectedCount: 2,
},
{
name: "compound with parallel bond",
bondedFrom: []types.BondRef{
{SourceID: "proto-a", BondType: types.BondTypeParallel},
{SourceID: "proto-b", BondType: types.BondTypeParallel},
},
isCompound: true,
expectedCount: 2,
},
{
name: "compound with bond point",
bondedFrom: []types.BondRef{
{SourceID: "proto-a", BondType: types.BondTypeSequential, BondPoint: "step-2"},
},
isCompound: true,
expectedCount: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
issue := &types.Issue{
ID: "test-compound",
Title: "Test Compound Molecule",
BondedFrom: tt.bondedFrom,
}
if got := issue.IsCompound(); got != tt.isCompound {
t.Errorf("IsCompound() = %v, want %v", got, tt.isCompound)
}
constituents := issue.GetConstituents()
if len(constituents) != tt.expectedCount {
t.Errorf("GetConstituents() returned %d items, want %d", len(constituents), tt.expectedCount)
}
})
}
}
// TestFormatBondType tests the formatBondType helper function
func TestFormatBondType(t *testing.T) {
tests := []struct {
bondType string
expected string
}{
{types.BondTypeSequential, "sequential"},
{types.BondTypeParallel, "parallel"},
{types.BondTypeConditional, "on-failure"},
{types.BondTypeRoot, "root"},
{"", "default"},
{"custom-type", "custom-type"},
}
for _, tt := range tests {
t.Run(tt.bondType, func(t *testing.T) {
if got := formatBondType(tt.bondType); got != tt.expected {
t.Errorf("formatBondType(%q) = %q, want %q", tt.bondType, got, tt.expected)
}
})
}
}