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

@@ -12,6 +12,7 @@ import (
"sync"
"time"
"github.com/steveyegge/beads/internal/config"
"github.com/steveyegge/beads/internal/storage"
"github.com/steveyegge/beads/internal/types"
)
@@ -21,14 +22,14 @@ type MemoryStorage struct {
mu sync.RWMutex // Protects all maps
// Core data
issues map[string]*types.Issue // ID -> Issue
issues map[string]*types.Issue // ID -> Issue
dependencies map[string][]*types.Dependency // IssueID -> Dependencies
labels map[string][]string // IssueID -> Labels
events map[string][]*types.Event // IssueID -> Events
comments map[string][]*types.Comment // IssueID -> Comments
config map[string]string // Config key-value pairs
metadata map[string]string // Metadata key-value pairs
counters map[string]int // Prefix -> Last ID
labels map[string][]string // IssueID -> Labels
events map[string][]*types.Event // IssueID -> Events
comments map[string][]*types.Comment // IssueID -> Comments
config map[string]string // Config key-value pairs
metadata map[string]string // Metadata key-value pairs
counters map[string]int // Prefix -> Last ID
// Indexes for O(1) lookups
externalRefToID map[string]string // ExternalRef -> IssueID
@@ -1598,10 +1599,9 @@ func (m *MemoryStorage) GetNextChildID(ctx context.Context, parentID string) (st
return "", fmt.Errorf("parent issue %s does not exist", parentID)
}
// Calculate depth (count dots)
depth := strings.Count(parentID, ".")
if depth >= 3 {
return "", fmt.Errorf("maximum hierarchy depth (3) exceeded for parent %s", parentID)
// Check hierarchy depth limit (GH#995)
if err := types.CheckHierarchyDepth(parentID, config.GetInt("hierarchy.max-depth")); err != nil {
return "", err
}
// Get or initialize counter for this parent