fix: misc improvements and dependency updates

- Update nix vendorHash after fatih/color removal
- Bump version to 0.30.7
- Add GroupID to remaining commands for proper cobra grouping
- Apply semantic color rendering to list and stale commands
- Update pre-commit hook template
This commit is contained in:
Ryan Snodgrass
2025-12-20 13:05:32 -08:00
parent 6ca141712c
commit acfdcebc0f
28 changed files with 168 additions and 73 deletions
+6 -4
View File
@@ -14,8 +14,9 @@ import (
)
var commentsCmd = &cobra.Command{
Use: "comments [issue-id]",
Short: "View or manage comments on an issue",
Use: "comments [issue-id]",
GroupID: "issues",
Short: "View or manage comments on an issue",
Long: `View or manage comments on an issue.
Examples:
@@ -215,8 +216,9 @@ Examples:
// commentCmd is a top-level alias for commentsAddCmd
var commentCmd = &cobra.Command{
Use: "comment [issue-id] [text]",
Short: "Add a comment to an issue (alias for 'comments add')",
Use: "comment [issue-id] [text]",
GroupID: "issues",
Short: "Add a comment to an issue (alias for 'comments add')",
Long: `Add a comment to an issue. This is a convenient alias for 'bd comments add'.
Examples:
+4 -2
View File
@@ -34,9 +34,11 @@ var (
compactLimit int
)
// TODO: Consider consolidating into 'bd doctor --fix' for simpler maintenance UX
var compactCmd = &cobra.Command{
Use: "compact",
Short: "Compact old closed issues to save space",
Use: "compact",
GroupID: "maint",
Short: "Compact old closed issues to save space",
Long: `Compact old closed issues using semantic summarization.
Compaction reduces database size by summarizing closed issues that are no longer
+3 -2
View File
@@ -11,8 +11,9 @@ import (
)
var configCmd = &cobra.Command{
Use: "config",
Short: "Manage configuration settings",
Use: "config",
GroupID: "setup",
Short: "Manage configuration settings",
Long: `Manage configuration settings for external integrations and preferences.
Configuration is stored per-project in .beads/*.db and is version-control-friendly.
+3 -2
View File
@@ -14,8 +14,9 @@ import (
)
var countCmd = &cobra.Command{
Use: "count",
Short: "Count issues matching filters",
Use: "count",
GroupID: "views",
Short: "Count issues matching filters",
Long: `Count issues matching the specified filters.
By default, returns the total count of issues matching the filters.
+3 -2
View File
@@ -20,8 +20,9 @@ import (
)
var daemonCmd = &cobra.Command{
Use: "daemon",
Short: "Manage background sync daemon",
Use: "daemon",
GroupID: "sync",
Short: "Manage background sync daemon",
Long: `Manage the background daemon that automatically syncs issues with git remote.
The daemon will:
+3 -2
View File
@@ -14,8 +14,9 @@ import (
"github.com/steveyegge/beads/internal/daemon"
)
var daemonsCmd = &cobra.Command{
Use: "daemons",
Short: "Manage multiple bd daemons",
Use: "daemons",
GroupID: "sync",
Short: "Manage multiple bd daemons",
Long: `Manage bd daemon processes across all repositories and worktrees.
Subcommands:
list - Show all running daemons
+3 -2
View File
@@ -111,8 +111,9 @@ func validateExportPath(path string) error {
}
var exportCmd = &cobra.Command{
Use: "export",
Short: "Export issues to JSONL format",
Use: "export",
GroupID: "sync",
Short: "Export issues to JSONL format",
Long: `Export all issues to JSON Lines format (one JSON object per line).
Issues are sorted by ID for consistent diffs.
+12 -2
View File
@@ -160,8 +160,9 @@ func FormatHookWarnings(statuses []HookStatus) string {
// Cobra commands
var hooksCmd = &cobra.Command{
Use: "hooks",
Short: "Manage git hooks for bd auto-sync",
Use: "hooks",
GroupID: "setup",
Short: "Manage git hooks for bd auto-sync",
Long: `Install, uninstall, or list git hooks that provide automatic bd sync.
The hooks ensure that:
@@ -407,6 +408,8 @@ func uninstallHooks() error {
// runPreCommitHook flushes pending changes to JSONL before commit.
// Returns 0 on success (or if not applicable), non-zero on error.
//
//nolint:unparam // Always returns 0 by design - warns but doesn't block commits
func runPreCommitHook() int {
// Check if we're in a bd workspace
if _, err := os.Stat(".beads"); os.IsNotExist(err) {
@@ -430,6 +433,7 @@ func runPreCommitHook() int {
// Stage all tracked JSONL files
for _, f := range []string{".beads/beads.jsonl", ".beads/issues.jsonl", ".beads/deletions.jsonl", ".beads/interactions.jsonl"} {
if _, err := os.Stat(f); err == nil {
// #nosec G204 -- f is a fixed string from the hardcoded slice above
gitAdd := exec.Command("git", "add", f)
_ = gitAdd.Run() // Ignore errors - file may not exist
}
@@ -440,6 +444,8 @@ func runPreCommitHook() int {
// runPostMergeHook imports JSONL after pull/merge.
// Returns 0 on success (or if not applicable), non-zero on error.
//
//nolint:unparam // Always returns 0 by design - warns but doesn't block merges
func runPostMergeHook() int {
// Skip during rebase
if isRebaseInProgress() {
@@ -504,6 +510,7 @@ func runPrePushHook() int {
files = append(files, f)
} else {
// Check if tracked by git
// #nosec G204 -- f is a fixed string from the hardcoded slice above
checkCmd := exec.Command("git", "ls-files", "--error-unmatch", f)
if checkCmd.Run() == nil {
files = append(files, f)
@@ -517,6 +524,7 @@ func runPrePushHook() int {
// Check for uncommitted changes using git status
args := append([]string{"status", "--porcelain", "--"}, files...)
// #nosec G204 -- args contains only fixed strings from hardcoded slice
statusCmd := exec.Command("git", args...)
output, _ := statusCmd.Output()
if len(output) > 0 {
@@ -539,6 +547,8 @@ func runPrePushHook() int {
// runPostCheckoutHook imports JSONL after branch checkout.
// args: [previous-HEAD, new-HEAD, flag] where flag=1 for branch checkout
// Returns 0 on success (or if not applicable), non-zero on error.
//
//nolint:unparam // Always returns 0 by design - warns but doesn't block checkouts
func runPostCheckoutHook(args []string) int {
// Only run on branch checkouts (flag=1)
if len(args) >= 3 && args[2] != "1" {
+3 -2
View File
@@ -21,8 +21,9 @@ import (
)
var importCmd = &cobra.Command{
Use: "import",
Short: "Import issues from JSONL format",
Use: "import",
GroupID: "sync",
Short: "Import issues from JSONL format",
Long: `Import issues from JSON Lines format (one JSON object per line).
Reads from stdin by default, or use -i flag for file input.
+3 -2
View File
@@ -12,8 +12,9 @@ import (
)
var infoCmd = &cobra.Command{
Use: "info",
Short: "Show database and daemon information",
Use: "info",
GroupID: "setup",
Short: "Show database and daemon information",
Long: `Display information about the current database path and daemon status.
This command helps debug issues where bd is using an unexpected database
+3 -2
View File
@@ -37,8 +37,9 @@ type JiraSyncResult struct {
}
var jiraCmd = &cobra.Command{
Use: "jira",
Short: "Jira integration commands",
Use: "jira",
GroupID: "advanced",
Short: "Jira integration commands",
Long: `Synchronize issues between beads and Jira.
Configuration:
+3 -2
View File
@@ -18,8 +18,9 @@ import (
// linearCmd is the root command for Linear integration.
var linearCmd = &cobra.Command{
Use: "linear",
Short: "Linear integration commands",
Use: "linear",
GroupID: "advanced",
Short: "Linear integration commands",
Long: `Synchronize issues between beads and Linear.
Configuration:
+68 -12
View File
@@ -16,6 +16,7 @@ import (
"github.com/steveyegge/beads/internal/rpc"
"github.com/steveyegge/beads/internal/storage"
"github.com/steveyegge/beads/internal/types"
"github.com/steveyegge/beads/internal/ui"
"github.com/steveyegge/beads/internal/util"
"github.com/steveyegge/beads/internal/validation"
)
@@ -100,8 +101,9 @@ func sortIssues(issues []*types.Issue, sortBy string, reverse bool) {
}
var listCmd = &cobra.Command{
Use: "list",
Short: "List issues",
Use: "list",
GroupID: "issues",
Short: "List issues",
Run: func(cmd *cobra.Command, args []string) {
status, _ := cmd.Flags().GetString("status")
assignee, _ := cmd.Flags().GetString("assignee")
@@ -422,8 +424,22 @@ var listCmd = &cobra.Command{
// Long format: multi-line with details
fmt.Printf("\nFound %d issues:\n\n", len(issues))
for _, issue := range issues {
fmt.Printf("%s%s [P%d] [%s] %s\n", pinIndicator(issue), issue.ID, issue.Priority, issue.IssueType, issue.Status)
fmt.Printf(" %s\n", issue.Title)
status := string(issue.Status)
if status == "closed" {
// Entire closed issue is dimmed
line := fmt.Sprintf("%s%s [P%d] [%s] %s\n %s",
pinIndicator(issue), issue.ID, issue.Priority,
issue.IssueType, status, issue.Title)
fmt.Println(ui.RenderClosedLine(line))
} else {
fmt.Printf("%s%s [%s] [%s] %s\n",
pinIndicator(issue),
ui.RenderID(issue.ID),
ui.RenderPriority(issue.Priority),
ui.RenderType(string(issue.IssueType)),
ui.RenderStatus(status))
fmt.Printf(" %s\n", issue.Title)
}
if issue.Assignee != "" {
fmt.Printf(" Assignee: %s\n", issue.Assignee)
}
@@ -443,9 +459,22 @@ var listCmd = &cobra.Command{
if issue.Assignee != "" {
assigneeStr = fmt.Sprintf(" @%s", issue.Assignee)
}
fmt.Printf("%s%s [P%d] [%s] %s%s%s - %s\n",
pinIndicator(issue), issue.ID, issue.Priority, issue.IssueType, issue.Status,
assigneeStr, labelsStr, issue.Title)
status := string(issue.Status)
if status == "closed" {
// Entire closed line is dimmed
line := fmt.Sprintf("%s%s [P%d] [%s] %s%s%s - %s",
pinIndicator(issue), issue.ID, issue.Priority,
issue.IssueType, status, assigneeStr, labelsStr, issue.Title)
fmt.Println(ui.RenderClosedLine(line))
} else {
fmt.Printf("%s%s [%s] [%s] %s%s%s - %s\n",
pinIndicator(issue),
ui.RenderID(issue.ID),
ui.RenderPriority(issue.Priority),
ui.RenderType(string(issue.IssueType)),
ui.RenderStatus(status),
assigneeStr, labelsStr, issue.Title)
}
}
}
return
@@ -529,9 +558,23 @@ var listCmd = &cobra.Command{
fmt.Printf("\nFound %d issues:\n\n", len(issues))
for _, issue := range issues {
labels := labelsMap[issue.ID]
status := string(issue.Status)
fmt.Printf("%s%s [P%d] [%s] %s\n", pinIndicator(issue), issue.ID, issue.Priority, issue.IssueType, issue.Status)
fmt.Printf(" %s\n", issue.Title)
if status == "closed" {
// Entire closed issue is dimmed
line := fmt.Sprintf("%s%s [P%d] [%s] %s\n %s",
pinIndicator(issue), issue.ID, issue.Priority,
issue.IssueType, status, issue.Title)
fmt.Println(ui.RenderClosedLine(line))
} else {
fmt.Printf("%s%s [%s] [%s] %s\n",
pinIndicator(issue),
ui.RenderID(issue.ID),
ui.RenderPriority(issue.Priority),
ui.RenderType(string(issue.IssueType)),
ui.RenderStatus(status))
fmt.Printf(" %s\n", issue.Title)
}
if issue.Assignee != "" {
fmt.Printf(" Assignee: %s\n", issue.Assignee)
}
@@ -553,9 +596,22 @@ var listCmd = &cobra.Command{
if issue.Assignee != "" {
assigneeStr = fmt.Sprintf(" @%s", issue.Assignee)
}
fmt.Printf("%s%s [P%d] [%s] %s%s%s - %s\n",
pinIndicator(issue), issue.ID, issue.Priority, issue.IssueType, issue.Status,
assigneeStr, labelsStr, issue.Title)
status := string(issue.Status)
if status == "closed" {
// Entire closed line is dimmed
line := fmt.Sprintf("%s%s [P%d] [%s] %s%s%s - %s",
pinIndicator(issue), issue.ID, issue.Priority,
issue.IssueType, status, assigneeStr, labelsStr, issue.Title)
fmt.Println(ui.RenderClosedLine(line))
} else {
fmt.Printf("%s%s [%s] [%s] %s%s%s - %s\n",
pinIndicator(issue),
ui.RenderID(issue.ID),
ui.RenderPriority(issue.Priority),
ui.RenderType(string(issue.IssueType)),
ui.RenderStatus(status),
assigneeStr, labelsStr, issue.Title)
}
}
}
+3 -2
View File
@@ -15,8 +15,9 @@ import (
)
var mailCmd = &cobra.Command{
Use: "mail",
Short: "Send and receive messages via beads",
Use: "mail",
GroupID: "advanced",
Short: "Send and receive messages via beads",
Long: `Send and receive messages between agents using beads storage.
Messages are stored as issues with type=message, enabling git-native
+3 -2
View File
@@ -16,8 +16,9 @@ var (
)
var mergeCmd = &cobra.Command{
Use: "merge <output> <base> <left> <right>",
Short: "Git merge driver for beads JSONL files",
Use: "merge <output> <base> <left> <right>",
GroupID: "sync",
Short: "Git merge driver for beads JSONL files",
Long: `bd merge is a git merge driver for beads issue tracker JSONL files.
NOTE: This command is for git merge operations, NOT for merging duplicate issues.
+4 -2
View File
@@ -12,9 +12,11 @@ import (
"github.com/steveyegge/beads/internal/storage/sqlite"
)
// TODO: Consider integrating into 'bd doctor' migration detection
var migrateIssuesCmd = &cobra.Command{
Use: "migrate-issues",
Short: "Move issues between repositories",
Use: "migrate-issues",
GroupID: "maint",
Short: "Move issues between repositories",
Long: `Move issues from one source repository to another with filtering and dependency preservation.
This command updates the source_repo field for selected issues, allowing you to:
+4 -2
View File
@@ -13,9 +13,11 @@ import (
"github.com/steveyegge/beads/internal/syncbranch"
)
// TODO: Consider integrating into 'bd doctor' migration detection
var migrateSyncCmd = &cobra.Command{
Use: "migrate-sync <branch-name>",
Short: "Migrate to sync.branch workflow for multi-clone setups",
Use: "migrate-sync <branch-name>",
GroupID: "maint",
Short: "Migrate to sync.branch workflow for multi-clone setups",
Long: `Migrate to using a dedicated sync branch for beads data.
This command configures the repository to commit .beads changes to a separate
+3 -2
View File
@@ -21,8 +21,9 @@ var (
)
var primeCmd = &cobra.Command{
Use: "prime",
Short: "Output AI-optimized workflow context",
Use: "prime",
GroupID: "setup",
Short: "Output AI-optimized workflow context",
Long: `Output essential Beads workflow context in AI-optimized markdown format.
Automatically detects if MCP server is active and adapts output:
+3 -2
View File
@@ -12,8 +12,9 @@ import (
)
var quickCmd = &cobra.Command{
Use: "q [title]",
Short: "Quick capture: create issue and output only ID",
Use: "q [title]",
GroupID: "issues",
Short: "Quick capture: create issue and output only ID",
Long: `Quick capture creates an issue and outputs only the issue ID.
Designed for scripting and AI agent integration.
+3 -2
View File
@@ -12,8 +12,9 @@ import (
)
var repoCmd = &cobra.Command{
Use: "repo",
Short: "Manage multiple repository configuration",
Use: "repo",
GroupID: "advanced",
Short: "Manage multiple repository configuration",
Long: `Configure and manage multiple repository support for multi-clone sync.
Examples:
+3 -2
View File
@@ -15,8 +15,9 @@ import (
)
var searchCmd = &cobra.Command{
Use: "search [query]",
Short: "Search issues by text query",
Use: "search [query]",
GroupID: "issues",
Short: "Search issues by text query",
Long: `Search issues across title, description, and ID.
Examples:
+3 -2
View File
@@ -13,8 +13,9 @@ var (
)
var setupCmd = &cobra.Command{
Use: "setup",
Short: "Setup integration with AI editors",
Use: "setup",
GroupID: "setup",
Short: "Setup integration with AI editors",
Long: `Setup integration files for AI editors like Claude Code, Cursor, Aider, and Factory.ai Droid.`,
}
+10 -9
View File
@@ -1,17 +1,20 @@
package main
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/rpc"
"github.com/steveyegge/beads/internal/types"
"github.com/steveyegge/beads/internal/ui"
)
var staleCmd = &cobra.Command{
Use: "stale",
Short: "Show stale issues (not updated recently)",
Use: "stale",
GroupID: "views",
Short: "Show stale issues (not updated recently)",
Long: `Show issues that haven't been updated recently and may need attention.
This helps identify:
- In-progress issues with no recent activity (may be abandoned)
@@ -88,17 +91,15 @@ This helps identify:
}
func displayStaleIssues(issues []*types.Issue, days int) {
if len(issues) == 0 {
green := color.New(color.FgGreen).SprintFunc()
fmt.Printf("\n%s No stale issues found (all active)\n\n", green("✨"))
fmt.Printf("\n%s No stale issues found (all active)\n\n", ui.RenderPass("✨"))
return
}
yellow := color.New(color.FgYellow).SprintFunc()
fmt.Printf("\n%s Stale issues (%d not updated in %d+ days):\n\n", yellow("⏰"), len(issues), days)
fmt.Printf("\n%s Stale issues (%d not updated in %d+ days):\n\n", ui.RenderWarn("⏰"), len(issues), days)
now := time.Now()
for i, issue := range issues {
daysStale := int(now.Sub(issue.UpdatedAt).Hours() / 24)
fmt.Printf("%d. [P%d] %s: %s\n", i+1, issue.Priority, issue.ID, issue.Title)
fmt.Printf(" Status: %s, Last updated: %d days ago\n", issue.Status, daysStale)
fmt.Printf("%d. [%s] %s: %s\n", i+1, ui.RenderPriority(issue.Priority), ui.RenderID(issue.ID), issue.Title)
fmt.Printf(" Status: %s, Last updated: %d days ago\n", ui.RenderStatus(string(issue.Status)), daysStale)
if issue.Assignee != "" {
fmt.Printf(" Assignee: %s\n", issue.Assignee)
}
+3 -2
View File
@@ -41,8 +41,9 @@ type RecentActivitySummary struct {
}
var statusCmd = &cobra.Command{
Use: "status",
Short: "Show issue database overview",
Use: "status",
GroupID: "views",
Short: "Show issue database overview",
Long: `Show a quick snapshot of the issue database state.
This command provides a summary of issue counts by state (open, in_progress,
+3 -2
View File
@@ -23,8 +23,9 @@ import (
)
var syncCmd = &cobra.Command{
Use: "sync",
Short: "Synchronize issues with git remote",
Use: "sync",
GroupID: "sync",
Short: "Synchronize issues with git remote",
Long: `Synchronize issues with git remote in a single operation:
1. Export pending changes to JSONL
2. Commit changes to git
+1
View File
@@ -1,5 +1,6 @@
#!/bin/sh
# bd-shim v1
# bd-hooks-version: 0.30.7
#
# bd (beads) pre-commit hook - thin shim
#
+3 -2
View File
@@ -10,8 +10,9 @@ import (
)
var upgradeCmd = &cobra.Command{
Use: "upgrade",
Short: "Check and manage bd version upgrades",
Use: "upgrade",
GroupID: "maint",
Short: "Check and manage bd version upgrades",
Long: `Commands for checking bd version upgrades and reviewing changes.
The upgrade command helps you stay aware of bd version changes:
+2 -2
View File
@@ -1,7 +1,7 @@
{ pkgs, self }:
pkgs.buildGoModule {
pname = "beads";
version = "0.30.2";
version = "0.30.7";
src = self;
@@ -9,7 +9,7 @@ pkgs.buildGoModule {
subPackages = [ "cmd/bd" ];
doCheck = false;
# Go module dependencies hash (computed via nix build)
vendorHash = "sha256-Gyj/Vs3IEWPwqzfNoNBSL4VFifEhjnltlr1AROwGPc4=";
vendorHash = "sha256-RDH0YU4GblmS87VCCYVv0FlrW/cxMlcnkRNaHZ2DT4Q=";
# Git is required for tests
nativeBuildInputs = [ pkgs.git ];