fix(list): stabilize tree ordering for consistent --tree output (#1228)
Motivation: The ○ bd-e3r ● P2 Responsible-Vibe Development: beads ├── ○ bd-e3r.7 ● P1 Test feature B │ ├── ○ bd-e3r.7.1 ● P2 Subfeature B1 │ └── ○ bd-e3r.7.2 ● P2 Subfeature B2 ├── ○ bd-e3r.6 ● P1 Test feature A │ ├── ○ bd-e3r.6.2 ● P2 Subfeature A2 │ └── ○ bd-e3r.6.1 ● P2 Subfeature A1 │ ├── ○ bd-e3r.6.1.1 ● P3 Task A1.1 │ └── ○ bd-e3r.6.1.2 ● P3 Task A1.2 ├── ○ bd-e3r.8 ● P2 Test identical priority A ├── ○ bd-e3r.9 ● P2 Test identical priority B ├── ○ bd-e3r.1 ● P3 Reproduce ├── ○ bd-e3r.2 ● P3 Analyze ├── ○ bd-e3r.4 ● P3 Verify ├── ○ bd-e3r.5 ● P3 Finalize └── ○ bd-e3r.3 ● P3 Fix -------------------------------------------------------------------------------- Total: 16 issues (16 open, 0 in progress) Status: ○ open ◐ in_progress ● blocked ✓ closed ❄ deferred command produced non-deterministic ordering between consecutive executions, making [?1049h[1;24r(B[m[4l[?7h[?25l[H[2J[2d[AEvery 2.0s: bd list --tree[1;56Hbwpm-D6KDQ60Q6R: 08:29:46[2;68Hin 0.343s (0)[3;1H○ bd-e3r ● P2 Responsible-Vibe Development: beads [4d├── ○ bd-e3r.6 ● P1 Test feature A [5d│ ├── ○ bd-e3r.6.1 ● P2 Subfeature A1 [6d│ │ ├── ○ bd-e3r.6.1.1 ● P3 Task A1.1 [7d│ │ └── ○ bd-e3r.6.1.2 ● P3 Task A1.2 [8d│ └── ○ bd-e3r.6.2 ● P2 Subfeature A2 [9d├── ○ bd-e3r.7 ● P1 Test feature B [10d│ ├── ○ bd-e3r.7.2 ● P2 Subfeature B2 [11d│ └── ○ bd-e3r.7.1 ● P2 Subfeature B1 [12d├── ○ bd-e3r.8 ● P2 Test identical priority A [13d├── ○ bd-e3r.9 ● P2 Test identical priority B [14d├── ○ bd-e3r.1 ● P3 Reproduce [15d├── ○ bd-e3r.4 ● P3 Verify [16d├── ○ bd-e3r.5 ● P3 Finalize [17d├── ○ bd-e3r.2 ● P3 Analyze [18d└── ○ bd-e3r.3 ● P3 Fix [20d--------------------------------------------------------------------------------[21;1HTotal: 16 issues (16 open, 0 in progress) [23dStatus: ○ open ◐ in_progress ● blocked ✓ closed ❄ deferred [3d[24;1H[?12l[?25h[?1049l [?1l> unusable due to constantly changing output. Root issues and children with identical priorities appeared in different orders across runs. Key Changes: - Add compareIssuesByPriority() function with primary sort by priority and secondary sort by ID for deterministic behavior - Apply stable sorting to root issues in buildIssueTreeWithDeps() - Apply stable sorting to children in childrenMap for complete consistency - Update printPrettyTree() to use same comparison function Side-Effects: - Tree output now consistently orders by priority (P0→P1→P2→P3→P4) - Items with identical priority are sorted alphabetically by ID - Adds comprehensive TestStableTreeOrdering test with 5-run stability verification - Minor performance overhead from sorting (negligible for typical issue counts) - Fixes indentation inconsistencies in existing test code via gofmt
This commit is contained in:
@@ -247,18 +247,37 @@ func buildIssueTreeWithDeps(issues []*types.Issue, allDeps map[string][]*types.D
|
||||
}
|
||||
}
|
||||
|
||||
// Sort roots for stable tree ordering (fixes unstable --tree output)
|
||||
// Use same sorting logic as children for consistency
|
||||
slices.SortFunc(roots, compareIssuesByPriority)
|
||||
|
||||
// Sort children within each parent for stable ordering in data structure
|
||||
for parentID := range childrenMap {
|
||||
slices.SortFunc(childrenMap[parentID], compareIssuesByPriority)
|
||||
}
|
||||
|
||||
return roots, childrenMap
|
||||
}
|
||||
|
||||
// compareIssuesByPriority provides stable sorting for tree display
|
||||
// Primary sort: priority (P0 before P1 before P2...)
|
||||
// Secondary sort: ID for deterministic ordering when priorities match
|
||||
func compareIssuesByPriority(a, b *types.Issue) int {
|
||||
// Primary: priority (ascending: P0 before P1 before P2...)
|
||||
if result := cmp.Compare(a.Priority, b.Priority); result != 0 {
|
||||
return result
|
||||
}
|
||||
// Secondary: ID for deterministic order when priorities match
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
}
|
||||
|
||||
// printPrettyTree recursively prints the issue tree
|
||||
// Children are sorted by priority (P0 first) for intuitive reading
|
||||
func printPrettyTree(childrenMap map[string][]*types.Issue, parentID string, prefix string) {
|
||||
children := childrenMap[parentID]
|
||||
|
||||
// Sort children by priority (ascending: P0 before P1 before P2...)
|
||||
slices.SortFunc(children, func(a, b *types.Issue) int {
|
||||
return cmp.Compare(a.Priority, b.Priority)
|
||||
})
|
||||
// Sort children by priority using same comparison as roots for consistency
|
||||
slices.SortFunc(children, compareIssuesByPriority)
|
||||
|
||||
for i, child := range children {
|
||||
isLast := i == len(children)-1
|
||||
|
||||
Reference in New Issue
Block a user