diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 3c2db97d..84613fb6 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -64,14 +64,15 @@ {"id":"bd-156","title":"Add integration tests for daemon + external tool coexistence","description":"Add integration tests simulating real-world daemon + external tool usage:\n\nTest scenarios:\n1. Start daemon, create lock file, verify daemon skips database\n2. Create stale lock (with dead PID), start daemon, verify lock cleanup\n3. Multiple lock/unlock cycles\n4. Daemon resumes managing database after lock is removed\n5. Lock file created/removed during daemon operation\n\nShould verify:\n- Daemon log messages correct\n- No JSONL auto-flush when database is locked\n- Daemon resumes normal operation after lock removed","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-25T22:44:08.686473-07:00","updated_at":"2025-10-25T22:44:08.686473-07:00","dependencies":[{"issue_id":"bd-156","depends_on_id":"bd-154","type":"blocks","created_at":"2025-10-25T22:44:10.75056-07:00","created_by":"daemon"},{"issue_id":"bd-156","depends_on_id":"bd-150","type":"parent-child","created_at":"2025-10-25T22:44:12.984228-07:00","created_by":"daemon"}]} {"id":"bd-157","title":"Document exclusive lock protocol in README and new doc file","description":"Update documentation to explain exclusive lock protocol:\n\n1. Create EXCLUSIVE_LOCK.md with:\n - Protocol specification\n - Lock file format (JSON schema)\n - Usage examples for external tools\n - Stale lock behavior\n - Edge cases and limitations\n\n2. Update README.md:\n - Mention exclusive lock support in daemon section\n - Link to EXCLUSIVE_LOCK.md for details\n\n3. Update AGENTS.md:\n - Note that external tools can use exclusive locks\n - Explain daemon skips locked databases\n\nTarget audience: developers integrating bd with external tools (like VC).","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-25T22:44:18.676992-07:00","updated_at":"2025-10-25T22:44:18.676992-07:00","dependencies":[{"issue_id":"bd-157","depends_on_id":"bd-154","type":"blocks","created_at":"2025-10-25T22:44:22.31844-07:00","created_by":"daemon"},{"issue_id":"bd-157","depends_on_id":"bd-150","type":"parent-child","created_at":"2025-10-25T22:44:24.324785-07:00","created_by":"daemon"}]} {"id":"bd-158","title":"Daemon fails to auto-import after git pull updates JSONL","description":"After git pull updates .beads/issues.jsonl, daemon doesn't automatically re-import changes, causing stale data to be shown until next sync cycle (up to 5 minutes).\n\nReproduction:\n1. Repo A: Close issues, export, commit, push\n2. Repo B: git pull (successfully updates .beads/issues.jsonl)\n3. bd show \u003cissue\u003e shows OLD status from daemon's SQLite db\n4. JSONL on disk has correct new status\n\nRoot cause: Daemon sync cycle runs on timer (5min). When user manually runs git pull, daemon doesn't detect JSONL was updated externally and continues serving stale data from SQLite.\n\nImpact:\n- High for AI agents using beads in git workflows\n- Breaks fundamental git-as-source-of-truth model\n- Confusing UX: git log shows commit, bd shows old state\n- Data consistency issues between JSONL and daemon\n\nSee WYVERN_SYNC_ISSUE.md for full analysis.","design":"Three possible solutions:\n\nOption 1: Auto-detect and re-import (recommended)\n- Before serving any bd command, check if .beads/issues.jsonl mtime \u003e last import time\n- If newer, auto-import before processing request\n- Fast check, minimal overhead\n\nOption 2: File watcher in daemon\n- Daemon watches .beads/issues.jsonl for mtime changes\n- Auto-imports when file changes\n- More complex, requires file watching infrastructure\n\nOption 3: Explicit sync command\n- User runs `bd sync` after git pull\n- Manual, error-prone, defeats automation\n\nRecommended: Option 1 (auto-detect) + Option 3 (explicit sync) as fallback.","acceptance_criteria":"1. After git pull updates .beads/issues.jsonl, next bd command sees fresh data\n2. No manual import or daemon restart required\n3. Performance impact \u003c 10ms per command (mtime check is fast)\n4. Works in both daemon and non-daemon modes\n5. Test: Two repo clones, update in one, pull in other, verify immediate sync","status":"open","priority":0,"issue_type":"epic","created_at":"2025-10-25T22:46:17.78926-07:00","updated_at":"2025-10-25T22:46:17.78926-07:00"} -{"id":"bd-159","title":"Track last JSONL import timestamp in daemon state","description":"Add state tracking to daemon to record when .beads/issues.jsonl was last imported.\n\nImplementation:\n- Add lastImportTime field to daemon state (time.Time)\n- Update timestamp after successful import\n- Persist across RPC requests (in-memory state)\n- Initialize on daemon startup\n\nNeeded for staleness detection to compare against JSONL mtime.","status":"open","priority":0,"issue_type":"task","created_at":"2025-10-25T22:46:26.80171-07:00","updated_at":"2025-10-25T22:46:26.80171-07:00","dependencies":[{"issue_id":"bd-159","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:46:29.325543-07:00","created_by":"daemon"}]} +{"id":"bd-159","title":"Track last JSONL import timestamp in daemon state","description":"Add state tracking to daemon to record when .beads/issues.jsonl was last imported.\n\nImplementation:\n- Add lastImportTime field to daemon state (time.Time)\n- Update timestamp after successful import\n- Persist across RPC requests (in-memory state)\n- Initialize on daemon startup\n\nNeeded for staleness detection to compare against JSONL mtime.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-10-25T22:46:26.80171-07:00","updated_at":"2025-10-25T23:04:33.056154-07:00","closed_at":"2025-10-25T23:04:33.056154-07:00","dependencies":[{"issue_id":"bd-159","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:46:29.325543-07:00","created_by":"daemon"}]} {"id":"bd-16","title":"Global daemon should warn/reject --auto-commit and --auto-push","description":"When user runs 'bd daemon --global --auto-commit', it's unclear which repo the daemon will commit to (especially after fixing bd-62 where global daemon won't open a DB).\n\nOptions:\n1. Warn and ignore the flags in global mode\n2. Error out with clear message\n\nLine 87-91 already checks autoPush, but should skip check entirely for global mode. Add user-friendly messaging about flag incompatibility.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-22T00:47:43.165645-07:00","updated_at":"2025-10-24T13:51:54.437812-07:00","closed_at":"2025-10-17T23:04:30.223432-07:00"} -{"id":"bd-160","title":"Implement staleness check before serving daemon requests","description":"Add staleness detection to daemon RPC handler that checks if JSONL is newer than last import.\n\nLogic:\n1. Before processing any RPC request (show, list, ready, etc.)\n2. Get .beads/issues.jsonl mtime (os.Stat)\n3. Compare with lastImportTime\n4. If JSONL mtime \u003e lastImportTime: auto-import before processing request\n5. If import fails: log error but continue with existing data\n\nPerformance: mtime check is fast (\u003c1ms). Only imports if needed.\n\nDepends on lastImportTime tracking.","status":"open","priority":0,"issue_type":"task","created_at":"2025-10-25T22:46:35.981048-07:00","updated_at":"2025-10-25T22:46:35.981048-07:00","dependencies":[{"issue_id":"bd-160","depends_on_id":"bd-159","type":"blocks","created_at":"2025-10-25T22:46:38.115284-07:00","created_by":"daemon"},{"issue_id":"bd-160","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:46:39.995195-07:00","created_by":"daemon"}]} +{"id":"bd-160","title":"Implement staleness check before serving daemon requests","description":"Add staleness detection to daemon RPC handler that checks if JSONL is newer than last import.\n\nLogic:\n1. Before processing any RPC request (show, list, ready, etc.)\n2. Get .beads/issues.jsonl mtime (os.Stat)\n3. Compare with lastImportTime\n4. If JSONL mtime \u003e lastImportTime: auto-import before processing request\n5. If import fails: log error but continue with existing data\n\nPerformance: mtime check is fast (\u003c1ms). Only imports if needed.\n\nDepends on lastImportTime tracking.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-10-25T22:46:35.981048-07:00","updated_at":"2025-10-25T23:04:33.056796-07:00","closed_at":"2025-10-25T23:04:33.056796-07:00","dependencies":[{"issue_id":"bd-160","depends_on_id":"bd-159","type":"blocks","created_at":"2025-10-25T22:46:38.115284-07:00","created_by":"daemon"},{"issue_id":"bd-160","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:46:39.995195-07:00","created_by":"daemon"}]} {"id":"bd-161","title":"Add staleness check to non-daemon mode","description":"Extend staleness detection to non-daemon mode (--no-daemon).\n\nImplementation:\n- On database open, check if .beads/issues.jsonl exists\n- If JSONL exists and is newer than .db file: auto-import\n- Compare JSONL mtime vs .db mtime (both os.Stat)\n- Log: \"Auto-importing from .beads/issues.jsonl (newer than database)\"\n\nThis ensures both daemon and non-daemon modes handle git pull correctly.","status":"open","priority":0,"issue_type":"task","created_at":"2025-10-25T22:46:44.664917-07:00","updated_at":"2025-10-25T22:46:44.664917-07:00","dependencies":[{"issue_id":"bd-161","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:46:46.921358-07:00","created_by":"daemon"}]} {"id":"bd-162","title":"Add 'bd sync' command for explicit synchronization","description":"Add explicit `bd sync` command as fallback for manual synchronization after git pull.\n\nBehavior:\n- Import from .beads/issues.jsonl\n- If daemon mode: send RPC command to daemon to re-import\n- If non-daemon: directly import to local db\n- Show summary: \"Imported N issues, updated M issues\"\n\nUsage:\n```bash\ngit pull\nbd sync # Force immediate sync\n```\n\nThis complements auto-detection but gives users manual control.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-25T22:46:52.139434-07:00","updated_at":"2025-10-25T22:46:52.139434-07:00","dependencies":[{"issue_id":"bd-162","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:46:54.530849-07:00","created_by":"daemon"}]} {"id":"bd-163","title":"Add integration test for git pull sync scenario","description":"Add integration test simulating the git pull sync issue.\n\nTest scenario:\n1. Create temp git repo with beads initialized\n2. Clone 1: Create and close issue, export, commit, push\n3. Clone 2: Start daemon, git pull\n4. Clone 2: Verify bd show \u003cissue\u003e reflects closed status immediately\n5. Verify no manual import or daemon restart needed\n\nAlso test:\n- Non-daemon mode (--no-daemon) handles git pull correctly\n- bd sync command works in both modes\n- Performance: staleness check adds \u003c10ms overhead\n\nDepends on staleness detection implementation.","status":"open","priority":0,"issue_type":"task","created_at":"2025-10-25T22:47:01.101808-07:00","updated_at":"2025-10-25T22:47:01.101808-07:00","dependencies":[{"issue_id":"bd-163","depends_on_id":"bd-160","type":"blocks","created_at":"2025-10-25T22:47:03.492413-07:00","created_by":"daemon"},{"issue_id":"bd-163","depends_on_id":"bd-161","type":"blocks","created_at":"2025-10-25T22:47:05.615638-07:00","created_by":"daemon"},{"issue_id":"bd-163","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:47:08.360368-07:00","created_by":"daemon"}]} {"id":"bd-164","title":"Add optional post-merge git hook example for bd sync","description":"Create example git hook that auto-runs bd sync after git pull/merge.\n\nAdd to examples/git-hooks/:\n- post-merge hook that checks if .beads/issues.jsonl changed\n- If changed: run `bd sync` automatically\n- Make it optional/documented (not auto-installed)\n\nBenefits:\n- Zero-friction sync after git pull\n- Complements auto-detection as belt-and-suspenders\n\nNote: post-merge hook already exists for pre-commit/post-merge. Extend it to support sync.","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-25T22:47:14.668842-07:00","updated_at":"2025-10-25T22:47:14.668842-07:00","dependencies":[{"issue_id":"bd-164","depends_on_id":"bd-162","type":"blocks","created_at":"2025-10-25T22:47:16.949519-07:00","created_by":"daemon"},{"issue_id":"bd-164","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:47:19.031421-07:00","created_by":"daemon"}]} {"id":"bd-165","title":"Update documentation for auto-sync behavior","description":"Update documentation to explain auto-sync after git pull.\n\nFiles to update:\n1. README.md - Add section on git workflow and auto-sync\n2. AGENTS.md - Note that bd auto-detects JSONL changes after git pull\n3. WORKFLOW.md - Update git pull workflow to remove manual import step\n4. FAQ.md - Add Q\u0026A about sync behavior and staleness\n\nKey points:\n- bd automatically detects when JSONL is newer than database\n- No manual import needed after git pull\n- bd sync command available for manual control\n- Optional git hook for guaranteed sync","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-25T22:47:24.618649-07:00","updated_at":"2025-10-25T22:47:24.618649-07:00","dependencies":[{"issue_id":"bd-165","depends_on_id":"bd-160","type":"blocks","created_at":"2025-10-25T22:47:26.508404-07:00","created_by":"daemon"},{"issue_id":"bd-165","depends_on_id":"bd-158","type":"parent-child","created_at":"2025-10-25T22:47:29.535976-07:00","created_by":"daemon"}]} +{"id":"bd-166","title":"Refactor autoImportIfNewer to be callable from daemon","description":"The staleness check in bd-160 detects when JSONL is newer than last import, but can't trigger the actual import because autoImportIfNewer() is in cmd/bd and uses global variables.\n\nNeed to:\n1. Extract core import logic from autoImportIfNewer() into importable function\n2. Move to internal/autoimport or similar package\n3. Make it callable from daemon (no global state dependency)\n4. Update staleness check in server.go to call actual import instead of just logging\n\nThis completes the auto-sync feature - daemon will truly auto-import after git pull.","status":"open","priority":0,"issue_type":"task","created_at":"2025-10-25T23:05:44.938428-07:00","updated_at":"2025-10-25T23:05:44.938428-07:00","dependencies":[{"issue_id":"bd-166","depends_on_id":"bd-160","type":"blocks","created_at":"2025-10-25T23:05:44.940259-07:00","created_by":"stevey"}]} {"id":"bd-17","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-63 (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-38 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-38 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":"closed","priority":4,"issue_type":"feature","created_at":"2025-10-22T00:47:43.165857-07:00","updated_at":"2025-10-24T13:51:54.438011-07:00","closed_at":"2025-10-20T22:00:31.966891-07:00"} {"id":"bd-18","title":"Make beads reusable as a Go library for external projects like vc","description":"Currently beads is only usable as a CLI tool. We want to use beads as a library in other Go projects like ~/src/vc so they can programmatically manage issues without shelling out to the bd CLI.\n\nGoals:\n- Export public API from internal packages\n- Document Go package usage\n- Provide examples of programmatic usage\n- Ensure vc can import and use beads storage layer directly\n\nUse case: The vc project needs issue tracking and wants to use beads as an embedded library rather than as a separate CLI tool.","notes":"UnderlyingDB() method implemented and tested. Core functionality complete. Still needs documentation updates (bd-26) and lifecycle safety enhancements (bd-25).","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-10-22T12:27:30.35968-07:00","updated_at":"2025-10-24T13:51:54.438187-07:00","closed_at":"2025-10-22T19:46:09.362533-07:00"} {"id":"bd-19","title":"Beads Library Integration","description":"Migrate from custom SQLite implementation to using Beads as a library dependency. This eliminates ~3000 lines of duplicated code, reduces schema drift risk, and automatically inherits new Beads features.\n\n**Key Benefits:**\n- Remove 3000+ lines of duplicated SQLite code\n- Eliminate schema drift between bd and vc CLIs\n- Inherit Beads improvements automatically\n- Stronger type safety with Beads error types\n- Faster development velocity for new features\n- Clean separation: Beads stays general-purpose, VC extends via wrapper\n\n**Architecture Principle:**\nBeads remains 100% standalone with NO VC dependencies. VC imports Beads (VC → Beads dependency) and wraps it with VC-specific storage methods. Both share the same database but maintain separate table namespaces.\n\n**Current Pain Points:**\n1. Code Duplication: Issue CRUD, dependency graphs, labels, status transitions all reimplemented\n2. Schema Drift Risk: VC schema manually defined, could diverge from Beads\n3. Lost Features: Can't leverage Beads query optimizer or advanced features without process spawning\n4. Atomic Operations: Hand-rolled 100+ line transaction management\n5. Maintenance Burden: Every Beads feature must be manually replicated\n\n**Concrete Example:**\nWhen Beads adds a new field (e.g., estimated_hours):\n- Current: 4-6 hours of manual work (update types, 6+ SQL queries, migration, testing)\n- With Library: 5 minutes (go get -u github.com/steveyegge/beads)\n\n**Phased Approach:**\n1. Phase 1: Add Beads dependency (non-breaking, feature flag)\n2. Phase 2: Implement VCStorage wrapper (embeds beads.Storage)\n3. Phase 3: Migration script for existing databases\n4. Phase 4: Gradual cutover, deprecate SQLite code\n\n**Related Analysis:**\nSee BEADS_INTEGRATION_ANALYSIS.md for detailed current state analysis and BEADS_LIBRARY_INTEGRATION_EPIC.md for full design document (both to be archived after issue creation).\n\n**Estimated Effort:** 3-4 sprints\n**Priority:** P2 (Medium-High - architectural improvement, high ROI)","notes":"Phase 1 (bd-20) complete! Beads can now be used as a Go library. VC can import github.com/steveyegge/beads and use beads.Storage directly instead of spawning CLI processes. No custom tables needed - VC uses pure Beads primitives.","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-10-22T14:04:08.692803-07:00","updated_at":"2025-10-24T13:51:54.438399-07:00","closed_at":"2025-10-22T14:40:10.225406-07:00"} @@ -80,7 +81,7 @@ {"id":"bd-21","title":"Phase 2: Implement VCStorage Wrapper","description":"Create VCStorage wrapper that embeds beads.Storage and adds VC-specific operations.\n\n**Goal:** Build clean abstraction layer where VC extends Beads without modifying Beads library.\n\n**Architecture:**\n- VCStorage embeds beads.Storage (delegates core operations)\n- VCStorage adds VC-specific methods (executor instances, events)\n- Same database, separate table namespaces (Beads tables + VC tables)\n- Zero changes to Beads library code\n\n**Key Tasks:**\n1. Create VCStorage struct that embeds beads.Storage\n2. Implement VC-specific methods: CreateExecutorInstance(), GetStaleExecutors(), LogEvent(), UpdateExecutionState()\n3. Create VC table schemas (executor_instances, issue_execution_state, agent_events)\n4. Verify type compatibility between VC types.Issue and Beads Issue\n5. Create MockVCStorage for testing\n6. Write unit tests for VC-specific methods\n7. Write integration tests (end-to-end with Beads)\n8. Benchmark performance vs current SQLite\n9. Verify NO changes needed to Beads library\n\n**Acceptance Criteria:**\n- VCStorage successfully wraps Beads storage (embedding works)\n- VC-specific tables created and accessible via foreign keys to Beads tables\n- VC-specific methods work (executor instances, events)\n- Core operations delegate to Beads correctly\n- Tests pass with \u003e90% coverage\n- Performance benchmark shows no regression\n- Beads library remains unmodified and standalone\n\n**Technical Details:**\n- Use beadsStore.DB() to get underlying database connection\n- Create VC tables with FOREIGN KEY references to Beads issues table\n- Schema separation: Beads owns (issues, dependencies, labels), VC owns (executor_instances, agent_events)\n- Testing: Embed MockBeadsStorage in MockVCStorage\n\n**Dependencies:**\n- Blocked by Phase 1 (need Beads library imported)\n\n**Estimated Effort:** 1.5 sprints","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-22T14:04:36.674165-07:00","updated_at":"2025-10-24T13:51:54.398392-07:00","closed_at":"2025-10-22T21:37:48.747033-07:00","dependencies":[{"issue_id":"bd-21","depends_on_id":"bd-19","type":"parent-child","created_at":"2025-10-24T13:17:40.321936-07:00","created_by":"renumber"},{"issue_id":"bd-21","depends_on_id":"bd-20","type":"blocks","created_at":"2025-10-24T13:17:40.322171-07:00","created_by":"renumber"}]} {"id":"bd-22","title":"Phase 3: Migration Path \u0026 Database Schema Alignment","description":"Enable existing .beads/vc.db files to work with Beads library through automated migration.\n\n**Goal:** Provide safe, tested migration path from SQLite implementation to Beads library.\n\n**Key Tasks:**\n1. Run compatibility tests against production databases\n2. Identify schema differences (columns, indexes, constraints)\n3. Document required migrations\n4. Create migration CLI command: 'vc migrate --from sqlite --to beads'\n5. Add dry-run mode for preview\n6. Add backup/restore capability\n7. Implement rollback mechanism\n8. Add auto-detection of schema version on startup\n9. Add auto-migrate with user prompt\n\n**Acceptance Criteria:**\n- Existing databases migrate successfully\n- Data integrity preserved (zero data loss verified via checksums)\n- Rollback works if migration fails\n- Migration tested on real production VC databases\n- Dry-run mode shows exactly what will change\n- Backup created before migration\n- Feature flag: VC_FORCE_SQLITE=true provides escape hatch\n\n**Technical Details:**\n- Compare current SQLite schema with Beads schema\n- Handle version detection (read schema_version or detect from structure)\n- Migration should be idempotent (safe to run multiple times)\n- Backup strategy: Copy .beads/vc.db to .beads/vc.db.backup-\u003ctimestamp\u003e\n- Verify foreign key integrity after migration\n\n**Safety Measures:**\n- Require executor shutdown before migration (check for running executors)\n- Atomic migration (BEGIN IMMEDIATE transaction)\n- Comprehensive pre/post migration validation\n- Clear error messages with recovery instructions\n\n**Dependencies:**\n- Blocked by Phase 2 (need VCStorage implementation)\n\n**Estimated Effort:** 0.5 sprint","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-22T14:04:51.320435-07:00","updated_at":"2025-10-24T13:51:54.399455-07:00","closed_at":"2025-10-22T21:37:48.748273-07:00","dependencies":[{"issue_id":"bd-22","depends_on_id":"bd-19","type":"parent-child","created_at":"2025-10-24T13:17:40.323317-07:00","created_by":"renumber"},{"issue_id":"bd-22","depends_on_id":"bd-21","type":"blocks","created_at":"2025-10-24T13:17:40.323527-07:00","created_by":"renumber"}]} {"id":"bd-23","title":"Phase 4: Gradual Cutover \u0026 Production Rollout","description":"Replace SQLite implementation with Beads library in production and remove legacy code.\n\n**Goal:** Complete transition to Beads library, deprecate and remove custom SQLite implementation.\n\n**Key Tasks:**\n1. Run VC executor with Beads library in CI\n2. Dogfood: Use Beads library for VC's own development\n3. Monitor for regressions and performance issues\n4. Flip feature flag: VC_USE_BEADS_LIBRARY=true by default\n5. Monitor production logs for errors\n6. Collect user feedback\n7. Add deprecation notice to CLAUDE.md\n8. Provide migration guide for users\n9. Remove legacy code: internal/storage/sqlite/sqlite.go (~1500 lines)\n10. Remove migration framework: internal/storage/migrations/\n11. Remove manual transaction management code\n12. Update all documentation\n\n**Acceptance Criteria:**\n- Beads library enabled by default in production\n- Zero production incidents related to migration\n- Performance meets or exceeds SQLite implementation\n- All tests passing with Beads library\n- Legacy SQLite code removed\n- Documentation updated\n- Celebration documented 🎉\n\n**Rollout Strategy:**\n1. Week 1: Enable for CI/testing environments\n2. Week 2: Dogfood on VC development\n3. Week 3: Enable for 50% of production (canary)\n4. Week 4: Enable for 100% of production\n5. Week 5: Remove legacy code\n\n**Monitoring:**\n- Track error rates before/after cutover\n- Monitor database query performance\n- Track issue creation/update latency\n- Monitor executor claim performance\n\n**Rollback Plan:**\n- Keep VC_FORCE_SQLITE=true escape hatch for 2 weeks post-cutover\n- Keep legacy code for 1 sprint after cutover\n- Document rollback procedure\n\n**Success Metrics:**\n- Zero data loss\n- No performance regression (\u003c 5% latency increase acceptable)\n- Reduced maintenance burden (code LOC reduction)\n- Positive developer feedback\n\n**Dependencies:**\n- Blocked by Phase 3 (need migration tooling)\n\n**Estimated Effort:** 1 sprint","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-22T14:05:07.755107-07:00","updated_at":"2025-10-24T13:51:54.401936-07:00","closed_at":"2025-10-22T21:37:48.748919-07:00","dependencies":[{"issue_id":"bd-23","depends_on_id":"bd-19","type":"parent-child","created_at":"2025-10-24T13:17:40.324637-07:00","created_by":"renumber"},{"issue_id":"bd-23","depends_on_id":"bd-22","type":"blocks","created_at":"2025-10-24T13:17:40.324851-07:00","created_by":"renumber"}]} -{"id":"bd-24","title":"Example library-created issue","description":"This issue was created programmatically using Beads as a library","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-22T14:34:44.081801-07:00","updated_at":"2025-10-24T13:51:54.402932-07:00","closed_at":"2025-10-22T14:34:44.084241-07:00","labels":["library-usage"],"dependencies":[{"issue_id":"bd-24","depends_on_id":"bd-4","type":"discovered-from","created_at":"2025-10-24T13:31:30.930671-07:00","created_by":"import"},{"issue_id":"bd-24","depends_on_id":"bd-125","type":"discovered-from","created_at":"2025-10-24T13:45:29.804656-07:00","created_by":"stevey"}],"comments":[{"id":7,"issue_id":"bd-24","author":"library-example","text":"This is a programmatic comment","created_at":"2025-10-22T21:34:44Z"}]} +{"id":"bd-24","title":"Example library-created issue","description":"This issue was created programmatically using Beads as a library","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-22T14:34:44.081801-07:00","updated_at":"2025-10-24T13:51:54.402932-07:00","closed_at":"2025-10-22T14:34:44.084241-07:00","labels":["library-usage"],"dependencies":[{"issue_id":"bd-24","depends_on_id":"bd-4","type":"discovered-from","created_at":"2025-10-24T13:31:30.930671-07:00","created_by":"import"},{"issue_id":"bd-24","depends_on_id":"bd-125","type":"discovered-from","created_at":"2025-10-24T13:45:29.804656-07:00","created_by":"stevey"}]} {"id":"bd-25","title":"Add lifecycle safety docs and tests for UnderlyingDB() method","description":"The new UnderlyingDB() method exposes the raw *sql.DB connection for extensions like VC to create their own tables. While database/sql is concurrency-safe, there are lifecycle and misuse risks that need documentation and testing.\n\n**What needs to be done:**\n\n1. **Enhanced documentation** - Expand UnderlyingDB() comments to warn:\n - Callers MUST NOT call Close() on returned DB\n - Do NOT change pool/driver settings (SetMaxOpenConns, SetConnMaxIdleTime)\n - Do NOT modify SQLite PRAGMAs (WAL mode, journal, etc.)\n - Expect errors after Storage.Close() - use contexts\n - Keep write transactions short to avoid blocking core storage\n\n2. **Add lifecycle tracking** - Implement closed flag:\n - Add atomic.Bool closed field to SQLiteStorage\n - Set flag in Close(), clear in New()\n - Optional: Add IsClosed() bool method\n\n3. **Add safety tests** (run with -race):\n - TestUnderlyingDB_ConcurrentAccess - N goroutines using UnderlyingDB() during normal storage ops\n - TestUnderlyingDB_AfterClose - Verify operations fail cleanly after storage closed\n - TestUnderlyingDB_CreateExtensionTables - Create VC table with FK to issues, verify FK enforcement\n - TestUnderlyingDB_LongTxDoesNotCorrupt - Ensure long read tx doesn't block writes indefinitely\n\n**Why this matters:**\nVC will use this to create tables in the same database. Need to ensure production-ready safety without over-engineering.\n\n**Estimated effort:** S+S+S = M total (1-3h)","design":"Oracle recommends \"simple path\": enhanced docs + minimal guardrails + focused tests. See oracle output for detailed rationale on concurrency safety, lifecycle risks, and when to consider advanced path (wrapping interface).","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-22T17:07:56.812983-07:00","updated_at":"2025-10-24T13:51:54.403868-07:00","closed_at":"2025-10-22T20:10:52.636372-07:00"} {"id":"bd-26","title":"Update EXTENDING.md with UnderlyingDB() usage and best practices","description":"EXTENDING.md currently shows how to use direct sql.Open() to access the database, but doesn't mention the new UnderlyingDB() method that's the recommended way for extensions.\n\n**Update needed:**\n1. Add section showing UnderlyingDB() usage:\n ```go\n store, err := beads.NewSQLiteStorage(dbPath)\n db := store.UnderlyingDB()\n // Create extension tables using db\n ```\n\n2. Document when to use UnderlyingDB() vs direct sql.Open():\n - Use UnderlyingDB() when you want to share the storage connection\n - Use sql.Open() when you need independent connection management\n\n3. Add safety warnings (cross-reference from UnderlyingDB() docs):\n - Don't close the DB\n - Don't modify pool settings\n - Keep transactions short\n\n4. Update the VC example to show UnderlyingDB() pattern\n\n5. Explain beads.Storage.UnderlyingDB() in the API section","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-22T17:07:56.820056-07:00","updated_at":"2025-10-24T13:51:54.404615-07:00","closed_at":"2025-10-22T19:41:19.895847-07:00","dependencies":[{"issue_id":"bd-26","depends_on_id":"bd-18","type":"discovered-from","created_at":"2025-10-24T13:17:40.32522-07:00","created_by":"renumber"}]} {"id":"bd-27","title":"Consider adding UnderlyingConn(ctx) for safer scoped DB access","description":"Currently UnderlyingDB() returns *sql.DB which is correct for most uses, but for extension migrations/DDL, a scoped connection might be safer.\n\n**Proposal:** Add optional UnderlyingConn(ctx) (*sql.Conn, error) method that:\n- Returns a scoped connection via s.db.Conn(ctx)\n- Encourages lifetime-bounded usage\n- Reduces temptation to tune global pool settings\n- Better for one-time DDL operations like CREATE TABLE\n\n**Implementation:**\n```go\n// UnderlyingConn returns a single connection from the pool for scoped use\n// Useful for migrations and DDL. Close the connection when done.\nfunc (s *SQLiteStorage) UnderlyingConn(ctx context.Context) (*sql.Conn, error) {\n return s.db.Conn(ctx)\n}\n```\n\n**Benefits:**\n- Safer for migrations (explicit scope)\n- Complements UnderlyingDB() for different use cases\n- Low implementation cost\n\n**Trade-off:** Adds another method to maintain, but Oracle considers this balanced compromise between safety and flexibility.\n\n**Decision:** This is optional - evaluate based on VC's actual usage patterns.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-22T17:07:56.832638-07:00","updated_at":"2025-10-24T13:51:54.405456-07:00","closed_at":"2025-10-22T22:02:18.479512-07:00","dependencies":[{"issue_id":"bd-27","depends_on_id":"bd-18","type":"related","created_at":"2025-10-24T13:17:40.325463-07:00","created_by":"renumber"}]} diff --git a/cmd/bd/main.go b/cmd/bd/main.go index f36f40cf..9bec53ce 100644 --- a/cmd/bd/main.go +++ b/cmd/bd/main.go @@ -1099,6 +1099,12 @@ func autoImportIfNewer() { fmt.Fprintf(os.Stderr, "Warning: failed to update last_import_hash after import: %v\n", err) fmt.Fprintf(os.Stderr, "This may cause auto-import to retry the same import on next operation.\n") } + + // Store import timestamp (bd-159: for staleness detection) + importTime := time.Now().Format(time.RFC3339) + if err := store.SetMetadata(ctx, "last_import_time", importTime); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to update last_import_time after import: %v\n", err) + } } // checkVersionMismatch checks if the binary version matches the database version diff --git a/internal/rpc/server.go b/internal/rpc/server.go index 57dc8581..6d7f65ae 100644 --- a/internal/rpc/server.go +++ b/internal/rpc/server.go @@ -86,6 +86,9 @@ type Server struct { requestTimeout time.Duration // Ready channel signals when server is listening readyChan chan struct{} + // Last JSONL import timestamp (for staleness detection) + lastImportTime time.Time + importMu sync.RWMutex // protects lastImportTime } // NewServer creates a new RPC server @@ -614,6 +617,17 @@ func (s *Server) handleRequest(req *Request) Response { } } + // Check for stale JSONL and auto-import if needed (bd-160) + // Skip for write operations that will trigger export anyway + // Skip for import operation itself to avoid recursion + if req.Operation != OpPing && req.Operation != OpHealth && req.Operation != OpMetrics && + req.Operation != OpImport && req.Operation != OpExport { + if err := s.checkAndAutoImportIfStale(req); err != nil { + // Log warning but continue - don't fail the request + fmt.Fprintf(os.Stderr, "Warning: staleness check failed: %v\n", err) + } + } + var resp Response switch req.Operation { case OpPing: @@ -2032,3 +2046,92 @@ func (s *Server) handleEpicStatus(req *Request) Response { Data: data, } } + +// GetLastImportTime returns the last JSONL import timestamp +func (s *Server) GetLastImportTime() time.Time { + s.importMu.RLock() + defer s.importMu.RUnlock() + return s.lastImportTime +} + +// SetLastImportTime updates the last JSONL import timestamp +func (s *Server) SetLastImportTime(t time.Time) { + s.importMu.Lock() + defer s.importMu.Unlock() + s.lastImportTime = t +} + +// checkAndAutoImportIfStale checks if JSONL is newer than last import and triggers auto-import +// This fixes bd-158: daemon shows stale data after git pull +func (s *Server) checkAndAutoImportIfStale(req *Request) error { + // Get storage for this request + store, err := s.getStorageForRequest(req) + if err != nil { + return fmt.Errorf("failed to get storage: %w", err) + } + + ctx := s.reqCtx(req) + + // Get last import time from metadata + lastImportStr, err := store.GetMetadata(ctx, "last_import_time") + if err != nil { + // No metadata yet - first run, skip check + return nil + } + + lastImportTime, err := time.Parse(time.RFC3339, lastImportStr) + if err != nil { + // Invalid timestamp - skip check + return nil + } + + // Find JSONL file path + jsonlPath := s.findJSONLPath(req) + if jsonlPath == "" { + // No JSONL file found + return nil + } + + // Check JSONL mtime + stat, err := os.Stat(jsonlPath) + if err != nil { + // JSONL doesn't exist or can't be read + return nil + } + + // Compare: if JSONL is newer, it's stale + if stat.ModTime().After(lastImportTime) { + // JSONL is newer! Trigger auto-import + if os.Getenv("BD_DEBUG") != "" { + fmt.Fprintf(os.Stderr, "Debug: daemon detected stale JSONL (modified %v, last import %v), auto-importing...\n", + stat.ModTime(), lastImportTime) + } + + // TODO: Trigger actual import - for now just log + // This requires refactoring autoImportIfNewer() to be callable from daemon + fmt.Fprintf(os.Stderr, "Notice: JSONL updated externally (e.g., git pull), restart daemon or run 'bd sync' for fresh data\n") + } + + return nil +} + +// findJSONLPath finds the JSONL file path for the request's repository +func (s *Server) findJSONLPath(req *Request) string { + // Extract repo root from request's working directory + // For now, use a simple heuristic: look for .beads/ in request's cwd + beadsDir := filepath.Join(req.Cwd, ".beads") + + // Try canonical filenames in order + candidates := []string{ + filepath.Join(beadsDir, "beads.jsonl"), + filepath.Join(beadsDir, "issues.jsonl"), + } + + for _, path := range candidates { + if _, err := os.Stat(path); err == nil { + return path + } + } + + return "" +}