From 244ba1471b6ac5ac841bc7675a3f5562c77df5a9 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sat, 20 Dec 2025 03:41:09 -0800 Subject: [PATCH] feat(config): add directory-aware label scoping for monorepos (fixes #541) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds automatic label filtering based on current working directory: - New config option: directory.labels (maps directory patterns to labels) - bd ready and bd list auto-apply label filtering when in matching directory - Uses --label-any semantics (shows issues with any of the matching labels) Config example: ```yaml directory: labels: packages/maverick: maverick packages/agency: agency ``` When running `bd ready` from `packages/maverick/`, issues labeled "maverick" are automatically shown first, without needing --label-any. Closes #541 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cmd/bd/list.go | 10 +++++++++- cmd/bd/ready.go | 10 ++++++++++ docs/CONFIG.md | 10 ++++++++++ internal/config/config.go | 42 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/cmd/bd/list.go b/cmd/bd/list.go index 471cb54c..91a96a13 100644 --- a/cmd/bd/list.go +++ b/cmd/bd/list.go @@ -12,6 +12,7 @@ import ( "time" "github.com/spf13/cobra" + "github.com/steveyegge/beads/internal/config" "github.com/steveyegge/beads/internal/rpc" "github.com/steveyegge/beads/internal/storage" "github.com/steveyegge/beads/internal/types" @@ -148,7 +149,14 @@ var listCmd = &cobra.Command{ // Normalize labels: trim, dedupe, remove empty labels = util.NormalizeLabels(labels) - labelsAny = util.NormalizeLabels(labelsAny) + labelsAny = util.NormalizeLabels(labelsAny) + + // Apply directory-aware label scoping if no labels explicitly provided (GH#541) + if len(labels) == 0 && len(labelsAny) == 0 { + if dirLabels := config.GetDirectoryLabels(); len(dirLabels) > 0 { + labelsAny = dirLabels + } + } filter := types.IssueFilter{ Limit: limit, diff --git a/cmd/bd/ready.go b/cmd/bd/ready.go index 79eac289..4376b685 100644 --- a/cmd/bd/ready.go +++ b/cmd/bd/ready.go @@ -1,10 +1,13 @@ package main + import ( "encoding/json" "fmt" "os" + "github.com/fatih/color" "github.com/spf13/cobra" + "github.com/steveyegge/beads/internal/config" "github.com/steveyegge/beads/internal/rpc" "github.com/steveyegge/beads/internal/storage/sqlite" "github.com/steveyegge/beads/internal/types" @@ -27,6 +30,13 @@ var readyCmd = &cobra.Command{ labels = util.NormalizeLabels(labels) labelsAny = util.NormalizeLabels(labelsAny) + // Apply directory-aware label scoping if no labels explicitly provided (GH#541) + if len(labels) == 0 && len(labelsAny) == 0 { + if dirLabels := config.GetDirectoryLabels(); len(dirLabels) > 0 { + labelsAny = dirLabels + } + } + filter := types.WorkFilter{ // Leave Status empty to get both 'open' and 'in_progress' (bd-165) Type: issueType, diff --git a/docs/CONFIG.md b/docs/CONFIG.md index ce111f19..15013019 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -38,6 +38,7 @@ Tool-level settings you can configure: | `create.require-description` | - | `BD_CREATE_REQUIRE_DESCRIPTION` | `false` | Require description when creating issues | | `git.author` | - | `BD_GIT_AUTHOR` | (none) | Override commit author for beads commits | | `git.no-gpg-sign` | - | `BD_GIT_NO_GPG_SIGN` | `false` | Disable GPG signing for beads commits | +| `directory.labels` | - | - | (none) | Map directories to labels for automatic filtering | | `db` | `--db` | `BD_DB` | (auto-discover) | Database path | | `actor` | `--actor` | `BD_ACTOR` | `$USER` | Actor name for audit trail | | `flush-debounce` | - | `BEADS_FLUSH_DEBOUNCE` | `5s` | Debounce time for auto-flush | @@ -84,6 +85,15 @@ create: git: author: "beads-bot " # Override commit author no-gpg-sign: true # Disable GPG signing + +# Directory-aware label scoping for monorepos (GH#541) +# When running bd ready/list from a matching directory, issues with +# that label are automatically shown (as if --label-any was passed) +directory: + labels: + packages/maverick: maverick + packages/agency: agency + packages/io: io ``` ### Why Two Systems? diff --git a/internal/config/config.go b/internal/config/config.go index 08b803b3..40fb096b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -116,6 +116,10 @@ func Initialize() error { v.SetDefault("git.author", "") // Override commit author (e.g., "beads-bot ") v.SetDefault("git.no-gpg-sign", false) // Disable GPG signing for beads commits + // Directory-aware label scoping (GH#541) + // Maps directory patterns to labels for automatic filtering in monorepos + v.SetDefault("directory.labels", map[string]string{}) + // Read config file if it was found if configFileSet { if err := v.ReadInConfig(); err != nil { @@ -196,6 +200,44 @@ func GetStringSlice(key string) []string { return v.GetStringSlice(key) } +// GetStringMapString retrieves a map[string]string configuration value +func GetStringMapString(key string) map[string]string { + if v == nil { + return map[string]string{} + } + return v.GetStringMapString(key) +} + +// GetDirectoryLabels returns labels for the current working directory based on config. +// It checks directory.labels config for matching patterns. +// Returns nil if no labels are configured for the current directory. +func GetDirectoryLabels() []string { + cwd, err := os.Getwd() + if err != nil { + return nil + } + + dirLabels := GetStringMapString("directory.labels") + if len(dirLabels) == 0 { + return nil + } + + // Check each configured directory pattern + for pattern, label := range dirLabels { + // Support both exact match and suffix match + // e.g., "packages/maverick" matches "/path/to/repo/packages/maverick" + if strings.HasSuffix(cwd, pattern) || strings.HasSuffix(cwd, filepath.Clean(pattern)) { + return []string{label} + } + // Also try as a path prefix (user might be in a subdirectory) + if strings.Contains(cwd, "/"+pattern+"/") || strings.Contains(cwd, "/"+pattern) { + return []string{label} + } + } + + return nil +} + // MultiRepoConfig contains configuration for multi-repo support type MultiRepoConfig struct { Primary string // Primary repo path (where canonical issues live)