feat: Linear Integration (#655)
* Add Linear integration CLI with sync and status commands - Add `bd linear sync` for bidirectional issue sync with Linear - Add `bd linear status` to show configuration and sync state - Stub pull/push functions pending GraphQL client (bd-b6b.2) * Implement Linear GraphQL client with full sync support - Add LinearClient with auth, fetch, create, update methods - Implement pull/push operations with Beads type mapping - Clean up redundant comments and remove unused code * Add configurable data mapping and dependency sync for Linear - Add LinearMappingConfig with configurable priority/state/label/relation maps - Import parent-child and issue relations as Beads dependencies - Support custom workflow states via linear.state_map.* config * Add incremental sync support for Linear integration - Add FetchIssuesSince() method using updatedAt filter in GraphQL - Check linear.last_sync config to enable incremental pulls - Track sync mode (incremental vs full) in LinearPullStats * feat(linear): implement push updates for existing Linear issues Add FetchIssueByIdentifier method to retrieve single issues by identifier (e.g., "TEAM-123") for timestamp comparison during push. Update doPushToLinear to: - Fetch Linear issue to get internal ID and UpdatedAt timestamp - Compare timestamps: only update if local is newer - Build update payload with title, description, priority, and state - Call UpdateIssue for issues where local has newer changes Closes bd-b6b.5 * Implement Linear conflict resolution strategies - Add true conflict detection by fetching Linear timestamps via API - Implement --prefer-linear resolution (re-import from Linear) - Implement timestamp-based resolution (newer wins as default) - Fix linter issues: handle resp.Body.Close() and remove unused error return * Add Linear integration tests and documentation - Add comprehensive unit tests for Linear mapping (priority, state, labels, relations) - Update docs/CONFIG.md with Linear configuration reference - Add examples/linear-workflow guide for bidirectional sync - Remove AI section header comments from tests * Fix Linear GraphQL filter construction and improve test coverage - Refactor filter handling to combine team ID into main filter object - Add test for duplicate issue relation mapping - Add HTTP round-trip helper for testing request payload validation * Refactor Linear queries to use shared constant and add UUID validation - Extract linearIssuesQuery to deduplicate FetchIssues/FetchIssuesSince - Add linearMaxPageSize constant and UUID validation with regex - Expand test coverage for new functionality * Refactor Linear integration into internal/linear package - Extract types, client, and mapping logic from cmd/bd/linear.go - Create internal/linear/ package for better code organization - Update tests to work with new package structure * Add linear teams command to list available teams - Add FetchTeams GraphQL query to Linear client - Refactor config reading to support daemon mode - Add tests for teams listing functionality * Refactor Linear config to use getLinearConfig helper - Consolidate config/env var lookup using getLinearConfig function - Add LINEAR_TEAM_ID environment variable support - Update error messages to include env var configuration options * Add hash ID generation and improve Linear conflict detection - Add configurable hash ID mode for Linear imports (matches bd/Jira) - Improve conflict detection with content hash comparison - Enhance conflict resolution with skip/force update tracking * Fix test for updated doPushToLinear signature - Add missing skipUpdateIDs parameter to test call
This commit is contained in:
529
examples/linear-workflow/README.md
Normal file
529
examples/linear-workflow/README.md
Normal file
@@ -0,0 +1,529 @@
|
||||
# Linear Integration for bd
|
||||
|
||||
Bidirectional synchronization between Linear and bd (beads) using the built-in `bd linear` commands.
|
||||
|
||||
## Overview
|
||||
|
||||
The Linear integration provides:
|
||||
|
||||
- **Pull**: Import issues from Linear into bd
|
||||
- **Push**: Export bd issues to Linear
|
||||
- **Bidirectional Sync**: Two-way sync with conflict resolution
|
||||
- **Incremental Sync**: Only sync issues changed since last sync
|
||||
- **Configurable Mappings**: Customize priority, state, label, and relation mappings
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Get Linear Credentials
|
||||
|
||||
1. **API Key**: Go to Linear → Settings → API → Personal API keys → Create key
|
||||
2. **Team ID**: Go to Linear → Settings → General → find the Team ID (UUID format)
|
||||
|
||||
### 2. Configure bd
|
||||
|
||||
```bash
|
||||
# Set API key (or use LINEAR_API_KEY environment variable)
|
||||
bd config set linear.api_key "lin_api_YOUR_API_KEY_HERE"
|
||||
|
||||
# Set team ID
|
||||
bd config set linear.team_id "YOUR_TEAM_UUID"
|
||||
```
|
||||
|
||||
### 3. Sync with Linear
|
||||
|
||||
```bash
|
||||
# Check configuration status
|
||||
bd linear status
|
||||
|
||||
# Pull issues from Linear
|
||||
bd linear sync --pull
|
||||
|
||||
# Push local issues to Linear
|
||||
bd linear sync --push
|
||||
|
||||
# Full bidirectional sync (pull, resolve conflicts, push)
|
||||
bd linear sync
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### API Key
|
||||
|
||||
Linear uses Personal API Keys for authentication. Create one at:
|
||||
**Linear → Settings → API → Personal API keys**
|
||||
|
||||
Store securely:
|
||||
|
||||
```bash
|
||||
# Option 1: bd config (stored in database)
|
||||
bd config set linear.api_key "lin_api_..."
|
||||
|
||||
# Option 2: Environment variable
|
||||
export LINEAR_API_KEY="lin_api_..."
|
||||
```
|
||||
|
||||
### Team ID
|
||||
|
||||
Find your Team ID in Linear:
|
||||
- **Settings → General** → Look for Team ID
|
||||
- Or extract from URLs: `https://linear.app/YOUR_TEAM/...` → Go to team settings
|
||||
|
||||
## Sync Modes
|
||||
|
||||
### Pull Only (Linear → bd)
|
||||
|
||||
Import issues from Linear without pushing local changes:
|
||||
|
||||
```bash
|
||||
bd linear sync --pull
|
||||
|
||||
# Filter by state
|
||||
bd linear sync --pull --state open # Only open issues
|
||||
bd linear sync --pull --state closed # Only closed issues
|
||||
bd linear sync --pull --state all # All issues (default)
|
||||
```
|
||||
|
||||
### Push Only (bd → Linear)
|
||||
|
||||
Export local issues to Linear without pulling:
|
||||
|
||||
```bash
|
||||
bd linear sync --push
|
||||
|
||||
# Create only (don't update existing Linear issues)
|
||||
bd linear sync --push --create-only
|
||||
|
||||
# Disable automatic external_ref update
|
||||
bd linear sync --push --update-refs=false
|
||||
```
|
||||
|
||||
### Bidirectional Sync
|
||||
|
||||
Full two-way sync with conflict detection and resolution:
|
||||
|
||||
```bash
|
||||
# Default: newer timestamp wins conflicts
|
||||
bd linear sync
|
||||
|
||||
# Always prefer local version on conflicts
|
||||
bd linear sync --prefer-local
|
||||
|
||||
# Always prefer Linear version on conflicts
|
||||
bd linear sync --prefer-linear
|
||||
```
|
||||
|
||||
### Dry Run
|
||||
|
||||
Preview what would happen without making changes:
|
||||
|
||||
```bash
|
||||
bd linear sync --dry-run
|
||||
```
|
||||
|
||||
## Data Mapping
|
||||
|
||||
### Priority Mapping
|
||||
|
||||
Linear and Beads use different priority semantics:
|
||||
|
||||
| Linear | Meaning | Beads | Meaning |
|
||||
|--------|---------|-------|---------|
|
||||
| 0 | No priority | 4 | Backlog |
|
||||
| 1 | Urgent | 0 | Critical |
|
||||
| 2 | High | 1 | High |
|
||||
| 3 | Medium | 2 | Medium |
|
||||
| 4 | Low | 3 | Low |
|
||||
|
||||
**Default mapping** (Linear → Beads):
|
||||
- 0 (no priority) → 4 (backlog)
|
||||
- 1 (urgent) → 0 (critical)
|
||||
- 2 (high) → 1 (high)
|
||||
- 3 (medium) → 2 (medium)
|
||||
- 4 (low) → 3 (low)
|
||||
|
||||
**Custom mappings:**
|
||||
|
||||
```bash
|
||||
# Override default mappings
|
||||
bd config set linear.priority_map.0 2 # No priority -> Medium (instead of Backlog)
|
||||
bd config set linear.priority_map.1 1 # Urgent -> High (instead of Critical)
|
||||
```
|
||||
|
||||
### State Mapping
|
||||
|
||||
Map Linear workflow states to bd statuses:
|
||||
|
||||
| Linear State Type | Beads Status |
|
||||
|-------------------|--------------|
|
||||
| backlog | open |
|
||||
| unstarted | open |
|
||||
| started | in_progress |
|
||||
| completed | closed |
|
||||
| canceled | closed |
|
||||
|
||||
**Custom state mappings** (for custom workflow states):
|
||||
|
||||
```bash
|
||||
# Map by state type
|
||||
bd config set linear.state_map.started in_progress
|
||||
|
||||
# Map by state name (for custom workflow states)
|
||||
bd config set linear.state_map.in_review in_progress
|
||||
bd config set linear.state_map.blocked blocked
|
||||
bd config set linear.state_map.on_hold blocked
|
||||
bd config set linear.state_map.testing in_progress
|
||||
bd config set linear.state_map.deployed closed
|
||||
```
|
||||
|
||||
### Label to Issue Type
|
||||
|
||||
Infer bd issue type from Linear labels:
|
||||
|
||||
| Linear Label | Beads Type |
|
||||
|--------------|------------|
|
||||
| bug, defect | bug |
|
||||
| feature, enhancement | feature |
|
||||
| epic | epic |
|
||||
| chore, maintenance | chore |
|
||||
| task | task |
|
||||
|
||||
**Custom label mappings:**
|
||||
|
||||
```bash
|
||||
bd config set linear.label_type_map.incident bug
|
||||
bd config set linear.label_type_map.improvement feature
|
||||
bd config set linear.label_type_map.tech_debt chore
|
||||
bd config set linear.label_type_map.story feature
|
||||
```
|
||||
|
||||
### Relation Mapping
|
||||
|
||||
Map Linear relations to bd dependencies:
|
||||
|
||||
| Linear Relation | Beads Dependency |
|
||||
|-----------------|------------------|
|
||||
| blocks | blocks |
|
||||
| blockedBy | blocks (inverted) |
|
||||
| duplicate | duplicates |
|
||||
| related | related |
|
||||
| (parent) | parent-child |
|
||||
|
||||
**Custom relation mappings:**
|
||||
|
||||
```bash
|
||||
bd config set linear.relation_map.causes discovered-from
|
||||
bd config set linear.relation_map.duplicate related
|
||||
```
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
Conflicts occur when both local and Linear versions are modified since the last sync.
|
||||
|
||||
### Timestamp-based (Default)
|
||||
|
||||
The newer version wins:
|
||||
|
||||
```bash
|
||||
bd linear sync # Newer timestamp wins
|
||||
```
|
||||
|
||||
### Prefer Local
|
||||
|
||||
Local bd version always wins:
|
||||
|
||||
```bash
|
||||
bd linear sync --prefer-local
|
||||
```
|
||||
|
||||
Use when:
|
||||
- Local is your source of truth
|
||||
- You've made deliberate changes locally
|
||||
|
||||
### Prefer Linear
|
||||
|
||||
Linear version always wins:
|
||||
|
||||
```bash
|
||||
bd linear sync --prefer-linear
|
||||
```
|
||||
|
||||
Use when:
|
||||
- Linear is your source of truth
|
||||
- You want to accept team changes
|
||||
|
||||
## Workflows
|
||||
|
||||
### Workflow 1: Initial Import from Linear
|
||||
|
||||
First-time import of existing Linear issues:
|
||||
|
||||
```bash
|
||||
# Configure credentials
|
||||
bd config set linear.api_key "lin_api_..."
|
||||
bd config set linear.team_id "team-uuid"
|
||||
|
||||
# Check status
|
||||
bd linear status
|
||||
|
||||
# Import all issues
|
||||
bd linear sync --pull
|
||||
|
||||
# See what was imported
|
||||
bd stats
|
||||
bd list --json
|
||||
```
|
||||
|
||||
### Workflow 2: Daily Sync
|
||||
|
||||
Regular synchronization:
|
||||
|
||||
```bash
|
||||
# Pull latest from Linear (incremental since last sync)
|
||||
bd linear sync --pull
|
||||
|
||||
# Do local work
|
||||
bd update bd-123 --status in_progress
|
||||
# ... work ...
|
||||
bd close bd-123 --reason "Fixed"
|
||||
|
||||
# Push changes to Linear
|
||||
bd linear sync --push
|
||||
|
||||
# Or do full bidirectional sync
|
||||
bd linear sync
|
||||
```
|
||||
|
||||
### Workflow 3: Create Local Issues, Push to Linear
|
||||
|
||||
Create issues locally and sync to Linear:
|
||||
|
||||
```bash
|
||||
# Create issue locally
|
||||
bd create "Fix authentication bug" -t bug -p 1
|
||||
|
||||
# Push to Linear (creates new Linear issue, updates external_ref)
|
||||
bd linear sync --push
|
||||
|
||||
# Verify
|
||||
bd show bd-abc # Should have external_ref pointing to Linear
|
||||
```
|
||||
|
||||
### Workflow 4: Migrate to bd
|
||||
|
||||
Full migration from Linear to bd:
|
||||
|
||||
```bash
|
||||
# Import all issues
|
||||
bd linear sync --pull --state all
|
||||
|
||||
# Preview import
|
||||
bd stats
|
||||
|
||||
# Continue using bd locally, push updates back to Linear
|
||||
bd linear sync # Regular bidirectional sync
|
||||
```
|
||||
|
||||
### Workflow 5: Read-Only Linear Mirror
|
||||
|
||||
Mirror Linear issues locally without pushing back:
|
||||
|
||||
```bash
|
||||
# Only ever pull, never push
|
||||
bd linear sync --pull
|
||||
|
||||
# Set up a cron job or alias
|
||||
alias bd-mirror="bd linear sync --pull"
|
||||
```
|
||||
|
||||
## Status & Debugging
|
||||
|
||||
### Check Sync Status
|
||||
|
||||
```bash
|
||||
bd linear status
|
||||
```
|
||||
|
||||
Shows:
|
||||
- Configuration status (API key, team ID)
|
||||
- Last sync timestamp
|
||||
- Issues with Linear links
|
||||
- Issues pending push (local only)
|
||||
|
||||
### JSON Output
|
||||
|
||||
```bash
|
||||
bd linear status --json
|
||||
bd linear sync --json
|
||||
```
|
||||
|
||||
### Verbose Output
|
||||
|
||||
The sync command shows progress:
|
||||
- Number of issues pulled/pushed
|
||||
- Conflicts detected and resolved
|
||||
- Errors and warnings
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
All configuration keys for Linear integration:
|
||||
|
||||
```bash
|
||||
# Required
|
||||
linear.api_key # Linear API key (or LINEAR_API_KEY env var)
|
||||
linear.team_id # Linear team UUID
|
||||
|
||||
# Automatic (set by bd)
|
||||
linear.last_sync # ISO8601 timestamp of last sync
|
||||
|
||||
# ID generation (optional)
|
||||
linear.id_mode # hash (default) or db (let bd generate IDs)
|
||||
linear.hash_length # Hash length 3-8 (default: 6)
|
||||
|
||||
# Priority mapping (Linear 0-4 to Beads 0-4)
|
||||
linear.priority_map.0 # No priority -> ? (default: 4/backlog)
|
||||
linear.priority_map.1 # Urgent -> ? (default: 0/critical)
|
||||
linear.priority_map.2 # High -> ? (default: 1/high)
|
||||
linear.priority_map.3 # Medium -> ? (default: 2/medium)
|
||||
linear.priority_map.4 # Low -> ? (default: 3/low)
|
||||
|
||||
# State mapping (Linear state type/name to Beads status)
|
||||
linear.state_map.backlog # (default: open)
|
||||
linear.state_map.unstarted # (default: open)
|
||||
linear.state_map.started # (default: in_progress)
|
||||
linear.state_map.completed # (default: closed)
|
||||
linear.state_map.canceled # (default: closed)
|
||||
linear.state_map.<custom> # Map custom state names
|
||||
|
||||
# Label to issue type mapping
|
||||
linear.label_type_map.bug # (default: bug)
|
||||
linear.label_type_map.defect # (default: bug)
|
||||
linear.label_type_map.feature # (default: feature)
|
||||
linear.label_type_map.enhancement # (default: feature)
|
||||
linear.label_type_map.epic # (default: epic)
|
||||
linear.label_type_map.chore # (default: chore)
|
||||
linear.label_type_map.maintenance # (default: chore)
|
||||
linear.label_type_map.task # (default: task)
|
||||
linear.label_type_map.<custom> # Map custom labels
|
||||
|
||||
# Relation mapping (Linear relation type to Beads dependency type)
|
||||
linear.relation_map.blocks # (default: blocks)
|
||||
linear.relation_map.blockedBy # (default: blocks)
|
||||
linear.relation_map.duplicate # (default: duplicates)
|
||||
linear.relation_map.related # (default: related)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Linear API key not configured"
|
||||
|
||||
Set the API key:
|
||||
|
||||
```bash
|
||||
bd config set linear.api_key "lin_api_YOUR_KEY"
|
||||
# Or
|
||||
export LINEAR_API_KEY="lin_api_YOUR_KEY"
|
||||
```
|
||||
|
||||
### "Linear team ID not configured"
|
||||
|
||||
Set the team ID:
|
||||
|
||||
```bash
|
||||
bd config set linear.team_id "YOUR_TEAM_UUID"
|
||||
```
|
||||
|
||||
### "GraphQL errors: Not authorized"
|
||||
|
||||
- Verify your API key is correct
|
||||
- Check that the API key has access to the team
|
||||
- Ensure the key hasn't been revoked
|
||||
|
||||
### "Rate limited"
|
||||
|
||||
Linear has API rate limits. The client automatically retries with exponential backoff:
|
||||
- 3 retries with increasing delays
|
||||
- If still failing, wait and retry later
|
||||
|
||||
### "Conflict detection failed"
|
||||
|
||||
- Check network connectivity
|
||||
- Verify API key permissions
|
||||
- Check `bd linear status` for configuration issues
|
||||
|
||||
### Sync seems slow
|
||||
|
||||
For large projects, initial sync fetches all issues. Subsequent syncs are incremental (only issues changed since `linear.last_sync`).
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Single team**: Sync is configured per-team (one team_id per bd project)
|
||||
- **No attachments**: Attachments are not synced
|
||||
- **No comments**: Comments are not synced (only description)
|
||||
- **Custom fields**: Linear custom fields are not mapped
|
||||
- **Projects**: Linear projects are not mapped (use labels for categorization)
|
||||
- **Cycles**: Linear cycles/sprints are not mapped
|
||||
|
||||
## See Also
|
||||
|
||||
- [CONFIG.md](../../docs/CONFIG.md) - Full configuration documentation
|
||||
- [Jira Import Example](../jira-import/) - Similar integration for Jira
|
||||
- [Linear GraphQL API](https://developers.linear.app/docs/graphql/working-with-the-graphql-api)
|
||||
|
||||
---
|
||||
|
||||
## Example Session
|
||||
|
||||
```bash
|
||||
# Initial setup
|
||||
$ bd init --quiet
|
||||
$ bd config set linear.api_key "lin_api_abc123..."
|
||||
$ bd config set linear.team_id "team-uuid-456"
|
||||
|
||||
# Check status
|
||||
$ bd linear status
|
||||
Linear Sync Status
|
||||
==================
|
||||
|
||||
Team ID: team-uuid-456
|
||||
API Key: lin_...c123
|
||||
Last Sync: Never
|
||||
|
||||
Total Issues: 0
|
||||
With Linear: 0
|
||||
Local Only: 0
|
||||
|
||||
# Pull from Linear
|
||||
$ bd linear sync --pull
|
||||
→ Pulling issues from Linear...
|
||||
Full sync (no previous sync timestamp)
|
||||
✓ Pulled 47 issues (47 created, 0 updated)
|
||||
|
||||
✓ Linear sync complete
|
||||
|
||||
# Check what we got
|
||||
$ bd stats
|
||||
Issues: 47 (42 open, 5 closed)
|
||||
Types: 23 task, 15 bug, 7 feature, 2 epic
|
||||
|
||||
# Create a local issue
|
||||
$ bd create "New bug from testing" -t bug -p 1
|
||||
Created: bd-a1b2c3
|
||||
|
||||
# Push to Linear
|
||||
$ bd linear sync --push
|
||||
→ Pushing issues to Linear...
|
||||
Created: bd-a1b2c3 -> TEAM-148
|
||||
✓ Pushed 1 issues (1 created, 0 updated)
|
||||
|
||||
✓ Linear sync complete
|
||||
|
||||
# Full bidirectional sync
|
||||
$ bd linear sync
|
||||
→ Pulling issues from Linear...
|
||||
Incremental sync since 2025-01-17 10:30:00
|
||||
✓ Pulled 3 issues (0 created, 3 updated)
|
||||
→ Pushing issues to Linear...
|
||||
✓ Pushed 2 issues (0 created, 2 updated)
|
||||
|
||||
✓ Linear sync complete
|
||||
```
|
||||
Reference in New Issue
Block a user