Merge bd-7r4l-prospector: GH#540

This commit is contained in:
Steve Yegge
2025-12-16 01:17:25 -08:00
7 changed files with 1071 additions and 905 deletions

File diff suppressed because one or more lines are too long

View File

@@ -54,8 +54,11 @@ var createCmd = &cobra.Command{
FatalError("title required (or use --file to create from markdown)")
}
// Warn if creating a test issue in production database
if strings.HasPrefix(strings.ToLower(title), "test") {
// Get silent flag
silent, _ := cmd.Flags().GetBool("silent")
// Warn if creating a test issue in production database (unless silent mode)
if strings.HasPrefix(strings.ToLower(title), "test") && !silent && !debug.IsQuiet() {
yellow := color.New(color.FgYellow).SprintFunc()
fmt.Fprintf(os.Stderr, "%s Creating issue with 'Test' prefix in production database.\n", yellow("⚠"))
fmt.Fprintf(os.Stderr, " For testing, consider using: BEADS_DB=/tmp/test.db ./bd create \"Test issue\"\n")
@@ -77,8 +80,8 @@ var createCmd = &cobra.Command{
description = tmpl.Description
}
// Warn if creating an issue without a description (unless it's a test issue)
if description == "" && !strings.Contains(strings.ToLower(title), "test") {
// Warn if creating an issue without a description (unless it's a test issue or silent mode)
if description == "" && !strings.Contains(strings.ToLower(title), "test") && !silent && !debug.IsQuiet() {
yellow := color.New(color.FgYellow).SprintFunc()
fmt.Fprintf(os.Stderr, "%s Creating issue without description.\n", yellow("⚠"))
fmt.Fprintf(os.Stderr, " Issues without descriptions lack context for future work.\n")
@@ -249,6 +252,12 @@ var createCmd = &cobra.Command{
if jsonOutput {
fmt.Println(string(resp.Data))
} else if silent {
var issue types.Issue
if err := json.Unmarshal(resp.Data, &issue); err != nil {
FatalError("parsing response: %v", err)
}
fmt.Println(issue.ID)
} else {
var issue types.Issue
if err := json.Unmarshal(resp.Data, &issue); err != nil {
@@ -386,6 +395,8 @@ var createCmd = &cobra.Command{
if jsonOutput {
outputJSON(issue)
} else if silent {
fmt.Println(issue.ID)
} else {
green := color.New(color.FgGreen).SprintFunc()
fmt.Printf("%s Created issue: %s\n", green("✓"), issue.ID)
@@ -403,6 +414,7 @@ func init() {
createCmd.Flags().StringP("file", "f", "", "Create multiple issues from markdown file")
createCmd.Flags().String("from-template", "", "Create issue from template (e.g., 'epic', 'bug', 'feature')")
createCmd.Flags().String("title", "", "Issue title (alternative to positional argument)")
createCmd.Flags().Bool("silent", false, "Output only the issue ID (for scripting)")
registerPriorityFlag(createCmd, "2")
createCmd.Flags().StringP("type", "t", "task", "Issue type (bug|feature|task|epic|chore)")
registerCommonIssueFlags(createCmd)

View File

@@ -296,11 +296,15 @@ func CheckBdInPath() DoctorCheck {
// CheckDocumentationBdPrimeReference checks if AGENTS.md or CLAUDE.md reference 'bd prime'
// and verifies the command exists. This helps catch version mismatches where docs
// reference features not available in the installed version.
// Also supports local-only variants (claude.local.md) that are gitignored.
func CheckDocumentationBdPrimeReference(repoPath string) DoctorCheck {
docFiles := []string{
filepath.Join(repoPath, "AGENTS.md"),
filepath.Join(repoPath, "CLAUDE.md"),
filepath.Join(repoPath, ".claude", "CLAUDE.md"),
// Local-only variants (not committed to repo)
filepath.Join(repoPath, "claude.local.md"),
filepath.Join(repoPath, ".claude", "claude.local.md"),
}
var filesWithBdPrime []string

View File

@@ -72,6 +72,22 @@ func TestCheckDocumentationBdPrimeReference(t *testing.T) {
expectedStatus: "ok",
expectDetail: true,
},
{
name: "claude.local.md references bd prime (local-only)",
fileContent: map[string]string{
"claude.local.md": "Run bd prime for context.",
},
expectedStatus: "ok",
expectDetail: true,
},
{
name: ".claude/claude.local.md references bd prime (local-only)",
fileContent: map[string]string{
".claude/claude.local.md": "Use bd prime for workflow context.",
},
expectedStatus: "ok",
expectDetail: true,
},
{
name: "multiple files reference bd prime",
fileContent: map[string]string{

View File

@@ -22,6 +22,9 @@ func CheckLegacyBeadsSlashCommands(repoPath string) DoctorCheck {
filepath.Join(repoPath, "AGENTS.md"),
filepath.Join(repoPath, "CLAUDE.md"),
filepath.Join(repoPath, ".claude", "CLAUDE.md"),
// Local-only variants (not committed to repo)
filepath.Join(repoPath, "claude.local.md"),
filepath.Join(repoPath, ".claude", "claude.local.md"),
}
var filesWithLegacyCommands []string
@@ -71,11 +74,15 @@ func CheckLegacyBeadsSlashCommands(repoPath string) DoctorCheck {
// CheckAgentDocumentation checks if agent documentation (AGENTS.md or CLAUDE.md) exists
// and recommends adding it if missing, suggesting bd onboard or bd setup claude.
// Also supports local-only variants (claude.local.md) that are gitignored.
func CheckAgentDocumentation(repoPath string) DoctorCheck {
docFiles := []string{
filepath.Join(repoPath, "AGENTS.md"),
filepath.Join(repoPath, "CLAUDE.md"),
filepath.Join(repoPath, ".claude", "CLAUDE.md"),
// Local-only variants (not committed to repo)
filepath.Join(repoPath, "claude.local.md"),
filepath.Join(repoPath, ".claude", "claude.local.md"),
}
var foundDocs []string
@@ -103,6 +110,10 @@ func CheckAgentDocumentation(repoPath string) DoctorCheck {
" • Run 'bd onboard' to create AGENTS.md with workflow guidance\n" +
" • Or run 'bd setup claude' to add Claude-specific documentation\n" +
"\n" +
"For local-only documentation (not committed to repo):\n" +
" • Create claude.local.md or .claude/claude.local.md\n" +
" • Add 'claude.local.md' to your .gitignore\n" +
"\n" +
"Recommended: Include bd workflow in your project documentation so\n" +
"AI agents understand how to track issues and manage dependencies",
}

View File

@@ -39,6 +39,18 @@ func TestCheckAgentDocumentation(t *testing.T) {
expectedStatus: "ok",
expectFix: false,
},
{
name: "claude.local.md exists (local-only)",
files: []string{"claude.local.md"},
expectedStatus: "ok",
expectFix: false,
},
{
name: ".claude/claude.local.md exists (local-only)",
files: []string{".claude/claude.local.md"},
expectedStatus: "ok",
expectFix: false,
},
{
name: "multiple docs",
files: []string{"AGENTS.md", "CLAUDE.md"},
@@ -127,6 +139,22 @@ func TestCheckLegacyBeadsSlashCommands(t *testing.T) {
expectedStatus: "warning",
expectWarning: true,
},
{
name: "legacy slash command in claude.local.md",
fileContent: map[string]string{
"claude.local.md": "Use /beads:show to see an issue.",
},
expectedStatus: "warning",
expectWarning: true,
},
{
name: "legacy slash command in .claude/claude.local.md",
fileContent: map[string]string{
".claude/claude.local.md": "Use /beads:ready to see ready issues.",
},
expectedStatus: "warning",
expectWarning: true,
},
{
name: "multiple files with legacy commands",
fileContent: map[string]string{

94
cmd/bd/quick.go Normal file
View File

@@ -0,0 +1,94 @@
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/rpc"
"github.com/steveyegge/beads/internal/types"
"github.com/steveyegge/beads/internal/validation"
)
var quickCmd = &cobra.Command{
Use: "q [title]",
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.
Example:
bd q "Fix login bug" # Outputs: bd-a1b2
ISSUE=$(bd q "New feature") # Capture ID in variable
bd q "Task" | xargs bd show # Pipe to other commands`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
CheckReadonly("create")
title := strings.Join(args, " ")
// Get optional flags
priorityStr, _ := cmd.Flags().GetString("priority")
issueType, _ := cmd.Flags().GetString("type")
labels, _ := cmd.Flags().GetStringSlice("labels")
// Parse priority
priority, err := validation.ValidatePriority(priorityStr)
if err != nil {
FatalError("%v", err)
}
// If daemon is running, use RPC
if daemonClient != nil {
createArgs := &rpc.CreateArgs{
Title: title,
Priority: priority,
IssueType: issueType,
Labels: labels,
}
resp, err := daemonClient.Create(createArgs)
if err != nil {
FatalError("%v", err)
}
var issue types.Issue
if err := json.Unmarshal(resp.Data, &issue); err != nil {
FatalError("parsing response: %v", err)
}
fmt.Println(issue.ID)
return
}
// Direct mode
issue := &types.Issue{
Title: title,
Status: types.StatusOpen,
Priority: priority,
IssueType: types.IssueType(issueType),
}
ctx := rootCtx
if err := store.CreateIssue(ctx, issue, actor); err != nil {
FatalError("%v", err)
}
// Add labels if specified (silently ignore failures)
for _, label := range labels {
_ = store.AddLabel(ctx, issue.ID, label, actor)
}
// Schedule auto-flush
markDirtyAndScheduleFlush()
// Output only the ID
fmt.Println(issue.ID)
},
}
func init() {
quickCmd.Flags().StringP("priority", "p", "2", "Priority (0-4 or P0-P4)")
quickCmd.Flags().StringP("type", "t", "task", "Issue type")
quickCmd.Flags().StringSliceP("labels", "l", []string{}, "Labels")
rootCmd.AddCommand(quickCmd)
}