feat: Add bd init --contributor and --team wizards
- Implement OSS contributor workflow wizard - Auto-detects fork relationships (upstream remote) - Checks push access (SSH vs HTTPS) - Creates separate planning repository - Configures auto-routing to keep planning out of PRs - Implement team workflow wizard - Detects protected main branches - Creates sync branch if needed - Configures auto-commit/auto-push - Supports both direct and PR-based workflows - Add comprehensive documentation - examples/contributor-workflow/README.md - examples/team-workflow/README.md - Updated AGENTS.md, README.md, QUICKSTART.md - Updated docs/MULTI_REPO_MIGRATION.md Closes: bd-kla1, bd-twlr, bd-6z7l Amp-Thread-ID: https://ampcode.com/threads/T-b4d124a2-447e-47d1-8124-d7c5dab9a97b Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
10
AGENTS.md
10
AGENTS.md
@@ -1048,6 +1048,16 @@ Happy coding! 🔗
|
||||
bd init --prefix bd
|
||||
```
|
||||
|
||||
**OSS Contributor?** Use the contributor wizard for fork workflows:
|
||||
```bash
|
||||
bd init --contributor # Interactive setup for separate planning repo
|
||||
```
|
||||
|
||||
**Team Member?** Use the team wizard for branch workflows:
|
||||
```bash
|
||||
bd init --team # Interactive setup for team collaboration
|
||||
```
|
||||
|
||||
**Check for ready work:**
|
||||
```bash
|
||||
bd ready --json
|
||||
|
||||
@@ -10,6 +10,30 @@ go build -o bd ./cmd/bd
|
||||
./bd --help
|
||||
```
|
||||
|
||||
## Initialize
|
||||
|
||||
First time in a repository:
|
||||
|
||||
```bash
|
||||
# Basic setup
|
||||
bd init
|
||||
|
||||
# OSS contributor (fork workflow with separate planning repo)
|
||||
bd init --contributor
|
||||
|
||||
# Team member (branch workflow for collaboration)
|
||||
bd init --team
|
||||
|
||||
# Protected main branch (GitHub/GitLab)
|
||||
bd init --branch beads-metadata
|
||||
```
|
||||
|
||||
The wizard will:
|
||||
- Create `.beads/` directory and database
|
||||
- Import existing issues from git (if any)
|
||||
- Prompt to install git hooks (recommended)
|
||||
- Auto-start daemon for sync
|
||||
|
||||
## Your First Issues
|
||||
|
||||
```bash
|
||||
|
||||
@@ -102,6 +102,12 @@ Beads is designed for **AI coding agents** to use on your behalf. Setup takes 30
|
||||
# In your project root:
|
||||
bd init
|
||||
|
||||
# For OSS contributors (fork workflow):
|
||||
bd init --contributor
|
||||
|
||||
# For team members (branch workflow):
|
||||
bd init --team
|
||||
|
||||
# For protected branches (GitHub/GitLab):
|
||||
bd init --branch beads-metadata
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ With --no-db: creates .beads/ directory and issues.jsonl file instead of SQLite
|
||||
prefix, _ := cmd.Flags().GetString("prefix")
|
||||
quiet, _ := cmd.Flags().GetBool("quiet")
|
||||
branch, _ := cmd.Flags().GetString("branch")
|
||||
contributor, _ := cmd.Flags().GetBool("contributor")
|
||||
team, _ := cmd.Flags().GetBool("team")
|
||||
|
||||
// Initialize config (PersistentPreRun doesn't run for init command)
|
||||
if err := config.Initialize(); err != nil {
|
||||
@@ -272,6 +274,24 @@ bd.db
|
||||
}
|
||||
}
|
||||
|
||||
// Run contributor wizard if --contributor flag is set
|
||||
if contributor {
|
||||
if err := runContributorWizard(ctx, store); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error running contributor wizard: %v\n", err)
|
||||
_ = store.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Run team wizard if --team flag is set
|
||||
if team {
|
||||
if err := runTeamWizard(ctx, store); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error running team wizard: %v\n", err)
|
||||
_ = store.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := store.Close(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to close database: %v\n", err)
|
||||
}
|
||||
@@ -331,6 +351,8 @@ func init() {
|
||||
initCmd.Flags().StringP("prefix", "p", "", "Issue prefix (default: current directory name)")
|
||||
initCmd.Flags().BoolP("quiet", "q", false, "Suppress output (quiet mode)")
|
||||
initCmd.Flags().StringP("branch", "b", "", "Git branch for beads commits (default: current branch)")
|
||||
initCmd.Flags().Bool("contributor", false, "Run OSS contributor setup wizard")
|
||||
initCmd.Flags().Bool("team", false, "Run team workflow setup wizard")
|
||||
rootCmd.AddCommand(initCmd)
|
||||
}
|
||||
|
||||
|
||||
237
cmd/bd/init_contributor.go
Normal file
237
cmd/bd/init_contributor.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/steveyegge/beads/internal/storage"
|
||||
)
|
||||
|
||||
// runContributorWizard guides the user through OSS contributor setup
|
||||
func runContributorWizard(ctx context.Context, store storage.Storage) error {
|
||||
green := color.New(color.FgGreen).SprintFunc()
|
||||
cyan := color.New(color.FgCyan).SprintFunc()
|
||||
yellow := color.New(color.FgYellow).SprintFunc()
|
||||
bold := color.New(color.Bold).SprintFunc()
|
||||
|
||||
fmt.Printf("\n%s %s\n\n", bold("bd"), bold("Contributor Workflow Setup Wizard"))
|
||||
fmt.Println("This wizard will configure beads for OSS contribution.")
|
||||
fmt.Println()
|
||||
|
||||
// Step 1: Detect fork relationship
|
||||
fmt.Printf("%s Detecting git repository setup...\n", cyan("▶"))
|
||||
|
||||
isFork, upstreamURL, err := detectForkSetup()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to detect git setup: %w", err)
|
||||
}
|
||||
|
||||
if isFork {
|
||||
fmt.Printf("%s Detected fork workflow (upstream: %s)\n", green("✓"), upstreamURL)
|
||||
} else {
|
||||
fmt.Printf("%s No upstream remote detected\n", yellow("⚠"))
|
||||
fmt.Println("\n For fork workflows, add an 'upstream' remote:")
|
||||
fmt.Println(" git remote add upstream <original-repo-url>")
|
||||
fmt.Println()
|
||||
|
||||
// Ask if they want to continue anyway
|
||||
fmt.Print("Continue with contributor setup? [y/N]: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, _ := reader.ReadString('\n')
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
|
||||
if response != "y" && response != "yes" {
|
||||
fmt.Println("Setup cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Check push access to origin
|
||||
fmt.Printf("\n%s Checking repository access...\n", cyan("▶"))
|
||||
|
||||
hasPushAccess, originURL := checkPushAccess()
|
||||
|
||||
if hasPushAccess {
|
||||
fmt.Printf("%s You have push access to origin (%s)\n", green("✓"), originURL)
|
||||
fmt.Printf(" %s You can commit directly to this repository.\n", yellow("⚠"))
|
||||
fmt.Println()
|
||||
fmt.Print("Do you want to use a separate planning repo anyway? [Y/n]: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, _ := reader.ReadString('\n')
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
|
||||
if response == "n" || response == "no" {
|
||||
fmt.Println("\nSetup cancelled. Your issues will be stored in the current repository.")
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("%s Read-only access to origin (%s)\n", green("✓"), originURL)
|
||||
fmt.Println(" Planning repo recommended to keep experimental work separate.")
|
||||
}
|
||||
|
||||
// Step 3: Configure planning repository
|
||||
fmt.Printf("\n%s Setting up planning repository...\n", cyan("▶"))
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get home directory: %w", err)
|
||||
}
|
||||
|
||||
defaultPlanningRepo := filepath.Join(homeDir, ".beads-planning")
|
||||
|
||||
fmt.Printf("\nWhere should contributor planning issues be stored?\n")
|
||||
fmt.Printf("Default: %s\n", cyan(defaultPlanningRepo))
|
||||
fmt.Print("Planning repo path [press Enter for default]: ")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
planningPath, _ := reader.ReadString('\n')
|
||||
planningPath = strings.TrimSpace(planningPath)
|
||||
|
||||
if planningPath == "" {
|
||||
planningPath = defaultPlanningRepo
|
||||
}
|
||||
|
||||
// Expand ~ if present
|
||||
if strings.HasPrefix(planningPath, "~/") {
|
||||
planningPath = filepath.Join(homeDir, planningPath[2:])
|
||||
}
|
||||
|
||||
// Create planning repository if it doesn't exist
|
||||
if _, err := os.Stat(planningPath); os.IsNotExist(err) {
|
||||
fmt.Printf("\nCreating planning repository at %s\n", cyan(planningPath))
|
||||
|
||||
if err := os.MkdirAll(planningPath, 0750); err != nil {
|
||||
return fmt.Errorf("failed to create planning repo directory: %w", err)
|
||||
}
|
||||
|
||||
// Initialize git repo in planning directory
|
||||
cmd := exec.Command("git", "init")
|
||||
cmd.Dir = planningPath
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to initialize git in planning repo: %w", err)
|
||||
}
|
||||
|
||||
// Initialize beads in planning repo
|
||||
beadsDir := filepath.Join(planningPath, ".beads")
|
||||
if err := os.MkdirAll(beadsDir, 0750); err != nil {
|
||||
return fmt.Errorf("failed to create .beads in planning repo: %w", err)
|
||||
}
|
||||
|
||||
// Create issues.jsonl
|
||||
jsonlPath := filepath.Join(beadsDir, "beads.jsonl")
|
||||
if err := os.WriteFile(jsonlPath, []byte{}, 0644); err != nil {
|
||||
return fmt.Errorf("failed to create issues.jsonl: %w", err)
|
||||
}
|
||||
|
||||
// Create README in planning repo
|
||||
readmePath := filepath.Join(planningPath, "README.md")
|
||||
readmeContent := fmt.Sprintf(`# Beads Planning Repository
|
||||
|
||||
This repository stores contributor planning issues for OSS projects.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Keep experimental planning separate from upstream PRs
|
||||
- Track discovered work and implementation notes
|
||||
- Maintain private todos and design exploration
|
||||
|
||||
## Usage
|
||||
|
||||
Issues here are automatically created when working on forked repositories.
|
||||
|
||||
Created by: bd init --contributor
|
||||
`)
|
||||
if err := os.WriteFile(readmePath, []byte(readmeContent), 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to create README: %v\n", err)
|
||||
}
|
||||
|
||||
// Initial commit in planning repo
|
||||
cmd = exec.Command("git", "add", ".")
|
||||
cmd.Dir = planningPath
|
||||
_ = cmd.Run()
|
||||
|
||||
cmd = exec.Command("git", "commit", "-m", "Initial commit: beads planning repository")
|
||||
cmd.Dir = planningPath
|
||||
_ = cmd.Run()
|
||||
|
||||
fmt.Printf("%s Planning repository created\n", green("✓"))
|
||||
} else {
|
||||
fmt.Printf("%s Using existing planning repository\n", green("✓"))
|
||||
}
|
||||
|
||||
// Step 4: Configure contributor routing
|
||||
fmt.Printf("\n%s Configuring contributor auto-routing...\n", cyan("▶"))
|
||||
|
||||
// Set contributor.planning_repo config
|
||||
if err := store.SetConfig(ctx, "contributor.planning_repo", planningPath); err != nil {
|
||||
return fmt.Errorf("failed to set planning repo config: %w", err)
|
||||
}
|
||||
|
||||
// Set contributor.auto_route to true
|
||||
if err := store.SetConfig(ctx, "contributor.auto_route", "true"); err != nil {
|
||||
return fmt.Errorf("failed to enable auto-routing: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Auto-routing enabled\n", green("✓"))
|
||||
|
||||
// Step 5: Summary
|
||||
fmt.Printf("\n%s %s\n\n", green("✓"), bold("Contributor setup complete!"))
|
||||
|
||||
fmt.Println("Configuration:")
|
||||
fmt.Printf(" Current repo issues: %s\n", cyan(".beads/beads.jsonl"))
|
||||
fmt.Printf(" Planning repo issues: %s\n", cyan(filepath.Join(planningPath, ".beads/beads.jsonl")))
|
||||
fmt.Println()
|
||||
fmt.Println("How it works:")
|
||||
fmt.Println(" • Issues you create will route to the planning repo")
|
||||
fmt.Println(" • Planning stays out of your PRs to upstream")
|
||||
fmt.Println(" • Use 'bd list' to see issues from both repos")
|
||||
fmt.Println()
|
||||
fmt.Printf("Try it: %s\n", cyan("bd create \"Plan feature X\" -p 2"))
|
||||
fmt.Println()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectForkSetup checks if we're in a fork by looking for upstream remote
|
||||
func detectForkSetup() (isFork bool, upstreamURL string, err error) {
|
||||
cmd := exec.Command("git", "remote", "get-url", "upstream")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// No upstream remote found
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
upstreamURL = strings.TrimSpace(string(output))
|
||||
return true, upstreamURL, nil
|
||||
}
|
||||
|
||||
// checkPushAccess determines if we have push access to origin
|
||||
func checkPushAccess() (hasPush bool, originURL string) {
|
||||
// Get origin URL
|
||||
cmd := exec.Command("git", "remote", "get-url", "origin")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
originURL = strings.TrimSpace(string(output))
|
||||
|
||||
// SSH URLs indicate likely push access (git@github.com:...)
|
||||
if strings.HasPrefix(originURL, "git@") {
|
||||
return true, originURL
|
||||
}
|
||||
|
||||
// HTTPS URLs typically indicate read-only clone
|
||||
if strings.HasPrefix(originURL, "https://") {
|
||||
return false, originURL
|
||||
}
|
||||
|
||||
// Other protocols (file://, etc.) assume push access
|
||||
return true, originURL
|
||||
}
|
||||
224
cmd/bd/init_team.go
Normal file
224
cmd/bd/init_team.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/steveyegge/beads/internal/storage"
|
||||
)
|
||||
|
||||
// runTeamWizard guides the user through team workflow setup
|
||||
func runTeamWizard(ctx context.Context, store storage.Storage) error {
|
||||
green := color.New(color.FgGreen).SprintFunc()
|
||||
cyan := color.New(color.FgCyan).SprintFunc()
|
||||
yellow := color.New(color.FgYellow).SprintFunc()
|
||||
bold := color.New(color.Bold).SprintFunc()
|
||||
|
||||
fmt.Printf("\n%s %s\n\n", bold("bd"), bold("Team Workflow Setup Wizard"))
|
||||
fmt.Println("This wizard will configure beads for team collaboration.")
|
||||
fmt.Println()
|
||||
|
||||
// Step 1: Check if we're in a git repository
|
||||
fmt.Printf("%s Detecting git repository setup...\n", cyan("▶"))
|
||||
|
||||
if !isGitRepo() {
|
||||
fmt.Printf("%s Not in a git repository\n", yellow("⚠"))
|
||||
fmt.Println("\n Initialize git first:")
|
||||
fmt.Println(" git init")
|
||||
fmt.Println()
|
||||
return fmt.Errorf("not in a git repository")
|
||||
}
|
||||
|
||||
// Get current branch
|
||||
currentBranch, err := getGitBranch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current branch: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Current branch: %s\n", green("✓"), currentBranch)
|
||||
|
||||
// Step 2: Check for protected main branch
|
||||
fmt.Printf("\n%s Checking branch configuration...\n", cyan("▶"))
|
||||
|
||||
fmt.Println("\nIs your main branch protected (prevents direct commits)?")
|
||||
fmt.Println(" GitHub: Settings → Branches → Branch protection rules")
|
||||
fmt.Println(" GitLab: Settings → Repository → Protected branches")
|
||||
fmt.Print("\nProtected main branch? [y/N]: ")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, _ := reader.ReadString('\n')
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
|
||||
protectedMain := (response == "y" || response == "yes")
|
||||
|
||||
var syncBranch string
|
||||
|
||||
if protectedMain {
|
||||
fmt.Printf("\n%s Protected main detected\n", green("✓"))
|
||||
fmt.Println("\n Beads will commit issue updates to a separate branch.")
|
||||
fmt.Printf(" Default sync branch: %s\n", cyan("beads-metadata"))
|
||||
fmt.Print("\n Sync branch name [press Enter for default]: ")
|
||||
|
||||
branchName, _ := reader.ReadString('\n')
|
||||
branchName = strings.TrimSpace(branchName)
|
||||
|
||||
if branchName == "" {
|
||||
syncBranch = "beads-metadata"
|
||||
} else {
|
||||
syncBranch = branchName
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s Sync branch set to: %s\n", green("✓"), syncBranch)
|
||||
|
||||
// Set sync.branch config
|
||||
if err := store.SetConfig(ctx, "sync.branch", syncBranch); err != nil {
|
||||
return fmt.Errorf("failed to set sync branch: %w", err)
|
||||
}
|
||||
|
||||
// Create the sync branch if it doesn't exist
|
||||
fmt.Printf("\n%s Creating sync branch...\n", cyan("▶"))
|
||||
|
||||
if err := createSyncBranch(syncBranch); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to create sync branch: %v\n", err)
|
||||
fmt.Println(" You can create it manually: git checkout -b", syncBranch)
|
||||
} else {
|
||||
fmt.Printf("%s Sync branch created\n", green("✓"))
|
||||
}
|
||||
|
||||
} else {
|
||||
fmt.Printf("%s Direct commits to %s\n", green("✓"), currentBranch)
|
||||
syncBranch = currentBranch
|
||||
}
|
||||
|
||||
// Step 3: Configure team settings
|
||||
fmt.Printf("\n%s Configuring team settings...\n", cyan("▶"))
|
||||
|
||||
// Set team.enabled to true
|
||||
if err := store.SetConfig(ctx, "team.enabled", "true"); err != nil {
|
||||
return fmt.Errorf("failed to enable team mode: %w", err)
|
||||
}
|
||||
|
||||
// Set team.sync_branch
|
||||
if err := store.SetConfig(ctx, "team.sync_branch", syncBranch); err != nil {
|
||||
return fmt.Errorf("failed to set team sync branch: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Team mode enabled\n", green("✓"))
|
||||
|
||||
// Step 4: Configure auto-sync
|
||||
fmt.Println("\n Enable automatic sync (daemon commits/pushes)?")
|
||||
fmt.Println(" • Auto-commit: Commits issue changes every 5 seconds")
|
||||
fmt.Println(" • Auto-push: Pushes commits to remote")
|
||||
fmt.Print("\nEnable auto-sync? [Y/n]: ")
|
||||
|
||||
response, _ = reader.ReadString('\n')
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
|
||||
autoSync := !(response == "n" || response == "no")
|
||||
|
||||
if autoSync {
|
||||
if err := store.SetConfig(ctx, "daemon.auto_commit", "true"); err != nil {
|
||||
return fmt.Errorf("failed to enable auto-commit: %w", err)
|
||||
}
|
||||
|
||||
if err := store.SetConfig(ctx, "daemon.auto_push", "true"); err != nil {
|
||||
return fmt.Errorf("failed to enable auto-push: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Auto-sync enabled\n", green("✓"))
|
||||
} else {
|
||||
fmt.Printf("%s Auto-sync disabled (manual sync with 'bd sync')\n", yellow("⚠"))
|
||||
}
|
||||
|
||||
// Step 5: Summary
|
||||
fmt.Printf("\n%s %s\n\n", green("✓"), bold("Team setup complete!"))
|
||||
|
||||
fmt.Println("Configuration:")
|
||||
if protectedMain {
|
||||
fmt.Printf(" Protected main: %s\n", cyan("yes"))
|
||||
fmt.Printf(" Sync branch: %s\n", cyan(syncBranch))
|
||||
fmt.Printf(" Commits will go to: %s\n", cyan(syncBranch))
|
||||
fmt.Printf(" Merge to main via: %s\n", cyan("Pull Request"))
|
||||
} else {
|
||||
fmt.Printf(" Protected main: %s\n", cyan("no"))
|
||||
fmt.Printf(" Commits will go to: %s\n", cyan(currentBranch))
|
||||
}
|
||||
|
||||
if autoSync {
|
||||
fmt.Printf(" Auto-sync: %s\n", cyan("enabled"))
|
||||
} else {
|
||||
fmt.Printf(" Auto-sync: %s\n", cyan("disabled"))
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("How it works:")
|
||||
fmt.Println(" • All team members work on the same repository")
|
||||
fmt.Println(" • Issues are shared via git commits")
|
||||
fmt.Println(" • Use 'bd list' to see all team's issues")
|
||||
|
||||
if protectedMain {
|
||||
fmt.Println(" • Issue updates commit to", syncBranch)
|
||||
fmt.Println(" • Periodically merge", syncBranch, "to main via PR")
|
||||
}
|
||||
|
||||
if autoSync {
|
||||
fmt.Println(" • Daemon automatically commits and pushes changes")
|
||||
} else {
|
||||
fmt.Println(" • Run 'bd sync' manually to sync changes")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("Try it: %s\n", cyan("bd create \"Team planning issue\" -p 2"))
|
||||
fmt.Println()
|
||||
|
||||
if protectedMain {
|
||||
fmt.Println("Next steps:")
|
||||
fmt.Printf(" 1. %s\n", "Share the "+syncBranch+" branch with your team")
|
||||
fmt.Printf(" 2. %s\n", "Team members: git pull origin "+syncBranch)
|
||||
fmt.Printf(" 3. %s\n", "Periodically: merge "+syncBranch+" to main via PR")
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getGitBranch returns the current git branch name
|
||||
func getGitBranch() (string, error) {
|
||||
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
// createSyncBranch creates a new branch for beads sync
|
||||
func createSyncBranch(branchName string) error {
|
||||
// Check if branch already exists
|
||||
cmd := exec.Command("git", "rev-parse", "--verify", branchName)
|
||||
if err := cmd.Run(); err == nil {
|
||||
// Branch exists, nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create new branch from current HEAD
|
||||
cmd = exec.Command("git", "checkout", "-b", branchName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Switch back to original branch
|
||||
currentBranch, err := getGitBranch()
|
||||
if err == nil && currentBranch != branchName {
|
||||
cmd = exec.Command("git", "checkout", "-")
|
||||
_ = cmd.Run() // Ignore error, branch creation succeeded
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -483,5 +483,5 @@ bd create "Issue" -p 1
|
||||
|
||||
- [bd-8rd](/.beads/beads.jsonl#bd-8rd) - Migration and onboarding epic
|
||||
- [bd-mlcz](/.beads/beads.jsonl#bd-mlcz) - `bd migrate` command (planned)
|
||||
- [bd-kla1](/.beads/beads.jsonl#bd-kla1) - `bd init --contributor` wizard (planned)
|
||||
- [bd-twlr](/.beads/beads.jsonl#bd-twlr) - `bd init --team` wizard (planned)
|
||||
- [bd-kla1](/.beads/beads.jsonl#bd-kla1) - `bd init --contributor` wizard ✅ implemented
|
||||
- [bd-twlr](/.beads/beads.jsonl#bd-twlr) - `bd init --team` wizard ✅ implemented
|
||||
|
||||
@@ -12,6 +12,8 @@ This directory contains examples of how to integrate bd with AI agents and workf
|
||||
<!-- REMOVED (bd-4c74): branch-merge example - collision resolution no longer needed with hash IDs -->
|
||||
- **[claude-desktop-mcp/](claude-desktop-mcp/)** - MCP server for Claude Desktop integration
|
||||
- **[claude-code-skill/](claude-code-skill/)** - Claude Code skill for effective beads usage patterns
|
||||
- **[contributor-workflow/](contributor-workflow/)** - OSS contributor setup with separate planning repo
|
||||
- **[protected-branch/](protected-branch/)** - Protected branch workflow for team collaboration
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
||||
211
examples/contributor-workflow/README.md
Normal file
211
examples/contributor-workflow/README.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# OSS Contributor Workflow Example
|
||||
|
||||
This example demonstrates how to use beads' contributor workflow to keep your planning issues separate from upstream PRs when contributing to open-source projects.
|
||||
|
||||
## Problem
|
||||
|
||||
When contributing to OSS projects, you want to:
|
||||
- Track your planning, todos, and design notes
|
||||
- Keep experimental work organized
|
||||
- **NOT** pollute upstream PRs with your personal planning issues
|
||||
|
||||
## Solution
|
||||
|
||||
Use `bd init --contributor` to set up a separate planning repository that never gets committed to the upstream project.
|
||||
|
||||
## Setup
|
||||
|
||||
### Step 1: Fork and Clone
|
||||
|
||||
```bash
|
||||
# Fork the project on GitHub, then clone your fork
|
||||
git clone https://github.com/YOUR_USERNAME/project.git
|
||||
cd project
|
||||
|
||||
# Add upstream remote (important for fork detection!)
|
||||
git remote add upstream https://github.com/ORIGINAL_OWNER/project.git
|
||||
```
|
||||
|
||||
### Step 2: Initialize Beads with Contributor Wizard
|
||||
|
||||
```bash
|
||||
# Run the contributor setup wizard
|
||||
bd init --contributor
|
||||
```
|
||||
|
||||
The wizard will:
|
||||
1. ✅ Detect that you're in a fork (checks for 'upstream' remote)
|
||||
2. ✅ Prompt you to create a planning repo (`~/.beads-planning` by default)
|
||||
3. ✅ Configure auto-routing so your planning stays separate
|
||||
4. ✅ Initialize the planning repo with git
|
||||
|
||||
### Step 3: Start Working
|
||||
|
||||
```bash
|
||||
# Create a planning issue
|
||||
bd create "Plan how to fix bug X" -p 2
|
||||
|
||||
# This issue goes to ~/.beads-planning automatically!
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Auto-Routing
|
||||
|
||||
When you create issues as a contributor:
|
||||
|
||||
```bash
|
||||
bd create "Fix authentication bug" -p 1
|
||||
```
|
||||
|
||||
Beads automatically routes this to your planning repo (`~/.beads-planning/.beads/beads.jsonl`), not the current repo.
|
||||
|
||||
### Viewing Issues
|
||||
|
||||
```bash
|
||||
# See all issues (from both repos)
|
||||
bd list
|
||||
|
||||
# See only current repo issues
|
||||
bd list --source-repo .
|
||||
|
||||
# See only planning issues
|
||||
bd list --source-repo ~/.beads-planning
|
||||
```
|
||||
|
||||
### Discovered Work
|
||||
|
||||
When you discover work while implementing:
|
||||
|
||||
```bash
|
||||
# The new issue inherits source_repo from parent
|
||||
bd create "Found edge case in auth" -p 1 --deps discovered-from:bd-42
|
||||
```
|
||||
|
||||
### Committing Code (Not Planning)
|
||||
|
||||
Your code changes get committed to the fork, but planning issues stay separate:
|
||||
|
||||
```bash
|
||||
# Only commits to fork (not planning repo)
|
||||
git add src/auth.go
|
||||
git commit -m "Fix: authentication bug"
|
||||
git push origin my-feature-branch
|
||||
```
|
||||
|
||||
Your planning issues in `~/.beads-planning` **never appear in PRs**.
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```bash
|
||||
# 1. Create fork and clone
|
||||
git clone https://github.com/you/upstream-project.git
|
||||
cd upstream-project
|
||||
git remote add upstream https://github.com/upstream/upstream-project.git
|
||||
|
||||
# 2. Run contributor setup
|
||||
bd init --contributor
|
||||
# Wizard detects fork ✓
|
||||
# Creates ~/.beads-planning ✓
|
||||
# Configures auto-routing ✓
|
||||
|
||||
# 3. Plan your work (routes to planning repo)
|
||||
bd create "Research how auth module works" -p 2
|
||||
bd create "Design fix for bug #123" -p 1
|
||||
bd ready # Shows planning issues
|
||||
|
||||
# 4. Implement (commit code only)
|
||||
git checkout -b fix-auth-bug
|
||||
# ... make changes ...
|
||||
git add . && git commit -m "Fix: auth bug"
|
||||
|
||||
# 5. Track discovered work (stays in planning repo)
|
||||
bd create "Found related issue in logout" -p 2 --deps discovered-from:bd-abc
|
||||
|
||||
# 6. Push code (planning never included)
|
||||
git push origin fix-auth-bug
|
||||
# Create PR on GitHub - zero planning pollution!
|
||||
|
||||
# 7. Clean up after PR merges
|
||||
bd close bd-abc --reason "PR merged"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The wizard configures these settings in `.beads/beads.db`:
|
||||
|
||||
```yaml
|
||||
contributor:
|
||||
planning_repo: ~/.beads-planning
|
||||
auto_route: true
|
||||
```
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
If you prefer manual setup:
|
||||
|
||||
```bash
|
||||
# Initialize beads normally
|
||||
bd init
|
||||
|
||||
# Configure planning repo
|
||||
bd config set contributor.planning_repo ~/.beads-planning
|
||||
bd config set contributor.auto_route true
|
||||
```
|
||||
|
||||
## Multi-Repository View
|
||||
|
||||
Beads aggregates issues from multiple repos:
|
||||
|
||||
```bash
|
||||
# List issues from all configured repos
|
||||
bd list
|
||||
|
||||
# Filter by source repository
|
||||
bd list --source-repo . # Current repo only
|
||||
bd list --source-repo ~/.beads-planning # Planning repo only
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **Clean PRs** - No personal todos in upstream contributions
|
||||
✅ **Private planning** - Experimental work stays local
|
||||
✅ **Git ledger** - Everything is version controlled
|
||||
✅ **Unified view** - See all issues with `bd list`
|
||||
✅ **Auto-routing** - No manual sorting needed
|
||||
|
||||
## Common Questions
|
||||
|
||||
### Q: What if I want some issues in the upstream repo?
|
||||
|
||||
A: Override auto-routing with `--source-repo` flag:
|
||||
|
||||
```bash
|
||||
bd create "Document new API" -p 2 --source-repo .
|
||||
```
|
||||
|
||||
### Q: Can I change the planning repo location?
|
||||
|
||||
A: Yes, configure it:
|
||||
|
||||
```bash
|
||||
bd config set contributor.planning_repo /path/to/my-planning
|
||||
```
|
||||
|
||||
### Q: What if I have push access to upstream?
|
||||
|
||||
A: The wizard will ask if you want a planning repo anyway. You can say "no" to store everything in the current repo.
|
||||
|
||||
### Q: How do I disable auto-routing?
|
||||
|
||||
A: Turn it off:
|
||||
|
||||
```bash
|
||||
bd config set contributor.auto_route false
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Multi-Repo Migration Guide](../../docs/MULTI_REPO_MIGRATION.md)
|
||||
- [Team Workflow Example](../team-workflow/)
|
||||
- [Protected Branch Setup](../protected-branch/)
|
||||
402
examples/team-workflow/README.md
Normal file
402
examples/team-workflow/README.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# Team Workflow Example
|
||||
|
||||
This example demonstrates how to use beads for team collaboration with shared repositories.
|
||||
|
||||
## Problem
|
||||
|
||||
When working as a team on a shared repository, you want to:
|
||||
- Track issues collaboratively
|
||||
- Keep everyone in sync via git
|
||||
- Handle protected main branches
|
||||
- Maintain clean git history
|
||||
|
||||
## Solution
|
||||
|
||||
Use `bd init --team` to set up team collaboration with automatic sync and optional protected branch support.
|
||||
|
||||
## Setup
|
||||
|
||||
### Step 1: Initialize Team Workflow
|
||||
|
||||
```bash
|
||||
# In your shared repository
|
||||
cd my-project
|
||||
|
||||
# Run the team setup wizard
|
||||
bd init --team
|
||||
```
|
||||
|
||||
The wizard will:
|
||||
1. ✅ Detect your git configuration
|
||||
2. ✅ Ask if main branch is protected
|
||||
3. ✅ Configure sync branch (if needed)
|
||||
4. ✅ Set up automatic sync
|
||||
5. ✅ Enable team mode
|
||||
|
||||
### Step 2: Protected Branch Configuration
|
||||
|
||||
If your main branch is protected (GitHub/GitLab), the wizard will:
|
||||
- Create a separate `beads-metadata` branch for issue updates
|
||||
- Configure beads to commit to this branch automatically
|
||||
- Set up periodic PR workflow for merging to main
|
||||
|
||||
### Step 3: Team Members Join
|
||||
|
||||
Other team members just need to:
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/org/project.git
|
||||
cd project
|
||||
|
||||
# Initialize beads (auto-imports existing issues)
|
||||
bd init
|
||||
|
||||
# Start working!
|
||||
bd ready
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Direct Commits (No Protected Branch)
|
||||
|
||||
If main isn't protected:
|
||||
|
||||
```bash
|
||||
# Create issue
|
||||
bd create "Implement feature X" -p 1
|
||||
|
||||
# Daemon auto-commits to main
|
||||
# (or run 'bd sync' manually)
|
||||
|
||||
# Pull to see team's issues
|
||||
git pull
|
||||
bd list
|
||||
```
|
||||
|
||||
### Protected Branch Workflow
|
||||
|
||||
If main is protected:
|
||||
|
||||
```bash
|
||||
# Create issue
|
||||
bd create "Implement feature X" -p 1
|
||||
|
||||
# Daemon commits to beads-metadata branch
|
||||
# (or run 'bd sync' manually)
|
||||
|
||||
# Push beads-metadata
|
||||
git push origin beads-metadata
|
||||
|
||||
# Periodically: merge beads-metadata to main via PR
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The wizard configures:
|
||||
|
||||
```yaml
|
||||
team:
|
||||
enabled: true
|
||||
sync_branch: beads-metadata # or main if not protected
|
||||
|
||||
daemon:
|
||||
auto_commit: true
|
||||
auto_push: true
|
||||
```
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
```bash
|
||||
# Enable team mode
|
||||
bd config set team.enabled true
|
||||
|
||||
# Set sync branch
|
||||
bd config set team.sync_branch beads-metadata
|
||||
|
||||
# Enable auto-sync
|
||||
bd config set daemon.auto_commit true
|
||||
bd config set daemon.auto_push true
|
||||
```
|
||||
|
||||
## Example Workflows
|
||||
|
||||
### Scenario 1: Unprotected Main
|
||||
|
||||
```bash
|
||||
# Alice creates an issue
|
||||
bd create "Fix authentication bug" -p 1
|
||||
|
||||
# Daemon commits and pushes to main
|
||||
# (auto-sync enabled)
|
||||
|
||||
# Bob pulls changes
|
||||
git pull
|
||||
bd list # Sees Alice's issue
|
||||
|
||||
# Bob claims it
|
||||
bd update bd-abc --status in_progress
|
||||
|
||||
# Daemon commits Bob's update
|
||||
# Alice pulls and sees Bob is working on it
|
||||
```
|
||||
|
||||
### Scenario 2: Protected Main
|
||||
|
||||
```bash
|
||||
# Alice creates an issue
|
||||
bd create "Add new API endpoint" -p 1
|
||||
|
||||
# Daemon commits to beads-metadata
|
||||
git push origin beads-metadata
|
||||
|
||||
# Bob pulls beads-metadata
|
||||
git pull origin beads-metadata
|
||||
bd list # Sees Alice's issue
|
||||
|
||||
# Later: merge beads-metadata to main via PR
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git merge beads-metadata
|
||||
# Create PR, get approval, merge
|
||||
```
|
||||
|
||||
## Team Workflows
|
||||
|
||||
### Daily Standup
|
||||
|
||||
```bash
|
||||
# See what everyone's working on
|
||||
bd list --status in_progress
|
||||
|
||||
# See what's ready for work
|
||||
bd ready
|
||||
|
||||
# See recently closed issues
|
||||
bd list --status closed --limit 10
|
||||
```
|
||||
|
||||
### Sprint Planning
|
||||
|
||||
```bash
|
||||
# Create sprint issues
|
||||
bd create "Implement user auth" -p 1
|
||||
bd create "Add profile page" -p 1
|
||||
bd create "Fix responsive layout" -p 2
|
||||
|
||||
# Assign to team members
|
||||
bd update bd-abc --assignee alice
|
||||
bd update bd-def --assignee bob
|
||||
|
||||
# Track dependencies
|
||||
bd dep add bd-def bd-abc --type blocks
|
||||
```
|
||||
|
||||
### PR Integration
|
||||
|
||||
```bash
|
||||
# Create issue for PR work
|
||||
bd create "Refactor auth module" -p 1
|
||||
|
||||
# Work on it
|
||||
bd update bd-abc --status in_progress
|
||||
|
||||
# Open PR with issue reference
|
||||
git push origin feature-branch
|
||||
# PR title: "feat: refactor auth module (bd-abc)"
|
||||
|
||||
# Close when PR merges
|
||||
bd close bd-abc --reason "PR #123 merged"
|
||||
```
|
||||
|
||||
## Sync Strategies
|
||||
|
||||
### Auto-Sync (Recommended)
|
||||
|
||||
Daemon commits and pushes automatically:
|
||||
|
||||
```bash
|
||||
bd daemon start --auto-commit --auto-push
|
||||
```
|
||||
|
||||
Benefits:
|
||||
- ✅ Always in sync
|
||||
- ✅ No manual intervention
|
||||
- ✅ Real-time collaboration
|
||||
|
||||
### Manual Sync
|
||||
|
||||
Sync when you want:
|
||||
|
||||
```bash
|
||||
bd sync # Export, commit, pull, import, push
|
||||
```
|
||||
|
||||
Benefits:
|
||||
- ✅ Full control
|
||||
- ✅ Batch updates
|
||||
- ✅ Review before push
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
Hash-based IDs prevent most conflicts. If conflicts occur:
|
||||
|
||||
```bash
|
||||
# During git pull/merge
|
||||
git pull origin beads-metadata
|
||||
# CONFLICT in .beads/beads.jsonl
|
||||
|
||||
# Option 1: Accept remote
|
||||
git checkout --theirs .beads/beads.jsonl
|
||||
bd import -i .beads/beads.jsonl
|
||||
|
||||
# Option 2: Accept local
|
||||
git checkout --ours .beads/beads.jsonl
|
||||
bd import -i .beads/beads.jsonl
|
||||
|
||||
# Option 3: Use beads-merge tool (recommended)
|
||||
# See AGENTS.md for beads-merge integration
|
||||
|
||||
git add .beads/beads.jsonl
|
||||
git commit
|
||||
```
|
||||
|
||||
## Protected Branch Best Practices
|
||||
|
||||
### For Protected Main:
|
||||
|
||||
1. **Create beads-metadata branch**
|
||||
```bash
|
||||
git checkout -b beads-metadata
|
||||
git push origin beads-metadata
|
||||
```
|
||||
|
||||
2. **Configure protection rules**
|
||||
- Allow direct pushes to beads-metadata
|
||||
- Require PR for main
|
||||
|
||||
3. **Periodic PR workflow**
|
||||
```bash
|
||||
# Once per day/sprint
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout beads-metadata
|
||||
git pull origin beads-metadata
|
||||
git checkout main
|
||||
git merge beads-metadata
|
||||
# Create PR, get approval, merge
|
||||
```
|
||||
|
||||
4. **Keep beads-metadata clean**
|
||||
```bash
|
||||
# After PR merges
|
||||
git checkout beads-metadata
|
||||
git rebase main
|
||||
git push origin beads-metadata --force-with-lease
|
||||
```
|
||||
|
||||
## Common Questions
|
||||
|
||||
### Q: How do team members see each other's issues?
|
||||
|
||||
A: Issues are stored in `.beads/beads.jsonl` which is version-controlled. Pull from git to sync.
|
||||
|
||||
```bash
|
||||
git pull
|
||||
bd list # See everyone's issues
|
||||
```
|
||||
|
||||
### Q: What if two people create issues at the same time?
|
||||
|
||||
A: Hash-based IDs prevent collisions. Even if created simultaneously, they get different IDs.
|
||||
|
||||
### Q: How do I disable auto-sync?
|
||||
|
||||
A: Turn it off:
|
||||
|
||||
```bash
|
||||
bd config set daemon.auto_commit false
|
||||
bd config set daemon.auto_push false
|
||||
|
||||
# Sync manually
|
||||
bd sync
|
||||
```
|
||||
|
||||
### Q: Can we use different sync branches per person?
|
||||
|
||||
A: Not recommended. Use a single shared branch for consistency. If needed:
|
||||
|
||||
```bash
|
||||
bd config set sync.branch my-custom-branch
|
||||
```
|
||||
|
||||
### Q: What about CI/CD integration?
|
||||
|
||||
A: Add to your CI pipeline:
|
||||
|
||||
```bash
|
||||
# In .github/workflows/main.yml
|
||||
- name: Sync beads issues
|
||||
run: |
|
||||
bd sync
|
||||
git push origin beads-metadata
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Daemon not committing
|
||||
|
||||
Check daemon status:
|
||||
|
||||
```bash
|
||||
bd daemon status
|
||||
bd daemons list
|
||||
```
|
||||
|
||||
Verify config:
|
||||
|
||||
```bash
|
||||
bd config get daemon.auto_commit
|
||||
bd config get daemon.auto_push
|
||||
```
|
||||
|
||||
Restart daemon:
|
||||
|
||||
```bash
|
||||
bd daemon stop
|
||||
bd daemon start --auto-commit --auto-push
|
||||
```
|
||||
|
||||
### Issue: Merge conflicts in JSONL
|
||||
|
||||
Use beads-merge (see [AGENTS.md](../../AGENTS.md#advanced-intelligent-merge-tools)) or resolve manually:
|
||||
|
||||
```bash
|
||||
git checkout --theirs .beads/beads.jsonl
|
||||
bd import -i .beads/beads.jsonl
|
||||
git add .beads/beads.jsonl
|
||||
git commit
|
||||
```
|
||||
|
||||
### Issue: Issues not syncing
|
||||
|
||||
Manually sync:
|
||||
|
||||
```bash
|
||||
bd sync
|
||||
git push
|
||||
```
|
||||
|
||||
Check for conflicts:
|
||||
|
||||
```bash
|
||||
git status
|
||||
bd validate --checks=conflicts
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Protected Branch Setup](../protected-branch/)
|
||||
- [Contributor Workflow](../contributor-workflow/)
|
||||
- [Multi-Repo Migration Guide](../../docs/MULTI_REPO_MIGRATION.md)
|
||||
- [Advanced Merge Tools](../../AGENTS.md#advanced-intelligent-merge-tools)
|
||||
Reference in New Issue
Block a user