feat: add display guards for large molecules in bd mol current (bd-vln0)

For molecules with >100 steps, shows summary instead of full step list:
- Counts children first using efficient GetMoleculeProgress query
- Shows progress summary with pointer to bd mol progress
- Add --limit flag to cap output (e.g., --limit 50)
- Add --range flag for specific ranges (e.g., --range 1-50)

Also closes epic bd-5nu1 (mega-molecule progress support).

🤖 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/fang
2025-12-31 13:08:41 -08:00
committed by Steve Yegge
parent f3224278a8
commit 7c9b975436
2 changed files with 123 additions and 4 deletions

View File

@@ -6,6 +6,8 @@ import (
"fmt"
"os"
"sort"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/rpc"
@@ -15,6 +17,10 @@ import (
"github.com/steveyegge/beads/internal/utils"
)
// LargeMoleculeThreshold is the step count above which we show summary instead of full list.
// This prevents overwhelming output and slow queries for mega-molecules.
const LargeMoleculeThreshold = 100
// MoleculeProgress holds the progress information for a molecule
type MoleculeProgress struct {
MoleculeID string `json:"molecule_id"`
@@ -47,11 +53,18 @@ The output shows all steps with status indicators:
[current] - Step is in_progress (you are here)
[ready] - Step is ready to start (unblocked)
[blocked] - Step is blocked by dependencies
[pending] - Step is waiting`,
[pending] - Step is waiting
For large molecules (>100 steps), a summary is shown instead.
Use --limit or --range to view specific steps:
bd mol current <id> --limit 50 # Show first 50 steps
bd mol current <id> --range 100-150 # Show steps 100-150`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ctx := rootCtx
forAgent, _ := cmd.Flags().GetString("for")
limit, _ := cmd.Flags().GetInt("limit")
rangeStr, _ := cmd.Flags().GetString("range")
// Determine who we're looking for
agent := forAgent
@@ -70,6 +83,20 @@ The output shows all steps with status indicators:
os.Exit(1)
}
// Parse range flag if provided
var rangeStart, rangeEnd int
if rangeStr != "" {
var err error
rangeStart, rangeEnd, err = parseRange(rangeStr)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: invalid range '%s': %v\n", rangeStr, err)
os.Exit(1)
}
}
// Determine if user explicitly requested steps
explicitSteps := limit > 0 || rangeStr != ""
var molecules []*MoleculeProgress
if len(args) == 1 {
@@ -80,11 +107,32 @@ The output shows all steps with status indicators:
os.Exit(1)
}
// Check child count first for large molecule detection
stats, err := store.GetMoleculeProgress(ctx, moleculeID)
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading molecule: %v\n", err)
os.Exit(1)
}
// If large molecule and no explicit flags, show summary
if stats.Total > LargeMoleculeThreshold && !explicitSteps && !jsonOutput {
printLargeMoleculeSummary(stats)
return
}
progress, err := getMoleculeProgress(ctx, store, moleculeID)
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading molecule: %v\n", err)
os.Exit(1)
}
// Apply limit or range filtering
if rangeStr != "" {
progress.Steps = filterStepsByRange(progress.Steps, rangeStart, rangeEnd)
} else if limit > 0 && len(progress.Steps) > limit {
progress.Steps = progress.Steps[:limit]
}
molecules = append(molecules, progress)
} else {
// Infer from in_progress issues
@@ -480,7 +528,76 @@ func PrintContinueResult(result *ContinueResult) {
}
}
// parseRange parses a range string like "1-50" or "100-150" into start and end indices.
// Returns 1-based indices (start=1 means first step).
func parseRange(rangeStr string) (start, end int, err error) {
parts := strings.Split(rangeStr, "-")
if len(parts) != 2 {
return 0, 0, fmt.Errorf("expected format 'start-end' (e.g., '1-50')")
}
start, err = strconv.Atoi(strings.TrimSpace(parts[0]))
if err != nil {
return 0, 0, fmt.Errorf("invalid start: %w", err)
}
end, err = strconv.Atoi(strings.TrimSpace(parts[1]))
if err != nil {
return 0, 0, fmt.Errorf("invalid end: %w", err)
}
if start < 1 {
return 0, 0, fmt.Errorf("start must be >= 1")
}
if end < start {
return 0, 0, fmt.Errorf("end must be >= start")
}
return start, end, nil
}
// filterStepsByRange filters steps to a 1-based range [start, end].
func filterStepsByRange(steps []*StepStatus, start, end int) []*StepStatus {
// Convert to 0-based indices
startIdx := start - 1
endIdx := end
if startIdx >= len(steps) {
return nil
}
if endIdx > len(steps) {
endIdx = len(steps)
}
return steps[startIdx:endIdx]
}
// printLargeMoleculeSummary prints a summary for molecules with many steps.
func printLargeMoleculeSummary(stats *types.MoleculeProgressStats) {
fmt.Printf("Molecule: %s\n", ui.RenderAccent(stats.MoleculeID))
fmt.Printf(" %s\n", stats.MoleculeTitle)
fmt.Println()
// Progress summary
var percent float64
if stats.Total > 0 {
percent = float64(stats.Completed) * 100 / float64(stats.Total)
}
fmt.Printf("Progress: %d / %d steps (%.1f%%)\n", stats.Completed, stats.Total, percent)
if stats.CurrentStepID != "" {
fmt.Printf("Current step: %s\n", stats.CurrentStepID)
} else if stats.InProgress > 0 {
fmt.Printf("In progress: %d step(s)\n", stats.InProgress)
}
fmt.Println()
fmt.Printf("%s This molecule has %d steps (threshold: %d).\n",
ui.RenderWarn("Note:"), stats.Total, LargeMoleculeThreshold)
fmt.Println("To view steps, use one of:")
fmt.Printf(" bd mol current %s --limit 50 # First 50 steps\n", stats.MoleculeID)
fmt.Printf(" bd mol current %s --range 1-50 # Steps 1-50\n", stats.MoleculeID)
fmt.Printf(" bd mol progress %s # Efficient progress summary\n", stats.MoleculeID)
}
func init() {
molCurrentCmd.Flags().String("for", "", "Show molecules for a specific agent/assignee")
molCurrentCmd.Flags().Int("limit", 0, "Maximum number of steps to display (0 = auto, use 'all' threshold)")
molCurrentCmd.Flags().String("range", "", "Display specific step range (e.g., '1-50', '100-150')")
molCmd.AddCommand(molCurrentCmd)
}