Jira 工作流编排技能
完整的 Jira 工作流管理:构建故事(SAFe)、获取审批以及将项目推进至开发生命周期(待办 → 进行中 → 完成)。
重要:此项目使用下一代(团队管理)Jira 与自定义工作流状态。实际状态为:
待办(待办事项)审核中进行中(活跃工作)审核外已完成
始终先查询可用的转换:GET /rest/api/3/issue/{key}/transitions
何时使用
- 为项目创建新用户故事、史诗或任务
- 在创建 Jira 项目前获取用户审批
- 随着工作进展,将故事通过工作流状态推进
- 与 Jira 状态同步 Claude Code 任务完成情况
- 管理冲刺计划和待办事项细化
- 实时跟踪开发进度
前置条件
环境变量:
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
项目配置:
- 必须知道项目是下一代(团队管理)还是经典(公司管理)
- 下一代:使用
parent字段链接史诗 - 经典:使用
customfield_10014链接史诗
核心工作流模式
审批-创建-跟踪循环
1. 计划:分析任务需求
↓
2. 提议:向用户展示故事以获取审批
↓
3. 审批:用户确认或修改
↓
4. 创建:在 Jira 待办事项中创建问题
↓
5. 开始:当工作开始时转换为“进行中”
↓
6. 完成:当工作验证后转换为“已完成”
↓
7. 同步:用实施细节更新 Jira
第一阶段:故事构建(SAFe 格式)
构建故事提议
当用户请求工作时,构建一个符合 SAFe 的故事提议:
function buildStoryProposal(task) {
return {
summary: `作为 ${task.persona},我想要 ${task.goal},以便 ${task.benefit}`,
description: {
userStory: `作为 **${task.persona}**,我想要 **${task.goal}**,以便 **${task.benefit}**。`,
acceptanceCriteria: task.scenarios.map(s => ({
name: s.name,
given: s.given,
when: s.when,
then: s.then
})),
definitionOfDone: [
'代码审核通过',
'单元测试编写并通过',
'集成测试通过',
'文档更新',
'部署到测试环境',
'在生产环境中验证'
],
technicalNotes: task.technicalNotes || []
},
category: task.category, // 认证、UI、API、数据库等
estimatedComplexity: task.complexity || 'medium', // 小型、中型、大型
subtasks: task.subtasks || []
};
}
提交审批
关键:在创建 Jira 项目前始终获取用户审批。
使用此提示模式:
## 提议的 Jira 故事
**摘要:**作为 [persona],我想要 [goal],以便 [benefit]
**类别:** [category]
**复杂性:** [小型/中型/大型]
### 验收标准
**场景 1:[名称]**
- **GIVEN** [前提条件]
- **WHEN** [动作]
- **THEN** [预期结果]
### 子任务(如果有)
1. [子任务 1]
2. [子任务 2]
3. [子任务 3]
---
**你希望我在 Jira 中创建这个吗?**
选项:
1. **是的,按原样创建** - 我现在就创建故事
2. **修改** - 告诉我需要更改什么
3. **跳过** - 不在 Jira 中创建,只做工作
第二阶段:问题创建
在 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, '-')]
}
};
// 链接到史诗(下一代项目)
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(`创建故事失败:${error}`);
}
const issue = await response.json();
console.log(`已创建:${issue.key} - ${proposal.summary}`);
// 如果有,创建子任务
if (proposal.subtasks?.length > 0) {
for (const subtask of proposal.subtasks) {
await createSubtask(issue.key, subtask);
await delay(100); // 速率限制
}
}
return issue;
}
async function createSubtask(parentKey, summary) {
const body = {
fields: {
project: { key: PROJECT_KEY },
issuetype: { name: 'Subtask' }, // 注意:下一代的 'Subtask'
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(`创建子任务失败:${error}`);
}
return response.json();
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
构建 Atlassian 文档格式(ADF)
function buildADF(content) {
const sections = [];
// 用户故事部分
sections.push({
type: 'heading',
attrs: { level: 2 },
content: [{ type: 'text', text: '用户故事' }]
});
sections.push({
type: 'paragraph',
content: [{ type: 'text', text: content.userStory }]
});
// 验收标准部分
sections.push({
type: 'heading',
attrs: { level: 2 },
content: [{ type: 'text', text: '验收标准' }]
});
for (const scenario of content.acceptanceCriteria) {
sections.push({
type: 'heading',
attrs: { level: 3 },
content: [{ type: 'text', text: `场景:${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' }] }] }] }
]
});
}
// 完成定义部分
sections.push({
type: 'heading',
attrs: { level: 2 },
content: [{ type: 'text', text: '完成定义' }]
});
sections.push({
type: 'bulletList',
content: content.definitionOfDone.map(item => ({
type: 'listItem',
content: [{ type: 'paragraph', content: [{ type: 'text', text: `[ ] ${item}` }] }]
}))
});
// 技术注释(如果有)
if (content.technicalNotes?.length > 0) {
sections.push({
type: 'heading',
attrs: { level: 2 },
content: [{ type: 'text', text: '技术注释' }]
});
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 };
}
第三阶段:工作流转换
获取可用转换
async function getTransitions(issueKey) {
const response = await fetch(
`${JIRA_BASE_URL}/rest/api/3/issue/${issueKey}/transitions`,
{ headers }
);
if (!response.ok) {
throw new Error(`获取转换失败:${response.status}`);
}
const data = await response.json();
return data.transitions;
}
转换问题到状态
async function transitionTo(issueKey, targetState) {
// 获取可用转换
const transitions = await getTransitions(issueKey);
// 查找目标状态的转换
const transition = transitions.find(t =>
t.to.name.toLowerCase() === targetState.toLowerCase() ||
t.name.toLowerCase() === targetState.toLowerCase()
);
if (!transition) {
console.log(`$${issueKey} 的可用转换:`);
transitions.forEach(t => console.log(` - ${t.name} → ${t.to.name}`));
throw new Error(`未找到到 "${targetState}" 的转换`);
}
// 执行转换
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(`转换失败:${error}`);
}
console.log(`${issueKey} 转换到 ${targetState}`);
return true;
}
常见工作流操作
// 开始故事工作(待办 → 进行中)
async function startWork(issueKey) {
await transitionTo(issueKey, 'Progressing');
console.log(`已开始:${issueKey}`);
}
// 完成故事(进行中 → 已完成)
async function completeWork(issueKey) {
await transitionTo(issueKey, 'Done');
console.log(`已完成:${issueKey}`);
}
// 移回待办事项(任何状态 → 待办)
async function moveToBacklog(issueKey) {
await transitionTo(issueKey, 'To Do');
console.log(`已移回待办事项:${issueKey}`);
}
// 重新开启已完成的问题(已完成 → 待办)
async function reopenWork(issueKey) {
await transitionTo(issueKey, 'To Do');
console.log(`已重新开启:${issueKey}`);
}
第四阶段:添加评论和更新
添加工作日志评论
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(`添加评论失败:${response.status}`);
}
console.log(`已添加评论到 ${issueKey}`);
return response.json();
}
添加实施细节评论
async function addImplementationDetails(issueKey, details) {
const content = [
{ type: 'heading', attrs: { level: 3 }, content: [{ type: 'text', text: '实施细节' }] },
{ type: 'paragraph', content: [{ type: 'text', text: `已完成:${new Date().toISOString()}` }] }
];
if (details.files?.length > 0) {
content.push(
{ type: 'heading', attrs: { level: 4 }, content: [{ type: 'text', text: '修改的文件' }] },
{
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: '提交' }] },
{
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: '注释' }] },
{ 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();
}
完整工作流示例
完整周期:提议 → 审批 → 创建 → 工作 → 完成
async function fullWorkflowCycle(task) {
// 1. 构建提议
const proposal = buildStoryProposal(task);
// 2. 提交审批(使用 AskUserQuestion 工具)
const approved = await presentForApproval(proposal);
if (!approved) {
console.log('用户跳过故事创建');
return null;
}
// 3. 在 Jira 中创建
const issue = await createStory(proposal, task.epicKey);
console.log(`已创建:${issue.key}`);
// 4. 开始工作(转换为 进行中)
await startWork(issue.key);
// 5. 执行实际工作(你的实现在这里)
const result = await doTheWork(task);
// 6. 添加实施细节
await addImplementationDetails(issue.key, {
files: result.modifiedFiles,
commits: result.commits,
notes: result.notes
});
// 7. 完成工作
await completeWork(issue.key);
return issue;
}
与 Claude Code 编排集成
与 TodoWrite 同步
在处理 Jira 故事时,与 TodoWrite 同步:
TodoWrite 待办事项:
[
{ "content": "SCRUM-55: 创建注册 API", "status": "in_progress", "activeForm": "正在处理 SCRUM-55" },
{ "content": "SCRUM-56: 创建登录 API", "status": "pending", "activeForm": "等待 SCRUM-55" },
{ "content": "SCRUM-57: 创建登出 API", "status": "pending", "activeForm": "等待 SCRUM-56" }
]
随着每个任务的完成:
1. 标记 TodoWrite 项目为已完成
2. 将 Jira 问题转换为已完成
3. 在 Jira 中添加实施评论
4. 转到下一个任务
自动转换模式
// 当开始任务时
async function startTask(issueKey) {
// 1. 转换 Jira 为进行中
await startWork(issueKey);
// 2. 更新 TodoWrite(在 Claude Code 中)
// TodoWrite:标记为 in_progress
return issueKey;
}
// 当完成任务时
async function completeTask(issueKey, details) {
// 1. 添加实施评论
await addImplementationDetails(issueKey, details);
// 2. 转换 Jira 为已完成
await completeWork(issueKey);
// 3. 更新 TodoWrite(在 Claude Code 中)
// TodoWrite:标记为 completed
return issueKey;
}
快速参考
状态转换(SCRUM 项目 - 下一代)
| 从 | 到 | 转换名称 | 典型用途 |
|---|---|---|---|
| 待办 | 进行中 | “进行中” | 开始工作 |
| 待办 | 审核中 | “审核中” | 首先需要审核 |
| 进行中 | 已完成 | “已完成” | 工作完成 |
| 进行中 | 待办 | “待办” | 阻塞/降低优先级 |
| 已完成 | 待办 | “待办” | 重新开启 |
可用状态: 待办、审核中、进行中、审核外、已完成
注意: 始终先查询转换 - 它们因问题类型和当前状态而异。
API 端点
| 动作 | 方法 | 端点 |
|---|---|---|
| 创建问题 | POST | /rest/api/3/issue |
| 获取问题 | GET | /rest/api/3/issue/{key} |
| 更新问题 | PUT | /rest/api/3/issue/{key} |
| 删除问题 | DELETE | /rest/api/3/issue/{key} |
| 获取转换 | GET | /rest/api/3/issue/{key}/transitions |
| 执行转换 | POST | /rest/api/3/issue/{key}/transitions |
| 添加评论 | POST | /rest/api/3/issue/{key}/comment |
| 搜索 | GET | /rest/api/3/search/jql?jql=... |
速率限制
- 最大 10 个请求/秒
- 在批量操作之间添加 100ms 延迟
- 尽可能批量操作
错误处理
async function safeJiraOperation(operation, issueKey) {
try {
return await operation();
} catch (error) {
console.error(`Jira 操作失败 ${issueKey}: ${error.message}`);
// 常见错误模式
if (error.message.includes('404')) {
console.log('问题未找到 - 可能已被删除');
}
if (error.message.includes('401')) {
console.log('认证失败 - 检查 API 令牌');
}
if (error.message.includes('403')) {
console.log('权限被拒绝 - 检查项目访问权限');
}
if (error.message.includes('400')) {
console.log('请求错误 - 检查字段名称和值');
}
throw error;
}
}
可执行脚本
在 Node.js 和 Python 中都有现成的脚本可供运行:
使用跨平台运行器
# 从 .claude/skills/jira 目录
node scripts/run.js workflow demo SCRUM-100 # 演示完整工作流
node scripts/run.js test # 测试认证
# 强制特定运行时
node scripts/run.js --python workflow demo SCRUM-100
node scripts/run.js --node workflow demo SCRUM-100
直接脚本执行
# 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(推荐在 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
可用脚本
| 脚本 | Node.js | Python | 目的 |
|---|---|---|---|
| 工作流演示 | jira-workflow-demo.mjs |
jira-workflow-demo.py |
完整待办 → 进行中 → 已完成演示 |
| 添加子任务 | jira-add-subtasks.mjs |
jira-add-subtasks.py |
在故事下创建子任务 |
| 创建故事 | jira-create-one.mjs |
jira-create-one.py |
创建单个故事 |
| 批量创建 | jira-bulk-create.mjs |
jira-bulk-create.py |
从 git 提交创建 |