feat(config): add directory-aware label scoping for monorepos (fixes #541)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/steveyegge/beads/internal/config"
|
||||||
"github.com/steveyegge/beads/internal/rpc"
|
"github.com/steveyegge/beads/internal/rpc"
|
||||||
"github.com/steveyegge/beads/internal/storage"
|
"github.com/steveyegge/beads/internal/storage"
|
||||||
"github.com/steveyegge/beads/internal/types"
|
"github.com/steveyegge/beads/internal/types"
|
||||||
@@ -148,7 +149,14 @@ var listCmd = &cobra.Command{
|
|||||||
|
|
||||||
// Normalize labels: trim, dedupe, remove empty
|
// Normalize labels: trim, dedupe, remove empty
|
||||||
labels = util.NormalizeLabels(labels)
|
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{
|
filter := types.IssueFilter{
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/steveyegge/beads/internal/config"
|
||||||
"github.com/steveyegge/beads/internal/rpc"
|
"github.com/steveyegge/beads/internal/rpc"
|
||||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
"github.com/steveyegge/beads/internal/storage/sqlite"
|
||||||
"github.com/steveyegge/beads/internal/types"
|
"github.com/steveyegge/beads/internal/types"
|
||||||
@@ -27,6 +30,13 @@ var readyCmd = &cobra.Command{
|
|||||||
labels = util.NormalizeLabels(labels)
|
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.WorkFilter{
|
filter := types.WorkFilter{
|
||||||
// Leave Status empty to get both 'open' and 'in_progress' (bd-165)
|
// Leave Status empty to get both 'open' and 'in_progress' (bd-165)
|
||||||
Type: issueType,
|
Type: issueType,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ Tool-level settings you can configure:
|
|||||||
| `create.require-description` | - | `BD_CREATE_REQUIRE_DESCRIPTION` | `false` | Require description when creating issues |
|
| `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.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 |
|
| `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 |
|
| `db` | `--db` | `BD_DB` | (auto-discover) | Database path |
|
||||||
| `actor` | `--actor` | `BD_ACTOR` | `$USER` | Actor name for audit trail |
|
| `actor` | `--actor` | `BD_ACTOR` | `$USER` | Actor name for audit trail |
|
||||||
| `flush-debounce` | - | `BEADS_FLUSH_DEBOUNCE` | `5s` | Debounce time for auto-flush |
|
| `flush-debounce` | - | `BEADS_FLUSH_DEBOUNCE` | `5s` | Debounce time for auto-flush |
|
||||||
@@ -84,6 +85,15 @@ create:
|
|||||||
git:
|
git:
|
||||||
author: "beads-bot <beads@example.com>" # Override commit author
|
author: "beads-bot <beads@example.com>" # Override commit author
|
||||||
no-gpg-sign: true # Disable GPG signing
|
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?
|
### Why Two Systems?
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ func Initialize() error {
|
|||||||
v.SetDefault("git.author", "") // Override commit author (e.g., "beads-bot <beads@example.com>")
|
v.SetDefault("git.author", "") // Override commit author (e.g., "beads-bot <beads@example.com>")
|
||||||
v.SetDefault("git.no-gpg-sign", false) // Disable GPG signing for beads commits
|
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
|
// Read config file if it was found
|
||||||
if configFileSet {
|
if configFileSet {
|
||||||
if err := v.ReadInConfig(); err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
@@ -196,6 +200,44 @@ func GetStringSlice(key string) []string {
|
|||||||
return v.GetStringSlice(key)
|
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
|
// MultiRepoConfig contains configuration for multi-repo support
|
||||||
type MultiRepoConfig struct {
|
type MultiRepoConfig struct {
|
||||||
Primary string // Primary repo path (where canonical issues live)
|
Primary string // Primary repo path (where canonical issues live)
|
||||||
|
|||||||
Reference in New Issue
Block a user