diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index ae0b29d5..e212dacb 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -4,51 +4,53 @@ {"id":"bd-0a43","title":"Split monolithic sqlite.go into focused files","description":"internal/storage/sqlite/sqlite.go is 1050 lines containing initialization, 20+ CRUD methods, query building, and schema management.\n\nSplit into:\n- store.go: Store struct \u0026 initialization (150 lines)\n- bead_queries.go: Bead CRUD (300 lines)\n- work_queries.go: Work queries (200 lines) \n- stats_queries.go: Statistics (150 lines)\n- schema.go: Schema \u0026 migrations (150 lines)\n- helpers.go: Common utilities (100 lines)\n\nImpact: Impossible to understand at a glance; hard to find specific functionality; high cognitive load\n\nEffort: 6-8 hours","status":"closed","issue_type":"task","created_at":"2025-11-16T14:51:16.520465-08:00","updated_at":"2025-12-17T23:13:40.533947-08:00","closed_at":"2025-12-17T16:51:30.236012-08:00"} {"id":"bd-0d5p","title":"Fix TestRunSync_Timeout failing on macOS","description":"The hooks timeout test fails because exec.CommandContext doesn't properly terminate child processes of shell scripts on macOS. The test creates a hook that runs 'sleep 60' with a 500ms timeout, but it waits the full 60 seconds.\n\nOptions to fix:\n- Use SysProcAttr{Setpgid: true} to create process group and kill the group\n- Skip test on darwin with build tag\n- Use a different approach for timeout testing\n\nLocation: internal/hooks/hooks_test.go:220-253","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-16T20:52:51.771217-08:00","updated_at":"2025-12-17T23:13:40.532688-08:00","closed_at":"2025-12-17T17:23:55.678799-08:00"} {"id":"bd-0fvq","title":"bd doctor should recommend bd prime migration for existing repos","description":"bd doctor should detect old beads integration patterns and recommend migrating to bd prime approach.\n\n## Current behavior\n- bd doctor checks if Claude hooks are installed globally\n- Doesn't check project-level integration (AGENTS.md, CLAUDE.md)\n- Doesn't recommend migration for repos using old patterns\n\n## Desired behavior\nbd doctor should detect and suggest:\n\n1. **Old slash command pattern detected**\n - Check for /beads:* references in AGENTS.md, CLAUDE.md\n - Suggest: These slash commands are deprecated, use bd prime hooks instead\n \n2. **No agent documentation**\n - Check if AGENTS.md or CLAUDE.md exists\n - Suggest: Run 'bd onboard' or 'bd setup claude' to document workflow\n \n3. **Old MCP-only pattern**\n - Check for instructions to use MCP tools but no bd prime hooks\n - Suggest: Add bd prime hooks for better token efficiency\n\n4. **Migration path**\n - Show: 'Run bd setup claude to add SessionStart/PreCompact hooks'\n - Show: 'Update AGENTS.md to reference bd prime instead of slash commands'\n\n## Example output\n\n⚠ Warning: Old beads integration detected in CLAUDE.md\n Found: /beads:* slash command references (deprecated)\n Recommend: Migrate to bd prime hooks for better token efficiency\n Fix: Run 'bd setup claude' and update CLAUDE.md\n\nšŸ’” Tip: bd prime + hooks reduces token usage by 80-99% vs slash commands\n MCP mode: ~50 tokens vs ~10.5k for full MCP scan\n CLI mode: ~1-2k tokens with automatic context recovery\n\n## Benefits\n- Helps existing repos adopt new best practices\n- Clear migration path for users\n- Better token efficiency messaging","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-12T03:20:25.567748-08:00","updated_at":"2025-11-12T03:20:25.567748-08:00"} -{"id":"bd-0kai","title":"Work on beads-ocs: Thin shim hooks to eliminate version d...","description":"Work on beads-ocs: Thin shim hooks to eliminate version drift (GH#615). Replace full hook scripts with thin shims that call bd hooks run. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:57:22.91347-08:00","updated_at":"2025-12-20T00:26:12.605248-08:00","closed_at":"2025-12-19T23:24:08.828172-08:00"} +{"id":"bd-0kai","title":"Work on beads-ocs: Thin shim hooks to eliminate version d...","description":"Work on beads-ocs: Thin shim hooks to eliminate version drift (GH#615). Replace full hook scripts with thin shims that call bd hooks run. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:57:22.91347-08:00","updated_at":"2025-12-20T00:49:51.926425-08:00","closed_at":"2025-12-19T23:24:08.828172-08:00","close_reason":"Implemented thin shim hooks to eliminate version drift (beads-ocs)"} {"id":"bd-0vg","title":"Pinned issues: persistent context markers","description":"Add ability to pin issues so they remain visible and are excluded from work-finding commands. Pinned issues serve as persistent context markers (handoffs, architectural notes, recovery instructions) that should not be claimed as work items.\n\nUse Cases:\n1. Handoff messages - Pin session handoffs so new agents always see them\n2. Architecture decisions - Pin ADRs or design notes for reference \n3. Recovery context - Pin amnesia-cure notes that help agents orient\n\nCore commands: bd pin, bd unpin, bd list --pinned/--no-pinned","status":"open","priority":1,"issue_type":"epic","created_at":"2025-12-18T23:33:10.911092-08:00","updated_at":"2025-12-18T23:33:17.882415-08:00"} {"id":"bd-0w5","title":"Fix update-hooks verification in version-bump.yaml","description":"The update-hooks task verification command at version-bump.yaml:358 always succeeds due to '|| echo ...' fallback. Remove the fallback so verification actually fails when hooks aren't installed.","status":"closed","priority":3,"issue_type":"bug","created_at":"2025-12-17T22:23:06.55467-08:00","updated_at":"2025-12-17T22:34:07.290409-08:00","closed_at":"2025-12-17T22:34:07.290409-08:00"} {"id":"bd-0zp7","title":"Add missing hook calls in mail reply and ack","description":"The mail commands are missing hook calls:\n\n1. runMailReply (mail.go:525-672) creates a message but doesn't call hookRunner.Run(hooks.EventMessage, ...) after creating the reply in direct mode (around line 640)\n\n2. runMailAck (mail.go:432-523) closes messages but doesn't call hookRunner.Run(hooks.EventClose, ...) after closing each message (around line 487 for daemon mode, 493 for direct mode)\n\nThis means GGT hooks won't fire for replies or message acknowledgments.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-16T20:52:53.069412-08:00","updated_at":"2025-12-17T23:13:40.532054-08:00","closed_at":"2025-12-17T17:22:59.368024-08:00"} -{"id":"bd-14ie","title":"Work on beads-2vn: Add simple built-in beads viewer (GH#6...","description":"Work on beads-2vn: Add simple built-in beads viewer (GH#654). Add bd list --pretty with --watch flag, tree view with priority/status symbols. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:56:47.305831-08:00","updated_at":"2025-12-20T00:26:12.605897-08:00","closed_at":"2025-12-19T23:23:13.928323-08:00"} +{"id":"bd-14ie","title":"Work on beads-2vn: Add simple built-in beads viewer (GH#6...","description":"Work on beads-2vn: Add simple built-in beads viewer (GH#654). Add bd list --pretty with --watch flag, tree view with priority/status symbols. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:56:47.305831-08:00","updated_at":"2025-12-19T23:28:32.429492-08:00","closed_at":"2025-12-19T23:23:13.928323-08:00","close_reason":"Implemented --pretty flag with tree view and symbols. Tests pass."} {"id":"bd-1slh","title":"Investigate charmbracelet-based TUI for beads","description":"Now that we've merged the create-form command (PR #603) which uses charmbracelet/huh, investigate whether beads should have a more comprehensive TUI.\n\nConsiderations:\n- Should this be in core or a separate binary (bd-tui)?\n- What functionality would benefit from a TUI? (list view, issue details, search, bulk operations)\n- Plugin/extension architecture vs build tags vs separate binary\n- Dependency cost vs user experience tradeoff\n- Target audience: humans who want interactive workflows vs CLI/scripting users\n\nRelated: PR #603 added charmbracelet/huh dependency for create-form command.","status":"open","priority":3,"issue_type":"feature","created_at":"2025-12-17T14:20:51.503563-08:00","updated_at":"2025-12-17T14:20:51.503563-08:00"} {"id":"bd-1tw","title":"Fix G104 errors unhandled in internal/storage/sqlite/queries.go:1186","description":"Linting issue: G104: Errors unhandled (gosec) at internal/storage/sqlite/queries.go:1186:2. Error: rows.Close()","status":"closed","issue_type":"bug","created_at":"2025-12-07T15:35:13.051671889-07:00","updated_at":"2025-12-17T23:13:40.53486-08:00","closed_at":"2025-12-17T16:46:11.0289-08:00"} {"id":"bd-20j","title":"sync branch not match config","description":"./bd sync\n→ Exporting pending changes to JSONL...\n→ No changes to commit\n→ Pulling from sync branch 'gh-386'...\nError pulling from sync branch: failed to create worktree: failed to create worktree parent directory: mkdir /var/home/matt/dev/beads/worktree-db-fail/.git: not a directory\nmatt@blufin-framation ~/d/b/worktree-db-fail (worktree-db-fail) [1]\u003e bd config list\n\nConfiguration:\n auto_compact_enabled = false\n compact_batch_size = 50\n compact_model = claude-3-5-haiku-20241022\n compact_parallel_workers = 5\n compact_tier1_days = 30\n compact_tier1_dep_levels = 2\n compact_tier2_commits = 100\n compact_tier2_days = 90\n compact_tier2_dep_levels = 5\n compaction_enabled = false\n issue_prefix = worktree-db-fail\n sync.branch = worktree-db-fail","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-08T06:49:04.449094018-07:00","updated_at":"2025-12-08T06:49:04.449094018-07:00"} {"id":"bd-28db","title":"Add 'bd status' command for issue database overview","description":"Implement a bd status command that provides a quick snapshot of the issue database state, similar to how git status shows working tree state.\n\nExpected output: Show summary including counts by state (open, in-progress, blocked, closed), recent activity (last 7 days), and quick overview without needing multiple queries.\n\nExample output showing issue counts, recent activity stats, and pointer to bd list for details.\n\nProposed options: --all (show all issues), --assigned (show issues assigned to current user), --json (JSON format output)\n\nUse cases: Quick project health check, onboarding for new contributors, integration with shell prompts or CI/CD, daily standup reference","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-02T17:25:59.203549-08:00","updated_at":"2025-11-02T17:25:59.203549-08:00"} -{"id":"bd-2ep8","title":"Update CHANGELOG.md with release notes","description":"Add meaningful release notes to CHANGELOG.md describing what changed in 0.30.7","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:48.649053-08:00","updated_at":"2025-12-19T22:57:31.69559-08:00","closed_at":"2025-12-19T22:57:31.69559-08:00","close_reason":"Updated CHANGELOG.md with release notes","dependencies":[{"issue_id":"bd-2ep8","depends_on_id":"bd-8pyn","type":"parent-child","created_at":"2025-12-19T22:56:48.650816-08:00","created_by":"stevey"},{"issue_id":"bd-2ep8","depends_on_id":"bd-rupw","type":"blocks","created_at":"2025-12-19T22:56:48.651136-08:00","created_by":"stevey"}]} +{"id":"bd-2ep8","title":"Update CHANGELOG.md with release notes","description":"Add meaningful release notes to CHANGELOG.md describing what changed in 0.30.7","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:48.649053-08:00","updated_at":"2025-12-19T22:57:31.69559-08:00","closed_at":"2025-12-19T22:57:31.69559-08:00","dependencies":[{"issue_id":"bd-2ep8","depends_on_id":"bd-8pyn","type":"parent-child","created_at":"2025-12-19T22:56:48.650816-08:00","created_by":"stevey"},{"issue_id":"bd-2ep8","depends_on_id":"bd-rupw","type":"blocks","created_at":"2025-12-19T22:56:48.651136-08:00","created_by":"stevey"}]} {"id":"bd-2oo","title":"Edge Schema Consolidation: Unify all edges in dependencies table","description":"Consolidate all edge types into the dependency table per decision 004.\n\n## Changes\n- Add metadata column to dependencies table\n- Add thread_id column for conversation grouping\n- Remove redundant Issue fields: replies_to, relates_to, duplicate_of, superseded_by\n- Update all code to use dependencies API\n- Migration script for existing data\n- JSONL format change (breaking)\n\nReference: ~/gt/hop/decisions/004-edge-schema-consolidation.md","status":"closed","issue_type":"epic","created_at":"2025-12-18T02:01:48.785558-08:00","updated_at":"2025-12-18T02:49:10.61237-08:00","closed_at":"2025-12-18T02:49:10.61237-08:00","close_reason":"Phase 4 complete: all edge fields removed, dependencies API used exclusively"} {"id":"bd-2oo.1","title":"Add metadata and thread_id columns to dependencies table","description":"Schema changes:\n- ALTER TABLE dependencies ADD COLUMN metadata TEXT DEFAULT '{}'\n- ALTER TABLE dependencies ADD COLUMN thread_id TEXT DEFAULT ''\n- CREATE INDEX idx_dependencies_thread ON dependencies(thread_id) WHERE thread_id != ''","status":"closed","issue_type":"task","created_at":"2025-12-18T02:02:00.468223-08:00","updated_at":"2025-12-18T02:49:10.575133-08:00","closed_at":"2025-12-18T02:49:10.575133-08:00","close_reason":"Phase 4 complete: all edge fields removed, dependencies API used exclusively","dependencies":[{"issue_id":"bd-2oo.1","depends_on_id":"bd-2oo","type":"parent-child","created_at":"2025-12-18T02:02:00.470012-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-2oo.2","title":"Remove redundant edge fields from Issue struct","description":"Remove from Issue struct:\n- RepliesTo -\u003e dependency with type replies-to\n- RelatesTo -\u003e dependencies with type relates-to \n- DuplicateOf -\u003e dependency with type duplicates\n- SupersededBy -\u003e dependency with type supersedes\n\nKeep: Sender, Ephemeral (these are attributes, not relationships)","status":"closed","issue_type":"task","created_at":"2025-12-18T02:02:00.891206-08:00","updated_at":"2025-12-18T02:49:10.584381-08:00","closed_at":"2025-12-18T02:49:10.584381-08:00","close_reason":"Phase 4 complete: all edge fields removed, dependencies API used exclusively","dependencies":[{"issue_id":"bd-2oo.2","depends_on_id":"bd-2oo","type":"parent-child","created_at":"2025-12-18T02:02:00.891655-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-2oo.3","title":"Update all code to use dependencies API for edges","description":"Find and update all code that reads/writes:\n- replies_to field -\u003e use dependency API\n- relates_to field -\u003e use dependency API\n- duplicate_of field -\u003e use dependency API\n- superseded_by field -\u003e use dependency API\n\nCommands affected: bd mail, bd relate, bd duplicate, bd supersede, bd show, etc.","status":"closed","issue_type":"task","created_at":"2025-12-18T02:02:01.317006-08:00","updated_at":"2025-12-18T02:49:10.59233-08:00","closed_at":"2025-12-18T02:49:10.59233-08:00","close_reason":"Phase 4 complete: all edge fields removed, dependencies API used exclusively","dependencies":[{"issue_id":"bd-2oo.3","depends_on_id":"bd-2oo","type":"parent-child","created_at":"2025-12-18T02:02:01.31856-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-2oo.4","title":"Create migration script for edge field to dependency conversion","description":"Migration must:\n1. Read existing JSONL with old fields\n2. Convert field values to dependency records\n3. Write updated JSONL without old fields\n4. Handle edge cases (missing refs, duplicates)\n\nRun via: bd migrate or automatic on bd prime","status":"closed","issue_type":"task","created_at":"2025-12-18T02:02:01.760277-08:00","updated_at":"2025-12-18T02:49:10.602446-08:00","closed_at":"2025-12-18T02:49:10.602446-08:00","close_reason":"Phase 4 complete: all edge fields removed, dependencies API used exclusively","dependencies":[{"issue_id":"bd-2oo.4","depends_on_id":"bd-2oo","type":"parent-child","created_at":"2025-12-18T02:02:01.760694-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-2q6d","title":"Beads commands operate on stale database without warning","description":"All beads read operations should validate database is in sync with JSONL before proceeding.\n\n**Current Behavior:**\n- Commands can query/read from stale database\n- Only mutation operations (like 'bd sync') check if JSONL is newer\n- User gets incorrect results without realizing database is out of sync\n\n**Expected Behavior:**\n- All beads commands should have pre-flight check for database freshness\n- If JSONL is newer than database, refuse to operate with error: \"Database out of sync. Run 'bd import' first.\"\n- Same safety check that exists for 'bd sync' should apply to ALL operations\n\n**Impact:**\n- Users make decisions based on incomplete/outdated data\n- Silent failures lead to confusion (e.g., thinking issues don't exist when they do)\n- Similar to running git commands on stale repo without being warned to pull\n\n**Example:**\n- Searched for bd-g9eu issue file: not found\n- Issue exists in .beads/issues.jsonl (in git)\n- Database was stale, but no warning was given\n- Led to incorrect conclusion that issue was already closed/deleted","notes":"## Implementation Complete\n\n**Phase 1: Created staleness check (cmd/bd/staleness.go)**\n- ensureDatabaseFresh() function checks JSONL mtime vs last_import_time\n- Returns error with helpful message when database is stale\n- Auto-skips in daemon mode (daemon has auto-import)\n\n**Phase 2: Added to all read commands**\n- list, show, ready, status, stale, info, duplicates, validate\n- Check runs before database queries in direct mode\n- Daemon mode already protected via checkAndAutoImportIfStale()\n\n**Phase 3: Code Review Findings**\nSee follow-up issues:\n- bd-XXXX: Add warning when staleness check errors\n- bd-YYYY: Improve CheckStaleness error handling\n- bd-ZZZZ: Refactor redundant daemon checks (low priority)\n\n**Testing:**\n- Build successful: go build ./cmd/bd\n- Binary works: ./bd --version\n- Ready for manual testing\n\n**Next Steps:**\n1. Test with stale database scenario\n2. Implement review improvements\n3. Close issue when tests pass","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-20T19:33:40.019297-05:00","updated_at":"2025-12-17T23:13:40.535149-08:00","closed_at":"2025-12-17T19:11:12.982639-08:00"} +{"id":"bd-2vh3","title":"Ephemeral issue cleanup and history compaction","description":"## Problem\n\nBeads history grows without bound. Every message, handoff, work assignment\nstays in issues.jsonl forever. Enterprise users will balk at \"git as database.\"\n\n## Solution: Two-Tier Cleanup\n\n### Tier 1: Ephemeral Cleanup (v1)\n\nbd cleanup --ephemeral --closed\n\n- Deletes closed issues where ephemeral=true from issues.jsonl\n- Safe: only removes explicitly marked ephemeral + closed\n- Preserves git history (commits still exist)\n- Run after swarm completion\n\n### Tier 2: History Compaction (v2)\n\nbd compact --squash\n\n- Rewrites issues.jsonl to remove tombstones\n- Optionally squashes git history (interactive rebase equivalent)\n- Preserves Merkle proofs for deleted items\n- Advanced: cold storage tiering\n\n## HOP Context\n\n| Layer | HOP Role | Persistence |\n|-------|----------|-------------|\n| Execution trace | None | Ephemeral |\n| Work scaffolding | None | Summarizable |\n| Work outcome | CV entry | Permanent |\n| Validation record | Stake proof | Permanent |\n\n\"Execution is ephemeral. Outcomes are permanent. You can't squash your CV.\"\n\n## Success Criteria\n\n- After cleanup --ephemeral: issues.jsonl only contains persistent work\n- Work outcomes preserved (CV entries)\n- Validation records preserved (stake proofs)\n- Execution scaffolding removed (transient coordination)","status":"open","priority":1,"issue_type":"feature","created_at":"2025-12-20T21:02:20.101367-08:00","updated_at":"2025-12-20T21:02:20.101367-08:00"} {"id":"bd-2wh","title":"Test pinned for stats","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-18T21:47:09.334108-08:00","updated_at":"2025-12-18T21:47:25.17917-08:00","deleted_at":"2025-12-18T21:47:25.17917-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} {"id":"bd-379","title":"Implement `bd setup cursor` for Cursor IDE integration","description":"Create a `bd setup cursor` command that integrates Beads workflow into Cursor IDE via .cursorrules file. Unlike Claude Code (which has hooks), Cursor uses a static rules file to provide context to its AI.","status":"open","priority":3,"issue_type":"feature","created_at":"2025-11-11T23:32:22.170083-08:00","updated_at":"2025-11-11T23:32:22.170083-08:00"} {"id":"bd-3852","title":"Add orphan detection migration","description":"Create migration to detect orphaned children in existing databases. Query: SELECT id FROM issues WHERE id LIKE '%.%' AND substr(id, 1, instr(id || '.', '.') - 1) NOT IN (SELECT id FROM issues). Log results, let user decide action (delete orphans or convert to top-level).","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-04T12:32:30.727044-08:00","updated_at":"2025-11-04T12:32:30.727044-08:00"} {"id":"bd-3ggb","title":"Rebuild local binary","description":"Build and verify: go build -o bd ./cmd/bd \u0026\u0026 ./bd version","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T22:43:03.101428-08:00","updated_at":"2025-12-18T22:46:40.955673-08:00","closed_at":"2025-12-18T22:46:40.955673-08:00","dependencies":[{"issue_id":"bd-3ggb","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:43:16.748289-08:00","created_by":"daemon"},{"issue_id":"bd-3ggb","depends_on_id":"bd-4y4g","type":"blocks","created_at":"2025-12-18T22:43:20.950376-08:00","created_by":"daemon"}]} {"id":"bd-3sz0","title":"Auto-repair stale merge driver configs with invalid placeholders","description":"Old bd versions (\u003c0.24.0) installed merge driver with invalid placeholders %L %R instead of %A %B. Add detection to bd doctor --fix: check if git config merge.beads.driver contains %L or %R, auto-repair to 'bd merge %A %O %A %B'. One-time migration for users who initialized with old versions.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-21T23:16:10.762808-08:00","updated_at":"2025-11-21T23:16:28.892655-08:00","dependencies":[{"issue_id":"bd-3sz0","depends_on_id":"bd-tbz3","type":"parent-child","created_at":"2025-11-21T23:16:10.763612-08:00","created_by":"daemon","metadata":"{}"}]} -{"id":"bd-3x9o","title":"Merge: bd-by0d","description":"branch: polecat/furiosa\ntarget: main\nsource_issue: bd-by0d\nrig: beads","status":"open","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:21:26.817906-08:00","updated_at":"2025-12-19T23:21:26.817906-08:00"} -{"id":"bd-401h","title":"Work on beads-7jl: Fix Windows installer file locking iss...","description":"Work on beads-7jl: Fix Windows installer file locking issue (GH#652). Close file handle before extraction in postinstall.js. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:55:57.873767-08:00","updated_at":"2025-12-19T23:20:05.747664-08:00","closed_at":"2025-12-19T23:20:05.747664-08:00"} +{"id":"bd-3x9o","title":"Merge: bd-by0d","description":"branch: polecat/furiosa\ntarget: main\nsource_issue: bd-by0d\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:21:26.817906-08:00","updated_at":"2025-12-20T23:17:26.998785-08:00","closed_at":"2025-12-20T23:17:26.998785-08:00","close_reason":"Branches nuked, MRs obsolete"} +{"id":"bd-401h","title":"Work on beads-7jl: Fix Windows installer file locking iss...","description":"Work on beads-7jl: Fix Windows installer file locking issue (GH#652). Close file handle before extraction in postinstall.js. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","assignee":"beads/rictus","created_at":"2025-12-19T22:55:57.873767-08:00","updated_at":"2025-12-19T23:20:05.747664-08:00","closed_at":"2025-12-19T23:20:05.747664-08:00","close_reason":"Fixed file handle closure race condition in postinstall.js"} {"id":"bd-411u","title":"Document BEADS_DIR pattern for multi-agent workspaces (Gas Town)","description":"Gas Town and similar multi-agent systems need to configure separate beads databases per workspace/rig, distinct from any project-level beads.\n\n## Use Case\n\nIn Gas Town:\n- Each 'rig' (managed project) has multiple agents (polecats, refinery, witness)\n- All agents in a rig should share a single beads database at the rig level\n- This should be separate from any .beads/ the project itself uses\n- The BEADS_DIR env var enables this\n\n## Documentation Needed\n\n1. Add a section to docs explaining BEADS_DIR for multi-agent setups\n2. Example: setting BEADS_DIR in agent startup scripts/hooks\n3. Clarify interaction with project-level .beads/ (BEADS_DIR takes precedence)\n\n## Current Support\n\nAlready implemented in internal/beads/beads.go:FindDatabasePath():\n- BEADS_DIR env var is checked first (preferred)\n- BEADS_DB env var still supported (deprecated)\n- Falls back to .beads/ search in tree\n\nJust needs documentation for the multi-agent workspace pattern.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-15T22:08:22.158027-08:00","updated_at":"2025-12-15T22:08:22.158027-08:00"} {"id":"bd-47tn","title":"Add bd daemon --stop-all command to kill all daemon processes","description":"Currently there's no easy way to stop all running bd daemon processes. Users must resort to pkill -f 'bd daemon' or similar shell commands.\n\nAdd a --stop-all flag to bd daemon that:\n1. Finds all running bd daemon processes (not just the current repo's daemon)\n2. Gracefully stops them all\n3. Reports how many were stopped\n\nThis is useful when:\n- Multiple daemons are running and causing race conditions\n- User wants a clean slate before running bd sync\n- Debugging daemon-related issues","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-13T06:34:45.080633-08:00","updated_at":"2025-12-16T01:14:49.501989-08:00","closed_at":"2025-12-14T17:33:03.057089-08:00"} {"id":"bd-49kw","title":"Workaround for FastMCP outputSchema bug in Claude Code","description":"The beads MCP server (v0.23.1) successfully connects to Claude Code, but all tools fail to load with a schema validation error due to a bug in FastMCP 2.13.1.\n\nError: \"Invalid literal value, expected \\\"object\\\"\" in outputSchema.\n\nRoot Cause: FastMCP generates outputSchema with $ref at root level without \"type\": \"object\" for self-referential models (Issue).\n\nWorkaround: Use slash commands (/beads:ready) or wait for FastMCP fix.\n","status":"open","priority":1,"issue_type":"bug","created_at":"2025-11-20T18:55:39.041831-05:00","updated_at":"2025-11-20T18:55:39.041831-05:00"} {"id":"bd-4ec8","title":"Widespread double JSON encoding bug in daemon mode RPC calls","description":"Multiple CLI commands had the same double JSON encoding bug found in bd-1048. All commands that called ResolveID via RPC used string(resp.Data) instead of properly unmarshaling the JSON response. This caused IDs to retain JSON quotes (\"bd-1048\" instead of bd-1048), which then got double-encoded when passed to subsequent RPC calls.\n\nAffected commands:\n- bd show (3 instances)\n- bd dep add/remove/tree (5 instances)\n- bd label add/remove/list (3 instances)\n- bd reopen (1 instance)\n\nRoot cause: resp.Data is json.RawMessage (already JSON-encoded), so string() conversion preserves quotes.\n\nFix: Replace all string(resp.Data) with json.Unmarshal(resp.Data, \u0026id) for proper deserialization.\n\nAll commands now tested and working correctly with daemon mode.","status":"closed","issue_type":"bug","created_at":"2025-11-02T22:33:01.632691-08:00","updated_at":"2025-12-17T23:13:40.533631-08:00","closed_at":"2025-12-17T16:26:05.851197-08:00"} {"id":"bd-4hn","title":"wish: list \u0026 ready show issues as hierarchy tree","description":"`bd ready` and `bd list` just show a flat list, and it's up to the reader to parse which ones are dependent or sub-issues of others. It would be much easier to understand if they were shown in a tree format","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-08T06:38:24.016316945-07:00","updated_at":"2025-12-08T06:39:04.065882225-07:00"} +{"id":"bd-4lm3","title":"Correction: Pinned field already in v0.31.0","description":"Quick correction - the Pinned field is already in the current bd v0.31.0:\n\n```go\n// In beads internal/types/types.go\nPinned bool `json:\"pinned,omitempty\"`\n```\n\nSo you just need to:\n1. Add `Pinned bool `json:\"pinned,omitempty\"`` to BeadsMessage in types.go\n2. Sort pinned messages first in listBeads() after fetching\n\nNo migration needed - the field is already there.\n\n-- Mayor","status":"open","priority":2,"issue_type":"message","assignee":"gastown/crew/max","created_at":"2025-12-20T17:52:27.321458-08:00","updated_at":"2025-12-20T17:52:27.321458-08:00","labels":["from:beads-crew-dave","thread:thread-4dd70157dbc1"]} {"id":"bd-4nqq","title":"Remove dead test code in info_test.go","description":"Code health review found cmd/bd/info_test.go has two tests permanently skipped:\n\n- TestInfoCommand\n- TestInfoCommandNoDaemon\n\nBoth skip with: 'Manual test - bd info command is working, see manual testing'\n\nThese are essentially dead code. Either automate them or remove them entirely.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-16T18:17:27.554019-08:00","updated_at":"2025-12-16T18:17:27.554019-08:00","dependencies":[{"issue_id":"bd-4nqq","depends_on_id":"bd-tggf","type":"blocks","created_at":"2025-12-16T18:19:06.381694-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-4q8","title":"bd cleanup --hard should skip tombstone creation for true permanent deletion","description":"## Problem\n\nWhen using bd cleanup --hard --older-than N --force, the command:\n1. Deletes closed issues older than N days (converting them to tombstones with NOW timestamp)\n2. Then tries to prune tombstones older than N days (finds none because they were just created)\n\nThis leaves the database bloated with fresh tombstones that will not be pruned.\n\n## Expected Behavior\n\nIn --hard mode, the deletion should be permanent without creating tombstones, since the user explicitly requested bypassing sync safety.\n\n## Workaround\n\nManually delete from database: sqlite3 .beads/beads.db 'DELETE FROM issues WHERE status=tombstone'\n\n## Fix Options\n\n1. In --hard mode, use a different delete path that does not create tombstones\n2. After deleting, immediately prune the just-created tombstones regardless of age\n3. Pass a skip_tombstone flag to the delete operation\n\nOption 1 is cleanest - --hard should mean permanent delete without tombstone.","status":"tombstone","priority":1,"issue_type":"bug","created_at":"2025-12-16T01:33:36.580657-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-4qfb","title":"Improve bd doctor output formatting for better readability","description":"The current bd doctor output is a wall of text that's hard to process. Consider improvements like:\n- Grouping related checks into collapsible sections\n- Using color/bold for section headers\n- Showing only failures/warnings by default with --verbose for full output\n- Better visual hierarchy between major sections\n- Summary line at top (e.g., '24 checks passed, 0 warnings, 0 errors')","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-13T09:29:27.557578+11:00","updated_at":"2025-12-13T09:29:27.557578+11:00"} {"id":"bd-4ri","title":"Fix TestFallbackToDirectModeEnablesFlush deadlock causing 10min test timeout","description":"## Problem\n\nTestFallbackToDirectModeEnablesFlush in direct_mode_test.go deadlocks for 9m59s before timing out, causing the entire test suite to take 10+ minutes instead of \u003c10 seconds.\n\n## Root Cause\n\nDatabase lock contention between test cleanup and flushToJSONL():\n- Test cleanup (line 36) tries to close DB via defer\n- flushToJSONL() (line 132) is still accessing DB\n- Results in deadlock: database/sql.(*DB).Close() waits for mutex while GetJSONLFileHash() holds it\n\n## Stack Trace Evidence\n\n```\ngoroutine 512 [sync.Mutex.Lock, 9 minutes]:\ndatabase/sql.(*DB).Close(0x14000643790)\n .../database/sql/sql.go:927 +0x84\ngithub.com/steveyegge/beads/cmd/bd.TestFallbackToDirectModeEnablesFlush.func1()\n .../direct_mode_test.go:36 +0xf4\n\nWhile goroutine running flushToJSONL() holds DB connection via GetJSONLFileHash()\n```\n\n## Impact\n\n- Test suite: 10+ minutes → should be \u003c10 seconds\n- ALL other tests pass in ~4 seconds\n- This ONE test accounts for 99.9% of test runtime\n\n## Related\n\nThis is the EXACT same issue documented in MAIN_TEST_REFACTOR_NOTES.md for why main_test.go refactoring was deferred - global state manipulation + DB cleanup = deadlock.\n\n## Fix Approaches\n\n1. **Add proper cleanup sequencing** - stop flush goroutines BEFORE closing DB\n2. **Use test-specific DB lifecycle** - ensure flush completes before cleanup\n3. **Mock the flush mechanism** - avoid real DB for testing this code path \n4. **Add explicit timeout handling** - fail fast with clear error instead of hanging\n\n## Files\n\n- cmd/bd/direct_mode_test.go:36-132\n- cmd/bd/autoflush.go:353 (validateJSONLIntegrity)\n- cmd/bd/autoflush.go:508 (flushToJSONLWithState)\n\n## Acceptance\n\n- Test passes without timeout\n- Test suite completes in \u003c10 seconds\n- No deadlock between cleanup and flush operations","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-21T20:09:00.794372-05:00","updated_at":"2025-12-17T23:13:40.533279-08:00","closed_at":"2025-12-17T17:25:07.626617-08:00"} -{"id":"bd-4sfl","title":"Merge: bd-14ie","description":"branch: polecat/toast\ntarget: main\nsource_issue: bd-14ie\nrig: beads","status":"open","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:23:37.360782-08:00","updated_at":"2025-12-19T23:23:37.360782-08:00"} +{"id":"bd-4sfl","title":"Merge: bd-14ie","description":"branch: polecat/toast\ntarget: main\nsource_issue: bd-14ie\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:23:37.360782-08:00","updated_at":"2025-12-20T23:17:26.997276-08:00","closed_at":"2025-12-20T23:17:26.997276-08:00","close_reason":"Branches nuked, MRs obsolete"} {"id":"bd-4uoc","title":"Code Review Followup Summary: PR #481 + PR #551","description":"## Merged PRs Summary\n\n### PR #551: Persist close_reason to issues table\n- āœ… Merged successfully\n- āœ… Bug fix: close_reason now persisted in database column (not just events table)\n- āœ… Comprehensive test coverage added\n- āœ… Handles reopen case (clearing close_reason)\n\n**Followup Issues Filed:**\n- bd-lxzx: Document close_reason in JSONL export format\n- bd-077e: Update CLI documentation for close_reason field\n\n---\n\n### PR #481: Context Engineering Optimizations (80-90% context reduction)\n- āœ… Merged successfully \n- āœ… Lazy tool discovery: discover_tools() + get_tool_info()\n- āœ… Minimal issue models: IssueMinimal (~80% smaller than full Issue)\n- āœ… Result compaction: Auto-compacts results \u003e20 items\n- āœ… All 28 tests passing\n- āš ļø Breaking change: ready() and list() return type changed\n\n**Followup Issues Filed:**\n- bd-b318: Add integration tests for CompactedResult\n- bd-4u2b: Make compaction settings configurable (THRESHOLD, PREVIEW_COUNT)\n- bd-2kf8: Document CompactedResult response format in CONTEXT_ENGINEERING.md\n- bd-pdr2: Document backwards compatibility considerations\n\n---\n\n## Overall Assessment\n\nBoth PRs are production-ready with solid implementations. All critical functionality works and tests pass. Followup issues focus on:\n1. Documentation improvements (5 issues)\n2. Integration test coverage (1 issue)\n3. Configuration flexibility (1 issue)\n4. Backwards compatibility guidance (1 issue)\n\nNo critical bugs or design issues found.\n\n## Review Completed By\nCode review process completed. Issues auto-created for tracking improvements.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-14T14:25:59.214886-08:00","updated_at":"2025-12-14T14:25:59.214886-08:00","dependencies":[{"issue_id":"bd-4uoc","depends_on_id":"bd-otf4","type":"discovered-from","created_at":"2025-12-14T14:25:59.216884-08:00","created_by":"stevey","metadata":"{}"},{"issue_id":"bd-4uoc","depends_on_id":"bd-z86n","type":"discovered-from","created_at":"2025-12-14T14:25:59.217296-08:00","created_by":"stevey","metadata":"{}"}]} {"id":"bd-4y4g","title":"Bump version in all files","description":"Run ./scripts/bump-version.sh {{version}} to update 10 version files. Then run with --commit after info.go is updated.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T22:43:01.859728-08:00","updated_at":"2025-12-18T22:46:24.537336-08:00","closed_at":"2025-12-18T22:46:24.537336-08:00","dependencies":[{"issue_id":"bd-4y4g","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:43:16.623724-08:00","created_by":"daemon"},{"issue_id":"bd-4y4g","depends_on_id":"bd-8v2","type":"blocks","created_at":"2025-12-18T22:43:20.823329-08:00","created_by":"daemon"}]} {"id":"bd-56x","title":"Review PR #514: fix plugin install docs","description":"Review and merge PR #514 from aspiers. This PR fixes incorrect docs for installing Claude Code plugin from source in docs/PLUGIN.md. Clarifies shell vs Claude Code commands and fixes the . vs ./beads argument issue. URL: https://github.com/anthropics/beads/pull/514","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:15:16.865354+11:00","updated_at":"2025-12-13T07:07:19.729213-08:00","closed_at":"2025-12-13T07:07:19.729213-08:00"} {"id":"bd-581b80b3","title":"bd find-duplicates - AI-powered duplicate detection","description":"Find semantically duplicate issues.\n\nApproaches:\n1. Mechanical: Exact title/description matching\n2. Embeddings: Cosine similarity (cheap, scalable)\n3. AI: LLM-based semantic comparison (expensive, accurate)\n\nUses embeddings by default for \u003e100 issues.\n\nFiles: cmd/bd/find_duplicates.go (new)","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T20:49:49.126801-07:00","updated_at":"2025-12-17T22:58:34.563511-08:00","closed_at":"2025-12-17T22:58:34.563511-08:00","close_reason":"Closed"} -{"id":"bd-589x","title":"HANDOFF: Version 0.30.7 release in progress","description":"## Context\nDoing a 0.30.7 patch release with bug fixes.\n\n## What's done\n- Fixed #657: bd graph nil pointer crash (graph.go:102)\n- Fixed #652: Windows npm installer file lock (postinstall.js)\n- Updated CHANGELOG.md and info.go\n- Pushed to main, CI running (run 20390861825)\n- Created version-bump molecule template (bd-6s61) and instantiated for 0.30.7 (bd-8pyn)\n\n## In progress\nMolecule bd-8pyn has 3 remaining tasks:\n - bd-dxo7: Wait for CI to pass\n - bd-7l70: Verify release artifacts \n - bd-5c91: Update local installation\n\n## Check CI\n gh run list --repo steveyegge/beads --limit 1\n gh run view 20390861825 --repo steveyegge/beads\n\n## New feature filed\nbd-n777: Timer beads for scheduled agent callbacks\nDesign for Deacon-managed timers that can interrupt agents via tmux\n\n## Resume commands\n bd --no-daemon show bd-8pyn\n gh run list --repo steveyegge/beads --limit 1","status":"closed","priority":2,"issue_type":"message","assignee":"beads/dave","created_at":"2025-12-19T23:06:14.902334-08:00","updated_at":"2025-12-20T00:25:59.596546-08:00","closed_at":"2025-12-20T00:25:59.596546-08:00","close_reason":"Handoff completed - v0.30.7 released","sender":"Steve Yegge","ephemeral":true} +{"id":"bd-589x","title":"HANDOFF: Version 0.30.7 release in progress","description":"## Context\nDoing a 0.30.7 patch release with bug fixes.\n\n## What's done\n- Fixed #657: bd graph nil pointer crash (graph.go:102)\n- Fixed #652: Windows npm installer file lock (postinstall.js)\n- Updated CHANGELOG.md and info.go\n- Pushed to main, CI running (run 20390861825)\n- Created version-bump molecule template (bd-6s61) and instantiated for 0.30.7 (bd-8pyn)\n\n## In progress\nMolecule bd-8pyn has 3 remaining tasks:\n - bd-dxo7: Wait for CI to pass\n - bd-7l70: Verify release artifacts \n - bd-5c91: Update local installation\n\n## Check CI\n gh run list --repo steveyegge/beads --limit 1\n gh run view 20390861825 --repo steveyegge/beads\n\n## New feature filed\nbd-n777: Timer beads for scheduled agent callbacks\nDesign for Deacon-managed timers that can interrupt agents via tmux\n\n## Resume commands\n bd --no-daemon show bd-8pyn\n gh run list --repo steveyegge/beads --limit 1","status":"closed","priority":2,"issue_type":"message","assignee":"beads/dave","created_at":"2025-12-19T23:06:14.902334-08:00","updated_at":"2025-12-20T00:49:51.927111-08:00","closed_at":"2025-12-20T00:25:59.596546-08:00"} {"id":"bd-5b6e","title":"Add tests for helper functions (GetDirtyIssueHash, GetAllDependencyRecords, export hashes)","description":"Several utility functions have 0% coverage:\n- GetDirtyIssueHash (dirty.go)\n- GetAllDependencyRecords (dependencies.go)\n- GetExportHash, SetExportHash, ClearAllExportHashes (hash.go)\n\nThese are lower priority but should have basic coverage.","status":"open","priority":4,"issue_type":"task","created_at":"2025-11-01T22:40:58.989976-07:00","updated_at":"2025-11-01T22:40:58.989976-07:00"} {"id":"bd-5qim","title":"Optimize GetReadyWork performance - 752ms on 10K database (target: \u003c50ms)","notes":"# Performance Analysis (10K Issue Database)\n\nAnalyzed using CPU profiles from benchmark suite on Apple M2 Pro.\n\n## Operation Performance\n\n| Operation | Time | Allocations | Memory |\n|----------------------------------|---------|-------------|--------|\n| bd ready (GetReadyWork) | ~752ms | 167,466 | 16MB |\n| bd list (SearchIssues no filter) | ~11.6ms | 89,214 | 5.8MB |\n| bd list (SearchIssues filtered) | ~9.2ms | 62,365 | 3.5MB |\n| bd create (CreateIssue) | ~2.6ms | 146 | 8.6KB |\n| bd update (UpdateIssue) | ~0.32ms | 364 | 15KB |\n| bd close (UpdateIssue) | ~0.32ms | 364 | 15KB |\n\n**Target: \u003c50ms for all operations on 10K database**\n\n**Current issue: GetReadyWork is 15x over target (752ms vs 50ms)**\n\n## Root Cause\n\nGetReadyWork (internal/storage/sqlite/ready.go:90-128) uses recursive CTE to propagate blocking:\n- 65x slower than SearchIssues\n- Recalculates entire blocked issue tree on every call\n- Algorithm:\n 1. Find directly blocked issues via 'blocks' dependencies\n 2. Recursively propagate blockage to descendants (max depth: 50)\n 3. Exclude all blocked issues from results\n\n## CPU Profile Analysis\n\n- Database syscalls (pthread_cond_signal, syscall6): ~75%\n- SQLite engine overhead: inherent to recursive CTE\n- Application code (query construction): \u003c1%\n\n**Bottleneck is the recursive CTE query execution, not application code.**\n\n## Optimization Recommendations\n\n### High Impact (Likely to achieve \u003c50ms target)\n\n1. **Cache blocked issue calculation**\n - Add `blocked_issues` table updated on dependency changes\n - Trade write complexity for read speed (ready called \u003e\u003e dependency changes)\n - Eliminates recursive CTE on every read\n\n2. **Add/verify database indexes**\n ```sql\n CREATE INDEX IF NOT EXISTS idx_dependencies_blocked \n ON dependencies(issue_id, type, depends_on_id);\n CREATE INDEX IF NOT EXISTS idx_issues_status \n ON issues(status);\n ```\n\n### Medium Impact\n\n3. **Reduce allocations** (167K allocations for GetReadyWork)\n - Profile `scanIssues()` for object pooling opportunities\n - Reuse slice capacity for repeated calls\n\n### Low Impact (Not recommended)\n- Query optimization for CRUD operations (already \u003c3ms)\n- Connection pooling tuning (not showing in profiles)\n\n## Verification\n\nRun benchmarks to validate optimization:\n```bash\nmake bench-quick\ngo tool pprof -http=:8080 internal/storage/sqlite/bench-cpu-*.prof\n```\n\nProfile files automatically generated in `internal/storage/sqlite/`.","status":"closed","issue_type":"bug","created_at":"2025-11-14T09:02:46.507526-08:00","updated_at":"2025-12-17T23:13:40.534258-08:00","closed_at":"2025-12-17T16:21:37.918868-08:00"} {"id":"bd-6fe4622f","title":"Remove unreachable utility functions","description":"Several small utility functions are unreachable:\n\nFiles to clean:\n1. `internal/storage/sqlite/hash.go` - `computeIssueContentHash` (line 17)\n - Check if entire file can be deleted if only contains this function\n\n2. `internal/config/config.go` - `FileUsed` (line 151)\n - Delete unused config helper\n\n3. `cmd/bd/git_sync_test.go` - `verifyIssueOpen` (line 300)\n - Delete dead test helper\n\n4. `internal/compact/haiku.go` - `HaikuClient.SummarizeTier2` (line 81)\n - Tier 2 summarization not implemented\n - Options: implement feature OR delete method\n\nImpact: Removes 50-100 LOC depending on decisions","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-28T16:20:02.434573-07:00","updated_at":"2025-12-17T22:58:34.563993-08:00","closed_at":"2025-12-17T22:58:34.563993-08:00","close_reason":"Closed"} {"id":"bd-6gd","title":"Remove legacy MCP Agent Mail integration","description":"## Summary\n\nRemove the legacy MCP Agent Mail system that requires an external HTTP server. Keep the native `bd mail` system which stores messages as git-synced issues.\n\n## Background\n\nTwo mail systems exist in the codebase:\n1. **Legacy Agent Mail** (`bd message`) - External server dependency, complex setup\n2. **Native bd mail** (`bd mail`) - Built-in, git-synced, no dependencies\n\nThe legacy system causes confusion and is no longer needed. Gas Town's Town Mail will use the native `bd mail` system.\n\n## Files to Delete\n\n### CLI Command\n- [ ] `cmd/bd/message.go` - The `bd message` command implementation\n\n### MCP Integration\n- [ ] `integrations/beads-mcp/src/beads_mcp/mail.py` - HTTP wrapper for Agent Mail server\n- [ ] `integrations/beads-mcp/src/beads_mcp/mail_tools.py` - MCP tool definitions\n- [ ] `integrations/beads-mcp/tests/test_mail.py` - Tests for legacy mail\n\n### Documentation\n- [ ] `docs/AGENT_MAIL.md`\n- [ ] `docs/AGENT_MAIL_QUICKSTART.md`\n- [ ] `docs/AGENT_MAIL_DEPLOYMENT.md`\n- [ ] `docs/AGENT_MAIL_MULTI_WORKSPACE_SETUP.md`\n- [ ] `docs/adr/002-agent-mail-integration.md`\n\n## Code to Update\n\n- [ ] Remove `message` command registration from `cmd/bd/main.go`\n- [ ] Remove mail tool imports/registration from MCP server `__init__.py` or `server.py`\n- [ ] Check for any other references to Agent Mail in the codebase\n\n## Verification\n\n- [ ] `bd message` command no longer exists\n- [ ] `bd mail` command still works\n- [ ] MCP server starts without errors\n- [ ] Tests pass\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T23:04:04.099935-08:00","updated_at":"2025-12-17T23:13:24.128752-08:00","closed_at":"2025-12-17T23:13:24.128752-08:00","close_reason":"Removed legacy MCP Agent Mail integration. Kept native bd mail system."} {"id":"bd-6pc","title":"Implement bd pin/unpin commands","description":"Add 'bd pin \u003cid\u003e' and 'bd unpin \u003cid\u003e' commands to toggle the pinned status of issues. Should support multiple IDs like other bd commands.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T23:33:28.292937-08:00","updated_at":"2025-12-19T17:43:35.713398-08:00","closed_at":"2025-12-19T00:35:31.612589-08:00","dependencies":[{"issue_id":"bd-6pc","depends_on_id":"bd-0vg","type":"blocks","created_at":"2025-12-18T23:33:56.119852-08:00","created_by":"daemon"},{"issue_id":"bd-6pc","depends_on_id":"bd-7h5","type":"blocks","created_at":"2025-12-18T23:34:07.352848-08:00","created_by":"daemon"}]} {"id":"bd-6rl","title":"Merge3Way public API does not expose TTL parameter","description":"The public Merge3Way() function in merge.go does not allow callers to configure the tombstone TTL. It hard-codes the default via merge3WayWithTTL(). While merge3WayWithTTL() exists, it is unexported (lowercase). This means the CLI and tests cannot configure TTL at merge time. Use cases: testing with different TTL values, per-repository TTL configuration, debugging with short TTL, supporting --ttl flag in bd merge command (mentioned in design doc bd-zvg). Recommendation: Export Merge3WayWithTTL (rename to uppercase). Files: internal/merge/merge.go:77, 292-298","status":"open","priority":3,"issue_type":"feature","created_at":"2025-12-05T16:36:15.756814-08:00","updated_at":"2025-12-05T16:36:15.756814-08:00"} -{"id":"bd-6s61","title":"Version Bump: {{version}}","description":"Release checklist for version {{version}}. This molecule ensures all release steps are completed properly.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-19T22:55:42.487701-08:00","updated_at":"2025-12-20T01:18:47.905306-08:00","closed_at":"2025-12-20T01:18:47.905306-08:00","close_reason":"Version bump complete","labels":["molecule","template"]} +{"id":"bd-6s61","title":"Version Bump: {{version}}","description":"Release checklist for version {{version}}. This molecule ensures all release steps are completed properly.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-19T22:55:42.487701-08:00","updated_at":"2025-12-20T17:59:26.261233-08:00","closed_at":"2025-12-20T01:18:47.905306-08:00","labels":["molecule","template"]} {"id":"bd-6sm6","title":"Improve test coverage for internal/export (37.1% → 60%)","description":"The export package has only 37.1% test coverage. Export functionality needs good coverage to ensure data integrity.\n\nCurrent coverage: 37.1%\nTarget coverage: 60%","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-13T20:43:06.802277-08:00","updated_at":"2025-12-13T21:01:19.08088-08:00"} {"id":"bd-746","title":"Fix resolvePartialID stub in workflow.go","description":"The resolvePartialID function at workflow.go:921-925 is a stub that just returns the ID unchanged. Should use utils.ResolvePartialID for proper partial ID resolution in direct mode (non-daemon).","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-17T22:22:57.586917-08:00","updated_at":"2025-12-17T22:34:07.270168-08:00","closed_at":"2025-12-17T22:34:07.270168-08:00"} {"id":"bd-74w1","title":"Consolidate duplicate path-finding utilities (findJSONLPath, findBeadsDir, findGitRoot)","description":"Code health review found these functions defined in multiple places:\n\n- findJSONLPath() in autoflush.go:45-73 and doctor/fix/migrate.go\n- findBeadsDir() in autoimport.go:197-239 (with git worktree handling)\n- findGitRoot() in autoimport.go:242-269 (Windows path conversion)\n\nThe beads package has public FindBeadsDir() and FindJSONLPath() APIs that should be used consistently.\n\nImpact: Bug fixes need to be applied in multiple places. Git worktree handling may not be replicated everywhere.\n\nFix: Consolidate all implementations to use the beads package APIs. Remove duplicates.","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-16T18:17:16.694293-08:00","updated_at":"2025-12-16T18:17:16.694293-08:00","dependencies":[{"issue_id":"bd-74w1","depends_on_id":"bd-tggf","type":"blocks","created_at":"2025-12-16T18:19:05.526122-08:00","created_by":"daemon","metadata":"{}"}]} @@ -62,15 +64,17 @@ {"id":"bd-7tuu","title":"Commit and push release","description":"git add -A \u0026\u0026 git commit \u0026\u0026 git push to trigger CI","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:02.053382-08:00","updated_at":"2025-12-20T01:23:52.484043-08:00","closed_at":"2025-12-20T01:23:52.484043-08:00","close_reason":"Superseded by 0.30.7 release - already committed and pushed","dependencies":[{"issue_id":"bd-7tuu","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:15.021087-08:00","created_by":"daemon"},{"issue_id":"bd-7tuu","depends_on_id":"bd-hw3w","type":"blocks","created_at":"2025-12-19T22:56:23.291591-08:00","created_by":"daemon"}]} {"id":"bd-7yg","title":"Git merge driver uses invalid placeholders (%L, %R instead of %A, %B)","description":"## Problem\n\nThe beads git merge driver is configured with invalid Git placeholders:\n\n```\ngit config merge.beads.driver \"bd merge %A %O %L %R\"\n```\n\nGit doesn't recognize `%L` or `%R` as valid merge driver placeholders. The valid placeholders are:\n- `%O` = base (common ancestor)\n- `%A` = current version (ours)\n- `%B` = other version (theirs)\n\n## Impact\n\n- Affects ALL users when they have `.beads/beads.jsonl` merge conflicts\n- Automatic JSONL merge fails with error: \"error reading left file: failed to open file: open 7: no such file or directory\"\n- Users must manually resolve conflicts instead of getting automatic merge\n\n## Root Cause\n\nThe `bd init` command (or wherever the merge driver is configured) is using non-standard placeholders. When Git encounters `%L` and `%R`, it either passes them literally or interprets them incorrectly.\n\n## Fix\n\nUpdate the merge driver configuration to:\n```\ngit config merge.beads.driver \"bd merge %A %O %A %B\"\n```\n\nWhere:\n- 1st `%A` = output file (current file, will be overwritten)\n- `%O` = base (common ancestor)\n- 2nd `%A` = left/current version\n- `%B` = right/other version\n\n## Action Items\n\n1. Fix `bd init` (or equivalent setup command) to use correct placeholders\n2. Add migration/warning for existing users with misconfigured merge driver\n3. Update documentation with correct merge driver setup\n4. Consider adding validation when `bd init` is run","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-21T19:51:55.747608-05:00","updated_at":"2025-12-17T23:13:40.532368-08:00","closed_at":"2025-12-17T17:24:52.678668-08:00"} {"id":"bd-89f89fc0","title":"Remove unreachable RPC methods","description":"Several RPC server and client methods are unreachable and should be removed:\n\nServer methods (internal/rpc/server.go):\n- `Server.GetLastImportTime` (line 2116)\n- `Server.SetLastImportTime` (line 2123)\n- `Server.findJSONLPath` (line 2255)\n\nClient methods (internal/rpc/client.go):\n- `Client.Import` (line 311) - RPC import not used (daemon uses autoimport)\n\nEvidence:\n```bash\ngo run golang.org/x/tools/cmd/deadcode@latest -test ./...\n```\n\nImpact: Removes ~80 LOC of unused RPC code","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-28T16:20:02.432202-07:00","updated_at":"2025-12-17T22:58:34.564401-08:00","closed_at":"2025-12-17T22:58:34.564401-08:00","close_reason":"Closed"} -{"id":"bd-8e0q","title":"Merge: beads-ocs","description":"branch: polecat/valkyrie\ntarget: main\nsource_issue: beads-ocs\nrig: beads","status":"open","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:24:45.281478-08:00","updated_at":"2025-12-19T23:24:45.281478-08:00"} +{"id":"bd-8e0q","title":"Merge: beads-ocs","description":"branch: polecat/valkyrie\ntarget: main\nsource_issue: beads-ocs\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:24:45.281478-08:00","updated_at":"2025-12-20T23:17:26.995706-08:00","closed_at":"2025-12-20T23:17:26.995706-08:00","close_reason":"Branches nuked, MRs obsolete"} {"id":"bd-8fgn","title":"test hash length","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-16T13:49:32.113843-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} {"id":"bd-8g8","title":"Fix G304 potential file inclusion in cmd/bd/tips.go:259","description":"Linting issue: G304: Potential file inclusion via variable (gosec) at cmd/bd/tips.go:259:18. Error: if data, err := os.ReadFile(settingsPath); err == nil {","status":"closed","issue_type":"bug","created_at":"2025-12-07T15:34:57.189730843-07:00","updated_at":"2025-12-17T23:13:40.534569-08:00","closed_at":"2025-12-17T16:46:11.029837-08:00"} {"id":"bd-8hy","title":"Kill running daemons","description":"Stop all bd daemons before release:\n\n```bash\npkill -f 'bd.*daemon' || true\nsleep 1\npgrep -lf 'bd.*daemon' # Should show nothing\n```","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T22:42:58.255478-08:00","updated_at":"2025-12-18T22:43:55.394966-08:00","closed_at":"2025-12-18T22:43:55.394966-08:00","dependencies":[{"issue_id":"bd-8hy","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:43:16.23168-08:00","created_by":"daemon"}]} -{"id":"bd-8pyn","title":"Version Bump: 0.30.7","description":"Release checklist for version 0.30.7. This molecule ensures all release steps are completed properly.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-19T22:56:48.648694-08:00","updated_at":"2025-12-20T00:25:59.529183-08:00","closed_at":"2025-12-20T00:25:59.529183-08:00","close_reason":"Version 0.30.7 released successfully - all steps completed"} +{"id":"bd-8pyn","title":"Version Bump: 0.30.7","description":"Release checklist for version 0.30.7. This molecule ensures all release steps are completed properly.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-19T22:56:48.648694-08:00","updated_at":"2025-12-20T00:49:51.927518-08:00","closed_at":"2025-12-20T00:25:59.529183-08:00"} {"id":"bd-8v2","title":"Add {{version}} to versionChanges in info.go","description":"Add new entry at TOP of versionChanges in cmd/bd/info.go with release notes from CHANGELOG.md. Must do before bump-version.sh --commit.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T22:43:00.482846-08:00","updated_at":"2025-12-18T22:45:21.465817-08:00","closed_at":"2025-12-18T22:45:21.465817-08:00","dependencies":[{"issue_id":"bd-8v2","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:43:16.496649-08:00","created_by":"daemon"},{"issue_id":"bd-8v2","depends_on_id":"bd-kyo","type":"blocks","created_at":"2025-12-18T22:43:20.69619-08:00","created_by":"daemon"}]} {"id":"bd-90v","title":"bd prime: AI context loading and Claude Code integration","description":"Implement `bd prime` command and Claude Code hooks for context recovery. Hooks work with BOTH MCP server and CLI approaches - they solve the context memory problem (keeping bd workflow fresh after compaction) not the tool access problem (MCP vs CLI).","status":"open","priority":2,"issue_type":"epic","created_at":"2025-11-11T23:31:12.119012-08:00","updated_at":"2025-11-12T00:11:07.743189-08:00"} +{"id":"bd-95k8","title":"Pinned field available in beads v0.37.0","description":"Hey max,\n\nHeads up on your mail overhaul work:\n\n1. **Pinned field is available** - beads v0.37.0 (released by dave earlier) includes the pinned field on issues. You'll want to add this to BeadsMessage in types.go.\n\n2. **Database migration** - Check if existing .beads databases need migration to support the pinned field. Run `bd doctor` to see if it flags anything.\n\n3. **Sorting task** - Once you have the pinned field, gt-ngu1 (pinned beads first in mail inbox) needs implementing. Since messages now come from `bd list --type=message`, you'll need to either:\n - Sort in listBeads() after fetching, or\n - Ensure bd list returns pinned items first (may already do this?)\n\nCheck what version of bd you're building against.\n\n-- Mayor","status":"open","priority":2,"issue_type":"message","assignee":"gastown/crew/max","created_at":"2025-12-20T17:51:57.315956-08:00","updated_at":"2025-12-20T17:51:57.315956-08:00","labels":["from:beads-crew-dave","thread:thread-71ac20c7e432"]} {"id":"bd-9cdc","title":"Update docs for import bug fix","description":"Update AGENTS.md, README.md, TROUBLESHOOTING.md with import.orphan_handling config documentation. Document resurrection behavior, tombstones, config modes. Add troubleshooting section for import failures with deleted parents.","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-04T12:32:30.770415-08:00","updated_at":"2025-11-04T12:32:30.770415-08:00"} {"id":"bd-9g1z","title":"Fix or remove TestFindJSONLPathDefault (issue #356)","description":"Code health review found .test-skip permanently skips TestFindJSONLPathDefault.\n\nThe test references issue #356 about wrong JSONL filename expectations (issues.jsonl vs beads.jsonl).\n\nTest file: internal/beads/beads_test.go\n\nThe underlying migration from beads.jsonl to issues.jsonl may be complete, so either:\n1. Fix the test expectations\n2. Remove the test if no longer needed\n3. Document why it remains skipped","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-16T18:17:31.33975-08:00","updated_at":"2025-12-16T18:17:31.33975-08:00","dependencies":[{"issue_id":"bd-9g1z","depends_on_id":"bd-tggf","type":"blocks","created_at":"2025-12-16T18:19:06.169617-08:00","created_by":"daemon","metadata":"{}"}]} +{"id":"bd-9l0h","title":"Run tests and linting","description":"go test -short ./... \u0026\u0026 golangci-lint run ./...","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-20T21:53:19.527602-08:00","updated_at":"2025-12-20T21:55:29.660914-08:00","closed_at":"2025-12-20T21:55:29.660914-08:00","close_reason":"Tests passed, go vet clean","dependencies":[{"issue_id":"bd-9l0h","depends_on_id":"bd-an4s","type":"parent-child","created_at":"2025-12-20T21:53:19.529203-08:00","created_by":"daemon"},{"issue_id":"bd-9l0h","depends_on_id":"bd-gocx","type":"blocks","created_at":"2025-12-20T21:53:29.753682-08:00","created_by":"daemon"}]} {"id":"bd-9usz","title":"Test suite hangs/never finishes","description":"Running 'go test ./... -count=1' hangs indefinitely. The full test suite never completes, making it difficult to verify changes. Need to investigate which tests are hanging and fix or add timeouts.","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-16T21:56:27.80191-08:00","updated_at":"2025-12-16T21:56:27.80191-08:00"} {"id":"bd-a0cp","title":"Consider using types.Status in merge package for type safety","description":"The merge package uses string for status comparison (e.g., result.Status == closed, issue.Status == StatusTombstone). The types package defines Status as a type alias with validation. While the merge package needs its own Issue struct for JSONL flexibility, it could import and use types.Status for constants to get compile-time type checking. Current code: if left == closed || right == closed. Could be: if left == string(types.StatusClosed). This is low priority since string comparison works correctly. Files: internal/merge/merge.go:44, 488, 501-521","status":"open","priority":4,"issue_type":"task","created_at":"2025-12-05T16:37:10.690424-08:00","updated_at":"2025-12-05T16:37:10.690424-08:00"} {"id":"bd-a15d","title":"Add test files for internal/storage","description":"The internal/storage package has no test files at all. This package provides the storage interface abstraction.\n\nCurrent coverage: N/A (no test files)\nTarget: Add basic interface tests","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-13T20:43:11.363017-08:00","updated_at":"2025-12-13T21:01:20.925779-08:00"} @@ -78,7 +82,8 @@ {"id":"bd-abjw","title":"Consider consolidating config.yaml parsing into shared utility","description":"Multiple places parse config.yaml with custom structs:\n\n1. **autoimport.go:148** - `localConfig{SyncBranch}`\n2. **main.go:310** - strings.Contains for no-db (fragile, see bd-r6k2)\n3. **doctor.go:863** - strings.Contains for no-db (fragile, see bd-r6k2)\n4. **internal/config/config.go** - Uses viper (but caches at startup, problematic for tests)\n\nConsider creating a shared utility in `internal/configfile/` or extending the viper config:\n\n```go\n// internal/configfile/yaml.go\ntype YAMLConfig struct {\n SyncBranch string `yaml:\"sync-branch\"`\n NoDb bool `yaml:\"no-db\"`\n IssuePrefix string `yaml:\"issue-prefix\"`\n Author string `yaml:\"author\"`\n}\n\nfunc LoadYAML(beadsDir string) (*YAMLConfig, error) {\n // Parse config.yaml with proper YAML library\n}\n```\n\nBenefits:\n- Single source of truth for config.yaml structure\n- Proper YAML parsing everywhere\n- Easier to add new config fields\n\nTrade-off: May add complexity for simple one-off reads.","status":"open","priority":4,"issue_type":"task","created_at":"2025-12-07T02:03:26.067311-08:00","updated_at":"2025-12-07T02:03:26.067311-08:00"} {"id":"bd-adoe","title":"Add --hard flag to bd cleanup to permanently cull tombstones before cutoff date","description":"Currently tombstones persist for 30 days before cleanup prunes them. Need an official way to force-cull tombstones earlier than the default TTL, for scenarios like cleaning house after extended absence where resurrection from old clones is not a concern. Proposed: bd cleanup --hard --older-than N to bypass the 30-day tombstone TTL.","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2025-12-16T01:17:31.064914-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"} {"id":"bd-aec5439f","title":"Update LINTING.md with current baseline","description":"After cleanup, document the remaining acceptable baseline in LINTING.md so we can track regression.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-27T18:53:10.38679-07:00","updated_at":"2025-12-17T22:58:34.564854-08:00","closed_at":"2025-12-17T22:58:34.564854-08:00","close_reason":"Closed"} -{"id":"bd-ao0s","title":"bd graph crashes with --no-daemon on closed issues","description":"The `bd graph` command panics with nil pointer dereference when using `--no-daemon` flag on an issue with closed children.\n\n**Reproduction:**\n```bash\nbd graph bd-qqc --no-daemon\n# panic: runtime error: invalid memory address or nil pointer dereference\n# in main.computeDependencyCounts\n```\n\n**Stack trace:**\n```\npanic: runtime error: invalid memory address or nil pointer dereference\n[signal SIGSEGV: segmentation violation code=0x2 addr=0x20 pc=0x1010bdfb0]\n\ngoroutine 1 [running]:\nmain.computeDependencyCounts(...)\n /Users/stevey/gt/beads/crew/emma/cmd/bd/graph.go:428\nmain.renderGraph(0x1400033bb80, 0x0)\n /Users/stevey/gt/beads/crew/emma/cmd/bd/graph.go:307 +0x300\n```\n\n**Location:** cmd/bd/graph.go:428 - computeDependencyCounts() not handling nil case","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-18T22:57:36.972585-08:00","updated_at":"2025-12-18T22:57:36.972585-08:00"} +{"id":"bd-an4s","title":"Version Bump: 0.32.1","description":"Release checklist for version 0.32.1. Patch release with MCP output control params and pin field fix.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-20T21:53:01.315592-08:00","updated_at":"2025-12-20T21:57:13.909864-08:00","closed_at":"2025-12-20T21:57:13.909864-08:00","close_reason":"Version 0.32.1 released"} +{"id":"bd-ao0s","title":"bd graph crashes with --no-daemon on closed issues","description":"The `bd graph` command panics with nil pointer dereference when using `--no-daemon` flag on an issue with closed children.\n\n**Reproduction:**\n```bash\nbd graph bd-qqc --no-daemon\n# panic: runtime error: invalid memory address or nil pointer dereference\n# in main.computeDependencyCounts\n```\n\n**Stack trace:**\n```\npanic: runtime error: invalid memory address or nil pointer dereference\n[signal SIGSEGV: segmentation violation code=0x2 addr=0x20 pc=0x1010bdfb0]\n\ngoroutine 1 [running]:\nmain.computeDependencyCounts(...)\n /Users/stevey/gt/beads/crew/emma/cmd/bd/graph.go:428\nmain.renderGraph(0x1400033bb80, 0x0)\n /Users/stevey/gt/beads/crew/emma/cmd/bd/graph.go:307 +0x300\n```\n\n**Location:** cmd/bd/graph.go:428 - computeDependencyCounts() not handling nil case","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-18T22:57:36.972585-08:00","updated_at":"2025-12-20T01:13:29.206821-08:00","closed_at":"2025-12-20T01:13:29.206821-08:00","close_reason":"Fixed: pass subgraph instead of nil to renderGraph"} {"id":"bd-au0","title":"Command Set Standardization \u0026 Flag Consistency","description":"Comprehensive improvements to bd command set based on 2025 audit findings.\n\n## Background\nSee docs/command-audit-2025.md for detailed analysis.\n\n## Goals\n1. Standardize flag naming and behavior across all commands\n2. Add missing flags for feature parity\n3. Fix naming confusion\n4. Improve consistency in JSON output\n\n## Success Criteria\n- All mutating commands support --dry-run (no --preview variants)\n- bd update supports label operations\n- bd search has filter parity with bd list\n- Priority flags accept both int and P0-P4 format everywhere\n- JSON output is consistent across all commands","status":"open","priority":2,"issue_type":"epic","created_at":"2025-11-21T21:05:55.672749-05:00","updated_at":"2025-11-21T21:05:55.672749-05:00"} {"id":"bd-au0.10","title":"Add global verbosity flags (--verbose, --quiet)","description":"Add consistent verbosity controls across all commands.\n\n**Current state:**\n- bd init has --quiet flag\n- No other commands have verbosity controls\n- Debug output controlled by BD_VERBOSE env var\n\n**Proposal:**\nAdd persistent flags:\n- --verbose / -v: Enable debug output\n- --quiet / -q: Suppress non-essential output\n\n**Implementation:**\n- Add to rootCmd.PersistentFlags()\n- Replace BD_VERBOSE checks with flag checks\n- Standardize output levels:\n * Quiet: Errors only\n * Normal: Errors + success messages\n * Verbose: Errors + success + debug info\n\n**Files to modify:**\n- cmd/bd/main.go (add flags)\n- internal/debug/debug.go (respect flags)\n- Update all commands to respect quiet mode\n\n**Testing:**\n- Verify --verbose shows debug output\n- Verify --quiet suppresses normal output\n- Ensure errors always show regardless of mode","status":"open","priority":3,"issue_type":"task","created_at":"2025-11-21T21:08:21.600209-05:00","updated_at":"2025-11-21T21:08:21.600209-05:00","dependencies":[{"issue_id":"bd-au0.10","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:08:21.602557-05:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-au0.5","title":"Add date and priority filters to bd search","description":"Add filter parity with bd list for consistent querying.\n\n**Missing filters to add:**\n- --priority, --priority-min, --priority-max\n- --created-after, --created-before\n- --updated-after, --updated-before\n- --closed-after, --closed-before\n- --empty-description, --no-assignee, --no-labels\n- --desc-contains, --notes-contains\n\n**Files to modify:**\n- cmd/bd/search.go\n- internal/rpc/protocol.go (SearchArgs)\n- internal/storage/storage.go (Search method)\n\n**Testing:**\n- All filter combinations\n- Date format parsing\n- Daemon and direct mode","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-21T21:07:05.496726-05:00","updated_at":"2025-11-21T21:07:05.496726-05:00","dependencies":[{"issue_id":"bd-au0.5","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:07:05.497762-05:00","created_by":"daemon","metadata":"{}"}]} @@ -103,7 +108,7 @@ {"id":"bd-bw6","title":"Fix G104 errors unhandled in internal/storage/sqlite/queries.go:1181","description":"Linting issue: G104: Errors unhandled (gosec) at internal/storage/sqlite/queries.go:1181:4. Error: rows.Close()","status":"closed","issue_type":"bug","created_at":"2025-12-07T15:35:09.008444133-07:00","updated_at":"2025-12-17T23:13:40.536627-08:00","closed_at":"2025-12-17T16:46:11.029355-08:00"} {"id":"bd-bwk2","title":"Centralize error handling patterns in storage layer","description":"80+ instances of inconsistent error handling across sqlite.go with mix of %w, %v, and no wrapping.\n\nLocation: internal/storage/sqlite/sqlite.go (throughout)\n\nProblem:\n- Some use fmt.Errorf(\"op failed: %w\", err) - correct wrapping\n- Some use fmt.Errorf(\"op failed: %v\", err) - loses error chain\n- Some return err directly - no context\n- Hard to debug production issues\n- Can't distinguish error types\n\nSolution: Create internal/storage/sqlite/errors.go:\n- Define sentinel errors (ErrNotFound, ErrInvalidID, etc.)\n- Create wrapDBError(op string, err error) helper\n- Convert sql.ErrNoRows to ErrNotFound\n- Always wrap with operation context\n\nImpact: Lost error context; inconsistent messages; hard to debug\n\nEffort: 5-7 hours","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-16T14:51:54.974909-08:00","updated_at":"2025-11-16T14:51:54.974909-08:00"} {"id":"bd-bxha","title":"Default to YES for git hooks and merge driver installation","description":"Currently bd init prompts user to install git hooks and merge driver, but setup is incomplete if user declines. Change to install by default unless --skip-hooks or --skip-merge-driver flags are passed. Better safe defaults. If installation fails, warn user and suggest bd doctor --fix.","status":"open","priority":1,"issue_type":"feature","created_at":"2025-11-21T23:16:10.172238-08:00","updated_at":"2025-11-21T23:16:28.369137-08:00","dependencies":[{"issue_id":"bd-bxha","depends_on_id":"bd-tbz3","type":"parent-child","created_at":"2025-11-21T23:16:10.173034-08:00","created_by":"daemon","metadata":"{}"}]} -{"id":"bd-by0d","title":"Work on beads-ldv: Fix bd graph crashes with nil pointer ...","description":"Work on beads-ldv: Fix bd graph crashes with nil pointer dereference (GH#657). Fix nil pointer in computeDependencyCounts at graph.go:428. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:55:27.829359-08:00","updated_at":"2025-12-20T00:26:12.607018-08:00","closed_at":"2025-12-19T23:20:49.038441-08:00"} +{"id":"bd-by0d","title":"Work on beads-ldv: Fix bd graph crashes with nil pointer ...","description":"Work on beads-ldv: Fix bd graph crashes with nil pointer dereference (GH#657). Fix nil pointer in computeDependencyCounts at graph.go:428. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:55:27.829359-08:00","updated_at":"2025-12-19T23:28:32.428314-08:00","closed_at":"2025-12-19T23:20:49.038441-08:00","close_reason":"Fixed nil pointer in computeDependencyCounts by passing subgraph instead of nil"} {"id":"bd-c2xs","title":"Exclude pinned issues from bd blocked","description":"Update bd blocked to exclude pinned issues. Pinned issues are context markers and should not appear in the blocked work list.","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-18T23:33:44.684242-08:00","updated_at":"2025-12-18T23:33:44.684242-08:00","dependencies":[{"issue_id":"bd-c2xs","depends_on_id":"bd-0vg","type":"blocks","created_at":"2025-12-18T23:33:56.521323-08:00","created_by":"daemon"},{"issue_id":"bd-c2xs","depends_on_id":"bd-7h5","type":"blocks","created_at":"2025-12-18T23:34:07.736681-08:00","created_by":"daemon"}]} {"id":"bd-c3u","title":"Review PR #512: clarify bd ready docs","description":"Review and merge PR #512 from aspiers. This PR clarifies what bd ready does after git pull in README.md. Simple 1-line change. URL: https://github.com/anthropics/beads/pull/512","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:15:13.405161+11:00","updated_at":"2025-12-13T07:07:29.641265-08:00","closed_at":"2025-12-13T07:07:29.641265-08:00"} {"id":"bd-c83r","title":"Prevent multiple daemons from running on the same repo","description":"Multiple bd daemons running on the same repo clone causes race conditions and data corruption risks.\n\n**Problem:**\n- Nothing prevents spawning multiple daemons for the same repository\n- Multiple daemons watching the same files can conflict during sync operations\n- Observed: 4 daemons running simultaneously caused sync race condition\n\n**Solution:**\nImplement daemon singleton enforcement per repo:\n1. Use a lock file (e.g., .beads/.daemon.lock) with PID\n2. On daemon start, check if lock exists and process is alive\n3. If stale lock (dead PID), clean up and acquire lock\n4. If active daemon exists, either:\n - Exit with message 'daemon already running (PID xxx)'\n - Or offer --replace flag to kill existing and take over\n5. Release lock on graceful shutdown\n\n**Edge cases to handle:**\n- Daemon crashes without releasing lock (stale PID detection)\n- Multiple repos in same directory tree (each repo gets own lock)\n- Race between two daemons starting simultaneously (atomic lock acquisition)","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-13T06:37:23.377131-08:00","updated_at":"2025-12-16T01:14:49.50347-08:00","closed_at":"2025-12-14T17:34:14.990077-08:00"} @@ -119,11 +124,12 @@ {"id":"bd-cbed9619.3","title":"Implement global N-way collision resolution algorithm","description":"**Summary:** Replaced pairwise collision resolution with a global N-way algorithm that deterministically resolves issue ID conflicts across multiple clones. The new approach groups collisions, deduplicates by content hash, and assigns sequential IDs to ensure consistent synchronization.\n\n**Key Decisions:**\n- Use content hash for global, stable sorting\n- Group collisions by base ID\n- Assign sequential IDs based on sorted unique versions\n- Eliminate order-dependent remapping logic\n\n**Resolution:** Implemented ResolveNWayCollisions function that guarantees deterministic issue ID assignment across multiple synchronization scenarios, solving the core challenge of maintaining consistency in distributed systems with potential conflicts.","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-10-28T18:37:42.85616-07:00","updated_at":"2025-12-17T23:18:29.112335-08:00","close_reason":"Closed","dependencies":[{"issue_id":"bd-cbed9619.3","depends_on_id":"bd-cbed9619.5","type":"blocks","created_at":"2025-10-28T18:39:28.30886-07:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-cbed9619.3","depends_on_id":"bd-cbed9619.4","type":"blocks","created_at":"2025-10-28T18:39:28.336312-07:00","created_by":"daemon","metadata":"{}"}],"deleted_at":"2025-12-17T23:18:29.112335-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} {"id":"bd-cbed9619.4","title":"Make DetectCollisions read-only (separate detection from modification)","description":"**Summary:** The project restructured the collision detection process in the database to separate read-only detection from state modification, eliminating race conditions and improving system reliability. This was achieved by introducing a two-phase approach: first detecting potential collisions, then applying resolution separately.\n\n**Key Decisions:**\n- Create read-only DetectCollisions method\n- Add RenameDetail to track potential issue renames\n- Implement atomic ApplyCollisionResolution function\n- Separate detection logic from database modification\n\n**Resolution:** The refactoring creates a more robust, composable collision handling mechanism that prevents partial failures and maintains database consistency during complex issue import scenarios.","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-10-28T18:37:09.652326-07:00","updated_at":"2025-12-17T23:18:29.112637-08:00","close_reason":"Closed","dependencies":[{"issue_id":"bd-cbed9619.4","depends_on_id":"bd-cbed9619.5","type":"blocks","created_at":"2025-10-28T18:39:28.285653-07:00","created_by":"daemon","metadata":"{}"}],"deleted_at":"2025-12-17T23:18:29.112637-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} {"id":"bd-cbed9619.5","title":"Add content-addressable identity to Issue type","description":"**Summary:** Added content-addressable identity to Issue type by implementing a ContentHash field that generates a unique SHA256 fingerprint based on semantic issue content. This resolves issue identification challenges when multiple system instances create issues with identical IDs but different contents.\n\n**Key Decisions:**\n- Use SHA256 for content hashing\n- Hash excludes ID and timestamps\n- Compute hash automatically at creation/import time\n- Add database column for hash storage\n\n**Resolution:** Successfully implemented a deterministic content hashing mechanism that enables reliable issue identification across distributed systems, improving data integrity and collision detection.","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-10-28T18:36:44.914967-07:00","updated_at":"2025-12-17T23:18:29.112933-08:00","close_reason":"Closed","deleted_at":"2025-12-17T23:18:29.112933-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} -{"id":"bd-cils","title":"Work on beads-2nh: Fix gt spawn --issue to find issues in...","description":"Work on beads-2nh: Fix gt spawn --issue to find issues in rig's beads database. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:55:47.573854-08:00","updated_at":"2025-12-20T00:26:12.608015-08:00","closed_at":"2025-12-19T23:28:28.605343-08:00"} +{"id":"bd-cils","title":"Work on beads-2nh: Fix gt spawn --issue to find issues in...","description":"Work on beads-2nh: Fix gt spawn --issue to find issues in rig's beads database. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:55:47.573854-08:00","updated_at":"2025-12-20T00:49:51.927884-08:00","closed_at":"2025-12-19T23:28:28.605343-08:00","close_reason":"Completed - fixed spawn beads path in gastown"} {"id":"bd-crgr","title":"GH#517: Claude sets priority wrong on new install","description":"Claude uses 'medium/high/low' for priority instead of P0-P4. Update bd prime/onboard output to be clearer about priority syntax. See GitHub issue #517.","status":"tombstone","priority":2,"issue_type":"bug","created_at":"2025-12-16T01:03:34.803084-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} -{"id":"bd-czss","title":"Update CHANGELOG.md with release notes","description":"Add meaningful release notes to CHANGELOG.md describing what changed in {{version}}","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:55:59.909641-08:00","updated_at":"2025-12-20T01:23:51.407302-08:00","closed_at":"2025-12-20T01:23:51.407302-08:00","close_reason":"Superseded by 0.30.7 release - CHANGELOG already updated","dependencies":[{"issue_id":"bd-czss","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:14.862724-08:00","created_by":"daemon"},{"issue_id":"bd-czss","depends_on_id":"bd-qkw9","type":"blocks","created_at":"2025-12-19T22:56:23.145894-08:00","created_by":"daemon"}]} +{"id":"bd-czss","title":"Update CHANGELOG.md with release notes","description":"Add meaningful release notes to CHANGELOG.md describing what changed in {{version}}","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:55:59.909641-08:00","updated_at":"2025-12-20T17:59:26.262153-08:00","closed_at":"2025-12-20T01:23:51.407302-08:00","dependencies":[{"issue_id":"bd-czss","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:14.862724-08:00","created_by":"daemon"},{"issue_id":"bd-czss","depends_on_id":"bd-qkw9","type":"blocks","created_at":"2025-12-19T22:56:23.145894-08:00","created_by":"daemon"}]} {"id":"bd-d148","title":"GH#483: Pre-commit hook fails unnecessarily when .beads removed","description":"Pre-commit hook fails on bd sync when .beads directory exists but user is on branch without beads. Should exit gracefully. See GitHub issue #483.","status":"tombstone","priority":2,"issue_type":"bug","created_at":"2025-12-16T01:03:40.049785-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-d3e5","title":"Test issue 2","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-14T11:21:13.878680387-07:00","updated_at":"2025-12-14T11:21:13.878680387-07:00","closed_at":"2025-12-14T00:32:13.890274-08:00"} +{"id":"bd-d4jl","title":"Commit and push release","description":"git add -A \u0026\u0026 git commit -m 'chore: bump version to 0.32.1' \u0026\u0026 git push","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-20T21:53:21.928138-08:00","updated_at":"2025-12-20T21:57:12.81943-08:00","closed_at":"2025-12-20T21:57:12.81943-08:00","close_reason":"Committed and pushed to origin","dependencies":[{"issue_id":"bd-d4jl","depends_on_id":"bd-an4s","type":"parent-child","created_at":"2025-12-20T21:53:21.930015-08:00","created_by":"daemon"},{"issue_id":"bd-d4jl","depends_on_id":"bd-tj00","type":"blocks","created_at":"2025-12-20T21:53:29.884457-08:00","created_by":"daemon"}]} {"id":"bd-d73u","title":"Re: Thread Test 2","description":"Great! Thread is working well.","status":"tombstone","priority":2,"issue_type":"message","created_at":"2025-12-16T18:21:46.655093-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","dependencies":[{"issue_id":"bd-d73u","depends_on_id":"bd-vpan","type":"replies-to","created_at":"2025-12-18T13:45:31.137191-08:00","created_by":"migration"}],"deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"message"} {"id":"bd-de6","title":"Fix FindBeadsDir to prioritize main repo .beads for worktrees","description":"The FindBeadsDir function should prioritize finding .beads in the main repository root when accessed from a worktree, rather than finding worktree-local .beads directories. This ensures proper sharing of the database across all worktrees.","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-07T16:48:36.883117467-07:00","updated_at":"2025-12-07T16:48:36.883117467-07:00"} {"id":"bd-dhza","title":"Reduce global state in cmd/bd/main.go (25+ variables)","description":"Code health review found main.go has 25+ global variables (lines 57-112):\n\n- dbPath, actor, store, jsonOutput, daemonClient, noDaemon\n- rootCtx, rootCancel, autoFlushEnabled\n- isDirty (marked 'USED BY LEGACY CODE')\n- needsFullExport (marked 'USED BY LEGACY CODE')\n- flushTimer (marked 'DEPRECATED')\n- flushMutex, storeMutex, storeActive\n- flushFailureCount, lastFlushError, flushManager\n- skipFinalFlush, autoImportEnabled\n- versionUpgradeDetected, previousVersion, upgradeAcknowledged\n\nImpact:\n- Hard to test individual commands\n- Race conditions possible\n- State leakage between commands\n\nFix: Move toward dependency injection. Remove deprecated variables. Consider cmd/bd/internal package.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-16T18:17:29.643293-08:00","updated_at":"2025-12-16T18:17:29.643293-08:00","dependencies":[{"issue_id":"bd-dhza","depends_on_id":"bd-tggf","type":"blocks","created_at":"2025-12-16T18:19:06.492112-08:00","created_by":"daemon","metadata":"{}"}]} @@ -135,17 +141,19 @@ {"id":"bd-dyy","title":"Review PR #513: fix hooks install docs","description":"Review and merge PR #513 from aspiers. This PR fixes incorrect docs for how to install git hooks - updates README to use bd hooks install instead of removed install.sh. Simple 1-line change. URL: https://github.com/anthropics/beads/pull/513","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:15:14.838772+11:00","updated_at":"2025-12-13T07:07:19.718544-08:00","closed_at":"2025-12-13T07:07:19.718544-08:00"} {"id":"bd-e1085716","title":"bd validate - Comprehensive health check","description":"Run all validation checks in one command.\n\nChecks:\n- Duplicates\n- Orphaned dependencies\n- Test pollution\n- Git conflicts\n\nSupports --fix-all for auto-repair.\n\nDepends on bd-cbed9619.1, bd-0dcea000, bd-31aab707, bd-9826b69a.\n\nFiles: cmd/bd/validate.go (new)","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T23:05:13.980679-07:00","updated_at":"2025-12-17T22:58:34.562008-08:00","closed_at":"2025-12-17T22:58:34.562008-08:00","close_reason":"Closed"} {"id":"bd-elqd","title":"Systematic bd sync stability investigation","description":"## Context\n\nbd sync has chronic instability issues that have persisted since inception:\n- issues.jsonl is always dirty after push\n- bd sync often creates messes requiring manual cleanup\n- Problems escalating despite accumulated bug fixes\n- Workarounds are getting increasingly draconian\n\n## Goal\n\nSystematically observe and diagnose bd sync failures rather than applying band-aid fixes.\n\n## Approach\n\n1. Start fresh session with latest binary (all fixes applied)\n2. Run bd sync and carefully observe what happens\n3. Document exact sequence of events when things go wrong\n4. File specific issues for each discrete problem identified\n5. Track the root causes, not just symptoms\n\n## Test Environment\n\n- Fresh clone or clean state\n- Latest bd binary with all bug fixes\n- Monitor both local and remote JSONL state\n- Check for timing issues, race conditions, merge conflicts\n\n## Success Criteria\n\n- Identify root causes of sync instability\n- Create actionable issues for each problem\n- Eventually achieve stable bd sync (no manual intervention needed)","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-16T22:57:25.35289-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} -{"id":"bd-eyrh","title":"šŸ¤ HANDOFF: Review remaining beads PRs","description":"## Current State\nJust merged PR #653 (doctor refactor) and added tests to restore coverage.\n\n## Remaining Open PRs to Review\nRun `gh pr list --repo steveyegge/beads` to see current list. As of handoff:\n\n1. #655 - feat: Linear Integration (jblwilliams)\n2. #651 - feat(audit): agent audit trail (dchichkov)\n3. #648 - Stop init creating redundant @AGENTS.md (maphew)\n4. #646 - fix(unix): handle Statfs field types (jordanhubbard)\n5. #645 - feat: /plan-to-beads Claude Code command (petebytes)\n6. #642, #641, #640 - sync branch fixes (cpdata)\n\n## Review Checklist\n- Check CI status with `gh pr checks \u003cnum\u003e --repo steveyegge/beads`\n- Verify no .beads/ data leaking (we have a hook now)\n- Review code quality\n- Merge good ones, request changes on problematic ones\n\n## Notes\n- User wants us to be proactive about merging good PRs\n- Can add tests ourselves if coverage drops","status":"closed","priority":2,"issue_type":"message","created_at":"2025-12-19T17:44:34.149837-08:00","updated_at":"2025-12-19T23:20:44.79446-08:00","closed_at":"2025-12-19T17:53:20.386937-08:00","sender":"Steve Yegge","ephemeral":true} +{"id":"bd-eyrh","title":"šŸ¤ HANDOFF: Review remaining beads PRs","description":"## Current State\nJust merged PR #653 (doctor refactor) and added tests to restore coverage.\n\n## Remaining Open PRs to Review\nRun `gh pr list --repo steveyegge/beads` to see current list. As of handoff:\n\n1. #655 - feat: Linear Integration (jblwilliams)\n2. #651 - feat(audit): agent audit trail (dchichkov)\n3. #648 - Stop init creating redundant @AGENTS.md (maphew)\n4. #646 - fix(unix): handle Statfs field types (jordanhubbard)\n5. #645 - feat: /plan-to-beads Claude Code command (petebytes)\n6. #642, #641, #640 - sync branch fixes (cpdata)\n\n## Review Checklist\n- Check CI status with `gh pr checks \u003cnum\u003e --repo steveyegge/beads`\n- Verify no .beads/ data leaking (we have a hook now)\n- Review code quality\n- Merge good ones, request changes on problematic ones\n\n## Notes\n- User wants us to be proactive about merging good PRs\n- Can add tests ourselves if coverage drops","status":"closed","priority":2,"issue_type":"message","created_at":"2025-12-19T17:44:34.149837-08:00","updated_at":"2025-12-19T23:28:32.428731-08:00","closed_at":"2025-12-19T17:53:20.386937-08:00","sender":"Steve Yegge","ephemeral":true} {"id":"bd-eyto","title":"Time-dependent tests may be flaky near TTL boundary","description":"Several tombstone merge tests use time.Now() to create test data: time.Now().Add(-24 * time.Hour), time.Now().Add(-60 * 24 * time.Hour), etc. While these work reliably in practice (24h vs 30d TTL has large margin), they could theoretically be flaky if: 1) Tests run slowly, 2) System clock changes during test, 3) TTL constants change. Recommendation: Consider using a fixed reference time or time injection for deterministic tests. Lower priority since current margin is large. Files: internal/merge/merge_test.go:1337-1338, 1352-1353, 1548-1549, 1590-1591","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-05T16:37:02.348143-08:00","updated_at":"2025-12-05T16:37:02.348143-08:00"} -{"id":"bd-f3ll","title":"Merge: bd-ot0w","description":"branch: polecat/dementus\ntarget: main\nsource_issue: bd-ot0w\nrig: beads","status":"open","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:20:33.495772-08:00","updated_at":"2025-12-19T23:20:33.495772-08:00"} +{"id":"bd-f3ll","title":"Merge: bd-ot0w","description":"branch: polecat/dementus\ntarget: main\nsource_issue: bd-ot0w\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:20:33.495772-08:00","updated_at":"2025-12-20T23:17:27.000252-08:00","closed_at":"2025-12-20T23:17:27.000252-08:00","close_reason":"Branches nuked, MRs obsolete"} {"id":"bd-f5cc","title":"Thread Test","description":"Testing the thread feature","status":"tombstone","priority":2,"issue_type":"message","created_at":"2025-12-16T18:21:01.244501-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","dependencies":[{"issue_id":"bd-f5cc","depends_on_id":"bd-x36g","type":"supersedes","created_at":"2025-12-18T13:45:31.137191-08:00","created_by":"migration"}],"deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"message"} -{"id":"bd-fgw3","title":"Update local installation","description":"Run install script or brew upgrade to get new version locally: curl -fsSL .../install.sh | bash","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:05.052016-08:00","updated_at":"2025-12-20T00:25:52.805029-08:00","closed_at":"2025-12-20T00:25:52.805029-08:00","close_reason":"Local bd updated to 0.30.7","dependencies":[{"issue_id":"bd-fgw3","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:15.248427-08:00","created_by":"daemon"},{"issue_id":"bd-fgw3","depends_on_id":"bd-si4g","type":"blocks","created_at":"2025-12-19T22:56:23.497325-08:00","created_by":"daemon"}]} +{"id":"bd-fa2h","title":"šŸ¤ HANDOFF: v0.31.0 released, molecules discussion","description":"Session completed 0.31.0 release and had important molecules discussion.\n\n## Completed\n- v0.31.0 released (deferred status, audit trail, directory labels, etc.)\n- Fixed lint issues, hook version markers, codesigning\n- All CI green, artifacts verified\n\n## Filed Issues\n- bd-usro: Rename template instantiate → bd mol bond\n- bd-y8bj: Auto-detect identity for bd mail (P1 bug)\n- gt-975: Molecule execution support for polecats/crew\n- gt-976: Crew lifecycle support in Deacon\n\n## Key Insight\nMolecules are the future - TodoWrite is ephemeral, molecules are persistent institutional memory on the world chain. I tried to use TodoWrite for version bump and missed steps (codesigning, MCP verification). Molecules would have caught this.\n\n## Next Steps\n- bd mol bond implementation is priority\n- Max has gt-976 for crew lifecycle (enables automated refresh mid-molecule)\n\nCheck bd ready and gt-975/976 status.","status":"open","priority":2,"issue_type":"message","assignee":"beads/crew/dave","created_at":"2025-12-20T17:23:09.889562-08:00","updated_at":"2025-12-20T17:23:09.889562-08:00","sender":"Steve Yegge","ephemeral":true} +{"id":"bd-fgw3","title":"Update local installation","description":"Run install script or brew upgrade to get new version locally: curl -fsSL .../install.sh | bash","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:05.052016-08:00","updated_at":"2025-12-20T00:49:51.928221-08:00","closed_at":"2025-12-20T00:25:52.805029-08:00","dependencies":[{"issue_id":"bd-fgw3","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:15.248427-08:00","created_by":"daemon"},{"issue_id":"bd-fgw3","depends_on_id":"bd-si4g","type":"blocks","created_at":"2025-12-19T22:56:23.497325-08:00","created_by":"daemon"}]} {"id":"bd-fi05","title":"bd sync fails with orphaned issues and duplicate ID conflict","description":"After fixing the deleted_at TEXT column scanning bug (commit 18b1eb2), bd sync still fails with two issues:\n\n1. Orphan Detection Warning: 12 orphaned child issues whose parents no longer exist (bd-cb64c226.* and bd-cbed9619.*)\n\n2. Import Failure: UNIQUE constraint failed for bd-360 - this tombstone exists in both DB and JSONL\n\nError: \"Import failed: error creating depth-0 issues: bulk insert issues: failed to insert issue bd-360: sqlite3: constraint failed: UNIQUE constraint failed: issues.id\"\n\nFix options:\n- Delete orphaned child issues with bd delete\n- Resolve bd-360 duplicate (in deletions.jsonl vs tombstone in DB)\n- Reset sync branch: git branch -f beads-sync main \u0026\u0026 git push --force-with-lease origin beads-sync","notes":"Fixed tombstone constraint violation bug. When deleting closed issues, the CHECK constraint (status = 'closed') = (closed_at IS NOT NULL) was violated because CreateTombstone didn't clear closed_at. Fix: set closed_at = NULL in tombstone creation SQL.\n\nThe sync data corruption (orphaned issues in beads-sync branch) requires manual cleanup: reset sync branch with 'git branch -f beads-sync main \u0026\u0026 git push --force-with-lease origin beads-sync'","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-13T07:14:33.831346-08:00","updated_at":"2025-12-13T10:50:48.545465-08:00","closed_at":"2025-12-13T07:30:33.843986-08:00"} {"id":"bd-fom","title":"Remove all deletions.jsonl code except migration","description":"There's deletions manifest code spread across the entire codebase that should have been removed after tombstone migration:\n\nFiles with deletions code (non-migration):\n- internal/deletions/ - entire package\n- cmd/bd/sync.go - 25+ references, auto-compact, sanitize\n- cmd/bd/delete.go - dual-writes to deletions.jsonl\n- internal/importer/importer.go - checks deletions manifest\n- internal/syncbranch/worktree.go - merges deletions.jsonl\n- cmd/bd/doctor/fix/sync.go - cleanupDeletionsManifest\n- cmd/bd/doctor/fix/deletions.go - HydrateDeletionsManifest\n- cmd/bd/integrity.go - checks deletions for data loss\n- cmd/bd/deleted.go - entire command\n- cmd/bd/compact.go - pruneDeletionsManifest\n- cmd/bd/doctor.go - checkDeletionsManifest\n- Plus many more\n\nAction: Aggressively remove all non-migration deletions code. Tombstones are the only deletion mechanism now.","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-16T13:29:04.960863-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} {"id":"bd-fu83","title":"Fix daemon/direct mode inconsistency in relate and duplicate commands","description":"The relate.go and duplicate.go commands have inconsistent daemon/direct mode handling:\n\nWhen daemonClient is connected, they resolve IDs via RPC but then perform updates directly via store.UpdateIssue(), bypassing the daemon.\n\nAffected locations:\n- relate.go:125-139 (runRelate update)\n- relate.go:235-246 (runUnrelate update) \n- duplicate.go:120 (runDuplicate update)\n- duplicate.go:207 (runSupersede update)\n\nShould either use RPC for updates when daemon is running, or document why direct access is intentional.","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-16T20:52:54.164189-08:00","updated_at":"2025-12-16T20:52:54.164189-08:00"} {"id":"bd-fx7v","title":"Improve test coverage for cmd/bd/doctor/fix (23.9% → 50%)","description":"The doctor/fix package has only 23.9% test coverage. The doctor fix functionality is important for troubleshooting.\n\nCurrent coverage: 23.9%\nTarget coverage: 50%","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-13T20:43:05.67127-08:00","updated_at":"2025-12-13T21:01:20.839298-08:00"} {"id":"bd-g9eu","title":"Investigate TestRoutingIntegration failure","description":"TestRoutingIntegration/maintainer_with_SSH_remote failed during pre-commit check with \"expected role maintainer, got contributor\".\nThis occurred while running `go test -short ./...` on darwin/arm64.\nThe failure appears unrelated to storage/sqlite changes.\nNeed to investigate if this is a flaky test or environmental issue.","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-20T15:55:19.337094-08:00","updated_at":"2025-11-20T15:55:19.337094-08:00"} {"id":"bd-gjla","title":"Test Thread","description":"Initial message for threading test","status":"tombstone","priority":2,"issue_type":"message","created_at":"2025-12-16T18:19:51.704324-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","dependencies":[{"issue_id":"bd-gjla","depends_on_id":"bd-f5cc","type":"duplicates","created_at":"2025-12-18T13:45:31.137191-08:00","created_by":"migration"}],"deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"message"} +{"id":"bd-gocx","title":"Run bump-version.sh 0.32.1","description":"Execute ./scripts/bump-version.sh 0.32.1 to update all version references","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-20T21:53:18.470174-08:00","updated_at":"2025-12-20T21:54:54.500836-08:00","closed_at":"2025-12-20T21:54:54.500836-08:00","close_reason":"Version bumped to 0.32.1","dependencies":[{"issue_id":"bd-gocx","depends_on_id":"bd-an4s","type":"parent-child","created_at":"2025-12-20T21:53:18.471793-08:00","created_by":"daemon"},{"issue_id":"bd-gocx","depends_on_id":"bd-x3j8","type":"blocks","created_at":"2025-12-20T21:53:29.688436-08:00","created_by":"daemon"}]} {"id":"bd-gxq","title":"Simplify bd onboard to minimal AGENTS.md snippet pointing to bd prime","description":"## Context\nGH#604 raised concerns about bd onboard bloating AGENTS.md with ~100+ lines of static instructions that:\n- Load every session whether beads is being used or not\n- Get stale when bd upgrades\n- Waste tokens\n\n## Solution\nSimplify `bd onboard` to output a minimal snippet (~2 lines) that points to `bd prime`:\n\n```markdown\n## Issue Tracking\nThis project uses beads (`bd`) for issue tracking.\nRun `bd prime` for workflow context, or hooks auto-inject it.\n```\n\n## Rationale\n- `bd prime` is dynamic, concise (~80 lines), and always matches installed bd version\n- Hooks already auto-inject `bd prime` at session start when .beads/ detected\n- AGENTS.md only needs to mention beads exists, not contain full instructions\n\n## Implementation\n1. Update `cmd/bd/onboard.go` to output minimal snippet\n2. Keep `--output` flag for BD_GUIDE.md generation (may still be useful)\n3. Update help text to explain the new approach","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T11:42:38.604891-08:00","updated_at":"2025-12-18T11:47:28.020419-08:00","closed_at":"2025-12-18T11:47:28.020419-08:00","close_reason":"Implemented: bd onboard now outputs minimal snippet pointing to bd prime"} {"id":"bd-haze","title":"Fix beads-9yc: pinned column missing from schema. gt mail...","description":"Fix beads-9yc: pinned column missing from schema. gt mail send fails because some beads DBs lack the pinned column. Add migration to ensure it exists.","status":"in_progress","priority":2,"issue_type":"task","created_at":"2025-12-19T15:05:33.394801-08:00","updated_at":"2025-12-19T15:05:33.524491-08:00"} {"id":"bd-hkr6","title":"GH#518: Document bd setup command","description":"bd setup is undiscoverable. Add to README/docs. Currently only findable by grepping source. See GitHub issue #518.","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-16T01:03:54.664668-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} @@ -153,10 +161,11 @@ {"id":"bd-hlsw.3","title":"Auto-recovery mode (bd sync --auto-recover)","description":"Add bd sync --auto-recover flag that: detects problematic sync state, backs up .beads/issues.db with timestamp, rebuilds DB from JSONL atomically, verifies consistency, reports what was fixed. Provides safety valve when sync integrity fails.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-14T10:40:20.599836875-07:00","updated_at":"2025-12-14T10:40:20.599836875-07:00","dependencies":[{"issue_id":"bd-hlsw.3","depends_on_id":"bd-hlsw","type":"parent-child","created_at":"2025-12-14T10:40:20.600435888-07:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-hlsw.4","title":"Sync branch integrity guards","description":"Track sync branch parent commit. If sync branch was force-pushed, warn user and require confirmation before proceeding. Add option to reset to remote if user accepts rebase. Prevents silent corruption from forced pushes.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-14T10:40:20.645402352-07:00","updated_at":"2025-12-14T10:40:20.645402352-07:00","dependencies":[{"issue_id":"bd-hlsw.4","depends_on_id":"bd-hlsw","type":"parent-child","created_at":"2025-12-14T10:40:20.646425761-07:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-hnkg","title":"GH#540: Add silent quick-capture mode (bd q)","description":"Add bd q alias for quick capture that outputs only issue ID. Useful for piping/scripting. See GitHub issue #540.","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2025-12-16T01:03:38.260135-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"} -{"id":"bd-hvng","title":"Merge: bd-w193","description":"branch: polecat/nux\ntarget: main\nsource_issue: bd-w193\nrig: beads","status":"open","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:23:47.496139-08:00","updated_at":"2025-12-19T23:23:47.496139-08:00"} -{"id":"bd-hw3w","title":"Update info.go versionChanges","description":"Add entry to versionChanges in cmd/bd/info.go with agent-actionable changes for {{version}}","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:01.016558-08:00","updated_at":"2025-12-20T01:23:50.3879-08:00","closed_at":"2025-12-20T01:23:50.3879-08:00","close_reason":"Superseded by bd-hzvz (actual 0.30.7 release)","dependencies":[{"issue_id":"bd-hw3w","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:14.941855-08:00","created_by":"daemon"},{"issue_id":"bd-hw3w","depends_on_id":"bd-czss","type":"blocks","created_at":"2025-12-19T22:56:23.219257-08:00","created_by":"daemon"}]} +{"id":"bd-hvng","title":"Merge: bd-w193","description":"branch: polecat/nux\ntarget: main\nsource_issue: bd-w193\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:23:47.496139-08:00","updated_at":"2025-12-20T23:17:26.996479-08:00","closed_at":"2025-12-20T23:17:26.996479-08:00","close_reason":"Branches nuked, MRs obsolete"} +{"id":"bd-hw3w","title":"Update info.go versionChanges","description":"Add entry to versionChanges in cmd/bd/info.go with agent-actionable changes for {{version}}","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:01.016558-08:00","updated_at":"2025-12-20T17:59:26.262511-08:00","closed_at":"2025-12-20T01:23:50.3879-08:00","dependencies":[{"issue_id":"bd-hw3w","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:14.941855-08:00","created_by":"daemon"},{"issue_id":"bd-hw3w","depends_on_id":"bd-czss","type":"blocks","created_at":"2025-12-19T22:56:23.219257-08:00","created_by":"daemon"}]} {"id":"bd-hy9p","title":"Add --body-file flag to bd create for reading descriptions from files","description":"## Problem\n\nCreating issues with long/complex descriptions via CLI requires shell escaping gymnastics:\n\n```bash\n# Current workaround - awkward heredoc quoting\nbd create --title=\"...\" --description=\"$(cat \u003c\u003c'EOF'\n...markdown...\nEOF\n)\"\n\n# Often fails with quote escaping errors in eval context\n# Agents resort to writing temp files then reading them\n```\n\n## Proposed Solution\n\nAdd `--body-file` and `--description-file` flags to read description from a file, matching `gh` CLI pattern.\n\n```bash\n# Natural pattern that aligns with training data\ncat \u003e /tmp/desc.md \u003c\u003c 'EOF'\n...markdown content...\nEOF\n\nbd create --title=\"...\" --body-file=/tmp/desc.md\n```\n\n## Implementation\n\n### 1. Add new flags to `bd create`\n\n```go\ncreateCmd.Flags().String(\"body-file\", \"\", \"Read description from file (use - for stdin)\")\ncreateCmd.Flags().String(\"description-file\", \"\", \"Alias for --body-file\")\n```\n\n### 2. Flag precedence\n\n- If `--body-file` or `--description-file` is provided, read from file\n- If value is `-`, read from stdin\n- Otherwise fall back to `--body` or `--description` flag\n- If neither provided, description is empty (current behavior)\n\n### 3. Error handling\n\n- File doesn't exist → clear error message\n- File not readable → clear error message\n- stdin specified but not available → clear error message\n\n## Benefits\n\nāœ… **Matches training data**: `gh issue create --body-file file.txt` is a common pattern\nāœ… **No shell escaping issues**: File content is read directly\nāœ… **Works with any content**: Markdown, special characters, quotes, etc.\nāœ… **Agent-friendly**: Agents already write complex content to temp files\nāœ… **User-friendly**: Easier for humans too when pasting long descriptions\n\n## Related Commands\n\nConsider adding similar support to:\n- `bd update --body-file` (for updating descriptions)\n- `bd comment --body-file` (if/when we add comments)\n\n## Examples\n\n```bash\n# From file\nbd create --title=\"Add new feature\" --body-file=feature.md\n\n# From stdin\necho \"Quick description\" | bd create --title=\"Bug fix\" --body-file=-\n\n# With other flags\nbd create \\\n --title=\"Security issue\" \\\n --type=bug \\\n --priority=0 \\\n --body-file=security-report.md \\\n --label=security\n```\n\n## Testing\n\n- Test with normal files\n- Test with stdin (`-`)\n- Test with non-existent files (error handling)\n- Test with binary files (should handle gracefully)\n- Test with empty files (valid - empty description)\n- Test that `--description-file` and `--body-file` are equivalent aliases","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-11-22T00:02:08.762684-08:00","updated_at":"2025-12-17T23:13:40.536024-08:00","closed_at":"2025-12-17T17:28:52.505239-08:00"} -{"id":"bd-hzvz","title":"Update info.go versionChanges","description":"Add entry to versionChanges in cmd/bd/info.go with agent-actionable changes for 0.30.7","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:48.649359-08:00","updated_at":"2025-12-19T22:57:31.604229-08:00","closed_at":"2025-12-19T22:57:31.604229-08:00","close_reason":"Updated info.go versionChanges","dependencies":[{"issue_id":"bd-hzvz","depends_on_id":"bd-8pyn","type":"parent-child","created_at":"2025-12-19T22:56:48.652068-08:00","created_by":"stevey"},{"issue_id":"bd-hzvz","depends_on_id":"bd-2ep8","type":"blocks","created_at":"2025-12-19T22:56:48.652376-08:00","created_by":"stevey"}]} +{"id":"bd-hzvz","title":"Update info.go versionChanges","description":"Add entry to versionChanges in cmd/bd/info.go with agent-actionable changes for 0.30.7","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:48.649359-08:00","updated_at":"2025-12-19T22:57:31.604229-08:00","closed_at":"2025-12-19T22:57:31.604229-08:00","dependencies":[{"issue_id":"bd-hzvz","depends_on_id":"bd-8pyn","type":"parent-child","created_at":"2025-12-19T22:56:48.652068-08:00","created_by":"stevey"},{"issue_id":"bd-hzvz","depends_on_id":"bd-2ep8","type":"blocks","created_at":"2025-12-19T22:56:48.652376-08:00","created_by":"stevey"}]} +{"id":"bd-i0rx","title":"Merge: bd-ao0s","description":"branch: polecat/rictus\ntarget: main\nsource_issue: bd-ao0s\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-20T01:13:42.716658-08:00","updated_at":"2025-12-20T23:17:26.993744-08:00","closed_at":"2025-12-20T23:17:26.993744-08:00","close_reason":"Branches nuked, MRs obsolete"} {"id":"bd-in7","title":"Test message","description":"Hello world","status":"closed","priority":2,"issue_type":"message","created_at":"2025-12-17T23:16:13.184946-08:00","updated_at":"2025-12-18T17:42:26.000073-08:00","closed_at":"2025-12-17T23:37:38.563369-08:00"} {"id":"bd-indn","title":"bd template commands fail with daemon mode","description":"The `bd template show` and `bd template instantiate` commands fail with 'Error loading template: no database connection' when daemon is running.\n\n**Reproduction:**\n```bash\nbd daemon --start\nbd template show bd-qqc # Error: no database connection\nbd template show bd-qqc --no-daemon # Works\n```\n\n**Expected:** Template commands should work with daemon like other commands.\n\n**Workaround:** Use `--no-daemon` flag.\n\n**Location:** Likely in cmd/bd/template.go - daemon RPC path not implemented for template operations.","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-18T22:57:35.16596-08:00","updated_at":"2025-12-18T22:57:35.16596-08:00"} {"id":"bd-io8c","title":"Improve test coverage for internal/syncbranch (33.0% → 70%)","description":"The syncbranch package has only 33.0% test coverage. This package handles git sync operations and is critical for data integrity.\n\nCurrent coverage: 33.0%\nTarget coverage: 70%","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-13T20:43:02.079145-08:00","updated_at":"2025-12-13T21:01:14.972533-08:00"} @@ -200,11 +209,13 @@ {"id":"bd-o4qy","title":"Improve CheckStaleness error handling","description":"## Problem\n\nCheckStaleness returns 'false' (not stale) for multiple error conditions instead of returning errors. This masks problems.\n\n**Location:** internal/autoimport/autoimport.go:253-285\n\n## Edge Cases That Return False\n\n1. **Invalid last_import_time format** (line 259-262)\n2. **No JSONL file found** (line 267-277) \n3. **JSONL stat fails** (line 279-282)\n\n## Fix\n\nReturn errors for abnormal conditions:\n\n```go\nlastImportTime, err := time.Parse(time.RFC3339, lastImportStr)\nif err != nil {\n return false, fmt.Errorf(\"corrupted last_import_time: %w\", err)\n}\n\nif jsonlPath == \"\" {\n return false, fmt.Errorf(\"no JSONL file found\")\n}\n\nstat, err := os.Stat(jsonlPath)\nif err != nil {\n return false, fmt.Errorf(\"cannot stat JSONL: %w\", err)\n}\n```\n\n## Impact\nMedium - edge cases are rare but should be handled\n\n## Effort \n30 minutes - requires updating callers in RPC server","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-20T20:17:27.606219-05:00","updated_at":"2025-12-17T23:13:40.536905-08:00","closed_at":"2025-12-17T19:11:12.965289-08:00","dependencies":[{"issue_id":"bd-o4qy","depends_on_id":"bd-2q6d","type":"blocks","created_at":"2025-11-20T20:18:26.81065-05:00","created_by":"stevey","metadata":"{}"}]} {"id":"bd-o55a","title":"GH#509: bd doesn't find .beads when running from nested worktrees","description":"When worktrees are nested under main repo (.worktrees/feature/), bd stops at worktree git root instead of continuing to find .beads in parent. See GitHub issue #509 for detailed fix suggestion.","status":"tombstone","priority":2,"issue_type":"bug","created_at":"2025-12-16T01:03:20.281591-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-o9o","title":"Exclude pinned issues from bd ready","description":"Update bd ready to exclude pinned issues. Pinned issues are context markers, not work items, and should never appear in the ready-to-work list.","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-18T23:33:41.979073-08:00","updated_at":"2025-12-18T23:33:41.979073-08:00","dependencies":[{"issue_id":"bd-o9o","depends_on_id":"bd-0vg","type":"blocks","created_at":"2025-12-18T23:33:56.392931-08:00","created_by":"daemon"},{"issue_id":"bd-o9o","depends_on_id":"bd-7h5","type":"blocks","created_at":"2025-12-18T23:34:07.612655-08:00","created_by":"daemon"}]} +{"id":"bd-of2p","title":"Improve version bump molecule with missing steps","description":"During v0.32.1 release, discovered missing steps in the release molecule:\n\n**Missing from molecule:**\n1. Rebuild ~/go/bin/bd (only did ~/.local/bin/bd)\n2. Install beads-mcp from local source: `uv tool install --reinstall ./integrations/beads-mcp`\n3. Restart daemons with `bd daemons killall`\n4. (Optional) Publish beads-mcp to PyPI\n\n**Current molecule steps (bd-6s61):**\n1. Update CHANGELOG.md\n2. Update info.go versionChanges \n3. Run bump-version.sh\n4. Run tests and linting\n5. Update local installation\n6. Commit and push release\n7. Wait for CI\n8. Verify release artifacts\n\n**Proposed additions:**\n- After \"Update local installation\": rebuild BOTH ~/.local/bin/bd AND ~/go/bin/bd\n- Add: \"Install beads-mcp from source\" step\n- Add: \"Restart daemons\" step\n- Add: \"Verify all versions match\" step that checks all artifacts\n\n**Also learned:**\n- Must run from mayor/rig to avoid git conflicts with bd sync (already documented in bump-version.sh)","notes":"CORRECTION: npm publishing IS automated and working!\n\n**Package naming:**\n- OLD: `beads` (npm) - deprecated, stuck at 0.2.1\n- CURRENT: `@beads/bd` (npm) - scoped package, auto-published by CI\n\n**How it works:**\n- CI uses OIDC trusted publishing (no token needed)\n- Workflow: .github/workflows/release.yml → publish-npm job\n- Permissions: `id-token: write` enables GitHub OIDC\n- To install: `npm install -g @beads/bd` (not `npm install beads`)\n\n**All publishing is automated on tag push:**\n1. GitHub Release - goreleaser āœ“\n2. PyPI - publish-pypi job āœ“\n3. Homebrew - update-homebrew job āœ“\n4. npm (@beads/bd) - publish-npm job āœ“\n\n**Remaining molecule improvements (local steps only):**\n- Rebuild BOTH ~/.local/bin/bd AND ~/go/bin/bd\n- Install beads-mcp from source: `uv tool install --reinstall ./integrations/beads-mcp`\n- Restart daemons: `bd daemons killall`\n- Run from mayor/rig to avoid git conflicts with bd sync\n- Final verification step to check all local versions match","status":"in_progress","priority":2,"issue_type":"task","assignee":"beads/dave","created_at":"2025-12-20T22:09:11.845787-08:00","updated_at":"2025-12-20T22:35:53.687587-08:00"} {"id":"bd-ola6","title":"Implement transaction retry logic for SQLITE_BUSY","description":"BEGIN IMMEDIATE fails immediately on SQLITE_BUSY instead of retrying with exponential backoff.\n\nLocation: internal/storage/sqlite/sqlite.go:223-225\n\nProblem:\n- Under concurrent write load, BEGIN IMMEDIATE can fail with SQLITE_BUSY\n- Current implementation fails immediately instead of retrying\n- Results in spurious failures under normal concurrent usage\n\nSolution: Implement exponential backoff retry:\n- Retry up to N times (e.g., 5)\n- Backoff: 10ms, 20ms, 40ms, 80ms, 160ms\n- Check for context cancellation between retries\n- Only retry on SQLITE_BUSY/database locked errors\n\nImpact: Spurious failures under concurrent write load\n\nEffort: 3 hours","status":"open","priority":1,"issue_type":"feature","created_at":"2025-11-16T14:51:31.247147-08:00","updated_at":"2025-11-16T14:51:31.247147-08:00"} {"id":"bd-ork0","title":"Add comments to 30+ silently ignored errors or fix them","description":"Code health review found 30+ instances of error suppression using blank identifier without explanation:\n\nGood examples (with comments):\n- merge.go: _ = gitRmCmd.Run() // Ignore errors\n- daemon_watcher.go: _ = watcher.Add(...) // Ignore error\n\nBad examples (no context):\n- create.go:213: dbPrefix, _ = store.GetConfig(ctx, \"issue_prefix\")\n- daemon_sync_branch.go: _ = daemonClient.Close()\n- migrate_hash_ids.go, version_tracking.go: _ = store.Close()\n\nFix: Add comments explaining WHY errors are ignored, or handle them properly.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-16T18:17:25.899372-08:00","updated_at":"2025-12-16T18:17:25.899372-08:00","dependencies":[{"issue_id":"bd-ork0","depends_on_id":"bd-tggf","type":"blocks","created_at":"2025-12-16T18:19:06.275843-08:00","created_by":"daemon","metadata":"{}"}]} -{"id":"bd-ot0w","title":"Work on beads-tip: Fix broken Claude integration link in ...","description":"Work on beads-tip: Fix broken Claude integration link in bd doctor (GH#623). Update URL that doesn't exist. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:56:08.429157-08:00","updated_at":"2025-12-19T23:20:39.790305-08:00","closed_at":"2025-12-19T23:20:39.790305-08:00"} +{"id":"bd-ot0w","title":"Work on beads-tip: Fix broken Claude integration link in ...","description":"Work on beads-tip: Fix broken Claude integration link in bd doctor (GH#623). Update URL that doesn't exist. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","assignee":"beads/dementus","created_at":"2025-12-19T22:56:08.429157-08:00","updated_at":"2025-12-19T23:20:39.790305-08:00","closed_at":"2025-12-19T23:20:39.790305-08:00","close_reason":"Fixed broken Claude plugin URL in bd doctor"} {"id":"bd-otf4","title":"Code Review: PR #481 - Context Engineering Optimizations","description":"Comprehensive code review of the merged context engineering PR (PR #481) that reduces MCP context usage by 80-90%.\n\n## Summary\nThe PR successfully implements lazy tool schema loading and minimal issue models to reduce context window usage. Overall implementation is solid and well-tested.\n\n## Positive Findings\nāœ… Well-designed models (IssueMinimal, CompactedResult)\nāœ… Comprehensive test coverage (28 tests, all passing)\nāœ… Clear documentation and comments\nāœ… Backward compatibility preserved (show() still returns full Issue)\nāœ… Sensible defaults (COMPACTION_THRESHOLD=20, PREVIEW_COUNT=5)\nāœ… Tool catalog complete with all 15 tools documented\n\n## Issues Identified\nSee linked issues for specific followup tasks.\n\n## Context Engineering Architecture\n- discover_tools(): List tool names only (~500 bytes vs ~15KB)\n- get_tool_info(name): Get specific tool details on-demand\n- IssueMinimal: Lightweight model for list views (~80 bytes vs ~400 bytes)\n- CompactedResult: Auto-compacts results with \u003e20 issues\n- _to_minimal(): Conversion function (efficient, no N+1 issues)","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-14T14:24:13.523532-08:00","updated_at":"2025-12-14T14:24:13.523532-08:00"} -{"id":"bd-otli","title":"Wait for CI to pass","description":"Monitor GitHub Actions - all checks must pass before release artifacts are built","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:03.022281-08:00","updated_at":"2025-12-20T00:25:52.635223-08:00","closed_at":"2025-12-20T00:25:52.635223-08:00","close_reason":"CI passed - Release workflow completed successfully","dependencies":[{"issue_id":"bd-otli","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:15.097564-08:00","created_by":"daemon"},{"issue_id":"bd-otli","depends_on_id":"bd-7tuu","type":"blocks","created_at":"2025-12-19T22:56:23.360436-08:00","created_by":"daemon"}]} +{"id":"bd-otli","title":"Wait for CI to pass","description":"Monitor GitHub Actions - all checks must pass before release artifacts are built","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:03.022281-08:00","updated_at":"2025-12-20T00:49:51.928591-08:00","closed_at":"2025-12-20T00:25:52.635223-08:00","dependencies":[{"issue_id":"bd-otli","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:15.097564-08:00","created_by":"daemon"},{"issue_id":"bd-otli","depends_on_id":"bd-7tuu","type":"blocks","created_at":"2025-12-19T22:56:23.360436-08:00","created_by":"daemon"}]} +{"id":"bd-p5za","title":"mol-christmas-launch: 3-day execution plan","description":"Christmas Launch Molecule - execute phases in order, survive restarts.\n\nPIN THIS BEAD. Check progress each session start.\n\n## Step: phase0-beads-foundation\nFix blocking issues before swarming:\n1. Verify gastown beads schema works: bd list --status=open\n2. Ensure bd mol bond exists (check bd-usro)\n3. Verify bd-2vh3 (squash) is filed\n\n## Step: phase1-polecat-loop\nSerial work on polecat execution:\n1. gt-9nf: Fresh polecats only\n2. gt-975: Molecule execution support\n3. gt-8v8: Refuse uncommitted work\nThen swarm: gt-e1y, gt-f8v, gt-eu9\nNeeds: phase0-beads-foundation\n\n## Step: phase2-refinery\nSerial work on refinery autonomy:\n1. gt-5gkd: Refinery CLAUDE.md\n2. gt-bj6f: Refinery context in gt prime\n3. gt-0qki: Refinery-Witness protocol\nNeeds: phase1-polecat-loop\n\n## Step: phase3-deacon\nHealth monitoring infrastructure:\n1. gt-5af.4: Simplify daemon\n2. gt-5af.7: Crew session patterns\n3. gt-976: Crew lifecycle\nNeeds: phase2-refinery\n\n## Step: phase4-code-review\nSelf-improvement flywheel:\n1. Define mol-code-review (gt-fjvo)\n2. Test on open MRs\n3. Integrate with Refinery\nNeeds: phase3-deacon\n\n## Step: phase5-polish\nDemo readiness:\n1. gt-b2hj: Find orphaned work\n2. Doctor checks\n3. Clean up open MRs\nNeeds: phase4-code-review\n\n## Step: verify-flywheel\nSuccess criteria:\n- gt spawn works with molecules\n- Refinery processes MRs autonomously\n- mol-code-review runs on a PR\n- bd cleanup --ephemeral works\nNeeds: phase5-polish","status":"open","issue_type":"epic","created_at":"2025-12-20T21:20:02.462889-08:00","updated_at":"2025-12-20T21:20:02.462889-08:00"} {"id":"bd-pbh","title":"Release v0.30.4","description":"## Version Bump Workflow\n\nCoordinating release from 0.30.3 to 0.30.4.\n\n### Components Updated\n- Go CLI (cmd/bd/version.go)\n- Claude Plugin (.claude-plugin/*.json)\n- MCP Server (integrations/beads-mcp/)\n- npm Package (npm-package/package.json)\n- Git hooks (cmd/bd/templates/hooks/)\n\n### Release Channels\n- GitHub Releases (GoReleaser)\n- PyPI (beads-mcp)\n- npm (@beads/cli)\n- Homebrew (homebrew-beads tap)\n","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-17T21:19:10.926133-08:00","updated_at":"2025-12-17T21:46:46.192948-08:00","closed_at":"2025-12-17T21:46:46.192948-08:00","labels":["release","v0.30.4","workflow"]} {"id":"bd-pbh.1","title":"Update cmd/bd/version.go to 0.30.4","description":"Update the Version constant in cmd/bd/version.go:\n```go\nVersion = \"0.30.4\"\n```\n\n\n```verify\ngrep -q 'Version = \"0.30.4\"' cmd/bd/version.go\n```","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T21:19:10.9462-08:00","updated_at":"2025-12-17T21:46:46.20387-08:00","closed_at":"2025-12-17T21:46:46.20387-08:00","labels":["workflow"],"dependencies":[{"issue_id":"bd-pbh.1","depends_on_id":"bd-pbh","type":"parent-child","created_at":"2025-12-17T21:19:10.946633-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-pbh.10","title":"Run check-versions.sh - all must pass","description":"Run the version consistency check:\n```bash\n./scripts/check-versions.sh\n```\n\nAll versions must match 0.30.4.\n\n\n```verify\n./scripts/check-versions.sh\n```","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T21:19:11.047311-08:00","updated_at":"2025-12-17T21:46:46.28316-08:00","closed_at":"2025-12-17T21:46:46.28316-08:00","labels":["workflow"],"dependencies":[{"issue_id":"bd-pbh.10","depends_on_id":"bd-pbh","type":"parent-child","created_at":"2025-12-17T21:19:11.047888-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-pbh.10","depends_on_id":"bd-pbh.1","type":"blocks","created_at":"2025-12-17T21:19:11.159084-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-pbh.10","depends_on_id":"bd-pbh.4","type":"blocks","created_at":"2025-12-17T21:19:11.168248-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-pbh.10","depends_on_id":"bd-pbh.5","type":"blocks","created_at":"2025-12-17T21:19:11.177869-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-pbh.10","depends_on_id":"bd-pbh.6","type":"blocks","created_at":"2025-12-17T21:19:11.187629-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-pbh.10","depends_on_id":"bd-pbh.7","type":"blocks","created_at":"2025-12-17T21:19:11.199955-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-pbh.10","depends_on_id":"bd-pbh.8","type":"blocks","created_at":"2025-12-17T21:19:11.211479-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-pbh.10","depends_on_id":"bd-pbh.9","type":"blocks","created_at":"2025-12-17T21:19:11.224059-08:00","created_by":"daemon","metadata":"{}"}]} @@ -232,8 +243,8 @@ {"id":"bd-pgcs","title":"Clean up orphaned child issues (bd-cb64c226.*, bd-cbed9619.*)","description":"## Problem\n\nEvery bd command shows warnings about 12 orphaned child issues:\n- bd-cb64c226.1, .6, .8, .9, .10, .12, .13\n- bd-cbed9619.1, .2, .3, .4, .5\n\nThese are hierarchical IDs (parent.child format) where the parent issues no longer exist.\n\n## Impact\n\n- Clutters output of every bd command\n- Confusing for users\n- Indicates incomplete cleanup of deleted parent issues\n\n## Proposed Solution\n\n1. Delete the orphaned issues since their parents no longer exist:\n ```bash\n bd delete bd-cb64c226.1 bd-cb64c226.6 bd-cb64c226.8 ...\n ```\n\n2. Or convert them to top-level issues if they contain useful content\n\n## Investigation Needed\n\n- What were the parent issues bd-cb64c226 and bd-cbed9619?\n- Why were they deleted without their children?\n- Should bd delete cascade to children automatically?","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-16T23:06:17.240571-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} {"id":"bd-pzw7","title":"gt handoff deadlock at handoff.go:125","notes":"When running 'gt handoff -m \"message\"' after successful MR submit, go panics with 'fatal error: all goroutines are asleep - deadlock\\!' at handoff.go:125. The shutdown request still appears to be sent successfully but the command crashes. Stack trace shows issue is in runHandoff select statement.","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-19T23:22:12.46315-08:00","updated_at":"2025-12-19T23:22:18.613883-08:00"} {"id":"bd-qioh","title":"Standardize error handling: replace direct fmt.Fprintf+os.Exit with FatalError","description":"Code health review found inconsistent error handling patterns:\n\nPattern A (preferred): FatalError() from errors.go:21-24\nPattern B (inconsistent): Direct fmt.Fprintf(os.Stderr) + os.Exit(1)\n\nAffected files:\n- delete.go:35-49 uses direct pattern\n- create.go:213 ignores error silently\n- Various other commands\n\nFix: Standardize on FatalError() wrapper for consistent error messages and testability.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-16T18:17:19.309394-08:00","updated_at":"2025-12-16T18:17:19.309394-08:00","dependencies":[{"issue_id":"bd-qioh","depends_on_id":"bd-tggf","type":"blocks","created_at":"2025-12-16T18:19:05.953559-08:00","created_by":"daemon","metadata":"{}"}]} -{"id":"bd-qkw9","title":"Run bump-version.sh {{version}}","description":"Run ./scripts/bump-version.sh {{version}} to update version in all files","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:55:58.841424-08:00","updated_at":"2025-12-20T01:18:48.99813-08:00","closed_at":"2025-12-20T01:18:48.99813-08:00","close_reason":"Version bump complete","dependencies":[{"issue_id":"bd-qkw9","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:14.786027-08:00","created_by":"daemon"}]} -{"id":"bd-qqc","title":"Release v{{version}}","description":"Version bump workflow for beads release {{version}}.\n\n## Variables\n- `{{version}}` - The new version number (e.g., 0.31.0)\n- `{{date}}` - Release date (YYYY-MM-DD format)\n\n## Workflow Steps\n1. Kill running daemons\n2. Run tests and linting\n3. Bump version in all files (10 files total)\n4. Update cmd/bd/info.go with release notes\n5. Commit and push version bump\n6. Create and push git tag\n7. Update Homebrew formula\n8. Upgrade local Homebrew installation\n9. Verify installation\n\n## Files Updated by bump-version.sh\n- cmd/bd/version.go\n- .claude-plugin/plugin.json\n- .claude-plugin/marketplace.json\n- integrations/beads-mcp/pyproject.toml\n- integrations/beads-mcp/src/beads_mcp/__init__.py\n- README.md\n- npm-package/package.json\n- cmd/bd/templates/hooks/* (4 files)\n- CHANGELOG.md\n\n## Manual Step Required\n- cmd/bd/info.go - Add versionChanges entry with release notes","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-18T12:59:00.610371-08:00","updated_at":"2025-12-20T01:18:46.71424-08:00","closed_at":"2025-12-20T01:18:46.71424-08:00","close_reason":"Release complete","labels":["template"]} +{"id":"bd-qkw9","title":"Run bump-version.sh {{version}}","description":"Run ./scripts/bump-version.sh {{version}} to update version in all files","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:55:58.841424-08:00","updated_at":"2025-12-20T17:59:26.262877-08:00","closed_at":"2025-12-20T01:18:48.99813-08:00","dependencies":[{"issue_id":"bd-qkw9","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:14.786027-08:00","created_by":"daemon"}]} +{"id":"bd-qqc","title":"Release v{{version}}","description":"Version bump workflow for beads release {{version}}.\n\n## Variables\n- `{{version}}` - The new version number (e.g., 0.31.0)\n- `{{date}}` - Release date (YYYY-MM-DD format)\n\n## Workflow Steps\n1. Kill running daemons\n2. Run tests and linting\n3. Bump version in all files (10 files total)\n4. Update cmd/bd/info.go with release notes\n5. Commit and push version bump\n6. Create and push git tag\n7. Update Homebrew formula\n8. Upgrade local Homebrew installation\n9. Verify installation\n\n## Files Updated by bump-version.sh\n- cmd/bd/version.go\n- .claude-plugin/plugin.json\n- .claude-plugin/marketplace.json\n- integrations/beads-mcp/pyproject.toml\n- integrations/beads-mcp/src/beads_mcp/__init__.py\n- README.md\n- npm-package/package.json\n- cmd/bd/templates/hooks/* (4 files)\n- CHANGELOG.md\n\n## Manual Step Required\n- cmd/bd/info.go - Add versionChanges entry with release notes","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-18T12:59:00.610371-08:00","updated_at":"2025-12-20T17:59:26.263219-08:00","closed_at":"2025-12-20T01:18:46.71424-08:00","labels":["template"]} {"id":"bd-qqc.1","title":"Update version to {{version}} in version.go","description":"Edit cmd/bd/version.go line 17:\n\n```go\nVersion = \"{{version}}\"\n```\n\nVerify with: `grep 'Version =' cmd/bd/version.go`","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T12:59:13.887087-08:00","updated_at":"2025-12-18T23:34:18.630067-08:00","closed_at":"2025-12-18T22:41:41.82664-08:00","dependencies":[{"issue_id":"bd-qqc.1","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T12:59:13.887655-08:00","created_by":"stevey"}]} {"id":"bd-qqc.10","title":"Upgrade local Homebrew installation","description":"Upgrade bd via Homebrew:\n\n```bash\nbrew update\nbrew upgrade bd\n/opt/homebrew/bin/bd version # Verify shows {{version}}\n```","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T22:42:37.60241-08:00","updated_at":"2025-12-18T23:07:55.686598-08:00","closed_at":"2025-12-18T22:52:00.331429-08:00","dependencies":[{"issue_id":"bd-qqc.10","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:42:37.603893-08:00","created_by":"daemon"},{"issue_id":"bd-qqc.10","depends_on_id":"bd-qqc.9","type":"blocks","created_at":"2025-12-18T22:43:21.458817-08:00","created_by":"daemon"}]} {"id":"bd-qqc.11","title":"Update go install bd to {{version}}","description":"Rebuild and install bd to ~/go/bin:\n\n```bash\ngo install ./cmd/bd\n~/go/bin/bd version # Verify shows {{version}}\n```\n\nNote: If ~/go/bin is in PATH before /opt/homebrew/bin, this is the version that runs by default.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T23:07:55.838013-08:00","updated_at":"2025-12-18T23:09:05.775582-08:00","closed_at":"2025-12-18T23:09:05.775582-08:00","dependencies":[{"issue_id":"bd-qqc.11","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T23:07:55.838432-08:00","created_by":"daemon"},{"issue_id":"bd-qqc.11","depends_on_id":"bd-qqc.10","type":"blocks","created_at":"2025-12-18T23:08:19.629947-08:00","created_by":"daemon"}]} @@ -247,6 +258,7 @@ {"id":"bd-qqc.7","title":"Push release v{{version}} to remote","description":"Push the commit and tag:\n\n```bash\ngit push \u0026\u0026 git push --tags\n```\n\nVerify on GitHub that the tag appears in releases.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T13:00:26.933082-08:00","updated_at":"2025-12-18T23:34:18.630538-08:00","closed_at":"2025-12-18T22:41:41.882956-08:00","dependencies":[{"issue_id":"bd-qqc.7","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T13:00:26.933687-08:00","created_by":"stevey"},{"issue_id":"bd-qqc.7","depends_on_id":"bd-qqc.6","type":"blocks","created_at":"2025-12-18T13:01:12.711161-08:00","created_by":"stevey"}]} {"id":"bd-qqc.8","title":"Create and push git tag v{{version}}","description":"Create the release tag and push it:\n\n```bash\ngit tag v{{version}}\ngit push origin v{{version}}\n```\n\nThis triggers the GoReleaser GitHub Action to build release binaries.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T22:42:34.659927-08:00","updated_at":"2025-12-18T22:47:14.054232-08:00","closed_at":"2025-12-18T22:47:14.054232-08:00","dependencies":[{"issue_id":"bd-qqc.8","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:42:34.660248-08:00","created_by":"daemon"},{"issue_id":"bd-qqc.8","depends_on_id":"bd-vgi5","type":"blocks","created_at":"2025-12-18T22:43:21.209529-08:00","created_by":"daemon"}]} {"id":"bd-qqc.9","title":"Update Homebrew formula","description":"Update the Homebrew tap with new version:\n\n```bash\n./scripts/update-homebrew.sh {{version}}\n```\n\nThis script waits for GitHub Actions to complete (~5 min), then updates the formula with new SHA256 hashes.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T22:42:35.815096-08:00","updated_at":"2025-12-18T22:51:07.863862-08:00","closed_at":"2025-12-18T22:51:07.863862-08:00","dependencies":[{"issue_id":"bd-qqc.9","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:42:35.816752-08:00","created_by":"daemon"},{"issue_id":"bd-qqc.9","depends_on_id":"bd-qqc.8","type":"blocks","created_at":"2025-12-18T22:43:21.332955-08:00","created_by":"daemon"}]} +{"id":"bd-r36u","title":"gt mq list shows empty when MRs exist","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-20T01:13:07.561256-08:00","updated_at":"2025-12-20T01:13:07.561256-08:00"} {"id":"bd-r46","title":"Support --reason flag in daemon mode for reopen command","description":"The reopen.go command has a TODO at line 61 to add reason as a comment once RPC supports AddComment. Currently --reason flag is ignored in daemon mode with a warning.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-21T18:55:10.773626-05:00","updated_at":"2025-11-21T18:55:10.773626-05:00"} {"id":"bd-r6a","title":"Redesign workflow system: templates as native Beads","description":"## Problem\n\nThe current workflow system (YAML templates in cmd/bd/templates/workflows/) is architecturally flawed:\n\n1. **Out-of-band data plane** - YAML files are a parallel system outside Beads itself\n2. **Heavyweight DSL** - YAML is gross; even TOML would have been better, but neither is ideal\n3. **Not graph-native** - Beads IS already a dependency graph with priorities, so why reinvent it?\n4. **Can't use bd commands on templates** - They're opaque YAML, not viewable/editable Beads\n\n## The Right Design\n\n**Templates should be Beads themselves.**\n\nA \"workflow template\" should be:\n- An epic marked as a template (via label, type, or prefix like `tpl-`)\n- Child issues with dependencies between them (using normal bd dep)\n- Titles and descriptions containing `{{variable}}` placeholders\n- Normal priorities that control serialization order\n\n\"Instantiation\" becomes:\n1. Clone the template subgraph (epic + children + dependencies)\n2. Substitute variables in titles/descriptions\n3. Generate new IDs for all cloned issues\n4. Return the new epic ID\n\n## Benefits\n\n- **No YAML** - Templates are just Beads\n- **Use existing tools** - `bd show`, `bd edit`, `bd dep` work on templates\n- **Graph-native** - Dependencies are real Beads dependencies\n- **Simpler codebase** - Remove all the YAML parsing/workflow code\n- **Composable** - Templates can reference other templates\n\n## Tasks\n\n1. Delete the YAML workflow system code (revert recent push + remove existing workflow code)\n2. Design template marking convention (label? type? id prefix?)\n3. Implement `bd template create` or `bd clone --as-template`\n4. Implement `bd template instantiate \u003ctemplate-id\u003e --var key=value`\n5. Migrate version-bump workflow to native Beads template\n6. Update documentation","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-17T22:41:57.359643-08:00","updated_at":"2025-12-18T17:42:26.000769-08:00","closed_at":"2025-12-18T13:47:04.632525-08:00"} {"id":"bd-r6a.1","title":"Revert/remove YAML workflow system","description":"Revert the recent commit and remove all YAML workflow code:\n\n1. `git revert aae8407a` (the commit we just pushed with workflow fixes)\n2. Remove `cmd/bd/templates/workflows/` directory\n3. Remove workflow.go or gut it to minimal stub\n4. Remove WorkflowTemplate types from internal/types/workflow.go\n5. Remove any workflow-related RPC handlers\n\nKeep only minimal scaffolding if needed for the new template system.","status":"closed","issue_type":"task","created_at":"2025-12-17T22:42:07.339684-08:00","updated_at":"2025-12-17T22:46:08.606088-08:00","closed_at":"2025-12-17T22:46:08.606088-08:00","dependencies":[{"issue_id":"bd-r6a.1","depends_on_id":"bd-r6a","type":"parent-child","created_at":"2025-12-17T22:42:07.340117-08:00","created_by":"daemon","metadata":"{}"}]} @@ -254,48 +266,55 @@ {"id":"bd-r6a.3","title":"Create version-bump template as native Beads","description":"Migrate the version-bump workflow from YAML to a native Beads template:\n\n1. Create epic with template label: Release {{version}}\n2. Create child tasks for each step (update version files, changelog, commit, push, publish)\n3. Set up dependencies between tasks\n4. Add verification commands in task descriptions\n\nThis serves as both migration and validation of the new system.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-17T22:43:40.694931-08:00","updated_at":"2025-12-18T17:42:26.001149-08:00","closed_at":"2025-12-18T13:02:09.039457-08:00","dependencies":[{"issue_id":"bd-r6a.3","depends_on_id":"bd-r6a","type":"parent-child","created_at":"2025-12-17T22:43:40.695392-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-r6a.3","depends_on_id":"bd-r6a.2","type":"blocks","created_at":"2025-12-17T22:44:03.311902-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-r6a.4","title":"Add bd template list command","description":"Add a convenience command to list available templates:\n\nbd template list\n\nThis is equivalent to 'bd list --label=template' but more discoverable.\nCould also show variable placeholders found in each template.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-17T22:43:47.525316-08:00","updated_at":"2025-12-17T23:02:45.700582-08:00","closed_at":"2025-12-17T23:02:45.700582-08:00","dependencies":[{"issue_id":"bd-r6a.4","depends_on_id":"bd-r6a","type":"parent-child","created_at":"2025-12-17T22:43:47.525743-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-r6a.4","depends_on_id":"bd-r6a.2","type":"blocks","created_at":"2025-12-17T22:44:03.474353-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-r6a.5","title":"Update documentation for template system","description":"Update AGENTS.md and help text to document the new template system:\n\n- How to create a template (epic + template label + child issues)\n- How to define variables (just use {{name}} placeholders)\n- How to instantiate (bd template instantiate)\n- Migration from YAML workflows (if any users had custom ones)","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-17T22:43:55.461345-08:00","updated_at":"2025-12-18T17:42:26.001474-08:00","closed_at":"2025-12-18T13:46:53.446262-08:00","dependencies":[{"issue_id":"bd-r6a.5","depends_on_id":"bd-r6a","type":"parent-child","created_at":"2025-12-17T22:43:55.461763-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-r6a.5","depends_on_id":"bd-r6a.3","type":"blocks","created_at":"2025-12-17T22:44:03.632404-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"bd-r6a.5","depends_on_id":"bd-r6a.4","type":"blocks","created_at":"2025-12-17T22:44:03.788517-08:00","created_by":"daemon","metadata":"{}"}]} +{"id":"bd-rgd7","title":"Update CHANGELOG.md with release notes","description":"Add release notes for 0.32.1: MCP output control params (#667), pin field fix","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-20T21:53:16.031879-08:00","updated_at":"2025-12-20T21:54:07.982164-08:00","closed_at":"2025-12-20T21:54:07.982164-08:00","close_reason":"Added 0.32.1 release notes","dependencies":[{"issue_id":"bd-rgd7","depends_on_id":"bd-an4s","type":"parent-child","created_at":"2025-12-20T21:53:16.034926-08:00","created_by":"daemon"}]} {"id":"bd-rgyd","title":"Split internal/storage/sqlite/queries.go (1586 lines)","description":"Code health review found queries.go is too large at 1586 lines with:\n\n- Issue scanning logic duplicated between transaction.go and queries.go\n- Timestamp defensive fixes duplicated in CreateIssue, CreateIssues, batch_ops.go\n- Multiple similar patterns that should be consolidated\n\nAlso: transaction.go at 1284 lines, dependencies.go at 893 lines\n\nRecommendation:\n1. Extract common scanning into shared function\n2. Consolidate timestamp defensive fixes\n3. Split queries.go by domain (CRUD, search, batch, events)","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-16T18:17:23.85869-08:00","updated_at":"2025-12-16T18:17:23.85869-08:00","dependencies":[{"issue_id":"bd-rgyd","depends_on_id":"bd-tggf","type":"blocks","created_at":"2025-12-16T18:19:06.062038-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"bd-rp4o","title":"Deleted issues resurrect during bd sync (tombstones not propagating)","description":"## Problem\n\nWhen issues are deleted with bd delete --force, they get deleted from the local DB but resurrect during the next bd sync.\n\n## Reproduction\n\n1. Observe orphan warnings (bd-cb64c226.*, bd-cbed9619.*)\n2. Delete them: bd delete bd-cb64c226.1 ... --force\n3. Run bd sync\n4. Orphan warnings reappear - issues were resurrected!\n\n## Root Cause Hypothesis\n\nThe sync branch workflow (beads-sync) has the old state before deletions. When bd sync pulls from beads-sync and copies JSONL to main, the deleted issues are re-imported.\n\nTombstones may not be properly:\n1. Written to beads-sync during export\n2. Propagated during pull/merge\n3. Honored during import\n\n## Related\n\n- bd-7b7h: chicken-and-egg sync.branch bug (same workflow)\n- bd-ncwo: ID-based fallback matching to prevent ghost resurrection\n\n## Files to Investigate\n\n- cmd/bd/sync.go (export/import flow)\n- internal/syncbranch/worktree.go (PullFromSyncBranch, copyJSONLToMainRepo)\n- internal/importer/ (tombstone handling)","status":"tombstone","priority":1,"issue_type":"bug","created_at":"2025-12-16T23:09:43.072696-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} -{"id":"bd-rupw","title":"Run bump-version.sh 0.30.7","description":"Run ./scripts/bump-version.sh 0.30.7 to update version in all files","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:48.649647-08:00","updated_at":"2025-12-19T22:57:31.512956-08:00","closed_at":"2025-12-19T22:57:31.512956-08:00","close_reason":"Already ran bump-version.sh","dependencies":[{"issue_id":"bd-rupw","depends_on_id":"bd-8pyn","type":"parent-child","created_at":"2025-12-19T22:56:48.653475-08:00","created_by":"stevey"}]} +{"id":"bd-rupw","title":"Run bump-version.sh 0.30.7","description":"Run ./scripts/bump-version.sh 0.30.7 to update version in all files","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:48.649647-08:00","updated_at":"2025-12-19T22:57:31.512956-08:00","closed_at":"2025-12-19T22:57:31.512956-08:00","dependencies":[{"issue_id":"bd-rupw","depends_on_id":"bd-8pyn","type":"parent-child","created_at":"2025-12-19T22:56:48.653475-08:00","created_by":"stevey"}]} {"id":"bd-s0qf","title":"GH#405: Fix prefix parsing with hyphens - multi-hyphen prefixes parsed incorrectly","description":"Fixed: ExtractIssuePrefix was falling back to first-hyphen for word-like suffixes, breaking multi-hyphen prefixes like 'hacker-news' and 'me-py-toolkit'.","status":"tombstone","priority":2,"issue_type":"bug","created_at":"2025-12-16T01:13:56.951359-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-s2t","title":"wish: a 'continue' or similar cmd/flag which means alter last issue","description":"so many time I create an issue and then have another thought: 'oh, before I did X and it crashed there was ZZZ happening' or 'actually that is P4 not P2'. It would be nice if when `bd {cmd}` is used without a {title} or {id} it just adds or updates the most recently touched issue.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-08T06:46:37.529160416-07:00","updated_at":"2025-12-08T06:46:37.529160416-07:00"} {"id":"bd-sh4c","title":"Improve test coverage for cmd/bd/setup (28.4% → 50%)","description":"The setup package has only 28.4% test coverage. Setup commands are critical for first-time user experience.\n\nCurrent coverage: 28.4%\nTarget coverage: 50%","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-13T20:43:04.409346-08:00","updated_at":"2025-12-13T21:01:18.98833-08:00"} -{"id":"bd-si4g","title":"Verify release artifacts","description":"Check GitHub releases page - binaries for darwin/linux/windows should be available","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:04.183029-08:00","updated_at":"2025-12-20T00:25:52.720816-08:00","closed_at":"2025-12-20T00:25:52.720816-08:00","close_reason":"Release v0.30.7 verified - all platform artifacts published","dependencies":[{"issue_id":"bd-si4g","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:15.173619-08:00","created_by":"daemon"},{"issue_id":"bd-si4g","depends_on_id":"bd-otli","type":"blocks","created_at":"2025-12-19T22:56:23.428507-08:00","created_by":"daemon"}]} +{"id":"bd-si4g","title":"Verify release artifacts","description":"Check GitHub releases page - binaries for darwin/linux/windows should be available","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:04.183029-08:00","updated_at":"2025-12-20T00:49:51.92894-08:00","closed_at":"2025-12-20T00:25:52.720816-08:00","dependencies":[{"issue_id":"bd-si4g","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:15.173619-08:00","created_by":"daemon"},{"issue_id":"bd-si4g","depends_on_id":"bd-otli","type":"blocks","created_at":"2025-12-19T22:56:23.428507-08:00","created_by":"daemon"}]} {"id":"bd-siz1","title":"GH#532: bd sync circular error (suggests running bd sync)","description":"bd sync error message recommends running bd sync to fix the bd sync error. Fix error handling to provide useful guidance. See GitHub issue #532.","status":"tombstone","priority":2,"issue_type":"bug","created_at":"2025-12-16T01:04:00.543573-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-su45","title":"Protect pinned issues from bd cleanup/compact","description":"Update bd cleanup and bd compact to never delete pinned issues, even if they are closed. Pinned issues should persist indefinitely as reference material.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T23:33:46.204783-08:00","updated_at":"2025-12-19T17:43:35.712617-08:00","closed_at":"2025-12-19T00:43:04.06406-08:00","dependencies":[{"issue_id":"bd-su45","depends_on_id":"bd-0vg","type":"blocks","created_at":"2025-12-18T23:33:56.64582-08:00","created_by":"daemon"},{"issue_id":"bd-su45","depends_on_id":"bd-7h5","type":"blocks","created_at":"2025-12-18T23:34:07.857586-08:00","created_by":"daemon"}]} -{"id":"bd-sumr","title":"Merge: bd-t4sb","description":"branch: polecat/capable\ntarget: main\nsource_issue: bd-t4sb\nrig: beads","status":"open","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:22:21.343724-08:00","updated_at":"2025-12-19T23:22:21.343724-08:00"} +{"id":"bd-sumr","title":"Merge: bd-t4sb","description":"branch: polecat/capable\ntarget: main\nsource_issue: bd-t4sb\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:22:21.343724-08:00","updated_at":"2025-12-20T23:17:26.997992-08:00","closed_at":"2025-12-20T23:17:26.997992-08:00","close_reason":"Branches nuked, MRs obsolete"} {"id":"bd-svb5","title":"GH#505: Add bd reset/wipe command","description":"Add command to cleanly reset/wipe beads database. User reports painful manual process to start fresh. See GitHub issue #505.","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2025-12-16T01:03:42.160966-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"} -{"id":"bd-t4sb","title":"Work on beads-d8h: Fix prefix mismatch false positive wit...","description":"Work on beads-d8h: Fix prefix mismatch false positive with multi-hyphen prefixes like 'asianops-audit-' (GH#422). When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:56:19.545069-08:00","updated_at":"2025-12-20T00:26:12.606657-08:00","closed_at":"2025-12-19T23:21:45.471711-08:00"} +{"id":"bd-t4sb","title":"Work on beads-d8h: Fix prefix mismatch false positive wit...","description":"Work on beads-d8h: Fix prefix mismatch false positive with multi-hyphen prefixes like 'asianops-audit-' (GH#422). When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:56:19.545069-08:00","updated_at":"2025-12-19T23:28:32.429127-08:00","closed_at":"2025-12-19T23:21:45.471711-08:00","close_reason":"Fixed multi-hyphen prefix false positive in ValidateIDFormat (GH#422)"} {"id":"bd-t4u1","title":"False positive detection by Kaspersky Antivirus (Trojan)","description":"Kaspersky Antivirus falsely detects beads (bd.exe v0.23.1) as a Trojan (PDM:Trojan.Win32.Generic) and removes it.\nEvent: Malicious object detected\nComponent: System Watcher\nObject name: bd.exe\n","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-20T18:56:12.498187-05:00","updated_at":"2025-11-20T18:56:12.498187-05:00"} {"id":"bd-tbz3","title":"bd init UX Improvements","description":"bd init leaves users with incomplete setup, requiring manual bd doctor --fix. Issues found: (1) git hooks not installed if user declines prompt, (2) no auto-migration when CLI is upgraded, (3) stale merge driver configs from old versions. Fix by making bd init more robust with better defaults and auto-migration.","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-21T23:16:00.333543-08:00","updated_at":"2025-11-21T23:16:37.811233-08:00"} {"id":"bd-tggf","title":"Code Health Review Dec 2025: Technical Debt Cleanup","description":"Epic grouping technical debt identified in the Dec 16, 2025 code health review.\n\n## Overall Health Grade: B (Solid foundation, needs cleanup)\n\n### P1 (High Priority):\n- bd-74w1: Consolidate duplicate path-finding utilities\n- bd-b6xo: Remove/fix ClearDirtyIssues() race condition\n- bd-b3og: Fix TestImportBugIntegration deadlock\n\n### P2 (Medium Priority):\n- bd-05a8: Split large files (doctor.go, sync.go)\n- bd-qioh: Standardize error handling patterns\n- bd-rgyd: Split queries.go (1586 lines)\n- bd-9g1z: Fix/remove TestFindJSONLPathDefault\n\n### P3 (Low Priority):\n- bd-ork0: Add comments to 30+ ignored errors\n- bd-4nqq: Remove dead test code in info_test.go\n- bd-dhza: Reduce global state in main.go\n\n## Key Areas:\n1. Code duplication in path utilities\n2. Large monolithic files (5 files \u003e1000 lines)\n3. Global state (25+ variables, 3 deprecated)\n4. Silent error suppression (30+ instances)\n5. Test gaps and dead test code\n6. Atomicity risks in batch operations","status":"open","priority":2,"issue_type":"epic","created_at":"2025-12-16T18:18:58.115507-08:00","updated_at":"2025-12-16T18:21:50.561709-08:00"} {"id":"bd-thgk","title":"Improve test coverage for internal/compact (18.2% → 70%)","description":"The compact package has only 18.2% test coverage. This is a core package handling issue compaction and should have at least 70% coverage.\n\nKey functions needing tests:\n- runCompactSingle (0%)\n- runCompactAll (0%)\n- runCompactRPC (0%)\n- runCompactStatsRPC (0%)\n- runCompactAnalyze (0%)\n- runCompactApply (0%)\n- pruneDeletionsManifest (0%)\n\nCurrent coverage: 18.2%\nTarget coverage: 70%","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-13T20:42:58.455767-08:00","updated_at":"2025-12-13T21:01:15.070551-08:00"} +{"id":"bd-tj00","title":"Update local installation","description":"go build -o ~/.local/bin/bd ./cmd/bd \u0026\u0026 codesign -s - ~/.local/bin/bd","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-20T21:53:20.616907-08:00","updated_at":"2025-12-20T21:55:42.756171-08:00","closed_at":"2025-12-20T21:55:42.756171-08:00","close_reason":"Installed bd 0.32.1","dependencies":[{"issue_id":"bd-tj00","depends_on_id":"bd-an4s","type":"parent-child","created_at":"2025-12-20T21:53:20.619834-08:00","created_by":"daemon"},{"issue_id":"bd-tj00","depends_on_id":"bd-9l0h","type":"blocks","created_at":"2025-12-20T21:53:29.817989-08:00","created_by":"daemon"}]} {"id":"bd-tm2p","title":"Polecats get stuck on interactive shell prompts (cp/mv/rm -i)","description":"During swarm operations, polecats frequently get stuck waiting for interactive prompts from shell commands like:\n- cp prompting 'overwrite file? (y/n)'\n- mv prompting 'overwrite file? (y/n)' \n- rm prompting 'remove file?'\n\nThis happens because macOS aliases or shell configs may have -i flags set by default.\n\nRoot cause: Claude Code runs commands that trigger interactive confirmation prompts, but cannot respond to them, causing the agent to hang indefinitely.\n\nObserved in: Multiple polecats during GH issues swarm (Dec 2024)\n- Derrick, Roustabout, Prospector, Warboy all got stuck on y/n prompts\n\nSuggested fixes:\n1. AGENTS.md should instruct agents to always use -f flag with cp/mv/rm\n2. Polecat startup could set shell aliases to use non-interactive versions\n3. bd prime hook could include guidance about non-interactive commands\n4. Consider detecting stuck prompts and auto-recovering","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-14T16:51:24.572271-08:00","updated_at":"2025-12-17T23:13:40.536312-08:00","closed_at":"2025-12-17T19:13:04.074424-08:00"} {"id":"bd-toy3","title":"Test hook","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-16T18:33:39.717036-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} {"id":"bd-tvu3","title":"Improve test coverage for internal/beads (48.1% → 70%)","description":"The beads package is a core package with 48.1% coverage. As the primary package for issue management, it should have at least 70% coverage.\n\nCurrent coverage: 48.1%\nTarget coverage: 70%","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-13T20:42:59.739142-08:00","updated_at":"2025-12-13T21:01:14.874359-08:00"} {"id":"bd-u0g9","title":"GH#405: Prefix parsing with hyphens treats first segment as prefix","description":"Prefix me-py-toolkit gets parsed as just me- when detecting mismatches. Fix prefix parsing to handle multi-hyphen prefixes. See GitHub issue #405.","status":"tombstone","priority":2,"issue_type":"bug","created_at":"2025-12-16T01:03:18.354066-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} +{"id":"bd-u0sb","title":"Merge: bd-uqfn","description":"branch: polecat/cheedo\ntarget: main\nsource_issue: bd-uqfn\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-20T01:11:52.033964-08:00","updated_at":"2025-12-20T23:17:26.994875-08:00","closed_at":"2025-12-20T23:17:26.994875-08:00","close_reason":"Branches nuked, MRs obsolete"} {"id":"bd-umbf","title":"Design contributor namespace isolation for beads pollution prevention","description":"## Problem\n\nWhen contributors work on beads-the-project using beads-the-tool, their personal work-tracking issues leak into PRs. The .beads/issues.jsonl is intentionally tracked (it's the project's issue database), but contributors' local issues pollute the diff.\n\nThis is a recursion problem unique to self-hosting projects.\n\n## Possible Solutions to Explore\n\n1. **Contributor namespaces** - Each contributor gets a private prefix (e.g., `bd-steve-xxxx`) that's gitignored or filtered\n2. **Separate database** - Contributors use BEADS_DIR pointing elsewhere for personal tracking\n3. **Issue ownership/visibility flags** - Mark issues as \"local-only\" vs \"project\"\n4. **Prefix-based filtering** - Configure which prefixes are committed vs ignored\n\n## Design Considerations\n\n- Should be zero-friction for contributors (no manual setup)\n- Must not break existing workflows\n- Needs to work with sync/collaboration features\n- Consider: what if a \"personal\" issue graduates to \"project\" issue?\n\n## Expansion Needed\n\nThis is a placeholder. Needs detailed design exploration before implementation.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-13T18:00:29.638743-08:00","updated_at":"2025-12-13T18:00:41.345673-08:00"} -{"id":"bd-uqfn","title":"Work on beads-wkt: Output control parameters for MCP tool...","description":"Work on beads-wkt: Output control parameters for MCP tools (GH#622). Add brief, fields, max_description_length params to ready/list/show. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:57:10.675535-08:00","updated_at":"2025-12-20T00:26:12.606293-08:00","closed_at":"2025-12-19T23:28:25.362931-08:00"} -{"id":"bd-usro","title":"Rename 'template instantiate' to 'mol bond'","description":"Rename the template instantiation command to match molecule metaphor.\n\nCurrent: bd template instantiate \u003cid\u003e --var key=value\nTarget: bd mol bond \u003cid\u003e --var key=value\n\nChanges needed:\n- Add 'mol' command group (or extend existing)\n- Add 'bond' subcommand that wraps template instantiate logic\n- Keep 'template instantiate' as deprecated alias for backward compat\n- Update help text and docs to use molecule terminology\n\nThe 'bond' verb captures:\n1. Chemistry metaphor (molecules bond to form structures)\n2. Dependency linking (child issues bonded in a DAG)\n3. Short and active\n\nSee also: molecule execution model in Gas Town","status":"open","priority":1,"issue_type":"feature","created_at":"2025-12-20T16:56:37.582795-08:00","updated_at":"2025-12-20T16:56:37.582795-08:00"} -{"id":"bd-uutv","title":"Work on beads-rs0: Namepool configuration for themed pole...","description":"Work on beads-rs0: Namepool configuration for themed polecat names. See bd show beads-rs0 for full details.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T21:49:48.129778-08:00","updated_at":"2025-12-19T21:59:25.565894-08:00","closed_at":"2025-12-19T21:59:25.565894-08:00"} +{"id":"bd-uqfn","title":"Work on beads-wkt: Output control parameters for MCP tool...","description":"Work on beads-wkt: Output control parameters for MCP tools (GH#622). Add brief, fields, max_description_length params to ready/list/show. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:57:10.675535-08:00","updated_at":"2025-12-20T00:49:51.929271-08:00","closed_at":"2025-12-19T23:28:25.362931-08:00","close_reason":"Implemented output control parameters for MCP tools (GH#622)"} +{"id":"bd-usro","title":"Rename 'template instantiate' to 'mol bond'","description":"Rename the template instantiation command to match molecule metaphor.\n\nCurrent: bd template instantiate \u003cid\u003e --var key=value\nTarget: bd mol bond \u003cid\u003e --var key=value\n\nChanges needed:\n- Add 'mol' command group (or extend existing)\n- Add 'bond' subcommand that wraps template instantiate logic\n- Keep 'template instantiate' as deprecated alias for backward compat\n- Update help text and docs to use molecule terminology\n\nThe 'bond' verb captures:\n1. Chemistry metaphor (molecules bond to form structures)\n2. Dependency linking (child issues bonded in a DAG)\n3. Short and active\n\nSee also: molecule execution model in Gas Town","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-20T16:56:37.582795-08:00","updated_at":"2025-12-20T23:22:43.567337-08:00","closed_at":"2025-12-20T23:22:43.567337-08:00","close_reason":"Implemented mol command: catalog, show, bond"} +{"id":"bd-uutv","title":"Work on beads-rs0: Namepool configuration for themed pole...","description":"Work on beads-rs0: Namepool configuration for themed polecat names. See bd show beads-rs0 for full details.","status":"closed","priority":2,"issue_type":"task","assignee":"beads/polecat-02","created_at":"2025-12-19T21:49:48.129778-08:00","updated_at":"2025-12-19T21:59:25.565894-08:00","closed_at":"2025-12-19T21:59:25.565894-08:00","close_reason":"Completed work on beads-rs0: Implemented themed namepool feature"} {"id":"bd-vgi5","title":"Push version bump to GitHub","description":"git push origin main - triggers CI but no release yet.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T22:43:05.363604-08:00","updated_at":"2025-12-18T22:46:57.50777-08:00","closed_at":"2025-12-18T22:46:57.50777-08:00","dependencies":[{"issue_id":"bd-vgi5","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:43:16.87736-08:00","created_by":"daemon"},{"issue_id":"bd-vgi5","depends_on_id":"bd-3ggb","type":"blocks","created_at":"2025-12-18T22:43:21.078208-08:00","created_by":"daemon"}]} {"id":"bd-vpan","title":"Re: Thread Test 2","description":"Got your message. Testing reply feature.","status":"tombstone","priority":2,"issue_type":"message","created_at":"2025-12-16T18:21:29.144352-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","dependencies":[{"issue_id":"bd-vpan","depends_on_id":"bd-x36g","type":"replies-to","created_at":"2025-12-18T13:45:31.137191-08:00","created_by":"migration"}],"deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"message"} {"id":"bd-vs9","title":"Fix unparam unused parameter in cmd/bd/doctor.go:541","description":"Linting issue: checkHooksQuick - path is unused (unparam) at cmd/bd/doctor.go:541:22. Error: func checkHooksQuick(path string) string {","status":"closed","issue_type":"bug","created_at":"2025-12-07T15:35:17.02177046-07:00","updated_at":"2025-12-17T23:13:40.535743-08:00","closed_at":"2025-12-17T16:46:11.028332-08:00"} -{"id":"bd-w193","title":"Work on beads-399: Add omitempty to JSONL fields for smal...","description":"Work on beads-399: Add omitempty to JSONL fields for smaller notifications. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:55:37.440894-08:00","updated_at":"2025-12-20T00:26:12.607705-08:00","closed_at":"2025-12-19T23:23:09.542288-08:00"} +{"id":"bd-w193","title":"Work on beads-399: Add omitempty to JSONL fields for smal...","description":"Work on beads-399: Add omitempty to JSONL fields for smaller notifications. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:55:37.440894-08:00","updated_at":"2025-12-19T23:28:32.42751-08:00","closed_at":"2025-12-19T23:23:09.542288-08:00","close_reason":"Added omitempty to Description and Dependency.CreatedBy fields in types.go"} +{"id":"bd-w8g0","title":"test pin issue","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-20T22:44:27.963361-08:00","updated_at":"2025-12-20T22:44:57.977229-08:00","deleted_at":"2025-12-20T22:44:57.977229-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} {"id":"bd-wc2","title":"Test body-file","description":"This is a test description from a file.\n\nIt has multiple lines.\n","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-17T17:27:20.508724-08:00","updated_at":"2025-12-17T17:28:33.83142-08:00","closed_at":"2025-12-17T17:28:33.83142-08:00"} -{"id":"bd-whgv","title":"Merge: bd-401h","description":"branch: polecat/rictus\ntarget: main\nsource_issue: bd-401h\nrig: beads","status":"open","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:20:37.854953-08:00","updated_at":"2025-12-19T23:20:37.854953-08:00"} -{"id":"bd-x1xs","title":"Work on beads-1ra: Add molecules.jsonl as separate catalo...","description":"Work on beads-1ra: Add molecules.jsonl as separate catalog file for template molecules","status":"in_progress","priority":2,"issue_type":"task","created_at":"2025-12-19T20:17:44.840032-08:00","updated_at":"2025-12-19T20:17:44.905944-08:00"} +{"id":"bd-whgv","title":"Merge: bd-401h","description":"branch: polecat/rictus\ntarget: main\nsource_issue: bd-401h\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-19T23:20:37.854953-08:00","updated_at":"2025-12-20T23:17:26.999477-08:00","closed_at":"2025-12-20T23:17:26.999477-08:00","close_reason":"Branches nuked, MRs obsolete"} +{"id":"bd-x1xs","title":"Work on beads-1ra: Add molecules.jsonl as separate catalo...","description":"Work on beads-1ra: Add molecules.jsonl as separate catalog file for template molecules","status":"in_progress","priority":2,"issue_type":"task","assignee":"beads/polecat-01","created_at":"2025-12-19T20:17:44.840032-08:00","updated_at":"2025-12-19T20:17:44.905944-08:00"} {"id":"bd-x36g","title":"Thread Test 2","description":"Testing direct mode","status":"tombstone","priority":2,"issue_type":"message","created_at":"2025-12-16T18:21:16.470631-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"message"} +{"id":"bd-x3hi","title":"Support redirect files in .beads/ directory","description":"Gas Town creates polecat worktrees with .beads/redirect files that point to a shared beads database. The bd CLI should:\n\n1. When finding a .beads/ directory, check if it contains a 'redirect' file\n2. If redirect exists, read the relative path and use that as the beads directory\n3. This allows multiple git worktrees to share a single beads database\n\nExample:\n- polecats/alpha/.beads/redirect contains '../../mayor/rig/.beads'\n- bd commands from alpha should use mayor/rig/.beads\n\nCurrently bd ignores redirect files and either uses the local .beads/ or walks up to find a parent .beads/.\n\nRelated: gt-nriy (test message that can't be retrieved due to missing redirect support)","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-20T21:46:23.415172-08:00","updated_at":"2025-12-20T21:59:25.759664-08:00","closed_at":"2025-12-20T21:59:25.759664-08:00","close_reason":"Not applicable - filed against stale bd v0.30.6"} +{"id":"bd-x3j8","title":"Update info.go versionChanges","description":"Add 0.32.1 entry to versionChanges map in cmd/bd/info.go","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-20T21:53:17.344841-08:00","updated_at":"2025-12-20T21:54:31.906761-08:00","closed_at":"2025-12-20T21:54:31.906761-08:00","close_reason":"Added 0.32.1 to versionChanges","dependencies":[{"issue_id":"bd-x3j8","depends_on_id":"bd-an4s","type":"parent-child","created_at":"2025-12-20T21:53:17.346736-08:00","created_by":"daemon"},{"issue_id":"bd-x3j8","depends_on_id":"bd-rgd7","type":"blocks","created_at":"2025-12-20T21:53:29.62309-08:00","created_by":"daemon"}]} {"id":"bd-xctp","title":"GH#519: bd sync fails when sync.branch is currently checked-out branch","status":"tombstone","priority":2,"issue_type":"bug","created_at":"2025-12-16T01:06:05.319281-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-xj2e","title":"GH#522: Add --type flag to bd update command","description":"Add --type flag to bd update for changing issue type (task/epic/bug/feature). Storage layer already supports it. See GitHub issue #522.","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-16T01:03:12.506583-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"} {"id":"bd-y2v","title":"Refactor duplicate JSONL-from-git parsing code","description":"Both readFirstIssueFromGit() in init.go and importFromGit() in autoimport.go have similar code patterns for:\n1. Running git show \u003cref\u003e:\u003cpath\u003e\n2. Scanning the output with bufio.Scanner\n3. Parsing JSON lines\n\nCould be refactored to share a helper like:\n- readJSONLFromGit(gitRef, path string) ([]byte, error)\n- Or a streaming version: streamJSONLFromGit(gitRef, path string) (io.Reader, error)\n\nFiles:\n- cmd/bd/autoimport.go:225-256 (importFromGit)\n- cmd/bd/init.go:1212-1243 (readFirstIssueFromGit)\n\nPriority is low since code duplication is minimal and both functions work correctly.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-05T14:51:18.41124-08:00","updated_at":"2025-12-05T14:51:18.41124-08:00"} -{"id":"bd-y4vz","title":"Work on beads-eub: Consolidated context tool for MCP serv...","description":"Work on beads-eub: Consolidated context tool for MCP server (GH#636). Merge set_context, where_am_i, init into single 'context' tool. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:56:58.527144-08:00","updated_at":"2025-12-20T00:26:12.607357-08:00","closed_at":"2025-12-19T23:31:11.906952-08:00"} +{"id":"bd-y4vz","title":"Work on beads-eub: Consolidated context tool for MCP serv...","description":"Work on beads-eub: Consolidated context tool for MCP server (GH#636). Merge set_context, where_am_i, init into single 'context' tool. When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:56:58.527144-08:00","updated_at":"2025-12-20T00:49:51.929597-08:00","closed_at":"2025-12-19T23:31:11.906952-08:00","close_reason":"Completed: Consolidated set_context, where_am_i, init into unified context tool"} +{"id":"bd-y8bj","title":"Auto-detect identity from directory context for bd mail","description":"Currently bd mail inbox defaults to git user name, requiring --identity flag with exact format.\n\n## Problem\n- Mail sent to `gastown/crew/max`\n- Max runs `bd mail inbox` → defaults to 'Steve Yegge' (git user)\n- Max must know to use `--identity 'gastown/crew/max'` with exact slashes\n\n## Proposed Fix\nAuto-detect identity from directory context when in a Gas Town workspace:\n- In `/Users/stevey/gt/gastown/crew/max`, infer identity = `gastown/crew/max`\n- Pattern: `\u003ctown\u003e/\u003crig\u003e/\u003crole\u003e/\u003cname\u003e` → `\u003crig\u003e/\u003crole\u003e/\u003cname\u003e`\n\n## Additional Improvements\n1. Support GT_IDENTITY env var (set by gt crew at / session spawning)\n2. Support identity in .beads/config.yaml\n3. Normalize format: accept both slashes and dashes as equivalent\n\n## Context\nDiscovered during crew-to-crew work assignment. Max couldn't see mail despite correct nudge because identity defaulted wrong.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-20T17:22:53.938586-08:00","updated_at":"2025-12-20T18:12:58.472262-08:00","closed_at":"2025-12-20T17:58:51.034201-08:00"} {"id":"bd-y8tn","title":"Test Molecule","description":"A test molecule","status":"closed","priority":2,"issue_type":"molecule","created_at":"2025-12-19T18:30:24.491279-08:00","updated_at":"2025-12-19T18:31:12.49898-08:00","closed_at":"2025-12-19T18:31:12.49898-08:00","close_reason":"test molecule - deleting"} {"id":"bd-yck","title":"Fix checkExistingBeadsData to be worktree-aware","description":"The checkExistingBeadsData function in cmd/bd/init.go checks for .beads in the current working directory, but for worktrees it should check the main repository root instead. This prevents proper worktree compatibility.","status":"open","priority":2,"issue_type":"bug","created_at":"2025-12-07T16:48:32.082776345-07:00","updated_at":"2025-12-07T16:48:32.082776345-07:00"} {"id":"bd-ykd9","title":"Add bd doctor --fix flag to automatically repair issues","description":"Implement a --fix flag for bd doctor that can automatically repair detected issues.\n\nRequirements:\n- Add --fix flag to bd doctor command\n- Show all fixable issues and prompt for confirmation before applying fixes\n- Organize fix implementations under doctor/fix/\u003ctype_of_fix\u003e.go\n- Each fix type should have its own file (e.g., doctor/fix/hooks.go, doctor/fix/sync.go)\n- Display what will be fixed and ask user to confirm (Y/n) before proceeding\n- Support fixing issues like:\n - Missing or broken git hooks\n - Sync problems with remote\n - File permission issues\n - Any other auto-repairable issues doctor detects\n\nImplementation notes:\n- Maintain separation between detection (existing doctor code) and repair (new fix code)\n- Each fix should be idempotent and safe to run multiple times\n- Provide clear output about what was fixed\n- Log any fixes that fail with actionable error messages","status":"open","priority":2,"issue_type":"feature","created_at":"2025-11-14T18:17:48.411264-08:00","updated_at":"2025-11-14T18:17:58.88609-08:00"} {"id":"bd-z86n","title":"Code Review: PR #551 - Persist close_reason to issues table","description":"Code review of PR #551 which fixes close_reason persistence bug.\n\n## Summary\nThe PR correctly fixes a bug where close_reason was only stored in the events table, not in the issues.close_reason column. This caused `bd show --json` to return empty close_reason.\n\n## What Was Fixed\n- āœ… CloseIssue now updates both close_reason and closed_at\n- āœ… ReOpenIssue clears both close_reason and closed_at\n- āœ… Comprehensive tests added for both storage and CLI layers\n- āœ… Clear documentation in queries.go about dual storage strategy\n\n## Quality Assessment\nāœ… Tests cover both storage layer and CLI JSON output\nāœ… Handles reopen case (clearing close_reason)\nāœ… Good comments explaining dual-storage design\nāœ… No known issues\n\n## Potential Followups\nSee linked issues for suggestions.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-14T14:25:06.887069-08:00","updated_at":"2025-12-14T14:25:06.887069-08:00"} {"id":"bd-z8a6","title":"bd delete --from-file should add deleted issues to deletions manifest","description":"When using bd delete --from-file to bulk delete issues, the deleted issue IDs are not being added to the deletions.jsonl manifest.\n\nThis causes those issues to be resurrected during bd sync when git history scanning finds them in old commits.\n\nExpected: All deleted issues should be added to deletions.jsonl so they wont be reimported from git history.\n\nWorkaround: Manually add deletion records to deletions.jsonl.","status":"tombstone","priority":1,"issue_type":"bug","created_at":"2025-12-16T01:48:14.099855-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-zc3","title":"Add --pinned and --no-pinned flags to bd list","description":"Add filtering flags to bd list: --pinned shows only pinned issues, --no-pinned excludes pinned issues. Default behavior shows all issues with a pin indicator.","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-18T23:33:29.518028-08:00","updated_at":"2025-12-18T23:33:29.518028-08:00","dependencies":[{"issue_id":"bd-zc3","depends_on_id":"bd-0vg","type":"blocks","created_at":"2025-12-18T23:33:56.256764-08:00","created_by":"daemon"},{"issue_id":"bd-zc3","depends_on_id":"bd-7h5","type":"blocks","created_at":"2025-12-18T23:34:07.486361-08:00","created_by":"daemon"}]} -{"id":"bd-zclk","title":"Commit and push release","description":"git add -A \u0026\u0026 git commit \u0026\u0026 git push to trigger CI","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:56:48.6489-08:00","updated_at":"2025-12-19T22:57:58.978614-08:00","closed_at":"2025-12-19T22:57:58.978614-08:00","close_reason":"Pushed to main","dependencies":[{"issue_id":"bd-zclk","depends_on_id":"bd-8pyn","type":"parent-child","created_at":"2025-12-19T22:56:48.650043-08:00","created_by":"stevey"},{"issue_id":"bd-zclk","depends_on_id":"bd-hzvz","type":"blocks","created_at":"2025-12-19T22:56:48.650488-08:00","created_by":"stevey"}]} +{"id":"bd-zf5w","title":"bd mail uses git user.name for sender instead of BEADS_AGENT_NAME","description":"When sending mail via `bd mail send`, the sender field in the stored issue uses git config user.name instead of the BEADS_AGENT_NAME environment variable.\n\nReproduction:\n1. Set BEADS_AGENT_NAME=gastown-alpha\n2. Run: bd mail send mayor/ -s 'Test' -m 'Body'\n3. Check the issue.jsonl: sender is 'Steve Yegge' (git user.name) not 'gastown-alpha'\n\nExpected: The sender field should use BEADS_AGENT_NAME when set.\n\nThis breaks the mail system for multi-agent workflows where agents need to identify themselves by their role (polecat, refinery, etc.) rather than the human user's git identity.\n\nRelated: gt mail routing integration with Gas Town","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-20T21:46:33.646746-08:00","updated_at":"2025-12-20T21:59:25.771325-08:00","closed_at":"2025-12-20T21:59:25.771325-08:00","close_reason":"Not applicable - filed against stale bd v0.30.6"} {"id":"bd-ziy5","title":"GH#409: bd init uses issues.jsonl but docs say beads.jsonl","description":"bd init creates config referencing issues.jsonl but README/docs reference beads.jsonl as canonical. Standardize naming. See GitHub issue #409.","status":"tombstone","priority":2,"issue_type":"bug","created_at":"2025-12-16T01:03:58.109954-08:00","updated_at":"2025-12-17T16:11:17.070763-08:00","deleted_at":"2025-12-17T16:11:17.070763-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-zwtq","title":"Run bd doctor at end of bd init to verify setup","description":"Run bd doctor diagnostics at end of bd init (after line 398 in init.go). If issues found, warn user immediately: '⚠ Setup incomplete. Run bd doctor --fix to complete setup.' Catches configuration problems before user encounters them in normal workflow.","status":"open","priority":1,"issue_type":"feature","created_at":"2025-11-21T23:16:09.596778-08:00","updated_at":"2025-11-21T23:16:27.781976-08:00","dependencies":[{"issue_id":"bd-zwtq","depends_on_id":"bd-tbz3","type":"parent-child","created_at":"2025-11-21T23:16:09.597617-08:00","created_by":"daemon","metadata":"{}"}]} diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 23cfeaf5..eae5790a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "beads", "source": "./", "description": "AI-supervised issue tracker for coding workflows", - "version": "0.31.0" + "version": "0.32.1" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 260d02ee..85474ea6 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "beads", "description": "AI-supervised issue tracker for coding workflows. Manage tasks, discover work, and maintain context with simple CLI commands.", - "version": "0.31.0", + "version": "0.32.1", "author": { "name": "Steve Yegge", "url": "https://github.com/steveyegge" diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7f21a0..869da647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.32.1] - 2025-12-20 + +## [0.32.1] - 2025-12-21 + +### Added + +- **MCP output control parameters** (PR#667) - Reduce context window usage by up to 97% + - `brief` - Return minimal responses: `BriefIssue` for reads, `OperationResult` for writes + - `brief_deps` - Full issue with compact dependencies + - `fields` - Custom field projection with validation + - `max_description_length` - Truncate long descriptions + - New models: `BriefIssue`, `BriefDep`, `OperationResult` + - Default `brief=True` for writes (minimal confirmations) + +- **MCP filtering parameters** - Align MCP tools with CLI capabilities + - `labels` / `labels_any` - AND/OR label filtering + - `query` - Title search (case-insensitive) + - `unassigned` - Filter to unassigned issues + - `sort_policy` - Sort ready work by hybrid/priority/oldest + +### Fixed + +- **Pin field not in allowed update fields** (gt-zr0a) + - `bd update --pinned` was failing with "invalid field" error + - Added `pinned` to allowedUpdateFields and importer + +## [0.32.0] - 2025-12-20 + +### Changed + +- **Removed `bd mail` commands** - Mail is orchestration, not data plane + - Removed: `bd mail send`, `bd mail inbox`, `bd mail read`, `bd mail ack`, `bd mail reply` + - The underlying data model is unchanged: `type=message`, `Sender`, `Ephemeral`, `replies_to` fields remain + - Orchestration tools should implement mail interfaces on top of the beads data model + - This follows the principle that beads is a data store, not a workflow engine + +### Fixed + +- **Symlink preservation in atomicWriteFile** (PR#665) - Thanks @qmx + - `bd setup claude` no longer clobbers nix/home-manager managed `~/.claude/settings.json` + - New `ResolveForWrite()` helper writes to symlink target instead of replacing symlink + +- **Broken link in examples** (GH#666) - Fixed LABELS.md link in multiple-personas example + ## [0.31.0] - 2025-12-20 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 39d2b07f..1c4aa898 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,7 +19,7 @@ the overseer, not as part of a swarm. **Your mail address:** `beads/dave` -Check your mail with: `bd mail inbox --identity beads-dave` +Check your mail with: `gt mail inbox` (mail is handled by Gas Town, not beads) ## Gas Town Architecture @@ -38,7 +38,7 @@ Town (/Users/stevey/gt) ## Key Commands ### Finding Work -- `bd mail inbox --identity beads-dave` - Check your inbox +- `gt mail inbox` - Check your inbox (mail is in Gas Town) - `bd ready` - Available issues - `bd list --status=in_progress` - Your active work @@ -49,8 +49,8 @@ Town (/Users/stevey/gt) - `bd sync` - Sync beads changes ### Communication -- `bd mail send mayor -s "Subject" -m "Message"` - To Mayor -- `bd mail send beads-dave -s "Subject" -m "Message"` - To yourself (handoff) +- `gt mail send mayor/ -s "Subject" -m "Message"` - To Mayor +- `gt mail send beads/dave -s "Subject" -m "Message"` - To yourself (handoff) ## Beads Database @@ -113,19 +113,12 @@ bd info --whats-new # 2. Update git hooks to match new bd version bd hooks install -# 3. Regenerate BD_GUIDE.md if it exists (optional but recommended) -bd onboard --output .beads/BD_GUIDE.md - -# 4. Check for any outdated hooks (optional) +# 3. Check for any outdated hooks (optional) bd info # Shows warnings if hooks are outdated ``` **Why update hooks?** Git hooks (pre-commit, post-merge, pre-push) are versioned with bd. Outdated hooks may miss new auto-sync features or bug fixes. Running `bd hooks install` ensures hooks match your bd version. -**About BD_GUIDE.md:** This is an optional auto-generated file that separates bd-specific instructions from project-specific ones. If your project uses this file (in `.beads/BD_GUIDE.md`), regenerate it after upgrades to get the latest bd documentation. The file is version-stamped and should never be manually edited. - -**Related:** See GitHub Discussion #239 for background on agent upgrade workflows. - ## Human Setup vs Agent Usage **IMPORTANT:** If you need to initialize bd, use the `--quiet` flag: @@ -361,47 +354,6 @@ bd create "Add feature" -t feature --json # What feature? Why needed? bd create "Refactor code" -t task --json # What code? Why refactor? ``` -### Inter-Agent Messaging (bd mail) - -Beads includes a built-in messaging system for direct agent-to-agent communication. Messages are stored as beads issues, synced via git. - -**Setup:** - -```bash -# Set your identity (add to environment or .beads/config.json) -export BEADS_IDENTITY="worker-1" -``` - -**Commands:** - -```bash -# Send a message -bd mail send -s "Subject" -m "Body" -bd mail send worker-2 -s "Handoff" -m "Your turn on bd-xyz" --urgent - -# Check your inbox -bd mail inbox - -# Read a specific message -bd mail read bd-a1b2 - -# Acknowledge (mark as read/close) -bd mail ack bd-a1b2 - -# Reply to a message (creates thread) -bd mail reply bd-a1b2 -m "Thanks, on it!" -``` - -**Use cases:** -- Task handoffs between agents -- Status updates to coordinator -- Blocking questions requiring response -- Priority signaling with `--urgent` flag - -**Cleanup:** Messages are ephemeral. Run `bd cleanup --ephemeral --force` to delete closed messages. - -See [docs/messaging.md](docs/messaging.md) for full documentation. - ### Deletion Tracking When issues are deleted (via `bd delete` or `bd cleanup`), they are recorded in `.beads/deletions.jsonl`. This manifest: diff --git a/cmd/bd/info.go b/cmd/bd/info.go index 2f064d02..dbf08396 100644 --- a/cmd/bd/info.go +++ b/cmd/bd/info.go @@ -288,6 +288,27 @@ type VersionChange struct { // versionChanges contains agent-actionable changes for recent versions var versionChanges = []VersionChange{ + { + Version: "0.32.1", + Date: "2025-12-21", + Changes: []string{ + "NEW: MCP output control params (PR#667) - brief, brief_deps, fields, max_description_length", + "NEW: MCP filtering params - labels, labels_any, query, unassigned, sort_policy", + "NEW: BriefIssue, BriefDep, OperationResult models for 97% context reduction", + "FIX: Pin field not in allowed update fields (gt-zr0a) - bd update --pinned now works", + }, + }, + { + Version: "0.32.0", + Date: "2025-12-20", + Changes: []string{ + "REMOVED: bd mail commands (send, inbox, read, ack, reply) - Mail is orchestration, not data plane", + "NOTE: Data model unchanged - type=message, Sender, Ephemeral, replies_to fields remain", + "NOTE: Orchestration tools should implement mail UI on top of beads data model", + "FIX: Symlink preservation in atomicWriteFile (PR#665) - bd setup no longer clobbers nix/home-manager configs", + "FIX: Broken link to LABELS.md in examples (GH#666)", + }, + }, { Version: "0.31.0", Date: "2025-12-20", diff --git a/cmd/bd/list.go b/cmd/bd/list.go index f758651d..9e4cd3f4 100644 --- a/cmd/bd/list.go +++ b/cmd/bd/list.go @@ -39,9 +39,9 @@ func parseTimeFlag(s string) (time.Time, error) { return time.Time{}, fmt.Errorf("unable to parse time %q (try formats: 2006-01-02, 2006-01-02T15:04:05, or RFC3339)", s) } -// pinIndicator returns a pushpin emoji prefix for pinned issues (bd-18b) +// pinIndicator returns a pushpin emoji prefix for pinned issues (bd-18b, bd-7h5) func pinIndicator(issue *types.Issue) string { - if issue.Status == types.StatusPinned { + if issue.Pinned { return "šŸ“Œ " } return "" diff --git a/cmd/bd/mail.go b/cmd/bd/mail.go deleted file mode 100644 index 62825ab1..00000000 --- a/cmd/bd/mail.go +++ /dev/null @@ -1,728 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - "strings" - "time" - - "github.com/spf13/cobra" - "github.com/steveyegge/beads/internal/config" - "github.com/steveyegge/beads/internal/hooks" - "github.com/steveyegge/beads/internal/rpc" - "github.com/steveyegge/beads/internal/types" -) - -var mailCmd = &cobra.Command{ - Use: "mail", - GroupID: "advanced", - Short: "Send and receive messages via beads", - Long: `Send and receive messages between agents using beads storage. - -Messages are stored as issues with type=message, enabling git-native -inter-agent communication without external services. - -Examples: - bd mail send worker-1 -s "Task complete" -m "Finished bd-xyz" - bd mail inbox - bd mail read bd-abc123 - bd mail ack bd-abc123`, -} - -var mailSendCmd = &cobra.Command{ - Use: "send -s -m ", - Short: "Send a message to another agent", - Long: `Send a message to another agent via beads. - -Creates an issue with type=message, sender=your identity, assignee=recipient. -The --urgent flag sets priority=0. Use --priority for explicit priority control. - -Priority levels: 0=critical/urgent, 1=high, 2=normal (default), 3=low, 4=backlog - -Examples: - bd mail send worker-1 -s "Task complete" -m "Finished bd-xyz" - bd mail send worker-1 -s "Help needed" -m "Blocked on auth" --urgent - bd mail send worker-1 -s "Quick note" -m "FYI" --priority 3 - bd mail send worker-1 -s "Task" -m "Do this" --type task --priority 1 - bd mail send worker-1 -s "Re: Hello" -m "Hi back" --reply-to bd-abc123 - bd mail send worker-1 -s "Quick note" -m "FYI" --identity refinery`, - Args: cobra.ExactArgs(1), - RunE: runMailSend, -} - -var mailInboxCmd = &cobra.Command{ - Use: "inbox", - Short: "List messages addressed to you", - Long: `List open messages where assignee matches your identity. - -Messages are sorted by priority (urgent first), then by date (newest first). - -Examples: - bd mail inbox - bd mail inbox --from worker-1 - bd mail inbox --priority 0`, - RunE: runMailInbox, -} - -var mailReadCmd = &cobra.Command{ - Use: "read ", - Short: "Read a specific message", - Long: `Display the full content of a message. - -Does NOT mark the message as read - use 'bd mail ack' for that. - -Example: - bd mail read bd-abc123`, - Args: cobra.ExactArgs(1), - RunE: runMailRead, -} - -var mailAckCmd = &cobra.Command{ - Use: "ack [id2...]", - Short: "Acknowledge (close) messages", - Long: `Mark messages as read by closing them. - -Can acknowledge multiple messages at once. - -Examples: - bd mail ack bd-abc123 - bd mail ack bd-abc123 bd-def456 bd-ghi789`, - Args: cobra.MinimumNArgs(1), - RunE: runMailAck, -} - -var mailReplyCmd = &cobra.Command{ - Use: "reply -m ", - Short: "Reply to a message", - Long: `Reply to an existing message, creating a conversation thread. - -Creates a new message with replies_to set to the original message, -and sends it to the original sender. - -Examples: - bd mail reply bd-abc123 -m "Thanks for the update!" - bd mail reply bd-abc123 -m "Done" --urgent`, - Args: cobra.ExactArgs(1), - RunE: runMailReply, -} - -// Mail command flags -var ( - mailSubject string - mailBody string - mailUrgent bool - mailIdentity string - mailFrom string - mailPriorityFlag int - // New flags for GGT compatibility (gt-8j8e) - mailSendPriority int // Numeric priority for mail send (0-4) - mailMsgType string // Message type (task, scavenge, notification, reply) - mailThreadID string // Thread ID for conversation grouping - mailReplyTo string // Message ID being replied to -) - -func init() { - rootCmd.AddCommand(mailCmd) - mailCmd.AddCommand(mailSendCmd) - mailCmd.AddCommand(mailInboxCmd) - mailCmd.AddCommand(mailReadCmd) - mailCmd.AddCommand(mailAckCmd) - mailCmd.AddCommand(mailReplyCmd) - - // Send command flags - mailSendCmd.Flags().StringVarP(&mailSubject, "subject", "s", "", "Message subject (required)") - mailSendCmd.Flags().StringVarP(&mailBody, "body", "m", "", "Message body (required)") - mailSendCmd.Flags().BoolVar(&mailUrgent, "urgent", false, "Set priority=0 (urgent)") - mailSendCmd.Flags().StringVar(&mailIdentity, "identity", "", "Override sender identity") - // GGT compatibility flags (gt-8j8e) - mailSendCmd.Flags().IntVar(&mailSendPriority, "priority", -1, "Message priority (0-4, where 0=urgent)") - mailSendCmd.Flags().StringVar(&mailMsgType, "type", "", "Message type (task, scavenge, notification, reply)") - mailSendCmd.Flags().StringVar(&mailThreadID, "thread-id", "", "Thread ID for conversation grouping") - mailSendCmd.Flags().StringVar(&mailReplyTo, "reply-to", "", "Message ID this is replying to") - _ = mailSendCmd.MarkFlagRequired("subject") - _ = mailSendCmd.MarkFlagRequired("body") - - // Inbox command flags - mailInboxCmd.Flags().StringVar(&mailFrom, "from", "", "Filter by sender") - mailInboxCmd.Flags().IntVar(&mailPriorityFlag, "priority", -1, "Filter by priority (0-4)") - mailInboxCmd.Flags().StringVar(&mailIdentity, "identity", "", "Override recipient identity") - - // Read command flags - mailReadCmd.Flags().StringVar(&mailIdentity, "identity", "", "Override identity for access check") - - // Ack command flags - mailAckCmd.Flags().StringVar(&mailIdentity, "identity", "", "Override identity") - - // Reply command flags - mailReplyCmd.Flags().StringVarP(&mailBody, "body", "m", "", "Reply body (required)") - mailReplyCmd.Flags().BoolVar(&mailUrgent, "urgent", false, "Set priority=0 (urgent)") - mailReplyCmd.Flags().StringVar(&mailIdentity, "identity", "", "Override sender identity") - _ = mailReplyCmd.MarkFlagRequired("body") -} - -func runMailSend(cmd *cobra.Command, args []string) error { - CheckReadonly("mail send") - - recipient := args[0] - sender := config.GetIdentity(mailIdentity) - - // Determine priority (gt-8j8e: --priority takes precedence over --urgent) - priority := 2 // default: normal - if cmd.Flags().Changed("priority") && mailSendPriority >= 0 && mailSendPriority <= 4 { - priority = mailSendPriority - } else if mailUrgent { - priority = 0 - } - - // Build labels for GGT metadata (gt-8j8e) - var labels []string - if mailMsgType != "" { - labels = append(labels, "msg-type:"+mailMsgType) - } - if mailThreadID != "" { - labels = append(labels, "thread:"+mailThreadID) - } - - // If daemon is running, use RPC - if daemonClient != nil { - createArgs := &rpc.CreateArgs{ - Title: mailSubject, - Description: mailBody, - IssueType: string(types.TypeMessage), - Priority: priority, - Assignee: recipient, - Sender: sender, - Ephemeral: true, // Messages can be bulk-deleted - Labels: labels, - RepliesTo: mailReplyTo, // Thread link (gt-8j8e) - } - - resp, err := daemonClient.Create(createArgs) - if err != nil { - return fmt.Errorf("failed to send message: %w", err) - } - - // Parse response to get issue ID - var issue types.Issue - if err := json.Unmarshal(resp.Data, &issue); err != nil { - return fmt.Errorf("parsing response: %w", err) - } - - // Run message hook (bd-kwro.8) - if hookRunner != nil { - hookRunner.Run(hooks.EventMessage, &issue) - } - - if jsonOutput { - result := map[string]interface{}{ - "id": issue.ID, - "to": recipient, - "from": sender, - "subject": mailSubject, - "priority": priority, - "timestamp": issue.CreatedAt, - } - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - return encoder.Encode(result) - } - - fmt.Printf("Message sent: %s\n", issue.ID) - fmt.Printf(" To: %s\n", recipient) - fmt.Printf(" Subject: %s\n", mailSubject) - if priority <= 1 { - fmt.Printf(" Priority: P%d\n", priority) - } - return nil - } - - // Direct mode - now := time.Now() - issue := &types.Issue{ - Title: mailSubject, - Description: mailBody, - Status: types.StatusOpen, - Priority: priority, - IssueType: types.TypeMessage, - Assignee: recipient, - Sender: sender, - Ephemeral: true, // Messages can be bulk-deleted - Labels: labels, - CreatedAt: now, - UpdatedAt: now, - } - - if err := store.CreateIssue(rootCtx, issue, actor); err != nil { - return fmt.Errorf("failed to send message: %w", err) - } - - // Add reply-to dependency if specified (gt-8j8e) - if mailReplyTo != "" { - dep := &types.Dependency{ - IssueID: issue.ID, - DependsOnID: mailReplyTo, - Type: types.DepRepliesTo, - CreatedAt: now, - CreatedBy: actor, - } - if err := store.AddDependency(rootCtx, dep, actor); err != nil { - // Log but don't fail - the message was still sent - fmt.Fprintf(os.Stderr, "Warning: failed to create reply-to link: %v\n", err) - } - } - - // Trigger auto-flush - if flushManager != nil { - flushManager.MarkDirty(false) - } - - // Run message hook (bd-kwro.8) - if hookRunner != nil { - hookRunner.Run(hooks.EventMessage, issue) - } - - if jsonOutput { - result := map[string]interface{}{ - "id": issue.ID, - "to": recipient, - "from": sender, - "subject": mailSubject, - "priority": priority, - "timestamp": issue.CreatedAt, - } - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - return encoder.Encode(result) - } - - fmt.Printf("Message sent: %s\n", issue.ID) - fmt.Printf(" To: %s\n", recipient) - fmt.Printf(" Subject: %s\n", mailSubject) - if priority <= 1 { - fmt.Printf(" Priority: P%d\n", priority) - } - - return nil -} - -func runMailInbox(cmd *cobra.Command, args []string) error { - identity := config.GetIdentity(mailIdentity) - - // Query for open messages assigned to this identity - messageType := types.TypeMessage - openStatus := types.StatusOpen - filter := types.IssueFilter{ - IssueType: &messageType, - Status: &openStatus, - Assignee: &identity, - } - - var issues []*types.Issue - var err error - - if daemonClient != nil { - // Daemon mode - use RPC list - resp, rpcErr := daemonClient.List(&rpc.ListArgs{ - Status: string(openStatus), - IssueType: string(messageType), - Assignee: identity, - }) - if rpcErr != nil { - return fmt.Errorf("failed to fetch inbox: %w", rpcErr) - } - if err := json.Unmarshal(resp.Data, &issues); err != nil { - return fmt.Errorf("parsing response: %w", err) - } - } else { - // Direct mode - issues, err = store.SearchIssues(rootCtx, "", filter) - if err != nil { - return fmt.Errorf("failed to fetch inbox: %w", err) - } - } - - // Filter by sender if specified - var filtered []*types.Issue - for _, issue := range issues { - if mailFrom != "" && issue.Sender != mailFrom { - continue - } - // Filter by priority if specified - if cmd.Flags().Changed("priority") && mailPriorityFlag >= 0 && issue.Priority != mailPriorityFlag { - continue - } - filtered = append(filtered, issue) - } - - // Sort by priority (ascending), then by date (descending) - // Priority 0 is highest priority - for i := 0; i < len(filtered)-1; i++ { - for j := i + 1; j < len(filtered); j++ { - swap := false - if filtered[i].Priority > filtered[j].Priority { - swap = true - } else if filtered[i].Priority == filtered[j].Priority { - if filtered[i].CreatedAt.Before(filtered[j].CreatedAt) { - swap = true - } - } - if swap { - filtered[i], filtered[j] = filtered[j], filtered[i] - } - } - } - - if jsonOutput { - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - return encoder.Encode(filtered) - } - - if len(filtered) == 0 { - fmt.Printf("No messages for %s\n", identity) - return nil - } - - fmt.Printf("Inbox for %s (%d messages):\n\n", identity, len(filtered)) - for _, msg := range filtered { - // Format timestamp - age := time.Since(msg.CreatedAt) - var timeStr string - if age < time.Hour { - timeStr = fmt.Sprintf("%dm ago", int(age.Minutes())) - } else if age < 24*time.Hour { - timeStr = fmt.Sprintf("%dh ago", int(age.Hours())) - } else { - timeStr = fmt.Sprintf("%dd ago", int(age.Hours()/24)) - } - - // Priority indicator - priorityStr := "" - if msg.Priority == 0 { - priorityStr = " [URGENT]" - } else if msg.Priority == 1 { - priorityStr = " [HIGH]" - } - - fmt.Printf(" %s: %s%s\n", msg.ID, msg.Title, priorityStr) - fmt.Printf(" From: %s (%s)\n", msg.Sender, timeStr) - // NOTE: Thread info now in dependencies (Decision 004) - fmt.Println() - } - - return nil -} - -func runMailRead(cmd *cobra.Command, args []string) error { - messageID := args[0] - - var issue *types.Issue - - if daemonClient != nil { - // Daemon mode - use RPC show - resp, err := daemonClient.Show(&rpc.ShowArgs{ID: messageID}) - if err != nil { - return fmt.Errorf("failed to read message: %w", err) - } - if err := json.Unmarshal(resp.Data, &issue); err != nil { - return fmt.Errorf("parsing response: %w", err) - } - } else { - // Direct mode - var err error - issue, err = store.GetIssue(rootCtx, messageID) - if err != nil { - return fmt.Errorf("failed to read message: %w", err) - } - } - - if issue == nil { - return fmt.Errorf("message not found: %s", messageID) - } - - if issue.IssueType != types.TypeMessage { - return fmt.Errorf("%s is not a message (type: %s)", messageID, issue.IssueType) - } - - if jsonOutput { - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - return encoder.Encode(issue) - } - - // Display message - fmt.Println(strings.Repeat("─", 66)) - fmt.Printf("ID: %s\n", issue.ID) - fmt.Printf("From: %s\n", issue.Sender) - fmt.Printf("To: %s\n", issue.Assignee) - fmt.Printf("Subject: %s\n", issue.Title) - fmt.Printf("Time: %s\n", issue.CreatedAt.Format("2006-01-02 15:04:05")) - if issue.Priority <= 1 { - fmt.Printf("Priority: P%d\n", issue.Priority) - } - // NOTE: Thread info (RepliesTo) now in dependencies (Decision 004) - fmt.Printf("Status: %s\n", issue.Status) - fmt.Println(strings.Repeat("─", 66)) - fmt.Println() - fmt.Println(issue.Description) - fmt.Println() - - return nil -} - -func runMailAck(cmd *cobra.Command, args []string) error { - CheckReadonly("mail ack") - - var acked []string - var errors []string - - for _, messageID := range args { - var issue *types.Issue - - if daemonClient != nil { - // Daemon mode - use RPC - resp, err := daemonClient.Show(&rpc.ShowArgs{ID: messageID}) - if err != nil { - errors = append(errors, fmt.Sprintf("%s: %v", messageID, err)) - continue - } - if err := json.Unmarshal(resp.Data, &issue); err != nil { - errors = append(errors, fmt.Sprintf("%s: parse error: %v", messageID, err)) - continue - } - } else { - // Direct mode - var err error - issue, err = store.GetIssue(rootCtx, messageID) - if err != nil { - errors = append(errors, fmt.Sprintf("%s: %v", messageID, err)) - continue - } - } - - if issue == nil { - errors = append(errors, fmt.Sprintf("%s: not found", messageID)) - continue - } - - if issue.IssueType != types.TypeMessage { - errors = append(errors, fmt.Sprintf("%s: not a message (type: %s)", messageID, issue.IssueType)) - continue - } - - if issue.Status == types.StatusClosed { - errors = append(errors, fmt.Sprintf("%s: already acknowledged", messageID)) - continue - } - - // Close the message - if daemonClient != nil { - // Daemon mode - use RPC close - _, err := daemonClient.CloseIssue(&rpc.CloseArgs{ID: messageID}) - if err != nil { - errors = append(errors, fmt.Sprintf("%s: %v", messageID, err)) - continue - } - // Fire close hook for GGT notifications (daemon mode) - if hookRunner != nil { - hookRunner.Run(hooks.EventClose, issue) - } - } else { - // Direct mode - use CloseIssue for proper close handling - if err := store.CloseIssue(rootCtx, messageID, "acknowledged", actor); err != nil { - errors = append(errors, fmt.Sprintf("%s: %v", messageID, err)) - continue - } - // Fire close hook for GGT notifications (direct mode) - if hookRunner != nil { - hookRunner.Run(hooks.EventClose, issue) - } - } - - acked = append(acked, messageID) - } - - // Trigger auto-flush if any messages were acked (direct mode only) - if len(acked) > 0 && flushManager != nil { - flushManager.MarkDirty(false) - } - - if jsonOutput { - result := map[string]interface{}{ - "acknowledged": acked, - "errors": errors, - } - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - return encoder.Encode(result) - } - - for _, id := range acked { - fmt.Printf("Acknowledged: %s\n", id) - } - for _, errMsg := range errors { - fmt.Fprintf(os.Stderr, "Error: %s\n", errMsg) - } - - if len(errors) > 0 && len(acked) == 0 { - return fmt.Errorf("failed to acknowledge any messages") - } - - return nil -} - -func runMailReply(cmd *cobra.Command, args []string) error { - CheckReadonly("mail reply") - - messageID := args[0] - sender := config.GetIdentity(mailIdentity) - - // Get the original message - var originalMsg *types.Issue - - if daemonClient != nil { - resp, err := daemonClient.Show(&rpc.ShowArgs{ID: messageID}) - if err != nil { - return fmt.Errorf("failed to get original message: %w", err) - } - if err := json.Unmarshal(resp.Data, &originalMsg); err != nil { - return fmt.Errorf("parsing response: %w", err) - } - } else { - var err error - originalMsg, err = store.GetIssue(rootCtx, messageID) - if err != nil { - return fmt.Errorf("failed to get original message: %w", err) - } - } - - if originalMsg == nil { - return fmt.Errorf("message not found: %s", messageID) - } - - if originalMsg.IssueType != types.TypeMessage { - return fmt.Errorf("%s is not a message (type: %s)", messageID, originalMsg.IssueType) - } - - // Determine recipient: reply goes to the original sender - recipient := originalMsg.Sender - if recipient == "" { - return fmt.Errorf("original message has no sender, cannot determine reply recipient") - } - - // Build reply subject - subject := originalMsg.Title - if !strings.HasPrefix(strings.ToLower(subject), "re:") { - subject = "Re: " + subject - } - - // Determine priority - priority := 2 // default: normal - if mailUrgent { - priority = 0 - } - - // Create the reply message - now := time.Now() - reply := &types.Issue{ - Title: subject, - Description: mailBody, - Status: types.StatusOpen, - Priority: priority, - IssueType: types.TypeMessage, - Assignee: recipient, - Sender: sender, - Ephemeral: true, - // NOTE: RepliesTo now handled via dependency API (Decision 004) - CreatedAt: now, - UpdatedAt: now, - } - _ = messageID // RepliesTo handled via CreateArgs.RepliesTo -> server creates dependency - - if daemonClient != nil { - // Daemon mode - create reply with all messaging fields - createArgs := &rpc.CreateArgs{ - Title: reply.Title, - Description: reply.Description, - IssueType: string(types.TypeMessage), - Priority: priority, - Assignee: recipient, - Sender: sender, - Ephemeral: true, - RepliesTo: messageID, // Thread link - } - - resp, err := daemonClient.Create(createArgs) - if err != nil { - return fmt.Errorf("failed to send reply: %w", err) - } - - var createdIssue types.Issue - if err := json.Unmarshal(resp.Data, &createdIssue); err != nil { - return fmt.Errorf("parsing response: %w", err) - } - - if jsonOutput { - result := map[string]interface{}{ - "id": createdIssue.ID, - "to": recipient, - "from": sender, - "subject": subject, - "replies_to": messageID, - "priority": priority, - "timestamp": createdIssue.CreatedAt, - } - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - return encoder.Encode(result) - } - - fmt.Printf("Reply sent: %s\n", createdIssue.ID) - fmt.Printf(" To: %s\n", recipient) - fmt.Printf(" Re: %s\n", messageID) - if mailUrgent { - fmt.Printf(" Priority: URGENT\n") - } - return nil - } - - // Direct mode - if err := store.CreateIssue(rootCtx, reply, actor); err != nil { - return fmt.Errorf("failed to send reply: %w", err) - } - - // Trigger auto-flush - if flushManager != nil { - flushManager.MarkDirty(false) - } - - // Fire message hook for GGT notifications - if hookRunner != nil { - hookRunner.Run(hooks.EventMessage, reply) - } - - if jsonOutput { - result := map[string]interface{}{ - "id": reply.ID, - "to": recipient, - "from": sender, - "subject": subject, - "replies_to": messageID, - "priority": priority, - "timestamp": reply.CreatedAt, - } - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - return encoder.Encode(result) - } - - fmt.Printf("Reply sent: %s\n", reply.ID) - fmt.Printf(" To: %s\n", recipient) - fmt.Printf(" Re: %s\n", messageID) - if mailUrgent { - fmt.Printf(" Priority: URGENT\n") - } - - return nil -} diff --git a/cmd/bd/mail_test.go b/cmd/bd/mail_test.go deleted file mode 100644 index 1986d44e..00000000 --- a/cmd/bd/mail_test.go +++ /dev/null @@ -1,392 +0,0 @@ -package main - -import ( - "context" - "testing" - "time" - - "github.com/steveyegge/beads/internal/types" -) - -func TestMailSendAndInbox(t *testing.T) { - tmpDir := t.TempDir() - testStore := newTestStore(t, tmpDir+"/.beads/beads.db") - ctx := context.Background() - - // Set up global state - oldStore := store - oldRootCtx := rootCtx - oldActor := actor - store = testStore - rootCtx = ctx - actor = "test-user" - defer func() { - store = oldStore - rootCtx = oldRootCtx - actor = oldActor - }() - - // Create a message (simulating mail send) - now := time.Now() - msg := &types.Issue{ - Title: "Test Subject", - Description: "Test message body", - Status: types.StatusOpen, - Priority: 2, - IssueType: types.TypeMessage, - Assignee: "worker-1", - Sender: "manager", - Ephemeral: true, - CreatedAt: now, - UpdatedAt: now, - } - - if err := testStore.CreateIssue(ctx, msg, actor); err != nil { - t.Fatalf("Failed to create message: %v", err) - } - - // Query inbox for worker-1 - messageType := types.TypeMessage - openStatus := types.StatusOpen - assignee := "worker-1" - filter := types.IssueFilter{ - IssueType: &messageType, - Status: &openStatus, - Assignee: &assignee, - } - - messages, err := testStore.SearchIssues(ctx, "", filter) - if err != nil { - t.Fatalf("SearchIssues failed: %v", err) - } - - if len(messages) != 1 { - t.Fatalf("Expected 1 message, got %d", len(messages)) - } - - if messages[0].Title != "Test Subject" { - t.Errorf("Title = %q, want %q", messages[0].Title, "Test Subject") - } - if messages[0].Sender != "manager" { - t.Errorf("Sender = %q, want %q", messages[0].Sender, "manager") - } - if !messages[0].Ephemeral { - t.Error("Ephemeral should be true") - } -} - -func TestMailInboxEmpty(t *testing.T) { - tmpDir := t.TempDir() - testStore := newTestStore(t, tmpDir+"/.beads/beads.db") - ctx := context.Background() - - // Query inbox for non-existent user - messageType := types.TypeMessage - openStatus := types.StatusOpen - assignee := "nobody" - filter := types.IssueFilter{ - IssueType: &messageType, - Status: &openStatus, - Assignee: &assignee, - } - - messages, err := testStore.SearchIssues(ctx, "", filter) - if err != nil { - t.Fatalf("SearchIssues failed: %v", err) - } - - if len(messages) != 0 { - t.Errorf("Expected 0 messages, got %d", len(messages)) - } -} - -func TestMailAck(t *testing.T) { - tmpDir := t.TempDir() - testStore := newTestStore(t, tmpDir+"/.beads/beads.db") - ctx := context.Background() - - // Create a message - now := time.Now() - msg := &types.Issue{ - Title: "Ack Test", - Description: "Test body", - Status: types.StatusOpen, - Priority: 2, - IssueType: types.TypeMessage, - Assignee: "recipient", - Sender: "sender", - Ephemeral: true, - CreatedAt: now, - UpdatedAt: now, - } - - if err := testStore.CreateIssue(ctx, msg, "test"); err != nil { - t.Fatalf("Failed to create message: %v", err) - } - - // Acknowledge (close) the message - if err := testStore.CloseIssue(ctx, msg.ID, "acknowledged", "test"); err != nil { - t.Fatalf("Failed to close message: %v", err) - } - - // Verify it's closed - updated, err := testStore.GetIssue(ctx, msg.ID) - if err != nil { - t.Fatalf("GetIssue failed: %v", err) - } - - if updated.Status != types.StatusClosed { - t.Errorf("Status = %q, want %q", updated.Status, types.StatusClosed) - } - - // Verify it no longer appears in inbox - messageType := types.TypeMessage - openStatus := types.StatusOpen - assignee := "recipient" - filter := types.IssueFilter{ - IssueType: &messageType, - Status: &openStatus, - Assignee: &assignee, - } - - messages, err := testStore.SearchIssues(ctx, "", filter) - if err != nil { - t.Fatalf("SearchIssues failed: %v", err) - } - - if len(messages) != 0 { - t.Errorf("Expected 0 messages in inbox after ack, got %d", len(messages)) - } -} - -func TestMailReply(t *testing.T) { - tmpDir := t.TempDir() - testStore := newTestStore(t, tmpDir+"/.beads/beads.db") - ctx := context.Background() - - // Create original message - now := time.Now() - original := &types.Issue{ - Title: "Original Subject", - Description: "Original body", - Status: types.StatusOpen, - Priority: 2, - IssueType: types.TypeMessage, - Assignee: "worker", - Sender: "manager", - Ephemeral: true, - CreatedAt: now, - UpdatedAt: now, - } - - if err := testStore.CreateIssue(ctx, original, "test"); err != nil { - t.Fatalf("Failed to create original message: %v", err) - } - - // Create reply (thread link now done via dependencies per Decision 004) - reply := &types.Issue{ - Title: "Re: Original Subject", - Description: "Reply body", - Status: types.StatusOpen, - Priority: 2, - IssueType: types.TypeMessage, - Assignee: "manager", // Reply goes to original sender - Sender: "worker", - Ephemeral: true, - CreatedAt: now.Add(time.Minute), - UpdatedAt: now.Add(time.Minute), - } - - if err := testStore.CreateIssue(ctx, reply, "test"); err != nil { - t.Fatalf("Failed to create reply: %v", err) - } - - // Add replies-to dependency (thread link per Decision 004) - dep := &types.Dependency{ - IssueID: reply.ID, - DependsOnID: original.ID, - Type: types.DepRepliesTo, - } - if err := testStore.AddDependency(ctx, dep, "test"); err != nil { - t.Fatalf("Failed to add replies-to dependency: %v", err) - } - - // Verify reply has correct thread link via dependencies - deps, err := testStore.GetDependenciesWithMetadata(ctx, reply.ID) - if err != nil { - t.Fatalf("GetDependenciesWithMetadata failed: %v", err) - } - - var foundReplyLink bool - for _, d := range deps { - if d.DependencyType == types.DepRepliesTo && d.ID == original.ID { - foundReplyLink = true - break - } - } - if !foundReplyLink { - t.Errorf("Reply missing replies-to link to original message") - } -} - -func TestMailPriority(t *testing.T) { - tmpDir := t.TempDir() - testStore := newTestStore(t, tmpDir+"/.beads/beads.db") - ctx := context.Background() - - // Create messages with different priorities - now := time.Now() - messages := []struct { - title string - priority int - }{ - {"Normal message", 2}, - {"Urgent message", 0}, - {"High priority", 1}, - } - - for i, m := range messages { - msg := &types.Issue{ - Title: m.title, - Description: "Body", - Status: types.StatusOpen, - Priority: m.priority, - IssueType: types.TypeMessage, - Assignee: "inbox", - Sender: "sender", - Ephemeral: true, - CreatedAt: now.Add(time.Duration(i) * time.Minute), - UpdatedAt: now.Add(time.Duration(i) * time.Minute), - } - if err := testStore.CreateIssue(ctx, msg, "test"); err != nil { - t.Fatalf("Failed to create message %d: %v", i, err) - } - } - - // Query all messages - messageType := types.TypeMessage - openStatus := types.StatusOpen - assignee := "inbox" - filter := types.IssueFilter{ - IssueType: &messageType, - Status: &openStatus, - Assignee: &assignee, - } - - results, err := testStore.SearchIssues(ctx, "", filter) - if err != nil { - t.Fatalf("SearchIssues failed: %v", err) - } - - if len(results) != 3 { - t.Fatalf("Expected 3 messages, got %d", len(results)) - } - - // Verify we can filter by priority - urgentPriority := 0 - urgentFilter := types.IssueFilter{ - IssueType: &messageType, - Status: &openStatus, - Assignee: &assignee, - Priority: &urgentPriority, - } - - urgent, err := testStore.SearchIssues(ctx, "", urgentFilter) - if err != nil { - t.Fatalf("SearchIssues failed: %v", err) - } - - if len(urgent) != 1 { - t.Errorf("Expected 1 urgent message, got %d", len(urgent)) - } -} - -func TestMailTypeValidation(t *testing.T) { - tmpDir := t.TempDir() - testStore := newTestStore(t, tmpDir+"/.beads/beads.db") - ctx := context.Background() - - // Create a regular issue (not a message) - now := time.Now() - task := &types.Issue{ - Title: "Regular Task", - Status: types.StatusOpen, - Priority: 2, - IssueType: types.TypeTask, - CreatedAt: now, - UpdatedAt: now, - } - - if err := testStore.CreateIssue(ctx, task, "test"); err != nil { - t.Fatalf("Failed to create task: %v", err) - } - - // Query for messages should not return the task - messageType := types.TypeMessage - filter := types.IssueFilter{ - IssueType: &messageType, - } - - messages, err := testStore.SearchIssues(ctx, "", filter) - if err != nil { - t.Fatalf("SearchIssues failed: %v", err) - } - - for _, m := range messages { - if m.ID == task.ID { - t.Errorf("Task %s should not appear in message query", task.ID) - } - } -} - -func TestMailSenderField(t *testing.T) { - tmpDir := t.TempDir() - testStore := newTestStore(t, tmpDir+"/.beads/beads.db") - ctx := context.Background() - - // Create messages from different senders - now := time.Now() - senders := []string{"alice", "bob", "charlie"} - - for i, sender := range senders { - msg := &types.Issue{ - Title: "Message from " + sender, - Description: "Body", - Status: types.StatusOpen, - Priority: 2, - IssueType: types.TypeMessage, - Assignee: "inbox", - Sender: sender, - Ephemeral: true, - CreatedAt: now.Add(time.Duration(i) * time.Minute), - UpdatedAt: now.Add(time.Duration(i) * time.Minute), - } - if err := testStore.CreateIssue(ctx, msg, "test"); err != nil { - t.Fatalf("Failed to create message from %s: %v", sender, err) - } - } - - // Query all messages and verify sender - messageType := types.TypeMessage - filter := types.IssueFilter{ - IssueType: &messageType, - } - - messages, err := testStore.SearchIssues(ctx, "", filter) - if err != nil { - t.Fatalf("SearchIssues failed: %v", err) - } - - senderSet := make(map[string]bool) - for _, m := range messages { - if m.Sender != "" { - senderSet[m.Sender] = true - } - } - - for _, s := range senders { - if !senderSet[s] { - t.Errorf("Sender %q not found in messages", s) - } - } -} diff --git a/cmd/bd/setup/utils.go b/cmd/bd/setup/utils.go index 788a051f..4dbca094 100644 --- a/cmd/bd/setup/utils.go +++ b/cmd/bd/setup/utils.go @@ -4,12 +4,20 @@ import ( "fmt" "os" "path/filepath" + + "github.com/steveyegge/beads/internal/utils" ) // atomicWriteFile writes data to a file atomically using a unique temporary file. // This prevents race conditions when multiple processes write to the same file. +// If path is a symlink, writes to the resolved target (preserving the symlink). func atomicWriteFile(path string, data []byte) error { - dir := filepath.Dir(path) + targetPath, err := utils.ResolveForWrite(path) + if err != nil { + return fmt.Errorf("resolve path: %w", err) + } + + dir := filepath.Dir(targetPath) // Create unique temp file in same directory tmpFile, err := os.CreateTemp(dir, ".*.tmp") @@ -38,7 +46,7 @@ func atomicWriteFile(path string, data []byte) error { } // Atomic rename - if err := os.Rename(tmpPath, path); err != nil { + if err := os.Rename(tmpPath, targetPath); err != nil { _ = os.Remove(tmpPath) // Best effort cleanup return fmt.Errorf("rename temp file: %w", err) } diff --git a/cmd/bd/setup/utils_test.go b/cmd/bd/setup/utils_test.go index dc68740a..4c5eeb03 100644 --- a/cmd/bd/setup/utils_test.go +++ b/cmd/bd/setup/utils_test.go @@ -67,6 +67,45 @@ func TestAtomicWriteFile(t *testing.T) { } } +func TestAtomicWriteFile_PreservesSymlink(t *testing.T) { + tmpDir := t.TempDir() + + // Create target file + target := filepath.Join(tmpDir, "target.txt") + if err := os.WriteFile(target, []byte("original"), 0644); err != nil { + t.Fatal(err) + } + + // Create symlink + link := filepath.Join(tmpDir, "link.txt") + if err := os.Symlink(target, link); err != nil { + t.Fatal(err) + } + + // Write via symlink + if err := atomicWriteFile(link, []byte("updated")); err != nil { + t.Fatalf("atomicWriteFile failed: %v", err) + } + + // Verify symlink still exists + info, err := os.Lstat(link) + if err != nil { + t.Fatalf("failed to lstat link: %v", err) + } + if info.Mode()&os.ModeSymlink == 0 { + t.Error("symlink was replaced with regular file") + } + + // Verify target was updated + data, err := os.ReadFile(target) + if err != nil { + t.Fatalf("failed to read target: %v", err) + } + if string(data) != "updated" { + t.Errorf("target content = %q, want %q", string(data), "updated") + } +} + func TestDirExists(t *testing.T) { tmpDir := t.TempDir() diff --git a/cmd/bd/templates/hooks/post-checkout b/cmd/bd/templates/hooks/post-checkout index 7cfc500f..0442752a 100755 --- a/cmd/bd/templates/hooks/post-checkout +++ b/cmd/bd/templates/hooks/post-checkout @@ -1,6 +1,6 @@ #!/bin/sh # bd-shim v1 -# bd-hooks-version: 0.31.0 +# bd-hooks-version: 0.32.1 # # bd (beads) post-checkout hook - thin shim # diff --git a/cmd/bd/templates/hooks/post-merge b/cmd/bd/templates/hooks/post-merge index c3581d75..1fde4c7a 100755 --- a/cmd/bd/templates/hooks/post-merge +++ b/cmd/bd/templates/hooks/post-merge @@ -1,6 +1,6 @@ #!/bin/sh # bd-shim v1 -# bd-hooks-version: 0.31.0 +# bd-hooks-version: 0.32.1 # # bd (beads) post-merge hook - thin shim # diff --git a/cmd/bd/templates/hooks/pre-commit b/cmd/bd/templates/hooks/pre-commit index c540f60f..5b9adfdc 100755 --- a/cmd/bd/templates/hooks/pre-commit +++ b/cmd/bd/templates/hooks/pre-commit @@ -1,6 +1,6 @@ #!/bin/sh # bd-shim v1 -# bd-hooks-version: 0.31.0 +# bd-hooks-version: 0.32.1 # # bd (beads) pre-commit hook - thin shim # diff --git a/cmd/bd/templates/hooks/pre-push b/cmd/bd/templates/hooks/pre-push index 1541b0d3..c6084c5e 100755 --- a/cmd/bd/templates/hooks/pre-push +++ b/cmd/bd/templates/hooks/pre-push @@ -1,6 +1,6 @@ #!/bin/sh # bd-shim v1 -# bd-hooks-version: 0.31.0 +# bd-hooks-version: 0.32.1 # # bd (beads) pre-push hook - thin shim # diff --git a/cmd/bd/version.go b/cmd/bd/version.go index 38f2b4db..b4467553 100644 --- a/cmd/bd/version.go +++ b/cmd/bd/version.go @@ -14,7 +14,7 @@ import ( var ( // Version is the current version of bd (overridden by ldflags at build time) - Version = "0.31.0" + Version = "0.32.1" // Build can be set via ldflags at compile time Build = "dev" // Commit and branch the git revision the binary was built from (optional ldflag) diff --git a/cmd/bd/version_tracking.go b/cmd/bd/version_tracking.go index 2ef3c91e..10eb5cf3 100644 --- a/cmd/bd/version_tracking.go +++ b/cmd/bd/version_tracking.go @@ -149,67 +149,9 @@ func maybeShowUpgradeNotification() { fmt.Println("šŸ’” Run 'bd upgrade review' to see what changed") fmt.Println("šŸ’Š Run 'bd doctor' to verify upgrade completed cleanly") - // Check if BD_GUIDE.md exists and needs updating - checkAndSuggestBDGuideUpdate() - fmt.Println() } -// checkAndSuggestBDGuideUpdate checks if .beads/BD_GUIDE.md exists and suggests regeneration if outdated. -// bd-woro: Auto-update BD_GUIDE.md on version changes -func checkAndSuggestBDGuideUpdate() { - beadsDir := beads.FindBeadsDir() - if beadsDir == "" { - return - } - - guidePath := beadsDir + "/BD_GUIDE.md" - - // Check if BD_GUIDE.md exists - if _, err := os.Stat(guidePath); os.IsNotExist(err) { - // File doesn't exist - no suggestion needed - return - } - - // Read first few lines to check version stamp - // #nosec G304 - guidePath is constructed from beadsDir + constant string - content, err := os.ReadFile(guidePath) - if err != nil { - return // Silent failure - } - - // Look for version in the first 200 bytes (should be in the header) - header := string(content) - if len(header) > 200 { - header = header[:200] - } - - // Check if the file has the old version stamp - oldVersionStamp := fmt.Sprintf("bd v%s", previousVersion) - currentVersionStamp := fmt.Sprintf("bd v%s", Version) - - if containsSubstring(header, oldVersionStamp) && !containsSubstring(header, currentVersionStamp) { - // BD_GUIDE.md is outdated - fmt.Printf("šŸ“„ BD_GUIDE.md is outdated (v%s → v%s)\n", previousVersion, Version) - fmt.Printf("šŸ’” Run 'bd onboard --output .beads/BD_GUIDE.md' to regenerate\n") - } -} - -// containsSubstring checks if haystack contains needle (case-sensitive) -func containsSubstring(haystack, needle string) bool { - return len(haystack) >= len(needle) && findSubstring(haystack, needle) >= 0 -} - -// findSubstring returns the index of needle in haystack, or -1 if not found -func findSubstring(haystack, needle string) int { - for i := 0; i <= len(haystack)-len(needle); i++ { - if haystack[i:i+len(needle)] == needle { - return i - } - } - return -1 -} - // findActualJSONLFile scans .beads/ for the actual JSONL file in use. // Prefers issues.jsonl over beads.jsonl (canonical name), skips backups and merge artifacts. // Returns empty string if no JSONL file is found. diff --git a/docs/UNINSTALLING.md b/docs/UNINSTALLING.md index 191d46df..fd7e4c2d 100644 --- a/docs/UNINSTALLING.md +++ b/docs/UNINSTALLING.md @@ -111,7 +111,6 @@ The `.beads/` directory contains: | `config.yaml` | Project configuration | | `metadata.json` | Version tracking | | `deletions.jsonl` | Soft-deleted issues | -| `BD_GUIDE.md` | AI agent instructions | | `README.md` | Human-readable overview | Remove everything: diff --git a/docs/graph-links.md b/docs/graph-links.md index 6f3c6a39..100ae449 100644 --- a/docs/graph-links.md +++ b/docs/graph-links.md @@ -9,7 +9,8 @@ Beads supports several types of links between issues to create a knowledge graph Creates message threads, similar to email or chat conversations. **Created by:** -- `bd mail reply ` command +- `gt mail reply ` command (Gas Town handles messaging) +- `bd dep add --type replies_to` (manual linking) **Use cases:** - Agent-to-agent message threads @@ -19,19 +20,19 @@ Creates message threads, similar to email or chat conversations. **Example:** ```bash -# Original message -bd mail send worker-1 -s "Review needed" -m "Please review bd-xyz" -# Creates: bd-a1b2 +# Original message (via Gas Town) +gt mail send gastown/worker -s "Review needed" -m "Please review gt-xyz" +# Creates: gt-a1b2 -# Reply (automatically sets replies_to: bd-a1b2) -bd mail reply bd-a1b2 -m "Done! Approved with minor comments." -# Creates: bd-c3d4 with replies_to: bd-a1b2 +# Reply (automatically sets replies_to) +gt mail reply gt-a1b2 -m "Done! Approved with minor comments." +# Creates: gt-c3d4 with replies_to: gt-a1b2 ``` **Viewing threads:** ```bash -bd show bd-a1b2 --thread +bd show gt-a1b2 --thread ``` ### relates_to - Loose Associations @@ -255,12 +256,12 @@ bd supersede bd-rfc2 --with bd-rfc3 ### Message Threading -Build conversation chains: +Build conversation chains (via Gas Town): ```bash -bd mail send dev -s "Question" -m "How does X work?" -bd mail reply bd-q1 -m "X works by..." -bd mail reply bd-q1.reply -m "Thanks!" +gt mail send gastown/dev -s "Question" -m "How does X work?" +gt mail reply gt-q1 -m "X works by..." +gt mail reply gt-q1.reply -m "Thanks!" ``` ## Best Practices diff --git a/docs/messaging.md b/docs/messaging.md deleted file mode 100644 index b3428ad0..00000000 --- a/docs/messaging.md +++ /dev/null @@ -1,254 +0,0 @@ -# Beads Messaging System - -Beads provides a built-in messaging system for inter-agent communication. Messages are stored as beads issues with type `message`, enabling git-native communication without external services. - -## Overview - -The messaging system enables: -- **Agent-to-agent communication** - Send messages between workers -- **Thread tracking** - Replies link back to original messages -- **Priority signaling** - Mark messages as urgent (P0) or routine -- **Ephemeral cleanup** - Messages can be bulk-deleted after completion - -## Identity Configuration - -Before using mail commands, configure your identity: - -### Environment Variable - -```bash -export BEADS_IDENTITY="worker-1" -``` - -### Config File - -Add to `.beads/config.json`: - -```json -{ - "identity": "worker-1" -} -``` - -### Priority - -1. `--identity` flag (if provided) -2. `BEADS_IDENTITY` environment variable -3. `.beads/config.json` identity field -4. System username (fallback) - -## Commands - -### Send a Message - -```bash -bd mail send -s -m -``` - -**Options:** -- `-s, --subject` - Message subject (required) -- `-m, --body` - Message body (required) -- `--urgent` - Set priority=0 (urgent) -- `--identity` - Override sender identity - -**Examples:** - -```bash -# Basic message -bd mail send worker-1 -s "Task complete" -m "Finished bd-xyz" - -# Urgent message -bd mail send manager -s "Blocked!" -m "Need credentials for deploy" --urgent - -# With custom identity -bd mail send worker-2 -s "Handoff" -m "Your turn on bd-abc" --identity refinery -``` - -### Check Your Inbox - -```bash -bd mail inbox -``` - -Lists all open messages addressed to your identity. - -**Options:** -- `--from ` - Filter by sender -- `--priority ` - Filter by priority (0-4) - -**Output:** - -``` -Inbox for worker-1 (2 messages): - - bd-a1b2: Task assigned [URGENT] - From: manager (5m ago) - - bd-c3d4: FYI: Design doc updated - From: worker-2 (1h ago) - Re: bd-x7y8 -``` - -### Read a Message - -```bash -bd mail read -``` - -Displays full message content. Does NOT mark as read. - -**Example output:** - -``` -────────────────────────────────────────────────────────────────── -ID: bd-a1b2 -From: manager -To: worker-1 -Subject: Task assigned -Time: 2025-12-16 10:30:45 -Priority: P0 -Status: open -────────────────────────────────────────────────────────────────── - -Please prioritize bd-xyz. It's blocking the release. - -Let me know if you need anything. -``` - -### Acknowledge (Mark as Read) - -```bash -bd mail ack [id2...] -``` - -Closes messages to mark them as acknowledged. - -**Examples:** - -```bash -# Single message -bd mail ack bd-a1b2 - -# Multiple messages -bd mail ack bd-a1b2 bd-c3d4 bd-e5f6 -``` - -### Reply to a Message - -```bash -bd mail reply -m -``` - -Creates a threaded reply to an existing message. - -**Options:** -- `-m, --body` - Reply body (required) -- `--urgent` - Set priority=0 -- `--identity` - Override sender identity - -**Behavior:** -- Sets `replies_to` to original message ID -- Sends to original message's sender -- Prefixes subject with "Re:" if not already present - -**Example:** - -```bash -bd mail reply bd-a1b2 -m "On it! Should be done by EOD." -``` - -## Message Storage - -Messages are stored as issues with these fields: - -| Field | Description | -|-------|-------------| -| `type` | `message` | -| `title` | Subject line | -| `description` | Message body | -| `assignee` | Recipient identity | -| `sender` | Sender identity | -| `priority` | 0 (urgent) to 4 (routine), default 2 | -| `ephemeral` | `true` - can be bulk-deleted | -| `replies_to` | ID of parent message (for threads) | -| `status` | `open` (unread) / `closed` (read) | - -## Cleanup - -Messages are ephemeral by default and can be cleaned up: - -```bash -# Preview ephemeral message cleanup -bd cleanup --ephemeral --dry-run - -# Delete all closed ephemeral messages -bd cleanup --ephemeral --force -``` - -## Hooks - -The messaging system fires hooks when messages are sent: - -**Hook file:** `.beads/hooks/on_message` - -The hook receives: -- **Arg 1:** Issue ID -- **Arg 2:** Event type (`message`) -- **Stdin:** Full issue JSON - -**Example hook:** - -```bash -#!/bin/sh -# .beads/hooks/on_message - -ISSUE_ID="$1" -EVENT="$2" - -# Parse assignee from JSON stdin -ASSIGNEE=$(cat | jq -r '.assignee') - -# Notify recipient (example: send to external system) -curl -X POST "https://example.com/notify" \ - -d "to=$ASSIGNEE&message=$ISSUE_ID" -``` - -Make the hook executable: - -```bash -chmod +x .beads/hooks/on_message -``` - -## JSON Output - -All commands support `--json` for programmatic use: - -```bash -bd mail inbox --json -bd mail read bd-a1b2 --json -bd mail send worker-1 -s "Hi" -m "Test" --json -``` - -## Thread Visualization - -Use `bd show --thread` to view message threads: - -```bash -bd show bd-c3d4 --thread -``` - -This shows the full conversation chain via `replies_to` links. - -## Best Practices - -1. **Use descriptive subjects** - Recipients scan subjects first -2. **Mark urgent sparingly** - P0 should be reserved for blockers -3. **Acknowledge promptly** - Keep inbox clean -4. **Clean up after sprints** - Run `bd cleanup --ephemeral` periodically -5. **Configure identity** - Use `BEADS_IDENTITY` for consistent sender names - -## See Also - -- [Graph Links](graph-links.md) - Other link types (relates_to, duplicates, supersedes) -- [Hooks](EXTENDING.md) - Custom hook scripts -- [Config](CONFIG.md) - Configuration options diff --git a/examples/multiple-personas/README.md b/examples/multiple-personas/README.md index 5d8d90ea..1e45c030 100644 --- a/examples/multiple-personas/README.md +++ b/examples/multiple-personas/README.md @@ -662,4 +662,4 @@ bd dep add bd-uat1 bd-review1 --type blocks - [Multi-Phase Development](../multi-phase-development/) - Organize work by phase - [Team Workflow](../team-workflow/) - Collaborate across personas - [Contributor Workflow](../contributor-workflow/) - External contributions -- [Labels Documentation](../../LABELS.md) - Label management guide +- [Labels Documentation](../../docs/LABELS.md) - Label management guide diff --git a/integrations/beads-mcp/pyproject.toml b/integrations/beads-mcp/pyproject.toml index 8a4efafc..5d08ba03 100644 --- a/integrations/beads-mcp/pyproject.toml +++ b/integrations/beads-mcp/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "beads-mcp" -version = "0.31.0" +version = "0.32.1" description = "MCP server for beads issue tracker." readme = "README.md" requires-python = ">=3.10" diff --git a/integrations/beads-mcp/src/beads_mcp/__init__.py b/integrations/beads-mcp/src/beads_mcp/__init__.py index 68d4e347..f5ff5a2c 100644 --- a/integrations/beads-mcp/src/beads_mcp/__init__.py +++ b/integrations/beads-mcp/src/beads_mcp/__init__.py @@ -4,4 +4,4 @@ This package provides an MCP (Model Context Protocol) server that exposes beads (bd) issue tracker functionality to MCP Clients. """ -__version__ = "0.31.0" +__version__ = "0.32.1" diff --git a/integrations/beads-mcp/src/beads_mcp/bd_client.py b/integrations/beads-mcp/src/beads_mcp/bd_client.py index a20759b0..056fa8aa 100644 --- a/integrations/beads-mcp/src/beads_mcp/bd_client.py +++ b/integrations/beads-mcp/src/beads_mcp/bd_client.py @@ -385,6 +385,16 @@ class BdCliClient(BdClientBase): args.extend(["--priority", str(params.priority)]) if params.assignee: args.extend(["--assignee", params.assignee]) + if params.labels: + for label in params.labels: + args.extend(["--label", label]) + if params.labels_any: + for label in params.labels_any: + args.extend(["--label-any", label]) + if params.unassigned: + args.append("--unassigned") + if params.sort_policy: + args.extend(["--sort", params.sort_policy]) data = await self._run_command(*args) if not isinstance(data, list): @@ -412,6 +422,16 @@ class BdCliClient(BdClientBase): args.extend(["--type", params.issue_type]) if params.assignee: args.extend(["--assignee", params.assignee]) + if params.labels: + for label in params.labels: + args.extend(["--label", label]) + if params.labels_any: + for label in params.labels_any: + args.extend(["--label-any", label]) + if params.query: + args.extend(["--title", params.query]) + if params.unassigned: + args.append("--no-assignee") if params.limit: args.extend(["--limit", str(params.limit)]) diff --git a/integrations/beads-mcp/src/beads_mcp/bd_daemon_client.py b/integrations/beads-mcp/src/beads_mcp/bd_daemon_client.py index 5ec671d5..e960ffbb 100644 --- a/integrations/beads-mcp/src/beads_mcp/bd_daemon_client.py +++ b/integrations/beads-mcp/src/beads_mcp/bd_daemon_client.py @@ -377,6 +377,14 @@ class BdDaemonClient(BdClientBase): args["issue_type"] = params.issue_type if params.assignee: args["assignee"] = params.assignee + if params.labels: + args["labels"] = params.labels + if params.labels_any: + args["labels_any"] = params.labels_any + if params.query: + args["query"] = params.query + if params.unassigned: + args["unassigned"] = params.unassigned if params.limit: args["limit"] = params.limit @@ -414,6 +422,14 @@ class BdDaemonClient(BdClientBase): args["assignee"] = params.assignee if params.priority is not None: args["priority"] = params.priority + if params.labels: + args["labels"] = params.labels + if params.labels_any: + args["labels_any"] = params.labels_any + if params.unassigned: + args["unassigned"] = params.unassigned + if params.sort_policy: + args["sort_policy"] = params.sort_policy if params.limit: args["limit"] = params.limit diff --git a/integrations/beads-mcp/src/beads_mcp/models.py b/integrations/beads-mcp/src/beads_mcp/models.py index 118c456b..1d095650 100644 --- a/integrations/beads-mcp/src/beads_mcp/models.py +++ b/integrations/beads-mcp/src/beads_mcp/models.py @@ -9,6 +9,7 @@ from pydantic import BaseModel, Field, field_validator IssueStatus = Literal["open", "in_progress", "blocked", "deferred", "closed"] IssueType = Literal["bug", "feature", "task", "epic", "chore"] DependencyType = Literal["blocks", "related", "parent-child", "discovered-from"] +OperationAction = Literal["created", "updated", "closed", "reopened"] # ============================================================================= @@ -43,7 +44,7 @@ class IssueMinimal(BaseModel): class CompactedResult(BaseModel): """Result container for compacted list responses. - + When results exceed threshold, returns preview + metadata instead of full data. This prevents context window overflow for large issue lists. """ @@ -54,6 +55,42 @@ class CompactedResult(BaseModel): hint: str = "Use show(issue_id) for full issue details" +class BriefIssue(BaseModel): + """Ultra-minimal issue for scanning (4 fields). + + Use for quick scans where only identification + priority needed. + ~95% smaller than full Issue. + """ + id: str + title: str + status: IssueStatus + priority: int = Field(ge=0, le=4) + + +class BriefDep(BaseModel): + """Brief dependency for overview (5 fields). + + Use with brief_deps=True to get full issue but compact dependencies. + ~90% smaller than full LinkedIssue. + """ + id: str + title: str + status: IssueStatus + priority: int = Field(ge=0, le=4) + dependency_type: DependencyType | None = None + + +class OperationResult(BaseModel): + """Minimal confirmation for write operations. + + Default response for create/update/close/reopen when verbose=False. + ~97% smaller than returning full Issue object. + """ + id: str + action: OperationAction + message: str | None = None + + # ============================================================================= # ORIGINAL MODELS (unchanged for backward compatibility) # ============================================================================= @@ -168,6 +205,10 @@ class ReadyWorkParams(BaseModel): limit: int = Field(default=10, ge=1, le=100) priority: int | None = Field(default=None, ge=0, le=4) assignee: str | None = None + labels: list[str] | None = None # AND: must have ALL labels + labels_any: list[str] | None = None # OR: must have at least one + unassigned: bool = False # Filter to only unassigned issues + sort_policy: str | None = None # hybrid, priority, oldest class ListIssuesParams(BaseModel): @@ -177,6 +218,10 @@ class ListIssuesParams(BaseModel): priority: int | None = Field(default=None, ge=0, le=4) issue_type: IssueType | None = None assignee: str | None = None + labels: list[str] | None = None # AND: must have ALL labels + labels_any: list[str] | None = None # OR: must have at least one + query: str | None = None # Search in title (case-insensitive) + unassigned: bool = False # Filter to only unassigned issues limit: int = Field(default=20, ge=1, le=100) # Reduced to avoid MCP buffer overflow diff --git a/integrations/beads-mcp/src/beads_mcp/server.py b/integrations/beads-mcp/src/beads_mcp/server.py index 1ed3d00e..f522c3b9 100644 --- a/integrations/beads-mcp/src/beads_mcp/server.py +++ b/integrations/beads-mcp/src/beads_mcp/server.py @@ -25,13 +25,17 @@ from typing import Any, Awaitable, Callable, TypeVar from fastmcp import FastMCP from beads_mcp.models import ( - BlockedIssue, + BlockedIssue, + BriefDep, + BriefIssue, CompactedResult, - DependencyType, + DependencyType, Issue, IssueMinimal, - IssueStatus, - IssueType, + IssueStatus, + IssueType, + LinkedIssue, + OperationResult, Stats, ) from beads_mcp.tools import ( @@ -363,10 +367,17 @@ async def get_tool_info(tool_name: str) -> dict[str, Any]: "limit": "int (1-100, default 10) - Max issues to return", "priority": "int (0-4, optional) - Filter by priority", "assignee": "str (optional) - Filter by assignee", + "labels": "list[str] (optional) - AND filter: must have ALL labels", + "labels_any": "list[str] (optional) - OR filter: must have at least one", + "unassigned": "bool (default false) - Only unassigned issues", + "sort_policy": "str (optional) - hybrid|priority|oldest", + "brief": "bool (default false) - Return only {id, title, status, priority}", + "fields": "list[str] (optional) - Custom field projection", + "max_description_length": "int (optional) - Truncate descriptions", "workspace_root": "str (optional) - Workspace path" }, "returns": "List of ready issues (minimal format for context efficiency)", - "example": "ready(limit=5, priority=1)" + "example": "ready(limit=5, priority=1, unassigned=True)" }, "list": { "name": "list", @@ -376,21 +387,32 @@ async def get_tool_info(tool_name: str) -> dict[str, Any]: "priority": "int 0-4 (optional)", "issue_type": "bug|feature|task|epic|chore (optional)", "assignee": "str (optional)", + "labels": "list[str] (optional) - AND filter: must have ALL labels", + "labels_any": "list[str] (optional) - OR filter: must have at least one", + "query": "str (optional) - Search in title (case-insensitive)", + "unassigned": "bool (default false) - Only unassigned issues", "limit": "int (1-100, default 20)", + "brief": "bool (default false) - Return only {id, title, status, priority}", + "fields": "list[str] (optional) - Custom field projection", + "max_description_length": "int (optional) - Truncate descriptions", "workspace_root": "str (optional)" }, "returns": "List of issues (compacted if >20 results)", - "example": "list(status='open', priority=1, limit=10)" + "example": "list(status='open', labels=['bug'], query='auth')" }, "show": { "name": "show", "description": "Show full details for a specific issue including dependencies", "parameters": { "issue_id": "str (required) - e.g., 'bd-a1b2'", + "brief": "bool (default false) - Return only {id, title, status, priority}", + "brief_deps": "bool (default false) - Full issue with compact dependencies", + "fields": "list[str] (optional) - Custom field projection", + "max_description_length": "int (optional) - Truncate description", "workspace_root": "str (optional)" }, - "returns": "Full Issue object with dependencies and dependents", - "example": "show(issue_id='bd-a1b2')" + "returns": "Full Issue object (or BriefIssue/dict based on params)", + "example": "show(issue_id='bd-a1b2', brief_deps=True)" }, "create": { "name": "create", @@ -403,9 +425,10 @@ async def get_tool_info(tool_name: str) -> dict[str, Any]: "assignee": "str (optional)", "labels": "list[str] (optional)", "deps": "list[str] (optional) - dependency IDs", + "brief": "bool (default true) - Return OperationResult instead of full Issue", "workspace_root": "str (optional)" }, - "returns": "Created Issue object", + "returns": "OperationResult {id, action} or full Issue if brief=False", "example": "create(title='Fix auth bug', priority=1, issue_type='bug')" }, "update": { @@ -418,9 +441,10 @@ async def get_tool_info(tool_name: str) -> dict[str, Any]: "assignee": "str (optional)", "title": "str (optional)", "description": "str (optional)", + "brief": "bool (default true) - Return OperationResult instead of full Issue", "workspace_root": "str (optional)" }, - "returns": "Updated Issue object", + "returns": "OperationResult {id, action} or full Issue if brief=False", "example": "update(issue_id='bd-a1b2', status='in_progress')" }, "close": { @@ -429,9 +453,10 @@ async def get_tool_info(tool_name: str) -> dict[str, Any]: "parameters": { "issue_id": "str (required)", "reason": "str (default 'Completed')", + "brief": "bool (default true) - Return OperationResult instead of full Issue", "workspace_root": "str (optional)" }, - "returns": "List of closed issues", + "returns": "List of OperationResult or full Issues if brief=False", "example": "close(issue_id='bd-a1b2', reason='Fixed in PR #123')" }, "reopen": { @@ -440,9 +465,10 @@ async def get_tool_info(tool_name: str) -> dict[str, Any]: "parameters": { "issue_ids": "list[str] (required)", "reason": "str (optional)", + "brief": "bool (default true) - Return OperationResult instead of full Issue", "workspace_root": "str (optional)" }, - "returns": "List of reopened issues", + "returns": "List of OperationResult or full Issues if brief=False", "example": "reopen(issue_ids=['bd-a1b2'], reason='Need more work')" }, "dep": { @@ -467,9 +493,13 @@ async def get_tool_info(tool_name: str) -> dict[str, Any]: "blocked": { "name": "blocked", "description": "Show blocked issues and what blocks them", - "parameters": {"workspace_root": "str (optional)"}, - "returns": "List of blocked issues with blocker info", - "example": "blocked()" + "parameters": { + "brief": "bool (default false) - Return only {id, title, status, priority}", + "brief_deps": "bool (default false) - Full issues with compact dependencies", + "workspace_root": "str (optional)" + }, + "returns": "List of BlockedIssue (or BriefIssue/dict based on params)", + "example": "blocked(brief=True)" }, "admin": { "name": "admin", @@ -669,26 +699,131 @@ def _to_minimal(issue: Issue) -> IssueMinimal: ) +def _to_brief(issue: Issue) -> BriefIssue: + """Convert full Issue to brief format (id, title, status, priority).""" + return BriefIssue( + id=issue.id, + title=issue.title, + status=issue.status, + priority=issue.priority, + ) + + +def _to_brief_dep(linked: LinkedIssue) -> BriefDep: + """Convert LinkedIssue to brief dependency format.""" + return BriefDep( + id=linked.id, + title=linked.title, + status=linked.status, + priority=linked.priority, + dependency_type=linked.dependency_type, + ) + + +# Valid fields for Issue model (used for field validation) +VALID_ISSUE_FIELDS: set[str] = { + "id", "title", "description", "design", "acceptance_criteria", "notes", + "external_ref", "status", "priority", "issue_type", "created_at", + "updated_at", "closed_at", "assignee", "labels", "dependency_count", + "dependent_count", "dependencies", "dependents", +} + + +def _filter_fields(obj: Issue, fields: list[str]) -> dict[str, Any]: + """Extract only specified fields from an Issue object. + + Raises: + ValueError: If any requested field is not a valid Issue field. + """ + # Validate fields first + requested = set(fields) + invalid = requested - VALID_ISSUE_FIELDS + if invalid: + raise ValueError( + f"Invalid field(s): {sorted(invalid)}. " + f"Valid fields: {sorted(VALID_ISSUE_FIELDS)}" + ) + + result: dict[str, Any] = {} + for field in fields: + value = getattr(obj, field) + # Handle nested Pydantic models + if hasattr(value, 'model_dump'): + result[field] = value.model_dump() + elif isinstance(value, list) and value and hasattr(value[0], 'model_dump'): + result[field] = [item.model_dump() for item in value] + else: + result[field] = value + return result + + +def _truncate_description(issue: Issue, max_length: int) -> Issue: + """Return issue copy with truncated description if needed.""" + if issue.description and len(issue.description) > max_length: + data = issue.model_dump() + data['description'] = issue.description[:max_length] + "..." + return Issue(**data) + return issue + + @mcp.tool(name="ready", description="Find tasks that have no blockers and are ready to be worked on. Returns minimal format for context efficiency.") @with_workspace async def ready_work( limit: int = 10, priority: int | None = None, assignee: str | None = None, + labels: list[str] | None = None, + labels_any: list[str] | None = None, + unassigned: bool = False, + sort_policy: str | None = None, workspace_root: str | None = None, -) -> list[IssueMinimal] | CompactedResult: + brief: bool = False, + fields: list[str] | None = None, + max_description_length: int | None = None, +) -> list[IssueMinimal] | list[BriefIssue] | list[dict[str, Any]] | CompactedResult: """Find issues with no blocking dependencies that are ready to work on. - + + Args: + limit: Maximum issues to return (1-100, default 10) + priority: Filter by priority level (0-4) + assignee: Filter by assignee + labels: Filter by labels (AND: must have ALL specified labels) + labels_any: Filter by labels (OR: must have at least one) + unassigned: Filter to only unassigned issues + sort_policy: Sort policy: hybrid (default), priority, oldest + workspace_root: Workspace path override + brief: If True, return only {id, title, status} (~97% smaller) + fields: Return only specified fields (custom projections) + max_description_length: Truncate descriptions to this length + Returns minimal issue format to reduce context usage by ~80%. Use show(issue_id) for full details including dependencies. - - If results exceed threshold, returns compacted preview. """ - issues = await beads_ready_work(limit=limit, priority=priority, assignee=assignee) - - # Convert to minimal format + issues = await beads_ready_work( + limit=limit, + priority=priority, + assignee=assignee, + labels=labels, + labels_any=labels_any, + unassigned=unassigned, + sort_policy=sort_policy, + ) + + # Apply description truncation first + if max_description_length: + issues = [_truncate_description(i, max_description_length) for i in issues] + + # Return brief format if requested + if brief: + return [_to_brief(issue) for issue in issues] + + # Return specific fields if requested + if fields: + return [_filter_fields(issue, fields) for issue in issues] + + # Default: minimal format with compaction minimal_issues = [_to_minimal(issue) for issue in issues] - + # Apply compaction if over threshold if len(minimal_issues) > COMPACTION_THRESHOLD: return CompactedResult( @@ -704,7 +839,7 @@ async def ready_work( @mcp.tool( name="list", - description="List all issues with optional filters (status, priority, type, assignee). Returns minimal format for context efficiency.", + description="List all issues with optional filters. When status='blocked', returns BlockedIssue with blocked_by info.", ) @with_workspace async def list_issues( @@ -712,27 +847,63 @@ async def list_issues( priority: int | None = None, issue_type: IssueType | None = None, assignee: str | None = None, + labels: list[str] | None = None, + labels_any: list[str] | None = None, + query: str | None = None, + unassigned: bool = False, limit: int = 20, workspace_root: str | None = None, -) -> list[IssueMinimal] | CompactedResult: + brief: bool = False, + fields: list[str] | None = None, + max_description_length: int | None = None, +) -> list[IssueMinimal] | list[BriefIssue] | list[dict[str, Any]] | CompactedResult: """List all issues with optional filters. - + + Args: + status: Filter by status (open, in_progress, blocked, closed) + priority: Filter by priority level (0-4) + issue_type: Filter by type (bug, feature, task, epic, chore) + assignee: Filter by assignee + labels: Filter by labels (AND: must have ALL specified labels) + labels_any: Filter by labels (OR: must have at least one) + query: Search in title (case-insensitive substring) + unassigned: Filter to only unassigned issues + limit: Maximum issues to return (1-100, default 20) + workspace_root: Workspace path override + brief: If True, return only {id, title, status} (~97% smaller) + fields: Return only specified fields (custom projections) + max_description_length: Truncate descriptions to this length + Returns minimal issue format to reduce context usage by ~80%. Use show(issue_id) for full details including dependencies. - - If results exceed threshold, returns compacted preview. """ issues = await beads_list_issues( status=status, priority=priority, issue_type=issue_type, assignee=assignee, + labels=labels, + labels_any=labels_any, + query=query, + unassigned=unassigned, limit=limit, ) - # Convert to minimal format + # Apply description truncation first + if max_description_length: + issues = [_truncate_description(i, max_description_length) for i in issues] + + # Return brief format if requested + if brief: + return [_to_brief(issue) for issue in issues] + + # Return specific fields if requested + if fields: + return [_filter_fields(issue, fields) for issue in issues] + + # Default: minimal format with compaction minimal_issues = [_to_minimal(issue) for issue in issues] - + # Apply compaction if over threshold if len(minimal_issues) > COMPACTION_THRESHOLD: return CompactedResult( @@ -751,9 +922,44 @@ async def list_issues( description="Show detailed information about a specific issue including dependencies and dependents.", ) @with_workspace -async def show_issue(issue_id: str, workspace_root: str | None = None) -> Issue: - """Show detailed information about a specific issue.""" - return await beads_show_issue(issue_id=issue_id) +async def show_issue( + issue_id: str, + workspace_root: str | None = None, + brief: bool = False, + brief_deps: bool = False, + fields: list[str] | None = None, + max_description_length: int | None = None, +) -> Issue | BriefIssue | dict[str, Any]: + """Show detailed information about a specific issue. + + Args: + issue_id: The issue ID to show (e.g., 'bd-a1b2') + workspace_root: Workspace path override + brief: If True, return only {id, title, status, priority} + brief_deps: If True, return full issue but with compact dependencies + fields: Return only specified fields (custom projections) + max_description_length: Truncate description to this length + """ + issue = await beads_show_issue(issue_id=issue_id) + + if max_description_length: + issue = _truncate_description(issue, max_description_length) + + # Brief mode - just identification + if brief: + return _to_brief(issue) + + # Brief deps mode - full issue but compact dependencies + if brief_deps: + data = issue.model_dump() + data["dependencies"] = [_to_brief_dep(d).model_dump() for d in issue.dependencies] + data["dependents"] = [_to_brief_dep(d).model_dump() for d in issue.dependents] + return data + + if fields: + return _filter_fields(issue, fields) + + return issue @mcp.tool( @@ -776,9 +982,14 @@ async def create_issue( id: str | None = None, deps: list[str] | None = None, workspace_root: str | None = None, -) -> Issue: - """Create a new issue.""" - return await beads_create_issue( + brief: bool = True, +) -> Issue | OperationResult: + """Create a new issue. + + Args: + brief: If True (default), return minimal OperationResult; if False, return full Issue + """ + issue = await beads_create_issue( title=title, description=description, design=design, @@ -792,6 +1003,10 @@ async def create_issue( deps=deps, ) + if brief: + return OperationResult(id=issue.id, action="created") + return issue + @mcp.tool( name="update", @@ -812,14 +1027,23 @@ async def update_issue( notes: str | None = None, external_ref: str | None = None, workspace_root: str | None = None, -) -> Issue | list[Issue] | None: - """Update an existing issue.""" + brief: bool = True, +) -> Issue | OperationResult | list[Issue] | list[OperationResult] | None: + """Update an existing issue. + + Args: + brief: If True (default), return minimal OperationResult; if False, return full Issue + """ # If trying to close via update, redirect to close_issue to preserve approval workflow if status == "closed": issues = await beads_close_issue(issue_id=issue_id, reason="Closed via update") - return issues[0] if issues else None - - return await beads_update_issue( + if not issues: + return None + if brief: + return OperationResult(id=issues[0].id, action="closed", message="Closed via update") + return issues[0] + + issue = await beads_update_issue( issue_id=issue_id, status=status, priority=priority, @@ -832,6 +1056,12 @@ async def update_issue( external_ref=external_ref, ) + if issue is None: + return None + if brief: + return OperationResult(id=issue.id, action="updated") + return issue + @mcp.tool( name="close", @@ -839,9 +1069,23 @@ async def update_issue( ) @with_workspace @require_context -async def close_issue(issue_id: str, reason: str = "Completed", workspace_root: str | None = None) -> list[Issue]: - """Close (complete) an issue.""" - return await beads_close_issue(issue_id=issue_id, reason=reason) +async def close_issue( + issue_id: str, + reason: str = "Completed", + workspace_root: str | None = None, + brief: bool = True, +) -> list[Issue] | list[OperationResult]: + """Close (complete) an issue. + + Args: + brief: If True (default), return minimal OperationResult list; if False, return full Issues + """ + issues = await beads_close_issue(issue_id=issue_id, reason=reason) + + if not brief: + return issues + + return [OperationResult(id=issue_id, action="closed", message=reason)] @mcp.tool( @@ -850,9 +1094,22 @@ async def close_issue(issue_id: str, reason: str = "Completed", workspace_root: ) @with_workspace @require_context -async def reopen_issue(issue_ids: list[str], reason: str | None = None, workspace_root: str | None = None) -> list[Issue]: - """Reopen one or more closed issues.""" - return await beads_reopen_issue(issue_ids=issue_ids, reason=reason) +async def reopen_issue( + issue_ids: list[str], + reason: str | None = None, + workspace_root: str | None = None, + brief: bool = True, +) -> list[Issue] | list[OperationResult]: + """Reopen one or more closed issues. + + Args: + brief: If True (default), return minimal OperationResult list; if False, return full Issues + """ + issues = await beads_reopen_issue(issue_ids=issue_ids, reason=reason) + + if brief: + return [OperationResult(id=i.id, action="reopened", message=reason) for i in issues] + return issues @mcp.tool( @@ -891,9 +1148,34 @@ async def stats(workspace_root: str | None = None) -> Stats: description="Get blocked issues showing what dependencies are blocking them from being worked on.", ) @with_workspace -async def blocked(workspace_root: str | None = None) -> list[BlockedIssue]: - """Get blocked issues.""" - return await beads_blocked() +async def blocked( + workspace_root: str | None = None, + brief: bool = False, + brief_deps: bool = False, +) -> list[BlockedIssue] | list[BriefIssue] | list[dict[str, Any]]: + """Get blocked issues. + + Args: + brief: If True, return only {id, title, status, priority} per issue + brief_deps: If True, return full issues but with compact dependencies + """ + issues = await beads_blocked() + + # Brief mode - just identification (most compact) + if brief: + return [_to_brief(issue) for issue in issues] + + # Brief deps mode - full issue but compact dependencies + if brief_deps: + result = [] + for issue in issues: + data = issue.model_dump() + data["dependencies"] = [_to_brief_dep(d).model_dump() for d in issue.dependencies] + data["dependents"] = [_to_brief_dep(d).model_dump() for d in issue.dependents] + result.append(data) + return result + + return issues @mcp.tool( diff --git a/integrations/beads-mcp/src/beads_mcp/tools.py b/integrations/beads-mcp/src/beads_mcp/tools.py index d340eea5..e47a70cb 100644 --- a/integrations/beads-mcp/src/beads_mcp/tools.py +++ b/integrations/beads-mcp/src/beads_mcp/tools.py @@ -305,6 +305,10 @@ async def beads_ready_work( limit: Annotated[int, "Maximum number of issues to return (1-100)"] = 10, priority: Annotated[int | None, "Filter by priority (0-4, 0=highest)"] = None, assignee: Annotated[str | None, "Filter by assignee"] = None, + labels: Annotated[list[str] | None, "Filter by labels (AND: must have ALL)"] = None, + labels_any: Annotated[list[str] | None, "Filter by labels (OR: must have at least one)"] = None, + unassigned: Annotated[bool, "Filter to only unassigned issues"] = False, + sort_policy: Annotated[str | None, "Sort policy: hybrid (default), priority, oldest"] = None, ) -> list[Issue]: """Find issues with no blocking dependencies that are ready to work on. @@ -312,7 +316,15 @@ async def beads_ready_work( Perfect for agents to claim next work! """ client = await _get_client() - params = ReadyWorkParams(limit=limit, priority=priority, assignee=assignee) + params = ReadyWorkParams( + limit=limit, + priority=priority, + assignee=assignee, + labels=labels, + labels_any=labels_any, + unassigned=unassigned, + sort_policy=sort_policy, + ) return await client.ready(params) @@ -321,7 +333,11 @@ async def beads_list_issues( priority: Annotated[int | None, "Filter by priority (0-4, 0=highest)"] = None, issue_type: Annotated[IssueType | None, "Filter by type (bug, feature, task, epic, chore)"] = None, assignee: Annotated[str | None, "Filter by assignee"] = None, - limit: Annotated[int, "Maximum number of issues to return (1-1000)"] = 50, + labels: Annotated[list[str] | None, "Filter by labels (AND: must have ALL)"] = None, + labels_any: Annotated[list[str] | None, "Filter by labels (OR: must have at least one)"] = None, + query: Annotated[str | None, "Search in title (case-insensitive substring)"] = None, + unassigned: Annotated[bool, "Filter to only unassigned issues"] = False, + limit: Annotated[int, "Maximum number of issues to return (1-100)"] = 20, ) -> list[Issue]: """List all issues with optional filters.""" client = await _get_client() @@ -331,6 +347,10 @@ async def beads_list_issues( priority=priority, issue_type=issue_type, assignee=assignee, + labels=labels, + labels_any=labels_any, + query=query, + unassigned=unassigned, limit=limit, ) return await client.list_issues(params) diff --git a/integrations/beads-mcp/tests/test_mcp_server_integration.py b/integrations/beads-mcp/tests/test_mcp_server_integration.py index e0a6ba72..a06b5a2d 100644 --- a/integrations/beads-mcp/tests/test_mcp_server_integration.py +++ b/integrations/beads-mcp/tests/test_mcp_server_integration.py @@ -118,6 +118,7 @@ async def test_create_issue_tool(mcp_client): "description": "Created via MCP server", "priority": 1, "issue_type": "bug", + "brief": False, # Get full Issue object }, ) @@ -141,7 +142,7 @@ async def test_show_issue_tool(mcp_client): # First create an issue create_result = await mcp_client.call_tool( "create", - {"title": "Issue to show", "priority": 2, "issue_type": "task"}, + {"title": "Issue to show", "priority": 2, "issue_type": "task", "brief": False}, ) import json @@ -161,10 +162,10 @@ async def test_list_issues_tool(mcp_client): """Test list_issues tool.""" # Create some issues first await mcp_client.call_tool( - "create", {"title": "Issue 1", "priority": 0, "issue_type": "bug"} + "create", {"title": "Issue 1", "priority": 0, "issue_type": "bug", "brief": False} ) await mcp_client.call_tool( - "create", {"title": "Issue 2", "priority": 1, "issue_type": "feature"} + "create", {"title": "Issue 2", "priority": 1, "issue_type": "feature", "brief": False} ) # List all issues @@ -188,7 +189,7 @@ async def test_update_issue_tool(mcp_client): # Create issue create_result = await mcp_client.call_tool( - "create", {"title": "Issue to update", "priority": 2, "issue_type": "task"} + "create", {"title": "Issue to update", "priority": 2, "issue_type": "task", "brief": False} ) created = json.loads(create_result.content[0].text) issue_id = created["id"] @@ -201,6 +202,7 @@ async def test_update_issue_tool(mcp_client): "status": "in_progress", "priority": 0, "title": "Updated title", + "brief": False, # Get full Issue object }, ) @@ -218,14 +220,14 @@ async def test_close_issue_tool(mcp_client): # Create issue create_result = await mcp_client.call_tool( - "create", {"title": "Issue to close", "priority": 1, "issue_type": "bug"} + "create", {"title": "Issue to close", "priority": 1, "issue_type": "bug", "brief": False} ) created = json.loads(create_result.content[0].text) issue_id = created["id"] - # Close issue + # Close issue with brief=False to get full Issue object close_result = await mcp_client.call_tool( - "close", {"issue_id": issue_id, "reason": "Test complete"} + "close", {"issue_id": issue_id, "reason": "Test complete", "brief": False} ) closed_issues = json.loads(close_result.content[0].text) @@ -243,7 +245,7 @@ async def test_reopen_issue_tool(mcp_client): # Create and close issue create_result = await mcp_client.call_tool( - "create", {"title": "Issue to reopen", "priority": 1, "issue_type": "bug"} + "create", {"title": "Issue to reopen", "priority": 1, "issue_type": "bug", "brief": False} ) created = json.loads(create_result.content[0].text) issue_id = created["id"] @@ -252,9 +254,9 @@ async def test_reopen_issue_tool(mcp_client): "close", {"issue_id": issue_id, "reason": "Done"} ) - # Reopen issue + # Reopen issue with brief=False to get full Issue object reopen_result = await mcp_client.call_tool( - "reopen", {"issue_ids": [issue_id]} + "reopen", {"issue_ids": [issue_id], "brief": False} ) reopened_issues = json.loads(reopen_result.content[0].text) @@ -272,21 +274,21 @@ async def test_reopen_multiple_issues_tool(mcp_client): # Create and close two issues issue1_result = await mcp_client.call_tool( - "create", {"title": "Issue 1 to reopen", "priority": 1, "issue_type": "task"} + "create", {"title": "Issue 1 to reopen", "priority": 1, "issue_type": "task", "brief": False} ) issue1 = json.loads(issue1_result.content[0].text) issue2_result = await mcp_client.call_tool( - "create", {"title": "Issue 2 to reopen", "priority": 1, "issue_type": "task"} + "create", {"title": "Issue 2 to reopen", "priority": 1, "issue_type": "task", "brief": False} ) issue2 = json.loads(issue2_result.content[0].text) await mcp_client.call_tool("close", {"issue_id": issue1["id"], "reason": "Done"}) await mcp_client.call_tool("close", {"issue_id": issue2["id"], "reason": "Done"}) - # Reopen both issues + # Reopen both issues with brief=False reopen_result = await mcp_client.call_tool( - "reopen", {"issue_ids": [issue1["id"], issue2["id"]]} + "reopen", {"issue_ids": [issue1["id"], issue2["id"]], "brief": False} ) reopened_issues = json.loads(reopen_result.content[0].text) @@ -305,17 +307,17 @@ async def test_reopen_with_reason_tool(mcp_client): # Create and close issue create_result = await mcp_client.call_tool( - "create", {"title": "Issue to reopen with reason", "priority": 1, "issue_type": "bug"} + "create", {"title": "Issue to reopen with reason", "priority": 1, "issue_type": "bug", "brief": False} ) created = json.loads(create_result.content[0].text) issue_id = created["id"] await mcp_client.call_tool("close", {"issue_id": issue_id, "reason": "Done"}) - # Reopen with reason + # Reopen with reason and brief=False reopen_result = await mcp_client.call_tool( "reopen", - {"issue_ids": [issue_id], "reason": "Found regression"} + {"issue_ids": [issue_id], "reason": "Found regression", "brief": False} ) reopened_issues = json.loads(reopen_result.content[0].text) @@ -333,18 +335,18 @@ async def test_ready_work_tool(mcp_client): # Create a ready issue (no dependencies) ready_result = await mcp_client.call_tool( - "create", {"title": "Ready work", "priority": 1, "issue_type": "task"} + "create", {"title": "Ready work", "priority": 1, "issue_type": "task", "brief": False} ) ready_issue = json.loads(ready_result.content[0].text) # Create blocked issue blocking_result = await mcp_client.call_tool( - "create", {"title": "Blocking issue", "priority": 1, "issue_type": "task"} + "create", {"title": "Blocking issue", "priority": 1, "issue_type": "task", "brief": False} ) blocking_issue = json.loads(blocking_result.content[0].text) blocked_result = await mcp_client.call_tool( - "create", {"title": "Blocked issue", "priority": 1, "issue_type": "task"} + "create", {"title": "Blocked issue", "priority": 1, "issue_type": "task", "brief": False} ) blocked_issue = json.loads(blocked_result.content[0].text) @@ -374,12 +376,12 @@ async def test_add_dependency_tool(mcp_client): # Create two issues issue1_result = await mcp_client.call_tool( - "create", {"title": "Issue 1", "priority": 1, "issue_type": "task"} + "create", {"title": "Issue 1", "priority": 1, "issue_type": "task", "brief": False} ) issue1 = json.loads(issue1_result.content[0].text) issue2_result = await mcp_client.call_tool( - "create", {"title": "Issue 2", "priority": 1, "issue_type": "task"} + "create", {"title": "Issue 2", "priority": 1, "issue_type": "task", "brief": False} ) issue2 = json.loads(issue2_result.content[0].text) @@ -409,6 +411,7 @@ async def test_create_with_all_fields(mcp_client): "issue_type": "feature", "assignee": "testuser", "labels": ["urgent", "backend"], + "brief": False, # Get full Issue object }, ) @@ -433,6 +436,7 @@ async def test_list_with_filters(mcp_client): "priority": 0, "issue_type": "bug", "assignee": "alice", + "brief": False, }, ) await mcp_client.call_tool( @@ -442,6 +446,7 @@ async def test_list_with_filters(mcp_client): "priority": 1, "issue_type": "feature", "assignee": "bob", + "brief": False, }, ) @@ -468,10 +473,10 @@ async def test_ready_work_with_priority_filter(mcp_client): # Create issues with different priorities await mcp_client.call_tool( - "create", {"title": "P0 issue", "priority": 0, "issue_type": "bug"} + "create", {"title": "P0 issue", "priority": 0, "issue_type": "bug", "brief": False} ) await mcp_client.call_tool( - "create", {"title": "P1 issue", "priority": 1, "issue_type": "task"} + "create", {"title": "P1 issue", "priority": 1, "issue_type": "task", "brief": False} ) # Get ready work with priority filter @@ -493,14 +498,15 @@ async def test_update_partial_fields(mcp_client): "description": "Original description", "priority": 2, "issue_type": "task", + "brief": False, }, ) created = json.loads(create_result.content[0].text) issue_id = created["id"] - # Update only status + # Update only status with brief=False to get full Issue update_result = await mcp_client.call_tool( - "update", {"issue_id": issue_id, "status": "in_progress"} + "update", {"issue_id": issue_id, "status": "in_progress", "brief": False} ) updated = json.loads(update_result.content[0].text) assert updated["status"] == "in_progress" @@ -515,12 +521,12 @@ async def test_dependency_types(mcp_client): # Create issues issue1_result = await mcp_client.call_tool( - "create", {"title": "Issue 1", "priority": 1, "issue_type": "task"} + "create", {"title": "Issue 1", "priority": 1, "issue_type": "task", "brief": False} ) issue1 = json.loads(issue1_result.content[0].text) issue2_result = await mcp_client.call_tool( - "create", {"title": "Issue 2", "priority": 1, "issue_type": "task"} + "create", {"title": "Issue 2", "priority": 1, "issue_type": "task", "brief": False} ) issue2 = json.loads(issue2_result.content[0].text) @@ -542,10 +548,10 @@ async def test_stats_tool(mcp_client): # Create some issues to get stats await mcp_client.call_tool( - "create", {"title": "Stats test 1", "priority": 1, "issue_type": "bug"} + "create", {"title": "Stats test 1", "priority": 1, "issue_type": "bug", "brief": False} ) await mcp_client.call_tool( - "create", {"title": "Stats test 2", "priority": 2, "issue_type": "task"} + "create", {"title": "Stats test 2", "priority": 2, "issue_type": "task", "brief": False} ) # Get stats @@ -564,12 +570,12 @@ async def test_blocked_tool(mcp_client): # Create two issues blocking_result = await mcp_client.call_tool( - "create", {"title": "Blocking issue", "priority": 1, "issue_type": "task"} + "create", {"title": "Blocking issue", "priority": 1, "issue_type": "task", "brief": False} ) blocking_issue = json.loads(blocking_result.content[0].text) blocked_result = await mcp_client.call_tool( - "create", {"title": "Blocked issue", "priority": 1, "issue_type": "task"} + "create", {"title": "Blocked issue", "priority": 1, "issue_type": "task", "brief": False} ) blocked_issue = json.loads(blocked_result.content[0].text) @@ -664,3 +670,412 @@ async def test_context_default_show(mcp_client, temp_db): # Verify output contains workspace info (same as show action) assert "Workspace root:" in output assert "Database:" in output + + +# ============================================================================= +# OUTPUT CONTROL PARAMETER TESTS +# ============================================================================= + + +@pytest.mark.asyncio +async def test_create_brief_default(mcp_client): + """Test create returns OperationResult by default (brief=True).""" + import json + + result = await mcp_client.call_tool( + "create", + {"title": "Brief test issue", "priority": 2, "issue_type": "task"}, + ) + + data = json.loads(result.content[0].text) + # Default brief=True returns OperationResult + assert "id" in data + assert data["action"] == "created" + # Should NOT have full Issue fields + assert "title" not in data + assert "description" not in data + + +@pytest.mark.asyncio +async def test_create_brief_false(mcp_client): + """Test create returns full Issue when brief=False.""" + import json + + result = await mcp_client.call_tool( + "create", + { + "title": "Full issue test", + "description": "Full description", + "priority": 1, + "issue_type": "bug", + "brief": False, + }, + ) + + data = json.loads(result.content[0].text) + # brief=False returns full Issue + assert data["title"] == "Full issue test" + assert data["description"] == "Full description" + assert data["priority"] == 1 + assert data["issue_type"] == "bug" + assert data["status"] == "open" + + +@pytest.mark.asyncio +async def test_update_brief_default(mcp_client): + """Test update returns OperationResult by default (brief=True).""" + import json + + # Create issue first + create_result = await mcp_client.call_tool( + "create", {"title": "Update brief test", "brief": False} + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + # Update with default brief=True + update_result = await mcp_client.call_tool( + "update", {"issue_id": issue_id, "status": "in_progress"} + ) + + data = json.loads(update_result.content[0].text) + assert data["id"] == issue_id + assert data["action"] == "updated" + assert "title" not in data + + +@pytest.mark.asyncio +async def test_update_brief_false(mcp_client): + """Test update returns full Issue when brief=False.""" + import json + + # Create issue first + create_result = await mcp_client.call_tool( + "create", {"title": "Update full test", "brief": False} + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + # Update with brief=False + update_result = await mcp_client.call_tool( + "update", {"issue_id": issue_id, "status": "in_progress", "brief": False} + ) + + data = json.loads(update_result.content[0].text) + assert data["id"] == issue_id + assert data["status"] == "in_progress" + assert data["title"] == "Update full test" + + +@pytest.mark.asyncio +async def test_close_brief_default(mcp_client): + """Test close returns OperationResult by default (brief=True).""" + import json + + # Create issue first + create_result = await mcp_client.call_tool( + "create", {"title": "Close brief test", "brief": False} + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + # Close with default brief=True + close_result = await mcp_client.call_tool( + "close", {"issue_id": issue_id, "reason": "Done"} + ) + + data = json.loads(close_result.content[0].text) + assert isinstance(data, list) + assert len(data) == 1 + assert data[0]["id"] == issue_id + assert data[0]["action"] == "closed" + + +@pytest.mark.asyncio +async def test_close_brief_false(mcp_client): + """Test close returns full Issue when brief=False.""" + import json + + # Create issue first + create_result = await mcp_client.call_tool( + "create", {"title": "Close full test", "brief": False} + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + # Close with brief=False + close_result = await mcp_client.call_tool( + "close", {"issue_id": issue_id, "reason": "Done", "brief": False} + ) + + data = json.loads(close_result.content[0].text) + assert isinstance(data, list) + assert len(data) >= 1 + assert data[0]["id"] == issue_id + assert data[0]["status"] == "closed" + assert data[0]["title"] == "Close full test" + + +@pytest.mark.asyncio +async def test_reopen_brief_default(mcp_client): + """Test reopen returns OperationResult by default (brief=True).""" + import json + + # Create and close issue first + create_result = await mcp_client.call_tool( + "create", {"title": "Reopen brief test", "brief": False} + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + await mcp_client.call_tool("close", {"issue_id": issue_id}) + + # Reopen with default brief=True + reopen_result = await mcp_client.call_tool( + "reopen", {"issue_ids": [issue_id]} + ) + + data = json.loads(reopen_result.content[0].text) + assert isinstance(data, list) + assert len(data) == 1 + assert data[0]["id"] == issue_id + assert data[0]["action"] == "reopened" + + +@pytest.mark.asyncio +async def test_show_brief(mcp_client): + """Test show with brief=True returns BriefIssue.""" + import json + + # Create issue first + create_result = await mcp_client.call_tool( + "create", + {"title": "Show brief test", "description": "Long description", "brief": False}, + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + # Show with brief=True + show_result = await mcp_client.call_tool( + "show", {"issue_id": issue_id, "brief": True} + ) + + data = json.loads(show_result.content[0].text) + # BriefIssue has only: id, title, status, priority + assert data["id"] == issue_id + assert data["title"] == "Show brief test" + assert data["status"] == "open" + assert "priority" in data + # Should NOT have full Issue fields + assert "description" not in data + assert "dependencies" not in data + + +@pytest.mark.asyncio +async def test_show_fields_projection(mcp_client): + """Test show with fields parameter for custom projection.""" + import json + + # Create issue first + create_result = await mcp_client.call_tool( + "create", + { + "title": "Fields test", + "description": "Test description", + "priority": 1, + "brief": False, + }, + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + # Show with specific fields + show_result = await mcp_client.call_tool( + "show", {"issue_id": issue_id, "fields": ["id", "title", "priority"]} + ) + + data = json.loads(show_result.content[0].text) + # Should have only requested fields + assert data["id"] == issue_id + assert data["title"] == "Fields test" + assert data["priority"] == 1 + # Should NOT have other fields + assert "description" not in data + assert "status" not in data + + +@pytest.mark.asyncio +async def test_show_fields_invalid(mcp_client): + """Test show with invalid fields raises error.""" + import json + from fastmcp.exceptions import ToolError + + # Create issue first + create_result = await mcp_client.call_tool( + "create", {"title": "Invalid fields test", "brief": False} + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + # Show with invalid field should raise ToolError + with pytest.raises(ToolError) as exc_info: + await mcp_client.call_tool( + "show", {"issue_id": issue_id, "fields": ["id", "nonexistent_field"]} + ) + + # Verify error message mentions invalid field + assert "Invalid field" in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_show_max_description_length(mcp_client): + """Test show with max_description_length truncates description.""" + import json + + # Create issue with long description + long_desc = "A" * 200 + create_result = await mcp_client.call_tool( + "create", + {"title": "Truncate test", "description": long_desc, "brief": False}, + ) + created = json.loads(create_result.content[0].text) + issue_id = created["id"] + + # Show with truncation + show_result = await mcp_client.call_tool( + "show", {"issue_id": issue_id, "max_description_length": 50} + ) + + data = json.loads(show_result.content[0].text) + # Description should be truncated + assert len(data["description"]) <= 53 # 50 + "..." + assert data["description"].endswith("...") + + +@pytest.mark.asyncio +async def test_list_brief(mcp_client): + """Test list with brief=True returns BriefIssue format.""" + import json + + # Create some issues + await mcp_client.call_tool( + "create", {"title": "List brief 1", "priority": 1, "brief": False} + ) + await mcp_client.call_tool( + "create", {"title": "List brief 2", "priority": 2, "brief": False} + ) + + # List with brief=True + result = await mcp_client.call_tool("list", {"brief": True}) + issues = json.loads(result.content[0].text) + + assert len(issues) >= 2 + for issue in issues: + # BriefIssue has only: id, title, status, priority + assert "id" in issue + assert "title" in issue + assert "status" in issue + assert "priority" in issue + # Should NOT have full Issue fields + assert "description" not in issue + assert "issue_type" not in issue + + +@pytest.mark.asyncio +async def test_ready_brief(mcp_client): + """Test ready with brief=True returns BriefIssue format.""" + import json + + # Create a ready issue + await mcp_client.call_tool( + "create", {"title": "Ready brief test", "priority": 1, "brief": False} + ) + + # Ready with brief=True + result = await mcp_client.call_tool("ready", {"brief": True, "limit": 100}) + issues = json.loads(result.content[0].text) + + assert len(issues) >= 1 + for issue in issues: + # BriefIssue has only: id, title, status, priority + assert "id" in issue + assert "title" in issue + assert "status" in issue + assert "priority" in issue + # Should NOT have full Issue fields + assert "description" not in issue + + +@pytest.mark.asyncio +async def test_blocked_brief(mcp_client): + """Test blocked with brief=True returns BriefIssue format.""" + import json + + # Create blocking dependency + blocking_result = await mcp_client.call_tool( + "create", {"title": "Blocker for brief test", "brief": False} + ) + blocking = json.loads(blocking_result.content[0].text) + + blocked_result = await mcp_client.call_tool( + "create", {"title": "Blocked for brief test", "brief": False} + ) + blocked = json.loads(blocked_result.content[0].text) + + await mcp_client.call_tool( + "dep", + {"issue_id": blocked["id"], "depends_on_id": blocking["id"], "dep_type": "blocks"}, + ) + + # Blocked with brief=True + result = await mcp_client.call_tool("blocked", {"brief": True}) + issues = json.loads(result.content[0].text) + + # Find our blocked issue + our_blocked = [i for i in issues if i["id"] == blocked["id"]] + assert len(our_blocked) == 1 + # BriefIssue format + assert "title" in our_blocked[0] + assert "status" in our_blocked[0] + # Should NOT have BlockedIssue-specific fields + assert "blocked_by" not in our_blocked[0] + + +@pytest.mark.asyncio +async def test_show_brief_deps(mcp_client): + """Test show with brief_deps=True returns compact dependencies.""" + import json + + # Create two issues with dependency + dep_result = await mcp_client.call_tool( + "create", {"title": "Dependency issue", "brief": False} + ) + dep_issue = json.loads(dep_result.content[0].text) + + main_result = await mcp_client.call_tool( + "create", {"title": "Main issue", "brief": False} + ) + main_issue = json.loads(main_result.content[0].text) + + await mcp_client.call_tool( + "dep", + {"issue_id": main_issue["id"], "depends_on_id": dep_issue["id"], "dep_type": "blocks"}, + ) + + # Show with brief_deps=True + show_result = await mcp_client.call_tool( + "show", {"issue_id": main_issue["id"], "brief_deps": True} + ) + + data = json.loads(show_result.content[0].text) + # Full issue data + assert data["id"] == main_issue["id"] + assert data["title"] == "Main issue" + # Dependencies should be compact (BriefDep format) + assert len(data["dependencies"]) >= 1 + dep = data["dependencies"][0] + assert "id" in dep + assert "title" in dep + assert "status" in dep + # BriefDep should NOT have full LinkedIssue fields + assert "description" not in dep diff --git a/internal/hooks/hooks.go b/internal/hooks/hooks.go index fc61b22b..240c8456 100644 --- a/internal/hooks/hooks.go +++ b/internal/hooks/hooks.go @@ -12,18 +12,16 @@ import ( // Event types const ( - EventCreate = "create" - EventUpdate = "update" - EventClose = "close" - EventMessage = "message" + EventCreate = "create" + EventUpdate = "update" + EventClose = "close" ) // Hook file names const ( - HookOnCreate = "on_create" - HookOnUpdate = "on_update" - HookOnClose = "on_close" - HookOnMessage = "on_message" + HookOnCreate = "on_create" + HookOnUpdate = "on_update" + HookOnClose = "on_close" ) // Runner handles hook execution @@ -120,8 +118,6 @@ func eventToHook(event string) string { return HookOnUpdate case EventClose: return HookOnClose - case EventMessage: - return HookOnMessage default: return "" } diff --git a/internal/hooks/hooks_test.go b/internal/hooks/hooks_test.go index 53f60b45..db4204e5 100644 --- a/internal/hooks/hooks_test.go +++ b/internal/hooks/hooks_test.go @@ -44,7 +44,6 @@ func TestEventToHook(t *testing.T) { {EventCreate, HookOnCreate}, {EventUpdate, HookOnUpdate}, {EventClose, HookOnClose}, - {EventMessage, HookOnMessage}, {"unknown", ""}, {"", ""}, } @@ -182,7 +181,7 @@ echo "$1 $2" > ` + outputFile func TestRunSync_ReceivesJSON(t *testing.T) { tmpDir := t.TempDir() - hookPath := filepath.Join(tmpDir, HookOnMessage) + hookPath := filepath.Join(tmpDir, HookOnCreate) outputFile := filepath.Join(tmpDir, "stdin.txt") // Create a hook that captures stdin @@ -194,13 +193,12 @@ cat > ` + outputFile runner := NewRunner(tmpDir) issue := &types.Issue{ - ID: "bd-msg", - Title: "Test Message", - Sender: "alice", + ID: "bd-test", + Title: "Test Issue", Assignee: "bob", } - err := runner.RunSync(EventMessage, issue) + err := runner.RunSync(EventCreate, issue) if err != nil { t.Errorf("RunSync returned error: %v", err) } @@ -380,7 +378,6 @@ func TestAllHookEvents(t *testing.T) { {EventCreate, HookOnCreate}, {EventUpdate, HookOnUpdate}, {EventClose, HookOnClose}, - {EventMessage, HookOnMessage}, } for _, e := range events { diff --git a/internal/importer/importer.go b/internal/importer/importer.go index 88db09b3..5d21c75f 100644 --- a/internal/importer/importer.go +++ b/internal/importer/importer.go @@ -554,6 +554,8 @@ func upsertIssues(ctx context.Context, sqliteStore *sqlite.SQLiteStorage, issues updates["acceptance_criteria"] = incoming.AcceptanceCriteria updates["notes"] = incoming.Notes updates["closed_at"] = incoming.ClosedAt + // Pinned field (bd-7h5) + updates["pinned"] = incoming.Pinned if incoming.Assignee != "" { updates["assignee"] = incoming.Assignee @@ -647,6 +649,8 @@ func upsertIssues(ctx context.Context, sqliteStore *sqlite.SQLiteStorage, issues updates["acceptance_criteria"] = incoming.AcceptanceCriteria updates["notes"] = incoming.Notes updates["closed_at"] = incoming.ClosedAt + // Pinned field (bd-7h5) + updates["pinned"] = incoming.Pinned if incoming.Assignee != "" { updates["assignee"] = incoming.Assignee diff --git a/internal/importer/utils.go b/internal/importer/utils.go index b6828730..0cf5c6ab 100644 --- a/internal/importer/utils.go +++ b/internal/importer/utils.go @@ -112,6 +112,15 @@ func (fc *fieldComparator) equalPriority(existing int, newVal interface{}) bool return ok && int64(existing) == newPriority } +func (fc *fieldComparator) equalBool(existingVal bool, newVal interface{}) bool { + switch t := newVal.(type) { + case bool: + return existingVal == t + default: + return false + } +} + func (fc *fieldComparator) checkFieldChanged(key string, existing *types.Issue, newVal interface{}) bool { switch key { case "title": @@ -134,6 +143,8 @@ func (fc *fieldComparator) checkFieldChanged(key string, existing *types.Issue, return !fc.equalStr(existing.Assignee, newVal) case "external_ref": return !fc.equalPtrStr(existing.ExternalRef, newVal) + case "pinned": + return !fc.equalBool(existing.Pinned, newVal) default: return false } diff --git a/internal/storage/sqlite/events.go b/internal/storage/sqlite/events.go index 1bdd11e8..3e24b692 100644 --- a/internal/storage/sqlite/events.go +++ b/internal/storage/sqlite/events.go @@ -121,7 +121,7 @@ func (s *SQLiteStorage) GetStatistics(ctx context.Context) (*types.Statistics, e COALESCE(SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END), 0) as closed, COALESCE(SUM(CASE WHEN status = 'deferred' THEN 1 ELSE 0 END), 0) as deferred, COALESCE(SUM(CASE WHEN status = 'tombstone' THEN 1 ELSE 0 END), 0) as tombstone, - COALESCE(SUM(CASE WHEN status = 'pinned' THEN 1 ELSE 0 END), 0) as pinned + COALESCE(SUM(CASE WHEN pinned = 1 THEN 1 ELSE 0 END), 0) as pinned FROM issues `).Scan(&stats.TotalIssues, &stats.OpenIssues, &stats.InProgressIssues, &stats.ClosedIssues, &stats.DeferredIssues, &stats.TombstoneIssues, &stats.PinnedIssues) if err != nil { diff --git a/internal/storage/sqlite/queries.go b/internal/storage/sqlite/queries.go index 13bca543..b9c90d2d 100644 --- a/internal/storage/sqlite/queries.go +++ b/internal/storage/sqlite/queries.go @@ -558,6 +558,8 @@ var allowedUpdateFields = map[string]bool{ // Messaging fields (bd-kwro) "sender": true, "ephemeral": true, + // Pinned field (bd-7h5) + "pinned": true, // NOTE: replies_to, relates_to, duplicate_of, superseded_by removed per Decision 004 // Use AddDependency() to create graph edges instead } diff --git a/internal/utils/path.go b/internal/utils/path.go index d03c63e0..58590bbf 100644 --- a/internal/utils/path.go +++ b/internal/utils/path.go @@ -69,6 +69,23 @@ func FindMoleculesJSONLInDir(dbDir string) string { return "" } +// ResolveForWrite returns the path to write to, resolving symlinks. +// If path is a symlink, returns the resolved target path. +// If path doesn't exist, returns path unchanged (new file). +func ResolveForWrite(path string) (string, error) { + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return path, nil + } + return "", err + } + if info.Mode()&os.ModeSymlink != 0 { + return filepath.EvalSymlinks(path) + } + return path, nil +} + // CanonicalizePath converts a path to its canonical form by: // 1. Converting to absolute path // 2. Resolving symlinks diff --git a/internal/utils/path_test.go b/internal/utils/path_test.go index c70b64b3..165c7529 100644 --- a/internal/utils/path_test.go +++ b/internal/utils/path_test.go @@ -179,3 +179,56 @@ func TestCanonicalizePathSymlink(t *testing.T) { } } } + +func TestResolveForWrite(t *testing.T) { + t.Run("regular file", func(t *testing.T) { + tmpDir := t.TempDir() + file := filepath.Join(tmpDir, "regular.txt") + if err := os.WriteFile(file, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + + got, err := ResolveForWrite(file) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != file { + t.Errorf("got %q, want %q", got, file) + } + }) + + t.Run("symlink", func(t *testing.T) { + tmpDir := t.TempDir() + target := filepath.Join(tmpDir, "target.txt") + if err := os.WriteFile(target, []byte("test"), 0644); err != nil { + t.Fatal(err) + } + link := filepath.Join(tmpDir, "link.txt") + if err := os.Symlink(target, link); err != nil { + t.Fatal(err) + } + + got, err := ResolveForWrite(link) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // Resolve target too - on macOS, /var is symlink to /private/var + wantTarget, _ := filepath.EvalSymlinks(target) + if got != wantTarget { + t.Errorf("got %q, want %q", got, wantTarget) + } + }) + + t.Run("non-existent", func(t *testing.T) { + tmpDir := t.TempDir() + newFile := filepath.Join(tmpDir, "new.txt") + + got, err := ResolveForWrite(newFile) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != newFile { + t.Errorf("got %q, want %q", got, newFile) + } + }) +} diff --git a/npm-package/package.json b/npm-package/package.json index e1b68da9..98e203be 100644 --- a/npm-package/package.json +++ b/npm-package/package.json @@ -1,6 +1,6 @@ { "name": "@beads/bd", - "version": "0.31.0", + "version": "0.32.1", "description": "Beads issue tracker - lightweight memory system for coding agents with native binary support", "main": "bin/bd.js", "bin": { diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index f2ce1dbd..be61cf1f 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -1,6 +1,41 @@ #!/bin/bash set -e +# ============================================================================= +# MOLECULE WORKFLOW (Recommended) +# ============================================================================= +# +# This script handles the mechanical version updates, but the full release +# workflow is captured as a molecule. For guided, resumable releases: +# +# bd template instantiate bd-6s61 --var version=X.Y.Z --assignee +# +# This creates child beads for each release step. The molecule survives +# session restarts - any agent can pick up where another left off. +# +# The molecule (bd-6s61 "Version Bump: {{version}}") includes: +# 1. Update CHANGELOG.md and info.go +# 2. Run this script (bump-version.sh) +# 3. Run tests and linting +# 4. Commit, tag, push +# 5. Update Homebrew formula +# 6. Verify installation +# +# Until bd mol bond is implemented (bd-usro), use bd molecule instantiate. +# +# IMPORTANT: Run from mayor/rig to avoid git conflicts with bd sync +# ============================================================================= +# +# Gas Town agents share a beads database at mayor/rig/.beads/. The bd sync +# command commits from that clone. Running version bumps from a different +# clone (e.g., crew/dave) causes push conflicts when bd sync tries to push. +# +# Always run releases from the rig root: +# +# cd ~/gt/beads/mayor/rig && ./scripts/bump-version.sh X.Y.Z --commit --tag --push +# +# ============================================================================= + # Colors for output RED='\033[0;31m' GREEN='\033[0;32m'