diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index d5abab3d..9f24ef08 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,18 +1,18 @@ -{"id":"bd-1","title":"Add export/import commands","description":"Support bd export --format=jsonl and bd import for text-based git workflow","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-12T00:43:03.453438-07:00","updated_at":"2025-10-12T00:43:03.453438-07:00"} -{"id":"bd-10","title":"Extend export to include dependencies in JSONL","description":"Modify export.go to include dependencies array in each issue's JSONL output. This makes JSONL self-contained and enables proper collision resolution. Format: {\"id\":\"bd-10\",\"dependencies\":[{\"depends_on_id\":\"bd-5\",\"type\":\"blocks\"}]}","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:32:19.163526-07:00","updated_at":"2025-10-12T14:32:19.163526-07:00"} -{"id":"bd-11","title":"Test issue to verify fix","description":"This should be bd-11 if the fix works","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-12T14:40:21.419082-07:00","updated_at":"2025-10-12T14:40:32.963312-07:00","closed_at":"2025-10-12T14:40:32.963312-07:00"} -{"id":"bd-12","title":"Implement collision detection in import","description":"Create collision.go with detectCollisions() function. Compare incoming JSONL issues against DB state. Distinguish between: (1) exact match (idempotent), (2) ID match but different content (collision), (3) new issue. Return list of colliding issues.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.056588-07:00","updated_at":"2025-10-12T14:40:56.056588-07:00"} -{"id":"bd-13","title":"Implement reference scoring algorithm","description":"Count references for each colliding issue: text mentions in descriptions/notes/design fields + dependency references. Sort collisions by score ascending (fewest refs first). This minimizes total updates during renumbering.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.204518-07:00","updated_at":"2025-10-12T14:40:56.204518-07:00"} -{"id":"bd-14","title":"Implement ID remapping with reference updates","description":"Allocate new IDs for colliding issues. Update all text field references using word-boundary regex (\\bbd-10\\b). Update dependency records. Build id_mapping for reporting. Handle chain dependencies properly.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.367596-07:00","updated_at":"2025-10-12T14:40:56.367596-07:00"} -{"id":"bd-15","title":"Add --resolve-collisions flag and user reporting","description":"Add import flags: --resolve-collisions (auto-fix) and --dry-run (preview). Display clear report: collisions detected, remappings applied (old→new with scores), reference counts updated. Default behavior: fail on collision (safe).","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.534721-07:00","updated_at":"2025-10-12T14:40:56.534721-07:00"} -{"id":"bd-16","title":"Write comprehensive collision resolution tests","description":"Test cases: simple collision, multiple collisions, dependency updates, text reference updates, chain dependencies, edge cases (partial ID matches, case sensitivity, triple merges). Add to import_test.go and collision_test.go.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.702127-07:00","updated_at":"2025-10-12T14:40:56.702127-07:00"} -{"id":"bd-17","title":"Update documentation for collision resolution","description":"Update README.md with collision resolution section. Update CLAUDE.md with new workflow. Document --resolve-collisions and --dry-run flags. Add example scenarios showing branch merge workflows.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.866649-07:00","updated_at":"2025-10-12T14:40:56.866649-07:00"} -{"id":"bd-18","title":"Add design/notes/acceptance_criteria fields to update command","description":"Currently bd update only supports status, priority, title, assignee. Add support for --design, --notes, --acceptance-criteria flags. This makes it easier to add detailed designs to issues after creation.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-12T14:40:57.032395-07:00","updated_at":"2025-10-12T14:40:57.032395-07:00"} -{"id":"bd-2","title":"Add PostgreSQL backend","description":"Implement PostgreSQL storage backend as alternative to SQLite for larger teams","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-12T00:43:03.457453-07:00","updated_at":"2025-10-12T14:15:04.00695-07:00","closed_at":"2025-10-12T14:15:04.00695-07:00"} -{"id":"bd-3","title":"Document git workflow in README","description":"Add Git Workflow section to README explaining binary vs text approaches","status":"closed","priority":1,"issue_type":"chore","created_at":"2025-10-12T00:43:03.461615-07:00","updated_at":"2025-10-12T00:43:30.283178-07:00","closed_at":"2025-10-12T00:43:30.283178-07:00"} -{"id":"bd-4","title":"Add demo GIF/video showing bd quickstart in action","description":"Record asciinema or create animated GIF showing the full workflow","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-12T10:50:49.500051-07:00","updated_at":"2025-10-12T10:50:49.500051-07:00"} -{"id":"bd-5","title":"Implement MCP server for Claude Desktop","description":"Complete the claude-desktop-mcp example with working TypeScript implementation","status":"open","priority":1,"issue_type":"feature","created_at":"2025-10-12T10:50:50.942964-07:00","updated_at":"2025-10-12T10:50:50.942964-07:00"} -{"id":"bd-6","title":"Add migration scripts for GitHub Issues","description":"Create scripts to import from GitHub Issues API or exported JSON","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-12T10:50:52.140018-07:00","updated_at":"2025-10-12T10:50:52.140018-07:00"} -{"id":"bd-7","title":"Add performance benchmarks document","description":"Document actual performance metrics with hyperfine tests","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-12T10:50:53.294516-07:00","updated_at":"2025-10-12T10:50:53.294516-07:00"} -{"id":"bd-8","title":"Reach 1.0 release milestone","description":"Stabilize API, finalize documentation, comprehensive testing","status":"open","priority":1,"issue_type":"epic","created_at":"2025-10-12T10:50:54.457348-07:00","updated_at":"2025-10-12T10:50:54.457348-07:00"} -{"id":"bd-9","title":"Build collision resolution tooling for distributed branch workflows","description":"When branches diverge and both create issues, auto-incrementing IDs collide on merge. Build excellent tooling to detect collisions during import, auto-renumber issues with fewer dependencies, update all references in descriptions and dependency links, and provide clear user feedback. Goal: keep beautiful brevity of numeric IDs (bd-302) while handling distributed creation gracefully.","status":"in_progress","priority":1,"issue_type":"feature","created_at":"2025-10-12T13:39:34.608218-07:00","updated_at":"2025-10-12T14:19:11.103828-07:00"} +{"id":"bd-1","title":"Add export/import commands","description":"Support bd export --format=jsonl and bd import for text-based git workflow","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-12T00:43:03.453438-07:00","updated_at":"2025-10-12T15:07:26.332681-07:00"} +{"id":"bd-10","title":"Extend export to include dependencies in JSONL","description":"Modify export.go to include dependencies array in each issue's JSONL output. This makes JSONL self-contained and enables proper collision resolution. Format: {\"id\":\"bd-10\",\"dependencies\":[{\"depends_on_id\":\"bd-5\",\"type\":\"blocks\"}]}","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-12T14:32:19.163526-07:00","updated_at":"2025-10-12T15:07:47.937992-07:00","closed_at":"2025-10-12T15:07:47.937992-07:00","dependencies":[{"issue_id":"bd-10","depends_on_id":"bd-9","type":"parent-child","created_at":"2025-10-12T14:35:59.213222-07:00","created_by":"stevey"}]} +{"id":"bd-11","title":"Test issue to verify fix","description":"This should be bd-11 if the fix works","status":"closed","priority":3,"issue_type":"task","created_at":"2025-10-12T14:40:21.419082-07:00","updated_at":"2025-10-12T15:07:26.333114-07:00","closed_at":"2025-10-12T14:40:32.963312-07:00"} +{"id":"bd-12","title":"Implement collision detection in import","description":"Create collision.go with detectCollisions() function. Compare incoming JSONL issues against DB state. Distinguish between: (1) exact match (idempotent), (2) ID match but different content (collision), (3) new issue. Return list of colliding issues.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.056588-07:00","updated_at":"2025-10-12T15:07:26.333204-07:00","dependencies":[{"issue_id":"bd-12","depends_on_id":"bd-9","type":"parent-child","created_at":"2025-10-12T14:41:07.947358-07:00","created_by":"stevey"}]} +{"id":"bd-13","title":"Implement reference scoring algorithm","description":"Count references for each colliding issue: text mentions in descriptions/notes/design fields + dependency references. Sort collisions by score ascending (fewest refs first). This minimizes total updates during renumbering.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.204518-07:00","updated_at":"2025-10-12T15:07:26.333301-07:00","dependencies":[{"issue_id":"bd-13","depends_on_id":"bd-9","type":"parent-child","created_at":"2025-10-12T14:41:07.951605-07:00","created_by":"stevey"}]} +{"id":"bd-14","title":"Implement ID remapping with reference updates","description":"Allocate new IDs for colliding issues. Update all text field references using word-boundary regex (\\bbd-10\\b). Update dependency records. Build id_mapping for reporting. Handle chain dependencies properly.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.367596-07:00","updated_at":"2025-10-12T15:07:26.333388-07:00","dependencies":[{"issue_id":"bd-14","depends_on_id":"bd-9","type":"parent-child","created_at":"2025-10-12T14:41:07.956041-07:00","created_by":"stevey"}]} +{"id":"bd-15","title":"Add --resolve-collisions flag and user reporting","description":"Add import flags: --resolve-collisions (auto-fix) and --dry-run (preview). Display clear report: collisions detected, remappings applied (old→new with scores), reference counts updated. Default behavior: fail on collision (safe).","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.534721-07:00","updated_at":"2025-10-12T15:07:26.33347-07:00","dependencies":[{"issue_id":"bd-15","depends_on_id":"bd-9","type":"parent-child","created_at":"2025-10-12T14:41:07.961157-07:00","created_by":"stevey"}]} +{"id":"bd-16","title":"Write comprehensive collision resolution tests","description":"Test cases: simple collision, multiple collisions, dependency updates, text reference updates, chain dependencies, edge cases (partial ID matches, case sensitivity, triple merges). Add to import_test.go and collision_test.go.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.702127-07:00","updated_at":"2025-10-12T15:07:26.333559-07:00","dependencies":[{"issue_id":"bd-16","depends_on_id":"bd-9","type":"parent-child","created_at":"2025-10-12T14:41:07.965816-07:00","created_by":"stevey"}]} +{"id":"bd-17","title":"Update documentation for collision resolution","description":"Update README.md with collision resolution section. Update CLAUDE.md with new workflow. Document --resolve-collisions and --dry-run flags. Add example scenarios showing branch merge workflows.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-12T14:40:56.866649-07:00","updated_at":"2025-10-12T15:07:26.333636-07:00","dependencies":[{"issue_id":"bd-17","depends_on_id":"bd-9","type":"parent-child","created_at":"2025-10-12T14:41:07.970302-07:00","created_by":"stevey"}]} +{"id":"bd-18","title":"Add design/notes/acceptance_criteria fields to update command","description":"Currently bd update only supports status, priority, title, assignee. Add support for --design, --notes, --acceptance-criteria flags. This makes it easier to add detailed designs to issues after creation.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-12T14:40:57.032395-07:00","updated_at":"2025-10-12T15:07:26.33372-07:00"} +{"id":"bd-2","title":"Add PostgreSQL backend","description":"Implement PostgreSQL storage backend as alternative to SQLite for larger teams","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-10-12T00:43:03.457453-07:00","updated_at":"2025-10-12T15:07:26.333816-07:00","closed_at":"2025-10-12T14:15:04.00695-07:00"} +{"id":"bd-3","title":"Document git workflow in README","description":"Add Git Workflow section to README explaining binary vs text approaches","status":"closed","priority":1,"issue_type":"chore","created_at":"2025-10-12T00:43:03.461615-07:00","updated_at":"2025-10-12T15:07:26.333908-07:00","closed_at":"2025-10-12T00:43:30.283178-07:00"} +{"id":"bd-4","title":"Add demo GIF/video showing bd quickstart in action","description":"Record asciinema or create animated GIF showing the full workflow","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-12T10:50:49.500051-07:00","updated_at":"2025-10-12T15:07:26.334-07:00","dependencies":[{"issue_id":"bd-4","depends_on_id":"bd-8","type":"parent-child","created_at":"2025-10-12T10:51:08.399915-07:00","created_by":"stevey"}]} +{"id":"bd-5","title":"Implement MCP server for Claude Desktop","description":"Complete the claude-desktop-mcp example with working TypeScript implementation","status":"open","priority":1,"issue_type":"feature","created_at":"2025-10-12T10:50:50.942964-07:00","updated_at":"2025-10-12T15:07:26.334092-07:00","dependencies":[{"issue_id":"bd-5","depends_on_id":"bd-8","type":"parent-child","created_at":"2025-10-12T10:51:08.404381-07:00","created_by":"stevey"}]} +{"id":"bd-6","title":"Add migration scripts for GitHub Issues","description":"Create scripts to import from GitHub Issues API or exported JSON","status":"open","priority":2,"issue_type":"feature","created_at":"2025-10-12T10:50:52.140018-07:00","updated_at":"2025-10-12T15:07:26.334162-07:00","dependencies":[{"issue_id":"bd-6","depends_on_id":"bd-8","type":"parent-child","created_at":"2025-10-12T10:51:08.40857-07:00","created_by":"stevey"}]} +{"id":"bd-7","title":"Add performance benchmarks document","description":"Document actual performance metrics with hyperfine tests","status":"open","priority":3,"issue_type":"task","created_at":"2025-10-12T10:50:53.294516-07:00","updated_at":"2025-10-12T15:07:26.334232-07:00","dependencies":[{"issue_id":"bd-7","depends_on_id":"bd-8","type":"parent-child","created_at":"2025-10-12T10:51:08.412835-07:00","created_by":"stevey"}]} +{"id":"bd-8","title":"Reach 1.0 release milestone","description":"Stabilize API, finalize documentation, comprehensive testing","status":"open","priority":1,"issue_type":"epic","created_at":"2025-10-12T10:50:54.457348-07:00","updated_at":"2025-10-12T15:07:26.334308-07:00"} +{"id":"bd-9","title":"Build collision resolution tooling for distributed branch workflows","description":"When branches diverge and both create issues, auto-incrementing IDs collide on merge. Build excellent tooling to detect collisions during import, auto-renumber issues with fewer dependencies, update all references in descriptions and dependency links, and provide clear user feedback. Goal: keep beautiful brevity of numeric IDs (bd-302) while handling distributed creation gracefully.","status":"in_progress","priority":1,"issue_type":"feature","created_at":"2025-10-12T13:39:34.608218-07:00","updated_at":"2025-10-12T15:07:26.334385-07:00"} diff --git a/cmd/bd/export.go b/cmd/bd/export.go index a3e290c8..4e73969d 100644 --- a/cmd/bd/export.go +++ b/cmd/bd/export.go @@ -48,6 +48,16 @@ Output to stdout by default, or use -o flag for file output.`, return issues[i].ID < issues[j].ID }) + // Populate dependencies for each issue + for _, issue := range issues { + deps, err := store.GetDependencyRecords(ctx, issue.ID) + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting dependencies for %s: %v\n", issue.ID, err) + os.Exit(1) + } + issue.Dependencies = deps + } + // Open output out := os.Stdout if output != "" { diff --git a/cmd/bd/import.go b/cmd/bd/import.go index 86581554..e376cb1a 100644 --- a/cmd/bd/import.go +++ b/cmd/bd/import.go @@ -47,6 +47,7 @@ Behavior: scanner := bufio.NewScanner(in) var created, updated, skipped int + var allIssues []*types.Issue // Store all issues for dependency processing lineNum := 0 for scanner.Scan() { @@ -65,6 +66,9 @@ Behavior: os.Exit(1) } + // Store for dependency processing later + allIssues = append(allIssues, &issue) + // Check if issue exists existing, err := store.GetIssue(ctx, issue.ID) if err != nil { @@ -121,11 +125,59 @@ Behavior: os.Exit(1) } + // Second pass: Process dependencies + // Do this after all issues are created to handle forward references + var depsCreated, depsSkipped int + for _, issue := range allIssues { + if len(issue.Dependencies) == 0 { + continue + } + + for _, dep := range issue.Dependencies { + // Check if dependency already exists + existingDeps, err := store.GetDependencyRecords(ctx, dep.IssueID) + if err != nil { + fmt.Fprintf(os.Stderr, "Error checking dependencies for %s: %v\n", dep.IssueID, err) + os.Exit(1) + } + + // Skip if this exact dependency already exists + exists := false + for _, existing := range existingDeps { + if existing.DependsOnID == dep.DependsOnID && existing.Type == dep.Type { + exists = true + break + } + } + + if exists { + depsSkipped++ + continue + } + + // Add dependency + if err := store.AddDependency(ctx, dep, "import"); err != nil { + // Ignore errors for missing target issues or cycles + // This can happen if dependencies reference issues not in the import + fmt.Fprintf(os.Stderr, "Warning: could not add dependency %s → %s: %v\n", + dep.IssueID, dep.DependsOnID, err) + continue + } + depsCreated++ + } + } + // Print summary fmt.Fprintf(os.Stderr, "Import complete: %d created, %d updated", created, updated) if skipped > 0 { fmt.Fprintf(os.Stderr, ", %d skipped", skipped) } + if depsCreated > 0 || depsSkipped > 0 { + fmt.Fprintf(os.Stderr, ", %d dependencies added", depsCreated) + if depsSkipped > 0 { + fmt.Fprintf(os.Stderr, " (%d already existed)", depsSkipped) + } + } fmt.Fprintf(os.Stderr, "\n") }, } diff --git a/internal/storage/sqlite/dependencies.go b/internal/storage/sqlite/dependencies.go index f6950e30..f6b8088b 100644 --- a/internal/storage/sqlite/dependencies.go +++ b/internal/storage/sqlite/dependencies.go @@ -178,6 +178,38 @@ func (s *SQLiteStorage) GetDependents(ctx context.Context, issueID string) ([]*t return scanIssues(rows) } +// GetDependencyRecords returns raw dependency records for an issue +func (s *SQLiteStorage) GetDependencyRecords(ctx context.Context, issueID string) ([]*types.Dependency, error) { + rows, err := s.db.QueryContext(ctx, ` + SELECT issue_id, depends_on_id, type, created_at, created_by + FROM dependencies + WHERE issue_id = ? + ORDER BY created_at ASC + `, issueID) + if err != nil { + return nil, fmt.Errorf("failed to get dependency records: %w", err) + } + defer rows.Close() + + var deps []*types.Dependency + for rows.Next() { + var dep types.Dependency + err := rows.Scan( + &dep.IssueID, + &dep.DependsOnID, + &dep.Type, + &dep.CreatedAt, + &dep.CreatedBy, + ) + if err != nil { + return nil, fmt.Errorf("failed to scan dependency: %w", err) + } + deps = append(deps, &dep) + } + + return deps, nil +} + // GetDependencyTree returns the full dependency tree func (s *SQLiteStorage) GetDependencyTree(ctx context.Context, issueID string, maxDepth int) ([]*types.TreeNode, error) { if maxDepth <= 0 { diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 47b42091..54f29f71 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -21,6 +21,7 @@ type Storage interface { RemoveDependency(ctx context.Context, issueID, dependsOnID string, actor string) error GetDependencies(ctx context.Context, issueID string) ([]*types.Issue, error) GetDependents(ctx context.Context, issueID string) ([]*types.Issue, error) + GetDependencyRecords(ctx context.Context, issueID string) ([]*types.Dependency, error) GetDependencyTree(ctx context.Context, issueID string, maxDepth int) ([]*types.TreeNode, error) DetectCycles(ctx context.Context) ([][]*types.Issue, error) diff --git a/internal/types/types.go b/internal/types/types.go index bd94598a..8c80054d 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -8,20 +8,21 @@ import ( // Issue represents a trackable work item type Issue struct { - ID string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - Design string `json:"design,omitempty"` - AcceptanceCriteria string `json:"acceptance_criteria,omitempty"` - Notes string `json:"notes,omitempty"` - Status Status `json:"status"` - Priority int `json:"priority"` - IssueType IssueType `json:"issue_type"` - Assignee string `json:"assignee,omitempty"` - EstimatedMinutes *int `json:"estimated_minutes,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - ClosedAt *time.Time `json:"closed_at,omitempty"` + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Design string `json:"design,omitempty"` + AcceptanceCriteria string `json:"acceptance_criteria,omitempty"` + Notes string `json:"notes,omitempty"` + Status Status `json:"status"` + Priority int `json:"priority"` + IssueType IssueType `json:"issue_type"` + Assignee string `json:"assignee,omitempty"` + EstimatedMinutes *int `json:"estimated_minutes,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + Dependencies []*Dependency `json:"dependencies,omitempty"` // Populated only for export/import } // Validate checks if the issue has valid field values