Jira工作流编排Skill jira-workflow

Jira 工作流编排技能用于在 Jira 中端到端地协调工作流,包括构建用户故事、获取审批、在开发生命周期中推进项目状态、同步任务完成与 Jira 状态等。

DevOps 0 次安装 0 次浏览 更新于 2/28/2026

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 提交创建

参考资料