Implement bd-10: Export/import dependencies in JSONL

Added dependency support to export/import workflow:

Changes:
- Added GetDependencyRecords() to Storage interface to get raw dependency records
- Extended Issue struct with Dependencies field (omitempty for backward compat)
- Modified export.go to populate dependencies for each issue
- Modified import.go to process dependencies in second pass after all issues exist
- All tests pass

Benefits:
- JSONL is now self-contained with full dependency information
- Enables proper collision resolution in future (bd-12+)
- Idempotent imports: existing dependencies are not duplicated
- Forward references handled: dependencies created after all issues exist

Example output:
{
  "id": "bd-10",
  "title": "...",
  "dependencies": [{
    "issue_id": "bd-10",
    "depends_on_id": "bd-9",
    "type": "parent-child",
    "created_at": "...",
    "created_by": "stevey"
  }]
}

Closes bd-10

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-10-12 15:08:05 -07:00
parent 216f640ab4
commit 8d3bbbcc52
6 changed files with 128 additions and 32 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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