From 6811d1cf42c28c8aff075facaed110f5087917f7 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sat, 18 Oct 2025 09:58:35 -0700 Subject: [PATCH] Fix multiple issues and renumber database - bd-170: Implement hybrid sorting for ready work (recent 48h first, then oldest) - bd-87: Use safer null-byte placeholders in ID remapping - bd-92: Make auto-flush debounce configurable via BEADS_FLUSH_DEBOUNCE - bd-171: Fix nil pointer dereference in renumber command - Delete spurious test issues (bd-7, bd-130-134) - Renumber database from 171 down to 144 issues --- .beads/issues.jsonl | 15 ++--- .golangci.bck.yml | 89 ++++++++++++++++++++++++++++ cmd/bd/main.go | 23 ++++++- cmd/bd/main_test.go | 5 +- cmd/bd/renumber.go | 16 +++++ internal/storage/sqlite/collision.go | 2 +- 6 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 .golangci.bck.yml diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index c5d2888e..3c07bd6f 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -31,11 +31,6 @@ {"id":"bd-126","title":"Add cross-repo issue references (future enhancement)","description":"Support referencing issues across different beads repositories. Useful for tracking dependencies between separate projects.\n\nProposed syntax:\n- Local reference: bd-123 (current behavior)\n- Cross-repo by path: ~/src/other-project#bd-456\n- Cross-repo by workspace name: @project2:bd-789\n\nUse cases:\n1. Frontend project depends on backend API issue\n2. Shared library changes blocking multiple projects\n3. System administrator tracking work across machines\n4. Monorepo with separate beads databases per component\n\nImplementation challenges:\n- Storage layer needs to query external databases\n- Dependency resolution across repos\n- What if external repo not available?\n- How to handle in JSONL export/import?\n- Security: should repos be able to read others?\n\nDesign questions to resolve first:\n1. Read-only references vs full cross-repo dependencies?\n2. How to handle repo renames/moves?\n3. Absolute paths vs workspace names vs git remotes?\n4. Should bd-73 auto-discover related repos?\n\nRecommendation: \n- Gather user feedback first\n- Start with read-only references\n- Implement as plugin/extension?\n\nContext: This is mentioned in bd-73 as approach #2. Much more complex than daemon multi-repo approach. Only implement if there's strong user demand.\n\nPriority: Backlog (4) - wait for user feedback before designing","status":"open","priority":4,"issue_type":"feature","created_at":"2025-10-17T20:43:54.04594-07:00","updated_at":"2025-10-17T20:43:54.04594-07:00","dependencies":[{"issue_id":"bd-126","depends_on_id":"bd-73","type":"parent-child","created_at":"2025-10-17T20:44:07.576103-07:00","created_by":"daemon"}]} {"id":"bd-127","title":"Add batch deletion command for issues","description":"Support deleting multiple issues efficiently instead of one at a time.\n\n**Use Cases:**\n- Cleaning up duplicate/spam issues (e.g., bd-100 to bd-117 watchdog spam)\n- Removing test-only issues after feature removal\n- Bulk cleanup of obsolete/spurious bugs\n- Renumbering prep: delete ranges before compaction\n\n**Proposed Syntax Options:**\n\n**Option 1: Multiple IDs as arguments**\n```bash\nbd delete vc-1 vc-2 vc-3 --force\nbd delete vc-{1..20} --force # Shell expansion\n```\n\n**Option 2: Read from file (RECOMMENDED)**\n```bash\nbd delete --from-file deletions.txt --force --dry-run # Preview\nbd delete --from-file deletions.txt --force # Execute\n# File format: one issue ID per line\n```\n\n**Option 3: Query-based deletion**\n```bash\nbd delete --where \"priority=3 AND type=chore\" --force\nbd delete --label test-only --force\nbd delete --prefix bd- --status open --force\n```\n\n**Must-Have Features:**\n\n1. **Dry-run mode**: `--dry-run` to preview what would be deleted\n - Show issue IDs, titles, dependency counts\n - Warn about issues with dependents\n\n2. **Dependency handling**:\n - `--cascade`: Delete dependents recursively\n - `--force`: Delete even if dependents exist (orphans them)\n - Default: Fail if any issue has dependents\n\n3. **Summary output**:\n ```\n Deleted 162 issues\n Removed 347 dependencies\n Removed 89 labels\n Orphaned 5 issues (use --cascade to delete)\n ```\n\n4. **Transaction safety**: All-or-nothing for file/query input\n - Either all deletions succeed or none do\n - Rollback on error\n\n**Nice-to-Have Features:**\n\n1. **Interactive confirmation** for large batches (\u003e10 issues)\n ```\n About to delete 162 issues. Continue? [y/N]\n (Use --force to skip confirmation)\n ```\n\n2. **Progress indicator** for large batches (\u003e50 deletions)\n ```\n Deleting issues... [####------] 42/162 (26%)\n ```\n\n3. **Undo support**:\n ```bash\n bd undelete --last-batch # Restore from snapshots\n bd undelete bd-100 # Restore single issue\n ```\n\n**Implementation Notes:**\n\n- Leverage existing `DeleteIssue()` in storage layer\n- Wrap in transaction for atomicity\n- Consider adding `DeleteIssues(ctx, []string)` for efficiency\n- May need to query dependents before deletion\n- File format should support comments (#) and blank lines\n- JSON output mode should list all deleted IDs\n\n**Example Workflow:**\n```bash\n# Identify issues to delete\nbd list --label test-only --json | jq -r '.[].id' \u003e /tmp/delete.txt\n\n# Preview deletion\nbd delete --from-file /tmp/delete.txt --dry-run\n\n# Execute with cascade\nbd delete --from-file /tmp/delete.txt --cascade --force\n\n# Verify\nbd stats\n```\n\n**Security Considerations:**\n- Require explicit `--force` flag to prevent accidents\n- Warn when deleting issues with dependencies\n- Log deletions to audit trail\n- Consider requiring confirmation for \u003e100 deletions even with --force\n\n**Requested by:** Another agent during cleanup of bd-100 to bd-117 watchdog spam","notes":"Fixed critical issues found in code review:\n1. Dry-run mode now properly uses dryRun parameter instead of deleting data\n2. Text references are pre-collected before deletion so they update correctly\n3. Added orphan deduplication to prevent duplicate IDs\n4. Added rows.Err() checks in all row iteration loops\n5. Updated defer to ignore rollback error per Go best practices","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-17T20:49:30.921943-07:00","updated_at":"2025-10-17T21:11:58.670841-07:00","closed_at":"2025-10-17T21:03:29.165515-07:00"} {"id":"bd-13","title":"Implement ID remapping with reference updates","description":"Allocate new IDs for colliding issues. Update all text field references using word-boundary regex (\\bbd-10\\b). Update dependency records. Build id_mapping for reporting. Handle chain dependencies properly.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.643252-07:00","closed_at":"2025-10-14T02:51:52.198356-07:00","dependencies":[{"issue_id":"bd-13","depends_on_id":"bd-48","type":"parent-child","created_at":"2025-10-16T21:51:08.92251-07:00","created_by":"renumber"}]} -{"id":"bd-130","title":"Test issue 3","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-17T20:59:40.171507-07:00","updated_at":"2025-10-17T20:59:40.171507-07:00"} -{"id":"bd-131","title":"Test issue 4","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-17T20:59:40.234886-07:00","updated_at":"2025-10-17T20:59:40.234886-07:00"} -{"id":"bd-132","title":"Batch test 1","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-17T21:01:21.047341-07:00","updated_at":"2025-10-17T21:01:21.047341-07:00"} -{"id":"bd-133","title":"Batch test 2","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-17T21:01:21.055026-07:00","updated_at":"2025-10-17T21:01:21.055026-07:00"} -{"id":"bd-134","title":"Batch test 3","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-17T21:01:21.055526-07:00","updated_at":"2025-10-17T21:01:21.055526-07:00"} {"id":"bd-14","title":"Add --resolve-collisions flag and user reporting","description":"Add import flags: --resolve-collisions (auto-fix) and --dry-run (preview). Display clear report: collisions detected, remappings applied (old→new with scores), reference counts updated. Default behavior: fail on collision (safe).","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.645323-07:00","closed_at":"2025-10-16T10:07:34.003238-07:00","dependencies":[{"issue_id":"bd-14","depends_on_id":"bd-48","type":"parent-child","created_at":"2025-10-16T21:51:08.923374-07:00","created_by":"renumber"}]} {"id":"bd-148","title":"bd list shows 0 issues despite database containing 115 issues","description":"When running 'bd list --status all' it shows 'Found 0 issues' even though 'bd stats' shows 115 total issues and 'sqlite3 .beads/vc.db \"SELECT COUNT(*) FROM issues\"' returns 115.\n\nReproduction:\n1. cd ~/src/vc/vc\n2. bd stats # Shows 115 issues\n3. bd list --status all # Shows 0 issues\n4. sqlite3 .beads/vc.db 'SELECT COUNT(*) FROM issues;' # Shows 115\n\nExpected: bd list should show all 115 issues\nActual: Shows 'Found 0 issues:'\n\nThis occurs with both /opt/homebrew/bin/bd (v0.9.9) and ~/src/vc/adar/beads/bd (v0.9.10)","design":"Possible causes:\n- Default filter excluding all issues\n- Database query issue in list command\n- Auto-discovery finding wrong database (but stats works?)\n- Recent deletion operation corrupted some index","acceptance_criteria":"bd list --status all shows all issues that bd stats counts","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-10-17T21:19:08.225181-07:00","updated_at":"2025-10-17T21:55:40.788625-07:00","closed_at":"2025-10-17T21:55:40.788625-07:00"} {"id":"bd-149","title":"Confusing version mismatch warnings with contradictory messages","description":"The version mismatch warning shows contradictory messages depending on which binary version is used:\n\nWhen using v0.9.10 binary with v0.9.9 database:\n'Your bd binary (v0.9.10) differs from the database version (v0.9.9)'\n'Your binary appears to be OUTDATED.'\n\nWhen using v0.9.9 binary with v0.9.10 database:\n'Your bd binary (v0.9.9) differs from the database version (v0.9.10)'\n'Your binary appears NEWER than the database.'\n\nThe first message is incorrect - v0.9.10 \u003e v0.9.9, so the binary is NEWER, not outdated.\n\nReproduction:\n1. Use ~/src/vc/adar/beads/bd (v0.9.10) with a v0.9.9 database\n2. Observe warning says binary is OUTDATED when it's actually newer\n\nExpected: Correct version comparison\nActual: Inverted comparison logic","design":"Fix version comparison in warning message generation. Should compare semantic versions correctly.","acceptance_criteria":"Warning correctly identifies which component (binary vs database) is newer/older","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-17T21:19:19.540274-07:00","updated_at":"2025-10-17T22:14:27.015397-07:00","closed_at":"2025-10-17T22:14:27.015397-07:00"} @@ -57,6 +52,7 @@ {"id":"bd-169","title":"Add 'bd stale' command to show orphaned claims and dead executors","description":"Need visibility into orphaned claims - issues stuck in_progress with execution_state but executor is dead/stopped. Add command to show: 1) All issues with execution_state where executor status=stopped or last_heartbeat \u003e threshold, 2) Executor instance details (when died, how long claimed), 3) Option to auto-release them. Makes manual recovery easier until auto-cleanup (bd-167) is implemented.","design":"Query: SELECT i.*, ei.status, ei.last_heartbeat FROM issues i JOIN issue_execution_state ies ON i.id = ies.issue_id JOIN executor_instances ei ON ies.executor_instance_id = ei.instance_id WHERE ei.status='stopped' OR ei.last_heartbeat \u003c NOW() - threshold. Add --release flag to auto-release all found issues.","acceptance_criteria":"bd stale shows orphaned claims, bd stale --release cleans them up","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-10-18T00:25:16.530937-07:00","updated_at":"2025-10-18T02:09:12.529064-07:00","closed_at":"2025-10-18T02:09:12.529064-07:00"} {"id":"bd-17","title":"bd should auto-detect .beads/*.db in current directory","description":"When bd is run without --db flag, it defaults to beads' own database instead of looking for a .beads/*.db file in the current working directory. This causes confusion when working on other projects that use beads for issue tracking (like vc).\n\nExpected behavior: bd should search for .beads/*.db in cwd and use that if found, before falling back to default beads database.\n\nExample: Running 'bd ready' in /Users/stevey/src/vc/vc/ should automatically find and use .beads/vc.db without requiring --db flag every time.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.650584-07:00","closed_at":"2025-10-16T10:07:34.046944-07:00"} {"id":"bd-170","title":"Bias ready work towards recent issues before oldest-first","description":"Currently 'bd ready' shows oldest issues first (by created_at). This can bury recently discovered work that might be more relevant. Propose a hybrid approach: show issues from the past 1-2 days first (sorted by priority), then fall back to oldest-first for older issues. This keeps fresh discoveries visible while still surfacing old forgotten work.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-18T09:31:15.036495-07:00","updated_at":"2025-10-18T09:35:55.084891-07:00","closed_at":"2025-10-18T09:35:55.084891-07:00"} +{"id":"bd-171","title":"Fix nil pointer dereference in renumber command","description":"The 'bd renumber' command crashes with a nil pointer dereference at renumber.go:52 because store is nil. The command doesn't properly handle daemon/direct mode initialization like other commands do. Error occurs on both --dry-run and --force modes.","status":"open","priority":1,"issue_type":"bug","created_at":"2025-10-18T09:54:31.59912-07:00","updated_at":"2025-10-18T09:54:31.59912-07:00"} {"id":"bd-18","title":"Document or automate JSONL sync workflow for git collaboration","description":"When using beads across multiple machines/environments via git, there's a workflow gap:\n\n1. Machine A: Create issues → stored in .beads/project.db\n2. Machine A: bd export -o .beads/issues.jsonl\n3. Machine A: git add .beads/issues.jsonl \u0026\u0026 git commit \u0026\u0026 git push\n4. Machine B: git pull\n5. Machine B: ??? issues.jsonl exists but project.db is empty/stale\n\nThe missing step is: bd import --db .beads/project.db -i .beads/issues.jsonl\n\nThis needs to be either:\na) Documented clearly in workflow docs\nb) Automated (e.g., git hook, or bd auto-imports if jsonl is newer than db)\nc) Both\n\nReal-world impact: User had Claude Code on GCP VM create vc issues from BOOTSTRAP.md. They were exported to issues.jsonl and committed. But on local machine, vc.db was empty until manual import was run.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.651438-07:00","closed_at":"2025-10-14T02:51:52.199766-07:00"} {"id":"bd-19","title":"Root issue for dep tree test","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.652067-07:00","closed_at":"2025-10-16T10:07:34.1266-07:00"} {"id":"bd-2","title":"Shared dependency C","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.585691-07:00","closed_at":"2025-10-16T10:07:34.12808-07:00"} @@ -114,7 +110,6 @@ {"id":"bd-67","title":"Test hash-based import","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.905866-07:00","closed_at":"2025-10-14T13:39:56.958248-07:00"} {"id":"bd-68","title":"Add migration scripts for GitHub Issues","description":"Create scripts to import from GitHub Issues API or exported JSON","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T23:51:47.390748-07:00","closed_at":"2025-10-17T23:51:47.390748-07:00","dependencies":[{"issue_id":"bd-68","depends_on_id":"bd-47","type":"parent-child","created_at":"2025-10-16T21:51:08.915539-07:00","created_by":"renumber"}]} {"id":"bd-69","title":"Add test for deep hierarchy blocking (50+ levels)","description":"Current tests verify 2-level depth (grandparent → parent → child). The depth limit is hardcoded to 50 in the recursive CTE, but we don't test edge cases near that limit.\n\n**Test cases needed:**\n1. Verify 50-level deep hierarchy works correctly\n2. Verify depth limit prevents runaway recursion\n3. Measure performance impact of deep hierarchies\n4. Consider if 50 is the right limit (why not 100? why not 20?)\n\n**Rationale:**\n- Most hierarchies are 2-5 levels deep\n- But pathological cases (malicious or accidental) could create 50+ level nesting\n- Need to ensure graceful degradation, not catastrophic failure\n\n**Implementation:**\nAdd TestDeepHierarchyBlocking to ready_test.go","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.915131-07:00","closed_at":"2025-10-14T13:12:16.610152-07:00"} -{"id":"bd-7","title":"Test auto-export timing","description":"","status":"open","priority":4,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.599423-07:00"} {"id":"bd-70","title":"Document hierarchical blocking behavior in README","description":"The fix for bd-65 changes user-visible behavior: children of blocked epics are now automatically blocked.\n\n**What needs documenting:**\n1. README.md dependency section should explain blocking propagation\n2. Clarify that 'blocks' + 'parent-child' together create transitive blocking\n3. Note that 'related' and 'discovered-from' do NOT propagate blocking\n4. Add example showing epic → child blocking propagation\n\n**Example to add:**\n```bash\n# If epic is blocked, children are too\nbd create \"Epic 1\" -t epic -p 1\nbd create \"Task 1\" -t task -p 1\nbd dep add task-1 epic-1 --type parent-child\n\n# Block the epic\nbd create \"Blocker\" -t task -p 0\nbd dep add epic-1 blocker-1 --type blocks\n\n# Now both epic-1 AND task-1 are blocked\nbd ready # Neither will show up\n```","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:01.020334-07:00","closed_at":"2025-10-14T13:10:38.482538-07:00"} {"id":"bd-71","title":"Document versioning and release strategy","description":"Create comprehensive versioning strategy for beads ecosystem.\n\nComponents to document:\n1. bd CLI (Go binary) - main version number\n2. Plugin (Claude Code) - tracks CLI version\n3. MCP server (Python) - bundled with plugin\n4. Release workflow - how to sync all three\n\nDecisions to make:\n- Should plugin.json auto-update from bd CLI version?\n- Should we have a VERSION file at repo root?\n- How to handle breaking changes across components?\n- What's the update notification strategy?\n\nReferences:\n- plugin.json engines field now requires bd \u003e=0.9.0\n- /bd-version command added for checking compatibility\n- PLUGIN.md now documents update workflow","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.91836-07:00","closed_at":"2025-10-14T13:55:59.178075-07:00"} {"id":"bd-72","title":"Create version bump script","description":"Create scripts/bump-version.sh to automate version syncing across all components.\n\nThe script should:\n1. Take a version number as argument (e.g., ./scripts/bump-version.sh 0.9.3)\n2. Update all version files:\n - cmd/bd/version.go (Version constant)\n - .claude-plugin/plugin.json (version field)\n - .claude-plugin/marketplace.json (plugins[].version)\n - integrations/beads-mcp/pyproject.toml (version field)\n - README.md (Alpha version mention)\n - PLUGIN.md (version requirements)\n3. Validate semantic versioning format\n4. Show diff preview before applying\n5. Optionally create git commit with standard message\n\nThis prevents the version mismatch issue that occurred when only version.go was updated.\n\nRelated: bd-73 (version sync issue)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:01.02371-07:00","closed_at":"2025-10-14T13:49:22.368581-07:00"} @@ -132,14 +127,14 @@ {"id":"bd-83","title":"Simplify getNextID SQL query parameters","description":"Query passes prefix four times to same SQL query. Works but fragile if query changes. Consider simplifying SQL to require fewer parameters. Location: internal/storage/sqlite/sqlite.go:73-78","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.92955-07:00","closed_at":"2025-10-16T10:07:34.038708-07:00"} {"id":"bd-84","title":"Add validation/warning for malformed issue IDs","description":"getNextID silently ignores non-numeric ID suffixes (e.g., bd-foo). CAST returns NULL for invalid strings. Consider detecting and warning about malformed IDs in database. Location: internal/storage/sqlite/sqlite.go:79-82","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.930472-07:00","closed_at":"2025-10-14T02:51:52.198988-07:00"} {"id":"bd-85","title":"Optimize export dependency queries (N+1 problem)","description":"Export triggers separate GetDependencyRecords() per issue. For large DBs (1000+ issues), this is N+1 queries. Add GetAllDependencyRecords() to fetch all dependencies in one query. Location: cmd/bd/export.go:52-59, import.go:138-142","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.931201-07:00","closed_at":"2025-10-14T02:51:52.19905-07:00"} -{"id":"bd-86","title":"Improve error handling in dependency removal during remapping","description":"In updateDependencyReferences(), RemoveDependency errors are caught and ignored with continue (line 392). Comment says 'if dependency doesn't exist' but this catches ALL errors including real failures. Should check error type with errors.Is(err, ErrDependencyNotFound) and only ignore not-found errors, returning other errors properly.","status":"open","priority":3,"issue_type":"bug","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.931812-07:00"} -{"id":"bd-87","title":"Use safer placeholder pattern in replaceIDReferences","description":"Currently uses bd-313 which could theoretically collide with user text. Use a truly unique placeholder like null bytes: \\x00REMAP\\x00_0_\\x00 which are unlikely to appear in normal text. Located in collision.go:324. Very low probability issue but worth fixing for completeness.","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.932507-07:00"} +{"id":"bd-86","title":"Improve error handling in dependency removal during remapping","description":"In updateDependencyReferences(), RemoveDependency errors are caught and ignored with continue (line 392). Comment says 'if dependency doesn't exist' but this catches ALL errors including real failures. Should check error type with errors.Is(err, ErrDependencyNotFound) and only ignore not-found errors, returning other errors properly.","status":"closed","priority":3,"issue_type":"bug","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-18T09:41:18.209717-07:00","closed_at":"2025-10-18T09:41:18.209717-07:00"} +{"id":"bd-87","title":"Use safer placeholder pattern in replaceIDReferences","description":"Currently uses bd-313 which could theoretically collide with user text. Use a truly unique placeholder like null bytes: \\x00REMAP\\x00_0_\\x00 which are unlikely to appear in normal text. Located in collision.go:324. Very low probability issue but worth fixing for completeness.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-18T09:43:18.250156-07:00","closed_at":"2025-10-18T09:43:18.250156-07:00"} {"id":"bd-88","title":"Test issue for design field","description":"Testing the new update flags","design":"## Design Plan\\n- Add flags to update command\\n- Test thoroughly\\n- Document changes","acceptance_criteria":"- All three fields (design, notes, acceptance-criteria) can be updated\\n- Changes persist in database\\n- bd show displays the fields correctly","notes":"Implementation complete. All tests passing.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.93305-07:00","closed_at":"2025-10-14T02:51:52.199634-07:00"} {"id":"bd-89","title":"Implement full cross-type cycle prevention in AddDependency","description":"Expand cycle prevention in AddDependency to check for cycles across ALL dependency types, not just 'blocks'. Currently only 'blocks' type dependencies are checked for cycles, allowing cross-type circular dependencies to form (e.g., A blocks B, B parent-child A). This can cause semantic confusion and is a maintenance hazard for future operations that traverse dependencies.","design":"Implementation approach:\n1. Modify the cycle check in AddDependency (postgres.go:559-599)\n2. Remove the 'type = blocks' filter from the recursive CTE\n3. Check for cycles regardless of dependency type being added\n4. Return a clear error message indicating which types form the cycle\n\nTrade-offs to consider:\n- This is more mathematically correct (no cycles in dependency DAG)\n- May break legitimate use cases where cross-type cycles are intentional\n- Need to evaluate whether ANY cross-type cycles are valid in practice\n- Alternative: make this configurable with a --allow-cycle flag\n\nBefore implementing, should investigate:\n- Are there legitimate reasons for cross-type cycles?\n- What's the performance impact on large graphs (1000+ issues)?\n- Should certain type combinations be allowed to cycle?","acceptance_criteria":"- AddDependency prevents cycles across all dependency types, not just 'blocks'\n- Clear error message when cycle would be created, including dependency types\n- All existing tests pass\n- Performance benchmarked on large dependency graphs (100+ issues)\n- Decision documented on whether to add --allow-cycle flag or exception rules","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.933764-07:00","closed_at":"2025-10-16T20:31:19.174534-07:00"} {"id":"bd-9","title":"Add transaction support to storage layer for atomic multi-operation workflows","description":"Currently each storage method (CreateIssue, UpdateIssue, etc.) starts its own transaction. This makes it impossible to perform atomic multi-step operations like collision resolution. Add support for passing *sql.Tx through the storage interface, or create transaction-aware versions of methods. This would make remapCollisions and other batch operations truly atomic.","status":"closed","priority":4,"issue_type":"feature","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.627507-07:00","closed_at":"2025-10-14T02:51:52.199176-07:00"} -{"id":"bd-90","title":"Refactor duplicate flush logic in PersistentPostRun","description":"PersistentPostRun contains a complete copy of the flush logic instead of calling flushToJSONL(). This violates DRY principle and makes maintenance harder. Refactor to use flushToJSONL() with a force parameter to bypass isDirty check, or extract shared logic into a helper function. Located in cmd/bd/main.go:104-138.","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.936145-07:00"} +{"id":"bd-90","title":"Refactor duplicate flush logic in PersistentPostRun","description":"PersistentPostRun contains a complete copy of the flush logic instead of calling flushToJSONL(). This violates DRY principle and makes maintenance harder. Refactor to use flushToJSONL() with a force parameter to bypass isDirty check, or extract shared logic into a helper function. Located in cmd/bd/main.go:104-138.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-18T09:44:24.167574-07:00","closed_at":"2025-10-18T09:44:24.167574-07:00"} {"id":"bd-91","title":"Optimize auto-flush to use incremental updates","description":"Every flush exports ALL issues and ALL dependencies, even if only one issue changed. For large projects (1000+ issues), this could be expensive. Current approach guarantees consistency, which is fine for MVP, but future optimization could track which issues changed and use incremental updates. Located in cmd/bd/main.go:255-276.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.940307-07:00","closed_at":"2025-10-14T02:51:52.200141-07:00"} -{"id":"bd-92","title":"Make auto-flush debounce duration configurable","description":"flushDebounce is hardcoded to 5 seconds. Make it configurable via environment variable BEADS_FLUSH_DEBOUNCE (e.g., '500ms', '10s'). Current 5-second value is reasonable for interactive use, but CI/automated scenarios might want faster flush. Add getDebounceDuration() helper function. Located in cmd/bd/main.go:31.","status":"open","priority":3,"issue_type":"feature","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.94553-07:00"} +{"id":"bd-92","title":"Make auto-flush debounce duration configurable","description":"flushDebounce is hardcoded to 5 seconds. Make it configurable via environment variable BEADS_FLUSH_DEBOUNCE (e.g., '500ms', '10s'). Current 5-second value is reasonable for interactive use, but CI/automated scenarios might want faster flush. Add getDebounceDuration() helper function. Located in cmd/bd/main.go:31.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-18T09:47:43.22126-07:00","closed_at":"2025-10-18T09:47:43.22126-07:00"} {"id":"bd-93","title":"Add EXPLAIN QUERY PLAN tests for ready work query","description":"Verify that the hierarchical blocking query uses proper indexes and doesn't do full table scans.\n\n**Queries to analyze:**\n1. The recursive CTE (both base case and recursive case)\n2. The final SELECT with NOT EXISTS\n3. Impact of various filters (status, priority, assignee)\n\n**Implementation:**\nAdd test function that:\n- Runs EXPLAIN QUERY PLAN on GetReadyWork query\n- Parses output to verify no SCAN TABLE operations\n- Documents expected query plan in comments\n- Fails if query plan degrades\n\n**Benefits:**\n- Catch performance regressions in tests\n- Document expected query behavior\n- Ensure indexes are being used\n\nRelated to: bd-66 (composite index on depends_on_id, type)","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:01.026648-07:00"} {"id":"bd-94","title":"Add performance benchmarks document","description":"Document actual performance metrics with hyperfine tests","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.948902-07:00","dependencies":[{"issue_id":"bd-94","depends_on_id":"bd-47","type":"parent-child","created_at":"2025-10-16T21:51:08.918095-07:00","created_by":"renumber"}]} {"id":"bd-95","title":"Investigate vector/semantic search for issue discovery","description":"From GH issue #2 RFC discussion: Evaluate if vector/semantic search over issues would provide value for beads.\n\n**Use case:** Find semantically related issues (e.g., 'login broken' finds 'authentication failure', 'session expired').\n\n**Questions to answer:**\n1. What workflows would this enable that we can't do now?\n2. Is dataset size (typically 50-200 issues) large enough to benefit?\n3. Do structured features (deps, tags, types) already provide better relationships?\n4. What's the maintenance cost (embeddings, storage, recomputation)?\n\n**Alternatives to consider:**\n- Improve 'bd list' filtering with regex/boolean queries\n- Add 'bd related \u003cid\u003e' showing deps + mentions + same tags\n- Export to JSON and pipe to external AI tools\n\n**Decision:** Only implement if clear use case emerges. Don't add complexity for theoretical benefits.\n\n**Context:** Part of evaluating Turso RFC ideas (GH #2). Vector search was proposed but unclear if needed for typical beads usage.","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-16T20:46:08.971822-07:00","updated_at":"2025-10-17T01:32:00.954311-07:00"} diff --git a/.golangci.bck.yml b/.golangci.bck.yml new file mode 100644 index 00000000..712228c9 --- /dev/null +++ b/.golangci.bck.yml @@ -0,0 +1,89 @@ +# golangci-lint configuration for beads +# See https://golangci-lint.run/usage/configuration/ + +run: + timeout: 5m + tests: true + +linters: + enable: + # Core linters (always available) + - errcheck # Check for unchecked errors + - govet # Go vet + - ineffassign # Detect ineffectual assignments + - staticcheck # Static analysis + - unused # Find unused code + + # Additional linters + - revive # Fast, configurable, extensible linter + - misspell # Fix commonly misspelled English words + - unconvert # Remove unnecessary type conversions + - unparam # Find unused function parameters + - goconst # Find repeated strings that could be constants + - gocyclo # Check cyclomatic complexity + - dupl # Find duplicated code + - gosec # Security-focused linter + +linters-settings: + errcheck: + check-type-assertions: true + check-blank: false + exclude-functions: + - (*database/sql.DB).Close + - (*database/sql.Rows).Close + - (*database/sql.Tx).Rollback + + gocyclo: + min-complexity: 15 + + goconst: + min-len: 3 + min-occurrences: 3 + + dupl: + threshold: 100 + + misspell: + locale: US + + revive: + rules: + - name: var-naming + - name: exported + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + + # Exclude known false positives and idiomatic patterns + exclude: + # Idiomatic Go: ignoring errors from defer cleanup + - "Error return value.*\\.Close.*is not checked" + - "Error return value.*\\.Rollback.*is not checked" + - "Error return value.*\\.RemoveAll.*is not checked" + + # Cobra handlers: unused params required by interface + - "unused-parameter: parameter 'cmd' seems to be unused" + - "unused-parameter: parameter 'args' seems to be unused" + + # Style preferences: naming decisions + - "var-naming: avoid meaningless package names" + - "exported.*SQLiteStorage.*stutters" + + # False positives: validated SQL construction + - "G201: SQL string formatting" + + # False positives: user-specified file paths (intended feature) + - "G304.*file inclusion via variable" + + # False positive: directory is for user data + - "G301: Expect directory permissions" + + # Exclude some linters from running on tests + exclude-rules: + - path: _test\.go + linters: + - dupl # Test duplication is acceptable + - goconst # Test constants are acceptable + - errcheck # Test cleanup errors are acceptable + - gocyclo # Test complexity is acceptable diff --git a/cmd/bd/main.go b/cmd/bd/main.go index d28e06c4..5ac1cbcf 100644 --- a/cmd/bd/main.go +++ b/cmd/bd/main.go @@ -43,7 +43,6 @@ var ( needsFullExport = false // Set to true when IDs change (renumber, rename-prefix) flushMutex sync.Mutex flushTimer *time.Timer - flushDebounce = 5 * time.Second storeMutex sync.Mutex // Protects store access from background goroutine storeActive = false // Tracks if store is available flushFailureCount = 0 // Consecutive flush failures @@ -189,6 +188,24 @@ var rootCmd = &cobra.Command{ }, } +// getDebounceDuration returns the auto-flush debounce duration +// Configurable via BEADS_FLUSH_DEBOUNCE (e.g., "500ms", "10s") +// Defaults to 5 seconds if not set or invalid +func getDebounceDuration() time.Duration { + envVal := os.Getenv("BEADS_FLUSH_DEBOUNCE") + if envVal == "" { + return 5 * time.Second + } + + duration, err := time.ParseDuration(envVal) + if err != nil { + fmt.Fprintf(os.Stderr, "Warning: invalid BEADS_FLUSH_DEBOUNCE value '%s', using default 5s\n", envVal) + return 5 * time.Second + } + + return duration +} + // shouldAutoStartDaemon checks if daemon auto-start is enabled func shouldAutoStartDaemon() bool { // Check environment variable (default: true) @@ -737,7 +754,7 @@ func markDirtyAndScheduleFlush() { } // Schedule new flush - flushTimer = time.AfterFunc(flushDebounce, func() { + flushTimer = time.AfterFunc(getDebounceDuration(), func() { flushToJSONL() }) } @@ -761,7 +778,7 @@ func markDirtyAndScheduleFullExport() { } // Schedule new flush - flushTimer = time.AfterFunc(flushDebounce, func() { + flushTimer = time.AfterFunc(getDebounceDuration(), func() { flushToJSONL() }) } diff --git a/cmd/bd/main_test.go b/cmd/bd/main_test.go index 38312f08..3543de3d 100644 --- a/cmd/bd/main_test.go +++ b/cmd/bd/main_test.go @@ -114,9 +114,8 @@ func TestAutoFlushDebounce(t *testing.T) { storeMutex.Unlock() // Set short debounce for testing (100ms) - originalDebounce := flushDebounce - flushDebounce = 100 * time.Millisecond - defer func() { flushDebounce = originalDebounce }() + os.Setenv("BEADS_FLUSH_DEBOUNCE", "100ms") + defer os.Unsetenv("BEADS_FLUSH_DEBOUNCE") // Reset auto-flush state autoFlushEnabled = true diff --git a/cmd/bd/renumber.go b/cmd/bd/renumber.go index 9771f2ab..b77844a8 100644 --- a/cmd/bd/renumber.go +++ b/cmd/bd/renumber.go @@ -46,6 +46,22 @@ Risks: os.Exit(1) } + // Renumber command needs direct access to storage + // Ensure we have a direct store connection + if store == nil { + var err error + if dbPath == "" { + fmt.Fprintf(os.Stderr, "Error: no database path found\n") + os.Exit(1) + } + store, err = sqlite.New(dbPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: failed to open database: %v\n", err) + os.Exit(1) + } + defer store.Close() + } + ctx := context.Background() // Get prefix from config, or derive from first issue if not set diff --git a/internal/storage/sqlite/collision.go b/internal/storage/sqlite/collision.go index 13c05be2..eaf574ff 100644 --- a/internal/storage/sqlite/collision.go +++ b/internal/storage/sqlite/collision.go @@ -429,7 +429,7 @@ func buildReplacementCache(idMapping map[string]string) ([]*idReplacementCache, cache = append(cache, &idReplacementCache{ oldID: oldID, newID: newID, - placeholder: fmt.Sprintf("__PLACEHOLDER_%d__", i), + placeholder: fmt.Sprintf("\x00REMAP\x00_%d_\x00", i), regex: re, }) i++