Merge branch 'main' of https://github.com/steveyegge/beads
This commit is contained in:
6016
.beads/issues.jsonl
6016
.beads/issues.jsonl
File diff suppressed because one or more lines are too long
@@ -312,12 +312,17 @@ func runDiagnostics(path string) doctorResult {
|
|||||||
result.Checks = append(result.Checks, claudeCheck)
|
result.Checks = append(result.Checks, claudeCheck)
|
||||||
// Don't fail overall check for missing Claude integration, just warn
|
// Don't fail overall check for missing Claude integration, just warn
|
||||||
|
|
||||||
// Check 12: Legacy beads slash commands in documentation
|
// Check 12: Agent documentation presence
|
||||||
|
agentDocsCheck := convertDoctorCheck(doctor.CheckAgentDocumentation(path))
|
||||||
|
result.Checks = append(result.Checks, agentDocsCheck)
|
||||||
|
// Don't fail overall check for missing docs, just warn
|
||||||
|
|
||||||
|
// Check 13: Legacy beads slash commands in documentation
|
||||||
legacyDocsCheck := convertDoctorCheck(doctor.CheckLegacyBeadsSlashCommands(path))
|
legacyDocsCheck := convertDoctorCheck(doctor.CheckLegacyBeadsSlashCommands(path))
|
||||||
result.Checks = append(result.Checks, legacyDocsCheck)
|
result.Checks = append(result.Checks, legacyDocsCheck)
|
||||||
// Don't fail overall check for legacy docs, just warn
|
// Don't fail overall check for legacy docs, just warn
|
||||||
|
|
||||||
// Check 13: Gitignore up to date
|
// Check 14: Gitignore up to date
|
||||||
gitignoreCheck := convertDoctorCheck(doctor.CheckGitignore())
|
gitignoreCheck := convertDoctorCheck(doctor.CheckGitignore())
|
||||||
result.Checks = append(result.Checks, gitignoreCheck)
|
result.Checks = append(result.Checks, gitignoreCheck)
|
||||||
// Don't fail overall check for gitignore, just warn
|
// Don't fail overall check for gitignore, just warn
|
||||||
|
|||||||
@@ -58,14 +58,34 @@ func CheckClaude() DoctorCheck {
|
|||||||
Name: "Claude Integration",
|
Name: "Claude Integration",
|
||||||
Status: "warning",
|
Status: "warning",
|
||||||
Message: "MCP server installed but hooks missing",
|
Message: "MCP server installed but hooks missing",
|
||||||
Fix: "Run: bd setup claude",
|
Detail: "MCP-only mode: relies on tools for every query (~10.5k tokens)\n" +
|
||||||
|
" bd prime hooks provide much better token efficiency",
|
||||||
|
Fix: "Add bd prime hooks for better token efficiency:\n" +
|
||||||
|
" 1. Run 'bd setup claude' to add SessionStart/PreCompact hooks\n" +
|
||||||
|
"\n" +
|
||||||
|
"Benefits:\n" +
|
||||||
|
" • MCP mode: ~50 tokens vs ~10.5k for full tool scan (99% reduction)\n" +
|
||||||
|
" • Automatic context refresh on session start and compaction\n" +
|
||||||
|
" • Works alongside MCP tools for when you need them\n" +
|
||||||
|
"\n" +
|
||||||
|
"See: bd setup claude --help",
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return DoctorCheck{
|
return DoctorCheck{
|
||||||
Name: "Claude Integration",
|
Name: "Claude Integration",
|
||||||
Status: "warning",
|
Status: "warning",
|
||||||
Message: "Not configured",
|
Message: "Not configured",
|
||||||
Fix: "Run: bd setup claude (and install beads plugin for slash commands)",
|
Detail: "Claude can use bd more effectively with hooks and optional plugin",
|
||||||
|
Fix: "Set up Claude integration:\n" +
|
||||||
|
" 1. Run 'bd setup claude' to add SessionStart/PreCompact hooks\n" +
|
||||||
|
" 2. (Optional) Install beads plugin for slash commands\n" +
|
||||||
|
"\n" +
|
||||||
|
"Benefits:\n" +
|
||||||
|
" • Hooks: Auto-inject workflow context (~50-2k tokens)\n" +
|
||||||
|
" • Plugin: Convenient slash commands + MCP tools\n" +
|
||||||
|
" • CLI mode: Works without plugin (hooks + manual 'bd prime')\n" +
|
||||||
|
"\n" +
|
||||||
|
"See: bd setup claude --help",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,16 +42,64 @@ func CheckLegacyBeadsSlashCommands(repoPath string) DoctorCheck {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return DoctorCheck{
|
return DoctorCheck{
|
||||||
Name: "Documentation",
|
Name: "Integration Pattern",
|
||||||
Status: "warning",
|
Status: "warning",
|
||||||
Message: fmt.Sprintf("Legacy /beads:* slash commands found in: %s", strings.Join(filesWithLegacyCommands, ", ")),
|
Message: fmt.Sprintf("Old beads integration detected in %s", strings.Join(filesWithLegacyCommands, ", ")),
|
||||||
Detail: "Old pattern: /beads:quickstart, /beads:ready (~10.5k tokens)",
|
Detail: "Found: /beads:* slash command references (deprecated)\n" +
|
||||||
Fix: "Migration steps:\n" +
|
" These commands are token-inefficient (~10.5k tokens per session)",
|
||||||
" 1. Run 'bd setup claude' (or 'bd setup cursor') to install bd prime hooks\n" +
|
Fix: "Migrate to bd prime hooks for better token efficiency:\n" +
|
||||||
" 2. Remove /beads:* slash commands from AGENTS.md/CLAUDE.md\n" +
|
"\n" +
|
||||||
" 3. Add this to AGENTS.md/CLAUDE.md for team members without hooks:\n" +
|
"Migration Steps:\n" +
|
||||||
" \"This project uses bd (beads) for issue tracking. Run 'bd prime' at session start for workflow context.\"\n" +
|
" 1. Run 'bd setup claude' to add SessionStart/PreCompact hooks\n" +
|
||||||
" Token savings: ~10.5k → ~50-2k tokens per session",
|
" 2. Update AGENTS.md/CLAUDE.md:\n" +
|
||||||
|
" - Remove /beads:* slash command references\n" +
|
||||||
|
" - Add: \"Run 'bd prime' for workflow context\" (for users without hooks)\n" +
|
||||||
|
"\n" +
|
||||||
|
"Benefits:\n" +
|
||||||
|
" • MCP mode: ~50 tokens vs ~10.5k for full MCP scan (99% reduction)\n" +
|
||||||
|
" • CLI mode: ~1-2k tokens with automatic context recovery\n" +
|
||||||
|
" • Hooks auto-refresh context on session start and before compaction\n" +
|
||||||
|
"\n" +
|
||||||
|
"See: bd setup claude --help",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAgentDocumentation checks if agent documentation (AGENTS.md or CLAUDE.md) exists
|
||||||
|
// and recommends adding it if missing, suggesting bd onboard or bd setup claude.
|
||||||
|
func CheckAgentDocumentation(repoPath string) DoctorCheck {
|
||||||
|
docFiles := []string{
|
||||||
|
filepath.Join(repoPath, "AGENTS.md"),
|
||||||
|
filepath.Join(repoPath, "CLAUDE.md"),
|
||||||
|
filepath.Join(repoPath, ".claude", "CLAUDE.md"),
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundDocs []string
|
||||||
|
for _, docFile := range docFiles {
|
||||||
|
if _, err := os.Stat(docFile); err == nil {
|
||||||
|
foundDocs = append(foundDocs, filepath.Base(docFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(foundDocs) > 0 {
|
||||||
|
return DoctorCheck{
|
||||||
|
Name: "Agent Documentation",
|
||||||
|
Status: "ok",
|
||||||
|
Message: fmt.Sprintf("Documentation found: %s", strings.Join(foundDocs, ", ")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DoctorCheck{
|
||||||
|
Name: "Agent Documentation",
|
||||||
|
Status: "warning",
|
||||||
|
Message: "No agent documentation found",
|
||||||
|
Detail: "Missing: AGENTS.md or CLAUDE.md\n" +
|
||||||
|
" Documenting workflow helps AI agents work more effectively",
|
||||||
|
Fix: "Add agent documentation:\n" +
|
||||||
|
" • Run 'bd onboard' to create AGENTS.md with workflow guidance\n" +
|
||||||
|
" • Or run 'bd setup claude' to add Claude-specific documentation\n" +
|
||||||
|
"\n" +
|
||||||
|
"Recommended: Include bd workflow in your project documentation so\n" +
|
||||||
|
"AI agents understand how to track issues and manage dependencies",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
239
cmd/bd/doctor/legacy_test.go
Normal file
239
cmd/bd/doctor/legacy_test.go
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
package doctor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckAgentDocumentation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
files []string
|
||||||
|
expectedStatus string
|
||||||
|
expectFix bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no documentation",
|
||||||
|
files: []string{},
|
||||||
|
expectedStatus: "warning",
|
||||||
|
expectFix: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AGENTS.md exists",
|
||||||
|
files: []string{"AGENTS.md"},
|
||||||
|
expectedStatus: "ok",
|
||||||
|
expectFix: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CLAUDE.md exists",
|
||||||
|
files: []string{"CLAUDE.md"},
|
||||||
|
expectedStatus: "ok",
|
||||||
|
expectFix: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".claude/CLAUDE.md exists",
|
||||||
|
files: []string{".claude/CLAUDE.md"},
|
||||||
|
expectedStatus: "ok",
|
||||||
|
expectFix: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple docs",
|
||||||
|
files: []string{"AGENTS.md", "CLAUDE.md"},
|
||||||
|
expectedStatus: "ok",
|
||||||
|
expectFix: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// Create test files
|
||||||
|
for _, file := range tt.files {
|
||||||
|
filePath := filepath.Join(tmpDir, file)
|
||||||
|
dir := filepath.Dir(filePath)
|
||||||
|
if dir != tmpDir {
|
||||||
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filePath, []byte("# Test documentation"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check := CheckAgentDocumentation(tmpDir)
|
||||||
|
|
||||||
|
if check.Status != tt.expectedStatus {
|
||||||
|
t.Errorf("Expected status %s, got %s", tt.expectedStatus, check.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expectFix && check.Fix == "" {
|
||||||
|
t.Error("Expected fix message, got empty string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.expectFix && check.Fix != "" {
|
||||||
|
t.Errorf("Expected no fix message, got: %s", check.Fix)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckLegacyBeadsSlashCommands(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fileContent map[string]string // filename -> content
|
||||||
|
expectedStatus string
|
||||||
|
expectWarning bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no documentation files",
|
||||||
|
fileContent: map[string]string{},
|
||||||
|
expectedStatus: "ok",
|
||||||
|
expectWarning: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clean documentation",
|
||||||
|
fileContent: map[string]string{
|
||||||
|
"AGENTS.md": "# Agents\n\nUse bd ready to see ready issues.",
|
||||||
|
},
|
||||||
|
expectedStatus: "ok",
|
||||||
|
expectWarning: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "legacy slash command in AGENTS.md",
|
||||||
|
fileContent: map[string]string{
|
||||||
|
"AGENTS.md": "# Agents\n\nUse /beads:ready to see ready issues.",
|
||||||
|
},
|
||||||
|
expectedStatus: "warning",
|
||||||
|
expectWarning: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "legacy slash command in CLAUDE.md",
|
||||||
|
fileContent: map[string]string{
|
||||||
|
"CLAUDE.md": "# Claude\n\nRun /beads:quickstart to get started.",
|
||||||
|
},
|
||||||
|
expectedStatus: "warning",
|
||||||
|
expectWarning: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "legacy slash command in .claude/CLAUDE.md",
|
||||||
|
fileContent: map[string]string{
|
||||||
|
".claude/CLAUDE.md": "Use /beads:show to see an issue.",
|
||||||
|
},
|
||||||
|
expectedStatus: "warning",
|
||||||
|
expectWarning: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple files with legacy commands",
|
||||||
|
fileContent: map[string]string{
|
||||||
|
"AGENTS.md": "Use /beads:ready",
|
||||||
|
"CLAUDE.md": "Use /beads:show",
|
||||||
|
},
|
||||||
|
expectedStatus: "warning",
|
||||||
|
expectWarning: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// Create test files
|
||||||
|
for filename, content := range tt.fileContent {
|
||||||
|
filePath := filepath.Join(tmpDir, filename)
|
||||||
|
dir := filepath.Dir(filePath)
|
||||||
|
if dir != tmpDir {
|
||||||
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check := CheckLegacyBeadsSlashCommands(tmpDir)
|
||||||
|
|
||||||
|
if check.Status != tt.expectedStatus {
|
||||||
|
t.Errorf("Expected status %s, got %s", tt.expectedStatus, check.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expectWarning {
|
||||||
|
if check.Fix == "" {
|
||||||
|
t.Error("Expected fix message for warning, got empty string")
|
||||||
|
}
|
||||||
|
if !strings.Contains(check.Fix, "bd setup claude") {
|
||||||
|
t.Error("Expected fix message to mention 'bd setup claude'")
|
||||||
|
}
|
||||||
|
if !strings.Contains(check.Fix, "token") {
|
||||||
|
t.Error("Expected fix message to mention token savings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckLegacyJSONLFilename(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
files []string
|
||||||
|
expectedStatus string
|
||||||
|
expectWarning bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no JSONL files",
|
||||||
|
files: []string{},
|
||||||
|
expectedStatus: "ok",
|
||||||
|
expectWarning: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "canonical beads.jsonl",
|
||||||
|
files: []string{"beads.jsonl"},
|
||||||
|
expectedStatus: "ok",
|
||||||
|
expectWarning: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "legacy issues.jsonl",
|
||||||
|
files: []string{"issues.jsonl"},
|
||||||
|
expectedStatus: "warning",
|
||||||
|
expectWarning: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both files present",
|
||||||
|
files: []string{"beads.jsonl", "issues.jsonl"},
|
||||||
|
expectedStatus: "warning",
|
||||||
|
expectWarning: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||||
|
if err := os.Mkdir(beadsDir, 0750); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test files
|
||||||
|
for _, file := range tt.files {
|
||||||
|
filePath := filepath.Join(beadsDir, file)
|
||||||
|
if err := os.WriteFile(filePath, []byte("{}"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check := CheckLegacyJSONLFilename(tmpDir)
|
||||||
|
|
||||||
|
if check.Status != tt.expectedStatus {
|
||||||
|
t.Errorf("Expected status %s, got %s", tt.expectedStatus, check.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expectWarning && check.Fix == "" {
|
||||||
|
t.Error("Expected fix message for warning, got empty string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user