diff --git a/cmd/bd/setup.go b/cmd/bd/setup.go index 74cda015..77df295a 100644 --- a/cmd/bd/setup.go +++ b/cmd/bd/setup.go @@ -172,6 +172,9 @@ func runRecipe(name string) { case "cursor": runCursorRecipe() return + case "junie": + runJunieRecipe() + return } // For all other recipes (built-in or user), use generic file-based install @@ -296,6 +299,18 @@ func runAiderRecipe() { setup.InstallAider() } +func runJunieRecipe() { + if setupCheck { + setup.CheckJunie() + return + } + if setupRemove { + setup.RemoveJunie() + return + } + setup.InstallJunie() +} + func findBeadsDir() string { // Check for .beads in current directory if info, err := os.Stat(".beads"); err == nil && info.IsDir() { diff --git a/cmd/bd/setup/junie.go b/cmd/bd/setup/junie.go new file mode 100644 index 00000000..026a36da --- /dev/null +++ b/cmd/bd/setup/junie.go @@ -0,0 +1,241 @@ +package setup + +import ( + "encoding/json" + "fmt" + "os" +) + +const junieGuidelinesTemplate = `# Beads Issue Tracking Instructions + +This project uses **Beads (bd)** for issue tracking. Use the bd CLI or MCP tools for all task management. + +## Core Workflow Rules + +1. **Track ALL work in bd** - Never use markdown TODOs or comment-based task lists +2. **Check ready work first** - Run ` + "`bd ready`" + ` to find unblocked issues +3. **Always include descriptions** - Provide meaningful context when creating issues +4. **Link discovered work** - Use ` + "`discovered-from`" + ` dependencies for issues found during work +5. **Sync at session end** - Run ` + "`bd sync`" + ` before ending your session + +## Quick Command Reference + +### Finding Work +` + "```bash" + ` +bd ready # Show unblocked issues ready for work +bd list --status open # List all open issues +bd show # View issue details +bd blocked # Show blocked issues and their blockers +` + "```" + ` + +### Creating Issues +` + "```bash" + ` +bd create "Title" --description="Details" -t bug|feature|task -p 0-4 --json +bd create "Found bug" --description="Details" --deps discovered-from:bd-42 --json +` + "```" + ` + +### Working on Issues +` + "```bash" + ` +bd update --status in_progress # Claim work +bd update --priority 1 # Change priority +bd close --reason "Completed" # Mark complete +` + "```" + ` + +### Dependencies +` + "```bash" + ` +bd dep add # Add dependency (issue depends on depends-on) +bd dep add --type=related # Soft link +` + "```" + ` + +### Syncing +` + "```bash" + ` +bd sync # ALWAYS run at session end - commits and pushes changes +` + "```" + ` + +## Issue Types + +- ` + "`bug`" + ` - Something broken that needs fixing +- ` + "`feature`" + ` - New functionality +- ` + "`task`" + ` - Work item (tests, docs, refactoring) +- ` + "`epic`" + ` - Large feature composed of multiple issues +- ` + "`chore`" + ` - Maintenance work (dependencies, tooling) + +## Priorities + +- ` + "`0`" + ` - Critical (security, data loss, broken builds) +- ` + "`1`" + ` - High (major features, important bugs) +- ` + "`2`" + ` - Medium (default, nice-to-have) +- ` + "`3`" + ` - Low (polish, optimization) +- ` + "`4`" + ` - Backlog (future ideas) + +## MCP Tools Available + +If the MCP server is configured, you can use these tools directly: +- ` + "`mcp_beads_ready`" + ` - Find ready tasks +- ` + "`mcp_beads_list`" + ` - List issues with filters +- ` + "`mcp_beads_show`" + ` - Show issue details +- ` + "`mcp_beads_create`" + ` - Create new issues +- ` + "`mcp_beads_update`" + ` - Update issue status/priority +- ` + "`mcp_beads_close`" + ` - Close completed issues +- ` + "`mcp_beads_dep`" + ` - Manage dependencies +- ` + "`mcp_beads_blocked`" + ` - Show blocked issues +- ` + "`mcp_beads_stats`" + ` - Get issue statistics + +## Important Rules + +- ✅ Use bd for ALL task tracking +- ✅ Always use ` + "`--json`" + ` flag for programmatic use +- ✅ Link discovered work with ` + "`discovered-from`" + ` dependencies +- ✅ Check ` + "`bd ready`" + ` before asking "what should I work on?" +- ✅ Run ` + "`bd sync`" + ` at end of session +- ❌ Do NOT create markdown TODO lists +- ❌ Do NOT use external issue trackers +- ❌ Do NOT duplicate tracking systems + +For more details, run ` + "`bd --help`" + ` or see the project's AGENTS.md file. +` + +// junieMCPConfig generates the MCP configuration for Junie +func junieMCPConfig() map[string]interface{} { + return map[string]interface{}{ + "mcpServers": map[string]interface{}{ + "beads": map[string]interface{}{ + "command": "bd", + "args": []string{"mcp"}, + }, + }, + } +} + +// InstallJunie installs Junie integration +func InstallJunie() { + guidelinesPath := ".junie/guidelines.md" + mcpPath := ".junie/mcp/mcp.json" + + fmt.Println("Installing Junie integration...") + + // Ensure .junie directory exists + if err := EnsureDir(".junie", 0755); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + // Ensure .junie/mcp directory exists + if err := EnsureDir(".junie/mcp", 0755); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + // Write guidelines file + if err := atomicWriteFile(guidelinesPath, []byte(junieGuidelinesTemplate)); err != nil { + fmt.Fprintf(os.Stderr, "Error: write guidelines: %v\n", err) + os.Exit(1) + } + + // Write MCP config file + mcpConfig := junieMCPConfig() + mcpData, err := json.MarshalIndent(mcpConfig, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "Error: marshal MCP config: %v\n", err) + os.Exit(1) + } + + if err := atomicWriteFile(mcpPath, mcpData); err != nil { + fmt.Fprintf(os.Stderr, "Error: write MCP config: %v\n", err) + os.Exit(1) + } + + fmt.Printf("\n✓ Junie integration installed\n") + fmt.Printf(" Guidelines: %s (agent instructions)\n", guidelinesPath) + fmt.Printf(" MCP Config: %s (MCP server configuration)\n", mcpPath) + fmt.Println("\nJunie will automatically read these files on session start.") + fmt.Println("The MCP server provides direct access to beads tools.") +} + +// CheckJunie checks if Junie integration is installed +func CheckJunie() { + guidelinesPath := ".junie/guidelines.md" + mcpPath := ".junie/mcp/mcp.json" + + guidelinesExists := false + mcpExists := false + + if _, err := os.Stat(guidelinesPath); err == nil { + guidelinesExists = true + } + if _, err := os.Stat(mcpPath); err == nil { + mcpExists = true + } + + if guidelinesExists && mcpExists { + fmt.Println("✓ Junie integration installed") + fmt.Printf(" Guidelines: %s\n", guidelinesPath) + fmt.Printf(" MCP Config: %s\n", mcpPath) + return + } + + if guidelinesExists { + fmt.Println("⚠ Partial Junie integration (guidelines only)") + fmt.Printf(" Guidelines: %s\n", guidelinesPath) + fmt.Println(" Missing: MCP config") + fmt.Println(" Run: bd setup junie (to complete installation)") + os.Exit(1) + } + + if mcpExists { + fmt.Println("⚠ Partial Junie integration (MCP only)") + fmt.Printf(" MCP Config: %s\n", mcpPath) + fmt.Println(" Missing: Guidelines") + fmt.Println(" Run: bd setup junie (to complete installation)") + os.Exit(1) + } + + fmt.Println("✗ Junie integration not installed") + fmt.Println(" Run: bd setup junie") + os.Exit(1) +} + +// RemoveJunie removes Junie integration +func RemoveJunie() { + guidelinesPath := ".junie/guidelines.md" + mcpPath := ".junie/mcp/mcp.json" + mcpDir := ".junie/mcp" + junieDir := ".junie" + + fmt.Println("Removing Junie integration...") + + removed := false + + // Remove guidelines + if err := os.Remove(guidelinesPath); err != nil { + if !os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "Error: failed to remove guidelines: %v\n", err) + os.Exit(1) + } + } else { + removed = true + } + + // Remove MCP config + if err := os.Remove(mcpPath); err != nil { + if !os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "Error: failed to remove MCP config: %v\n", err) + os.Exit(1) + } + } else { + removed = true + } + + // Try to remove .junie/mcp directory if empty + _ = os.Remove(mcpDir) + + // Try to remove .junie directory if empty + _ = os.Remove(junieDir) + + if !removed { + fmt.Println("No Junie integration files found") + return + } + + fmt.Println("✓ Removed Junie integration") +} diff --git a/cmd/bd/setup/junie_test.go b/cmd/bd/setup/junie_test.go new file mode 100644 index 00000000..8785d1ea --- /dev/null +++ b/cmd/bd/setup/junie_test.go @@ -0,0 +1,501 @@ +package setup + +import ( + "encoding/json" + "os" + "strings" + "testing" +) + +func TestJunieGuidelinesTemplate(t *testing.T) { + requiredContent := []string{ + "bd ready", + "bd create", + "bd update", + "bd close", + "bd sync", + "mcp_beads_ready", + "mcp_beads_list", + "mcp_beads_create", + "bug", + "feature", + "task", + "epic", + } + + for _, req := range requiredContent { + if !strings.Contains(junieGuidelinesTemplate, req) { + t.Errorf("junieGuidelinesTemplate missing required content: %q", req) + } + } +} + +func TestJunieMCPConfig(t *testing.T) { + config := junieMCPConfig() + + // Verify structure + mcpServers, ok := config["mcpServers"].(map[string]interface{}) + if !ok { + t.Fatal("mcpServers key missing or wrong type") + } + + beads, ok := mcpServers["beads"].(map[string]interface{}) + if !ok { + t.Fatal("beads server config missing or wrong type") + } + + command, ok := beads["command"].(string) + if !ok || command != "bd" { + t.Errorf("Expected command 'bd', got %v", beads["command"]) + } + + args, ok := beads["args"].([]string) + if !ok || len(args) != 1 || args[0] != "mcp" { + t.Errorf("Expected args ['mcp'], got %v", beads["args"]) + } + + // Verify it's valid JSON + data, err := json.Marshal(config) + if err != nil { + t.Errorf("MCP config should be valid JSON: %v", err) + } + + // Verify it can be unmarshaled back + var parsed map[string]interface{} + if err := json.Unmarshal(data, &parsed); err != nil { + t.Errorf("MCP config JSON should be parseable: %v", err) + } +} + +func TestInstallJunie(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + InstallJunie() + + // Verify guidelines file was created + guidelinesPath := ".junie/guidelines.md" + if !FileExists(guidelinesPath) { + t.Errorf("File was not created: %s", guidelinesPath) + } else { + data, err := os.ReadFile(guidelinesPath) + if err != nil { + t.Errorf("Failed to read %s: %v", guidelinesPath, err) + } else if string(data) != junieGuidelinesTemplate { + t.Errorf("File %s content doesn't match expected template", guidelinesPath) + } + } + + // Verify MCP config file was created + mcpPath := ".junie/mcp/mcp.json" + if !FileExists(mcpPath) { + t.Errorf("File was not created: %s", mcpPath) + } else { + data, err := os.ReadFile(mcpPath) + if err != nil { + t.Errorf("Failed to read %s: %v", mcpPath, err) + } else { + // Verify it's valid JSON + var parsed map[string]interface{} + if err := json.Unmarshal(data, &parsed); err != nil { + t.Errorf("MCP config should be valid JSON: %v", err) + } + + // Verify structure + mcpServers, ok := parsed["mcpServers"].(map[string]interface{}) + if !ok { + t.Error("mcpServers key missing or wrong type") + } else if _, ok := mcpServers["beads"]; !ok { + t.Error("beads server config missing") + } + } + } +} + +func TestInstallJunie_ExistingDirectory(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Pre-create the directories + if err := os.MkdirAll(".junie/mcp", 0755); err != nil { + t.Fatalf("failed to create directory: %v", err) + } + + // Should not fail + InstallJunie() + + // Verify files were created + if !FileExists(".junie/guidelines.md") { + t.Error("guidelines.md not created") + } + if !FileExists(".junie/mcp/mcp.json") { + t.Error("mcp.json not created") + } +} + +func TestInstallJunieIdempotent(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Run twice + InstallJunie() + firstGuidelines, _ := os.ReadFile(".junie/guidelines.md") + firstMCP, _ := os.ReadFile(".junie/mcp/mcp.json") + + InstallJunie() + secondGuidelines, _ := os.ReadFile(".junie/guidelines.md") + secondMCP, _ := os.ReadFile(".junie/mcp/mcp.json") + + if string(firstGuidelines) != string(secondGuidelines) { + t.Error("InstallJunie should be idempotent for guidelines") + } + if string(firstMCP) != string(secondMCP) { + t.Error("InstallJunie should be idempotent for MCP config") + } +} + +func TestRemoveJunie(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Install first + InstallJunie() + + // Verify files exist + files := []string{".junie/guidelines.md", ".junie/mcp/mcp.json"} + for _, f := range files { + if !FileExists(f) { + t.Fatalf("File should exist before removal: %s", f) + } + } + + // Remove + RemoveJunie() + + // Verify files are gone + for _, f := range files { + if FileExists(f) { + t.Errorf("File should have been removed: %s", f) + } + } +} + +func TestRemoveJunie_NoFiles(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Should not panic when files don't exist + RemoveJunie() +} + +func TestRemoveJunie_PartialFiles(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Create only the guidelines file + if err := os.MkdirAll(".junie", 0755); err != nil { + t.Fatalf("failed to create directory: %v", err) + } + if err := os.WriteFile(".junie/guidelines.md", []byte(junieGuidelinesTemplate), 0644); err != nil { + t.Fatalf("failed to create guidelines file: %v", err) + } + + // Should not panic + RemoveJunie() + + // Guidelines should be removed + if FileExists(".junie/guidelines.md") { + t.Error("Guidelines file should have been removed") + } +} + +func TestRemoveJunie_DirectoryCleanup(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Install + InstallJunie() + + // Remove + RemoveJunie() + + // Directories should be cleaned up if empty + if DirExists(".junie/mcp") { + t.Error(".junie/mcp directory should be removed when empty") + } + if DirExists(".junie") { + t.Error(".junie directory should be removed when empty") + } +} + +func TestRemoveJunie_DirectoryWithOtherFiles(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Install + InstallJunie() + + // Add another file to .junie directory + if err := os.WriteFile(".junie/other.txt", []byte("keep me"), 0644); err != nil { + t.Fatalf("failed to create other file: %v", err) + } + + // Remove + RemoveJunie() + + // Directory should still exist (has other files) + if !DirExists(".junie") { + t.Error("Directory should not be removed when it has other files") + } + + // Other file should still exist + if !FileExists(".junie/other.txt") { + t.Error("Other files should be preserved") + } +} + +func TestCheckJunie_NotInstalled(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // CheckJunie calls os.Exit(1) when not installed + // We can't easily test that, but we document expected behavior +} + +func TestCheckJunie_Installed(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Install first + InstallJunie() + + // Should not panic or exit + CheckJunie() +} + +func TestCheckJunie_PartialInstall_GuidelinesOnly(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Create only guidelines + if err := os.MkdirAll(".junie", 0755); err != nil { + t.Fatalf("failed to create directory: %v", err) + } + if err := os.WriteFile(".junie/guidelines.md", []byte(junieGuidelinesTemplate), 0644); err != nil { + t.Fatalf("failed to create guidelines file: %v", err) + } + + // CheckJunie calls os.Exit(1) for partial installation + // We can't easily test that, but we document expected behavior +} + +func TestCheckJunie_PartialInstall_MCPOnly(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + // Create only MCP config + if err := os.MkdirAll(".junie/mcp", 0755); err != nil { + t.Fatalf("failed to create directory: %v", err) + } + mcpConfig := junieMCPConfig() + mcpData, _ := json.MarshalIndent(mcpConfig, "", " ") + if err := os.WriteFile(".junie/mcp/mcp.json", mcpData, 0644); err != nil { + t.Fatalf("failed to create MCP config file: %v", err) + } + + // CheckJunie calls os.Exit(1) for partial installation + // We can't easily test that, but we document expected behavior +} + +func TestJunieFilePaths(t *testing.T) { + origDir, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + + tmpDir := t.TempDir() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to change to temp directory: %v", err) + } + defer func() { + if err := os.Chdir(origDir); err != nil { + t.Fatalf("failed to restore working directory: %v", err) + } + }() + + InstallJunie() + + // Check expected file paths + expectedPaths := []string{ + ".junie/guidelines.md", + ".junie/mcp/mcp.json", + } + + for _, path := range expectedPaths { + if !FileExists(path) { + t.Errorf("Expected file at %s", path) + } + } +} + +func TestJunieGuidelinesWorkflowPattern(t *testing.T) { + // Verify guidelines contain the workflow patterns Junie users need + guidelines := junieGuidelinesTemplate + + // Should mention core workflow commands + if !strings.Contains(guidelines, "bd ready") { + t.Error("Should mention bd ready") + } + if !strings.Contains(guidelines, "bd sync") { + t.Error("Should mention bd sync") + } + + // Should explain MCP tools + if !strings.Contains(guidelines, "MCP Tools Available") { + t.Error("Should have MCP Tools section") + } +} diff --git a/integrations/junie/README.md b/integrations/junie/README.md new file mode 100644 index 00000000..f6ea2f70 --- /dev/null +++ b/integrations/junie/README.md @@ -0,0 +1,89 @@ +# Junie Integration for Beads + +Integration for [Junie](https://www.jetbrains.com/junie/) (JetBrains AI Agent) with beads issue tracking. + +## Prerequisites + +```bash +# Install beads +curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize beads in your project +bd init +``` + +## Installation + +```bash +bd setup junie +``` + +This creates: +- `.junie/guidelines.md` - Agent instructions for beads workflow +- `.junie/mcp/mcp.json` - MCP server configuration + +## What Gets Installed + +### Guidelines (`.junie/guidelines.md`) + +Junie automatically reads this file on session start. It contains: +- Core workflow rules for using beads +- Command reference for the `bd` CLI +- Issue types and priorities +- MCP tool documentation + +### MCP Config (`.junie/mcp/mcp.json`) + +Configures the beads MCP server so Junie can use beads tools directly: + +```json +{ + "mcpServers": { + "beads": { + "command": "bd", + "args": ["mcp"] + } + } +} +``` + +## Usage + +Once installed, Junie will: +1. Read workflow instructions from `.junie/guidelines.md` +2. Have access to beads MCP tools for direct issue management +3. Be able to use `bd` CLI commands + +### MCP Tools Available + +- `mcp_beads_ready` - Find tasks ready for work +- `mcp_beads_list` - List issues with filters +- `mcp_beads_show` - Show issue details +- `mcp_beads_create` - Create new issues +- `mcp_beads_update` - Update issue status/priority +- `mcp_beads_close` - Close completed issues +- `mcp_beads_dep` - Manage dependencies +- `mcp_beads_blocked` - Show blocked issues +- `mcp_beads_stats` - Get issue statistics + +## Verification + +```bash +bd setup junie --check +``` + +## Removal + +```bash +bd setup junie --remove +``` + +## Related + +- `bd prime` - Get full workflow context +- `bd ready` - Find unblocked work +- `bd sync` - Sync changes to git (run at session end) + +## License + +Same as beads (see repository root). diff --git a/internal/recipes/recipes.go b/internal/recipes/recipes.go index 4060afcf..62c5a02c 100644 --- a/internal/recipes/recipes.go +++ b/internal/recipes/recipes.go @@ -90,6 +90,12 @@ var BuiltinRecipes = map[string]Recipe{ Description: "Aider config and instruction files", Paths: []string{".aider.conf.yml", ".aider/BEADS.md", ".aider/README.md"}, }, + "junie": { + Name: "Junie", + Type: TypeMultiFile, + Description: "Junie guidelines and MCP configuration", + Paths: []string{".junie/guidelines.md", ".junie/mcp/mcp.json"}, + }, } // UserRecipes holds recipes loaded from user config file. diff --git a/website/docs/integrations/junie.md b/website/docs/integrations/junie.md new file mode 100644 index 00000000..e606d50f --- /dev/null +++ b/website/docs/integrations/junie.md @@ -0,0 +1,220 @@ +--- +id: junie +title: Junie +sidebar_position: 4 +--- + +# Junie Integration + +How to use beads with Junie (JetBrains AI Agent). + +## Setup + +### Quick Setup + +```bash +bd setup junie +``` + +This creates: +- **`.junie/guidelines.md`** - Agent instructions for beads workflow +- **`.junie/mcp/mcp.json`** - MCP server configuration + +### Verify Setup + +```bash +bd setup junie --check +``` + +## How It Works + +1. **Session starts** → Junie reads `.junie/guidelines.md` for workflow context +2. **MCP tools available** → Junie can use beads MCP tools directly +3. **You work** → Use `bd` CLI commands or MCP tools +4. **Session ends** → Run `bd sync` to save work to git + +## Configuration Files + +### Guidelines (`.junie/guidelines.md`) + +Contains workflow instructions that Junie reads automatically: +- Core workflow rules +- Command reference +- Issue types and priorities +- MCP tool documentation + +### MCP Config (`.junie/mcp/mcp.json`) + +Configures the beads MCP server: + +```json +{ + "mcpServers": { + "beads": { + "command": "bd", + "args": ["mcp"] + } + } +} +``` + +## MCP Tools + +With MCP configured, Junie can use these tools directly: + +| Tool | Description | +| --- | --- | +| `mcp_beads_ready` | Find tasks ready for work | +| `mcp_beads_list` | List issues with filters | +| `mcp_beads_show` | Show issue details | +| `mcp_beads_create` | Create new issues | +| `mcp_beads_update` | Update issue status/priority | +| `mcp_beads_close` | Close completed issues | +| `mcp_beads_dep` | Manage dependencies | +| `mcp_beads_blocked` | Show blocked issues | +| `mcp_beads_stats` | Get issue statistics | + +## CLI Commands + +You can also use the `bd` CLI directly: + +### Creating Issues + +```bash +# Always include description for context +bd create "Fix authentication bug" \ + --description="Login fails with special characters in password" \ + -t bug -p 1 --json + +# Link discovered issues +bd create "Found SQL injection" \ + --description="User input not sanitized in query builder" \ + --deps discovered-from:bd-42 --json +``` + +### Working on Issues + +```bash +# Find ready work +bd ready --json + +# Start work +bd update bd-42 --status in_progress --json + +# Complete work +bd close bd-42 --reason "Fixed in commit abc123" --json +``` + +### Querying + +```bash +# List open issues +bd list --status open --json + +# Show issue details +bd show bd-42 --json + +# Check blocked issues +bd blocked --json +``` + +### Syncing + +```bash +# ALWAYS run at session end +bd sync +``` + +## Best Practices + +### Always Use `--json` + +```bash +bd list --json # Parse programmatically +bd create "Task" --json # Get issue ID from output +bd show bd-42 --json # Structured data +``` + +### Always Include Descriptions + +```bash +# Good +bd create "Fix auth bug" \ + --description="Login fails when password contains quotes" \ + -t bug -p 1 --json + +# Bad - no context for future work +bd create "Fix auth bug" -t bug -p 1 --json +``` + +### Link Related Work + +```bash +# When you discover issues during work +bd create "Found related bug" \ + --deps discovered-from:bd-current --json +``` + +### Sync Before Session End + +```bash +# ALWAYS run before ending +bd sync +``` + +## Troubleshooting + +### Guidelines not loaded + +```bash +# Check setup +bd setup junie --check + +# Reinstall if needed +bd setup junie +``` + +### MCP tools not available + +```bash +# Verify MCP config exists +cat .junie/mcp/mcp.json + +# Test MCP server +bd mcp --help +``` + +### Changes not syncing + +```bash +# Force sync +bd sync + +# Check daemon +bd info +bd daemons health +``` + +### Database not found + +```bash +# Initialize beads +bd init --quiet +``` + +## Removing Integration + +```bash +bd setup junie --remove +``` + +This removes: +- `.junie/guidelines.md` +- `.junie/mcp/mcp.json` +- Empty `.junie/mcp/` and `.junie/` directories + +## See Also + +- [MCP Server](/integrations/mcp-server) - MCP server details +- [Claude Code](/integrations/claude-code) - Similar hook-based integration +- [IDE Setup](/getting-started/ide-setup) - Other editors diff --git a/website/sidebars.ts b/website/sidebars.ts index 29b8e141..dc7b2c0b 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -91,6 +91,7 @@ const sidebars: SidebarsConfig = { 'integrations/claude-code', 'integrations/mcp-server', 'integrations/aider', + 'integrations/junie', ], }, {