## Problem
Export deduplication feature broke when JSONL and export_hashes diverged
(e.g., after git pull/reset). This caused exports to skip issues that
weren't actually in the file, leading to silent data loss.
## Solution
1. JSONL integrity validation before every export
- Store JSONL file hash after export
- Validate hash before export, clear export_hashes if mismatch
- Automatically recovers from git operations changing JSONL
2. Clear export_hashes on all imports
- Prevents stale hashes from causing future export failures
- Import operations invalidate export_hashes state
3. Add Storage interface methods:
- GetJSONLFileHash/SetJSONLFileHash for integrity tracking
- ClearAllExportHashes for recovery
## Tests Added
- TestJSONLIntegrityValidation: Unit tests for validation logic
- TestImportClearsExportHashes: Verifies imports clear hashes
- TestExportIntegrityAfterJSONLTruncation: Simulates git reset (would have caught bd-160)
- TestExportIntegrityAfterJSONLDeletion: Tests recovery from file deletion
- TestMultipleExportsStayConsistent: Tests repeated export integrity
## Follow-up
Created bd-179 epic for remaining integration test gaps (multi-repo sync,
daemon auto-sync, corruption recovery tests).
Closes bd-160
After 2 weeks of collision/stale-data fixes, reviewed all changes to identify
spurious code that is no longer needed after content-hash resolution was implemented.
**Removed:**
1. countReferences() function from collision.go (lines 274-328)
- Was used for reference-count based collision scoring
- Completely unused after switching to content-hash based resolution (commit 2e87329)
- Still exists in duplicates.go for deduplication (different use case)
2. ReferenceScore field from CollisionDetail struct
- Marked as DEPRECATED but never removed
- No longer used by ScoreCollisions() which now uses content hashing
3. TestCountReferences and TestCountReferencesWordBoundary tests
- Tested the now-deleted countReferences() function
- No longer relevant
**Fixed:**
- Updated CheckpointWAL comments to remove misleading "staleness detection" claim
- Staleness detection uses metadata (last_import_time), NOT file mtime
- CheckpointWAL is still valuable for data persistence and WAL size reduction
- Comments now accurately reflect actual benefits
**Verified:**
- All tests pass (internal/storage/sqlite)
- Content-hash collision resolution still works correctly
- No behavioral changes, just cleanup
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Detect fsnotify.NewWatcher() errors and auto-fallback to polling mode
- Add BEADS_WATCHER_FALLBACK env var to control behavior (default: enabled)
- Poll every 5 seconds with comprehensive change detection:
- Track file existence, size, and mtime to catch all changes
- Handle file disappearance/reappearance correctly
- Trigger on file recreation even with older timestamps
- Fix goroutine leak: Close() now stops background goroutines via cancel context
- Tighten git refs filtering to only trigger for events under .git/refs/heads
- Trigger after successful JSONL rewatch on rename/remove events
- Improve logging to show actual poll interval in warnings
All tests passing.
Amp-Thread-ID: https://ampcode.com/threads/T-8f5edc23-4b78-4b80-b8f3-66050f45eb61
Co-authored-by: Amp <amp@ampcode.com>
The test was comparing full JSON output including timestamps, causing
false negative failures. The 2-clone collision resolution actually works
correctly - both clones converge to identical semantic content.
Changes:
- Added compareIssuesIgnoringTimestamps helper to compare issue content
- Compares ID, title, description, status, priority, type (not timestamps)
- Added filterTrackedChanges to ignore untracked files in git status
- Test now passes, proving collision resolution works for 2-clone case
Closes bd-91
Amp-Thread-ID: https://ampcode.com/threads/T-a58284d2-f39f-482a-9db0-bec31c19e9e5
Co-authored-by: Amp <amp@ampcode.com>
- Replace timestamp-based collision scoring with deterministic content hashing
- Add hashIssueContent() using SHA-256 of all substantive fields
- Modify ScoreCollisions to compare hashes and set RemapIncoming flag
- Update RemapCollisions to handle both directions (remap incoming OR existing)
- Add CollisionDetail.RemapIncoming field to control which version gets remapped
- Add unit tests for hash function and deterministic collision resolution
Status: Hash-based resolution works correctly, but TestTwoCloneCollision still
fails due to missing rename detection. After Clone B resolves collision,
Clone A needs to recognize its issue was remapped to a different ID.
Next: Add content-based rename detection during import to prevent
re-resolving already-resolved collisions.
Progress on bd-86.
Amp-Thread-ID: https://ampcode.com/threads/T-b19b49e8-b52a-463d-b052-8a526a500260
Co-authored-by: Amp <amp@ampcode.com>