feat(setup): add Codex CLI setup recipe (#1243)
* Add Codex setup recipe * Sync beads issues (bd-1zo) --------- Co-authored-by: Amp <amp@example.com>
This commit is contained in:
1400
.beads/issues.jsonl
1400
.beads/issues.jsonl
File diff suppressed because one or more lines are too long
@@ -30,7 +30,7 @@ var setupCmd = &cobra.Command{
|
|||||||
Long: `Setup integration files for AI editors and coding assistants.
|
Long: `Setup integration files for AI editors and coding assistants.
|
||||||
|
|
||||||
Recipes define where beads workflow instructions are written. Built-in recipes
|
Recipes define where beads workflow instructions are written. Built-in recipes
|
||||||
include cursor, claude, gemini, aider, factory, windsurf, cody, and kilocode.
|
include cursor, claude, gemini, aider, factory, codex, windsurf, cody, and kilocode.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
bd setup cursor # Install Cursor IDE integration
|
bd setup cursor # Install Cursor IDE integration
|
||||||
@@ -166,6 +166,9 @@ func runRecipe(name string) {
|
|||||||
case "factory":
|
case "factory":
|
||||||
runFactoryRecipe()
|
runFactoryRecipe()
|
||||||
return
|
return
|
||||||
|
case "codex":
|
||||||
|
runCodexRecipe()
|
||||||
|
return
|
||||||
case "aider":
|
case "aider":
|
||||||
runAiderRecipe()
|
runAiderRecipe()
|
||||||
return
|
return
|
||||||
@@ -287,6 +290,18 @@ func runFactoryRecipe() {
|
|||||||
setup.InstallFactory()
|
setup.InstallFactory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runCodexRecipe() {
|
||||||
|
if setupCheck {
|
||||||
|
setup.CheckCodex()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if setupRemove {
|
||||||
|
setup.RemoveCodex()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setup.InstallCodex()
|
||||||
|
}
|
||||||
|
|
||||||
func runAiderRecipe() {
|
func runAiderRecipe() {
|
||||||
if setupCheck {
|
if setupCheck {
|
||||||
setup.CheckAider()
|
setup.CheckAider()
|
||||||
|
|||||||
307
cmd/bd/setup/agents.go
Normal file
307
cmd/bd/setup/agents.go
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AGENTS.md integration markers for beads section
|
||||||
|
const (
|
||||||
|
agentsBeginMarker = "<!-- BEGIN BEADS INTEGRATION -->"
|
||||||
|
agentsEndMarker = "<!-- END BEADS INTEGRATION -->"
|
||||||
|
)
|
||||||
|
|
||||||
|
const agentsBeadsSection = `<!-- BEGIN BEADS INTEGRATION -->
|
||||||
|
## Issue Tracking with bd (beads)
|
||||||
|
|
||||||
|
**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods.
|
||||||
|
|
||||||
|
### Why bd?
|
||||||
|
|
||||||
|
- Dependency-aware: Track blockers and relationships between issues
|
||||||
|
- Git-friendly: Auto-syncs to JSONL for version control
|
||||||
|
- Agent-optimized: JSON output, ready work detection, discovered-from links
|
||||||
|
- Prevents duplicate tracking systems and confusion
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
|
||||||
|
**Check for ready work:**
|
||||||
|
|
||||||
|
` + "```bash" + `
|
||||||
|
bd ready --json
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
**Create new issues:**
|
||||||
|
|
||||||
|
` + "```bash" + `
|
||||||
|
bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json
|
||||||
|
bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
**Claim and update:**
|
||||||
|
|
||||||
|
` + "```bash" + `
|
||||||
|
bd update bd-42 --status in_progress --json
|
||||||
|
bd update bd-42 --priority 1 --json
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
**Complete work:**
|
||||||
|
|
||||||
|
` + "```bash" + `
|
||||||
|
bd close bd-42 --reason "Completed" --json
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
### Issue Types
|
||||||
|
|
||||||
|
- ` + "`bug`" + ` - Something broken
|
||||||
|
- ` + "`feature`" + ` - New functionality
|
||||||
|
- ` + "`task`" + ` - Work item (tests, docs, refactoring)
|
||||||
|
- ` + "`epic`" + ` - Large feature with subtasks
|
||||||
|
- ` + "`chore`" + ` - Maintenance (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)
|
||||||
|
|
||||||
|
### Workflow for AI Agents
|
||||||
|
|
||||||
|
1. **Check ready work**: ` + "`bd ready`" + ` shows unblocked issues
|
||||||
|
2. **Claim your task**: ` + "`bd update <id> --status in_progress`" + `
|
||||||
|
3. **Work on it**: Implement, test, document
|
||||||
|
4. **Discover new work?** Create linked issue:
|
||||||
|
- ` + "`bd create \"Found bug\" --description=\"Details about what was found\" -p 1 --deps discovered-from:<parent-id>`" + `
|
||||||
|
5. **Complete**: ` + "`bd close <id> --reason \"Done\"`" + `
|
||||||
|
|
||||||
|
### Auto-Sync
|
||||||
|
|
||||||
|
bd automatically syncs with git:
|
||||||
|
|
||||||
|
- Exports to ` + "`.beads/issues.jsonl`" + ` after changes (5s debounce)
|
||||||
|
- Imports from JSONL when newer (e.g., after ` + "`git pull`" + `)
|
||||||
|
- No manual export/import needed!
|
||||||
|
|
||||||
|
### 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?"
|
||||||
|
- ❌ Do NOT create markdown TODO lists
|
||||||
|
- ❌ Do NOT use external issue trackers
|
||||||
|
- ❌ Do NOT duplicate tracking systems
|
||||||
|
|
||||||
|
For more details, see README.md and docs/QUICKSTART.md.
|
||||||
|
|
||||||
|
<!-- END BEADS INTEGRATION -->
|
||||||
|
`
|
||||||
|
|
||||||
|
var (
|
||||||
|
errAgentsFileMissing = errors.New("agents file not found")
|
||||||
|
errBeadsSectionMissing = errors.New("beads section missing")
|
||||||
|
)
|
||||||
|
|
||||||
|
type agentsEnv struct {
|
||||||
|
agentsPath string
|
||||||
|
stdout io.Writer
|
||||||
|
stderr io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentsIntegration struct {
|
||||||
|
name string
|
||||||
|
setupCommand string
|
||||||
|
readHint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultAgentsEnv() agentsEnv {
|
||||||
|
return agentsEnv{
|
||||||
|
agentsPath: "AGENTS.md",
|
||||||
|
stdout: os.Stdout,
|
||||||
|
stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func installAgents(env agentsEnv, integration agentsIntegration) error {
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, "Installing %s integration...\n", integration.name)
|
||||||
|
|
||||||
|
var currentContent string
|
||||||
|
data, err := os.ReadFile(env.agentsPath)
|
||||||
|
if err == nil {
|
||||||
|
currentContent = string(data)
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
_, _ = fmt.Fprintf(env.stderr, "Error: failed to read %s: %v\n", env.agentsPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentContent != "" {
|
||||||
|
if strings.Contains(currentContent, agentsBeginMarker) {
|
||||||
|
newContent := updateBeadsSection(currentContent)
|
||||||
|
if err := atomicWriteFile(env.agentsPath, []byte(newContent)); err != nil {
|
||||||
|
_, _ = fmt.Fprintf(env.stderr, "Error: write %s: %v\n", env.agentsPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "✓ Updated existing beads section in AGENTS.md")
|
||||||
|
} else {
|
||||||
|
newContent := currentContent + "\n\n" + agentsBeadsSection
|
||||||
|
if err := atomicWriteFile(env.agentsPath, []byte(newContent)); err != nil {
|
||||||
|
_, _ = fmt.Fprintf(env.stderr, "Error: write %s: %v\n", env.agentsPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "✓ Added beads section to existing AGENTS.md")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newContent := createNewAgentsFile()
|
||||||
|
if err := atomicWriteFile(env.agentsPath, []byte(newContent)); err != nil {
|
||||||
|
_, _ = fmt.Fprintf(env.stderr, "Error: write %s: %v\n", env.agentsPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "✓ Created new AGENTS.md with beads integration")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, "\n✓ %s integration installed\n", integration.name)
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, " File: %s\n", env.agentsPath)
|
||||||
|
if integration.readHint != "" {
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, "\n%s\n", integration.readHint)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "No additional configuration needed!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAgents(env agentsEnv, integration agentsIntegration) error {
|
||||||
|
data, err := os.ReadFile(env.agentsPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "✗ AGENTS.md not found")
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, " Run: %s\n", integration.setupCommand)
|
||||||
|
return errAgentsFileMissing
|
||||||
|
} else if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(env.stderr, "Error: failed to read %s: %v\n", env.agentsPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(data)
|
||||||
|
if strings.Contains(content, agentsBeginMarker) {
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, "✓ %s integration installed: %s\n", integration.name, env.agentsPath)
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, " Beads section found in AGENTS.md")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "⚠ AGENTS.md exists but no beads section found")
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, " Run: %s (to add beads section)\n", integration.setupCommand)
|
||||||
|
return errBeadsSectionMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeAgents(env agentsEnv, integration agentsIntegration) error {
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, "Removing %s integration...\n", integration.name)
|
||||||
|
data, err := os.ReadFile(env.agentsPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "No AGENTS.md file found")
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(env.stderr, "Error: failed to read %s: %v\n", env.agentsPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(data)
|
||||||
|
if !strings.Contains(content, agentsBeginMarker) {
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "No beads section found in AGENTS.md")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newContent := removeBeadsSection(content)
|
||||||
|
trimmed := strings.TrimSpace(newContent)
|
||||||
|
if trimmed == "" {
|
||||||
|
if err := os.Remove(env.agentsPath); err != nil {
|
||||||
|
_, _ = fmt.Fprintf(env.stderr, "Error: failed to remove %s: %v\n", env.agentsPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(env.stdout, "✓ Removed %s (file was empty after removing beads section)\n", env.agentsPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := atomicWriteFile(env.agentsPath, []byte(newContent)); err != nil {
|
||||||
|
_, _ = fmt.Fprintf(env.stderr, "Error: write %s: %v\n", env.agentsPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(env.stdout, "✓ Removed beads section from AGENTS.md")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateBeadsSection replaces the beads section in existing content
|
||||||
|
func updateBeadsSection(content string) string {
|
||||||
|
start := strings.Index(content, agentsBeginMarker)
|
||||||
|
end := strings.Index(content, agentsEndMarker)
|
||||||
|
|
||||||
|
if start == -1 || end == -1 || start > end {
|
||||||
|
// Markers not found or invalid, append instead
|
||||||
|
return content + "\n\n" + agentsBeadsSection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace section between markers (including end marker line)
|
||||||
|
endOfEndMarker := end + len(agentsEndMarker)
|
||||||
|
// Find the next newline after end marker
|
||||||
|
nextNewline := strings.Index(content[endOfEndMarker:], "\n")
|
||||||
|
if nextNewline != -1 {
|
||||||
|
endOfEndMarker += nextNewline + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return content[:start] + agentsBeadsSection + content[endOfEndMarker:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeBeadsSection removes the beads section from content
|
||||||
|
func removeBeadsSection(content string) string {
|
||||||
|
start := strings.Index(content, agentsBeginMarker)
|
||||||
|
end := strings.Index(content, agentsEndMarker)
|
||||||
|
|
||||||
|
if start == -1 || end == -1 || start > end {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the next newline after end marker
|
||||||
|
endOfEndMarker := end + len(agentsEndMarker)
|
||||||
|
nextNewline := strings.Index(content[endOfEndMarker:], "\n")
|
||||||
|
if nextNewline != -1 {
|
||||||
|
endOfEndMarker += nextNewline + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also remove leading blank lines before the section
|
||||||
|
trimStart := start
|
||||||
|
for trimStart > 0 && (content[trimStart-1] == '\n' || content[trimStart-1] == '\r') {
|
||||||
|
trimStart--
|
||||||
|
}
|
||||||
|
|
||||||
|
return content[:trimStart] + content[endOfEndMarker:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// createNewAgentsFile creates a new AGENTS.md with a basic template
|
||||||
|
func createNewAgentsFile() string {
|
||||||
|
return `# Project Instructions for AI Agents
|
||||||
|
|
||||||
|
This file provides instructions and context for AI coding agents working on this project.
|
||||||
|
|
||||||
|
` + agentsBeadsSection + `
|
||||||
|
|
||||||
|
## Build & Test
|
||||||
|
|
||||||
|
_Add your build and test commands here_
|
||||||
|
|
||||||
|
` + "```bash" + `
|
||||||
|
# Example:
|
||||||
|
# npm install
|
||||||
|
# npm test
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
_Add a brief overview of your project architecture_
|
||||||
|
|
||||||
|
## Conventions & Patterns
|
||||||
|
|
||||||
|
_Add your project-specific conventions here_
|
||||||
|
`
|
||||||
|
}
|
||||||
45
cmd/bd/setup/codex.go
Normal file
45
cmd/bd/setup/codex.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
var codexIntegration = agentsIntegration{
|
||||||
|
name: "Codex CLI",
|
||||||
|
setupCommand: "bd setup codex",
|
||||||
|
readHint: "Codex reads AGENTS.md at the start of each run or session. Restart Codex if it is already running.",
|
||||||
|
}
|
||||||
|
|
||||||
|
var codexEnvProvider = defaultAgentsEnv
|
||||||
|
|
||||||
|
// InstallCodex installs Codex integration.
|
||||||
|
func InstallCodex() {
|
||||||
|
env := codexEnvProvider()
|
||||||
|
if err := installCodex(env); err != nil {
|
||||||
|
setupExit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func installCodex(env agentsEnv) error {
|
||||||
|
return installAgents(env, codexIntegration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckCodex checks if Codex integration is installed.
|
||||||
|
func CheckCodex() {
|
||||||
|
env := codexEnvProvider()
|
||||||
|
if err := checkCodex(env); err != nil {
|
||||||
|
setupExit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCodex(env agentsEnv) error {
|
||||||
|
return checkAgents(env, codexIntegration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveCodex removes Codex integration.
|
||||||
|
func RemoveCodex() {
|
||||||
|
env := codexEnvProvider()
|
||||||
|
if err := removeCodex(env); err != nil {
|
||||||
|
setupExit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeCodex(env agentsEnv) error {
|
||||||
|
return removeAgents(env, codexIntegration)
|
||||||
|
}
|
||||||
36
cmd/bd/setup/codex_test.go
Normal file
36
cmd/bd/setup/codex_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stubCodexEnvProvider(t *testing.T, env agentsEnv) {
|
||||||
|
t.Helper()
|
||||||
|
orig := codexEnvProvider
|
||||||
|
codexEnvProvider = func() agentsEnv {
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { codexEnvProvider = orig })
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstallCodexCreatesNewFile(t *testing.T) {
|
||||||
|
env, stdout, _ := newFactoryTestEnv(t)
|
||||||
|
if err := installCodex(env); err != nil {
|
||||||
|
t.Fatalf("installCodex returned error: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), "Codex CLI integration installed") {
|
||||||
|
t.Error("expected Codex install success message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckCodexMissingFile(t *testing.T) {
|
||||||
|
env, stdout, _ := newFactoryTestEnv(t)
|
||||||
|
err := checkCodex(env)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for missing AGENTS.md")
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), "bd setup codex") {
|
||||||
|
t.Error("expected setup guidance for codex")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,325 +1,47 @@
|
|||||||
package setup
|
package setup
|
||||||
|
|
||||||
import (
|
var factoryIntegration = agentsIntegration{
|
||||||
"errors"
|
name: "Factory.ai (Droid)",
|
||||||
"fmt"
|
setupCommand: "bd setup factory",
|
||||||
"io"
|
readHint: "Factory Droid will automatically read AGENTS.md on session start.",
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Factory/Droid integration markers for AGENTS.md
|
|
||||||
const (
|
|
||||||
factoryBeginMarker = "<!-- BEGIN BEADS INTEGRATION -->"
|
|
||||||
factoryEndMarker = "<!-- END BEADS INTEGRATION -->"
|
|
||||||
)
|
|
||||||
|
|
||||||
const factoryBeadsSection = `<!-- BEGIN BEADS INTEGRATION -->
|
|
||||||
## Issue Tracking with bd (beads)
|
|
||||||
|
|
||||||
**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods.
|
|
||||||
|
|
||||||
### Why bd?
|
|
||||||
|
|
||||||
- Dependency-aware: Track blockers and relationships between issues
|
|
||||||
- Git-friendly: Auto-syncs to JSONL for version control
|
|
||||||
- Agent-optimized: JSON output, ready work detection, discovered-from links
|
|
||||||
- Prevents duplicate tracking systems and confusion
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
**Check for ready work:**
|
|
||||||
|
|
||||||
` + "```bash" + `
|
|
||||||
bd ready --json
|
|
||||||
` + "```" + `
|
|
||||||
|
|
||||||
**Create new issues:**
|
|
||||||
|
|
||||||
` + "```bash" + `
|
|
||||||
bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json
|
|
||||||
bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json
|
|
||||||
` + "```" + `
|
|
||||||
|
|
||||||
**Claim and update:**
|
|
||||||
|
|
||||||
` + "```bash" + `
|
|
||||||
bd update bd-42 --status in_progress --json
|
|
||||||
bd update bd-42 --priority 1 --json
|
|
||||||
` + "```" + `
|
|
||||||
|
|
||||||
**Complete work:**
|
|
||||||
|
|
||||||
` + "```bash" + `
|
|
||||||
bd close bd-42 --reason "Completed" --json
|
|
||||||
` + "```" + `
|
|
||||||
|
|
||||||
### Issue Types
|
|
||||||
|
|
||||||
- ` + "`bug`" + ` - Something broken
|
|
||||||
- ` + "`feature`" + ` - New functionality
|
|
||||||
- ` + "`task`" + ` - Work item (tests, docs, refactoring)
|
|
||||||
- ` + "`epic`" + ` - Large feature with subtasks
|
|
||||||
- ` + "`chore`" + ` - Maintenance (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)
|
|
||||||
|
|
||||||
### Workflow for AI Agents
|
|
||||||
|
|
||||||
1. **Check ready work**: ` + "`bd ready`" + ` shows unblocked issues
|
|
||||||
2. **Claim your task**: ` + "`bd update <id> --status in_progress`" + `
|
|
||||||
3. **Work on it**: Implement, test, document
|
|
||||||
4. **Discover new work?** Create linked issue:
|
|
||||||
- ` + "`bd create \"Found bug\" --description=\"Details about what was found\" -p 1 --deps discovered-from:<parent-id>`" + `
|
|
||||||
5. **Complete**: ` + "`bd close <id> --reason \"Done\"`" + `
|
|
||||||
|
|
||||||
### Auto-Sync
|
|
||||||
|
|
||||||
bd automatically syncs with git:
|
|
||||||
|
|
||||||
- Exports to ` + "`.beads/issues.jsonl`" + ` after changes (5s debounce)
|
|
||||||
- Imports from JSONL when newer (e.g., after ` + "`git pull`" + `)
|
|
||||||
- No manual export/import needed!
|
|
||||||
|
|
||||||
### 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?"
|
|
||||||
- ❌ Do NOT create markdown TODO lists
|
|
||||||
- ❌ Do NOT use external issue trackers
|
|
||||||
- ❌ Do NOT duplicate tracking systems
|
|
||||||
|
|
||||||
For more details, see README.md and docs/QUICKSTART.md.
|
|
||||||
|
|
||||||
<!-- END BEADS INTEGRATION -->
|
|
||||||
`
|
|
||||||
|
|
||||||
var (
|
|
||||||
errAgentsFileMissing = errors.New("agents file not found")
|
|
||||||
errBeadsSectionMissing = errors.New("beads section missing")
|
|
||||||
)
|
|
||||||
|
|
||||||
type factoryEnv struct {
|
|
||||||
agentsPath string
|
|
||||||
stdout io.Writer
|
|
||||||
stderr io.Writer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var factoryEnvProvider = defaultFactoryEnv
|
type factoryEnv = agentsEnv
|
||||||
|
|
||||||
func defaultFactoryEnv() factoryEnv {
|
var factoryEnvProvider = defaultAgentsEnv
|
||||||
return factoryEnv{
|
|
||||||
agentsPath: "AGENTS.md",
|
|
||||||
stdout: os.Stdout,
|
|
||||||
stderr: os.Stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallFactory installs Factory.ai/Droid integration
|
// InstallFactory installs Factory.ai/Droid integration.
|
||||||
func InstallFactory() {
|
func InstallFactory() {
|
||||||
env := factoryEnvProvider()
|
env := factoryEnvProvider()
|
||||||
if err := installFactory(env); err != nil {
|
if err := installAgents(env, factoryIntegration); err != nil {
|
||||||
setupExit(1)
|
setupExit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func installFactory(env factoryEnv) error {
|
func installFactory(env factoryEnv) error {
|
||||||
_, _ = fmt.Fprintln(env.stdout, "Installing Factory.ai (Droid) integration...")
|
return installAgents(env, factoryIntegration)
|
||||||
|
|
||||||
var currentContent string
|
|
||||||
data, err := os.ReadFile(env.agentsPath)
|
|
||||||
if err == nil {
|
|
||||||
currentContent = string(data)
|
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
_, _ = fmt.Fprintf(env.stderr, "Error: failed to read %s: %v\n", env.agentsPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentContent != "" {
|
|
||||||
if strings.Contains(currentContent, factoryBeginMarker) {
|
|
||||||
newContent := updateBeadsSection(currentContent)
|
|
||||||
if err := atomicWriteFile(env.agentsPath, []byte(newContent)); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(env.stderr, "Error: write %s: %v\n", env.agentsPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "✓ Updated existing beads section in AGENTS.md")
|
|
||||||
} else {
|
|
||||||
newContent := currentContent + "\n\n" + factoryBeadsSection
|
|
||||||
if err := atomicWriteFile(env.agentsPath, []byte(newContent)); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(env.stderr, "Error: write %s: %v\n", env.agentsPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "✓ Added beads section to existing AGENTS.md")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newContent := createNewAgentsFile()
|
|
||||||
if err := atomicWriteFile(env.agentsPath, []byte(newContent)); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(env.stderr, "Error: write %s: %v\n", env.agentsPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "✓ Created new AGENTS.md with beads integration")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "\n✓ Factory.ai (Droid) integration installed")
|
|
||||||
_, _ = fmt.Fprintf(env.stdout, " File: %s\n", env.agentsPath)
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "\nFactory Droid will automatically read AGENTS.md on session start.")
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "No additional configuration needed!")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckFactory checks if Factory.ai integration is installed
|
// CheckFactory checks if Factory.ai integration is installed.
|
||||||
func CheckFactory() {
|
func CheckFactory() {
|
||||||
env := factoryEnvProvider()
|
env := factoryEnvProvider()
|
||||||
if err := checkFactory(env); err != nil {
|
if err := checkAgents(env, factoryIntegration); err != nil {
|
||||||
setupExit(1)
|
setupExit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkFactory(env factoryEnv) error {
|
func checkFactory(env factoryEnv) error {
|
||||||
data, err := os.ReadFile(env.agentsPath)
|
return checkAgents(env, factoryIntegration)
|
||||||
if os.IsNotExist(err) {
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "✗ AGENTS.md not found")
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, " Run: bd setup factory")
|
|
||||||
return errAgentsFileMissing
|
|
||||||
} else if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(env.stderr, "Error: failed to read %s: %v\n", env.agentsPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
content := string(data)
|
|
||||||
if strings.Contains(content, factoryBeginMarker) {
|
|
||||||
_, _ = fmt.Fprintf(env.stdout, "✓ Factory.ai integration installed: %s\n", env.agentsPath)
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, " Beads section found in AGENTS.md")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "⚠ AGENTS.md exists but no beads section found")
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, " Run: bd setup factory (to add beads section)")
|
|
||||||
return errBeadsSectionMissing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveFactory removes Factory.ai integration
|
// RemoveFactory removes Factory.ai integration.
|
||||||
func RemoveFactory() {
|
func RemoveFactory() {
|
||||||
env := factoryEnvProvider()
|
env := factoryEnvProvider()
|
||||||
if err := removeFactory(env); err != nil {
|
if err := removeAgents(env, factoryIntegration); err != nil {
|
||||||
setupExit(1)
|
setupExit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFactory(env factoryEnv) error {
|
func removeFactory(env factoryEnv) error {
|
||||||
_, _ = fmt.Fprintln(env.stdout, "Removing Factory.ai (Droid) integration...")
|
return removeAgents(env, factoryIntegration)
|
||||||
data, err := os.ReadFile(env.agentsPath)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "No AGENTS.md file found")
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(env.stderr, "Error: failed to read %s: %v\n", env.agentsPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
content := string(data)
|
|
||||||
if !strings.Contains(content, factoryBeginMarker) {
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "No beads section found in AGENTS.md")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newContent := removeBeadsSection(content)
|
|
||||||
trimmed := strings.TrimSpace(newContent)
|
|
||||||
if trimmed == "" {
|
|
||||||
if err := os.Remove(env.agentsPath); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(env.stderr, "Error: failed to remove %s: %v\n", env.agentsPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintf(env.stdout, "✓ Removed %s (file was empty after removing beads section)\n", env.agentsPath)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := atomicWriteFile(env.agentsPath, []byte(newContent)); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(env.stderr, "Error: write %s: %v\n", env.agentsPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintln(env.stdout, "✓ Removed beads section from AGENTS.md")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateBeadsSection replaces the beads section in existing content
|
|
||||||
func updateBeadsSection(content string) string {
|
|
||||||
start := strings.Index(content, factoryBeginMarker)
|
|
||||||
end := strings.Index(content, factoryEndMarker)
|
|
||||||
|
|
||||||
if start == -1 || end == -1 || start > end {
|
|
||||||
// Markers not found or invalid, append instead
|
|
||||||
return content + "\n\n" + factoryBeadsSection
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace section between markers (including end marker line)
|
|
||||||
endOfEndMarker := end + len(factoryEndMarker)
|
|
||||||
// Find the next newline after end marker
|
|
||||||
nextNewline := strings.Index(content[endOfEndMarker:], "\n")
|
|
||||||
if nextNewline != -1 {
|
|
||||||
endOfEndMarker += nextNewline + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return content[:start] + factoryBeadsSection + content[endOfEndMarker:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeBeadsSection removes the beads section from content
|
|
||||||
func removeBeadsSection(content string) string {
|
|
||||||
start := strings.Index(content, factoryBeginMarker)
|
|
||||||
end := strings.Index(content, factoryEndMarker)
|
|
||||||
|
|
||||||
if start == -1 || end == -1 || start > end {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the next newline after end marker
|
|
||||||
endOfEndMarker := end + len(factoryEndMarker)
|
|
||||||
nextNewline := strings.Index(content[endOfEndMarker:], "\n")
|
|
||||||
if nextNewline != -1 {
|
|
||||||
endOfEndMarker += nextNewline + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also remove leading blank lines before the section
|
|
||||||
trimStart := start
|
|
||||||
for trimStart > 0 && (content[trimStart-1] == '\n' || content[trimStart-1] == '\r') {
|
|
||||||
trimStart--
|
|
||||||
}
|
|
||||||
|
|
||||||
return content[:trimStart] + content[endOfEndMarker:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// createNewAgentsFile creates a new AGENTS.md with a basic template
|
|
||||||
func createNewAgentsFile() string {
|
|
||||||
return `# Project Instructions for AI Agents
|
|
||||||
|
|
||||||
This file provides instructions and context for AI coding agents working on this project.
|
|
||||||
|
|
||||||
` + factoryBeadsSection + `
|
|
||||||
|
|
||||||
## Build & Test
|
|
||||||
|
|
||||||
_Add your build and test commands here_
|
|
||||||
|
|
||||||
` + "```bash" + `
|
|
||||||
# Example:
|
|
||||||
# npm install
|
|
||||||
# npm test
|
|
||||||
` + "```" + `
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
_Add a brief overview of your project architecture_
|
|
||||||
|
|
||||||
## Conventions & Patterns
|
|
||||||
|
|
||||||
_Add your project-specific conventions here_
|
|
||||||
`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ More content after`,
|
|||||||
|
|
||||||
Some content
|
Some content
|
||||||
|
|
||||||
` + factoryBeadsSection + `
|
` + agentsBeadsSection + `
|
||||||
More content after`,
|
More content after`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "append when no markers exist",
|
name: "append when no markers exist",
|
||||||
content: "# My Project\n\nSome content",
|
content: "# My Project\n\nSome content",
|
||||||
expected: "# My Project\n\nSome content\n\n" + factoryBeadsSection,
|
expected: "# My Project\n\nSome content\n\n" + agentsBeadsSection,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "handle section at end of file",
|
name: "handle section at end of file",
|
||||||
@@ -47,7 +47,7 @@ Old content
|
|||||||
<!-- END BEADS INTEGRATION -->`,
|
<!-- END BEADS INTEGRATION -->`,
|
||||||
expected: `# My Project
|
expected: `# My Project
|
||||||
|
|
||||||
` + factoryBeadsSection,
|
` + agentsBeadsSection,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,11 +122,11 @@ func TestCreateNewAgentsFile(t *testing.T) {
|
|||||||
t.Error("Missing header in new agents file")
|
t.Error("Missing header in new agents file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(content, factoryBeginMarker) {
|
if !strings.Contains(content, agentsBeginMarker) {
|
||||||
t.Error("Missing begin marker in new agents file")
|
t.Error("Missing begin marker in new agents file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(content, factoryEndMarker) {
|
if !strings.Contains(content, agentsEndMarker) {
|
||||||
t.Error("Missing end marker in new agents file")
|
t.Error("Missing end marker in new agents file")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ func TestInstallFactoryCreatesNewFile(t *testing.T) {
|
|||||||
t.Fatalf("failed to read AGENTS.md: %v", err)
|
t.Fatalf("failed to read AGENTS.md: %v", err)
|
||||||
}
|
}
|
||||||
content := string(data)
|
content := string(data)
|
||||||
if !strings.Contains(content, factoryBeginMarker) || !strings.Contains(content, factoryEndMarker) {
|
if !strings.Contains(content, agentsBeginMarker) || !strings.Contains(content, agentsEndMarker) {
|
||||||
t.Fatal("missing factory markers in new file")
|
t.Fatal("missing factory markers in new file")
|
||||||
}
|
}
|
||||||
if !strings.Contains(stdout.String(), "Factory.ai (Droid) integration installed") {
|
if !strings.Contains(stdout.String(), "Factory.ai (Droid) integration installed") {
|
||||||
@@ -247,7 +247,7 @@ func TestCheckFactoryScenarios(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
env, stdout, _ := newFactoryTestEnv(t)
|
env, stdout, _ := newFactoryTestEnv(t)
|
||||||
if err := os.WriteFile(env.agentsPath, []byte(factoryBeadsSection), 0644); err != nil {
|
if err := os.WriteFile(env.agentsPath, []byte(agentsBeadsSection), 0644); err != nil {
|
||||||
t.Fatalf("failed to seed file: %v", err)
|
t.Fatalf("failed to seed file: %v", err)
|
||||||
}
|
}
|
||||||
if err := checkFactory(env); err != nil {
|
if err := checkFactory(env); err != nil {
|
||||||
@@ -262,7 +262,7 @@ func TestCheckFactoryScenarios(t *testing.T) {
|
|||||||
func TestRemoveFactoryScenarios(t *testing.T) {
|
func TestRemoveFactoryScenarios(t *testing.T) {
|
||||||
t.Run("remove section and keep file", func(t *testing.T) {
|
t.Run("remove section and keep file", func(t *testing.T) {
|
||||||
env, stdout, _ := newFactoryTestEnv(t)
|
env, stdout, _ := newFactoryTestEnv(t)
|
||||||
content := "# Top\n\n" + factoryBeadsSection + "\n\n# Bottom"
|
content := "# Top\n\n" + agentsBeadsSection + "\n\n# Bottom"
|
||||||
if err := os.WriteFile(env.agentsPath, []byte(content), 0644); err != nil {
|
if err := os.WriteFile(env.agentsPath, []byte(content), 0644); err != nil {
|
||||||
t.Fatalf("failed to seed AGENTS.md: %v", err)
|
t.Fatalf("failed to seed AGENTS.md: %v", err)
|
||||||
}
|
}
|
||||||
@@ -273,7 +273,7 @@ func TestRemoveFactoryScenarios(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to read AGENTS.md: %v", err)
|
t.Fatalf("failed to read AGENTS.md: %v", err)
|
||||||
}
|
}
|
||||||
if strings.Contains(string(data), factoryBeginMarker) {
|
if strings.Contains(string(data), agentsBeginMarker) {
|
||||||
t.Error("beads section should be removed")
|
t.Error("beads section should be removed")
|
||||||
}
|
}
|
||||||
if !strings.Contains(stdout.String(), "Removed beads section") {
|
if !strings.Contains(stdout.String(), "Removed beads section") {
|
||||||
@@ -283,7 +283,7 @@ func TestRemoveFactoryScenarios(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("delete file when only beads", func(t *testing.T) {
|
t.Run("delete file when only beads", func(t *testing.T) {
|
||||||
env, stdout, _ := newFactoryTestEnv(t)
|
env, stdout, _ := newFactoryTestEnv(t)
|
||||||
if err := os.WriteFile(env.agentsPath, []byte(factoryBeadsSection), 0644); err != nil {
|
if err := os.WriteFile(env.agentsPath, []byte(agentsBeadsSection), 0644); err != nil {
|
||||||
t.Fatalf("failed to seed AGENTS.md: %v", err)
|
t.Fatalf("failed to seed AGENTS.md: %v", err)
|
||||||
}
|
}
|
||||||
if err := removeFactory(env); err != nil {
|
if err := removeFactory(env); err != nil {
|
||||||
@@ -335,7 +335,7 @@ func TestWrapperExitsOnError(t *testing.T) {
|
|||||||
t.Run("RemoveFactory", func(t *testing.T) {
|
t.Run("RemoveFactory", func(t *testing.T) {
|
||||||
cap := stubSetupExit(t)
|
cap := stubSetupExit(t)
|
||||||
env := factoryEnv{agentsPath: filepath.Join(t.TempDir(), "AGENTS.md"), stdout: &bytes.Buffer{}, stderr: &bytes.Buffer{}}
|
env := factoryEnv{agentsPath: filepath.Join(t.TempDir(), "AGENTS.md"), stdout: &bytes.Buffer{}, stderr: &bytes.Buffer{}}
|
||||||
if err := os.WriteFile(env.agentsPath, []byte(factoryBeadsSection), 0644); err != nil {
|
if err := os.WriteFile(env.agentsPath, []byte(agentsBeadsSection), 0644); err != nil {
|
||||||
t.Fatalf("failed to seed file: %v", err)
|
t.Fatalf("failed to seed file: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.Chmod(env.agentsPath, 0o000); err != nil {
|
if err := os.Chmod(env.agentsPath, 0o000); err != nil {
|
||||||
@@ -350,36 +350,36 @@ func TestWrapperExitsOnError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFactoryBeadsSectionContent(t *testing.T) {
|
func TestFactoryBeadsSectionContent(t *testing.T) {
|
||||||
section := factoryBeadsSection
|
section := agentsBeadsSection
|
||||||
required := []string{"bd create", "bd update", "bd close", "bd ready", "discovered-from"}
|
required := []string{"bd create", "bd update", "bd close", "bd ready", "discovered-from"}
|
||||||
for _, token := range required {
|
for _, token := range required {
|
||||||
if !strings.Contains(section, token) {
|
if !strings.Contains(section, token) {
|
||||||
t.Errorf("factoryBeadsSection missing %q", token)
|
t.Errorf("agentsBeadsSection missing %q", token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFactoryMarkers(t *testing.T) {
|
func TestFactoryMarkers(t *testing.T) {
|
||||||
if !strings.Contains(factoryBeginMarker, "BEGIN") {
|
if !strings.Contains(agentsBeginMarker, "BEGIN") {
|
||||||
t.Error("begin marker should mention BEGIN")
|
t.Error("begin marker should mention BEGIN")
|
||||||
}
|
}
|
||||||
if !strings.Contains(factoryEndMarker, "END") {
|
if !strings.Contains(agentsEndMarker, "END") {
|
||||||
t.Error("end marker should mention END")
|
t.Error("end marker should mention END")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkersMatch(t *testing.T) {
|
func TestMarkersMatch(t *testing.T) {
|
||||||
if !strings.HasPrefix(factoryBeadsSection, factoryBeginMarker) {
|
if !strings.HasPrefix(agentsBeadsSection, agentsBeginMarker) {
|
||||||
t.Error("section should start with begin marker")
|
t.Error("section should start with begin marker")
|
||||||
}
|
}
|
||||||
trimmed := strings.TrimSpace(factoryBeadsSection)
|
trimmed := strings.TrimSpace(agentsBeadsSection)
|
||||||
if !strings.HasSuffix(trimmed, factoryEndMarker) {
|
if !strings.HasSuffix(trimmed, agentsEndMarker) {
|
||||||
t.Error("section should end with end marker")
|
t.Error("section should end with end marker")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateBeadsSectionPreservesWhitespace(t *testing.T) {
|
func TestUpdateBeadsSectionPreservesWhitespace(t *testing.T) {
|
||||||
content := "# Header\n\n" + factoryBeadsSection + "\n\n# Footer"
|
content := "# Header\n\n" + agentsBeadsSection + "\n\n# Footer"
|
||||||
updated := updateBeadsSection(content)
|
updated := updateBeadsSection(content)
|
||||||
if !strings.Contains(updated, "# Header") || !strings.Contains(updated, "# Footer") {
|
if !strings.Contains(updated, "# Header") || !strings.Contains(updated, "# Footer") {
|
||||||
t.Error("update should preserve surrounding content")
|
t.Error("update should preserve surrounding content")
|
||||||
|
|||||||
@@ -766,18 +766,21 @@ bd sync # Force immediate sync, bypass debounce
|
|||||||
```bash
|
```bash
|
||||||
# Setup editor integration (choose based on your editor)
|
# Setup editor integration (choose based on your editor)
|
||||||
bd setup factory # Factory.ai Droid - creates/updates AGENTS.md (universal standard)
|
bd setup factory # Factory.ai Droid - creates/updates AGENTS.md (universal standard)
|
||||||
|
bd setup codex # Codex CLI - creates/updates AGENTS.md
|
||||||
bd setup claude # Claude Code - installs SessionStart/PreCompact hooks
|
bd setup claude # Claude Code - installs SessionStart/PreCompact hooks
|
||||||
bd setup cursor # Cursor IDE - creates .cursor/rules/beads.mdc
|
bd setup cursor # Cursor IDE - creates .cursor/rules/beads.mdc
|
||||||
bd setup aider # Aider - creates .aider.conf.yml
|
bd setup aider # Aider - creates .aider.conf.yml
|
||||||
|
|
||||||
# Check if integration is installed
|
# Check if integration is installed
|
||||||
bd setup factory --check
|
bd setup factory --check
|
||||||
|
bd setup codex --check
|
||||||
bd setup claude --check
|
bd setup claude --check
|
||||||
bd setup cursor --check
|
bd setup cursor --check
|
||||||
bd setup aider --check
|
bd setup aider --check
|
||||||
|
|
||||||
# Remove integration
|
# Remove integration
|
||||||
bd setup factory --remove
|
bd setup factory --remove
|
||||||
|
bd setup codex --remove
|
||||||
bd setup claude --remove
|
bd setup claude --remove
|
||||||
bd setup cursor --remove
|
bd setup cursor --remove
|
||||||
bd setup aider --remove
|
bd setup aider --remove
|
||||||
@@ -792,6 +795,7 @@ bd setup claude --stealth # Use stealth mode (flush only, no git operations)
|
|||||||
|
|
||||||
**What each setup does:**
|
**What each setup does:**
|
||||||
- **Factory.ai** (`bd setup factory`): Creates or updates AGENTS.md with beads workflow instructions (works with multiple AI tools using the AGENTS.md standard)
|
- **Factory.ai** (`bd setup factory`): Creates or updates AGENTS.md with beads workflow instructions (works with multiple AI tools using the AGENTS.md standard)
|
||||||
|
- **Codex CLI** (`bd setup codex`): Creates or updates AGENTS.md with beads workflow instructions for Codex
|
||||||
- **Claude Code** (`bd setup claude`): Adds hooks to Claude Code's settings.json that run `bd prime` on SessionStart and PreCompact events
|
- **Claude Code** (`bd setup claude`): Adds hooks to Claude Code's settings.json that run `bd prime` on SessionStart and PreCompact events
|
||||||
- **Cursor** (`bd setup cursor`): Creates `.cursor/rules/beads.mdc` with workflow instructions
|
- **Cursor** (`bd setup cursor`): Creates `.cursor/rules/beads.mdc` with workflow instructions
|
||||||
- **Aider** (`bd setup aider`): Creates `.aider.conf.yml` with bd workflow instructions
|
- **Aider** (`bd setup aider`): Creates `.aider.conf.yml` with bd workflow instructions
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ bd init --quiet
|
|||||||
bd setup claude # Claude Code - installs SessionStart/PreCompact hooks
|
bd setup claude # Claude Code - installs SessionStart/PreCompact hooks
|
||||||
bd setup cursor # Cursor IDE - creates .cursor/rules/beads.mdc
|
bd setup cursor # Cursor IDE - creates .cursor/rules/beads.mdc
|
||||||
bd setup aider # Aider - creates .aider.conf.yml
|
bd setup aider # Aider - creates .aider.conf.yml
|
||||||
|
bd setup codex # Codex CLI - creates/updates AGENTS.md
|
||||||
```
|
```
|
||||||
|
|
||||||
**How it works:**
|
**How it works:**
|
||||||
@@ -208,6 +209,7 @@ bd setup aider # Aider - creates .aider.conf.yml
|
|||||||
bd setup claude --check # Check Claude Code integration
|
bd setup claude --check # Check Claude Code integration
|
||||||
bd setup cursor --check # Check Cursor integration
|
bd setup cursor --check # Check Cursor integration
|
||||||
bd setup aider --check # Check Aider integration
|
bd setup aider --check # Check Aider integration
|
||||||
|
bd setup codex --check # Check Codex integration
|
||||||
```
|
```
|
||||||
|
|
||||||
### Claude Code Plugin (Optional)
|
### Claude Code Plugin (Optional)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ The `bd setup` command uses a **recipe-based architecture** to configure beads i
|
|||||||
| `claude` | `~/.claude/settings.json` | SessionStart/PreCompact hooks |
|
| `claude` | `~/.claude/settings.json` | SessionStart/PreCompact hooks |
|
||||||
| `gemini` | `~/.gemini/settings.json` | SessionStart/PreCompress hooks |
|
| `gemini` | `~/.gemini/settings.json` | SessionStart/PreCompress hooks |
|
||||||
| `factory` | `AGENTS.md` | Marked section |
|
| `factory` | `AGENTS.md` | Marked section |
|
||||||
|
| `codex` | `AGENTS.md` | Marked section |
|
||||||
| `aider` | `.aider.conf.yml` + `.aider/` | Multi-file config |
|
| `aider` | `.aider.conf.yml` + `.aider/` | Multi-file config |
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -33,6 +34,7 @@ bd setup kilocode # Kilo Code
|
|||||||
bd setup claude # Claude Code
|
bd setup claude # Claude Code
|
||||||
bd setup gemini # Gemini CLI
|
bd setup gemini # Gemini CLI
|
||||||
bd setup factory # Factory.ai Droid
|
bd setup factory # Factory.ai Droid
|
||||||
|
bd setup codex # Codex CLI
|
||||||
bd setup aider # Aider
|
bd setup aider # Aider
|
||||||
|
|
||||||
# Verify installation
|
# Verify installation
|
||||||
@@ -139,6 +141,24 @@ If you already have an AGENTS.md file with other project instructions:
|
|||||||
|
|
||||||
You can use multiple integrations simultaneously - they complement each other!
|
You can use multiple integrations simultaneously - they complement each other!
|
||||||
|
|
||||||
|
## Codex CLI
|
||||||
|
|
||||||
|
Codex reads `AGENTS.md` instructions at the start of each run/session. Adding the beads section is enough to get Codex and beads working together.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bd setup codex
|
||||||
|
```
|
||||||
|
|
||||||
|
### What Gets Installed
|
||||||
|
|
||||||
|
Creates or updates `AGENTS.md` with the beads integration section (same markers as Factory.ai).
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- Restart Codex if it's already running to pick up the new instructions.
|
||||||
|
|
||||||
## Claude Code
|
## Claude Code
|
||||||
|
|
||||||
Claude Code integration uses hooks to automatically inject beads workflow context at session start and before context compaction.
|
Claude Code integration uses hooks to automatically inject beads workflow context at session start and before context compaction.
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ var BuiltinRecipes = map[string]Recipe{
|
|||||||
Type: TypeSection,
|
Type: TypeSection,
|
||||||
Description: "Factory Droid AGENTS.md section",
|
Description: "Factory Droid AGENTS.md section",
|
||||||
},
|
},
|
||||||
|
"codex": {
|
||||||
|
Name: "Codex CLI",
|
||||||
|
Path: "AGENTS.md",
|
||||||
|
Type: TypeSection,
|
||||||
|
Description: "Codex CLI AGENTS.md section",
|
||||||
|
},
|
||||||
"aider": {
|
"aider": {
|
||||||
Name: "Aider",
|
Name: "Aider",
|
||||||
Type: TypeMultiFile,
|
Type: TypeMultiFile,
|
||||||
|
|||||||
Reference in New Issue
Block a user