From bbe36b65270b96bde506c602dd1b7abdea7cc7bd Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Thu, 30 Oct 2025 14:24:54 -0700 Subject: [PATCH] bd sync: 2025-10-30 14:24:54 --- .beads/beads.jsonl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 44c2e869..9244fc5f 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -59,15 +59,15 @@ {"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":"d429410e478f428289b91d4fd258797d1140adf105b54a05fb6b7fa62c91f67f","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- **Storage:** bd-af78e9a2 (prefix + 8-char SHA256)\n- **CLI input:** Both bd-af78e9a2 AND a3f8e9 accepted (prefix optional)\n- **CLI output:** bd-af78e9a2 (always show prefix for copy-paste clarity)\n- **External refs:** bd-af78e9a2 (in commits, docs, unambiguous)\n\n**Why keep prefix in storage:**\n- Clear in external contexts (git commits, docs, Slack)\n- Grep-able across files\n- Distinguishable from git commit SHAs\n- Supports multiple databases (bd-, ticket-, bug- prefixes)\n\n**Why make optional in CLI:**\n- Less typing: bd show a3f8e9 works\n- Git-style convenience\n- Prefix inferred from context (bd command)\n\n### Hierarchical Children\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\n# All of these work (prefix optional in input):\nbd show a3f8e9\nbd show bd-a3f8e9\nbd show a3f8e9.1\nbd show bd-a3f8e9.1.2\n\n# Output always shows prefix:\nbd-a3f8e9 [epic] Auth System\n Status: open\n ...\n\n# External references use full ID:\ngit commit -m \"Implement login (bd-a3f8e9.1)\"\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 with prefix: bd-a3f8e9.1.2\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:32:21.431272-07:00"} -{"id":"bd-166","content_hash":"e5e68e05a19b8e08b51a6d91cda937b5a5006651d6db7aa47d9ad43473b98a2f","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","notes":"## Next Session: Continue bd-168\n\nWe've completed:\n- ✅ bd-166: Hash ID algorithm (returns full 64-char hash)\n- ✅ bd-167: child_counters table + getNextChildNumber()\n- ✅ Docs updated for 6-char progressive design\n\n**TODO for bd-168:**\nImplement progressive collision retry in CreateIssue():\n1. Try hash[:6] first (bd-a3f2dd)\n2. On UNIQUE constraint → try hash[:7] (bd-a3f2dda) \n3. On collision again → try hash[:8] (bd-a3f2dda8)\n4. Max 3 attempts, then error\n\nLocation: internal/storage/sqlite/sqlite.go CreateIssue() around line 748\nPattern: Detect sqlite UNIQUE constraint error, retry with longer hash\n\nSee: internal/types/id_generator.go GenerateHashID() - now returns full hash","status":"in_progress","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:01.843634-07:00","updated_at":"2025-10-30T14:04:13.585075-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-166","content_hash":"e5e68e05a19b8e08b51a6d91cda937b5a5006651d6db7aa47d9ad43473b98a2f","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","notes":"## Next Session: Continue bd-168\n\nWe've completed:\n- ✅ bd-166: Hash ID algorithm (returns full 64-char hash)\n- ✅ bd-167: child_counters table + getNextChildNumber()\n- ✅ Docs updated for 6-char progressive design\n\n**TODO for bd-168:**\nImplement progressive collision retry in CreateIssue():\n1. Try hash[:6] first (bd-a3f2dd)\n2. On UNIQUE constraint → try hash[:7] (bd-a3f2dda) \n3. On collision again → try hash[:8] (bd-a3f2dda8)\n4. Max 3 attempts, then error\n\nLocation: internal/storage/sqlite/sqlite.go CreateIssue() around line 748\nPattern: Detect sqlite UNIQUE constraint error, retry with longer hash\n\nSee: internal/types/id_generator.go GenerateHashID() - now returns full hash","status":"in_progress","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:01.843634-07:00","updated_at":"2025-10-30T14:22:59.356666-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":"64ad81d1a67f119ed3b9c66e215252aab9e569926e0a60586a61bc38bd8659b8","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":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:13.968241-07:00","updated_at":"2025-10-30T13:32:05.83292-07:00","closed_at":"2025-10-30T13:32:05.83292-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":"c2041472fbce7fbe3fc32be28f61d276ea725f649344c87a1fca0f9c054999b3","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":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:29.412237-07:00","updated_at":"2025-10-30T14:12:17.327987-07:00","closed_at":"2025-10-30T14:12:17.327987-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-168","content_hash":"1a53798d7a2eaf014f90a399745beb62b4bb265c9d03713f0b00dbc54c3073e2","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)","notes":"Work completed on feature/hash-ids branch. Reverted from main to avoid breaking changes. Will merge after migration strategy (bd-173) is ready.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:24:29.412237-07:00","updated_at":"2025-10-30T14:17:14.485149-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"},{"issue_id":"bd-169","depends_on_id":"bd-192","type":"blocks","created_at":"2025-10-30T14:22:59.347557-07:00","created_by":"import-remap"}]} {"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":"9a2120c5d56a818ae4d0b2acc3518b6705d62b6cb866703dd524e3bc1a462397","title":"CLI accepts hash ID prefixes (Git-style)","description":"Implement prefix-optional parsing for hash IDs throughout CLI.\n\n## Parsing Logic\n```go\n// internal/utils/id_parser.go\nfunc ParseIssueID(input string) (issueID string, err error) {\n // Already has prefix: bd-a3f8e9 or bd-a3f8e9.1.2\n if strings.HasPrefix(input, \"bd-\") {\n return input, nil\n }\n \n // Add prefix: a3f8e9 → bd-a3f8e9\n // Works with hierarchical too: a3f8e9.1.2 → bd-a3f8e9.1.2\n return \"bd-\" + input, nil\n}\n\n// Prefix matching for partial IDs\nfunc ResolvePartialID(input string) (fullID string, err error) {\n parsedID := ParseIssueID(input) // Ensure prefix\n \n // Query with LIKE for prefix match\n matches := storage.FindByPrefix(parsedID)\n \n if len(matches) == 0 {\n return \"\", fmt.Errorf(\"no issue found matching %q\", input)\n }\n if len(matches) \u003e 1 {\n return \"\", fmt.Errorf(\"ambiguous ID %q matches: %v\", input, matches)\n }\n return matches[0].ID, nil\n}\n```\n\n## Behavior Examples\n```bash\n# All these inputs work:\nbd show a3f8e9 # Add prefix → bd-a3f8e9\nbd show bd-a3f8e9 # Already has prefix\nbd show a3f8 # Shorter prefix match\nbd show a3f8e9.1 # Hierarchical without prefix\nbd show bd-a3f8e9.1.2 # Full hierarchical with prefix\n\n# Output always shows full ID with prefix:\nbd-a3f8e9 [epic] Auth System\nbd-a3f8e9.1 [epic] Login Flow\nbd-a3f8e9.1.2 [task] Backend validation\n```\n\n## Error Handling\n```bash\n# Not found\n$ bd show xyz789\nError: no issue found matching \"bd-xyz789\"\n\n# Ambiguous (multiple matches)\n$ bd show a3\nError: ambiguous ID \"bd-a3\" matches: bd-a3f8e9, bd-a34721, bd-a38f92\nUse more characters to disambiguate.\n```\n\n## Commands to Update\nAll commands that accept issue IDs:\n\n### Read operations\n- bd show \u003cid\u003e\n- bd list --id \u003cids\u003e\n- bd dep tree \u003cid\u003e\n\n### Write operations\n- bd update \u003cid\u003e\n- bd close \u003cid\u003e\n- bd reopen \u003cid\u003e\n- bd label add \u003cid\u003e\n- bd dep add \u003cid1\u003e \u003cid2\u003e\n- bd comment add \u003cid\u003e\n\n### Multiple IDs\n```bash\n# All work with optional prefix:\nbd show a3f8e9 b7c2d1 # → bd-a3f8e9 bd-b7c2d1\nbd dep add a3f8e9.1 b7c2d1 # → bd-a3f8e9.1 blocks bd-b7c2d1\n```\n\n## Display Format\n**Always show full ID with prefix** in output for:\n- Copy-paste clarity\n- External reference (git commits, docs)\n- Unambiguous identification\n\n```bash\n$ bd list\nbd-a3f8e9 [P1] Auth System [epic] open\nbd-b7c2d1 [P2] Fix daemon crash [bug] open\nbd-1a2b3c4d [P3] Add logging [task] open\n```\n\n## Files to Create/Modify\n- internal/utils/id_parser.go (new - parsing logic)\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/comment.go\n- cmd/bd/list.go\n\n## Testing\n- Test prefix omitted: a3f8e9 → bd-a3f8e9\n- Test prefix included: bd-a3f8e9 → bd-a3f8e9\n- Test hierarchical: a3f8e9.1.2 → bd-a3f8e9.1.2\n- Test partial match: a3f8 → bd-a3f8e9\n- Test ambiguous ID error\n- Test not found error\n- Test mixed input: bd show a3f8e9 bd-b7c2d1","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-29T21:25:06.256317-07:00","updated_at":"2025-10-30T00:32:47.510446-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":"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-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"},{"issue_id":"bd-172","depends_on_id":"bd-192","type":"blocks","created_at":"2025-10-30T14:22:59.348038-07:00","created_by":"import-remap"}]} +{"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"},{"issue_id":"bd-173","depends_on_id":"bd-192","type":"blocks","created_at":"2025-10-30T14:22:59.348502-07:00","created_by":"import-remap"}]} {"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":"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"}]} @@ -84,6 +84,8 @@ {"id":"bd-19","content_hash":"af7f41ff73c3aaba006d9cfbf8e35332e25d5b42f9e620b5e94d41c05550ea81","title":"Extract SQLite migrations into separate files","description":"The file `internal/storage/sqlite/sqlite.go` is 2,136 lines and contains 11 sequential migrations alongside core storage logic. Extract migrations into a versioned system.\n\nCurrent issues:\n- 11 migration functions mixed with core logic\n- Hard to see migration history\n- Sequential migrations slow database open\n- No clear migration versioning\n\nMigration functions to extract:\n- `migrateDirtyIssuesTable()`\n- `migrateIssueCountersTable()`\n- `migrateExternalRefColumn()`\n- `migrateCompositeIndexes()`\n- `migrateClosedAtConstraint()`\n- `migrateCompactionColumns()`\n- `migrateSnapshotsTable()`\n- `migrateCompactionConfig()`\n- `migrateCompactedAtCommitColumn()`\n- `migrateExportHashesTable()`\n- Plus 1 more (11 total)\n\nTarget structure:\n```\ninternal/storage/sqlite/\n├── sqlite.go # Core storage (~800 lines)\n├── schema.go # Table definitions (~200 lines)\n├── migrations.go # Migration orchestration (~200 lines)\n└── migrations/ # Individual migrations\n ├── 001_initial_schema.go\n ├── 002_dirty_issues.go\n ├── 003_issue_counters.go\n [... through 011_export_hashes.go]\n```\n\nBenefits:\n- Clear migration history\n- Each migration self-contained\n- Easier to review migration changes in PRs\n- Future migrations easier to add","acceptance_criteria":"- All 11 migrations extracted to separate files\n- Migration version tracking in database\n- Migrations run in order on fresh database\n- Existing databases upgrade correctly\n- All tests pass: `go test ./internal/storage/sqlite/...`\n- Database initialization time unchanged or improved\n- Add migration rollback capability (optional, nice-to-have)","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-27T20:30:47.870671-07:00","updated_at":"2025-10-27T22:22:23.81842-07:00","labels":["database","phase-2","refactor"],"dependencies":[{"issue_id":"bd-19","depends_on_id":"bd-26","type":"parent-child","created_at":"2025-10-27T20:30:47.875564-07:00","created_by":"daemon"}]} {"id":"bd-190","content_hash":"e2d1279edd9a6ea4dae06772350823e069230280cfca6eb69891527caa9b7446","title":"Fix import collision resolution - treats updates as collisions","description":"## Problem\n\nImport with `--resolve-collisions` incorrectly treats normal updates as collisions, creating duplicate issues after routine `git pull`.\n\n## Root Cause\n\nCurrent collision detection:\n```\nif (JSONL.id exists in DB \u0026\u0026 JSONL.content != DB.content) {\n collision = true // WRONG!\n}\n```\n\nThis treats EVERY update as a collision. After `git pull`, JSONL has updated issues (e.g., bd-106 status changed from open→closed). Import sees this as a \"collision\" and remaps to new ID (bd-187), creating duplicates.\n\n## What Should Happen\n\n**Normal import (default):** JSONL is source of truth, update database on ID match\n```\nif (JSONL.id exists in DB) {\n if (content_hash matches) { skip }\n else { UPDATE existing issue } // Not a collision!\n}\n```\n\n**Collision resolution (separate mode):** Only for branch merges where two independent actors created same ID with different content\n\n## Solution Architecture\n\n1. Fix default import to UPDATE on ID match (not treat as collision)\n2. Make `--resolve-collisions` a separate mode ONLY for branch merges\n3. Add import validation to detect duplicates before committing\n4. Write comprehensive tests for normal update vs actual collision scenarios\n\nSee ~/src/fred/beads/collision-resolution-failure-analysis.md for full analysis.","status":"open","priority":0,"issue_type":"epic","created_at":"2025-10-29T23:47:37.906532-07:00","updated_at":"2025-10-29T23:47:37.906532-07:00"} {"id":"bd-191","content_hash":"47b9f15a8741b9df77e30d448999ad9566f9c6cb1bebe1e760735bfb2cb26c66","title":"Add --parent flag to bd list command","description":"Add --parent flag to bd list to filter issues by parent ID.\n\n## Usage\n```bash\nbd list --parent 165 # List all children of bd-165\nbd list --parent a3f8e9 # List all children (with prefix match)\nbd list --parent a3f8e9 -s open # Combine with status filter\n```\n\n## Behavior\n- Show only direct children (not recursive by default)\n- Add --recursive flag for full subtree\n- Works with current parent-child dependency type\n- With hierarchical IDs (bd-165), this will be natural: filter by ID prefix\n\n## Implementation\nQuery dependencies table:\n```sql\nSELECT i.* FROM issues i\nJOIN dependencies d ON i.id = d.issue_id\nWHERE d.depends_on_id = ? AND d.type = 'parent-child'\n```\n\nWith hierarchical IDs (post bd-165):\n```sql\nSELECT * FROM issues \nWHERE id LIKE 'bd-a3f8e9.%' -- Much simpler!\n```\n\n## Related\n- Complements bd tree command (bd-174)\n- Will be cleaner with hierarchical IDs from bd-165\n- Could add --leaves flag (show only leaf nodes, no sub-epics)\n\n## Files\n- cmd/bd/list.go (add --parent flag)\n- May want to add --recursive flag too","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-30T00:27:44.562078-07:00","updated_at":"2025-10-30T00:27:44.562078-07:00"} +{"id":"bd-192","content_hash":"c2041472fbce7fbe3fc32be28f61d276ea725f649344c87a1fca0f9c054999b3","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":"closed","priority":1,"issue_type":"task","created_at":"2025-10-30T14:22:59.345449-07:00","updated_at":"2025-10-30T14:22:59.345449-07:00","closed_at":"2025-10-30T14:12:17.327987-07:00","dependencies":[{"issue_id":"bd-192","depends_on_id":"bd-165","type":"parent-child","created_at":"2025-10-30T14:22:59.346356-07:00","created_by":"import-remap"},{"issue_id":"bd-192","depends_on_id":"bd-166","type":"blocks","created_at":"2025-10-30T14:22:59.346967-07:00","created_by":"import-remap"}]} +{"id":"bd-193","content_hash":"2f0e8212084a4d53ec447be4c470117d4c2d697e1836ac8bd40a68d151f6083e","title":"Test hash ID issue","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-30T14:23:58.175147-07:00","updated_at":"2025-10-30T14:24:02.855391-07:00","closed_at":"2025-10-30T14:24:02.855391-07:00"} {"id":"bd-2","content_hash":"4ad564b5b844f5673cd8ec6355ad921cbf71e4fbd6d0a6aa5f4e9c4e3222408e","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-50])\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-50] to fix CI/local discrepancy","status":"in_progress","priority":2,"issue_type":"epic","created_at":"2025-10-24T01:01:12.997982-07:00","updated_at":"2025-10-28T16:20:02.454709-07:00"} {"id":"bd-20","content_hash":"b853675236e96269afb97649cc1a7b27451f15babf611a2abfea58986d0f5a2f","title":"Extract normalizeLabels to shared utility package","description":"The `normalizeLabels` function appears in multiple locations with identical implementation. Extract to a shared utility package.\n\nCurrent locations:\n- `internal/rpc/server.go:37` (53 lines) - full implementation\n- `cmd/bd/list.go:50-52` - uses the server version (needs to use new shared version)\n\nFunction purpose:\n- Trims whitespace from labels\n- Removes empty strings\n- Deduplicates labels\n- Preserves order\n\nTarget structure:\n```\ninternal/util/\n├── strings.go # String utilities\n └── NormalizeLabels([]string) []string\n```\n\nImpact: DRY principle, single source of truth, easier to test","acceptance_criteria":"- Create `internal/util/strings.go` with `NormalizeLabels`\n- Add comprehensive unit tests in `internal/util/strings_test.go`\n- Update `internal/rpc/server.go` to import and use `util.NormalizeLabels`\n- Update `cmd/bd/list.go` to import and use `util.NormalizeLabels`\n- Remove duplicate implementations\n- All tests pass: `go test ./...`\n- Verify label normalization works: test `bd list --label` commands","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-27T20:31:19.078622-07:00","updated_at":"2025-10-27T22:22:23.818801-07:00","labels":["deduplication","phase-3","refactor"],"dependencies":[{"issue_id":"bd-20","depends_on_id":"bd-26","type":"parent-child","created_at":"2025-10-27T20:31:19.08015-07:00","created_by":"daemon"}]} {"id":"bd-21","content_hash":"3e37bcf3e5090c1971f300f95fc904762857be05d4d47acfa2bfa049c8302043","title":"Centralize BD_DEBUG logging into debug package","description":"The codebase has 43 scattered instances of `if os.Getenv(\"BD_DEBUG\") != \"\"` debug checks across 6 files. Centralize into a debug logging package.\n\nCurrent locations:\n- `cmd/bd/main.go` - 15 checks\n- `cmd/bd/autoflush.go` - 6 checks\n- `cmd/bd/nodb.go` - 4 checks\n- `internal/rpc/server.go` - 2 checks\n- `internal/rpc/client.go` - 5 checks\n- `cmd/bd/daemon_autostart.go` - 11 checks\n\nTarget structure:\n```\ninternal/debug/\n└── debug.go\n```\n\nBenefits:\n- Centralized debug logging\n- Easier to add structured logging later\n- Testable (can mock debug output)\n- Consistent debug message format\n\nImpact: Removes 43 scattered checks, improves code clarity","acceptance_criteria":"- Create `internal/debug/debug.go` with `Enabled`, `Logf`, `Printf`\n- Add unit tests in `internal/debug/debug_test.go` (test with/without BD_DEBUG)\n- Replace all 43 instances of `os.Getenv(\"BD_DEBUG\")` checks with `debug.Logf()`\n- Verify debug output works: run with `BD_DEBUG=1 bd status`\n- All tests pass: `go test ./...`\n- No behavior change (output identical to before)","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-27T20:31:19.089078-07:00","updated_at":"2025-10-27T22:22:23.819123-07:00","labels":["deduplication","logging","phase-3","refactor"],"dependencies":[{"issue_id":"bd-21","depends_on_id":"bd-26","type":"parent-child","created_at":"2025-10-27T21:48:41.542395-07:00","created_by":"stevey"}]} @@ -138,6 +140,7 @@ {"id":"bd-67","content_hash":"3979df7395526a6796508aa1ed1e89c4fedc46ee5c2b79dd85066c8a78c8487a","title":"Create cmd/bd/daemon_event_loop.go (~200 LOC)","description":"Implement runEventDrivenLoop to replace polling ticker. Coordinate FileWatcher, mutation events, debouncer. Include health check ticker (60s) for daemon validation.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-28T16:20:02.429383-07:00","updated_at":"2025-10-28T16:20:02.429383-07:00","closed_at":"2025-10-28T12:30:44.067036-07:00"} {"id":"bd-68","content_hash":"37e71aade254736849f32c41515f554bac4b8b014ac50b58e4be7cf67973d4b0","title":"Add fsnotify dependency to go.mod","description":"","status":"in_progress","priority":1,"issue_type":"task","created_at":"2025-10-28T16:20:02.429763-07:00","updated_at":"2025-10-28T16:20:02.429763-07:00"} {"id":"bd-69","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-28T19:20:58.312809-07:00","closed_at":"2025-10-28T19:20:58.312809-07:00"} +{"id":"bd-6b82c2e3","content_hash":"919b081fad12885acfc2ff1defd8beb20b737db1372eff62306740371ab3b05e","title":"Test hash ID v2","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-30T14:24:38.507863-07:00","updated_at":"2025-10-30T14:24:47.272891-07:00","closed_at":"2025-10-30T14:24:47.272891-07:00"} {"id":"bd-7","content_hash":"e88e5d98a2a5bebc38b3ac505b00687bfe78bd72654bd0c756bceee4a01e15f5","title":"Enforce daemon singleton per workspace with file locking","description":"Agent in ~/src/wyvern discovered 4 simultaneous daemon processes running, causing infinite directory recursion (.beads/.beads/.beads/...). Each daemon used relative paths and created nested .beads/ directories.\n\nRoot cause: No singleton enforcement. Multiple `bd daemon` processes can start in same workspace.\n\nExpected: One daemon per workspace (each workspace = separate .beads/ dir with bd.sock)\nActual: Multiple daemons can run simultaneously in same workspace\n\nNote: Separate git clones = separate workspaces = separate daemons (correct). Git worktrees share .beads/ and have known limitations (documented, use --no-daemon).","design":"Use flock (file locking) on daemon socket or database file to enforce singleton:\n\n1. On daemon start, attempt exclusive lock on .beads/bd.sock or .beads/daemon.lock\n2. If lock held by another process, refuse to start (exit with clear error)\n3. Hold lock for lifetime of daemon process\n4. Release lock on daemon shutdown\n\nAlternative: Use PID file with stale detection (check if PID is still running)\n\nImplementation location: Daemon startup code in cmd/bd/ or internal/daemon/","acceptance_criteria":"1. Starting second daemon process in same workspace fails with clear error\n2. Test: Start daemon, attempt second start, verify failure\n3. Killing daemon releases lock, allowing new daemon to start\n4. No infinite .beads/ directory recursion possible\n5. Works correctly with auto-start mechanism","status":"in_progress","priority":0,"issue_type":"bug","created_at":"2025-10-25T23:13:12.269549-07:00","updated_at":"2025-10-27T22:22:23.814937-07:00"} {"id":"bd-70","content_hash":"c0b1677fe3f4aa3f395ae4d79bff5362632d5db26477bf571c09f9177b8741ef","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.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-10-28T16:20:02.430479-07:00","updated_at":"2025-10-28T16:30:26.631191-07:00","closed_at":"2025-10-28T16:30:26.631191-07:00"} {"id":"bd-72","content_hash":"a596aa8d6114d4938471e181ebc30da5d0315f74fd711a92dbbb83f5d0e7af88","title":"Create cmd/bd/daemon_debouncer.go (~60 LOC)","description":"Implement Debouncer to batch rapid events into single action. Default 500ms, configurable via BEADS_DEBOUNCE_MS. Thread-safe with mutex.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-28T16:20:02.431118-07:00","updated_at":"2025-10-28T16:20:02.431118-07:00","closed_at":"2025-10-28T12:03:35.614191-07:00"}