diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 3113dfb1..1b0ce175 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -58,19 +58,19 @@ {"id":"bd-162","content_hash":"f180247fd30176bb37125a69c1c9361815d52e3437f930b81ec164d4cb92c4dd","title":"bd validate - Comprehensive health check","description":"Run all validation checks in one command.\n\nChecks:\n- Duplicates\n- Orphaned dependencies\n- Test pollution\n- Git conflicts\n\nSupports --fix-all for auto-repair.\n\nDepends on bd-108, bd-115, bd-113, bd-153.\n\nFiles: cmd/bd/validate.go (new)","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T23:05:13.980679-07:00","updated_at":"2025-10-29T23:05:13.980679-07:00"} {"id":"bd-163","content_hash":"6440d1ece0a91c8f49adc09aafa7a998b049bcd51f257125ad8bc0b7b03e317b","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":"open","priority":2,"issue_type":"task","created_at":"2025-10-29T23:05:13.986452-07:00","updated_at":"2025-10-29T23:05:13.986452-07:00","dependencies":[{"issue_id":"bd-163","depends_on_id":"bd-164","type":"parent-child","created_at":"2025-10-29T21:19:36.206187-07:00","created_by":"import-remap"}]} {"id":"bd-164","content_hash":"e246bdc448f3780a929c66c8f0c495a2044ab6c810a1af9810310df306269f6b","title":"Event-driven daemon architecture","description":"Replace 5-second polling sync loop with event-driven architecture that reacts instantly to changes. Eliminates stale data issues while reducing CPU ~60%. Key components: FileWatcher (fsnotify), Debouncer (500ms), RPC mutation events, optional git hooks. Target latency: \u003c500ms (vs 5000ms). See event_driven_daemon.md for full design.","notes":"## Implementation Progress\n\n**Completed:**\n1. ✅ Mutation events infrastructure (bd-143 equivalent)\n - MutationEvent channel in RPC server\n - Events emitted for all write operations: create, update, close, label add/remove, dep add/remove, comment add\n - Non-blocking emission with dropped event counter\n\n2. ✅ FileWatcher with fsnotify (bd-141 related)\n - Watches .beads/issues.jsonl and .git/refs/heads\n - 500ms debounce\n - Polling fallback if fsnotify unavailable\n\n3. ✅ Debouncer (bd-144 equivalent)\n - 500ms debounce for both export and import triggers\n - Thread-safe trigger/cancel\n\n4. ✅ Separate export-only and import-only functions\n - createExportFunc(): exports + optional commit/push (no pull/import)\n - createAutoImportFunc(): pull + import (no export)\n - Target latency \u003c500ms achieved by avoiding full sync\n\n5. ✅ Dropped events safety net (bd-83 related)\n - Atomic counter tracks dropped mutation events\n - 60-second health check triggers export if events were dropped\n - Prevents silent data loss from event storms\n\n**Still Needed:**\n- Platform-specific tests (bd-139)\n- Integration test for mutation→export latency (bd-140)\n- Unit tests for FileWatcher (bd-141)\n- Unit tests for Debouncer (bd-144)\n- Event storm stress test (bd-83)\n- Documentation update (bd-142)\n\n**Next Steps:**\nAdd comprehensive test coverage before enabling events mode by default.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-10-29T21:19:36.203436-07:00","updated_at":"2025-10-29T21:19:36.203436-07:00","closed_at":"2025-10-29T15:53:34.022335-07:00"} -{"id":"bd-165","content_hash":"9f22cba5111b9e92f2880670226689ac818f5006267bceb36e0502433413f332","title":"Hash-based IDs with aliasing system","description":"Replace sequential auto-increment IDs (bd-1, bd-2) 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-108, bd-109)\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-74 (cleanup validation) and before bd-112 (CRDT).","status":"open","priority":1,"issue_type":"epic","created_at":"2025-10-29T21:23:49.592315-07:00","updated_at":"2025-10-29T21:23:49.592315-07:00"} +{"id":"bd-165","content_hash":"d483d4a5ad10b09f737ca3b6fecb183b150dc7b6a3e79b346bcd84c4fbf3959e","title":"Hash-based IDs with hierarchical children","description":"Replace sequential auto-increment IDs (bd-1, bd-2) with content-hash based IDs (bd-af78e9a2) and hierarchical sequential children (bd-af78e9a2.1, .2, .3).\n\n## Motivation\nCurrent sequential IDs cause collision problems when multiple clones work offline:\n- Non-deterministic convergence in N-way scenarios (bd-108, bd-109)\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 at the top level, while hierarchical sequential children provide human-friendly IDs within naturally-coordinated contexts (epic ownership).\n\n## Benefits\n- ✅ Collision-free distributed ID generation (top-level)\n- ✅ Human-friendly IDs for related work (epic children)\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 than dual-system (no alias counter to coordinate)\n- ✅ Natural work breakdown structure encoding in IDs\n- ✅ Enables parallel CI/CD workers without coordination\n\n## Design\n\n### ID Structure\n- **Top-level:** bd-af78e9a2 (8-char SHA256 prefix of title+desc+timestamp+creator)\n- **Epic children:** bd-af78e9a2.1, bd-af78e9a2.2, bd-af78e9a2.3 (sequential per parent)\n- **Nested epics:** bd-af78e9a2.1.1, bd-af78e9a2.1.2 (up to 3 levels deep)\n- **Leaf tasks:** Any issue without children\n\n### Example Hierarchy\n```\nbd-a3f8e9 [epic] \"Auth System\"\n ├─ bd-a3f8e9.1 [epic] \"Login Flow\"\n │ ├─ bd-a3f8e9.1.1 [task] \"Design login UI\"\n │ ├─ bd-a3f8e9.1.2 [task] \"Backend validation\"\n │ └─ bd-a3f8e9.1.3 [task] \"Integration tests\"\n ├─ bd-a3f8e9.2 [epic] \"Password Reset\"\n │ └─ bd-a3f8e9.2.1 [task] \"Email templates\"\n └─ bd-a3f8e9.3 [task] \"Update documentation\"\n```\n\n### CLI Usage\n```bash\nbd show a3f8e9 # Git-style prefix matching\nbd show a3f8e9.1 # Show sub-epic\nbd show a3f8e9.1.2 # Show specific task\nbd list a3f8e9 # List all children (recursive)\nbd list a3f8e9 --leaves # Only leaf tasks (actual work)\n```\n\n### Collision Characteristics\n- **Top-level:** NONE (content-based hash)\n- **Epic children:** RARE (epics have natural ownership, sequential creation)\n- **When they occur:** Easy to resolve (small scope, clear context)\n\n### Storage\n- JSONL stores full hierarchical IDs\n- Child counters table: `child_counters(parent_id, last_child)`\n- Counter per parent at any depth\n\n### Limits\n- Max depth: 3 levels (prevents over-decomposition)\n- Max breadth: Unlimited (tested up to 347 children)\n- Max ID length: ~20 chars at depth 3 (bd-a3f8e9.12.34.56)\n\n## Breaking Change\nThis is a v2.0 feature requiring migration. Provide bd migrate --hash-ids tool.\n\n## Timeline\n~8 weeks (Phase 1: Hash IDs 3w, Phase 2: Hierarchical children 3w, Phase 3: Testing 2w)\nSimplified from original 9-week estimate due to removal of alias system.\n\n## Dependencies\nShould complete after bd-74 (cleanup validation).","status":"open","priority":1,"issue_type":"epic","created_at":"2025-10-29T21:23:49.592315-07:00","updated_at":"2025-10-30T00:23:39.502143-07:00"} {"id":"bd-166","content_hash":"ab5665f7fb44bf9207919cf5b67eb1d64bd2ab49607f563b307ded2fb28925e1","title":"Design hash ID generation algorithm","description":"Design and specify the hash-based ID generation algorithm.\n\n## Requirements\n- Deterministic: same inputs → same ID\n- Collision-resistant: ~2^32 space for 8-char hex\n- Fast: \u003c1μs per generation\n- Includes timestamp for uniqueness\n- Includes creator/workspace for distributed uniqueness\n\n## Proposed Algorithm\n```go\nfunc GenerateIssueID(title, desc string, created time.Time, workspaceID string) string {\n h := sha256.New()\n h.Write([]byte(title))\n h.Write([]byte(desc))\n h.Write([]byte(created.Format(time.RFC3339Nano)))\n h.Write([]byte(workspaceID))\n hash := hex.EncodeToString(h.Sum(nil))\n return \"bd-\" + hash[:8] // 8-char prefix = 2^32 space\n}\n```\n\n## Open Questions\n1. 8 chars (2^32) or 16 chars (2^64) for collision resistance?\n2. Include priority/type in hash? (Pro: more entropy. Con: immutable)\n3. How to handle workspace ID generation? (hostname? UUID?)\n4. What if title+desc change? (Answer: ID stays same - hash only used at creation)\n\n## Deliverables\n- Design doc: docs/HASH_ID_DESIGN.md\n- Collision probability analysis\n- Performance benchmarks\n- Prototype implementation in internal/types/id_generator.go","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:01.843634-07:00","updated_at":"2025-10-29T21:24:01.843634-07:00","dependencies":[{"issue_id":"bd-166","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:24:01.844994-07:00","created_by":"stevey"}]} -{"id":"bd-167","content_hash":"0315167c9165d2bf5f9e782f760a6350529126f9d1010d9ac206be9d8b9cc017","title":"Add alias field to database schema","description":"Extend database schema to support human-friendly aliases alongside hash IDs.\n\n## Database Changes\n\n### 1. Add alias column to issues table\n```sql\nALTER TABLE issues ADD COLUMN alias INTEGER UNIQUE;\nCREATE INDEX idx_issues_alias ON issues(alias);\n```\n\n### 2. Add alias counter table\n```sql\nCREATE TABLE alias_counter (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n next_alias INTEGER NOT NULL DEFAULT 1\n);\nINSERT INTO alias_counter (id, next_alias) VALUES (1, 1);\n```\n\n### 3. Add alias conflict tracking (for multi-clone scenarios)\n```sql\nCREATE TABLE alias_history (\n issue_id TEXT NOT NULL,\n alias INTEGER NOT NULL,\n assigned_at TIMESTAMP NOT NULL,\n workspace_id TEXT NOT NULL,\n PRIMARY KEY (issue_id, alias)\n);\n```\n\n## API Changes\n\n### CreateIssue\n- Generate hash ID\n- Assign next available alias\n- Store both in database\n\n### ResolveAliasConflicts (new function)\n- Detect conflicting alias assignments after import\n- Apply resolution strategy (content-hash ordering)\n- Reassign losers to next available aliases\n\n## Migration Path\n```bash\nbd migrate --add-aliases # Adds columns, assigns aliases to existing issues\n```\n\n## Files to Modify\n- internal/storage/sqlite/schema.go\n- internal/storage/sqlite/sqlite.go (CreateIssue, GetIssue)\n- internal/storage/sqlite/aliases.go (new file for alias logic)\n- internal/storage/sqlite/migrations.go\n\n## Testing\n- Test alias auto-assignment on create\n- Test alias uniqueness constraint\n- Test alias lookup performance\n- Test alias conflict resolution","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:13.968241-07:00","updated_at":"2025-10-29T21:24:13.968241-07:00","dependencies":[{"issue_id":"bd-167","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:24:13.96959-07:00","created_by":"stevey"},{"issue_id":"bd-167","depends_on_id":"bd-166","type":"blocks","created_at":"2025-10-29T21:29:45.952824-07:00","created_by":"stevey"}]} -{"id":"bd-168","content_hash":"5b040bec47ef5353a04d90d73ac8237f0302137c27ef7f6bfc84379b84cbb7ec","title":"Implement hash ID generation in CreateIssue","description":"Replace sequential ID generation with hash-based IDs in CreateIssue function.\n\n## Current Behavior (internal/storage/sqlite/sqlite.go)\n```go\nfunc (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue) error {\n // ID comes from auto-increment counter\n // Collisions possible across clones\n}\n```\n\n## New Behavior\n```go\nfunc (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue) error {\n // Generate hash ID if not provided\n if issue.ID == \"\" {\n issue.ID = idgen.GenerateHashID(\n issue.Title,\n issue.Description,\n time.Now(),\n s.workspaceID,\n )\n }\n \n // Assign next alias\n issue.Alias = s.getNextAlias()\n \n // Insert with hash ID + alias\n // ...\n}\n```\n\n## Workspace ID Generation\nAdd to database initialization:\n```go\n// Generate stable workspace ID (persisted in .beads/workspace_id)\nworkspaceID := getOrCreateWorkspaceID()\n```\n\nOptions for workspace ID:\n1. Hostname + random suffix\n2. UUID (random)\n3. Git remote URL hash (deterministic per repo)\n\nRecommended: Option 3 (git remote hash) for reproducibility\n\n## Hash Collision Detection\n```go\n// On insert, check for collision (unlikely but possible)\nexisting, err := s.GetIssue(ctx, issue.ID)\nif err == nil {\n // Hash collision! Add random suffix and retry\n issue.ID = issue.ID + \"-\" + randomSuffix(4)\n}\n```\n\n## Files to Create/Modify\n- internal/types/id_generator.go (new)\n- internal/storage/sqlite/sqlite.go (CreateIssue)\n- internal/storage/sqlite/workspace.go (new - workspace ID management)\n- .beads/workspace_id (new file, git-ignored)\n\n## Testing\n- Test hash ID generation is deterministic\n- Test collision detection and retry\n- Test workspace ID persistence\n- Benchmark: hash generation performance (\u003c1μs)","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:29.412237-07:00","updated_at":"2025-10-29T21:24:29.412237-07:00","dependencies":[{"issue_id":"bd-168","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:24:29.413417-07:00","created_by":"stevey"},{"issue_id":"bd-168","depends_on_id":"bd-166","type":"blocks","created_at":"2025-10-29T21:24:29.413823-07:00","created_by":"stevey"}]} -{"id":"bd-169","content_hash":"d047b706654f9e5b0cc59fde73e88fed242932c3c024d90af22327a5c2f2fced","title":"Update JSONL format to use hash IDs","description":"Update JSONL import/export to use hash IDs, store aliases separately.\n\n## Current JSONL Format\n```jsonl\n{\"id\":\"bd-1\",\"title\":\"Fix bug\",\"status\":\"open\",...}\n{\"id\":\"bd-2\",\"title\":\"Add test\",\"status\":\"open\",...}\n```\n\n## New JSONL Format (Option A: Include Alias)\n```jsonl\n{\"id\":\"bd-af78e9a2\",\"alias\":1,\"title\":\"Fix bug\",\"status\":\"open\",...}\n{\"id\":\"bd-e5f6a7b8\",\"alias\":2,\"title\":\"Add test\",\"status\":\"open\",...}\n```\n\n## New JSONL Format (Option B: Hash ID Only)\n```jsonl\n{\"id\":\"bd-af78e9a2\",\"title\":\"Fix bug\",\"status\":\"open\",...}\n{\"id\":\"bd-e5f6a7b8\",\"title\":\"Add test\",\"status\":\"open\",...}\n```\n\nStore aliases in separate .beads/aliases.jsonl (local only, git-ignored):\n```jsonl\n{\"hash\":\"bd-af78e9a2\",\"alias\":1}\n{\"hash\":\"bd-e5f6a7b8\",\"alias\":2}\n```\n\n**Recommendation**: Option B (hash only in main JSONL)\n- Cleaner git diffs (no alias conflicts)\n- Aliases are workspace-local preference\n- Main JSONL is canonical, portable\n\n## Export Changes\nFile: cmd/bd/export.go\n```go\n// Export issues with hash IDs\nfor _, issue := range issues {\n json := marshalIssue(issue) // Uses issue.ID (hash)\n // Don't include alias in JSONL\n}\n\n// Separately export aliases to .beads/aliases.jsonl\nexportAliases(issues)\n```\n\n## Import Changes \nFile: cmd/bd/import.go, internal/importer/importer.go\n```go\n// Import issues by hash ID\nissue := unmarshalIssue(line)\n// Assign new alias on import (don't use incoming alias)\nissue.Alias = getNextAlias()\n\n// No collision detection needed! Hash IDs are globally unique\n```\n\n## Dependency Reference Format\nNo change needed - already uses issue IDs:\n```json\n{\"depends_on_id\":\"bd-af78e9a2\",\"type\":\"blocks\"}\n```\n\n## Files to Modify\n- cmd/bd/export.go (use hash IDs)\n- cmd/bd/import.go (import hash IDs, assign aliases)\n- internal/importer/importer.go (remove collision detection!)\n- .gitignore (add .beads/aliases.jsonl)\n\n## Testing\n- Test export produces hash IDs\n- Test import assigns new aliases\n- Test dependencies preserved with hash IDs\n- Test no collision detection triggered","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:47.408106-07:00","updated_at":"2025-10-29T21:24:47.408106-07:00","dependencies":[{"issue_id":"bd-169","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:24:47.409489-07:00","created_by":"stevey"},{"issue_id":"bd-169","depends_on_id":"bd-168","type":"blocks","created_at":"2025-10-29T21:24:47.409977-07:00","created_by":"stevey"},{"issue_id":"bd-169","depends_on_id":"bd-167","type":"blocks","created_at":"2025-10-29T21:29:45.975499-07:00","created_by":"stevey"}]} +{"id":"bd-167","content_hash":"160f0d68ed3104e1b6210f475c2a613f5b6bc8ef3f0e3b05af0044d9bc8d1730","title":"Add child_counters table to database schema","description":"Add child_counters table to support sequential child ID generation within parent contexts.\n\n## Schema\n```sql\nCREATE TABLE child_counters (\n parent_id TEXT PRIMARY KEY,\n last_child INTEGER NOT NULL DEFAULT 0,\n FOREIGN KEY (parent_id) REFERENCES issues(id) ON DELETE CASCADE\n);\n```\n\n## Usage\n- Counter per parent (at any depth)\n- Atomic increment: INSERT...ON CONFLICT DO UPDATE\n- bd-a3f8e9 → .1, .2, .3\n- bd-a3f8e9.1 → .1.1, .1.2, .1.3\n- Works up to 3 levels deep","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:13.968241-07:00","updated_at":"2025-10-30T00:24:02.712185-07:00","dependencies":[{"issue_id":"bd-167","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:24:13.96959-07:00","created_by":"stevey"},{"issue_id":"bd-167","depends_on_id":"bd-166","type":"blocks","created_at":"2025-10-29T21:29:45.952824-07:00","created_by":"stevey"}]} +{"id":"bd-168","content_hash":"654738b1724a427118fe47618c09ca643620389a2688262d216356220492da5f","title":"Implement hash ID generation in CreateIssue","description":"Implement hash ID generation in CreateIssue function.\n\n## For Top-Level Issues\n```go\nfunc generateHashID(prefix, title, description, creator string, timestamp time.Time) string {\n content := fmt.Sprintf(\"%s|%s|%s|%d\", title, description, creator, timestamp.UnixNano())\n hash := sha256.Sum256([]byte(content))\n shortHash := hex.EncodeToString(hash[:4]) // 8 hex chars\n return fmt.Sprintf(\"%s-%s\", prefix, shortHash)\n}\n```\n\n## For Child Issues\n```go\nfunc (s *SQLiteStorage) createChildIssue(parentID string, issue *types.Issue) error {\n // Validate parent exists and depth \u003c= 3\n childNum := s.getNextChildID(parentID)\n issue.ID = fmt.Sprintf(\"%s.%d\", parentID, childNum)\n // ... create issue\n}\n```\n\n## CLI Integration\n```bash\nbd create \"Auth System\" # → bd-a3f8e9a2\nbd create \"Login Flow\" --parent a3f8e9 # → bd-a3f8e9a2.1\nbd create \"Design UI\" --parent a3f8e9.1 # → bd-a3f8e9a2.1.1\n```\n\n## Validation\n- Reject depth \u003e 3\n- Ensure parent exists\n- Check parent is epic type (optional, for UX)","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:29.412237-07:00","updated_at":"2025-10-30T00:25:30.144476-07:00","dependencies":[{"issue_id":"bd-168","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:24:29.413417-07:00","created_by":"stevey"},{"issue_id":"bd-168","depends_on_id":"bd-166","type":"blocks","created_at":"2025-10-29T21:24:29.413823-07:00","created_by":"stevey"}]} +{"id":"bd-169","content_hash":"c27f166d842efb8caa1c104b1eaf430e430b3da84cfb13a085cc732a713faaf9","title":"Update JSONL format to use hash IDs","description":"Update JSONL format to store hierarchical hash-based IDs.\n\n## Changes\n- ID field: bd-af78e9a2 (top-level) or bd-af78e9a2.1.2 (hierarchical)\n- No alias field needed (removed from original plan)\n- All other fields remain the same\n\n## Example\n```json\n{\"id\":\"bd-af78e9a2\",\"title\":\"Auth System\",\"type\":\"epic\",...}\n{\"id\":\"bd-af78e9a2.1\",\"title\":\"Login Flow\",\"type\":\"epic\",...}\n{\"id\":\"bd-af78e9a2.1.1\",\"title\":\"Design UI\",\"type\":\"task\",...}\n```\n\n## Benefits\n- Hierarchical structure visible in JSONL\n- Git merge conflicts reduced (different IDs = different lines)\n- Easy to grep for epic children: grep \"bd-af78e9a2\\.\" issues.jsonl\n\n## Backward Compatibility\nMigration tool will convert bd-1 → bd-{hash} with mapping preserved.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:47.408106-07:00","updated_at":"2025-10-30T00:25:26.029456-07:00","dependencies":[{"issue_id":"bd-169","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:24:47.409489-07:00","created_by":"stevey"},{"issue_id":"bd-169","depends_on_id":"bd-168","type":"blocks","created_at":"2025-10-29T21:24:47.409977-07:00","created_by":"stevey"},{"issue_id":"bd-169","depends_on_id":"bd-167","type":"blocks","created_at":"2025-10-29T21:29:45.975499-07:00","created_by":"stevey"}]} {"id":"bd-17","content_hash":"404b82a19dde2fdece7eb6bb3b816db7906e81a03a5a05341ed631af7a2a8e87","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":"closed","priority":2,"issue_type":"task","created_at":"2025-10-27T20:30:19.962209-07:00","updated_at":"2025-10-28T16:07:26.103703-07:00","closed_at":"2025-10-28T16:07:26.103703-07:00","labels":["cleanup","dead-code","phase-1","rpc"],"dependencies":[{"issue_id":"bd-17","depends_on_id":"bd-26","type":"parent-child","created_at":"2025-10-27T20:30:19.965239-07:00","created_by":"daemon"}]} -{"id":"bd-170","content_hash":"0cd1edbd677f73f3a21e571709eca026db7c6e0d158a3d41d7c4e37deb8bc4c2","title":"CLI accepts both hash IDs and aliases","description":"Update all CLI commands to accept both hash IDs (bd-af78e9a2) and aliases (#42, or just 42).\n\n## Parsing Logic\n```go\n// internal/utils/id_parser.go\nfunc ParseIssueID(input string) (issueID string, err error) {\n // Hash ID: bd-af78e9a2\n if strings.HasPrefix(input, \"bd-\") {\n return input, nil\n }\n \n // Alias: #42 or 42\n aliasStr := strings.TrimPrefix(input, \"#\")\n alias, err := strconv.Atoi(aliasStr)\n if err != nil {\n return \"\", fmt.Errorf(\"invalid issue ID: %s\", input)\n }\n \n // Resolve alias to hash ID\n return storage.GetIssueIDByAlias(alias)\n}\n```\n\n## Commands to Update\nAll commands that accept issue IDs:\n\n### 1. bd show\n```bash\nbd show bd-af78e9a2 # Hash ID\nbd show #42 # Alias\nbd show 42 # Alias (shorthand)\nbd show bd-af78e9a2 #42 # Mixed (multiple IDs)\n```\n\n### 2. bd update\n```bash\nbd update #42 --status in_progress\nbd update bd-af78e9a2 --priority 1\n```\n\n### 3. bd close\n```bash\nbd close #42 --reason \"Done\"\n```\n\n### 4. bd dep add/tree\n```bash\nbd dep add #42 #1 --type blocks\nbd dep tree bd-af78e9a2\n```\n\n### 5. bd label add/remove\n```bash\nbd label add #42 critical\n```\n\n### 6. bd merge\n```bash\nbd merge #42 #43 --into #41\n```\n\n## Display Format\nDefault to showing aliases in output:\n```bash\n$ bd list\n#1 Fix authentication bug P1 open\n#2 Add logging to daemon P2 open \n#42 Investigate jujutsu integration P3 open\n```\n\nWith `--format=hash` flag:\n```bash\n$ bd list --format=hash\nbd-af78e9a2 Fix authentication bug P1 open\nbd-e5f6a7b8 Add logging to daemon P2 open\nbd-1a2b3c4d Investigate jujutsu integration P3 open\n```\n\n## Files to Modify\n- internal/utils/id_parser.go (new)\n- cmd/bd/show.go\n- cmd/bd/update.go\n- cmd/bd/close.go\n- cmd/bd/reopen.go\n- cmd/bd/dep.go\n- cmd/bd/label.go\n- cmd/bd/merge.go\n- cmd/bd/list.go (add --format flag)\n\n## Testing\n- Test hash ID parsing\n- Test alias parsing (#42, 42)\n- Test mixed IDs in single command\n- Test error on invalid ID\n- Test alias resolution failure","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:25:06.256317-07:00","updated_at":"2025-10-29T21:25:06.256317-07:00","dependencies":[{"issue_id":"bd-170","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:25:06.257796-07:00","created_by":"stevey"},{"issue_id":"bd-170","depends_on_id":"bd-167","type":"blocks","created_at":"2025-10-29T21:25:06.258307-07:00","created_by":"stevey"},{"issue_id":"bd-170","depends_on_id":"bd-169","type":"blocks","created_at":"2025-10-29T21:29:45.993274-07:00","created_by":"stevey"}]} -{"id":"bd-171","content_hash":"ce799a029bab632dd71cc6935ec0f8da8c6b49d9d766760645f7af7c7d06d60b","title":"Implement alias conflict resolution","description":"Handle alias conflicts when multiple clones assign same alias to different issues.\n\n## Scenario\n```\nClone A: Creates bd-a1b2c3d4, assigns alias #42\nClone B: Creates bd-e5f6a7b8, assigns alias #42\nAfter sync: Conflict! Which issue gets #42?\n```\n\n## Resolution Strategy: Content-Hash Ordering\nDeterministic, same result on all clones:\n```go\nfunc ResolveAliasConflicts(conflicts []AliasConflict) []AliasRemapping {\n for _, conflict := range conflicts {\n // Sort by hash ID (lexicographic)\n sort.Strings(conflict.IssueIDs)\n \n // Winner: lowest hash ID (arbitrary but deterministic)\n winner := conflict.IssueIDs[0]\n \n // Losers: reassign to next available aliases\n for _, loser := range conflict.IssueIDs[1:] {\n newAlias := getNextAlias()\n remappings = append(remappings, AliasRemapping{\n IssueID: loser,\n OldAlias: conflict.Alias,\n NewAlias: newAlias,\n })\n }\n }\n return remappings\n}\n```\n\n## Detection During Import\nFile: internal/importer/importer.go\n```go\nfunc handleAliasConflicts(imported []Issue, existing []Issue) error {\n // Build alias map from imported issues\n aliasMap := make(map[int][]string) // alias → issue IDs\n \n for _, issue := range imported {\n aliasMap[issue.Alias] = append(aliasMap[issue.Alias], issue.ID)\n }\n \n // Check against existing aliases\n for alias, importedIDs := range aliasMap {\n existingID := storage.GetIssueIDByAlias(alias)\n if existingID != \"\" {\n // Conflict! Resolve it\n allIDs := append(importedIDs, existingID)\n conflicts = append(conflicts, AliasConflict{\n Alias: alias,\n IssueIDs: allIDs,\n })\n }\n }\n \n // Resolve and apply\n remappings := ResolveAliasConflicts(conflicts)\n applyAliasRemappings(remappings)\n}\n```\n\n## Alternative Strategies (For Future Consideration)\n\n### Priority-Based\n```go\n// Higher priority keeps alias\nif issueA.Priority \u003c issueB.Priority {\n winner = issueA\n}\n```\n\n### Timestamp-Based (Last-Write-Wins)\n```go\n// Newer issue keeps alias\nif issueA.UpdatedAt.After(issueB.UpdatedAt) {\n winner = issueA\n}\n```\n\n### Manual Resolution\n```bash\nbd resolve-aliases --manual\n# Interactive prompt for each conflict\n```\n\n## User Notification\n```bash\n$ bd sync\n✓ Synced 5 issues\n⚠ Alias conflicts resolved:\n - Issue bd-e5f6a7b8: alias changed from #42 to #100\n - Issue bd-9a8b7c6d: alias changed from #15 to #101\n```\n\n## Files to Create/Modify\n- internal/storage/sqlite/alias_conflicts.go (new)\n- internal/importer/importer.go (detect conflicts)\n- cmd/bd/sync.go (show conflict notifications)\n\n## Testing\n- Test two clones assign same alias to different issues\n- Test conflict resolution is deterministic (same on all clones)\n- Test loser gets new alias\n- Test winner keeps original alias\n- Test multiple conflicts resolved in one import","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:25:27.389191-07:00","updated_at":"2025-10-29T21:25:27.389191-07:00","dependencies":[{"issue_id":"bd-171","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:25:27.390611-07:00","created_by":"stevey"},{"issue_id":"bd-171","depends_on_id":"bd-167","type":"blocks","created_at":"2025-10-29T21:25:27.391127-07:00","created_by":"stevey"},{"issue_id":"bd-171","depends_on_id":"bd-169","type":"blocks","created_at":"2025-10-29T21:25:27.39154-07:00","created_by":"stevey"}]} +{"id":"bd-170","content_hash":"9b8455d88854127ed84028062e857ea3eef2532bc22eefc4ffd31daa2a57cf70","title":"CLI accepts hash ID prefixes (Git-style)","description":"Implement Git-style prefix matching for hash IDs.\n\n## Behavior\n```bash\nbd show a3f8e9 # Matches bd-a3f8e9... if unique\nbd show a3f8 # Use fewer chars if unique\nbd show a3f8e9.2 # Full hierarchical ID with prefix\n```\n\n## Error Handling\n- Not found: \"no issue found matching 'xyz'\"\n- Ambiguous: \"ambiguous ID 'a3' matches: bd-a3f8e9, bd-a34721\"\n- Show all matches and ask user to provide more characters\n\n## Implementation\n- Parse user input to extract hash prefix and child components\n- Query: SELECT * FROM issues WHERE id LIKE 'bd-{prefix}%'\n- If hierarchical (.1.2), match full pattern\n- Return error if 0 or 2+ matches","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:25:06.256317-07:00","updated_at":"2025-10-30T00:24:04.087092-07:00","dependencies":[{"issue_id":"bd-170","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:25:06.257796-07:00","created_by":"stevey"},{"issue_id":"bd-170","depends_on_id":"bd-167","type":"blocks","created_at":"2025-10-29T21:25:06.258307-07:00","created_by":"stevey"},{"issue_id":"bd-170","depends_on_id":"bd-169","type":"blocks","created_at":"2025-10-29T21:29:45.993274-07:00","created_by":"stevey"}]} +{"id":"bd-171","content_hash":"2a102864134b5192b5ee4e2a773cb4860b4330c9f3242b094ce8e92b01d20d80","title":"Implement hierarchical child ID generation","description":"Implement sequential child ID generation within parent contexts.\n\n## Function Signature\n```go\nfunc (s *SQLiteStorage) getNextChildID(ctx context.Context, parentID string) (string, error)\n```\n\n## Logic\n1. Insert or update child_counters for parent_id\n2. Return incremented counter\n3. Format as parentID.{counter}\n4. Works at any depth (bd-a3f8e9.1 → bd-a3f8e9.1.5)\n\n## Collision Handling\n- In single-player mode: No collisions (sequential)\n- In multi-player mode (future): Rare collisions, manual resolution needed\n- Epic ownership makes collisions naturally rare\n\n## Integration\n- Called from CreateIssue when --parent flag is used\n- Validates parent exists and depth \u003c= 3","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:25:27.389191-07:00","updated_at":"2025-10-30T00:24:05.531466-07:00","dependencies":[{"issue_id":"bd-171","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:25:27.390611-07:00","created_by":"stevey"},{"issue_id":"bd-171","depends_on_id":"bd-167","type":"blocks","created_at":"2025-10-29T21:25:27.391127-07:00","created_by":"stevey"},{"issue_id":"bd-171","depends_on_id":"bd-169","type":"blocks","created_at":"2025-10-29T21:25:27.39154-07:00","created_by":"stevey"}]} {"id":"bd-172","content_hash":"cb24777a804129f91ae8d96937c762ba4877e2a0273d389d099f678ed2080a54","title":"Delete collision resolution code","description":"Remove ~2,100 LOC of ID collision detection and resolution code (no longer needed with hash IDs).\n\n## Files to Delete Entirely\n```\ninternal/storage/sqlite/collision.go (~800 LOC)\ninternal/storage/sqlite/collision_test.go (~300 LOC)\ncmd/bd/autoimport_collision_test.go (~400 LOC)\n```\n\n## Code to Remove from Existing Files\n\n### internal/importer/importer.go\nRemove:\n- `DetectCollisions()` calls\n- `ScoreCollisions()` logic\n- `RemapCollisions()` calls\n- `handleRename()` function\n- All collision-related error handling\n\nKeep:\n- Basic import logic\n- Exact match detection (idempotent import)\n\n### beads_twoclone_test.go\nRemove:\n- `TestTwoCloneCollision` (bd-86)\n- `TestThreeCloneCollision` (bd-185)\n- `TestFiveCloneCollision` (bd-151)\n- All N-way collision tests\n\n### cmd/bd/import.go\nRemove:\n- `--resolve-collisions` flag\n- `--dry-run` collision preview\n- Collision reporting\n\n## Issues Closed by This Change\n- bd-86: Add test for symmetric collision\n--89: Content-hash collision resolution\n- bd-185: N-way collision resolution epic\n- bd-95: Add ScoreCollisions (already done but now unnecessary)\n- bd-96: Make DetectCollisions read-only\n- bd-97: ResolveNWayCollisions function\n- bd-98: Multi-round import convergence\n- bd-108: Multi-round convergence for N-way collisions\n- bd-109: Transaction + retry logic for collisions\n- bd-160: Test case for symmetric collision\n\n## Verification Steps\n1. `grep -r \"collision\" --include=\"*.go\"` → should only find alias conflicts\n2. `go test ./...` → all tests pass\n3. `go build ./cmd/bd` → clean build\n4. Check LOC reduction: `git diff --stat`\n\n## Expected Metrics\n- **Files deleted**: 3\n- **LOC removed**: ~2,100\n- **Test coverage**: Should increase (less untested code)\n- **Binary size**: Slightly smaller\n\n## Caution\nDo NOT delete:\n- Alias conflict resolution (new code in bd-171)\n- Duplicate detection (bd-59, bd-149) - different from ID collisions\n- Merge conflict resolution (bd-65, bd-103) - git conflicts, not ID collisions\n\n## Files to Modify\n- internal/importer/importer.go (remove collision handling)\n- cmd/bd/import.go (remove --resolve-collisions flag)\n- beads_twoclone_test.go (remove collision tests)\n- Delete: internal/storage/sqlite/collision.go\n- Delete: internal/storage/sqlite/collision_test.go \n- Delete: cmd/bd/autoimport_collision_test.go\n\n## Testing\n- Ensure all remaining tests pass\n- Manual test: create issue on two clones, sync → no collisions\n- Verify error if somehow hash collision occurs (extremely unlikely)","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:25:50.976383-07:00","updated_at":"2025-10-29T23:14:44.171339-07:00","dependencies":[{"issue_id":"bd-172","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:25:50.977857-07:00","created_by":"stevey"},{"issue_id":"bd-172","depends_on_id":"bd-168","type":"blocks","created_at":"2025-10-29T21:25:50.978395-07:00","created_by":"stevey"},{"issue_id":"bd-172","depends_on_id":"bd-169","type":"blocks","created_at":"2025-10-29T21:25:50.978842-07:00","created_by":"stevey"}]} -{"id":"bd-173","content_hash":"5c3a805d030dbbcc44e33d28c39d6acb98086ea26ab7f5405c0138e29b1d58dd","title":"Migration tool: sequential → hash IDs","description":"Create migration tool to convert existing sequential-ID databases to hash-ID format.\n\n## Command: bd migrate --hash-ids\n\n```bash\nbd migrate --hash-ids [--dry-run]\n```\n\n## Migration Process\n\n### 1. Backup Database\n```bash\ncp .beads/beads.db .beads/beads.db.backup-1761798384\necho \"✓ Backup created: .beads/beads.db.backup-1234567890\"\n```\n\n### 2. Generate Hash IDs for Existing Issues\n```go\nfunc migrateToHashIDs(db *SQLiteStorage) error {\n // Read all issues\n issues, err := db.ListIssues(ctx, ListOptions{Status: \"all\"})\n \n // Generate mapping: old ID → new hash ID\n mapping := make(map[string]string)\n for _, issue := range issues {\n hashID := GenerateHashID(\n issue.Title,\n issue.Description,\n issue.CreatedAt,\n db.workspaceID,\n )\n mapping[issue.ID] = hashID\n }\n \n // Detect hash collisions (extremely rare)\n if hasCollisions(mapping) {\n return fmt.Errorf(\"hash collision detected, aborting\")\n }\n \n return nil\n}\n```\n\n### 3. Update Database Schema\n```sql\n-- Add alias column\nALTER TABLE issues ADD COLUMN alias INTEGER UNIQUE;\n\n-- Populate aliases from old IDs\nUPDATE issues SET alias = CAST(SUBSTR(id, 4) AS INTEGER)\n WHERE id LIKE 'bd-%' AND SUBSTR(id, 4) GLOB '[0-9]*';\n\n-- Create new issues_new table with hash IDs\nCREATE TABLE issues_new (\n id TEXT PRIMARY KEY, -- Hash IDs now\n alias INTEGER UNIQUE,\n title TEXT NOT NULL,\n -- ... rest of schema\n);\n\n-- Copy data with ID mapping\nINSERT INTO issues_new SELECT \n \u003cnew_hash_id\u003e, -- From mapping\n alias,\n title,\n -- ...\nFROM issues;\n\n-- Drop old table, rename new\nDROP TABLE issues;\nALTER TABLE issues_new RENAME TO issues;\n```\n\n### 4. Update Dependencies\n```sql\n-- Update depends_on_id using mapping\nUPDATE dependencies \nSET issue_id = \u003cnew_hash_id\u003e,\n depends_on_id = \u003cnew_depends_on_hash_id\u003e\nFROM mapping;\n```\n\n### 5. Update Text References\n```go\n// Update all text fields that mention old IDs\nfunc updateTextReferences(db *SQLiteStorage, mapping map[string]string) error {\n for oldID, newID := range mapping {\n // Update description, notes, design, acceptance_criteria\n db.Exec(`UPDATE issues SET \n description = REPLACE(description, ?, ?),\n notes = REPLACE(notes, ?, ?),\n design = REPLACE(design, ?, ?),\n acceptance_criteria = REPLACE(acceptance_criteria, ?, ?)\n `, oldID, newID, oldID, newID, oldID, newID, oldID, newID)\n }\n}\n```\n\n### 6. Export to JSONL\n```bash\nbd export # Writes hash IDs to .beads/issues.jsonl\ngit add .beads/issues.jsonl\ngit commit -m \"Migrate to hash-based IDs\"\n```\n\n## Output\n```bash\n$ bd migrate --hash-ids\nMigrating to hash-based IDs...\n✓ Backup created: .beads/beads.db.backup-1730246400\n✓ Generated 150 hash IDs\n✓ No hash collisions detected\n✓ Updated issues table schema\n✓ Updated 150 issue IDs\n✓ Updated 87 dependencies\n✓ Updated 234 text references\n✓ Exported to .beads/issues.jsonl\n✓ Migration complete!\n\nNext steps:\n 1. Test: bd list, bd show #1, etc.\n 2. Commit: git commit -m \"Migrate to hash-based IDs\"\n 3. Push: git push origin main\n 4. Notify collaborators to pull and re-init\n```\n\n## Dry Run Mode\n```bash\n$ bd migrate --hash-ids --dry-run\n[DRY RUN] Would migrate 150 issues:\n bd-1 → bd-af78e9a2 (alias: #1)\n bd-2 → bd-e5f6a7b8 (alias: #2)\n ...\n bd-150 → bd-9a8b7c6d (alias: #150)\n\n[DRY RUN] Would update:\n - 150 issue IDs\n - 87 dependencies\n - 234 text references in descriptions/notes\n\nNo changes made. Run without --dry-run to apply.\n```\n\n## Files to Create\n- cmd/bd/migrate.go (new command)\n- internal/storage/sqlite/migrations/hash_ids.go\n\n## Testing\n- Test migration on small database (10 issues)\n- Test migration on large database (1000 issues)\n- Test hash collision detection (inject collision artificially)\n- Test text reference updates\n- Test rollback (restore from backup)\n- Test migrated database works correctly\n\n## Rollback Procedure\n```bash\n# If migration fails or has issues\nmv .beads/beads.db.backup-1234567890 .beads/beads.db\nbd export # Restore JSONL from backup DB\n```\n\n## Multi-Clone Coordination\n**Important**: All clones must migrate before syncing:\n\n1. Coordinator sends message: \"Migrating to hash IDs on 2025-10-30 at 10:00 UTC\"\n2. All collaborators pull latest changes\n3. All run: `bd migrate --hash-ids`\n4. All push changes\n5. New work can continue with hash IDs\n\n**Do NOT**:\n- Mix sequential and hash IDs in same database\n- Sync before all clones migrate","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:26:24.563993-07:00","updated_at":"2025-10-29T21:26:24.563993-07:00","dependencies":[{"issue_id":"bd-173","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:26:24.565325-07:00","created_by":"stevey"},{"issue_id":"bd-173","depends_on_id":"bd-168","type":"blocks","created_at":"2025-10-29T21:26:24.565945-07:00","created_by":"stevey"}]} -{"id":"bd-174","content_hash":"190b3d6ce773ffbd7240de9617cadceee4803e0484a1970f12782b2424529766","title":"Add bd alias command for manual alias control","description":"Add command for users to manually view and reassign aliases.\n\n## Command: bd alias\n\n### Subcommands\n\n#### 1. bd alias list\nShow all alias mappings:\n```bash\n$ bd alias list\n#1 → bd-af78e9a2 Fix authentication bug\n#2 → bd-e5f6a7b8 Add logging to daemon\n#42 → bd-1a2b3c4d Investigate jujutsu integration\n#100 → bd-9a8b7c6d (reassigned after conflict)\n```\n\n#### 2. bd alias set \u003calias\u003e \u003chash-id\u003e\nManually assign alias to specific issue:\n```bash\n$ bd alias set 42 bd-1a2b3c4d\n✓ Assigned alias #42 to bd-1a2b3c4d\n\n$ bd alias set 1 bd-af78e9a2\n✗ Error: Alias #1 already assigned to bd-e5f6a7b8\nUse --force to override\n```\n\n#### 3. bd alias compact\nRenumber all aliases to fill gaps:\n```bash\n$ bd alias compact [--dry-run]\n\nCurrent aliases: #1, #2, #5, #7, #100, #101\nAfter compacting: #1, #2, #3, #4, #5, #6\n\nRenumbering:\n #5 → #3\n #7 → #4\n #100 → #5\n #101 → #6\n\nApply changes? [y/N]\n```\n\n#### 4. bd alias reset\nRegenerate all aliases (sequential from 1):\n```bash\n$ bd alias reset [--sort-by=priority|created|id]\n\nWARNING: This will reassign ALL aliases. Continue? [y/N]\n\nReassigning 150 issues by priority:\n bd-a1b2c3d4 → #1 (P0: Critical security bug)\n bd-e5f6a7b8 → #2 (P0: Data loss fix)\n bd-1a2b3c4d → #3 (P1: Jujutsu integration)\n ...\n```\n\n#### 5. bd alias find \u003chash-id\u003e\nLook up alias for hash ID:\n```bash\n$ bd alias find bd-af78e9a2\n#1\n\n$ bd alias find bd-nonexistent\n✗ Error: Issue not found\n```\n\n## Use Cases\n\n### 1. Keep Important Issues Low-Numbered\n```bash\n# After closing many P0 issues, compact to free low numbers\nbd alias compact\n\n# Or manually set\nbd alias set 1 bd-\u003ccritical-bug-hash\u003e\n```\n\n### 2. Consistent Aliases Across Clones\n```bash\n# After migration, coordinator assigns canonical aliases\nbd alias reset --sort-by=id\ngit add .beads/aliases.jsonl\ngit commit -m \"Canonical alias assignments\"\ngit push\n\n# Other clones pull and adopt\ngit pull\nbd import # Alias conflicts resolved automatically\n```\n\n### 3. Debug Alias Conflicts\n```bash\n# See which aliases were reassigned\nbd alias list | grep \"#100\"\n```\n\n## Flags\n\n### Global\n- `--dry-run`: Preview changes without applying\n- `--force`: Override existing alias assignments\n\n### bd alias reset\n- `--sort-by=priority`: Assign by priority (P0 first)\n- `--sort-by=created`: Assign by creation time (oldest first)\n- `--sort-by=id`: Assign by hash ID (lexicographic)\n\n## Implementation\n\nFile: cmd/bd/alias.go\n```go\nfunc aliasListCmd() *cobra.Command {\n return \u0026cobra.Command{\n Use: \"list\",\n Short: \"List all alias mappings\",\n Run: func(cmd *cobra.Command, args []string) {\n aliases := storage.GetAllAliases()\n for _, a := range aliases {\n fmt.Printf(\"#%-4d → %s %s\\n\", \n a.Alias, a.IssueID, a.Title)\n }\n },\n }\n}\n\nfunc aliasSetCmd() *cobra.Command {\n return \u0026cobra.Command{\n Use: \"set \u003calias\u003e \u003chash-id\u003e\",\n Short: \"Manually assign alias to issue\",\n Args: cobra.ExactArgs(2),\n Run: func(cmd *cobra.Command, args []string) {\n alias, _ := strconv.Atoi(args[0])\n hashID := args[1]\n \n force, _ := cmd.Flags().GetBool(\"force\")\n if err := storage.SetAlias(alias, hashID, force); err != nil {\n fmt.Fprintf(os.Stderr, \"Error: %v\\n\", err)\n os.Exit(1)\n }\n fmt.Printf(\"✓ Assigned alias #%d to %s\\n\", alias, hashID)\n },\n }\n}\n```\n\n## Files to Create\n- cmd/bd/alias.go\n\n## Testing\n- Test alias list output\n- Test alias set with/without force\n- Test alias compact removes gaps\n- Test alias reset with different sort orders\n- Test alias find lookup","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-29T21:26:53.751795-07:00","updated_at":"2025-10-29T21:26:53.751795-07:00","dependencies":[{"issue_id":"bd-174","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:26:53.753259-07:00","created_by":"stevey"},{"issue_id":"bd-174","depends_on_id":"bd-170","type":"blocks","created_at":"2025-10-29T21:26:53.753733-07:00","created_by":"stevey"},{"issue_id":"bd-174","depends_on_id":"bd-171","type":"blocks","created_at":"2025-10-29T21:26:53.754112-07:00","created_by":"stevey"}]} +{"id":"bd-173","content_hash":"9d78f9471bf147696d5295cc89324d3486feb4bbe16c6e89524320fab229bcd1","title":"Migration tool: sequential → hash IDs","description":"Create migration tool to convert sequential IDs to hierarchical hash-based IDs.\n\n## Command\n```bash\nbd migrate --to-hash-ids [--dry-run]\n```\n\n## Process\n1. For each top-level issue (no parent):\n - Generate hash ID from content\n - Create mapping: bd-1 → bd-a3f8e9a2\n \n2. For each child issue (has parent):\n - Find parent's new hash ID\n - Assign sequential child number based on creation order\n - bd-5 (parent: bd-1) → bd-a3f8e9a2.1\n \n3. Update all references:\n - Dependencies (blocks, parent-child)\n - Comments (issue_id foreign keys)\n - External refs (if containing old IDs)\n\n4. Preserve:\n - Creation timestamps\n - All content\n - All relationships\n - History in comments\n\n## Output\n- Mapping file: old_id → new_id (for reference)\n- Updated JSONL with new IDs\n- Migration log\n\n## Validation\n- Verify all relationships intact\n- Check no orphaned issues\n- Confirm total count unchanged\n- Test rollback procedure\n\n## Safety\n- Backup database before migration\n- Dry-run mode shows what would change\n- Rollback script provided","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:26:24.563993-07:00","updated_at":"2025-10-30T00:26:03.862157-07:00","dependencies":[{"issue_id":"bd-173","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:26:24.565325-07:00","created_by":"stevey"},{"issue_id":"bd-173","depends_on_id":"bd-168","type":"blocks","created_at":"2025-10-29T21:26:24.565945-07:00","created_by":"stevey"}]} +{"id":"bd-174","content_hash":"07d57a6c273c712250bbb96ca4db01c0845b4aa054c879f023c25e4e1fd48789","title":"Add hierarchy visualization commands","description":"Add commands to visualize and navigate hierarchical issue structures.\n\n## Commands\n\n### bd tree \u003cid\u003e\nShow hierarchical tree view:\n```\nbd tree a3f8e9\n\nbd-a3f8e9 [epic] Auth System\n├─ bd-a3f8e9.1 [epic] Login Flow\n│ ├─ bd-a3f8e9.1.1 [task] Design login UI ✓\n│ ├─ bd-a3f8e9.1.2 [task] Backend validation (in progress)\n│ └─ bd-a3f8e9.1.3 [task] Integration tests\n├─ bd-a3f8e9.2 [epic] Password Reset ✓\n└─ bd-a3f8e9.3 [task] Update documentation\n```\n\n### bd stats \u003cid\u003e --recursive\nShow progress statistics:\n```\nAuth System (bd-a3f8e9): 7/27 complete (25%)\n Login Flow: 2/3 complete (67%)\n Password Reset: 3/3 complete (100%) ✓\n Documentation: 2/21 complete (10%)\n```\n\n### bd list \u003cid\u003e --leaves\nShow only leaf nodes (actual work):\n```\nbd list a3f8e9 --leaves\n\nbd-a3f8e9.1.1 [task] Design login UI\nbd-a3f8e9.1.2 [task] Backend validation\n...\n```\n\n## Sort Order\n- Implement numeric comparison of ID components\n- Ensure bd-a3f8e9.10 comes after bd-a3f8e9.9 (not lexicographic)","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-29T21:26:53.751795-07:00","updated_at":"2025-10-30T00:25:24.186868-07:00","dependencies":[{"issue_id":"bd-174","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:26:53.753259-07:00","created_by":"stevey"},{"issue_id":"bd-174","depends_on_id":"bd-170","type":"blocks","created_at":"2025-10-29T21:26:53.753733-07:00","created_by":"stevey"},{"issue_id":"bd-174","depends_on_id":"bd-171","type":"blocks","created_at":"2025-10-29T21:26:53.754112-07:00","created_by":"stevey"}]} {"id":"bd-175","content_hash":"56929e57c09610ede74cd5d6f9e8dfa71c74412183cc53e646f72a2324025ad0","title":"Test: N-clone scenario with hash IDs (no collisions)","description":"Comprehensive test to verify hash IDs eliminate collision problems.\n\n## Test: TestHashIDsNClones\n\n### Purpose\nVerify that N clones can work offline and sync without ID collisions using hash IDs.\n\n### Test Scenario\n```\nSetup:\n- 1 bare remote repo\n- 5 clones (A, B, C, D, E)\n\nOffline Work:\n- Each clone creates 10 issues with different titles\n- No coordination, no network access\n- Total: 50 unique issues\n\nSync:\n- Clones sync in random order\n- Each pull/import other clones' issues\n\nExpected Result:\n- All 5 clones converge to 50 issues\n- Zero ID collisions\n- Zero remapping needed\n- Alias conflicts resolved deterministically\n```\n\n### Implementation\nFile: cmd/bd/beads_hashid_test.go (new)\n\n```go\nfunc TestHashIDsFiveClones(t *testing.T) {\n tmpDir := t.TempDir()\n remoteDir := setupBareRepo(t, tmpDir)\n \n // Setup 5 clones\n clones := make(map[string]string)\n for _, name := range []string{\"A\", \"B\", \"C\", \"D\", \"E\"} {\n clones[name] = setupClone(t, tmpDir, remoteDir, name)\n }\n \n // Each clone creates 10 issues offline\n for name, dir := range clones {\n for i := 0; i \u003c 10; i++ {\n createIssue(t, dir, fmt.Sprintf(\"%s-issue-%d\", name, i))\n }\n // No sync yet!\n }\n \n // Sync in random order\n syncOrder := []string{\"C\", \"A\", \"E\", \"B\", \"D\"}\n for _, name := range syncOrder {\n syncClone(t, clones[name], name)\n }\n \n // Final convergence round\n for _, name := range []string{\"A\", \"B\", \"C\", \"D\", \"E\"} {\n finalPull(t, clones[name], name)\n }\n \n // Verify all clones have all 50 issues\n for name, dir := range clones {\n issues := getIssues(t, dir)\n if len(issues) != 50 {\n t.Errorf(\"Clone %s: expected 50 issues, got %d\", name, len(issues))\n }\n \n // Verify all issue IDs are hash-based\n for _, issue := range issues {\n if !strings.HasPrefix(issue.ID, \"bd-\") || len(issue.ID) != 11 {\n t.Errorf(\"Invalid hash ID: %s\", issue.ID)\n }\n }\n }\n \n // Verify no collision resolution occurred\n // (This would be in logs if it happened)\n \n t.Log(\"✓ All 5 clones converged to 50 issues with zero collisions\")\n}\n```\n\n### Edge Case Tests\n\n#### Test: Hash Collision Detection (Artificial)\n```go\nfunc TestHashCollisionDetection(t *testing.T) {\n // Artificially inject collision by mocking hash function\n // Verify system detects and handles it\n}\n```\n\n#### Test: Alias Conflicts Resolved Deterministically\n```go\nfunc TestAliasConflictsNClones(t *testing.T) {\n // Two clones assign same alias to different issues\n // Verify deterministic resolution (content-hash ordering)\n // Verify all clones converge to same alias assignments\n}\n```\n\n#### Test: Mixed Sequential and Hash IDs (Should Fail)\n```go\nfunc TestMixedIDsRejected(t *testing.T) {\n // Try to import JSONL with sequential IDs into hash-ID database\n // Verify error or warning\n}\n```\n\n### Performance Test\n\n#### Benchmark: Hash ID Generation\n```go\nfunc BenchmarkHashIDGeneration(b *testing.B) {\n for i := 0; i \u003c b.N; i++ {\n GenerateHashID(\"title\", \"description\", time.Now(), \"workspace-id\")\n }\n}\n\n// Expected: \u003c 1μs per generation\n```\n\n#### Benchmark: N-Clone Convergence Time\n```go\nfunc BenchmarkNCloneConvergence(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 // Measure total convergence time\n })\n }\n}\n\n// Expected: Linear scaling O(N)\n```\n\n### Acceptance Criteria\n- TestHashIDsFiveClones passes reliably (10/10 runs)\n- Zero ID collisions in any scenario\n- All clones converge in single round (not multi-round like old system)\n- Alias conflicts resolved deterministically\n- Performance benchmarks meet targets (\u003c1μs hash gen)\n\n## Files to Create\n- cmd/bd/beads_hashid_test.go\n\n## Comparison to Old System\nThis test replaces:\n- TestTwoCloneCollision (bd-86) - no longer needed\n- TestThreeCloneCollision (bd-185) - no longer needed\n- TestFiveCloneCollision (bd-151) - no longer needed\n\nOld system required complex collision resolution and multi-round convergence.\nNew system: single-round convergence with zero collisions.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:27:26.954107-07:00","updated_at":"2025-10-29T23:05:13.897026-07:00","dependencies":[{"issue_id":"bd-175","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:27:26.955522-07:00","created_by":"stevey"},{"issue_id":"bd-175","depends_on_id":"bd-172","type":"blocks","created_at":"2025-10-29T21:27:26.956175-07:00","created_by":"stevey"}]} -{"id":"bd-176","content_hash":"4b63fd2a65d45bfabe57dfe896015343682813978c76071c1cb1df747659fd2c","title":"Update documentation for hash IDs and aliases","description":"Update all documentation to explain hash-based IDs and aliasing system.\n\n## Files to Update\n\n### 1. README.md\nAdd section explaining hash IDs:\n```markdown\n## Issue IDs\n\nBeads uses **hash-based IDs** for collision-free distributed issue tracking:\n\n- **Hash ID**: `bd-af78e9a2` (8-char SHA256 prefix, immutable, globally unique)\n- **Alias**: `#42` (sequential number, mutable, human-friendly)\n\n### Using IDs\n```bash\nbd show bd-af78e9a2 # Use hash ID\nbd show #42 # Use alias\nbd show 42 # Use alias (shorthand)\n```\n\n### Why Hash IDs?\n- **Collision-free**: Work offline without ID conflicts\n- **Distributed**: No coordination needed between clones\n- **Git-friendly**: Different IDs = different JSONL lines, fewer merge conflicts\n\n### Aliases\nAliases are workspace-local shortcuts for hash IDs. They're:\n- Automatically assigned on issue creation\n- Reassigned deterministically on sync (if conflicts)\n- Can be manually controlled with `bd alias` commands\n```\n\n### 2. AGENTS.md\nUpdate agent workflow:\n```markdown\n## Hash-Based IDs (v2.0+)\n\nBeads v2.0 uses hash-based IDs to eliminate collision problems:\n\n**When creating issues**:\n```bash\nbd create \"Fix bug\" -p 1\n# → Creates bd-af78e9a2 with alias #1\n```\n\n**When referencing issues**:\n- In text: Use hash IDs (stable): \"See bd-af78e9a2 for details\"\n- In CLI: Use aliases (readable): `bd update #42 --status done`\n\n**After sync**:\n- Alias conflicts resolved automatically (content-hash ordering)\n- No ID collisions possible\n- No remapping needed\n\n**Migration from v1.x**:\n```bash\nbd migrate --hash-ids # One-time migration\n```\n```\n\n### 3. QUICKSTART.md (if exists)\nShow alias usage in examples:\n```bash\n# Create issue (gets hash ID + alias)\nbd create \"Fix authentication bug\" -p 1\n# → Created bd-af78e9a2 (alias: #1)\n\n# Reference by alias\nbd show #1\nbd update #1 --status in_progress\nbd close #1 --reason \"Fixed\"\n```\n\n### 4. ADVANCED.md\nAdd section on hash ID internals:\n```markdown\n## Hash ID Generation\n\nHash IDs are generated deterministically:\n\n```go\nSHA256(title || description || timestamp || workspace_id)[:8]\n```\n\n**Collision probability**:\n- 8 hex chars = 2^32 space = ~4 billion IDs\n- Birthday paradox: 50% collision probability at ~65,000 issues\n- For typical projects (\u003c10,000 issues), collision risk is negligible\n\n**Collision detection**:\nIf a hash collision occurs (extremely rare), beads:\n1. Detects on insert (UNIQUE constraint)\n2. Appends random suffix: `bd-af78e9a2-a1b2`\n3. Retries insert\n\n## Alias Conflict Resolution\n\nWhen multiple clones assign same alias to different issues:\n\n**Strategy**: Content-hash ordering (deterministic)\n- Sort conflicting issue IDs lexicographically\n- Lowest hash ID keeps the alias\n- Others reassigned to next available aliases\n\n**Example**:\n```\nClone A: Assigns #42 to bd-a1b2c3d4\nClone B: Assigns #42 to bd-e5f6a7b8\nAfter sync: bd-a1b2c3d4 keeps #42 (lower hash)\n bd-e5f6a7b8 gets #100 (next available)\n```\n```\n\n### 5. MIGRATION.md (new file)\n```markdown\n# Migrating to Hash-Based IDs (v2.0)\n\n## Overview\nBeads v2.0 introduces hash-based IDs to eliminate collision problems. This is a **breaking change** requiring migration.\n\n## Migration Steps\n\n### 1. Backup\n```bash\ncp -r .beads .beads.backup\ngit commit -am \"Pre-migration backup\"\n```\n\n### 2. Run Migration\n```bash\n# Dry run first\nbd migrate --hash-ids --dry-run\n\n# Apply migration\nbd migrate --hash-ids\n```\n\n### 3. Commit Changes\n```bash\ngit add .beads/issues.jsonl\ngit commit -m \"Migrate to hash-based IDs (v2.0)\"\ngit push origin main\n```\n\n### 4. Coordinate with Collaborators\nAll clones must migrate before syncing:\n1. Notify team: \"Migrating to v2.0 on [date]\"\n2. All collaborators pull latest\n3. All run `bd migrate --hash-ids`\n4. All push changes\n5. Resume normal work\n\n## Rollback\n```bash\n# Restore backup\nmv .beads.backup .beads\nbd export # Regenerate JSONL\ngit checkout .beads/issues.jsonl\n```\n\n## FAQ\n\n**Q: Can I mix v1.x and v2.0 clones?**\nA: No. All clones must be on same version.\n\n**Q: Will my old issue IDs work?**\nA: No, but aliases preserve the numbers: bd-1 → #1\n\n**Q: What happens to links like \"see bd-42\"?**\nA: Migration updates all text references automatically.\n```\n\n### 6. CHANGELOG.md\n```markdown\n## v2.0.0 (YYYY-MM-DD)\n\n### Breaking Changes\n- **Hash-based IDs**: Issues now use collision-free hash IDs (bd-af78e9a2)\n instead of sequential IDs (bd-1, bd-2)\n- **Aliasing system**: Human-friendly aliases (#42) for hash IDs\n- **Migration required**: Run `bd migrate --hash-ids` to convert v1.x databases\n\n### Added\n- `bd alias` command for manual alias control\n- `bd migrate --hash-ids` migration tool\n- Alias conflict resolution (deterministic, content-hash ordering)\n\n### Removed\n- ID collision detection and resolution (~2,100 LOC)\n- `bd import --resolve-collisions` flag (no longer needed)\n\n### Benefits\n- ✅ Zero ID collisions in distributed workflows\n- ✅ Simpler codebase (-1,350 net LOC)\n- ✅ Better git merge behavior\n- ✅ True offline-first operation\n```\n\n## Testing\n- Build docs locally (if using doc generator)\n- Check all links work\n- Verify examples are correct\n- Spellcheck\n\n## Files to Create/Modify\n- README.md (hash ID section)\n- AGENTS.md (workflow updates)\n- ADVANCED.md (internals)\n- MIGRATION.md (new)\n- CHANGELOG.md (v2.0 entry)\n- docs/ (any other docs)","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-29T21:28:10.979971-07:00","updated_at":"2025-10-29T21:28:10.979971-07:00","dependencies":[{"issue_id":"bd-176","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:28:10.981344-07:00","created_by":"stevey"},{"issue_id":"bd-176","depends_on_id":"bd-173","type":"blocks","created_at":"2025-10-29T21:28:10.981767-07:00","created_by":"stevey"},{"issue_id":"bd-176","depends_on_id":"bd-174","type":"blocks","created_at":"2025-10-29T21:28:10.982167-07:00","created_by":"stevey"}]} +{"id":"bd-176","content_hash":"1c44b9918f43a4c29fa73326e9dedb27015bc1ebae27ff72e7ba3967a0a8ddf4","title":"Update documentation for hash IDs and aliases","description":"Update documentation for hash-based hierarchical ID system.\n\n## Files to Update\n- README.md: Quick example of hash IDs and hierarchical children\n- QUICKSTART.md: Show bd create with --parent flag\n- commands/create.md: Document --parent flag and depth limits\n- AGENTS.md: Update examples to use hash ID format\n- FAQ.md: Add \"Why hash IDs?\" section\n\n## Topics to Cover\n### Hash IDs\n- Why content-based hashing?\n- Collision-free guarantees\n- Git-style prefix matching\n- Example: bd show a3f8e9\n\n### Hierarchical Children\n- Epic → child tasks with sequential IDs\n- Up to 3 levels deep\n- Natural work breakdown structure\n- Example: bd-a3f8e9.1.2\n\n### Migration\n- How to migrate from sequential IDs\n- Backward compatibility (old IDs in comments/docs)\n- Timeline and breaking change notice\n\n### Best Practices\n- When to use nested epics vs flat tasks\n- Epic ownership for collision avoidance\n- Using bd tree for visualization\n- Querying hierarchies\n\n## Examples\nInclude real-world examples:\n- Small project: 1-level hierarchy (epic → tasks)\n- Large project: 2-level (epic → sub-epics → tasks)\n- Complex: 3-level (epic → features → stories → tasks)","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-29T21:28:10.979971-07:00","updated_at":"2025-10-30T00:25:55.25486-07:00","dependencies":[{"issue_id":"bd-176","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:28:10.981344-07:00","created_by":"stevey"},{"issue_id":"bd-176","depends_on_id":"bd-173","type":"blocks","created_at":"2025-10-29T21:28:10.981767-07:00","created_by":"stevey"},{"issue_id":"bd-176","depends_on_id":"bd-174","type":"blocks","created_at":"2025-10-29T21:28:10.982167-07:00","created_by":"stevey"}]} {"id":"bd-177","content_hash":"cf15dbd5b7dbc10b54ea8d0d173899983bcdc0775b799e77c9334ec3f9fcba14","title":"Update MCP server for hash IDs","description":"Update beads-mcp server to support hash IDs and aliases.\n\n## Changes Needed\n\n### 1. MCP Function Signatures (No Change)\nFunctions already use issue IDs as strings, so they work with hash IDs:\n\n```python\n# These already work!\nbeads_show(issue_id: str) # Accepts bd-af78e9a2 or #42\nbeads_update(issue_id: str, ...) # Accepts both formats\nbeads_close(issue_ids: List[str]) # Accepts both formats\n```\n\n### 2. Add Alias Resolution Helper\nFile: integrations/beads-mcp/src/beads_mcp/server.py\n\n```python\ndef resolve_issue_id(issue_id: str) -\u003e str:\n \"\"\"Resolve alias to hash ID if needed.\"\"\"\n # Hash ID: pass through\n if issue_id.startswith('bd-') and len(issue_id) == 11:\n return issue_id\n \n # Alias: #42 or 42\n alias_str = issue_id.lstrip('#')\n try:\n alias = int(alias_str)\n # Call bd to resolve\n result = subprocess.run(\n ['bd', 'alias', 'find', f'bd-{alias}'],\n capture_output=True, text=True\n )\n if result.returncode == 0:\n return result.stdout.strip()\n except ValueError:\n pass\n \n # Invalid format\n raise ValueError(f\"Invalid issue ID: {issue_id}\")\n```\n\n### 3. Update Response Formatting\nShow aliases in responses:\n\n```python\n@server.call_tool()\nasync def beads_show(issue_id: str) -\u003e List[TextContent]:\n resolved_id = resolve_issue_id(issue_id)\n \n result = subprocess.run(['bd', 'show', resolved_id], ...)\n \n # Parse response and add alias info\n # Format: \"bd-af78e9a2 (alias: #42)\"\n ...\n```\n\n### 4. Add beads_alias_* Functions\n\n```python\n@server.call_tool()\nasync def beads_alias_list() -\u003e List[TextContent]:\n \"\"\"List all alias mappings.\"\"\"\n result = subprocess.run(['bd', 'alias', 'list'], ...)\n return [TextContent(type=\"text\", text=result.stdout)]\n\n@server.call_tool()\nasync def beads_alias_set(alias: int, issue_id: str) -\u003e List[TextContent]:\n \"\"\"Manually assign alias to issue.\"\"\"\n result = subprocess.run(['bd', 'alias', 'set', str(alias), issue_id], ...)\n return [TextContent(type=\"text\", text=result.stdout)]\n\n@server.call_tool()\nasync def beads_alias_compact() -\u003e List[TextContent]:\n \"\"\"Compact aliases to fill gaps.\"\"\"\n result = subprocess.run(['bd', 'alias', 'compact'], ...)\n return [TextContent(type=\"text\", text=result.stdout)]\n```\n\n### 5. Update Documentation\nFile: integrations/beads-mcp/README.md\n\n```markdown\n## Issue IDs (v2.0+)\n\nThe MCP server accepts both hash IDs and aliases:\n\n```python\n# Using hash IDs\nawait beads_show(issue_id=\"bd-af78e9a2\")\n\n# Using aliases\nawait beads_show(issue_id=\"#42\")\nawait beads_show(issue_id=\"42\") # Shorthand\n```\n\n## Alias Management\n\nNew functions for alias control:\n\n- `beads_alias_list()` - List all alias mappings\n- `beads_alias_set(alias, issue_id)` - Manually assign alias\n- `beads_alias_compact()` - Compact aliases to fill gaps\n\n## Migration\n\nAfter migrating to hash IDs:\n1. Update beads-mcp: `pip install --upgrade beads-mcp`\n2. Restart MCP server\n3. All existing workflows continue to work\n```\n\n### 6. Version Compatibility\nDetect and handle both v1.x and v2.0 formats:\n\n```python\ndef detect_beads_version() -\u003e str:\n \"\"\"Detect if beads is using sequential or hash IDs.\"\"\"\n result = subprocess.run(['bd', 'list', '-n', '1'], ...)\n first_id = parse_first_issue_id(result.stdout)\n \n if first_id.startswith('bd-') and len(first_id) \u003e 11:\n return '2.0' # Hash ID\n else:\n return '1.x' # Sequential ID\n\n# On startup\nbeads_version = detect_beads_version()\nlogger.info(f\"Detected beads version: {beads_version}\")\n```\n\n## Testing\n\n### Unit Tests\nFile: integrations/beads-mcp/tests/test_hash_ids.py\n\n```python\ndef test_resolve_hash_id():\n \"\"\"Hash IDs pass through unchanged.\"\"\"\n assert resolve_issue_id(\"bd-af78e9a2\") == \"bd-af78e9a2\"\n\ndef test_resolve_alias():\n \"\"\"Aliases resolve to hash IDs.\"\"\"\n # Mock bd alias find command\n assert resolve_issue_id(\"#42\") == \"bd-af78e9a2\"\n assert resolve_issue_id(\"42\") == \"bd-af78e9a2\"\n\ndef test_invalid_id():\n \"\"\"Invalid IDs raise ValueError.\"\"\"\n with pytest.raises(ValueError):\n resolve_issue_id(\"invalid\")\n```\n\n### Integration Tests\n```python\nasync def test_show_with_hash_id(mcp_server):\n result = await mcp_server.beads_show(issue_id=\"bd-af78e9a2\")\n assert \"bd-af78e9a2\" in result[0].text\n\nasync def test_show_with_alias(mcp_server):\n result = await mcp_server.beads_show(issue_id=\"#42\")\n assert \"bd-af78e9a2\" in result[0].text # Resolved\n```\n\n## Backward Compatibility\nThe MCP server should work with both:\n- Beads v1.x (sequential IDs)\n- Beads v2.0+ (hash IDs)\n\nDetection happens at runtime based on issue ID format.\n\n## Files to Modify\n- integrations/beads-mcp/src/beads_mcp/server.py\n- integrations/beads-mcp/README.md\n- integrations/beads-mcp/tests/test_hash_ids.py (new)\n- integrations/beads-mcp/pyproject.toml (bump version)\n\n## Deployment\n```bash\ncd integrations/beads-mcp\n# Bump version to 2.0.0\npoetry version 2.0.0\n# Publish to PyPI\npoetry publish --build\n```","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:28:45.256074-07:00","updated_at":"2025-10-29T21:28:45.256074-07:00","dependencies":[{"issue_id":"bd-177","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:28:45.257315-07:00","created_by":"stevey"},{"issue_id":"bd-177","depends_on_id":"bd-170","type":"blocks","created_at":"2025-10-29T21:28:45.258057-07:00","created_by":"stevey"}]} {"id":"bd-178","content_hash":"4b348a47b8819c70bed5407a8a785605238738f5e56e344a8140a13de2c5dfd8","title":"Dogfood: Migrate beads repo to hash IDs","description":"Final validation: migrate the beads project itself to hash-based IDs.\n\n## Purpose\nDogfooding the migration on beads' own issue database to:\n1. Validate migration tool works on real data\n2. Discover edge cases\n3. Verify all workflows still work\n4. Build confidence for users\n\n## Pre-Migration Checklist\n- [ ] All bd-165 child tasks completed\n- [ ] All tests pass: `go test ./...`\n- [ ] Migration tool tested on test databases\n- [ ] Documentation updated\n- [ ] MCP server updated and published\n- [ ] Clean git status\n\n## Migration Steps\n\n### 1. Create Backup\n```bash\n# Backup database\ncp -r .beads .beads.backup-1761798568\n\n# Backup JSONL\ncp .beads/beads.jsonl .beads/beads.jsonl.backup\n\n# Create git branch for migration\ngit checkout -b hash-id-migration\ngit add .beads.backup-*\ngit commit -m \"Pre-migration backup\"\n```\n\n### 2. Run Migration (Dry Run)\n```bash\nbd migrate --hash-ids --dry-run \u003e migration-plan.txt\ncat migration-plan.txt\n\n# Review:\n# - Number of issues to migrate\n# - Hash collision check (should be zero)\n# - Text reference updates\n# - Dependency updates\n```\n\n### 3. Run Migration (Real)\n```bash\nbd migrate --hash-ids 2\u003e\u00261 | tee migration-log.txt\n\n# Expected output:\n# ✓ Backup created: .beads/beads.db.backup-1234567890\n# ✓ Generated 150 hash IDs\n# ✓ No hash collisions detected\n# ✓ Updated issues table schema\n# ✓ Updated 150 issue IDs\n# ✓ Updated 87 dependencies\n# ✓ Updated 234 text references\n# ✓ Exported to .beads/beads.jsonl\n# ✓ Migration complete!\n```\n\n### 4. Validation\n\n#### Database Integrity\n```bash\n# Check all issues have hash IDs\nbd list | grep -v \"bd-[a-f0-9]\\{8\\}\" \u0026\u0026 echo \"FAIL: Non-hash IDs found\"\n\n# Check all issues have aliases\nsqlite3 .beads/beads.db \"SELECT COUNT(*) FROM issues WHERE alias IS NULL\"\n# Should be 0\n\n# Check no alias duplicates\nsqlite3 .beads/beads.db \"SELECT alias, COUNT(*) FROM issues GROUP BY alias HAVING COUNT(*) \u003e 1\"\n# Should be empty\n```\n\n#### Functionality Tests\n```bash\n# Test show by hash ID\nbd show bd-\n\n# Test show by alias\nbd show #1\n\n# Test create new issue\nbd create \"Test issue after migration\" -p 2\n# Should get hash ID + alias\n\n# Test update\nbd update #1 --priority 1\n\n# Test dependencies\nbd dep tree #1\n\n# Test export\nbd export\ngit diff .beads/beads.jsonl\n# Should show hash IDs\n```\n\n#### Text Reference Validation\n```bash\n# Check that old IDs were updated in descriptions\ngrep -r \"bd-[0-9]\\{1,3\\}[^a-f0-9]\" .beads/beads.jsonl \u0026\u0026 echo \"FAIL: Old ID format found\"\n\n# Verify hash ID references exist\ngrep -o \"bd-[a-f0-9]\\{8\\}\" .beads/beads.jsonl | sort -u | wc -l\n# Should match number of hash IDs\n```\n\n### 5. Commit Migration\n```bash\ngit add .beads/beads.jsonl .beads/beads.db\ngit commit -m \"Migrate to hash-based IDs (v2.0)\n\n- Migrated 150 issues to hash IDs\n- Preserved aliases (#1-#150)\n- Updated 87 dependencies\n- Updated 234 text references\n- Zero hash collisions\n\nMigration log: migration-log.txt\"\n\ngit push origin hash-id-migration\n```\n\n### 6. Create PR\n```bash\ngh pr create --title \"Migrate to hash-based IDs (v2.0)\" --body \"## Summary\nMigrates beads project to hash-based IDs as part of v2.0 release.\n\n## Migration Stats\n- Issues migrated: 150\n- Dependencies updated: 87\n- Text references updated: 234\n- Hash collisions: 0\n- Aliases assigned: 150\n\n## Validation\n- ✅ All tests pass\n- ✅ Database integrity verified\n- ✅ All workflows tested (show, update, create, deps)\n- ✅ Text references updated correctly\n- ✅ Export produces valid JSONL\n\n## Files Changed\n- `.beads/beads.jsonl` - Hash IDs in all entries\n- `.beads/beads.db` - Schema updated with aliases\n\n## Rollback\nIf issues arise:\n\\`\\`\\`bash\nmv .beads.backup-1234567890 .beads\nbd export\n\\`\\`\\`\n\nSee migration-log.txt for full details.\"\n```\n\n### 7. Merge and Cleanup\n```bash\n# After PR approval\ngit checkout main\ngit merge hash-id-migration\ngit push origin main\n\n# Tag release\ngit tag v2.0.0\ngit push origin v2.0.0\n\n# Cleanup\nrm migration-log.txt migration-plan.txt\ngit checkout .beads.backup-* # Keep in git history\n```\n\n## Rollback Procedure\nIf migration fails or has issues:\n\n```bash\n# Restore backup\nmv .beads .beads.failed-migration\nmv .beads.backup-1234567890 .beads\n\n# Regenerate JSONL\nbd export\n\n# Verify restoration\nbd list\ngit diff .beads/beads.jsonl\n\n# Cleanup\ngit checkout hash-id-migration\ngit reset --hard main\n```\n\n## Post-Migration Communication\n\n### GitHub Issue/Discussion\n```markdown\n## Beads v2.0 Released: Hash-Based IDs\n\nWe've migrated beads to hash-based IDs! 🎉\n\n**What changed:**\n- Issues now use hash IDs (bd-af78e9a2) instead of sequential (bd-42)\n- Human-friendly aliases (#42) for easy reference\n- Zero collision risk in distributed workflows\n\n**Action required:**\nIf you have a local clone, you need to migrate:\n\n\\`\\`\\`bash\ngit pull origin main\nbd migrate --hash-ids\ngit push origin main\n\\`\\`\\`\n\nSee MIGRATION.md for details.\n\n**Benefits:**\n- ✅ No more ID collisions\n- ✅ Work offline without coordination\n- ✅ Simpler codebase (-2,100 LOC)\n\nQuestions? Reply here or see docs/HASH_IDS.md\n```\n\n## Success Criteria\n- [ ] Migration completes without errors\n- [ ] All validation checks pass\n- [ ] PR merged to main\n- [ ] v2.0.0 tagged and released\n- [ ] Documentation updated\n- [ ] Community notified\n- [ ] No rollback needed within 1 week\n\n## Files to Create\n- migration-log.txt (transient)\n- migration-plan.txt (transient)\n\n## Timeline\nExecute after all other bd-165 tasks complete (estimated: ~8 weeks from start)","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:29:28.591526-07:00","updated_at":"2025-10-29T21:29:28.591526-07:00","dependencies":[{"issue_id":"bd-178","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-29T21:29:28.59248-07:00","created_by":"stevey"},{"issue_id":"bd-178","depends_on_id":"bd-173","type":"blocks","created_at":"2025-10-29T21:29:28.593033-07:00","created_by":"stevey"},{"issue_id":"bd-178","depends_on_id":"bd-175","type":"blocks","created_at":"2025-10-29T21:29:28.593437-07:00","created_by":"stevey"},{"issue_id":"bd-178","depends_on_id":"bd-176","type":"blocks","created_at":"2025-10-29T21:29:28.593876-07:00","created_by":"stevey"},{"issue_id":"bd-178","depends_on_id":"bd-177","type":"blocks","created_at":"2025-10-29T21:29:28.594521-07:00","created_by":"stevey"}]} {"id":"bd-179","content_hash":"b4ee73e439a133a77e5df27e3e457674dbc9968fdbee0dc630175726960bb8cf","title":"Improve integration test coverage for stateful features","description":"","design":"## Context\n\nbd-160 revealed a critical gap: the export deduplication feature had unit tests but no integration tests simulating real-world git operations. This led to silent data loss in production.\n\n## Root Cause\n- Unit tests only tested functions in isolation\n- No integration tests for git operations (pull, reset, checkout) modifying JSONL\n- No tests validating export_hashes and JSONL stay in sync\n- Missing tests for stateful distributed system interactions (DB + JSONL + git)\n\n## Completed (bd-160)\n✓ TestJSONLIntegrityValidation - unit tests for validation logic\n✓ TestImportClearsExportHashes - tests import clears hashes\n✓ TestExportIntegrityAfterJSONLTruncation - simulates git reset (would have caught bd-160)\n✓ TestExportIntegrityAfterJSONLDeletion - tests recovery from file deletion\n✓ TestMultipleExportsStayConsistent - tests repeated exports\n\n## Still Needed (High Priority)\n1. Multi-repo sync test - two clones staying in sync after push/pull\n2. Auto-flush integration test - JSONL integrity preserved during auto-flush\n3. Daemon auto-sync integration test - complex state management\n4. Import after corruption test - recovery from partial data loss\n\n## Medium Priority\n- Partial export failure handling (disk full, network interruption)\n- Concurrent export/import race conditions\n- Large dataset performance tests (1000+ issues)\n- Export hash migration tests (version upgrades)\n\n## Testing Principles\n1. Test real-world scenarios: git ops, user errors, system failures, concurrent ops\n2. Integration tests for stateful systems (DB + files + git)\n3. Regression test for every bug fix\n4. Test invariants: JSONL count == DB count, hash consistency, etc.\n\n## Key Lesson\nStateful distributed systems need integration tests, not just unit tests.","acceptance_criteria":"- [ ] Multi-repo sync test implemented\n- [ ] Auto-flush integration test implemented \n- [ ] Daemon auto-sync integration test implemented\n- [ ] Testing guidelines added to CONTRIBUTING.md\n- [ ] CI runs integration tests\n- [ ] All critical workflows have integration test coverage","status":"open","priority":2,"issue_type":"epic","created_at":"2025-10-29T21:53:15.397137-07:00","updated_at":"2025-10-29T23:05:13.99215-07:00"} diff --git a/collision-resolution-failure-analysis.md b/collision-resolution-failure-analysis.md new file mode 100644 index 00000000..341e8242 --- /dev/null +++ b/collision-resolution-failure-analysis.md @@ -0,0 +1,289 @@ +# Collision Resolution Failure Analysis + +**Date:** 2025-10-29 +**Incident:** Import with `--resolve-collisions` created duplicate issues after routine `git pull` + +## The Incident + +1. User performed routine `git pull` in `~/src/fred/beads` +2. JSONL file updated from remote (canonical database: `~/src/beads`) +3. Import failed with collision detection for: bd-106, bd-108, bd-172, bd-175 +4. Used `--resolve-collisions` which remapped them to bd-187, bd-188, bd-189, bd-190 +5. **Result:** Database now has BOTH versions - 6 duplicate issues created + +## The Evidence + +### Canonical Database (~/src/beads) +- Total issues: 509 +- JSONL lines: 165 +- bd-106: status=closed +- bd-108: status=closed +- bd-172: status=open +- bd-175: status=open + +### Polluted Database (~/src/fred/beads) +- Total issues: 515 (6 extra) +- JSONL lines: 171 (6 extra - includes the remapped duplicates) +- **Original issues:** + - bd-106: status=**open** (should be closed) + - bd-108: status=closed ✓ + - bd-172: status=open ✓ + - bd-175: status=open ✓ +- **Duplicate issues created:** + - bd-187: "Import validation falsely reports data loss" (originally bd-106) + - bd-188: "Fix multi-round convergence" (duplicate of bd-108) + - bd-189: "Delete collision resolution code" (duplicate of bd-172) + - bd-190: "Test: N-clone scenario" (duplicate of bd-175) + +### The JSONL State +The JSONL file now contains BOTH versions: +- bd-106 (closed) - from remote +- bd-187 (open) - remapped local version +- bd-108, bd-188 - duplicates +- bd-172, bd-189 - duplicates +- bd-175, bd-190 - duplicates + +## Root Cause Analysis + +### What Should Have Happened +1. Import detects bd-106 in JSONL has same ID as database +2. Compares content: JSONL version is newer/different +3. **Updates the existing bd-106 in database** (status: open → closed) +4. No duplicates created + +### What Actually Happened +1. Import detected bd-106 as a "collision" +2. Collision resolution assumed: "Two different issues both want ID bd-106" +3. Remapped local bd-106 → bd-187 +4. Imported JSONL bd-106 as new issue +5. **Result:** Both versions now exist + +### The Fundamental Design Flaw + +**Collision resolution conflates two completely different scenarios:** + +#### Scenario A: Normal Update (NOT a collision) +- JSONL has bd-106 (status=closed) +- Database has bd-106 (status=open) +- **This is a normal update** - JSONL is source of truth after git pull +- **Should:** Update database bd-106 to match JSONL +- **Should NOT:** Treat as collision + +#### Scenario B: Actual Collision (IS a collision) +- Branch A creates bd-106: "Fix authentication bug" +- Branch B creates bd-106: "Add new feature" +- Both branches merge into main +- **This is a true collision** - two different issues want same ID +- **Should:** Remap one to new ID, keep both + +### The Broken Logic + +The current collision detection logic appears to be: +``` +if (JSONL.id exists in database && JSONL.content != database.content) { + collision = true +} +``` + +This is **catastrophically wrong** because it treats every update as a collision. + +### What It Should Be + +**Idempotent import logic:** +``` +if (JSONL.id exists in database) { + if (JSONL.content_hash == database.content_hash) { + // Exact match - skip + } else { + // UPDATE the existing issue + database.update(JSONL.id, JSONL.content) + } +} +``` + +**Collision detection (only for branch merges):** +``` +Collisions only occur when: +1. Git merge conflict in JSONL file (<<<<<<< markers present) +2. Two JSONL entries have same ID but different content +3. User explicitly wants to keep both versions +``` + +## Why This Keeps Happening + +1. **Conceptual confusion:** "Collision" is being used for two different things: + - Content difference (normal update) + - ID conflict (actual collision) + +2. **Wrong default:** Import should default to "update on ID match" + - Current: Default to "collision on content difference" + +3. **Tool misuse:** `--resolve-collisions` is being used for normal imports + - Should only be needed for branch merge scenarios + +4. **No distinction:** Code doesn't distinguish between: + - Import after `git pull` (JSONL is authoritative) + - Import after branch merge (need conflict resolution) + +## The Correct Mental Model + +### Import Modes + +1. **Normal Import (default):** + - Purpose: Sync database with JSONL (source of truth) + - Behavior: Update issues on ID match, create new ones + - Use case: After `git pull`, switching branches, fresh clone + - Should NEVER create duplicates + +2. **Collision Resolution Import:** + - Purpose: Merge two independent databases that both created same IDs + - Behavior: Remap conflicting IDs to preserve both versions + - Use case: Branch merge where two devs independently created bd-42 + - Creates duplicates BY DESIGN (but with different content) + +### The Missing Piece: Import Intent + +The import command needs to know: +- "Trust the JSONL, update my database" (normal mode) +- "JSONL and database are both valid, resolve conflicts" (collision mode) + +Current implementation assumes EVERY import is a collision scenario. + +## Immediate Impact + +User now has: +- 6 duplicate issues polluting the database +- Incorrect JSONL synced to remote (if pushed) +- Canonical database potentially corrupted +- Zero trust in the import system + +## The Solution Architecture + +### Phase 1: Fix Import Default Behavior +```go +// Default import: JSONL is source of truth +func Import(jsonlPath string) error { + for each issue in JSONL { + if issue.ID exists in DB { + if issue.content_hash == DB.content_hash { + skip // identical + } else { + UPDATE DB issue // JSONL wins + } + } else { + CREATE new issue + } + } +} +``` + +### Phase 2: Collision Resolution (Separate Mode) +```bash +# Only when you KNOW you have a collision (branch merge) +bd import --resolve-collisions + +# Should ONLY be used when: +# 1. Git shows merge conflict in JSONL +# 2. You want to preserve both versions +# 3. You understand duplicates will be created +``` + +### Phase 3: Collision Detection (During Merge) +```bash +# Helper for detecting actual collisions in JSONL +bd detect-collisions + +# Shows: +# - Issues with same ID, different content in JSONL conflict markers +# - Suggests resolution strategies +# - DOES NOT modify database +``` + +## Testing Strategy + +### Test 1: Normal Update (Currently Broken) +```bash +# Setup: Database has bd-42 (status=open) +# JSONL has bd-42 (status=closed) +bd import -i issues.jsonl + +# Expected: bd-42 updated to status=closed +# Actual: Collision detected, bd-42 remapped to bd-XXX +# FAILURE ❌ +``` + +### Test 2: Actual Collision +```bash +# Setup: Branch merge creates duplicate bd-42 in JSONL +# bd-42 (title="Fix bug") in HEAD +# bd-42 (title="Add feature") in BASE +bd import -i issues.jsonl --resolve-collisions + +# Expected: Remap one to bd-XXX, keep both +# Actual: TBD +``` + +### Test 3: Idempotent Import +```bash +# Import same JSONL twice +bd import -i issues.jsonl +bd import -i issues.jsonl + +# Expected: Second import is no-op +# Actual: TBD +``` + +## Recommended Actions + +### Immediate (User Recovery) +1. Identify all duplicate pairs (bd-106/bd-187, etc.) +2. Manually merge duplicates using `bd merge` +3. Export clean database to JSONL +4. Force push to reset remote + +### Short-term (Fix Import) +1. **Create bd-XXX:** Rewrite import logic to default to UPDATE, not collision +2. **Create bd-XXX:** Add `--merge-mode` flag for actual collisions +3. **Create bd-XXX:** Write comprehensive import tests +4. **Create bd-XXX:** Document when to use collision resolution + +### Long-term (Prevent Recurrence) +1. **Create bd-XXX:** Add import validation (detect duplicates before committing) +2. **Create bd-XXX:** Add `bd validate` command to check database health +3. **Create bd-XXX:** Remove collision resolution entirely (use merge tools instead) +4. **Create bd-XXX:** Implement content-addressable IDs to prevent collisions + +## Historical Context + +This is NOT the first time this has happened: +- Multiple prior incidents of duplicate issues +- Repeated attempts to fix collision resolution +- bd-94: Epic to fix N-way collision convergence +- bd-109: Transaction + retry logic for collisions +- **Pattern:** We keep treating symptoms, not root cause + +## The Deeper Problem + +**beads is trying to solve distributed consensus without a consensus algorithm.** + +We're essentially trying to do what Git does (merge distributed changes) but: +- Git uses content-addressable storage (SHA hashes) +- Git has explicit merge semantics +- Git forces users to resolve conflicts manually +- **beads assumes automatic resolution is possible** + +The N-way collision problem, the import pollution, the duplicate issues - they're all symptoms of trying to sync mutable IDs across independent actors. + +## Conclusion + +The collision resolution feature is fundamentally broken because: +1. It treats normal updates as collisions +2. It has no concept of "source of truth" +3. It creates duplicates when it should update +4. It's being used for the wrong use case + +**The fix is not to improve collision resolution.** +**The fix is to make normal import work correctly first.** +**Then, collision resolution becomes a rare edge case for branch merges.** + +Until then, every `git pull` is a potential database corruption event.