This implements the ability to separate bd-specific instructions from project-specific instructions by generating a canonical BD_GUIDE.md file. ## Changes 1. Added `--output` flag to `bd onboard` command - Generates version-stamped BD_GUIDE.md at specified path - Includes both agentsContent and copilotInstructionsContent - Auto-generated header warns against manual editing 2. Version tracking integration - checkAndSuggestBDGuideUpdate() detects outdated BD_GUIDE.md - Suggests regeneration when bd version changes - Integrated with maybeShowUpgradeNotification() 3. Comprehensive test coverage - Tests for BD_GUIDE.md generation - Tests for version stamp validation - Tests for content inclusion 4. Documentation updates - Updated AGENTS.md with BD_GUIDE.md workflow - Added regeneration instructions to upgrade workflow ## Benefits - Clear separation of concerns (bd vs project instructions) - Deterministic updates (no LLM involved) - Git-trackable diffs show exactly what changed - Progressive disclosure (agents read when needed) ## Usage \`\`\`bash # Generate BD_GUIDE.md bd onboard --output .beads/BD_GUIDE.md # After upgrading bd bd onboard --output .beads/BD_GUIDE.md # Regenerate \`\`\` Closes bd-woro
190 lines
5.1 KiB
Go
190 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestOnboardCommand(t *testing.T) {
|
|
t.Run("onboard output contains key sections", func(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
if err := renderOnboardInstructions(&buf); err != nil {
|
|
t.Fatalf("renderOnboardInstructions() error = %v", err)
|
|
}
|
|
output := buf.String()
|
|
|
|
// Verify output contains expected sections
|
|
expectedSections := []string{
|
|
"bd Onboarding Instructions",
|
|
"Update AGENTS.md",
|
|
"Update CLAUDE.md",
|
|
"BEGIN AGENTS.MD CONTENT",
|
|
"END AGENTS.MD CONTENT",
|
|
"Issue Tracking with bd (beads)",
|
|
"Managing AI-Generated Planning Documents",
|
|
"history/",
|
|
}
|
|
|
|
for _, section := range expectedSections {
|
|
if !strings.Contains(output, section) {
|
|
t.Errorf("Expected output to contain '%s', but it was missing", section)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("agents content includes slop management", func(t *testing.T) {
|
|
// Verify the agentsContent constant includes the new slop management section
|
|
if !strings.Contains(agentsContent, "Managing AI-Generated Planning Documents") {
|
|
t.Error("agentsContent should contain 'Managing AI-Generated Planning Documents' section")
|
|
}
|
|
if !strings.Contains(agentsContent, "history/") {
|
|
t.Error("agentsContent should mention the 'history/' directory")
|
|
}
|
|
if !strings.Contains(agentsContent, "PLAN.md") {
|
|
t.Error("agentsContent should mention example files like 'PLAN.md'")
|
|
}
|
|
if !strings.Contains(agentsContent, "Do NOT clutter repo root with planning documents") {
|
|
t.Error("agentsContent should include rule about not cluttering repo root")
|
|
}
|
|
})
|
|
|
|
t.Run("agents content includes bd workflow", func(t *testing.T) {
|
|
// Verify essential bd workflow content is present
|
|
essentialContent := []string{
|
|
"bd ready",
|
|
"bd create",
|
|
"bd update",
|
|
"bd close",
|
|
"discovered-from",
|
|
"--json",
|
|
"MCP Server",
|
|
}
|
|
|
|
for _, content := range essentialContent {
|
|
if !strings.Contains(agentsContent, content) {
|
|
t.Errorf("agentsContent should contain '%s'", content)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGenerateBDGuide(t *testing.T) {
|
|
t.Run("generates BD_GUIDE.md with version stamp", func(t *testing.T) {
|
|
// Create temp directory
|
|
tmpDir := t.TempDir()
|
|
outputPath := filepath.Join(tmpDir, "BD_GUIDE.md")
|
|
|
|
// Generate BD_GUIDE.md
|
|
if err := generateBDGuide(outputPath); err != nil {
|
|
t.Fatalf("generateBDGuide() error = %v", err)
|
|
}
|
|
|
|
// Read generated file
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read generated file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify version stamp in header
|
|
if !strings.Contains(output, "Auto-generated by bd v"+Version) {
|
|
t.Error("Generated file should contain version stamp in header")
|
|
}
|
|
|
|
if !strings.Contains(output, "DO NOT EDIT MANUALLY") {
|
|
t.Error("Generated file should contain DO NOT EDIT warning")
|
|
}
|
|
|
|
// Verify regeneration instructions
|
|
if !strings.Contains(output, "bd onboard --output") {
|
|
t.Error("Generated file should contain regeneration instructions")
|
|
}
|
|
})
|
|
|
|
t.Run("includes agents content", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
outputPath := filepath.Join(tmpDir, "BD_GUIDE.md")
|
|
|
|
if err := generateBDGuide(outputPath); err != nil {
|
|
t.Fatalf("generateBDGuide() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read generated file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify key sections from agentsContent are present
|
|
expectedSections := []string{
|
|
"Issue Tracking with bd (beads)",
|
|
"bd ready",
|
|
"bd create",
|
|
"MCP Server",
|
|
}
|
|
|
|
for _, section := range expectedSections {
|
|
if !strings.Contains(output, section) {
|
|
t.Errorf("Generated file should contain '%s'", section)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("includes copilot instructions content", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
outputPath := filepath.Join(tmpDir, "BD_GUIDE.md")
|
|
|
|
if err := generateBDGuide(outputPath); err != nil {
|
|
t.Fatalf("generateBDGuide() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read generated file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify key sections from copilotInstructionsContent are present
|
|
expectedSections := []string{
|
|
"GitHub Copilot Instructions",
|
|
"Project Structure",
|
|
"Tech Stack",
|
|
"Coding Guidelines",
|
|
}
|
|
|
|
for _, section := range expectedSections {
|
|
if !strings.Contains(output, section) {
|
|
t.Errorf("Generated file should contain '%s'", section)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("has proper structure with separators", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
outputPath := filepath.Join(tmpDir, "BD_GUIDE.md")
|
|
|
|
if err := generateBDGuide(outputPath); err != nil {
|
|
t.Fatalf("generateBDGuide() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read generated file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Count separators (should have at least 3: after header, between sections, before footer)
|
|
separatorCount := strings.Count(output, "---")
|
|
if separatorCount < 3 {
|
|
t.Errorf("Expected at least 3 separators (---), got %d", separatorCount)
|
|
}
|
|
})
|
|
}
|