diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 6e5739a2..39a80e53 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -1,6 +1,6 @@ {"id":"bd-0088","content_hash":"7449088a4560a2a2821eeda8dca1e44c0017667314236a13df8d8112cda20101","title":"Create npm package structure for bd-wasm","description":"Set up npm package for distribution:\n- Create package.json with bd-wasm name\n- Bundle bd.wasm + wasm_exec.js\n- Create CLI wrapper (bin/bd) that invokes WASM\n- Add installation scripts if needed\n- Configure package for Claude Code Web sandbox compatibility","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-02T21:58:07.295058-08:00","updated_at":"2025-11-03T20:56:22.700641-08:00","closed_at":"2025-11-03T20:56:22.700641-08:00","dependencies":[{"issue_id":"bd-0088","depends_on_id":"bd-44d0","type":"parent-child","created_at":"2025-11-02T22:23:49.475356-08:00","created_by":"stevey"}]} {"id":"bd-0134cc5a","content_hash":"d45c0e44c01c5855f14f07693bd800f4bfeac3084e10ceb17970ff54c58f6a40","title":"Fix auto-import creating duplicates instead of updating issues","description":"ROOT CAUSE: server_export_import_auto.go line 221 uses ResolveCollisions: true for ALL auto-imports. This is wrong.\n\nProblem:\n- ResolveCollisions is for branch merges (different issues with same ID)\n- Auto-import should UPDATE existing issues, not create duplicates\n- Every git pull creates NEW duplicate issues with different IDs\n- Two agents ping-pong creating endless duplicates\n\nEvidence:\n- 31 duplicate groups found (bd duplicates)\n- bd-236-246 are duplicates of bd-224-235\n- Both agents keep pulling and creating more duplicates\n- JSONL file grows endlessly with duplicates\n\nThe Fix:\nChange checkAndAutoImportIfStale in server_export_import_auto.go:\n- Remove ResolveCollisions: true (line 221)\n- Use normal import logic that updates existing issues by ID\n- Only use ResolveCollisions for explicit bd import --resolve-collisions\n\nImpact: Critical - makes beads unusable for multi-agent workflows","acceptance_criteria":"- Auto-import does NOT create duplicates when pulling git changes\n- Existing issues are updated in-place by ID match\n- No ping-pong commits between agents\n- Test: two agents updating same issue should NOT create duplicates\n- bd duplicates shows 0 groups after fix","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-10-27T21:48:57.733846-07:00","updated_at":"2025-10-30T17:12:58.21084-07:00","closed_at":"2025-10-27T22:26:40.627239-07:00"} -{"id":"bd-02a4","content_hash":"574c56e9380839d97c691ab8b844f9632a034dd3b0982166cf9234b1d5c3b68b","title":"Modify CreateIssue to support parent resurrection","description":"Update internal/storage/sqlite/sqlite.go:182-196 to call TryResurrectParent before failing on missing parent. Coordinate with EnsureIDs changes for consistent behavior. Handle edge case where parent never existed in JSONL (fail gracefully).","status":"open","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:59.701571-08:00","updated_at":"2025-11-04T12:31:59.701571-08:00"} +{"id":"bd-02a4","content_hash":"574c56e9380839d97c691ab8b844f9632a034dd3b0982166cf9234b1d5c3b68b","title":"Modify CreateIssue to support parent resurrection","description":"Update internal/storage/sqlite/sqlite.go:182-196 to call TryResurrectParent before failing on missing parent. Coordinate with EnsureIDs changes for consistent behavior. Handle edge case where parent never existed in JSONL (fail gracefully).","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:59.701571-08:00","updated_at":"2025-11-04T17:10:48.920499-08:00","closed_at":"2025-11-04T17:10:48.920499-08:00"} {"id":"bd-0447029c","content_hash":"f32f7d8f0b07aaaeb9d07d8a1d000eef8fc79cf864e8aa20ebb899f6e359ebda","title":"bd find-duplicates - AI-powered duplicate detection","description":"Find semantically duplicate issues.\n\nApproaches:\n1. Mechanical: Exact title/description matching\n2. Embeddings: Cosine similarity (cheap, scalable)\n3. AI: LLM-based semantic comparison (expensive, accurate)\n\nUses embeddings by default for \u003e100 issues.\n\nFiles: cmd/bd/find_duplicates.go (new)","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T16:43:28.182327-07:00","updated_at":"2025-10-30T17:12:58.188016-07:00","closed_at":"2025-10-29T16:15:10.64719-07:00"} {"id":"bd-0458","content_hash":"c4427da2aec84621525f7f286c626f6c94365a7e6ff8e35e9676b184c85e1adb","title":"Consolidate export/import/commit/push into sync.go","description":"Create internal/daemonrunner/sync.go with Syncer type. Add ExportOnce, ImportOnce, CommitAndMaybePush methods. Replace createExportFunc/createAutoImportFunc with thin closures calling Syncer.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-01T11:41:14.874539-07:00","updated_at":"2025-11-02T12:32:00.157369-08:00","closed_at":"2025-11-02T12:32:00.157375-08:00"} {"id":"bd-05a1","content_hash":"b79b0efa41b4eca8d7e5ab9738d5ecaa403c35497877a056a502efe0583ca251","title":"Isolate RPC server startup into rpc_server.go","description":"Create internal/daemonrunner/rpc_server.go with StartRPC function. Move startRPCServer logic here and return typed handle.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-01T11:41:14.876839-07:00","updated_at":"2025-11-02T12:32:00.158054-08:00","closed_at":"2025-11-02T12:32:00.158057-08:00"} @@ -62,7 +62,7 @@ {"id":"bd-31aab707","content_hash":"8f64a8dbcc5ed63bc73b7d91fca624527033265dc1c89a7775eb2f45b378f382","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"} {"id":"bd-325da116","content_hash":"d7c5637527778c5c835f5e4b6e15fbd51a3476d6749ab3155b8aeac08a8ef339","title":"Fix N-way collision convergence","description":"Epic to fix the N-way collision convergence problem documented in n-way-collision-convergence.md.\n\n## Problem Summary\nThe current collision resolution implementation works correctly for 2-way collisions but does not converge for 3-way (and by extension N-way) collisions. TestThreeCloneCollision demonstrates this with reproducible failures.\n\n## Root Causes Identified\n1. Pairwise resolution doesn't scale - each clone makes local decisions without global context\n2. DetectCollisions modifies state during detection (line 83-86 in collision.go)\n3. No remapping history - can't track transitive remap chains (test-1 → test-2 → test-3)\n4. Import-time resolution is too late - happens after git merge\n\n## Solution Architecture\nReplace pairwise resolution with deterministic global N-way resolution using:\n- Content-addressable identity (content hashing)\n- Global collision resolution (sort all versions by hash)\n- Read-only detection phase (separate from modification)\n- Idempotent imports (content-first matching)\n\n## Success Criteria\n- TestThreeCloneCollision passes without skipping\n- All clones converge to identical content after final pull\n- No data loss (all issues present in all clones)\n- Works for N workers (test with 5+ clones)\n- Idempotent imports (importing same JSONL multiple times is safe)\n\n## Implementation Phases\nSee child issues for detailed breakdown of each phase.","status":"closed","priority":0,"issue_type":"epic","created_at":"2025-10-29T23:05:13.889079-07:00","updated_at":"2025-10-31T11:59:41.031668-07:00","closed_at":"2025-10-31T11:59:41.031668-07:00"} {"id":"bd-3396","content_hash":"43addfac9a43239dd75e1292a6502a79479cb09e67ff5d6823cc3df1b73390bf","title":"Add merge helper commands (bd sync --merge)","description":"Add commands to merge beads branch back to main.\n\nTasks:\n- Implement bd sync --merge command\n- Implement bd sync --status command\n- Implement bd sync --auto-merge (optional, for automation)\n- Detect merge conflicts and provide guidance\n- Show commit diff between branches\n- Verify main branch is clean before merge\n- Push merged changes to remote\n\nEstimated effort: 2-3 days","acceptance_criteria":"- bd sync --merge successfully merges beads branch\n- Conflicts detected with helpful error message\n- bd sync --status shows clear diff\n- Works with protected main (user must have push access)\n- Git history is clean (no unnecessary merge commits)","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-02T15:22:35.580873-08:00","updated_at":"2025-11-02T17:12:34.620481-08:00","closed_at":"2025-11-02T17:12:34.620486-08:00","dependencies":[{"issue_id":"bd-3396","depends_on_id":"bd-a101","type":"parent-child","created_at":"2025-11-02T15:22:48.376916-08:00","created_by":"stevey"}]} -{"id":"bd-3433","content_hash":"bbe4dc610fa35560d1b9bcfff3450f7e712235f3f34e3bb4de2272d66c83382f","title":"Implement topological sort for import ordering","description":"Refactor upsertIssues() to sort issues by hierarchy depth before batch creation. Ensures parents are created before children, fixing latent bug where parent-child pairs in same batch can fail if ordered wrong. Sort by dot count, create in depth-order batches (0→1→2→3).","status":"open","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:42.22005-08:00","updated_at":"2025-11-04T12:31:42.22005-08:00"} +{"id":"bd-3433","content_hash":"bbe4dc610fa35560d1b9bcfff3450f7e712235f3f34e3bb4de2272d66c83382f","title":"Implement topological sort for import ordering","description":"Refactor upsertIssues() to sort issues by hierarchy depth before batch creation. Ensures parents are created before children, fixing latent bug where parent-child pairs in same batch can fail if ordered wrong. Sort by dot count, create in depth-order batches (0→1→2→3).","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:42.22005-08:00","updated_at":"2025-11-04T17:11:40.564403-08:00","closed_at":"2025-11-04T17:11:40.564403-08:00"} {"id":"bd-35c7","content_hash":"28e00b560e08ecbf061e998836f8a1dd11392680b273589341c13e6b267df37c","title":"Add label-based filtering to bd ready command","description":"Allow filtering ready work by labels to help organize work by sprint, week, or category.\n\nExample usage:\n bd ready --label week1-2\n bd ready --label frontend,high-priority\n\nThis helps teams organize work into batches and makes it easier for agents to focus on specific categories of work.\n\nImplementation notes:\n- Add --label flag to ready command\n- Support comma-separated labels (AND logic)\n- Should work with existing ready work logic (unblocked issues)","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-11-03T18:10:18.976536-08:00","updated_at":"2025-11-03T22:27:30.614911-08:00","closed_at":"2025-11-03T22:27:30.614911-08:00"} {"id":"bd-36320a04","content_hash":"883eb385fa9eded3826008fa6db3b842cabb2ce0e93a23293449f65024303fb7","title":"Add mutation channel to internal/rpc/server.go","description":"Add mutationChan chan MutationEvent to Server struct. Emit events on CreateIssue, UpdateIssue, DeleteIssue, AddComment. Non-blocking send with default case for full channel.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T19:42:29.860173-07:00","updated_at":"2025-10-31T18:31:27.928693-07:00","closed_at":"2025-10-31T18:31:27.928693-07:00"} {"id":"bd-363f","content_hash":"43a1be810b08059684d6745244ca60512616c1a3f7ba9a781be05c5357761b5d","title":"Document bd-wasm installation and usage","description":"Create documentation for bd-wasm:\n- Update README with npm installation instructions\n- Add troubleshooting section for WASM-specific issues\n- Document known limitations vs native bd\n- Add examples for Claude Code Web sandbox usage\n- Update INSTALLING.md with bd-wasm option","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-02T21:58:07.305711-08:00","updated_at":"2025-11-02T21:58:07.305711-08:00","dependencies":[{"issue_id":"bd-363f","depends_on_id":"bd-44d0","type":"parent-child","created_at":"2025-11-02T22:23:49.530675-08:00","created_by":"stevey"}]} @@ -101,6 +101,7 @@ {"id":"bd-5599","content_hash":"c48839a6f7f5ca4083ced2f0f47cd250046146032555a14864ac3469a42bb76b","title":"Fix TestListCommand duplicate dependency constraint violation","description":"","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-10-31T21:27:05.557548-07:00","updated_at":"2025-10-31T21:27:11.429018-07:00","closed_at":"2025-10-31T21:27:11.429018-07:00"} {"id":"bd-581b80b3","content_hash":"04c4d952852ae2673e551d9776698c52b0189754ac5f9ca295bed464a5b86a43","title":"bd find-duplicates - AI-powered duplicate detection","description":"Find semantically duplicate issues.\n\nApproaches:\n1. Mechanical: Exact title/description matching\n2. Embeddings: Cosine similarity (cheap, scalable)\n3. AI: LLM-based semantic comparison (expensive, accurate)\n\nUses embeddings by default for \u003e100 issues.\n\nFiles: cmd/bd/find_duplicates.go (new)","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T20:49:49.126801-07:00","updated_at":"2025-10-30T17:12:58.218673-07:00"} {"id":"bd-589c7c1e","content_hash":"efbc1fe1379d414d2af33f5aff9787e4f8a3234922199bdc9abce25dba99aef0","title":"Fix revive style issues (78 issues)","description":"Style violations: unused parameters (many cmd/args in cobra commands), missing exported comments, stuttering names (SQLiteStorage), indent-error-flow issues.","design":"Rename unused params to _, add godoc comments to exported types, fix stuttering names, simplify control flow.","notes":"Fixed 19 revive issues:\n- 14 unused-parameter (renamed to _)\n- 2 redefines-builtin-id (max→maxCount, min→minInt)\n- 3 indent-error-flow (gofmt fixed 2, skipped 1 complex nested one)\n\nRemaining issues are acceptable: 11 unused-params in deeper code, 2 empty-blocks with comments, 1 complex indent case, 1 superfluous-else in test.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-27T23:20:10.391821-07:00","updated_at":"2025-10-30T17:12:58.215077-07:00","closed_at":"2025-10-27T23:02:41.30653-07:00"} +{"id":"bd-58c0","content_hash":"838921a43598c1700ba05349637a5c69f4ea9427b61623f490c6284628afa89c","title":"Fix transaction conflict in TryResurrectParent","description":"Integration test TestImportWithDeletedParent fails with 'database is locked' error when resurrection happens inside CreateIssue.\n\nRoot cause: TryResurrectParent calls conn.Get() and insertIssue() which conflicts with existing transaction in CreateIssue.\n\nError: failed to create tombstone for parent bd-parent: failed to insert issue: sqlite3: database is locked\n\nSolution: Refactor resurrection to accept optional transaction parameter, use existing transaction when available instead of creating new connection.\n\nImpact: Blocks resurrection from working in CreateIssue flow, only works in EnsureIDs (which may not have active transaction).","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-11-04T16:32:20.981027-08:00","updated_at":"2025-11-04T17:00:44.258881-08:00","closed_at":"2025-11-04T17:00:44.258881-08:00","dependencies":[{"issue_id":"bd-58c0","depends_on_id":"bd-d19a","type":"discovered-from","created_at":"2025-11-04T16:32:20.981969-08:00","created_by":"daemon"}]} {"id":"bd-5a90","content_hash":"819c14b3bb55fcd113b4e848e4bfcb0c3475756658575dba8d34922ca8e14077","title":"Test parent issue","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-11-02T11:50:35.85367-08:00","updated_at":"2025-11-02T11:50:35.85367-08:00"} {"id":"bd-5aad5a9c","content_hash":"03aa8900fd0e6f4bd911364bb69d4446f239c6a5e9688f0fc4928f3de1259242","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-dcd6f14b 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-29T19:52:05.462747-07:00","updated_at":"2025-10-31T12:00:43.198413-07:00","closed_at":"2025-10-31T12:00:43.198413-07:00"} {"id":"bd-5b40a0bf","content_hash":"fd8a94f744b6504f677cc8e196c5a8d8b5d13b230200add5a8d9b7a54a537eb9","title":"Batch test 5","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-29T15:29:02.136118-07:00","updated_at":"2025-10-31T12:00:43.181513-07:00","closed_at":"2025-10-31T12:00:43.181513-07:00"} @@ -173,7 +174,7 @@ {"id":"bd-89e2","content_hash":"ddf4626e586440f379ff19872eac29941cecb925d0a4aae8a6f9c08c969ca05d","title":"Daemon race condition: stale export overwrites recent DB changes","description":"**Symptom:**\nMerged bd-fc2d into bd-fb05 in ~/src/beads (commit ce4d756), pushed to remote. The ~/src/fred/beads daemon then exported its stale DB state and committed (8cc1bb4), reverting bd-fc2d back to \"open\" status.\n\n**Timeline:**\n1. 21:45:12 - Merge committed from ~/src/beads (ce4d756): bd-fc2d closed\n2. 21:49:42 - Daemon in ~/src/fred/beads exported stale state (8cc1bb4): bd-fc2d open again\n\n**Root cause:**\nThe fred/beads daemon had a stale database (bd-fc2d still open) and didn't auto-import the newer JSONL before exporting. When it exported, it overwrote the merge with its stale state.\n\n**Expected behavior:**\nDaemon should detect that JSONL is newer than its last export and import before exporting.\n\n**Actual behavior:**\nDaemon exported stale DB state, creating a conflicting commit that reverted upstream changes.\n\n**Impact:**\nMulti-workspace setups with daemons can silently lose changes if one daemon has stale state and exports.","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-11-01T21:53:07.930819-07:00","updated_at":"2025-11-01T22:01:25.54126-07:00","closed_at":"2025-11-01T22:01:25.54126-07:00"} {"id":"bd-89f89fc0","content_hash":"235c3bdeb45e3069167f81e7b4e798fc98547478bb16df40556100478c5e505a","title":"Remove unreachable RPC methods","description":"Several RPC server and client methods are unreachable and should be removed:\n\nServer methods (internal/rpc/server.go):\n- `Server.GetLastImportTime` (line 2116)\n- `Server.SetLastImportTime` (line 2123)\n- `Server.findJSONLPath` (line 2255)\n\nClient methods (internal/rpc/client.go):\n- `Client.Import` (line 311) - RPC import not used (daemon uses autoimport)\n\nEvidence:\n```bash\ngo run golang.org/x/tools/cmd/deadcode@latest -test ./...\n```\n\nImpact: Removes ~80 LOC of unused RPC code","acceptance_criteria":"- Remove the 4 unreachable methods (~80 LOC total)\n- Verify no callers: `grep -r \"GetLastImportTime\\|SetLastImportTime\\|findJSONLPath\" .`\n- All tests pass: `go test ./internal/rpc/...`\n- Daemon functionality works: test daemon start/stop/operations","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-28T16:20:02.432202-07:00","updated_at":"2025-10-30T17:12:58.222655-07:00"} {"id":"bd-8a39","content_hash":"cf11bd12f8906b73236f46998076d6111d69f05e76198e9823a8f10f3e03112b","title":"Fix Windows-specific test failures in CI","description":"Several tests are failing on Windows but passing on Linux:\n\n**Failing tests:**\n- TestFindDatabasePathEnvVar\n- TestHashIDs_MultiCloneConverge\n- TestHashIDs_IdenticalContentDedup\n- TestDatabaseReinitialization (all 5 subtests):\n - fresh_clone_auto_import\n - database_removal_scenario\n - legacy_filename_support\n - precedence_test\n - init_safety_check\n- TestFindBeadsDir_NotFound\n- TestMetricsSnapshot/uptime (in internal/rpc)\n\n**CI Run:** https://github.com/steveyegge/beads/actions/runs/19015638968\n\nThese are likely path separator or filesystem behavior differences between Windows and Linux.","notes":"Fixed all Windows path issues:\n1. TestFindDatabasePathEnvVar - expects canonicalized paths ✅\n2. TestHashIDs tests - use platform-specific bd.exe command ✅ \n3. TestMetricsSnapshot/uptime - enforce minimum 1 second uptime ✅\n4. TestFindBeadsDir_NotFound - allow finding .beads in parent dirs ✅\n5. TestDatabaseReinitialization - fix git path conversion on Windows (git returns /c/Users/... but filepath needs C:\\Users\\...) ✅\n\nCI run in progress to verify all fixes.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-02T09:29:37.274103-08:00","updated_at":"2025-11-02T12:32:00.158713-08:00","closed_at":"2025-11-02T12:32:00.158716-08:00","dependencies":[{"issue_id":"bd-8a39","depends_on_id":"bd-1231","type":"blocks","created_at":"2025-11-02T09:29:37.276579-08:00","created_by":"stevey"}]} -{"id":"bd-8b65","content_hash":"d3c3bae56822a4be293f7597de1e20d30e06407e9f031ef61f2a10d8d36b7c92","title":"Add depth-based batch creation in upsertIssues","description":"Replace single batch creation with depth-level batching (max depth 3). Create issues at depth 0, then 1, then 2, then 3. Prevents parent validation errors when importing hierarchical issues in same batch. File: internal/importer/importer.go:534-546","status":"open","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:42.267746-08:00","updated_at":"2025-11-04T12:31:42.267746-08:00"} +{"id":"bd-8b65","content_hash":"d3c3bae56822a4be293f7597de1e20d30e06407e9f031ef61f2a10d8d36b7c92","title":"Add depth-based batch creation in upsertIssues","description":"Replace single batch creation with depth-level batching (max depth 3). Create issues at depth 0, then 1, then 2, then 3. Prevents parent validation errors when importing hierarchical issues in same batch. File: internal/importer/importer.go:534-546","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:42.267746-08:00","updated_at":"2025-11-04T17:11:40.564789-08:00","closed_at":"2025-11-04T17:11:40.564789-08:00"} {"id":"bd-8hf","content_hash":"c50faf4690b24606603a10f49f898717f2f368fe656afa70180b8f4738ea943c","title":"Auto-routing and maintainer detection","description":"Implement intelligent routing to automatically send new issues to correct repo based on user's maintainer vs contributor status, with discovered issues inheriting parent's source_repo.","design":"Features:\n- Detect maintainer vs contributor (git config, permissions)\n- Config-based routing rules (no schema changes)\n- Auto-route 'bd add' to primary or planning repo\n- Discovered issues inherit parent's source_repo\n- Explicit override via --repo flag\n\nConfig schema:\n[routing]\nmode = \"auto\" # auto | explicit\ndefault = \"~/.beads-planning\"\n[routing.auto]\nmaintainer = \".\"\ncontributor = \"~/.beads-planning\"","acceptance_criteria":"1. Auto-detect maintainer vs contributor status\n2. Route bd add to correct repo automatically\n3. Discovered issues inherit parent's source_repo\n4. --repo flag overrides auto-routing\n5. Config allows explicit routing rules\n6. Works without breaking single-repo workflows","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-04T11:21:50.961196-08:00","updated_at":"2025-11-04T11:21:50.961196-08:00","dependencies":[{"issue_id":"bd-8hf","depends_on_id":"bd-4ms","type":"parent-child","created_at":"2025-11-04T11:22:24.262815-08:00","created_by":"daemon"}]} {"id":"bd-8rd","content_hash":"35b240c77c0960acd2810a0ace985b85cdde4d16a2c4af7192a9526434143fa9","title":"Migration and onboarding for multi-repo","description":"Create migration tools, wizards, and documentation to help users adopt multi-repo workflow, with special focus on OSS contributor onboarding and team adoption scenarios.","design":"Components:\n- Migration guide documentation\n- bd migrate command to move issues between repos\n- bd init --contributor wizard for OSS setup\n- bd init --team wizard for team setup\n- Auto-detect scenarios and prompt users\n- Examples for common workflows (OSS, multi-phase, personas)\n\nScenarios:\n1. OSS contributor (fork workflow)\n2. Team member (branch workflow)\n3. Multi-phase development\n4. Multiple personas (architect/implementer)","acceptance_criteria":"1. Migration guide covers all scenarios\n2. bd migrate moves issues with filtering\n3. Init wizards guide common setups\n4. Examples demonstrate key workflows\n5. Auto-detection prompts users when appropriate\n6. Docs updated with multi-repo patterns\n7. Backward compatibility clearly documented","status":"open","priority":2,"issue_type":"epic","created_at":"2025-11-04T11:22:13.491033-08:00","updated_at":"2025-11-04T11:22:13.491033-08:00","dependencies":[{"issue_id":"bd-8rd","depends_on_id":"bd-4ms","type":"parent-child","created_at":"2025-11-04T11:22:21.858002-08:00","created_by":"daemon"}]} {"id":"bd-9063acda","content_hash":"0ea4606188e376705c46a14e5d64da1b706aad47a39054a732c21330db601960","title":"Clean up linter errors (914 total issues)","description":"The codebase has 914 linter issues reported by golangci-lint. While many are documented as baseline in LINTING.md, we should clean these up systematically to improve code quality and maintainability.","design":"Break down by linter category, prioritizing high-impact issues:\n1. dupl (7) - Code duplication\n2. goconst (12) - Repeated strings\n3. gocyclo (11) - High complexity functions\n4. revive (78) - Style issues\n5. gosec (102) - Security warnings\n6. errcheck (683) - Unchecked errors (many in tests)","acceptance_criteria":"All linter categories reduced to acceptable levels, with remaining baseline documented in LINTING.md","notes":"Reduced from 56 to 41 issues locally, then to 0 issues.\n\n**Fixed in commits:**\n- c2c7eda: Fixed 15 actual errors (dupl, gosec, revive, staticcheck, unparam)\n- 963181d: Configured exclusions to get to 0 issues locally\n\n**Current status:**\n- ✅ Local: golangci-lint reports 0 issues\n- ❌ CI: Still failing (see [deleted:bd-cb64c226.1])\n\n**Problem:**\nConfig v2 format or golangci-lint-action@v8 compatibility issue causing CI to fail despite local success.\n\n**Next:** Debug [deleted:bd-cb64c226.1] to fix CI/local discrepancy","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-10-24T01:01:12.997982-07:00","updated_at":"2025-11-04T11:10:23.532431-08:00","closed_at":"2025-11-04T11:10:23.532433-08:00"} @@ -242,19 +243,19 @@ {"id":"bd-cbed9619.4","content_hash":"49aad5fa2497f7f88fb74d54553825b93c1021ed7db04cfb2e58682699d8dca9","title":"Make DetectCollisions read-only (separate detection from modification)","description":"## Overview\nPhase 2: Separate collision detection from state modification to enable safe, composable collision resolution.\n\n## Current Problem\nDetectCollisions (collision.go:38-111) modifies database state during detection:\n- Line 83-86: Deletes issues when content matches but ID differs\n- This violates separation of concerns\n- Causes race conditions when processing multiple issues\n- Makes contentToDBIssue map stale after first deletion\n- Partial failures leave DB in inconsistent state\n\n## Solution\nMake DetectCollisions purely read-only. Move all modifications to a separate ApplyCollisionResolution function.\n\n## Implementation Tasks\n\n### 1. Add RenameDetail to CollisionResult\nFile: internal/storage/sqlite/collision.go\n```go\ntype CollisionResult struct {\n ExactMatches []string\n Collisions []*CollisionDetail\n NewIssues []string\n Renames []*RenameDetail // NEW\n}\n\ntype RenameDetail struct {\n OldID string // ID in database\n NewID string // ID in incoming\n Issue *types.Issue // The issue with new ID\n}\n```\n\n### 2. Remove deletion from DetectCollisions\nReplace lines 83-86:\n```go\n// OLD (DELETE THIS):\nif err := s.DeleteIssue(ctx, dbMatch.ID); err != nil {\n return nil, fmt.Errorf(\"failed to delete renamed issue...\")\n}\n\n// NEW (ADD THIS):\nresult.Renames = append(result.Renames, \u0026RenameDetail{\n OldID: dbMatch.ID,\n NewID: incoming.ID,\n Issue: incoming,\n})\ncontinue // Don't mark as NewIssue yet\n```\n\n### 3. Create ApplyCollisionResolution function\nNew function to apply all modifications atomically:\n```go\nfunc ApplyCollisionResolution(ctx context.Context, s *SQLiteStorage,\n result *CollisionResult, mapping map[string]string) error {\n \n // Phase 1: Handle renames (delete old IDs)\n for _, rename := range result.Renames {\n if err := s.DeleteIssue(ctx, rename.OldID); err != nil {\n return fmt.Errorf(\"failed to delete renamed issue %s: %w\", \n rename.OldID, err)\n }\n }\n \n // Phase 2: Create new IDs (from mapping)\n // Phase 3: Update references\n return nil\n}\n```\n\n### 4. Update callers to use two-phase approach\nFile: internal/importer/importer.go (handleCollisions)\n```go\n// Phase 1: Detect (read-only)\ncollisionResult, err := sqlite.DetectCollisions(ctx, sqliteStore, issues)\n\n// Phase 2: Resolve (compute mapping)\nmapping, err := sqlite.ResolveNWayCollisions(ctx, sqliteStore, collisionResult)\n\n// Phase 3: Apply (modify DB)\nerr = sqlite.ApplyCollisionResolution(ctx, sqliteStore, collisionResult, mapping)\n```\n\n### 5. Update tests\n- Verify DetectCollisions doesn't modify DB\n- Test ApplyCollisionResolution separately\n- Add test for rename detection without modification\n\n## Acceptance Criteria\n- DetectCollisions performs zero writes to database\n- DetectCollisions returns RenameDetail entries for content matches\n- ApplyCollisionResolution handles all modifications\n- All existing tests still pass\n- New test verifies read-only detection\n- contentToDBIssue map stays consistent throughout detection\n\n## Files to Modify\n- internal/storage/sqlite/collision.go (DetectCollisions, new function)\n- internal/importer/importer.go (handleCollisions caller)\n- internal/storage/sqlite/collision_test.go (add tests)\n\n## Testing\n- Unit test: DetectCollisions with content match doesn't delete DB issue\n- Unit test: RenameDetail correctly populated\n- Unit test: ApplyCollisionResolution applies renames\n- Integration test: Full flow still works end-to-end\n\n## Risk Mitigation\nThis is a significant refactor of core collision logic. Recommend:\n1. Add comprehensive tests before modifying\n2. Use feature flag to enable/disable new behavior\n3. Test thoroughly with TestTwoCloneCollision first","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-28T18:37:09.652326-07:00","updated_at":"2025-10-30T17:12:58.228266-07:00","closed_at":"2025-10-28T19:08:17.715416-07:00","dependencies":[{"issue_id":"bd-cbed9619.4","depends_on_id":"bd-325da116","type":"parent-child","created_at":"2025-10-28T18:39:20.570276-07:00","created_by":"daemon"},{"issue_id":"bd-cbed9619.4","depends_on_id":"bd-cbed9619.5","type":"blocks","created_at":"2025-10-28T18:39:28.285653-07:00","created_by":"daemon"}]} {"id":"bd-cbed9619.5","content_hash":"12cd30dee3c08ba58d03e4468e6fe261a47d58c3b75397d9f14f38ee644fab6e","title":"Add content-addressable identity to Issue type","description":"## Overview\nPhase 1: Add content hashing to enable global identification of issues regardless of their assigned IDs.\n\n## Current Problem\nThe system identifies issues only by ID (e.g., test-1, test-2). When multiple clones create the same ID with different content, there's no way to identify that these are semantically different issues without comparing all fields.\n\n## Solution\nAdd a ContentHash field to the Issue type that represents the canonical content fingerprint.\n\n## Implementation Tasks\n\n### 1. Add ContentHash field to Issue type\nFile: internal/types/types.go\n```go\ntype Issue struct {\n ID string\n ContentHash string // SHA256 of canonical content\n // ... existing fields\n}\n```\n\n### 2. Add content hash computation method\nUse existing hashIssueContent from collision.go:186 as foundation:\n```go\nfunc (i *Issue) ComputeContentHash() string {\n return hashIssueContent(i)\n}\n```\n\n### 3. Compute hash at creation time\n- Modify CreateIssue to compute and store ContentHash\n- Modify CreateIssues (batch) to compute hashes\n\n### 4. Compute hash at import time \n- Modify ImportIssues to compute ContentHash for all incoming issues\n- Store hash in database\n\n### 5. Add database column\n- Add migration to add content_hash column to issues table\n- Update SELECT/INSERT statements to include content_hash\n- Index on content_hash for fast lookups\n\n### 6. Populate existing issues\n- Add migration step to compute ContentHash for all existing issues\n- Use hashIssueContent function\n\n## Acceptance Criteria\n- Issue type has ContentHash field\n- Hash is computed automatically at creation time\n- Hash is computed for imported issues\n- Database stores content_hash column\n- All existing issues have non-empty ContentHash\n- Hash is deterministic (same content → same hash)\n- Hash excludes ID, timestamps (only semantic content)\n\n## Files to Modify\n- internal/types/types.go\n- internal/storage/sqlite/sqlite.go (schema, CreateIssue, CreateIssues)\n- internal/storage/sqlite/migrations.go (new migration)\n- internal/importer/importer.go (compute hash during import)\n- cmd/bd/create.go (compute hash at creation)\n\n## Testing\n- Unit test: same content produces same hash\n- Unit test: different content produces different hash \n- Unit test: hash excludes ID and timestamps\n- Integration test: hash persists in database\n- Migration test: existing issues get hashes populated","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-28T18:36:44.914967-07:00","updated_at":"2025-10-30T17:12:58.2279-07:00","closed_at":"2025-10-28T18:57:10.985198-07:00","dependencies":[{"issue_id":"bd-cbed9619.5","depends_on_id":"bd-325da116","type":"parent-child","created_at":"2025-10-28T18:39:20.547325-07:00","created_by":"daemon"}]} {"id":"bd-cc03","content_hash":"99b9674d285bebe120ea55f0d917bb9c9262845261a7a757e58ee8bc11f3fc36","title":"Build Node.js CLI wrapper for WASM","description":"Create npm package that wraps bd.wasm. Child of epic bd-44d0.\n\n## Tasks\n- [ ] Set up npm package structure (package.json)\n- [ ] Implement CLI argument parsing\n- [ ] Load and execute WASM module\n- [ ] Handle stdout/stderr correctly\n- [ ] Support --json flag for all commands\n- [ ] Add bd-wasm bin script\n\n## Success Criteria\n- bd-wasm ready --json works identically to bd\n- All core commands supported","status":"open","priority":0,"issue_type":"task","created_at":"2025-11-02T18:33:31.310268-08:00","updated_at":"2025-11-02T18:33:31.310268-08:00","dependencies":[{"issue_id":"bd-cc03","depends_on_id":"bd-197b","type":"blocks","created_at":"2025-11-02T18:33:31.311017-08:00","created_by":"daemon"}]} -{"id":"bd-cc4f","content_hash":"ca1078a7fd3facc3551ab2fe7d78cdd03c335b807a6e452d6cf714dd448e4cc4","title":"Implement TryResurrectParent function","description":"Create internal/storage/sqlite/resurrection.go with TryResurrectParent(ctx, parentID) function. Parse JSONL history to find deleted parent, create tombstone with status=deleted and is_tombstone=true flag. Handle recursive resurrection for multi-level missing parents (bd-abc.1.2 with missing bd-abc and bd-abc.1).","status":"open","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:59.61107-08:00","updated_at":"2025-11-04T12:31:59.61107-08:00"} +{"id":"bd-cc4f","content_hash":"ca1078a7fd3facc3551ab2fe7d78cdd03c335b807a6e452d6cf714dd448e4cc4","title":"Implement TryResurrectParent function","description":"Create internal/storage/sqlite/resurrection.go with TryResurrectParent(ctx, parentID) function. Parse JSONL history to find deleted parent, create tombstone with status=deleted and is_tombstone=true flag. Handle recursive resurrection for multi-level missing parents (bd-abc.1.2 with missing bd-abc and bd-abc.1).","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:59.61107-08:00","updated_at":"2025-11-04T17:10:48.899955-08:00","closed_at":"2025-11-04T17:10:48.899955-08:00"} {"id":"bd-cdf7","content_hash":"ebe962c7eb6dba6d112f7ccf59a8920e0354ea9cd8b039974a8fc4a58373809b","title":"Add tests for DetectCycles to improve coverage from 29.6%","description":"DetectCycles currently has 29.6% coverage. Need comprehensive tests for:\n- Simple cycles (A-\u003eB-\u003eA)\n- Complex multi-node cycles\n- Acyclic graphs (should not detect cycles)\n- Self-loops\n- Multiple independent cycles\n- Edge cases (empty graph, single node)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-01T22:40:58.977156-07:00","updated_at":"2025-11-01T22:52:02.243223-07:00","closed_at":"2025-11-01T22:52:02.243223-07:00"} {"id":"bd-ce37850f","content_hash":"3ef2872c3fcb1e5acc90d33fd5a76291742cbcecfbf697b611aa5b4d8ce80078","title":"Add embedding generation for duplicate detection","description":"Use embeddings for scalable duplicate detection.\n\nModel: text-embedding-3-small (OpenAI) or all-MiniLM-L6-v2 (local)\nStorage: SQLite vector extension or in-memory\nCost: ~/bin/bash.0002 per 100 issues\n\nMuch cheaper than LLM comparisons for large databases.\n\nFiles: internal/embeddings/ (new package)","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-28T14:48:29.072913-07:00","updated_at":"2025-10-30T17:12:58.219921-07:00"} {"id":"bd-ce75","content_hash":"025d43c12e9cc08c6d1db0b4a97f7a086a1a9f24f07769d48a7e2666d04ea217","title":"Test parent issue","description":"","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-02T11:50:37.759865-08:00","updated_at":"2025-11-02T11:50:47.308988-08:00","closed_at":"2025-11-02T11:50:47.308988-08:00"} {"id":"bd-cf349eb3","content_hash":"1b42289a0cb1da0626a69c6f004bf62fc9ba6e3a0f8eb70159c5f1446497020b","title":"Update LINTING.md with current baseline","description":"After cleanup, document the remaining acceptable baseline in LINTING.md so we can track regression.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-27T23:20:10.39272-07:00","updated_at":"2025-10-30T17:12:58.215471-07:00","closed_at":"2025-10-27T23:05:31.945614-07:00"} -{"id":"bd-d19a","content_hash":"0eef7b7a3dfd0b761bdcd7577f0ad682381b82217bc0d48117c3b51fcaa103fe","title":"Fix import failure on missing parent issues","description":"Import process fails atomically when JSONL references deleted parent issues. Implement hybrid solution: topological sorting + parent resurrection to handle deleted parents gracefully while maintaining referential integrity. See docs/import-bug-analysis-bd-3xq.md for full analysis.","status":"open","priority":0,"issue_type":"epic","created_at":"2025-11-04T12:31:30.994759-08:00","updated_at":"2025-11-04T12:31:30.994759-08:00"} +{"id":"bd-d19a","content_hash":"0eef7b7a3dfd0b761bdcd7577f0ad682381b82217bc0d48117c3b51fcaa103fe","title":"Fix import failure on missing parent issues","description":"Import process fails atomically when JSONL references deleted parent issues. Implement hybrid solution: topological sorting + parent resurrection to handle deleted parents gracefully while maintaining referential integrity. See docs/import-bug-analysis-bd-3xq.md for full analysis.","status":"closed","priority":0,"issue_type":"epic","created_at":"2025-11-04T12:31:30.994759-08:00","updated_at":"2025-11-04T17:11:40.563983-08:00","closed_at":"2025-11-04T17:11:40.563983-08:00"} {"id":"bd-d33c","content_hash":"0c3eb277be0ec16edae305156aa8824b6bc9c37fbd6151477f235e859e9b6181","title":"Separate process/lock/PID concerns into process.go","description":"Create internal/daemonrunner/process.go with: acquireDaemonLock, PID file read/write, stopDaemon, isDaemonRunning, getPIDFilePath, socket path helpers, version check.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-01T11:41:14.871122-07:00","updated_at":"2025-11-01T23:43:55.66159-07:00","closed_at":"2025-11-01T23:43:55.66159-07:00"} {"id":"bd-d355a07d","content_hash":"b4f98403e209eadf33dd4913660c1538fd922c89339a9ed034ef504aac358662","title":"Import validation falsely reports data loss on collision resolution","description":"## Problem\n\nPost-import validation reports 'data loss detected!' when import count reduces due to legitimate collision resolution.\n\n## Example\n\n```\nImport complete: 1 created, 8 updated, 142 unchanged, 19 skipped, 1 issues remapped\nPost-import validation failed: import reduced issue count: 165 → 164 (data loss detected!)\n```\n\nThis was actually successful collision resolution (bd-70419816 duplicated → remapped to-70419816), not data loss.\n\n## Impact\n\n- False alarms waste investigation time\n- Undermines confidence in import validation\n- Confuses users/agents about sync health\n\n## Solution\n\nImprove validation to distinguish:\n- Collision-resolution merges (expected count reduction)\n- Actual data loss (unexpected disappearance)\n\nTrack remapped issue count and adjust expected post-import count accordingly.","status":"open","priority":2,"issue_type":"bug","created_at":"2025-10-29T23:15:00.815227-07:00","updated_at":"2025-10-31T19:38:09.19996-07:00"} {"id":"bd-d3e5","content_hash":"16f978c58b9988457aeb1eaff37fb17f12e91325549b38be10362a08923e9a2d","title":"Test issue 2","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-02T09:44:17.116768539Z","updated_at":"2025-11-02T09:45:20.780838695Z","closed_at":"2025-11-02T09:45:20.780838695Z"} {"id":"bd-d3f0","content_hash":"d759327f1a1e4817d3e8ec212fd6af2607d0bb5e654201a6fc3640ad0a3b18fd","title":"Add 'bd comment' as alias for 'bd comments add'","description":"The command 'bd comments add' is verbose and unintuitive. Add 'bd comment' as a shorter alias that works the same way.\n\n## Rationale\n- More natural: 'bd comment \u003cissue-id\u003e \u003ctext\u003e' reads better than 'bd comments add \u003cissue-id\u003e \u003ctext\u003e'\n- Matches user expectations: users naturally try 'bd comment' first\n- Follows convention: other commands like 'bd create', 'bd show', 'bd close' are verbs\n\n## Implementation\nCould be implemented as:\n1. A new command that wraps bd comments add\n2. An alias registered in cobra\n3. Keep 'bd comments add' for backwards compatibility\n\n## Examples\n```bash\nbd comment bd-1234 'This is a comment'\nbd comment bd-1234 'Multi-line comment' --body 'Additional details here'\n```","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-02T17:13:18.82563-08:00","updated_at":"2025-11-03T22:20:30.223939-08:00","closed_at":"2025-11-03T22:20:30.223939-08:00"} {"id":"bd-d4ec5a82","content_hash":"872448809bfa26d39d68ba6cac5071379756c30bcd3b08dc75de6da56c133956","title":"Add MCP functions for repair commands","description":"Add repair commands to beads-mcp for agent access:\n- beads_resolve_conflicts()\n- beads_find_duplicates()\n- beads_detect_pollution()\n- beads_validate()\n\nFiles: integrations/beads-mcp/src/beads_mcp/server.py","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-28T14:48:29.071495-07:00","updated_at":"2025-10-30T17:12:58.219499-07:00"} {"id":"bd-d68f","content_hash":"4b5b5340749fba1c419c22f9937717b363ee8a49e4c5e0a5e0066a24b652a936","title":"Add tests for Comments API (AddIssueComment, GetIssueComments)","description":"Comments API currently has 0% coverage. Need tests for:\n- AddIssueComment - adding comments to issues\n- GetIssueComments - retrieving comments\n- Comment ordering and pagination\n- Edge cases (non-existent issues, empty comments)","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-01T22:40:58.980688-07:00","updated_at":"2025-11-01T22:53:42.124391-07:00","closed_at":"2025-11-01T22:53:42.124391-07:00"} -{"id":"bd-d76d","content_hash":"951394eaae461d97aebd41d40f4779176940a1daa0f040ee76d259690d0a68a1","title":"Modify EnsureIDs to support parent resurrection","description":"Update internal/storage/sqlite/ids.go:189-202 to call TryResurrectParent before failing on missing parent. Add resurrection mode flag, log resurrected parents for transparency. Maintain backwards compatibility with strict validation mode.","status":"open","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:59.659507-08:00","updated_at":"2025-11-04T12:31:59.659507-08:00"} +{"id":"bd-d76d","content_hash":"951394eaae461d97aebd41d40f4779176940a1daa0f040ee76d259690d0a68a1","title":"Modify EnsureIDs to support parent resurrection","description":"Update internal/storage/sqlite/ids.go:189-202 to call TryResurrectParent before failing on missing parent. Add resurrection mode flag, log resurrected parents for transparency. Maintain backwards compatibility with strict validation mode.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-04T12:31:59.659507-08:00","updated_at":"2025-11-04T17:10:48.9102-08:00","closed_at":"2025-11-04T17:10:48.9102-08:00"} {"id":"bd-d7e88238","content_hash":"b69ec861618b03129fad7807b085ee6365860cfd2e9901b49eb846e192b95a0d","title":"Rapid 3","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-29T19:11:57.459655-07:00","updated_at":"2025-10-30T17:12:58.189494-07:00"} {"id":"bd-d9e0","content_hash":"de4e01414f8863b63cb693a709048b85c3f4417f03e7d7b2528560076be0e1f7","title":"Extract validation functions to validators.go","description":"Move validatePriority, validateStatus, validateIssueType, validateTitle, validateEstimatedMinutes, validateFieldUpdate to validators.go","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-01T19:28:54.915909-07:00","updated_at":"2025-11-02T12:32:00.159298-08:00","closed_at":"2025-11-02T12:32:00.1593-08:00"} {"id":"bd-da4d8951","content_hash":"a4e81b23d88d41c8fd3fe31fb7ef387f99cb54ea42a6baa210ede436ecce3288","title":"Replace getStorageForRequest with Direct Access","description":"Replace all getStorageForRequest(req) calls with s.storage","acceptance_criteria":"- No references to getStorageForRequest() in codebase (except in deleted file)\n- All handlers use s.storage directly\n- Code compiles without errors\n\nFiles to update:\n- internal/rpc/server_issues_epics.go (~8 calls)\n- internal/rpc/server_labels_deps_comments.go (~4 calls)\n- internal/rpc/server_compact.go (~2 calls)\n- internal/rpc/server_export_import_auto.go (~2 calls)\n- internal/rpc/server_routing_validation_diagnostics.go (~1 call)\n\nPattern: store, err := s.getStorageForRequest(req) → store := s.storage","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-28T16:20:02.430127-07:00","updated_at":"2025-10-30T17:12:58.22099-07:00","closed_at":"2025-10-28T19:20:58.312809-07:00"} diff --git a/AGENTS.md b/AGENTS.md index 40584162..096caf14 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -213,6 +213,13 @@ bd import -i .beads/issues.jsonl --dry-run # Preview changes bd import -i .beads/issues.jsonl # Import and update issues bd import -i .beads/issues.jsonl --dedupe-after # Import + detect duplicates +# Note: Import automatically handles missing parents! +# - If a hierarchical child's parent is missing (e.g., bd-abc.1 but no bd-abc) +# - bd will search the JSONL history for the parent +# - If found, creates a tombstone placeholder (Status=Closed, Priority=4) +# - Dependencies are also resurrected on best-effort basis +# - This prevents import failures after parent deletion + # Find and merge duplicate issues bd duplicates # Show all duplicates bd duplicates --auto-merge # Automatically merge all diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e41d98..503c232e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **Parent Resurrection** (bd-58c0): Automatic resurrection of deleted parent issues from JSONL history + - Prevents import failures when parent issues have been deleted + - Creates tombstone placeholders for missing hierarchical parents + - Best-effort dependency resurrection from JSONL + +### Changed + +- **Error Messages**: Improved error messages for missing parent issues + - Old: `"parent issue X does not exist"` + - New: `"parent issue X does not exist and could not be resurrected from JSONL history"` + - **Breaking**: Scripts parsing exact error messages may need updates + +### Fixed + +- **JSONL Resurrection Logic**: Fixed to use LAST occurrence instead of FIRST (append-only semantics) + ## [0.21.7] - 2025-11-04 ### Fixed diff --git a/CODE_REVIEW_GUIDE.md b/CODE_REVIEW_GUIDE.md deleted file mode 100644 index b8a07bb6..00000000 --- a/CODE_REVIEW_GUIDE.md +++ /dev/null @@ -1,381 +0,0 @@ -# Code Review Guide: Parent Resurrection Feature (bd-d19a) - -**Branch**: `fix/import-missing-parents` -**Epic**: bd-d19a - Fix import failure on missing parent issues -**Reviewer Instructions**: Follow this guide to perform a systematic code review, filing issues for any problems found. - ---- - -## Overview - -This branch implements a **parent resurrection** feature that allows beads to gracefully handle deleted parent issues during import. When a child issue references a missing parent, the system: - -1. Searches JSONL history for the deleted parent -2. Creates a tombstone (status=closed) to preserve referential integrity -3. Allows the import to proceed instead of failing atomically - -**Key Files Changed**: -- `internal/storage/sqlite/resurrection.go` (NEW) -- `internal/storage/sqlite/sqlite.go` (CreateIssue modifications) -- `internal/storage/sqlite/ids.go` (EnsureIDs modifications) -- `internal/storage/sqlite/child_id_test.go` (test updates) - ---- - -## Critical Review Areas - -### 1. **BACKWARDS COMPATIBILITY** ⚠️ HIGHEST PRIORITY - -**Why Critical**: Existing beads installations must continue to work without migration, data loss, or behavior changes. - -#### 1.1 Database Schema -- [ ] **Review**: Check if any new columns/tables were added to schema -- [ ] **Test**: Confirm old databases work without migration -- [ ] **Verify**: No schema version bump required for resurrection feature -- [ ] **Edge Case**: What happens if user downgrades bd after using resurrection? -- [ ] **Issue**: File bug if resurrection creates tombstones that old versions can't handle - -**Files**: `internal/storage/sqlite/schema.go`, `internal/storage/sqlite/resurrection.go` - -#### 1.2 JSONL Format -- [ ] **Review**: Confirm JSONL export/import format unchanged -- [ ] **Test**: Old JSONL files can be imported by new version -- [ ] **Test**: New JSONL files (with resurrected parents) can be read by old versions -- [ ] **Verify**: No new fields added to Issue struct that break old parsers -- [ ] **Issue**: File bug if format is incompatible - -**Files**: `internal/types/types.go`, `internal/importer/importer.go` - -#### 1.3 API/CLI Behavior Changes -- [ ] **Review**: Check if any existing commands have different behavior -- [ ] **Test**: `bd create` with hierarchical IDs still works as before -- [ ] **Test**: `bd import` still works for normal (non-deleted-parent) cases -- [ ] **Verify**: Error messages for truly invalid cases unchanged -- [ ] **Issue**: File bug if existing workflows break - -**Files**: `cmd/bd/*.go` - -#### 1.4 Error Message Changes -- [ ] **Review**: Document all error message changes (breaking for scripts/tests) -- [ ] **Check**: `internal/storage/sqlite/child_id_test.go:200` - error message updated, why? -- [ ] **Verify**: All tests updated to match new error messages -- [ ] **Issue**: File bug if error messages are worse/less informative than before - -**Key Question**: Did we change `"parent issue X does not exist"` to `"parent issue X does not exist and could not be resurrected from JSONL history"`? Is this acceptable? - ---- - -### 2. **Transaction Safety** ⚠️ HIGH PRIORITY - -**Why Critical**: SQLite is sensitive to transaction conflicts. The resurrection feature must participate in existing transactions correctly. - -#### 2.1 Connection/Transaction Handling -- [ ] **Review**: `resurrection.go` - verify all DB operations use the provided connection -- [ ] **Check**: `tryResurrectParentWithConn()` uses `conn` parameter, not `s.db` -- [ ] **Verify**: No calls to `s.db.Conn()` inside transaction-aware functions -- [ ] **Test**: Run `TestImportWithDeletedParent` to confirm no "database is locked" errors -- [ ] **Edge Case**: What if resurrection is called outside a transaction? -- [ ] **Issue**: File bug if any transaction conflict scenarios remain - -**Files**: `internal/storage/sqlite/resurrection.go:38-104` - -#### 2.2 Rollback Behavior -- [ ] **Review**: If resurrection fails mid-chain (bd-abc.1.2 → bd-abc fails), does it rollback? -- [ ] **Check**: `tryResurrectParentChainWithConn()` error handling -- [ ] **Verify**: Failed resurrection doesn't leave partial tombstones in DB -- [ ] **Test**: Create test case for partial resurrection failure -- [ ] **Issue**: File bug if rollback behavior is incorrect - -**Files**: `internal/storage/sqlite/resurrection.go:189-206` - ---- - -### 3. **Resurrection Logic Correctness** - -#### 3.1 Parent Chain Resurrection -- [ ] **Review**: `extractParentChain()` correctly extracts all parent IDs -- [ ] **Test**: Multi-level hierarchy (bd-abc.1.2.3) resurrects bd-abc, bd-abc.1, bd-abc.1.2 in order -- [ ] **Edge Case**: What if JSONL has bd-abc.1.2 but not bd-abc.1? Does it fail gracefully? -- [ ] **Verify**: Root-to-leaf ordering (depth 0 → 1 → 2 → 3) -- [ ] **Issue**: File bug if chain resurrection can fail silently - -**Files**: `internal/storage/sqlite/resurrection.go:189-206`, `resurrection.go:207-221` - -#### 3.2 JSONL Search -- [ ] **Review**: `findIssueInJSONL()` - can it handle large JSONL files (>10MB)? -- [ ] **Performance**: Line-by-line scanning is O(n) - acceptable? -- [ ] **Edge Case**: What if JSONL is malformed/corrupted? -- [ ] **Edge Case**: What if issue appears multiple times in JSONL (updates)? -- [ ] **Verify**: Uses latest version if issue appears multiple times -- [ ] **Issue**: File bug if JSONL search has correctness or performance issues - -**Files**: `internal/storage/sqlite/resurrection.go:116-179` - -**Key Question**: Does it pick the FIRST or LAST occurrence of an issue in JSONL? (JSONL may have updates) - -#### 3.3 Tombstone Creation -- [ ] **Review**: Tombstone fields - are they correct? - - Status: `closed` ✓ - - Priority: `4` (lowest) ✓ - - Description: `[RESURRECTED]` prefix ✓ - - Timestamps: CreatedAt from original, UpdatedAt/ClosedAt = now ✓ -- [ ] **Edge Case**: What if original issue had dependencies? Are they resurrected? -- [ ] **Check**: Lines 79-95 - dependency resurrection is "best effort", acceptable? -- [ ] **Verify**: Tombstone doesn't trigger export/sync loops -- [ ] **Issue**: File bug if tombstone creation causes side effects - -**Files**: `internal/storage/sqlite/resurrection.go:48-95` - ---- - -### 4. **Integration Points** - -#### 4.1 CreateIssue Integration -- [ ] **Review**: `sqlite.go:182-196` - resurrection called before `insertIssue()` -- [ ] **Verify**: Resurrection only happens for hierarchical IDs (contains ".") -- [ ] **Edge Case**: What if user manually creates issue with ID "bd-abc.1" but parent exists? -- [ ] **Performance**: Does resurrection check happen on EVERY CreateIssue call? -- [ ] **Issue**: File bug if resurrection adds unnecessary overhead - -**Files**: `internal/storage/sqlite/sqlite.go:182-196` - -#### 4.2 EnsureIDs Integration -- [ ] **Review**: `ids.go:189-202` - resurrection in batch ID validation -- [ ] **Verify**: Works correctly during import (batch operations) -- [ ] **Edge Case**: What if 100 issues all reference same missing parent? -- [ ] **Performance**: Is parent resurrected once or 100 times? -- [ ] **Issue**: File bug if resurrection is inefficient in batch scenarios - -**Files**: `internal/storage/sqlite/ids.go:189-202` - -#### 4.3 Import Flow -- [ ] **Review**: Does topological sort + resurrection work together correctly? -- [ ] **Check**: `importer.go:540-558` - depth sorting happens BEFORE EnsureIDs -- [ ] **Verify**: Resurrection is a fallback, not the primary mechanism -- [ ] **Test**: Import batch with mix of new hierarchical issues + deleted parent refs -- [ ] **Issue**: File bug if import flow has race conditions or ordering issues - -**Files**: `internal/importer/importer.go:540-558` - ---- - -### 5. **Testing Coverage** - -#### 5.1 Existing Tests Updated -- [ ] **Review**: `child_id_test.go:200` - why was error message changed? -- [ ] **Verify**: All tests pass with `go test ./internal/storage/sqlite/...` -- [ ] **Check**: Integration test `TestImportWithDeletedParent` passes -- [ ] **Issue**: File bug for any test failures or inadequate test updates - -#### 5.2 Missing Test Cases (File issues for these!) -- [ ] **Test Case**: Multi-level resurrection (bd-abc.1.2 with missing bd-abc and bd-abc.1) -- [ ] **Test Case**: Resurrection when JSONL doesn't exist at all -- [ ] **Test Case**: Resurrection when JSONL exists but parent never existed -- [ ] **Test Case**: Resurrection with corrupted JSONL file -- [ ] **Test Case**: Resurrection during concurrent operations (race conditions) -- [ ] **Test Case**: Backwards compat - old DB with new code, new DB with old code -- [ ] **Test Case**: Performance test - resurrect parent with 1000 children -- [ ] **Test Case**: Dependency resurrection - parent had dependencies, are they restored? - -**Files**: `beads_integration_test.go`, `internal/storage/sqlite/*_test.go` - ---- - -### 6. **Edge Cases & Error Handling** - -#### 6.1 Error Handling -- [ ] **Review**: All error paths return meaningful messages -- [ ] **Check**: JSONL file doesn't exist - returns `nil, nil` (acceptable?) -- [ ] **Check**: Parent not found in JSONL - returns `false, nil` (acceptable?) -- [ ] **Check**: Database locked - returns error (handled by transaction refactor) -- [ ] **Verify**: No silent failures that leave DB in inconsistent state -- [ ] **Issue**: File bug for any unclear or missing error messages - -#### 6.2 Pathological Cases -- [ ] **Edge Case**: Circular references (bd-abc.1 parent is bd-abc.1.2)? -- [ ] **Edge Case**: ID with 10+ levels of nesting (bd-abc.1.2.3.4.5.6.7.8.9.10)? -- [ ] **Edge Case**: Issue ID contains multiple dots in hash (bd-abc.def.1)? -- [ ] **Edge Case**: JSONL has 1 million issues - how long does resurrection take? -- [ ] **Edge Case**: Resurrection triggered during daemon auto-sync - conflicts? -- [ ] **Issue**: File bug for any unhandled pathological cases - ---- - -### 7. **Documentation & User Impact** - -#### 7.1 User-Facing Documentation -- [ ] **Review**: Is resurrection behavior documented in README.md? -- [ ] **Review**: Is it documented in AGENTS.md (for AI users)? -- [ ] **Review**: Should there be a tombstone cleanup command (bd compact-tombstones)? -- [ ] **Verify**: Error messages guide users to solutions -- [ ] **Issue**: File doc bug if feature is undocumented - -#### 7.2 Migration Path -- [ ] **Review**: Do users need to do anything when upgrading? -- [ ] **Review**: What happens to existing orphaned children in old DBs? -- [ ] **Verify**: Feature is opt-in or backwards compatible by default? -- [ ] **Issue**: File bug if migration path is unclear - ---- - -## Review Workflow - -### Step 1: Read the Code -```bash -# Review core resurrection logic -cat internal/storage/sqlite/resurrection.go - -# Review integration points -cat internal/storage/sqlite/sqlite.go -cat internal/storage/sqlite/ids.go - -# Review tests -cat internal/storage/sqlite/child_id_test.go -cat beads_integration_test.go | grep -A 30 "TestImportWithDeletedParent" -``` - -### Step 2: Run Tests -```bash -# Unit tests -go test ./internal/storage/sqlite/... -v - -# Integration tests -go test -v ./beads_integration_test.go -run TestImportWithDeletedParent - -# Full test suite -go test ./... -``` - -### Step 3: Test Backwards Compatibility Manually -```bash -# Create old-style database -git checkout main -./bd init --prefix test -./bd create "Parent" --id test-parent -./bd create "Child" --id test-parent.1 - -# Switch to new branch and verify it works -git checkout fix/import-missing-parents -./bd show test-parent test-parent.1 # Should work - -# Test resurrection -./bd delete test-parent -./bd sync -# Edit JSONL to add back child reference, then import -./bd import -i .beads/issues.jsonl -./bd show test-parent # Should show tombstone -``` - -### Step 4: File Issues -For each problem found, create an issue: -```bash -./bd create "BUG: [description]" -t bug -p 0 --deps discovered-from:bd-d19a -``` - ---- - -## Specific Code Snippets to Review - -### Critical: Transaction Handling -**File**: `internal/storage/sqlite/resurrection.go:67-76` (BEFORE fix) -```go -// Get a connection for the transaction -conn, err := s.db.Conn(ctx) -if err != nil { - return false, fmt.Errorf("failed to get database connection: %w", err) -} -defer conn.Close() - -// Insert tombstone into database -if err := insertIssue(ctx, conn, tombstone); err != nil { - return false, fmt.Errorf("failed to create tombstone for parent %s: %w", parentID, err) -} -``` - -**Question**: Does this create a NEW connection inside an existing transaction? (YES - this was bd-58c0 bug, fixed by refactor) - -### Critical: Error Message Change -**File**: `internal/storage/sqlite/child_id_test.go:200` -```go -// OLD: -if err != nil && err.Error() != "parent issue bd-nonexistent does not exist" { - -// NEW: -expectedErr := "parent issue bd-nonexistent does not exist and could not be resurrected from JSONL history" -if err != nil && err.Error() != expectedErr { -``` - -**Question**: Is this error message change acceptable for backwards compatibility? (Scripts parsing errors may break) - -### Critical: JSONL Lookup Logic -**File**: `internal/storage/sqlite/resurrection.go:138-156` -```go -for scanner.Scan() { - lineNum++ - line := scanner.Text() - - // Quick check: does this line contain our issue ID? - if !strings.Contains(line, `"`+issueID+`"`) { - continue - } - - // Parse JSON - var issue types.Issue - if err := json.Unmarshal([]byte(line), &issue); err != nil { - fmt.Fprintf(os.Stderr, "Warning: skipping malformed JSONL line %d: %v\n", lineNum, err) - continue - } - - if issue.ID == issueID { - return &issue, nil // Returns FIRST match - } -} -``` - -**Question**: Should this return the LAST match instead (latest version of issue)? JSONL may have updates. - ---- - -## Sign-Off Checklist - -Before approving this branch for merge, confirm: - -- [ ] All backwards compatibility concerns addressed -- [ ] No schema migration required -- [ ] No JSONL format changes -- [ ] Transaction safety verified (no "database is locked" errors) -- [ ] Error messages are informative and backwards-compatible -- [ ] Integration tests pass (`TestImportWithDeletedParent`) -- [ ] Unit tests pass (`go test ./internal/storage/sqlite/...`) -- [ ] Documentation updated (README.md, AGENTS.md) -- [ ] Edge cases have test coverage or filed issues -- [ ] Performance acceptable for common cases (resurrect 1 parent with 100 children) - ---- - -## Expected Issues to File - -Based on this review, you should file issues for: - -1. **JSONL lookup returns first match, not last** - should return latest version -2. **No test for multi-level resurrection** (bd-abc.1.2 with missing bd-abc and bd-abc.1) -3. **No test for resurrection when JSONL doesn't exist** -4. **No test for backwards compat** (old DB → new code, new DB → old code) -5. **Error message change may break user scripts** - document or add deprecation warning -6. **Performance concern**: Resurrection in EnsureIDs may resurrect same parent N times for N children -7. **Missing documentation** - README.md doesn't mention resurrection feature -8. **No cleanup mechanism** for tombstones (consider `bd compact-tombstones` command) - ---- - -## Questions for Original Implementer - -1. Why does `findIssueInJSONL()` return the FIRST match instead of LAST? -2. Is the error message change acceptable for backwards compatibility? -3. Should resurrection be opt-in via config flag? -4. What's the performance impact of resurrection on large JSONL files (1M+ issues)? -5. Should tombstones be marked with a special flag (`is_tombstone=true`) in the database? - ---- - -**Good luck with the review!** Be thorough, file issues for everything you find, and don't hesitate to ask questions. diff --git a/internal/storage/sqlite/resurrection.go b/internal/storage/sqlite/resurrection.go index d05acfc0..d5589844 100644 --- a/internal/storage/sqlite/resurrection.go +++ b/internal/storage/sqlite/resurrection.go @@ -133,6 +133,7 @@ func (s *SQLiteStorage) findIssueInJSONL(issueID string) (*types.Issue, error) { scanner.Buffer(buf, maxCapacity) lineNum := 0 + var lastMatch *types.Issue for scanner.Scan() { lineNum++ line := scanner.Text() @@ -156,9 +157,10 @@ func (s *SQLiteStorage) findIssueInJSONL(issueID string) (*types.Issue, error) { continue } - // Check if this is the issue we're looking for + // Keep the last occurrence (JSONL append-only semantics) if issue.ID == issueID { - return &issue, nil + issueCopy := issue + lastMatch = &issueCopy } } @@ -166,7 +168,7 @@ func (s *SQLiteStorage) findIssueInJSONL(issueID string) (*types.Issue, error) { return nil, fmt.Errorf("error reading JSONL file: %w", err) } - return nil, nil // Not found + return lastMatch, nil // Returns last match or nil if not found } // TryResurrectParentChain recursively resurrects all missing parents in a hierarchical ID chain. diff --git a/internal/storage/sqlite/resurrection_test.go b/internal/storage/sqlite/resurrection_test.go index 297e63ee..49423420 100644 --- a/internal/storage/sqlite/resurrection_test.go +++ b/internal/storage/sqlite/resurrection_test.go @@ -539,6 +539,90 @@ func writeIssuesToJSONL(path string, issues []types.Issue) error { return nil } +// TestTryResurrectParent_MultipleVersionsInJSONL verifies that the LAST occurrence is used +func TestTryResurrectParent_MultipleVersionsInJSONL(t *testing.T) { + s := newTestStore(t, "") + ctx := context.Background() + + // Create JSONL with multiple versions of the same issue (append-only semantics) + dbDir := filepath.Dir(s.dbPath) + jsonlPath := filepath.Join(dbDir, "issues.jsonl") + + // First version: priority 3, title "Old Version" + v1 := &types.Issue{ + ID: "bd-multi", + Title: "Old Version", + Status: types.StatusOpen, + Priority: 3, + IssueType: types.TypeTask, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + v1JSON, _ := json.Marshal(v1) + + // Second version: priority 2, title "Updated Version" + time.Sleep(10 * time.Millisecond) // Ensure different timestamp + v2 := &types.Issue{ + ID: "bd-multi", + Title: "Updated Version", + Status: types.StatusInProgress, + Priority: 2, + IssueType: types.TypeTask, + CreatedAt: v1.CreatedAt, // Same creation time + UpdatedAt: time.Now(), + } + v2JSON, _ := json.Marshal(v2) + + // Third version: priority 1, title "Latest Version" + time.Sleep(10 * time.Millisecond) + v3 := &types.Issue{ + ID: "bd-multi", + Title: "Latest Version", + Status: types.StatusClosed, + Priority: 1, + IssueType: types.TypeTask, + CreatedAt: v1.CreatedAt, + UpdatedAt: time.Now(), + } + v3JSON, _ := json.Marshal(v3) + + // Write all three versions (append-only) + content := string(v1JSON) + "\n" + string(v2JSON) + "\n" + string(v3JSON) + "\n" + if err := os.WriteFile(jsonlPath, []byte(content), 0644); err != nil { + t.Fatalf("Failed to create JSONL: %v", err) + } + + // Resurrect - should get the LAST version (v3) + resurrected, err := s.TryResurrectParent(ctx, "bd-multi") + if err != nil { + t.Fatalf("TryResurrectParent failed: %v", err) + } + if !resurrected { + t.Error("Expected successful resurrection") + } + + // Verify we got the latest version's data + retrieved, err := s.GetIssue(ctx, "bd-multi") + if err != nil { + t.Fatalf("Failed to retrieve resurrected issue: %v", err) + } + + // Most important: title should be from LAST occurrence (v3) + if retrieved.Title != "Latest Version" { + t.Errorf("Expected title 'Latest Version', got '%s' (should use LAST occurrence in JSONL)", retrieved.Title) + } + + // CreatedAt should be preserved from original (all versions share this) + if !retrieved.CreatedAt.Equal(v1.CreatedAt) { + t.Errorf("Expected CreatedAt %v, got %v", v1.CreatedAt, retrieved.CreatedAt) + } + + // Note: Priority, Status, and Description are modified by tombstone logic + // (Priority=4, Status=Closed, Description="[RESURRECTED]...") + // This is expected behavior - the test verifies we read the LAST occurrence + // before creating the tombstone. +} + // Helper function to check if string contains substring func contains(s, substr string) bool { return len(s) > 0 && len(substr) > 0 && (s == substr || len(s) >= len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || containsMiddle(s, substr)))