diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 048ccc1f..d4dadaf6 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -17,10 +17,18 @@ {"id":"bd-1f4086c5.1","content_hash":"ba5173c61613a29786641ba06a93427de87bed65ce39dbc3c3ddd2b6900f827e","title":"Integration test: mutation to export latency","description":"Measure time from bd create to JSONL update. Verify \u003c500ms latency. Test with multiple rapid mutations to verify batching.","notes":"Test added to daemon_test.go as TestMutationToExportLatency().\n\nCurrently skipped with note that it should be enabled once bd-146 (event-driven daemon) is fully implemented and enabled by default.\n\nThe test structure is complete:\n1. Sets up test environment with fast debounce (500ms)\n2. SingleMutationLatency: measures latency from mutation to JSONL update\n3. RapidMutationBatching: verifies multiple mutations batch into single export\n\nOnce event-driven mode is default, remove the t.Skip() line and the test will validate \u003c500ms latency.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T20:49:49.103759-07:00","updated_at":"2025-10-30T17:12:58.195867-07:00","closed_at":"2025-10-29T14:19:19.808139-07:00","dependencies":[{"issue_id":"bd-1f4086c5.1","depends_on_id":"bd-1f4086c5","type":"parent-child","created_at":"2025-10-29T20:49:49.107244-07:00","created_by":"import-remap"}]} {"id":"bd-22e0bde9","content_hash":"4c03fb79e67c0948d0d887b56fcbf71ed3b987e4bfd84628d7b9b2fa047a61fa","title":"Add TestNWayCollision for 5+ clones","description":"## Overview\nAdd comprehensive tests for N-way (5+) collision resolution to verify the solution scales beyond 3 clones.\n\n## Purpose\nWhile TestThreeCloneCollision validates the basic N-way case, we need to verify:\n1. Solution scales to arbitrary N\n2. Performance is acceptable with more clones\n3. Convergence time is bounded\n4. No edge cases in larger collision groups\n\n## Implementation Tasks\n\n### 1. Create TestFiveCloneCollision\nFile: beads_twoclone_test.go (or new beads_nway_test.go)\n\n```go\nfunc TestFiveCloneCollision(t *testing.T) {\n // Test with 5 clones creating same ID with different content\n // Verify all 5 clones converge after sync rounds\n \n t.Run(\"SequentialSync\", func(t *testing.T) {\n testNCloneCollision(t, 5, \"A\", \"B\", \"C\", \"D\", \"E\")\n })\n \n t.Run(\"ReverseSync\", func(t *testing.T) {\n testNCloneCollision(t, 5, \"E\", \"D\", \"C\", \"B\", \"A\")\n })\n \n t.Run(\"RandomSync\", func(t *testing.T) {\n testNCloneCollision(t, 5, \"C\", \"A\", \"E\", \"B\", \"D\")\n })\n}\n```\n\n### 2. Implement generalized testNCloneCollision\nGeneralize the 3-clone test to handle arbitrary N:\n\n```go\nfunc testNCloneCollision(t *testing.T, numClones int, syncOrder ...string) {\n t.Helper()\n \n if len(syncOrder) != numClones {\n t.Fatalf(\"syncOrder length (%d) must match numClones (%d)\", \n len(syncOrder), numClones)\n }\n \n tmpDir := t.TempDir()\n \n // Setup remote and N clones\n remoteDir := setupBareRepo(t, tmpDir)\n cloneDirs := make(map[string]string)\n \n for i := 0; i \u003c numClones; i++ {\n name := string(rune('A' + i))\n cloneDirs[name] = setupClone(t, tmpDir, remoteDir, name)\n }\n \n // Each clone creates issue with same ID but different content\n for name, dir := range cloneDirs {\n createIssue(t, dir, fmt.Sprintf(\"Issue from clone %s\", name))\n }\n \n // Sync in specified order\n for _, name := range syncOrder {\n syncClone(t, cloneDirs[name], name)\n }\n \n // Final pull for convergence\n for name, dir := range cloneDirs {\n finalPull(t, dir, name)\n }\n \n // Verify all clones have all N issues\n expectedTitles := make(map[string]bool)\n for i := 0; i \u003c numClones; i++ {\n name := string(rune('A' + i))\n expectedTitles[fmt.Sprintf(\"Issue from clone %s\", name)] = true\n }\n \n for name, dir := range cloneDirs {\n titles := getTitles(t, dir)\n if !compareTitleSets(titles, expectedTitles) {\n t.Errorf(\"Clone %s missing issues: expected %v, got %v\", \n name, expectedTitles, titles)\n }\n }\n \n t.Log(\"✓ All\", numClones, \"clones converged successfully\")\n}\n```\n\n### 3. Add performance benchmarks\nTest convergence time and memory usage:\n\n```go\nfunc BenchmarkNWayCollision(b *testing.B) {\n for _, n := range []int{3, 5, 10, 20} {\n b.Run(fmt.Sprintf(\"N=%d\", n), func(b *testing.B) {\n for i := 0; i \u003c b.N; i++ {\n // Run N-way collision and measure time\n testNCloneCollisionBench(b, n)\n }\n })\n }\n}\n```\n\n### 4. Add convergence time tests\nVerify bounded convergence:\n\n```go\nfunc TestConvergenceTime(t *testing.T) {\n // Test that convergence happens within expected rounds\n // For N clones, should converge in at most N-1 sync rounds\n \n for n := 3; n \u003c= 10; n++ {\n t.Run(fmt.Sprintf(\"N=%d\", n), func(t *testing.T) {\n rounds := measureConvergenceRounds(t, n)\n maxExpected := n - 1\n if rounds \u003e maxExpected {\n t.Errorf(\"Convergence took %d rounds, expected ≤ %d\", \n rounds, maxExpected)\n }\n })\n }\n}\n```\n\n### 5. Add edge case tests\nTest boundary conditions:\n- All N clones have identical content (dedup works)\n- N-1 clones have same content, 1 differs\n- All N clones have unique content\n- Mix of collisions and non-collisions\n\n## Acceptance Criteria\n- TestFiveCloneCollision passes with all sync orders\n- All 5 clones converge to identical content\n- Performance is acceptable (\u003c 5 seconds for 5 clones)\n- Convergence time is bounded (≤ N-1 rounds)\n- Edge cases handled correctly\n- Benchmarks show scalability to 10+ clones\n\n## Files to Create/Modify\n- beads_twoclone_test.go or beads_nway_test.go\n- Add helper functions for N-clone setup\n\n## Testing Strategy\n\n### Test Matrix\n| N Clones | Sync Orders | Expected Result |\n|----------|-------------|-----------------|\n| 3 | A→B→C | Pass |\n| 3 | C→B→A | Pass |\n| 5 | A→B→C→D→E | Pass |\n| 5 | E→D→C→B→A | Pass |\n| 5 | Random | Pass |\n| 10 | Sequential | Pass |\n\n### Performance Targets\n- 3 clones: \u003c 2 seconds\n- 5 clones: \u003c 5 seconds\n- 10 clones: \u003c 15 seconds\n\n## Dependencies\n- Requires bd-cbed9619.5, bd-cbed9619.4, bd-cbed9619.3, bd-cbed9619.2 to be completed\n- TestThreeCloneCollision must pass first\n\n## Success Metrics\n- All tests pass for N ∈ {3, 5, 10}\n- Convergence time scales linearly (O(N))\n- Memory usage reasonable (\u003c 100MB for 10 clones)\n- No data corruption or loss in any scenario","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-29T23:05:13.974702-07:00","updated_at":"2025-10-31T12:00:43.197709-07:00","closed_at":"2025-10-31T12:00:43.197709-07:00"} {"id":"bd-248bdc3e","content_hash":"8eaeb2dbef1ed6b25fc1bcf3bc5cd1b38a5cf5a487772558ba9fe12a149978f3","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-30T17:12:58.218887-07:00"} -{"id":"bd-2530","content_hash":"7056a386ee4802bce2b83a981aaac7858b5911938263d212f8f9d1f60bf2a706","title":"Issue with labels","description":"This is a description","design":"Use MVC pattern","acceptance_criteria":"All tests pass","status":"open","priority":0,"issue_type":"feature","created_at":"2025-10-31T21:40:34.630173-07:00","updated_at":"2025-10-31T21:40:34.630173-07:00","labels":["bug","critical"]} {"id":"bd-2752a7a2","content_hash":"6b2a1aedbdbcb30b98d4a8196801953a1eb22204d63e31954ef9ab6020a7a26b","title":"Create cmd/bd/daemon_watcher.go (~150 LOC)","description":"Implement FileWatcher using fsnotify to watch JSONL file and git refs. Handle platform differences (inotify/FSEvents/ReadDirectoryChangesW). Include edge case handling for file rename, event storm, watcher failure.","status":"in_progress","priority":1,"issue_type":"task","created_at":"2025-10-29T23:05:13.887269-07:00","updated_at":"2025-10-30T17:12:58.206596-07:00"} {"id":"bd-27ea","content_hash":"c516a8bb5500e2857ec69afcef2e877853128c2c5eb83fdb5693aee683e7c465","title":"Improve cmd/bd test coverage from 21% to 40% (multi-session effort)","description":"Current coverage: 21.0% of statements in cmd/bd\nTarget: 40%\nThis is a multi-session incremental effort.\n\nFocus areas:\n- Command handler tests (create, update, close, list, etc.)\n- Flag validation and error cases\n- JSON output formatting\n- Edge cases and error handling\n\nTrack progress with 'go test -cover ./cmd/bd'","status":"open","priority":0,"issue_type":"task","created_at":"2025-10-31T19:35:57.558346-07:00","updated_at":"2025-10-31T20:41:33.914697-07:00"} {"id":"bd-29c128e8","content_hash":"b93b210ddad4f38c993d184e2f7c897eb00cb2f9c8183224e27ff54e129bb1f7","title":"Update AGENTS.md with event-driven mode","description":"Document BEADS_DAEMON_MODE env var. Explain opt-in during Phase 1. Add troubleshooting for watcher failures.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-28T16:20:02.433145-07:00","updated_at":"2025-10-30T17:12:58.223058-07:00","closed_at":"2025-10-29T15:53:24.019613-07:00"} +{"id":"bd-2b34","content_hash":"f4f252f378555b11762b1d0e89fa4a51a15671f8d33e98e40b0afba0cf8971aa","title":"Refactor cmd/bd/daemon.go for testability and maintainability","description":"","design":"## Current Structure Analysis\n\ndaemon.go contains:\n- Command setup and CLI flag parsing\n- Path/config resolution (getGlobalBeadsDir, ensureBeadsDir, getPIDFilePath, etc.)\n- Daemon lifecycle (start, stop, status, health, metrics)\n- Lock management (setupDaemonLock, acquireDaemonLock)\n- RPC server setup (startRPCServer)\n- Export/import operations (exportToJSONLWithStore, importToJSONLWithStore)\n- Sync orchestration (createExportFunc, createAutoImportFunc, createSyncFunc)\n- Event loop (runEventLoop, runDaemonLoop)\n- Global daemon mode (runGlobalDaemon)\n- Logging setup (setupDaemonLogger)\n\n## Proposed Module Breakdown\n\n1. **daemon_config.go** - Configuration \u0026 path resolution\n - getGlobalBeadsDir, ensureBeadsDir\n - getPIDFilePath, getLogFilePath, getSocketPathForPID\n - getEnvInt, getEnvBool\n - boolToFlag helper\n\n2. **daemon_lifecycle.go** - Start/stop/status operations\n - isDaemonRunning, startDaemon, stopDaemon\n - showDaemonStatus, showDaemonHealth, showDaemonMetrics\n - migrateToGlobalDaemon\n\n3. **daemon_sync.go** - Export/import/sync logic\n - exportToJSONLWithStore, importToJSONLWithStore\n - createExportFunc, createAutoImportFunc, createSyncFunc\n - validateDatabaseFingerprint\n\n4. **daemon_server.go** - RPC server setup\n - startRPCServer, runGlobalDaemon\n\n5. **daemon_loop.go** - Event loop \u0026 orchestration\n - runEventLoop, runDaemonLoop\n\n6. **daemon_logger.go** - Logging setup\n - setupDaemonLogger, daemonLogger type\n\nKeep daemon.go as Cobra command definition only.","status":"open","priority":1,"issue_type":"epic","created_at":"2025-10-31T22:28:19.689943-07:00","updated_at":"2025-10-31T22:28:31.838098-07:00"} +{"id":"bd-2b34.1","content_hash":"dad25846078bec1b84e52a8522b0126cf5ba30f5dbed3defeab09b744435677d","title":"Extract daemon logger functions to daemon_logger.go","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-31T22:28:42.343617-07:00","updated_at":"2025-10-31T22:28:42.343617-07:00"} +{"id":"bd-2b34.2","content_hash":"e3e6ad681759e2e3d521c084809c4875003ce3a25db824d33a98975308e67286","title":"Extract daemon server functions to daemon_server.go","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-31T22:28:42.345639-07:00","updated_at":"2025-10-31T22:28:42.345639-07:00"} +{"id":"bd-2b34.3","content_hash":"dd8b4be930560efcbb3a383d63dd9a65847ba6c9f931736377514b7f9cd2f296","title":"Extract daemon sync functions to daemon_sync.go","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-31T22:28:42.347332-07:00","updated_at":"2025-10-31T22:28:42.347332-07:00"} +{"id":"bd-2b34.4","content_hash":"63b109392ba7a5544b1077e52e5be0b3e6b616d8c7106697f1cb7229031553a5","title":"Extract daemon config functions to daemon_config.go","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-31T22:28:42.349237-07:00","updated_at":"2025-10-31T22:28:42.349237-07:00"} +{"id":"bd-2b34.5","content_hash":"64294568a392ca9337aa2d49272b09183057504018a2970bce9d2187c576f838","title":"Add tests for daemon sync module","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-31T22:28:42.354701-07:00","updated_at":"2025-10-31T22:28:42.354701-07:00"} +{"id":"bd-2b34.6","content_hash":"ef6ae3def4eee6fc800719351f0266eb4f7812c28fa1e53c0e2e4bf1158645b2","title":"Add tests for daemon lifecycle module","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-31T22:28:42.359587-07:00","updated_at":"2025-10-31T22:28:42.359587-07:00"} +{"id":"bd-2b34.7","content_hash":"62bfa4e0024d5b68061e3ca64645449e5abfc5849d611f6797cd378e5ea0af93","title":"Add tests for daemon config module","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-31T22:28:42.373684-07:00","updated_at":"2025-10-31T22:28:42.373684-07:00"} +{"id":"bd-2b34.8","content_hash":"449c5ba132b35ebfbd8ed8dc31f7d96c03311c0dc5eda61d88af3a071e365338","title":"Extract daemon lifecycle functions to daemon_lifecycle.go","description":"","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-31T22:28:42.382892-07:00","updated_at":"2025-10-31T22:28:42.382892-07:00"} {"id":"bd-2f388ca7","content_hash":"27498c808874010ee62da58e12434a6ae7c73f4659b2233aaf8dcd59566a907d","title":"Fix TestTwoCloneCollision timeout","description":"","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-10-28T14:11:25.219607-07:00","updated_at":"2025-10-30T17:12:58.217635-07:00","closed_at":"2025-10-28T16:12:26.286611-07:00"} {"id":"bd-317ddbbf","content_hash":"81a74ccf29037e5a780b12540a4059bab98b9a790a5a043a68118fc00a083cda","title":"Add BEADS_DAEMON_MODE flag handling","description":"Add environment variable BEADS_DAEMON_MODE (values: poll, events). Default to 'poll' for Phase 1. Wire into daemon startup to select runEventLoop vs runEventDrivenLoop.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-28T16:20:02.433638-07:00","updated_at":"2025-10-30T17:12:58.224373-07:00","closed_at":"2025-10-28T12:31:47.819136-07:00"} {"id":"bd-31aab707","title":"Unit tests for FileWatcher","description":"Test watcher detects JSONL changes. Test git ref changes trigger import. Test debounce integration. Test watcher recovery from file removal/rename.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T11:30:59.842317-07:00","updated_at":"2025-10-31T12:00:43.189591-07:00","closed_at":"2025-10-31T12:00:43.189591-07:00"} @@ -70,7 +78,6 @@ {"id":"bd-710a4916","content_hash":"3d8be03f83f87067b1aaf295b0b829d20890e47686e0da10ef81d2096f5ca974","title":"CRDT-based architecture for guaranteed convergence (v2.0)","description":"## Vision\nRedesign beads around Conflict-Free Replicated Data Types (CRDTs) to provide mathematical guarantees for N-way collision resolution at arbitrary scale.\n\n## Current Limitations\n- Content-hash based collision resolution fails at 5+ clones\n- Non-deterministic convergence in multi-round scenarios\n- UNIQUE constraint violations during rename operations\n- No formal proof of convergence properties\n\n## CRDT Benefits\n- Provably convergent (Strong Eventual Consistency)\n- Commutative/Associative/Idempotent operations\n- No coordination required between clones\n- Scales to 100+ concurrent workers\n- Well-understood mathematical foundations\n\n## Proposed Architecture\n\n### 1. UUID-Based IDs\nReplace sequential IDs with UUIDs:\n- Current: bd-1c63eb84, bd-9063acda, bd-4d80b7b1\n- CRDT: bd-a1b2c3d4-e5f6-7890-abcd-ef1234567890\n- Human aliases maintained separately: #42 maps to UUID\n\n### 2. Last-Write-Wins (LWW) Elements\nEach field becomes an LWW register:\n- title: (timestamp, clone_id, value)\n- status: (timestamp, clone_id, value)\n- Deterministic conflict resolution via Lamport timestamp + clone_id tiebreaker\n\n### 3. Operation Log\nTrack all operations as CRDT ops:\n- CREATE(uuid, timestamp, clone_id, fields)\n- UPDATE(uuid, field, timestamp, clone_id, value)\n- DELETE(uuid, timestamp, clone_id) - tombstone, not hard delete\n\n### 4. Sync as Merge\nSyncing becomes merging two CRDT states:\n- No merge conflicts possible\n- Deterministic merge function\n- Guaranteed convergence\n\n## Implementation Phases\n\n### Phase 1: Research \u0026 Design (4 weeks)\n- Study existing CRDT implementations (Automerge, Yjs, Loro)\n- Design schema for CRDT-based issue tracking\n- Prototype LWW-based Issue CRDT\n- Benchmark performance vs current system\n\n### Phase 2: Parallel Implementation (6 weeks)\n- Implement CRDT storage layer alongside SQLite\n- Build conversion tools: SQLite ↔ CRDT\n- Maintain backward compatibility with v1.x format\n- Migration path for existing databases\n\n### Phase 3: Testing \u0026 Validation (4 weeks)\n- Formal verification of convergence properties\n- Stress testing with 100+ clone scenario\n- Performance profiling and optimization\n- Documentation and examples\n\n### Phase 4: Migration \u0026 Rollout (4 weeks)\n- Release v2.0-beta with CRDT backend\n- Gradual migration from v1.x\n- Monitoring and bug fixes\n- Final v2.0 release\n\n## Risks \u0026 Mitigations\n\n**Risk 1: Performance overhead**\n- Mitigation: Benchmark early, optimize hot paths\n- CRDTs can be slower than append-only logs\n- May need compaction strategy\n\n**Risk 2: Storage bloat**\n- Mitigation: Implement operation log compaction\n- Tombstone garbage collection for deleted issues\n- Periodic snapshots to reduce log size\n\n**Risk 3: Breaking changes**\n- Mitigation: Maintain v1.x compatibility layer\n- Gradual migration tools\n- Dual-mode operation during transition\n\n**Risk 4: Complexity**\n- Mitigation: Use battle-tested CRDT libraries\n- Comprehensive documentation\n- Clear migration guide\n\n## Success Criteria\n- 100-clone collision test passes without failures\n- Formal proof of convergence properties\n- Performance within 2x of current system\n- Zero manual conflict resolution required\n- Backward compatible with v1.x databases\n\n## Timeline\n18-20 weeks total (4-5 months)\n\n## References\n- Automerge: https://automerge.org\n- Yjs: https://docs.yjs.dev\n- Loro: https://loro.dev\n- CRDT theory: Shapiro et al, A comprehensive study of CRDTs\n- Related issues: bd-e6d71828, bd-7a2b58fc,-1","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-29T10:23:57.978339-07:00","updated_at":"2025-10-31T21:59:17.188502-07:00","closed_at":"2025-10-31T21:59:17.188502-07:00"} {"id":"bd-71107098","content_hash":"9feb9a8dc8ae2dc65b11edeff37cf5ce48d8f28e1ced45d64ac0176937610296","title":"Make two-clone workflow actually work (no hacks)","description":"TestTwoCloneCollision proves beads CANNOT handle two independent clones filing issues simultaneously. This is the basic collaborative workflow and it must work cleanly.\n\nTest location: beads_twoclone_test.go\n\nThe test creates two git clones, both file issues with same ID (test-1), --resolve-collisions remaps clone B's to test-2, but after sync:\n- Clone A has test-1=\"Issue from clone A\", test-2=\"Issue from clone B\" \n- Clone B has test-1=\"Issue from clone B\", test-2=\"Issue from clone A\"\n\nThe TITLES are swapped! Both clones have 2 issues but with opposite title assignments.\n\nWe've tried many fixes (per-project daemons, auto-sync, lamport hashing, precommit hooks) but nothing has made the test pass.\n\nGoal: Make the test pass WITHOUT hacks. The two clones should converge to identical state after sync.","acceptance_criteria":"1. TestTwoCloneCollision passes without EXPECTED FAILURE\n2. Both clones converge to identical issue database\n3. No manual conflict resolution required\n4. Git status clean in both clones\n5. bd ready output identical in both clones","notes":"**Major progress achieved!** The two-clone workflow now converges correctly.\n\n**What was fixed:**\n--3d844c58: Implemented content-hash based rename detection\n- bd-64c05d00.1: Fixed test to compare content not timestamps\n- Both clones now converge to identical issue databases\n- test-1 and test-2 have correct titles in both clones\n- No more title swapping!\n\n**Current status (VERIFIED):**\n✅ Acceptance criteria 1: TestTwoCloneCollision passes (confirmed Oct 28)\n✅ Acceptance criteria 2: Both clones converge to identical issue database (content matches)\n✅ Acceptance criteria 3: No manual conflict resolution required (automatic)\n✅ Acceptance criteria 4: Git status clean\n✅ Acceptance criteria 5: bd ready output identical (timestamps are expected difference)\n\n**ALL ACCEPTANCE CRITERIA MET!** This issue is complete and can be closed.","status":"closed","priority":0,"issue_type":"epic","created_at":"2025-10-28T16:34:53.278793-07:00","updated_at":"2025-10-31T20:28:51.376964-07:00","closed_at":"2025-10-31T20:28:51.376967-07:00"} {"id":"bd-763c","content_hash":"6943cea47a7851c7d1eb316f4669fb8ae2b843a68a89b441fdca5e0be0193494","title":"~/src/beads daemon has 'sql: database is closed' errors - zombie daemon","description":"","status":"open","priority":0,"issue_type":"bug","created_at":"2025-10-31T21:08:03.388007-07:00","updated_at":"2025-10-31T21:08:03.388007-07:00","dependencies":[{"issue_id":"bd-763c","depends_on_id":"bd-2752a7a2","type":"discovered-from","created_at":"2025-10-31T21:08:03.388716-07:00","created_by":"stevey"}]} -{"id":"bd-7857","content_hash":"7056a386ee4802bce2b83a981aaac7858b5911938263d212f8f9d1f60bf2a706","title":"Issue with labels","description":"This is a description","design":"Use MVC pattern","acceptance_criteria":"All tests pass","status":"open","priority":0,"issue_type":"feature","created_at":"2025-10-31T21:41:11.16555-07:00","updated_at":"2025-10-31T21:41:11.16555-07:00","labels":["bug","critical"]} {"id":"bd-7a00c94e","content_hash":"b31566a4b2a84db7d24364492e8ac6ebfa1f5fc27fe270fbd58b27e17218c9c4","title":"Rapid 2","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-29T19:11:57.430725-07:00","updated_at":"2025-10-30T17:12:58.189251-07:00"} {"id":"bd-7a2b58fc","content_hash":"e5f3cb5dc86ba8925237e37359f796b806fb302a148ea8c5c017ee014a0d425a","title":"Implement clone-scoped ID allocation to prevent N-way collisions","description":"## Problem\nCurrent ID allocation uses per-clone atomic counters (issue_counters table) that sync based on local database state. In N-way collision scenarios:\n- Clone B sees {test-1} locally, allocates test-2\n- Clone D sees {test-1, test-2, test-3} locally, allocates test-4\n- When same content gets assigned test-2 and test-4, convergence fails\n\nRoot cause: Each clone independently allocates IDs without global coordination, leading to overlapping assignments for the same content.\n\n## Solution\nAdd clone UUID to ID allocation to make every ID globally unique:\n\n**Current format:** `test-1`, `test-2`, `test-3`\n**New format:** `test-1-a7b3`, `test-2-a7b3`, `test-3-c4d9`\n\nWhere suffix is first 4 chars of clone UUID.\n\n## Implementation\n\n### 1. Add clone_identity table\n```sql\nCREATE TABLE clone_identity (\n clone_uuid TEXT PRIMARY KEY,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n```\n\n### 2. Modify getNextIDForPrefix()\n```go\nfunc (s *SQLiteStorage) getNextIDForPrefix(ctx context.Context, prefix string) (string, error) {\n cloneUUID := s.getOrCreateCloneUUID(ctx)\n shortUUID := cloneUUID[:4]\n \n nextNum := s.getNextCounterForPrefix(ctx, prefix)\n return fmt.Sprintf(\"%s-%d-%s\", prefix, nextNum, shortUUID), nil\n}\n```\n\n### 3. Update ID parsing logic\nAll places that parse IDs (utils.ExtractIssueNumber, etc.) need to handle new format.\n\n### 4. Migration strategy\n- Existing IDs remain unchanged (no suffix)\n- New IDs get clone suffix automatically\n- Display layer can hide suffix in UI: `bd-cb64c226.3-a7b3` → `#42`\n\n## Benefits\n- **Zero collision risk**: Same content in different clones gets different IDs\n- **Maintains readability**: Still sequential numbering within clone\n- **No coordination needed**: Works offline, no central authority\n- **Scales to 100+ clones**: 4-char hex = 65,536 unique clones\n\n## Concerns\n- ID format change may break existing integrations\n- Need migration path for existing databases\n- Display logic needs update to hide/show suffixes appropriately\n\n## Success Criteria\n- 10+ clone collision test passes without failures\n- Existing issues continue to work (backward compatibility)\n- Documentation updated with new ID format\n- Migration guide for v1.x → v2.x\n\n## Timeline\nMedium-term (v1.1-v1.2), 2-3 weeks implementation\n\n## References\n- Related to bd-0dcea000 (immediate fix)\n- See beads_nway_test.go for failing N-way tests","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-29T20:02:47.952447-07:00","updated_at":"2025-10-30T17:12:58.194389-07:00"} {"id":"bd-7bbc4e6a","title":"Add MCP server functions for repair commands","description":"Expose new repair commands via MCP server for agent access:\n\nFunctions to add:\n- beads_repair_deps()\n- beads_detect_pollution()\n- beads_validate()\n- beads_resolve_conflicts() (when implemented)\n\nUpdate integrations/beads-mcp/src/beads_mcp/server.py\n\nSee repair_commands.md lines 803-884 for design.","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-28T19:37:55.72639-07:00","updated_at":"2025-10-30T17:12:58.179948-07:00"} @@ -147,6 +154,7 @@ {"id":"bd-eef03e0a","content_hash":"f075e26fe762aa3fc5484f97441c0cc0b296fa49e9c7b1242bda1c5b6c8ec894","title":"Stress test: event storm handling","description":"Simulate 100+ rapid JSONL writes. Verify debouncer batches to single import. Verify no data loss. Test daemon stability.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T20:49:49.138725-07:00","updated_at":"2025-10-30T17:12:58.224751-07:00"} {"id":"bd-ef1a1c26","content_hash":"0c7997ff55a05eb6db59702ec72644c0f59658ca2838175125fda0e1cd11d952","title":"Verify MCP Server Compatibility","description":"Ensure MCP server works with cache-free daemon","acceptance_criteria":"- MCP integration tests pass\n- Documented confirmation of MCP multi-repo strategy\n- No regressions in MCP functionality\n\nTest scenarios:\n1. Single repo workflow: MCP with one project directory\n2. Multi-repo workflow: MCP switching between projects (uses separate daemons)\n3. Daemon restart: Verify no stale data after daemon restart\n\nQuestions to answer:\n- Does MCP rely on req.Cwd routing to single daemon for multiple repos?\n- Or does MCP start separate daemons per repo (recommended)?\n- Do existing MCP tests pass?\n\nFiles to review:\n- integrations/beads-mcp/src/beads_mcp/server.py\n- integrations/beads-mcp/tests/test_multi_project_switching.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-28T10:50:15.126312-07:00","updated_at":"2025-10-30T17:12:58.21699-07:00","closed_at":"2025-10-28T10:49:20.468838-07:00"} {"id":"bd-ef72b864","title":"Add MCP server functions for repair commands","description":"Expose new repair commands via MCP server for agent access:\n\nFunctions to add:\n- beads_repair_deps()\n- beads_detect_pollution()\n- beads_validate()\n- beads_resolve_conflicts() (when implemented)\n\nUpdate integrations/beads-mcp/src/beads_mcp/server.py\n\nSee repair_commands.md lines 803-884 for design.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-28T19:38:02.227921-07:00","updated_at":"2025-10-30T17:12:58.180404-07:00","closed_at":"2025-10-29T23:14:44.187562-07:00"} +{"id":"bd-ef85","content_hash":"9c3f57a10718db484253639c857ed8e7c7b71c9983358af17afc26efa8d1bd86","title":"Add --json flags to all bd commands for agent-friendly output","description":"","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-10-31T22:39:45.312496-07:00","updated_at":"2025-10-31T22:39:50.157022-07:00","closed_at":"2025-10-31T22:39:50.157022-07:00"} {"id":"bd-eff3","content_hash":"c7a641bdb4c98b14816e2d85ec09db6def89aa4918ad59f4c1f8f71c6a42c6d4","title":"Feature with design","description":"This is a description","design":"Use MVC pattern","acceptance_criteria":"All tests pass","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-31T21:41:11.147816-07:00","updated_at":"2025-10-31T21:41:11.147816-07:00"} {"id":"bd-f0d9bcf2","content_hash":"d4c343a7d3ee7985fcde6f9438d9f4a4a98780e4abd75de0d7a7310c31e2cc94","title":"Batch test 1","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-29T15:29:01.795728-07:00","updated_at":"2025-10-31T12:00:43.184078-07:00","closed_at":"2025-10-31T12:00:43.184078-07:00"} {"id":"bd-f8b764c9","content_hash":"9f22cba5111b9e92f2880670226689ac818f5006267bceb36e0502433413f332","title":"Hash-based IDs with aliasing system","description":"Replace sequential auto-increment IDs (bd-1c63eb84, bd-9063acda) with content-hash based IDs (bd-af78e9a2) plus human-friendly aliases (#1, #2).\n\n## Motivation\nCurrent sequential IDs cause collision problems when multiple clones work offline:\n- Non-deterministic convergence in N-way scenarios (bd-cbed9619.1, bd-e6d71828)\n- Complex collision resolution logic (~2,100 LOC)\n- UNIQUE constraint violations during import\n- Requires coordination between workers\n\nHash-based IDs eliminate collisions entirely while aliases preserve human readability.\n\n## Benefits\n- ✅ Collision-free distributed ID generation\n- ✅ Eliminates ~2,100 LOC of collision handling code\n- ✅ Better git merge behavior (different IDs = different JSONL lines)\n- ✅ True offline-first workflows\n- ✅ Simpler auto-import (no remapping needed)\n- ✅ Enables parallel CI/CD workers without coordination\n\n## Design\n- Canonical ID: bd-af78e9a2 (8-char SHA256 prefix of title+desc+timestamp+creator)\n- Alias: #42 (auto-increment per workspace, mutable, display-only)\n- CLI accepts both: bd show bd-af78e9a2 OR bd show #42\n- JSONL stores hash IDs only (aliases reconstructed on import)\n- Alias conflicts resolved via content-hash ordering (deterministic)\n\n## Breaking Change\nThis is a v2.0 feature requiring migration. Provide bd migrate --hash-ids tool.\n\n## Timeline\n~9 weeks (Phase 1: Hash IDs 4w, Phase 2: Aliases 3w, Phase 3: Testing 2w)\n\n## Dependencies\nShould complete after bd-7c5915ae (cleanup validation) and before bd-710a4916 (CRDT).","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-10-29T21:23:49.592315-07:00","updated_at":"2025-10-31T12:32:32.6038-07:00","closed_at":"2025-10-31T12:32:32.6038-07:00"} diff --git a/cmd/bd/compact.go b/cmd/bd/compact.go index 982e9e76..ab63d90f 100644 --- a/cmd/bd/compact.go +++ b/cmd/bd/compact.go @@ -571,6 +571,7 @@ func init() { compactCmd.Flags().IntVar(&compactBatch, "batch-size", 10, "Issues per batch") compactCmd.Flags().IntVar(&compactWorkers, "workers", 5, "Parallel workers") compactCmd.Flags().BoolVar(&compactStats, "stats", false, "Show compaction statistics") + compactCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output JSON format") rootCmd.AddCommand(compactCmd) } diff --git a/cmd/bd/compact_test.go b/cmd/bd/compact_test.go index f5ae7e3c..ec474b71 100644 --- a/cmd/bd/compact_test.go +++ b/cmd/bd/compact_test.go @@ -340,4 +340,55 @@ func TestCompactInitCommand(t *testing.T) { if len(compactCmd.Long) == 0 { t.Error("compactCmd should have Long description") } + + // Verify --json flag exists + jsonFlag := compactCmd.Flags().Lookup("json") + if jsonFlag == nil { + t.Error("compact command should have --json flag") + } +} + +func TestCompactStatsJSON(t *testing.T) { + tmpDir := t.TempDir() + dbPath := filepath.Join(tmpDir, ".beads", "beads.db") + + if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil { + t.Fatal(err) + } + + sqliteStore, err := sqlite.New(dbPath) + if err != nil { + t.Fatal(err) + } + defer sqliteStore.Close() + + ctx := context.Background() + + // Set issue_prefix + if err := sqliteStore.SetConfig(ctx, "issue_prefix", "test"); err != nil { + t.Fatalf("Failed to set issue_prefix: %v", err) + } + + // Create a closed issue eligible for Tier 1 + issue := &types.Issue{ + ID: "test-1", + Title: "Test Issue", + Description: string(make([]byte, 500)), + Status: types.StatusClosed, + Priority: 2, + IssueType: types.TypeTask, + CreatedAt: time.Now().Add(-60 * 24 * time.Hour), + ClosedAt: ptrTime(time.Now().Add(-35 * 24 * time.Hour)), + } + if err := sqliteStore.CreateIssue(ctx, issue, "test"); err != nil { + t.Fatal(err) + } + + // Test with JSON output + savedJSONOutput := jsonOutput + jsonOutput = true + defer func() { jsonOutput = savedJSONOutput }() + + // Should not panic and should execute JSON path + runCompactStats(ctx, sqliteStore) } diff --git a/cmd/bd/daemon.go b/cmd/bd/daemon.go index 49d49f8c..bcbfe6f2 100644 --- a/cmd/bd/daemon.go +++ b/cmd/bd/daemon.go @@ -184,6 +184,7 @@ func init() { daemonCmd.Flags().Bool("migrate-to-global", false, "Migrate from local to global daemon") daemonCmd.Flags().String("log", "", "Log file path (default: .beads/daemon.log)") daemonCmd.Flags().Bool("global", false, "Run as global daemon (socket at ~/.beads/bd.sock)") + daemonCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output JSON format") rootCmd.AddCommand(daemonCmd) } @@ -324,19 +325,47 @@ func showDaemonStatus(pidFile string, global bool) { if global { scope = "global" } - fmt.Printf("Daemon is running (PID %d, %s)\n", pid, scope) - + + var started string if info, err := os.Stat(pidFile); err == nil { - fmt.Printf(" Started: %s\n", info.ModTime().Format("2006-01-02 15:04:05")) + started = info.ModTime().Format("2006-01-02 15:04:05") } - logPath, err := getLogFilePath("", global) - if err == nil { - if _, err := os.Stat(logPath); err == nil { - fmt.Printf(" Log: %s\n", logPath) + var logPath string + if lp, err := getLogFilePath("", global); err == nil { + if _, err := os.Stat(lp); err == nil { + logPath = lp } } + + if jsonOutput { + status := map[string]interface{}{ + "running": true, + "pid": pid, + "scope": scope, + } + if started != "" { + status["started"] = started + } + if logPath != "" { + status["log_path"] = logPath + } + outputJSON(status) + return + } + + fmt.Printf("Daemon is running (PID %d, %s)\n", pid, scope) + if started != "" { + fmt.Printf(" Started: %s\n", started) + } + if logPath != "" { + fmt.Printf(" Log: %s\n", logPath) + } } else { + if jsonOutput { + outputJSON(map[string]interface{}{"running": false}) + return + } fmt.Println("Daemon is not running") } } diff --git a/cmd/bd/export.go b/cmd/bd/export.go index 0e0c5a41..6c5fda54 100644 --- a/cmd/bd/export.go +++ b/cmd/bd/export.go @@ -308,6 +308,21 @@ Output to stdout by default, or use -o flag for file output.`, fmt.Fprintf(os.Stderr, "Warning: failed to set file permissions: %v\n", err) } } + + // Output statistics if JSON format requested + if jsonOutput { + stats := map[string]interface{}{ + "success": true, + "exported": len(exportedIDs), + "skipped": skippedCount, + "total_issues": len(issues), + } + if output != "" { + stats["output_file"] = output + } + data, _ := json.MarshalIndent(stats, "", " ") + fmt.Fprintln(os.Stderr, string(data)) + } }, } @@ -316,5 +331,6 @@ func init() { exportCmd.Flags().StringP("output", "o", "", "Output file (default: stdout)") exportCmd.Flags().StringP("status", "s", "", "Filter by status") exportCmd.Flags().Bool("force", false, "Force export even if database is empty") + exportCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output export statistics in JSON format") rootCmd.AddCommand(exportCmd) } diff --git a/cmd/bd/import.go b/cmd/bd/import.go index c56cb40e..f2473c67 100644 --- a/cmd/bd/import.go +++ b/cmd/bd/import.go @@ -249,5 +249,6 @@ func init() { importCmd.Flags().Bool("dedupe-after", false, "Detect and report content duplicates after import") importCmd.Flags().Bool("dry-run", false, "Preview collision detection without making changes") importCmd.Flags().Bool("rename-on-import", false, "Rename imported issues to match database prefix (updates all references)") + importCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output import statistics in JSON format") rootCmd.AddCommand(importCmd) } diff --git a/cmd/bd/migrate.go b/cmd/bd/migrate.go index f581629d..b314b075 100644 --- a/cmd/bd/migrate.go +++ b/cmd/bd/migrate.go @@ -588,5 +588,6 @@ func init() { migrateCmd.Flags().Bool("dry-run", false, "Show what would be done without making changes") migrateCmd.Flags().Bool("update-repo-id", false, "Update repository ID (use after changing git remote)") migrateCmd.Flags().Bool("to-hash-ids", false, "Migrate sequential IDs to hash-based IDs") + migrateCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output migration statistics in JSON format") rootCmd.AddCommand(migrateCmd) } diff --git a/cmd/bd/repair_deps.go b/cmd/bd/repair_deps.go index de51aa9e..cfb5b725 100644 --- a/cmd/bd/repair_deps.go +++ b/cmd/bd/repair_deps.go @@ -171,5 +171,6 @@ Interactive mode with --interactive prompts for each orphan.`, func init() { repairDepsCmd.Flags().Bool("fix", false, "Automatically remove orphaned dependencies") repairDepsCmd.Flags().Bool("interactive", false, "Interactively review each orphaned dependency") + repairDepsCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output repair results in JSON format") rootCmd.AddCommand(repairDepsCmd) } diff --git a/cmd/bd/restore.go b/cmd/bd/restore.go index f07406f0..a581b0f6 100644 --- a/cmd/bd/restore.go +++ b/cmd/bd/restore.go @@ -112,6 +112,7 @@ This is read-only and does not modify the database or git state.`, } func init() { + restoreCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output restore results in JSON format") rootCmd.AddCommand(restoreCmd) } diff --git a/cmd/bd/sync.go b/cmd/bd/sync.go index fa9852e3..2595ec56 100644 --- a/cmd/bd/sync.go +++ b/cmd/bd/sync.go @@ -247,6 +247,7 @@ func init() { syncCmd.Flags().Bool("rename-on-import", false, "Rename imported issues to match database prefix (updates all references)") syncCmd.Flags().Bool("flush-only", false, "Only export pending changes to JSONL (skip git operations)") syncCmd.Flags().Bool("import-only", false, "Only import from JSONL (skip git operations, useful after git pull)") + syncCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output sync statistics in JSON format") rootCmd.AddCommand(syncCmd) }