fix: respect hierarchy.max-depth config setting (GH#995) (#997)

* fix: respect hierarchy.max-depth config setting (GH#995)

The hierarchy.max-depth config setting was being ignored because storage
implementations had the depth limit hardcoded to 3. This fix:

- Registers hierarchy.max-depth default (3) in config initialization
- Adds hierarchy.max-depth to yaml-only keys for config.yaml storage
- Updates SQLite and Memory storage to read max depth from config
- Adds validation to reject hierarchy.max-depth values < 1
- Adds tests for configurable hierarchy depth

Users can now set deeper hierarchies:
  bd config set hierarchy.max-depth 10

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: extract shared CheckHierarchyDepth function (GH#995)

- Extract duplicated depth-checking logic to types.CheckHierarchyDepth()
- Update sqlite and memory storage backends to use shared function
- Add t.Cleanup() for proper test isolation in sqlite test
- Add equivalent test coverage for memory storage backend
- Add comprehensive unit tests for CheckHierarchyDepth function

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Erick Matsen
2026-01-10 13:36:52 -08:00
committed by GitHub
parent 69dae103db
commit 3f2b693bea
9 changed files with 347 additions and 43 deletions

View File

@@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/steveyegge/beads/internal/config"
"github.com/steveyegge/beads/internal/types"
)
@@ -1276,3 +1277,76 @@ func TestGetIssueByExternalRefLoadFromIssues(t *testing.T) {
t.Errorf("Expected to find bd-2 by external ref jira#200")
}
}
// TestGetNextChildID_ConfigurableMaxDepth tests that hierarchy.max-depth config is respected (GH#995)
func TestGetNextChildID_ConfigurableMaxDepth(t *testing.T) {
// Initialize config for testing
if err := config.Initialize(); err != nil {
t.Fatalf("failed to initialize config: %v", err)
}
// Ensure config is reset even if test fails or panics
t.Cleanup(func() {
config.Set("hierarchy.max-depth", 3)
})
store := setupTestMemory(t)
defer store.Close()
ctx := context.Background()
// Create a chain of issues up to depth 3
issues := []struct {
id string
title string
}{
{"bd-depth", "Root"},
{"bd-depth.1", "Level 1"},
{"bd-depth.1.1", "Level 2"},
{"bd-depth.1.1.1", "Level 3"},
}
for _, issue := range issues {
iss := &types.Issue{
ID: issue.id,
Title: issue.title,
Description: "Test issue",
Status: types.StatusOpen,
Priority: 1,
IssueType: types.TypeTask,
}
if err := store.CreateIssue(ctx, iss, "test"); err != nil {
t.Fatalf("failed to create issue %s: %v", issue.id, err)
}
}
// Test 1: With default max-depth (3), depth 4 should fail
config.Set("hierarchy.max-depth", 3)
_, err := store.GetNextChildID(ctx, "bd-depth.1.1.1")
if err == nil {
t.Errorf("expected error for depth 4 with max-depth=3, got nil")
}
if err != nil && err.Error() != "maximum hierarchy depth (3) exceeded for parent bd-depth.1.1.1" {
t.Errorf("unexpected error message: %v", err)
}
// Test 2: With max-depth=5, depth 4 should succeed
config.Set("hierarchy.max-depth", 5)
childID, err := store.GetNextChildID(ctx, "bd-depth.1.1.1")
if err != nil {
t.Errorf("depth 4 should be allowed with max-depth=5, got error: %v", err)
}
expectedID := "bd-depth.1.1.1.1"
if childID != expectedID {
t.Errorf("expected %s, got %s", expectedID, childID)
}
// Test 3: With max-depth=2, depth 3 should fail
config.Set("hierarchy.max-depth", 2)
_, err = store.GetNextChildID(ctx, "bd-depth.1.1")
if err == nil {
t.Errorf("expected error for depth 3 with max-depth=2, got nil")
}
if err != nil && err.Error() != "maximum hierarchy depth (2) exceeded for parent bd-depth.1.1" {
t.Errorf("unexpected error message: %v", err)
}
}