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 ReviewProgressing(active work)Out ReviewDone
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:
JIRA_EMAIL=your.email@domain.com
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
parentfield for Epic links - Classic: Use
customfield_10014for 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:
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:
## 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
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)
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: section