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:
committed by
Steve Yegge
parent
3d93166b3f
commit
bb0a0ea703
@@ -70,14 +70,27 @@ func showMolecule(subgraph *MoleculeSubgraph) {
|
|||||||
"issues": subgraph.Issues,
|
"issues": subgraph.Issues,
|
||||||
"dependencies": subgraph.Dependencies,
|
"dependencies": subgraph.Dependencies,
|
||||||
"variables": extractAllVariables(subgraph),
|
"variables": extractAllVariables(subgraph),
|
||||||
|
"is_compound": subgraph.Root.IsCompound(),
|
||||||
|
"bonded_from": subgraph.Root.BondedFrom,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s Molecule: %s\n", ui.RenderAccent("🧪"), subgraph.Root.Title)
|
// Determine molecule type label
|
||||||
|
moleculeType := "Molecule"
|
||||||
|
if subgraph.Root.IsCompound() {
|
||||||
|
moleculeType = "Compound"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n%s %s: %s\n", ui.RenderAccent("🧪"), moleculeType, subgraph.Root.Title)
|
||||||
fmt.Printf(" ID: %s\n", subgraph.Root.ID)
|
fmt.Printf(" ID: %s\n", subgraph.Root.ID)
|
||||||
fmt.Printf(" Steps: %d\n", len(subgraph.Issues))
|
fmt.Printf(" Steps: %d\n", len(subgraph.Issues))
|
||||||
|
|
||||||
|
// Show compound bonding info if this is a compound molecule
|
||||||
|
if subgraph.Root.IsCompound() {
|
||||||
|
showCompoundBondingInfo(subgraph.Root)
|
||||||
|
}
|
||||||
|
|
||||||
vars := extractAllVariables(subgraph)
|
vars := extractAllVariables(subgraph)
|
||||||
if len(vars) > 0 {
|
if len(vars) > 0 {
|
||||||
fmt.Printf("\n%s Variables:\n", ui.RenderWarn("📝"))
|
fmt.Printf("\n%s Variables:\n", ui.RenderWarn("📝"))
|
||||||
@@ -91,6 +104,52 @@ func showMolecule(subgraph *MoleculeSubgraph) {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// showCompoundBondingInfo displays the bonding lineage for compound molecules
|
||||||
|
func showCompoundBondingInfo(root *types.Issue) {
|
||||||
|
if !root.IsCompound() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
constituents := root.GetConstituents()
|
||||||
|
fmt.Printf("\n%s Bonded from:\n", ui.RenderAccent("🔗"))
|
||||||
|
|
||||||
|
for i, ref := range constituents {
|
||||||
|
connector := "├──"
|
||||||
|
if i == len(constituents)-1 {
|
||||||
|
connector = "└──"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format bond type for display
|
||||||
|
bondTypeDisplay := formatBondType(ref.BondType)
|
||||||
|
|
||||||
|
// Show source ID with bond type
|
||||||
|
if ref.BondPoint != "" {
|
||||||
|
fmt.Printf(" %s %s (%s, at %s)\n", connector, ref.SourceID, bondTypeDisplay, ref.BondPoint)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" %s %s (%s)\n", connector, ref.SourceID, bondTypeDisplay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatBondType returns a human-readable bond type description
|
||||||
|
func formatBondType(bondType string) string {
|
||||||
|
switch bondType {
|
||||||
|
case types.BondTypeSequential:
|
||||||
|
return "sequential"
|
||||||
|
case types.BondTypeParallel:
|
||||||
|
return "parallel"
|
||||||
|
case types.BondTypeConditional:
|
||||||
|
return "on-failure"
|
||||||
|
case types.BondTypeRoot:
|
||||||
|
return "root"
|
||||||
|
default:
|
||||||
|
if bondType == "" {
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
return bondType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ParallelInfo holds parallel analysis information for a step
|
// ParallelInfo holds parallel analysis information for a step
|
||||||
type ParallelInfo struct {
|
type ParallelInfo struct {
|
||||||
StepID string `json:"step_id"`
|
StepID string `json:"step_id"`
|
||||||
@@ -327,14 +386,27 @@ func showMoleculeWithParallel(subgraph *MoleculeSubgraph) {
|
|||||||
"dependencies": subgraph.Dependencies,
|
"dependencies": subgraph.Dependencies,
|
||||||
"variables": extractAllVariables(subgraph),
|
"variables": extractAllVariables(subgraph),
|
||||||
"parallel": analysis,
|
"parallel": analysis,
|
||||||
|
"is_compound": subgraph.Root.IsCompound(),
|
||||||
|
"bonded_from": subgraph.Root.BondedFrom,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s Molecule: %s\n", ui.RenderAccent("🧪"), subgraph.Root.Title)
|
// Determine molecule type label
|
||||||
|
moleculeType := "Molecule"
|
||||||
|
if subgraph.Root.IsCompound() {
|
||||||
|
moleculeType = "Compound"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n%s %s: %s\n", ui.RenderAccent("🧪"), moleculeType, subgraph.Root.Title)
|
||||||
fmt.Printf(" ID: %s\n", subgraph.Root.ID)
|
fmt.Printf(" ID: %s\n", subgraph.Root.ID)
|
||||||
fmt.Printf(" Steps: %d (%d ready)\n", analysis.TotalSteps, analysis.ReadySteps)
|
fmt.Printf(" Steps: %d (%d ready)\n", analysis.TotalSteps, analysis.ReadySteps)
|
||||||
|
|
||||||
|
// Show compound bonding info if this is a compound molecule
|
||||||
|
if subgraph.Root.IsCompound() {
|
||||||
|
showCompoundBondingInfo(subgraph.Root)
|
||||||
|
}
|
||||||
|
|
||||||
// Show parallel groups summary
|
// Show parallel groups summary
|
||||||
if len(analysis.ParallelGroups) > 0 {
|
if len(analysis.ParallelGroups) > 0 {
|
||||||
fmt.Printf("\n%s Parallel Groups:\n", ui.RenderPass("⚡"))
|
fmt.Printf("\n%s Parallel Groups:\n", ui.RenderPass("⚡"))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user