diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 02e42ef1..1b5f0ace 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -12,7 +12,9 @@ {"id":"bd-1pj6","title":"Proposal: Custom status states via config","description":"Proposal to add 'custom status states' via `bd config`.\nUsers could define an optional issue status enum (e.g., awaiting_review, review_in_progress) in the config.\nThis would enable multi-step pipelines to process issues where each step correlates to a specific status.\n\nExamples:\n- awaiting_verification\n- awaiting_docs\n- awaiting_testing\n","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-11-20T18:55:48.670499-05:00","updated_at":"2025-11-30T10:50:04.268428-08:00","closed_at":"2025-11-28T23:18:45.862553-08:00"} {"id":"bd-1r5","title":"Design tombstone TTL and expiration semantics","description":"Design the TTL and expiration behavior for tombstone records. Key decisions: default TTL (recommend 30 days), configuration via config.yaml, expiration semantics (remove vs keep expired), merge behavior when tombstone expires (live issue wins), clock skew handling.","design":"# Design: Tombstone TTL and Expiration Semantics\n\n**Issue:** bd-1r5 \n**Author:** beads/refinery \n**Status:** Draft \n**Date:** 2025-12-05\n\n## Overview\n\nThis document defines how tombstone records expire over time, ensuring that deleted issues eventually disappear from the system while providing adequate time for deletion propagation across all clones.\n\n## Design Decisions\n\n### 1. Default TTL: 30 Days\n\n**Recommendation:** 30-day default TTL for tombstones.\n\n**Rationale:**\n- The current 3-day default for `deletions.jsonl` is too aggressive\n- Long-lived branches and dormant clones may not sync for weeks\n- 30 days provides ample time for:\n - Infrequent contributors to sync\n - Feature branches to merge\n - CI/CD pipelines to catch up\n - Vacation/leave scenarios\n- Aligns with Tier 1 compaction age (30 days for closed issues)\n\n**Tradeoffs:**\n- Pro: Reduces \"resurrection\" bugs from expired deletions\n- Con: Larger JSONL files (mitigated: tombstones are minimal records)\n\n### 2. Configuration\n\nTombstone TTL should be configurable via:\n\n```yaml\n# .beads/config.yaml\ntombstone:\n ttl_days: 30 # Default: 30\n min_ttl_days: 7 # Hard minimum, enforced\n```\n\nThe `min_ttl_days` floor prevents misconfiguration that could cause data loss.\n\n**Implementation notes:**\n- Store in `config.yaml` (new sync-branch config file format)\n- Also honor legacy `metadata.json` field `deletions_retention_days` during migration\n- Default 30 days if not configured\n\n### 3. Expiration Semantics\n\n#### 3.1 What \"Expired\" Means\n\nWhen a tombstone expires:\n1. It is **removed during compaction** (not during normal sync)\n2. An issue with matching ID can now be \"resurrected\" if it appears in incoming JSONL\n3. The original deletion is no longer authoritative\n\n#### 3.2 When Expiration is Checked\n\nExpiration checks happen during:\n1. `bd compact` - Removes expired tombstones\n2. 3-way merge - Expired tombstones don't block incoming issues\n\nExpiration is **NOT** checked during:\n- `bd sync pull` - Reads all tombstones for merge decisions\n- `bd list` - Doesn't display tombstones anyway\n\n#### 3.3 Expiration Formula\n\n```go\nisExpired := time.Now().After(tombstone.DeletedAt.Add(ttl))\n```\n\nWhere:\n- `DeletedAt` is when the deletion occurred (not when we learned of it)\n- `ttl` is the configured TTL duration (default 30 days)\n\n### 4. Clock Skew Handling\n\n**Problem:** Different machines may have slightly different clocks.\n\n**Approach:** Conservative expiration with grace period.\n\n```go\nconst clockSkewGrace = 1 * time.Hour\n\nfunc (t *Tombstone) IsExpired(ttl time.Duration) bool {\n expiresAt := t.DeletedAt.Add(ttl).Add(clockSkewGrace)\n return time.Now().After(expiresAt)\n}\n```\n\n**Rationale:**\n- 1 hour handles typical NTP drift scenarios\n- Worst case: tombstone lives 1 hour longer than configured\n- Clock skew won't cause premature expiration\n\n### 5. Merge Behavior at Expiration Boundary\n\n**Scenario:** Tombstone is about to expire, incoming JSONL has the same issue.\n\n**Rule:** Live issue wins if tombstone is expired.\n\n```go\nfunc resolveConflict(tombstone *Tombstone, incoming *Issue, ttl time.Duration) *Issue {\n if tombstone.IsExpired(ttl) {\n // Tombstone no longer authoritative - accept incoming issue\n return incoming\n }\n // Tombstone still valid - issue remains deleted\n return nil\n}\n```\n\n**Edge case:** If incoming issue has `updated_at` newer than tombstone's `deleted_at`, consider if we should allow resurrection regardless of TTL. **Recommendation:** No - always respect non-expired tombstones for predictability.\n\n### 6. Compaction Behavior\n\nDuring `bd compact`:\n\n1. Load all tombstones from `issues.jsonl`\n2. Partition into expired vs. active\n3. Remove expired tombstones from output\n4. Report: `\"Removed N expired tombstones\"`\n\nThis is the **only** time tombstones are physically removed.\n\n### 7. Interactions with Other Design Tasks\n\n#### bd-2m7 (Storage Format)\n- Tombstone record needs `deleted_at` field for TTL calculation\n- Optional `expires_at` field could pre-compute expiration for indexing\n- Recommendation: Store both for flexibility\n\n#### bd-zvg (Merge Semantics) \n- Expired tombstones don't participate in merge conflicts\n- Non-expired tombstones always win over incoming issues\n\n#### bd-dli (Migration)\n- Existing `deletions.jsonl` entries converted with their original timestamp\n- Migration preserves remaining TTL: if deleted 20 days ago with 30-day TTL, 10 days remain\n\n## Summary of Recommendations\n\n| Parameter | Value | Notes |\n|-----------|-------|-------|\n| Default TTL | 30 days | Configurable in config.yaml |\n| Minimum TTL | 7 days | Hard floor to prevent data loss |\n| Clock skew grace | 1 hour | Added to TTL before expiration check |\n| Expiration check | Compaction only | Not during sync/list |\n| Expired tombstone behavior | Allow resurrection | Live issue wins if tombstone expired |\n\n## Open Questions\n\n1. **Should we support per-tombstone TTL overrides?** (e.g., `bd delete --ttl=90d`)\n - Recommendation: No for v1, adds complexity\n\n2. **Should expired tombstones emit warnings?**\n - Recommendation: Yes, `bd doctor` should report \"N tombstones expiring soon\"\n\n3. **Should we track when tombstones were last seen across clones?**\n - Recommendation: No for v1, out of scope","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-05T13:42:57.242067-08:00","updated_at":"2025-12-05T14:59:11.461169-08:00","closed_at":"2025-12-05T14:59:11.461169-08:00"} {"id":"bd-1sn","title":"Tombstone fields not merged in mergeIssue function","description":"The mergeIssue() function in merge.go does not handle tombstone fields (DeletedAt, DeletedBy, DeleteReason, OriginalType) when merging live issues. While tombstones are handled separately in merge3WayWithTTL(), if a tombstone ever reaches mergeIssue() via the mergeStatus safety fallback, the tombstone fields would be lost. The mergeIssue() function should either: 1) Copy tombstone fields when result.Status becomes tombstone, or 2) Assert that tombstones never reach mergeIssue() (defensive check). Files: internal/merge/merge.go:458-499","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-05T16:36:24.726818-08:00","updated_at":"2025-12-05T17:15:57.641091-08:00","closed_at":"2025-12-05T17:15:57.641091-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":"open","priority":0,"issue_type":"bug","created_at":"2025-12-07T15:35:13.051671889-07:00","updated_at":"2025-12-07T15:35:13.051671889-07:00"} {"id":"bd-1u4","title":"Fix gosec lint warnings in doctor.go, main.go, and fix subdirectory","description":"CI lint job failing with 4 gosec warnings:\n- cmd/bd/doctor.go:664 (G304: file inclusion via variable)\n- cmd/bd/doctor/fix/database_config.go:166 (G304: file inclusion via variable) \n- cmd/bd/doctor/fix/untracked.go:61 (G204: subprocess launched with variable)\n- cmd/bd/main.go:645 (G304: file inclusion via variable)\n\nEither suppress with `// #nosec` if false positives, or refactor to validate paths properly.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-29T00:43:07.393406783-07:00","updated_at":"2025-11-29T23:31:20.977129-08:00","closed_at":"2025-11-29T23:31:16.4478-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-2au","title":"bd doctor: add SQLite integrity check (PRAGMA integrity_check)","description":"Add a database integrity check using SQLite's PRAGMA integrity_check to detect corruption. Currently bd doctor checks schema compatibility but not data integrity. This would help diagnose corrupted databases that pass schema checks but have internal inconsistencies.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-02T12:52:17.5244-08:00","updated_at":"2025-12-03T22:14:02.580777-08:00","closed_at":"2025-12-03T22:14:02.580777-08:00"} {"id":"bd-2e0","title":"Add TTL to deletions manifest entries","description":"Deletions manifest entries should have a TTL (time-to-live) to automatically expire after a certain period. Stale deletion entries can poison beads installations when an agent gets out of sync, causing issues to remain deleted even when they shouldn't be. Adding expiration would limit the blast radius of stale deletions.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-03T17:40:59.994296-08:00","updated_at":"2025-12-03T22:16:32.419289-08:00","closed_at":"2025-12-03T22:16:32.419289-08:00"} {"id":"bd-2em","title":"Expand checkHooksQuick to verify all hook versions","description":"Currently checkHooksQuick only checks post-merge hook version. Should also check pre-commit, pre-push, and post-checkout for completeness. Keep it lightweight but catch more outdated hooks.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-25T19:27:47.432243-08:00","updated_at":"2025-11-25T19:50:21.378464-08:00","closed_at":"2025-11-25T19:50:21.378464-08:00"} @@ -27,9 +29,11 @@ {"id":"bd-4aao","title":"Fix failing integration tests in beads-mcp","description":"The `beads-mcp` test suite has failures in `tests/test_bd_client_integration.py` (assertion error in `test_init_creates_beads_directory`) and errors in `tests/test_worktree_separate_dbs.py` (setup failures finding database). These need to be investigated and fixed to ensure a reliable CI baseline.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-20T18:53:28.4803-05:00","updated_at":"2025-11-25T21:39:20.967106-08:00","closed_at":"2025-11-25T21:39:20.967106-08:00"} {"id":"bd-4ew","title":"bd doctor should detect fresh clone and recommend 'bd init'","description":"When running `bd doctor` on a fresh clone (JSONL exists, no .db file), it should:\n\n1. Detect this is a fresh clone situation\n2. Recommend `bd init --prefix \u003cdetected-prefix\u003e` as the fix\n3. Show the prefix detected from the JSONL file\n\nCurrently it shows various warnings (git hooks, merge driver, etc.) but doesn't address the fundamental issue: the database needs to be hydrated.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-27T20:21:15.691764-08:00","updated_at":"2025-11-30T10:50:04.269427-08:00","closed_at":"2025-11-28T22:14:49.092112-08:00"} {"id":"bd-4h3","title":"Add test coverage for internal/git package","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-20T21:21:23.497486-05:00","updated_at":"2025-11-30T10:50:04.269711-08:00","closed_at":"2025-11-28T21:55:45.2527-08:00","dependencies":[{"issue_id":"bd-4h3","depends_on_id":"bd-ge7","type":"blocks","created_at":"2025-11-20T21:21:31.277639-05:00","created_by":"daemon"}]} +{"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-4l5","title":"bd prime: Detect ephemeral branches and adjust workflow output","description":"When 'bd prime' runs on a branch with no upstream (ephemeral branch), it should output a different SESSION CLOSE PROTOCOL.\n\n**Current output (wrong for ephemeral branches):**\n```\n[ ] 1. git status\n[ ] 2. git add \u003cfiles\u003e\n[ ] 3. bd sync\n[ ] 4. git commit -m \"...\"\n[ ] 5. bd sync\n[ ] 6. git push\n```\n\n**Needed output for ephemeral branches:**\n```\n[ ] 1. git status\n[ ] 2. git add \u003cfiles\u003e\n[ ] 3. bd sync --from-main (pull updates from main)\n[ ] 4. git commit -m \"...\"\n[ ] 5. (no push - branch is ephemeral)\n```\n\n**Detection:** `git rev-parse --abbrev-ref --symbolic-full-name @{u}` returns error code 128 if no upstream.\n\nAlso update Sync \u0026 Collaboration section to mention `bd sync --from-main` for ephemeral branches.\n\n**Use case:** Gastown polecats work on ephemeral local branches that are never pushed. Their code gets merged to main via local merge, and beads changes stay local (communicated via gm mail to Overseer).","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-11-25T16:55:24.984104-08:00","updated_at":"2025-11-25T17:12:46.604978-08:00","closed_at":"2025-11-25T17:12:46.604978-08:00"} {"id":"bd-4pd","title":"Imperator lacks mail hooks for automatic notification","description":"","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-30T22:53:52.007971-08:00","updated_at":"2025-12-02T23:28:48.751957-08:00","closed_at":"2025-12-01T22:01:27.835516-08:00"} {"id":"bd-4pv","title":"bd export only outputs 1 issue after auto-import corrupts database","description":"When auto-import runs and purges issues (due to git history backfill bug), subsequent 'bd export' only exports 1 issue even though the database should have many.\n\nReproduction:\n1. Have issues.jsonl with 55 issues\n2. Auto-import triggers and purges all issues via git history backfill\n3. Run 'bd export' - only exports 1 issue (the last one created before corruption)\n\nThe database gets into an inconsistent state where most issues are purged but export doesn't realize this.\n\nWorkaround: Rebuild database from scratch with 'rm .beads/beads.db \u0026\u0026 bd init --prefix bd'","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-26T22:28:40.828866-08:00","updated_at":"2025-11-28T17:28:55.545056-08:00","closed_at":"2025-11-27T22:50:35.036227-08:00"} +{"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-4t7","title":"Auto-import runs during --no-auto-import operations via stats/ready commands","description":"Even when using --no-auto-import flag, certain commands like 'bd stats' and 'bd ready' still trigger auto-import internally, which can cause the git history backfill bug to corrupt data.\n\nExample:\n bd stats --no-auto-import\n # Still prints 'Purged bd-xxx (recovered from git history...)'\n\nThe flag should completely disable auto-import for the command, but it appears some code paths still trigger it.\n\nWorkaround: Use --allow-stale instead, or --sandbox mode.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-26T22:28:59.305898-08:00","updated_at":"2025-11-27T00:54:20.335013-08:00","closed_at":"2025-11-27T00:54:12.561872-08:00"} {"id":"bd-4u8","title":"Config option sync.require_confirmation_on_mass_delete","description":"Add config option for paranoid users who want confirmation before pushing after mass deletion.\n\nOption: sync.require_confirmation_on_mass_delete\nDefault: false\n\nWhen enabled AND safety check triggers:\n- Prompt user for confirmation before pushing\n- Show them what would be pushed\n\nFor users who have been burned and want extra safety. Most users will not need this.\n\nLocation: internal/config/ and internal/syncbranch/worktree.go\nParent issue: bd-3s8","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-02T19:31:00.927907-08:00","updated_at":"2025-12-03T22:17:49.4224-08:00","closed_at":"2025-12-03T22:17:49.4224-08:00","dependencies":[{"issue_id":"bd-4u8","depends_on_id":"bd-7ch","type":"blocks","created_at":"2025-12-02T19:31:09.134795-08:00","created_by":"daemon"}]} {"id":"bd-52q","title":"Fix SyncJSONLToWorktree to merge instead of overwrite","description":"SyncJSONLToWorktree blindly copies local JSONL to worktree, overwriting remote issues.\n\n## Root Cause (GitHub #464)\nWhen Computer B syncs with fewer issues than remote:\n1. preemptiveFetchAndFastForward updates worktree (10 issues)\n2. SyncJSONLToWorktree overwrites with local (1 issue)\n3. Commit records deletion of 9 issues\n4. 3-way merge sees \"deletion in left\" → deletion wins\n\n## Fix\nBefore copying, check if worktree has more issues. If so:\n- Option A: Merge local into worktree (preferred)\n- Option B: Fail with helpful error message\n- Option C: Warn and require --force flag\n\n## Location\ninternal/syncbranch/worktree.go:107 - SyncJSONLToWorktree call\n\n## Test Case\n1. Clone repo with sync-branch containing issues\n2. bd init (starts empty since JSONL on sync-branch)\n3. bd create \"test\"\n4. bd sync\n5. Verify all issues preserved, not just \"test\"","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-05T13:43:50.93096-08:00","updated_at":"2025-12-05T14:24:20.951645-08:00","closed_at":"2025-12-05T14:24:20.951645-08:00"} @@ -46,11 +50,14 @@ {"id":"bd-736d","title":"Refactor path canonicalization into helper function","description":"The path canonicalization logic (filepath.Abs + EvalSymlinks) is duplicated in 3 places:\n- beads.go:131-137 (BEADS_DIR handling)\n- cmd/bd/main.go:446-451 (--no-db cleanup)\n- cmd/bd/nodb.go:26-31 (--no-db initialization)\n\nRefactoring suggestion:\nExtract to a helper function like:\n func canonicalizePath(path string) string\n\nThis would:\n- Reduce code duplication\n- Make the logic easier to maintain\n- Ensure consistent behavior across all path handling\n\nRelated to bd-e16b implementation.","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-11-02T18:33:47.727443-08:00","updated_at":"2025-11-25T22:27:33.738672-08:00","closed_at":"2025-11-25T22:27:33.738672-08:00"} {"id":"bd-73u","title":"Refactor daemon local-only sync functions to reduce duplication","description":"PR #433 added three new local-only sync functions (createLocalSyncFunc, createLocalExportFunc, createLocalAutoImportFunc) that are largely copy-paste of existing ones with git operations removed. Refactor to use a single implementation with a skipGit bool parameter or extract shared logic into helper functions to reduce ~200 lines of duplication.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-01T17:38:02.632264-08:00","updated_at":"2025-12-02T23:28:48.752202-08:00","closed_at":"2025-12-01T21:08:11.952459-08:00"} {"id":"bd-7ch","title":"Auto-push after merge with safety check","description":"Make bd sync a true one-command solution by auto-pushing after successful content merge.\n\nBehavior:\n- After successful content merge, auto-push by default\n- Safety check: detect when \u003e50% issues vanished AND \u003e5 existed before\n- On safety check trigger: warn but still push (do not block happy path)\n\nVanished means issues removed from issues.jsonl entirely, NOT status=closed (closed is legitimate swarm completion).\n\nLocation: internal/syncbranch/worktree.go\nParent issue: bd-3s8","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-02T19:30:59.540636-08:00","updated_at":"2025-12-03T22:12:25.328091-08:00","closed_at":"2025-12-03T22:12:25.328091-08:00"} +{"id":"bd-7di","title":"worktree: any bd command is slow","description":"in a git worktree any bd command is slow, with a 2-3s pause before any results are shown. The identical command with `--no-daemon` is near instant.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-05T15:33:42.924618693-07:00","updated_at":"2025-12-05T15:33:42.924618693-07:00"} +{"id":"bd-7v3","title":"Add commit and branch to bd version output","description":"The 'bd version' command reports commit and branch correctly when built with 'go build' (thanks to Go 1.25+ automatic VCS info embedding), but NOT when installed with 'go install' or 'make install'. Need to either: 1) Update Makefile to use 'go build' instead of 'go install', or 2) Add explicit ldflags to both Makefile and .goreleaser.yml to ensure commit/branch are always set regardless of build method.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-09T05:56:13.642971756-07:00","updated_at":"2025-12-09T06:01:56.787528774-07:00","closed_at":"2025-12-09T06:01:56.787528774-07:00"} {"id":"bd-81a","title":"Add programmatic tip injection API","description":"Allow tips to be programmatically injected at runtime based on detected conditions. This enables dynamic tips (not just pre-defined ones) to be shown with custom priority and frequency.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-11T23:29:46.645583-08:00","updated_at":"2025-11-25T17:52:35.096882-08:00","closed_at":"2025-11-25T17:52:35.096882-08:00","dependencies":[{"issue_id":"bd-81a","depends_on_id":"bd-d4i","type":"blocks","created_at":"2025-11-11T23:29:46.646327-08:00","created_by":"daemon"}]} {"id":"bd-81x6","title":"Tombstone export includes tombstones even with --status filter","description":"In export.go, we set IncludeTombstones: true unconditionally. However, if a user runs:\n\n bd export --status=open\n\nThey might not expect tombstones in the output. The IncludeTombstones flag should probably only be set when no status filter is specified, or when --status=tombstone is explicitly requested.\n\nCurrent behavior: exports tombstones regardless of status filter\nExpected behavior: respect status filter, only include tombstones when explicitly requested or no filter","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-07T01:40:52.762758-08:00","updated_at":"2025-12-07T02:00:02.011512-08:00","closed_at":"2025-12-07T02:00:02.011512-08:00","dependencies":[{"issue_id":"bd-81x6","depends_on_id":"bd-dve","type":"blocks","created_at":"2025-12-07T01:41:28.320845-08:00","created_by":"daemon"}]} {"id":"bd-8a5","title":"Refactor: deduplicate FindJSONLInDir and FindJSONLPath","description":"## Background\n\nAfter fixing bd-tqo, we now have two nearly identical functions for finding the JSONL file:\n- `autoimport.FindJSONLInDir(dbDir string)` in internal/autoimport/autoimport.go\n- `beads.FindJSONLPath(dbPath string)` in internal/beads/beads.go\n\nBoth implement the same logic:\n1. Prefer issues.jsonl\n2. Fall back to beads.jsonl for legacy support\n3. Skip deletions.jsonl and merge artifacts\n4. Default to issues.jsonl if nothing found\n\n## Problem\n\nCode duplication means bug fixes need to be applied in multiple places (as we just experienced with bd-tqo).\n\n## Proposed Solution\n\nExtract shared logic to a utility package that both can import. Options:\n1. Create `internal/jsonlpath` package with the core logic\n2. Have `autoimport` import `beads` and call `FindJSONLPath` (but APIs differ slightly)\n3. Move to `internal/utils` if appropriate\n\nNeed to verify no import cycles would be created.\n\n## Affected Files\n- internal/autoimport/autoimport.go\n- internal/beads/beads.go","status":"closed","priority":4,"issue_type":"task","created_at":"2025-11-26T23:45:18.974339-08:00","updated_at":"2025-11-30T10:50:04.269989-08:00","closed_at":"2025-11-28T23:07:08.912247-08:00"} {"id":"bd-8an","title":"bd import auto-detects wrong prefix from directory name instead of issue IDs","description":"When importing issues.jsonl into a fresh database, 'bd import' prints:\n\n ✓ Initialized database with prefix 'beads' (detected from issues)\n\nBut the issues all have prefix 'bd-' (e.g., bd-03r). It appears to be detecting the prefix from the directory name (.beads/) rather than from the actual issue IDs in the JSONL.\n\nThis causes import to fail with:\n validate ID prefix for bd-03r: issue ID 'bd-03r' does not match configured prefix 'beads'\n\nWorkaround: Run 'bd config set issue_prefix bd' before import, or use 'bd init --prefix bd'.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-26T22:28:01.582564-08:00","updated_at":"2025-11-30T10:50:04.270284-08:00","closed_at":"2025-11-27T22:38:48.971617-08:00"} {"id":"bd-8f9","title":"Add bd migrate-tombstones command","description":"Create migration command to convert existing deletions.jsonl entries to tombstones. Support --dry-run and --verbose flags. Archive old file with .migrated suffix. Per design bd-dli.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-05T15:14:45.098831-08:00","updated_at":"2025-12-13T08:14:58.904037+11:00","closed_at":"2025-12-07T02:30:19.4741-08:00","dependencies":[{"issue_id":"bd-8f9","depends_on_id":"bd-dve","type":"blocks","created_at":"2025-12-05T15:14:58.968329-08:00","created_by":"daemon"}]} +{"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":"open","priority":0,"issue_type":"bug","created_at":"2025-12-07T15:34:57.189730843-07:00","updated_at":"2025-12-07T15:34:57.189730843-07:00"} {"id":"bd-8ib","title":"Update git hooks to be sync.branch aware","description":"## Problem\n\nThe pre-push hook blocks pushes when .beads/issues.jsonl has uncommitted changes. But with sync.branch configured, those changes are intentionally NOT committed to main - they go to the sync branch via worktree.\n\n## Current Behavior\n\n1. User configures sync.branch=beads-sync\n2. bd sync commits changes to beads-sync via worktree \n3. Local .beads/issues.jsonl is updated (needed for import)\n4. git push to main triggers pre-push hook\n5. Hook sees uncommitted .beads changes and blocks push\n6. User must use --no-verify to push\n\n## Expected Behavior\n\nWhen sync.branch is configured, the pre-push hook should:\n1. Check if sync.branch is set (bd config get sync.branch)\n2. If set, skip the .beads uncommitted check OR\n3. Verify changes are committed to the sync branch instead\n\n## Affected Files\n\n- examples/git-hooks/pre-push\n- examples/git-hooks/pre-commit (may also need update)\n\n## Workaround\n\nUse git push --no-verify","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-30T00:43:40.991951-08:00","updated_at":"2025-11-30T10:46:14.501434-08:00","closed_at":"2025-11-30T10:46:14.501434-08:00"} {"id":"bd-8nz","title":"Merge timestamp tie-breaker should prefer local (left)","description":"In mergeFieldByUpdatedAt, when timestamps are exactly equal, right wins. For consistency with IssueType (where local/left wins), equal timestamps should prefer left. Minor inconsistency.","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-02T20:14:59.898345-08:00","updated_at":"2025-12-03T22:18:17.242011-08:00","closed_at":"2025-12-03T22:18:17.242011-08:00"} {"id":"bd-8q0","title":"Add Claude Code web installation docs to README","description":"GH #439 reported installation issues in Claude Code web environment. The go install fallback works, but users need guidance. Add a section to README documenting the workaround: go install + PATH export.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-01T21:02:24.511955-08:00","updated_at":"2025-12-01T21:10:10.587639-08:00","closed_at":"2025-12-01T21:10:10.587639-08:00"} @@ -66,33 +73,37 @@ {"id":"bd-a6m","title":"isLikelyHash rejects 3-char all-letter base36 hashes","description":"ExtractIssuePrefix fails for multi-hyphen prefixes when the 3-char hash suffix has no digits.\n\nExample: `xa-adt-bat` returns prefix `xa` instead of `xa-adt`.\n\nRoot cause: `isLikelyHash()` requires at least one digit to distinguish hashes from English words, but base36 hashes can be all-letters by chance.\n\nAffected: 20 issues in xa-adapt with suffixes like bat, dev, fbi, oil, etc.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-02T17:11:19.783336327-05:00","updated_at":"2025-12-02T17:12:37.634260315-05:00","closed_at":"2025-12-02T17:12:37.634260315-05:00"} {"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-alz","title":"bd doctor: add configuration value validation","description":"Currently bd doctor checks for missing config files but doesn't validate config values. Add validation for: invalid priority values, malformed sync intervals, invalid branch names, unsupported storage backends, etc. This would catch misconfigurations before they cause runtime errors.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-02T12:52:31.852532-08:00","updated_at":"2025-12-03T22:15:23.137554-08:00","closed_at":"2025-12-03T22:15:23.137554-08:00"} -{"id":"bd-aydr","title":"Add bd reset command for clean slate restart","description":"Implement a `bd reset` command to reset beads to a clean starting state.\n\n## Context\nGitHub issue #479 - users sometimes get beads into an invalid state after updates, and there's no clean way to start fresh. The git backup/restore mechanism that protects against accidental deletion also makes it hard to intentionally reset.\n\n## Design\n\n### Command Interface\n```\nbd reset [--hard] [--force] [--backup] [--dry-run] [--no-init]\n```\n\n| Flag | Effect |\n|------|--------|\n| `--hard` | Also remove from git index and commit |\n| `--force` | Skip confirmation prompt |\n| `--backup` | Create `.beads-backup-{timestamp}/` first |\n| `--dry-run` | Preview what would happen |\n| `--no-init` | Don't re-initialize after clearing |\n\n### Reset Levels\n1. **Soft Reset (default)** - Kill daemons, clear .beads/, re-init. Git history unchanged.\n2. **Hard Reset (`--hard`)** - Also git rm and commit the removal, then commit fresh state.\n\n### Implementation Flow\n1. Validate .beads/ exists\n2. If not --force: show impact summary, prompt confirmation\n3. If --backup: copy .beads/ to .beads-backup-{timestamp}/\n4. Kill daemons\n5. If --hard: git rm + commit\n6. rm -rf .beads/*\n7. If not --no-init: bd init (and git add+commit if --hard)\n8. Print summary\n\n### Safety Mechanisms\n- Confirmation prompt (skip with --force)\n- Impact summary (issue/tombstone counts)\n- Backup option\n- Dry-run preview\n- Git dirty check warning\n\n### Code Structure\n- `cmd/bd/reset.go` - CLI command\n- `internal/reset/` - Core logic package","acceptance_criteria":"- [ ] `bd reset` clears local state and re-initializes\n- [ ] `bd reset --hard` also handles git operations\n- [ ] `bd reset --backup` creates timestamped backup\n- [ ] `bd reset --dry-run` shows preview without action\n- [ ] Confirmation prompt shown by default\n- [ ] `bd doctor` suggests reset for severely broken states\n- [ ] All new code has tests\n- [ ] Responds to GitHub issue #479 with solution","status":"open","priority":2,"issue_type":"epic","created_at":"2025-12-13T08:44:01.38379+11:00","updated_at":"2025-12-13T08:44:01.38379+11:00","external_ref":"gh-479"} +{"id":"bd-aydr","title":"Add bd reset command for clean slate restart","description":"Implement a `bd reset` command to reset beads to a clean starting state.\n\n## Context\nGitHub issue #479 - users sometimes get beads into an invalid state after updates, and there's no clean way to start fresh. The git backup/restore mechanism that protects against accidental deletion also makes it hard to intentionally reset.\n\n## Design\n\n### Command Interface\n```\nbd reset [--hard] [--force] [--backup] [--dry-run] [--no-init]\n```\n\n| Flag | Effect |\n|------|--------|\n| `--hard` | Also remove from git index and commit |\n| `--force` | Skip confirmation prompt |\n| `--backup` | Create `.beads-backup-{timestamp}/` first |\n| `--dry-run` | Preview what would happen |\n| `--no-init` | Don't re-initialize after clearing |\n\n### Reset Levels\n1. **Soft Reset (default)** - Kill daemons, clear .beads/, re-init. Git history unchanged.\n2. **Hard Reset (`--hard`)** - Also git rm and commit the removal, then commit fresh state.\n\n### Implementation Flow\n1. Validate .beads/ exists\n2. If not --force: show impact summary, prompt confirmation\n3. If --backup: copy .beads/ to .beads-backup-{timestamp}/\n4. Kill daemons\n5. If --hard: git rm + commit\n6. rm -rf .beads/*\n7. If not --no-init: bd init (and git add+commit if --hard)\n8. Print summary\n\n### Safety Mechanisms\n- Confirmation prompt (skip with --force)\n- Impact summary (issue/tombstone counts)\n- Backup option\n- Dry-run preview\n- Git dirty check warning\n\n### Code Structure\n- `cmd/bd/reset.go` - CLI command\n- `internal/reset/` - Core logic package","acceptance_criteria":"- [ ] `bd reset` clears local state and re-initializes\n- [ ] `bd reset --hard` also handles git operations\n- [ ] `bd reset --backup` creates timestamped backup\n- [ ] `bd reset --dry-run` shows preview without action\n- [ ] Confirmation prompt shown by default\n- [ ] `bd doctor` suggests reset for severely broken states\n- [ ] All new code has tests\n- [ ] Responds to GitHub issue #479 with solution","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-13T08:44:01.38379+11:00","updated_at":"2025-12-13T10:18:19.965287+11:00","closed_at":"2025-12-13T10:18:19.965287+11:00","external_ref":"gh-479"} {"id":"bd-aydr.1","title":"Implement core reset package (internal/reset)","description":"Create the core reset logic in internal/reset/ package.\n\n## Responsibilities\n- ResetOptions struct with all flag options\n- CountImpact() - count issues/tombstones that will be deleted\n- ValidateState() - check .beads/ exists, check git dirty state\n- ExecuteReset() - main reset logic (without CLI concerns)\n- Integrate with daemon killall\n\n## Interface Design\n```go\ntype ResetOptions struct {\n Hard bool // Include git operations (git rm, commit)\n Backup bool // Create backup before reset\n DryRun bool // Preview only, don't execute\n SkipInit bool // Don't re-initialize after reset\n}\n\ntype ResetResult struct {\n IssuesDeleted int\n TombstonesDeleted int\n BackupPath string // if backup was created\n DaemonsKilled int\n}\n\ntype ImpactSummary struct {\n IssueCount int\n OpenCount int\n ClosedCount int\n TombstoneCount int\n HasUncommitted bool // git dirty state\n}\n\nfunc Reset(opts ResetOptions) (*ResetResult, error)\nfunc CountImpact() (*ImpactSummary, error)\nfunc ValidateState() error\n```\n\n## IMPORTANT: CLI vs Core Separation\n- `Force` (skip confirmation) is NOT in ResetOptions - that's a CLI concern\n- Core always executes when called; CLI decides whether to prompt first\n- Keep CLI-agnostic: no prompts, no colored output, no user interaction\n- Return errors for CLI to handle with user-friendly messages\n- Unit testable in isolation\n\n## Dependencies\n- Uses daemon.KillAllDaemons() from internal/daemon/\n- Calls bd init logic after reset (unless SkipInit)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:44:50.145364+11:00","updated_at":"2025-12-13T09:20:06.184893+11:00","closed_at":"2025-12-13T09:20:06.184893+11:00","dependencies":[{"issue_id":"bd-aydr.1","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:50.145775+11:00","created_by":"daemon"}]} {"id":"bd-aydr.2","title":"Implement backup functionality for reset","description":"Add backup capability that can be used by reset command.\n\n## Functionality\n- Copy .beads/ to .beads-backup-{timestamp}/\n- Timestamp format: YYYYMMDD-HHMMSS\n- Preserve file permissions\n- Return backup path for user feedback\n\n## Location\n`internal/reset/backup.go` - keep with reset package for now (YAGNI)\n\n## Interface\n```go\nfunc CreateBackup(beadsDir string) (backupPath string, err error)\n```\n\n## Notes\n- Simple recursive file copy, no compression needed\n- Error if backup dir already exists (unlikely with timestamp)\n- Backup directories SHOULD be gitignored\n- Add `.beads-backup-*/` pattern to .beads/.gitignore template in doctor package\n- Consider: ListBackups() for future `bd backup list` command (not for this PR)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:44:51.306103+11:00","updated_at":"2025-12-13T09:20:20.590488+11:00","closed_at":"2025-12-13T09:20:20.590488+11:00","dependencies":[{"issue_id":"bd-aydr.2","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:51.306474+11:00","created_by":"daemon"}]} {"id":"bd-aydr.3","title":"Add git operations for --hard reset","description":"Implement git integration for hard reset mode.\n\n## Operations Needed\n1. `git rm -rf .beads/*.jsonl` - remove data files from index\n2. `git commit -m 'beads: reset to clean state'` - commit removal\n3. After re-init: `git add .beads/` and commit fresh state\n\n## Edge Cases to Handle\n- Uncommitted changes in .beads/ - warn or error\n- Detached HEAD state - warn, maybe block\n- Git not initialized - skip git ops, warn\n- Git operations fail mid-way - clear error messaging\n\n## Interface\n```go\ntype GitState struct {\n IsRepo bool\n IsDirty bool // uncommitted changes in .beads/\n IsDetached bool // detached HEAD\n Branch string // current branch name\n}\n\nfunc CheckGitState(beadsDir string) (*GitState, error)\nfunc GitRemoveBeads(beadsDir string) error\nfunc GitCommitReset(message string) error\nfunc GitAddAndCommit(beadsDir, message string) error\n```\n\n## Location\n`internal/reset/git.go` - keep with reset package for now\n\nNote: Codebase has no central git package. internal/compact/git.go is compact-specific.\nFuture refactoring could extract shared git utilities, but YAGNI for now.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:44:52.798312+11:00","updated_at":"2025-12-13T09:17:40.785927+11:00","closed_at":"2025-12-13T09:17:40.785927+11:00","dependencies":[{"issue_id":"bd-aydr.3","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:52.798715+11:00","created_by":"daemon"}]} {"id":"bd-aydr.4","title":"Implement CLI command (cmd/bd/reset.go)","description":"Wire up the reset command with Cobra CLI.\n\n## Responsibilities\n- Define command and all flags\n- User confirmation prompt (unless --force)\n- Display impact summary before confirmation\n- Colored output and progress indicators\n- Call core reset package\n- Handle errors with user-friendly messages\n- Register command with rootCmd in init()\n\n## Flags\n```go\n--hard bool \"Also remove from git and commit\"\n--force bool \"Skip confirmation prompt\"\n--backup bool \"Create backup before reset\"\n--dry-run bool \"Preview what would happen\"\n--skip-init bool \"Do not re-initialize after reset\"\n--verbose bool \"Show detailed progress output\"\n```\n\n## Output Format\n```\n⚠️ This will reset beads to a clean state.\n\nWill be deleted:\n • 47 issues (23 open, 24 closed)\n • 12 tombstones\n\nContinue? [y/N] y\n\n→ Stopping daemons... ✓\n→ Removing .beads/... ✓\n→ Initializing fresh... ✓\n\n✓ Reset complete. Run 'bd onboard' to set up hooks.\n```\n\n## Implementation Notes\n- Confirmation logic lives HERE, not in core package\n- Use color package (github.com/fatih/color) for output\n- Follow patterns from other commands (init.go, doctor.go)\n- Add to rootCmd in init() function\n\n## File Location\n`cmd/bd/reset.go`","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:44:54.318854+11:00","updated_at":"2025-12-13T09:59:41.72638+11:00","closed_at":"2025-12-13T09:59:41.72638+11:00","dependencies":[{"issue_id":"bd-aydr.4","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:54.319237+11:00","created_by":"daemon"},{"issue_id":"bd-aydr.4","depends_on_id":"bd-aydr.1","type":"blocks","created_at":"2025-12-13T08:45:09.762138+11:00","created_by":"daemon"},{"issue_id":"bd-aydr.4","depends_on_id":"bd-aydr.2","type":"blocks","created_at":"2025-12-13T08:45:09.817854+11:00","created_by":"daemon"},{"issue_id":"bd-aydr.4","depends_on_id":"bd-aydr.3","type":"blocks","created_at":"2025-12-13T08:45:09.883658+11:00","created_by":"daemon"}]} -{"id":"bd-aydr.5","title":"Enhance bd doctor to suggest reset for broken states","description":"Update bd doctor to detect severely broken states and suggest reset.\n\n## Detection Criteria\nSuggest reset when:\n- Multiple unfixable errors detected\n- Corrupted JSONL that can't be repaired\n- Schema version mismatch that can't be migrated\n- Daemon state inconsistent and unkillable\n\n## Implementation\nAdd to doctor's check/fix flow:\n```go\nif unfixableErrors \u003e threshold {\n suggest('State may be too broken to fix. Consider: bd reset')\n}\n```\n\n## Output Example\n```\n✗ Found 5 unfixable errors\n \n Your beads state may be too corrupted to repair.\n Consider running 'bd reset' to start fresh.\n (Use 'bd reset --backup' to save current state first)\n```\n\n## Notes\n- Don't auto-run reset, just suggest\n- This is lower priority, can be done in parallel with main work","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-13T08:44:55.591986+11:00","updated_at":"2025-12-13T08:44:55.591986+11:00","dependencies":[{"issue_id":"bd-aydr.5","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:55.59239+11:00","created_by":"daemon"}]} +{"id":"bd-aydr.5","title":"Enhance bd doctor to suggest reset for broken states","description":"Update bd doctor to detect severely broken states and suggest reset.\n\n## Detection Criteria\nSuggest reset when:\n- Multiple unfixable errors detected\n- Corrupted JSONL that can't be repaired\n- Schema version mismatch that can't be migrated\n- Daemon state inconsistent and unkillable\n\n## Implementation\nAdd to doctor's check/fix flow:\n```go\nif unfixableErrors \u003e threshold {\n suggest('State may be too broken to fix. Consider: bd reset')\n}\n```\n\n## Output Example\n```\n✗ Found 5 unfixable errors\n \n Your beads state may be too corrupted to repair.\n Consider running 'bd reset' to start fresh.\n (Use 'bd reset --backup' to save current state first)\n```\n\n## Notes\n- Don't auto-run reset, just suggest\n- This is lower priority, can be done in parallel with main work","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-13T08:44:55.591986+11:00","updated_at":"2025-12-13T10:17:23.4522+11:00","closed_at":"2025-12-13T10:17:23.4522+11:00","dependencies":[{"issue_id":"bd-aydr.5","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:55.59239+11:00","created_by":"daemon"}]} {"id":"bd-aydr.6","title":"Add unit tests for reset package","description":"Comprehensive unit tests for internal/reset package.\n\n## Test Cases\n\n### ValidateState tests\n- .beads/ exists → success\n- .beads/ missing → appropriate error\n- git dirty state detection\n\n### CountImpact tests \n- Empty .beads/ → zero counts\n- With issues → correct count (open vs closed)\n- With tombstones → correct count\n- Returns HasUncommitted correctly\n\n### Backup tests\n- Creates backup with correct timestamp format\n- Preserves all files and permissions\n- Returns correct path\n- Handles missing .beads/ gracefully\n- Errors on pre-existing backup dir\n\n### Git operation tests\n- CheckGitState detects dirty, detached, not-a-repo\n- GitRemoveBeads removes correct files\n- GitCommitReset creates commit with message\n- Operations skip gracefully when not in git repo\n\n### Reset tests (with mocks/temp dirs)\n- Soft reset removes files, calls init\n- Hard reset includes git operations\n- Dry run doesn't modify anything\n- SkipInit flag prevents re-initialization\n- Daemon killall is called\n- Backup is created when requested\n\n## Approach\n- Can start with interface definitions (TDD style)\n- Use testify for assertions\n- Create temp directories for isolation\n- Mock git operations where needed\n- Test completion depends on implementation tasks\n\n## File Location\n`internal/reset/reset_test.go`\n`internal/reset/backup_test.go`\n`internal/reset/git_test.go`","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:44:57.01739+11:00","updated_at":"2025-12-13T09:59:20.820314+11:00","closed_at":"2025-12-13T09:59:20.820314+11:00","dependencies":[{"issue_id":"bd-aydr.6","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:57.017813+11:00","created_by":"daemon"}]} -{"id":"bd-aydr.7","title":"Add integration tests for bd reset command","description":"End-to-end integration tests for the reset command.\n\n## Test Scenarios\n\n### Basic reset\n1. Init beads, create some issues\n2. Run bd reset --force\n3. Verify .beads/ is fresh, issues gone\n\n### Hard reset\n1. Init beads, create issues, commit\n2. Run bd reset --hard --force \n3. Verify git history has reset commits\n\n### Backup functionality\n1. Init beads, create issues\n2. Run bd reset --backup --force\n3. Verify backup exists with correct contents\n4. Verify main .beads/ is reset\n\n### Dry run\n1. Init beads, create issues\n2. Run bd reset --dry-run\n3. Verify nothing changed\n\n### Confirmation prompt\n1. Init beads\n2. Run bd reset (no --force)\n3. Verify prompts for confirmation\n4. Test both y and n responses\n\n## Location\ntests/integration/reset_test.go or similar","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-13T08:44:58.479282+11:00","updated_at":"2025-12-13T08:44:58.479282+11:00","dependencies":[{"issue_id":"bd-aydr.7","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:58.479686+11:00","created_by":"daemon"},{"issue_id":"bd-aydr.7","depends_on_id":"bd-aydr.4","type":"blocks","created_at":"2025-12-13T08:45:11.15972+11:00","created_by":"daemon"}]} -{"id":"bd-aydr.8","title":"Respond to GitHub issue #479 with solution","description":"Once bd reset is implemented and released, respond to GitHub issue #479.\n\n## Response should include\n- Announce the new bd reset command\n- Show basic usage examples\n- Link to any documentation\n- Thank the user for the feedback\n\n## Example response\n```\nThanks for raising this! We've added a `bd reset` command to handle this case.\n\nUsage:\n- `bd reset` - Reset to clean state (prompts for confirmation)\n- `bd reset --backup` - Create backup first\n- `bd reset --hard` - Also clean up git history\n\nThis is available in version X.Y.Z.\n```\n\n## Notes\n- Wait until feature is merged and released\n- Consider if issue should be closed or left for user confirmation","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-13T08:45:00.112351+11:00","updated_at":"2025-12-13T08:45:00.112351+11:00","dependencies":[{"issue_id":"bd-aydr.8","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:45:00.112732+11:00","created_by":"daemon"},{"issue_id":"bd-aydr.8","depends_on_id":"bd-aydr.7","type":"blocks","created_at":"2025-12-13T08:45:12.640243+11:00","created_by":"daemon"}]} +{"id":"bd-aydr.7","title":"Add integration tests for bd reset command","description":"End-to-end integration tests for the reset command.\n\n## Test Scenarios\n\n### Basic reset\n1. Init beads, create some issues\n2. Run bd reset --force\n3. Verify .beads/ is fresh, issues gone\n\n### Hard reset\n1. Init beads, create issues, commit\n2. Run bd reset --hard --force \n3. Verify git history has reset commits\n\n### Backup functionality\n1. Init beads, create issues\n2. Run bd reset --backup --force\n3. Verify backup exists with correct contents\n4. Verify main .beads/ is reset\n\n### Dry run\n1. Init beads, create issues\n2. Run bd reset --dry-run\n3. Verify nothing changed\n\n### Confirmation prompt\n1. Init beads\n2. Run bd reset (no --force)\n3. Verify prompts for confirmation\n4. Test both y and n responses\n\n## Location\ntests/integration/reset_test.go or similar","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-13T08:44:58.479282+11:00","updated_at":"2025-12-13T10:15:59.221637+11:00","closed_at":"2025-12-13T10:15:59.221637+11:00","dependencies":[{"issue_id":"bd-aydr.7","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:44:58.479686+11:00","created_by":"daemon"},{"issue_id":"bd-aydr.7","depends_on_id":"bd-aydr.4","type":"blocks","created_at":"2025-12-13T08:45:11.15972+11:00","created_by":"daemon"}]} +{"id":"bd-aydr.8","title":"Respond to GitHub issue #479 with solution","description":"Once bd reset is implemented and released, respond to GitHub issue #479.\n\n## Response should include\n- Announce the new bd reset command\n- Show basic usage examples\n- Link to any documentation\n- Thank the user for the feedback\n\n## Example response\n```\nThanks for raising this! We've added a `bd reset` command to handle this case.\n\nUsage:\n- `bd reset` - Reset to clean state (prompts for confirmation)\n- `bd reset --backup` - Create backup first\n- `bd reset --hard` - Also clean up git history\n\nThis is available in version X.Y.Z.\n```\n\n## Notes\n- Wait until feature is merged and released\n- Consider if issue should be closed or left for user confirmation","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-13T08:45:00.112351+11:00","updated_at":"2025-12-13T10:18:06.646796+11:00","closed_at":"2025-12-13T10:18:06.646796+11:00","dependencies":[{"issue_id":"bd-aydr.8","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:45:00.112732+11:00","created_by":"daemon"},{"issue_id":"bd-aydr.8","depends_on_id":"bd-aydr.7","type":"blocks","created_at":"2025-12-13T08:45:12.640243+11:00","created_by":"daemon"}]} {"id":"bd-aydr.9","title":"Add .beads-backup-* pattern to gitignore template","description":"Update the gitignore template in doctor package to include backup directories.\n\n## Change\nAdd `.beads-backup-*/` to the GitignoreTemplate in `cmd/bd/doctor/gitignore.go`\n\n## Why\nBackup directories created by `bd reset --backup` should not be committed to git.\nThey are local-only recovery tools.\n\n## File\n`cmd/bd/doctor/gitignore.go` - look for GitignoreTemplate constant","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-13T08:49:42.453483+11:00","updated_at":"2025-12-13T09:16:44.201889+11:00","closed_at":"2025-12-13T09:16:44.201889+11:00","dependencies":[{"issue_id":"bd-aydr.9","depends_on_id":"bd-aydr","type":"parent-child","created_at":"2025-12-13T08:49:42.453886+11:00","created_by":"daemon"}]} {"id":"bd-azh","title":"Fix bd doctor --fix recursive message for deletions manifest","description":"When running bd doctor --fix, if the deletions manifest check fails but there are no deleted issues in git history, the fix succeeds but doesn't create the file. The check then runs again and tells user to run bd doctor --fix - the same command they just ran.\n\nFix: Create empty deletions.jsonl when hydration finds no deletions, and recognize empty file as valid in the check.\n\nFixes: https://github.com/steveyegge/beads/issues/403","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-27T12:41:09.426143-08:00","updated_at":"2025-11-27T12:41:23.521981-08:00","closed_at":"2025-11-27T12:41:23.521981-08:00"} {"id":"bd-b8h","title":"Refactor check-health DB access to avoid repeated path resolution","description":"The runCheckHealth lightweight checks (hintsDisabled, checkVersionMismatch, checkSyncBranchQuick) each have duplicated database path resolution logic. Extract a helper function to DRY this up.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-25T19:27:35.075929-08:00","updated_at":"2025-11-25T19:50:21.272961-08:00","closed_at":"2025-11-25T19:50:21.272961-08:00"} +{"id":"bd-bgm","title":"Fix unparam unused parameter in cmd/bd/doctor.go:1879","description":"Linting issue: checkGitHooks - path is unused (unparam) at cmd/bd/doctor.go:1879:20. Error: func checkGitHooks(path string) doctorCheck {","status":"open","priority":0,"issue_type":"bug","created_at":"2025-12-07T15:35:25.270293252-07:00","updated_at":"2025-12-07T15:35:25.270293252-07:00"} {"id":"bd-bgs","title":"Git history fallback doesn't escape regex special chars in IDs","description":"## Problem\n\nIn `batchCheckGitHistory`, IDs are directly interpolated into a regex pattern:\n\n```go\npatterns = append(patterns, fmt.Sprintf(\\`\"id\":\"%s\"\\`, id))\nsearchPattern := strings.Join(patterns, \"|\")\ncmd := exec.Command(\"git\", \"log\", \"--all\", \"-G\", searchPattern, ...)\n```\n\nIf an ID contains regex special characters (e.g., `bd-foo.bar` or `bd-test+1`), the pattern will be malformed or match unintended strings.\n\n## Location\n`internal/importer/importer.go:923-926`\n\n## Impact\n- False positives: IDs with `.` could match any character\n- Regex errors: IDs with `[` or `(` could cause git to fail\n- Security: potential for regex injection (low risk since IDs are validated)\n\n## Fix\nEscape regex special characters:\n\n```go\nimport \"regexp\"\n\nescapedID := regexp.QuoteMeta(id)\npatterns = append(patterns, fmt.Sprintf(\\`\"id\":\"%s\"\\`, escapedID))\n```","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-25T12:50:30.132232-08:00","updated_at":"2025-11-25T15:04:06.217695-08:00","closed_at":"2025-11-25T15:04:06.217695-08:00"} {"id":"bd-bhd","title":"Git history fallback assumes .beads is direct child of repo root","description":"## Problem\n\n`checkGitHistoryForDeletions` assumes the repo structure:\n\n```go\nrepoRoot := filepath.Dir(beadsDir) // Assumes .beads is in repo root\njsonlPath := filepath.Join(\".beads\", \"beads.jsonl\")\n```\n\nBut `.beads` could be in a subdirectory (monorepo, nested project), and the actual JSONL filename could be different (configured via `metadata.json`).\n\n## Location\n`internal/importer/importer.go:865-869`\n\n## Impact\n- Git search will fail silently for repos with non-standard structure\n- Monorepo users won't get deletion propagation\n\n## Fix\n1. Use `git rev-parse --show-toplevel` to find actual repo root\n2. Compute relative path from repo root to JSONL\n3. Or use `git -C \u003cdir\u003e` to run from beadsDir directly","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-25T12:51:03.46856-08:00","updated_at":"2025-11-25T15:05:40.754716-08:00","closed_at":"2025-11-25T15:05:40.754716-08:00"} {"id":"bd-bob","title":"Missing test: base is tombstone, both left and right are live","description":"The tombstone merge tests do not cover the case where the base version is a tombstone but both left and right have resurrected/live versions. This scenario could occur if: 1) Clone A deletes an issue, 2) Clone B and Clone C both sync from A (getting tombstone), 3) Both B and C independently recreate an issue with same ID. The current code would likely fall through to standard mergeIssue() which may not be correct. Need to add test and verify behavior. Files: internal/merge/merge_test.go, internal/merge/merge.go:347-387","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-05T16:36:34.895944-08:00","updated_at":"2025-12-13T08:14:58.904636+11:00","closed_at":"2025-12-07T02:27:40.788701-08:00"} {"id":"bd-bok","title":"bd doctor --fix needs non-interactive mode (-y/--yes flag)","description":"When running `bd doctor --fix` in non-interactive mode (scripts, CI, Claude Code), it prompts 'Continue? (Y/n):' and fails with EOF.\n\n**Expected**: A `-y` or `--yes` flag to auto-confirm fixes.\n\n**Workaround**: Currently have to run `bd init` instead, but that's not discoverable from the doctor output.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-27T20:21:10.290649-08:00","updated_at":"2025-11-30T10:50:04.270576-08:00","closed_at":"2025-11-28T21:56:14.708313-08:00"} {"id":"bd-bt6y","title":"Improve compact/daemon/merge documentation and UX","description":"Multiple documentation and UX issues encountered:\n1. \"bd compact --analyze\" fails with misleading \"requires SQLite storage\" error when daemon is running. Needs --no-daemon or better error.\n2. \"bd merge\" help text is outdated (refers to 3-way merge instead of issue merging).\n3. Daemon mode purpose isn't clear to local-only users.\n4. Compact/cleanup commands are hard to discover.\n\nProposed fixes:\n- Fix compact+daemon interaction or error message.\n- Update \"bd merge\" help text.\n- Add \"when to use daemon\" section to docs.\n- Add maintenance section to quickstart.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-20T18:55:43.637047-05:00","updated_at":"2025-11-30T10:50:04.270853-08:00","closed_at":"2025-11-28T23:10:43.884784-08:00"} +{"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":"open","priority":0,"issue_type":"bug","created_at":"2025-12-07T15:35:09.008444133-07:00","updated_at":"2025-12-07T15:35:09.008444133-07:00"} {"id":"bd-bx9","title":"bd init --contributor should configure sync.remote=upstream for fork workflows","description":"When running `bd init --contributor` in a fork workflow (where `upstream` remote points to the original repo), the wizard should configure beads to sync from `upstream/main` rather than `origin/main`.\n\n**Current behavior:**\n- Contributor mode detects the fork setup (upstream remote exists)\n- Sets up planning repo and auto-routing\n- Does NOT configure sync remote\n- `bd sync` on feature branches shows \"No upstream configured, using --from-main mode\" and syncs from `origin/main`\n\n**Expected behavior:**\n- Contributor mode should also set `sync.remote = upstream` (or similar config)\n- `bd sync` should pull beads from `upstream/main` (source of truth)\n\n**Why this matters:**\n- The fork's `origin/main` may be behind `upstream/main`\n- Contributors want the latest issues from the source repo\n- Code PRs go: local -\u003e origin -\u003e upstream, but beads should come FROM upstream\n\n**Suggested fix:**\nAdd to `runContributorWizard()` after detecting fork:\n```go\nif isFork {\n store.SetConfig(ctx, \"sync.remote\", \"upstream\")\n}\n```","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-29T00:39:05.137488727-05:00","updated_at":"2025-11-29T23:23:01.219117-08:00","closed_at":"2025-11-29T23:22:57.631519-08:00","labels":["contributor","sync"]} {"id":"bd-c362","title":"Extract database search logic into helper function","description":"The logic for finding a database in a beads directory is duplicated:\n- FindDatabasePath() BEADS_DIR section (beads.go:141-169)\n- findDatabaseInTree() (beads.go:248-280)\n\nBoth implement the same search order:\n1. Check config.json first (single source of truth)\n2. Fall back to canonical beads.db\n3. Search for *.db files, filtering backups and vc.db\n\nRefactoring suggestion:\nExtract to a helper function like:\n func findDatabaseInBeadsDir(beadsDir string) string\n\nBenefits:\n- Single source of truth for database search logic\n- Easier to maintain and update search order\n- Reduces code duplication\n\nRelated to [deleted:bd-e16b] implementation.","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-11-02T18:34:02.831543-08:00","updated_at":"2025-11-25T22:27:33.794656-08:00","closed_at":"2025-11-25T22:27:33.794656-08:00"} {"id":"bd-c4rq","title":"Refactor: Move staleness check inside daemon branch","description":"## Problem\n\nCurrently ensureDatabaseFresh() is called before the daemon mode check, but it checks daemonClient != nil internally and returns early. This is redundant.\n\n**Location:** All read commands (list.go:196, show.go:27, ready.go:102, status.go:80, etc.)\n\n## Current Pattern\n\nCall happens before daemon check, function checks daemonClient internally.\n\n## Better Pattern\n\nMove staleness check to direct mode branch only, after daemon check.\n\n## Impact\nLow - minor performance improvement (avoids one function call per command in daemon mode)\n\n## Effort\nMedium - requires refactoring 8 command files\n\n## Priority\nLow - can defer to future cleanup PR","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-11-20T20:17:45.119583-05:00","updated_at":"2025-11-30T10:50:04.271139-08:00","closed_at":"2025-11-28T23:37:52.276192-08:00"} {"id":"bd-c5m","title":"Safety check tests use string(rune()) which only works for single digits","description":"","status":"closed","priority":4,"issue_type":"bug","created_at":"2025-12-02T21:55:41.688857-08:00","updated_at":"2025-12-02T22:10:15.616882-08:00","closed_at":"2025-12-02T22:10:15.616882-08:00"} +{"id":"bd-c6w","title":"bd info whats-new missing releases","description":"Current release is v0.25.1 but `bd info --whats-new` stops at v0.23.0, indicating something is missing in the create new release workflow.","notes":"github issue #386","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-26T05:50:37.252394374-07:00","updated_at":"2025-11-26T06:28:21.974264087-07:00","closed_at":"2025-11-26T06:28:21.974264087-07:00"} {"id":"bd-c8x","title":"Don't search parent directories for .beads databases","description":"bd currently walks up the directory tree looking for .beads directories, which can find unrelated databases (e.g., ~/.beads). This causes confusing warnings and potential data pollution.\n\nShould either:\n1. Stop at git root (don't search above it)\n2. Only use explicit BEADS_DB env var or local .beads\n3. At minimum, don't search in home directory","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-27T22:10:41.992686-08:00","updated_at":"2025-11-30T10:50:04.271413-08:00","closed_at":"2025-11-28T22:15:55.878353-08:00"} {"id":"bd-ciu","title":"formatVanishedIssues output order is non-deterministic","description":"","status":"closed","priority":3,"issue_type":"bug","created_at":"2025-12-02T21:55:32.897206-08:00","updated_at":"2025-12-02T22:07:23.677332-08:00","closed_at":"2025-12-02T22:07:23.677332-08:00"} {"id":"bd-clg","title":"bd jira sync command","description":"Add a built-in bd command for Jira synchronization.\n\n**Requires**: Both import and export scripts working\n\n**Features**:\n- `bd jira sync --pull` - Import from Jira to beads\n- `bd jira sync --push` - Export from beads to Jira\n- `bd jira sync` - Bidirectional (pull then push, with conflict resolution)\n- `bd jira status` - Show sync status and last sync time\n\n**Conflict resolution**:\n- Timestamp-based: newer update wins\n- Option for --prefer-local or --prefer-jira to override\n- Interactive mode for manual conflict resolution (optional)\n\n**Integration**:\n- Uses jira.* config settings from bd config\n- Stores last sync timestamp in config\n- Logs sync activity for audit\n\n**Stretch goals**:\n- Webhook integration for real-time sync\n- Selective sync by JQL filter","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-11-30T12:56:27.716537-08:00","updated_at":"2025-11-30T15:25:37.896045-08:00","closed_at":"2025-11-30T15:25:37.896045-08:00","dependencies":[{"issue_id":"bd-clg","depends_on_id":"bd-qvj","type":"parent-child","created_at":"2025-11-30T12:56:49.796568-08:00","created_by":"stevey"},{"issue_id":"bd-clg","depends_on_id":"bd-tjn","type":"blocks","created_at":"2025-11-30T12:57:00.075288-08:00","created_by":"stevey"},{"issue_id":"bd-clg","depends_on_id":"bd-93d","type":"blocks","created_at":"2025-11-30T12:57:05.206431-08:00","created_by":"stevey"}]} {"id":"bd-co0","title":"Imperator does not know its own mail identity","description":"","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-30T22:53:49.963803-08:00","updated_at":"2025-12-02T23:28:48.752455-08:00","closed_at":"2025-12-01T22:01:26.953199-08:00"} {"id":"bd-d0t","title":"Priority 0 in merge may incorrectly win over set priorities","description":"In mergePriority(), priority 0 (which may mean 'unset' due to Go's zero value) beats any explicitly set priority like P1, P2, etc. Should probably treat 0 as 'no priority set' and not let it win conflicts.","status":"closed","priority":3,"issue_type":"bug","created_at":"2025-12-02T20:14:58.906543-08:00","updated_at":"2025-12-03T22:16:02.66552-08:00","closed_at":"2025-12-03T22:16:02.66552-08:00"} {"id":"bd-d4i","title":"Create tip system infrastructure for contextual hints","description":"Implement a tip/hint system that shows helpful contextual messages after successful commands. This is different from the existing error-path \"Hint:\" messages - tips appear on success paths to educate users about features they might not know about.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-11T23:29:15.693956-08:00","updated_at":"2025-11-25T17:47:30.747566-08:00","closed_at":"2025-11-25T17:47:30.747566-08:00"} +{"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-dli","title":"Design migration path from deletions.jsonl to tombstones","description":"Plan the migration from current deletions manifest to inline tombstones. Steps: 1) Read existing deletions.jsonl and convert to tombstones during import, 2) Continue reading deletions.jsonl for backward compat, 3) Stop writing to deletions.jsonl, 4) Eventually remove deletions.jsonl support. Consider version flags and deprecation warnings.","design":"# Design: Migration Path from deletions.jsonl to Tombstones\n\n**Issue:** bd-dli \n**Author:** beads/refinery \n**Status:** Draft \n**Date:** 2025-12-05\n\n## Overview\n\nThis document describes the migration strategy for transitioning from the current `deletions.jsonl` manifest to inline tombstones in `issues.jsonl`. The migration must be backward-compatible and support mixed-version environments during rollout.\n\n## Migration Phases\n\n### Phase 1: Dual-Write (Transitional)\n\n**Duration:** 2 minor releases (e.g., v0.9.0 → v0.11.0)\n\n| Action | deletions.jsonl | issues.jsonl |\n|--------|-----------------|--------------|\n| Delete issue | Write | Write tombstone |\n| Read deletions | Read | Read tombstones |\n| Sync | Include | Include |\n\n**Behavior:**\n- `bd delete` writes to BOTH locations\n- Import reads from BOTH locations\n- Tombstones take precedence when duplicates exist\n\n**Why dual-write:**\n- Old `bd` versions can still read `deletions.jsonl`\n- New `bd` versions see tombstones\n- No data loss during transition\n\n### Phase 2: Read-Only Legacy (Deprecated)\n\n**Duration:** 2 minor releases\n\n| Action | deletions.jsonl | issues.jsonl |\n|--------|-----------------|--------------|\n| Delete issue | No write | Write tombstone |\n| Read deletions | Read (deprecated) | Read tombstones |\n| Sync | Include (read-only) | Include |\n\n**Behavior:**\n- `bd delete` only writes tombstone to `issues.jsonl`\n- Import still reads `deletions.jsonl` for backward compat\n- Warning: `\"deletions.jsonl is deprecated, will be removed in v1.0\"`\n\n### Phase 3: Removal\n\n**Version:** v1.0.0\n\n| Action | deletions.jsonl | issues.jsonl |\n|--------|-----------------|--------------|\n| Delete issue | N/A | Write tombstone |\n| Read deletions | Ignored | Read tombstones |\n| Sync | Ignored | Include |\n\n**Behavior:**\n- `deletions.jsonl` completely ignored\n- Can be safely deleted from `.beads/`\n- `bd doctor` warns if stale `deletions.jsonl` exists\n\n## Migration Script: `bd migrate-tombstones`\n\nA one-time migration command to convert existing deletions:\n\n```bash\nbd migrate-tombstones [--dry-run] [--verbose]\n```\n\n### Algorithm\n\n```go\nfunc migrateTombstones(beadsDir string, dryRun bool) error {\n // 1. Load existing deletions\n deletionsPath := filepath.Join(beadsDir, \"deletions.jsonl\")\n loadResult, err := deletions.LoadDeletions(deletionsPath)\n if err != nil {\n return err\n }\n \n // 2. Load existing issues.jsonl\n issuesPath := filepath.Join(beadsDir, \"issues.jsonl\")\n issues, err := loadIssues(issuesPath)\n if err != nil {\n return err\n }\n \n // 3. Convert deletions to tombstones\n issueMap := make(map[string]*Issue)\n for _, issue := range issues {\n issueMap[issue.ID] = issue\n }\n \n var newTombstones []*Issue\n for id, record := range loadResult.Records {\n // Skip if tombstone already exists\n if existing, ok := issueMap[id]; ok \u0026\u0026 IsTombstone(existing) {\n continue\n }\n \n tombstone := \u0026Issue{\n ID: id,\n Status: \"tombstone\",\n Title: \"(deleted)\",\n DeletedAt: \u0026record.Timestamp,\n DeletedBy: record.Actor,\n DeleteReason: record.Reason,\n UpdatedAt: record.Timestamp,\n // Note: original_type unknown from deletions.jsonl\n }\n newTombstones = append(newTombstones, tombstone)\n }\n \n if dryRun {\n fmt.Printf(\"Would create %d tombstones\\n\", len(newTombstones))\n return nil\n }\n \n // 4. Append tombstones to issues.jsonl\n if err := appendIssues(issuesPath, newTombstones); err != nil {\n return err\n }\n \n // 5. Optionally archive deletions.jsonl\n archivePath := deletionsPath + \".migrated\"\n if err := os.Rename(deletionsPath, archivePath); err != nil {\n // Warn but don't fail\n fmt.Fprintf(os.Stderr, \"Warning: could not archive deletions.jsonl: %v\\n\", err)\n }\n \n fmt.Printf(\"Migrated %d deletions to tombstones\\n\", len(newTombstones))\n return nil\n}\n```\n\n### Field Mapping\n\n| deletions.jsonl | Tombstone |\n|-----------------|-----------|\n| `id` | `id` |\n| `ts` | `deleted_at` |\n| `by` | `deleted_by` |\n| `reason` | `delete_reason` |\n| (not available) | `original_type` |\n| (derived) | `updated_at` = `deleted_at` |\n| (constant) | `status` = \"tombstone\" |\n| (constant) | `title` = \"(deleted)\" |\n\n### Handling Missing Data\n\nThe `original_type` field is not available in `deletions.jsonl`. Options:\n\n1. **Leave empty** (recommended): `original_type: \"\"`\n2. Query git history to find original issue type (slow, complex)\n3. Default to `\"task\"` (misleading)\n\n**Recommendation:** Leave empty. The field is optional and only useful for stats.\n\n## Import Behavior Changes\n\n### Current Import Flow\n\n```go\nfunc Import(ctx context.Context) error {\n // 1. Read issues.jsonl\n issues := readJSONL(issuesPath)\n \n // 2. Read deletions.jsonl\n deletions := deletions.LoadDeletions(deletionsPath)\n \n // 3. Filter out deleted issues\n for _, issue := range issues {\n if _, isDeleted := deletions[issue.ID]; isDeleted {\n continue // Skip deleted issues\n }\n insertIssue(issue)\n }\n \n // 4. Store deletions for propagation\n storeDeletions(deletions)\n}\n```\n\n### New Import Flow\n\n```go\nfunc Import(ctx context.Context) error {\n // 1. Read issues.jsonl (includes tombstones)\n records := readJSONL(issuesPath)\n \n // 2. Build tombstone map\n tombstones := make(map[string]*Issue)\n for _, record := range records {\n if IsTombstone(record) {\n tombstones[record.ID] = record\n }\n }\n \n // 3. LEGACY: Read deletions.jsonl if exists\n if fileExists(deletionsPath) {\n legacyDeletions := deletions.LoadDeletions(deletionsPath)\n for id, record := range legacyDeletions {\n // Convert to tombstone if not already\n if _, exists := tombstones[id]; !exists {\n tombstones[id] = convertToTombstone(record)\n }\n }\n \n // Emit deprecation warning\n warnOnce(\"deletions.jsonl is deprecated, run 'bd migrate-tombstones'\")\n }\n \n // 4. Import non-tombstone issues\n for _, record := range records {\n if IsTombstone(record) {\n // Store tombstone in DB for sync propagation\n storeTombstone(record)\n continue\n }\n \n // Skip if tombstoned\n if _, isDeleted := tombstones[record.ID]; isDeleted {\n continue\n }\n \n insertIssue(record)\n }\n}\n```\n\n## Database Schema Changes\n\nNew columns for tombstone storage:\n\n```sql\n-- Migration 00X_tombstone_fields.sql\nALTER TABLE issues ADD COLUMN deleted_at TEXT;\nALTER TABLE issues ADD COLUMN deleted_by TEXT;\nALTER TABLE issues ADD COLUMN delete_reason TEXT;\nALTER TABLE issues ADD COLUMN original_type TEXT;\n\n-- Index for tombstone filtering\nCREATE INDEX idx_issues_status_deleted ON issues(status) WHERE status = 'tombstone';\n```\n\nThe separate `deletions` table (if exists) becomes unused but is not dropped during migration.\n\n## Export Behavior Changes\n\n### Current Export Flow\n\n```go\nfunc Export(ctx context.Context) error {\n // 1. Export issues to issues.jsonl\n issues := getAllIssues()\n writeJSONL(issuesPath, issues)\n \n // 2. Export deletions to deletions.jsonl (separate file)\n deletions := getAllDeletions()\n writeDeletions(deletionsPath, deletions)\n}\n```\n\n### New Export Flow (Phase 1: Dual-Write)\n\n```go\nfunc Export(ctx context.Context) error {\n // 1. Get all records including tombstones\n allRecords := getAllIssuesIncludingTombstones()\n \n // 2. Write to issues.jsonl (includes tombstones)\n writeJSONL(issuesPath, allRecords)\n \n // 3. LEGACY: Also write to deletions.jsonl\n var deletionRecords []DeletionRecord\n for _, record := range allRecords {\n if IsTombstone(record) {\n deletionRecords = append(deletionRecords, tombstoneToLegacy(record))\n }\n }\n deletions.WriteDeletions(deletionsPath, deletionRecords)\n}\n```\n\n### New Export Flow (Phase 2+: No Legacy Write)\n\n```go\nfunc Export(ctx context.Context) error {\n // Export all records including tombstones\n allRecords := getAllIssuesIncludingTombstones()\n writeJSONL(issuesPath, allRecords)\n // No more deletions.jsonl writes\n}\n```\n\n## Version Detection\n\nTo determine which phase applies:\n\n```go\ntype MigrationPhase int\n\nconst (\n PhaseDualWrite MigrationPhase = 1\n PhaseReadOnlyLegacy MigrationPhase = 2\n PhaseRemoval MigrationPhase = 3\n)\n\nfunc detectPhase() MigrationPhase {\n // Check bd version\n if version.Compare(currentVersion, \"1.0.0\") \u003e= 0 {\n return PhaseRemoval\n }\n if version.Compare(currentVersion, \"0.11.0\") \u003e= 0 {\n return PhaseReadOnlyLegacy\n }\n return PhaseDualWrite\n}\n```\n\n## Doctor Checks\n\nNew `bd doctor` checks for migration:\n\n```go\nfunc checkTombstoneMigration() []Warning {\n var warnings []Warning\n \n // Check for unmigrated deletions.jsonl\n if fileExists(deletionsPath) {\n deletions := loadDeletions(deletionsPath)\n tombstones := loadTombstones(issuesPath)\n \n unmigrated := 0\n for id := range deletions {\n if _, exists := tombstones[id]; !exists {\n unmigrated++\n }\n }\n \n if unmigrated \u003e 0 {\n warnings = append(warnings, Warning{\n Severity: \"warn\",\n Message: fmt.Sprintf(\"%d deletions not migrated to tombstones, run 'bd migrate-tombstones'\", unmigrated),\n })\n }\n }\n \n // Check for stale deletions.jsonl in Phase 3\n if detectPhase() == PhaseRemoval \u0026\u0026 fileExists(deletionsPath) {\n warnings = append(warnings, Warning{\n Severity: \"info\",\n Message: \"deletions.jsonl is obsolete, safe to delete\",\n })\n }\n \n return warnings\n}\n```\n\n## Summary\n\n| Phase | Version | deletions.jsonl Write | deletions.jsonl Read | Tombstone Write | Tombstone Read |\n|-------|---------|----------------------|---------------------|-----------------|----------------|\n| 1 | v0.9.0-v0.10.x | Yes | Yes | Yes | Yes |\n| 2 | v0.11.0-v0.x.x | No | Yes (deprecated) | Yes | Yes |\n| 3 | v1.0.0+ | No | No (ignored) | Yes | Yes |\n\n## Timeline\n\n1. **v0.9.0**: Implement dual-write, add `bd migrate-tombstones`\n2. **v0.10.0**: Continue dual-write, encourage migration\n3. **v0.11.0**: Stop writing to deletions.jsonl, deprecation warnings\n4. **v1.0.0**: Remove deletions.jsonl support entirely\n\n## Open Questions\n\n1. **Should `bd migrate-tombstones` run automatically?**\n - Recommendation: No, explicit command to avoid surprises\n\n2. **Should we keep a backup of deletions.jsonl?**\n - Recommendation: Yes, rename to `.migrated` suffix\n\n3. **What if user downgrades after migration?**\n - Old versions won't see tombstones, but deletions.jsonl still works until Phase 2\n - Recommendation: Document this in upgrade notes","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-05T13:43:26.856406-08:00","updated_at":"2025-12-05T15:02:58.521601-08:00","closed_at":"2025-12-05T15:02:58.521601-08:00"} {"id":"bd-dmb","title":"Fresh clone: bd should suggest 'bd init' when no database exists","description":"On a fresh clone of a repo using beads, running `bd stats` or `bd list` gives a cryptic error:\n\n```\nError: failed to open database: post-migration validation failed: migration invariants failed:\n - required_config_present: required config key missing: issue_prefix (database has 2 issues)\n```\n\n**Expected**: A helpful message like:\n```\nNo database found. This appears to be a fresh clone.\nRun 'bd init --prefix \u003cprefix\u003e' to hydrate from the committed JSONL file.\nFound: .beads/beads.jsonl (38 issues)\n```\n\n**Why this matters**: The current UX is confusing for new contributors or fresh clones. The happy path should be obvious.","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-11-27T20:21:04.947959-08:00","updated_at":"2025-11-27T22:40:11.654051-08:00","closed_at":"2025-11-27T22:40:11.654051-08:00"} {"id":"bd-dmd","title":"Duplicate safety check message when confirmation required","description":"","status":"closed","priority":3,"issue_type":"bug","created_at":"2025-12-02T21:55:50.204007-08:00","updated_at":"2025-12-02T22:08:09.569984-08:00","closed_at":"2025-12-02T22:08:09.569984-08:00"} @@ -102,6 +113,7 @@ {"id":"bd-e3w","title":"bd sync: use worktree for sync.branch commits (non-daemon mode)","description":"## Summary\n\nWhen `sync.branch` is configured (e.g., `beads-sync`), `bd sync` should commit beads changes to that branch via git worktree, even when the user's working directory is on a different branch (e.g., `main`).\n\nCurrently:\n- `bd sync` always commits to the current branch\n- Daemon with `--auto-commit` uses worktrees to commit to sync branch\n- Gap: `bd sync` ignores sync.branch config\n\n## Goal\n\nWorkers stay on `main` (or feature branches) while beads metadata automatically flows to `beads-sync` branch. No daemon required.\n\n## Design Considerations\n\n1. **Worktree lifecycle**: Create on first use, reuse thereafter\n2. **Daemon interaction**: Ensure daemon and bd sync don't conflict\n3. **MCP server**: Uses daemon or direct mode - needs same behavior\n4. **Pull semantics**: Pull from sync branch, not current branch\n5. **Push semantics**: Push to sync branch remote\n6. **Error handling**: Worktree corruption, missing branch, etc.\n\n## Affected Components\n\n- `cmd/bd/sync.go` - Main changes\n- `cmd/bd/daemon_sync_branch.go` - Reuse existing functions\n- `internal/git/worktree.go` - Already implemented\n- MCP server (if it bypasses daemon)\n\n## Edge Cases\n\n- Fresh clone (no sync branch exists yet)\n- Worktree exists but is corrupted\n- Concurrent bd sync from multiple processes\n- sync.branch configured but remote doesn't have that branch\n- User switches sync.branch config mid-session","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-11-30T00:31:20.026356-08:00","updated_at":"2025-12-07T21:25:07.141293+11:00","closed_at":"2025-11-30T00:41:35.335791-08:00"} {"id":"bd-e6x","title":"bd sync --squash: batch multiple syncs into single commit","description":"For solo developers who don't need real-time multi-agent coordination, add a --squash option to bd sync that accumulates changes and commits them in a single commit rather than one commit per sync.\n\nThis addresses the git history pollution concern (many 'bd sync: timestamp' commits) while preserving the default behavior needed for orchestration.\n\n**Proposed behavior:**\n- `bd sync --squash` accumulates pending exports without committing\n- Commits accumulated changes on session end or explicit `bd sync` (without --squash)\n- Default behavior unchanged (immediate commits for orchestration)\n\n**Use case:** Solo developers who want cleaner git history but don't need real-time coordination between agents.\n\n**Related:** PR #411 (docs: reduce bd sync commit pollution)\n**See also:** Multi-repo support as alternative solution (docs/MULTI_REPO_AGENTS.md)","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-11-28T18:21:47.789887-08:00","updated_at":"2025-11-30T10:50:04.271702-08:00","closed_at":"2025-11-28T21:56:57.608777-08:00"} {"id":"bd-e92","title":"Add test coverage for internal/autoimport package","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-20T21:21:22.338577-05:00","updated_at":"2025-11-30T10:50:04.27197-08:00","closed_at":"2025-11-28T21:52:34.222127-08:00","dependencies":[{"issue_id":"bd-e92","depends_on_id":"bd-ge7","type":"blocks","created_at":"2025-11-20T21:21:31.128625-05:00","created_by":"daemon"}]} +{"id":"bd-efm","title":"sync tries to create worktree in .git file","description":"example: bd sync --no-daemon\n→ Exporting pending changes to JSONL...\n→ Committing changes to sync branch 'worktree-db-fail'...\nError committing to 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","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-07T15:43:38.086614222-07:00","updated_at":"2025-12-07T15:43:38.086614222-07:00"} {"id":"bd-emg","title":"bd init should refuse when JSONL already has issues (safety guard)","description":"When running `bd init` in a directory with an existing JSONL containing issues, bd should refuse and suggest the correct action instead of proceeding.\n\n## The Problem\n\nCurrent behavior when database is missing but JSONL exists:\n```\n$ bd create \"test\"\nError: no beads database found\nHint: run 'bd init' to create a database...\n```\n\nThis leads users (and AI agents) to reflexively run `bd init`, which can cause:\n- Prefix mismatch if wrong prefix specified\n- Data corruption if JSONL is damaged\n- Confusion about what actually happened\n\n## Proposed Behavior\n\n```\n$ bd init --prefix bd\n\n⚠ Found existing .beads/issues.jsonl with 76 issues.\n\nThis appears to be a fresh clone, not a new project.\n\nTo hydrate the database from existing JSONL:\n bd doctor --fix\n\nTo force re-initialization (may cause data loss):\n bd init --prefix bd --force\n\nAborting.\n```\n\n## Trigger Conditions\n\n- `.beads/issues.jsonl` or `.beads/beads.jsonl` exists\n- File contains \u003e 0 valid issue lines\n- No `--force` flag provided\n\n## Edge Cases\n\n- Empty JSONL (0 issues) → allow init (new project)\n- Corrupted JSONL → warn but allow with confirmation\n- Existing `.db` file → definitely refuse (weird state)\n\n## Related\n\n- bd-dmb: Fresh clone should suggest hydration (better error messages)\n- bd-4ew: bd doctor should detect fresh clone\n\nThis issue is about the safety guard on `bd init` itself, not the error messages from other commands.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-28T18:21:41.149304-08:00","updated_at":"2025-11-30T10:50:04.272249-08:00","closed_at":"2025-11-28T22:17:18.849507-08:00"} {"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-f0n","title":"Git history fallback missing timeout - could hang on large repos","description":"## Problem\n\nThe git commands in `checkGitHistoryForDeletions` have no timeout. On large repos with extensive history, `git log --all -S` or `git log --all -G` can take a very long time (minutes).\n\n## Location\n`internal/importer/importer.go:899` and `:930`\n\n## Impact\n- Import could hang indefinitely\n- User has no feedback that git search is running\n- No way to cancel except killing the process\n\n## Fix\nAdd context with timeout to git commands:\n\n```go\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\ncmd := exec.CommandContext(ctx, \"git\", ...)\n```\n\nAlso consider adding a `--since` flag to bound the git history search.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-25T12:48:24.388639-08:00","updated_at":"2025-11-25T15:04:53.669714-08:00","closed_at":"2025-11-25T15:04:53.669714-08:00"} @@ -140,6 +152,7 @@ {"id":"bd-mdw","title":"Add integration test for cross-clone deletion propagation","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-25T14:56:38.997009-08:00","updated_at":"2025-11-25T16:35:59.052914-08:00","closed_at":"2025-11-25T16:35:59.052914-08:00"} {"id":"bd-mnap","title":"Investigate performance issues in VS Code Copilot (Windows)","description":"Beads unusable in Windows 11 VS Code Copilot chat with Sonnet 4.5.\nSummary event happens every 3-4 turns, taking 3 minutes.\nCopilot summarizes after ~125k tokens despite model supporting 1M.\nLarge context size of beads might be triggering aggressive summarization.\nNeed workaround or optimization for context size.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-20T18:56:30.124918-05:00","updated_at":"2025-11-30T10:50:04.274348-08:00","closed_at":"2025-11-28T23:37:52.199294-08:00"} {"id":"bd-mql4","title":"getLocalSyncBranch silently ignores YAML parse errors","description":"In autoimport.go:170-172, YAML parsing errors are silently ignored. If a user has malformed YAML in config.yaml, sync-branch will just silently be empty with no feedback.\n\nRecommendation: Add debug logging since this function is only called during auto-import, and debugging silent failures is painful.\n\nAdd: debug.Logf(\"Warning: failed to parse config.yaml: %v\", err)","status":"open","priority":4,"issue_type":"task","created_at":"2025-12-07T02:03:44.217728-08:00","updated_at":"2025-12-07T02:03:44.217728-08:00"} +{"id":"bd-n3v","title":"Error committing to sync branch: failed to create worktree","description":"\u003e bd sync --no-daemon\n→ Exporting pending changes to JSONL...\n→ Committing changes to sync branch 'beads-sync'...\nError committing to sync branch: failed to create worktree: failed to create worktree parent directory: mkdir /var/home/matt/dev/beads/fix-ci/.git: not a directory","notes":"**Problem Diagnosed**: The `bd sync` command was failing with \"mkdir /var/home/matt/dev/beads/fix-ci/.git: not a directory\" because it was being executed from the wrong directory.\n\n**Root Cause**: The command was run from `/var/home/matt/dev/beads` (where the `fix-ci` worktree exists) instead of the main repository directory `/var/home/matt/dev/beads/main`. Since `fix-ci` is a git worktree with a `.git` file (not directory), the worktree creation logic failed when trying to create `\u003ccurrent_dir\u003e/.git/beads-worktrees/\u003cbranch\u003e`.\n\n**Solution Verified**: Execute `bd sync` from the main repository directory:\n```bash\ncd main \u0026\u0026 bd sync --dry-run\n```\n","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-05T15:25:24.514998248-07:00","updated_at":"2025-12-05T15:42:32.910166956-07:00"} {"id":"bd-nl2","title":"No logging/debugging for tombstone resurrection events","description":"Per the design document bd-zvg Open Question 1: Should resurrection log a warning? Recommendation was Yes. Currently, when an expired tombstone loses to a live issue (resurrection), there is no logging or debugging output. This makes it hard to understand why an issue reappeared. Recommendation: Add optional debug logging when resurrection occurs, e.g., Issue bd-abc resurrected (tombstone expired). Files: internal/merge/merge.go:359-366, 371-378, 400-405, 410-415","status":"open","priority":4,"issue_type":"feature","created_at":"2025-12-05T16:36:52.27525-08:00","updated_at":"2025-12-05T16:36:52.27525-08:00"} {"id":"bd-nq41","title":"Fix Homebrew warning about Ruby file location","description":"Homebrew warning: Found Ruby file outside steveyegge/beads tap formula directory.\nWarning points to: /opt/homebrew/Library/Taps/steveyegge/homebrew-beads/bd.rb\nIt should likely be inside a Formula/ directory or similar structure expected by Homebrew taps.\n","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-11-20T18:56:21.226579-05:00","updated_at":"2025-11-26T22:25:37.362928-08:00","closed_at":"2025-11-26T22:25:37.362928-08:00"} {"id":"bd-nsb","title":"Doctor should exclude merge artifacts from 'multiple JSONL' warning","description":"Doctor command warns about 'multiple JSONL files' when .base.jsonl and .left.jsonl merge artifacts exist. These are expected during/after merge operations and should be excluded from the warning.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-28T17:27:36.988178-08:00","updated_at":"2025-11-28T18:22:16.903917-08:00","closed_at":"2025-11-28T17:41:50.700658-08:00"} @@ -156,6 +169,7 @@ {"id":"bd-r6k2","title":"Fragile no-db detection uses strings.Contains instead of YAML parsing","description":"Two locations use `strings.Contains(string(configData), \"no-db: true\")` to detect JSONL-only mode:\n\n1. **cmd/bd/main.go:310** - Auto-enabling no-db mode\n2. **cmd/bd/doctor.go:863** - Database status check\n\nThis is fragile because it could match:\n- Comments: `# no-db: true for testing`\n- Nested keys: `settings:\\n no-db: true`\n- String values: `note: \"set no-db: true to disable\"`\n\nShould use proper YAML parsing like we did for sync-branch in bd-0rh:\n\n```go\ntype noDbConfig struct {\n NoDb bool `yaml:\"no-db\"`\n}\n\nvar cfg noDbConfig\nif err := yaml.Unmarshal(configData, \u0026cfg); err == nil {\n isNoDbMode = cfg.NoDb\n}\n```\n\nRelated: bd-0rh (fixed similar issue for sync-branch)","status":"closed","priority":3,"issue_type":"bug","created_at":"2025-12-07T02:03:12.120296-08:00","updated_at":"2025-12-07T02:09:30.628372-08:00","closed_at":"2025-12-07T02:09:30.628372-08:00","dependencies":[{"issue_id":"bd-r6k2","depends_on_id":"bd-0rh","type":"discovered-from","created_at":"2025-12-07T02:03:50.599423-08:00","created_by":"daemon"}]} {"id":"bd-r9iq","title":"purgeDeletedIssues still writes to deletions.jsonl during git-history-backfill","description":"In purgeDeletedIssues (lines 929-939), when a deletion is recovered from git history, we:\n1. Append to deletions.jsonl (backfill)\n2. Create a tombstone in DB\n\nWith the tombstone approach, writing to deletions.jsonl is redundant since the tombstone will be exported to JSONL. Consider removing the deletions.jsonl backfill once we're confident in the tombstone approach.\n\nThis is related to the Phase 2 migration (bd-vw8) - we should stop writing to deletions.jsonl entirely.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-07T01:40:42.581876-08:00","updated_at":"2025-12-07T01:40:42.581876-08:00","dependencies":[{"issue_id":"bd-r9iq","depends_on_id":"bd-dve","type":"blocks","created_at":"2025-12-07T01:41:28.285457-08:00","created_by":"daemon"}]} {"id":"bd-s0z","title":"Consider extracting error handling helpers","description":"Evaluate creating FatalError() and WarnError() helpers as suggested in ERROR_HANDLING.md to reduce boilerplate and enforce consistency. Prototype in a few files first to validate the approach.","status":"closed","priority":4,"issue_type":"task","created_at":"2025-11-24T00:28:57.248959-08:00","updated_at":"2025-11-30T10:50:04.275143-08:00","closed_at":"2025-11-28T23:28:00.886536-08:00"} +{"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-s3v","title":"Add tombstone doctor checks","description":"Add bd doctor checks for: unmigrated deletions.jsonl, stale deletions.jsonl in Phase 3, tombstones expiring soon. Per design bd-dli.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-05T15:14:46.733558-08:00","updated_at":"2025-12-05T15:14:46.733558-08:00","dependencies":[{"issue_id":"bd-s3v","depends_on_id":"bd-8f9","type":"blocks","created_at":"2025-12-05T15:14:59.006029-08:00","created_by":"daemon"}]} {"id":"bd-saa","title":"Add index on deleted_at for TTL queries","description":"Per bd-2m7 design: 'Index for efficient tombstone filtering' was recommended. The current implementation does NOT add an index on deleted_at.\n\nFor TTL cleanup queries like 'SELECT * FROM issues WHERE deleted_at \u003c datetime(now, -30 days)', this will require a full table scan.\n\nAdd to migration or new migration:\nCREATE INDEX IF NOT EXISTS idx_issues_deleted_at ON issues(deleted_at) WHERE deleted_at IS NOT NULL","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-05T15:35:33.842664-08:00","updated_at":"2025-12-05T15:48:07.584268-08:00","closed_at":"2025-12-05T15:48:07.584268-08:00","dependencies":[{"issue_id":"bd-saa","depends_on_id":"bd-vw8","type":"parent-child","created_at":"2025-12-05T15:35:47.222711-08:00","created_by":"daemon"}]} {"id":"bd-t3b","title":"Add test coverage for internal/config package","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-20T21:21:22.91657-05:00","updated_at":"2025-11-30T10:50:04.275382-08:00","closed_at":"2025-11-28T21:54:15.009889-08:00","dependencies":[{"issue_id":"bd-t3b","depends_on_id":"bd-ge7","type":"blocks","created_at":"2025-11-20T21:21:31.201036-05:00","created_by":"daemon"}]} @@ -170,6 +184,7 @@ {"id":"bd-upd","title":"Sync should cleanup snapshot files after completion","description":"After sync completion, orphan .base.jsonl and .left.jsonl snapshot files remain in .beads/ directory. These should be cleaned up on successful sync.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-28T17:27:36.727246-08:00","updated_at":"2025-11-28T18:22:16.904307-08:00","closed_at":"2025-11-28T17:42:14.57165-08:00"} {"id":"bd-v0x","title":"Auto-detect issue prefix from existing JSONL in 'bd init'","description":"When running `bd init` in a fresh clone with existing JSONL, it should auto-detect the issue prefix from the JSONL file instead of requiring `--prefix`.\n\nCurrently you must specify `--prefix ef` manually. But the JSONL file already contains issues like `ef-1it`, `ef-1jp` etc., so the prefix is known.\n\n**Ideal UX**:\n```\n$ bd init\nDetected issue prefix 'ef' from existing JSONL (38 issues).\n✓ Database initialized...\n```\n\nThis would make fresh clone hydration a single command: `bd init` with no flags.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-27T20:21:21.049215-08:00","updated_at":"2025-11-30T10:50:04.275631-08:00","closed_at":"2025-11-28T21:57:11.164293-08:00"} {"id":"bd-v29","title":"Deletions pruning doesn't include results in JSON output","description":"## Problem\n\nWhen `bd compact --json` runs with deletions pruning, the prune results are silently discarded:\n\n```go\n// Only report if there were deletions to prune\nif result.PrunedCount \u003e 0 {\n if jsonOutput {\n // JSON output will be included in the main response\n return // \u003c-- BUG: results are NOT included anywhere\n }\n ...\n}\n```\n\n## Location\n`cmd/bd/compact.go:925-929`\n\n## Impact\n- JSON consumers don't know deletions were pruned\n- No way to audit pruning via automation\n\n## Fix\nReturn prune results and include in JSON output structure:\n\n```json\n{\n \"success\": true,\n \"compacted\": {...},\n \"deletions_pruned\": {\n \"count\": 5,\n \"retention_days\": 7\n }\n}\n```","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-25T12:48:59.730979-08:00","updated_at":"2025-11-25T15:11:54.363653-08:00","closed_at":"2025-11-25T15:11:54.363653-08:00"} +{"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":"open","priority":0,"issue_type":"bug","created_at":"2025-12-07T15:35:17.02177046-07:00","updated_at":"2025-12-07T15:35:17.02177046-07:00"} {"id":"bd-vw8","title":"Switch from deletions manifest to inline tombstones","description":"Replace the current deletions.jsonl manifest with inline tombstone records in issues.jsonl.\n\n## Problem Statement\n\nThe current deletions manifest approach has several issues:\n\n1. **Wild poisoning** - A stale clone's deletions manifest can poison fresh databases when synced\n2. **Two-level merge inconsistency** - Git content merge and beads snapshot merge use different bases\n3. **SyncJSONLToWorktree overwrites** - Blindly copies local JSONL to worktree, losing remote issues\n4. **3-day TTL too aggressive** - Deletions expire before dormant branches get merged\n\n## Proposed Solution: Inline Tombstones\n\nInstead of a separate deletions.jsonl file, embed deletion records directly in issues.jsonl:\n\n```json\n{\"id\":\"beads-abc\",\"status\":\"tombstone\",\"title\":\"Original title\",\"deleted_at\":\"2025-12-01T...\",\"deleted_by\":\"user\",\"expires_at\":\"2025-12-31T...\"}\n```\n\n### Benefits\n\n1. **Single source of truth** - No separate manifest to sync/merge\n2. **Participates in normal 3-way merge** - Deletion conflicts resolved same as other fields\n3. **Atomic with issue data** - Can't have orphaned deletions or missing tombstones\n4. **Preserves metadata** - Can optionally keep title/type for audit trail\n\n### Design Decisions Needed\n\n1. **TTL duration** - 30 days default? Configurable via config.yaml?\n2. **Tombstone content** - Minimal (just ID + timestamps) vs. full issue preservation?\n3. **Migration path** - How to handle existing deletions.jsonl files?\n4. **Status value** - Use \"tombstone\" or \"deleted\"? (tombstone clearer, deleted more intuitive)\n5. **Merge semantics** - Does tombstone always win, or use updated_at like other fields?\n\n### Migration Strategy\n\n1. On import, convert deletions.jsonl entries to tombstones in JSONL\n2. Deprecate but still read deletions.jsonl for backward compatibility\n3. Stop writing to deletions.jsonl after N versions\n\n## Related Issues\n\n- GitHub #464: Beads deletes issues (fresh clone sync problem)\n- bd-2e0: Add TTL to deletions manifest entries (current 3-day TTL)\n- bd-53c: bd sync corrupts issues.jsonl in multi-clone environments","status":"open","priority":1,"issue_type":"epic","created_at":"2025-12-05T13:42:24.384792-08:00","updated_at":"2025-12-05T13:42:24.384792-08:00","dependencies":[{"issue_id":"bd-vw8","depends_on_id":"bd-1r5","type":"blocks","created_at":"2025-12-05T14:57:31.111259-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-2m7","type":"blocks","created_at":"2025-12-05T14:57:33.57722-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-dli","type":"blocks","created_at":"2025-12-05T14:57:34.902784-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-zvg","type":"blocks","created_at":"2025-12-05T14:57:36.665817-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-fbj","type":"blocks","created_at":"2025-12-05T15:14:59.081452-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-olt","type":"blocks","created_at":"2025-12-05T15:14:59.118268-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-0ih","type":"blocks","created_at":"2025-12-05T15:14:59.155689-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-3b4","type":"blocks","created_at":"2025-12-05T15:14:59.192575-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-dve","type":"blocks","created_at":"2025-12-05T15:14:59.227233-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-8f9","type":"blocks","created_at":"2025-12-05T15:14:59.262816-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-s3v","type":"blocks","created_at":"2025-12-05T15:14:59.299481-08:00","created_by":"daemon"},{"issue_id":"bd-vw8","depends_on_id":"bd-okh","type":"blocks","created_at":"2025-12-05T15:14:59.337656-08:00","created_by":"daemon"}]} {"id":"bd-wcl","title":"Document CLI + hooks as recommended approach over MCP","description":"Update documentation to position CLI + bd prime hooks as the primary recommended approach over MCP server, explaining why minimizing context matters even with large context windows (compute cost, energy, environment, latency).","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-12T00:15:25.923025-08:00","updated_at":"2025-11-26T20:13:52.891053-08:00","closed_at":"2025-11-26T18:06:51.020351-08:00"} {"id":"bd-wmo","title":"PruneDeletions iterates map non-deterministically","description":"## Problem\n\n`PruneDeletions` iterates over `loadResult.Records` which is a map. Go maps iterate in random order, so:\n\n1. `result.PrunedIDs` order is non-deterministic\n2. `kept` slice order is non-deterministic → `WriteDeletions` output order varies\n\n## Location\n`internal/deletions/deletions.go:213`\n\n## Impact\n- Git diffs are noisy (file changes order on each prune)\n- Tests could be flaky if they depend on order\n- Harder to debug/audit\n\n## Fix\nSort by ID or timestamp before iterating:\n\n```go\n// Convert map to slice and sort\nvar records []DeletionRecord\nfor _, r := range loadResult.Records {\n records = append(records, r)\n}\nsort.Slice(records, func(i, j int) bool {\n return records[i].ID \u003c records[j].ID\n})\n```","status":"closed","priority":3,"issue_type":"bug","created_at":"2025-11-25T12:49:11.290916-08:00","updated_at":"2025-11-25T15:15:21.903649-08:00","closed_at":"2025-11-25T15:15:21.903649-08:00"} @@ -180,6 +195,7 @@ {"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-y68","title":"Block direct status update to tombstone in UpdateIssue","description":"Per bd-2m7 design: 'Should we support tombstone as valid input to bd update --status? Recommendation: No, tombstones only created via bd delete'\n\nCurrently nothing prevents 'bd update \u003cid\u003e --status=tombstone', which would create an invalid tombstone (missing deleted_at, deleted_by, etc.).\n\nLocation: internal/storage/sqlite/queries.go:500-664 UpdateIssue\n\nAdd validation to reject status=tombstone in updates.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-05T15:35:31.5176-08:00","updated_at":"2025-12-05T15:41:14.813862-08:00","closed_at":"2025-12-05T15:41:14.813862-08:00","dependencies":[{"issue_id":"bd-y68","depends_on_id":"bd-vw8","type":"parent-child","created_at":"2025-12-05T15:35:47.181896-08:00","created_by":"daemon"}]} {"id":"bd-ybv5","title":"Refactor AGENTS.md to use external references","description":"Suggestion to use external references (e.g., \"ALWAYS REFER TO ./beads/prompt.md\") instead of including all instructions directly within AGENTS.md.\nReasons:\n1. Agents can follow external references.\n2. Prevents context pollution/stuffing in AGENTS.md as more tools append instructions.\n","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-20T18:55:53.259144-05:00","updated_at":"2025-11-26T22:25:57.772875-08:00","closed_at":"2025-11-26T22:25:57.772875-08:00"} +{"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-ye0d","title":"troubleshoot GH#278 daemon exits every few secs","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-13T06:27:23.39509215-07:00","updated_at":"2025-11-25T17:48:43.62418-08:00","closed_at":"2025-11-25T17:48:43.62418-08:00"} {"id":"bd-yk8w","title":"Add export test verifying tombstones are included in JSONL output","description":"The export now includes tombstones (bd-dve) but there's no test that:\n1. Creates issues in DB including a tombstone\n2. Runs export\n3. Verifies the JSONL contains the tombstone with all fields\n\nAdd a test: TestExportIncludesTombstones","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-07T01:41:19.987907-08:00","updated_at":"2025-12-07T02:16:16.882688-08:00","closed_at":"2025-12-07T02:16:16.882688-08:00","dependencies":[{"issue_id":"bd-yk8w","depends_on_id":"bd-dve","type":"blocks","created_at":"2025-12-07T01:41:28.426945-08:00","created_by":"daemon"}]} {"id":"bd-yuv","title":"Add tests for tombstone edge cases","description":"Missing test coverage for tombstone implementation:\n\n1. Test tombstone validation (deleted_at requirement)\n2. Test GetReadyWork excludes tombstones\n3. Test SearchIssues behavior with tombstones\n4. Integration test for creating/retrieving a tombstone via the delete command\n5. Test that status=tombstone update is rejected","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-05T15:35:39.101881-08:00","updated_at":"2025-12-05T15:35:39.101881-08:00","dependencies":[{"issue_id":"bd-yuv","depends_on_id":"bd-vw8","type":"parent-child","created_at":"2025-12-05T15:35:47.335167-08:00","created_by":"daemon"}]} diff --git a/cmd/bd/doctor.go b/cmd/bd/doctor.go index 246cb16f..f5b0cd87 100644 --- a/cmd/bd/doctor.go +++ b/cmd/bd/doctor.go @@ -1318,6 +1318,7 @@ func printDiagnostics(result doctorResult) { // Print warnings/errors with fixes hasIssues := false + unfixableErrors := 0 for _, check := range result.Checks { if check.Status != statusOK && check.Fix != "" { if !hasIssues { @@ -1332,12 +1333,27 @@ func printDiagnostics(result doctorResult) { } fmt.Printf(" Fix: %s\n\n", check.Fix) + } else if check.Status == statusError && check.Fix == "" { + // Count unfixable errors + unfixableErrors++ } } if !hasIssues { color.Green("✓ All checks passed\n") } + + // Suggest reset if there are multiple unfixable errors + if unfixableErrors >= 3 { + fmt.Println() + color.Yellow("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n") + color.Yellow("⚠ Found %d unfixable errors\n", unfixableErrors) + fmt.Println() + fmt.Println(" Your beads state may be too corrupted to repair automatically.") + fmt.Println(" Consider running 'bd reset' to start fresh.") + fmt.Println(" (Use 'bd reset --backup' to save current state first)") + color.Yellow("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n") + } } func checkMultipleDatabases(path string) doctorCheck { diff --git a/cmd/bd/reset_test.go b/cmd/bd/reset_test.go new file mode 100644 index 00000000..d7a22b15 --- /dev/null +++ b/cmd/bd/reset_test.go @@ -0,0 +1,331 @@ +//go:build integration +// +build integration + +package main + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// runBDResetExec runs bd reset via exec.Command for clean state isolation +// Reset has persistent flag state that doesn't work well with in-process testing +func runBDResetExec(t *testing.T, dir string, stdin string, args ...string) (string, error) { + t.Helper() + + // Add --no-daemon to all commands + args = append([]string{"--no-daemon"}, args...) + + cmd := exec.Command(testBD, args...) + cmd.Dir = dir + cmd.Env = append(os.Environ(), "BEADS_NO_DAEMON=1") + + if stdin != "" { + cmd.Stdin = strings.NewReader(stdin) + } + + out, err := cmd.CombinedOutput() + return string(out), err +} + +func TestCLI_ResetDryRun(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + tmpDir := setupCLITestDB(t) + + // Create some issues + runBDExec(t, tmpDir, "create", "Issue 1", "-p", "1") + runBDExec(t, tmpDir, "create", "Issue 2", "-p", "2") + + // Run dry-run reset + out, err := runBDResetExec(t, tmpDir, "", "reset", "--dry-run") + if err != nil { + t.Fatalf("dry-run reset failed: %v\nOutput: %s", err, out) + } + + // Verify output contains impact summary + if !strings.Contains(out, "Reset Impact Summary") { + t.Errorf("Expected 'Reset Impact Summary' in output, got: %s", out) + } + if !strings.Contains(out, "dry run") { + t.Errorf("Expected 'dry run' in output, got: %s", out) + } + + // Verify .beads directory still exists (dry run shouldn't delete anything) + beadsDir := filepath.Join(tmpDir, ".beads") + if _, err := os.Stat(beadsDir); os.IsNotExist(err) { + t.Error("dry run should not delete .beads directory") + } + + // Verify we can still list issues + listOut := runBDExec(t, tmpDir, "list") + if !strings.Contains(listOut, "Issue 1") { + t.Errorf("Issues should still exist after dry run, got: %s", listOut) + } +} + +func TestCLI_ResetForce(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + tmpDir := setupCLITestDB(t) + + // Create some issues + runBDExec(t, tmpDir, "create", "Issue to delete", "-p", "1") + + // Run reset with --force (no confirmation needed) + out, err := runBDResetExec(t, tmpDir, "", "reset", "--force") + if err != nil { + t.Fatalf("reset --force failed: %v\nOutput: %s", err, out) + } + + // Verify success message + if !strings.Contains(out, "Reset complete") { + t.Errorf("Expected 'Reset complete' in output, got: %s", out) + } + + // Verify .beads directory was recreated (reinit by default) + beadsDir := filepath.Join(tmpDir, ".beads") + if _, err := os.Stat(beadsDir); os.IsNotExist(err) { + t.Error(".beads directory should be recreated after reset") + } + + // Verify issues are gone (reinit creates empty workspace) + listOut := runBDExec(t, tmpDir, "list") + if strings.Contains(listOut, "Issue to delete") { + t.Errorf("Issues should be deleted after reset, got: %s", listOut) + } +} + +func TestCLI_ResetSkipInit(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + tmpDir := setupCLITestDB(t) + + // Create an issue + runBDExec(t, tmpDir, "create", "Test issue", "-p", "1") + + // Run reset with --skip-init + out, err := runBDResetExec(t, tmpDir, "", "reset", "--force", "--skip-init") + if err != nil { + t.Fatalf("reset --skip-init failed: %v\nOutput: %s", err, out) + } + + // Verify .beads directory doesn't exist + beadsDir := filepath.Join(tmpDir, ".beads") + if _, err := os.Stat(beadsDir); !os.IsNotExist(err) { + t.Error(".beads directory should not exist after reset with --skip-init") + } + + // Verify output mentions bd init + if !strings.Contains(out, "bd init") { + t.Errorf("Expected hint about 'bd init' in output, got: %s", out) + } +} + +func TestCLI_ResetBackup(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + tmpDir := setupCLITestDB(t) + + // Create some issues + runBDExec(t, tmpDir, "create", "Backup test issue", "-p", "1") + + // Run reset with --backup + out, err := runBDResetExec(t, tmpDir, "", "reset", "--force", "--backup") + if err != nil { + t.Fatalf("reset --backup failed: %v\nOutput: %s", err, out) + } + + // Verify backup was mentioned in output + if !strings.Contains(out, "Backup created") || !strings.Contains(out, ".beads-backup-") { + t.Errorf("Expected backup path in output, got: %s", out) + } + + // Verify a backup directory exists + entries, err := os.ReadDir(tmpDir) + if err != nil { + t.Fatalf("Failed to read dir: %v", err) + } + + foundBackup := false + for _, entry := range entries { + if strings.HasPrefix(entry.Name(), ".beads-backup-") && entry.IsDir() { + foundBackup = true + // Verify backup has content + backupPath := filepath.Join(tmpDir, entry.Name()) + backupEntries, err := os.ReadDir(backupPath) + if err != nil { + t.Fatalf("Failed to read backup dir: %v", err) + } + if len(backupEntries) == 0 { + t.Error("Backup directory should not be empty") + } + break + } + } + + if !foundBackup { + t.Error("No backup directory found") + } +} + +func TestCLI_ResetWithConfirmation(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + tmpDir := setupCLITestDB(t) + + // Create an issue + runBDExec(t, tmpDir, "create", "Confirm test", "-p", "1") + + // Run reset with confirmation (type 'y') + out, err := runBDResetExec(t, tmpDir, "y\n", "reset") + if err != nil { + t.Fatalf("reset with confirmation failed: %v\nOutput: %s", err, out) + } + + // Verify success + if !strings.Contains(out, "Reset complete") { + t.Errorf("Expected 'Reset complete' in output, got: %s", out) + } +} + +func TestCLI_ResetCancelled(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + tmpDir := setupCLITestDB(t) + + // Create an issue + runBDExec(t, tmpDir, "create", "Keep this", "-p", "1") + + // Run reset but cancel (type 'n') + out, err := runBDResetExec(t, tmpDir, "n\n", "reset") + // Cancellation is not an error + if err != nil { + t.Fatalf("reset cancellation failed: %v\nOutput: %s", err, out) + } + + // Verify cancelled message + if !strings.Contains(out, "cancelled") { + t.Errorf("Expected 'cancelled' in output, got: %s", out) + } + + // Verify issue still exists + listOut := runBDExec(t, tmpDir, "list") + if !strings.Contains(listOut, "Keep this") { + t.Errorf("Issues should still exist after cancelled reset, got: %s", listOut) + } +} + +func TestCLI_ResetNoBeadsDir(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + // Create temp dir without .beads + tmpDir := createTempDirWithCleanup(t) + + // Run reset - should fail + out, err := runBDResetExec(t, tmpDir, "", "reset", "--force") + if err == nil { + t.Error("reset should fail when no .beads directory exists") + } + + // Verify error message + if !strings.Contains(out, "no .beads directory found") && !strings.Contains(out, "Error") { + t.Errorf("Expected error about missing .beads directory, got: %s", out) + } +} + +func TestCLI_ResetWithIssues(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + tmpDir := setupCLITestDB(t) + + // Create multiple issues with different states + runBDExec(t, tmpDir, "create", "Open issue 1", "-p", "1") + runBDExec(t, tmpDir, "create", "Open issue 2", "-p", "2") + + out1 := runBDExec(t, tmpDir, "create", "To close", "-p", "1", "--json") + id := extractIDFromJSON(t, out1) + runBDExec(t, tmpDir, "close", id) + + // Run dry-run to see counts + out, err := runBDResetExec(t, tmpDir, "", "reset", "--dry-run") + if err != nil { + t.Fatalf("dry-run failed: %v\nOutput: %s", err, out) + } + + // Verify impact shows correct counts + if !strings.Contains(out, "Issues to delete") { + t.Errorf("Expected 'Issues to delete' in output, got: %s", out) + } + if !strings.Contains(out, "Open:") { + t.Errorf("Expected 'Open:' count in output, got: %s", out) + } + if !strings.Contains(out, "Closed:") { + t.Errorf("Expected 'Closed:' count in output, got: %s", out) + } + + // Now do actual reset + out, err = runBDResetExec(t, tmpDir, "", "reset", "--force") + if err != nil { + t.Fatalf("reset failed: %v\nOutput: %s", err, out) + } + + // Verify all issues are gone + listOut := runBDExec(t, tmpDir, "list") + if strings.Contains(listOut, "Open issue") || strings.Contains(listOut, "To close") { + t.Errorf("All issues should be deleted after reset, got: %s", listOut) + } +} + +func TestCLI_ResetVerbose(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + tmpDir := setupCLITestDB(t) + + // Create an issue + runBDExec(t, tmpDir, "create", "Verbose test", "-p", "1") + + // Run reset with --verbose + out, err := runBDResetExec(t, tmpDir, "", "reset", "--force", "--verbose") + if err != nil { + t.Fatalf("reset --verbose failed: %v\nOutput: %s", err, out) + } + + // Verify verbose output shows more details + if !strings.Contains(out, "Starting reset") { + t.Errorf("Expected 'Starting reset' in verbose output, got: %s", out) + } +} + +// extractIDFromJSON extracts an ID from JSON output +func extractIDFromJSON(t *testing.T, out string) string { + t.Helper() + // Try both formats: "id":"xxx" and "id": "xxx" + idx := strings.Index(out, `"id":"`) + offset := 6 + if idx == -1 { + idx = strings.Index(out, `"id": "`) + offset = 7 + } + if idx == -1 { + t.Fatalf("No id found in JSON output: %s", out) + } + start := idx + offset + end := strings.Index(out[start:], `"`) + if end == -1 { + t.Fatalf("Malformed JSON output: %s", out) + } + return out[start : start+end] +}