# Jira Integration

> JIRA integration for automated ticket creation, issue tracking, and project management workflows

- Skill: `lodetomasi/jira-integration` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lodetomasi/jira-integration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lodetomasi/jira-integration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: lodetomasi (https://skillmd.com/u/lodetomasi)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/lodetomasi/jira-integration

---


# JIRA Integration Skill

## Overview
JIRA integration skill for automating ticket creation from conversations, bug reporting, task management, and synchronizing development workflows with project management.

## Capabilities

### 1. Ticket Management
- Create issues from conversations
- Update issue status
- Add comments
- Link related issues
- Track time

### 2. Automation
- Auto-create bugs from errors
- Link commits to issues
- Update status from PR merges
- Generate reports

### 3. Query & Search
- Search issues (JQL)
- Filter by project/status
- Get sprint information
- Track velocity

### 4. Reporting
- Sprint reports
- Burndown charts
- Team velocity
- Issue statistics

## JIRA REST API Setup

### Authentication

```python
from jira import JIRA
import os

# Basic auth
jira = JIRA(
    server='https://your-company.atlassian.net',
    basic_auth=(
        os.getenv('JIRA_EMAIL'),
        os.getenv('JIRA_API_TOKEN')
    )
)

# Or OAuth
jira = JIRA(
    server='https://your-company.atlassian.net',
    oauth={
        'access_token': os.getenv('JIRA_ACCESS_TOKEN'),
        'access_token_secret': os.getenv('JIRA_ACCESS_TOKEN_SECRET'),
        'consumer_key': os.getenv('JIRA_CONSUMER_KEY'),
        'key_cert': open('jira.pem').read()
    }
)
```

### Environment Variables

```bash
# .env file
JIRA_SERVER=https://your-company.atlassian.net
JIRA_EMAIL=your-email@example.com
JIRA_API_TOKEN=your-api-token
JIRA_PROJECT_KEY=PROJ  # Your project key
```

## Common Operations

### 1. Create Issue

```python
def create_bug_ticket(summary, description, priority='Medium'):
    """Create a bug ticket in JIRA"""
    issue_dict = {
        'project': {'key': 'PROJ'},
        'summary': summary,
        'description': description,
        'issuetype': {'name': 'Bug'},
        'priority': {'name': priority},
        'labels': ['auto-generated', 'from-monitoring']
    }

    new_issue = jira.create_issue(fields=issue_dict)
    return new_issue.key

# Example usage
ticket_key = create_bug_ticket(
    summary='N+1 query detected in AthleteService',
    description='''
    ## Issue
    N+1 query pattern detected in AthleteService.findAll()

    ## Details
    - Location: src/main/java/com/siae/sport/service/AthleteService.java:45
    - Queries executed: 1 + 100 (N+1)
    - Performance impact: 2.3s → 0.15s after fix

    ## Recommendation
    Use @EntityGraph or JOIN FETCH to eager load licenses

    ## Priority
    High - affects production performance
    ''',
    priority='High'
)

print(f"Created ticket: {ticket_key}")  # e.g., PROJ-1234
```

### 2. Search Issues

```python
def find_open_bugs(project_key='PROJ'):
    """Find all open bugs in project"""
    jql = f'project = {project_key} AND issuetype = Bug AND status != Done'
    issues = jira.search_issues(jql, maxResults=50)

    for issue in issues:
        print(f"{issue.key}: {issue.fields.summary}")
        print(f"  Status: {issue.fields.status}")
        print(f"  Assignee: {issue.fields.assignee}")
        print(f"  Priority: {issue.fields.priority}\n")

    return issues

def find_my_issues():
    """Find issues assigned to me"""
    jql = 'assignee = currentUser() AND status != Done ORDER BY priority DESC'
    return jira.search_issues(jql)
```

### 3. Update Issue

```python
def update_issue_status(issue_key, new_status):
    """Transition issue to new status"""
    issue = jira.issue(issue_key)

    # Get available transitions
    transitions = jira.transitions(issue)
    for t in transitions:
        if t['name'] == new_status:
            jira.transition_issue(issue, t['id'])
            print(f"Updated {issue_key} to {new_status}")
            return True

    print(f"Transition '{new_status}' not available for {issue_key}")
    return False

# Example
update_issue_status('PROJ-1234', 'In Progress')
```

### 4. Add Comment

```python
def add_comment(issue_key, comment):
    """Add comment to issue"""
    jira.add_comment(issue_key, comment)

# Example
add_comment('PROJ-1234', '''
Fix implemented:
- Added @EntityGraph annotation
- Query count reduced from 101 to 1
- Response time improved from 2.3s to 0.15s

Ready for review.
''')
```

### 5. Link Commit to Issue

```python
def link_commit_to_issue(issue_key, commit_sha, commit_message, repo_url):
    """Link Git commit to JIRA issue"""
    commit_url = f"{repo_url}/commit/{commit_sha}"

    comment = f'''
    Commit linked: [{commit_sha[:7]}|{commit_url}]

    {commit_message}
    '''

    jira.add_comment(issue_key, comment)
```

## Automation Workflows

### 1. Auto-Create Bug from Error Log

```python
#!/usr/bin/env python3
import re
from jira import JIRA

def parse_error_log(log_file):
    """Parse error log and create JIRA tickets"""
    errors = []

    with open(log_file) as f:
        for line in f:
            if 'ERROR' in line or 'Exception' in line:
                # Extract error details
                match = re.search(r'(\w+Exception): (.+)', line)
                if match:
                    exception_type = match.group(1)
                    message = match.group(2)
                    errors.append({
                        'type': exception_type,
                        'message': message
                    })

    return errors

def create_tickets_from_errors(errors, jira):
    """Create JIRA tickets for unique errors"""
    # Deduplicate errors
    unique_errors = {}
    for error in errors:
        key = error['type']
        if key not in unique_errors:
            unique_errors[key] = error

    # Create tickets
    created_tickets = []
    for error_type, error in unique_errors.items():
        ticket = jira.create_issue(fields={
            'project': {'key': 'PROJ'},
            'summary': f"{error_type} in production",
            'description': f"Error message: {error['message']}\n\nSee logs for stack trace.",
            'issuetype': {'name': 'Bug'},
            'priority': {'name': 'High'},
            'labels': ['production', 'auto-generated']
        })
        created_tickets.append(ticket.key)
        print(f"Created ticket: {ticket.key}")

    return created_tickets
```

### 2. Link PR to JIRA Issue

```bash
#!/bin/bash
# Git hook: Extract JIRA issue from branch name and link PR

BRANCH_NAME=$(git branch --show-current)

# Extract issue key (e.g., feature/PROJ-1234-add-auth → PROJ-1234)
ISSUE_KEY=$(echo $BRANCH_NAME | grep -oE '[A-Z]+-[0-9]+')

if [ -z "$ISSUE_KEY" ]; then
    echo "No JIRA issue key found in branch name"
    exit 0
fi

echo "Found JIRA issue: $ISSUE_KEY"

# Add issue key to commit message if not present
COMMIT_MSG=$(cat .git/COMMIT_EDITMSG)
if ! echo "$COMMIT_MSG" | grep -q "$ISSUE_KEY"; then
    echo "$COMMIT_MSG" | sed "1s/$/\n\nRelates to: $ISSUE_KEY/" > .git/COMMIT_EDITMSG
fi
```

### 3. Update Status on PR Merge

```python
# GitHub Action or GitLab CI script
def update_jira_on_merge(pr_title, pr_body, merged_by):
    """Update JIRA issue status when PR is merged"""
    # Extract JIRA issue key from PR
    match = re.search(r'([A-Z]+-\d+)', pr_title + pr_body)
    if not match:
        print("No JIRA issue found in PR")
        return

    issue_key = match.group(1)

    # Add comment
    jira.add_comment(issue_key, f'''
    Pull request merged by {merged_by}

    Changes deployed to staging. Ready for QA testing.
    ''')

    # Transition to "Ready for QA"
    update_issue_status(issue_key, 'Ready for QA')
```

## Integration Scripts

### jira_cli.py
Command-line JIRA interface:
```python
#!/usr/bin/env python3
import sys
import os
from jira import JIRA

jira = JIRA(
    server=os.getenv('JIRA_SERVER'),
    basic_auth=(os.getenv('JIRA_EMAIL'), os.getenv('JIRA_API_TOKEN'))
)

def create_issue(args):
    """Create new issue"""
    project = args[0] if args else 'PROJ'
    issue_type = args[1] if len(args) > 1 else 'Task'

    summary = input("Summary: ")
    description = input("Description: ")
    priority = input("Priority (Low/Medium/High): ") or 'Medium'

    issue = jira.create_issue(fields={
        'project': {'key': project},
        'summary': summary,
        'description': description,
        'issuetype': {'name': issue_type},
        'priority': {'name': priority}
    })

    print(f"\n✅ Created: {issue.key}")
    print(f"   URL: {jira.server}/browse/{issue.key}")

def list_issues(args):
    """List issues"""
    project = args[0] if args else 'PROJ'
    jql = f'project = {project} AND status != Done ORDER BY priority DESC, created DESC'

    issues = jira.search_issues(jql, maxResults=20)

    print(f"\n📋 Issues in {project}:\n")
    for issue in issues:
        print(f"{issue.key}: {issue.fields.summary}")
        print(f"  Status: {issue.fields.status.name} | Priority: {issue.fields.priority.name}")
        assignee = issue.fields.assignee.displayName if issue.fields.assignee else 'Unassigned'
        print(f"  Assignee: {assignee}\n")

def show_issue(args):
    """Show issue details"""
    if not args:
        print("Usage: jira_cli.py show ISSUE-KEY")
        sys.exit(1)

    issue_key = args[0]
    issue = jira.issue(issue_key)

    print(f"\n{issue.key}: {issue.fields.summary}")
    print(f"Status: {issue.fields.status.name}")
    print(f"Priority: {issue.fields.priority.name}")
    print(f"Assignee: {issue.fields.assignee.displayName if issue.fields.assignee else 'Unassigned'}")
    print(f"\nDescription:\n{issue.fields.description}")

    # Comments
    comments = jira.comments(issue)
    if comments:
        print(f"\nComments ({len(comments)}):")
        for comment in comments[-3:]:  # Last 3 comments
            print(f"\n  {comment.author.displayName} ({comment.created}):")
            print(f"  {comment.body[:100]}...")

def main():
    if len(sys.argv) < 2:
        print("Usage: jira_cli.py {create|list|show} [args]")
        sys.exit(1)

    command = sys.argv[1]
    args = sys.argv[2:]

    commands = {
        'create': create_issue,
        'list': list_issues,
        'show': show_issue,
    }

    if command in commands:
        commands[command](args)
    else:
        print(f"Unknown command: {command}")
        sys.exit(1)

if __name__ == '__main__':
    main()
```

### sprint_report.py
Generate sprint report:
```python
#!/usr/bin/env python3
from jira import JIRA
import os
from collections import defaultdict

jira = JIRA(
    server=os.getenv('JIRA_SERVER'),
    basic_auth=(os.getenv('JIRA_EMAIL'), os.getenv('JIRA_API_TOKEN'))
)

def get_sprint_report(board_id):
    """Generate sprint report"""
    # Get active sprint
    sprints = jira.sprints(board_id, state='active')
    if not sprints:
        print("No active sprint")
        return

    sprint = sprints[0]
    print(f"=== Sprint Report: {sprint.name} ===\n")

    # Get sprint issues
    jql = f'sprint = {sprint.id}'
    issues = jira.search_issues(jql, maxResults=100)

    # Group by status
    by_status = defaultdict(list)
    by_type = defaultdict(int)
    story_points = defaultdict(int)

    for issue in issues:
        status = issue.fields.status.name
        issue_type = issue.fields.issuetype.name

        by_status[status].append(issue)
        by_type[issue_type] += 1

        # Story points (if custom field configured)
        # points = getattr(issue.fields, 'customfield_10016', 0) or 0
        # story_points[status] += points

    # Print summary
    print(f"Total Issues: {len(issues)}\n")

    print("By Status:")
    for status, issues_list in by_status.items():
        print(f"  {status}: {len(issues_list)}")

    print("\nBy Type:")
    for issue_type, count in by_type.items():
        print(f"  {issue_type}: {count}")

    # Completion rate
    done = len(by_status.get('Done', []))
    completion_rate = (done / len(issues) * 100) if issues else 0
    print(f"\nCompletion Rate: {completion_rate:.1f}%")

    # List incomplete items
    print("\n⚠️  Incomplete Items:")
    for status, issues_list in by_status.items():
        if status != 'Done':
            for issue in issues_list:
                print(f"  {issue.key}: {issue.fields.summary}")

if __name__ == '__main__':
    import sys
    board_id = sys.argv[1] if len(sys.argv) > 1 else 1
    get_sprint_report(board_id)
```

## JQL (JIRA Query Language) Examples

```jql
# My open issues
assignee = currentUser() AND status != Done

# High priority bugs
project = PROJ AND issuetype = Bug AND priority = High AND status != Done

# Issues updated this week
project = PROJ AND updated >= -7d

# Issues in current sprint
sprint in openSprints()

# Overdue issues
duedate < now() AND status != Done

# Issues without assignee
project = PROJ AND assignee is EMPTY

# Issues created by me
reporter = currentUser() AND created >= -30d

# Performance-related issues
project = PROJ AND (summary ~ "performance" OR labels = performance)
```

## GitHub Actions Integration

```yaml
# .github/workflows/jira-integration.yml
name: JIRA Integration

on:
  pull_request:
    types: [opened, closed]
  issues:
    types: [opened]

jobs:
  jira:
    runs-on: ubuntu-latest
    steps:
      - name: Extract JIRA Issue
        id: extract
        run: |
          ISSUE_KEY=$(echo "${{ github.event.pull_request.title || github.event.issue.title }}" | grep -oE '[A-Z]+-[0-9]+' | head -1)
          echo "issue_key=$ISSUE_KEY" >> $GITHUB_OUTPUT

      - name: Update JIRA Issue
        if: steps.extract.outputs.issue_key != ''
        uses: atlassian/gajira-transition@v3
        with:
          issue: ${{ steps.extract.outputs.issue_key }}
          transition: "In Progress"
        env:
          JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
          JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
          JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
```

## Best Practices

1. **Use Branch Naming**: Include JIRA key (feature/PROJ-1234-description)
2. **Link Commits**: Reference JIRA in commit messages
3. **Automate Status**: Update on PR merge/deploy
4. **Consistent Labels**: Use standard labels (performance, bug, enhancement)
5. **Track Time**: Log work time on issues
6. **Sprint Planning**: Move issues to sprint during planning
7. **Comment Updates**: Add progress comments regularly
8. **Acceptance Criteria**: Define clear completion criteria
9. **Link Related Issues**: Use "blocks", "relates to"
10. **Close on Deploy**: Auto-close when deployed to production

## Project Configuration

### Issue Types
- **Epic**: Large features
- **Story**: User stories
- **Task**: Development tasks
- **Bug**: Defects
- **Improvement**: Enhancements

### Workflow
```
To Do → In Progress → Code Review → QA → Done
```

### Custom Fields (Examples)
- **Story Points**: Effort estimation
- **Sprint**: Current sprint
- **Environment**: Dev/Staging/Production
- **Component**: Frontend/Backend/Database

## Requirements

```bash
# Python JIRA library
pip install jira

# Environment variables
export JIRA_SERVER=https://siae.atlassian.net
export JIRA_EMAIL=your-email@siae.it
export JIRA_API_TOKEN=your-token

# GitHub CLI (for Actions)
brew install gh
```

## Metrics to Track

- **Sprint velocity**: Story points completed per sprint
- **Cycle time**: Days from "To Do" to "Done"
- **Lead time**: Days from created to done
- **Bug rate**: Bugs per sprint
- **Completion rate**: % of sprint goals achieved
- **WIP limit**: Issues in "In Progress"

