jira-workflow
Orchestrate Jira workflows end-to-end. Use when building stories with approvals, transitioning items through lifecycle states, or syncing task completion with Jira.
$ インストール
git clone https://github.com/01000001-01001110/agent-jira-skills /tmp/agent-jira-skills && cp -r /tmp/agent-jira-skills/jira-safe/jira-workflow ~/.claude/skills/agent-jira-skills// tip: Run this command in your terminal to install the skill
name: jira-workflow description: Orchestrate Jira workflows end-to-end. Use when building stories with approvals, transitioning items through lifecycle states, or syncing task completion with Jira.
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: sections };
}
Phase 3: Workflow Transitions
Get Available Transitions
async function getTransitions(issueKey) {
const response = await fetch(
`${JIRA_BASE_URL}/rest/api/3/issue/${issueKey}/transitions`,
{ headers }
);
if (!response.ok) {
throw new Error(`Failed to get transitions: ${response.status}`);
}
const data = await response.json();
return data.transitions;
}
Transition Issue to State
async function transitionTo(issueKey, targetState) {
// Get available transitions
const transitions = await getTransitions(issueKey);
// Find the transition to target state
const transition = transitions.find(t =>
t.to.name.toLowerCase() === targetState.toLowerCase() ||
t.name.toLowerCase() === targetState.toLowerCase()
);
if (!transition) {
console.log(`Available transitions for ${issueKey}:`);
transitions.forEach(t => console.log(` - ${t.name} → ${t.to.name}`));
throw new Error(`No transition to "${targetState}" found`);
}
// Execute the transition
const response = await fetch(
`${JIRA_BASE_URL}/rest/api/3/issue/${issueKey}/transitions`,
{
method: 'POST',
headers,
body: JSON.stringify({ transition: { id: transition.id } })
}
);
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to transition: ${error}`);
}
console.log(`${issueKey} transitioned to ${targetState}`);
return true;
}
Common Workflow Operations
// Start work on a story (To Do → Progressing)
async function startWork(issueKey) {
await transitionTo(issueKey, 'Progressing');
console.log(`Started: ${issueKey}`);
}
// Complete a story (Progressing → Done)
async function completeWork(issueKey) {
await transitionTo(issueKey, 'Done');
console.log(`Completed: ${issueKey}`);
}
// Move back to backlog (any state → To Do)
async function moveToBacklog(issueKey) {
await transitionTo(issueKey, 'To Do');
console.log(`Moved to backlog: ${issueKey}`);
}
// Reopen a completed issue (Done → To Do)
async function reopenWork(issueKey) {
await transitionTo(issueKey, 'To Do');
console.log(`Reopened: ${issueKey}`);
}
Phase 4: Add Comments and Updates
Add Work Log Comment
async function addComment(issueKey, comment) {
const body = {
body: {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: comment }]
}
]
}
};
const response = await fetch(
`${JIRA_BASE_URL}/rest/api/3/issue/${issueKey}/comment`,
{
method: 'POST',
headers,
body: JSON.stringify(body)
}
);
if (!response.ok) {
throw new Error(`Failed to add comment: ${response.status}`);
}
console.log(`Comment added to ${issueKey}`);
return response.json();
}
Add Implementation Details Comment
async function addImplementationDetails(issueKey, details) {
const content = [
{ type: 'heading', attrs: { level: 3 }, content: [{ type: 'text', text: 'Implementation Details' }] },
{ type: 'paragraph', content: [{ type: 'text', text: `Completed: ${new Date().toISOString()}` }] }
];
if (details.files?.length > 0) {
content.push(
{ type: 'heading', attrs: { level: 4 }, content: [{ type: 'text', text: 'Files Modified' }] },
{
type: 'bulletList',
content: details.files.map(f => ({
type: 'listItem',
content: [{ type: 'paragraph', content: [{ type: 'text', text: f }] }]
}))
}
);
}
if (details.commits?.length > 0) {
content.push(
{ type: 'heading', attrs: { level: 4 }, content: [{ type: 'text', text: 'Commits' }] },
{
type: 'bulletList',
content: details.commits.map(c => ({
type: 'listItem',
content: [{ type: 'paragraph', content: [{ type: 'text', text: c }] }]
}))
}
);
}
if (details.notes) {
content.push(
{ type: 'heading', attrs: { level: 4 }, content: [{ type: 'text', text: 'Notes' }] },
{ type: 'paragraph', content: [{ type: 'text', text: details.notes }] }
);
}
const body = { body: { type: 'doc', version: 1, content } };
const response = await fetch(
`${JIRA_BASE_URL}/rest/api/3/issue/${issueKey}/comment`,
{
method: 'POST',
headers,
body: JSON.stringify(body)
}
);
return response.json();
}
Complete Workflow Example
Full Cycle: Propose → Approve → Create → Work → Complete
async function fullWorkflowCycle(task) {
// 1. Build proposal
const proposal = buildStoryProposal(task);
// 2. Present for approval (use AskUserQuestion tool)
const approved = await presentForApproval(proposal);
if (!approved) {
console.log('Story creation skipped by user');
return null;
}
// 3. Create in Jira
const issue = await createStory(proposal, task.epicKey);
console.log(`Created: ${issue.key}`);
// 4. Start work (transition to In Progress)
await startWork(issue.key);
// 5. Do the actual work (your implementation here)
const result = await doTheWork(task);
// 6. Add implementation details
await addImplementationDetails(issue.key, {
files: result.modifiedFiles,
commits: result.commits,
notes: result.notes
});
// 7. Complete the work
await completeWork(issue.key);
return issue;
}
Integration with Claude Code Orchestration
Sync with TodoWrite
When working on Jira stories, sync with TodoWrite:
TodoWrite todos:
[
{ "content": "SCRUM-55: Create signup API", "status": "in_progress", "activeForm": "Working on SCRUM-55" },
{ "content": "SCRUM-56: Create login API", "status": "pending", "activeForm": "Waiting for SCRUM-55" },
{ "content": "SCRUM-57: Create logout API", "status": "pending", "activeForm": "Waiting for SCRUM-56" }
]
As each task completes:
1. Mark TodoWrite item as completed
2. Transition Jira issue to Done
3. Add implementation comment to Jira
4. Move to next task
Auto-Transition Pattern
// When starting a task
async function startTask(issueKey) {
// 1. Transition Jira to Progressing
await startWork(issueKey);
// 2. Update TodoWrite (in Claude Code)
// TodoWrite: Mark as in_progress
return issueKey;
}
// When completing a task
async function completeTask(issueKey, details) {
// 1. Add implementation comment
await addImplementationDetails(issueKey, details);
// 2. Transition Jira to Done
await completeWork(issueKey);
// 3. Update TodoWrite (in Claude Code)
// TodoWrite: Mark as completed
return issueKey;
}
Quick Reference
Status Transitions (SCRUM Project - Next-Gen)
| From | To | Transition Name | Typical Use |
|---|---|---|---|
| To Do | Progressing | "Progressing" | Starting work |
| To Do | In Review | "In Review" | Needs review first |
| Progressing | Done | "Done" | Work complete |
| Progressing | To Do | "To Do" | Blocked/deprioritized |
| Done | To Do | "To Do" | Reopening |
Available States: To Do, In Review, Progressing, Out Review, Done
Note: Always query transitions first - they vary by issue type and current state.
API Endpoints
| Action | Method | Endpoint |
|---|---|---|
| Create Issue | POST | /rest/api/3/issue |
| Get Issue | GET | /rest/api/3/issue/{key} |
| Update Issue | PUT | /rest/api/3/issue/{key} |
| Delete Issue | DELETE | /rest/api/3/issue/{key} |
| Get Transitions | GET | /rest/api/3/issue/{key}/transitions |
| Do Transition | POST | /rest/api/3/issue/{key}/transitions |
| Add Comment | POST | /rest/api/3/issue/{key}/comment |
| Search | GET | /rest/api/3/search/jql?jql=... |
Rate Limiting
- Max 10 requests/second
- Add 100ms delay between bulk operations
- Batch operations where possible
Error Handling
async function safeJiraOperation(operation, issueKey) {
try {
return await operation();
} catch (error) {
console.error(`Jira operation failed for ${issueKey}: ${error.message}`);
// Common error patterns
if (error.message.includes('404')) {
console.log('Issue not found - may have been deleted');
}
if (error.message.includes('401')) {
console.log('Authentication failed - check API token');
}
if (error.message.includes('403')) {
console.log('Permission denied - check project access');
}
if (error.message.includes('400')) {
console.log('Bad request - check field names and values');
}
throw error;
}
}
Executable Scripts
Ready-to-run scripts are available in both Node.js and Python:
Using the Cross-Platform Runner
# From the .claude/skills/jira directory
node scripts/run.js workflow demo SCRUM-100 # Demo full workflow
node scripts/run.js test # Test authentication
# Force specific runtime
node scripts/run.js --python workflow demo SCRUM-100
node scripts/run.js --node workflow demo SCRUM-100
Direct Script Execution
# Node.js
node scripts/jira-workflow-demo.mjs demo SCRUM-100
node scripts/jira-workflow-demo.mjs start SCRUM-100
node scripts/jira-workflow-demo.mjs complete SCRUM-100
node scripts/jira-workflow-demo.mjs reopen SCRUM-100
node scripts/jira-workflow-demo.mjs status SCRUM-100
# Python (recommended on Windows)
python scripts/jira-workflow-demo.py demo SCRUM-100
python scripts/jira-workflow-demo.py start SCRUM-100
python scripts/jira-workflow-demo.py complete SCRUM-100
python scripts/jira-workflow-demo.py reopen SCRUM-100
python scripts/jira-workflow-demo.py status SCRUM-100
Available Scripts
| Script | Node.js | Python | Purpose |
|---|---|---|---|
| Workflow Demo | jira-workflow-demo.mjs | jira-workflow-demo.py | Full To Do → Progressing → Done demo |
| Add Subtasks | jira-add-subtasks.mjs | jira-add-subtasks.py | Create subtasks under a story |
| Create Story | jira-create-one.mjs | jira-create-one.py | Create single story |
| Bulk Create | jira-bulk-create.mjs | jira-bulk-create.py | Create from git commits |
References
Repository
