Implement BD_GUIDE.md generation for version-stamped documentation (bd-woro)

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
This commit is contained in:
Steve Yegge
2025-11-23 21:16:09 -08:00
parent 71502b1b93
commit 9e16469b2e
6 changed files with 559 additions and 4 deletions

View File

@@ -2,6 +2,8 @@ package main
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -68,3 +70,120 @@ func TestOnboardCommand(t *testing.T) {
}
})
}
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)
}
})
}