Merge GH#532
This commit is contained in:
@@ -25,6 +25,12 @@ if [ ! -d .beads ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Skip if bd sync is already in progress (GH#532: prevents circular error)
|
||||||
|
# bd sync sets this env var when pushing from worktree
|
||||||
|
if [ -n "$BD_SYNC_IN_PROGRESS" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Optionally flush pending bd changes so they surface in JSONL
|
# Optionally flush pending bd changes so they surface in JSONL
|
||||||
# This prevents the race where a debounced flush lands after the check
|
# This prevents the race where a debounced flush lands after the check
|
||||||
if command -v bd >/dev/null 2>&1; then
|
if command -v bd >/dev/null 2>&1; then
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
117
cmd/bd/init.go
117
cmd/bd/init.go
@@ -423,6 +423,15 @@ With --stealth: configures global git settings for invisible beads usage:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add "landing the plane" instructions to AGENTS.md and @AGENTS.md
|
||||||
|
// Skip in stealth mode (user wants invisible setup) and quiet mode (suppress all output)
|
||||||
|
if !stealth {
|
||||||
|
if err := addLandingThePlaneInstructions(!quiet); err != nil && !quiet {
|
||||||
|
yellow := color.New(color.FgYellow).SprintFunc()
|
||||||
|
fmt.Fprintf(os.Stderr, "%s Failed to add landing-the-plane instructions: %v\n", yellow("⚠"), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Skip output if quiet mode
|
// Skip output if quiet mode
|
||||||
if quiet {
|
if quiet {
|
||||||
return
|
return
|
||||||
@@ -1550,6 +1559,114 @@ Aborting.`, yellow("⚠"), dbPath, cyan("bd list"), prefix)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// landingThePlaneSection is the "landing the plane" instructions for AI agents
|
||||||
|
// This gets appended to AGENTS.md and @AGENTS.md during bd init
|
||||||
|
const landingThePlaneSection = `
|
||||||
|
## Landing the Plane (Session Completion)
|
||||||
|
|
||||||
|
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until ` + "`git push`" + ` succeeds.
|
||||||
|
|
||||||
|
**MANDATORY WORKFLOW:**
|
||||||
|
|
||||||
|
1. **File issues for remaining work** - Create issues for anything that needs follow-up
|
||||||
|
2. **Run quality gates** (if code changed) - Tests, linters, builds
|
||||||
|
3. **Update issue status** - Close finished work, update in-progress items
|
||||||
|
4. **PUSH TO REMOTE** - This is MANDATORY:
|
||||||
|
` + "```bash" + `
|
||||||
|
git pull --rebase
|
||||||
|
bd sync
|
||||||
|
git push
|
||||||
|
git status # MUST show "up to date with origin"
|
||||||
|
` + "```" + `
|
||||||
|
5. **Clean up** - Clear stashes, prune remote branches
|
||||||
|
6. **Verify** - All changes committed AND pushed
|
||||||
|
7. **Hand off** - Provide context for next session
|
||||||
|
|
||||||
|
**CRITICAL RULES:**
|
||||||
|
- Work is NOT complete until ` + "`git push`" + ` succeeds
|
||||||
|
- NEVER stop before pushing - that leaves work stranded locally
|
||||||
|
- NEVER say "ready to push when you are" - YOU must push
|
||||||
|
- If push fails, resolve and retry until it succeeds
|
||||||
|
`
|
||||||
|
|
||||||
|
// addLandingThePlaneInstructions adds "landing the plane" instructions to AGENTS.md and @AGENTS.md
|
||||||
|
func addLandingThePlaneInstructions(verbose bool) error {
|
||||||
|
// Files to update (AGENTS.md and @AGENTS.md for web Claude)
|
||||||
|
agentFiles := []string{"AGENTS.md", "@AGENTS.md"}
|
||||||
|
|
||||||
|
for _, filename := range agentFiles {
|
||||||
|
if err := updateAgentFile(filename, verbose); err != nil {
|
||||||
|
// Non-fatal - continue with other files
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Warning: failed to update %s: %v\n", filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateAgentFile creates or updates an agent instructions file with landing the plane section
|
||||||
|
func updateAgentFile(filename string, verbose bool) error {
|
||||||
|
// Check if file exists
|
||||||
|
content, err := os.ReadFile(filename)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// File doesn't exist - create it with basic structure
|
||||||
|
newContent := fmt.Sprintf(`# Agent Instructions
|
||||||
|
|
||||||
|
This project uses **bd** (beads) for issue tracking. Run ` + "`bd onboard`" + ` to get started.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
` + "```bash" + `
|
||||||
|
bd ready # Find available work
|
||||||
|
bd show <id> # View issue details
|
||||||
|
bd update <id> --status in_progress # Claim work
|
||||||
|
bd close <id> # Complete work
|
||||||
|
bd sync # Sync with git
|
||||||
|
` + "```" + `
|
||||||
|
%s
|
||||||
|
`, landingThePlaneSection)
|
||||||
|
|
||||||
|
// #nosec G306 - markdown needs to be readable
|
||||||
|
if err := os.WriteFile(filename, []byte(newContent), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to create %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
green := color.New(color.FgGreen).SprintFunc()
|
||||||
|
fmt.Printf(" %s Created %s with landing-the-plane instructions\n", green("✓"), filename)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to read %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File exists - check if it already has landing the plane section
|
||||||
|
if strings.Contains(string(content), "Landing the Plane") {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf(" %s already has landing-the-plane instructions\n", filename)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the landing the plane section
|
||||||
|
newContent := string(content)
|
||||||
|
if !strings.HasSuffix(newContent, "\n") {
|
||||||
|
newContent += "\n"
|
||||||
|
}
|
||||||
|
newContent += landingThePlaneSection
|
||||||
|
|
||||||
|
// #nosec G306 - markdown needs to be readable
|
||||||
|
if err := os.WriteFile(filename, []byte(newContent), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
green := color.New(color.FgGreen).SprintFunc()
|
||||||
|
fmt.Printf(" %s Added landing-the-plane instructions to %s\n", green("✓"), filename)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// setupClaudeSettings creates or updates .claude/settings.local.json with onboard instruction
|
// setupClaudeSettings creates or updates .claude/settings.local.json with onboard instruction
|
||||||
func setupClaudeSettings(verbose bool) error {
|
func setupClaudeSettings(verbose bool) error {
|
||||||
claudeDir := ".claude"
|
claudeDir := ".claude"
|
||||||
|
|||||||
@@ -23,20 +23,12 @@ if ! command -v bd >/dev/null 2>&1; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if we're in a bd workspace with an actual database
|
# Check if we're in a bd workspace
|
||||||
# Just having a .beads directory isn't enough - it must be properly initialized
|
|
||||||
if [ ! -d .beads ]; then
|
if [ ! -d .beads ]; then
|
||||||
# Not a bd workspace, nothing to do
|
# Not a bd workspace, nothing to do
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify beads is actually initialized (has database or config)
|
|
||||||
# This handles the case where .beads was removed from git but directory lingers
|
|
||||||
if [ ! -f .beads/beads.db ] && [ ! -f .beads/config.yaml ] && [ ! -f .beads/issues.jsonl ]; then
|
|
||||||
# Directory exists but beads not initialized, nothing to do
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if sync-branch is configured in config.yaml or env var
|
# Check if sync-branch is configured in config.yaml or env var
|
||||||
# If so, .beads changes go to a separate branch via worktree, not the current branch
|
# If so, .beads changes go to a separate branch via worktree, not the current branch
|
||||||
SYNC_BRANCH="${BEADS_SYNC_BRANCH:-}"
|
SYNC_BRANCH="${BEADS_SYNC_BRANCH:-}"
|
||||||
@@ -54,36 +46,18 @@ fi
|
|||||||
# Flush pending changes to JSONL
|
# Flush pending changes to JSONL
|
||||||
# Use --flush-only to skip git operations (we're already in a git hook)
|
# Use --flush-only to skip git operations (we're already in a git hook)
|
||||||
# Suppress output unless there's an error
|
# Suppress output unless there's an error
|
||||||
<<<<<<< HEAD
|
|
||||||
# Note: We don't block commits on flush failure - beads issues shouldn't prevent code commits
|
|
||||||
if ! bd sync --flush-only >/dev/null 2>&1; then
|
|
||||||
echo "Warning: Failed to flush bd changes to JSONL" >&2
|
|
||||||
echo "Run 'bd sync --flush-only' manually to diagnose" >&2
|
|
||||||
# Continue with commit - don't block code changes due to beads issues
|
|
||||||
=======
|
|
||||||
# Note: We warn but don't fail - this allows commits to proceed even if
|
# Note: We warn but don't fail - this allows commits to proceed even if
|
||||||
# beads has issues (e.g., user removed .beads from their branch)
|
# beads has issues (e.g., user removed .beads from their branch)
|
||||||
if ! bd sync --flush-only >/dev/null 2>&1; then
|
if ! bd sync --flush-only >/dev/null 2>&1; then
|
||||||
echo "Warning: Failed to flush bd changes to JSONL" >&2
|
echo "Warning: Failed to flush bd changes to JSONL" >&2
|
||||||
echo "Run 'bd sync --flush-only' manually to diagnose" >&2
|
echo "Run 'bd sync --flush-only' manually to diagnose" >&2
|
||||||
# Don't block the commit - user may have removed beads or have other issues
|
# Don't block the commit - user may have removed beads or have other issues
|
||||||
>>>>>>> origin/bd-l0pg-slit
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Stage all tracked JSONL files (issues.jsonl is canonical, beads.jsonl for backward compat, deletions.jsonl for deletion propagation)
|
# Stage all tracked JSONL files (beads.jsonl, issues.jsonl for backward compat, deletions.jsonl for deletion propagation)
|
||||||
# For worktrees, .beads is in the main repo's working tree, not the worktree,
|
# git add is harmless if file doesn't exist
|
||||||
# so we can't use git add. Skip staging for worktrees.
|
for f in .beads/beads.jsonl .beads/issues.jsonl .beads/deletions.jsonl; do
|
||||||
if [ "$(git rev-parse --git-dir)" = "$(git rev-parse --git-common-dir)" ]; then
|
[ -f "$f" ] && git add "$f" 2>/dev/null || true
|
||||||
# Regular repo: files are in the working tree, safe to add
|
done
|
||||||
# git add is harmless if file doesn't exist
|
|
||||||
for f in .beads/beads.jsonl .beads/issues.jsonl .beads/deletions.jsonl; do
|
|
||||||
[ -f "$f" ] && git add "$f" 2>/dev/null || true
|
|
||||||
done
|
|
||||||
else
|
|
||||||
# Worktree: .beads is in the main repo's working tree, not this worktree
|
|
||||||
# Git rejects adding files outside the worktree, so we skip it.
|
|
||||||
# The main repo will see the changes on the next pull/sync.
|
|
||||||
: # do nothing
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ if [ ! -d .beads ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Skip if bd sync is already in progress (GH#532: prevents circular error)
|
||||||
|
# bd sync sets this env var when pushing from worktree
|
||||||
|
if [ -n "$BD_SYNC_IN_PROGRESS" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if sync-branch is configured in config.yaml or env var
|
# Check if sync-branch is configured in config.yaml or env var
|
||||||
# If so, .beads changes go to a separate branch via worktree, not the current branch
|
# If so, .beads changes go to a separate branch via worktree, not the current branch
|
||||||
SYNC_BRANCH="${BEADS_SYNC_BRANCH:-}"
|
SYNC_BRANCH="${BEADS_SYNC_BRANCH:-}"
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ if [ ! -d .beads ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Skip if bd sync is already in progress (GH#532: prevents circular error)
|
||||||
|
# bd sync sets this env var when pushing from worktree
|
||||||
|
if [ -n "$BD_SYNC_IN_PROGRESS" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if sync-branch is configured in config.yaml or env var
|
# Check if sync-branch is configured in config.yaml or env var
|
||||||
# If so, .beads changes go to a separate branch via worktree, not the current branch
|
# If so, .beads changes go to a separate branch via worktree, not the current branch
|
||||||
SYNC_BRANCH="${BEADS_SYNC_BRANCH:-}"
|
SYNC_BRANCH="${BEADS_SYNC_BRANCH:-}"
|
||||||
|
|||||||
@@ -855,6 +855,9 @@ func pushFromWorktree(ctx context.Context, worktreePath, branch string) error {
|
|||||||
for attempt := 0; attempt < maxRetries; attempt++ {
|
for attempt := 0; attempt < maxRetries; attempt++ {
|
||||||
// Push with explicit remote and branch, set upstream if not set
|
// Push with explicit remote and branch, set upstream if not set
|
||||||
cmd := exec.CommandContext(ctx, "git", "-C", worktreePath, "push", "--set-upstream", remote, branch)
|
cmd := exec.CommandContext(ctx, "git", "-C", worktreePath, "push", "--set-upstream", remote, branch)
|
||||||
|
// Set BD_SYNC_IN_PROGRESS so pre-push hook knows to skip checks (GH#532)
|
||||||
|
// This prevents circular error where hook suggests running bd sync
|
||||||
|
cmd.Env = append(os.Environ(), "BD_SYNC_IN_PROGRESS=1")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user