From 8e1a841daae9fc9a8cd845284e43013337073e09 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sun, 14 Dec 2025 17:20:43 -0800 Subject: [PATCH] feat(init): auto-add 'landing the plane' instructions to AGENTS.md Closes GH#516: bd init now automatically: - Creates AGENTS.md and @AGENTS.md with landing-the-plane instructions if they don't exist - Appends the instructions to existing files if they don't have them - Skips if the section already exists (idempotent) - Skips in stealth mode (user wants invisible setup) The landing-the-plane instructions ensure AI agents properly complete their work sessions by pushing all changes to remote before ending. --- .beads/issues.jsonl | 3 +- cmd/bd/init.go | 117 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index a93c6420..fcf0e425 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -148,7 +148,6 @@ {"id":"bd-3tfh","title":"Benchmark Helper Functions","description":"Extend existing benchmark helpers in internal/storage/sqlite/bench_helpers_test.go (or create if organizing separately).\n\nExisting helper (in compact_bench_test.go):\n- setupBenchDB(tb) - Creates temp SQLite database with basic config\n * Used by compact and cycle benchmarks\n * Returns (*SQLiteStorage, cleanup func())\n\nNew helpers to add:\n- setupLargeBenchDB(b *testing.B) storage.Storage\n * Creates 10K issue database using LargeSQLite fixture\n * Returns configured storage instance\n \n- setupXLargeBenchDB(b *testing.B) storage.Storage\n * Creates 20K issue database using XLargeSQLite fixture\n * Returns configured storage instance\n\nImplementation options:\n1. Add to existing compact_bench_test.go (co-located with setupBenchDB)\n2. Create new bench_helpers_test.go for organization\n\nBoth approaches:\n- Build tag: //go:build bench\n- Uses fixture generator from internal/testutil/fixtures\n- Follows existing setupBenchDB() pattern\n- Handles database cleanup\n\nThese helpers reduce duplication across new benchmark functions and provide consistent large-scale database setup.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-13T22:22:55.694834-08:00","updated_at":"2025-11-13T23:13:41.244758-08:00","closed_at":"2025-11-13T23:13:41.244758-08:00","dependencies":[{"issue_id":"bd-3tfh","depends_on_id":"bd-m62x","type":"blocks","created_at":"2025-11-13T22:24:02.632994-08:00","created_by":"daemon"}]} {"id":"bd-3xl","title":"bd doctor: per-fix confirmation mode","description":"Add an interactive mode where users can approve/reject each fix individually rather than all-or-nothing. Currently --fix prompts once for all fixes. This would give users more control over which repairs to apply, especially useful when some fixes may have side effects.","status":"closed","priority":4,"issue_type":"feature","created_at":"2025-12-02T12:52:38.137201-08:00","updated_at":"2025-12-13T08:15:21.911204+11:00","closed_at":"2025-12-03T22:17:11.405848-08:00"} {"id":"bd-40a0","title":"bd doctor should check for multiple DBs, multiple JSONLs, daemon health","description":"","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-10-31T21:16:47.042913-07:00","updated_at":"2025-10-31T21:21:27.093525-07:00","closed_at":"2025-10-31T21:21:27.093525-07:00"} -{"id":"bd-442","title":"(deleted)","description":"","status":"tombstone","priority":0,"issue_type":"task","created_at":"2025-12-14T16:43:19.41799-08:00","updated_at":"2025-12-14T16:43:19.41799-08:00","deleted_at":"0001-01-01T00:00:00Z"} {"id":"bd-4462","title":"Test basic bd commands in WASM (init, create, list)","description":"Compile and verify basic bd functionality works in WASM:\n- Test bd init --quiet\n- Test bd create with simple issue\n- Test bd list --json output\n- Verify SQLite database creation and queries work\n- Document any runtime issues or workarounds needed","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-02T21:58:07.291771-08:00","updated_at":"2025-11-02T23:07:10.273212-08:00","closed_at":"2025-11-02T23:07:10.273212-08:00","dependencies":[{"issue_id":"bd-4462","depends_on_id":"bd-44d0","type":"parent-child","created_at":"2025-11-02T22:23:49.448668-08:00","created_by":"stevey"},{"issue_id":"bd-4462","depends_on_id":"bd-b4b0","type":"blocks","created_at":"2025-11-02T22:23:55.596771-08:00","created_by":"stevey"}]} {"id":"bd-44d0","title":"WASM port of bd for Claude Code Web sandboxes","description":"Enable beads to work in Claude Code Web sandboxes by compiling bd to WebAssembly.\n\n## Problem\nClaude Code Web sandboxes cannot install bd CLI due to network restrictions:\n- GitHub releases return 403\n- go install fails with DNS errors\n- Binary cannot be downloaded\n\n## Solution\nCompile bd Go codebase to WASM, publish to npm as drop-in replacement.\n\n## Technical Approach\n- Use GOOS=js GOARCH=wasm to compile bd\n- modernc.org/sqlite already supports js/wasm target\n- Publish to npm as bd-wasm package\n- Full feature parity with bd CLI\n\n## Success Criteria\n- bd-wasm installs via npm in web sandbox\n- All core bd commands work identically\n- JSONL output matches native bd\n- Performance within 2x of native","notes":"WASM port abandoned - Claude Code Web has full VMs not browser restrictions. Better: npm + native binary","status":"closed","priority":0,"issue_type":"epic","created_at":"2025-11-02T18:32:27.660794-08:00","updated_at":"2025-12-13T18:00:31.061977-08:00","closed_at":"2025-11-02T23:36:38.679515-08:00"} {"id":"bd-44e","title":"Ensure deletions.jsonl is tracked in git","description":"Parent: bd-imj\n\nEnsure deletions.jsonl is tracked in git (not ignored).\n\nUpdate bd init and gitignore upgrade logic to:\n1. NOT add deletions.jsonl to .gitignore\n2. Ensure it is committed alongside beads.jsonl\n\nThe file must be in git for cross-clone propagation to work.\n\nAcceptance criteria:\n- bd init does not ignore deletions.jsonl\n- Existing .gitignore files are not broken\n- File appears in git status when modified","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-25T09:57:21.663196-08:00","updated_at":"2025-11-25T14:55:43.225883-08:00","closed_at":"2025-11-25T14:55:43.225883-08:00"} @@ -865,7 +864,7 @@ {"id":"bd-zo7o","title":"Create multi-agent race condition test","description":"Automated test that runs 2+ agents simultaneously to verify collision prevention.\n\nAcceptance Criteria:\n- Script spawns 2 agents in parallel\n- Both try to claim same issue\n- Only one succeeds (via reservation)\n- Other agent skips to different work\n- Verify in JSONL that no duplicate claims\n- Test with Agent Mail enabled/disabled\n\nFile: tests/integration/test_agent_race.py\n\nSuccess Metric: Zero duplicate claims with Agent Mail, collisions without it","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-07T22:43:21.360663-08:00","updated_at":"2025-11-08T00:34:14.40119-08:00","closed_at":"2025-11-08T00:34:14.40119-08:00","dependencies":[{"issue_id":"bd-zo7o","depends_on_id":"bd-fzbg","type":"blocks","created_at":"2025-11-07T22:43:21.361571-08:00","created_by":"daemon"}]} {"id":"bd-zpnq","title":"Daemons don't exit when parent process dies, causing accumulation and race conditions","description":"Multiple daemon processes accumulate over time because daemons don't automatically stop when their parent process (e.g., coding agent) is killed. This causes:\n\n1. Race conditions: 8+ daemons watching same .beads/beads.db, each with own 30s debounce timer\n2. Git conflicts: Multiple daemons racing to commit/push .beads/issues.jsonl\n3. Resource waste: Orphaned daemons from sessions days/hours old still running\n\nExample: User had 8 daemons from multiple sessions (12:37AM, 7:20PM, 7:22PM, 7:47PM, 9:19PM yesterday + 9:54AM, 10:55AM today).\n\nSolutions to consider:\n1. Track parent PID and exit when parent dies\n2. Use single global daemon instead of per-session\n3. Document manual cleanup: pkill -f \"bd daemon\"\n4. Add daemon lifecycle management (auto-cleanup of stale daemons)","notes":"Implementation complete:\n\n1. Added ParentPID field to DaemonLockInfo struct (stored in daemon.lock JSON)\n2. Daemon now tracks parent PID via os.Getppid() at startup\n3. Both event loops (polling and event-driven) check parent process every 10 seconds\n4. Daemon gracefully exits if parent process dies (detected via isProcessRunning check)\n5. Handles edge cases:\n - ParentPID=0: Older daemons without tracking (ignored)\n - ParentPID=1: Adopted by init means parent died (exits)\n - Otherwise checks if parent process is still running\n\nThe fix prevents daemon accumulation by ensuring orphaned daemons automatically exit within 10 seconds of parent death.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-07T18:48:41.65456-08:00","updated_at":"2025-11-07T18:53:26.382573-08:00","closed_at":"2025-11-07T18:53:26.382573-08:00"} {"id":"bd-zqmb","title":"Fix goroutine leak in daemon restart","description":"Fire-and-forget goroutine in daemon restart leaks on every restart.\n\nLocation: cmd/bd/daemons.go:251\n\nProblem:\ngo func() { _ = daemonCmd.Wait() }()\n\n- Spawns goroutine without timeout or cancellation\n- If daemon command never completes, goroutine leaks forever\n- Each daemon restart leaks one more goroutine\n\nSolution: Add timeout and cleanup:\ngo func() {\n done := make(chan struct{})\n go func() {\n _ = daemonCmd.Wait()\n close(done)\n }()\n \n select {\n case \u003c-done:\n // Exited normally\n case \u003c-time.After(10 * time.Second):\n // Timeout - daemon should have forked by now\n _ = daemonCmd.Process.Kill()\n }\n}()\n\nImpact: Goroutine leak on every daemon restart\n\nEffort: 2 hours","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-16T14:52:01.897215-08:00","updated_at":"2025-11-16T15:04:00.497517-08:00","closed_at":"2025-11-16T15:04:00.497517-08:00"} -{"id":"bd-zsle","title":"GH#516: Automate 'landing the plane' setup in AGENTS.md","description":"bd init or /beads:init should auto-add landing-the-plane instructions to AGENTS.md (and @AGENTS.md for web Claude). Reduces manual setup. See: https://github.com/steveyegge/beads/issues/516","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-14T16:31:57.541154-08:00","updated_at":"2025-12-14T16:31:57.541154-08:00"} +{"id":"bd-zsle","title":"GH#516: Automate 'landing the plane' setup in AGENTS.md","description":"bd init or /beads:init should auto-add landing-the-plane instructions to AGENTS.md (and @AGENTS.md for web Claude). Reduces manual setup. See: https://github.com/steveyegge/beads/issues/516","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-14T16:31:57.541154-08:00","updated_at":"2025-12-14T17:20:34.891647-08:00","closed_at":"2025-12-14T17:20:34.891647-08:00"} {"id":"bd-zsz","title":"Add --parent flag to bd onboard output","description":"bd onboard didn't document --parent flag for epic subtasks, causing AI agents to guess wrong syntax. Added --parent example and CLI help section pointing to bd \u003ccmd\u003e --help.\n\nFixes: https://github.com/steveyegge/beads/issues/402","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-27T13:01:51.366625-08:00","updated_at":"2025-11-27T13:02:02.018003-08:00","closed_at":"2025-11-27T13:02:02.018003-08:00"} {"id":"bd-zvg","title":"Design tombstone merge semantics","description":"Define how tombstones participate in 3-way merge. Key question: does tombstone always win over live issue (like current deletion-wins rule), or should we use updated_at comparison? Consider: tombstone with older timestamp vs live issue with newer edits. Recommendation: tombstone wins unless expired, matching current behavior but with longer TTL window.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-05T13:43:13.522002-08:00","updated_at":"2025-12-05T15:01:30.998256-08:00","closed_at":"2025-12-05T15:01:30.998256-08:00"} {"id":"bd-zwpw","title":"Test dependency child","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-05T11:23:05.998311-08:00","updated_at":"2025-11-05T11:23:30.389454-08:00","closed_at":"2025-11-05T11:23:30.389454-08:00","dependencies":[{"issue_id":"bd-zwpw","depends_on_id":"bd-k0j9","type":"blocks","created_at":"2025-11-05T11:23:05.998981-08:00","created_by":"daemon"}]} diff --git a/cmd/bd/init.go b/cmd/bd/init.go index 67198d82..0c29f69d 100644 --- a/cmd/bd/init.go +++ b/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 if quiet { 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 # View issue details +bd update --status in_progress # Claim work +bd close # 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 func setupClaudeSettings(verbose bool) error { claudeDir := ".claude"