Claude
Skills
Sign in
Back

jira-workflow

Included with Lifetime
$97 forever

Orchestrate Jira workflows end-to-end. Use when building stories with approvals, transitioning items through lifecycle states, or syncing task completion with Jira.

Productivity

What this skill does

# Jira Workflow Orchestration Skill

> Complete workflow management for Jira: building stories (SAFe), getting approvals, and transitioning items through the development lifecycle (To Do → Progressing → Done).

**IMPORTANT**: This project uses Next-Gen (Team-managed) Jira with custom workflow states. The actual states are:
- `To Do` (backlog)
- `In Review`
- `Progressing` (active work)
- `Out Review`
- `Done`

Always query available transitions first: `GET /rest/api/3/issue/{key}/transitions`

## When to Use

- Creating new user stories, epics, or tasks for the project
- Getting user approval before creating Jira items
- Moving stories through workflow states as work progresses
- Syncing Claude Code task completion with Jira status
- Managing sprint planning and backlog refinement
- Tracking development progress in real-time

## Prerequisites

**Environment Variables:**
```bash
[email protected]
JIRA_API_TOKEN=your_api_token
JIRA_BASE_URL=https://your-org.atlassian.net
JIRA_PROJECT_KEY=SCRUM
JIRA_BOARD_ID=1
```

**Project Configuration:**
- Must know if project is Next-Gen (Team-managed) or Classic (Company-managed)
- Next-Gen: Use `parent` field for Epic links
- Classic: Use `customfield_10014` for Epic links

---

## Core Workflow Pattern

### The Approval-Create-Track Loop

```
1. PLAN: Analyze task requirements
   ↓
2. PROPOSE: Present story to user for approval
   ↓
3. APPROVE: User confirms or modifies
   ↓
4. CREATE: Issue created in Jira backlog
   ↓
5. START: Transition to "Progressing" when work begins
   ↓
6. COMPLETE: Transition to "Done" when work verified
   ↓
7. SYNC: Update Jira with implementation details
```

---

## Phase 1: Story Building (SAFe Format)

### Building a Story Proposal

When user requests work, build a SAFe-compliant story proposal:

```javascript
function buildStoryProposal(task) {
  return {
    summary: `As a ${task.persona}, I want ${task.goal}, so that ${task.benefit}`,
    description: {
      userStory: `As a **${task.persona}**, I want **${task.goal}**, so that **${task.benefit}**.`,
      acceptanceCriteria: task.scenarios.map(s => ({
        name: s.name,
        given: s.given,
        when: s.when,
        then: s.then
      })),
      definitionOfDone: [
        'Code reviewed and approved',
        'Unit tests written and passing',
        'Integration tests passing',
        'Documentation updated',
        'Deployed to staging',
        'Validated in production'
      ],
      technicalNotes: task.technicalNotes || []
    },
    category: task.category, // authentication, ui, api, database, etc.
    estimatedComplexity: task.complexity || 'medium', // small, medium, large
    subtasks: task.subtasks || []
  };
}
```

### Presenting for Approval

**CRITICAL: Always get user approval before creating Jira items.**

Use this prompt pattern:

```markdown
## Proposed Jira Story

**Summary:** As a [persona], I want [goal], so that [benefit]

**Category:** [category]
**Complexity:** [small/medium/large]

### Acceptance Criteria

**Scenario 1: [Name]**
- **GIVEN** [precondition]
- **WHEN** [action]
- **THEN** [expected result]

### Subtasks (if any)
1. [Subtask 1]
2. [Subtask 2]
3. [Subtask 3]

---

**Do you want me to create this in Jira?**

Options:
1. **Yes, create as-is** - I'll create the story now
2. **Modify** - Tell me what to change
3. **Skip** - Don't create in Jira, just do the work
```

---

## Phase 2: Issue Creation

### Create Story in Jira

```javascript
const JIRA_EMAIL = process.env.JIRA_EMAIL;
const JIRA_API_TOKEN = process.env.JIRA_API_TOKEN;
const JIRA_BASE_URL = process.env.JIRA_BASE_URL;
const PROJECT_KEY = process.env.JIRA_PROJECT_KEY;

const auth = Buffer.from(`${JIRA_EMAIL}:${JIRA_API_TOKEN}`).toString('base64');
const headers = {
  'Authorization': `Basic ${auth}`,
  'Content-Type': 'application/json',
  'Accept': 'application/json'
};

async function createStory(proposal, epicKey = null) {
  const body = {
    fields: {
      project: { key: PROJECT_KEY },
      issuetype: { name: 'Story' },
      summary: proposal.summary,
      description: buildADF(proposal.description),
      labels: [proposal.category.toLowerCase().replace(/\s+/g, '-')]
    }
  };

  // Link to Epic (Next-Gen project)
  if (epicKey) {
    body.fields.parent = { key: epicKey };
  }

  const response = await fetch(`${JIRA_BASE_URL}/rest/api/3/issue`, {
    method: 'POST',
    headers,
    body: JSON.stringify(body)
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Failed to create story: ${error}`);
  }

  const issue = await response.json();
  console.log(`Created: ${issue.key} - ${proposal.summary}`);

  // Create subtasks if any
  if (proposal.subtasks?.length > 0) {
    for (const subtask of proposal.subtasks) {
      await createSubtask(issue.key, subtask);
      await delay(100); // Rate limiting
    }
  }

  return issue;
}

async function createSubtask(parentKey, summary) {
  const body = {
    fields: {
      project: { key: PROJECT_KEY },
      issuetype: { name: 'Subtask' }, // Note: 'Subtask' for Next-Gen
      parent: { key: parentKey },
      summary: summary
    }
  };

  const response = await fetch(`${JIRA_BASE_URL}/rest/api/3/issue`, {
    method: 'POST',
    headers,
    body: JSON.stringify(body)
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Failed to create subtask: ${error}`);
  }

  return response.json();
}

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
```

### Build Atlassian Document Format (ADF)

```javascript
function buildADF(content) {
  const sections = [];

  // User Story Section
  sections.push({
    type: 'heading',
    attrs: { level: 2 },
    content: [{ type: 'text', text: 'User Story' }]
  });
  sections.push({
    type: 'paragraph',
    content: [{ type: 'text', text: content.userStory }]
  });

  // Acceptance Criteria Section
  sections.push({
    type: 'heading',
    attrs: { level: 2 },
    content: [{ type: 'text', text: 'Acceptance Criteria' }]
  });

  for (const scenario of content.acceptanceCriteria) {
    sections.push({
      type: 'heading',
      attrs: { level: 3 },
      content: [{ type: 'text', text: `Scenario: ${scenario.name}` }]
    });
    sections.push({
      type: 'bulletList',
      content: [
        { type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: `GIVEN ${scenario.given}`, marks: [{ type: 'strong' }] }] }] },
        { type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: `WHEN ${scenario.when}`, marks: [{ type: 'strong' }] }] }] },
        { type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: `THEN ${scenario.then}`, marks: [{ type: 'strong' }] }] }] }
      ]
    });
  }

  // Definition of Done Section
  sections.push({
    type: 'heading',
    attrs: { level: 2 },
    content: [{ type: 'text', text: 'Definition of Done' }]
  });
  sections.push({
    type: 'bulletList',
    content: content.definitionOfDone.map(item => ({
      type: 'listItem',
      content: [{ type: 'paragraph', content: [{ type: 'text', text: `[ ] ${item}` }] }]
    }))
  });

  // Technical Notes (if any)
  if (content.technicalNotes?.length > 0) {
    sections.push({
      type: 'heading',
      attrs: { level: 2 },
      content: [{ type: 'text', text: 'Technical Notes' }]
    });
    sections.push({
      type: 'bulletList',
      content: content.technicalNotes.map(note => ({
        type: 'listItem',
        content: [{ type: 'paragraph', content: [{ type: 'text', text: note }] }]
      }))
    });
  }

  return { type: 'doc', version: 1, content: secti

Related in Productivity